9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2026-01-04 15:41:38 +00:00

Merge branch 'Xiao-MoMi:dev' into dev

This commit is contained in:
iqtesterrr
2025-04-18 23:27:52 +08:00
committed by GitHub
137 changed files with 5656 additions and 1042 deletions

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();
@@ -336,25 +342,21 @@ public class BlockEventListener implements Listener {
if (!this.enableNoteBlockCheck) return;
// for vanilla blocks
if (event.getChangedType() == Material.NOTE_BLOCK) {
try {
Block block = event.getBlock();
World world = block.getWorld();
Location location = block.getLocation();
Block sourceBlock = event.getSourceBlock();
BlockFace direction = sourceBlock.getFace(block);
if (direction == BlockFace.UP || direction == BlockFace.DOWN) {
Object serverLevel = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(world);
Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(serverLevel);
Object blockPos = LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());
FastNMS.INSTANCE.method$ServerChunkCache$blockChanged(chunkSource, blockPos);
if (direction == BlockFace.UP) {
NoteBlockChainUpdateUtils.noteBlockChainUpdate(serverLevel, chunkSource, Reflections.instance$Direction$UP, blockPos, 0);
} else {
NoteBlockChainUpdateUtils.noteBlockChainUpdate(serverLevel, chunkSource, Reflections.instance$Direction$DOWN, blockPos, 0);
}
Block block = event.getBlock();
World world = block.getWorld();
Location location = block.getLocation();
Block sourceBlock = event.getSourceBlock();
BlockFace direction = sourceBlock.getFace(block);
if (direction == BlockFace.UP || direction == BlockFace.DOWN) {
Object serverLevel = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(world);
Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(serverLevel);
Object blockPos = LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());
FastNMS.INSTANCE.method$ServerChunkCache$blockChanged(chunkSource, blockPos);
if (direction == BlockFace.UP) {
NoteBlockChainUpdateUtils.noteBlockChainUpdate(serverLevel, chunkSource, Reflections.instance$Direction$UP, blockPos, 0);
} else {
NoteBlockChainUpdateUtils.noteBlockChainUpdate(serverLevel, chunkSource, Reflections.instance$Direction$DOWN, blockPos, 0);
}
} catch (ReflectiveOperationException e) {
plugin.logger().warn("Failed to sync note block states", e);
}
}
}

View File

@@ -798,7 +798,7 @@ public class BukkitBlockManager extends AbstractBlockManager {
newBlockState = getOnlyBlockState(newRealBlock);
@SuppressWarnings("unchecked")
Optional<Object> optionalHolder = (Optional<Object>) Reflections.method$Registry$getHolder0.invoke(Reflections.instance$BuiltInRegistries$BLOCK, resourceLocation);
Optional<Object> optionalHolder = (Optional<Object>) Reflections.method$Registry$getHolder1.invoke(Reflections.instance$BuiltInRegistries$BLOCK, Reflections.method$ResourceKey$create.invoke(null, Reflections.instance$Registries$BLOCK, resourceLocation));
blockHolder = optionalHolder.get();
} else {
try {

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

@@ -142,7 +142,7 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior {
if (direction != Reflections.instance$Direction$DOWN || canSolidify(blockState)) {
Reflections.method$MutableBlockPos$setWithOffset.invoke(mutablePos, pos, direction);
blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, mutablePos);
if (canSolidify(blockState) && !(boolean) Reflections.method$BlockStateBase$isFaceSturdy.invoke(blockState, level, pos, Reflections.getOppositeDirection(direction), Reflections.instance$SupportType$FULL)) {
if (canSolidify(blockState) && !(boolean) Reflections.method$BlockStateBase$isFaceSturdy.invoke(blockState, level, pos, FastNMS.INSTANCE.method$Direction$getOpposite(direction), Reflections.instance$SupportType$FULL)) {
flag = true;
break;
}

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

@@ -349,6 +349,20 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
public void handleBaseEntityLoadEarly(ItemDisplay display) {
String id = display.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
if (id == null) return;
// Remove the entity if it's not a valid furniture
if (Config.handleInvalidFurniture()) {
String mapped = Config.furnitureMappings().get(id);
if (mapped != null) {
if (mapped.isEmpty()) {
display.remove();
return;
} else {
id = mapped;
display.getPersistentDataContainer().set(FURNITURE_KEY, PersistentDataType.STRING, id);
}
}
}
Key key = Key.of(id);
Optional<CustomFurniture> optionalFurniture = furnitureById(key);
if (optionalFurniture.isPresent()) {
@@ -357,13 +371,6 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
if (previous != null) return;
LoadedFurniture furniture = addNewFurniture(display, customFurniture, getAnchorType(display, customFurniture));
furniture.initializeColliders(); // safely do it here
return;
}
// Remove the entity if it's not a valid furniture
if (Config.removeInvalidFurniture()) {
if (Config.furnitureToRemove().isEmpty() || Config.furnitureToRemove().contains(id)) {
display.remove();
}
}
}

View File

@@ -10,5 +10,6 @@ public class BukkitHitBoxTypes extends HitBoxTypes {
register(INTERACTION, InteractionHitBox.FACTORY);
register(SHULKER, ShulkerHitBox.FACTORY);
register(HAPPY_GHAST, HappyGhastHitBox.FACTORY);
register(CUSTOM, CustomHitBox.FACTORY);
}
}

View File

@@ -0,0 +1,87 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.entity.EntityType;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class CustomHitBox extends AbstractHitBox {
public static final Factory FACTORY = new Factory();
private final float scale;
private final EntityType entityType;
private final List<Object> cachedValues = new ArrayList<>();
public CustomHitBox(Seat[] seats, Vector3f position, EntityType type, float scale) {
super(seats, position);
this.scale = scale;
this.entityType = type;
BaseEntityData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedValues);
BaseEntityData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedValues);
BaseEntityData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues);
}
public EntityType entityType() {
return entityType;
}
public float scale() {
return scale;
}
@Override
public Key type() {
return HitBoxTypes.CUSTOM;
}
@Override
public void addSpawnPackets(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer<Object, Boolean> packets) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
try {
packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance(
entityId[0], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw,
FastNMS.INSTANCE.toNMSEntityType(this.entityType), 0, Reflections.instance$Vec3$Zero, 0
), true);
packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId[0], List.copyOf(this.cachedValues)), true);
if (VersionHelper.isVersionNewerThan1_20_5() && this.scale != 1) {
Object attributeInstance = Reflections.constructor$AttributeInstance.newInstance(Reflections.instance$Holder$Attribute$scale, (Consumer<?>) (o) -> {});
Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale);
packets.accept(Reflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityId[0], Collections.singletonList(attributeInstance)), false);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct custom hitbox spawn packet", e);
}
}
@Override
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
return new int[] {entityIdSupplier.get()};
}
public static class Factory implements HitBoxFactory {
@Override
public HitBox create(Map<String, Object> arguments) {
Vector3f position = MiscUtils.getVector3f(arguments.getOrDefault("position", "0"));
float scale = MiscUtils.getAsFloat(arguments.getOrDefault("scale", "1"));
EntityType entityType = Registry.ENTITY_TYPE.get(new NamespacedKey("minecraft", (String) arguments.getOrDefault("entity-type", "slime")));
if (entityType == null) {
throw new IllegalArgumentException("EntityType not found: " + arguments.get("entity-type"));
}
return new CustomHitBox(HitBoxFactory.getSeats(arguments), position, entityType, scale);
}
}
}

View File

@@ -55,7 +55,7 @@ public class InteractionHitBox extends AbstractHitBox {
), true);
packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId[0], List.copyOf(this.cachedValues)), true);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct hitbox spawn packet", e);
throw new RuntimeException("Failed to construct interaction hitbox spawn packet", e);
}
}

View File

@@ -140,9 +140,9 @@ public class ShulkerHitBox extends AbstractHitBox {
entityIds[1], (short) 0, ya, (short) 0, true
), false);
}
if (VersionHelper.isVersionNewerThan1_20_5()) {
if (VersionHelper.isVersionNewerThan1_20_5() && this.scale != 1) {
Object attributeInstance = Reflections.constructor$AttributeInstance.newInstance(Reflections.instance$Holder$Attribute$scale, (Consumer<?>) (o) -> {});
Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, scale);
Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale);
packets.accept(Reflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[1], Collections.singletonList(attributeInstance)), false);
}
if (this.interactionEntity) {

View File

@@ -145,8 +145,8 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
private Key id;
private Material material;
private Key materialKey;
private ItemSettings settings;
private final List<ItemBehavior> behaviors = new ArrayList<>();
private ItemSettings settings = ItemSettings.of();
private final List<ItemDataModifier<ItemStack>> modifiers = new ArrayList<>();
private final List<ItemDataModifier<ItemStack>> clientBoundModifiers = new ArrayList<>();
@@ -208,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, modifiers, clientBoundModifiers, behaviors, settings);
return new BukkitCustomItem(this.id, this.materialKey, this.material, this.modifiers, this.clientBoundModifiers, this.behaviors, this.settings);
}
}
}

View File

@@ -20,7 +20,6 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehaviors;
import net.momirealms.craftengine.core.item.modifier.CustomModelDataModifier;
import net.momirealms.craftengine.core.item.modifier.IdModifier;
import net.momirealms.craftengine.core.item.modifier.ItemModelModifier;
import net.momirealms.craftengine.core.pack.LegacyOverridesModel;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.ResourceLocation;
@@ -79,9 +78,9 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
if (plugin.hasMod()) {
Class<?> clazz$CustomStreamCodec = ReflectionUtils.getClazz("net.momirealms.craftengine.mod.item.CustomStreamCodec");
if (clazz$CustomStreamCodec != null) {
Field cProcessor = ReflectionUtils.getDeclaredField(clazz$CustomStreamCodec, Function.class, 0);
Field sProcessor = ReflectionUtils.getDeclaredField(clazz$CustomStreamCodec, Function.class, 1);
Function<Object, Object> c = (raw) -> {
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();
@@ -99,7 +98,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
return wrapped.getLiteralObject();
};
Function<Object, Object> s = (raw) -> {
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();
@@ -117,10 +116,10 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
return wrapped.getLiteralObject();
};
try {
assert cProcessor != null;
cProcessor.set(null, c);
assert sProcessor != null;
sProcessor.set(null, s);
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);
}
@@ -267,7 +266,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;
@@ -284,8 +286,6 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
Key itemModelKey = null;
CustomItem.Builder<ItemStack> itemBuilder = BukkitCustomItem.builder().id(id).material(materialId);
itemBuilder.dataModifier(new IdModifier<>(id));
boolean hasItemModelSection = section.containsKey("item-model");
// To get at least one model provider
@@ -341,6 +341,10 @@ 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) {
@@ -355,10 +359,17 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
}
}
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

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

@@ -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,21 +74,35 @@ 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();
// todo adventure check
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,

View File

@@ -67,8 +67,10 @@ 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)) {

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

View File

@@ -14,8 +14,8 @@ import net.momirealms.craftengine.bukkit.util.RecipeUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.recipe.Recipe;
import net.momirealms.craftengine.core.item.recipe.*;
import net.momirealms.craftengine.core.item.recipe.Recipe;
import net.momirealms.craftengine.core.item.recipe.vanilla.*;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;

View File

@@ -12,8 +12,8 @@ import net.momirealms.craftengine.bukkit.util.ItemUtils;
import net.momirealms.craftengine.bukkit.util.LegacyInventoryUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.item.*;
import net.momirealms.craftengine.core.item.recipe.Recipe;
import net.momirealms.craftengine.core.item.recipe.*;
import net.momirealms.craftengine.core.item.recipe.Recipe;
import net.momirealms.craftengine.core.item.recipe.input.CraftingInput;
import net.momirealms.craftengine.core.item.recipe.input.SingleItemInput;
import net.momirealms.craftengine.core.item.recipe.input.SmithingInput;

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.pack;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.api.event.AsyncResourcePackGenerateEvent;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.command.feature.ReloadCommand;
@@ -8,236 +7,161 @@ import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.util.ResourcePackUtils;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.pack.AbstractPackManager;
import net.momirealms.craftengine.core.pack.host.HostMode;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.pack.host.impl.NoneHost;
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.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerResourcePackStatusEvent;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class BukkitPackManager extends AbstractPackManager implements Listener {
private final BukkitCraftEngine plugin;
private HostMode previousHostMode = HostMode.NONE;
private UUID previousHostUUID;
public static final String FAKE_URL = "https://127.0.0.1:65536";
private final BukkitCraftEngine plugin;
public BukkitPackManager(BukkitCraftEngine plugin) {
public BukkitPackManager(BukkitCraftEngine plugin) {
super(plugin, (rf, zp) -> {
AsyncResourcePackGenerateEvent endEvent = new AsyncResourcePackGenerateEvent(rf, zp);
EventUtils.fireAndForget(endEvent);
});
AsyncResourcePackGenerateEvent endEvent = new AsyncResourcePackGenerateEvent(rf, zp);
EventUtils.fireAndForget(endEvent);
});
this.plugin = plugin;
}
}
@Override
public void delayedInit() {
super.delayedInit();
Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap());
}
@Override
public void delayedInit() {
super.delayedInit();
Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap());
}
@EventHandler(priority = EventPriority.LOW)
public void onPlayerJoin(PlayerJoinEvent event) {
// for 1.20.1 servers, not recommended to use
if (Config.sendPackOnJoin() && !VersionHelper.isVersionNewerThan1_20_2()) {
this.sendResourcePack(plugin.networkManager().getUser(event.getPlayer()), null);
}
}
@EventHandler(priority = EventPriority.LOW)
public void onPlayerJoin(PlayerJoinEvent event) {
if (Config.sendPackOnJoin() && !VersionHelper.isVersionNewerThan1_20_2()) {
Player player = plugin.adapt(event.getPlayer());
this.sendResourcePack(player);
}
}
@EventHandler(priority = EventPriority.LOW)
public void onResourcePackStatus(PlayerResourcePackStatusEvent event) {
// for 1.20.1 servers, not recommended to use
if (Config.sendPackOnJoin() && Config.kickOnDeclined() && !VersionHelper.isVersionNewerThan1_20_2()) {
if (event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED || event.getStatus() == PlayerResourcePackStatusEvent.Status.FAILED_DOWNLOAD) {
event.getPlayer().kick();
}
}
}
@EventHandler(priority = EventPriority.LOW)
public void onResourcePackStatus(PlayerResourcePackStatusEvent event) {
// for 1.20.1 servers, not recommended to use
if (Config.sendPackOnJoin() && Config.kickOnDeclined() && !VersionHelper.isVersionNewerThan1_20_2()) {
if (event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED || event.getStatus() == PlayerResourcePackStatusEvent.Status.FAILED_DOWNLOAD) {
event.getPlayer().kick();
}
}
}
@Override
public void load() {
super.load();
@Override
public void load() {
if (ReloadCommand.RELOAD_PACK_FLAG || CraftEngine.instance().isInitializing()) {
super.load();
if (Config.sendPackOnJoin() && VersionHelper.isVersionNewerThan1_20_2() && !(resourcePackHost() instanceof NoneHost)) {
this.modifyServerSettings();
}
}
}
// update server properties
if (VersionHelper.isVersionNewerThan1_20_2()) {
if (Config.hostMode() == HostMode.SELF_HOST) {
if (Files.exists(resourcePackPath())) {
updateResourcePackSettings(super.packUUID, ResourcePackHost.instance().url(), super.packHash, Config.kickOnDeclined(), Config.resourcePackPrompt());
}
} else if (Config.hostMode() == HostMode.EXTERNAL_HOST) {
updateResourcePackSettings(Config.externalPackUUID(), Config.externalPackUrl(), Config.externalPackSha1(), Config.kickOnDeclined(), Config.resourcePackPrompt());
}
}
if (Config.sendPackOnReload()) {
if (this.previousHostMode == HostMode.SELF_HOST) {
this.previousHostUUID = super.packUUID;
}
// unload packs if user changed to none host
if (Config.hostMode() == HostMode.NONE && this.previousHostMode != HostMode.NONE) {
unloadResourcePackForOnlinePlayers(this.previousHostUUID);
}
// load new external resource pack on reload
if (Config.hostMode() == HostMode.EXTERNAL_HOST) {
if (this.previousHostMode == HostMode.NONE) {
updateResourcePackForOnlinePlayers(null);
} else {
updateResourcePackForOnlinePlayers(this.previousHostUUID);
}
// record previous host uuid here
this.previousHostUUID = Config.externalPackUUID();
}
if (Config.hostMode() == HostMode.SELF_HOST && this.previousHostMode != HostMode.SELF_HOST) {
if (ReloadCommand.RELOAD_PACK_FLAG) {
ReloadCommand.RELOAD_PACK_FLAG = false;
if (this.previousHostMode == HostMode.NONE) {
updateResourcePackForOnlinePlayers(null);
} else if (this.previousHostMode == HostMode.EXTERNAL_HOST) {
updateResourcePackForOnlinePlayers(this.previousHostUUID);
}
}
}
}
this.previousHostMode = Config.hostMode();
}
@Override
public void unload() {
super.unload();
if (VersionHelper.isVersionNewerThan1_20_2() && this.previousHostMode != HostMode.NONE) {
resetResourcePackSettings();
}
}
@Override
public void disable() {
super.disable();
HandlerList.unregisterAll(this);
}
@Override
public void generateResourcePack() {
// generate pack
super.generateResourcePack();
// update server properties
if (VersionHelper.isVersionNewerThan1_20_2()) {
if (Config.hostMode() == HostMode.SELF_HOST) {
updateResourcePackSettings(super.packUUID, ResourcePackHost.instance().url(), super.packHash, Config.kickOnDeclined(), Config.resourcePackPrompt());
}
}
// resend packs
if (Config.hostMode() == HostMode.SELF_HOST && Config.sendPackOnReload()) {
updateResourcePackForOnlinePlayers(this.previousHostUUID);
}
}
protected void updateResourcePackForOnlinePlayers(UUID previousUUID) {
for (Player player : Bukkit.getOnlinePlayers()) {
BukkitServerPlayer serverPlayer = plugin.adapt(player);
sendResourcePack(serverPlayer, previousUUID);
}
}
private void resetResourcePackSettings() {
try {
Object settings = Reflections.field$DedicatedServer$settings.get(Reflections.method$MinecraftServer$getServer.invoke(null));
Object properties = Reflections.field$DedicatedServerSettings$properties.get(settings);
Reflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.empty());
} catch (Exception e) {
this.plugin.logger().warn("Failed to update resource pack settings", e);
}
}
private void updateResourcePackSettings(UUID uuid, String url, String sha1, boolean required, Component prompt) {
if (!Config.sendPackOnJoin()) {
resetResourcePackSettings();
return;
}
try {
Object settings = Reflections.field$DedicatedServer$settings.get(Reflections.method$MinecraftServer$getServer.invoke(null));
Object properties = Reflections.field$DedicatedServerSettings$properties.get(settings);
public void modifyServerSettings() {
try {
Object settings = Reflections.field$DedicatedServer$settings.get(Reflections.method$MinecraftServer$getServer.invoke(null));
Object properties = Reflections.field$DedicatedServerSettings$properties.get(settings);
Object info;
if (VersionHelper.isVersionNewerThan1_20_3()) {
info = Reflections.constructor$ServerResourcePackInfo.newInstance(uuid, url, sha1, required, ComponentUtils.adventureToMinecraft(prompt));
info = Reflections.constructor$ServerResourcePackInfo.newInstance(new UUID(0, 0), FAKE_URL, "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt()));
} else {
info = Reflections.constructor$ServerResourcePackInfo.newInstance(url + uuid, sha1, required, ComponentUtils.adventureToMinecraft(prompt));
info = Reflections.constructor$ServerResourcePackInfo.newInstance(FAKE_URL, "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt()));
}
Reflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.of(info));
} catch (Exception e) {
this.plugin.logger().warn("Failed to update resource pack settings", e);
}
}
this.plugin.logger().warn("Failed to update resource pack settings", e);
}
}
public void sendResourcePack(NetWorkUser user, @Nullable UUID previousPack) {
if (Config.hostMode() == HostMode.NONE) return;
String url;
String sha1;
UUID uuid;
if (Config.hostMode() == HostMode.SELF_HOST) {
url = ResourcePackHost.instance().url();
sha1 = super.packHash;
uuid = super.packUUID;
if (!Files.exists(resourcePackPath())) return;
} else {
url = Config.externalPackUrl();
sha1 = Config.externalPackSha1();
uuid = Config.externalPackUUID();
if (uuid.equals(previousPack)) return;
}
@Override
public void unload() {
super.unload();
if (ReloadCommand.RELOAD_PACK_FLAG) {
if (VersionHelper.isVersionNewerThan1_20_2()) {
this.resetServerSettings();
}
}
}
Object packPrompt = ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt());
try {
Object packPacket;
if (VersionHelper.isVersionNewerThan1_20_5()) {
packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(
uuid, url, sha1, Config.kickOnDeclined(), Optional.of(packPrompt)
);
} else if (VersionHelper.isVersionNewerThan1_20_3()) {
packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(
uuid, url, sha1, Config.kickOnDeclined(), packPrompt
);
} else {
packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(
url + uuid, sha1, Config.kickOnDeclined(), packPrompt
);
}
if (user.decoderState() == ConnectionState.PLAY) {
if (previousPack != null && VersionHelper.isVersionNewerThan1_20_3()) {
plugin.networkManager().sendPackets(user, List.of(Reflections.constructor$ClientboundResourcePackPopPacket.newInstance(Optional.of(previousPack)), packPacket));
} else {
user.sendPacket(packPacket, false);
}
} else {
user.nettyChannel().writeAndFlush(packPacket);
}
} catch (Exception e) {
this.plugin.logger().warn("Failed to send resource pack", e);
}
}
@Override
public void disable() {
super.disable();
HandlerList.unregisterAll(this);
this.resetServerSettings();
}
public void unloadResourcePackForOnlinePlayers(UUID uuid) {
try {
for (Player player : Bukkit.getOnlinePlayers()) {
BukkitServerPlayer serverPlayer = plugin.adapt(player);
if (serverPlayer.decoderState() == ConnectionState.PLAY) {
serverPlayer.sendPacket(Reflections.constructor$ClientboundResourcePackPopPacket.newInstance(Optional.of(uuid)), true);
}
}
} catch (Exception e) {
this.plugin.logger().warn("Failed to unload online player resource pack", e);
}
}
public void resetServerSettings() {
try {
Object settings = Reflections.field$DedicatedServer$settings.get(Reflections.method$MinecraftServer$getServer.invoke(null));
Object properties = Reflections.field$DedicatedServerSettings$properties.get(settings);
Reflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.empty());
} catch (Exception e) {
this.plugin.logger().warn("Failed to reset resource pack settings", e);
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onAsyncResourcePackGenerate(AsyncResourcePackGenerateEvent event) {
if (!Config.autoUpload()) return;
uploadResourcePack();
}
@Override
public void uploadResourcePack() {
resourcePackHost().upload(Config.fileToUpload()).whenComplete((d, e) -> {
if (e != null) {
CraftEngine.instance().logger().warn("Failed to upload resource pack", e);
return;
}
if (!Config.sendPackOnUpload()) return;
for (BukkitServerPlayer player : this.plugin.networkManager().onlineUsers()) {
sendResourcePack(player);
}
});
}
private void sendResourcePack(Player player) {
CompletableFuture<List<ResourcePackDownloadData>> future = resourcePackHost().requestResourcePackDownloadLink(player.uuid());
future.thenAccept(dataList -> {
if (player.isOnline()) {
player.unloadCurrentResourcePack();
if (dataList.isEmpty()) {
return;
}
if (dataList.size() == 1) {
ResourcePackDownloadData data = dataList.get(0);
player.sendPacket(ResourcePackUtils.createPacket(data.uuid(), data.url(), data.sha1()), true);
player.addResourcePackUUID(data.uuid());
} else {
List<Object> packets = new ArrayList<>();
for (ResourcePackDownloadData data : dataList) {
packets.add(ResourcePackUtils.createPacket(data.uuid(), data.url(), data.sha1()));
player.addResourcePackUUID(data.uuid());
}
player.sendPackets(packets, true);
}
}
}).exceptionally(throwable -> {
CraftEngine.instance().logger().warn("Failed to send resource pack to player " + player.name(), throwable);
return null;
});
}
}

View File

@@ -43,6 +43,7 @@ import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Pose;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;

View File

@@ -46,7 +46,11 @@ 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),
new UploadPackCommand(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

@@ -35,7 +35,6 @@ public class ReloadCommand extends BukkitCommandFeature<CommandSender> {
}
if (argument == ReloadArgument.CONFIG) {
try {
RELOAD_PACK_FLAG = true;
plugin().reloadPlugin(plugin().scheduler().async(), r -> plugin().scheduler().sync().run(r), false).thenAccept(reloadResult -> {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_SUCCESS,
Component.text(reloadResult.asyncTime() + reloadResult.syncTime()),
@@ -49,13 +48,13 @@ public class ReloadCommand extends BukkitCommandFeature<CommandSender> {
}
} else if (argument == ReloadArgument.RECIPE) {
try {
RELOAD_PACK_FLAG = true;
plugin().reloadPlugin(plugin().scheduler().async(), r -> plugin().scheduler().sync().run(r), true).thenAccept(reloadResult -> {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_SUCCESS,
Component.text(reloadResult.asyncTime() + reloadResult.syncTime()),
Component.text(reloadResult.asyncTime()),
Component.text(reloadResult.syncTime())
);
});
} catch (Exception e) {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_FAILURE);
@@ -69,24 +68,30 @@ public class ReloadCommand extends BukkitCommandFeature<CommandSender> {
long time2 = System.currentTimeMillis();
long packTime = time2 - time1;
handleFeedback(context, MessageConstants.COMMAND_RELOAD_PACK_SUCCESS, Component.text(packTime));
} catch (Exception e) {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_PACK_FAILURE);
plugin().logger().warn("Failed to generate resource pack", e);
}
});
} else if (argument == ReloadArgument.ALL) {
RELOAD_PACK_FLAG = true;
try {
plugin().reloadPlugin(plugin().scheduler().async(), r -> plugin().scheduler().sync().run(r), true).thenAcceptAsync(reloadResult -> {
long time1 = System.currentTimeMillis();
plugin().packManager().generateResourcePack();
long time2 = System.currentTimeMillis();
long packTime = time2 - time1;
handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_SUCCESS,
Component.text(reloadResult.asyncTime() + reloadResult.syncTime() + packTime),
Component.text(reloadResult.asyncTime()),
Component.text(reloadResult.syncTime()),
Component.text(packTime)
);
try {
long time1 = System.currentTimeMillis();
plugin().packManager().generateResourcePack();
long time2 = System.currentTimeMillis();
long packTime = time2 - time1;
handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_SUCCESS,
Component.text(reloadResult.asyncTime() + reloadResult.syncTime() + packTime),
Component.text(reloadResult.asyncTime()),
Component.text(reloadResult.syncTime()),
Component.text(packTime)
);
} finally {
RELOAD_PACK_FLAG = false;
}
}, plugin().scheduler().async());
} catch (Exception e) {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_FAILURE);

View File

@@ -1,14 +1,11 @@
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.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.entity.Pose;
import org.incendo.cloud.Command;
public class TestCommand extends BukkitCommandFeature<CommandSender> {
@@ -23,10 +20,6 @@ public class TestCommand extends BukkitCommandFeature<CommandSender> {
.senderType(Player.class)
.handler(context -> {
Player player = context.sender();
ItemStack itemStack = new ItemStack(Material.STONE);
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(itemStack);
wrapped.lore(null);
player.getInventory().addItem(wrapped.load());
});
}

View File

@@ -0,0 +1,36 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
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.incendo.cloud.Command;
public class UploadPackCommand extends BukkitCommandFeature<CommandSender> {
public UploadPackCommand(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 -> {
ResourcePackHost host = plugin().packManager().resourcePackHost();
if (host.canUpload()) {
handleFeedback(context, MessageConstants.COMMAND_UPLOAD_ON_PROGRESS);
plugin().packManager().uploadResourcePack();
} else {
handleFeedback(context, MessageConstants.COMMAND_UPLOAD_FAILURE_NOT_SUPPORTED, Component.text(host.type().value()));
}
});
}
@Override
public String getFeatureID() {
return "upload";
}
}

View File

@@ -130,6 +130,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
registerNMSPacketConsumer(PacketConsumers.PLAYER_INFO_UPDATE, Reflections.clazz$ClientboundPlayerInfoUpdatePacket);
registerNMSPacketConsumer(PacketConsumers.PLAYER_ACTION, Reflections.clazz$ServerboundPlayerActionPacket);
registerNMSPacketConsumer(PacketConsumers.SWING_HAND, Reflections.clazz$ServerboundSwingPacket);
registerNMSPacketConsumer(PacketConsumers.HELLO_C2S, Reflections.clazz$ServerboundHelloPacket);
registerNMSPacketConsumer(PacketConsumers.USE_ITEM_ON, Reflections.clazz$ServerboundUseItemOnPacket);
registerNMSPacketConsumer(PacketConsumers.PICK_ITEM_FROM_BLOCK, Reflections.clazz$ServerboundPickItemFromBlockPacket);
registerNMSPacketConsumer(PacketConsumers.SET_CREATIVE_SLOT, Reflections.clazz$ServerboundSetCreativeModeSlotPacket);
@@ -144,6 +145,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
registerNMSPacketConsumer(PacketConsumers.SIGN_UPDATE, Reflections.clazz$ServerboundSignUpdatePacket);
registerNMSPacketConsumer(PacketConsumers.EDIT_BOOK, Reflections.clazz$ServerboundEditBookPacket);
registerNMSPacketConsumer(PacketConsumers.CUSTOM_PAYLOAD, Reflections.clazz$ServerboundCustomPayloadPacket);
registerNMSPacketConsumer(PacketConsumers.RESOURCE_PACK_PUSH, Reflections.clazz$ClientboundResourcePackPushPacket);
registerByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket());
registerByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket());
registerByteBufPacketConsumer(VersionHelper.isVersionNewerThan1_21_3() ? PacketConsumers.LEVEL_PARTICLE_1_21_3 : (VersionHelper.isVersionNewerThan1_20_5() ? PacketConsumers.LEVEL_PARTICLE_1_20_5 : PacketConsumers.LEVEL_PARTICLE_1_20), this.packetIds.clientboundLevelParticlesPacket());
@@ -286,7 +288,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
return hasModelEngine;
}
public void receivePacket(@NotNull NetWorkUser player, Object packet) {
public void simulatePacket(@NotNull NetWorkUser player, Object packet) {
Channel channel = player.nettyChannel();
if (channel.isOpen()) {
List<String> handlerNames = channel.pipeline().names();

View File

@@ -13,6 +13,7 @@ import net.momirealms.craftengine.bukkit.compatibility.modelengine.ModelEngineUt
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.pack.BukkitPackManager;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
@@ -21,6 +22,8 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult;
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
@@ -28,6 +31,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;
@@ -50,6 +54,7 @@ public class PacketConsumers {
private static int[] mappingsMOD;
private static IntIdentityList BLOCK_LIST;
private static IntIdentityList BIOME_LIST;
private static final UUID EMPTY_UUID = new UUID(0, 0);
public static void init(Map<Integer, Integer> map, int registrySize) {
mappings = new int[registrySize];
@@ -221,7 +226,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 +1123,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();
@@ -1161,6 +1160,28 @@ public class PacketConsumers {
}
}
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> HELLO_C2S = (user, event, packet) -> {
try {
BukkitServerPlayer player = (BukkitServerPlayer) user;
String name = (String) Reflections.field$ServerboundHelloPacket$name.get(packet);
player.setName(name);
if (VersionHelper.isVersionNewerThan1_20_2()) {
UUID uuid = (UUID) Reflections.field$ServerboundHelloPacket$uuid.get(packet);
player.setUUID(uuid);
} else {
@SuppressWarnings("unchecked")
Optional<UUID> uuid = (Optional<UUID>) Reflections.field$ServerboundHelloPacket$uuid.get(packet);
if (uuid.isPresent()) {
player.setUUID(uuid.get());
} else {
player.setUUID(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8)));
}
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ServerboundHelloPacket", e);
}
};
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> SWING_HAND = (user, event, packet) -> {
try {
if (!user.isOnline()) return;
@@ -1272,7 +1293,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;
@@ -2088,4 +2109,36 @@ public class PacketConsumers {
CraftEngine.instance().logger().warn("Failed to handle ClientboundSetScorePacket", e);
}
};
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> RESOURCE_PACK_PUSH = (user, event, packet) -> {
try {
if (!VersionHelper.isVersionNewerThan1_20_2()) return;
// we should only handle fake urls
String url = FastNMS.INSTANCE.field$ClientboundResourcePackPushPacket$url(packet);
if (!url.equals(BukkitPackManager.FAKE_URL)) {
return;
}
event.setCancelled(true);
UUID packUUID = FastNMS.INSTANCE.field$ClientboundResourcePackPushPacket$uuid(packet);
ResourcePackHost host = CraftEngine.instance().packManager().resourcePackHost();
host.requestResourcePackDownloadLink(user.uuid()).thenAccept(dataList -> {
if (dataList.isEmpty()) {
user.simulatePacket(FastNMS.INSTANCE.constructor$ServerboundResourcePackPacket$SUCCESSFULLY_LOADED(packUUID));
return;
}
for (ResourcePackDownloadData data : dataList) {
Object newPacket = ResourcePackUtils.createPacket(data.uuid(), data.url(), data.sha1());
user.nettyChannel().writeAndFlush(newPacket);
user.addResourcePackUUID(data.uuid());
}
}).exceptionally(throwable -> {
CraftEngine.instance().logger().warn("Failed to handle ClientboundResourcePackPushPacket", throwable);
user.simulatePacket(FastNMS.INSTANCE.constructor$ServerboundResourcePackPacket$SUCCESSFULLY_LOADED(packUUID));
return null;
});
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ClientboundResourcePackPushPacket", e);
}
};
}

View File

@@ -9,50 +9,54 @@ 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;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
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 String name;
private UUID uuid;
private ConnectionState decoderState;
private ConnectionState encoderState;
private final Set<UUID> resourcePackUUID = Collections.synchronizedSet(new HashSet<>());
// 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 +65,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 +93,17 @@ 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));
this.uuid = player.getUniqueId();
this.name = player.getName();
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 +131,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 +159,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 +189,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
@@ -190,9 +222,24 @@ public class BukkitServerPlayer extends Player {
@Override
public String name() {
org.bukkit.entity.Player player = platformPlayer();
if (player == null) return "Unknown";
return player.getName();
return this.name;
}
@Override
public void setName(String name) {
if (this.name != null) return;
this.name = name;
}
@Override
public UUID uuid() {
return this.uuid;
}
@Override
public void setUUID(UUID uuid) {
if (this.uuid != null) return;
this.uuid = uuid;
}
@Override
@@ -210,39 +257,19 @@ 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 receivePacket(Object packet) {
this.plugin.networkManager().receivePacket(this, packet);
public void sendPackets(List<Object> packet, boolean immediately) {
this.plugin.networkManager().sendPackets(this, packet, immediately);
}
@Override
public void simulatePacket(Object packet) {
this.plugin.networkManager().simulatePacket(this, packet);
}
@Override
@@ -290,48 +317,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 +421,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 +431,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 +462,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 +483,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 +496,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 +512,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 +579,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 +593,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;
}
}
}
@@ -657,9 +741,29 @@ public class BukkitServerPlayer extends Player {
this.hasClientMod = enable;
}
@Override
public void addResourcePackUUID(UUID uuid) {
if (VersionHelper.isVersionNewerThan1_20_3()) {
this.resourcePackUUID.add(uuid);
}
}
@Override
public void clearView() {
this.entityTypeView.clear();
this.furnitureView.clear();
}
@Override
public void unloadCurrentResourcePack() {
if (!VersionHelper.isVersionNewerThan1_20_3()) {
return;
}
if (decoderState() == ConnectionState.PLAY && !this.resourcePackUUID.isEmpty()) {
for (UUID u : this.resourcePackUUID) {
sendPacket(FastNMS.INSTANCE.constructor$ClientboundResourcePackPopPacket(u), true);
}
this.resourcePackUUID.clear();
}
}
}

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

@@ -7,9 +7,9 @@ public class NoteBlockChainUpdateUtils {
private NoteBlockChainUpdateUtils() {}
public static void noteBlockChainUpdate(Object level, Object chunkSource, Object direction, Object blockPos, int times) throws ReflectiveOperationException {
public static void noteBlockChainUpdate(Object level, Object chunkSource, Object direction, Object blockPos, int times) {
if (times >= Config.maxChainUpdate()) return;
Object relativePos = Reflections.method$BlockPos$relative.invoke(blockPos, direction);
Object relativePos = FastNMS.INSTANCE.method$BlockPos$relative(blockPos, direction);
Object state = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, relativePos);
if (BlockStateUtils.isClientSideNoteBlock(state)) {
FastNMS.INSTANCE.method$ServerChunkCache$blockChanged(chunkSource, relativePos);

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;
@@ -1527,7 +1522,6 @@ public class Reflections {
public static final Object instance$Direction$WEST;
public static final Object instance$Direction$EAST;
public static final Object[] instance$Directions;
private static final Map<Object, Object> oppositeDirections = new HashMap<>();
static {
try {
@@ -1538,20 +1532,14 @@ public class Reflections {
instance$Direction$SOUTH = instance$Directions[3];
instance$Direction$WEST = instance$Directions[4];
instance$Direction$EAST = instance$Directions[5];
oppositeDirections.put(instance$Direction$DOWN, instance$Direction$UP);
oppositeDirections.put(instance$Direction$UP, instance$Direction$DOWN);
oppositeDirections.put(instance$Direction$NORTH, instance$Direction$SOUTH);
oppositeDirections.put(instance$Direction$SOUTH, instance$Direction$NORTH);
oppositeDirections.put(instance$Direction$WEST, instance$Direction$EAST);
oppositeDirections.put(instance$Direction$EAST, instance$Direction$WEST);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object getOppositeDirection(Object direction) {
return oppositeDirections.get(direction);
}
// public static Object getOppositeDirection(Object direction) {
// return oppositeDirections.get(direction);
// }
public static final Class<?> clazz$CraftBlock = requireNonNull(
ReflectionUtils.getClazz(
@@ -2418,6 +2406,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 +2919,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 +2931,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 +3003,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 +3016,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 +3988,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 +4949,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 +5762,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 +6432,84 @@ 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
);
public static final Class<?> clazz$ServerboundHelloPacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.login.ServerboundHelloPacket"),
BukkitReflectionUtils.assembleMCClass("network.protocol.login.PacketLoginInStart")
)
);
public static final Field field$ServerboundHelloPacket$name = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ServerboundHelloPacket, String.class, 0
)
);
public static final Field field$ServerboundHelloPacket$uuid = requireNonNull(
VersionHelper.isVersionNewerThan1_20_2() ?
ReflectionUtils.getDeclaredField(
clazz$ServerboundHelloPacket, UUID.class, 0
) :
ReflectionUtils.getDeclaredField(
clazz$ServerboundHelloPacket, Optional.class, 0
)
);
public static final Field field$ClientboundResourcePackPushPacket$id =
ReflectionUtils.getDeclaredField(
clazz$ClientboundResourcePackPushPacket, UUID.class, 0
);
public static final Field field$ClientboundResourcePackPushPacket$prompt = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ClientboundResourcePackPushPacket,
VersionHelper.isVersionNewerThan1_20_5() ? Optional.class : clazz$Component,
0
)
);
public static final Class<?> clazz$ServerboundResourcePackPacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundResourcePackPacket"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayInResourcePackStatus")
)
);
public static final Class<?> clazz$ServerboundResourcePackPacket$Action = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundResourcePackPacket$Action"),
BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundResourcePackPacket$a"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ServerboundResourcePackPacket$Action"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayInResourcePackStatus$EnumResourcePackStatus")
)
);
public static final Method method$ServerboundResourcePackPacket$Action$values = requireNonNull(
ReflectionUtils.getStaticMethod(
clazz$ServerboundResourcePackPacket$Action, clazz$ServerboundResourcePackPacket$Action.arrayType()
)
);
public static final Object instance$ServerboundResourcePackPacket$Action$ACCEPTED;
static {
try {
Object[] values = (Object[]) method$ServerboundResourcePackPacket$Action$values.invoke(null);
instance$ServerboundResourcePackPacket$Action$ACCEPTED = values[3];
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static final Constructor<?> constructor$ServerboundResourcePackPacket = requireNonNull(
field$ClientboundResourcePackPushPacket$id != null
? ReflectionUtils.getConstructor(clazz$ServerboundResourcePackPacket, UUID.class, clazz$ServerboundResourcePackPacket$Action)
: ReflectionUtils.getConstructor(clazz$ServerboundResourcePackPacket, clazz$ServerboundResourcePackPacket$Action)
);
}

View File

@@ -0,0 +1,13 @@
package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.plugin.config.Config;
import java.util.UUID;
public class ResourcePackUtils {
public static Object createPacket(UUID uuid, String url, String hash) {
return FastNMS.INSTANCE.constructor$ClientboundResourcePackPushPacket(uuid, url, hash, Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt()));
}
}