mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-29 03:49:15 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
package net.momirealms.craftengine.bukkit.api;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.UpdateOption;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
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.sparrow.nbt.CompoundTag;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.SoundCategory;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class CraftEngineBlocks {
|
||||
|
||||
/**
|
||||
* Gets a custom block by ID
|
||||
*
|
||||
* @param id id
|
||||
* @return the custom block
|
||||
*/
|
||||
@Nullable
|
||||
public static CustomBlock byId(@NotNull Key id) {
|
||||
return BukkitBlockManager.instance().getBlock(id).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a custom block state at a certain location
|
||||
*
|
||||
* @param location location
|
||||
* @param block block state to place
|
||||
* @param playSound whether to play place sounds
|
||||
* @return success or not
|
||||
*/
|
||||
public static boolean place(@NotNull Location location,
|
||||
@NotNull ImmutableBlockState block,
|
||||
boolean playSound) {
|
||||
return place(location, block, UpdateOption.UPDATE_ALL, playSound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a custom block with given properties
|
||||
*
|
||||
* @param location location
|
||||
* @param blockId block owner id
|
||||
* @param properties properties
|
||||
* @param playSound whether to play place sounds
|
||||
* @return success or not
|
||||
*/
|
||||
public static boolean place(@NotNull Location location,
|
||||
@NotNull Key blockId,
|
||||
@NotNull CompoundTag properties,
|
||||
boolean playSound) {
|
||||
CustomBlock block = byId(blockId);
|
||||
if (block == null) return false;
|
||||
return place(location, block.getBlockState(properties), UpdateOption.UPDATE_ALL, playSound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a custom block with given properties
|
||||
*
|
||||
* @param location location
|
||||
* @param blockId block owner id
|
||||
* @param properties properties
|
||||
* @param option update options
|
||||
* @param playSound whether to play place sounds
|
||||
* @return success or not
|
||||
*/
|
||||
public static boolean place(@NotNull Location location,
|
||||
@NotNull Key blockId,
|
||||
@NotNull CompoundTag properties,
|
||||
@NotNull UpdateOption option,
|
||||
boolean playSound) {
|
||||
CustomBlock block = byId(blockId);
|
||||
if (block == null) return false;
|
||||
return place(location, block.getBlockState(properties), option, playSound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a custom block state at a certain location
|
||||
*
|
||||
* @param location location
|
||||
* @param block block state to place
|
||||
* @param option update options
|
||||
* @param playSound whether to play place sounds
|
||||
* @return success or not
|
||||
*/
|
||||
public static boolean place(@NotNull Location location,
|
||||
@NotNull ImmutableBlockState block,
|
||||
@NotNull UpdateOption option,
|
||||
boolean playSound) {
|
||||
boolean success;
|
||||
try {
|
||||
Object worldServer = Reflections.field$CraftWorld$ServerLevel.get(location.getWorld());
|
||||
Object blockPos = Reflections.constructor$BlockPos.newInstance(location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
||||
Object blockState = block.customBlockState().handle();
|
||||
Object oldBlockState = Reflections.method$BlockGetter$getBlockState.invoke(worldServer, blockPos);
|
||||
success = (boolean) Reflections.method$LevelWriter$setBlock.invoke(worldServer, blockPos, blockState, option.flags());
|
||||
if (success) {
|
||||
Reflections.method$BlockStateBase$onPlace.invoke(blockState, worldServer, blockPos, oldBlockState, true);
|
||||
if (playSound) {
|
||||
location.getWorld().playSound(location, block.sounds().placeSound().toString(), SoundCategory.BLOCKS, 1, 0.8f);
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
CraftEngine.instance().logger().warn("Failed to set nms block", e);
|
||||
return false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a block from the world if it's a custom one
|
||||
*
|
||||
* @param block block to remove
|
||||
* @return success or not
|
||||
*/
|
||||
public static boolean remove(@NotNull Block block) {
|
||||
if (!isCustomBlock(block)) return false;
|
||||
block.setType(Material.AIR, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a block from the world if it's a custom one
|
||||
*
|
||||
* @param block block to remove
|
||||
* @param applyPhysics whether to apply physics
|
||||
* @return success or not
|
||||
*/
|
||||
public static boolean remove(@NotNull Block block,
|
||||
boolean applyPhysics) {
|
||||
if (!isCustomBlock(block)) return false;
|
||||
block.setType(Material.AIR, applyPhysics);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a block from the world if it's a custom one
|
||||
*
|
||||
* @param block block to remove
|
||||
* @param player player who breaks the block
|
||||
* @param applyPhysics whether to apply physics
|
||||
* @param dropLoot whether to drop block loots
|
||||
* @param playSound whether to play break sounds
|
||||
* @param sendParticles whether to send break particles
|
||||
* @return success or not
|
||||
*/
|
||||
public static boolean remove(@NotNull Block block,
|
||||
@Nullable Player player,
|
||||
boolean applyPhysics,
|
||||
boolean dropLoot,
|
||||
boolean playSound,
|
||||
boolean sendParticles) {
|
||||
ImmutableBlockState state = getCustomBlockState(block);
|
||||
if (state == null || state.isEmpty()) return false;
|
||||
World world = new BukkitWorld(block.getWorld());
|
||||
Location location = block.getLocation();
|
||||
Vec3d vec3d = new Vec3d(location.getBlockX() + 0.5, location.getBlockY() + 0.5, location.getBlockZ() + 0.5);
|
||||
if (dropLoot) {
|
||||
ContextHolder.Builder builder = new ContextHolder.Builder().withParameter(LootParameters.WORLD, world).withParameter(LootParameters.LOCATION, vec3d);
|
||||
BukkitServerPlayer serverPlayer = BukkitCraftEngine.instance().adapt(player);
|
||||
if (player != null) {
|
||||
builder.withParameter(LootParameters.PLAYER, serverPlayer);
|
||||
builder.withParameter(LootParameters.TOOL, serverPlayer.getItemInHand(InteractionHand.MAIN_HAND));
|
||||
}
|
||||
for (Item<?> item : state.getDrops(builder, world)) {
|
||||
world.dropItemNaturally(vec3d, item);
|
||||
}
|
||||
}
|
||||
if (playSound) {
|
||||
world.playBlockSound(vec3d, state.sounds().breakSound(), 1, 0.8f);
|
||||
}
|
||||
if (sendParticles) {
|
||||
// TODO Particles
|
||||
//ParticleUtils.addBlockBreakParticles(block.getWorld(), LocationUtils.toBlockPos(location), state.customBlockState().handle());
|
||||
}
|
||||
block.setType(Material.AIR, applyPhysics);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a block is a custom one
|
||||
*
|
||||
* @param block block
|
||||
* @return is custom block or not
|
||||
*/
|
||||
public static boolean isCustomBlock(@NotNull Block block) {
|
||||
BlockData blockData = block.getBlockData();
|
||||
int stateId = BlockStateUtils.blockDataToId(blockData);
|
||||
return !BlockStateUtils.isVanillaBlock(stateId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets custom block state from a bukkit block
|
||||
*
|
||||
* @param block block
|
||||
* @return custom block state
|
||||
*/
|
||||
@Nullable
|
||||
public static ImmutableBlockState getCustomBlockState(@NotNull Block block) {
|
||||
BlockData blockData = block.getBlockData();
|
||||
int stateId = BlockStateUtils.blockDataToId(blockData);
|
||||
return BukkitBlockManager.instance().getImmutableBlockState(stateId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets custom block state from bukkit block data
|
||||
*
|
||||
* @param blockData block data
|
||||
* @return custom block state
|
||||
*/
|
||||
@Nullable
|
||||
public static ImmutableBlockState getCustomBlockState(@NotNull BlockData blockData) {
|
||||
int stateId = BlockStateUtils.blockDataToId(blockData);
|
||||
return BukkitBlockManager.instance().getImmutableBlockState(stateId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates bukkit block data from a custom block state
|
||||
*
|
||||
* @param blockState custom block state
|
||||
* @return bukkit block data
|
||||
*/
|
||||
@NotNull
|
||||
public static BlockData createBukkitBlockData(@NotNull ImmutableBlockState blockState) {
|
||||
return BlockStateUtils.createBlockData(blockState.customBlockState().handle());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package net.momirealms.craftengine.bukkit.api;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.LocationUtils;
|
||||
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
|
||||
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
|
||||
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.loot.LootTable;
|
||||
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
|
||||
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 org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CraftEngineFurniture {
|
||||
|
||||
/**
|
||||
* Gets custom furniture by ID
|
||||
*
|
||||
* @param id id
|
||||
* @return the custom furniture
|
||||
*/
|
||||
public static CustomFurniture byId(@NotNull Key id) {
|
||||
return BukkitFurnitureManager.instance().getFurniture(id).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places furniture at the certain location
|
||||
*
|
||||
* @param location location
|
||||
* @param furnitureId furniture to place
|
||||
* @param anchorType anchor type
|
||||
* @return the loaded furniture
|
||||
*/
|
||||
@Nullable
|
||||
public static LoadedFurniture place(Location location, Key furnitureId, AnchorType anchorType) {
|
||||
CustomFurniture furniture = byId(furnitureId);
|
||||
if (furniture == null) return null;
|
||||
return BukkitFurnitureManager.instance().place(furniture, location, anchorType, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places furniture at the certain location
|
||||
*
|
||||
* @param location location
|
||||
* @param furniture furniture to place
|
||||
* @param anchorType anchor type
|
||||
* @return the loaded furniture
|
||||
*/
|
||||
@NotNull
|
||||
public static LoadedFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType) {
|
||||
return BukkitFurnitureManager.instance().place(furniture, location, anchorType, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places furniture at the certain location
|
||||
*
|
||||
* @param location location
|
||||
* @param furnitureId furniture to place
|
||||
* @param anchorType anchor type
|
||||
* @param playSound whether to play place sounds
|
||||
* @return the loaded furniture
|
||||
*/
|
||||
@Nullable
|
||||
public static LoadedFurniture place(Location location, Key furnitureId, AnchorType anchorType, boolean playSound) {
|
||||
CustomFurniture furniture = byId(furnitureId);
|
||||
if (furniture == null) return null;
|
||||
return BukkitFurnitureManager.instance().place(furniture, location, anchorType, playSound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places furniture at the certain location
|
||||
*
|
||||
* @param location location
|
||||
* @param furniture furniture to place
|
||||
* @param anchorType anchor type
|
||||
* @param playSound whether to play place sounds
|
||||
* @return the loaded furniture
|
||||
*/
|
||||
@NotNull
|
||||
public static LoadedFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType, boolean playSound) {
|
||||
return BukkitFurnitureManager.instance().place(furniture, location, anchorType, playSound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entity is a piece of furniture
|
||||
*
|
||||
* @param entity entity to check
|
||||
* @return is furniture or not
|
||||
*/
|
||||
public static boolean isFurniture(@NotNull Entity entity) {
|
||||
String furnitureId = entity.getPersistentDataContainer().get(BukkitFurnitureManager.FURNITURE_KEY, PersistentDataType.STRING);
|
||||
return furnitureId != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entity is a seat
|
||||
*
|
||||
* @param entity entity to check
|
||||
* @return is seat or not
|
||||
*/
|
||||
public static boolean isSeat(@NotNull Entity entity) {
|
||||
Integer baseEntityId = entity.getPersistentDataContainer().get(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER);
|
||||
return baseEntityId != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base furniture by the base entity
|
||||
*
|
||||
* @param baseEntity base entity
|
||||
* @return the loaded furniture
|
||||
*/
|
||||
@Nullable
|
||||
public static LoadedFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) {
|
||||
return BukkitFurnitureManager.instance().getLoadedFurnitureByBaseEntityId(baseEntity.getEntityId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base furniture by the seat entity
|
||||
*
|
||||
* @param seat seat entity
|
||||
* @return the loaded furniture
|
||||
*/
|
||||
@Nullable
|
||||
public static LoadedFurniture getLoadedFurnitureBySeat(@NotNull Entity seat) {
|
||||
Integer baseEntityId = seat.getPersistentDataContainer().get(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER);
|
||||
if (baseEntityId == null) return null;
|
||||
return BukkitFurnitureManager.instance().getLoadedFurnitureByBaseEntityId(baseEntityId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes furniture
|
||||
*
|
||||
* @param furniture furniture base entity
|
||||
* @return success or not
|
||||
*/
|
||||
public static boolean remove(@NotNull Entity furniture) {
|
||||
if (!isFurniture(furniture)) return false;
|
||||
LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().getLoadedFurnitureByBaseEntityId(furniture.getEntityId());
|
||||
if (loadedFurniture == null) return false;
|
||||
loadedFurniture.destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes furniture, with more options
|
||||
*
|
||||
* @param furniture furniture base entity
|
||||
* @param dropLoot whether to drop loots
|
||||
* @param playSound whether to play break sound
|
||||
* @return success or not
|
||||
*/
|
||||
public static boolean remove(@NotNull Entity furniture,
|
||||
boolean dropLoot,
|
||||
boolean playSound) {
|
||||
if (!isFurniture(furniture)) return false;
|
||||
LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().getLoadedFurnitureByBaseEntityId(furniture.getEntityId());
|
||||
if (loadedFurniture == null) return false;
|
||||
remove(loadedFurniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes furniture, with more options
|
||||
*
|
||||
* @param furniture furniture base entity
|
||||
* @param player the player who removes the furniture
|
||||
* @param dropLoot whether to drop loots
|
||||
* @param playSound whether to play break sound
|
||||
* @return success or not
|
||||
*/
|
||||
public static boolean remove(@NotNull Entity furniture,
|
||||
@Nullable Player player,
|
||||
boolean dropLoot,
|
||||
boolean playSound) {
|
||||
if (!isFurniture(furniture)) return false;
|
||||
LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().getLoadedFurnitureByBaseEntityId(furniture.getEntityId());
|
||||
if (loadedFurniture == null) return false;
|
||||
remove(loadedFurniture, player, dropLoot, playSound);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes furniture by providing a plugin furniture instance
|
||||
*
|
||||
* @param loadedFurniture loaded furniture
|
||||
* @param dropLoot whether to drop loots
|
||||
* @param playSound whether to play break sound
|
||||
*/
|
||||
public static void remove(@NotNull LoadedFurniture loadedFurniture,
|
||||
boolean dropLoot,
|
||||
boolean playSound) {
|
||||
remove(loadedFurniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes furniture by providing a plugin furniture instance
|
||||
*
|
||||
* @param loadedFurniture loaded furniture
|
||||
* @param player the player who removes the furniture
|
||||
* @param dropLoot whether to drop loots
|
||||
* @param playSound whether to play break sound
|
||||
*/
|
||||
|
||||
public static void remove(@NotNull LoadedFurniture loadedFurniture,
|
||||
@Nullable Player player,
|
||||
boolean dropLoot,
|
||||
boolean playSound) {
|
||||
remove(loadedFurniture, player == null ? null : BukkitCraftEngine.instance().adapt(player), dropLoot, playSound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes furniture by providing a plugin furniture instance
|
||||
*
|
||||
* @param loadedFurniture loaded furniture
|
||||
* @param player the player who removes the furniture
|
||||
* @param dropLoot whether to drop loots
|
||||
* @param playSound whether to play break sound
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void remove(@NotNull LoadedFurniture loadedFurniture,
|
||||
@Nullable net.momirealms.craftengine.core.entity.player.Player player,
|
||||
boolean dropLoot,
|
||||
boolean playSound) {
|
||||
Location location = loadedFurniture.location();
|
||||
loadedFurniture.destroy();
|
||||
LootTable<ItemStack> lootTable = (LootTable<ItemStack>) loadedFurniture.furniture().lootTable();
|
||||
Vec3d vec3d = LocationUtils.toVec3d(location);
|
||||
World world = new BukkitWorld(location.getWorld());
|
||||
if (dropLoot && lootTable != null) {
|
||||
ContextHolder.Builder builder = ContextHolder.builder();
|
||||
builder.withParameter(LootParameters.LOCATION, vec3d);
|
||||
builder.withParameter(LootParameters.WORLD, world);
|
||||
if (player != null) {
|
||||
builder.withParameter(LootParameters.PLAYER, player);
|
||||
builder.withParameter(LootParameters.TOOL, player.getItemInHand(InteractionHand.MAIN_HAND));
|
||||
}
|
||||
List<Item<ItemStack>> items = lootTable.getRandomItems(builder.build(), world);
|
||||
for (Item<ItemStack> item : items) {
|
||||
world.dropItemNaturally(vec3d, item);
|
||||
}
|
||||
}
|
||||
if (playSound) {
|
||||
world.playBlockSound(vec3d, loadedFurniture.furniture().settings().sounds().breakSound(), 1f, 0.8f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.momirealms.craftengine.bukkit.api;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.craftengine.bukkit.util.ItemUtils;
|
||||
import net.momirealms.craftengine.core.item.CustomItem;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class CraftEngineItems {
|
||||
|
||||
/**
|
||||
* Gets a custom item by ID
|
||||
*
|
||||
* @param id id
|
||||
* @return the custom item
|
||||
*/
|
||||
@Nullable
|
||||
public static CustomItem<ItemStack> byId(@NotNull Key id) {
|
||||
return BukkitItemManager.instance().getCustomItem(id).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a custom item by existing item stack
|
||||
*
|
||||
* @param itemStack item stack
|
||||
* @return the custom item
|
||||
*/
|
||||
@Nullable
|
||||
public static CustomItem<ItemStack> byItemStack(@NotNull ItemStack itemStack) {
|
||||
if (ItemUtils.isEmpty(itemStack)) return null;
|
||||
return BukkitItemManager.instance().wrap(itemStack).getCustomItem().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item is a custom one
|
||||
*
|
||||
* @param itemStack item stack
|
||||
* @return true if it's a custom item
|
||||
*/
|
||||
public static boolean isCustomItem(@NotNull ItemStack itemStack) {
|
||||
if (ItemUtils.isEmpty(itemStack)) return false;
|
||||
return BukkitItemManager.instance().wrap(itemStack).isCustomItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets custom item id from item stack
|
||||
*
|
||||
* @param itemStack item stack
|
||||
* @return the custom id, null if it's not a custom one
|
||||
*/
|
||||
@Nullable
|
||||
public static Key getCustomItemId(@NotNull ItemStack itemStack) {
|
||||
if (ItemUtils.isEmpty(itemStack)) return null;
|
||||
return BukkitItemManager.instance().wrap(itemStack).customId().orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.momirealms.craftengine.bukkit.api.event;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class AsyncResourcePackGenerateEvent extends Event {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private final Path generatedPackPath;
|
||||
private final Path zipFilePath;
|
||||
|
||||
public AsyncResourcePackGenerateEvent(@NotNull Path generatedPackPath,
|
||||
@NotNull Path zipFilePath) {
|
||||
super(true);
|
||||
this.generatedPackPath = generatedPackPath;
|
||||
this.zipFilePath = zipFilePath;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Path resourcePackFolder() {
|
||||
return generatedPackPath;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Path zipFilePath() {
|
||||
return zipFilePath;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HandlerList getHandlers() {
|
||||
return getHandlerList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.momirealms.craftengine.bukkit.api.event;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CraftEngineReloadEvent extends Event {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private final BukkitCraftEngine plugin;
|
||||
|
||||
public CraftEngineReloadEvent(BukkitCraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public BukkitCraftEngine plugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package net.momirealms.craftengine.bukkit.api.event;
|
||||
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CustomBlockAttemptPlaceEvent extends PlayerEvent implements Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private boolean cancelled;
|
||||
private final CustomBlock customBlock;
|
||||
private final ImmutableBlockState state;
|
||||
private final Location location;
|
||||
private final BlockFace clickedFace;
|
||||
private final Block clickedBlock;
|
||||
private final InteractionHand hand;
|
||||
|
||||
public CustomBlockAttemptPlaceEvent(@NotNull Player player,
|
||||
@NotNull Location location,
|
||||
@NotNull ImmutableBlockState state,
|
||||
@NotNull BlockFace clickedFace,
|
||||
@NotNull Block clickedBlock,
|
||||
@NotNull InteractionHand hand) {
|
||||
super(player);
|
||||
this.customBlock = state.owner().value();
|
||||
this.state = state;
|
||||
this.location = location;
|
||||
this.clickedFace = clickedFace;
|
||||
this.clickedBlock = clickedBlock;
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Player player() {
|
||||
return getPlayer();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public InteractionHand hand() {
|
||||
return hand;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Block clickedBlock() {
|
||||
return clickedBlock;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public BlockFace clickedFace() {
|
||||
return clickedFace;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomBlock customBlock() {
|
||||
return this.customBlock;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Location location() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ImmutableBlockState blockState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HandlerList getHandlers() {
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package net.momirealms.craftengine.bukkit.api.event;
|
||||
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CustomBlockBreakEvent extends PlayerEvent implements Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private boolean cancelled;
|
||||
private final CustomBlock customBlock;
|
||||
private final ImmutableBlockState state;
|
||||
private final Location location;
|
||||
private final Block bukkitBlock;
|
||||
private boolean dropItems;
|
||||
|
||||
public CustomBlockBreakEvent(@NotNull Player player,
|
||||
@NotNull Location location,
|
||||
@NotNull Block bukkitBlock,
|
||||
@NotNull ImmutableBlockState state) {
|
||||
super(player);
|
||||
this.customBlock = state.owner().value();
|
||||
this.state = state;
|
||||
this.bukkitBlock = bukkitBlock;
|
||||
this.location = location;
|
||||
this.dropItems = true;
|
||||
}
|
||||
|
||||
public boolean dropItems() {
|
||||
return dropItems;
|
||||
}
|
||||
|
||||
public void setDropItems(boolean dropItems) {
|
||||
this.dropItems = dropItems;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Block bukkitBlock() {
|
||||
return bukkitBlock;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Player player() {
|
||||
return getPlayer();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomBlock customBlock() {
|
||||
return this.customBlock;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Location location() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ImmutableBlockState blockState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HandlerList getHandlers() {
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package net.momirealms.craftengine.bukkit.api.event;
|
||||
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class CustomBlockInteractEvent extends PlayerEvent implements Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private boolean cancelled;
|
||||
private final CustomBlock customBlock;
|
||||
private final Block bukkitBlock;
|
||||
private final ImmutableBlockState state;
|
||||
private final Location location;
|
||||
private final Location interactionPoint;
|
||||
private final InteractionHand hand;
|
||||
private final Action action;
|
||||
private final BlockFace clickedFace;
|
||||
|
||||
public CustomBlockInteractEvent(@NotNull Player player,
|
||||
@NotNull Location location,
|
||||
@Nullable Location interactionPoint,
|
||||
@NotNull ImmutableBlockState state,
|
||||
@NotNull Block bukkitBlock,
|
||||
@NotNull BlockFace clickedFace,
|
||||
@NotNull InteractionHand hand,
|
||||
@NotNull Action action) {
|
||||
super(player);
|
||||
this.customBlock = state.owner().value();
|
||||
this.bukkitBlock = bukkitBlock;
|
||||
this.state = state;
|
||||
this.location = location;
|
||||
this.interactionPoint = interactionPoint;
|
||||
this.hand = hand;
|
||||
this.action = action;
|
||||
this.clickedFace = clickedFace;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public BlockFace clickedFace() {
|
||||
return clickedFace;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Location interactionPoint() {
|
||||
return interactionPoint;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Action action() {
|
||||
return action;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public InteractionHand hand() {
|
||||
return hand;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Block bukkitBlock() {
|
||||
return bukkitBlock;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomBlock customBlock() {
|
||||
return this.customBlock;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Player player() {
|
||||
return getPlayer();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Location location() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ImmutableBlockState blockState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HandlerList getHandlers() {
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
LEFT_CLICK,
|
||||
RIGHT_CLICK
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package net.momirealms.craftengine.bukkit.api.event;
|
||||
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CustomBlockPlaceEvent extends PlayerEvent implements Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private final CustomBlock customBlock;
|
||||
private final ImmutableBlockState state;
|
||||
private final Location location;
|
||||
private final InteractionHand hand;
|
||||
private final Block bukkitBlock;
|
||||
private boolean cancelled;
|
||||
|
||||
public CustomBlockPlaceEvent(@NotNull Player player,
|
||||
@NotNull Location location,
|
||||
@NotNull ImmutableBlockState state,
|
||||
@NotNull Block bukkitBlock,
|
||||
@NotNull InteractionHand hand) {
|
||||
super(player);
|
||||
this.customBlock = state.owner().value();
|
||||
this.state = state;
|
||||
this.location = location;
|
||||
this.hand = hand;
|
||||
this.bukkitBlock = bukkitBlock;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Block bukkitBlock() {
|
||||
return bukkitBlock;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public InteractionHand hand() {
|
||||
return hand;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomBlock customBlock() {
|
||||
return this.customBlock;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Location location() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ImmutableBlockState blockState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Player player() {
|
||||
return getPlayer();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HandlerList getHandlers() {
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package net.momirealms.craftengine.bukkit.api.event;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
|
||||
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FurnitureAttemptPlaceEvent extends PlayerEvent implements Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private boolean cancelled;
|
||||
private final CustomFurniture furniture;
|
||||
private final Location location;
|
||||
private final AnchorType anchorType;
|
||||
private final BlockFace clickedFace;
|
||||
private final Block clickedBlock;
|
||||
private final InteractionHand hand;
|
||||
|
||||
public FurnitureAttemptPlaceEvent(@NotNull Player player,
|
||||
@NotNull CustomFurniture furniture,
|
||||
@NotNull AnchorType anchorType,
|
||||
@NotNull Location location,
|
||||
@NotNull BlockFace clickedFace,
|
||||
@NotNull InteractionHand hand,
|
||||
@NotNull Block clickedBlock) {
|
||||
super(player);
|
||||
this.furniture = furniture;
|
||||
this.location = location;
|
||||
this.anchorType = anchorType;
|
||||
this.clickedFace = clickedFace;
|
||||
this.clickedBlock = clickedBlock;
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Block clickedBlock() {
|
||||
return clickedBlock;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public InteractionHand hand() {
|
||||
return hand;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public BlockFace clickedFace() {
|
||||
return clickedFace;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Player player() {
|
||||
return getPlayer();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AnchorType anchorType() {
|
||||
return anchorType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Location location() {
|
||||
return location;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomFurniture furniture() {
|
||||
return furniture;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HandlerList getHandlers() {
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package net.momirealms.craftengine.bukkit.api.event;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FurnitureBreakEvent extends PlayerEvent implements Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private boolean cancelled;
|
||||
private final LoadedFurniture furniture;
|
||||
|
||||
public FurnitureBreakEvent(@NotNull Player player,
|
||||
@NotNull LoadedFurniture furniture) {
|
||||
super(player);
|
||||
this.furniture = furniture;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Player player() {
|
||||
return getPlayer();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public LoadedFurniture furniture() {
|
||||
return this.furniture;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HandlerList getHandlers() {
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package net.momirealms.craftengine.bukkit.api.event;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FurnitureInteractEvent extends PlayerEvent implements Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private boolean cancelled;
|
||||
private final LoadedFurniture furniture;
|
||||
private final InteractionHand hand;
|
||||
private final Location interactionPoint;
|
||||
|
||||
public FurnitureInteractEvent(@NotNull Player player,
|
||||
@NotNull LoadedFurniture furniture,
|
||||
@NotNull InteractionHand hand,
|
||||
@NotNull Location interactionPoint) {
|
||||
super(player);
|
||||
this.furniture = furniture;
|
||||
this.hand = hand;
|
||||
this.interactionPoint = interactionPoint;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Location interactionPoint() {
|
||||
return interactionPoint;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Player player() {
|
||||
return getPlayer();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public InteractionHand hand() {
|
||||
return hand;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public LoadedFurniture furniture() {
|
||||
return this.furniture;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HandlerList getHandlers() {
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package net.momirealms.craftengine.bukkit.api.event;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FurniturePlaceEvent extends PlayerEvent implements Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private final Location location;
|
||||
private final LoadedFurniture furniture;
|
||||
private final InteractionHand hand;
|
||||
private boolean cancelled;
|
||||
|
||||
public FurniturePlaceEvent(@NotNull Player player,
|
||||
@NotNull LoadedFurniture furniture,
|
||||
@NotNull Location location,
|
||||
@NotNull InteractionHand hand) {
|
||||
super(player);
|
||||
this.location = location;
|
||||
this.furniture = furniture;
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public InteractionHand hand() {
|
||||
return hand;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public LoadedFurniture furniture() {
|
||||
return furniture;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Location location() {
|
||||
return location;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Player player() {
|
||||
return getPlayer();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HandlerList getHandlers() {
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
package net.momirealms.craftengine.bukkit.block;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.api.event.CustomBlockBreakEvent;
|
||||
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.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.PushReaction;
|
||||
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.parameter.LootParameters;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.context.ContextHolder;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.NoteBlock;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.*;
|
||||
import org.bukkit.event.entity.EntityExplodeEvent;
|
||||
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;
|
||||
private final boolean enableNoteBlockCheck;
|
||||
private final BukkitBlockManager manager;
|
||||
// private static final Set<Material> WATER_BUCKETS = Arrays.stream(ItemKeys.WATER_BUCKETS).map(it -> Registry.MATERIAL.get(new NamespacedKey(it.namespace(), it.value()))).collect(Collectors.toSet());
|
||||
|
||||
public BlockEventListener(BukkitCraftEngine plugin, BukkitBlockManager manager, boolean enableNoteBlockCheck) {
|
||||
this.plugin = plugin;
|
||||
this.manager = manager;
|
||||
this.enableNoteBlockCheck = enableNoteBlockCheck;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlaceBlock(BlockPlaceEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
BukkitServerPlayer serverPlayer = plugin.adapt(player);
|
||||
// send swing if player is clicking a replaceable block
|
||||
if (serverPlayer.shouldResendSwing()) {
|
||||
player.swingHand(event.getHand());
|
||||
}
|
||||
// send sound if the placed block's sounds are removed
|
||||
if (ConfigManager.enableSoundSystem()) {
|
||||
Block block = event.getBlock();
|
||||
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
|
||||
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
|
||||
if (this.manager.isBlockSoundRemoved(ownerBlock)) {
|
||||
if (player.getInventory().getItemInMainHand().getType() != Material.DEBUG_STICK) {
|
||||
try {
|
||||
Object soundType = Reflections.field$BlockBehaviour$soundType.get(ownerBlock);
|
||||
Object placeSound = Reflections.field$SoundType$placeSound.get(soundType);
|
||||
player.playSound(block.getLocation(), Reflections.field$SoundEvent$location.get(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
this.plugin.logger().warn("Failed to get sound type", e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// resend sound if the clicked block is interactable on client side
|
||||
if (serverPlayer.shouldResendSound()) {
|
||||
try {
|
||||
Block block = event.getBlock();
|
||||
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
|
||||
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
|
||||
Object soundType = Reflections.field$BlockBehaviour$soundType.get(ownerBlock);
|
||||
Object placeSound = Reflections.field$SoundType$placeSound.get(soundType);
|
||||
player.playSound(block.getLocation(), Reflections.field$SoundEvent$location.get(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
this.plugin.logger().warn("Failed to get sound type", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onPlayerBreak(BlockBreakEvent event) {
|
||||
org.bukkit.block.Block block = event.getBlock();
|
||||
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
|
||||
int stateId = BlockStateUtils.blockStateToId(blockState);
|
||||
if (!BlockStateUtils.isVanillaBlock(stateId)) {
|
||||
ImmutableBlockState state = manager.getImmutableBlockStateUnsafe(stateId);
|
||||
if (!state.isEmpty()) {
|
||||
Location location = block.getLocation();
|
||||
|
||||
// trigger event
|
||||
CustomBlockBreakEvent customBreakEvent = new CustomBlockBreakEvent(event.getPlayer(), location, block, state);
|
||||
boolean isCancelled = EventUtils.fireAndCheckCancel(customBreakEvent);
|
||||
if (isCancelled) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
net.momirealms.craftengine.core.world.World world = new BukkitWorld(location.getWorld());
|
||||
// handle waterlogged blocks
|
||||
@SuppressWarnings("unchecked")
|
||||
Property<Boolean> waterloggedProperty = (Property<Boolean>) state.owner().value().getProperty("waterlogged");
|
||||
if (waterloggedProperty != null) {
|
||||
boolean waterlogged = state.get(waterloggedProperty);
|
||||
if (waterlogged) {
|
||||
location.getWorld().setBlockData(location, Material.WATER.createBlockData());
|
||||
}
|
||||
}
|
||||
// play sound
|
||||
Vec3d vec3d = new Vec3d(location.getBlockX() + 0.5, location.getBlockY() + 0.5, location.getBlockZ() + 0.5);
|
||||
world.playBlockSound(vec3d, state.sounds().breakSound(), 1f, 0.8f);
|
||||
|
||||
Player player = event.getPlayer();
|
||||
if (player.getGameMode() == GameMode.CREATIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
BukkitServerPlayer serverPlayer = 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;
|
||||
}
|
||||
// drop items
|
||||
ContextHolder.Builder builder = ContextHolder.builder();
|
||||
builder.withParameter(LootParameters.WORLD, world);
|
||||
builder.withParameter(LootParameters.LOCATION, vec3d);
|
||||
builder.withParameter(LootParameters.PLAYER, plugin.adapt(player));
|
||||
builder.withParameter(LootParameters.TOOL, itemInHand);
|
||||
for (Item<Object> item : state.getDrops(builder, world)) {
|
||||
world.dropItemNaturally(vec3d, item);
|
||||
}
|
||||
}
|
||||
} else if (ConfigManager.enableSoundSystem()) {
|
||||
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
|
||||
if (manager.isBlockSoundRemoved(ownerBlock)) {
|
||||
try {
|
||||
Object soundType = Reflections.field$BlockBehaviour$soundType.get(ownerBlock);
|
||||
Object breakSound = Reflections.field$SoundType$breakSound.get(soundType);
|
||||
block.getWorld().playSound(block.getLocation(), Reflections.field$SoundEvent$location.get(breakSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to get sound type", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onStep(GenericGameEvent event) {
|
||||
if (event.getEvent() != GameEvent.STEP) return;
|
||||
Entity entity = event.getEntity();
|
||||
if (!(entity instanceof Player player)) return;
|
||||
BlockPos pos = EntityUtils.getOnPos(player);
|
||||
Location playerLocation = player.getLocation();
|
||||
BlockData blockData = player.getWorld().getBlockData(pos.x(), pos.y(), pos.z());
|
||||
Object blockState = BlockStateUtils.blockDataToBlockState(blockData);
|
||||
int stateId = BlockStateUtils.blockStateToId(blockState);
|
||||
if (!BlockStateUtils.isVanillaBlock(stateId)) {
|
||||
ImmutableBlockState state = manager.getImmutableBlockStateUnsafe(stateId);
|
||||
player.playSound(playerLocation, state.sounds().stepSound().toString(), SoundCategory.BLOCKS, 0.15f, 1f);
|
||||
} else if (ConfigManager.enableSoundSystem()) {
|
||||
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
|
||||
if (manager.isBlockSoundRemoved(ownerBlock)) {
|
||||
try {
|
||||
Object soundType = Reflections.field$BlockBehaviour$soundType.get(ownerBlock);
|
||||
Object stepSound = Reflections.field$SoundType$stepSound.get(soundType);
|
||||
player.playSound(playerLocation, Reflections.field$SoundEvent$location.get(stepSound).toString(), SoundCategory.BLOCKS, 0.15f, 1f);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to get sound type", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onPistonRetract(BlockPistonRetractEvent event) {
|
||||
handlePistonEvent(event.getDirection(), event.getBlocks(), event.getBlock());
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onPistonExtend(BlockPistonExtendEvent event) {
|
||||
handlePistonEvent(event.getDirection(), event.getBlocks(), event.getBlock());
|
||||
}
|
||||
|
||||
private void handlePistonEvent(BlockFace face, List<Block> blocksList, Block piston) {
|
||||
int blocks = blocksList.size();
|
||||
net.momirealms.craftengine.core.world.World world = new BukkitWorld(piston.getWorld());
|
||||
for (int i = blocks - 1; i >= 0; --i) {
|
||||
Location oldLocation = blocksList.get(i).getLocation();
|
||||
BlockPos oldPos = new BlockPos(oldLocation.getBlockX(), oldLocation.getBlockY(), oldLocation.getBlockZ());
|
||||
Block block = blocksList.get(i);
|
||||
ImmutableBlockState blockState = manager.getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData()));
|
||||
if (blockState != null && blockState.pushReaction() == PushReaction.DESTROY) {
|
||||
// break actions
|
||||
ContextHolder.Builder builder = ContextHolder.builder();
|
||||
Vec3d vec3d = Vec3d.atCenterOf(oldPos);
|
||||
builder.withParameter(LootParameters.LOCATION, vec3d);
|
||||
builder.withParameter(LootParameters.WORLD, world);
|
||||
for (Item<Object> item : blockState.getDrops(builder, world)) {
|
||||
world.dropItemNaturally(vec3d, item);
|
||||
}
|
||||
world.playBlockSound(vec3d, blockState.sounds().breakSound(),1f, 0.8f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onEntityExplode(EntityExplodeEvent event) {
|
||||
handleExplodeEvent(event.blockList(), new BukkitWorld(event.getEntity().getWorld()), event.getYield());
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onBlockExplode(BlockExplodeEvent event) {
|
||||
handleExplodeEvent(event.blockList(), new BukkitWorld(event.getBlock().getWorld()), event.getYield());
|
||||
}
|
||||
|
||||
private void handleExplodeEvent(List<org.bukkit.block.Block> blocks, net.momirealms.craftengine.core.world.World world, float yield) {
|
||||
for (int i = blocks.size() - 1; i >= 0; i--) {
|
||||
Block block = blocks.get(i);
|
||||
Location location = block.getLocation();
|
||||
BlockPos blockPos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
||||
ImmutableBlockState state = manager.getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData()));
|
||||
if (state != null && !state.isEmpty()) {
|
||||
ContextHolder.Builder builder = ContextHolder.builder();
|
||||
Vec3d vec3d = Vec3d.atCenterOf(blockPos);
|
||||
builder.withParameter(LootParameters.LOCATION, vec3d);
|
||||
builder.withParameter(LootParameters.WORLD, world);
|
||||
if (yield < 1f) {
|
||||
builder.withParameter(LootParameters.EXPLOSION_RADIUS, 1.0f / yield);
|
||||
}
|
||||
for (Item<Object> item : state.getDrops(builder, world)) {
|
||||
world.dropItemNaturally(vec3d, item);
|
||||
}
|
||||
world.playBlockSound(vec3d, state.sounds().breakSound(), 1f, 0.8f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onBlockPhysics(BlockPhysicsEvent event) {
|
||||
if (!this.enableNoteBlockCheck) return;
|
||||
Block block = event.getBlock();
|
||||
// for vanilla blocks
|
||||
if (block.getBlockData() instanceof NoteBlock) {
|
||||
try {
|
||||
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 = Reflections.field$CraftWorld$ServerLevel.get(world);
|
||||
Object chunkSource = Reflections.field$ServerLevel$chunkSource.get(serverLevel);
|
||||
Object blockPos = Reflections.constructor$BlockPos.newInstance(location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
||||
Reflections.method$ServerChunkCache$blockChanged.invoke(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Is there a way to deceive the server?
|
||||
// @SuppressWarnings("unchecked")
|
||||
// @EventHandler(ignoreCancelled = true)
|
||||
// public void onDispenserWork(BlockDispenseEvent event) {
|
||||
// ItemStack itemStack = event.getItem();
|
||||
// Material type = itemStack.getType();
|
||||
// Block block = event.getBlock();
|
||||
// if (type == Material.BUCKET) {
|
||||
// if (block.getBlockData() instanceof Dispenser dispenser) {
|
||||
// Block against = block.getRelative(dispenser.getFacing());
|
||||
// ImmutableBlockState state = this.manager.getImmutableBlockState(BlockStateUtils.blockDataToId(against.getBlockData()));
|
||||
// if (state != null && !state.isEmpty()) {
|
||||
// Location location = against.getLocation();
|
||||
// CustomBlock customBlock = state.owner().value();
|
||||
// Property<Boolean> waterlogged = (Property<Boolean>) customBlock.getProperty("waterlogged");
|
||||
// if (waterlogged == null) return;
|
||||
// if (!state.get(waterlogged)) return;
|
||||
// ImmutableBlockState nextState = state.with(waterlogged, false);
|
||||
// CraftEngineBlocks.place(location, nextState, UpdateOption.UPDATE_ALL);
|
||||
// }
|
||||
// }
|
||||
// } else if (WATER_BUCKETS.contains(type)) {
|
||||
// if (block.getBlockData() instanceof Dispenser dispenser) {
|
||||
// Block against = block.getRelative(dispenser.getFacing());
|
||||
// ImmutableBlockState state = this.manager.getImmutableBlockState(BlockStateUtils.blockDataToId(against.getBlockData()));
|
||||
// if (state != null && !state.isEmpty()) {
|
||||
// Location location = against.getLocation();
|
||||
// CustomBlock customBlock = state.owner().value();
|
||||
// Property<Boolean> waterlogged = (Property<Boolean>) customBlock.getProperty("waterlogged");
|
||||
// if (waterlogged == null) return;
|
||||
// ImmutableBlockState nextState = state.with(waterlogged, true);
|
||||
// CraftEngineBlocks.place(location, nextState, UpdateOption.UPDATE_ALL);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,775 @@
|
||||
package net.momirealms.craftengine.bukkit.block;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
|
||||
import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.KeyUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.bukkit.util.RegistryUtils;
|
||||
import net.momirealms.craftengine.core.block.*;
|
||||
import net.momirealms.craftengine.core.block.properties.Properties;
|
||||
import net.momirealms.craftengine.core.block.properties.Property;
|
||||
import net.momirealms.craftengine.core.loot.LootTable;
|
||||
import net.momirealms.craftengine.core.pack.Pack;
|
||||
import net.momirealms.craftengine.core.pack.model.generator.ModelGeneration;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
|
||||
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
|
||||
import net.momirealms.craftengine.core.registry.Holder;
|
||||
import net.momirealms.craftengine.core.registry.WritableRegistry;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class BukkitBlockManager extends AbstractBlockManager {
|
||||
private static BukkitBlockManager instance;
|
||||
private final BukkitCraftEngine plugin;
|
||||
|
||||
// Used to store override information of json files
|
||||
private final Map<Key, Map<String, JsonElement>> blockStateOverrides = new HashMap<>();
|
||||
|
||||
// A temporary map used to detect whether the same block state corresponds to multiple models.
|
||||
private final Map<Integer, Key> tempRegistryIdConflictMap = new HashMap<>();
|
||||
// A temporary map that converts the custom block registered on the server to the vanilla block ID.
|
||||
private final Map<Integer, Integer> tempBlockAppearanceConvertor = new HashMap<>();
|
||||
|
||||
// The total amount of blocks registered
|
||||
private int customBlockCount;
|
||||
|
||||
// CraftEngine objects
|
||||
private final Map<Key, CustomBlock> id2CraftEngineBlocks = new HashMap<>();
|
||||
private final ImmutableBlockState[] stateId2ImmutableBlockStates;
|
||||
|
||||
// Minecraft objects
|
||||
// Cached new blocks $ holders
|
||||
private ImmutableMap<Key, Integer> internalId2StateId;
|
||||
private ImmutableMap<Integer, Object> stateId2BlockHolder;
|
||||
// This map is used to change the block states that are not necessarily needed into a certain block state
|
||||
private ImmutableMap<Integer, Integer> blockAppearanceMapper;
|
||||
// Used to automatically arrange block states for strings such as minecraft:note_block:0
|
||||
private ImmutableMap<Key, List<Integer>> blockAppearanceArranger;
|
||||
private ImmutableMap<Key, List<Integer>> realBlockArranger;
|
||||
// Record the amount of real blocks by block type
|
||||
private LinkedHashMap<Key, Integer> registeredRealBlockSlots;
|
||||
// A set of blocks that sounds have been removed
|
||||
private ImmutableSet<Object> affectedSoundBlocks;
|
||||
private ImmutableMap<Key, Key> soundMapper;
|
||||
// A list to record the order of registration
|
||||
private List<Key> blockRegisterOrder = new ArrayList<>();
|
||||
|
||||
// a reverted mapper
|
||||
private final Map<Integer, List<Integer>> appearanceToRealState = new HashMap<>();
|
||||
// Cached command suggestions
|
||||
private final List<Suggestion> cachedSuggestions = new ArrayList<>();
|
||||
// Event listeners
|
||||
private final BlockEventListener blockEventListener;
|
||||
private final FallingBlockRemoveListener fallingBlockRemoveListener;
|
||||
|
||||
public BukkitBlockManager(BukkitCraftEngine plugin) {
|
||||
super(plugin);
|
||||
this.plugin = plugin;
|
||||
this.initVanillaRegistry();
|
||||
this.loadMappingsAndAdditionalBlocks();
|
||||
if (plugin.hasMod() && plugin.requiresRestart()) {
|
||||
blockEventListener = null;
|
||||
fallingBlockRemoveListener = null;
|
||||
stateId2ImmutableBlockStates = null;
|
||||
return;
|
||||
}
|
||||
this.registerBlocks();
|
||||
this.registerEmptyBlock();
|
||||
this.initMirrorRegistry();
|
||||
boolean enableNoteBlocks = this.blockAppearanceArranger.containsKey(BlockKeys.NOTE_BLOCK);
|
||||
this.blockEventListener = new BlockEventListener(plugin, this, enableNoteBlocks);
|
||||
if (enableNoteBlocks) {
|
||||
this.recordVanillaNoteBlocks();
|
||||
}
|
||||
if (VersionHelper.isVersionNewerThan1_20_3()) {
|
||||
this.fallingBlockRemoveListener = new FallingBlockRemoveListener();
|
||||
} else this.fallingBlockRemoveListener = null;
|
||||
this.stateId2ImmutableBlockStates = new ImmutableBlockState[customBlockCount];
|
||||
Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.getDefaultState());
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static BukkitBlockManager instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayedInit() {
|
||||
Bukkit.getPluginManager().registerEvents(this.blockEventListener, plugin.bootstrap());
|
||||
if (this.fallingBlockRemoveListener != null) {
|
||||
Bukkit.getPluginManager().registerEvents(this.fallingBlockRemoveListener, plugin.bootstrap());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
super.clearModelsToGenerate();
|
||||
this.clearCache();
|
||||
this.appearanceToRealState.clear();
|
||||
this.id2CraftEngineBlocks.clear();
|
||||
this.cachedSuggestions.clear();
|
||||
this.blockStateOverrides.clear();
|
||||
if (EmptyBlock.INSTANCE != null)
|
||||
Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.getDefaultState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
this.unload();
|
||||
HandlerList.unregisterAll(this.blockEventListener);
|
||||
if (this.fallingBlockRemoveListener != null) HandlerList.unregisterAll(this.fallingBlockRemoveListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Key, Key> soundMapper() {
|
||||
return this.soundMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayedLoad() {
|
||||
initSuggestions();
|
||||
resetPacketConsumers();
|
||||
clearCache();
|
||||
}
|
||||
|
||||
private void clearCache() {
|
||||
this.tempRegistryIdConflictMap.clear();
|
||||
this.tempBlockAppearanceConvertor.clear();
|
||||
}
|
||||
|
||||
public void initFastAsyncWorldEditHook() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void initWorldEditHook() {
|
||||
for (Key newBlockId : this.blockRegisterOrder) {
|
||||
WorldEditHook.register(newBlockId);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Object getMinecraftBlockHolder(int stateId) {
|
||||
return stateId2BlockHolder.get(stateId);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ImmutableBlockState getImmutableBlockStateUnsafe(int stateId) {
|
||||
return stateId2ImmutableBlockStates[stateId - BlockStateUtils.vanillaStateSize()];
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ImmutableBlockState getImmutableBlockState(int stateId) {
|
||||
if (!BlockStateUtils.isVanillaBlock(stateId)) {
|
||||
return stateId2ImmutableBlockStates[stateId - BlockStateUtils.vanillaStateSize()];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Key, Map<String, JsonElement>> blockOverrides() {
|
||||
return blockStateOverrides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Key, CustomBlock> blocks() {
|
||||
return this.id2CraftEngineBlocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CustomBlock> getBlock(Key key) {
|
||||
return Optional.ofNullable(this.id2CraftEngineBlocks.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Suggestion> cachedSuggestions() {
|
||||
return this.cachedSuggestions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSuggestions() {
|
||||
this.cachedSuggestions.clear();
|
||||
Set<String> states = new HashSet<>();
|
||||
for (CustomBlock block : this.id2CraftEngineBlocks.values()) {
|
||||
states.add(block.id().toString());
|
||||
for (ImmutableBlockState state : block.variantProvider().states()) {
|
||||
states.add(state.toString());
|
||||
}
|
||||
}
|
||||
for (String state : states) {
|
||||
this.cachedSuggestions.add(Suggestion.suggestion(state));
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableMap<Key, List<Integer>> blockAppearanceArranger() {
|
||||
return blockAppearanceArranger;
|
||||
}
|
||||
|
||||
public ImmutableMap<Key, List<Integer>> realBlockArranger() {
|
||||
return realBlockArranger;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<Integer> appearanceToRealStates(int appearanceStateId) {
|
||||
return this.appearanceToRealState.get(appearanceStateId);
|
||||
}
|
||||
|
||||
private void initMirrorRegistry() {
|
||||
int size = RegistryUtils.currentBlockRegistrySize();
|
||||
PackedBlockState[] states = new PackedBlockState[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
states[i] = new PackedBlockState(BlockStateUtils.idToBlockState(i), i);
|
||||
}
|
||||
BlockRegistryMirror.init(states);
|
||||
}
|
||||
|
||||
private void registerEmptyBlock() {
|
||||
Holder.Reference<CustomBlock> holder = ((WritableRegistry<CustomBlock>) BuiltInRegistries.BLOCK).registerForHolder(new ResourceKey<>(BuiltInRegistries.BLOCK.key().location(), Key.withDefaultNamespace("empty")));
|
||||
EmptyBlock emptyBlock = new EmptyBlock(Key.withDefaultNamespace("empty"), holder);
|
||||
holder.bindValue(emptyBlock);
|
||||
}
|
||||
|
||||
private void resetPacketConsumers() {
|
||||
Map<Integer, Integer> finalMapping = new HashMap<>(this.blockAppearanceMapper);
|
||||
int stoneId = BlockStateUtils.blockStateToId(Reflections.instance$Blocks$STONE$defaultState);
|
||||
for (int custom : this.internalId2StateId.values()) {
|
||||
finalMapping.put(custom, stoneId);
|
||||
}
|
||||
finalMapping.putAll(this.tempBlockAppearanceConvertor);
|
||||
PacketConsumers.init(finalMapping, RegistryUtils.currentBlockRegistrySize());
|
||||
}
|
||||
|
||||
private void initVanillaRegistry() {
|
||||
int vanillaStateCount;
|
||||
if (plugin.hasMod()) {
|
||||
try {
|
||||
Class<?> modClass = ReflectionUtils.getClazz(CraftEngine.MOD_CLASS);
|
||||
Field amountField = ReflectionUtils.getDeclaredField(modClass, "vanillaRegistrySize");
|
||||
vanillaStateCount = (int) amountField.get(null);
|
||||
} catch (Exception e) {
|
||||
vanillaStateCount = RegistryUtils.currentBlockRegistrySize();
|
||||
plugin.logger().severe("Fatal error", e);
|
||||
}
|
||||
} else {
|
||||
vanillaStateCount = RegistryUtils.currentBlockRegistrySize();
|
||||
}
|
||||
plugin.logger().info("Vanilla block count: " + vanillaStateCount);
|
||||
BlockStateUtils.init(vanillaStateCount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void registerBlocks() {
|
||||
plugin.logger().info("Registering blocks. Please wait...");
|
||||
try {
|
||||
ImmutableMap.Builder<Key, Integer> builder1 = ImmutableMap.builder();
|
||||
ImmutableMap.Builder<Integer, Object> builder2 = ImmutableMap.builder();
|
||||
ImmutableMap.Builder<Key, List<Integer>> builder3 = ImmutableMap.builder();
|
||||
Set<Object> affectedSounds = new HashSet<>();
|
||||
Set<Object> affectedBlocks = new HashSet<>();
|
||||
List<Key> order = new ArrayList<>();
|
||||
|
||||
unfreezeRegistry();
|
||||
|
||||
int counter = 0;
|
||||
for (Map.Entry<Key, Integer> baseBlockAndItsCount : this.registeredRealBlockSlots.entrySet()) {
|
||||
counter = registerBlockVariants(baseBlockAndItsCount, counter, builder1, builder2, builder3, affectedSounds, order);
|
||||
}
|
||||
|
||||
freezeRegistry();
|
||||
this.plugin.logger().info("Registered block count: " + counter);
|
||||
this.customBlockCount = counter;
|
||||
this.internalId2StateId = builder1.build();
|
||||
this.stateId2BlockHolder = builder2.build();
|
||||
this.realBlockArranger = builder3.build();
|
||||
this.blockRegisterOrder = ImmutableList.copyOf(order);
|
||||
|
||||
for (Object block : (Iterable<Object>) Reflections.instance$BuiltInRegistries$BLOCK) {
|
||||
Object soundType = Reflections.field$BlockBehaviour$soundType.get(block);
|
||||
if (affectedSounds.contains(soundType)) {
|
||||
affectedBlocks.add(block);
|
||||
}
|
||||
}
|
||||
|
||||
this.affectedSoundBlocks = ImmutableSet.copyOf(affectedBlocks);
|
||||
|
||||
ImmutableMap.Builder<Key, Key> soundMapperBuilder = ImmutableMap.builder();
|
||||
for (Object soundType : affectedSounds) {
|
||||
for (Field field : List.of(Reflections.field$SoundType$placeSound, Reflections.field$SoundType$fallSound, Reflections.field$SoundType$hitSound, Reflections.field$SoundType$stepSound, Reflections.field$SoundType$breakSound)) {
|
||||
Object soundEvent = field.get(soundType);
|
||||
Key previousId = Key.of(Reflections.field$SoundEvent$location.get(soundEvent).toString());
|
||||
soundMapperBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value()));
|
||||
}
|
||||
}
|
||||
|
||||
this.soundMapper = soundMapperBuilder.build();
|
||||
} catch (Throwable e) {
|
||||
plugin.logger().warn("Failed to inject blocks.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
// read block settings
|
||||
BlockSettings settings = BlockSettings.fromMap(MiscUtils.castToMap(section.getOrDefault("settings", Map.of()), false));
|
||||
// read loot table
|
||||
LootTable<ItemStack> lootTable = LootTable.fromMap(MiscUtils.castToMap(section.getOrDefault("loot", Map.of()), false));
|
||||
// read states
|
||||
Map<String, Property<?>> properties;
|
||||
Map<String, Integer> appearances;
|
||||
Map<String, VariantState> variants;
|
||||
Map<String, Object> stateSection = MiscUtils.castToMap(section.get("state"), true);
|
||||
if (stateSection != null) {
|
||||
properties = Map.of();
|
||||
int internalId = MiscUtils.getAsInt(stateSection.getOrDefault("id", -1));
|
||||
if (PreConditions.runIfTrue(internalId < 0, () -> plugin.logger().warn(path, "No state id configured for block " + id))) return;
|
||||
Pair<Key, Integer> pair = parseAppearanceSection(path, stateSection, id);
|
||||
if (pair == null) return;
|
||||
appearances = Map.of("default", pair.right());
|
||||
Key internalBlockId = Key.of(CraftEngine.NAMESPACE, pair.left().value() + "_" + internalId);
|
||||
int internalBlockRegistryId = MiscUtils.getAsInt(this.internalId2StateId.getOrDefault(internalBlockId, -1));
|
||||
if (internalBlockRegistryId == -1) {
|
||||
plugin.logger().warn(path, "Failed to register " + id + " because id " + internalId + " is not a value between 0~" + (MiscUtils.getAsInt(this.registeredRealBlockSlots.get(pair.left()))-1) +
|
||||
". Consider editing additional-real-blocks.yml if the number of real block IDs is insufficient while there are still available appearances");
|
||||
return;
|
||||
}
|
||||
variants = Map.of("", new VariantState("default", settings, internalBlockRegistryId));
|
||||
} else {
|
||||
Map<String, Object> statesSection = MiscUtils.castToMap(section.get("states"), true);
|
||||
if (statesSection == null) {
|
||||
plugin.logger().warn(path, "No states configured for block " + id);
|
||||
return;
|
||||
}
|
||||
Map<String, Object> propertySection = MiscUtils.castToMap(statesSection.get("properties"), true);
|
||||
if (PreConditions.isNull(propertySection, () -> plugin.logger().warn(path, "No properties configured for block " + id))) return;
|
||||
properties = parseProperties(path, propertySection);
|
||||
Map<String, Object> appearancesSection = MiscUtils.castToMap(statesSection.get("appearances"), true);
|
||||
if (PreConditions.isNull(appearancesSection, () -> plugin.logger().warn(path, "No appearances configured for block " + id))) return;
|
||||
appearances = new HashMap<>();
|
||||
Map<String, Key> tempTypeMap = new HashMap<>();
|
||||
for (Map.Entry<String, Object> appearanceEntry : appearancesSection.entrySet()) {
|
||||
if (appearanceEntry.getValue() instanceof Map<?, ?> appearanceSection) {
|
||||
Pair<Key, Integer> pair = parseAppearanceSection(path, MiscUtils.castToMap(appearanceSection, false), id);
|
||||
if (pair == null) return;
|
||||
appearances.put(appearanceEntry.getKey(), pair.right());
|
||||
tempTypeMap.put(appearanceEntry.getKey(), pair.left());
|
||||
}
|
||||
}
|
||||
Map<String, Object> variantsSection = MiscUtils.castToMap(statesSection.get("variants"), true);
|
||||
if (PreConditions.isNull(variantsSection, () -> plugin.logger().warn(path, "No variants configured for block " + id))) return;
|
||||
variants = new HashMap<>();
|
||||
for (Map.Entry<String, Object> variantEntry : variantsSection.entrySet()) {
|
||||
if (variantEntry.getValue() instanceof Map<?, ?> variantSection0) {
|
||||
Map<String, Object> variantSection = MiscUtils.castToMap(variantSection0, false);
|
||||
String variantName = variantEntry.getKey();
|
||||
String appearance = (String) variantSection.get("appearance");
|
||||
if (appearance == null) {
|
||||
plugin.logger().warn(path, "No appearance configured for variant " + variantName);
|
||||
return;
|
||||
}
|
||||
if (!appearances.containsKey(appearance)) {
|
||||
plugin.logger().warn(path, appearance + " is not a valid appearance for block " + id);
|
||||
return;
|
||||
}
|
||||
int internalId = MiscUtils.getAsInt(variantSection.getOrDefault("id", -1));
|
||||
Key baseBlock = tempTypeMap.get(appearance);
|
||||
Key internalBlockId = Key.of(CraftEngine.NAMESPACE, baseBlock.value() + "_" + internalId);
|
||||
int internalBlockRegistryId = MiscUtils.getAsInt(this.internalId2StateId.getOrDefault(internalBlockId, -1));
|
||||
if (internalBlockRegistryId == -1) {
|
||||
plugin.logger().warn(path, "Failed to register " + id + " because id " + internalId + " is not a value between 0~" + (MiscUtils.getAsInt(this.registeredRealBlockSlots.getOrDefault(baseBlock, 1))-1) +
|
||||
". Consider editing additional-real-blocks.yml if the number of real block IDs is insufficient while there are still available appearances");
|
||||
return;
|
||||
}
|
||||
Map<String, Object> anotherSetting = MiscUtils.castToMap(variantSection.get("settings"), true);
|
||||
variants.put(variantName, new VariantState(appearance, anotherSetting == null ? settings : BlockSettings.ofFullCopy(settings, anotherSetting), internalBlockRegistryId));
|
||||
}
|
||||
}
|
||||
}
|
||||
// create or get block holder
|
||||
Holder.Reference<CustomBlock> holder = BuiltInRegistries.BLOCK.get(id).orElseGet(() ->
|
||||
((WritableRegistry<CustomBlock>) BuiltInRegistries.BLOCK).registerForHolder(new ResourceKey<>(BuiltInRegistries.BLOCK.key().location(), id)));
|
||||
// create block
|
||||
Map<String, Object> behaviorSection = MiscUtils.castToMap(section.getOrDefault("behavior", Map.of()), false);
|
||||
|
||||
CustomBlock block = new BukkitCustomBlock(id, holder, properties, appearances, variants, settings, behaviorSection, lootTable);
|
||||
|
||||
// bind appearance
|
||||
bindAppearance(block);
|
||||
this.id2CraftEngineBlocks.put(id, block);
|
||||
}
|
||||
|
||||
private void bindAppearance(CustomBlock block) {
|
||||
for (ImmutableBlockState state : block.variantProvider().states()) {
|
||||
ImmutableBlockState previous = this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()];
|
||||
if (previous != null && !previous.isEmpty()) {
|
||||
this.plugin.logger().severe("[Fatal] Failed to bind real block state for " + state + ": the state is already occupied by " + previous);
|
||||
continue;
|
||||
}
|
||||
this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state;
|
||||
this.tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId());
|
||||
this.appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new ArrayList<>()).add(state.customBlockState().registryId());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Property<?>> parseProperties(Path path, Map<String, Object> propertiesSection) {
|
||||
Map<String, Property<?>> properties = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : propertiesSection.entrySet()) {
|
||||
if (entry.getValue() instanceof Map<?, ?> params) {
|
||||
Property<?> property = Properties.fromMap(entry.getKey(), MiscUtils.castToMap(params, false));
|
||||
properties.put(entry.getKey(), property);
|
||||
} else {
|
||||
this.plugin.logger().warn(path, "Invalid property format: " + entry.getKey());
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Pair<Key, Integer> parseAppearanceSection(Path path, Map<String, Object> section, Key id) {
|
||||
String vanillaStateString = (String) section.get("state");
|
||||
if (PreConditions.isNull(vanillaStateString,
|
||||
() -> this.plugin.logger().warn(path, "No block state found for: " + id))) return null;
|
||||
int vanillaStateRegistryId;
|
||||
try {
|
||||
vanillaStateRegistryId = parseVanillaStateRegistryId(vanillaStateString);
|
||||
} catch (BlockStateArrangeException e) {
|
||||
this.plugin.logger().warn(path, "Failed to load " + id + " - " + e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
Key ifAny = this.tempRegistryIdConflictMap.get(vanillaStateRegistryId);
|
||||
if (ifAny != null && !ifAny.equals(id)) {
|
||||
this.plugin.logger().warn(path, "Can't use " + BlockStateUtils.idToBlockState(vanillaStateRegistryId) + " as the base block for " + id + " because the state has already been used by " + ifAny);
|
||||
return null;
|
||||
}
|
||||
Object models = section.getOrDefault("models", section.get("model"));
|
||||
List<JsonObject> variants = new ArrayList<>();
|
||||
if (models instanceof Map<?, ?> singleModelSection) {
|
||||
loadVariantModel(variants, MiscUtils.castToMap(singleModelSection, false));
|
||||
} else if (models instanceof List<?> modelList) {
|
||||
for (Object model : modelList) {
|
||||
if (model instanceof Map<?,?> singleModelMap) {
|
||||
loadVariantModel(variants, MiscUtils.castToMap(singleModelMap, false));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.plugin.logger().warn(path, "No model set for " + id);
|
||||
return null;
|
||||
}
|
||||
if (variants.isEmpty()) return null;
|
||||
this.tempRegistryIdConflictMap.put(vanillaStateRegistryId, id);
|
||||
String blockState = BlockStateUtils.idToBlockState(vanillaStateRegistryId).toString();
|
||||
Key block = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}')));
|
||||
String propertyData = blockState.substring(blockState.indexOf('[') + 1, blockState.lastIndexOf(']'));
|
||||
Map<String, JsonElement> paths = this.blockStateOverrides.computeIfAbsent(block, k -> new HashMap<>());
|
||||
if (variants.size() == 1) {
|
||||
paths.put(propertyData, variants.get(0));
|
||||
} else {
|
||||
JsonArray array = new JsonArray();
|
||||
for (JsonObject object : variants) {
|
||||
array.add(object);
|
||||
}
|
||||
paths.put(propertyData, array);
|
||||
}
|
||||
return Pair.of(block, vanillaStateRegistryId);
|
||||
}
|
||||
|
||||
private void loadVariantModel(List<JsonObject> variants, Map<String, Object> singleModelMap) {
|
||||
JsonObject json = new JsonObject();
|
||||
String modelPath = (String) singleModelMap.get("path");
|
||||
json.addProperty("model", modelPath);
|
||||
if (singleModelMap.containsKey("x")) json.addProperty("x", MiscUtils.getAsInt(singleModelMap.get("x")));
|
||||
if (singleModelMap.containsKey("y")) json.addProperty("y", MiscUtils.getAsInt(singleModelMap.get("y")));
|
||||
if (singleModelMap.containsKey("uvlock")) json.addProperty("uvlock", (boolean) singleModelMap.get("uvlock"));
|
||||
if (singleModelMap.containsKey("weight")) json.addProperty("weight", MiscUtils.getAsInt(singleModelMap.get("weight")));
|
||||
Map<String, Object> generationMap = MiscUtils.castToMap(singleModelMap.get("generation"), true);
|
||||
if (generationMap != null) {
|
||||
prepareModelGeneration(new ModelGeneration(Key.of(modelPath), generationMap));
|
||||
}
|
||||
variants.add(json);
|
||||
}
|
||||
|
||||
private int parseVanillaStateRegistryId(String blockState) throws BlockStateArrangeException {
|
||||
String[] split = blockState.split(":", 3);
|
||||
PreConditions.runIfTrue(split.length >= 4, () -> {
|
||||
throw new BlockStateArrangeException("Invalid vanilla block state: " + blockState);
|
||||
});
|
||||
int registryId;
|
||||
// minecraft:xxx:0
|
||||
// xxx:0
|
||||
String stateOrId = split[split.length - 1];
|
||||
boolean isId = !stateOrId.contains("[") && !stateOrId.contains("]");
|
||||
if (isId) {
|
||||
if (split.length == 1) {
|
||||
throw new BlockStateArrangeException("Invalid vanilla block state: " + blockState);
|
||||
}
|
||||
Key block = split.length == 2 ? Key.of(split[0]) : Key.of(split[0], split[1]);
|
||||
try {
|
||||
int id = split.length == 2 ? Integer.parseInt(split[1]) : Integer.parseInt(split[2]);
|
||||
PreConditions.runIfTrue(id < 0, () -> {
|
||||
throw new BlockStateArrangeException("Invalid block state: " + blockState);
|
||||
});
|
||||
List<Integer> arranger = this.blockAppearanceArranger.get(block);
|
||||
if (arranger == null) {
|
||||
throw new BlockStateArrangeException("No freed block state is available for block " + block);
|
||||
}
|
||||
if (id >= arranger.size()) {
|
||||
throw new BlockStateArrangeException(blockState + " is not a valid block state because " + id + " is outside of the range (0~" + (arranger.size() - 1) + ")");
|
||||
}
|
||||
registryId = arranger.get(id);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BlockStateArrangeException("Invalid block state: " + blockState);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
BlockData blockData = Bukkit.createBlockData(blockState);
|
||||
registryId = BlockStateUtils.blockDataToId(blockData);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BlockStateArrangeException("Invalid block state: " + blockState);
|
||||
}
|
||||
}
|
||||
return registryId;
|
||||
}
|
||||
|
||||
private void loadMappingsAndAdditionalBlocks() {
|
||||
this.plugin.logger().info("Loading mappings.yml.");
|
||||
File mappingFile = new File(plugin.dataFolderFile(), "mappings.yml");
|
||||
YamlDocument mappings = ConfigManager.instance().loadOrCreateYamlData("mappings.yml");
|
||||
Map<String, String> blockStateMappings = loadBlockStateMappings(mappings);
|
||||
this.validateBlockStateMappings(mappingFile, blockStateMappings);
|
||||
Map<Integer, String> stateMap = new HashMap<>();
|
||||
Map<Key, Integer> blockTypeCounter = new LinkedHashMap<>();
|
||||
Map<Integer, Integer> appearanceMapper = new HashMap<>();
|
||||
Map<Key, List<Integer>> appearanceArranger = new HashMap<>();
|
||||
for (Map.Entry<String, String> entry : blockStateMappings.entrySet()) {
|
||||
this.processBlockStateMapping(mappingFile, entry, stateMap, blockTypeCounter, appearanceMapper, appearanceArranger);
|
||||
}
|
||||
this.blockAppearanceMapper = ImmutableMap.copyOf(appearanceMapper);
|
||||
this.blockAppearanceArranger = ImmutableMap.copyOf(appearanceArranger);
|
||||
this.plugin.logger().info("Freed " + this.blockAppearanceMapper.size() + " block state appearances.");
|
||||
YamlDocument additionalYaml = ConfigManager.instance().loadOrCreateYamlData("additional-real-blocks.yml");
|
||||
this.registeredRealBlockSlots = this.buildRegisteredRealBlockSlots(blockTypeCounter, additionalYaml);
|
||||
}
|
||||
|
||||
private void recordVanillaNoteBlocks() {
|
||||
try {
|
||||
Object resourceLocation = Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, BlockKeys.NOTE_BLOCK.namespace(), BlockKeys.NOTE_BLOCK.value());
|
||||
Object block = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$BLOCK, resourceLocation);
|
||||
Object stateDefinition = Reflections.field$Block$StateDefinition.get(block);
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableList<Object> states = (ImmutableList<Object>) Reflections.field$StateDefinition$states.get(stateDefinition);
|
||||
for (Object state : states) {
|
||||
BlockStateUtils.CLIENT_SIDE_NOTE_BLOCKS.put(state, new Object());
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to init vanilla note block", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Key replaceSoundIfExist(Key id) {
|
||||
return this.soundMapper.get(id);
|
||||
}
|
||||
|
||||
public boolean isBlockSoundRemoved(Object block) {
|
||||
return this.affectedSoundBlocks.contains(block);
|
||||
}
|
||||
|
||||
private Map<String, String> loadBlockStateMappings(YamlDocument mappings) {
|
||||
Map<String, String> blockStateMappings = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Object> entry : mappings.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof String afterValue) {
|
||||
blockStateMappings.put(entry.getKey(), afterValue);
|
||||
}
|
||||
}
|
||||
return blockStateMappings;
|
||||
}
|
||||
|
||||
private void validateBlockStateMappings(File mappingFile, Map<String, String> blockStateMappings) {
|
||||
Map<String, String> temp = new HashMap<>(blockStateMappings);
|
||||
for (Map.Entry<String, String> entry : temp.entrySet()) {
|
||||
String state = entry.getValue();
|
||||
if (blockStateMappings.containsKey(state)) {
|
||||
String after = blockStateMappings.remove(state);
|
||||
plugin.logger().warn(mappingFile, "'" + state + ": " + after + "' is invalid because '" + state + "' has already been used as a base block.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processBlockStateMapping(File mappingFile,
|
||||
Map.Entry<String, String> entry,
|
||||
Map<Integer, String> stateMap,
|
||||
Map<Key, Integer> counter,
|
||||
Map<Integer, Integer> mapper,
|
||||
Map<Key, List<Integer>> arranger) {
|
||||
BlockData before = createBlockData(mappingFile, entry.getKey());
|
||||
BlockData after = createBlockData(mappingFile, entry.getValue());
|
||||
if (before == null || after == null) return;
|
||||
|
||||
int beforeId = BlockStateUtils.blockDataToId(before);
|
||||
int afterId = BlockStateUtils.blockDataToId(after);
|
||||
|
||||
Integer previous = mapper.put(beforeId, afterId);
|
||||
if (previous == null) {
|
||||
Key key = KeyUtils.namespacedKey2Key(before.getMaterial().getKey());
|
||||
counter.compute(key, (k, count) -> count == null ? 1 : count + 1);
|
||||
stateMap.put(beforeId, entry.getKey());
|
||||
stateMap.put(afterId, entry.getValue());
|
||||
arranger.computeIfAbsent(key, (k) -> new ArrayList<>()).add(beforeId);
|
||||
} else {
|
||||
String previousState = stateMap.get(previous);
|
||||
plugin.logger().warn(mappingFile, "Duplicate entry: '" + previousState + "' equals '" + entry.getKey() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private BlockData createBlockData(File mappingFile, String blockState) {
|
||||
try {
|
||||
return Bukkit.createBlockData(blockState);
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.logger().warn(mappingFile, "'" + blockState + "' is not a valid block state.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedHashMap<Key, Integer> buildRegisteredRealBlockSlots(Map<Key, Integer> counter, YamlDocument additionalYaml) {
|
||||
LinkedHashMap<Key, Integer> map = new LinkedHashMap<>();
|
||||
for (Map.Entry<Key, Integer> entry : counter.entrySet()) {
|
||||
String id = entry.getKey().toString();
|
||||
int additionalStates = additionalYaml.getInt(id, 0);
|
||||
int internalIds = entry.getValue() + additionalStates;
|
||||
plugin.logger().info("Loaded " + id + " with " + entry.getValue() + " appearances and " + internalIds + " real block states");
|
||||
map.put(entry.getKey(), internalIds);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private void unfreezeRegistry() throws IllegalAccessException {
|
||||
Reflections.field$MappedRegistry$frozen.set(Reflections.instance$BuiltInRegistries$BLOCK, false);
|
||||
Reflections.field$MappedRegistry$unregisteredIntrusiveHolders.set(Reflections.instance$BuiltInRegistries$BLOCK, new IdentityHashMap<>());
|
||||
}
|
||||
|
||||
private void freezeRegistry() throws IllegalAccessException {
|
||||
Reflections.field$MappedRegistry$frozen.set(Reflections.instance$BuiltInRegistries$BLOCK, true);
|
||||
}
|
||||
|
||||
private int registerBlockVariants(Map.Entry<Key, Integer> blockWithCount,
|
||||
int counter,
|
||||
ImmutableMap.Builder<Key, Integer> builder1,
|
||||
ImmutableMap.Builder<Integer, Object> builder2,
|
||||
ImmutableMap.Builder<Key, List<Integer>> builder3,
|
||||
Set<Object> affectSoundTypes,
|
||||
List<Key> order) throws Exception {
|
||||
Key clientSideBlockType = blockWithCount.getKey();
|
||||
boolean isNoteBlock = clientSideBlockType.equals(BlockKeys.NOTE_BLOCK);
|
||||
Object clientSideBlock = getBlockFromRegistry(createResourceLocation(clientSideBlockType));
|
||||
int amount = blockWithCount.getValue();
|
||||
|
||||
List<Integer> stateIds = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < amount; i++) {
|
||||
Key realBlockKey = createRealBlockKey(clientSideBlockType, i);
|
||||
Object blockProperties = createBlockProperties(realBlockKey);
|
||||
|
||||
Object newRealBlock;
|
||||
Object newBlockState;
|
||||
Object blockHolder;
|
||||
Object resourceLocation = createResourceLocation(realBlockKey);
|
||||
|
||||
if (plugin.hasMod()) {
|
||||
newRealBlock = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$BLOCK, resourceLocation);
|
||||
newBlockState = getOnlyBlockState(newRealBlock);
|
||||
@SuppressWarnings("unchecked")
|
||||
Optional<Object> optionalHolder = (Optional<Object>) Reflections.method$Registry$getHolder0.invoke(Reflections.instance$BuiltInRegistries$BLOCK, resourceLocation);
|
||||
blockHolder = optionalHolder.get();
|
||||
} else {
|
||||
try {
|
||||
newRealBlock = BukkitInjector.generateBlock(clientSideBlockType, clientSideBlock, blockProperties);
|
||||
} catch (Throwable throwable) {
|
||||
plugin.logger().warn("Failed to generate dynamic block class", throwable);
|
||||
continue;
|
||||
}
|
||||
|
||||
blockHolder = Reflections.method$Registry$registerForHolder.invoke(null, Reflections.instance$BuiltInRegistries$BLOCK, resourceLocation, newRealBlock);
|
||||
Reflections.method$Holder$Reference$bindValue.invoke(blockHolder, newRealBlock);
|
||||
Reflections.field$Holder$Reference$tags.set(blockHolder, Set.of());
|
||||
|
||||
newBlockState = getOnlyBlockState(newRealBlock);
|
||||
Reflections.method$IdMapper$add.invoke(Reflections.instance$BLOCK_STATE_REGISTRY, newBlockState);
|
||||
}
|
||||
|
||||
if (isNoteBlock) {
|
||||
BlockStateUtils.CLIENT_SIDE_NOTE_BLOCKS.put(newBlockState, new Object());
|
||||
}
|
||||
|
||||
int stateId = BlockStateUtils.vanillaStateSize() + counter;
|
||||
|
||||
builder1.put(realBlockKey, stateId);
|
||||
builder2.put(stateId, blockHolder);
|
||||
stateIds.add(stateId);
|
||||
|
||||
deceiveBukkit(newRealBlock, clientSideBlockType);
|
||||
order.add(realBlockKey);
|
||||
counter++;
|
||||
}
|
||||
|
||||
builder3.put(clientSideBlockType, stateIds);
|
||||
Object soundType = Reflections.field$BlockBehaviour$soundType.get(clientSideBlock);
|
||||
affectSoundTypes.add(soundType);
|
||||
return counter;
|
||||
}
|
||||
|
||||
private Object createResourceLocation(Key key) throws Exception {
|
||||
return Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, key.namespace(), key.value());
|
||||
}
|
||||
|
||||
private Object getBlockFromRegistry(Object resourceLocation) throws Exception {
|
||||
return Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$BLOCK, resourceLocation);
|
||||
}
|
||||
|
||||
private Key createRealBlockKey(Key replacedBlock, int index) {
|
||||
return Key.of(CraftEngine.NAMESPACE, replacedBlock.value() + "_" + index);
|
||||
}
|
||||
|
||||
private Object createBlockProperties(Key realBlockKey) throws Exception {
|
||||
Object blockProperties = Reflections.method$BlockBehaviour$Properties$of.invoke(null);
|
||||
Object realBlockResourceLocation = createResourceLocation(realBlockKey);
|
||||
Object realBlockResourceKey = Reflections.method$ResourceKey$create.invoke(null, Reflections.instance$Registries$BLOCK, realBlockResourceLocation);
|
||||
if (Reflections.field$BlockBehaviour$Properties$id != null) {
|
||||
Reflections.field$BlockBehaviour$Properties$id.set(blockProperties, realBlockResourceKey);
|
||||
}
|
||||
return blockProperties;
|
||||
}
|
||||
|
||||
private Object getOnlyBlockState(Object newBlock) throws IllegalAccessException {
|
||||
Object stateDefinition = Reflections.field$Block$StateDefinition.get(newBlock);
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableList<Object> states = (ImmutableList<Object>) Reflections.field$StateDefinition$states.get(stateDefinition);
|
||||
return states.get(0);
|
||||
}
|
||||
|
||||
private void deceiveBukkit(Object newBlock, Key replacedBlock) throws IllegalAccessException {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Object, Material> magicMap = (Map<Object, Material>) Reflections.field$CraftMagicNumbers$BLOCK_MATERIAL.get(null);
|
||||
magicMap.put(newBlock, org.bukkit.Registry.MATERIAL.get(Objects.requireNonNull(NamespacedKey.fromString(replacedBlock.toString()))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package net.momirealms.craftengine.bukkit.block;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.shared.block.BlockShape;
|
||||
|
||||
public class BukkitBlockShape implements BlockShape {
|
||||
private final Object rawBlockState;
|
||||
|
||||
public BukkitBlockShape(Object rawBlockState) {
|
||||
this.rawBlockState = rawBlockState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getShape(Object thisObj, Object[] args) throws Exception {
|
||||
return Reflections.method$BlockBehaviour$getShape.invoke(Reflections.field$StateHolder$owner.get(this.rawBlockState), this.rawBlockState, args[1], args[2], args[3]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package net.momirealms.craftengine.bukkit.block;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.bukkit.util.SoundUtils;
|
||||
import net.momirealms.craftengine.core.block.*;
|
||||
import net.momirealms.craftengine.core.block.properties.Property;
|
||||
import net.momirealms.craftengine.core.loot.LootTable;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.registry.Holder;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.Tristate;
|
||||
import net.momirealms.craftengine.shared.ObjectHolder;
|
||||
import net.momirealms.craftengine.shared.block.BlockBehavior;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class BukkitCustomBlock extends CustomBlock {
|
||||
|
||||
public BukkitCustomBlock(
|
||||
Key id,
|
||||
Holder.Reference<CustomBlock> holder,
|
||||
Map<String, Property<?>> properties,
|
||||
Map<String, Integer> appearances,
|
||||
Map<String, VariantState> variantMapper,
|
||||
BlockSettings settings,
|
||||
Map<String, Object> behaviorSettings,
|
||||
@Nullable LootTable<?> lootTable
|
||||
) {
|
||||
super(id, holder, properties, appearances, variantMapper, settings, behaviorSettings, lootTable);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
@Override
|
||||
public LootTable<ItemStack> lootTable() {
|
||||
return (LootTable<ItemStack>) super.lootTable();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyPlatformSettings() {
|
||||
try {
|
||||
for (ImmutableBlockState state : variantProvider().states()) {
|
||||
if (state.vanillaBlockState() == null) {
|
||||
CraftEngine.instance().logger().warn("Could not find vanilla block state for " + state + ". This might cause errors!");
|
||||
continue;
|
||||
} else if (state.customBlockState() == null) {
|
||||
CraftEngine.instance().logger().warn("Could not find custom block state for " + state + ". This might cause errors!");
|
||||
continue;
|
||||
}
|
||||
Object mcBlockState = state.customBlockState().handle();
|
||||
BlockSettings settings = state.settings();
|
||||
// set block state properties
|
||||
BlockStateUtils.setInstrument(mcBlockState, settings.instrument());
|
||||
BlockStateUtils.setMapColor(mcBlockState, settings.mapColor());
|
||||
BlockStateUtils.setLightEmission(mcBlockState, settings.luminance());
|
||||
BlockStateUtils.setBurnable(mcBlockState, settings.burnable());
|
||||
BlockStateUtils.setHardness(mcBlockState, settings.hardness());
|
||||
BlockStateUtils.setPushReaction(mcBlockState, settings.pushReaction());
|
||||
BlockStateUtils.setReplaceable(mcBlockState, settings.replaceable());
|
||||
BlockStateUtils.setCanOcclude(mcBlockState, settings.canOcclude());
|
||||
if (settings.isRedstoneConductor() == Tristate.TRUE) {
|
||||
BlockStateUtils.setIsRedstoneConductor(mcBlockState, StatePredicate.alwaysTrue());
|
||||
} else if (settings.isRedstoneConductor() == Tristate.FALSE) {
|
||||
BlockStateUtils.setIsRedstoneConductor(mcBlockState, StatePredicate.alwaysFalse());
|
||||
}
|
||||
if (settings.isSuffocating() == Tristate.TRUE) {
|
||||
BlockStateUtils.setIsSuffocating(mcBlockState, StatePredicate.alwaysTrue());
|
||||
} else if (settings.isSuffocating() == Tristate.FALSE) {
|
||||
BlockStateUtils.setIsSuffocating(mcBlockState, StatePredicate.alwaysFalse());
|
||||
}
|
||||
if (settings.isViewBlocking() == Tristate.TRUE) {
|
||||
BlockStateUtils.setIsViewBlocking(mcBlockState, StatePredicate.alwaysTrue());
|
||||
} else if (settings.isViewBlocking() == Tristate.FALSE) {
|
||||
BlockStateUtils.setIsViewBlocking(mcBlockState, StatePredicate.alwaysFalse());
|
||||
} else {
|
||||
if (settings.isSuffocating() == Tristate.TRUE) {
|
||||
BlockStateUtils.setIsViewBlocking(mcBlockState, StatePredicate.alwaysTrue());
|
||||
} else if (settings.isSuffocating() == Tristate.FALSE) {
|
||||
BlockStateUtils.setIsViewBlocking(mcBlockState, StatePredicate.alwaysFalse());
|
||||
}
|
||||
}
|
||||
// set parent block properties
|
||||
Object mcBlock = BlockStateUtils.getBlockOwner(mcBlockState);
|
||||
// bind shape
|
||||
Field shapeField = mcBlock.getClass().getField("shapeHolder");
|
||||
@SuppressWarnings("unchecked")
|
||||
ObjectHolder<BukkitBlockShape> shapeHolder = (ObjectHolder<BukkitBlockShape>) shapeField.get(mcBlock);
|
||||
shapeHolder.bindValue(new BukkitBlockShape(state.vanillaBlockState().handle()));
|
||||
// bind behavior
|
||||
Field behaviorField = mcBlock.getClass().getField("behaviorHolder");
|
||||
@SuppressWarnings("unchecked")
|
||||
ObjectHolder<BlockBehavior> behaviorHolder = (ObjectHolder<BlockBehavior>) behaviorField.get(mcBlock);
|
||||
behaviorHolder.bindValue(super.behavior);
|
||||
// set block side properties
|
||||
Reflections.field$BlockBehaviour$explosionResistance.set(mcBlock, settings.resistance());
|
||||
Reflections.field$BlockBehaviour$soundType.set(mcBlock, SoundUtils.toSoundType(settings.sounds()));
|
||||
// init cache
|
||||
Reflections.method$BlockStateBase$initCache.invoke(mcBlockState);
|
||||
// set random tick later
|
||||
BlockStateUtils.setIsRandomlyTicking(mcBlockState, settings.isRandomlyTicking());
|
||||
// bind tags
|
||||
Object holder = BukkitCraftEngine.instance().blockManager().getMinecraftBlockHolder(state.customBlockState().registryId());
|
||||
Set<Object> tags = new HashSet<>();
|
||||
for (Key tag : settings.tags()) {
|
||||
tags.add(Reflections.method$TagKey$create.invoke(null, Reflections.instance$Registries$BLOCK, Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, tag.namespace(), tag.value())));
|
||||
}
|
||||
Reflections.field$Holder$Reference$tags.set(holder, tags);
|
||||
// set burning properties
|
||||
if (settings.burnable()) {
|
||||
Reflections.method$FireBlock$setFlammable.invoke(Reflections.instance$Blocks$FIRE, mcBlock, settings.burnChance(), settings.fireSpreadChance());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to init block settings", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package net.momirealms.craftengine.bukkit.block;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.util.context.ContextHolder;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import org.bukkit.entity.FallingBlock;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public class FallingBlockRemoveListener implements Listener {
|
||||
|
||||
/*
|
||||
* This is not an event that would be removed
|
||||
* Paper mistakenly marked it as deprecated in 1.20.4
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@EventHandler
|
||||
public void onFallingBlockBreak(org.bukkit.event.entity.EntityRemoveEvent event) {
|
||||
if (event.getCause() == org.bukkit.event.entity.EntityRemoveEvent.Cause.DROP && event.getEntity() instanceof FallingBlock fallingBlock) {
|
||||
try {
|
||||
Object fallingBlockEntity = Reflections.field$CraftEntity$entity.get(fallingBlock);
|
||||
boolean cancelDrop = (boolean) Reflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity);
|
||||
if (cancelDrop) return;
|
||||
Object blockState = Reflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity);
|
||||
int stateId = BlockStateUtils.blockStateToId(blockState);
|
||||
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(stateId);
|
||||
if (immutableBlockState == null || immutableBlockState.isEmpty()) return;
|
||||
ContextHolder.Builder builder = ContextHolder.builder();
|
||||
builder.withParameter(LootParameters.FALLING_BLOCK, true);
|
||||
double x = Reflections.field$Entity$xo.getDouble(fallingBlockEntity);
|
||||
double y = Reflections.field$Entity$yo.getDouble(fallingBlockEntity);
|
||||
double z = Reflections.field$Entity$zo.getDouble(fallingBlockEntity);
|
||||
Vec3d vec3d = new Vec3d(x, y, z);
|
||||
net.momirealms.craftengine.core.world.World world = new BukkitWorld(fallingBlock.getWorld());
|
||||
builder.withParameter(LootParameters.LOCATION, vec3d);
|
||||
builder.withParameter(LootParameters.WORLD, world);
|
||||
for (Item<Object> item : immutableBlockState.getDrops(builder, world)) {
|
||||
world.dropItemNaturally(vec3d, item);
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle EntityRemoveEvent", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package net.momirealms.craftengine.bukkit.block;
|
||||
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
|
||||
public class WorldEditHook {
|
||||
|
||||
public static void register(Key id) {
|
||||
BlockType.REGISTRY.register(id.toString(), new BlockType(id.toString(), blockState -> blockState));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.momirealms.craftengine.bukkit.block.behavior;
|
||||
|
||||
import net.momirealms.craftengine.core.block.behavior.BlockBehaviors;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.shared.block.EmptyBlockBehavior;
|
||||
|
||||
public class BukkitBlockBehaviors extends BlockBehaviors {
|
||||
public static final Key EMPTY = Key.from("craftengine:empty");
|
||||
public static final Key BUSH_BLOCK = Key.from("craftengine:bush_block");
|
||||
public static final Key FALLING_BLOCK = Key.from("craftengine:falling_block");
|
||||
public static final Key LEAVES_BLOCK = Key.from("craftengine:leaves_block");
|
||||
public static final Key STRIPPABLE_BLOCK = Key.from("craftengine:strippable_block");
|
||||
public static final Key SAPLING_BLOCK = Key.from("craftengine:sapling_block");
|
||||
|
||||
public static void init() {
|
||||
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
|
||||
register(FALLING_BLOCK, FallingBlockBehavior.FACTORY);
|
||||
register(BUSH_BLOCK, BushBlockBehavior.FACTORY);
|
||||
register(LEAVES_BLOCK, LeavesBlockBehavior.FACTORY);
|
||||
register(STRIPPABLE_BLOCK, StrippableBlockBehavior.FACTORY);
|
||||
register(SAPLING_BLOCK, SaplingBlockBehavior.FACTORY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package net.momirealms.craftengine.bukkit.block.behavior;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockTags;
|
||||
import net.momirealms.craftengine.bukkit.util.LocationUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
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.shared.block.BlockBehavior;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class BushBlockBehavior extends BlockBehavior {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
protected static final Object DIRT_TAG = BlockTags.getOrCreate(Key.of("minecraft", "dirt"));
|
||||
protected static final Object FARMLAND = BlockTags.getOrCreate(Key.of("minecraft", "farmland"));
|
||||
public static final BushBlockBehavior INSTANCE = new BushBlockBehavior(List.of(DIRT_TAG, FARMLAND));
|
||||
protected final List<Object> tagsCanSurviveOn;
|
||||
|
||||
public BushBlockBehavior(List<Object> tagsCanSurviveOn) {
|
||||
this.tagsCanSurviveOn = tagsCanSurviveOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlace(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object world = args[1];
|
||||
Object blockPos = args[2];
|
||||
Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object level;
|
||||
Object blockPos;
|
||||
Object state = args[0];
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
level = args[1];
|
||||
blockPos = args[3];
|
||||
} else {
|
||||
level = args[3];
|
||||
blockPos = args[4];
|
||||
}
|
||||
if (!canSurvive(thisBlock, state, level, blockPos)) {
|
||||
ImmutableBlockState previousState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(state));
|
||||
if (previousState != null && !previousState.isEmpty()) {
|
||||
ContextHolder.Builder builder = ContextHolder.builder();
|
||||
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
|
||||
Vec3d vec3d = Vec3d.atCenterOf(pos);
|
||||
net.momirealms.craftengine.core.world.World world = new BukkitWorld((World) Reflections.method$Level$getCraftWorld.invoke(level));
|
||||
builder.withParameter(LootParameters.LOCATION, vec3d);
|
||||
builder.withParameter(LootParameters.WORLD, world);
|
||||
for (Item<Object> item : previousState.getDrops(builder, world)) {
|
||||
world.dropItemNaturally(vec3d, item);
|
||||
}
|
||||
}
|
||||
return Reflections.method$Block$defaultBlockState.invoke(Reflections.instance$Blocks$AIR);
|
||||
}
|
||||
return super.updateShape(thisBlock, args, superMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSurvive(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object state = args[0];
|
||||
Object world = args[1];
|
||||
Object pos = args[2];
|
||||
return canSurvive(thisBlock, state, world, pos);
|
||||
}
|
||||
|
||||
public static class Factory implements BlockBehaviorFactory {
|
||||
@Override
|
||||
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
|
||||
if (arguments.containsKey("tags")) {
|
||||
return new BushBlockBehavior(MiscUtils.getAsStringList(arguments.get("tags")).stream().map(it -> BlockTags.getOrCreate(Key.of(it))).toList());
|
||||
} else {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException {
|
||||
int y = Reflections.field$Vec3i$y.getInt(blockPos);
|
||||
int x = Reflections.field$Vec3i$x.getInt(blockPos);
|
||||
int z = Reflections.field$Vec3i$z.getInt(blockPos);
|
||||
Object belowPos = Reflections.constructor$BlockPos.newInstance(x, y - 1, z);
|
||||
Object belowState = Reflections.method$BlockGetter$getBlockState.invoke(world, belowPos);
|
||||
return mayPlaceOn(belowState, world, belowPos);
|
||||
}
|
||||
|
||||
private boolean mayPlaceOn(Object belowState, Object world, Object blockPos) throws ReflectiveOperationException {
|
||||
for (Object tag : this.tagsCanSurviveOn) {
|
||||
if ((boolean) Reflections.method$BlockStateBase$hasTag.invoke(belowState, tag)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package net.momirealms.craftengine.bukkit.block.behavior;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import net.momirealms.craftengine.core.util.context.ContextHolder;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import net.momirealms.craftengine.shared.block.BlockBehavior;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class FallingBlockBehavior extends BlockBehavior {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
private final float hurtAmount;
|
||||
private final int maxHurt;
|
||||
|
||||
public FallingBlockBehavior(float hurtAmount, int maxHurt) {
|
||||
this.hurtAmount = hurtAmount;
|
||||
this.maxHurt = maxHurt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlace(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object world = args[1];
|
||||
Object blockPos = args[2];
|
||||
Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object world;
|
||||
Object blockPos;
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
world = args[1];
|
||||
blockPos = args[3];
|
||||
} else {
|
||||
world = args[3];
|
||||
blockPos = args[4];
|
||||
}
|
||||
Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 2);
|
||||
return super.updateShape(thisBlock, args, superMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object blockPos = args[2];
|
||||
int y = Reflections.field$Vec3i$y.getInt(blockPos);
|
||||
Object world = args[1];
|
||||
Object dimension = Reflections.method$$LevelReader$dimensionType.invoke(world);
|
||||
int minY = Reflections.field$DimensionType$minY.getInt(dimension);
|
||||
if (y < minY) {
|
||||
return;
|
||||
}
|
||||
int x = Reflections.field$Vec3i$x.getInt(blockPos);
|
||||
int z = Reflections.field$Vec3i$z.getInt(blockPos);
|
||||
Object belowPos = Reflections.constructor$BlockPos.newInstance(x, y - 1, z);
|
||||
Object belowState = Reflections.method$BlockGetter$getBlockState.invoke(world, belowPos);
|
||||
boolean isFree = (boolean) Reflections.method$FallingBlock$isFree.invoke(null, belowState);
|
||||
if (!isFree) {
|
||||
return;
|
||||
}
|
||||
Object blockState = args[0];
|
||||
Object fallingBlockEntity = Reflections.method$FallingBlockEntity$fall.invoke(null, world, blockPos, blockState);
|
||||
if (this.hurtAmount > 0 && this.maxHurt > 0) {
|
||||
Reflections.method$FallingBlockEntity$setHurtsEntities.invoke(fallingBlockEntity, this.hurtAmount, this.maxHurt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBrokenAfterFall(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
// Use EntityRemoveEvent for 1.20.3+
|
||||
if (VersionHelper.isVersionNewerThan1_20_3()) return;
|
||||
Object level = args[0];
|
||||
Object fallingBlockEntity = args[2];
|
||||
boolean cancelDrop = (boolean) Reflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity);
|
||||
if (cancelDrop) return;
|
||||
Object blockState = Reflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity);
|
||||
int stateId = BlockStateUtils.blockStateToId(blockState);
|
||||
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(stateId);
|
||||
if (immutableBlockState == null || immutableBlockState.isEmpty()) return;
|
||||
ContextHolder.Builder builder = ContextHolder.builder();
|
||||
builder.withParameter(LootParameters.FALLING_BLOCK, true);
|
||||
double x = Reflections.field$Entity$xo.getDouble(fallingBlockEntity);
|
||||
double y = Reflections.field$Entity$yo.getDouble(fallingBlockEntity);
|
||||
double z = Reflections.field$Entity$zo.getDouble(fallingBlockEntity);
|
||||
Vec3d vec3d = new Vec3d(x, y, z);
|
||||
net.momirealms.craftengine.core.world.World world = new BukkitWorld((World) Reflections.method$Level$getCraftWorld.invoke(level));
|
||||
builder.withParameter(LootParameters.LOCATION, vec3d);
|
||||
builder.withParameter(LootParameters.WORLD, world);
|
||||
for (Item<Object> item : immutableBlockState.getDrops(builder, world)) {
|
||||
world.dropItemNaturally(vec3d, item);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements BlockBehaviorFactory {
|
||||
@Override
|
||||
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
|
||||
float hurtAmount = MiscUtils.getAsFloat(arguments.getOrDefault("hurt-amount", -1f));
|
||||
int hurtMax = MiscUtils.getAsInt(arguments.getOrDefault("max-hurt", -1));
|
||||
return new FallingBlockBehavior(hurtAmount, hurtMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package net.momirealms.craftengine.bukkit.block.behavior;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockTags;
|
||||
import net.momirealms.craftengine.bukkit.util.LocationUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.UpdateOption;
|
||||
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.block.properties.Property;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
|
||||
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.util.context.ContextHolder;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import net.momirealms.craftengine.shared.block.BlockBehavior;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.block.LeavesDecayEvent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class LeavesBlockBehavior extends BlockBehavior {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
private static final Object LOG_TAG = BlockTags.getOrCreate(Key.of("minecraft", "logs"));
|
||||
private final int maxDistance;
|
||||
private final Property<Integer> distanceProperty;
|
||||
private final Property<Boolean> persistentProperty;
|
||||
@Nullable
|
||||
private final Property<Boolean> waterloggedProperty;
|
||||
|
||||
public LeavesBlockBehavior(int maxDistance, Property<Integer> distanceProperty, Property<Boolean> persistentProperty, @Nullable Property<Boolean> waterloggedProperty) {
|
||||
this.maxDistance = maxDistance;
|
||||
this.distanceProperty = distanceProperty;
|
||||
this.persistentProperty = persistentProperty;
|
||||
this.waterloggedProperty = waterloggedProperty;
|
||||
}
|
||||
|
||||
public int getDistance(ImmutableBlockState state) {
|
||||
return state.get(this.distanceProperty);
|
||||
}
|
||||
|
||||
public boolean isPersistent(ImmutableBlockState state) {
|
||||
return state.get(this.persistentProperty);
|
||||
}
|
||||
|
||||
public boolean isWaterLogged(ImmutableBlockState state) {
|
||||
if (this.waterloggedProperty == null) return false;
|
||||
return state.get(this.waterloggedProperty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object world;
|
||||
Object blockPos;
|
||||
Object neighborState;
|
||||
Object blockState = args[0];
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
world = args[1];
|
||||
neighborState = args[6];
|
||||
blockPos = args[3];
|
||||
} else {
|
||||
world = args[3];
|
||||
blockPos = args[4];
|
||||
neighborState = args[2];
|
||||
}
|
||||
ImmutableBlockState thisState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
|
||||
if (thisState != null && thisState.behavior() instanceof LeavesBlockBehavior behavior) {
|
||||
int distance = behavior.getDistanceAt(neighborState) + 1;
|
||||
if (distance != 1 || behavior.getDistance(thisState) != distance) {
|
||||
Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 1);
|
||||
}
|
||||
}
|
||||
return blockState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object blockState = args[0];
|
||||
Object level = args[1];
|
||||
Object blockPos = args[2];
|
||||
ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
|
||||
if (currentState != null && !currentState.isEmpty() && currentState.behavior() instanceof LeavesBlockBehavior behavior) {
|
||||
ImmutableBlockState newState = behavior.updateDistance(currentState, level, blockPos);
|
||||
if (newState != currentState) {
|
||||
if (blockState == newState.customBlockState().handle()) {
|
||||
Reflections.method$BlockStateBase$updateNeighbourShapes.invoke(blockState, level, blockPos, UpdateOption.UPDATE_ALL.flags(), 512);
|
||||
} else {
|
||||
Reflections.method$Level$setBlock.invoke(level, blockPos, newState.customBlockState().handle(), UpdateOption.UPDATE_ALL.flags());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomTick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object level = args[1];
|
||||
Object blockPos = args[2];
|
||||
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(args[0]));
|
||||
if (immutableBlockState != null && immutableBlockState.behavior() instanceof LeavesBlockBehavior behavior && behavior.isDecaying(immutableBlockState)) {
|
||||
World bukkitWorld = (World) Reflections.method$Level$getCraftWorld.invoke(level);
|
||||
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
|
||||
// call bukkit event
|
||||
LeavesDecayEvent event = new LeavesDecayEvent(bukkitWorld.getBlockAt(pos.x(), pos.y(), pos.z()));
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
Reflections.method$Level$removeBlock.invoke(level, blockPos, false);
|
||||
if (isWaterLogged(immutableBlockState)) {
|
||||
bukkitWorld.setBlockData(pos.x(), pos.y(), pos.z(), Material.WATER.createBlockData());
|
||||
}
|
||||
Vec3d vec3d = Vec3d.atCenterOf(pos);
|
||||
net.momirealms.craftengine.core.world.World world = new BukkitWorld(bukkitWorld);
|
||||
ContextHolder.Builder builder = ContextHolder.builder()
|
||||
.withParameter(LootParameters.LOCATION, vec3d)
|
||||
.withParameter(LootParameters.WORLD, world);
|
||||
for (Item<Object> item : immutableBlockState.getDrops(builder, world)) {
|
||||
world.dropItemNaturally(vec3d, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDecaying(ImmutableBlockState blockState) {
|
||||
return !isPersistent(blockState) && getDistance(blockState) == this.maxDistance;
|
||||
}
|
||||
|
||||
private ImmutableBlockState updateDistance(ImmutableBlockState state, Object world, Object blockPos) throws ReflectiveOperationException {
|
||||
int i = this.maxDistance;
|
||||
Object mutablePos = Reflections.constructor$MutableBlockPos.newInstance();
|
||||
int j = Direction.values().length;
|
||||
for (int k = 0; k < j; ++k) {
|
||||
Object direction = Reflections.instance$Directions[k];
|
||||
Reflections.method$MutableBlockPos$setWithOffset.invoke(mutablePos, blockPos, direction);
|
||||
Object blockState = Reflections.method$BlockGetter$getBlockState.invoke(world, mutablePos);
|
||||
i = Math.min(i, getDistanceAt(blockState) + 1);
|
||||
if (i == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state.with(this.distanceProperty, i);
|
||||
}
|
||||
|
||||
private int getDistanceAt(Object blockState) throws ReflectiveOperationException {
|
||||
boolean isLog = (boolean) Reflections.method$BlockStateBase$hasTag.invoke(blockState, LOG_TAG);
|
||||
if (isLog) return 0;
|
||||
int id = BlockStateUtils.blockStateToId(blockState);
|
||||
if (BlockStateUtils.isVanillaBlock(id)) {
|
||||
Object distanceProperty = Reflections.field$LeavesBlock$DISTANCE.get(null);
|
||||
boolean hasDistanceProperty = (boolean) Reflections.method$StateHolder$hasProperty.invoke(blockState, distanceProperty);
|
||||
if (!hasDistanceProperty) return this.maxDistance;
|
||||
return (int) Reflections.method$StateHolder$getValue.invoke(blockState, distanceProperty);
|
||||
} else {
|
||||
ImmutableBlockState anotherBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(id);
|
||||
if (!(anotherBlockState.behavior() instanceof LeavesBlockBehavior otherBehavior)) return this.maxDistance;
|
||||
return otherBehavior.getDistance(anotherBlockState);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static class Factory implements BlockBehaviorFactory {
|
||||
@Override
|
||||
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
|
||||
Property<Boolean> persistent = (Property<Boolean>) block.getProperty("persistent");
|
||||
if (persistent == null) {
|
||||
throw new NullPointerException("persistent property not set for block " + block.id());
|
||||
}
|
||||
Property<Integer> distance = (Property<Integer>) block.getProperty("distance");
|
||||
if (distance == null) {
|
||||
throw new NullPointerException("distance not set for block " + block.id());
|
||||
}
|
||||
Property<Boolean> waterlogged = (Property<Boolean>) block.getProperty("waterlogged");
|
||||
int actual = distance.possibleValues().get(distance.possibleValues().size() - 1);
|
||||
return new LeavesBlockBehavior(actual, distance, persistent, waterlogged);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getFluidState(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object blockState = args[0];
|
||||
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
|
||||
if (state == null || state.isEmpty() || waterloggedProperty == null) return super.getFluidState(thisBlock, args, superMethod);
|
||||
boolean waterlogged = state.get(waterloggedProperty);
|
||||
return waterlogged ? Reflections.method$FlowingFluid$getSource.invoke(Reflections.instance$Fluids$WATER, false) : super.getFluidState(thisBlock, args, superMethod);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package net.momirealms.craftengine.bukkit.block.behavior;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.util.*;
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.UpdateOption;
|
||||
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.block.properties.Property;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import net.momirealms.craftengine.core.util.RandomUtils;
|
||||
import net.momirealms.craftengine.shared.block.BlockBehavior;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class SaplingBlockBehavior extends BushBlockBehavior {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
private final Key feature;
|
||||
private final Property<Integer> stageProperty;
|
||||
private final double boneMealSuccessChance;
|
||||
|
||||
public SaplingBlockBehavior(Key feature, Property<Integer> stageProperty, List<Object> tagsCanSurviveOn, double boneMealSuccessChance) {
|
||||
super(tagsCanSurviveOn);
|
||||
this.feature = feature;
|
||||
this.stageProperty = stageProperty;
|
||||
this.boneMealSuccessChance = boneMealSuccessChance;
|
||||
}
|
||||
|
||||
public Key treeFeature() {
|
||||
return feature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomTick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Object world = args[1];
|
||||
Object blockPos = args[2];
|
||||
Object blockState = args[0];
|
||||
int x = (int) Reflections.field$Vec3i$x.get(blockPos);
|
||||
int y = (int) Reflections.field$Vec3i$y.get(blockPos);
|
||||
int z = (int) Reflections.field$Vec3i$z.get(blockPos);
|
||||
Object aboveBlockPos = LocationUtils.toBlockPos(x, y + 1, z);
|
||||
if ((int) Reflections.method$LevelReader$getMaxLocalRawBrightness.invoke(world, aboveBlockPos) >= 9 && (float) Reflections.method$RandomSource$nextFloat.invoke(args[3]) < (1.0f / 7.0f)) {
|
||||
increaseStage(world, blockPos, blockState, args[3]);
|
||||
}
|
||||
}
|
||||
|
||||
private void increaseStage(Object world, Object blockPos, Object blockState, Object randomSource) throws Exception {
|
||||
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
|
||||
if (immutableBlockState == null || immutableBlockState.isEmpty()) return;
|
||||
int currentStage = immutableBlockState.get(this.stageProperty);
|
||||
if (currentStage != this.stageProperty.possibleValues().get(this.stageProperty.possibleValues().size() - 1)) {
|
||||
ImmutableBlockState nextStage = immutableBlockState.cycle(this.stageProperty);
|
||||
World bukkitWorld = (World) Reflections.method$Level$getCraftWorld.invoke(world);
|
||||
int x = (int) Reflections.field$Vec3i$x.get(blockPos);
|
||||
int y = (int) Reflections.field$Vec3i$y.get(blockPos);
|
||||
int z = (int) Reflections.field$Vec3i$z.get(blockPos);
|
||||
CraftEngineBlocks.place(new Location(bukkitWorld, x, y, z), nextStage, UpdateOption.UPDATE_NONE, false);
|
||||
} else {
|
||||
generateTree(world, blockPos, blockState, randomSource);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateTree(Object world, Object blockPos, Object blockState, Object randomSource) throws Exception {
|
||||
Object registry = Reflections.method$RegistryAccess$registryOrThrow.invoke(Reflections.instance$registryAccess, Reflections.instance$Registries$CONFIGURED_FEATURE);
|
||||
if (registry == null) return;
|
||||
@SuppressWarnings("unchecked")
|
||||
Optional<Object> holder = (Optional<Object>) Reflections.method$Registry$getHolder1.invoke(registry, FeatureUtils.createFeatureKey(treeFeature()));
|
||||
if (holder.isEmpty()) {
|
||||
CraftEngine.instance().logger().warn("Configured feature not found: " + treeFeature());
|
||||
return;
|
||||
}
|
||||
Object chunkGenerator = Reflections.method$ServerChunkCache$getGenerator.invoke(Reflections.field$ServerLevel$chunkSource.get(world));
|
||||
Object configuredFeature = Reflections.method$Holder$value.invoke(holder.get());
|
||||
Object fluidState = Reflections.method$Level$getFluidState.invoke(world, blockPos);
|
||||
Object legacyState = Reflections.method$FluidState$createLegacyBlock.invoke(fluidState);
|
||||
Reflections.method$Level$setBlock.invoke(world, blockPos, legacyState, UpdateOption.UPDATE_NONE.flags());
|
||||
if ((boolean) Reflections.method$ConfiguredFeature$place.invoke(configuredFeature, world, chunkGenerator, randomSource, blockPos)) {
|
||||
if (Reflections.method$BlockGetter$getBlockState.invoke(world, blockPos) == legacyState) {
|
||||
Reflections.method$ServerLevel$sendBlockUpdated.invoke(world, blockPos, blockState, legacyState, 2);
|
||||
}
|
||||
} else {
|
||||
// failed to place, rollback changes
|
||||
Reflections.method$Level$setBlock.invoke(world, blockPos, blockState, UpdateOption.UPDATE_NONE.flags());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBoneMealSuccess(Object thisBlock, Object[] args) {
|
||||
return RandomUtils.generateRandomDouble(0d, 1d) < this.boneMealSuccessChance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidBoneMealTarget(Object thisBlock, Object[] args) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performBoneMeal(Object thisBlock, Object[] args) throws Exception {
|
||||
this.increaseStage(args[0], args[2], args[3], args[1]);
|
||||
}
|
||||
|
||||
public static class Factory implements BlockBehaviorFactory {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
|
||||
String feature = (String) arguments.get("feature");
|
||||
if (feature == null) {
|
||||
throw new IllegalArgumentException("feature is null");
|
||||
}
|
||||
Property<Integer> stageProperty = (Property<Integer>) block.getProperty("stage");
|
||||
if (stageProperty == null) {
|
||||
throw new IllegalArgumentException("stage property not set for sapling");
|
||||
}
|
||||
double boneMealSuccessChance = MiscUtils.getAsDouble(arguments.getOrDefault("bone-meal-success-chance", 0.45));
|
||||
if (arguments.containsKey("tags")) {
|
||||
return new SaplingBlockBehavior(Key.of(feature), stageProperty, MiscUtils.getAsStringList(arguments.get("tags")).stream().map(it -> BlockTags.getOrCreate(Key.of(it))).toList(), boneMealSuccessChance);
|
||||
} else {
|
||||
return new SaplingBlockBehavior(Key.of(feature), stageProperty, List.of(DIRT_TAG, FARMLAND), boneMealSuccessChance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.momirealms.craftengine.bukkit.block.behavior;
|
||||
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.shared.block.BlockBehavior;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class StrippableBlockBehavior extends BlockBehavior {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
private final Key stripped;
|
||||
|
||||
public StrippableBlockBehavior(Key stripped) {
|
||||
this.stripped = stripped;
|
||||
}
|
||||
|
||||
public Key stripped() {
|
||||
return stripped;
|
||||
}
|
||||
|
||||
public static class Factory implements BlockBehaviorFactory {
|
||||
|
||||
@Override
|
||||
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
|
||||
String stripped = (String) arguments.get("stripped");
|
||||
if (stripped == null) {
|
||||
throw new IllegalArgumentException("stripped is null");
|
||||
}
|
||||
return new StrippableBlockBehavior(Key.of(stripped));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package net.momirealms.craftengine.bukkit.entity;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DisplayEntityData<T> {
|
||||
|
||||
private final int id;
|
||||
private final Object serializer;
|
||||
private final T defaultValue;
|
||||
|
||||
// Entity
|
||||
public static final DisplayEntityData<Byte> EntityMasks = of(0, EntityDataValue.Serializers$BYTE, (byte) 0);
|
||||
|
||||
// Display only
|
||||
public static final DisplayEntityData<Integer> InterpolationDelay = of(8, EntityDataValue.Serializers$INT, 0);
|
||||
|
||||
// 1.19.4-1.20.1
|
||||
public static final DisplayEntityData<Integer> InterpolationDuration = of(9, EntityDataValue.Serializers$INT, 0);
|
||||
|
||||
// 1.20.2+
|
||||
public static final DisplayEntityData<Integer> TransformationInterpolationDuration = of(9, EntityDataValue.Serializers$INT, 0);
|
||||
public static final DisplayEntityData<Integer> PositionRotationInterpolationDuration = of(10, EntityDataValue.Serializers$INT, 0);
|
||||
|
||||
public static final DisplayEntityData<Object> Translation = of(11, EntityDataValue.Serializers$VECTOR3, Reflections.instance$Vector3f$None);
|
||||
public static final DisplayEntityData<Object> Scale = of(12, EntityDataValue.Serializers$VECTOR3, Reflections.instance$Vector3f$Normal);
|
||||
public static final DisplayEntityData<Object> RotationLeft = of(13, EntityDataValue.Serializers$QUATERNION, Reflections.instance$Quaternionf$None);
|
||||
public static final DisplayEntityData<Object> RotationRight = of(14, EntityDataValue.Serializers$QUATERNION, Reflections.instance$Quaternionf$None);
|
||||
/**
|
||||
* Billboard Constraints (0 = FIXED, 1 = VERTICAL, 2 = HORIZONTAL, 3 = CENTER)
|
||||
*/
|
||||
public static final DisplayEntityData<Byte> BillboardConstraints = of(15, EntityDataValue.Serializers$BYTE, (byte) 0);
|
||||
/**
|
||||
* Brightness override (blockLight << 4 | skyLight << 20)
|
||||
*/
|
||||
public static final DisplayEntityData<Integer> BrightnessOverride = of(16, EntityDataValue.Serializers$INT, -1);
|
||||
public static final DisplayEntityData<Float> ViewRange = of(17, EntityDataValue.Serializers$FLOAT, 1f);
|
||||
public static final DisplayEntityData<Float> ShadowRadius = of(18, EntityDataValue.Serializers$FLOAT, 0f);
|
||||
public static final DisplayEntityData<Float> ShadowStrength = of(19, EntityDataValue.Serializers$FLOAT, 0f);
|
||||
public static final DisplayEntityData<Float> Width = of(20, EntityDataValue.Serializers$FLOAT, 0f);
|
||||
public static final DisplayEntityData<Float> Height = of(21, EntityDataValue.Serializers$FLOAT, 0f);
|
||||
public static final DisplayEntityData<Integer> GlowColorOverride = of(22, EntityDataValue.Serializers$INT, -1);
|
||||
|
||||
// Text display only
|
||||
public static final DisplayEntityData<Object> Text = of(23, EntityDataValue.Serializers$COMPONENT, Reflections.instance$Component$empty);
|
||||
public static final DisplayEntityData<Integer> LineWidth = of(24, EntityDataValue.Serializers$INT, 200);
|
||||
public static final DisplayEntityData<Integer> BackgroundColor = of(25, EntityDataValue.Serializers$INT, 0x40000000);
|
||||
public static final DisplayEntityData<Byte> TextOpacity = of(26, EntityDataValue.Serializers$BYTE, (byte) -1);
|
||||
public static final DisplayEntityData<Byte> TextDisplayMasks = of(27, EntityDataValue.Serializers$BYTE, (byte) 0);
|
||||
|
||||
// Item display only
|
||||
public static final DisplayEntityData<Object> DisplayedItem = of(23, EntityDataValue.Serializers$ITEM_STACK, Reflections.instance$ItemStack$Air);
|
||||
/**
|
||||
* Display type:
|
||||
* 0 = NONE
|
||||
* 1 = THIRD_PERSON_LEFT_HAND
|
||||
* 2 = THIRD_PERSON_RIGHT_HAND
|
||||
* 3 = FIRST_PERSON_LEFT_HAND
|
||||
* 4 = FIRST_PERSON_RIGHT_HAND
|
||||
* 5 = HEAD
|
||||
* 6 = GUI
|
||||
* 7 = GROUND
|
||||
* 8 = FIXED
|
||||
*/
|
||||
public static final DisplayEntityData<Byte> DisplayType = of(24, EntityDataValue.Serializers$BYTE, (byte) 0);
|
||||
|
||||
// Block display only
|
||||
public static final DisplayEntityData<Object> DisplayedBlock = of(23, EntityDataValue.Serializers$BLOCK_STATE, Reflections.instance$Blocks$AIR$defaultState);
|
||||
|
||||
public static <T> DisplayEntityData<T> of(final int id, final Object serializer, T defaultValue) {
|
||||
return new DisplayEntityData<>(id, serializer, defaultValue);
|
||||
}
|
||||
|
||||
public DisplayEntityData(int id, Object serializer, T defaultValue) {
|
||||
if (!VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
if (id >= 11) {
|
||||
id--;
|
||||
}
|
||||
}
|
||||
this.id = id;
|
||||
this.serializer = serializer;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public Object serializer() {
|
||||
return serializer;
|
||||
}
|
||||
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public T defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public Object createEntityDataIfNotDefaultValue(T value) {
|
||||
if (defaultValue().equals(value)) return null;
|
||||
return EntityDataValue.create(id, serializer, value);
|
||||
}
|
||||
|
||||
public void addEntityDataIfNotDefaultValue(T value, List<Object> list) {
|
||||
if (defaultValue().equals(value)) return;
|
||||
list.add(EntityDataValue.create(id, serializer, value));
|
||||
}
|
||||
|
||||
public void addEntityData(T value, List<Object> list) {
|
||||
list.add(EntityDataValue.create(id, serializer, value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package net.momirealms.craftengine.bukkit.entity;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.util.ReflectionUtils;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
|
||||
public class EntityDataValue {
|
||||
private static int internalID = 0;
|
||||
|
||||
private static final String[] fieldsObf = {
|
||||
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
||||
};
|
||||
|
||||
public static final Object Serializers$BYTE;
|
||||
public static final Object Serializers$INT;
|
||||
public static final Object Serializers$LONG;
|
||||
public static final Object Serializers$FLOAT;
|
||||
public static final Object Serializers$STRING;
|
||||
public static final Object Serializers$COMPONENT;
|
||||
public static final Object Serializers$OPTIONAL_COMPONENT;
|
||||
public static final Object Serializers$ITEM_STACK;
|
||||
public static final Object Serializers$BLOCK_STATE;
|
||||
public static final Object Serializers$OPTIONAL_BLOCK_STATE;
|
||||
public static final Object Serializers$BOOLEAN;
|
||||
public static final Object Serializers$PARTICLE;
|
||||
public static final Object Serializers$PARTICLES;
|
||||
public static final Object Serializers$ROTATIONS;
|
||||
public static final Object Serializers$BLOCK_POS;
|
||||
public static final Object Serializers$OPTIONAL_BLOCK_POS;
|
||||
public static final Object Serializers$DIRECTION;
|
||||
public static final Object Serializers$OPTIONAL_UUID;
|
||||
public static final Object Serializers$OPTIONAL_GLOBAL_POS;
|
||||
public static final Object Serializers$COMPOUND_TAG;
|
||||
public static final Object Serializers$VILLAGER_DATA;
|
||||
public static final Object Serializers$OPTIONAL_UNSIGNED_INT;
|
||||
public static final Object Serializers$POSE;
|
||||
public static final Object Serializers$CAT_VARIANT;
|
||||
public static final Object Serializers$WOLF_VARIANT;
|
||||
public static final Object Serializers$FROG_VARIANT;
|
||||
public static final Object Serializers$PAINTING_VARIANT;
|
||||
public static final Object Serializers$ARMADILLO_STATE;
|
||||
public static final Object Serializers$SNIFFER_STATE;
|
||||
public static final Object Serializers$VECTOR3;
|
||||
public static final Object Serializers$QUATERNION;
|
||||
|
||||
static {
|
||||
try {
|
||||
Serializers$BYTE = initSerializersByName("BYTE");
|
||||
Serializers$INT = initSerializersByName("INT");
|
||||
Serializers$LONG = initSerializersByName("LONG");
|
||||
Serializers$FLOAT = initSerializersByName("FLOAT");
|
||||
Serializers$STRING = initSerializersByName("STRING");
|
||||
Serializers$COMPONENT = initSerializersByName("COMPONENT");
|
||||
Serializers$OPTIONAL_COMPONENT = initSerializersByName("OPTIONAL_COMPONENT");
|
||||
Serializers$ITEM_STACK = initSerializersByName("ITEM_STACK");
|
||||
Serializers$BLOCK_STATE = initSerializersByName("BLOCK_STATE");
|
||||
Serializers$OPTIONAL_BLOCK_STATE = initSerializersByName("OPTIONAL_BLOCK_STATE");
|
||||
Serializers$BOOLEAN = initSerializersByName("BOOLEAN");
|
||||
Serializers$PARTICLE = initSerializersByName("PARTICLE");
|
||||
if (VersionHelper.isVersionNewerThan1_20_5()) Serializers$PARTICLES = initSerializersByName("PARTICLES");
|
||||
else Serializers$PARTICLES = null;
|
||||
Serializers$ROTATIONS = initSerializersByName("ROTATIONS");
|
||||
Serializers$BLOCK_POS = initSerializersByName("BLOCK_POS");
|
||||
Serializers$OPTIONAL_BLOCK_POS = initSerializersByName("OPTIONAL_BLOCK_POS");
|
||||
Serializers$DIRECTION = initSerializersByName("DIRECTION");
|
||||
Serializers$OPTIONAL_UUID = initSerializersByName("OPTIONAL_UUID");
|
||||
Serializers$OPTIONAL_GLOBAL_POS = initSerializersByName("OPTIONAL_GLOBAL_POS");
|
||||
Serializers$COMPOUND_TAG = initSerializersByName("COMPOUND_TAG");
|
||||
Serializers$VILLAGER_DATA = initSerializersByName("VILLAGER_DATA");
|
||||
Serializers$OPTIONAL_UNSIGNED_INT = initSerializersByName("OPTIONAL_UNSIGNED_INT");
|
||||
Serializers$POSE = initSerializersByName("POSE");
|
||||
Serializers$CAT_VARIANT = initSerializersByName("CAT_VARIANT");
|
||||
if (VersionHelper.isVersionNewerThan1_20_5()) Serializers$WOLF_VARIANT = initSerializersByName("WOLF_VARIANT");
|
||||
else Serializers$WOLF_VARIANT = null;
|
||||
Serializers$FROG_VARIANT = initSerializersByName("FROG_VARIANT");
|
||||
Serializers$PAINTING_VARIANT = initSerializersByName("PAINTING_VARIANT");
|
||||
if (VersionHelper.isVersionNewerThan1_20_5()) Serializers$ARMADILLO_STATE = initSerializersByName("ARMADILLO_STATE");
|
||||
else Serializers$ARMADILLO_STATE = null;
|
||||
Serializers$SNIFFER_STATE = initSerializersByName("SNIFFER_STATE");
|
||||
Serializers$VECTOR3 = initSerializersByName("VECTOR3");
|
||||
Serializers$QUATERNION = initSerializersByName("QUATERNION");
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new ExceptionInInitializerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object initSerializersByName(String name) throws ReflectiveOperationException {
|
||||
return ReflectionUtils.getDeclaredField(Reflections.clazz$EntityDataSerializers, new String[]{fieldsObf[internalID++], name}).get(null);
|
||||
}
|
||||
|
||||
private EntityDataValue() {
|
||||
throw new IllegalAccessError("Utility class");
|
||||
}
|
||||
|
||||
public static Object create(int id, Object serializer, Object value) {
|
||||
try {
|
||||
Object entityDataAccessor = Reflections.constructor$EntityDataAccessor.newInstance(id, serializer);
|
||||
return Reflections.method$SynchedEntityData$DataValue$create.invoke(null, entityDataAccessor, value);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.momirealms.craftengine.bukkit.entity;
|
||||
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class InteractionEntityData<T> {
|
||||
|
||||
private final int id;
|
||||
private final Object serializer;
|
||||
private final T defaultValue;
|
||||
|
||||
// Entity
|
||||
public static final InteractionEntityData<Byte> EntityMasks = of(0, EntityDataValue.Serializers$BYTE, (byte) 0);
|
||||
// Interaction only
|
||||
public static final InteractionEntityData<Float> Width = of(8, EntityDataValue.Serializers$FLOAT, 1F);
|
||||
public static final InteractionEntityData<Float> Height = of(9, EntityDataValue.Serializers$FLOAT, 1F);
|
||||
public static final InteractionEntityData<Boolean> Responsive = of(10, EntityDataValue.Serializers$BOOLEAN, false);
|
||||
|
||||
public static <T> InteractionEntityData<T> of(final int id, final Object serializer, T defaultValue) {
|
||||
return new InteractionEntityData<>(id, serializer, defaultValue);
|
||||
}
|
||||
|
||||
public InteractionEntityData(int id, Object serializer, T defaultValue) {
|
||||
if (!VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
if (id >= 11) {
|
||||
id--;
|
||||
}
|
||||
}
|
||||
this.id = id;
|
||||
this.serializer = serializer;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public Object serializer() {
|
||||
return serializer;
|
||||
}
|
||||
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public T defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public Object createEntityDataIfNotDefaultValue(T value) {
|
||||
if (defaultValue().equals(value)) return null;
|
||||
return EntityDataValue.create(id, serializer, value);
|
||||
}
|
||||
|
||||
public void addEntityDataIfNotDefaultValue(T value, List<Object> list) {
|
||||
if (defaultValue().equals(value)) return;
|
||||
list.add(EntityDataValue.create(id, serializer, value));
|
||||
}
|
||||
|
||||
public void addEntityData(T value, List<Object> list) {
|
||||
list.add(EntityDataValue.create(id, serializer, value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package net.momirealms.craftengine.bukkit.entity.furniture;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.EntityUtils;
|
||||
import net.momirealms.craftengine.core.entity.furniture.*;
|
||||
import net.momirealms.craftengine.core.loot.LootTable;
|
||||
import net.momirealms.craftengine.core.pack.Pack;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.entity.*;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class BukkitFurnitureManager implements FurnitureManager {
|
||||
public static final NamespacedKey FURNITURE_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:furniture_id"));
|
||||
public static final NamespacedKey FURNITURE_ANCHOR_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:anchor_type"));
|
||||
public static final NamespacedKey FURNITURE_SEAT_BASE_ENTITY_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:seat_to_base_entity"));
|
||||
public static final NamespacedKey FURNITURE_SEAT_VECTOR_3F_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:seat_vector"));
|
||||
private static BukkitFurnitureManager instance;
|
||||
private final BukkitCraftEngine plugin;
|
||||
|
||||
private final Map<Key, CustomFurniture> byId = new HashMap<>();
|
||||
|
||||
private final Map<Integer, LoadedFurniture> furnitureByBaseEntityId = new ConcurrentHashMap<>(256, 0.5f);
|
||||
private final Map<Integer, LoadedFurniture> furnitureByInteractionEntityId = new ConcurrentHashMap<>(512, 0.5f);
|
||||
private final Map<Integer, int[]> baseEntity2SubEntities = new ConcurrentHashMap<>(256, 0.5f);
|
||||
|
||||
// Delay furniture cache remove for about 4-5 ticks
|
||||
private static final int DELAYED_TICK = 5;
|
||||
private final IntSet[] delayedRemove = new IntSet[DELAYED_TICK];
|
||||
// Event listeners
|
||||
private final Listener dismountListener;
|
||||
private final FurnitureEventListener furnitureEventListener;
|
||||
// tick task
|
||||
private SchedulerTask tickTask;
|
||||
|
||||
public static BukkitFurnitureManager instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public BukkitFurnitureManager(BukkitCraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
this.furnitureEventListener = new FurnitureEventListener(this);
|
||||
this.dismountListener = VersionHelper.isVersionNewerThan1_20_3() ? new DismountListener1_20_3(this) : new DismountListener1_20(this::handleDismount);
|
||||
for (int i = 0; i < DELAYED_TICK; i++) {
|
||||
this.delayedRemove[i] = new IntOpenHashSet();
|
||||
}
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public LoadedFurniture place(CustomFurniture furniture, Location location, AnchorType anchorType, boolean playSound) {
|
||||
Entity furnitureEntity = EntityUtils.spawnEntity(location.getWorld(), location, EntityType.ITEM_DISPLAY, entity -> {
|
||||
ItemDisplay display = (ItemDisplay) entity;
|
||||
display.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_KEY, PersistentDataType.STRING, furniture.id().toString());
|
||||
display.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_ANCHOR_KEY, PersistentDataType.STRING, anchorType.name());
|
||||
handleEntityLoadEarly(display);
|
||||
});
|
||||
if (playSound) {
|
||||
location.getWorld().playSound(location, furniture.settings().sounds().placeSound().toString(), SoundCategory.BLOCKS,1f, 1f);
|
||||
}
|
||||
return getLoadedFurnitureByBaseEntityId(furnitureEntity.getEntityId());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
Map<String, Object> lootMap = MiscUtils.castToMap(section.get("loot"), true);
|
||||
Map<String, Object> settingsMap = MiscUtils.castToMap(section.get("settings"), true);
|
||||
Map<String, Object> placementMap = MiscUtils.castToMap(section.get("placement"), true);
|
||||
EnumMap<AnchorType, CustomFurniture.Placement> placements = new EnumMap<>(AnchorType.class);
|
||||
if (placementMap == null) {
|
||||
throw new IllegalArgumentException("Missing required parameter 'placement' for furniture " + id);
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : placementMap.entrySet()) {
|
||||
AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH));
|
||||
Map<String, Object> placementArguments = MiscUtils.castToMap(entry.getValue(), true);
|
||||
|
||||
List<FurnitureElement> elements = new ArrayList<>();
|
||||
List<Map<String, Object>> elementConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("elements", List.of());
|
||||
for (Map<String, Object> element : elementConfigs) {
|
||||
String key = (String) element.get("item");
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("Missing required parameter 'item' for furniture " + id);
|
||||
}
|
||||
ItemDisplayContext transform = ItemDisplayContext.valueOf(element.getOrDefault("transform", "NONE").toString().toUpperCase(Locale.ENGLISH));
|
||||
Billboard billboard = Billboard.valueOf(element.getOrDefault("billboard", "FIXED").toString().toUpperCase(Locale.ENGLISH));
|
||||
FurnitureElement furnitureElement = new FurnitureElement(Key.of(key), billboard, transform,
|
||||
MiscUtils.getVector3f(element.getOrDefault("scale", "1")),
|
||||
MiscUtils.getVector3f(element.getOrDefault("translation", "0")),
|
||||
MiscUtils.getVector3f(element.getOrDefault("position", "0")),
|
||||
MiscUtils.getQuaternionf(element.getOrDefault("rotation", "0"))
|
||||
);
|
||||
elements.add(furnitureElement);
|
||||
}
|
||||
List<Map<String, Object>> hitboxConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("hitboxes", List.of());
|
||||
List<HitBox> hitboxes = new ArrayList<>();
|
||||
for (Map<String, Object> config : hitboxConfigs) {
|
||||
List<String> seats = (List<String>) config.getOrDefault("seats", List.of());
|
||||
Seat[] seatArray = seats.stream()
|
||||
.map(arg -> {
|
||||
String[] split = arg.split(" ");
|
||||
if (split.length == 1) return new Seat(MiscUtils.getVector3f(split[0]), 0, false);
|
||||
return new Seat(MiscUtils.getVector3f(split[0]), Float.parseFloat(split[1]), true);
|
||||
})
|
||||
.toArray(Seat[]::new);
|
||||
Vector3f position = MiscUtils.getVector3f(config.getOrDefault("position", "0"));
|
||||
float width = MiscUtils.getAsFloat(config.getOrDefault("width", "1"));
|
||||
float height = MiscUtils.getAsFloat(config.getOrDefault("height", "1"));
|
||||
HitBox hitBox = new HitBox(
|
||||
position,
|
||||
new Vector3f(width, height, width),
|
||||
seatArray,
|
||||
(boolean) config.getOrDefault("interactive", true)
|
||||
);
|
||||
hitboxes.add(hitBox);
|
||||
}
|
||||
if (hitboxes.isEmpty()) {
|
||||
hitboxes.add(new HitBox(
|
||||
new Vector3f(),
|
||||
new Vector3f(1,1,1),
|
||||
new Seat[0],
|
||||
true
|
||||
));
|
||||
}
|
||||
Map<String, Object> ruleSection = MiscUtils.castToMap(placementArguments.get("rules"), true);
|
||||
if (ruleSection != null) {
|
||||
RotationRule rotationRule = Optional.ofNullable((String) ruleSection.get("rotation"))
|
||||
.map(it -> RotationRule.valueOf(it.toUpperCase(Locale.ENGLISH)))
|
||||
.orElse(RotationRule.ANY);
|
||||
AlignmentRule alignmentRule = Optional.ofNullable((String) ruleSection.get("alignment"))
|
||||
.map(it -> AlignmentRule.valueOf(it.toUpperCase(Locale.ENGLISH)))
|
||||
.orElse(AlignmentRule.CENTER);
|
||||
placements.put(anchorType, new CustomFurniture.Placement(
|
||||
elements.toArray(new FurnitureElement[0]),
|
||||
hitboxes.toArray(new HitBox[0]),
|
||||
rotationRule,
|
||||
alignmentRule
|
||||
));
|
||||
} else {
|
||||
placements.put(anchorType, new CustomFurniture.Placement(
|
||||
elements.toArray(new FurnitureElement[0]),
|
||||
hitboxes.toArray(new HitBox[0]),
|
||||
RotationRule.ANY,
|
||||
AlignmentRule.CENTER
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
CustomFurniture furniture = new CustomFurniture(
|
||||
id,
|
||||
FurnitureSettings.fromMap(settingsMap),
|
||||
placements,
|
||||
lootMap == null ? null : LootTable.fromMap(lootMap)
|
||||
);
|
||||
|
||||
this.byId.put(id, furniture);
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
IntSet first = this.delayedRemove[0];
|
||||
for (int i : first) {
|
||||
// unloaded furniture might be loaded again
|
||||
LoadedFurniture furniture = getLoadedFurnitureByBaseEntityId(i);
|
||||
if (furniture == null)
|
||||
this.baseEntity2SubEntities.remove(i);
|
||||
}
|
||||
first.clear();
|
||||
for (int i = 1; i < DELAYED_TICK; i++) {
|
||||
this.delayedRemove[i - 1] = this.delayedRemove[i];
|
||||
}
|
||||
this.delayedRemove[DELAYED_TICK-1] = first;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayedInit() {
|
||||
Bukkit.getPluginManager().registerEvents(this.dismountListener, this.plugin.bootstrap());
|
||||
Bukkit.getPluginManager().registerEvents(this.furnitureEventListener, this.plugin.bootstrap());
|
||||
this.tickTask = plugin.scheduler().sync().runRepeating(this::tick, 1, 1);
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
List<Entity> entities = world.getEntities();
|
||||
for (Entity entity : entities) {
|
||||
handleEntityLoadEarly(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
this.byId.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
HandlerList.unregisterAll(this.dismountListener);
|
||||
HandlerList.unregisterAll(this.furnitureEventListener);
|
||||
if (tickTask != null && !tickTask.cancelled()) {
|
||||
tickTask.cancel();
|
||||
}
|
||||
unload();
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
Entity vehicle = player.getVehicle();
|
||||
if (vehicle != null) {
|
||||
tryLeavingSeat(player, vehicle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CustomFurniture> getFurniture(Key id) {
|
||||
return Optional.ofNullable(this.byId.get(id));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public int[] getSubEntityIdsByBaseEntityId(int entityId) {
|
||||
return this.baseEntity2SubEntities.get(entityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFurnitureBaseEntity(int entityId) {
|
||||
return this.furnitureByBaseEntityId.containsKey(entityId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LoadedFurniture getLoadedFurnitureByBaseEntityId(int entityId) {
|
||||
return this.furnitureByBaseEntityId.get(entityId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LoadedFurniture getLoadedFurnitureByInteractionEntityId(int entityId) {
|
||||
return this.furnitureByInteractionEntityId.get(entityId);
|
||||
}
|
||||
|
||||
protected void handleEntityUnload(Entity entity) {
|
||||
int id = entity.getEntityId();
|
||||
LoadedFurniture furniture = this.furnitureByBaseEntityId.remove(id);
|
||||
if (furniture != null) {
|
||||
furniture.destroySeats();
|
||||
for (int sub : furniture.interactionEntityIds()) {
|
||||
this.furnitureByInteractionEntityId.remove(sub);
|
||||
}
|
||||
this.delayedRemove[DELAYED_TICK-1].add(id);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // just a misleading name `getTrackedPlayers`
|
||||
protected void handleEntityLoadLate(Entity entity) {
|
||||
if (entity instanceof ItemDisplay display) {
|
||||
String id = entity.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
|
||||
if (id == null) return;
|
||||
Key key = Key.of(id);
|
||||
Optional<CustomFurniture> optionalFurniture = getFurniture(key);
|
||||
if (optionalFurniture.isEmpty()) return;
|
||||
CustomFurniture customFurniture = optionalFurniture.get();
|
||||
LoadedFurniture previous = this.furnitureByBaseEntityId.get(display.getEntityId());
|
||||
if (previous != null) return;
|
||||
LoadedFurniture furniture = addNewFurniture(display, customFurniture, getAnchorType(entity, customFurniture));
|
||||
for (Player player : display.getTrackedPlayers()) {
|
||||
this.plugin.networkManager().sendPacket(player, furniture.spawnPacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleEntityLoadEarly(Entity entity) {
|
||||
if (entity instanceof ItemDisplay display) {
|
||||
String id = entity.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
|
||||
if (id == null) return;
|
||||
Key key = Key.of(id);
|
||||
Optional<CustomFurniture> optionalFurniture = getFurniture(key);
|
||||
if (optionalFurniture.isPresent()) {
|
||||
CustomFurniture customFurniture = optionalFurniture.get();
|
||||
LoadedFurniture previous = this.furnitureByBaseEntityId.get(display.getEntityId());
|
||||
if (previous != null) return;
|
||||
addNewFurniture(display, customFurniture, getAnchorType(entity, customFurniture));
|
||||
return;
|
||||
}
|
||||
// Remove the entity if it's not a valid furniture
|
||||
if (ConfigManager.removeInvalidFurniture()) {
|
||||
if (ConfigManager.furnitureToRemove().isEmpty() || ConfigManager.furnitureToRemove().contains(id)) {
|
||||
entity.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AnchorType getAnchorType(Entity baseEntity, CustomFurniture furniture) {
|
||||
String anchorType = baseEntity.getPersistentDataContainer().get(FURNITURE_ANCHOR_KEY, PersistentDataType.STRING);
|
||||
if (anchorType != null) {
|
||||
try {
|
||||
AnchorType unverified = AnchorType.valueOf(anchorType);
|
||||
if (furniture.isAllowedPlacement(unverified)) {
|
||||
return unverified;
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
AnchorType anchorTypeEnum = furniture.getAnyPlacement();
|
||||
baseEntity.getPersistentDataContainer().set(FURNITURE_ANCHOR_KEY, PersistentDataType.STRING, anchorTypeEnum.name());
|
||||
return anchorTypeEnum;
|
||||
}
|
||||
|
||||
private synchronized LoadedFurniture addNewFurniture(ItemDisplay display, CustomFurniture furniture, AnchorType anchorType) {
|
||||
LoadedFurniture loadedFurniture = new LoadedFurniture(display, furniture, anchorType);
|
||||
this.furnitureByBaseEntityId.put(loadedFurniture.baseEntityId(), loadedFurniture);
|
||||
this.baseEntity2SubEntities.put(loadedFurniture.baseEntityId(), loadedFurniture.subEntityIds());
|
||||
for (int entityId : loadedFurniture.interactionEntityIds()) {
|
||||
this.furnitureByInteractionEntityId.put(entityId, loadedFurniture);
|
||||
}
|
||||
return loadedFurniture;
|
||||
}
|
||||
|
||||
protected void handleDismount(Player player, Entity entity) {
|
||||
if (!isSeatCarrierType(entity)) return;
|
||||
Location location = entity.getLocation();
|
||||
plugin.scheduler().sync().runDelayed(() -> tryLeavingSeat(player, entity), player.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4);
|
||||
}
|
||||
|
||||
protected void tryLeavingSeat(@NotNull Player player, @NotNull Entity vehicle) {
|
||||
Integer baseFurniture = vehicle.getPersistentDataContainer().get(FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER);
|
||||
if (baseFurniture == null) return;
|
||||
vehicle.remove();
|
||||
LoadedFurniture furniture = getLoadedFurnitureByBaseEntityId(baseFurniture);
|
||||
if (furniture == null) {
|
||||
return;
|
||||
}
|
||||
String vector3f = vehicle.getPersistentDataContainer().get(BukkitFurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY, PersistentDataType.STRING);
|
||||
if (vector3f == null) {
|
||||
plugin.logger().warn("Failed to get vector3f for player " + player.getName() + "'s seat");
|
||||
return;
|
||||
}
|
||||
Vector3f seatPos = MiscUtils.getVector3f(vector3f);
|
||||
if (!furniture.releaseSeat(seatPos)) {
|
||||
plugin.logger().warn("Failed to release seat " + seatPos + " for player " + player.getName());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isSeatCarrierType(Entity entity) {
|
||||
return (entity instanceof ArmorStand || entity instanceof ItemDisplay);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.momirealms.craftengine.bukkit.entity.furniture;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDismountEvent;
|
||||
|
||||
public class DismountListener1_20_3 implements Listener {
|
||||
private final BukkitFurnitureManager manager;
|
||||
|
||||
public DismountListener1_20_3(final BukkitFurnitureManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onDismount(EntityDismountEvent event) {
|
||||
if (event.getEntity() instanceof Player player) {
|
||||
this.manager.handleDismount(player, event.getDismounted());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package net.momirealms.craftengine.bukkit.entity.furniture;
|
||||
|
||||
import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent;
|
||||
import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent;
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.world.ChunkUnloadEvent;
|
||||
import org.bukkit.event.world.EntitiesLoadEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FurnitureEventListener implements Listener {
|
||||
private final BukkitFurnitureManager manager;
|
||||
|
||||
public FurnitureEventListener(final BukkitFurnitureManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load Entities
|
||||
*/
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onEntitiesLoadEarly(EntitiesLoadEvent event) {
|
||||
List<Entity> entities = event.getEntities();
|
||||
for (Entity entity : entities) {
|
||||
this.manager.handleEntityLoadEarly(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onWorldLoad(WorldLoadEvent event) {
|
||||
List<Entity> entities = event.getWorld().getEntities();
|
||||
for (Entity entity : entities) {
|
||||
this.manager.handleEntityLoadEarly(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onEntityLoad(EntityAddToWorldEvent event) {
|
||||
this.manager.handleEntityLoadLate(event.getEntity());
|
||||
}
|
||||
|
||||
/*
|
||||
* Unload Entities
|
||||
*/
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onChunkUnload(ChunkUnloadEvent event) {
|
||||
Entity[] entities = event.getChunk().getEntities();
|
||||
for (Entity entity : entities) {
|
||||
this.manager.handleEntityUnload(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onWorldUnload(WorldUnloadEvent event) {
|
||||
List<Entity> entities = event.getWorld().getEntities();
|
||||
for (Entity entity : entities) {
|
||||
this.manager.handleEntityUnload(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onEntityUnload(EntityRemoveFromWorldEvent event) {
|
||||
this.manager.handleEntityUnload(event.getEntity());
|
||||
}
|
||||
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Entity entity = player.getVehicle();
|
||||
if (entity == null) return;
|
||||
if (this.manager.isSeatCarrierType(entity)) {
|
||||
this.manager.tryLeavingSeat(player, entity);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Entity entity = player.getVehicle();
|
||||
if (entity == null) return;
|
||||
if (this.manager.isSeatCarrierType(entity)) {
|
||||
this.manager.tryLeavingSeat(player, entity);
|
||||
}
|
||||
}
|
||||
|
||||
// do not allow players to put item on seats
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onInteractArmorStand(PlayerInteractAtEntityEvent event) {
|
||||
Entity clicked = event.getRightClicked();
|
||||
if (clicked instanceof ArmorStand armorStand) {
|
||||
Integer baseFurniture = armorStand.getPersistentDataContainer().get(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER);
|
||||
if (baseFurniture == null) return;
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
package net.momirealms.craftengine.bukkit.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.entity.DisplayEntityData;
|
||||
import net.momirealms.craftengine.bukkit.entity.InteractionEntityData;
|
||||
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.craftengine.bukkit.util.EntityUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.entity.furniture.*;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.util.ArrayUtils;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.QuaternionUtils;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.ItemDisplay;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
|
||||
public class LoadedFurniture {
|
||||
private final Key id;
|
||||
private final CustomFurniture furniture;
|
||||
private final AnchorType anchorType;
|
||||
private final Map<Integer, FurnitureElement> elements;
|
||||
private final Map<Integer, HitBox> hitBoxes;
|
||||
// location
|
||||
private Location location;
|
||||
// cached spawn packet
|
||||
private Object cachedSpawnPacket;
|
||||
// base entity
|
||||
private final WeakReference<Entity> baseEntity;
|
||||
private final int baseEntityId;
|
||||
// includes elements + interactions
|
||||
private final int[] subEntityIds;
|
||||
// interactions
|
||||
private final int[] interactionEntityIds;
|
||||
// seats
|
||||
private final Set<Vector3f> occupiedSeats = Collections.synchronizedSet(new HashSet<>());
|
||||
private final Vector<Entity> seats = new Vector<>();
|
||||
|
||||
public LoadedFurniture(Entity baseEntity,
|
||||
CustomFurniture furniture,
|
||||
AnchorType anchorType) {
|
||||
this.id = furniture.id();
|
||||
this.baseEntityId = baseEntity.getEntityId();
|
||||
this.anchorType = anchorType;
|
||||
this.location = baseEntity.getLocation();
|
||||
this.baseEntity = new WeakReference<>(baseEntity);
|
||||
this.furniture = furniture;
|
||||
this.hitBoxes = new HashMap<>();
|
||||
this.elements = new HashMap<>();
|
||||
List<Integer> entityIds = new ArrayList<>();
|
||||
List<Integer> interactionEntityIds = new ArrayList<>();
|
||||
CustomFurniture.Placement placement = furniture.getPlacement(anchorType);
|
||||
for (FurnitureElement element : placement.elements()) {
|
||||
int entityId = Reflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
|
||||
entityIds.add(entityId);
|
||||
this.elements.put(entityId, element);
|
||||
}
|
||||
for (HitBox hitBox : placement.hitbox()) {
|
||||
int entityId = Reflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
|
||||
entityIds.add(entityId);
|
||||
interactionEntityIds.add(entityId);
|
||||
this.hitBoxes.put(entityId, hitBox);
|
||||
}
|
||||
this.subEntityIds = new int[entityIds.size()];
|
||||
for (int i = 0; i < entityIds.size(); ++i) {
|
||||
this.subEntityIds[i] = entityIds.get(i);
|
||||
}
|
||||
this.interactionEntityIds = new int[interactionEntityIds.size()];
|
||||
for (int i = 0; i < interactionEntityIds.size(); ++i) {
|
||||
this.interactionEntityIds[i] = interactionEntityIds.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetSpawnPackets() {
|
||||
try {
|
||||
List<Object> packets = new ArrayList<>();
|
||||
for (Map.Entry<Integer, FurnitureElement> entry : elements.entrySet()) {
|
||||
int entityId = entry.getKey();
|
||||
FurnitureElement element = entry.getValue();
|
||||
Item<ItemStack> item = BukkitItemManager.instance().createWrappedItem(element.item(), null);
|
||||
if (item == null) {
|
||||
CraftEngine.instance().logger().warn("Failed to create furniture element for " + id + " because item " + element.item() + " not found");
|
||||
continue;
|
||||
}
|
||||
item.load();
|
||||
|
||||
Vector3f offset = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate().transform(new Vector3f(element.offset()));
|
||||
Object addEntityPacket = Reflections.constructor$ClientboundAddEntityPacket.newInstance(
|
||||
entityId, UUID.randomUUID(), this.location.getX() + offset.x, this.location.getY() + offset.y, this.location.getZ() - offset.z, 0, this.location.getYaw(),
|
||||
Reflections.instance$EntityType$ITEM_DISPLAY, 0, Reflections.instance$Vec3$Zero, 0
|
||||
);
|
||||
|
||||
ArrayList<Object> values = new ArrayList<>();
|
||||
DisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.getLiteralObject(), values);
|
||||
DisplayEntityData.Scale.addEntityDataIfNotDefaultValue(element.scale(), values);
|
||||
DisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(element.rotation(), values);
|
||||
DisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(element.billboard().id(), values);
|
||||
DisplayEntityData.Translation.addEntityDataIfNotDefaultValue(element.translation(), values);
|
||||
DisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(element.transform().id(), values);
|
||||
Object setDataPacket = Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId, values);
|
||||
|
||||
packets.add(addEntityPacket);
|
||||
packets.add(setDataPacket);
|
||||
}
|
||||
for (Map.Entry<Integer, HitBox> entry : hitBoxes.entrySet()) {
|
||||
int entityId = entry.getKey();
|
||||
HitBox hitBox = entry.getValue();
|
||||
Vector3f offset = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate().transform(new Vector3f(hitBox.offset()));
|
||||
Object addEntityPacket = Reflections.constructor$ClientboundAddEntityPacket.newInstance(
|
||||
entityId, UUID.randomUUID(), this.location.getX() + offset.x, this.location.getY() + offset.y, this.location.getZ() - offset.z, 0, this.location.getYaw(),
|
||||
Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0
|
||||
);
|
||||
|
||||
ArrayList<Object> values = new ArrayList<>();
|
||||
InteractionEntityData.Height.addEntityDataIfNotDefaultValue(hitBox.size().y, values);
|
||||
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(hitBox.size().x, values);
|
||||
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(hitBox.responsive(), values);
|
||||
Object setDataPacket = Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId, values);
|
||||
|
||||
packets.add(addEntityPacket);
|
||||
packets.add(setDataPacket);
|
||||
}
|
||||
this.cachedSpawnPacket = Reflections.constructor$ClientboundBundlePacket.newInstance(packets);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to init spawn packets for furniture " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Location location() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
public void teleport(@NotNull Location location) {
|
||||
if (location.equals(this.location)) return;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object spawnPacket() {
|
||||
if (this.cachedSpawnPacket == null) {
|
||||
this.resetSpawnPackets();
|
||||
}
|
||||
return this.cachedSpawnPacket;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Entity baseEntity() {
|
||||
Entity entity = baseEntity.get();
|
||||
if (entity == null) {
|
||||
throw new RuntimeException("Base entity not found");
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return baseEntity().isValid();
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
this.baseEntity().remove();
|
||||
for (Entity entity : this.seats) {
|
||||
for (Entity passenger : entity.getPassengers()) {
|
||||
entity.removePassenger(passenger);
|
||||
}
|
||||
entity.remove();
|
||||
}
|
||||
this.seats.clear();
|
||||
}
|
||||
|
||||
public void destroySeats() {
|
||||
for (Entity entity : this.seats) {
|
||||
entity.remove();
|
||||
}
|
||||
this.seats.clear();
|
||||
}
|
||||
|
||||
public void addSeatEntity(Entity entity) {
|
||||
this.seats.add(entity);
|
||||
}
|
||||
|
||||
public Optional<Seat> getAvailableSeat(int clickedEntityId) {
|
||||
HitBox hitbox = this.hitBoxes.get(clickedEntityId);
|
||||
if (hitbox == null)
|
||||
return Optional.empty();
|
||||
Seat[] seats = hitbox.seats();
|
||||
if (ArrayUtils.isEmpty(seats)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
for (Seat seat : seats) {
|
||||
if (!this.occupiedSeats.contains(seat.offset())) {
|
||||
return Optional.of(seat);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Location getSeatLocation(Seat seat) {
|
||||
Vector3f offset = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate().transform(new Vector3f(seat.offset()));
|
||||
double yaw = seat.yaw() + this.location.getYaw();
|
||||
if (yaw < -180) yaw += 360;
|
||||
Location newLocation = this.location.clone();
|
||||
newLocation.setYaw((float) yaw);
|
||||
newLocation.add(offset.x, offset.y + 0.6, -offset.z);
|
||||
return newLocation;
|
||||
}
|
||||
|
||||
public boolean releaseSeat(Vector3f seat) {
|
||||
return this.occupiedSeats.remove(seat);
|
||||
}
|
||||
|
||||
public boolean occupySeat(Seat seat) {
|
||||
if (this.occupiedSeats.contains(seat.offset())) {
|
||||
return false;
|
||||
}
|
||||
this.occupiedSeats.add(seat.offset());
|
||||
return true;
|
||||
}
|
||||
|
||||
public int baseEntityId() {
|
||||
return baseEntityId;
|
||||
}
|
||||
|
||||
public int[] interactionEntityIds() {
|
||||
return interactionEntityIds;
|
||||
}
|
||||
|
||||
public int[] subEntityIds() {
|
||||
return this.subEntityIds;
|
||||
}
|
||||
|
||||
public AnchorType anchorType() {
|
||||
return anchorType;
|
||||
}
|
||||
|
||||
public Key furnitureId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public CustomFurniture furniture() {
|
||||
return furniture;
|
||||
}
|
||||
|
||||
public void mountSeat(org.bukkit.entity.Player player, Seat seat) {
|
||||
Location location = this.getSeatLocation(seat);
|
||||
Entity seatEntity = seat.limitPlayerRotation() ?
|
||||
EntityUtils.spawnEntity(player.getWorld(), VersionHelper.isVersionNewerThan1_20_2() ? location.subtract(0,0.9875,0) : location.subtract(0,0.990625,0), EntityType.ARMOR_STAND, entity -> {
|
||||
ArmorStand armorStand = (ArmorStand) entity;
|
||||
if (VersionHelper.isVersionNewerThan1_21_3()) {
|
||||
Objects.requireNonNull(armorStand.getAttribute(Attribute.MAX_HEALTH)).setBaseValue(0.01);
|
||||
} else {
|
||||
LegacyAttributeUtils.setMaxHealth(armorStand);
|
||||
}
|
||||
armorStand.setSmall(true);
|
||||
armorStand.setInvisible(true);
|
||||
armorStand.setSilent(true);
|
||||
armorStand.setInvulnerable(true);
|
||||
armorStand.setArms(false);
|
||||
armorStand.setCanTick(false);
|
||||
armorStand.setAI(false);
|
||||
armorStand.setGravity(false);
|
||||
armorStand.setPersistent(false);
|
||||
armorStand.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER, this.baseEntityId());
|
||||
armorStand.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY, PersistentDataType.STRING, seat.offset().x + ", " + seat.offset().y + ", " + seat.offset().z);
|
||||
}) :
|
||||
EntityUtils.spawnEntity(player.getWorld(), VersionHelper.isVersionNewerThan1_20_2() ? location : location.subtract(0,0.25,0), EntityType.ITEM_DISPLAY, entity -> {
|
||||
ItemDisplay itemDisplay = (ItemDisplay) entity;
|
||||
itemDisplay.setPersistent(false);
|
||||
itemDisplay.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER, this.baseEntityId());
|
||||
itemDisplay.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY, PersistentDataType.STRING, seat.offset().x + ", " + seat.offset().y + ", " + seat.offset().z);
|
||||
});
|
||||
this.addSeatEntity(seatEntity);
|
||||
seatEntity.addPassenger(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package net.momirealms.craftengine.bukkit.item;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.MaterialUtils;
|
||||
import net.momirealms.craftengine.core.item.CustomItem;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.item.ItemBuildContext;
|
||||
import net.momirealms.craftengine.core.item.ItemSettings;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
|
||||
import net.momirealms.craftengine.core.item.modifier.ItemModifier;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class BukkitCustomItem implements CustomItem<ItemStack> {
|
||||
private final Key id;
|
||||
private final Key materialKey;
|
||||
private final Material material;
|
||||
private final List<ItemModifier<ItemStack>> modifiers;
|
||||
private final List<ItemBehavior> behavior;
|
||||
private final ItemSettings settings;
|
||||
|
||||
public BukkitCustomItem(Key id, Key materialKey, Material material, List<ItemModifier<ItemStack>> modifiers, List<ItemBehavior> behavior, ItemSettings settings) {
|
||||
this.id = id;
|
||||
this.material = material;
|
||||
this.modifiers = modifiers;
|
||||
this.behavior = behavior;
|
||||
this.materialKey = materialKey;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key material() {
|
||||
return materialKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemModifier<ItemStack>> modifiers() {
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack buildItemStack(ItemBuildContext context, int count) {
|
||||
ItemStack item = new ItemStack(material);
|
||||
if (this.modifiers.isEmpty()) {
|
||||
return item;
|
||||
}
|
||||
Item<ItemStack> wrapped = BukkitCraftEngine.instance().itemManager().wrap(item);
|
||||
wrapped.count(count);
|
||||
for (ItemModifier<ItemStack> modifier : this.modifiers) {
|
||||
modifier.apply(wrapped, context);
|
||||
}
|
||||
return wrapped.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemSettings settings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<ItemStack> buildItem(ItemBuildContext context) {
|
||||
ItemStack item = new ItemStack(material);
|
||||
Item<ItemStack> wrapped = BukkitCraftEngine.instance().itemManager().wrap(item);
|
||||
for (ItemModifier<ItemStack> modifier : modifiers()) {
|
||||
modifier.apply(wrapped, context);
|
||||
}
|
||||
wrapped.load();
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<ItemBehavior> behaviors() {
|
||||
return this.behavior;
|
||||
}
|
||||
|
||||
public static Builder<ItemStack> builder() {
|
||||
return new BuilderImpl();
|
||||
}
|
||||
|
||||
public static class BuilderImpl implements Builder<ItemStack> {
|
||||
private Key id;
|
||||
private Material material;
|
||||
private Key materialKey;
|
||||
private List<ItemBehavior> behavior = List.of();
|
||||
private ItemSettings settings = ItemSettings.of();
|
||||
private final List<ItemModifier<ItemStack>> modifiers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public Builder<ItemStack> id(Key id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<ItemStack> material(Key material) {
|
||||
this.materialKey = material;
|
||||
this.material = MaterialUtils.getMaterial(material.value());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<ItemStack> modifier(ItemModifier<ItemStack> modifier) {
|
||||
this.modifiers.add(modifier);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<ItemStack> modifiers(List<ItemModifier<ItemStack>> list) {
|
||||
this.modifiers.addAll(list);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<ItemStack> behavior(ItemBehavior behavior) {
|
||||
this.behavior= List.of(behavior);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<ItemStack> behavior(List<ItemBehavior> behaviors) {
|
||||
this.behavior = behaviors;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<ItemStack> settings(ItemSettings settings) {
|
||||
this.settings = settings;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomItem<ItemStack> build() {
|
||||
this.modifiers.addAll(this.settings.modifiers());
|
||||
return new BukkitCustomItem(id, materialKey, material, Collections.unmodifiableList(modifiers), behavior, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,615 @@
|
||||
package net.momirealms.craftengine.bukkit.item;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.item.behavior.AxeItemBehavior;
|
||||
import net.momirealms.craftengine.bukkit.item.behavior.BoneMealBehavior;
|
||||
import net.momirealms.craftengine.bukkit.item.behavior.BucketItemBehavior;
|
||||
import net.momirealms.craftengine.bukkit.item.behavior.WaterBucketItemBehavior;
|
||||
import net.momirealms.craftengine.bukkit.item.factory.BukkitItemFactory;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.ItemUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.MaterialUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.item.*;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehaviors;
|
||||
import net.momirealms.craftengine.core.item.modifier.CustomModelDataModifier;
|
||||
import net.momirealms.craftengine.core.item.modifier.IdModifier;
|
||||
import net.momirealms.craftengine.core.item.modifier.ItemModelModifier;
|
||||
import net.momirealms.craftengine.core.pack.LegacyOverridesModel;
|
||||
import net.momirealms.craftengine.core.pack.Pack;
|
||||
import net.momirealms.craftengine.core.pack.model.*;
|
||||
import net.momirealms.craftengine.core.pack.model.generator.ModelGeneration;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
|
||||
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
|
||||
import net.momirealms.craftengine.core.registry.Holder;
|
||||
import net.momirealms.craftengine.core.registry.WritableRegistry;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import net.momirealms.craftengine.core.util.ResourceKey;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import net.momirealms.craftengine.core.util.context.ContextHolder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.incendo.cloud.type.Either;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class BukkitItemManager extends AbstractItemManager<ItemStack> {
|
||||
private static final Map<Key, List<ItemBehavior>> VANILLA_ITEM_EXTRA_BEHAVIORS = new HashMap<>();
|
||||
private static final List<Key> VANILLA_ITEMS = new ArrayList<>();
|
||||
|
||||
static {
|
||||
registerVanillaItemExtraBehavior(AxeItemBehavior.INSTANCE, ItemKeys.AXES);
|
||||
registerVanillaItemExtraBehavior(WaterBucketItemBehavior.INSTANCE, ItemKeys.WATER_BUCKETS);
|
||||
registerVanillaItemExtraBehavior(BucketItemBehavior.INSTANCE, ItemKeys.BUCKET);
|
||||
registerVanillaItemExtraBehavior(BoneMealBehavior.INSTANCE, ItemKeys.BONE_MEAL);
|
||||
}
|
||||
|
||||
private static void registerVanillaItemExtraBehavior(ItemBehavior behavior, Key... items) {
|
||||
for (Key key : items) {
|
||||
VANILLA_ITEM_EXTRA_BEHAVIORS.computeIfAbsent(key, k -> new ArrayList<>()).add(behavior);
|
||||
}
|
||||
}
|
||||
|
||||
private static BukkitItemManager instance;
|
||||
private final BukkitItemFactory factory;
|
||||
private final Map<Key, TreeSet<LegacyOverridesModel>> legacyOverrides;
|
||||
private final Map<Key, TreeMap<Integer, ItemModel>> modernOverrides;
|
||||
private final BukkitCraftEngine plugin;
|
||||
private final ItemEventListener itemEventListener;
|
||||
private final DebugStickListener debugStickListener;
|
||||
private final Map<Key, List<Holder<Key>>> vanillaItemTags;
|
||||
private final Map<Key, List<Holder<Key>>> customItemTags;
|
||||
private final Map<Key, Map<Integer, Key>> cmdConflictChecker;
|
||||
private final Map<Key, ItemModel> modernItemModels1_21_4;
|
||||
private final Map<Key, List<LegacyOverridesModel>> modernItemModels1_21_2;
|
||||
// Cached command suggestions
|
||||
private final List<Suggestion> cachedSuggestions = new ArrayList<>();
|
||||
|
||||
public BukkitItemManager(BukkitCraftEngine plugin) {
|
||||
super(plugin);
|
||||
this.plugin = plugin;
|
||||
this.factory = BukkitItemFactory.create(plugin);
|
||||
this.legacyOverrides = new HashMap<>();
|
||||
this.modernOverrides = new HashMap<>();
|
||||
this.vanillaItemTags = new HashMap<>();
|
||||
this.customItemTags = new HashMap<>();
|
||||
this.cmdConflictChecker = new HashMap<>();
|
||||
this.modernItemModels1_21_4 = new HashMap<>();
|
||||
this.modernItemModels1_21_2 = new HashMap<>();
|
||||
this.itemEventListener = new ItemEventListener(plugin);
|
||||
this.debugStickListener = new DebugStickListener(plugin);
|
||||
this.registerAllVanillaItems();
|
||||
instance = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayedInit() {
|
||||
Bukkit.getPluginManager().registerEvents(this.itemEventListener, plugin.bootstrap());
|
||||
Bukkit.getPluginManager().registerEvents(this.debugStickListener, plugin.bootstrap());
|
||||
}
|
||||
|
||||
public static BukkitItemManager instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BuildableItem<ItemStack>> getVanillaItem(Key key) {
|
||||
Material material = Registry.MATERIAL.get(Objects.requireNonNull(NamespacedKey.fromString(key.toString())));
|
||||
if (material == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(new CloneableConstantItem(key, new ItemStack(material)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Holder<Key>> tagToItems(Key tag) {
|
||||
List<Holder<Key>> items = new ArrayList<>();
|
||||
List<Holder<Key>> holders = vanillaItemTags.get(tag);
|
||||
if (holders != null) {
|
||||
items.addAll(holders);
|
||||
}
|
||||
List<Holder<Key>> customItems = customItemTags.get(tag);
|
||||
if (customItems != null) {
|
||||
items.addAll(customItems);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Holder<Key>> tagToVanillaItems(Key tag) {
|
||||
return this.vanillaItemTags.getOrDefault(tag, List.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Holder<Key>> tagToCustomItems(Key tag) {
|
||||
return this.customItemTags.getOrDefault(tag, List.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fuelTime(ItemStack itemStack) {
|
||||
if (ItemUtils.isEmpty(itemStack)) return 0;
|
||||
Optional<CustomItem<ItemStack>> customItem = wrap(itemStack).getCustomItem();
|
||||
return customItem.map(it -> it.settings().fuelTime()).orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fuelTime(Key id) {
|
||||
return getCustomItem(id).map(it -> it.settings().fuelTime()).orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Suggestion> cachedSuggestions() {
|
||||
return this.cachedSuggestions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
super.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
super.unload();
|
||||
this.cachedSuggestions.clear();
|
||||
this.legacyOverrides.clear();
|
||||
this.modernOverrides.clear();
|
||||
this.customItemTags.clear();
|
||||
this.cmdConflictChecker.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
unload();
|
||||
HandlerList.unregisterAll(this.itemEventListener);
|
||||
HandlerList.unregisterAll(this.debugStickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack buildCustomItemStack(Key id, Player player) {
|
||||
return Optional.ofNullable(customItems.get(id)).map(it -> it.buildItemStack(new ItemBuildContext(player, ContextHolder.EMPTY), 1)).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack buildItemStack(Key id, @Nullable Player player) {
|
||||
return Optional.ofNullable(buildCustomItemStack(id, player)).orElseGet(() -> createVanillaItemStack(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<ItemStack> createCustomWrappedItem(Key id, Player player) {
|
||||
return Optional.ofNullable(customItems.get(id)).map(it -> it.buildItem(player)).orElse(null);
|
||||
}
|
||||
|
||||
private ItemStack createVanillaItemStack(Key id) {
|
||||
NamespacedKey key = NamespacedKey.fromString(id.toString());
|
||||
if (key == null) {
|
||||
this.plugin.logger().warn(id + " is not a valid namespaced key");
|
||||
return new ItemStack(Material.AIR);
|
||||
}
|
||||
Material material = Registry.MATERIAL.get(key);
|
||||
if (material == null) {
|
||||
this.plugin.logger().warn(id + " is not a valid material");
|
||||
return new ItemStack(Material.AIR);
|
||||
}
|
||||
return new ItemStack(material);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<ItemStack> createWrappedItem(Key id, @Nullable Player player) {
|
||||
return Optional.ofNullable(customItems.get(id)).map(it -> it.buildItem(player)).orElseGet(() -> {
|
||||
ItemStack itemStack = createVanillaItemStack(id);
|
||||
return wrap(itemStack);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<ItemStack> wrap(ItemStack itemStack) {
|
||||
if (ItemUtils.isEmpty(itemStack)) return null;
|
||||
return this.factory.wrap(itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key itemId(ItemStack itemStack) {
|
||||
Item<ItemStack> wrapped = wrap(itemStack);
|
||||
return wrapped.id();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key customItemId(ItemStack itemStack) {
|
||||
Item<ItemStack> wrapped = wrap(itemStack);
|
||||
if (!wrapped.hasTag(IdModifier.CRAFT_ENGINE_ID)) return null;
|
||||
return wrapped.id();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<List<ItemBehavior>> getItemBehavior(Key key) {
|
||||
Optional<CustomItem<ItemStack>> customItemOptional = getCustomItem(key);
|
||||
if (customItemOptional.isPresent()) {
|
||||
CustomItem<ItemStack> customItem = customItemOptional.get();
|
||||
Key vanillaMaterial = customItem.material();
|
||||
List<ItemBehavior> behavior = VANILLA_ITEM_EXTRA_BEHAVIORS.get(vanillaMaterial);
|
||||
if (behavior != null) {
|
||||
return Optional.of(Stream.concat(customItem.behaviors().stream(), behavior.stream()).toList());
|
||||
} else {
|
||||
return Optional.of(List.copyOf(customItem.behaviors()));
|
||||
}
|
||||
} else {
|
||||
List<ItemBehavior> behavior = VANILLA_ITEM_EXTRA_BEHAVIORS.get(key);
|
||||
if (behavior != null) {
|
||||
return Optional.of(List.copyOf(behavior));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Key> items() {
|
||||
return new ArrayList<>(customItems.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
// just register and recipes
|
||||
Holder.Reference<Key> holder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(id)
|
||||
.orElseGet(() -> ((WritableRegistry<Key>) BuiltInRegistries.OPTIMIZED_ITEM_ID)
|
||||
.register(new ResourceKey<>(BuiltInRegistries.OPTIMIZED_ITEM_ID.key().location(), id), id));
|
||||
|
||||
String materialStringId = (String) section.get("material");
|
||||
Material material = MaterialUtils.getMaterial(materialStringId);
|
||||
if (material == null) {
|
||||
plugin.logger().warn(path, "material " + Optional.ofNullable(materialStringId).map(it -> it + " ").orElse("") + "does not exist for item " + id);
|
||||
return;
|
||||
}
|
||||
Key materialId = Key.of(material.getKey().namespace(), material.getKey().value());
|
||||
|
||||
int customModelData = MiscUtils.getAsInt(section.getOrDefault("custom-model-data", 0));
|
||||
Key itemModelKey = null;
|
||||
|
||||
CustomItem.Builder<ItemStack> itemBuilder = BukkitCustomItem.builder().id(id).material(materialId);
|
||||
itemBuilder.modifier(new IdModifier<>(id));
|
||||
|
||||
// Sets some basic model info
|
||||
if (customModelData != 0) {
|
||||
itemBuilder.modifier(new CustomModelDataModifier<>(customModelData));
|
||||
} else if (section.containsKey("model") && VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
// check server version here because components require 1.21.2+
|
||||
// customize or use the id
|
||||
itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString());
|
||||
itemBuilder.modifier(new ItemModelModifier<>(itemModelKey));
|
||||
}
|
||||
|
||||
// Get item behaviors
|
||||
Object behaviorConfig = section.get("behavior");
|
||||
if (behaviorConfig instanceof List<?>) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> behavior = (List<Map<String, Object>>) behaviorConfig;
|
||||
List<ItemBehavior> behaviors = new ArrayList<>();
|
||||
for (Map<String, Object> behaviorMap : behavior) {
|
||||
behaviors.add(ItemBehaviors.fromMap(pack, path, id, behaviorMap));
|
||||
}
|
||||
itemBuilder.behavior(behaviors);
|
||||
} else if (behaviorConfig instanceof Map<?, ?>) {
|
||||
Map<String, Object> behaviorSection = MiscUtils.castToMap(section.get("behavior"), true);
|
||||
if (behaviorSection != null) {
|
||||
itemBuilder.behavior(ItemBehaviors.fromMap(pack, path, id, behaviorSection));
|
||||
}
|
||||
}
|
||||
|
||||
// Get item data
|
||||
Map<String, Object> dataSection = MiscUtils.castToMap(section.get("data"), true);
|
||||
if (dataSection != null) {
|
||||
for (Map.Entry<String, Object> dataEntry : dataSection.entrySet()) {
|
||||
Optional.ofNullable(dataFunctions.get(dataEntry.getKey())).ifPresent(function -> {
|
||||
try {
|
||||
itemBuilder.modifier(function.apply(dataEntry.getValue()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.logger().warn("Invalid data format", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (section.containsKey("settings")) {
|
||||
Map<String, Object> settings = MiscUtils.castToMap(section.get("settings"), false);
|
||||
itemBuilder.settings(ItemSettings.fromMap(settings));
|
||||
}
|
||||
|
||||
CustomItem<ItemStack> customItem = itemBuilder.build();
|
||||
this.customItems.put(id, customItem);
|
||||
this.cachedSuggestions.add(Suggestion.suggestion(id.toString()));
|
||||
|
||||
// regitser tags
|
||||
Set<Key> tags = customItem.settings().tags();
|
||||
for (Key tag : tags) {
|
||||
this.customItemTags.computeIfAbsent(tag, k -> new ArrayList<>()).add(holder);
|
||||
}
|
||||
|
||||
// model part, can be null
|
||||
// but if it exists, either custom model data or item model should be configured
|
||||
Map<String, Object> modelSection = MiscUtils.castToMap(section.get("model"), true);
|
||||
if (modelSection == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (customModelData != 0) {
|
||||
// use custom model data
|
||||
// check conflict
|
||||
Map<Integer, Key> conflict = this.cmdConflictChecker.computeIfAbsent(materialId, k -> new HashMap<>());
|
||||
if (conflict.containsKey(customModelData)) {
|
||||
plugin.logger().warn(path, "Failed to create model for " + id + " because custom-model-data " + customModelData + " already occupied by item " + conflict.get(customModelData).toString());
|
||||
return;
|
||||
}
|
||||
|
||||
conflict.put(customModelData, id);
|
||||
|
||||
// Parse models
|
||||
ItemModel model = ItemModels.fromMap(modelSection);
|
||||
for (ModelGeneration generation : model.modelsToGenerate()) {
|
||||
prepareModelGeneration(generation);
|
||||
}
|
||||
|
||||
if (ConfigManager.packMaxVersion() > 21.39f) {
|
||||
TreeMap<Integer, ItemModel> map = this.modernOverrides.computeIfAbsent(materialId, k -> new TreeMap<>());
|
||||
map.put(customModelData, model);
|
||||
}
|
||||
|
||||
if (ConfigManager.packMinVersion() < 21.39f) {
|
||||
List<LegacyOverridesModel> legacyOverridesModels = new ArrayList<>();
|
||||
processModelRecursively(model, new LinkedHashMap<>(), legacyOverridesModels, materialId, customModelData);
|
||||
TreeSet<LegacyOverridesModel> lom = this.legacyOverrides.computeIfAbsent(materialId, k -> new TreeSet<>());
|
||||
lom.addAll(legacyOverridesModels);
|
||||
}
|
||||
} else if (itemModelKey != null) {
|
||||
// use components
|
||||
ItemModel model = ItemModels.fromMap(modelSection);
|
||||
for (ModelGeneration generation : model.modelsToGenerate()) {
|
||||
prepareModelGeneration(generation);
|
||||
}
|
||||
|
||||
if (ConfigManager.packMaxVersion() > 21.39f) {
|
||||
this.modernItemModels1_21_4.put(itemModelKey, model);
|
||||
}
|
||||
|
||||
if (ConfigManager.packMaxVersion() > 21.19f && ConfigManager.packMinVersion() < 21.39f) {
|
||||
List<LegacyOverridesModel> legacyOverridesModels = new ArrayList<>();
|
||||
processModelRecursively(model, new LinkedHashMap<>(), legacyOverridesModels, materialId, 0);
|
||||
if (legacyOverridesModels.isEmpty()) {
|
||||
plugin.logger().warn(path, "Can't convert " + id + "'s model to legacy format.");
|
||||
return;
|
||||
}
|
||||
legacyOverridesModels.sort(LegacyOverridesModel::compareTo);
|
||||
this.modernItemModels1_21_2.put(itemModelKey, legacyOverridesModels);
|
||||
}
|
||||
} else {
|
||||
if (!VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
plugin.logger().warn(path, "No custom-model-data configured for " + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Key, ItemModel> modernItemModels1_21_4() {
|
||||
return this.modernItemModels1_21_4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Key, List<LegacyOverridesModel>> modernItemModels1_21_2() {
|
||||
return this.modernItemModels1_21_2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Key> vanillaItems() {
|
||||
return VANILLA_ITEMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Key, TreeSet<LegacyOverridesModel>> legacyItemOverrides() {
|
||||
return this.legacyOverrides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Key, TreeMap<Integer, ItemModel>> modernItemOverrides() {
|
||||
return this.modernOverrides;
|
||||
}
|
||||
|
||||
private void processModelRecursively(
|
||||
ItemModel currentModel,
|
||||
Map<String, Object> accumulatedPredicates,
|
||||
List<LegacyOverridesModel> resultList,
|
||||
Key materialId,
|
||||
int customModelData
|
||||
) {
|
||||
if (currentModel instanceof ConditionItemModel conditionModel) {
|
||||
handleConditionModel(conditionModel, accumulatedPredicates, resultList, materialId, customModelData);
|
||||
} else if (currentModel instanceof RangeDispatchItemModel rangeModel) {
|
||||
handleRangeModel(rangeModel, accumulatedPredicates, resultList, materialId, customModelData);
|
||||
} else if (currentModel instanceof SelectItemModel selectModel) {
|
||||
handleSelectModel(selectModel, accumulatedPredicates, resultList, materialId, customModelData);
|
||||
} else if (currentModel instanceof BaseItemModel baseModel) {
|
||||
resultList.add(new LegacyOverridesModel(
|
||||
new LinkedHashMap<>(accumulatedPredicates),
|
||||
baseModel.path(),
|
||||
customModelData
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private void handleConditionModel(
|
||||
ConditionItemModel model,
|
||||
Map<String, Object> parentPredicates,
|
||||
List<LegacyOverridesModel> resultList,
|
||||
Key materialId,
|
||||
int customModelData
|
||||
) {
|
||||
if (model.property() instanceof LegacyModelPredicate predicate) {
|
||||
String predicateId = predicate.legacyPredicateId(materialId);
|
||||
Map<String, Object> truePredicates = mergePredicates(
|
||||
parentPredicates,
|
||||
predicateId,
|
||||
predicate.toLegacyValue(true)
|
||||
);
|
||||
processModelRecursively(
|
||||
model.onTrue(),
|
||||
truePredicates,
|
||||
resultList,
|
||||
materialId,
|
||||
customModelData
|
||||
);
|
||||
Map<String, Object> falsePredicates = mergePredicates(
|
||||
parentPredicates,
|
||||
predicateId,
|
||||
predicate.toLegacyValue(false)
|
||||
);
|
||||
processModelRecursively(
|
||||
model.onFalse(),
|
||||
falsePredicates,
|
||||
resultList,
|
||||
materialId,
|
||||
customModelData
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private void handleRangeModel(
|
||||
RangeDispatchItemModel model,
|
||||
Map<String, Object> parentPredicates,
|
||||
List<LegacyOverridesModel> resultList,
|
||||
Key materialId,
|
||||
int customModelData
|
||||
) {
|
||||
if (model.property() instanceof LegacyModelPredicate predicate) {
|
||||
String predicateId = predicate.legacyPredicateId(materialId);
|
||||
for (Map.Entry<Float, ItemModel> entry : model.entries().entrySet()) {
|
||||
Map<String, Object> merged = mergePredicates(
|
||||
parentPredicates,
|
||||
predicateId,
|
||||
predicate.toLegacyValue(entry.getKey())
|
||||
);
|
||||
processModelRecursively(
|
||||
entry.getValue(),
|
||||
merged,
|
||||
resultList,
|
||||
materialId,
|
||||
customModelData
|
||||
);
|
||||
}
|
||||
if (model.fallBack() != null) {
|
||||
Map<String, Object> merged = mergePredicates(
|
||||
parentPredicates,
|
||||
predicateId,
|
||||
predicate.toLegacyValue(0f)
|
||||
);
|
||||
processModelRecursively(
|
||||
model.fallBack(),
|
||||
merged,
|
||||
resultList,
|
||||
materialId,
|
||||
customModelData
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private void handleSelectModel(
|
||||
SelectItemModel model,
|
||||
Map<String, Object> parentPredicates,
|
||||
List<LegacyOverridesModel> resultList,
|
||||
Key materialId,
|
||||
int customModelData
|
||||
) {
|
||||
if (model.property() instanceof LegacyModelPredicate predicate) {
|
||||
String predicateId = predicate.legacyPredicateId(materialId);
|
||||
for (Map.Entry<Either<String, List<String>>, ItemModel> entry : model.whenMap().entrySet()) {
|
||||
List<String> cases = entry.getKey().fallbackOrMapPrimary(List::of);
|
||||
for (String caseValue : cases) {
|
||||
Map<String, Object> merged = mergePredicates(
|
||||
parentPredicates,
|
||||
predicateId,
|
||||
predicate.toLegacyValue(caseValue)
|
||||
);
|
||||
// Additional check for crossbow
|
||||
if (materialId.equals(ItemKeys.CROSSBOW)) {
|
||||
merged = mergePredicates(
|
||||
merged,
|
||||
"charged",
|
||||
1
|
||||
);
|
||||
}
|
||||
processModelRecursively(
|
||||
entry.getValue(),
|
||||
merged,
|
||||
resultList,
|
||||
materialId,
|
||||
customModelData
|
||||
);
|
||||
}
|
||||
}
|
||||
// Additional check for crossbow
|
||||
if (model.fallBack() != null && materialId.equals(ItemKeys.CROSSBOW)) {
|
||||
Map<String, Object> merged = mergePredicates(
|
||||
parentPredicates,
|
||||
"charged",
|
||||
0
|
||||
);
|
||||
processModelRecursively(
|
||||
model.fallBack(),
|
||||
merged,
|
||||
resultList,
|
||||
materialId,
|
||||
customModelData
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> mergePredicates(
|
||||
Map<String, Object> existing,
|
||||
String newKey,
|
||||
Number newValue
|
||||
) {
|
||||
Map<String, Object> merged = new LinkedHashMap<>(existing);
|
||||
if (newKey == null) return merged;
|
||||
merged.put(newKey, newValue);
|
||||
return merged;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void registerAllVanillaItems() {
|
||||
try {
|
||||
for (Material material : Registry.MATERIAL) {
|
||||
if (material.getKey().namespace().equals("minecraft")) {
|
||||
if (!material.isLegacy() && material.isItem()) {
|
||||
Key id = Key.from(material.getKey().asString());
|
||||
VANILLA_ITEMS.add(id);
|
||||
Holder.Reference<Key> holder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(id)
|
||||
.orElseGet(() -> ((WritableRegistry<Key>) BuiltInRegistries.OPTIMIZED_ITEM_ID)
|
||||
.register(new ResourceKey<>(BuiltInRegistries.OPTIMIZED_ITEM_ID.key().location(), id), id));
|
||||
|
||||
Object resourceLocation = Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, id.namespace(), id.value());
|
||||
Object mcHolder = ((Optional<Object>) Reflections.method$Registry$getHolder1.invoke(Reflections.instance$BuiltInRegistries$ITEM, Reflections.method$ResourceKey$create.invoke(null, Reflections.instance$Registries$ITEM, resourceLocation))).get();
|
||||
Set<Object> tags = (Set<Object>) Reflections.field$Holder$Reference$tags.get(mcHolder);
|
||||
for (Object tag : tags) {
|
||||
Key tagId = Key.of(Reflections.field$TagKey$location.get(tag).toString());
|
||||
this.vanillaItemTags.computeIfAbsent(tagId, (key) -> new ArrayList<>()).add(holder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to init vanilla items", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package net.momirealms.craftengine.bukkit.item;
|
||||
|
||||
import net.momirealms.craftengine.core.item.BuildableItem;
|
||||
import net.momirealms.craftengine.core.item.ItemBuildContext;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class CloneableConstantItem implements BuildableItem<ItemStack> {
|
||||
private final ItemStack item;
|
||||
private final Key id;
|
||||
|
||||
public CloneableConstantItem(Key id, ItemStack item) {
|
||||
this.item = item;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack buildItemStack(ItemBuildContext context, int count) {
|
||||
ItemStack itemStack = this.item.clone();
|
||||
itemStack.setAmount(count);
|
||||
return itemStack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package net.momirealms.craftengine.bukkit.item;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.UpdateOption;
|
||||
import net.momirealms.craftengine.core.block.properties.Property;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.util.MCUtils;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DebugStickListener implements Listener {
|
||||
private final BukkitCraftEngine plugin;
|
||||
|
||||
public DebugStickListener(BukkitCraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onUseDebugStick(PlayerInteractEvent event) {
|
||||
Block clickedBlock = event.getClickedBlock();
|
||||
if (clickedBlock == null) return;
|
||||
ItemStack itemInHand = event.getItem();
|
||||
if (itemInHand == null) return;
|
||||
Material material = itemInHand.getType();
|
||||
if (material != Material.DEBUG_STICK) return;
|
||||
Player bukkitPlayer = event.getPlayer();
|
||||
BukkitServerPlayer player = this.plugin.adapt(bukkitPlayer);
|
||||
if (!(player.canInstabuild() && player.hasPermission("minecraft.debugstick")) && !player.hasPermission("minecraft.debugstick.always")) {
|
||||
return;
|
||||
}
|
||||
if (event.getHand() == EquipmentSlot.OFF_HAND) {
|
||||
int currentTicks = player.gameTicks();
|
||||
if (!player.updateLastSuccessfulInteractionTick(currentTicks)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Object blockState = BlockStateUtils.blockDataToBlockState(clickedBlock.getBlockData());
|
||||
int stateId = BlockStateUtils.blockStateToId(blockState);
|
||||
if (!BlockStateUtils.isVanillaBlock(stateId)) {
|
||||
event.setCancelled(true);
|
||||
boolean update = event.getAction() == Action.RIGHT_CLICK_BLOCK;
|
||||
ImmutableBlockState clickedCustomBlock = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
|
||||
CustomBlock block = clickedCustomBlock.owner().value();
|
||||
Collection<Property<?>> properties = block.properties();
|
||||
String blockId = block.id().toString();
|
||||
try {
|
||||
if (properties.isEmpty()) {
|
||||
Object systemChatPacket = Reflections.constructor$ClientboundSystemChatPacket.newInstance(
|
||||
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.empty").arguments(Component.text(blockId))), true);
|
||||
player.sendPacket(systemChatPacket, false);
|
||||
} else {
|
||||
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(itemInHand);
|
||||
Object storedData = wrapped.getTag("craftengine:debug_stick_state");
|
||||
if (storedData == null) storedData = new HashMap<>();
|
||||
if (storedData instanceof Map<?,?> map) {
|
||||
Map<String, Object> data = MiscUtils.castToMap(map, false);
|
||||
String currentPropertyName = (String) data.get(blockId);
|
||||
Property<?> currentProperty = block.getProperty(currentPropertyName);
|
||||
if (currentProperty == null) {
|
||||
currentProperty = properties.iterator().next();
|
||||
}
|
||||
if (update) {
|
||||
ImmutableBlockState nextState = cycleState(clickedCustomBlock, currentProperty, player.isSecondaryUseActive());
|
||||
CraftEngineBlocks.place(clickedBlock.getLocation(), nextState, new UpdateOption.Builder().updateClients().updateKnownShape().build(), false);
|
||||
Object systemChatPacket = Reflections.constructor$ClientboundSystemChatPacket.newInstance(
|
||||
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.update")
|
||||
.arguments(
|
||||
Component.text(currentProperty.name()),
|
||||
Component.text(getNameHelper(nextState, currentProperty))
|
||||
)), true);
|
||||
player.sendPacket(systemChatPacket, false);
|
||||
} else {
|
||||
currentProperty = getRelative(properties, currentProperty, player.isSecondaryUseActive());
|
||||
data.put(blockId, currentProperty.name());
|
||||
wrapped.setTag(data, "craftengine:debug_stick_state");
|
||||
wrapped.load();
|
||||
Object systemChatPacket = Reflections.constructor$ClientboundSystemChatPacket.newInstance(
|
||||
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.select")
|
||||
.arguments(
|
||||
Component.text(currentProperty.name()),
|
||||
Component.text(getNameHelper(clickedCustomBlock, currentProperty))
|
||||
)), true);
|
||||
player.sendPacket(systemChatPacket, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to send system chat packet", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>> ImmutableBlockState cycleState(ImmutableBlockState state, Property<T> property, boolean inverse) {
|
||||
return state.with(property, getRelative(property.possibleValues(), state.get(property), inverse));
|
||||
}
|
||||
|
||||
private static <T> T getRelative(Iterable<T> elements, @Nullable T current, boolean inverse) {
|
||||
return inverse ? MCUtils.findPreviousInIterable(elements, current) : MCUtils.findNextInIterable(elements, current);
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>> String getNameHelper(ImmutableBlockState state, Property<T> property) {
|
||||
return property.valueName(state.get(property));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package net.momirealms.craftengine.bukkit.item;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.momirealms.craftengine.bukkit.api.event.CustomBlockInteractEvent;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.item.behavior.BlockItemBehavior;
|
||||
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.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionResult;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
|
||||
import net.momirealms.craftengine.core.item.context.UseOnContext;
|
||||
import net.momirealms.craftengine.core.util.Direction;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.world.BlockHitResult;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ItemEventListener implements Listener {
|
||||
private final BukkitCraftEngine plugin;
|
||||
|
||||
public ItemEventListener(BukkitCraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onInteractBlock(PlayerInteractEvent event) {
|
||||
Action action = event.getAction();
|
||||
if (action != Action.LEFT_CLICK_BLOCK && action != Action.RIGHT_CLICK_BLOCK) {
|
||||
return;
|
||||
}
|
||||
Block block = Objects.requireNonNull(event.getClickedBlock());
|
||||
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
|
||||
int stateId = BlockStateUtils.blockStateToId(blockState);
|
||||
if (BlockStateUtils.isVanillaBlock(stateId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// it's breaking the block
|
||||
if (action == Action.LEFT_CLICK_BLOCK && event.getPlayer().getGameMode() == GameMode.CREATIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
CustomBlockInteractEvent interactEvent = new CustomBlockInteractEvent(
|
||||
event.getPlayer(),
|
||||
block.getLocation(),
|
||||
event.getInteractionPoint(),
|
||||
BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId),
|
||||
block,
|
||||
event.getBlockFace(),
|
||||
event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND,
|
||||
action == Action.RIGHT_CLICK_BLOCK ? CustomBlockInteractEvent.Action.RIGHT_CLICK : CustomBlockInteractEvent.Action.LEFT_CLICK
|
||||
);
|
||||
if (EventUtils.fireAndCheckCancel(interactEvent)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInteractAir(PlayerInteractEvent event) {
|
||||
if (event.getAction() != Action.RIGHT_CLICK_AIR) return;
|
||||
Player bukkitPlayer = event.getPlayer();
|
||||
BukkitServerPlayer player = this.plugin.adapt(bukkitPlayer);
|
||||
InteractionHand hand = event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
|
||||
if (cancelEventIfHasInteraction(event, player, hand)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.isSpectatorMode() || player.isAdventureMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Gets the item in hand
|
||||
Item<ItemStack> itemInHand = player.getItemInHand(hand);
|
||||
// should never be null
|
||||
if (itemInHand == null) return;
|
||||
Optional<List<ItemBehavior>> optionalItemBehaviors = itemInHand.getItemBehavior();
|
||||
|
||||
if (optionalItemBehaviors.isPresent()) {
|
||||
for (ItemBehavior itemBehavior : optionalItemBehaviors.get()) {
|
||||
InteractionResult result = itemBehavior.use(player.level(), player, hand);
|
||||
if (result == InteractionResult.SUCCESS_AND_CANCEL) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
if (result != InteractionResult.PASS) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onInteractAtBlock(PlayerInteractEvent event) {
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
|
||||
if (event.useItemInHand() == Event.Result.DENY || event.useInteractedBlock() == Event.Result.DENY) return;
|
||||
Location interactionPoint = event.getInteractionPoint();
|
||||
if (interactionPoint == null) return;
|
||||
Player bukkitPlayer = event.getPlayer();
|
||||
Block clickedBlock = Objects.requireNonNull(event.getClickedBlock());
|
||||
BukkitServerPlayer player = this.plugin.adapt(bukkitPlayer);
|
||||
InteractionHand hand = event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
|
||||
if (cancelEventIfHasInteraction(event, player, hand)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Gets the item in hand
|
||||
Item<ItemStack> itemInHand = player.getItemInHand(hand);
|
||||
if (itemInHand == null) return;
|
||||
Optional<List<ItemBehavior>> optionalItemBehaviors = itemInHand.getItemBehavior();
|
||||
|
||||
// has custom item behavior
|
||||
if (optionalItemBehaviors.isPresent()) {
|
||||
BlockPos pos = LocationUtils.toBlockPos(clickedBlock.getLocation());
|
||||
Vec3d vec3d = new Vec3d(interactionPoint.getX(), interactionPoint.getY(), interactionPoint.getZ());
|
||||
Direction direction = DirectionUtils.toDirection(event.getBlockFace());
|
||||
BlockHitResult hitResult = new BlockHitResult(vec3d, direction, pos, false);
|
||||
boolean interactable = InteractUtils.isInteractable(BlockStateUtils.getBlockOwnerId(clickedBlock), bukkitPlayer, clickedBlock.getBlockData(), hitResult, itemInHand);
|
||||
|
||||
// do not allow to place block if it's a vanilla block
|
||||
if (itemInHand.isBlockItem() && itemInHand.isCustomItem()) {
|
||||
if (!interactable || player.isSecondaryUseActive()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!player.isSecondaryUseActive() && interactable) {
|
||||
// if it's interactable on server, cancel the custom behaviors
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO We need to further investigate how to handle adventure mode
|
||||
// no spectator interactions
|
||||
if (player.isSpectatorMode() || player.isAdventureMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ItemBehavior itemBehavior : optionalItemBehaviors.get()) {
|
||||
InteractionResult result = itemBehavior.useOnBlock(new UseOnContext(player, hand, hitResult));
|
||||
if (result == InteractionResult.SUCCESS_AND_CANCEL) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
int maxY = player.level().worldHeight().getMaxBuildHeight() - 1;
|
||||
if (direction == Direction.UP
|
||||
&& result != InteractionResult.SUCCESS
|
||||
&& pos.y() >= maxY
|
||||
&& itemBehavior instanceof BlockItemBehavior
|
||||
) {
|
||||
player.sendActionBar(Component.translatable("build.tooHigh").arguments(Component.text(maxY)).color(NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
if (result != InteractionResult.PASS) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// it's a vanilla block
|
||||
if (itemInHand.isBlockItem() && !itemInHand.isCustomItem()) {
|
||||
// client won't have sounds if the fake block is interactable
|
||||
// so we should check and resend sounds on interact
|
||||
Object blockState = BlockStateUtils.blockDataToBlockState(clickedBlock.getBlockData());
|
||||
int stateId = BlockStateUtils.blockStateToId(blockState);
|
||||
ImmutableBlockState againCustomBlock = BukkitBlockManager.instance().getImmutableBlockState(stateId);
|
||||
if (againCustomBlock == null || againCustomBlock.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
BlockPos pos = LocationUtils.toBlockPos(clickedBlock.getLocation());
|
||||
Vec3d vec3d = new Vec3d(interactionPoint.getX(), interactionPoint.getY(), interactionPoint.getZ());
|
||||
Direction direction = DirectionUtils.toDirection(event.getBlockFace());
|
||||
BlockHitResult hitResult = new BlockHitResult(vec3d, direction, pos, false);
|
||||
try {
|
||||
BlockData craftBlockData = BlockStateUtils.createBlockData(againCustomBlock.vanillaBlockState().handle());
|
||||
if (InteractUtils.isInteractable(Key.of(clickedBlock.getType().getKey().asString()), bukkitPlayer, craftBlockData, hitResult, itemInHand)) {
|
||||
if (!player.isSecondaryUseActive()) {
|
||||
player.setResendSound();
|
||||
}
|
||||
} else {
|
||||
if (BlockStateUtils.isReplaceable(againCustomBlock.customBlockState().handle()) && !BlockStateUtils.isReplaceable(againCustomBlock.vanillaBlockState().handle())) {
|
||||
player.setResendSwing();
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to get CraftBlockData", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean cancelEventIfHasInteraction(PlayerInteractEvent event, BukkitServerPlayer player, InteractionHand hand) {
|
||||
if (hand == InteractionHand.OFF_HAND) {
|
||||
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)) {
|
||||
event.setCancelled(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package net.momirealms.craftengine.bukkit.item;
|
||||
|
||||
import com.saicone.rtag.RtagItem;
|
||||
import net.momirealms.craftengine.core.item.ItemWrapper;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class RTagItemWrapper implements ItemWrapper<ItemStack> {
|
||||
private final RtagItem rtagItem;
|
||||
private int count;
|
||||
|
||||
public RTagItemWrapper(RtagItem rtagItem, int count) {
|
||||
this.rtagItem = rtagItem;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItem() {
|
||||
ItemStack itemStack = this.rtagItem.getItem();
|
||||
itemStack.setAmount(this.count);
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean set(Object value, Object... path) {
|
||||
return this.rtagItem.set(value, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Object value, Object... path) {
|
||||
return this.rtagItem.add(value, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> V get(Object... path) {
|
||||
return this.rtagItem.get(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setComponent(Object path, Object value) {
|
||||
this.rtagItem.setComponent(path, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getComponent(Object path) {
|
||||
return this.rtagItem.getComponent(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void count(int amount) {
|
||||
if (amount < 0) amount = 0;
|
||||
this.count = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> V getExact(Object... path) {
|
||||
return this.rtagItem.get(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeComponent(Object path) {
|
||||
this.rtagItem.removeComponent(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasComponent(Object path) {
|
||||
return this.rtagItem.hasComponent(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
this.rtagItem.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object... path) {
|
||||
return this.rtagItem.remove(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTag(Object... path) {
|
||||
return this.rtagItem.hasTag(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack load() {
|
||||
ItemStack itemStack = this.rtagItem.load();
|
||||
itemStack.setAmount(this.count);
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack loadCopy() {
|
||||
ItemStack itemStack = this.rtagItem.loadCopy();
|
||||
itemStack.setAmount(this.count);
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getLiteralObject() {
|
||||
return this.rtagItem.getLiteralObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemWrapper<ItemStack> copyWithCount(int count) {
|
||||
return new RTagItemWrapper(new RtagItem(this.rtagItem.loadCopy()), count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package net.momirealms.craftengine.bukkit.item.behavior;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.block.behavior.StrippableBlockBehavior;
|
||||
import net.momirealms.craftengine.bukkit.util.*;
|
||||
import net.momirealms.craftengine.bukkit.world.BukkitWorldBlock;
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.UpdateOption;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionResult;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.item.ItemKeys;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.item.context.UseOnContext;
|
||||
import net.momirealms.craftengine.core.pack.Pack;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import net.momirealms.sparrow.nbt.CompoundTag;
|
||||
import org.bukkit.GameEvent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.event.entity.EntityChangeBlockEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AxeItemBehavior extends ItemBehavior {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
public static final AxeItemBehavior INSTANCE = new AxeItemBehavior();
|
||||
private static final Key AXE_STRIP_SOUND = Key.of("minecraft:item.axe.strip");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public InteractionResult useOnBlock(UseOnContext context) {
|
||||
BukkitWorldBlock clicked = (BukkitWorldBlock) context.getLevel().getBlockAt(context.getClickedPos());
|
||||
Block block = clicked.block();
|
||||
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData()));
|
||||
if (state == null || state.isEmpty()) return InteractionResult.PASS;
|
||||
|
||||
if (!(state.behavior() instanceof StrippableBlockBehavior blockBehavior)) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
Player player = context.getPlayer();
|
||||
|
||||
Item<ItemStack> offHandItem = (Item<ItemStack>) player.getItemInHand(InteractionHand.OFF_HAND);
|
||||
// is using a shield
|
||||
if (context.getHand() == InteractionHand.MAIN_HAND && offHandItem != null && offHandItem.vanillaId().equals(ItemKeys.SHIELD) && !player.isSecondaryUseActive()) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
Optional<CustomBlock> optionalNewCustomBlock = BukkitBlockManager.instance().getBlock(blockBehavior.stripped());
|
||||
if (optionalNewCustomBlock.isEmpty()) {
|
||||
CraftEngine.instance().logger().warn("stripped block " + blockBehavior.stripped() + " does not exist");
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
CustomBlock newCustomBlock = optionalNewCustomBlock.get();
|
||||
CompoundTag compoundTag = state.propertiesNbt();
|
||||
ImmutableBlockState newState = newCustomBlock.getBlockState(compoundTag);
|
||||
|
||||
org.bukkit.entity.Player bukkitPlayer = ((org.bukkit.entity.Player) player.platformPlayer());
|
||||
// Call bukkit event
|
||||
EntityChangeBlockEvent event = new EntityChangeBlockEvent(bukkitPlayer, block, BlockStateUtils.createBlockData(newState.customBlockState().handle()));
|
||||
if (EventUtils.fireAndCheckCancel(event)) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
BlockPos pos = context.getClickedPos();
|
||||
context.getLevel().playBlockSound(Vec3d.atCenterOf(pos), AXE_STRIP_SOUND, 1, 1);
|
||||
CraftEngineBlocks.place(block.getLocation(), newState, UpdateOption.UPDATE_ALL_IMMEDIATE, false);
|
||||
block.getWorld().sendGameEvent(bukkitPlayer, GameEvent.BLOCK_CHANGE, new Vector(pos.x(), pos.y(), pos.z()));
|
||||
Item<ItemStack> item = (Item<ItemStack>) context.getItem();
|
||||
Material material = MaterialUtils.getMaterial(item.vanillaId());
|
||||
bukkitPlayer.setStatistic(Statistic.USE_ITEM, material, bukkitPlayer.getStatistic(Statistic.USE_ITEM, material) + 1);
|
||||
|
||||
// resend swing if it's not interactable on client side
|
||||
if (!InteractUtils.isInteractable(BlockStateUtils.getBlockOwnerIdFromState(state.vanillaBlockState().handle()),
|
||||
bukkitPlayer, BlockStateUtils.fromBlockData(state.vanillaBlockState().handle()),
|
||||
context.getHitResult(), item
|
||||
)) {
|
||||
player.swingHand(context.getHand());
|
||||
}
|
||||
// shrink item amount
|
||||
if (VersionHelper.isVersionNewerThan1_20_5()) {
|
||||
Object itemStack = item.getLiteralObject();
|
||||
Object serverPlayer = player.serverPlayer();
|
||||
Object equipmentSlot = context.getHand() == InteractionHand.MAIN_HAND ? Reflections.instance$EquipmentSlot$MAINHAND : Reflections.instance$EquipmentSlot$OFFHAND;
|
||||
try {
|
||||
Reflections.method$ItemStack$hurtAndBreak.invoke(itemStack, 1, serverPlayer, equipmentSlot);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
CraftEngine.instance().logger().warn("Failed to hurt itemStack", e);
|
||||
}
|
||||
} else {
|
||||
ItemStack itemStack = item.getItem();
|
||||
itemStack.damage(1, bukkitPlayer);
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments) {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package net.momirealms.craftengine.bukkit.item.behavior;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
|
||||
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.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.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.UpdateOption;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionResult;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
|
||||
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.util.Key;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.event.block.BlockCanBuildEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BlockItemBehavior extends ItemBehavior {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
private final Key blockId;
|
||||
|
||||
public BlockItemBehavior(Key blockId) {
|
||||
this.blockId = blockId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult useOnBlock(UseOnContext context) {
|
||||
return this.place(new BlockPlaceContext(context));
|
||||
}
|
||||
|
||||
public InteractionResult place(BlockPlaceContext context) {
|
||||
if (!context.canPlace()) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
Optional<CustomBlock> optionalBlock = BukkitBlockManager.instance().getBlock(this.blockId);
|
||||
if (optionalBlock.isEmpty()) {
|
||||
CraftEngine.instance().logger().warn("Failed to place unknown block " + this.blockId);
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
CustomBlock block = optionalBlock.get();
|
||||
BlockPlaceContext placeContext = updatePlacementContext(context);
|
||||
if (placeContext == null) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
ImmutableBlockState blockStateToPlace = getPlacementState(placeContext, block);
|
||||
if (blockStateToPlace == null) {
|
||||
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().getHandle();
|
||||
Location placeLocation = new Location(world, pos.x(), pos.y(), pos.z());
|
||||
|
||||
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();
|
||||
|
||||
// trigger event
|
||||
CustomBlockAttemptPlaceEvent attemptPlaceEvent = new CustomBlockAttemptPlaceEvent(bukkitPlayer, placeLocation.clone(), blockStateToPlace,
|
||||
DirectionUtils.toBlockFace(context.getClickedFace()), bukkitBlock, context.getHand());
|
||||
if (EventUtils.fireAndCheckCancel(attemptPlaceEvent)) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
// it's just world + pos
|
||||
BlockState previousState = bukkitBlock.getState();
|
||||
// place custom block
|
||||
CraftEngineBlocks.place(placeLocation, blockStateToPlace, UpdateOption.UPDATE_ALL_IMMEDIATE, false);
|
||||
// call bukkit event
|
||||
BlockPlaceEvent bukkitPlaceEvent = new BlockPlaceEvent(bukkitBlock, previousState, againstBlock, (ItemStack) placeContext.getItem().getItem(), bukkitPlayer, true, context.getHand() == InteractionHand.MAIN_HAND ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND);
|
||||
if (EventUtils.fireAndCheckCancel(bukkitPlaceEvent)) {
|
||||
// revert changes
|
||||
previousState.update(true, false);
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
// call custom event
|
||||
CustomBlockPlaceEvent customPlaceEvent = new CustomBlockPlaceEvent(bukkitPlayer, placeLocation.clone(), blockStateToPlace, world.getBlockAt(placeLocation), context.getHand());
|
||||
if (EventUtils.fireAndCheckCancel(customPlaceEvent)) {
|
||||
// revert changes
|
||||
previousState.update(true, false);
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
if (!player.isCreativeMode()) {
|
||||
Item<?> item = placeContext.getItem();
|
||||
item.count(item.count() - 1);
|
||||
item.load();
|
||||
}
|
||||
|
||||
player.swingHand(placeContext.getHand());
|
||||
world.playSound(new Location(world, pos.x(), pos.y(), pos.z()), blockStateToPlace.sounds().placeSound().toString(), SoundCategory.BLOCKS, 1f, 0.8f);
|
||||
world.sendGameEvent(bukkitPlayer, GameEvent.BLOCK_PLACE, new Vector(pos.x(), pos.y(), pos.z()));
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockPlaceContext updatePlacementContext(BlockPlaceContext context) {
|
||||
return context;
|
||||
}
|
||||
|
||||
protected ImmutableBlockState getPlacementState(BlockPlaceContext context, CustomBlock block) {
|
||||
ImmutableBlockState state = block.getStateForPlacement(context);
|
||||
return state != null && this.canPlace(context, state) ? state : null;
|
||||
}
|
||||
|
||||
protected boolean checkStatePlacement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean canPlace(BlockPlaceContext context, ImmutableBlockState state) {
|
||||
try {
|
||||
Object player;
|
||||
try {
|
||||
player = Reflections.method$CraftPlayer$getHandle.invoke(context.getPlayer().platformPlayer());
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException("Failed to get server player", e);
|
||||
}
|
||||
Object blockState = state.customBlockState().handle();
|
||||
Object blockPos = LocationUtils.toBlockPos(context.getClickedPos());
|
||||
Object voxelShape = Reflections.method$CollisionContext$of.invoke(null, player);
|
||||
Object world = Reflections.field$CraftWorld$ServerLevel.get(context.getLevel().getHandle());
|
||||
boolean defaultReturn = ((!this.checkStatePlacement() || (boolean) Reflections.method$BlockStateBase$canSurvive.invoke(blockState, world, blockPos))
|
||||
&& (boolean) Reflections.method$ServerLevel$checkEntityCollision.invoke(world, blockState, player, voxelShape, blockPos, true));
|
||||
Block block = (Block) Reflections.method$CraftBlock$at.invoke(null, world, blockPos);
|
||||
BlockData blockData = (BlockData) Reflections.method$CraftBlockData$fromData.invoke(null, blockState);
|
||||
BlockCanBuildEvent canBuildEvent = new BlockCanBuildEvent(block, (org.bukkit.entity.Player) context.getPlayer().platformPlayer(), blockData, defaultReturn, context.getHand() == InteractionHand.MAIN_HAND ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND);
|
||||
Bukkit.getPluginManager().callEvent(canBuildEvent);
|
||||
return canBuildEvent.isBuildable();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
CraftEngine.instance().logger().warn("Failed to check canPlace", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Key blockId() {
|
||||
return this.blockId;
|
||||
}
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
|
||||
Object id = arguments.get("block");
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("Missing required parameter 'block' for block_item behavior");
|
||||
}
|
||||
if (id instanceof Map<?, ?> map) {
|
||||
BukkitBlockManager.instance().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
|
||||
return new BlockItemBehavior(key);
|
||||
} else {
|
||||
return new BlockItemBehavior(Key.of(id.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.momirealms.craftengine.bukkit.item.behavior;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionResult;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.item.context.UseOnContext;
|
||||
import net.momirealms.craftengine.core.pack.Pack;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
public class BoneMealBehavior extends ItemBehavior {
|
||||
public static final BoneMealBehavior INSTANCE = new BoneMealBehavior();
|
||||
|
||||
@Override
|
||||
public InteractionResult useOnBlock(UseOnContext context) {
|
||||
return super.useOnBlock(context);
|
||||
}
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments) {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package net.momirealms.craftengine.bukkit.item.behavior;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.world.BukkitWorldBlock;
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.UpdateOption;
|
||||
import net.momirealms.craftengine.core.block.properties.Property;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionResult;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.item.context.UseOnContext;
|
||||
import net.momirealms.craftengine.core.pack.Pack;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
public class BucketItemBehavior extends ItemBehavior {
|
||||
public static final BucketItemBehavior INSTANCE = new BucketItemBehavior();
|
||||
public static final Factory FACTORY = new Factory();
|
||||
private static final Key ITEM_BUCKET_FILL = Key.of("item.bucket.fill");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public InteractionResult useOnBlock(UseOnContext context) {
|
||||
BukkitWorldBlock clicked = (BukkitWorldBlock) context.getLevel().getBlockAt(context.getClickedPos());
|
||||
Block block = clicked.block();
|
||||
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData()));
|
||||
if (state == null || state.isEmpty()) return InteractionResult.PASS;
|
||||
CustomBlock customBlock = state.owner().value();
|
||||
Property<Boolean> waterlogged = (Property<Boolean>) customBlock.getProperty("waterlogged");
|
||||
if (waterlogged == null) return InteractionResult.PASS;
|
||||
boolean waterloggedState = state.get(waterlogged);
|
||||
if (!waterloggedState) return InteractionResult.PASS;
|
||||
BlockPos pos = context.getClickedPos();
|
||||
Player player = (Player) context.getPlayer().platformPlayer();
|
||||
World world = player.getWorld();
|
||||
Location location = new Location(world, pos.x(), pos.y(), pos.z());
|
||||
EquipmentSlot slot = context.getHand() == InteractionHand.MAIN_HAND ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND;
|
||||
|
||||
CraftEngineBlocks.place(location, state.with(waterlogged, false), UpdateOption.UPDATE_ALL, false);
|
||||
if (player.getGameMode() == GameMode.SURVIVAL) {
|
||||
// to prevent dupe in moment
|
||||
player.getInventory().setItem(slot, new ItemStack(Material.AIR));
|
||||
BukkitCraftEngine.instance().scheduler().sync().runDelayed(() ->
|
||||
player.getInventory().setItem(slot, new ItemStack(Material.WATER_BUCKET)), world, location.getBlockX() >> 4, location.getBlockZ() >> 4);
|
||||
}
|
||||
player.setStatistic(Statistic.USE_ITEM, Material.BUCKET, player.getStatistic(Statistic.USE_ITEM, Material.BUCKET) + 1);
|
||||
// client will assume it has sounds
|
||||
// context.getPlayer().level().playBlockSound(Vec3d.atCenterOf(context.getClickedPos()), ITEM_BUCKET_FILL, 1, 1);
|
||||
return InteractionResult.SUCCESS_AND_CANCEL;
|
||||
}
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments) {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.momirealms.craftengine.bukkit.item.behavior;
|
||||
|
||||
import net.momirealms.craftengine.core.item.behavior.EmptyItemBehavior;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehaviors;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
|
||||
public class BukkitItemBehaviors extends ItemBehaviors {
|
||||
public static final Key EMPTY = Key.from("craftengine:empty");
|
||||
public static final Key BLOCK_ITEM = Key.from("craftengine:block_item");
|
||||
public static final Key FURNITURE_ITEM = Key.from("craftengine:furniture_item");
|
||||
public static final Key AXE_ITEM = Key.from("craftengine:axe_item");
|
||||
public static final Key WATER_BUCKET_ITEM = Key.from("craftengine:water_bucket_item");
|
||||
public static final Key BUCKET_ITEM = Key.from("craftengine:bucket_item");
|
||||
|
||||
public static void init() {
|
||||
register(EMPTY, (pack, path, args, id) -> EmptyItemBehavior.INSTANCE);
|
||||
register(BLOCK_ITEM, BlockItemBehavior.FACTORY);
|
||||
register(FURNITURE_ITEM, FurnitureItemBehavior.FACTORY);
|
||||
register(AXE_ITEM, AxeItemBehavior.FACTORY);
|
||||
register(WATER_BUCKET_ITEM, WaterBucketItemBehavior.FACTORY);
|
||||
register(BUCKET_ITEM, BucketItemBehavior.FACTORY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package net.momirealms.craftengine.bukkit.item.behavior;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptPlaceEvent;
|
||||
import net.momirealms.craftengine.bukkit.api.event.FurniturePlaceEvent;
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.EventUtils;
|
||||
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
|
||||
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionResult;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.item.context.UseOnContext;
|
||||
import net.momirealms.craftengine.core.pack.Pack;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.util.Direction;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import net.momirealms.craftengine.core.util.Pair;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.SoundCategory;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class FurnitureItemBehavior extends ItemBehavior {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
private final Key id;
|
||||
|
||||
public FurnitureItemBehavior(Key id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Key furnitureId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult useOnBlock(UseOnContext context) {
|
||||
return this.place(context);
|
||||
}
|
||||
|
||||
public InteractionResult place(UseOnContext context) {
|
||||
Optional<CustomFurniture> optionalCustomFurniture = BukkitFurnitureManager.instance().getFurniture(this.id);
|
||||
if (optionalCustomFurniture.isEmpty()) {
|
||||
CraftEngine.instance().logger().warn("Furniture " + this.id + " not found");
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
CustomFurniture customFurniture = optionalCustomFurniture.get();
|
||||
|
||||
Direction clickedFace = context.getClickedFace();
|
||||
AnchorType anchorType = switch (clickedFace) {
|
||||
case EAST, WEST, NORTH, SOUTH -> AnchorType.WALL;
|
||||
case UP -> AnchorType.GROUND;
|
||||
case DOWN -> AnchorType.CEILING;
|
||||
};
|
||||
|
||||
CustomFurniture.Placement placement = customFurniture.getPlacement(anchorType);
|
||||
if (placement == null) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
Player player = context.getPlayer();
|
||||
int gameTicks = player.gameTicks();
|
||||
if (!player.updateLastSuccessfulInteractionTick(gameTicks)) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
Vec3d clickedPosition = context.getClickLocation();
|
||||
|
||||
// trigger event
|
||||
org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) player.platformPlayer();
|
||||
World world = (World) context.getLevel().getHandle();
|
||||
|
||||
// get position and rotation for placement
|
||||
Vec3d finalPlacePosition;
|
||||
double furnitureYaw;
|
||||
if (anchorType == AnchorType.WALL) {
|
||||
furnitureYaw = Direction.getYaw(clickedFace);
|
||||
if (clickedFace == Direction.EAST || clickedFace == Direction.WEST) {
|
||||
Pair<Double, Double> xz = placement.alignmentRule().apply(Pair.of(clickedPosition.y(), clickedPosition.z()));
|
||||
finalPlacePosition = new Vec3d(clickedPosition.x(), xz.left(), xz.right());
|
||||
} else {
|
||||
Pair<Double, Double> xz = placement.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.y()));
|
||||
finalPlacePosition = new Vec3d(xz.left(), xz.right(), clickedPosition.z());
|
||||
}
|
||||
} else {
|
||||
furnitureYaw = placement.rotationRule().apply(180 + player.getXRot());
|
||||
Pair<Double, Double> xz = placement.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.z()));
|
||||
finalPlacePosition = new Vec3d(xz.left(), clickedPosition.y(), xz.right());
|
||||
}
|
||||
|
||||
Location furnitureLocation = new Location(world, finalPlacePosition.x(), finalPlacePosition.y(), finalPlacePosition.z(), (float) furnitureYaw, 0);
|
||||
if (!BukkitCraftEngine.instance().antiGrief().canPlace(bukkitPlayer, furnitureLocation)) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
if (!BukkitCraftEngine.instance().antiGrief().canPlace(bukkitPlayer, furnitureLocation)) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
FurnitureAttemptPlaceEvent attemptPlaceEvent = new FurnitureAttemptPlaceEvent(bukkitPlayer, customFurniture, anchorType, furnitureLocation.clone(),
|
||||
DirectionUtils.toBlockFace(clickedFace), context.getHand(), world.getBlockAt(context.getClickedPos().x(), context.getClickedPos().y(), context.getClickedPos().z()));
|
||||
if (EventUtils.fireAndCheckCancel(attemptPlaceEvent)) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().place(customFurniture, furnitureLocation.clone(), anchorType, false);
|
||||
|
||||
FurniturePlaceEvent placeEvent = new FurniturePlaceEvent(bukkitPlayer, loadedFurniture, furnitureLocation, context.getHand());
|
||||
if (EventUtils.fireAndCheckCancel(placeEvent)) {
|
||||
loadedFurniture.destroy();
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
if (!player.isCreativeMode()) {
|
||||
Item<?> item = context.getItem();
|
||||
item.count(item.count() - 1);
|
||||
item.load();
|
||||
}
|
||||
|
||||
furnitureLocation.getWorld().playSound(furnitureLocation, customFurniture.settings().sounds().placeSound().toString(), SoundCategory.BLOCKS,1f, 1f);
|
||||
player.swingHand(context.getHand());
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
|
||||
Object id = arguments.get("furniture");
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("Missing required parameter 'furniture' for furniture_item behavior");
|
||||
}
|
||||
if (id instanceof Map<?,?> map) {
|
||||
BukkitFurnitureManager.instance().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
|
||||
return new FurnitureItemBehavior(key);
|
||||
} else {
|
||||
return new FurnitureItemBehavior(Key.of(id.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package net.momirealms.craftengine.bukkit.item.behavior;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.world.BukkitWorldBlock;
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.UpdateOption;
|
||||
import net.momirealms.craftengine.core.block.properties.Property;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionResult;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.item.context.UseOnContext;
|
||||
import net.momirealms.craftengine.core.pack.Pack;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
public class WaterBucketItemBehavior extends ItemBehavior {
|
||||
public static final WaterBucketItemBehavior INSTANCE = new WaterBucketItemBehavior();
|
||||
public static final Factory FACTORY = new Factory();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public InteractionResult useOnBlock(UseOnContext context) {
|
||||
BlockPos pos = context.getClickedPos();
|
||||
BukkitWorldBlock clicked = (BukkitWorldBlock) context.getLevel().getBlockAt(pos);
|
||||
Block block = clicked.block();
|
||||
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData()));
|
||||
if (state == null || state.isEmpty()) return InteractionResult.PASS;
|
||||
CustomBlock customBlock = state.owner().value();
|
||||
Property<Boolean> waterlogged = (Property<Boolean>) customBlock.getProperty("waterlogged");
|
||||
if (waterlogged == null) return InteractionResult.PASS;
|
||||
|
||||
Player player = (Player) context.getPlayer().platformPlayer();
|
||||
World world = player.getWorld();
|
||||
Location location = new Location(world, pos.x(), pos.y(), pos.z());
|
||||
|
||||
// TODO Refactor all of this because it's playing a trick with the server
|
||||
ImmutableBlockState nextState = state.with(waterlogged, true);
|
||||
block.setBlockData(BlockStateUtils.createBlockData(nextState.vanillaBlockState().handle()), false);
|
||||
// actually we should broadcast this change
|
||||
context.getPlayer().sendPacket(BlockStateUtils.createBlockUpdatePacket(pos, state), true);
|
||||
BukkitCraftEngine.instance().scheduler().sync().runDelayed(() ->
|
||||
CraftEngineBlocks.place(location, nextState, UpdateOption.UPDATE_ALL, false), world, location.getBlockX() >> 4, location.getBlockZ() >> 4);
|
||||
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments) {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package net.momirealms.craftengine.bukkit.item.factory;
|
||||
|
||||
import com.saicone.rtag.RtagItem;
|
||||
import net.momirealms.craftengine.bukkit.item.RTagItemWrapper;
|
||||
import net.momirealms.craftengine.bukkit.util.ItemTags;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.item.ItemFactory;
|
||||
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.inventory.ItemStack;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class BukkitItemFactory extends ItemFactory<CraftEngine, RTagItemWrapper, ItemStack> {
|
||||
|
||||
protected BukkitItemFactory(CraftEngine plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
public static BukkitItemFactory create(CraftEngine plugin) {
|
||||
Objects.requireNonNull(plugin, "plugin");
|
||||
switch (plugin.serverVersion()) {
|
||||
case "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4" -> {
|
||||
return new UniversalItemFactory(plugin);
|
||||
}
|
||||
case "1.20.5", "1.20.6",
|
||||
"1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4", "1.21.5",
|
||||
"1.22", "1.22.1" -> {
|
||||
return new ComponentItemFactory(plugin);
|
||||
}
|
||||
default -> throw new IllegalStateException("Unsupported server version: " + plugin.serverVersion());
|
||||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
return Key.of(id.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Key> customId(ItemWrapper<ItemStack> item) {
|
||||
Object id = item.get(IdModifier.CRAFT_ENGINE_ID);
|
||||
if (id == null) return Optional.empty();
|
||||
return Optional.of(Key.of(id.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isBlockItem(ItemWrapper<ItemStack> item) {
|
||||
return item.getItem().getType().isBlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Key vanillaId(ItemWrapper<ItemStack> item) {
|
||||
return Key.of(item.getItem().getType().getKey().asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemWrapper<ItemStack> wrapInternal(ItemStack item) {
|
||||
return new RTagItemWrapper(new RtagItem(item), item.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setTag(ItemWrapper<ItemStack> item, Object value, Object... path) {
|
||||
item.set(value, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getTag(ItemWrapper<ItemStack> item, Object... path) {
|
||||
return item.get(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasTag(ItemWrapper<ItemStack> item, Object... path) {
|
||||
return item.hasTag(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean removeTag(ItemWrapper<ItemStack> item, Object... path) {
|
||||
return item.remove(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setComponent(ItemWrapper<ItemStack> item, String type, Object value) {
|
||||
item.setComponent(type, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getComponent(ItemWrapper<ItemStack> item, String type) {
|
||||
return item.getComponent(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasComponent(ItemWrapper<ItemStack> item, String type) {
|
||||
return item.hasComponent(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeComponent(ItemWrapper<ItemStack> item, String type) {
|
||||
item.removeComponent(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void update(ItemWrapper<ItemStack> item) {
|
||||
item.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack load(ItemWrapper<ItemStack> item) {
|
||||
return item.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack getItem(ItemWrapper<ItemStack> item) {
|
||||
return item.getItem();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack loadCopy(ItemWrapper<ItemStack> item) {
|
||||
return item.loadCopy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean is(ItemWrapper<ItemStack> item, Key itemTag) {
|
||||
Object literalObject = item.getLiteralObject();
|
||||
Object tag = ItemTags.getOrCreate(itemTag);
|
||||
try {
|
||||
return (boolean) Reflections.method$ItemStack$isTag.invoke(literalObject, tag);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package net.momirealms.craftengine.bukkit.item.factory;
|
||||
|
||||
import com.saicone.rtag.data.ComponentType;
|
||||
import net.momirealms.craftengine.bukkit.util.EnchantmentUtils;
|
||||
import net.momirealms.craftengine.core.item.ComponentKeys;
|
||||
import net.momirealms.craftengine.core.item.Enchantment;
|
||||
import net.momirealms.craftengine.core.item.ItemWrapper;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class ComponentItemFactory extends BukkitItemFactory {
|
||||
|
||||
private final BiConsumer<ItemWrapper<ItemStack>, Integer> customModelDataSetter;
|
||||
private final Function<ItemWrapper<ItemStack>, Optional<Integer>> customModelDataGetter;
|
||||
|
||||
public ComponentItemFactory(CraftEngine plugin) {
|
||||
super(plugin);
|
||||
this.customModelDataSetter = VersionHelper.isVersionNewerThan1_21_4() ?
|
||||
((item, data) -> item.setComponent(ComponentKeys.CUSTOM_MODEL_DATA,
|
||||
Map.of("floats", List.of(data.floatValue())))) : ((item, data) -> item.setComponent(ComponentKeys.CUSTOM_MODEL_DATA, data));
|
||||
this.customModelDataGetter = VersionHelper.isVersionNewerThan1_21_4() ?
|
||||
(item) -> {
|
||||
Optional<Object> optional = ComponentType.encodeJava(ComponentKeys.CUSTOM_MODEL_DATA, item.getComponent(ComponentKeys.CUSTOM_MODEL_DATA));
|
||||
if (optional.isEmpty()) return Optional.empty();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> data = (Map<String, Object>) optional.get();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Float> floats = (List<Float>) data.get("floats");
|
||||
if (floats == null || floats.isEmpty()) return Optional.empty();
|
||||
return Optional.of((int) Math.floor(floats.get(0)));
|
||||
} : (item) -> Optional.ofNullable(
|
||||
(Integer) ComponentType.encodeJava(
|
||||
ComponentKeys.CUSTOM_MODEL_DATA,
|
||||
item.getComponent(ComponentKeys.CUSTOM_MODEL_DATA)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customModelData(ItemWrapper<ItemStack> item, Integer data) {
|
||||
if (data == null) {
|
||||
item.removeComponent(ComponentKeys.CUSTOM_MODEL_DATA);
|
||||
} else {
|
||||
this.customModelDataSetter.accept(item, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> customModelData(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasComponent(ComponentKeys.CUSTOM_MODEL_DATA)) return Optional.empty();
|
||||
return this.customModelDataGetter.apply(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayName(ItemWrapper<ItemStack> item, String json) {
|
||||
if (json == null) {
|
||||
item.removeComponent(ComponentKeys.CUSTOM_NAME);
|
||||
} else {
|
||||
item.setComponent(ComponentKeys.CUSTOM_NAME, json);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<String> displayName(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasComponent(ComponentKeys.CUSTOM_NAME)) return Optional.empty();
|
||||
return Optional.ofNullable(
|
||||
(String) ComponentType.encodeJava(
|
||||
ComponentKeys.CUSTOM_NAME,
|
||||
item.getComponent(ComponentKeys.CUSTOM_NAME)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void itemName(ItemWrapper<ItemStack> item, String json) {
|
||||
if (json == null) {
|
||||
item.removeComponent(ComponentKeys.ITEM_NAME);
|
||||
} else {
|
||||
item.setComponent(ComponentKeys.ITEM_NAME, json);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<String> itemName(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasComponent(ComponentKeys.ITEM_NAME)) return Optional.empty();
|
||||
return Optional.ofNullable(
|
||||
(String) ComponentType.encodeJava(
|
||||
ComponentKeys.ITEM_NAME,
|
||||
item.getComponent(ComponentKeys.ITEM_NAME)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void skull(ItemWrapper<ItemStack> item, String skullData) {
|
||||
final Map<String, Object> profile = Map.of(
|
||||
"properties", List.of(
|
||||
Map.of(
|
||||
"name", "textures",
|
||||
"value", skullData
|
||||
)
|
||||
)
|
||||
);
|
||||
item.setComponent("minecraft:profile", profile);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Optional<List<String>> lore(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasComponent(ComponentKeys.LORE)) return Optional.empty();
|
||||
return Optional.ofNullable(
|
||||
(List<String>) ComponentType.encodeJava(
|
||||
ComponentKeys.LORE,
|
||||
item.getComponent(ComponentKeys.LORE)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void lore(ItemWrapper<ItemStack> item, List<String> lore) {
|
||||
if (lore == null || lore.isEmpty()) {
|
||||
item.removeComponent(ComponentKeys.LORE);
|
||||
} else {
|
||||
item.setComponent(ComponentKeys.LORE, lore);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean unbreakable(ItemWrapper<ItemStack> item) {
|
||||
return item.hasComponent(ComponentKeys.UNBREAKABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unbreakable(ItemWrapper<ItemStack> item, boolean unbreakable) {
|
||||
if (unbreakable) {
|
||||
item.removeComponent(ComponentKeys.UNBREAKABLE);
|
||||
} else {
|
||||
item.setComponent(ComponentKeys.UNBREAKABLE, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Boolean> glint(ItemWrapper<ItemStack> item) {
|
||||
return Optional.ofNullable((Boolean) item.getComponent(ComponentKeys.ENCHANTMENT_GLINT_OVERRIDE));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void glint(ItemWrapper<ItemStack> item, Boolean glint) {
|
||||
item.setComponent(ComponentKeys.ENCHANTMENT_GLINT_OVERRIDE, glint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> damage(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasComponent(ComponentKeys.DAMAGE)) return Optional.empty();
|
||||
return Optional.ofNullable(
|
||||
(Integer) ComponentType.encodeJava(
|
||||
ComponentKeys.DAMAGE,
|
||||
item.getComponent(ComponentKeys.DAMAGE)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void damage(ItemWrapper<ItemStack> item, Integer damage) {
|
||||
if (damage == null) damage = 0;
|
||||
item.setComponent(ComponentKeys.DAMAGE, damage);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> maxDamage(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasComponent(ComponentKeys.MAX_DAMAGE)) return Optional.of((int) item.getItem().getType().getMaxDurability());
|
||||
return Optional.ofNullable(
|
||||
(Integer) ComponentType.encodeJava(
|
||||
ComponentKeys.MAX_DAMAGE,
|
||||
item.getComponent(ComponentKeys.MAX_DAMAGE)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maxDamage(ItemWrapper<ItemStack> item, Integer damage) {
|
||||
if (damage == null) {
|
||||
item.removeComponent(ComponentKeys.MAX_DAMAGE);
|
||||
} else {
|
||||
item.setComponent(ComponentKeys.MAX_DAMAGE, damage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Enchantment> getEnchantment(ItemWrapper<ItemStack> item, Key key) {
|
||||
Object enchant = item.getComponent(ComponentKeys.ENCHANTMENTS);
|
||||
try {
|
||||
Map<String, Integer> map = EnchantmentUtils.toMap(enchant);
|
||||
Integer level = map.get(key.toString());
|
||||
if (level == null) return Optional.empty();
|
||||
return Optional.of(new Enchantment(key, level));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to get enchantment " + key, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enchantments(ItemWrapper<ItemStack> item, List<Enchantment> enchantments) {
|
||||
Map<String, Integer> enchants = new HashMap<>();
|
||||
for (Enchantment enchantment : enchantments) {
|
||||
enchants.put(enchantment.id().toString(), enchantment.level());
|
||||
}
|
||||
item.setComponent(ComponentKeys.ENCHANTMENTS, enchants);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void storedEnchantments(ItemWrapper<ItemStack> item, List<Enchantment> enchantments) {
|
||||
Map<String, Integer> enchants = new HashMap<>();
|
||||
for (Enchantment enchantment : enchantments) {
|
||||
enchants.put(enchantment.id().toString(), enchantment.level());
|
||||
}
|
||||
item.setComponent(ComponentKeys.STORED_ENCHANTMENTS, enchants);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addEnchantment(ItemWrapper<ItemStack> item, Enchantment enchantment) {
|
||||
Object enchant = item.getComponent(ComponentKeys.ENCHANTMENTS);
|
||||
try {
|
||||
Map<String, Integer> map = EnchantmentUtils.toMap(enchant);
|
||||
map.put(enchantment.toString(), enchantment.level());
|
||||
item.setComponent(ComponentKeys.ENCHANTMENTS, map);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to add enchantment", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addStoredEnchantment(ItemWrapper<ItemStack> item, Enchantment enchantment) {
|
||||
Object enchant = item.getComponent(ComponentKeys.STORED_ENCHANTMENTS);
|
||||
try {
|
||||
Map<String, Integer> map = EnchantmentUtils.toMap(enchant);
|
||||
map.put(enchantment.toString(), enchantment.level());
|
||||
item.setComponent(ComponentKeys.STORED_ENCHANTMENTS, map);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to add stored enchantment", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void itemFlags(ItemWrapper<ItemStack> item, List<String> flags) {
|
||||
throw new UnsupportedOperationException("This feature is not available on 1.20.5+");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int maxStackSize(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasComponent(ComponentKeys.MAX_STACK_SIZE)) return item.getItem().getType().getMaxStackSize();
|
||||
return Optional.ofNullable((Integer) ComponentType.encodeJava(ComponentKeys.MAX_STACK_SIZE, item.getComponent(ComponentKeys.MAX_STACK_SIZE)).orElse(null))
|
||||
.orElse(item.getItem().getType().getMaxStackSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maxStackSize(ItemWrapper<ItemStack> item, Integer maxStackSize) {
|
||||
if (maxStackSize == null) {
|
||||
item.removeComponent(ComponentKeys.MAX_STACK_SIZE);
|
||||
} else {
|
||||
item.setComponent(ComponentKeys.MAX_STACK_SIZE, maxStackSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package net.momirealms.craftengine.bukkit.item.factory;
|
||||
|
||||
import com.saicone.rtag.tag.TagBase;
|
||||
import com.saicone.rtag.tag.TagCompound;
|
||||
import com.saicone.rtag.tag.TagList;
|
||||
import net.momirealms.craftengine.core.item.Enchantment;
|
||||
import net.momirealms.craftengine.core.item.ItemWrapper;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.SkullUtils;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
public class UniversalItemFactory extends BukkitItemFactory {
|
||||
|
||||
public UniversalItemFactory(CraftEngine plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayName(ItemWrapper<ItemStack> item, String json) {
|
||||
if (json != null) {
|
||||
item.set(json, "display", "Name");
|
||||
} else {
|
||||
item.remove("display", "Name");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<String> displayName(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasTag("display", "Name")) return Optional.empty();
|
||||
return Optional.of(item.get("display", "Name"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void itemName(ItemWrapper<ItemStack> item, String json) {
|
||||
throw new UnsupportedOperationException("This feature is only available on 1.20.5+");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<String> itemName(ItemWrapper<ItemStack> item) {
|
||||
throw new UnsupportedOperationException("This feature is only available on 1.20.5+");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customModelData(ItemWrapper<ItemStack> item, Integer data) {
|
||||
if (data == null) {
|
||||
item.remove("CustomModelData");
|
||||
} else {
|
||||
item.set(data, "CustomModelData");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> customModelData(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasTag("CustomModelData")) return Optional.empty();
|
||||
return Optional.of(item.get("CustomModelData"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void skull(ItemWrapper<ItemStack> item, String skullData) {
|
||||
if (skullData == null) {
|
||||
item.remove("SkullOwner");
|
||||
} else {
|
||||
item.set(UUID.nameUUIDFromBytes(SkullUtils.identifierFromBase64(skullData).getBytes(StandardCharsets.UTF_8)), "SkullOwner", "Id");
|
||||
item.set(
|
||||
List.of(Map.of("Value", skullData)),
|
||||
"SkullOwner", "Properties", "textures"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<List<String>> lore(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasTag("display", "Lore")) return Optional.empty();
|
||||
return Optional.of(item.get("display", "Lore"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void lore(ItemWrapper<ItemStack> item, List<String> lore) {
|
||||
if (lore == null || lore.isEmpty()) {
|
||||
item.remove("display", "Lore");
|
||||
} else {
|
||||
item.set(lore, "display", "Lore");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean unbreakable(ItemWrapper<ItemStack> item) {
|
||||
return Optional.ofNullable((Boolean) item.get("Unbreakable")).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unbreakable(ItemWrapper<ItemStack> item, boolean unbreakable) {
|
||||
item.set(unbreakable, "Unbreakable");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Boolean> glint(ItemWrapper<ItemStack> item) {
|
||||
return Optional.of(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void glint(ItemWrapper<ItemStack> item, Boolean glint) {
|
||||
throw new UnsupportedOperationException("This feature is only available on 1.20.5+");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> damage(ItemWrapper<ItemStack> item) {
|
||||
if (!item.hasTag("Damage")) return Optional.empty();
|
||||
return Optional.of(item.get("Damage"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void damage(ItemWrapper<ItemStack> item, Integer damage) {
|
||||
item.set(damage, "Damage");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> maxDamage(ItemWrapper<ItemStack> item) {
|
||||
// if (!item.hasTag("CustomFishing", "max_dur")) return Optional.empty();
|
||||
// return Optional.of(item.get("CustomFishing", "max_dur"));
|
||||
return Optional.of((int) item.getItem().getType().getMaxDurability());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maxDamage(ItemWrapper<ItemStack> item, Integer damage) {
|
||||
// if (damage == null) {
|
||||
// item.remove("CustomFishing", "max_dur");
|
||||
// } else {
|
||||
// item.set(damage, "CustomFishing", "max_dur");
|
||||
// }
|
||||
throw new UnsupportedOperationException("This feature is only available on 1.20.5+");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enchantments(ItemWrapper<ItemStack> item, List<Enchantment> enchantments) {
|
||||
ArrayList<Object> tags = new ArrayList<>();
|
||||
for (Enchantment enchantment : enchantments) {
|
||||
tags.add((Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level())));
|
||||
}
|
||||
item.set(tags, "Enchantments");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void storedEnchantments(ItemWrapper<ItemStack> item, List<Enchantment> enchantments) {
|
||||
ArrayList<Object> tags = new ArrayList<>();
|
||||
for (Enchantment enchantment : enchantments) {
|
||||
tags.add((Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level())));
|
||||
}
|
||||
item.set(tags, "StoredEnchantments");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addEnchantment(ItemWrapper<ItemStack> item, Enchantment enchantment) {
|
||||
Object enchantments = item.getExact("Enchantments");
|
||||
if (enchantments != null) {
|
||||
for (Object enchant : TagList.getValue(enchantments)) {
|
||||
if (TagBase.getValue(TagCompound.get(enchant, "id")).equals(enchant.toString())) {
|
||||
TagCompound.set(enchant, "lvl", TagBase.newTag(enchantment.level()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
item.add(Map.of("id", enchantment.toString(), "lvl", (short) enchantment.level()), "Enchantments");
|
||||
} else {
|
||||
item.set(List.of(Map.of("id", enchantment.toString(), "lvl", (short) enchantment.level())), "Enchantments");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addStoredEnchantment(ItemWrapper<ItemStack> item, Enchantment enchantment) {
|
||||
Object enchantments = item.getExact("StoredEnchantments");
|
||||
if (enchantments != null) {
|
||||
for (Object enchant : TagList.getValue(enchantments)) {
|
||||
if (TagBase.getValue(TagCompound.get(enchant, "id")).equals(enchant.toString())) {
|
||||
TagCompound.set(enchant, "lvl", TagBase.newTag(enchantment.level()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
item.add(Map.of("id", enchantment.toString(), "lvl", (short) enchantment.level()), "StoredEnchantments");
|
||||
} else {
|
||||
item.set(List.of(Map.of("id", enchantment.toString(), "lvl", (short) enchantment.level())), "StoredEnchantments");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
protected Optional<Enchantment> getEnchantment(ItemWrapper<ItemStack> item, Key key) {
|
||||
int level = item.getItem().getEnchantmentLevel(Objects.requireNonNull(Registry.ENCHANTMENT.get(new NamespacedKey(key.namespace(), key.value()))));
|
||||
if (level <= 0) return Optional.empty();
|
||||
return Optional.of(new Enchantment(key, level));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void itemFlags(ItemWrapper<ItemStack> item, List<String> flags) {
|
||||
if (flags == null || flags.isEmpty()) {
|
||||
item.remove("HideFlags");
|
||||
return;
|
||||
}
|
||||
int f = 0;
|
||||
for (String flag : flags) {
|
||||
ItemFlag itemFlag = ItemFlag.valueOf(flag);
|
||||
f = f | 1 << itemFlag.ordinal();
|
||||
}
|
||||
item.set(f, "HideFlags");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int maxStackSize(ItemWrapper<ItemStack> item) {
|
||||
return item.getItem().getType().getMaxStackSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maxStackSize(ItemWrapper<ItemStack> item, Integer maxStackSize) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,892 @@
|
||||
package net.momirealms.craftengine.bukkit.item.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.saicone.rtag.item.ItemObject;
|
||||
import com.saicone.rtag.tag.TagCompound;
|
||||
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.craftengine.bukkit.item.CloneableConstantItem;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.MaterialUtils;
|
||||
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.input.RecipeInput;
|
||||
import net.momirealms.craftengine.core.item.recipe.vanilla.*;
|
||||
import net.momirealms.craftengine.core.item.recipe.vanilla.reader.VanillaRecipeReader1_20;
|
||||
import net.momirealms.craftengine.core.item.recipe.vanilla.reader.VanillaRecipeReader1_20_5;
|
||||
import net.momirealms.craftengine.core.item.recipe.vanilla.reader.VanillaRecipeReader1_21_2;
|
||||
import net.momirealms.craftengine.core.pack.Pack;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
|
||||
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
|
||||
import net.momirealms.craftengine.core.registry.Holder;
|
||||
import net.momirealms.craftengine.core.util.HeptaFunction;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.PentaFunction;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.inventory.*;
|
||||
import org.bukkit.inventory.recipe.CookingBookCategory;
|
||||
import org.bukkit.inventory.recipe.CraftingBookCategory;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BukkitRecipeManager implements RecipeManager<ItemStack> {
|
||||
private static final Map<Key, BiConsumer<NamespacedKey, Recipe<ItemStack>>> BUKKIT_RECIPE_FACTORIES = new HashMap<>();
|
||||
private static Object minecraftRecipeManager;
|
||||
private static final List<Object> injectedIngredients = new ArrayList<>();
|
||||
private static final IdentityHashMap<Recipe<ItemStack>, Object> recipeToMcRecipeHolder = new IdentityHashMap<>();
|
||||
private static BukkitRecipeManager instance;
|
||||
|
||||
static {
|
||||
BUKKIT_RECIPE_FACTORIES.put(RecipeTypes.SHAPED, (key, recipe) -> {
|
||||
CustomShapedRecipe<ItemStack> ceRecipe = (CustomShapedRecipe<ItemStack>) recipe;
|
||||
ShapedRecipe shapedRecipe = new ShapedRecipe(key, ceRecipe.result(ItemBuildContext.EMPTY));
|
||||
if (ceRecipe.group() != null) {
|
||||
shapedRecipe.setGroup(Objects.requireNonNull(ceRecipe.group()));
|
||||
}
|
||||
if (ceRecipe.category() != null) {
|
||||
shapedRecipe.setCategory(CraftingBookCategory.valueOf(Objects.requireNonNull(ceRecipe.category()).name()));
|
||||
}
|
||||
shapedRecipe.shape(ceRecipe.pattern().pattern());
|
||||
for (Map.Entry<Character, Ingredient<ItemStack>> entry : ceRecipe.pattern().ingredients().entrySet()) {
|
||||
shapedRecipe.setIngredient(entry.getKey(), new RecipeChoice.MaterialChoice(ingredientToBukkitMaterials(entry.getValue())));
|
||||
}
|
||||
try {
|
||||
Object craftRecipe = Reflections.method$CraftShapedRecipe$fromBukkitRecipe.invoke(null, shapedRecipe);
|
||||
Reflections.method$CraftRecipe$addToCraftingManager.invoke(craftRecipe);
|
||||
injectShapedRecipe(new Key(key.namespace(), key.value()), ceRecipe);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to convert shaped recipe", e);
|
||||
}
|
||||
});
|
||||
BUKKIT_RECIPE_FACTORIES.put(RecipeTypes.SHAPELESS, (key, recipe) -> {
|
||||
CustomShapelessRecipe<ItemStack> ceRecipe = (CustomShapelessRecipe<ItemStack>) recipe;
|
||||
ShapelessRecipe shapelessRecipe = new ShapelessRecipe(key, ceRecipe.result(ItemBuildContext.EMPTY));
|
||||
if (ceRecipe.group() != null) {
|
||||
shapelessRecipe.setGroup(Objects.requireNonNull(ceRecipe.group()));
|
||||
}
|
||||
if (ceRecipe.category() != null) {
|
||||
shapelessRecipe.setCategory(CraftingBookCategory.valueOf(Objects.requireNonNull(ceRecipe.category()).name()));
|
||||
}
|
||||
for (Ingredient<ItemStack> ingredient : ceRecipe.ingredients()) {
|
||||
shapelessRecipe.addIngredient(new RecipeChoice.MaterialChoice(ingredientToBukkitMaterials(ingredient)));
|
||||
}
|
||||
try {
|
||||
Object craftRecipe = Reflections.method$CraftShapelessRecipe$fromBukkitRecipe.invoke(null, shapelessRecipe);
|
||||
Reflections.method$CraftRecipe$addToCraftingManager.invoke(craftRecipe);
|
||||
injectShapelessRecipe(new Key(key.namespace(), key.value()), ceRecipe);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to convert shapeless recipe", e);
|
||||
}
|
||||
});
|
||||
BUKKIT_RECIPE_FACTORIES.put(RecipeTypes.SMELTING, (key, recipe) -> {
|
||||
CustomSmeltingRecipe<ItemStack> ceRecipe = (CustomSmeltingRecipe<ItemStack>) recipe;
|
||||
FurnaceRecipe furnaceRecipe = new FurnaceRecipe(
|
||||
key, ceRecipe.result(ItemBuildContext.EMPTY),
|
||||
new RecipeChoice.MaterialChoice(ingredientToBukkitMaterials(ceRecipe.ingredient())),
|
||||
ceRecipe.experience(), ceRecipe.cookingTime()
|
||||
);
|
||||
if (ceRecipe.group() != null) {
|
||||
furnaceRecipe.setGroup(Objects.requireNonNull(ceRecipe.group()));
|
||||
}
|
||||
if (ceRecipe.category() != null) {
|
||||
furnaceRecipe.setCategory(CookingBookCategory.valueOf(Objects.requireNonNull(ceRecipe.category()).name()));
|
||||
}
|
||||
try {
|
||||
Object craftRecipe = Reflections.method$CraftFurnaceRecipe$fromBukkitRecipe.invoke(null, furnaceRecipe);
|
||||
Reflections.method$CraftRecipe$addToCraftingManager.invoke(craftRecipe);
|
||||
injectCookingRecipe(new Key(key.namespace(), key.value()), ceRecipe);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to convert smelting recipe", e);
|
||||
}
|
||||
});
|
||||
BUKKIT_RECIPE_FACTORIES.put(RecipeTypes.SMOKING, (key, recipe) -> {
|
||||
CustomSmokingRecipe<ItemStack> ceRecipe = (CustomSmokingRecipe<ItemStack>) recipe;
|
||||
SmokingRecipe smokingRecipe = new SmokingRecipe(
|
||||
key, ceRecipe.result(ItemBuildContext.EMPTY),
|
||||
new RecipeChoice.MaterialChoice(ingredientToBukkitMaterials(ceRecipe.ingredient())),
|
||||
ceRecipe.experience(), ceRecipe.cookingTime()
|
||||
);
|
||||
if (ceRecipe.group() != null) {
|
||||
smokingRecipe.setGroup(Objects.requireNonNull(ceRecipe.group()));
|
||||
}
|
||||
if (ceRecipe.category() != null) {
|
||||
smokingRecipe.setCategory(CookingBookCategory.valueOf(Objects.requireNonNull(ceRecipe.category()).name()));
|
||||
}
|
||||
try {
|
||||
Object craftRecipe = Reflections.method$CraftSmokingRecipe$fromBukkitRecipe.invoke(null, smokingRecipe);
|
||||
Reflections.method$CraftRecipe$addToCraftingManager.invoke(craftRecipe);
|
||||
injectCookingRecipe(new Key(key.namespace(), key.value()), ceRecipe);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to convert smoking recipe", e);
|
||||
}
|
||||
});
|
||||
BUKKIT_RECIPE_FACTORIES.put(RecipeTypes.BLASTING, (key, recipe) -> {
|
||||
CustomBlastingRecipe<ItemStack> ceRecipe = (CustomBlastingRecipe<ItemStack>) recipe;
|
||||
BlastingRecipe blastingRecipe = new BlastingRecipe(
|
||||
key, ceRecipe.result(ItemBuildContext.EMPTY),
|
||||
new RecipeChoice.MaterialChoice(ingredientToBukkitMaterials(ceRecipe.ingredient())),
|
||||
ceRecipe.experience(), ceRecipe.cookingTime()
|
||||
);
|
||||
if (ceRecipe.group() != null) {
|
||||
blastingRecipe.setGroup(Objects.requireNonNull(ceRecipe.group()));
|
||||
}
|
||||
if (ceRecipe.category() != null) {
|
||||
blastingRecipe.setCategory(CookingBookCategory.valueOf(Objects.requireNonNull(ceRecipe.category()).name()));
|
||||
}
|
||||
try {
|
||||
Object craftRecipe = Reflections.method$CraftBlastingRecipe$fromBukkitRecipe.invoke(null, blastingRecipe);
|
||||
Reflections.method$CraftRecipe$addToCraftingManager.invoke(craftRecipe);
|
||||
injectCookingRecipe(new Key(key.namespace(), key.value()), ceRecipe);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to convert blasting recipe", e);
|
||||
}
|
||||
});
|
||||
BUKKIT_RECIPE_FACTORIES.put(RecipeTypes.CAMPFIRE_COOKING, (key, recipe) -> {
|
||||
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) recipe;
|
||||
CampfireRecipe campfireRecipe = new CampfireRecipe(
|
||||
key, ceRecipe.result(ItemBuildContext.EMPTY),
|
||||
new RecipeChoice.MaterialChoice(ingredientToBukkitMaterials(ceRecipe.ingredient())),
|
||||
ceRecipe.experience(), ceRecipe.cookingTime()
|
||||
);
|
||||
if (ceRecipe.group() != null) {
|
||||
campfireRecipe.setGroup(Objects.requireNonNull(ceRecipe.group()));
|
||||
}
|
||||
if (ceRecipe.category() != null) {
|
||||
campfireRecipe.setCategory(CookingBookCategory.valueOf(Objects.requireNonNull(ceRecipe.category()).name()));
|
||||
}
|
||||
try {
|
||||
Object craftRecipe = Reflections.method$CraftCampfireRecipe$fromBukkitRecipe.invoke(null, campfireRecipe);
|
||||
Reflections.method$CraftRecipe$addToCraftingManager.invoke(craftRecipe);
|
||||
injectCookingRecipe(new Key(key.namespace(), key.value()), ceRecipe);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to convert campfire recipe", e);
|
||||
}
|
||||
});
|
||||
BUKKIT_RECIPE_FACTORIES.put(RecipeTypes.STONE_CUTTING, (key, recipe) -> {
|
||||
CustomStoneCuttingRecipe<ItemStack> ceRecipe = (CustomStoneCuttingRecipe<ItemStack>) recipe;
|
||||
List<ItemStack> itemStacks = new ArrayList<>();
|
||||
for (Holder<Key> item : ceRecipe.ingredient().items()) {
|
||||
itemStacks.add(BukkitItemManager.instance().buildItemStack(item.value(), null));
|
||||
}
|
||||
StonecuttingRecipe stonecuttingRecipe = new StonecuttingRecipe(
|
||||
key, ceRecipe.result(ItemBuildContext.EMPTY),
|
||||
new RecipeChoice.ExactChoice(itemStacks)
|
||||
);
|
||||
if (ceRecipe.group() != null) {
|
||||
stonecuttingRecipe.setGroup(Objects.requireNonNull(ceRecipe.group()));
|
||||
}
|
||||
try {
|
||||
Object craftRecipe = Reflections.method$CraftStonecuttingRecipe$fromBukkitRecipe.invoke(null, stonecuttingRecipe);
|
||||
Reflections.method$CraftRecipe$addToCraftingManager.invoke(craftRecipe);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to convert stone cutting recipe", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final BukkitCraftEngine plugin;
|
||||
private final RecipeEventListener recipeEventListener;
|
||||
private final CrafterEventListener crafterEventListener;
|
||||
private final Map<Key, List<Recipe<ItemStack>>> byType;
|
||||
private final Map<Key, Recipe<ItemStack>> byId;
|
||||
private final Map<Key, List<Recipe<ItemStack>>> byResult;
|
||||
private final VanillaRecipeReader recipeReader;
|
||||
private final List<NamespacedKey> injectedDataPackRecipes;
|
||||
private final List<NamespacedKey> registeredCustomRecipes;
|
||||
// data pack recipe resource locations [minecraft:xxx]
|
||||
private final Set<Key> dataPackRecipes;
|
||||
|
||||
private Object stolenFeatureFlagSet;
|
||||
|
||||
public BukkitRecipeManager(BukkitCraftEngine plugin) {
|
||||
instance = this;
|
||||
this.plugin = plugin;
|
||||
this.byType = new HashMap<>();
|
||||
this.byId = new HashMap<>();
|
||||
this.byResult = new HashMap<>();
|
||||
this.injectedDataPackRecipes = new ArrayList<>();
|
||||
this.registeredCustomRecipes = new ArrayList<>();
|
||||
this.dataPackRecipes = new HashSet<>();
|
||||
this.recipeEventListener = new RecipeEventListener(plugin, this, plugin.itemManager());
|
||||
if (VersionHelper.isVersionNewerThan1_21()) {
|
||||
this.crafterEventListener = new CrafterEventListener(plugin, this, plugin.itemManager());
|
||||
} else {
|
||||
this.crafterEventListener = null;
|
||||
}
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
this.recipeReader = new VanillaRecipeReader1_21_2();
|
||||
} else if (VersionHelper.isVersionNewerThan1_20_5()) {
|
||||
this.recipeReader = new VanillaRecipeReader1_20_5();
|
||||
} else {
|
||||
this.recipeReader = new VanillaRecipeReader1_20();
|
||||
}
|
||||
try {
|
||||
minecraftRecipeManager = Reflections.method$MinecraftServer$getRecipeManager.invoke(Reflections.method$MinecraftServer$getServer.invoke(null));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to get minecraft recipe manager", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDataPackRecipe(Key key) {
|
||||
return this.dataPackRecipes.contains(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCustomRecipe(Key key) {
|
||||
return this.byId.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Recipe<ItemStack>> getRecipeById(Key key) {
|
||||
return Optional.ofNullable(this.byId.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayedInit() {
|
||||
Bukkit.getPluginManager().registerEvents(this.recipeEventListener, this.plugin.bootstrap());
|
||||
if (this.crafterEventListener != null) {
|
||||
Bukkit.getPluginManager().registerEvents(this.crafterEventListener, this.plugin.bootstrap());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
try {
|
||||
this.stolenFeatureFlagSet = Reflections.field$RecipeManager$featureflagset.get(minecraftRecipeManager);
|
||||
Reflections.field$RecipeManager$featureflagset.set(minecraftRecipeManager, null);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
this.plugin.logger().warn("Failed to steal featureflagset", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
this.byType.clear();
|
||||
this.byId.clear();
|
||||
this.byResult.clear();
|
||||
this.dataPackRecipes.clear();
|
||||
|
||||
try {
|
||||
// do not unregister them
|
||||
// for (NamespacedKey key : this.injectedDataPackRecipes) {
|
||||
// unregisterRecipe(key);
|
||||
// }
|
||||
for (NamespacedKey key : this.registeredCustomRecipes) {
|
||||
unregisterRecipe(key);
|
||||
}
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
Reflections.method$RecipeManager$finalizeRecipeLoading.invoke(minecraftRecipeManager);
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to unregister recipes", e);
|
||||
}
|
||||
|
||||
this.registeredCustomRecipes.clear();
|
||||
this.injectedDataPackRecipes.clear();
|
||||
|
||||
recipeToMcRecipeHolder.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
HandlerList.unregisterAll(this.recipeEventListener);
|
||||
if (this.crafterEventListener != null) {
|
||||
HandlerList.unregisterAll(this.crafterEventListener);
|
||||
}
|
||||
unload();
|
||||
}
|
||||
|
||||
private void unregisterRecipe(NamespacedKey key) throws ReflectiveOperationException {
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
Object recipeMap = Reflections.field$RecipeManager$recipes.get(minecraftRecipeManager);
|
||||
Reflections.method$RecipeMap$removeRecipe.invoke(recipeMap, Reflections.method$CraftRecipe$toMinecraft.invoke(null, key));
|
||||
} else {
|
||||
Bukkit.removeRecipe(key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
if (this.byId.containsKey(id)) {
|
||||
this.plugin.logger().warn(path, "Duplicated recipe " + id);
|
||||
return;
|
||||
}
|
||||
Recipe<ItemStack> recipe = RecipeTypes.fromMap(id, section);
|
||||
NamespacedKey key = NamespacedKey.fromString(id.toString());
|
||||
BUKKIT_RECIPE_FACTORIES.get(recipe.type()).accept(key, recipe);
|
||||
try {
|
||||
this.registeredCustomRecipes.add(key);
|
||||
this.byType.computeIfAbsent(recipe.type(), k -> new ArrayList<>()).add(recipe);
|
||||
this.byId.put(id, recipe);
|
||||
this.byResult.computeIfAbsent(recipe.result().item().id(), k -> new ArrayList<>()).add(recipe);
|
||||
} catch (Exception e) {
|
||||
plugin.logger().warn("Failed to add custom recipe " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Recipe<ItemStack>> getRecipes(Key type) {
|
||||
return this.byType.getOrDefault(type, List.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Recipe<ItemStack>> getRecipeByResult(Key result) {
|
||||
return this.byResult.getOrDefault(result, List.of());
|
||||
}
|
||||
|
||||
// example: stone button
|
||||
public void addVanillaInternalRecipe(Key id, Recipe<ItemStack> recipe) {
|
||||
this.byType.computeIfAbsent(recipe.type(), k -> new ArrayList<>()).add(recipe);
|
||||
this.byId.put(id, recipe);
|
||||
this.byResult.computeIfAbsent(recipe.result().item().id(), k -> new ArrayList<>()).add(recipe);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Recipe<ItemStack> getRecipe(Key type, RecipeInput input) {
|
||||
List<Recipe<ItemStack>> recipes = this.byType.get(type);
|
||||
if (recipes == null) return null;
|
||||
for (Recipe<ItemStack> recipe : recipes) {
|
||||
if (recipe.matches(input)) {
|
||||
return recipe;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Recipe<ItemStack> getRecipe(Key type, RecipeInput input, Key lastRecipe) {
|
||||
if (lastRecipe != null) {
|
||||
Recipe<ItemStack> last = byId.get(lastRecipe);
|
||||
if (last != null && last.matches(input)) {
|
||||
return last;
|
||||
}
|
||||
}
|
||||
return getRecipe(type, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> delayedLoad() {
|
||||
if (!ConfigManager.enableRecipeSystem()) return CompletableFuture.completedFuture(null);
|
||||
return this.processVanillaRecipes().thenRun(() -> {
|
||||
try {
|
||||
// give flags back on 1.21.2+
|
||||
if (VersionHelper.isVersionNewerThan1_21_2() && this.stolenFeatureFlagSet != null) {
|
||||
Reflections.field$RecipeManager$featureflagset.set(minecraftRecipeManager, this.stolenFeatureFlagSet);
|
||||
this.stolenFeatureFlagSet = false;
|
||||
}
|
||||
|
||||
// refresh recipes
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
Reflections.method$RecipeManager$finalizeRecipeLoading.invoke(minecraftRecipeManager);
|
||||
}
|
||||
|
||||
// send to players
|
||||
Reflections.method$DedicatedPlayerList$reloadRecipes.invoke(Reflections.field$CraftServer$playerList.get(Bukkit.getServer()));
|
||||
|
||||
// now we need to remove the fake `exact`
|
||||
if (VersionHelper.isVersionNewerThan1_21_4()) {
|
||||
for (Object ingredient : injectedIngredients) {
|
||||
Reflections.field$Ingredient$itemStacks1_21_4.set(ingredient, null);
|
||||
}
|
||||
} else if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
for (Object ingredient : injectedIngredients) {
|
||||
Reflections.field$Ingredient$itemStacks1_21_2.set(ingredient, null);
|
||||
}
|
||||
}
|
||||
|
||||
// clear cache
|
||||
injectedIngredients.clear();
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to run delayed recipe tasks", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private CompletableFuture<Void> processVanillaRecipes() {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
try {
|
||||
List<Runnable> injectLogics = new ArrayList<>();
|
||||
plugin.scheduler().async().execute(() -> {
|
||||
try {
|
||||
Object fileToIdConverter = Reflections.method$FileToIdConverter$json.invoke(null, VersionHelper.isVersionNewerThan1_21() ? "recipe" : "recipes");
|
||||
Object minecraftServer = Reflections.method$MinecraftServer$getServer.invoke(null);
|
||||
Object packRepository = Reflections.method$MinecraftServer$getPackRepository.invoke(minecraftServer);
|
||||
List<Object> selected = (List<Object>) Reflections.field$PackRepository$selected.get(packRepository);
|
||||
List<Object> packResources = new ArrayList<>();
|
||||
for (Object pack : selected) {
|
||||
packResources.add(Reflections.method$Pack$open.invoke(pack));
|
||||
}
|
||||
try (AutoCloseable resourceManager = (AutoCloseable) Reflections.constructor$MultiPackResourceManager.newInstance(Reflections.instance$PackType$SERVER_DATA, packResources)) {
|
||||
Map<Object, Object> scannedResources = (Map<Object, Object>) Reflections.method$FileToIdConverter$listMatchingResources.invoke(fileToIdConverter, resourceManager);
|
||||
for (Map.Entry<Object, Object> entry : scannedResources.entrySet()) {
|
||||
Key id = extractKeyFromResourceLocation(entry.getKey().toString());
|
||||
this.dataPackRecipes.add(id);
|
||||
// Maybe it's unregistered by other plugins
|
||||
if (Bukkit.getRecipe(new NamespacedKey(id.namespace(), id.value())) == null) {
|
||||
continue;
|
||||
}
|
||||
Reader reader = (Reader) Reflections.method$Resource$openAsReader.invoke(entry.getValue());
|
||||
JsonObject jsonObject = JsonParser.parseReader(reader).getAsJsonObject();
|
||||
String type = jsonObject.get("type").getAsString();
|
||||
switch (type) {
|
||||
case "minecraft:crafting_shaped" -> {
|
||||
VanillaShapedRecipe recipe = this.recipeReader.readShaped(jsonObject);
|
||||
handleDataPackShapedRecipe(id, recipe, (injectLogics::add));
|
||||
}
|
||||
case "minecraft:crafting_shapeless" -> {
|
||||
VanillaShapelessRecipe recipe = this.recipeReader.readShapeless(jsonObject);
|
||||
handleDataPackShapelessRecipe(id, recipe, (injectLogics::add));
|
||||
}
|
||||
case "minecraft:smelting" -> {
|
||||
VanillaSmeltingRecipe recipe = this.recipeReader.readSmelting(jsonObject);
|
||||
handleDataPackCookingRecipe(id, recipe, FurnaceRecipe::new, CustomSmeltingRecipe::new, Reflections.method$CraftFurnaceRecipe$fromBukkitRecipe, (injectLogics::add));
|
||||
}
|
||||
case "minecraft:blasting" -> {
|
||||
VanillaBlastingRecipe recipe = this.recipeReader.readBlasting(jsonObject);
|
||||
handleDataPackCookingRecipe(id, recipe, BlastingRecipe::new, CustomBlastingRecipe::new, Reflections.method$CraftBlastingRecipe$fromBukkitRecipe, (injectLogics::add));
|
||||
}
|
||||
case "minecraft:smoking" -> {
|
||||
VanillaSmokingRecipe recipe = this.recipeReader.readSmoking(jsonObject);
|
||||
handleDataPackCookingRecipe(id, recipe, SmokingRecipe::new, CustomSmokingRecipe::new, Reflections.method$CraftSmokingRecipe$fromBukkitRecipe, (injectLogics::add));
|
||||
}
|
||||
case "minecraft:campfire_cooking" -> {
|
||||
VanillaCampfireRecipe recipe = this.recipeReader.readCampfire(jsonObject);
|
||||
handleDataPackCookingRecipe(id, recipe, CampfireRecipe::new, CustomCampfireRecipe::new, Reflections.method$CraftCampfireRecipe$fromBukkitRecipe, (injectLogics::add));
|
||||
}
|
||||
case "minecraft:stonecutting" -> {
|
||||
VanillaStoneCuttingRecipe recipe = this.recipeReader.readStoneCutting(jsonObject);
|
||||
handleDataPackStoneCuttingRecipe(id, recipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.logger().warn("Failed to read data pack recipes", e);
|
||||
} finally {
|
||||
plugin.scheduler().sync().run(() -> {
|
||||
try {
|
||||
for (Runnable runnable : injectLogics) {
|
||||
runnable.run();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to register recipes", e);
|
||||
} finally {
|
||||
future.complete(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to inject vanilla recipes", e);
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
private void handleDataPackShapelessRecipe(Key id, VanillaShapelessRecipe recipe, Consumer<Runnable> callback) {
|
||||
NamespacedKey key = new NamespacedKey(id.namespace(), id.value());
|
||||
ItemStack result = createResultStack(recipe.result());
|
||||
ShapelessRecipe shapelessRecipe = new ShapelessRecipe(key, result);
|
||||
if (recipe.group() != null) {
|
||||
shapelessRecipe.setGroup(recipe.group());
|
||||
}
|
||||
if (recipe.category() != null) {
|
||||
shapelessRecipe.setCategory(CraftingBookCategory.valueOf(recipe.category().name()));
|
||||
}
|
||||
|
||||
boolean hasCustomItemInTag = false;
|
||||
List<Ingredient<ItemStack>> ingredientList = new ArrayList<>();
|
||||
for (List<String> list : recipe.ingredients()) {
|
||||
Set<Material> materials = new HashSet<>();
|
||||
Set<Holder<Key>> holders = new HashSet<>();
|
||||
for (String item : list) {
|
||||
if (item.charAt(0) == '#') {
|
||||
Key tag = Key.of(item.substring(1));
|
||||
materials.addAll(tagToMaterials(tag));
|
||||
if (!hasCustomItemInTag) {
|
||||
if (!plugin.itemManager().tagToCustomItems(tag).isEmpty()) {
|
||||
hasCustomItemInTag = true;
|
||||
}
|
||||
}
|
||||
holders.addAll(plugin.itemManager().tagToItems(tag));
|
||||
} else {
|
||||
materials.add(MaterialUtils.getMaterial(item));
|
||||
holders.add(BuiltInRegistries.OPTIMIZED_ITEM_ID.get(Key.from(item)).orElseThrow());
|
||||
}
|
||||
}
|
||||
shapelessRecipe.addIngredient(new RecipeChoice.MaterialChoice(new ArrayList<>(materials)));
|
||||
ingredientList.add(Ingredient.of(holders));
|
||||
}
|
||||
|
||||
CustomShapelessRecipe<ItemStack> ceRecipe = new CustomShapelessRecipe<>(
|
||||
id,
|
||||
recipe.category(),
|
||||
recipe.group(),
|
||||
ingredientList,
|
||||
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), result), recipe.result().count())
|
||||
);
|
||||
if (hasCustomItemInTag) {
|
||||
callback.accept(() -> {
|
||||
try {
|
||||
unregisterRecipe(key);
|
||||
Reflections.method$CraftRecipe$addToCraftingManager.invoke(Reflections.method$CraftShapelessRecipe$fromBukkitRecipe.invoke(null, shapelessRecipe));
|
||||
injectShapelessRecipe(id, ceRecipe);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to convert shapeless recipe", e);
|
||||
}
|
||||
});
|
||||
this.injectedDataPackRecipes.add(key);
|
||||
}
|
||||
this.addVanillaInternalRecipe(id, ceRecipe);
|
||||
}
|
||||
|
||||
private void handleDataPackStoneCuttingRecipe(Key id, VanillaStoneCuttingRecipe recipe) {
|
||||
ItemStack result = createResultStack(recipe.result());
|
||||
Set<Holder<Key>> holders = new HashSet<>();
|
||||
|
||||
for (String item : recipe.ingredient()) {
|
||||
if (item.charAt(0) == '#') {
|
||||
Key tag = Key.from(item.substring(1));
|
||||
holders.addAll(plugin.itemManager().tagToItems(tag));
|
||||
} else {
|
||||
holders.add(BuiltInRegistries.OPTIMIZED_ITEM_ID.get(Key.from(item)).orElseThrow());
|
||||
}
|
||||
}
|
||||
|
||||
CustomStoneCuttingRecipe<ItemStack> ceRecipe = new CustomStoneCuttingRecipe<>(
|
||||
id,
|
||||
recipe.group(),
|
||||
Ingredient.of(holders),
|
||||
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), result), recipe.result().count())
|
||||
);
|
||||
this.addVanillaInternalRecipe(id, ceRecipe);
|
||||
}
|
||||
|
||||
private void handleDataPackShapedRecipe(Key id, VanillaShapedRecipe recipe, Consumer<Runnable> callback) {
|
||||
NamespacedKey key = new NamespacedKey(id.namespace(), id.value());
|
||||
ItemStack result = createResultStack(recipe.result());
|
||||
ShapedRecipe shapedRecipe = new ShapedRecipe(key, result);
|
||||
if (recipe.group() != null) {
|
||||
shapedRecipe.setGroup(recipe.group());
|
||||
}
|
||||
if (recipe.category() != null) {
|
||||
shapedRecipe.setCategory(CraftingBookCategory.valueOf(recipe.category().name()));
|
||||
}
|
||||
shapedRecipe.shape(recipe.pattern());
|
||||
|
||||
boolean hasCustomItemInTag = false;
|
||||
Map<Character, Ingredient<ItemStack>> ingredients = new HashMap<>();
|
||||
for (Map.Entry<Character, List<String>> entry : recipe.ingredients().entrySet()) {
|
||||
Set<Material> materials = new HashSet<>();
|
||||
Set<Holder<Key>> holders = new HashSet<>();
|
||||
for (String item : entry.getValue()) {
|
||||
if (item.charAt(0) == '#') {
|
||||
Key tag = Key.from(item.substring(1));
|
||||
materials.addAll(tagToMaterials(tag));
|
||||
if (!hasCustomItemInTag) {
|
||||
if (!plugin.itemManager().tagToCustomItems(tag).isEmpty()) {
|
||||
hasCustomItemInTag = true;
|
||||
}
|
||||
}
|
||||
holders.addAll(plugin.itemManager().tagToItems(tag));
|
||||
} else {
|
||||
materials.add(MaterialUtils.getMaterial(item));
|
||||
holders.add(BuiltInRegistries.OPTIMIZED_ITEM_ID.get(Key.from(item)).orElseThrow());
|
||||
}
|
||||
}
|
||||
shapedRecipe.setIngredient(entry.getKey(), new RecipeChoice.MaterialChoice(new ArrayList<>(materials)));
|
||||
ingredients.put(entry.getKey(), Ingredient.of(holders));
|
||||
}
|
||||
|
||||
CustomShapedRecipe<ItemStack> ceRecipe = new CustomShapedRecipe<>(
|
||||
id,
|
||||
recipe.category(),
|
||||
recipe.group(),
|
||||
new CustomShapedRecipe.Pattern<>(recipe.pattern(), ingredients),
|
||||
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), result), recipe.result().count())
|
||||
);
|
||||
if (hasCustomItemInTag) {
|
||||
callback.accept(() -> {
|
||||
try {
|
||||
unregisterRecipe(key);
|
||||
Reflections.method$CraftRecipe$addToCraftingManager.invoke(Reflections.method$CraftShapedRecipe$fromBukkitRecipe.invoke(null, shapedRecipe));
|
||||
injectShapedRecipe(id, ceRecipe);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to convert shaped recipe", e);
|
||||
}
|
||||
});
|
||||
this.injectedDataPackRecipes.add(key);
|
||||
}
|
||||
this.addVanillaInternalRecipe(id, ceRecipe);
|
||||
}
|
||||
|
||||
private void handleDataPackCookingRecipe(Key id,
|
||||
VanillaCookingRecipe recipe,
|
||||
PentaFunction<NamespacedKey, ItemStack, RecipeChoice, Float, Integer, org.bukkit.inventory.CookingRecipe<?>> constructor1,
|
||||
HeptaFunction<Key, CookingRecipeCategory, String, Ingredient<ItemStack>, Integer, Float, CustomRecipeResult<ItemStack>, CustomCookingRecipe<ItemStack>> constructor2,
|
||||
Method fromBukkitRecipeMethod,
|
||||
Consumer<Runnable> callback) {
|
||||
NamespacedKey key = new NamespacedKey(id.namespace(), id.value());
|
||||
ItemStack result = createResultStack(recipe.result());
|
||||
|
||||
Set<Material> materials = new HashSet<>();
|
||||
Set<Holder<Key>> holders = new HashSet<>();
|
||||
|
||||
boolean hasCustomItemInTag = false;
|
||||
for (String item : recipe.ingredient()) {
|
||||
if (item.charAt(0) == '#') {
|
||||
Key tag = Key.from(item.substring(1));
|
||||
materials.addAll(tagToMaterials(tag));
|
||||
if (!hasCustomItemInTag) {
|
||||
if (!plugin.itemManager().tagToCustomItems(tag).isEmpty()) {
|
||||
hasCustomItemInTag = true;
|
||||
}
|
||||
}
|
||||
holders.addAll(plugin.itemManager().tagToItems(tag));
|
||||
} else {
|
||||
materials.add(MaterialUtils.getMaterial(item));
|
||||
holders.add(BuiltInRegistries.OPTIMIZED_ITEM_ID.get(Key.from(item)).orElseThrow());
|
||||
}
|
||||
}
|
||||
org.bukkit.inventory.CookingRecipe<?> cookingRecipe = constructor1.apply(key, result, new RecipeChoice.MaterialChoice(new ArrayList<>(materials)), recipe.experience(), recipe.cookingTime());
|
||||
if (recipe.group() != null) {
|
||||
cookingRecipe.setGroup(recipe.group());
|
||||
}
|
||||
if (recipe.category() != null) {
|
||||
cookingRecipe.setCategory(CookingBookCategory.valueOf(recipe.category().name()));
|
||||
}
|
||||
|
||||
CustomCookingRecipe<ItemStack> ceRecipe = constructor2.apply(
|
||||
id,
|
||||
recipe.category(),
|
||||
recipe.group(),
|
||||
Ingredient.of(holders),
|
||||
recipe.cookingTime(),
|
||||
recipe.experience(),
|
||||
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), result), recipe.result().count())
|
||||
);
|
||||
if (hasCustomItemInTag) {
|
||||
callback.accept(() -> {
|
||||
try {
|
||||
unregisterRecipe(key);
|
||||
Reflections.method$CraftRecipe$addToCraftingManager.invoke(fromBukkitRecipeMethod.invoke(null, cookingRecipe));
|
||||
injectCookingRecipe(id, ceRecipe);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to convert smelting recipe", e);
|
||||
}
|
||||
});
|
||||
this.injectedDataPackRecipes.add(key);
|
||||
}
|
||||
this.addVanillaInternalRecipe(id, ceRecipe);
|
||||
}
|
||||
|
||||
private List<Material> tagToMaterials(Key tag) {
|
||||
Set<Material> materials = new HashSet<>();
|
||||
List<Holder<Key>> holders = this.plugin.itemManager().tagToVanillaItems(tag);
|
||||
if (holders != null) {
|
||||
for (Holder<Key> holder : holders) {
|
||||
materials.add(MaterialUtils.getMaterial(holder.value()));
|
||||
}
|
||||
}
|
||||
List<Holder<Key>> customItems = this.plugin.itemManager().tagToCustomItems(tag);
|
||||
if (customItems != null) {
|
||||
for (Holder<Key> holder : customItems) {
|
||||
this.plugin.itemManager().getCustomItem(holder.value()).ifPresent(it -> {
|
||||
materials.add(MaterialUtils.getMaterial(it.material()));
|
||||
});
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(materials);
|
||||
}
|
||||
|
||||
private ItemStack createResultStack(RecipeResult result) {
|
||||
ItemStack itemStack;
|
||||
if (result.components() == null) {
|
||||
itemStack = new ItemStack(Objects.requireNonNull(MaterialUtils.getMaterial(result.id())));
|
||||
itemStack.setAmount(result.count());
|
||||
} else {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("id", result.id());
|
||||
jsonObject.addProperty("count", result.count());
|
||||
jsonObject.add("components", result.components());
|
||||
Object nmsStack = ItemObject.newItem(TagCompound.newTag(jsonObject.toString()));
|
||||
try {
|
||||
itemStack = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, nmsStack);
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to create ItemStack mirror", e);
|
||||
return new ItemStack(Material.STICK);
|
||||
}
|
||||
}
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
private Key extractKeyFromResourceLocation(String input) {
|
||||
int prefixEndIndex = input.indexOf(':');
|
||||
String prefix = input.substring(0, prefixEndIndex);
|
||||
int lastSlashIndex = input.lastIndexOf('/');
|
||||
int lastDotIndex = input.lastIndexOf('.');
|
||||
String fileName = input.substring(lastSlashIndex + 1, lastDotIndex);
|
||||
return Key.of(prefix, fileName);
|
||||
}
|
||||
|
||||
private static List<Material> ingredientToBukkitMaterials(Ingredient<ItemStack> ingredient) {
|
||||
Set<Material> materials = new HashSet<>();
|
||||
for (Holder<Key> holder : ingredient.items()) {
|
||||
materials.add(getMaterialById(holder.value()));
|
||||
}
|
||||
return new ArrayList<>(materials);
|
||||
}
|
||||
|
||||
private static Material getMaterialById(Key key) {
|
||||
Material material = MaterialUtils.getMaterial(key);
|
||||
if (material != null) {
|
||||
return material;
|
||||
}
|
||||
Optional<CustomItem<ItemStack>> optionalItem = BukkitItemManager.instance().getCustomItem(key);
|
||||
return optionalItem.map(itemStackCustomItem -> MaterialUtils.getMaterial(itemStackCustomItem.material())).orElse(null);
|
||||
}
|
||||
|
||||
private static List<Object> getIngredientLooks(List<Holder<Key>> holders) throws ReflectiveOperationException {
|
||||
List<Object> itemStacks = new ArrayList<>();
|
||||
for (Holder<Key> holder : holders) {
|
||||
ItemStack itemStack = BukkitItemManager.instance().getBuildableItem(holder.value()).get().buildItemStack(ItemBuildContext.EMPTY, 1);
|
||||
Object nmsStack = Reflections.method$CraftItemStack$asNMSMirror.invoke(null, itemStack);
|
||||
itemStacks.add(nmsStack);
|
||||
}
|
||||
return itemStacks;
|
||||
}
|
||||
|
||||
private static void injectShapedRecipe(Key id, CustomShapedRecipe<ItemStack> recipe) throws ReflectiveOperationException {
|
||||
List<Ingredient<ItemStack>> actualIngredients = recipe.parsedPattern().ingredients()
|
||||
.stream()
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.toList();
|
||||
|
||||
Object shapedRecipe = getNMSRecipe(id);
|
||||
recipeToMcRecipeHolder.put(recipe, shapedRecipe);
|
||||
if (VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
shapedRecipe = Reflections.field$RecipeHolder$recipe.get(shapedRecipe);
|
||||
}
|
||||
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
Reflections.field$ShapedRecipe$placementInfo.set(shapedRecipe, null);
|
||||
}
|
||||
List<Object> ingredients = RecipeUtils.getIngredientsFromShapedRecipe(shapedRecipe);
|
||||
injectIngredients(ingredients, actualIngredients);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void injectShapelessRecipe(Key id, CustomShapelessRecipe<ItemStack> recipe) throws ReflectiveOperationException {
|
||||
List<Ingredient<ItemStack>> actualIngredients = recipe.ingredients();
|
||||
|
||||
Object shapelessRecipe = getNMSRecipe(id);
|
||||
recipeToMcRecipeHolder.put(recipe, shapelessRecipe);
|
||||
if (VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
shapelessRecipe = Reflections.field$RecipeHolder$recipe.get(shapelessRecipe);
|
||||
}
|
||||
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
Reflections.field$ShapelessRecipe$placementInfo.set(shapelessRecipe, null);
|
||||
}
|
||||
List<Object> ingredients = (List<Object>) Reflections.field$ShapelessRecipe$ingredients.get(shapelessRecipe);
|
||||
injectIngredients(ingredients, actualIngredients);
|
||||
}
|
||||
|
||||
private static void injectCookingRecipe(Key id, CustomCookingRecipe<ItemStack> recipe) throws ReflectiveOperationException {
|
||||
Ingredient<ItemStack> actualIngredient = recipe.ingredient();
|
||||
Object smeltingRecipe = getNMSRecipe(id);
|
||||
recipeToMcRecipeHolder.put(recipe, smeltingRecipe);
|
||||
if (VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
smeltingRecipe = Reflections.field$RecipeHolder$recipe.get(smeltingRecipe);
|
||||
}
|
||||
|
||||
Object ingredient;
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
ingredient = Reflections.field$SingleItemRecipe$input.get(smeltingRecipe);
|
||||
} else {
|
||||
ingredient = Reflections.field$AbstractCookingRecipe$input.get(smeltingRecipe);
|
||||
}
|
||||
injectIngredients(List.of(ingredient), List.of(actualIngredient));
|
||||
}
|
||||
|
||||
// recipe on 1.20.1 and holder on 1.20.2+
|
||||
private static Object getNMSRecipe(Key id) throws ReflectiveOperationException {
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
Object resourceKey = Reflections.method$CraftRecipe$toMinecraft.invoke(null, new NamespacedKey(id.namespace(), id.value()));
|
||||
@SuppressWarnings("unchecked")
|
||||
Optional<Object> optional = (Optional<Object>) Reflections.method$RecipeManager$byKey.invoke(minecraftRecipeManager, resourceKey);
|
||||
if (optional.isEmpty()) {
|
||||
throw new IllegalArgumentException("Recipe " + id + " not found");
|
||||
}
|
||||
return optional.get();
|
||||
} else {
|
||||
Object resourceLocation = Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, id.namespace(), id.value());
|
||||
@SuppressWarnings("unchecked")
|
||||
Optional<Object> optional = (Optional<Object>) Reflections.method$RecipeManager$byKey.invoke(minecraftRecipeManager, resourceLocation);
|
||||
if (optional.isEmpty()) {
|
||||
throw new IllegalArgumentException("Recipe " + id + " not found");
|
||||
}
|
||||
return optional.get();
|
||||
}
|
||||
}
|
||||
|
||||
private static void injectIngredients(List<Object> ingredients, List<Ingredient<ItemStack>> actualIngredients) throws ReflectiveOperationException {
|
||||
if (ingredients.size() != actualIngredients.size()) {
|
||||
throw new IllegalArgumentException("Ingredient count mismatch");
|
||||
}
|
||||
for (int i = 0; i < ingredients.size(); i++) {
|
||||
Object ingredient = ingredients.get(i);
|
||||
Ingredient<ItemStack> actualIngredient = actualIngredients.get(i);
|
||||
List<Object> items = getIngredientLooks(actualIngredient.items());
|
||||
if (VersionHelper.isVersionNewerThan1_21_4()) {
|
||||
Reflections.field$Ingredient$itemStacks1_21_4.set(ingredient, new HashSet<>(items));
|
||||
} else if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
Reflections.field$Ingredient$itemStacks1_21_2.set(ingredient, items);
|
||||
} else {
|
||||
Object itemStackArray = Array.newInstance(Reflections.clazz$ItemStack, items.size());
|
||||
for (int j = 0; j < items.size(); j++) {
|
||||
Array.set(itemStackArray, j, items.get(j));
|
||||
}
|
||||
Reflections.field$Ingredient$itemStacks1_20_1.set(ingredient, itemStackArray);
|
||||
}
|
||||
injectedIngredients.add(ingredient);
|
||||
}
|
||||
}
|
||||
|
||||
public Object getRecipeHolderByRecipe(Recipe<ItemStack> recipe) {
|
||||
return recipeToMcRecipeHolder.get(recipe);
|
||||
}
|
||||
|
||||
public static Object minecraftRecipeManager() {
|
||||
return minecraftRecipeManager;
|
||||
}
|
||||
|
||||
public static BukkitRecipeManager instance() {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package net.momirealms.craftengine.bukkit.item.recipe;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.ItemUtils;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.item.ItemBuildContext;
|
||||
import net.momirealms.craftengine.core.item.ItemManager;
|
||||
import net.momirealms.craftengine.core.item.recipe.OptimizedIDItem;
|
||||
import net.momirealms.craftengine.core.item.recipe.Recipe;
|
||||
import net.momirealms.craftengine.core.item.recipe.RecipeTypes;
|
||||
import net.momirealms.craftengine.core.item.recipe.input.CraftingInput;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
|
||||
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
|
||||
import net.momirealms.craftengine.core.registry.Holder;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.bukkit.block.Crafter;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.CrafterCraftEvent;
|
||||
import org.bukkit.inventory.CraftingRecipe;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CrafterEventListener implements Listener {
|
||||
private static final OptimizedIDItem<ItemStack> EMPTY = new OptimizedIDItem<>(null, null);
|
||||
private final ItemManager<ItemStack> itemManager;
|
||||
private final BukkitRecipeManager recipeManager;
|
||||
private final BukkitCraftEngine plugin;
|
||||
|
||||
public CrafterEventListener(BukkitCraftEngine plugin, BukkitRecipeManager recipeManager, ItemManager<ItemStack> itemManager) {
|
||||
this.itemManager = itemManager;
|
||||
this.recipeManager = recipeManager;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onCrafting(CrafterCraftEvent event) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
CraftingRecipe recipe = event.getRecipe();
|
||||
if (!(event.getBlock().getState() instanceof Crafter crafter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Key recipeId = Key.of(recipe.getKey().namespace(), recipe.getKey().value());
|
||||
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
|
||||
|
||||
// Maybe it's recipe from other plugins, then we ignore it
|
||||
if (!isCustom) {
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory inventory = crafter.getInventory();
|
||||
ItemStack[] ingredients = inventory.getStorageContents();
|
||||
|
||||
List<OptimizedIDItem<ItemStack>> optimizedIDItems = new ArrayList<>();
|
||||
for (ItemStack itemStack : ingredients) {
|
||||
if (ItemUtils.isEmpty(itemStack)) {
|
||||
optimizedIDItems.add(EMPTY);
|
||||
} else {
|
||||
Item<ItemStack> wrappedItem = this.itemManager.wrap(itemStack);
|
||||
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
|
||||
if (idHolder.isEmpty()) {
|
||||
// an invalid item is used in recipe, we disallow it
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
} else {
|
||||
optimizedIDItems.add(new OptimizedIDItem<>(idHolder.get(), itemStack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CraftingInput<ItemStack> input;
|
||||
if (ingredients.length == 9) {
|
||||
input = CraftingInput.of(3, 3, optimizedIDItems);
|
||||
} else if (ingredients.length == 4) {
|
||||
input = CraftingInput.of(2, 2, optimizedIDItems);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
Recipe<ItemStack> ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SHAPELESS, input);
|
||||
if (ceRecipe != null) {
|
||||
event.setResult(ceRecipe.result(ItemBuildContext.EMPTY));
|
||||
return;
|
||||
}
|
||||
ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SHAPED, input);
|
||||
if (ceRecipe != null) {
|
||||
event.setResult(ceRecipe.result(ItemBuildContext.EMPTY));
|
||||
return;
|
||||
}
|
||||
// clear result if not met
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
package net.momirealms.craftengine.bukkit.item.recipe;
|
||||
|
||||
import com.destroystokyo.paper.event.inventory.PrepareResultEvent;
|
||||
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
|
||||
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
|
||||
import net.momirealms.craftengine.bukkit.util.ItemUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.item.ItemBuildContext;
|
||||
import net.momirealms.craftengine.core.item.ItemManager;
|
||||
import net.momirealms.craftengine.core.item.recipe.CustomCampfireRecipe;
|
||||
import net.momirealms.craftengine.core.item.recipe.OptimizedIDItem;
|
||||
import net.momirealms.craftengine.core.item.recipe.Recipe;
|
||||
import net.momirealms.craftengine.core.item.recipe.RecipeTypes;
|
||||
import net.momirealms.craftengine.core.item.recipe.input.CraftingInput;
|
||||
import net.momirealms.craftengine.core.item.recipe.input.SingleItemInput;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
|
||||
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
|
||||
import net.momirealms.craftengine.core.registry.Holder;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import net.momirealms.craftengine.core.util.context.ContextHolder;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Campfire;
|
||||
import org.bukkit.block.Furnace;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.*;
|
||||
import org.bukkit.event.inventory.*;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class RecipeEventListener implements Listener {
|
||||
private static final OptimizedIDItem<ItemStack> EMPTY = new OptimizedIDItem<>(null, null);
|
||||
private final ItemManager<ItemStack> itemManager;
|
||||
private final BukkitRecipeManager recipeManager;
|
||||
private final BukkitCraftEngine plugin;
|
||||
|
||||
public RecipeEventListener(BukkitCraftEngine plugin, BukkitRecipeManager recipeManager, ItemManager<ItemStack> itemManager) {
|
||||
this.itemManager = itemManager;
|
||||
this.recipeManager = recipeManager;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onClickInventoryWithFuel(InventoryClickEvent event) {
|
||||
Inventory inventory = event.getInventory();
|
||||
if (!(inventory instanceof FurnaceInventory furnaceInventory)) return;
|
||||
ItemStack fuelStack = furnaceInventory.getFuel();
|
||||
Inventory clickedInventory = event.getClickedInventory();
|
||||
|
||||
Player player = (Player) event.getWhoClicked();
|
||||
if (clickedInventory == player.getInventory()) {
|
||||
if (event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT) {
|
||||
ItemStack item = event.getCurrentItem();
|
||||
if (ItemUtils.isEmpty(item)) return;
|
||||
if (fuelStack == null || fuelStack.getType() == Material.AIR) {
|
||||
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(item);
|
||||
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
|
||||
if (idHolder.isEmpty()) return;
|
||||
|
||||
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), item));
|
||||
Key recipeType;
|
||||
if (furnaceInventory.getType() == InventoryType.FURNACE) {
|
||||
recipeType = RecipeTypes.SMELTING;
|
||||
} else if (furnaceInventory.getType() == InventoryType.BLAST_FURNACE) {
|
||||
recipeType = RecipeTypes.BLASTING;
|
||||
} else {
|
||||
recipeType = RecipeTypes.SMOKING;
|
||||
}
|
||||
|
||||
Recipe<ItemStack> ceRecipe = recipeManager.getRecipe(recipeType, input);
|
||||
// The item is an ingredient, we should never consider it as fuel firstly
|
||||
if (ceRecipe != null) return;
|
||||
|
||||
int fuelTime = this.itemManager.fuelTime(item);
|
||||
if (fuelTime == 0) {
|
||||
if (ItemUtils.isCustomItem(item) && item.getType().isFuel()) {
|
||||
event.setCancelled(true);
|
||||
ItemStack smelting = furnaceInventory.getSmelting();
|
||||
if (ItemUtils.isEmpty(smelting)) {
|
||||
furnaceInventory.setSmelting(item.clone());
|
||||
item.setAmount(0);
|
||||
} else if (smelting.isSimilar(item)) {
|
||||
int maxStackSize = smelting.getMaxStackSize();
|
||||
int canGiveMaxCount = item.getAmount();
|
||||
if (maxStackSize > smelting.getAmount()) {
|
||||
if (canGiveMaxCount + smelting.getAmount() >= maxStackSize) {
|
||||
int givenCount = maxStackSize - smelting.getAmount();
|
||||
smelting.setAmount(maxStackSize);
|
||||
item.setAmount(item.getAmount() - givenCount);
|
||||
} else {
|
||||
smelting.setAmount(smelting.getAmount() + canGiveMaxCount);
|
||||
item.setAmount(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
player.updateInventory();
|
||||
}
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
furnaceInventory.setFuel(item.clone());
|
||||
item.setAmount(0);
|
||||
player.updateInventory();
|
||||
} else {
|
||||
if (fuelStack.isSimilar(item)) {
|
||||
event.setCancelled(true);
|
||||
int maxStackSize = fuelStack.getMaxStackSize();
|
||||
int canGiveMaxCount = item.getAmount();
|
||||
if (maxStackSize > fuelStack.getAmount()) {
|
||||
if (canGiveMaxCount + fuelStack.getAmount() >= maxStackSize) {
|
||||
int givenCount = maxStackSize - fuelStack.getAmount();
|
||||
fuelStack.setAmount(maxStackSize);
|
||||
item.setAmount(item.getAmount() - givenCount);
|
||||
} else {
|
||||
fuelStack.setAmount(fuelStack.getAmount() + canGiveMaxCount);
|
||||
item.setAmount(0);
|
||||
}
|
||||
player.updateInventory();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// click the furnace inventory
|
||||
int slot = event.getSlot();
|
||||
// click the fuel slot
|
||||
if (slot != 1) {
|
||||
return;
|
||||
}
|
||||
ClickType clickType = event.getClick();
|
||||
switch (clickType) {
|
||||
case SWAP_OFFHAND, NUMBER_KEY -> {
|
||||
ItemStack item;
|
||||
int hotBarSlot = event.getHotbarButton();
|
||||
if (clickType == ClickType.SWAP_OFFHAND) {
|
||||
item = player.getInventory().getItemInOffHand();
|
||||
} else {
|
||||
item = player.getInventory().getItem(hotBarSlot);
|
||||
}
|
||||
if (item == null) return;
|
||||
int fuelTime = this.plugin.itemManager().fuelTime(item);
|
||||
// only handle custom items
|
||||
if (fuelTime == 0) {
|
||||
if (ItemUtils.isCustomItem(item) && item.getType().isFuel()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
if (fuelStack == null || fuelStack.getType() == Material.AIR) {
|
||||
furnaceInventory.setFuel(item.clone());
|
||||
item.setAmount(0);
|
||||
} else {
|
||||
if (clickType == ClickType.SWAP_OFFHAND) {
|
||||
player.getInventory().setItemInOffHand(fuelStack);
|
||||
} else {
|
||||
player.getInventory().setItem(hotBarSlot, fuelStack);
|
||||
}
|
||||
furnaceInventory.setFuel(item.clone());
|
||||
}
|
||||
player.updateInventory();
|
||||
}
|
||||
case LEFT, RIGHT -> {
|
||||
ItemStack itemOnCursor = event.getCursor();
|
||||
// pick item
|
||||
if (ItemUtils.isEmpty(itemOnCursor)) return;
|
||||
int fuelTime = this.plugin.itemManager().fuelTime(itemOnCursor);
|
||||
// only handle custom items
|
||||
if (fuelTime == 0) {
|
||||
if (ItemUtils.isCustomItem(itemOnCursor) && itemOnCursor.getType().isFuel()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
// The slot is empty
|
||||
if (fuelStack == null || fuelStack.getType() == Material.AIR) {
|
||||
if (clickType == ClickType.LEFT) {
|
||||
furnaceInventory.setFuel(itemOnCursor.clone());
|
||||
itemOnCursor.setAmount(0);
|
||||
player.updateInventory();
|
||||
} else {
|
||||
ItemStack cloned = itemOnCursor.clone();
|
||||
cloned.setAmount(1);
|
||||
furnaceInventory.setFuel(cloned);
|
||||
itemOnCursor.setAmount(itemOnCursor.getAmount() - 1);
|
||||
player.updateInventory();
|
||||
}
|
||||
} else {
|
||||
boolean isSimilar = itemOnCursor.isSimilar(fuelStack);
|
||||
if (clickType == ClickType.LEFT) {
|
||||
if (isSimilar) {
|
||||
int maxStackSize = fuelStack.getMaxStackSize();
|
||||
int canGiveMaxCount = itemOnCursor.getAmount();
|
||||
if (maxStackSize > fuelStack.getAmount()) {
|
||||
if (canGiveMaxCount + fuelStack.getAmount() >= maxStackSize) {
|
||||
int givenCount = maxStackSize - fuelStack.getAmount();
|
||||
fuelStack.setAmount(maxStackSize);
|
||||
itemOnCursor.setAmount(itemOnCursor.getAmount() - givenCount);
|
||||
} else {
|
||||
fuelStack.setAmount(fuelStack.getAmount() + canGiveMaxCount);
|
||||
itemOnCursor.setAmount(0);
|
||||
}
|
||||
player.updateInventory();
|
||||
}
|
||||
} else {
|
||||
// swap item
|
||||
event.setCursor(fuelStack);
|
||||
furnaceInventory.setFuel(itemOnCursor.clone());
|
||||
player.updateInventory();
|
||||
}
|
||||
} else {
|
||||
if (isSimilar) {
|
||||
int maxStackSize = fuelStack.getMaxStackSize();
|
||||
if (maxStackSize > fuelStack.getAmount()) {
|
||||
fuelStack.setAmount(fuelStack.getAmount() + 1);
|
||||
itemOnCursor.setAmount(itemOnCursor.getAmount() - 1);
|
||||
player.updateInventory();
|
||||
}
|
||||
} else {
|
||||
// swap item
|
||||
event.setCursor(fuelStack);
|
||||
furnaceInventory.setFuel(itemOnCursor.clone());
|
||||
player.updateInventory();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onFurnaceBurn(FurnaceBurnEvent event) {
|
||||
ItemStack fuel = event.getFuel();
|
||||
int fuelTime = this.itemManager.fuelTime(fuel);
|
||||
if (fuelTime != 0) {
|
||||
event.setBurnTime(fuelTime);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onFurnaceInventoryOpen(InventoryOpenEvent event) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
if (!(event.getInventory() instanceof FurnaceInventory furnaceInventory)) {
|
||||
return;
|
||||
}
|
||||
Furnace furnace = furnaceInventory.getHolder();
|
||||
try {
|
||||
Object blockEntity = Reflections.field$CraftBlockEntityState$tileEntity.get(furnace);
|
||||
BukkitInjector.injectCookingBlockEntity(blockEntity);
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to inject cooking block entity", e);
|
||||
}
|
||||
}
|
||||
|
||||
// for 1.20.1-1.21.1
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockIgnite(BlockIgniteEvent event) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) return;
|
||||
Block block = event.getBlock();
|
||||
Material material = block.getType();
|
||||
if (material == Material.CAMPFIRE) {
|
||||
if (block.getState() instanceof Campfire campfire) {
|
||||
try {
|
||||
Object blockEntity = Reflections.field$CraftBlockEntityState$tileEntity.get(campfire);
|
||||
BukkitInjector.injectCookingBlockEntity(blockEntity);
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to inject cooking block entity", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onPlaceBlock(BlockPlaceEvent event) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
Block block = event.getBlock();
|
||||
Material material = block.getType();
|
||||
if (material == Material.FURNACE || material == Material.BLAST_FURNACE || material == Material.SMOKER) {
|
||||
if (block.getState() instanceof Furnace furnace) {
|
||||
try {
|
||||
Object blockEntity = Reflections.field$CraftBlockEntityState$tileEntity.get(furnace);
|
||||
BukkitInjector.injectCookingBlockEntity(blockEntity);
|
||||
} catch (Exception e) {
|
||||
plugin.logger().warn("Failed to inject cooking block entity", e);
|
||||
}
|
||||
}
|
||||
} else if (!VersionHelper.isVersionNewerThan1_21_2() && material == Material.CAMPFIRE) {
|
||||
if (block.getState() instanceof Campfire campfire) {
|
||||
try {
|
||||
Object blockEntity = Reflections.field$CraftBlockEntityState$tileEntity.get(campfire);
|
||||
BukkitInjector.injectCookingBlockEntity(blockEntity);
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to inject cooking block entity", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for 1.21.2+
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPutItemOnCampfire(PlayerInteractEvent event) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
if (!VersionHelper.isVersionNewerThan1_21_2()) return;
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
|
||||
Block clicked = event.getClickedBlock();
|
||||
if (clicked == null) return;
|
||||
Material type = clicked.getType();
|
||||
if (type != Material.CAMPFIRE && type != Material.SOUL_CAMPFIRE) return;
|
||||
if (!VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
if (clicked.getState() instanceof Campfire campfire) {
|
||||
try {
|
||||
Object blockEntity = Reflections.field$CraftBlockEntityState$tileEntity.get(campfire);
|
||||
BukkitInjector.injectCookingBlockEntity(blockEntity);
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to inject cooking block entity", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemStack itemStack = event.getItem();
|
||||
if (ItemUtils.isEmpty(itemStack)) return;
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Optional<Object> optionalMCRecipe = (Optional<Object>) Reflections.method$RecipeManager$getRecipeFor1.invoke(
|
||||
BukkitRecipeManager.minecraftRecipeManager(),
|
||||
Reflections.instance$RecipeType$CAMPFIRE_COOKING,
|
||||
Reflections.constructor$SingleRecipeInput.newInstance(Reflections.method$CraftItemStack$asNMSMirror.invoke(null, itemStack)),
|
||||
Reflections.field$CraftWorld$ServerLevel.get(event.getPlayer().getWorld()),
|
||||
null
|
||||
);
|
||||
if (optionalMCRecipe.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
|
||||
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
|
||||
if (idHolder.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
|
||||
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input);
|
||||
if (ceRecipe == null) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to handle interact campfire", e);
|
||||
}
|
||||
}
|
||||
|
||||
// for 1.21.2+
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onCampfireCook(CampfireStartEvent event) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
if (!VersionHelper.isVersionNewerThan1_21_2()) return;
|
||||
CampfireRecipe recipe = event.getRecipe();
|
||||
Key recipeId = new Key(recipe.getKey().namespace(), recipe.getKey().value());
|
||||
|
||||
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
|
||||
if (!isCustom) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack itemStack = event.getSource();
|
||||
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
|
||||
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
|
||||
if (idHolder.isEmpty()) {
|
||||
event.setTotalCookTime(Integer.MAX_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
|
||||
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input);
|
||||
if (ceRecipe == null) {
|
||||
event.setTotalCookTime(Integer.MAX_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
event.setTotalCookTime(ceRecipe.cookingTime());
|
||||
}
|
||||
|
||||
// for 1.21.2+
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onCampfireCook(BlockCookEvent event) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
if (!VersionHelper.isVersionNewerThan1_21_2()) return;
|
||||
Material type = event.getBlock().getType();
|
||||
if (type != Material.CAMPFIRE && type != Material.SOUL_CAMPFIRE) return;
|
||||
CampfireRecipe recipe = (CampfireRecipe) event.getRecipe();
|
||||
Key recipeId = new Key(recipe.getKey().namespace(), recipe.getKey().value());
|
||||
|
||||
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
|
||||
if (!isCustom) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack itemStack = event.getSource();
|
||||
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
|
||||
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
|
||||
if (idHolder.isEmpty()) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
|
||||
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input);
|
||||
if (ceRecipe == null) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
event.setResult(ceRecipe.result(ItemBuildContext.EMPTY));
|
||||
}
|
||||
|
||||
// Paper only
|
||||
@EventHandler
|
||||
public void onPrepareResult(PrepareResultEvent event) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
if (event.getInventory() instanceof CartographyInventory cartographyInventory) {
|
||||
if (ItemUtils.hasCustomItem(cartographyInventory.getStorageContents())) {
|
||||
event.setResult(new ItemStack(Material.AIR));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onSpecialRecipe(PrepareItemCraftEvent event) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
org.bukkit.inventory.Recipe recipe = event.getRecipe();
|
||||
if (recipe == null)
|
||||
return;
|
||||
if (!(recipe instanceof ComplexRecipe))
|
||||
return;
|
||||
CraftingInventory inventory = event.getInventory();
|
||||
if (ItemUtils.hasCustomItem(inventory.getMatrix())) {
|
||||
inventory.setResult(null);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onCraftingRecipe(PrepareItemCraftEvent event) {
|
||||
if (!ConfigManager.enableRecipeSystem()) return;
|
||||
org.bukkit.inventory.Recipe recipe = event.getRecipe();
|
||||
if (recipe == null)
|
||||
return;
|
||||
|
||||
// we only handle shaped and shapeless recipes
|
||||
boolean shapeless = event.getRecipe() instanceof ShapelessRecipe;
|
||||
boolean shaped = event.getRecipe() instanceof ShapedRecipe;
|
||||
if (!shaped && !shapeless) return;
|
||||
|
||||
CraftingRecipe craftingRecipe = (CraftingRecipe) recipe;
|
||||
Key recipeId = Key.of(craftingRecipe.getKey().namespace(), craftingRecipe.getKey().value());
|
||||
|
||||
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
|
||||
// Maybe it's recipe from other plugins, then we ignore it
|
||||
if (!isCustom) {
|
||||
return;
|
||||
}
|
||||
|
||||
CraftingInventory inventory = event.getInventory();
|
||||
ItemStack[] ingredients = inventory.getMatrix();
|
||||
|
||||
List<OptimizedIDItem<ItemStack>> optimizedIDItems = new ArrayList<>();
|
||||
for (ItemStack itemStack : ingredients) {
|
||||
if (ItemUtils.isEmpty(itemStack)) {
|
||||
optimizedIDItems.add(EMPTY);
|
||||
} else {
|
||||
Item<ItemStack> wrappedItem = this.itemManager.wrap(itemStack);
|
||||
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
|
||||
if (idHolder.isEmpty()) {
|
||||
// an invalid item is used in recipe, we disallow it
|
||||
inventory.setResult(null);
|
||||
return;
|
||||
} else {
|
||||
optimizedIDItems.add(new OptimizedIDItem<>(idHolder.get(), itemStack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CraftingInput<ItemStack> input;
|
||||
if (ingredients.length == 9) {
|
||||
input = CraftingInput.of(3, 3, optimizedIDItems);
|
||||
} else if (ingredients.length == 4) {
|
||||
input = CraftingInput.of(2, 2, optimizedIDItems);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player;
|
||||
try {
|
||||
player = (Player) Reflections.method$InventoryView$getPlayer.invoke(event.getView());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to get inventory viewer", e);
|
||||
return;
|
||||
}
|
||||
|
||||
BukkitServerPlayer serverPlayer = this.plugin.adapt(player);
|
||||
Key lastRecipe = serverPlayer.lastUsedRecipe();
|
||||
|
||||
Recipe<ItemStack> ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SHAPELESS, input, lastRecipe);
|
||||
if (ceRecipe != null) {
|
||||
inventory.setResult(ceRecipe.result(new ItemBuildContext(serverPlayer, ContextHolder.EMPTY)));
|
||||
serverPlayer.setLastUsedRecipe(ceRecipe.id());
|
||||
correctCraftingRecipeUsed(inventory, ceRecipe);
|
||||
return;
|
||||
}
|
||||
ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SHAPED, input, lastRecipe);
|
||||
if (ceRecipe != null) {
|
||||
inventory.setResult(ceRecipe.result(new ItemBuildContext(serverPlayer, ContextHolder.EMPTY)));
|
||||
serverPlayer.setLastUsedRecipe(ceRecipe.id());
|
||||
correctCraftingRecipeUsed(inventory, ceRecipe);
|
||||
return;
|
||||
}
|
||||
// clear result if not met
|
||||
inventory.setResult(null);
|
||||
}
|
||||
|
||||
private void correctCraftingRecipeUsed(CraftingInventory inventory, Recipe<ItemStack> recipe) {
|
||||
Object holderOrRecipe = recipeManager.getRecipeHolderByRecipe(recipe);
|
||||
if (holderOrRecipe == null) {
|
||||
// it's a vanilla recipe but not injected
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Object resultInventory = Reflections.field$CraftInventoryCrafting$resultInventory.get(inventory);
|
||||
Reflections.field$ResultContainer$recipeUsed.set(resultInventory, holderOrRecipe);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to correct used recipe", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
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;
|
||||
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.core.pack.AbstractPackManager;
|
||||
import net.momirealms.craftengine.core.pack.host.HostMode;
|
||||
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
|
||||
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.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class BukkitPackManager extends AbstractPackManager implements Listener {
|
||||
private final BukkitCraftEngine plugin;
|
||||
private HostMode previousHostMode = HostMode.NONE;
|
||||
private UUID previousHostUUID;
|
||||
|
||||
public BukkitPackManager(BukkitCraftEngine plugin) {
|
||||
super(plugin, (rf, zp) -> {
|
||||
AsyncResourcePackGenerateEvent endEvent = new AsyncResourcePackGenerateEvent(rf, zp);
|
||||
EventUtils.fireAndForget(endEvent);
|
||||
});
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@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 (ConfigManager.sendPackOnJoin() && !VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
this.sendResourcePack(plugin.networkManager().getUser(event.getPlayer()), null);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onResourcePackStatus(PlayerResourcePackStatusEvent event) {
|
||||
// for 1.20.1 servers, not recommended to use
|
||||
if (ConfigManager.sendPackOnJoin() && ConfigManager.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();
|
||||
|
||||
// update server properties
|
||||
if (VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
if (ConfigManager.hostMode() == HostMode.SELF_HOST) {
|
||||
if (Files.exists(resourcePackPath())) {
|
||||
updateResourcePackSettings(super.packUUID, ResourcePackHost.instance().url(), super.packHash, ConfigManager.kickOnDeclined(), ConfigManager.resourcePackPrompt());
|
||||
}
|
||||
} else if (ConfigManager.hostMode() == HostMode.EXTERNAL_HOST) {
|
||||
updateResourcePackSettings(ConfigManager.externalPackUUID(), ConfigManager.externalPackUrl(), ConfigManager.externalPackSha1(), ConfigManager.kickOnDeclined(), ConfigManager.resourcePackPrompt());
|
||||
}
|
||||
}
|
||||
|
||||
if (ConfigManager.sendPackOnReload()) {
|
||||
if (this.previousHostMode == HostMode.SELF_HOST) {
|
||||
this.previousHostUUID = super.packUUID;
|
||||
}
|
||||
// unload packs if user changed to none host
|
||||
if (ConfigManager.hostMode() == HostMode.NONE && this.previousHostMode != HostMode.NONE) {
|
||||
unloadResourcePackForOnlinePlayers(this.previousHostUUID);
|
||||
}
|
||||
// load new external resource pack on reload
|
||||
if (ConfigManager.hostMode() == HostMode.EXTERNAL_HOST) {
|
||||
if (this.previousHostMode == HostMode.NONE) {
|
||||
updateResourcePackForOnlinePlayers(null);
|
||||
} else {
|
||||
updateResourcePackForOnlinePlayers(this.previousHostUUID);
|
||||
}
|
||||
// record previous host uuid here
|
||||
this.previousHostUUID = ConfigManager.externalPackUUID();
|
||||
}
|
||||
if (ConfigManager.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 = ConfigManager.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 (ConfigManager.hostMode() == HostMode.SELF_HOST) {
|
||||
updateResourcePackSettings(super.packUUID, ResourcePackHost.instance().url(), super.packHash, ConfigManager.kickOnDeclined(), ConfigManager.resourcePackPrompt());
|
||||
}
|
||||
}
|
||||
// resend packs
|
||||
if (ConfigManager.hostMode() == HostMode.SELF_HOST && ConfigManager.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) {
|
||||
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));
|
||||
} else {
|
||||
info = Reflections.constructor$ServerResourcePackInfo.newInstance(url + uuid, sha1, required, ComponentUtils.adventureToMinecraft(prompt));
|
||||
}
|
||||
Reflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.of(info));
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to update resource pack settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendResourcePack(NetWorkUser user, @Nullable UUID previousPack) {
|
||||
if (ConfigManager.hostMode() == HostMode.NONE) return;
|
||||
String url;
|
||||
String sha1;
|
||||
UUID uuid;
|
||||
if (ConfigManager.hostMode() == HostMode.SELF_HOST) {
|
||||
url = ResourcePackHost.instance().url();
|
||||
sha1 = super.packHash;
|
||||
uuid = super.packUUID;
|
||||
if (!Files.exists(resourcePackPath())) return;
|
||||
} else {
|
||||
url = ConfigManager.externalPackUrl();
|
||||
sha1 = ConfigManager.externalPackSha1();
|
||||
uuid = ConfigManager.externalPackUUID();
|
||||
if (uuid.equals(previousPack)) return;
|
||||
}
|
||||
|
||||
Object packPrompt = ComponentUtils.adventureToMinecraft(ConfigManager.resourcePackPrompt());
|
||||
try {
|
||||
Object packPacket;
|
||||
if (VersionHelper.isVersionNewerThan1_20_5()) {
|
||||
packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(
|
||||
uuid, url, sha1, ConfigManager.kickOnDeclined(), Optional.of(packPrompt)
|
||||
);
|
||||
} else if (VersionHelper.isVersionNewerThan1_20_3()) {
|
||||
packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(
|
||||
uuid, url, sha1, ConfigManager.kickOnDeclined(), packPrompt
|
||||
);
|
||||
} else {
|
||||
packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(
|
||||
url + uuid, sha1, ConfigManager.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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin;
|
||||
|
||||
import net.momirealms.antigrieflib.AntiGriefLib;
|
||||
import net.momirealms.craftengine.bukkit.api.event.CraftEngineReloadEvent;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors;
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
|
||||
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.craftengine.bukkit.item.behavior.BukkitItemBehaviors;
|
||||
import net.momirealms.craftengine.bukkit.item.recipe.BukkitRecipeManager;
|
||||
import net.momirealms.craftengine.bukkit.pack.BukkitPackManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitSenderFactory;
|
||||
import net.momirealms.craftengine.bukkit.plugin.gui.BukkitGuiManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
|
||||
import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.papi.ImageExpansion;
|
||||
import net.momirealms.craftengine.bukkit.plugin.papi.ShiftExpansion;
|
||||
import net.momirealms.craftengine.bukkit.plugin.scheduler.BukkitSchedulerAdapter;
|
||||
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
|
||||
import net.momirealms.craftengine.bukkit.sound.BukkitSoundManager;
|
||||
import net.momirealms.craftengine.bukkit.util.EventUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.PlaceholderAPIUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.item.ItemManager;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.classpath.ReflectionClassPathAppender;
|
||||
import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory;
|
||||
import net.momirealms.craftengine.core.plugin.dependency.Dependencies;
|
||||
import net.momirealms.craftengine.core.plugin.dependency.Dependency;
|
||||
import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManagerImpl;
|
||||
import net.momirealms.craftengine.core.plugin.logger.JavaPluginLogger;
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerAdapter;
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
|
||||
import net.momirealms.craftengine.core.util.ReflectionUtils;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class BukkitCraftEngine extends CraftEngine {
|
||||
private static BukkitCraftEngine instance;
|
||||
private final JavaPlugin bootstrap;
|
||||
private SchedulerTask tickTask;
|
||||
private boolean successfullyLoaded = false;
|
||||
private boolean requiresRestart = false;
|
||||
private boolean hasMod = false;
|
||||
private AntiGriefLib antiGrief;
|
||||
private boolean hasPlaceholderAPI;
|
||||
|
||||
public BukkitCraftEngine(JavaPlugin bootstrap) {
|
||||
VersionHelper.init(serverVersion());
|
||||
instance = this;
|
||||
this.bootstrap = bootstrap;
|
||||
super.classPathAppender = new ReflectionClassPathAppender(this);
|
||||
super.scheduler = new BukkitSchedulerAdapter(this);
|
||||
super.logger = new JavaPluginLogger(bootstrap.getLogger());
|
||||
Class<?> modClass = ReflectionUtils.getClazz(MOD_CLASS);
|
||||
if (modClass != null) {
|
||||
Field isSuccessfullyRegistered = ReflectionUtils.getDeclaredField(modClass, "isSuccessfullyRegistered");
|
||||
try {
|
||||
assert isSuccessfullyRegistered != null;
|
||||
requiresRestart = !(boolean) isSuccessfullyRegistered.get(null);
|
||||
hasMod = true;
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
super.load();
|
||||
Reflections.init();
|
||||
BukkitInjector.init();
|
||||
super.networkManager = new BukkitNetworkManager(this);
|
||||
super.networkManager.init();
|
||||
// load mappings and inject blocks
|
||||
super.blockManager = new BukkitBlockManager(this);
|
||||
super.furnitureManager = new BukkitFurnitureManager(this);
|
||||
this.successfullyLoaded = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Dependency> platformDependencies() {
|
||||
return List.of(
|
||||
Dependencies.BSTATS_BUKKIT,
|
||||
Dependencies.CLOUD_BUKKIT, Dependencies.CLOUD_PAPER, Dependencies.CLOUD_BRIGADIER, Dependencies.CLOUD_MINECRAFT_EXTRAS
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
if (this.hasMod && this.requiresRestart) {
|
||||
logger().warn(" ");
|
||||
logger().warn(" ");
|
||||
logger().warn(" ");
|
||||
logger().warn("This is the first time you have installed CraftEngine. A restart is required to apply the changes.");
|
||||
logger().warn(" ");
|
||||
logger().warn(" ");
|
||||
logger().warn(" ");
|
||||
Bukkit.getServer().shutdown();
|
||||
return;
|
||||
}
|
||||
if (!this.successfullyLoaded) {
|
||||
logger().severe(" ");
|
||||
logger().severe(" ");
|
||||
logger().severe(" ");
|
||||
logger().severe("Failed to enable CraftEngine. Please check the log on loading stage.");
|
||||
logger().severe("To reduce the loss caused by plugin not loaded, now shutting down the server");
|
||||
logger().severe(" ");
|
||||
logger().severe(" ");
|
||||
logger().severe(" ");
|
||||
Bukkit.getServer().shutdown();
|
||||
return;
|
||||
}
|
||||
BukkitBlockBehaviors.init();
|
||||
BukkitItemBehaviors.init();
|
||||
super.packManager = new BukkitPackManager(this);
|
||||
super.senderFactory = new BukkitSenderFactory(this);
|
||||
super.itemManager = new BukkitItemManager(this);
|
||||
super.recipeManager = new BukkitRecipeManager(this);
|
||||
super.commandManager = new BukkitCommandManager(this);
|
||||
super.itemBrowserManager = new ItemBrowserManagerImpl(this);
|
||||
super.guiManager = new BukkitGuiManager(this);
|
||||
super.worldManager = new BukkitWorldManager(this);
|
||||
super.soundManager = new BukkitSoundManager(this);
|
||||
super.enable();
|
||||
// tick task
|
||||
if (VersionHelper.isFolia()) {
|
||||
this.tickTask = this.scheduler().sync().runRepeating(() -> {
|
||||
for (BukkitServerPlayer serverPlayer : networkManager().onlineUsers()) {
|
||||
org.bukkit.entity.Player player = serverPlayer.platformPlayer();
|
||||
Location location = player.getLocation();
|
||||
scheduler().sync().run(serverPlayer::tick, player.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4);
|
||||
}
|
||||
}, 1, 1);
|
||||
} else {
|
||||
this.tickTask = this.scheduler().sync().runRepeating(() -> {
|
||||
for (BukkitServerPlayer serverPlayer : networkManager().onlineUsers()) {
|
||||
serverPlayer.tick();
|
||||
}
|
||||
}, 1, 1);
|
||||
}
|
||||
|
||||
// compatibility
|
||||
// register expansion
|
||||
if (this.isPluginEnabled("PlaceholderAPI")) {
|
||||
new ShiftExpansion(this).register();
|
||||
new ImageExpansion(this).register();
|
||||
this.hasPlaceholderAPI = true;
|
||||
}
|
||||
// WorldEdit
|
||||
if (this.isPluginEnabled("FastAsyncWorldEdit")) {
|
||||
this.blockManager().initFastAsyncWorldEditHook();
|
||||
} else if (this.isPluginEnabled("WorldEdit")) {
|
||||
this.blockManager().initWorldEditHook();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
super.disable();
|
||||
if (this.tickTask != null) this.tickTask.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
super.reload();
|
||||
CraftEngineReloadEvent event = new CraftEngineReloadEvent(this);
|
||||
EventUtils.fireAndForget(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerParsers() {
|
||||
// register template parser
|
||||
this.packManager.registerConfigSectionParser(this.templateManager);
|
||||
// register font parser
|
||||
this.packManager.registerConfigSectionParser(this.imageManager);
|
||||
// register item parser
|
||||
this.packManager.registerConfigSectionParser(this.itemManager);
|
||||
// register furniture parser
|
||||
this.packManager.registerConfigSectionParser(this.furnitureManager);
|
||||
// register block parser
|
||||
this.packManager.registerConfigSectionParser(this.blockManager);
|
||||
// register recipe parser
|
||||
this.packManager.registerConfigSectionParser(this.recipeManager);
|
||||
// register category parser
|
||||
this.packManager.registerConfigSectionParser(this.itemBrowserManager);
|
||||
// register translation parser
|
||||
this.packManager.registerConfigSectionParser(this.translationManager);
|
||||
this.packManager.registerConfigSectionParser(this.translationManager.clientLangManager());
|
||||
// register sound parser
|
||||
this.packManager.registerConfigSectionParser(this.soundManager);
|
||||
this.packManager.registerConfigSectionParser(this.soundManager.jukeboxSongManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream resourceStream(String filePath) {
|
||||
return bootstrap.getResource(filePath.replace("\\", "/"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public File dataFolderFile() {
|
||||
return bootstrap().getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path dataFolderPath() {
|
||||
return bootstrap().getDataFolder().toPath().toAbsolutePath();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public String pluginVersion() {
|
||||
return bootstrap().getDescription().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serverVersion() {
|
||||
return Bukkit.getServer().getBukkitVersion().split("-")[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerAdapter<World> scheduler() {
|
||||
return (SchedulerAdapter<World>) scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemManager<ItemStack> itemManager() {
|
||||
return (ItemManager<ItemStack>) itemManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitBlockManager blockManager() {
|
||||
return (BukkitBlockManager) blockManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitFurnitureManager furnitureManager() {
|
||||
return (BukkitFurnitureManager) furnitureManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SenderFactory<CraftEngine, CommandSender> senderFactory() {
|
||||
return (SenderFactory<CraftEngine, CommandSender>) senderFactory;
|
||||
}
|
||||
|
||||
public JavaPlugin bootstrap() {
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
public static BukkitCraftEngine instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPlaceholderAPI() {
|
||||
return this.hasPlaceholderAPI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPluginEnabled(String plugin) {
|
||||
return Bukkit.getPluginManager().isPluginEnabled(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse(Player player, String text) {
|
||||
return PlaceholderAPIUtils.parse((org.bukkit.entity.Player) player.platformPlayer(), text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitNetworkManager networkManager() {
|
||||
return (BukkitNetworkManager) networkManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitPackManager packManager() {
|
||||
return (BukkitPackManager) packManager;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@Override
|
||||
public void saveResource(String resourcePath) {
|
||||
if (resourcePath.isEmpty()) {
|
||||
throw new IllegalArgumentException("ResourcePath cannot be null or empty");
|
||||
}
|
||||
|
||||
File outFile = new File(dataFolderFile(), resourcePath);
|
||||
if (outFile.exists())
|
||||
return;
|
||||
|
||||
resourcePath = resourcePath.replace('\\', '/');
|
||||
InputStream in = resourceStream(resourcePath);
|
||||
if (in == null)
|
||||
return;
|
||||
|
||||
int lastIndex = resourcePath.lastIndexOf('/');
|
||||
File outDir = new File(dataFolderFile(), resourcePath.substring(0, Math.max(lastIndex, 0)));
|
||||
|
||||
if (!outDir.exists()) {
|
||||
outDir.mkdirs();
|
||||
}
|
||||
|
||||
try {
|
||||
OutputStream out = new FileOutputStream(outFile);
|
||||
byte[] buf = new byte[1024];
|
||||
int len;
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
out.close();
|
||||
in.close();
|
||||
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public BukkitServerPlayer adapt(org.bukkit.entity.Player player) {
|
||||
if (player == null) return null;
|
||||
return Optional.ofNullable((BukkitServerPlayer) networkManager().getOnlineUser(player)).orElseGet(
|
||||
() -> (BukkitServerPlayer) networkManager().getUser(player)
|
||||
);
|
||||
}
|
||||
|
||||
public boolean hasMod() {
|
||||
return hasMod;
|
||||
}
|
||||
|
||||
public boolean requiresRestart() {
|
||||
return requiresRestart;
|
||||
}
|
||||
|
||||
public AntiGriefLib antiGrief() {
|
||||
if (this.antiGrief == null) {
|
||||
this.antiGrief = AntiGriefLib.builder(this.bootstrap)
|
||||
.ignoreOP(true)
|
||||
.silentLogs(true)
|
||||
.build();
|
||||
}
|
||||
return this.antiGrief;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.AbstractCommandFeature;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import net.momirealms.craftengine.core.util.Pair;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.incendo.cloud.bukkit.data.Selector;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public abstract class BukkitCommandFeature<C extends CommandSender> extends AbstractCommandFeature<C> {
|
||||
|
||||
public BukkitCommandFeature(CraftEngineCommandManager<C> commandManager, CraftEngine plugin) {
|
||||
super(commandManager, plugin);
|
||||
}
|
||||
|
||||
public Pair<TranslatableComponent.Builder, Component> resolveSelector(Selector<? extends Entity> selector, TranslatableComponent.Builder single, TranslatableComponent.Builder multiple) {
|
||||
Collection<? extends Entity> entities = selector.values();
|
||||
if (entities.size() == 1) {
|
||||
return Pair.of(single, Component.text(entities.iterator().next().getName()));
|
||||
} else {
|
||||
return Pair.of(multiple, Component.text(entities.size()));
|
||||
}
|
||||
}
|
||||
|
||||
public Pair<TranslatableComponent.Builder, Component> resolveSelector(Collection<? extends Entity> selector, TranslatableComponent.Builder single, TranslatableComponent.Builder multiple) {
|
||||
if (selector.size() == 1) {
|
||||
return Pair.of(single, Component.text(selector.iterator().next().getName()));
|
||||
} else {
|
||||
return Pair.of(multiple, Component.text(selector.size()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitCraftEngine plugin() {
|
||||
return (BukkitCraftEngine) super.plugin();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command;
|
||||
|
||||
import net.kyori.adventure.util.Index;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.feature.*;
|
||||
import net.momirealms.craftengine.core.plugin.command.AbstractCommandManager;
|
||||
import net.momirealms.craftengine.core.plugin.command.CommandFeature;
|
||||
import net.momirealms.craftengine.core.plugin.command.parser.BlockStateParser;
|
||||
import net.momirealms.craftengine.core.plugin.command.sender.Sender;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.incendo.cloud.SenderMapper;
|
||||
import org.incendo.cloud.bukkit.CloudBukkitCapabilities;
|
||||
import org.incendo.cloud.execution.ExecutionCoordinator;
|
||||
import org.incendo.cloud.paper.LegacyPaperCommandManager;
|
||||
import org.incendo.cloud.setting.ManagerSetting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BukkitCommandManager extends AbstractCommandManager<CommandSender> {
|
||||
private final BukkitCraftEngine plugin;
|
||||
private final Index<String, CommandFeature<CommandSender>> INDEX;
|
||||
|
||||
public BukkitCommandManager(BukkitCraftEngine plugin) {
|
||||
super(plugin, new LegacyPaperCommandManager<>(
|
||||
plugin.bootstrap(),
|
||||
ExecutionCoordinator.simpleCoordinator(),
|
||||
SenderMapper.identity()
|
||||
));
|
||||
this.plugin = plugin;
|
||||
this.INDEX = Index.create(CommandFeature::getFeatureID, List.of(
|
||||
new ReloadCommand(this, plugin),
|
||||
new GetItemCommand(this, plugin),
|
||||
new GiveItemCommand(this, plugin),
|
||||
new ItemBrowserCommand(this, plugin),
|
||||
new TestCommand(this, plugin),
|
||||
new DebugGetBlockStateRegistryIdCommand(this, plugin),
|
||||
new DebugGetBlockInternalIdCommand(this, plugin),
|
||||
new DebugAppearanceStateUsageCommand(this, plugin),
|
||||
new DebugRealStateUsageCommand(this, plugin),
|
||||
new DebugItemDataCommand(this, plugin),
|
||||
new DebugSetBlockCommand(this, plugin),
|
||||
new DebugTargetBlockCommand(this, plugin)
|
||||
));
|
||||
final LegacyPaperCommandManager<CommandSender> manager = (LegacyPaperCommandManager<CommandSender>) getCommandManager();
|
||||
manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true);
|
||||
manager.parserRegistry().registerParser(BlockStateParser.blockStateParser());
|
||||
if (manager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) {
|
||||
manager.registerBrigadier();
|
||||
manager.brigadierManager().setNativeNumberSuggestions(true);
|
||||
} else if (manager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) {
|
||||
manager.registerAsynchronousCompletions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Sender wrapSender(CommandSender sender) {
|
||||
return plugin.senderFactory().wrap(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Index<String, CommandFeature<CommandSender>> features() {
|
||||
return INDEX;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command;
|
||||
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.sender.Sender;
|
||||
import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory;
|
||||
import net.momirealms.craftengine.core.util.Tristate;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.command.RemoteConsoleCommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BukkitSenderFactory extends SenderFactory<BukkitCraftEngine, CommandSender> {
|
||||
private final BukkitAudiences audiences;
|
||||
|
||||
public BukkitSenderFactory(BukkitCraftEngine plugin) {
|
||||
super(plugin);
|
||||
this.audiences = BukkitAudiences.create(plugin.bootstrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String name(CommandSender sender) {
|
||||
if (sender instanceof Player) {
|
||||
return sender.getName();
|
||||
}
|
||||
return Sender.CONSOLE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID uniqueId(CommandSender sender) {
|
||||
if (sender instanceof Player) {
|
||||
return ((Player) sender).getUniqueId();
|
||||
}
|
||||
return Sender.CONSOLE_UUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Audience audience(CommandSender sender) {
|
||||
return this.audiences.sender(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendMessage(CommandSender sender, Component message) {
|
||||
// we can safely send async for players and the console - otherwise, send it sync
|
||||
if (sender instanceof Player || sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) {
|
||||
audience(sender).sendMessage(message);
|
||||
} else {
|
||||
plugin().scheduler().executeSync(() -> audience(sender).sendMessage(message));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Tristate permissionState(CommandSender sender, String node) {
|
||||
if (sender.hasPermission(node)) {
|
||||
return Tristate.TRUE;
|
||||
} else if (sender.isPermissionSet(node)) {
|
||||
return Tristate.FALSE;
|
||||
} else {
|
||||
return Tristate.UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasPermission(CommandSender sender, String node) {
|
||||
return sender.hasPermission(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCommand(CommandSender sender, String command) {
|
||||
plugin().bootstrap().getServer().dispatchCommand(sender, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isConsole(CommandSender sender) {
|
||||
return sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
this.audiences.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
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.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DebugAppearanceStateUsageCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public DebugAppearanceStateUsageCommand(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
|
||||
.required("id", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).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().blockManager().blockAppearanceArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList());
|
||||
}
|
||||
}))
|
||||
.handler(context -> {
|
||||
String data = context.get("id");
|
||||
BukkitBlockManager blockManager = plugin().blockManager();
|
||||
Key baseBlockId = Key.of(data);
|
||||
List<Integer> appearances = blockManager.blockAppearanceArranger().get(baseBlockId);
|
||||
if (appearances == null) return;
|
||||
int i = 0;
|
||||
Component component = Component.text(baseBlockId + ": ");
|
||||
List<Component> children = new ArrayList<>();
|
||||
for (int appearance : appearances) {
|
||||
Component text = Component.text("|");
|
||||
List<Integer> reals = blockManager.appearanceToRealStates(appearance);
|
||||
if (reals == null) {
|
||||
Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.GREEN);
|
||||
hover = hover.append(Component.newline()).append(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.GREEN));
|
||||
text = text.color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover));
|
||||
} else {
|
||||
Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.RED);
|
||||
List<Component> hoverChildren = new ArrayList<>();
|
||||
hoverChildren.add(Component.newline());
|
||||
hoverChildren.add(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.RED));
|
||||
for (int real : reals) {
|
||||
hoverChildren.add(Component.newline());
|
||||
hoverChildren.add(Component.text(blockManager.getImmutableBlockStateUnsafe(real).toString()).color(NamedTextColor.GRAY));
|
||||
}
|
||||
text = text.color(NamedTextColor.RED).hoverEvent(HoverEvent.showText(hover.children(hoverChildren)));
|
||||
}
|
||||
children.add(text);
|
||||
i++;
|
||||
}
|
||||
plugin().senderFactory().wrap(context.sender())
|
||||
.sendMessage(component.children(children));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "debug_appearance_state_usage";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command.feature;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.core.block.BlockStateParser;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
|
||||
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.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DebugGetBlockInternalIdCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public DebugGetBlockInternalIdCommand(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(FlagKeys.SILENT_FLAG)
|
||||
.required("id", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).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().blockManager().cachedSuggestions());
|
||||
}
|
||||
}))
|
||||
.handler(context -> {
|
||||
String data = context.get("id");
|
||||
ImmutableBlockState state = BlockStateParser.deserialize(data);
|
||||
if (state == null) return;
|
||||
context.sender().sendMessage(BlockStateUtils.getBlockOwnerIdFromState(state.customBlockState().handle()).toString());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "debug_get_block_internal_id";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command.feature;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.parser.standard.StringParser;
|
||||
|
||||
public class DebugGetBlockStateRegistryIdCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public DebugGetBlockStateRegistryIdCommand(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
|
||||
.senderType(Player.class)
|
||||
.required("state", StringParser.greedyStringParser())
|
||||
.handler(context -> {
|
||||
String state = context.get("state");
|
||||
BlockData blockData = Bukkit.createBlockData(state);
|
||||
int id = BlockStateUtils.blockDataToId(blockData);
|
||||
context.sender().sendMessage(String.valueOf(id));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "debug_get_block_state_registry_id";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command.feature;
|
||||
|
||||
import com.saicone.rtag.RtagMirror;
|
||||
import com.saicone.rtag.item.ItemObject;
|
||||
import com.saicone.rtag.tag.TagCompound;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.bukkit.util.ItemUtils;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import net.momirealms.craftengine.core.util.AdventureHelper;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.incendo.cloud.Command;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class DebugItemDataCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public DebugItemDataCommand(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
|
||||
.senderType(Player.class)
|
||||
.handler(context -> {
|
||||
ItemStack itemInHand = context.sender().getInventory().getItemInMainHand();
|
||||
if (ItemUtils.isEmpty(itemInHand)) {
|
||||
plugin().senderFactory().wrap(context.sender()).sendMessage(Component.text("Please hold an item").color(NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
Map<String, Object> readableMap = toReadableMap(itemInHand);
|
||||
readableMap.remove("rtagDataVersion");
|
||||
List<String> readableList = mapToList(readableMap);
|
||||
StringJoiner joiner = new StringJoiner("<newline><reset>");
|
||||
for (String text : readableList) {
|
||||
joiner.add(text);
|
||||
}
|
||||
plugin().senderFactory().wrap(context.sender()).sendMessage(AdventureHelper.miniMessage(joiner.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "debug_item_data";
|
||||
}
|
||||
|
||||
|
||||
public static Map<String, Object> toReadableMap(ItemStack item) {
|
||||
return toMap(item);
|
||||
}
|
||||
|
||||
private static Map<String, Object> toMap(ItemStack object) {
|
||||
return TagCompound.getValue(RtagMirror.INSTANCE, toCompound(object));
|
||||
}
|
||||
|
||||
private static Object toCompound(ItemStack object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
} else {
|
||||
Object compound = extract(object);
|
||||
return TagCompound.isTagCompound(compound) ? compound : null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Object extract(ItemStack object) {
|
||||
return ItemObject.save(ItemObject.asNMSCopy(object));
|
||||
}
|
||||
|
||||
private List<String> mapToList(Map<String, Object> readableDataMap) {
|
||||
List<String> list = new ArrayList<>();
|
||||
mapToList(readableDataMap, list, 0, false);
|
||||
return list;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void mapToList(Map<String, Object> map, List<String> readableList, int loopTimes, boolean isMapList) {
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
Object nbt = entry.getValue();
|
||||
if (nbt instanceof List<?> list) {
|
||||
if (isMapList && first) {
|
||||
first = false;
|
||||
readableList.add(" ".repeat(loopTimes - 1) +
|
||||
"<#F5F5F5>- <gradient:#FFD700:#FFFACD><hover:show_text:'<yellow>List'>" + entry.getKey() + "</hover></gradient>:</#F5F5F5>");
|
||||
} else {
|
||||
readableList.add(" ".repeat(loopTimes) +
|
||||
"<#F5F5F5><gradient:#FFD700:#FFFACD><hover:show_text:'<yellow>List'>" + entry.getKey() + "</hover></gradient>:</#F5F5F5>");
|
||||
}
|
||||
for (Object value : list) {
|
||||
if (value instanceof Map<?,?> innerDataMap) {
|
||||
mapToList((Map<String, Object>) innerDataMap, readableList, loopTimes + 2, true);
|
||||
} else {
|
||||
if (value instanceof String string) {
|
||||
readableList.add(" ".repeat(loopTimes + 1)
|
||||
+ "<#F5F5F5>- <hover:show_text:'<yellow>Copy'><click:suggest_command:'" + string.replace("'", "\\'") + "'>" + value + "</click></hover></#F5F5F5>");
|
||||
} else {
|
||||
readableList.add(" ".repeat(loopTimes + 1)
|
||||
+ "<#F5F5F5>- <hover:show_text:'<yellow>Copy'><click:suggest_command:'" + value + "'>" + value + "</click></hover></#F5F5F5>");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (nbt instanceof Map<?,?> innerMap) {
|
||||
if (isMapList && first) {
|
||||
first = false;
|
||||
readableList.add(" ".repeat(loopTimes - 1) +
|
||||
"<#F5F5F5>- <gradient:#FFD700:#FFFACD><hover:show_text:'<yellow>Map'>" + entry.getKey() + "</hover></gradient>:</#F5F5F5>");
|
||||
} else {
|
||||
readableList.add(" ".repeat(loopTimes) +
|
||||
"<#F5F5F5><gradient:#FFD700:#FFFACD><hover:show_text:'<yellow>Map'>" + entry.getKey() + "</hover></gradient>:");
|
||||
}
|
||||
mapToList((Map<String, Object>) innerMap, readableList, loopTimes + 1, false);
|
||||
} else {
|
||||
String value;
|
||||
if (nbt.getClass().isArray()) {
|
||||
if (nbt instanceof Object[]) {
|
||||
value = Arrays.deepToString((Object[]) nbt);
|
||||
} else if (nbt instanceof int[]) {
|
||||
value = Arrays.toString((int[]) nbt);
|
||||
} else if (nbt instanceof long[]) {
|
||||
value = Arrays.toString((long[]) nbt);
|
||||
} else if (nbt instanceof double[]) {
|
||||
value = Arrays.toString((double[]) nbt);
|
||||
} else if (nbt instanceof float[]) {
|
||||
value = Arrays.toString((float[]) nbt);
|
||||
} else if (nbt instanceof boolean[]) {
|
||||
value = Arrays.toString((boolean[]) nbt);
|
||||
} else if (nbt instanceof byte[]) {
|
||||
value = Arrays.toString((byte[]) nbt);
|
||||
} else if (nbt instanceof char[]) {
|
||||
value = Arrays.toString((char[]) nbt);
|
||||
} else if (nbt instanceof short[]) {
|
||||
value = Arrays.toString((short[]) nbt);
|
||||
} else {
|
||||
value = "Unknown array type";
|
||||
}
|
||||
} else {
|
||||
value = nbt.toString();
|
||||
}
|
||||
|
||||
if (isMapList && first) {
|
||||
first = false;
|
||||
readableList.add(" ".repeat(loopTimes - 1) +
|
||||
"<#F5F5F5>- <gradient:#FFD700:#FFFACD><hover:show_text:'<yellow>" + nbt.getClass().getSimpleName() + "'>" + entry.getKey() + "</hover></gradient>" +
|
||||
": " +
|
||||
"<#F5F5F5><hover:show_text:'<yellow>Copy'><click:suggest_command:'" + value.replace("'", "\\'") + "'>" + value + "</click></hover></#F5F5F5>");
|
||||
} else {
|
||||
readableList.add(" ".repeat(loopTimes) +
|
||||
"<#F5F5F5><gradient:#FFD700:#FFFACD><hover:show_text:'<yellow>" + nbt.getClass().getSimpleName() + "'>" + entry.getKey() + "</hover></gradient>" +
|
||||
": " +
|
||||
"<hover:show_text:'<yellow>Copy'><click:suggest_command:'" + value.replace("'", "\\'") + "'>" + value + "</click></hover></#F5F5F5>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DebugRealStateUsageCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public DebugRealStateUsageCommand(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
|
||||
.required("id", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).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().blockManager().blockAppearanceArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList());
|
||||
}
|
||||
}))
|
||||
.handler(context -> {
|
||||
String data = context.get("id");
|
||||
BukkitBlockManager blockManager = plugin().blockManager();
|
||||
Key baseBlockId = Key.of(data);
|
||||
List<Integer> reals = blockManager.realBlockArranger().get(baseBlockId);
|
||||
if (reals == null) return;
|
||||
int i = 0;
|
||||
Component component = Component.text(baseBlockId + ": ");
|
||||
List<Component> children = new ArrayList<>();
|
||||
for (int real : reals) {
|
||||
ImmutableBlockState state = blockManager.getImmutableBlockStateUnsafe(real);
|
||||
if (state.isEmpty()) {
|
||||
Component hover = Component.text("craftengine:" + baseBlockId.value() + "_" + i).color(NamedTextColor.GREEN);
|
||||
children.add(Component.text("|").color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover)));
|
||||
} else {
|
||||
Component hover = Component.text("craftengine:" + baseBlockId.value() + "_" + i).color(NamedTextColor.RED);
|
||||
hover = hover.append(Component.newline()).append(Component.text(state.toString()).color(NamedTextColor.GRAY));
|
||||
children.add(Component.text("|").color(NamedTextColor.RED).hoverEvent(HoverEvent.showText(hover)));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
plugin().senderFactory().wrap(context.sender())
|
||||
.sendMessage(component.children(children));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "debug_real_state_usage";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command.feature;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.core.block.BlockStateParser;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.UpdateOption;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.bukkit.parser.location.LocationParser;
|
||||
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.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DebugSetBlockCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public DebugSetBlockCommand(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
|
||||
.required("location", LocationParser.locationParser())
|
||||
.required("id", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).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().blockManager().cachedSuggestions());
|
||||
}
|
||||
}))
|
||||
.handler(context -> {
|
||||
String data = context.get("id");
|
||||
ImmutableBlockState state = BlockStateParser.deserialize(data);
|
||||
if (state == null) return;
|
||||
Location location = context.get("location");
|
||||
CraftEngineBlocks.place(location, state, UpdateOption.UPDATE_ALL, false);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "debug_set_block";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import net.momirealms.craftengine.core.plugin.command.sender.Sender;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class DebugTargetBlockCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public DebugTargetBlockCommand(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
|
||||
.senderType(Player.class)
|
||||
.flag(manager.flagBuilder("this").build())
|
||||
.handler(context -> {
|
||||
Player player = context.sender();
|
||||
Block block;
|
||||
if (context.flags().hasFlag("this")) {
|
||||
Location location = player.getLocation();
|
||||
block = location.getBlock();
|
||||
} else {
|
||||
block = player.getTargetBlockExact(10);
|
||||
if (block == null) return;
|
||||
}
|
||||
String bData = block.getBlockData().getAsString();
|
||||
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
|
||||
Sender sender = plugin().senderFactory().wrap(context.sender());
|
||||
sender.sendMessage(Component.text(bData));
|
||||
int id = BlockStateUtils.blockStateToId(blockState);
|
||||
|
||||
Object holder = BukkitBlockManager.instance().getMinecraftBlockHolder(id);
|
||||
if (holder != null) {
|
||||
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(id);
|
||||
if (immutableBlockState != null) {
|
||||
sender.sendMessage(Component.text(immutableBlockState.toString()));
|
||||
}
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<Object> tags = (Set<Object>) Reflections.field$Holder$Reference$tags.get(holder);
|
||||
if (!tags.isEmpty()) {
|
||||
sender.sendMessage(Component.text("tags: "));
|
||||
for (Object tag : tags) {
|
||||
sender.sendMessage(Component.text(" - " + Reflections.field$TagKey$location.get(tag).toString()));
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin().logger().warn("Could not get tags", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "debug_target_block";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.bukkit.util.PlayerUtils;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
|
||||
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.bukkit.parser.NamespacedKeyParser;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.context.CommandInput;
|
||||
import org.incendo.cloud.parser.standard.IntegerParser;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.incendo.cloud.suggestion.SuggestionProvider;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class GetItemCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public GetItemCommand(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
|
||||
.senderType(Player.class)
|
||||
.flag(FlagKeys.SILENT_FLAG)
|
||||
.flag(FlagKeys.TO_INVENTORY_FLAG)
|
||||
.required("id", NamespacedKeyParser.namespacedKeyComponent().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().itemManager().cachedSuggestions());
|
||||
}
|
||||
}))
|
||||
.optional("amount", IntegerParser.integerParser(1, 6400))
|
||||
.handler(context -> {
|
||||
Player player = context.sender();
|
||||
int amount = context.getOrDefault("amount", 1);
|
||||
boolean toInv = context.flags().hasFlag(FlagKeys.TO_INVENTORY);
|
||||
NamespacedKey namespacedKey = context.get("id");
|
||||
Key key = Key.of(namespacedKey.namespace(), namespacedKey.value());
|
||||
ItemStack builtItem = plugin().itemManager().buildCustomItemStack(key, plugin().adapt(context.sender()));
|
||||
if (builtItem == null) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_GET_FAILURE_NOT_EXIST, Component.text(key.toString()));
|
||||
return;
|
||||
}
|
||||
int amountToGive = amount;
|
||||
int maxStack = builtItem.getMaxStackSize();
|
||||
while (amountToGive > 0) {
|
||||
int perStackSize = Math.min(maxStack, amountToGive);
|
||||
amountToGive -= perStackSize;
|
||||
ItemStack more = builtItem.clone();
|
||||
more.setAmount(perStackSize);
|
||||
if (toInv) {
|
||||
PlayerUtils.putItemsToInventory(player.getInventory(), more, more.getAmount());
|
||||
} else {
|
||||
PlayerUtils.dropItem(player, more, false, true, false);
|
||||
}
|
||||
}
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_GET_SUCCESS, Component.text(amount), Component.text(key.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "get_item";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.bukkit.util.PlayerUtils;
|
||||
import net.momirealms.craftengine.core.item.CustomItem;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
|
||||
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.bukkit.data.MultiplePlayerSelector;
|
||||
import org.incendo.cloud.bukkit.parser.NamespacedKeyParser;
|
||||
import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.context.CommandInput;
|
||||
import org.incendo.cloud.parser.standard.IntegerParser;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.incendo.cloud.suggestion.SuggestionProvider;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class GiveItemCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public GiveItemCommand(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(FlagKeys.SILENT_FLAG)
|
||||
.flag(FlagKeys.TO_INVENTORY_FLAG)
|
||||
.required("player", MultiplePlayerSelectorParser.multiplePlayerSelectorParser(true))
|
||||
.required("id", NamespacedKeyParser.namespacedKeyComponent().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().itemManager().cachedSuggestions());
|
||||
}
|
||||
}))
|
||||
.optional("amount", IntegerParser.integerParser(1, 6400))
|
||||
.handler(context -> {
|
||||
MultiplePlayerSelector selector = context.get("player");
|
||||
int amount = context.getOrDefault("amount", 1);
|
||||
boolean toInv = context.flags().hasFlag(FlagKeys.TO_INVENTORY);
|
||||
NamespacedKey namespacedKey = context.get("id");
|
||||
Key key = Key.of(namespacedKey.namespace(), namespacedKey.value());
|
||||
Optional<CustomItem<ItemStack>> optionalItem = BukkitItemManager.instance().getCustomItem(key);
|
||||
if (optionalItem.isEmpty()) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_GIVE_FAILURE_NOT_EXIST, Component.text(key.toString()));
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<Player> players = selector.values();
|
||||
for (Player player : players) {
|
||||
ItemStack builtItem = optionalItem.get().buildItemStack(plugin().adapt(player));
|
||||
if (builtItem == null) {
|
||||
return;
|
||||
}
|
||||
int amountToGive = amount;
|
||||
int maxStack = builtItem.getMaxStackSize();
|
||||
while (amountToGive > 0) {
|
||||
int perStackSize = Math.min(maxStack, amountToGive);
|
||||
amountToGive -= perStackSize;
|
||||
ItemStack more = builtItem.clone();
|
||||
more.setAmount(perStackSize);
|
||||
if (toInv) {
|
||||
PlayerUtils.putItemsToInventory(player.getInventory(), more, more.getAmount());
|
||||
} else {
|
||||
PlayerUtils.dropItem(player, more, false, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (players.size() == 1) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_GIVE_SUCCESS_SINGLE, Component.text(amount), Component.text(key.toString()), Component.text(players.iterator().next().getName()));
|
||||
} else if (players.size() > 1) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_GIVE_SUCCESS_MULTIPLE, Component.text(amount), Component.text(key.toString()), Component.text(players.size()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "give_item";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command.feature;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
|
||||
public class ItemBrowserCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public ItemBrowserCommand(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
|
||||
.senderType(Player.class)
|
||||
.handler(context -> {
|
||||
Player player = context.sender();
|
||||
BukkitServerPlayer serverPlayer = plugin().adapt(player);
|
||||
plugin().itemBrowserManager().open(serverPlayer);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "item_browser";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
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.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;
|
||||
import org.incendo.cloud.parser.standard.EnumParser;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ReloadCommand extends BukkitCommandFeature<CommandSender> {
|
||||
public static boolean RELOAD_PACK_FLAG = false;
|
||||
|
||||
public ReloadCommand(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"))
|
||||
.optional("content", EnumParser.enumParser(ReloadArgument.class))
|
||||
.handler(context -> {
|
||||
if (plugin().isReloading()) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_RELOAD_FAILURE_IS_LOADING);
|
||||
return;
|
||||
}
|
||||
Optional<ReloadArgument> optional = context.optional("content");
|
||||
ReloadArgument argument = ReloadArgument.CONFIG;
|
||||
if (optional.isPresent()) {
|
||||
argument = optional.get();
|
||||
}
|
||||
if (argument == ReloadArgument.CONFIG) {
|
||||
try {
|
||||
RELOAD_PACK_FLAG = true;
|
||||
long time1 = System.currentTimeMillis();
|
||||
plugin().reload();
|
||||
long time2 = System.currentTimeMillis();
|
||||
handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_SUCCESS, Component.text(time2 - time1));
|
||||
} catch (Exception e) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_FAILURE);
|
||||
plugin().logger().warn("Failed to reload config", e);
|
||||
}
|
||||
} else if (argument == ReloadArgument.PACK) {
|
||||
plugin().scheduler().executeAsync(() -> {
|
||||
try {
|
||||
long time1 = System.currentTimeMillis();
|
||||
plugin().packManager().generateResourcePack();
|
||||
long time2 = System.currentTimeMillis();
|
||||
handleFeedback(context, MessageConstants.COMMAND_RELOAD_PACK_SUCCESS, Component.text(time2 - time1));
|
||||
} catch (Exception e) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_RELOAD_PACK_FAILURE);
|
||||
plugin().logger().warn("Failed to generate resource pack", e);
|
||||
}
|
||||
});
|
||||
} else if (argument == ReloadArgument.ALL) {
|
||||
long time1 = System.currentTimeMillis();
|
||||
try {
|
||||
plugin().reload();
|
||||
plugin().scheduler().executeAsync(() -> {
|
||||
try {
|
||||
plugin().packManager().generateResourcePack();
|
||||
long time2 = System.currentTimeMillis();
|
||||
handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_SUCCESS, Component.text(time2 - time1));
|
||||
} catch (Exception e) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_FAILURE);
|
||||
plugin().logger().warn("Failed to generate resource pack", e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_FAILURE);
|
||||
plugin().logger().warn("Failed to reload config", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "reload";
|
||||
}
|
||||
|
||||
public enum ReloadArgument {
|
||||
CONFIG,
|
||||
PACK,
|
||||
ALL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command.feature;
|
||||
|
||||
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.registry.Holder;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.incendo.cloud.Command;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TestCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public TestCommand(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 -> {
|
||||
List<Holder<Key>> holders = plugin().itemManager().tagToItems(Key.of("minecraft:planks"));
|
||||
for (Holder<Key> holder : holders) {
|
||||
context.sender().sendMessage(holder.registeredName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "debug_test";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.gui;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.ItemUtils;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.gui.Click;
|
||||
import net.momirealms.craftengine.core.plugin.gui.Gui;
|
||||
import net.momirealms.craftengine.core.plugin.gui.Inventory;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class BukkitClick implements Click {
|
||||
private final InventoryClickEvent event;
|
||||
private final Inventory inventory;
|
||||
private final Gui gui;
|
||||
|
||||
public BukkitClick(InventoryClickEvent event, Gui gui, Inventory inventory) {
|
||||
this.event = event;
|
||||
this.inventory = inventory;
|
||||
this.gui = gui;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Gui gui() {
|
||||
return this.gui;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory clickedInventory() {
|
||||
return this.inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int slot() {
|
||||
return this.event.getSlot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
this.event.setCancelled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.event.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return this.event.getClick().name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hotBarKey() {
|
||||
return this.event.getHotbarButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player clicker() {
|
||||
return BukkitCraftEngine.instance().adapt((org.bukkit.entity.Player) event.getWhoClicked());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void setItemOnCursor(Item<?> item) {
|
||||
this.event.setCursor((ItemStack) item.getItem());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<?> itemOnCursor() {
|
||||
ItemStack itemStack = this.event.getCursor();
|
||||
if (ItemUtils.isEmpty(itemStack)) return null;
|
||||
return BukkitItemManager.instance().wrap(itemStack);
|
||||
}
|
||||
|
||||
public InventoryClickEvent event() {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.gui;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.LegacyInventoryUtils;
|
||||
import net.momirealms.craftengine.core.plugin.gui.AbstractGui;
|
||||
import net.momirealms.craftengine.core.plugin.gui.Gui;
|
||||
import net.momirealms.craftengine.core.plugin.gui.GuiManager;
|
||||
import net.momirealms.craftengine.core.plugin.gui.Inventory;
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
|
||||
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.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
|
||||
public class BukkitGuiManager implements GuiManager, Listener {
|
||||
private final BukkitCraftEngine plugin;
|
||||
private SchedulerTask timerTask;
|
||||
|
||||
public BukkitGuiManager(BukkitCraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayedInit() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap());
|
||||
this.timerTask = plugin.scheduler().sync().runRepeating(this::timerTask, 20, 20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
HandlerList.unregisterAll(this);
|
||||
if (this.timerTask != null && !this.timerTask.cancelled()) {
|
||||
this.timerTask.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void timerTask() {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
org.bukkit.inventory.Inventory top = !VersionHelper.isVersionNewerThan1_21() ? LegacyInventoryUtils.getTopInventory(player) : player.getOpenInventory().getTopInventory();
|
||||
if (top.getHolder() instanceof CraftEngineInventoryHolder holder) {
|
||||
holder.gui().onTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(Gui gui, int size) {
|
||||
CraftEngineInventoryHolder holder = new CraftEngineInventoryHolder(gui);
|
||||
org.bukkit.inventory.Inventory inventory = Bukkit.createInventory(holder, size);
|
||||
holder.holder().bindValue(inventory);
|
||||
return new BukkitInventory(inventory);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
org.bukkit.inventory.Inventory inventory = event.getInventory();
|
||||
if (!(inventory.getHolder() instanceof CraftEngineInventoryHolder craftEngineInventoryHolder)) {
|
||||
return;
|
||||
}
|
||||
AbstractGui gui = (AbstractGui) craftEngineInventoryHolder.gui();
|
||||
Player player = (Player) event.getWhoClicked();
|
||||
if (event.getClickedInventory() == player.getInventory()) {
|
||||
gui.handleInventoryClick(new BukkitClick(event, gui, new BukkitInventory(player.getInventory())));
|
||||
} else if (event.getClickedInventory() == inventory) {
|
||||
gui.handleGuiClick(new BukkitClick(event, gui, new BukkitInventory(inventory)));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onInventoryDrag(InventoryDragEvent event) {
|
||||
org.bukkit.inventory.Inventory inventory = event.getInventory();
|
||||
if (!(inventory.getHolder() instanceof CraftEngineInventoryHolder)) {
|
||||
return;
|
||||
}
|
||||
for (int raw : event.getRawSlots()) {
|
||||
if (raw < inventory.getSize()) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.gui;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
|
||||
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.gui.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class BukkitInventory implements Inventory {
|
||||
private final org.bukkit.inventory.Inventory inventory;
|
||||
|
||||
public BukkitInventory(org.bukkit.inventory.Inventory inventory) {
|
||||
this.inventory = inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(Player player, Component title) {
|
||||
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) player;
|
||||
Object nmsPlayer = serverPlayer.serverPlayer();
|
||||
try {
|
||||
Object menuType = Reflections.method$CraftContainer$getNotchInventoryType.invoke(null, inventory);
|
||||
int nextId = (int) Reflections.method$ServerPlayer$nextContainerCounter.invoke(nmsPlayer);
|
||||
Object menu = Reflections.constructor$CraftContainer.newInstance(inventory, nmsPlayer, nextId);
|
||||
Reflections.field$AbstractContainerMenu$checkReachable.set(menu, false);
|
||||
Object packet = Reflections.constructor$ClientboundOpenScreenPacket.newInstance(nextId, menuType, ComponentUtils.adventureToMinecraft(title));
|
||||
serverPlayer.sendPacket(packet, false);
|
||||
Reflections.field$Player$containerMenu.set(nmsPlayer, menu);
|
||||
Reflections.method$ServerPlayer$initMenu.invoke(nmsPlayer, menu);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to update inventory title", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(int index, Item<?> item) {
|
||||
this.inventory.setItem(index, item == null ? null : (ItemStack) item.getItem());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.gui;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.gui.Gui;
|
||||
import net.momirealms.craftengine.shared.ObjectHolder;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CraftEngineInventoryHolder implements InventoryHolder {
|
||||
private final ObjectHolder<Inventory> inventory;
|
||||
private final Gui gui;
|
||||
|
||||
public CraftEngineInventoryHolder(Gui gui) {
|
||||
this.inventory = new ObjectHolder<>();
|
||||
this.gui = gui;
|
||||
}
|
||||
|
||||
public ObjectHolder<Inventory> holder() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
public Gui gui() {
|
||||
return gui;
|
||||
}
|
||||
|
||||
public Inventory inventory() {
|
||||
return inventory.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,831 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.injector;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.ClassFileVersion;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.FixedValue;
|
||||
import net.bytebuddy.implementation.MethodDelegation;
|
||||
import net.bytebuddy.implementation.bind.annotation.*;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockShape;
|
||||
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.craftengine.bukkit.item.recipe.BukkitRecipeManager;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.NoteBlockChainUpdateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.block.BlockKeys;
|
||||
import net.momirealms.craftengine.core.block.EmptyBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.StatePredicate;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.item.recipe.CustomCookingRecipe;
|
||||
import net.momirealms.craftengine.core.item.recipe.OptimizedIDItem;
|
||||
import net.momirealms.craftengine.core.item.recipe.RecipeTypes;
|
||||
import net.momirealms.craftengine.core.item.recipe.input.SingleItemInput;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
|
||||
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
|
||||
import net.momirealms.craftengine.core.registry.Holder;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.ReflectionUtils;
|
||||
import net.momirealms.craftengine.core.util.SectionPosUtils;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import net.momirealms.craftengine.core.world.CEWorld;
|
||||
import net.momirealms.craftengine.core.world.SectionPos;
|
||||
import net.momirealms.craftengine.core.world.chunk.CESection;
|
||||
import net.momirealms.craftengine.core.world.chunk.InjectedPalettedContainerHolder;
|
||||
import net.momirealms.craftengine.shared.ObjectHolder;
|
||||
import net.momirealms.craftengine.shared.block.*;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class BukkitInjector {
|
||||
private static final ByteBuddy byteBuddy = new ByteBuddy(ClassFileVersion.JAVA_V17);
|
||||
private static final BukkitBlockShape STONE_SHAPE = new BukkitBlockShape(Reflections.instance$Blocks$STONE$defaultState);
|
||||
|
||||
private static Class<?> clazz$InjectedPalettedContainer;
|
||||
private static Field field$InjectedPalettedContainer$target;
|
||||
private static Field field$InjectedPalettedContainer$section;
|
||||
private static Field field$InjectedPalettedContainer$world;
|
||||
private static Field field$InjectedPalettedContainer$pos;
|
||||
|
||||
private static Class<?> clazz$OptimizedItemDisplay;
|
||||
private static Constructor<?> constructor$OptimizedItemDisplay;
|
||||
|
||||
private static Class<?> clazz$OptimizedItemDisplayFatory;
|
||||
private static Object instance$OptimizedItemDisplayFactory;
|
||||
|
||||
private static Class<?> clazz$CraftEngineBlock;
|
||||
private static MethodHandle constructor$CraftEngineBlock;
|
||||
private static Field field$CraftEngineBlock$behavior;
|
||||
private static Field field$CraftEngineBlock$shape;
|
||||
private static Field field$CraftEngineBlock$isNoteBlock;
|
||||
|
||||
private static Class<?> clazz$InjectedCacheChecker;
|
||||
private static Field field$InjectedCacheChecker$recipeType;
|
||||
private static Field field$InjectedCacheChecker$lastRecipe;
|
||||
private static Field field$InjectedCacheChecker$lastCustomRecipe;
|
||||
|
||||
public static void init() {
|
||||
try {
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
|
||||
clazz$InjectedCacheChecker = byteBuddy
|
||||
.subclass(Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
|
||||
.implement(Reflections.clazz$RecipeManager$CachedCheck)
|
||||
.defineField("recipeType", Reflections.clazz$RecipeType, Visibility.PUBLIC)
|
||||
.defineField("lastRecipe", Object.class, Visibility.PUBLIC)
|
||||
.defineField("lastCustomRecipe", Key.class, Visibility.PUBLIC)
|
||||
.method(ElementMatchers.named("getRecipeFor").or(ElementMatchers.named("a")))
|
||||
.intercept(MethodDelegation.to(
|
||||
VersionHelper.isVersionNewerThan1_21_2() ?
|
||||
GetRecipeForMethodInterceptor1_21_2.INSTANCE :
|
||||
(VersionHelper.isVersionNewerThan1_21() ?
|
||||
GetRecipeForMethodInterceptor1_21.INSTANCE :
|
||||
VersionHelper.isVersionNewerThan1_20_5() ?
|
||||
GetRecipeForMethodInterceptor1_20_5.INSTANCE :
|
||||
GetRecipeForMethodInterceptor1_20.INSTANCE)
|
||||
))
|
||||
.make()
|
||||
.load(BukkitInjector.class.getClassLoader())
|
||||
.getLoaded();
|
||||
field$InjectedCacheChecker$recipeType = clazz$InjectedCacheChecker.getDeclaredField("recipeType");
|
||||
field$InjectedCacheChecker$lastRecipe = clazz$InjectedCacheChecker.getDeclaredField("lastRecipe");
|
||||
field$InjectedCacheChecker$lastCustomRecipe = clazz$InjectedCacheChecker.getDeclaredField("lastCustomRecipe");
|
||||
|
||||
// Paletted Container
|
||||
clazz$InjectedPalettedContainer = byteBuddy
|
||||
.subclass(Reflections.clazz$PalettedContainer)
|
||||
.name("net.minecraft.world.level.chunk.InjectedPalettedContainer")
|
||||
.implement(InjectedPalettedContainerHolder.class)
|
||||
.defineField("target", Reflections.clazz$PalettedContainer, Visibility.PRIVATE)
|
||||
.defineField("cesection", CESection.class, Visibility.PRIVATE)
|
||||
.defineField("ceworld", CEWorld.class, Visibility.PRIVATE)
|
||||
.defineField("cepos", SectionPos.class, Visibility.PRIVATE)
|
||||
.method(ElementMatchers.any()
|
||||
.and(ElementMatchers.not(ElementMatchers.is(Reflections.method$PalettedContainer$getAndSet)))
|
||||
.and(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class)))
|
||||
)
|
||||
.intercept(MethodDelegation.toField("target"))
|
||||
.method(ElementMatchers.is(Reflections.method$PalettedContainer$getAndSet))
|
||||
.intercept(MethodDelegation.to(GetAndSetInterceptor.INSTANCE))
|
||||
.method(ElementMatchers.named("target"))
|
||||
.intercept(FieldAccessor.ofField("target"))
|
||||
.method(ElementMatchers.named("ceSection"))
|
||||
.intercept(FieldAccessor.ofField("cesection"))
|
||||
.method(ElementMatchers.named("ceWorld"))
|
||||
.intercept(FieldAccessor.ofField("ceworld"))
|
||||
.method(ElementMatchers.named("cePos"))
|
||||
.intercept(FieldAccessor.ofField("cepos"))
|
||||
.make()
|
||||
.load(BukkitInjector.class.getClassLoader())
|
||||
.getLoaded();
|
||||
field$InjectedPalettedContainer$target = ReflectionUtils.getDeclaredField(clazz$InjectedPalettedContainer, "target");
|
||||
field$InjectedPalettedContainer$section = ReflectionUtils.getDeclaredField(clazz$InjectedPalettedContainer, "cesection");
|
||||
field$InjectedPalettedContainer$world = ReflectionUtils.getDeclaredField(clazz$InjectedPalettedContainer, "ceworld");
|
||||
field$InjectedPalettedContainer$pos = ReflectionUtils.getDeclaredField(clazz$InjectedPalettedContainer, "cepos");
|
||||
// State Predicate
|
||||
DynamicType.Unloaded<?> alwaysTrue = byteBuddy
|
||||
.subclass(Reflections.clazz$StatePredicate)
|
||||
.method(ElementMatchers.named("test"))
|
||||
.intercept(FixedValue.value(true))
|
||||
.make();
|
||||
Class<?> alwaysTrueClass = alwaysTrue.load(BukkitInjector.class.getClassLoader()).getLoaded();
|
||||
DynamicType.Unloaded<?> alwaysFalse = byteBuddy
|
||||
.subclass(Reflections.clazz$StatePredicate)
|
||||
.method(ElementMatchers.named("test"))
|
||||
.intercept(FixedValue.value(false))
|
||||
.make();
|
||||
Class<?> alwaysFalseClass = alwaysFalse.load(BukkitInjector.class.getClassLoader()).getLoaded();
|
||||
StatePredicate.init(alwaysTrueClass.getDeclaredConstructor().newInstance(), alwaysFalseClass.getDeclaredConstructor().newInstance());
|
||||
// Optimized Item Display
|
||||
clazz$OptimizedItemDisplay = byteBuddy
|
||||
.subclass(Reflections.clazz$Display$ItemDisplay, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
|
||||
.name("net.minecraft.world.entity.OptimizedItemDisplay")
|
||||
.make()
|
||||
.load(BukkitInjector.class.getClassLoader())
|
||||
.getLoaded();
|
||||
constructor$OptimizedItemDisplay = ReflectionUtils.getConstructor(clazz$OptimizedItemDisplay, Reflections.clazz$EntityType, Reflections.clazz$Level);
|
||||
clazz$OptimizedItemDisplayFatory = byteBuddy
|
||||
.subclass(Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
|
||||
.name("net.momirealms.craftengine.bukkit.entity.OptimizedItemDisplayFactory")
|
||||
.implement(Reflections.clazz$EntityType$EntityFactory)
|
||||
.method(ElementMatchers.named("create"))
|
||||
.intercept(MethodDelegation.to(OptimizedItemDisplayMethodInterceptor.INSTANCE))
|
||||
.make()
|
||||
.load(BukkitInjector.class.getClassLoader())
|
||||
.getLoaded();
|
||||
instance$OptimizedItemDisplayFactory = Objects.requireNonNull(ReflectionUtils.getConstructor(clazz$OptimizedItemDisplayFatory, 0)).newInstance();
|
||||
|
||||
String packageWithName = BukkitInjector.class.getName();
|
||||
String generatedClassName = packageWithName.substring(0, packageWithName.lastIndexOf('.')) + ".CraftEngineBlock";
|
||||
DynamicType.Builder<?> builder = byteBuddy
|
||||
.subclass(Reflections.clazz$Block, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
|
||||
.name(generatedClassName)
|
||||
.defineField("behaviorHolder", ObjectHolder.class, Visibility.PUBLIC)
|
||||
.defineField("shapeHolder", ObjectHolder.class, Visibility.PUBLIC)
|
||||
.defineField("isClientSideNoteBlock", boolean.class, Visibility.PUBLIC)
|
||||
// should always implement this interface
|
||||
.implement(Reflections.clazz$Fallable)
|
||||
.implement(Reflections.clazz$BonemealableBlock)
|
||||
// internal interfaces
|
||||
.implement(BehaviorHolder.class)
|
||||
.implement(ShapeHolder.class)
|
||||
.implement(NoteBlockIndicator.class)
|
||||
.method(ElementMatchers.named("getBehaviorHolder"))
|
||||
.intercept(FieldAccessor.ofField("behaviorHolder"))
|
||||
.method(ElementMatchers.named("getShapeHolder"))
|
||||
.intercept(FieldAccessor.ofField("shapeHolder"))
|
||||
.method(ElementMatchers.named("isNoteBlock"))
|
||||
.intercept(FieldAccessor.ofField("isClientSideNoteBlock"))
|
||||
// getShape
|
||||
.method(ElementMatchers.is(Reflections.method$BlockBehaviour$getShape))
|
||||
.intercept(MethodDelegation.to(GetShapeInterceptor.INSTANCE))
|
||||
// tick
|
||||
.method(ElementMatchers.is(Reflections.method$BlockBehaviour$tick))
|
||||
.intercept(MethodDelegation.to(TickInterceptor.INSTANCE))
|
||||
// isValidBoneMealTarget
|
||||
.method(ElementMatchers.is(Reflections.method$BonemealableBlock$isValidBonemealTarget))
|
||||
.intercept(MethodDelegation.to(IsValidBoneMealTargetInterceptor.INSTANCE))
|
||||
// isBoneMealSuccess
|
||||
.method(ElementMatchers.is(Reflections.method$BonemealableBlock$isBonemealSuccess))
|
||||
.intercept(MethodDelegation.to(IsBoneMealSuccessInterceptor.INSTANCE))
|
||||
// performBoneMeal
|
||||
.method(ElementMatchers.is(Reflections.method$BonemealableBlock$performBonemeal))
|
||||
.intercept(MethodDelegation.to(PerformBoneMealInterceptor.INSTANCE))
|
||||
// random tick
|
||||
.method(ElementMatchers.is(Reflections.method$BlockBehaviour$randomTick))
|
||||
.intercept(MethodDelegation.to(RandomTickInterceptor.INSTANCE))
|
||||
// onPlace
|
||||
.method(ElementMatchers.takesArguments(5)
|
||||
.and(ElementMatchers.takesArgument(0, Reflections.clazz$BlockState))
|
||||
.and(ElementMatchers.takesArgument(1, Reflections.clazz$Level))
|
||||
.and(ElementMatchers.takesArgument(2, Reflections.clazz$BlockPos))
|
||||
.and(ElementMatchers.takesArgument(3, Reflections.clazz$BlockState))
|
||||
.and(ElementMatchers.takesArgument(4, boolean.class))
|
||||
.and(ElementMatchers.named("onPlace").or(ElementMatchers.named("a")))
|
||||
)
|
||||
.intercept(MethodDelegation.to(OnPlaceInterceptor.INSTANCE))
|
||||
// onBrokenAfterFall
|
||||
.method(ElementMatchers.takesArguments(3)
|
||||
.and(ElementMatchers.takesArgument(0, Reflections.clazz$Level))
|
||||
.and(ElementMatchers.takesArgument(1, Reflections.clazz$BlockPos))
|
||||
.and(ElementMatchers.takesArgument(2, Reflections.clazz$FallingBlockEntity))
|
||||
)
|
||||
.intercept(MethodDelegation.to(OnBrokenAfterFallInterceptor.INSTANCE))
|
||||
// canSurvive
|
||||
.method(ElementMatchers.takesArguments(3)
|
||||
.and(ElementMatchers.takesArgument(0, Reflections.clazz$BlockState))
|
||||
.and(ElementMatchers.takesArgument(1, Reflections.clazz$LevelReader))
|
||||
.and(ElementMatchers.takesArgument(2, Reflections.clazz$BlockPos))
|
||||
)
|
||||
.intercept(MethodDelegation.to(CanSurviveInterceptor.INSTANCE))
|
||||
// updateShape
|
||||
.method(ElementMatchers.returns(Reflections.clazz$BlockState)
|
||||
.and(ElementMatchers.takesArgument(0, Reflections.clazz$BlockState))
|
||||
// LevelReader 1.21.3+ // 1.20-1.12.2
|
||||
.and(ElementMatchers.takesArgument(1, Reflections.clazz$LevelReader).or(ElementMatchers.takesArgument(1, Reflections.clazz$Direction)))
|
||||
.and(ElementMatchers.named("updateShape").or(ElementMatchers.named("a"))))
|
||||
.intercept(MethodDelegation.to(UpdateShapeInterceptor.INSTANCE))
|
||||
// getFluidState
|
||||
.method(ElementMatchers.returns(Reflections.clazz$FluidState)
|
||||
.and(ElementMatchers.takesArgument(0, Reflections.clazz$BlockState)))
|
||||
.intercept(MethodDelegation.to(FluidStateInterceptor.INSTANCE));
|
||||
clazz$CraftEngineBlock = builder.make().load(BukkitInjector.class.getClassLoader()).getLoaded();
|
||||
|
||||
constructor$CraftEngineBlock = MethodHandles.publicLookup().in(clazz$CraftEngineBlock)
|
||||
.findConstructor(clazz$CraftEngineBlock, MethodType.methodType(void.class, Reflections.clazz$BlockBehaviour$Properties))
|
||||
.asType(MethodType.methodType(Reflections.clazz$Block, Reflections.clazz$BlockBehaviour$Properties));
|
||||
|
||||
field$CraftEngineBlock$behavior = clazz$CraftEngineBlock.getField("behaviorHolder");
|
||||
field$CraftEngineBlock$shape = clazz$CraftEngineBlock.getField("shapeHolder");
|
||||
field$CraftEngineBlock$isNoteBlock = clazz$CraftEngineBlock.getField("isClientSideNoteBlock");
|
||||
} catch (Throwable e) {
|
||||
CraftEngine.instance().logger().severe("Failed to init injector", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void injectCookingBlockEntity(Object entity) throws ReflectiveOperationException {
|
||||
if (Reflections.clazz$AbstractFurnaceBlockEntity.isInstance(entity)) {
|
||||
Object quickCheck = Reflections.field$AbstractFurnaceBlockEntity$quickCheck.get(entity);
|
||||
if (clazz$InjectedCacheChecker.isInstance(quickCheck)) return; // already injected
|
||||
Object recipeType = Reflections.field$AbstractFurnaceBlockEntity$recipeType.get(entity);
|
||||
Object injectedChecker = Reflections.UNSAFE.allocateInstance(clazz$InjectedCacheChecker);
|
||||
field$InjectedCacheChecker$recipeType.set(injectedChecker, recipeType);
|
||||
Reflections.field$AbstractFurnaceBlockEntity$quickCheck.set(entity, injectedChecker);
|
||||
} else if (!VersionHelper.isVersionNewerThan1_21_2() && Reflections.clazz$CampfireBlockEntity.isInstance(entity)) {
|
||||
Object quickCheck = Reflections.field$CampfireBlockEntity$quickCheck.get(entity);
|
||||
if (clazz$InjectedCacheChecker.isInstance(quickCheck)) return; // already injected
|
||||
Object injectedChecker = Reflections.UNSAFE.allocateInstance(clazz$InjectedCacheChecker);
|
||||
field$InjectedCacheChecker$recipeType.set(injectedChecker, Reflections.instance$RecipeType$CAMPFIRE_COOKING);
|
||||
Reflections.field$CampfireBlockEntity$quickCheck.set(entity, injectedChecker);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object getOptimizedItemDisplayFactory() {
|
||||
return instance$OptimizedItemDisplayFactory;
|
||||
}
|
||||
|
||||
public static class OptimizedItemDisplayMethodInterceptor {
|
||||
public static final OptimizedItemDisplayMethodInterceptor INSTANCE = new OptimizedItemDisplayMethodInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public Object intercept(@AllArguments Object[] args) throws Exception {
|
||||
return constructor$OptimizedItemDisplay.newInstance(args[0], args[1]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void injectLevelChunkSection(Object targetSection, CESection ceSection, CEWorld ceWorld, SectionPos pos) {
|
||||
try {
|
||||
Object container = Reflections.field$LevelChunkSection$states.get(targetSection);
|
||||
if (!clazz$InjectedPalettedContainer.isInstance(container)) {
|
||||
Object injectedObject = Reflections.UNSAFE.allocateInstance(clazz$InjectedPalettedContainer);
|
||||
field$InjectedPalettedContainer$target.set(injectedObject, container);
|
||||
field$InjectedPalettedContainer$section.set(injectedObject, ceSection);
|
||||
field$InjectedPalettedContainer$world.set(injectedObject, ceWorld);
|
||||
field$InjectedPalettedContainer$pos.set(injectedObject, pos);
|
||||
Reflections.field$PalettedContainer$data.set(injectedObject, Reflections.field$PalettedContainer$data.get(container));
|
||||
Reflections.field$LevelChunkSection$states.set(targetSection, injectedObject);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to inject chunk section", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void uninjectLevelChunkSection(Object section) {
|
||||
try {
|
||||
Object states = Reflections.field$LevelChunkSection$states.get(section);
|
||||
if (clazz$InjectedPalettedContainer.isInstance(states)) {
|
||||
Reflections.field$LevelChunkSection$states.set(section, field$InjectedPalettedContainer$target.get(states));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to inject chunk section", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetRecipeForMethodInterceptor1_20 {
|
||||
public static final GetRecipeForMethodInterceptor1_20 INSTANCE = new GetRecipeForMethodInterceptor1_20();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@RuntimeType
|
||||
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
|
||||
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
|
||||
Object type = field$InjectedCacheChecker$recipeType.get(thisObj);
|
||||
Object lastRecipe = field$InjectedCacheChecker$lastRecipe.get(thisObj);
|
||||
Optional<Pair<Object, Object>> optionalRecipe = (Optional<Pair<Object, Object>>) Reflections.method$RecipeManager$getRecipeFor0.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe);
|
||||
if (optionalRecipe.isPresent()) {
|
||||
Pair<Object, Object> pair = optionalRecipe.get();
|
||||
Object resourceLocation = pair.getFirst();
|
||||
Key recipeId = Key.of(resourceLocation.toString());
|
||||
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
|
||||
|
||||
ItemStack itemStack;
|
||||
List<Object> items;
|
||||
if (type == Reflections.instance$RecipeType$CAMPFIRE_COOKING) {
|
||||
items = (List<Object>) Reflections.field$SimpleContainer$items.get(args[0]);
|
||||
} else {
|
||||
items = (List<Object>) Reflections.field$AbstractFurnaceBlockEntity$items.get(args[0]);
|
||||
}
|
||||
itemStack = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, items.get(0));
|
||||
|
||||
// it's a recipe from other plugins
|
||||
boolean isCustom = recipeManager.isCustomRecipe(recipeId);
|
||||
if (!isCustom) {
|
||||
field$InjectedCacheChecker$lastRecipe.set(thisObj, resourceLocation);
|
||||
return optionalRecipe;
|
||||
}
|
||||
|
||||
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
|
||||
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
|
||||
if (idHolder.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
|
||||
CustomCookingRecipe<ItemStack> ceRecipe;
|
||||
Key lastCustomRecipe = (Key) field$InjectedCacheChecker$lastCustomRecipe.get(thisObj);
|
||||
if (type == Reflections.instance$RecipeType$SMELTING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$BLASTING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$SMOKING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$CAMPFIRE_COOKING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input, lastCustomRecipe);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (ceRecipe == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Cache recipes, it might be incorrect on reloading
|
||||
field$InjectedCacheChecker$lastCustomRecipe.set(thisObj, ceRecipe.id());
|
||||
// It doesn't matter at all
|
||||
field$InjectedCacheChecker$lastRecipe.set(thisObj, resourceLocation);
|
||||
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(pair.getSecond()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetRecipeForMethodInterceptor1_20_5 {
|
||||
public static final GetRecipeForMethodInterceptor1_20_5 INSTANCE = new GetRecipeForMethodInterceptor1_20_5();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@RuntimeType
|
||||
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
|
||||
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
|
||||
Object type = field$InjectedCacheChecker$recipeType.get(thisObj);
|
||||
Object lastRecipe = field$InjectedCacheChecker$lastRecipe.get(thisObj);
|
||||
Optional<Object> optionalRecipe = (Optional<Object>) Reflections.method$RecipeManager$getRecipeFor0.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe);
|
||||
if (optionalRecipe.isPresent()) {
|
||||
Object holder = optionalRecipe.get();
|
||||
Object id = Reflections.field$RecipeHolder$id.get(holder);
|
||||
Key recipeId = Key.of(id.toString());
|
||||
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
|
||||
|
||||
ItemStack itemStack;
|
||||
List<Object> items;
|
||||
if (type == Reflections.instance$RecipeType$CAMPFIRE_COOKING) {
|
||||
items = (List<Object>) Reflections.field$SimpleContainer$items.get(args[0]);
|
||||
} else {
|
||||
items = (List<Object>) Reflections.field$AbstractFurnaceBlockEntity$items.get(args[0]);
|
||||
}
|
||||
itemStack = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, items.get(0));
|
||||
|
||||
// it's a recipe from other plugins
|
||||
boolean isCustom = recipeManager.isCustomRecipe(recipeId);
|
||||
if (!isCustom) {
|
||||
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
|
||||
return optionalRecipe;
|
||||
}
|
||||
|
||||
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
|
||||
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
|
||||
if (idHolder.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
|
||||
CustomCookingRecipe<ItemStack> ceRecipe;
|
||||
Key lastCustomRecipe = (Key) field$InjectedCacheChecker$lastCustomRecipe.get(thisObj);
|
||||
if (type == Reflections.instance$RecipeType$SMELTING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$BLASTING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$SMOKING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$CAMPFIRE_COOKING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input, lastCustomRecipe);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (ceRecipe == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Cache recipes, it might be incorrect on reloading
|
||||
field$InjectedCacheChecker$lastCustomRecipe.set(thisObj, ceRecipe.id());
|
||||
// It doesn't matter at all
|
||||
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
|
||||
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(holder));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetRecipeForMethodInterceptor1_21 {
|
||||
public static final GetRecipeForMethodInterceptor1_21 INSTANCE = new GetRecipeForMethodInterceptor1_21();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@RuntimeType
|
||||
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
|
||||
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
|
||||
Object type = field$InjectedCacheChecker$recipeType.get(thisObj);
|
||||
Object lastRecipe = field$InjectedCacheChecker$lastRecipe.get(thisObj);
|
||||
Optional<Object> optionalRecipe = (Optional<Object>) Reflections.method$RecipeManager$getRecipeFor0.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe);
|
||||
if (optionalRecipe.isPresent()) {
|
||||
Object holder = optionalRecipe.get();
|
||||
Object id = Reflections.field$RecipeHolder$id.get(holder);
|
||||
Key recipeId = Key.of(id.toString());
|
||||
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
|
||||
ItemStack itemStack = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, Reflections.field$SingleRecipeInput$item.get(args[0]));
|
||||
|
||||
// it's a recipe from other plugins
|
||||
boolean isCustom = recipeManager.isCustomRecipe(recipeId);
|
||||
if (!isCustom) {
|
||||
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
|
||||
return optionalRecipe;
|
||||
}
|
||||
|
||||
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
|
||||
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
|
||||
if (idHolder.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
|
||||
CustomCookingRecipe<ItemStack> ceRecipe;
|
||||
Key lastCustomRecipe = (Key) field$InjectedCacheChecker$lastCustomRecipe.get(thisObj);
|
||||
if (type == Reflections.instance$RecipeType$SMELTING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$BLASTING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$SMOKING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$CAMPFIRE_COOKING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input, lastCustomRecipe);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (ceRecipe == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Cache recipes, it might be incorrect on reloading
|
||||
field$InjectedCacheChecker$lastCustomRecipe.set(thisObj, ceRecipe.id());
|
||||
// It doesn't matter at all
|
||||
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
|
||||
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(holder));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetRecipeForMethodInterceptor1_21_2 {
|
||||
public static final GetRecipeForMethodInterceptor1_21_2 INSTANCE = new GetRecipeForMethodInterceptor1_21_2();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@RuntimeType
|
||||
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
|
||||
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
|
||||
Object type = field$InjectedCacheChecker$recipeType.get(thisObj);
|
||||
Object lastRecipe = field$InjectedCacheChecker$lastRecipe.get(thisObj);
|
||||
Optional<Object> optionalRecipe = (Optional<Object>) Reflections.method$RecipeManager$getRecipeFor1.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe);
|
||||
if (optionalRecipe.isPresent()) {
|
||||
Object holder = optionalRecipe.get();
|
||||
Object id = Reflections.field$RecipeHolder$id.get(holder);
|
||||
Object resourceLocation = Reflections.field$ResourceKey$location.get(id);
|
||||
Key recipeId = Key.of(resourceLocation.toString());
|
||||
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
|
||||
ItemStack itemStack = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, Reflections.field$SingleRecipeInput$item.get(args[0]));
|
||||
|
||||
// it's a recipe from other plugins
|
||||
boolean isCustom = recipeManager.isCustomRecipe(recipeId);
|
||||
if (!isCustom) {
|
||||
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
|
||||
return optionalRecipe;
|
||||
}
|
||||
|
||||
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
|
||||
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
|
||||
if (idHolder.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
|
||||
CustomCookingRecipe<ItemStack> ceRecipe;
|
||||
Key lastCustomRecipe = (Key) field$InjectedCacheChecker$lastCustomRecipe.get(thisObj);
|
||||
if (type == Reflections.instance$RecipeType$SMELTING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$BLASTING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input, lastCustomRecipe);
|
||||
} else if (type == Reflections.instance$RecipeType$SMOKING) {
|
||||
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input, lastCustomRecipe);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (ceRecipe == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Cache recipes, it might be incorrect on reloading
|
||||
field$InjectedCacheChecker$lastCustomRecipe.set(thisObj, ceRecipe.id());
|
||||
// It doesn't matter at all
|
||||
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
|
||||
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(holder));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PalettedContainerMethodInterceptor {
|
||||
public static final PalettedContainerMethodInterceptor INSTANCE = new PalettedContainerMethodInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public Object intercept(@This Object thisObj, @AllArguments Object[] args, @Origin Method method) throws Throwable {
|
||||
InjectedPalettedContainerHolder holder = (InjectedPalettedContainerHolder) thisObj;
|
||||
return method.invoke(holder.target(), args);
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetAndSetInterceptor {
|
||||
public static final GetAndSetInterceptor INSTANCE = new GetAndSetInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public Object intercept(@This Object thisObj, @AllArguments Object[] args, @Origin MethodHandle method) throws Throwable {
|
||||
InjectedPalettedContainerHolder holder = (InjectedPalettedContainerHolder) thisObj;
|
||||
Object targetStates = holder.target();
|
||||
int x = (int) args[0];
|
||||
int y = (int) args[1];
|
||||
int z = (int) args[2];
|
||||
Object previousState = method.invoke(targetStates, x, y, z, args[3]);
|
||||
try {
|
||||
Object newState = args[3];
|
||||
int stateId = BlockStateUtils.blockStateToId(newState);
|
||||
CESection section = holder.ceSection();
|
||||
if (BlockStateUtils.isVanillaBlock(stateId)) {
|
||||
section.setBlockState(x, y, z, EmptyBlock.INSTANCE.getDefaultState());
|
||||
if (ConfigManager.enableLightSystem() && ConfigManager.forceUpdateLight()) {
|
||||
updateLightIfChanged(holder, previousState, newState, null, y, z, x);
|
||||
}
|
||||
} else {
|
||||
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
|
||||
section.setBlockState(x, y, z, immutableBlockState);
|
||||
if (ConfigManager.enableLightSystem()) {
|
||||
updateLightIfChanged(holder, previousState, newState, immutableBlockState.vanillaBlockState().handle(), y, z, x);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to intercept setBlockState", e);
|
||||
}
|
||||
return previousState;
|
||||
}
|
||||
|
||||
private void updateLightIfChanged(@This InjectedPalettedContainerHolder thisObj, Object previousBlockState, Object newState, @Nullable Object clientSideNewState, int y, int z, int x) throws ReflectiveOperationException {
|
||||
int previousLight = BlockStateUtils.getLightEmission(previousBlockState);
|
||||
int newLight = BlockStateUtils.getLightEmission(newState);
|
||||
if (previousLight != newLight || (clientSideNewState != null && (BlockStateUtils.isOcclude(newState) != BlockStateUtils.isOcclude(clientSideNewState)))) {
|
||||
CEWorld world = thisObj.ceWorld();
|
||||
SectionPos sectionPos = thisObj.cePos();
|
||||
Set<SectionPos> posSet = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, Math.max(newLight, previousLight));
|
||||
world.sectionLightUpdated(posSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Object generateBlock(Key replacedBlock, Object ownerBlock, Object properties) throws Throwable {
|
||||
Object ownerProperties = Reflections.field$BlockBehaviour$properties.get(ownerBlock);
|
||||
Reflections.field$BlockBehaviour$Properties$hasCollision.set(properties, Reflections.field$BlockBehaviour$Properties$hasCollision.get(ownerProperties));
|
||||
|
||||
ObjectHolder<BlockBehavior> behaviorHolder = new ObjectHolder<>(EmptyBlockBehavior.INSTANCE);
|
||||
ObjectHolder<BlockShape> shapeHolder = new ObjectHolder<>(STONE_SHAPE);
|
||||
|
||||
Object newBlockInstance = constructor$CraftEngineBlock.invoke(properties);
|
||||
field$CraftEngineBlock$behavior.set(newBlockInstance, behaviorHolder);
|
||||
field$CraftEngineBlock$shape.set(newBlockInstance, shapeHolder);
|
||||
field$CraftEngineBlock$isNoteBlock.set(newBlockInstance, replacedBlock.equals(BlockKeys.NOTE_BLOCK));
|
||||
return newBlockInstance;
|
||||
}
|
||||
|
||||
public static class FluidStateInterceptor {
|
||||
public static final FluidStateInterceptor INSTANCE = new FluidStateInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public Object intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) {
|
||||
ObjectHolder<BlockBehavior> holder = ((BehaviorHolder) thisObj).getBehaviorHolder();
|
||||
try {
|
||||
return holder.value().getFluidState(thisObj, args, superMethod);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run getFluidState", e);
|
||||
return args[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class UpdateShapeInterceptor {
|
||||
public static final UpdateShapeInterceptor INSTANCE = new UpdateShapeInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public Object intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) throws Exception {
|
||||
ObjectHolder<BlockBehavior> holder = ((BehaviorHolder) thisObj).getBehaviorHolder();
|
||||
if (((NoteBlockIndicator) thisObj).isNoteBlock()) {
|
||||
startNoteBlockChain(args);
|
||||
}
|
||||
try {
|
||||
return holder.value().updateShape(thisObj, args, superMethod);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run updateShape", e);
|
||||
return args[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void startNoteBlockChain(Object[] args) throws ReflectiveOperationException {
|
||||
Object direction;
|
||||
Object serverLevel;
|
||||
Object blockPos;
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
direction = args[4];
|
||||
serverLevel = args[1];
|
||||
blockPos = args[3];
|
||||
} else {
|
||||
direction = args[1];
|
||||
serverLevel = args[3];
|
||||
blockPos = args[4];
|
||||
}
|
||||
int id = (int) Reflections.field$Direction$data3d.get(direction);
|
||||
// Y axis
|
||||
if (id == 0 || id == 1) {
|
||||
Object chunkSource = Reflections.field$ServerLevel$chunkSource.get(serverLevel);
|
||||
Reflections.method$ServerChunkCache$blockChanged.invoke(chunkSource, blockPos);
|
||||
if (id == 1) {
|
||||
NoteBlockChainUpdateUtils.noteBlockChainUpdate(serverLevel, chunkSource, Reflections.instance$Direction$DOWN, blockPos, 0);
|
||||
} else {
|
||||
NoteBlockChainUpdateUtils.noteBlockChainUpdate(serverLevel, chunkSource, Reflections.instance$Direction$UP, blockPos, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetShapeInterceptor {
|
||||
public static final GetShapeInterceptor INSTANCE = new GetShapeInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public Object intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) throws Exception {
|
||||
ObjectHolder<BlockShape> holder = ((ShapeHolder) thisObj).getShapeHolder();
|
||||
try {
|
||||
return holder.value().getShape(thisObj, args);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run getShape", e);
|
||||
return superMethod.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class RandomTickInterceptor {
|
||||
public static final RandomTickInterceptor INSTANCE = new RandomTickInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) {
|
||||
ObjectHolder<BlockBehavior> holder = ((BehaviorHolder) thisObj).getBehaviorHolder();
|
||||
try {
|
||||
holder.value().randomTick(thisObj, args, superMethod);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run randomTick", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class TickInterceptor {
|
||||
public static final TickInterceptor INSTANCE = new TickInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) {
|
||||
ObjectHolder<BlockBehavior> holder = ((BehaviorHolder) thisObj).getBehaviorHolder();
|
||||
try {
|
||||
holder.value().tick(thisObj, args, superMethod);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run tick", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class OnPlaceInterceptor {
|
||||
public static final OnPlaceInterceptor INSTANCE = new OnPlaceInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) {
|
||||
ObjectHolder<BlockBehavior> holder = ((BehaviorHolder) thisObj).getBehaviorHolder();
|
||||
try {
|
||||
holder.value().onPlace(thisObj, args, superMethod);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run onPlace", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class OnBrokenAfterFallInterceptor {
|
||||
public static final OnBrokenAfterFallInterceptor INSTANCE = new OnBrokenAfterFallInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) {
|
||||
ObjectHolder<BlockBehavior> holder = ((BehaviorHolder) thisObj).getBehaviorHolder();
|
||||
try {
|
||||
holder.value().onBrokenAfterFall(thisObj, args, superMethod);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run onBrokenAfterFall", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CanSurviveInterceptor {
|
||||
public static final CanSurviveInterceptor INSTANCE = new CanSurviveInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public boolean intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) {
|
||||
ObjectHolder<BlockBehavior> holder = ((BehaviorHolder) thisObj).getBehaviorHolder();
|
||||
try {
|
||||
return holder.value().canSurvive(thisObj, args, superMethod);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run canSurvive", e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class IsBoneMealSuccessInterceptor {
|
||||
public static final IsBoneMealSuccessInterceptor INSTANCE = new IsBoneMealSuccessInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public boolean intercept(@This Object thisObj, @AllArguments Object[] args) {
|
||||
ObjectHolder<BlockBehavior> holder = ((BehaviorHolder) thisObj).getBehaviorHolder();
|
||||
try {
|
||||
return holder.value().isBoneMealSuccess(thisObj, args);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run isBoneMealSuccess", e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class IsValidBoneMealTargetInterceptor {
|
||||
public static final IsValidBoneMealTargetInterceptor INSTANCE = new IsValidBoneMealTargetInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public boolean intercept(@This Object thisObj, @AllArguments Object[] args) {
|
||||
ObjectHolder<BlockBehavior> holder = ((BehaviorHolder) thisObj).getBehaviorHolder();
|
||||
try {
|
||||
return holder.value().isValidBoneMealTarget(thisObj, args);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run isValidBoneMealTarget", e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PerformBoneMealInterceptor {
|
||||
public static final PerformBoneMealInterceptor INSTANCE = new PerformBoneMealInterceptor();
|
||||
|
||||
@RuntimeType
|
||||
public void intercept(@This Object thisObj, @AllArguments Object[] args) {
|
||||
ObjectHolder<BlockBehavior> holder = ((BehaviorHolder) thisObj).getBehaviorHolder();
|
||||
try {
|
||||
holder.value().performBoneMeal(thisObj, args);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run performBoneMeal", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.plugin.network.impl.*;
|
||||
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
|
||||
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
|
||||
import net.momirealms.craftengine.core.plugin.network.NetworkManager;
|
||||
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
|
||||
import net.momirealms.craftengine.core.util.ListMonitor;
|
||||
import net.momirealms.craftengine.core.util.TriConsumer;
|
||||
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.PlayerQuitEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class BukkitNetworkManager implements NetworkManager, Listener {
|
||||
private static BukkitNetworkManager instance;
|
||||
private static final Map<Class<?>, TriConsumer<NetWorkUser, NMSPacketEvent, Object>> nmsPacketFunctions = new HashMap<>();
|
||||
private static final Map<Integer, BiConsumer<NetWorkUser, ByteBufPacketEvent>> byteBufPacketFunctions = new HashMap<>();
|
||||
|
||||
private static void registerNMSPacketConsumer(final TriConsumer<NetWorkUser, NMSPacketEvent, Object> function, @Nullable Class<?> packet) {
|
||||
if (packet == null) return;
|
||||
nmsPacketFunctions.put(packet, function);
|
||||
}
|
||||
|
||||
private static void registerByteBufPacketConsumer(final BiConsumer<NetWorkUser, ByteBufPacketEvent> function, int id) {
|
||||
byteBufPacketFunctions.put(id, function);
|
||||
}
|
||||
|
||||
private final BiConsumer<Object, List<Object>> packetsConsumer;
|
||||
private final BiConsumer<Object, Object> delayedPacketConsumer;
|
||||
private final BiConsumer<Object, Object> immediatePacketConsumer;
|
||||
private final BukkitCraftEngine plugin;
|
||||
|
||||
private final Map<ChannelPipeline, BukkitServerPlayer> users = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, BukkitServerPlayer> onlineUsers = new ConcurrentHashMap<>();
|
||||
private final HashSet<Channel> injectedChannels = new HashSet<>();
|
||||
|
||||
private final PacketIds packetIds;
|
||||
|
||||
private static final String CONNECTION_HANDLER_NAME = "craftengine_connection_handler";
|
||||
private static final String SERVER_CHANNEL_HANDLER_NAME = "craftengine_server_channel_handler";
|
||||
private static final String PLAYER_CHANNEL_HANDLER_NAME = "craftengine_player_packet_handler";
|
||||
private static final String PACKET_ENCODER = "craftengine_encoder";
|
||||
private static final String PACKET_DECODER = "craftengine_decoder";
|
||||
|
||||
private boolean active;
|
||||
private boolean init;
|
||||
|
||||
public BukkitNetworkManager(BukkitCraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
if (VersionHelper.isVersionNewerThan1_21_2()) {
|
||||
this.packetIds = new PacketIds1_21_2();
|
||||
} else if (VersionHelper.isVersionNewerThan1_20_5()) {
|
||||
this.packetIds = new PacketIds1_20_5();
|
||||
} else if (VersionHelper.isVersionNewerThan1_20_3()) {
|
||||
this.packetIds = new PacketIds1_20_3();
|
||||
} else if (VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
this.packetIds = new PacketIds1_20_2();
|
||||
} else {
|
||||
this.packetIds = new PacketIds1_20();
|
||||
}
|
||||
this.registerConsumers();
|
||||
this.packetsConsumer = ((serverPlayer, packets) -> {
|
||||
try {
|
||||
Object bundle = Reflections.constructor$ClientboundBundlePacket.newInstance(packets);
|
||||
Reflections.method$ServerGamePacketListenerImpl$sendPacket.invoke(
|
||||
Reflections.field$ServerPlayer$connection.get(serverPlayer), bundle);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to create bundle packet", e);
|
||||
}
|
||||
});
|
||||
this.delayedPacketConsumer = (serverPlayer, packet) -> {
|
||||
try {
|
||||
Reflections.method$ServerGamePacketListenerImpl$sendPacket.invoke(
|
||||
Reflections.field$ServerPlayer$connection.get(serverPlayer), packet);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to invoke send packet", e);
|
||||
}
|
||||
};
|
||||
this.immediatePacketConsumer = (serverPlayer, packet) -> {
|
||||
try {
|
||||
Reflections.method$Connection$sendPacketImmediate.invoke(Reflections.field$ServerCommonPacketListenerImpl$connection.get(Reflections.field$ServerPlayer$connection.get(serverPlayer)), packet, null, true);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to invoke send packet", e);
|
||||
}
|
||||
};
|
||||
this.active = true;
|
||||
instance = this;
|
||||
}
|
||||
|
||||
private void registerConsumers() {
|
||||
registerNMSPacketConsumer(PacketConsumers.LEVEL_CHUNK_WITH_LIGHT, Reflections.clazz$ClientboundLevelChunkWithLightPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.PLAYER_ACTION, Reflections.clazz$ServerboundPlayerActionPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.SWING_HAND, Reflections.clazz$ServerboundSwingPacket);
|
||||
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);
|
||||
registerNMSPacketConsumer(PacketConsumers.ADD_ENTITY, Reflections.clazz$ClientboundAddEntityPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.LOGIN, Reflections.clazz$ClientboundLoginPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.RESPAWN, Reflections.clazz$ClientboundRespawnPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.INTERACT_ENTITY, Reflections.clazz$ServerboundInteractPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.REMOVE_ENTITY, Reflections.clazz$ClientboundRemoveEntitiesPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.SYNC_ENTITY_POSITION, Reflections.clazz$ClientboundEntityPositionSyncPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.MOVE_ENTITY, Reflections.clazz$ClientboundMoveEntityPacket$Pos);
|
||||
registerNMSPacketConsumer(PacketConsumers.PICK_ITEM_FROM_ENTITY, Reflections.clazz$ServerboundPickItemFromEntityPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.SOUND, Reflections.clazz$ClientboundSoundPacket);
|
||||
registerByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket());
|
||||
registerByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket());
|
||||
registerByteBufPacketConsumer(PacketConsumers.LEVEL_PARTICLE, this.packetIds.clientboundLevelParticlesPacket());
|
||||
registerByteBufPacketConsumer(PacketConsumers.LEVEL_EVENT, this.packetIds.clientboundLevelEventPacket());
|
||||
}
|
||||
|
||||
public static BukkitNetworkManager instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
BukkitServerPlayer user = (BukkitServerPlayer) getUser(player);
|
||||
if (user != null) {
|
||||
user.setPlayer(player);
|
||||
this.onlineUsers.put(player.getUniqueId(), user);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Channel channel = getChannel(player);
|
||||
NetWorkUser user = removeUser(channel);
|
||||
if (user == null) return;
|
||||
handleDisconnection(channel);
|
||||
this.onlineUsers.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BukkitServerPlayer> onlineUsers() {
|
||||
return onlineUsers.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
if (init) return;
|
||||
try {
|
||||
Object server = Reflections.method$MinecraftServer$getServer.invoke(null);
|
||||
Object serverConnection = Reflections.field$MinecraftServer$connection.get(server);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ChannelFuture> channels = (List<ChannelFuture>) Reflections.field$ServerConnectionListener$channels.get(serverConnection);
|
||||
ListMonitor<ChannelFuture> monitor = new ListMonitor<>(channels, (future) -> {
|
||||
if (!active) return;
|
||||
Channel channel = future.channel();
|
||||
injectServerChannel(channel);
|
||||
injectedChannels.add(channel);
|
||||
}, (object) -> {});
|
||||
Reflections.field$ServerConnectionListener$channels.set(serverConnection, monitor);
|
||||
init = true;
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to init server connection", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
HandlerList.unregisterAll(this);
|
||||
for (Channel channel : injectedChannels) {
|
||||
uninjectServerChannel(channel);
|
||||
}
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
handleDisconnection(getChannel(player));
|
||||
}
|
||||
injectedChannels.clear();
|
||||
active = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUser(Channel channel, NetWorkUser user) {
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
users.put(pipeline, (BukkitServerPlayer) user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetWorkUser getUser(Channel channel) {
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
return users.get(pipeline);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetWorkUser removeUser(Channel channel) {
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
return users.remove(pipeline);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel getChannel(net.momirealms.craftengine.core.entity.player.Player player) {
|
||||
return getChannel((Player) player.platformPlayer());
|
||||
}
|
||||
|
||||
public NetWorkUser getUser(Player player) {
|
||||
return getUser(getChannel(player));
|
||||
}
|
||||
|
||||
public NetWorkUser getOnlineUser(Player player) {
|
||||
return onlineUsers.get(player.getUniqueId());
|
||||
}
|
||||
|
||||
public Channel getChannel(Player player) {
|
||||
try {
|
||||
return (Channel) Reflections.field$Channel.get(
|
||||
Reflections.field$NetworkManager.get(
|
||||
Reflections.field$ServerPlayer$connection.get(
|
||||
Reflections.method$CraftPlayer$getHandle.invoke(player)
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPacket(@NotNull Player player, @NotNull Object packet) {
|
||||
try {
|
||||
Object serverPlayer = Reflections.method$CraftPlayer$getHandle.invoke(player);
|
||||
this.immediatePacketConsumer.accept(serverPlayer, packet);
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to send packet", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPacket(@NotNull NetWorkUser player, Object packet, boolean immediately) {
|
||||
if (immediately) {
|
||||
this.immediatePacketConsumer.accept(player.serverPlayer(), packet);
|
||||
} else {
|
||||
this.delayedPacketConsumer.accept(player.serverPlayer(), packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPackets(@NotNull NetWorkUser player, List<Object> packet) {
|
||||
this.packetsConsumer.accept(player.serverPlayer(), packet);
|
||||
}
|
||||
|
||||
private void injectServerChannel(Channel serverChannel) {
|
||||
ChannelPipeline pipeline = serverChannel.pipeline();
|
||||
ChannelHandler connectionHandler = pipeline.get(CONNECTION_HANDLER_NAME);
|
||||
if (connectionHandler != null) {
|
||||
pipeline.remove(CONNECTION_HANDLER_NAME);
|
||||
}
|
||||
if (pipeline.get("SpigotNettyServerChannelHandler#0") != null) {
|
||||
pipeline.addAfter("SpigotNettyServerChannelHandler#0", CONNECTION_HANDLER_NAME, new ServerChannelHandler());
|
||||
} else if (pipeline.get("floodgate-init") != null) {
|
||||
pipeline.addAfter("floodgate-init", CONNECTION_HANDLER_NAME, new ServerChannelHandler());
|
||||
} else if (pipeline.get("MinecraftPipeline#0") != null) {
|
||||
pipeline.addAfter("MinecraftPipeline#0", CONNECTION_HANDLER_NAME, new ServerChannelHandler());
|
||||
} else {
|
||||
pipeline.addFirst(CONNECTION_HANDLER_NAME, new ServerChannelHandler());
|
||||
}
|
||||
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
Channel channel = getChannel(player);
|
||||
NetWorkUser user = getUser(player);
|
||||
if (user == null) {
|
||||
user = new BukkitServerPlayer(plugin, channel);
|
||||
((BukkitServerPlayer) user).setPlayer(player);
|
||||
injectChannel(channel, ConnectionState.PLAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void uninjectServerChannel(Channel channel) {
|
||||
if (channel.pipeline().get(CONNECTION_HANDLER_NAME) != null) {
|
||||
channel.pipeline().remove(CONNECTION_HANDLER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleDisconnection(Channel channel) {
|
||||
NetWorkUser user = removeUser(channel);
|
||||
if (user == null) return;
|
||||
if (channel.pipeline().get(PLAYER_CHANNEL_HANDLER_NAME) != null) {
|
||||
channel.pipeline().remove(PLAYER_CHANNEL_HANDLER_NAME);
|
||||
}
|
||||
if (channel.pipeline().get(PACKET_ENCODER) != null) {
|
||||
channel.pipeline().remove(PACKET_ENCODER);
|
||||
}
|
||||
if (channel.pipeline().get(PACKET_DECODER) != null) {
|
||||
channel.pipeline().remove(PACKET_DECODER);
|
||||
}
|
||||
}
|
||||
|
||||
public class ServerChannelHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object c) throws Exception {
|
||||
Channel channel = (Channel) c;
|
||||
channel.pipeline().addLast(SERVER_CHANNEL_HANDLER_NAME, new PreChannelInitializer());
|
||||
super.channelRead(context, c);
|
||||
}
|
||||
}
|
||||
|
||||
public class PreChannelInitializer extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class);
|
||||
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext context) {
|
||||
try {
|
||||
injectChannel(context.channel(), ConnectionState.HANDSHAKING);
|
||||
} catch (Throwable t) {
|
||||
exceptionCaught(context, t);
|
||||
} finally {
|
||||
ChannelPipeline pipeline = context.pipeline();
|
||||
if (pipeline.context(this) != null) {
|
||||
pipeline.remove(this);
|
||||
}
|
||||
}
|
||||
context.pipeline().fireChannelRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext context, Throwable t) {
|
||||
PreChannelInitializer.logger.warn("Failed to inject channel: " + context.channel(), t);
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void injectChannel(Channel channel, ConnectionState state) {
|
||||
if (isFakeChannel(channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BukkitServerPlayer user = new BukkitServerPlayer(plugin, channel);
|
||||
if (channel.pipeline().get("splitter") == null) {
|
||||
channel.close();
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
if (pipeline.get(PACKET_ENCODER) != null) {
|
||||
pipeline.remove(PACKET_ENCODER);
|
||||
}
|
||||
if (pipeline.get(PACKET_DECODER) != null) {
|
||||
pipeline.remove(PACKET_DECODER);
|
||||
}
|
||||
for (Map.Entry<String, ChannelHandler> entry : pipeline.toMap().entrySet()) {
|
||||
if (Reflections.clazz$Connection.isAssignableFrom(entry.getValue().getClass())) {
|
||||
pipeline.addBefore(entry.getKey(), PLAYER_CHANNEL_HANDLER_NAME, new PluginChannelHandler(user));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String decoderName = pipeline.names().contains("inbound_config") ? "inbound_config" : "decoder";
|
||||
pipeline.addBefore(decoderName, PACKET_DECODER, new PluginChannelDecoder(user));
|
||||
String encoderName = pipeline.names().contains("outbound_config") ? "outbound_config" : "encoder";
|
||||
pipeline.addBefore(encoderName, PACKET_ENCODER, new PluginChannelEncoder(user));
|
||||
|
||||
channel.closeFuture().addListener((ChannelFutureListener) future -> handleDisconnection(user.nettyChannel()));
|
||||
setUser(channel, user);
|
||||
}
|
||||
|
||||
public static boolean isFakeChannel(Object channel) {
|
||||
return channel.getClass().getSimpleName().equals("FakeChannel")
|
||||
|| channel.getClass().getSimpleName().equals("SpoofedChannel");
|
||||
}
|
||||
|
||||
public class PluginChannelHandler extends ChannelDuplexHandler {
|
||||
|
||||
private final NetWorkUser player;
|
||||
|
||||
public PluginChannelHandler(NetWorkUser player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) throws Exception {
|
||||
try {
|
||||
NMSPacketEvent event = new NMSPacketEvent(packet);
|
||||
onNMSPacketSend(player, event, packet);
|
||||
if (event.isCancelled()) return;
|
||||
super.write(context, packet, channelPromise);
|
||||
channelPromise.addListener((p) -> {
|
||||
for (Runnable task : event.getDelayedTasks()) {
|
||||
task.run();
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
plugin.logger().severe("An error occurred when reading packets", e);
|
||||
super.write(context, packet, channelPromise);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object packet) throws Exception {
|
||||
NMSPacketEvent event = new NMSPacketEvent(packet);
|
||||
onNMSPacketReceive(player, event, packet);
|
||||
if (event.isCancelled()) return;
|
||||
super.channelRead(context, packet);
|
||||
}
|
||||
}
|
||||
|
||||
public class PluginChannelEncoder extends MessageToMessageEncoder<ByteBuf> {
|
||||
private final NetWorkUser player;
|
||||
private boolean handledCompression = false;
|
||||
|
||||
public PluginChannelEncoder(NetWorkUser player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public PluginChannelEncoder(PluginChannelEncoder encoder) {
|
||||
this.player = encoder.player;
|
||||
this.handledCompression = encoder.handledCompression;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) {
|
||||
boolean needCompression = !handledCompression && handleCompression(channelHandlerContext, byteBuf);
|
||||
this.onByteBufSend(byteBuf);
|
||||
if (needCompression) {
|
||||
compress(channelHandlerContext, byteBuf);
|
||||
}
|
||||
if (byteBuf.isReadable()) {
|
||||
list.add(byteBuf.retain());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleCompression(ChannelHandlerContext ctx, ByteBuf buffer) {
|
||||
if (handledCompression) return false;
|
||||
int compressIndex = ctx.pipeline().names().indexOf("compress");
|
||||
if (compressIndex == -1) return false;
|
||||
handledCompression = true;
|
||||
int encoderIndex = ctx.pipeline().names().indexOf(PACKET_ENCODER);
|
||||
if (encoderIndex == -1) return false;
|
||||
if (compressIndex > encoderIndex) {
|
||||
decompress(ctx, buffer, buffer);
|
||||
PluginChannelEncoder encoder = (PluginChannelEncoder) ctx.pipeline().remove(PACKET_ENCODER);
|
||||
String encoderName = ctx.pipeline().names().contains("outbound_config") ? "outbound_config" : "encoder";
|
||||
ctx.pipeline().addBefore(encoderName, PACKET_ENCODER, new PluginChannelEncoder(encoder));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onByteBufSend(ByteBuf buffer) {
|
||||
// I don't care packets before PLAY for the moment
|
||||
if (player.encoderState() != ConnectionState.PLAY) return;
|
||||
int size = buffer.readableBytes();
|
||||
if (size != 0) {
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(buffer);
|
||||
int preProcessIndex = buf.readerIndex();
|
||||
int packetId = buf.readVarInt();
|
||||
ByteBufPacketEvent event = new ByteBufPacketEvent(packetId, buf);
|
||||
BukkitNetworkManager.this.handleByteBufPacket(this.player, event);
|
||||
if (event.isCancelled()) {
|
||||
buf.clear();
|
||||
} else if (!event.changed()) {
|
||||
buf.readerIndex(preProcessIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PluginChannelDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
private final NetWorkUser player;
|
||||
|
||||
public PluginChannelDecoder(NetWorkUser player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext context, ByteBuf byteBuf, List<Object> list) {
|
||||
if (byteBuf.isReadable()) {
|
||||
list.add(byteBuf.retain());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onNMSPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) {
|
||||
handleNMSPacket(user, event, packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void onNMSPacketSend(NetWorkUser player, NMSPacketEvent event, Object packet) throws ReflectiveOperationException {
|
||||
if (Reflections.clazz$ClientboundBundlePacket.isInstance(packet)) {
|
||||
Iterable<Object> packets = (Iterable<Object>) Reflections.field$BundlePacket$packets.get(packet);
|
||||
for (Object p : packets) {
|
||||
onNMSPacketSend(player, event, p);
|
||||
}
|
||||
} else {
|
||||
handleNMSPacket(player, event, packet);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleNMSPacket(NetWorkUser user, NMSPacketEvent event, Object packet) {
|
||||
Optional.ofNullable(nmsPacketFunctions.get(packet.getClass()))
|
||||
.ifPresent(function -> function.accept(user, event, packet));
|
||||
}
|
||||
|
||||
protected void handleByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) {
|
||||
int packetID = event.packetID();
|
||||
Optional.ofNullable(byteBufPacketFunctions.get(packetID))
|
||||
.ifPresent(function -> function.accept(user, event));
|
||||
}
|
||||
|
||||
private void compress(ChannelHandlerContext ctx, ByteBuf input) {
|
||||
ChannelHandler compressor = ctx.pipeline().get("compress");
|
||||
ByteBuf temp = ctx.alloc().buffer();
|
||||
try {
|
||||
if (compressor != null) {
|
||||
callEncode(compressor, ctx, input, temp);
|
||||
}
|
||||
} finally {
|
||||
input.clear().writeBytes(temp);
|
||||
temp.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void decompress(ChannelHandlerContext ctx, ByteBuf input, ByteBuf output) {
|
||||
ChannelHandler decompressor = ctx.pipeline().get("decompress");
|
||||
if (decompressor != null) {
|
||||
ByteBuf temp = (ByteBuf) callDecode(decompressor, ctx, input).get(0);
|
||||
try {
|
||||
output.clear().writeBytes(temp);
|
||||
} finally {
|
||||
temp.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void callEncode(Object encoder, ChannelHandlerContext ctx, ByteBuf msg, ByteBuf output) {
|
||||
try {
|
||||
Reflections.method$messageToByteEncoder$encode.invoke(encoder, ctx, msg, output);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
CraftEngine.instance().logger().warn("Failed to call encode", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Object> callDecode(Object decoder, Object ctx, Object input) {
|
||||
List<Object> output = new ArrayList<>();
|
||||
try {
|
||||
Reflections.method$byteToMessageDecoder$decode.invoke(decoder, ctx, input, output);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
CraftEngine.instance().logger().warn("Failed to call decode", e);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network;
|
||||
|
||||
import net.momirealms.craftengine.core.util.Cancellable;
|
||||
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ByteBufPacketEvent implements Cancellable {
|
||||
private boolean cancelled;
|
||||
private List<Runnable> delayedTasks = null;
|
||||
private final FriendlyByteBuf buf;
|
||||
private boolean changed;
|
||||
private final int packetID;
|
||||
|
||||
public ByteBufPacketEvent(int packetID, FriendlyByteBuf buf) {
|
||||
this.buf = buf;
|
||||
this.packetID = packetID;
|
||||
}
|
||||
|
||||
public int packetID() {
|
||||
return packetID;
|
||||
}
|
||||
|
||||
public FriendlyByteBuf getBuffer() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
public void setChanged(boolean dirty) {
|
||||
this.changed = dirty;
|
||||
}
|
||||
|
||||
public boolean changed() {
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void addDelayedTask(Runnable task) {
|
||||
if (delayedTasks == null) {
|
||||
delayedTasks = new ArrayList<>();
|
||||
}
|
||||
delayedTasks.add(task);
|
||||
}
|
||||
|
||||
public List<Runnable> getDelayedTasks() {
|
||||
return Optional.ofNullable(delayedTasks).orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network;
|
||||
|
||||
import net.momirealms.craftengine.core.util.Cancellable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class NMSPacketEvent implements Cancellable {
|
||||
private boolean cancelled;
|
||||
private List<Runnable> delayedTasks = null;
|
||||
private final Object packet;
|
||||
|
||||
public NMSPacketEvent(Object packet) {
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
public Object getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public void addDelayedTask(Runnable task) {
|
||||
if (delayedTasks == null) {
|
||||
delayedTasks = new ArrayList<>();
|
||||
}
|
||||
delayedTasks.add(task);
|
||||
}
|
||||
|
||||
public List<Runnable> getDelayedTasks() {
|
||||
return Optional.ofNullable(delayedTasks).orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,664 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture;
|
||||
import net.momirealms.craftengine.bukkit.api.event.FurnitureBreakEvent;
|
||||
import net.momirealms.craftengine.bukkit.api.event.FurnitureInteractEvent;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture;
|
||||
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.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
|
||||
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
|
||||
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
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;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.util.RayTraceResult;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class PacketConsumers {
|
||||
private static int[] mappings;
|
||||
private static IntIdentityList BLOCK_LIST;
|
||||
private static IntIdentityList BIOME_LIST;
|
||||
|
||||
public static void init(Map<Integer, Integer> map, int registrySize) {
|
||||
mappings = new int[registrySize];
|
||||
Arrays.fill(mappings, -1);
|
||||
for (int i = 0; i < registrySize; i++) {
|
||||
mappings[i] = i;
|
||||
}
|
||||
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
|
||||
mappings[entry.getKey()] = entry.getValue();
|
||||
}
|
||||
BLOCK_LIST = new IntIdentityList(registrySize);
|
||||
BIOME_LIST = new IntIdentityList(RegistryUtils.currentBiomeRegistrySize());
|
||||
}
|
||||
|
||||
public static int remap(int stateId) {
|
||||
return mappings[stateId];
|
||||
}
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> LEVEL_CHUNK_WITH_LIGHT = (user, event, packet) -> {
|
||||
try {
|
||||
BukkitServerPlayer player = (BukkitServerPlayer) user;
|
||||
Object chunkData = Reflections.field$ClientboundLevelChunkWithLightPacket$chunkData.get(packet);
|
||||
byte[] buffer = (byte[]) Reflections.field$ClientboundLevelChunkPacketData$buffer.get(chunkData);
|
||||
ByteBuf buf = Unpooled.copiedBuffer(buffer);
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf);
|
||||
FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
for (int i = 0, count = player.clientSideSectionCount(); i < count; i++) {
|
||||
try {
|
||||
MCSection mcSection = new MCSection(BLOCK_LIST, BIOME_LIST);
|
||||
mcSection.readPacket(friendlyByteBuf);
|
||||
PalettedContainer<Integer> container = mcSection.blockStateContainer();
|
||||
Palette<Integer> palette = container.data().palette();
|
||||
if (palette.canRemap()) {
|
||||
palette.remap(PacketConsumers::remap);
|
||||
} else {
|
||||
for (int j = 0; j < 4096; j ++) {
|
||||
int state = container.get(j);
|
||||
int newState = remap(state);
|
||||
if (newState != state) {
|
||||
container.set(j, newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
mcSection.writePacket(newBuf);
|
||||
} catch (Exception e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Reflections.field$ClientboundLevelChunkPacketData$buffer.set(chunkData, newBuf.array());
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelChunkWithLightPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final BiConsumer<NetWorkUser, ByteBufPacketEvent> SECTION_BLOCK_UPDATE = (user, event) -> {
|
||||
try {
|
||||
FriendlyByteBuf buf = event.getBuffer();
|
||||
long pos = buf.readLong();
|
||||
int blocks = buf.readVarInt();
|
||||
short[] positions = new short[blocks];
|
||||
int[] states = new int[blocks];
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
long k = buf.readVarLong();
|
||||
positions[i] = (short) ((int) (k & 4095L));
|
||||
states[i] = remap((int) (k >>> 12));
|
||||
}
|
||||
buf.clear();
|
||||
buf.writeVarInt(event.packetID());
|
||||
buf.writeLong(pos);
|
||||
buf.writeVarInt(blocks);
|
||||
for (int i = 0; i < blocks; i ++) {
|
||||
buf.writeVarLong((long) states[i] << 12 | positions[i]);
|
||||
}
|
||||
event.setChanged(true);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundSectionBlocksUpdatePacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final BiConsumer<NetWorkUser, ByteBufPacketEvent> BLOCK_UPDATE = (user, event) -> {
|
||||
try {
|
||||
FriendlyByteBuf buf = event.getBuffer();
|
||||
BlockPos pos = buf.readBlockPos(buf);
|
||||
int before = buf.readVarInt();
|
||||
int state = remap(before);
|
||||
if (state == before) {
|
||||
return;
|
||||
}
|
||||
event.setChanged(true);
|
||||
buf.clear();
|
||||
buf.writeVarInt(event.packetID());
|
||||
buf.writeBlockPos(pos);
|
||||
buf.writeVarInt(state);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundBlockUpdatePacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final BiConsumer<NetWorkUser, ByteBufPacketEvent> LEVEL_EVENT = (user, event) -> {
|
||||
try {
|
||||
FriendlyByteBuf buf = event.getBuffer();
|
||||
int eventId = buf.readInt();
|
||||
if (eventId != 2001) return;
|
||||
BlockPos blockPos = buf.readBlockPos(buf);
|
||||
int state = buf.readInt();
|
||||
boolean global = buf.readBoolean();
|
||||
int newState = remap(state);
|
||||
if (newState == state) {
|
||||
return;
|
||||
}
|
||||
event.setChanged(true);
|
||||
buf.clear();
|
||||
buf.writeVarInt(event.packetID());
|
||||
buf.writeInt(eventId);
|
||||
buf.writeBlockPos(blockPos);
|
||||
buf.writeInt(newState);
|
||||
buf.writeBoolean(global);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelEventPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final BiConsumer<NetWorkUser, ByteBufPacketEvent> LEVEL_PARTICLE = (user, event) -> {
|
||||
try {
|
||||
FriendlyByteBuf buf = event.getBuffer();
|
||||
Object mcByteBuf;
|
||||
Method writeMethod;
|
||||
if (VersionHelper.isVersionNewerThan1_20_5()) {
|
||||
mcByteBuf = Reflections.constructor$RegistryFriendlyByteBuf.newInstance(buf, Reflections.instance$registryAccess);
|
||||
writeMethod = Reflections.method$ClientboundLevelParticlesPacket$write;
|
||||
} else {
|
||||
mcByteBuf = Reflections.constructor$FriendlyByteBuf.newInstance(event.getBuffer().source());
|
||||
writeMethod = Reflections.method$Packet$write;
|
||||
}
|
||||
Object packet = Reflections.constructor$ClientboundLevelParticlesPacket.newInstance(mcByteBuf);
|
||||
Object option = Reflections.field$ClientboundLevelParticlesPacket$particle.get(packet);
|
||||
if (option == null) return;
|
||||
if (!Reflections.clazz$BlockParticleOption.isInstance(option)) return;
|
||||
Object blockState = Reflections.field$BlockParticleOption$blockState.get(option);
|
||||
int id = BlockStateUtils.blockStateToId(blockState);
|
||||
int remapped = remap(id);
|
||||
if (remapped == id) return;
|
||||
Reflections.field$BlockParticleOption$blockState.set(option, BlockStateUtils.idToBlockState(remapped));
|
||||
event.setChanged(true);
|
||||
buf.clear();
|
||||
buf.writeVarInt(event.packetID());
|
||||
writeMethod.invoke(packet, mcByteBuf);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelParticlesPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> PLAYER_ACTION = (user, event, packet) -> {
|
||||
try {
|
||||
if (!user.isOnline()) return;
|
||||
BukkitServerPlayer player = (BukkitServerPlayer) user;
|
||||
Player platformPlayer = player.platformPlayer();
|
||||
World world = platformPlayer.getWorld();
|
||||
Object blockPos = Reflections.field$ServerboundPlayerActionPacket$pos.get(packet);
|
||||
BlockPos pos = new BlockPos(
|
||||
(int) Reflections.field$Vec3i$x.get(blockPos),
|
||||
(int) Reflections.field$Vec3i$y.get(blockPos),
|
||||
(int) Reflections.field$Vec3i$z.get(blockPos)
|
||||
);
|
||||
if (VersionHelper.isFolia()) {
|
||||
BukkitCraftEngine.instance().scheduler().sync().run(() -> {
|
||||
try {
|
||||
handlePlayerActionPacketOnMainThread(player, world, pos, packet);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundPlayerActionPacket", e);
|
||||
}
|
||||
}, world, pos.x() >> 4, pos.z() >> 4);
|
||||
} else {
|
||||
handlePlayerActionPacketOnMainThread(player, world, pos, packet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundPlayerActionPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
private static void handlePlayerActionPacketOnMainThread(BukkitServerPlayer player, World world, BlockPos pos, Object packet) throws Exception {
|
||||
|
||||
Object action = Reflections.field$ServerboundPlayerActionPacket$action.get(packet);
|
||||
if (action == Reflections.instance$ServerboundPlayerActionPacket$Action$START_DESTROY_BLOCK) {
|
||||
Object serverLevel = Reflections.field$CraftWorld$ServerLevel.get(world);
|
||||
Object blockState = Reflections.method$BlockGetter$getBlockState.invoke(serverLevel, LocationUtils.toBlockPos(pos));
|
||||
int stateId = BlockStateUtils.blockStateToId(blockState);
|
||||
// not a custom block
|
||||
if (BlockStateUtils.isVanillaBlock(stateId)) {
|
||||
if (ConfigManager.enableSoundSystem()) {
|
||||
Object blockOwner = Reflections.field$StateHolder$owner.get(blockState);
|
||||
if (BukkitBlockManager.instance().isBlockSoundRemoved(blockOwner)) {
|
||||
player.startMiningBlock(world, pos, blockState, false, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (player.isMiningBlock() || player.shouldSyncAttribute()) {
|
||||
player.stopMiningBlock();
|
||||
}
|
||||
return;
|
||||
}
|
||||
player.startMiningBlock(world, pos, blockState, true, BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId));
|
||||
} else if (action == Reflections.instance$ServerboundPlayerActionPacket$Action$ABORT_DESTROY_BLOCK) {
|
||||
if (player.isMiningBlock()) {
|
||||
player.abortMiningBlock();
|
||||
}
|
||||
} else if (action == Reflections.instance$ServerboundPlayerActionPacket$Action$STOP_DESTROY_BLOCK) {
|
||||
if (player.isMiningBlock()) {
|
||||
player.stopMiningBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> SWING_HAND = (user, event, packet) -> {
|
||||
try {
|
||||
if (!user.isOnline()) return;
|
||||
BukkitServerPlayer player = (BukkitServerPlayer) user;
|
||||
if (!player.isMiningBlock()) return;
|
||||
Object hand = Reflections.field$ServerboundSwingPacket$hand.get(packet);
|
||||
if (hand == Reflections.instance$InteractionHand$MAIN_HAND) {
|
||||
player.onSwingHand();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundSwingPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> USE_ITEM_ON = (user, event, packet) -> {
|
||||
try {
|
||||
if (!user.isOnline()) return;
|
||||
BukkitServerPlayer player = (BukkitServerPlayer) user;
|
||||
if (player.isMiningBlock()) {
|
||||
player.stopMiningBlock();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundUseItemOnPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> RESPAWN = (user, event, packet) -> {
|
||||
try {
|
||||
BukkitServerPlayer player = (BukkitServerPlayer) user;
|
||||
Object dimensionKey;
|
||||
if (!VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
dimensionKey = Reflections.field$ClientboundRespawnPacket$dimension.get(packet);
|
||||
} else {
|
||||
Object commonInfo = Reflections.field$ClientboundRespawnPacket$commonPlayerSpawnInfo.get(packet);
|
||||
dimensionKey = Reflections.field$CommonPlayerSpawnInfo$dimension.get(commonInfo);
|
||||
}
|
||||
Object location = Reflections.field$ResourceKey$location.get(dimensionKey);
|
||||
World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString())));
|
||||
if (world != null) {
|
||||
int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16;
|
||||
player.setClientSideSectionCount(sectionCount);
|
||||
player.setClientSideDimension(Key.of(location.toString()));
|
||||
} else {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> LOGIN = (user, event, packet) -> {
|
||||
try {
|
||||
BukkitServerPlayer player = (BukkitServerPlayer) user;
|
||||
player.setConnectionState(ConnectionState.PLAY);
|
||||
Object dimensionKey;
|
||||
if (!VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
dimensionKey = Reflections.field$ClientboundLoginPacket$dimension.get(packet);
|
||||
} else {
|
||||
Object commonInfo = Reflections.field$ClientboundLoginPacket$commonPlayerSpawnInfo.get(packet);
|
||||
dimensionKey = Reflections.field$CommonPlayerSpawnInfo$dimension.get(commonInfo);
|
||||
}
|
||||
Object location = Reflections.field$ResourceKey$location.get(dimensionKey);
|
||||
World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString())));
|
||||
if (world != null) {
|
||||
int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16;
|
||||
player.setClientSideSectionCount(sectionCount);
|
||||
player.setClientSideDimension(Key.of(location.toString()));
|
||||
} else {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket: World " + location + " does not exist");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 1.21.4-
|
||||
// We can't find the best solution, we can only keep the feel as good as possible
|
||||
// When the hotbar is full, the latest creative mode inventory can only be accessed when the player opens the inventory screen. Currently, it is not worth further handling this issue.
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> SET_CREATIVE_SLOT = (user, event, packet) -> {
|
||||
try {
|
||||
if (VersionHelper.isVersionNewerThan1_21_4()) return;
|
||||
if (!user.isOnline()) return;
|
||||
BukkitServerPlayer player = (BukkitServerPlayer) user;
|
||||
if (VersionHelper.isFolia()) {
|
||||
BukkitCraftEngine.instance().scheduler().sync().run(() -> {
|
||||
try {
|
||||
handleSetCreativeSlotPacketOnMainThread(player, packet);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundSetCreativeModeSlotPacket", e);
|
||||
}
|
||||
}, (World) player.level().getHandle(), (MCUtils.fastFloor(player.x())) >> 4, (MCUtils.fastFloor(player.z())) >> 4);
|
||||
} else {
|
||||
handleSetCreativeSlotPacketOnMainThread(player, packet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundSetCreativeModeSlotPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
private static void handleSetCreativeSlotPacketOnMainThread(BukkitServerPlayer player, Object packet) throws Exception {
|
||||
Player bukkitPlayer = player.platformPlayer();
|
||||
if (bukkitPlayer == null) return;
|
||||
if (bukkitPlayer.getGameMode() != GameMode.CREATIVE) return;
|
||||
int slot = VersionHelper.isVersionNewerThan1_20_5() ? Reflections.field$ServerboundSetCreativeModeSlotPacket$slotNum.getShort(packet) : Reflections.field$ServerboundSetCreativeModeSlotPacket$slotNum.getInt(packet);
|
||||
if (slot < 36 || slot > 44) return;
|
||||
ItemStack item = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, Reflections.field$ServerboundSetCreativeModeSlotPacket$itemStack.get(packet));
|
||||
if (ItemUtils.isEmpty(item)) return;
|
||||
if (slot - 36 != bukkitPlayer.getInventory().getHeldItemSlot()) {
|
||||
return;
|
||||
}
|
||||
double interactionRange = player.getInteractionRange();
|
||||
// do ray trace to get current block
|
||||
RayTraceResult result = bukkitPlayer.rayTraceBlocks(interactionRange, FluidCollisionMode.NEVER);
|
||||
if (result == null) return;
|
||||
org.bukkit.block.Block hitBlock = result.getHitBlock();
|
||||
if (hitBlock == null) return;
|
||||
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(hitBlock.getBlockData()));
|
||||
// not a custom block
|
||||
if (state == null || state.isEmpty()) return;
|
||||
Key itemId = state.settings().itemId();
|
||||
// no item available
|
||||
if (itemId == null) return;
|
||||
BlockData data = BlockStateUtils.createBlockData(state.vanillaBlockState().handle());
|
||||
// compare item
|
||||
if (data == null || !data.getMaterial().equals(item.getType())) return;
|
||||
ItemStack itemStack = BukkitCraftEngine.instance().itemManager().buildCustomItemStack(itemId, player);
|
||||
if (ItemUtils.isEmpty(itemStack)) {
|
||||
CraftEngine.instance().logger().warn("Item: " + itemId + " is not a valid item");
|
||||
return;
|
||||
}
|
||||
PlayerInventory inventory = bukkitPlayer.getInventory();
|
||||
int sameItemSlot = -1;
|
||||
int emptySlot = -1;
|
||||
for (int i = 0; i < 9 + 27; i++) {
|
||||
ItemStack invItem = inventory.getItem(i);
|
||||
if (ItemUtils.isEmpty(invItem)) {
|
||||
if (emptySlot == -1 && i < 9) emptySlot = i;
|
||||
continue;
|
||||
}
|
||||
if (invItem.getType().equals(itemStack.getType()) && invItem.getItemMeta().equals(itemStack.getItemMeta())) {
|
||||
if (sameItemSlot == -1) sameItemSlot = i;
|
||||
}
|
||||
}
|
||||
if (sameItemSlot != -1) {
|
||||
if (sameItemSlot < 9) {
|
||||
inventory.setHeldItemSlot(sameItemSlot);
|
||||
ItemStack previousItem = inventory.getItem(slot - 36);
|
||||
BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> inventory.setItem(slot - 36, previousItem));
|
||||
} else {
|
||||
ItemStack sameItem = inventory.getItem(sameItemSlot);
|
||||
int finalSameItemSlot = sameItemSlot;
|
||||
BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> {
|
||||
inventory.setItem(finalSameItemSlot, new ItemStack(Material.AIR));
|
||||
inventory.setItem(slot - 36, sameItem);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (item.getAmount() == 1) {
|
||||
if (ItemUtils.isEmpty(inventory.getItem(slot - 36))) {
|
||||
BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> inventory.setItem(slot - 36, itemStack));
|
||||
return;
|
||||
}
|
||||
if (emptySlot != -1) {
|
||||
inventory.setHeldItemSlot(emptySlot);
|
||||
inventory.setItem(emptySlot, itemStack);
|
||||
} else {
|
||||
BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> inventory.setItem(slot - 36, itemStack));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1.21.4+
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> PICK_ITEM_FROM_BLOCK = (user, event, packet) -> {
|
||||
try {
|
||||
if (!user.isOnline()) return;
|
||||
Player player = (Player) user.platformPlayer();
|
||||
if (player == null) return;
|
||||
Object pos = Reflections.field$ServerboundPickItemFromBlockPacket$pos.get(packet);
|
||||
if (VersionHelper.isFolia()) {
|
||||
int x = (int) Reflections.field$Vec3i$x.get(pos);
|
||||
int z = (int) Reflections.field$Vec3i$z.get(pos);
|
||||
BukkitCraftEngine.instance().scheduler().sync().run(() -> {
|
||||
try {
|
||||
handlePickItemFromBlockPacketOnMainThread(player, pos);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromBlockPacket", e);
|
||||
}
|
||||
}, player.getWorld(), x >> 4, z >> 4);
|
||||
} else {
|
||||
BukkitCraftEngine.instance().scheduler().sync().run(() -> {
|
||||
try {
|
||||
handlePickItemFromBlockPacketOnMainThread(player, pos);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromBlockPacket", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromBlockPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
private static void handlePickItemFromBlockPacketOnMainThread(Player player, Object pos) throws Exception {
|
||||
Object serverLevel = Reflections.field$CraftWorld$ServerLevel.get(player.getWorld());
|
||||
Object blockState = Reflections.method$BlockGetter$getBlockState.invoke(serverLevel, pos);
|
||||
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
|
||||
if (state == null) return;
|
||||
Key itemId = state.settings().itemId();
|
||||
if (itemId == null) return;
|
||||
pickItem(player, itemId);
|
||||
}
|
||||
|
||||
// 1.21.4+
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> PICK_ITEM_FROM_ENTITY = (user, event, packet) -> {
|
||||
try {
|
||||
int entityId = (int) Reflections.field$ServerboundPickItemFromEntityPacket$id.get(packet);
|
||||
LoadedFurniture furniture = BukkitFurnitureManager.instance().getLoadedFurnitureByInteractionEntityId(entityId);
|
||||
if (furniture == null) return;
|
||||
Player player = (Player) user.platformPlayer();
|
||||
if (player == null) return;
|
||||
if (VersionHelper.isFolia()) {
|
||||
Location location = player.getLocation();
|
||||
int x = location.getBlockX();
|
||||
int z = location.getBlockZ();
|
||||
BukkitCraftEngine.instance().scheduler().sync().run(() -> {
|
||||
try {
|
||||
handlePickItemFromEntityOnMainThread(player, furniture);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromEntityPacket", e);
|
||||
}
|
||||
}, player.getWorld(), x >> 4, z >> 4);
|
||||
} else {
|
||||
BukkitCraftEngine.instance().scheduler().sync().run(() -> {
|
||||
try {
|
||||
handlePickItemFromEntityOnMainThread(player, furniture);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromEntityPacket", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromEntityPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
private static void handlePickItemFromEntityOnMainThread(Player player, LoadedFurniture furniture) throws Exception {
|
||||
Key itemId = furniture.furniture().settings().itemId();
|
||||
if (itemId == null) return;
|
||||
pickItem(player, itemId);
|
||||
}
|
||||
|
||||
private static void pickItem(Player player, Key itemId) throws IllegalAccessException, InvocationTargetException {
|
||||
ItemStack itemStack = BukkitCraftEngine.instance().itemManager().buildCustomItemStack(itemId, BukkitCraftEngine.instance().adapt(player));
|
||||
if (itemStack == null) {
|
||||
CraftEngine.instance().logger().warn("Item: " + itemId + " is not a valid item");
|
||||
return;
|
||||
}
|
||||
assert Reflections.method$ServerGamePacketListenerImpl$tryPickItem != null;
|
||||
Reflections.method$ServerGamePacketListenerImpl$tryPickItem.invoke(
|
||||
Reflections.field$ServerPlayer$connection.get(Reflections.method$CraftPlayer$getHandle.invoke(player)), Reflections.method$CraftItemStack$asNMSMirror.invoke(null, itemStack));
|
||||
}
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> ADD_ENTITY = (user, event, packet) -> {
|
||||
try {
|
||||
Object entityType = Reflections.field$ClientboundAddEntityPacket$type.get(packet);
|
||||
// Falling blocks
|
||||
if (entityType == Reflections.instance$EntityType$FALLING_BLOCK) {
|
||||
int data = Reflections.field$ClientboundAddEntityPacket$data.getInt(packet);
|
||||
int remapped = remap(data);
|
||||
if (remapped != data) {
|
||||
Reflections.field$ClientboundAddEntityPacket$data.set(packet, remapped);
|
||||
}
|
||||
} else if (entityType == Reflections.instance$EntityType$ITEM_DISPLAY) {
|
||||
// Furniture
|
||||
int entityId = (int) Reflections.field$ClientboundAddEntityPacket$entityId.get(packet);
|
||||
LoadedFurniture furniture = BukkitFurnitureManager.instance().getLoadedFurnitureByBaseEntityId(entityId);
|
||||
if (furniture != null) {
|
||||
user.sendPacket(furniture.spawnPacket(), false);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundAddEntityPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> SYNC_ENTITY_POSITION = (user, event, packet) -> {
|
||||
try {
|
||||
int entityId = (int) Reflections.field$ClientboundEntityPositionSyncPacket$id.get(packet);
|
||||
if (BukkitFurnitureManager.instance().isFurnitureBaseEntity(entityId)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundEntityPositionSyncPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> MOVE_ENTITY = (user, event, packet) -> {
|
||||
try {
|
||||
int entityId = (int) Reflections.field$ClientboundMoveEntityPacket$entityId.get(packet);
|
||||
if (BukkitFurnitureManager.instance().isFurnitureBaseEntity(entityId)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundMoveEntityPacket$Pos", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> REMOVE_ENTITY = (user, event, packet) -> {
|
||||
try {
|
||||
IntList intList = (IntList) Reflections.field$ClientboundRemoveEntitiesPacket$entityIds.get(packet);
|
||||
for (int i = 0, size = intList.size(); i < size; i++) {
|
||||
int[] entities = BukkitFurnitureManager.instance().getSubEntityIdsByBaseEntityId(intList.getInt(i));
|
||||
if (entities == null) continue;
|
||||
for (int entityId : entities) {
|
||||
intList.add(entityId);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundRemoveEntitiesPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> INTERACT_ENTITY = (user, event, packet) -> {
|
||||
try {
|
||||
Player player = (Player) user.platformPlayer();
|
||||
if (player == null) return;
|
||||
int entityId = (int) Reflections.field$ServerboundInteractPacket$entityId.get(packet);
|
||||
Object action = Reflections.field$ServerboundInteractPacket$action.get(packet);
|
||||
Object actionType = Reflections.method$ServerboundInteractPacket$Action$getType.invoke(action);
|
||||
if (actionType == null) return;
|
||||
LoadedFurniture furniture = BukkitFurnitureManager.instance().getLoadedFurnitureByInteractionEntityId(entityId);
|
||||
if (furniture == null) return;
|
||||
Location location = furniture.baseEntity().getLocation();
|
||||
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
|
||||
if (serverPlayer.isSpectatorMode() || serverPlayer.isAdventureMode()) return;
|
||||
BukkitCraftEngine.instance().scheduler().sync().run(() -> {
|
||||
if (actionType == Reflections.instance$ServerboundInteractPacket$ActionType$ATTACK) {
|
||||
if (furniture.isValid()) {
|
||||
if (!BukkitCraftEngine.instance().antiGrief().canBreak(player, location)) {
|
||||
return;
|
||||
}
|
||||
FurnitureBreakEvent breakEvent = new FurnitureBreakEvent(serverPlayer.platformPlayer(), furniture);
|
||||
if (EventUtils.fireAndCheckCancel(breakEvent)) {
|
||||
return;
|
||||
}
|
||||
CraftEngineFurniture.remove(furniture, serverPlayer, !serverPlayer.isCreativeMode(), true);
|
||||
}
|
||||
} else if (actionType == Reflections.instance$ServerboundInteractPacket$ActionType$INTERACT_AT) {
|
||||
InteractionHand hand;
|
||||
Location interactionPoint;
|
||||
try {
|
||||
Object interactionHand = Reflections.field$ServerboundInteractPacket$InteractionAtLocationAction$hand.get(action);
|
||||
hand = interactionHand == Reflections.instance$InteractionHand$MAIN_HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
|
||||
Object vec3 = Reflections.field$ServerboundInteractPacket$InteractionAtLocationAction$location.get(action);
|
||||
double x = (double) Reflections.field$Vec3$x.get(vec3);
|
||||
double y = (double) Reflections.field$Vec3$y.get(vec3);
|
||||
double z = (double) Reflections.field$Vec3$z.get(vec3);
|
||||
interactionPoint = new Location(location.getWorld(), x, y, z);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException("Failed to get interaction hand from interact packet", e);
|
||||
}
|
||||
FurnitureInteractEvent interactEvent = new FurnitureInteractEvent(serverPlayer.platformPlayer(), furniture, hand, interactionPoint);
|
||||
if (EventUtils.fireAndCheckCancel(interactEvent)) {
|
||||
return;
|
||||
}
|
||||
if (player.isSneaking())
|
||||
return;
|
||||
furniture.getAvailableSeat(entityId).ifPresent(seatPos -> {
|
||||
if (furniture.occupySeat(seatPos)) {
|
||||
furniture.mountSeat(Objects.requireNonNull(player.getPlayer()), seatPos);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, player.getWorld(), location.getBlockX() >> 4,location.getBlockZ() >> 4);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundInteractPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> SOUND = (user, event, packet) -> {
|
||||
try {
|
||||
Object sound = Reflections.field$ClientboundSoundPacket$sound.get(packet);
|
||||
Object soundEvent = Reflections.method$Holder$value.invoke(sound);
|
||||
Key mapped = BukkitBlockManager.instance().replaceSoundIfExist(Key.of(Reflections.field$SoundEvent$location.get(soundEvent).toString()));
|
||||
if (mapped != null) {
|
||||
event.setCancelled(true);
|
||||
Object newId = Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, mapped.namespace(), mapped.value());
|
||||
Object newSoundEvent = VersionHelper.isVersionNewerThan1_21_2() ?
|
||||
Reflections.constructor$SoundEvent.newInstance(newId, Reflections.field$SoundEvent$fixedRange.get(soundEvent)) :
|
||||
Reflections.constructor$SoundEvent.newInstance(newId, Reflections.field$SoundEvent$range.get(soundEvent), Reflections.field$SoundEvent$newSystem.get(soundEvent));
|
||||
Object newSoundPacket = Reflections.constructor$ClientboundSoundPacket.newInstance(
|
||||
Reflections.method$Holder$direct.invoke(null, newSoundEvent),
|
||||
Reflections.field$ClientboundSoundPacket$source.get(packet),
|
||||
(double) Reflections.field$ClientboundSoundPacket$x.getInt(packet) / 8,
|
||||
(double) Reflections.field$ClientboundSoundPacket$y.getInt(packet) / 8,
|
||||
(double) Reflections.field$ClientboundSoundPacket$z.getInt(packet) / 8,
|
||||
Reflections.field$ClientboundSoundPacket$volume.get(packet),
|
||||
Reflections.field$ClientboundSoundPacket$pitch.get(packet),
|
||||
Reflections.field$ClientboundSoundPacket$seed.get(packet)
|
||||
);
|
||||
user.sendPacket(newSoundPacket, true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundSoundPacket", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network;
|
||||
|
||||
public interface PacketIds {
|
||||
|
||||
int clientboundBlockUpdatePacket();
|
||||
|
||||
int clientboundSectionBlocksUpdatePacket();
|
||||
|
||||
int clientboundLevelParticlesPacket();
|
||||
|
||||
int clientboundLevelEventPacket();
|
||||
|
||||
int clientboundAddEntityPacket();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network.impl;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.network.PacketIds;
|
||||
|
||||
public class PacketIds1_20 implements PacketIds {
|
||||
|
||||
@Override
|
||||
public int clientboundBlockUpdatePacket() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundSectionBlocksUpdatePacket() {
|
||||
return 67;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundLevelParticlesPacket() {
|
||||
return 38;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundLevelEventPacket() {
|
||||
return 37;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundAddEntityPacket() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network.impl;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.network.PacketIds;
|
||||
|
||||
public class PacketIds1_20_2 implements PacketIds {
|
||||
|
||||
@Override
|
||||
public int clientboundBlockUpdatePacket() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundSectionBlocksUpdatePacket() {
|
||||
return 69;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundLevelParticlesPacket() {
|
||||
return 39;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundLevelEventPacket() {
|
||||
return 38;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundAddEntityPacket() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network.impl;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.network.PacketIds;
|
||||
|
||||
public class PacketIds1_20_3 implements PacketIds {
|
||||
|
||||
@Override
|
||||
public int clientboundBlockUpdatePacket() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundSectionBlocksUpdatePacket() {
|
||||
return 71;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundLevelParticlesPacket() {
|
||||
return 39;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundLevelEventPacket() {
|
||||
return 38;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundAddEntityPacket() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network.impl;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.network.PacketIds;
|
||||
|
||||
public class PacketIds1_20_5 implements PacketIds {
|
||||
|
||||
@Override
|
||||
public int clientboundBlockUpdatePacket() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundSectionBlocksUpdatePacket() {
|
||||
return 73;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundLevelParticlesPacket() {
|
||||
return 41;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundLevelEventPacket() {
|
||||
return 40;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundAddEntityPacket() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network.impl;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.network.PacketIds;
|
||||
|
||||
public class PacketIds1_21_2 implements PacketIds {
|
||||
|
||||
@Override
|
||||
public int clientboundBlockUpdatePacket() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundSectionBlocksUpdatePacket() {
|
||||
return 78;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundLevelParticlesPacket() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundLevelEventPacket() {
|
||||
return 41;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientboundAddEntityPacket() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.papi;
|
||||
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import net.momirealms.craftengine.core.font.BitmapImage;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.util.FormatUtils;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ImageExpansion extends PlaceholderExpansion {
|
||||
private final CraftEngine plugin;
|
||||
|
||||
public ImageExpansion(CraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier() {
|
||||
return "image";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getAuthor() {
|
||||
return "XiaoMoMi";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getVersion() {
|
||||
return "1.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean persist() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) {
|
||||
String[] split = params.split("_", 2);
|
||||
if (split.length != 2) return null;
|
||||
String[] param = split[1].split(":", 4);
|
||||
if (param.length < 2) return null;
|
||||
Key key;
|
||||
try {
|
||||
key = Key.of(param[0], param[1]);
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.logger().warn("Invalid image namespaced key: " + param[0] + ":" + param[1]);
|
||||
return null;
|
||||
}
|
||||
Optional<BitmapImage> optional = plugin.imageManager().bitmapImageByImageId(key);
|
||||
if (optional.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
BitmapImage image = optional.get();
|
||||
int codepoint;
|
||||
if (param.length == 4) {
|
||||
codepoint = image.codepointAt(Integer.parseInt(param[2]), Integer.parseInt(param[3]));
|
||||
} else if (param.length == 2) {
|
||||
codepoint = image.codepointAt(0,0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
switch (split[0]) {
|
||||
case "mm", "minimessage", "mini" -> {
|
||||
return FormatUtils.miniMessageFont(new String(Character.toChars(codepoint)), image.font().toString());
|
||||
}
|
||||
case "md", "minedown" -> {
|
||||
return FormatUtils.mineDownFont(new String(Character.toChars(codepoint)), image.font().toString());
|
||||
}
|
||||
case "raw" -> {
|
||||
return new String(Character.toChars(codepoint));
|
||||
}
|
||||
default -> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.papi;
|
||||
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ShiftExpansion extends PlaceholderExpansion {
|
||||
private final CraftEngine plugin;
|
||||
|
||||
public ShiftExpansion(CraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier() {
|
||||
return "shift";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getAuthor() {
|
||||
return "XiaoMoMi";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getVersion() {
|
||||
return "1.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean persist() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) {
|
||||
String[] split = params.split("_", 2);
|
||||
switch (split[0]) {
|
||||
case "mini", "minimessage", "mm" -> {
|
||||
if (split.length != 2) return null;
|
||||
try {
|
||||
return plugin.imageManager().createMiniMessageOffsets(Integer.parseInt(split[1]));
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
case "md", "minedown" -> {
|
||||
if (split.length != 2) return null;
|
||||
try {
|
||||
return plugin.imageManager().createMineDownOffsets(Integer.parseInt(split[1]));
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
case "raw" -> {
|
||||
if (split.length != 2) return null;
|
||||
try {
|
||||
return plugin.imageManager().createRawOffsets(Integer.parseInt(split[1]));
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
if (split.length != 1) return null;
|
||||
try {
|
||||
return plugin.imageManager().createMiniMessageOffsets(Integer.parseInt(split[0]));
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.scheduler;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.plugin.scheduler.impl.BukkitExecutor;
|
||||
import net.momirealms.craftengine.bukkit.plugin.scheduler.impl.FoliaExecutor;
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.AbstractJavaScheduler;
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.RegionExecutor;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import org.bukkit.World;
|
||||
|
||||
public class BukkitSchedulerAdapter extends AbstractJavaScheduler<World> {
|
||||
protected RegionExecutor<World> sync;
|
||||
|
||||
public BukkitSchedulerAdapter(BukkitCraftEngine plugin) {
|
||||
super(plugin);
|
||||
if (VersionHelper.isFolia()) {
|
||||
this.sync = new FoliaExecutor(plugin.bootstrap());
|
||||
} else {
|
||||
this.sync = new BukkitExecutor(plugin.bootstrap());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionExecutor<World> sync() {
|
||||
return this.sync;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.scheduler.impl;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.DummyTask;
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.RegionExecutor;
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BukkitExecutor implements RegionExecutor<World> {
|
||||
private final Plugin plugin;
|
||||
|
||||
public BukkitExecutor(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(Runnable runnable, World world, int x, int z) {
|
||||
execute(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runDelayed(Runnable r, World world, int x, int z) {
|
||||
Bukkit.getScheduler().runTask(plugin, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runAsyncRepeating(Runnable runnable, long delay, long period) {
|
||||
return new BukkitTask(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runAsyncLater(Runnable runnable, long delay) {
|
||||
return new BukkitTask(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runLater(Runnable runnable, long delay, World world, int x, int z) {
|
||||
if (delay <= 0) {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
runnable.run();
|
||||
return new DummyTask();
|
||||
} else {
|
||||
return new BukkitTask(Bukkit.getScheduler().runTask(plugin, runnable));
|
||||
}
|
||||
}
|
||||
return new BukkitTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runRepeating(Runnable runnable, long delay, long period, World world, int x, int z) {
|
||||
return new BukkitTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Runnable runnable) {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
Bukkit.getScheduler().runTask(plugin, runnable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.scheduler.impl;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
|
||||
|
||||
public class BukkitTask implements SchedulerTask {
|
||||
private final org.bukkit.scheduler.BukkitTask bukkitTask;
|
||||
|
||||
public BukkitTask(org.bukkit.scheduler.BukkitTask bukkitTask) {
|
||||
this.bukkitTask = bukkitTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
this.bukkitTask.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelled() {
|
||||
return bukkitTask.isCancelled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.scheduler.impl;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.RegionExecutor;
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class FoliaExecutor implements RegionExecutor<World> {
|
||||
private final Plugin plugin;
|
||||
|
||||
public FoliaExecutor(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(Runnable runnable, World world, int x, int z) {
|
||||
Optional.ofNullable(world).ifPresentOrElse(w ->
|
||||
Bukkit.getRegionScheduler().execute(plugin, w, x, z, runnable),
|
||||
() -> Bukkit.getGlobalRegionScheduler().execute(plugin, runnable)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runDelayed(Runnable runnable, World world, int x, int z) {
|
||||
run(runnable, world, x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runAsyncRepeating(Runnable runnable, long delay, long period) {
|
||||
return runRepeating(runnable, delay, period, null, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runAsyncLater(Runnable runnable, long delay) {
|
||||
return runLater(runnable, delay, null, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runLater(Runnable runnable, long delay, World world, int x, int z) {
|
||||
if (world == null) {
|
||||
if (delay <= 0) {
|
||||
return new FoliaTask(Bukkit.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), delay));
|
||||
} else {
|
||||
return new FoliaTask(Bukkit.getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()));
|
||||
}
|
||||
} else {
|
||||
if (delay <= 0) {
|
||||
return new FoliaTask(Bukkit.getRegionScheduler().run(plugin, world, x, z, scheduledTask -> runnable.run()));
|
||||
} else {
|
||||
return new FoliaTask(Bukkit.getRegionScheduler().runDelayed(plugin, world, x, z, scheduledTask -> runnable.run(), delay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runRepeating(Runnable runnable, long delay, long period, World world, int x, int z) {
|
||||
if (world == null) {
|
||||
return new FoliaTask(Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), delay, period));
|
||||
} else {
|
||||
return new FoliaTask(Bukkit.getRegionScheduler().runAtFixedRate(plugin, world, x, z, scheduledTask -> runnable.run(), delay, period));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Runnable runnable) {
|
||||
Bukkit.getGlobalRegionScheduler().execute(plugin, runnable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.scheduler.impl;
|
||||
|
||||
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
||||
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
|
||||
|
||||
public class FoliaTask implements SchedulerTask {
|
||||
private final ScheduledTask task;
|
||||
|
||||
public FoliaTask(ScheduledTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
this.task.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelled() {
|
||||
return task.isCancelled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.user;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.channel.Channel;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
|
||||
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.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.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.Vec3d;
|
||||
import net.momirealms.craftengine.core.world.World;
|
||||
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.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
public class BukkitServerPlayer extends Player {
|
||||
private final Channel channel;
|
||||
private final BukkitCraftEngine plugin;
|
||||
|
||||
private ConnectionState decoderState;
|
||||
private ConnectionState encoderState;
|
||||
|
||||
private Reference<org.bukkit.entity.Player> playerRef;
|
||||
private Reference<Object> serverPlayerRef;
|
||||
|
||||
private int sectionCount;
|
||||
private int lastSuccessfulInteraction;
|
||||
private long lastAttributeSyncTime;
|
||||
private Key clientSideDimension;
|
||||
|
||||
private int lastSentState = -1;
|
||||
private int lastHitBlockTime;
|
||||
private BlockPos destroyPos;
|
||||
private Object destroyedState;
|
||||
private boolean isDestroyingBlock;
|
||||
private boolean isDestroyingCustomBlock;
|
||||
private boolean swingHandAck;
|
||||
private float miningProgress;
|
||||
|
||||
private int resentSoundTick;
|
||||
private int resentSwingTick;
|
||||
|
||||
private Key lastUsedRecipe = null;
|
||||
|
||||
public BukkitServerPlayer(BukkitCraftEngine plugin, Channel channel) {
|
||||
this.channel = channel;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void setPlayer(org.bukkit.entity.Player player) {
|
||||
playerRef = new WeakReference<>(player);
|
||||
try {
|
||||
serverPlayerRef = new WeakReference<>(Reflections.method$CraftPlayer$getHandle.invoke(player));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel nettyChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CraftEngine plugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMiningBlock() {
|
||||
return destroyPos != null;
|
||||
}
|
||||
|
||||
public void setDestroyedState(Object destroyedState) {
|
||||
this.destroyedState = destroyedState;
|
||||
}
|
||||
|
||||
public void setDestroyPos(BlockPos destroyPos) {
|
||||
this.destroyPos = destroyPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSyncAttribute() {
|
||||
long current = System.currentTimeMillis();
|
||||
if (current - this.lastAttributeSyncTime > 10000) {
|
||||
this.lastAttributeSyncTime = current;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking() {
|
||||
return platformPlayer().isSneaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreativeMode() {
|
||||
return platformPlayer().getGameMode() == GameMode.CREATIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpectatorMode() {
|
||||
return platformPlayer().getGameMode() == GameMode.SPECTATOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAdventureMode() {
|
||||
return platformPlayer().getGameMode() == GameMode.ADVENTURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendActionBar(Component text) {
|
||||
try {
|
||||
Object packet = Reflections.constructor$ClientboundSetActionBarTextPacket.newInstance(ComponentUtils.adventureToMinecraft(text));
|
||||
sendPacket(packet, false);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to send action bar", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateLastSuccessfulInteractionTick(int tick) {
|
||||
if (lastSuccessfulInteraction != tick) {
|
||||
lastSuccessfulInteraction = tick;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swingHand(InteractionHand hand) {
|
||||
platformPlayer().swingHand(hand == InteractionHand.MAIN_HAND ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
return platformPlayer().hasPermission(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInstabuild() {
|
||||
try {
|
||||
Object abilities = Reflections.field$Player$abilities.get(serverPlayer());
|
||||
return (boolean) Reflections.field$Abilities$instabuild.get(abilities);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
CraftEngine.instance().logger().warn("Failed to get canInstabuild for " + name(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
org.bukkit.entity.Player player = platformPlayer();
|
||||
if (player == null) return "Unknown";
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(Key sound, float volume, float pitch) {
|
||||
platformPlayer().playSound(platformPlayer(), sound.toString(), SoundCategory.MASTER, volume, pitch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void giveItem(Item<?> item) {
|
||||
PlayerUtils.giveItem(platformPlayer(), (ItemStack) item.getItem(), item.count());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Object packet, boolean immediately) {
|
||||
this.plugin.networkManager().sendPacket(this, packet, immediately);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionState decoderState() {
|
||||
return decoderState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionState encoderState() {
|
||||
return encoderState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientSideSectionCount() {
|
||||
return sectionCount;
|
||||
}
|
||||
|
||||
public void setClientSideSectionCount(int sectionCount) {
|
||||
this.sectionCount = sectionCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key clientSideDimension() {
|
||||
return clientSideDimension;
|
||||
}
|
||||
|
||||
public void setClientSideDimension(Key clientSideDimension) {
|
||||
this.clientSideDimension = clientSideDimension;
|
||||
}
|
||||
|
||||
public void setConnectionState(ConnectionState connectionState) {
|
||||
this.encoderState = connectionState;
|
||||
this.decoderState = connectionState;
|
||||
}
|
||||
|
||||
public void setDecoderState(ConnectionState decoderState) {
|
||||
this.decoderState = decoderState;
|
||||
}
|
||||
|
||||
public void setEncoderState(ConnectionState encoderState) {
|
||||
this.encoderState = encoderState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
// not fully online
|
||||
if (serverPlayer() == null) return;
|
||||
if (this.isDestroyingBlock) {
|
||||
this.tickBlockDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDestroyProgress(Object blockState, BlockPos pos) {
|
||||
try {
|
||||
Object serverPlayer = serverPlayer();
|
||||
Object blockPos = Reflections.constructor$BlockPos.newInstance(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;
|
||||
}
|
||||
}
|
||||
|
||||
public void startMiningBlock(org.bukkit.World world, BlockPos pos, Object state, boolean custom, @Nullable ImmutableBlockState immutableBlockState) {
|
||||
// instant break
|
||||
if (custom && getDestroyProgress(state, pos) >= 1f) {
|
||||
assert immutableBlockState != null;
|
||||
// not an instant break on client side
|
||||
if (getDestroyProgress(immutableBlockState.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);
|
||||
}
|
||||
}
|
||||
//ParticleUtils.addBlockBreakParticles(world, LocationUtils.toBlockPos(pos), state);
|
||||
return;
|
||||
}
|
||||
setCanBreakBlock(!custom);
|
||||
setDestroyPos(pos);
|
||||
setDestroyedState(state);
|
||||
setIsDestroyingBlock(true, custom);
|
||||
}
|
||||
|
||||
private void setCanBreakBlock(boolean canBreak) {
|
||||
try {
|
||||
if (canBreak) {
|
||||
if (VersionHelper.isVersionNewerThan1_20_5()) {
|
||||
Object serverPlayer = serverPlayer();
|
||||
Object attributeInstance = Reflections.method$ServerPlayer$getAttribute.invoke(serverPlayer, Reflections.instance$Holder$Attribute$block_break_speed);
|
||||
Object newPacket = Reflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityID(), Lists.newArrayList(attributeInstance));
|
||||
sendPacket(newPacket, true);
|
||||
} else {
|
||||
resetEffect(Reflections.instance$MobEffecr$mining_fatigue);
|
||||
resetEffect(Reflections.instance$MobEffecr$haste);
|
||||
}
|
||||
} else {
|
||||
if (VersionHelper.isVersionNewerThan1_20_5()) {
|
||||
Object attributeModifier = VersionHelper.isVersionNewerThan1_21() ?
|
||||
Reflections.constructor$AttributeModifier.newInstance(Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, "craftengine", "custom_hardness"), -9999d, Reflections.instance$AttributeModifier$Operation$ADD_VALUE) :
|
||||
Reflections.constructor$AttributeModifier.newInstance(UUID.randomUUID(), "craftengine:custom_hardness", -9999d, Reflections.instance$AttributeModifier$Operation$ADD_VALUE);
|
||||
Object attributeSnapshot = Reflections.constructor$ClientboundUpdateAttributesPacket$AttributeSnapshot.newInstance(Reflections.instance$Holder$Attribute$block_break_speed, 1d, Lists.newArrayList(attributeModifier));
|
||||
Object newPacket = Reflections.constructor$ClientboundUpdateAttributesPacket1.newInstance(entityID(), Lists.newArrayList(attributeSnapshot));
|
||||
sendPacket(newPacket, true);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
plugin.logger().warn("Failed to set attribute for player " + platformPlayer().getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopMiningBlock() {
|
||||
setCanBreakBlock(true);
|
||||
setIsDestroyingBlock(false, false);
|
||||
}
|
||||
|
||||
private void resetEffect(Object mobEffect) throws ReflectiveOperationException {
|
||||
Object effectInstance = Reflections.method$ServerPlayer$getEffect.invoke(serverPlayer(), mobEffect);
|
||||
Object packet;
|
||||
if (effectInstance != null) {
|
||||
packet = Reflections.constructor$ClientboundUpdateMobEffectPacket.newInstance(entityID(), effectInstance);
|
||||
} else {
|
||||
packet = Reflections.constructor$ClientboundRemoveMobEffectPacket.newInstance(entityID(), mobEffect);
|
||||
}
|
||||
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 (!this.swingHandAck) return;
|
||||
this.swingHandAck = false;
|
||||
try {
|
||||
org.bukkit.entity.Player player = platformPlayer();
|
||||
double range = getInteractionRange();
|
||||
RayTraceResult result = player.rayTraceBlocks(range, FluidCollisionMode.NEVER);
|
||||
if (result == null) return;
|
||||
Block hitBlock = result.getHitBlock();
|
||||
if (hitBlock == null) return;
|
||||
Location location = hitBlock.getLocation();
|
||||
BlockPos hitPos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
||||
if (!hitPos.equals(this.destroyPos)) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
if (currentTick - this.lastHitBlockTime > 3) {
|
||||
Object blockOwner = Reflections.field$StateHolder$owner.get(this.destroyedState);
|
||||
Object soundType = Reflections.field$BlockBehaviour$soundType.get(blockOwner);
|
||||
Object soundEvent = Reflections.field$SoundType$hitSound.get(soundType);
|
||||
Object soundId = Reflections.field$SoundEvent$location.get(soundEvent);
|
||||
level().playBlockSound(new Vec3d(this.destroyPos.x(), this.destroyPos.y(), this.destroyPos.z()), Key.of(soundId.toString()), 0.5F, 0.5F);
|
||||
this.lastHitBlockTime = currentTick;
|
||||
}
|
||||
|
||||
// accumulate progress (custom blocks only)
|
||||
if (this.isDestroyingCustomBlock) {
|
||||
Item<ItemStack> item = this.getItemInHand(InteractionHand.MAIN_HAND);
|
||||
if (item != null) {
|
||||
Material itemMaterial = item.getItem().getType();
|
||||
if (canInstabuild() && (itemMaterial == Material.DEBUG_STICK
|
||||
|| itemMaterial == Material.TRIDENT
|
||||
|| (VersionHelper.isVersionNewerThan1_20_5() && itemMaterial == MaterialUtils.MACE)
|
||||
|| item.is(ItemTags.SWORDS))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.miningProgress = (float) Reflections.method$BlockStateBase$getDestroyProgress.invoke(this.destroyedState, serverPlayer, Reflections.method$Entity$level.invoke(serverPlayer), blockPos) + miningProgress;
|
||||
int packetStage = (int) (this.miningProgress * 10.0F);
|
||||
if (packetStage != this.lastSentState) {
|
||||
this.lastSentState = packetStage;
|
||||
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, BlockStateUtils.blockStateToId(this.destroyedState), false);
|
||||
sendPacket(levelEventPacket, false);
|
||||
this.stopMiningBlock();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.logger().warn("Failed to tick destroy for player " + platformPlayer().getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
for (org.bukkit.entity.Player other : player.getWorld().getPlayers()) {
|
||||
Location otherLocation = other.getLocation();
|
||||
double d0 = (double) hitPos.x() - otherLocation.getX();
|
||||
double d1 = (double) hitPos.y() - otherLocation.getY();
|
||||
double d2 = (double) hitPos.z() - otherLocation.getZ();
|
||||
if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) {
|
||||
plugin.networkManager().sendPacket(other, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwingHand() {
|
||||
this.swingHandAck = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int entityID() {
|
||||
return platformPlayer().getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
org.bukkit.entity.Player player = platformPlayer();
|
||||
if (player == null) return false;
|
||||
return player.isOnline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYRot() {
|
||||
return platformPlayer().getLocation().getPitch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXRot() {
|
||||
return platformPlayer().getLocation().getYaw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecondaryUseActive() {
|
||||
return isSneaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getDirection() {
|
||||
return DirectionUtils.toDirection(platformPlayer().getFacing());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Item<ItemStack> getItemInHand(InteractionHand hand) {
|
||||
PlayerInventory inventory = platformPlayer().getInventory();
|
||||
return BukkitItemManager.instance().wrap(hand == InteractionHand.MAIN_HAND ? inventory.getItemInMainHand() : inventory.getItemInOffHand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public World level() {
|
||||
return new BukkitWorld(platformPlayer().getWorld());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double x() {
|
||||
return platformPlayer().getLocation().getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double y() {
|
||||
return platformPlayer().getLocation().getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double z() {
|
||||
return platformPlayer().getLocation().getZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serverPlayer() {
|
||||
if (serverPlayerRef == null) return null;
|
||||
return serverPlayerRef.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.bukkit.entity.Player platformPlayer() {
|
||||
if (playerRef == null) return null;
|
||||
return playerRef.get();
|
||||
}
|
||||
|
||||
public void setResendSound() {
|
||||
resentSoundTick = gameTicks();
|
||||
}
|
||||
|
||||
public void setResendSwing() {
|
||||
resentSwingTick = gameTicks();
|
||||
}
|
||||
|
||||
public boolean shouldResendSound() {
|
||||
return resentSoundTick == gameTicks();
|
||||
}
|
||||
|
||||
public boolean shouldResendSwing() {
|
||||
return resentSwingTick == gameTicks();
|
||||
}
|
||||
|
||||
public Key lastUsedRecipe() {
|
||||
return lastUsedRecipe;
|
||||
}
|
||||
|
||||
public void setLastUsedRecipe(Key lastUsedRecipe) {
|
||||
this.lastUsedRecipe = lastUsedRecipe;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.momirealms.craftengine.bukkit.sound;
|
||||
|
||||
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.sound.song.AbstractJukeboxSongManager;
|
||||
import net.momirealms.craftengine.core.sound.song.JukeboxSong;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class BukkitJukeboxSongManager extends AbstractJukeboxSongManager {
|
||||
|
||||
public BukkitJukeboxSongManager(CraftEngine plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerSongs(Map<Key, JukeboxSong> songs) {
|
||||
if (songs.isEmpty()) return;
|
||||
try {
|
||||
unfreezeRegistry();
|
||||
for (Map.Entry<Key, JukeboxSong> entry : songs.entrySet()) {
|
||||
Key id = entry.getKey();
|
||||
JukeboxSong jukeboxSong = entry.getValue();
|
||||
Object resourceLocation = Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, id.namespace(), id.value());
|
||||
Object soundId = Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, jukeboxSong.sound().namespace(), jukeboxSong.sound().value());
|
||||
Object song = Reflections.method$Registry$get.invoke(Reflections.instance$InternalRegistries$JUKEBOX_SONG, resourceLocation);
|
||||
|
||||
Object soundEvent = VersionHelper.isVersionNewerThan1_21_2() ?
|
||||
Reflections.constructor$SoundEvent.newInstance(soundId, Optional.of(jukeboxSong.range())) :
|
||||
Reflections.constructor$SoundEvent.newInstance(soundId, jukeboxSong.range(), false);
|
||||
Object soundHolder = Reflections.method$Holder$direct.invoke(null, soundEvent);
|
||||
|
||||
if (song == null) {
|
||||
song = Reflections.constructor$JukeboxSong.newInstance(soundHolder, ComponentUtils.adventureToMinecraft(jukeboxSong.description()), jukeboxSong.lengthInSeconds(), jukeboxSong.comparatorOutput());
|
||||
Object holder = Reflections.method$Registry$registerForHolder.invoke(null, Reflections.instance$InternalRegistries$JUKEBOX_SONG, resourceLocation, song);
|
||||
Reflections.method$Holder$Reference$bindValue.invoke(holder, song);
|
||||
Reflections.field$Holder$Reference$tags.set(holder, Set.of());
|
||||
}
|
||||
}
|
||||
freezeRegistry();
|
||||
} catch (Exception e) {
|
||||
plugin.logger().warn("Failed to register jukebox songs.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void unfreezeRegistry() throws IllegalAccessException {
|
||||
Reflections.field$MappedRegistry$frozen.set(Reflections.instance$InternalRegistries$JUKEBOX_SONG, false);
|
||||
}
|
||||
|
||||
private void freezeRegistry() throws IllegalAccessException {
|
||||
Reflections.field$MappedRegistry$frozen.set(Reflections.instance$InternalRegistries$JUKEBOX_SONG, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package net.momirealms.craftengine.bukkit.sound;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.sound.AbstractSoundManager;
|
||||
import net.momirealms.craftengine.core.sound.song.JukeboxSongManager;
|
||||
|
||||
public class BukkitSoundManager extends AbstractSoundManager {
|
||||
|
||||
public BukkitSoundManager(CraftEngine plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JukeboxSongManager createJukeboxSongManager() {
|
||||
return new BukkitJukeboxSongManager(super.plugin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package net.momirealms.craftengine.bukkit.util;
|
||||
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.PushReaction;
|
||||
import net.momirealms.craftengine.core.util.Instrument;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.MapColor;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.event.block.BlockPhysicsEvent;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.IdentityHashMap;
|
||||
|
||||
public class BlockStateUtils {
|
||||
public static final IdentityHashMap<Object, Object> CLIENT_SIDE_NOTE_BLOCKS = new IdentityHashMap<>();
|
||||
private static int vanillaStateSize;
|
||||
private static boolean hasInit;
|
||||
|
||||
public static void init(int size) {
|
||||
if (hasInit) {
|
||||
throw new IllegalStateException("BlockStateUtils has already been initialized");
|
||||
}
|
||||
vanillaStateSize = size;
|
||||
hasInit = true;
|
||||
}
|
||||
|
||||
public static Object createBlockUpdatePacket(BlockPos pos, ImmutableBlockState state) {
|
||||
try {
|
||||
return Reflections.constructor$ClientboundBlockUpdatePacket.newInstance(LocationUtils.toBlockPos(pos), state.customBlockState().handle());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static BlockData createBlockData(Object blockState) {
|
||||
try {
|
||||
return (BlockData) Reflections.method$CraftBlockData$createData.invoke(null, blockState);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static BlockData fromBlockData(Object blockState) {
|
||||
try {
|
||||
return (BlockData) Reflections.method$CraftBlockData$fromData.invoke(null, blockState);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static int blockDataToId(BlockData blockData) {
|
||||
try {
|
||||
Object blockState = Reflections.field$CraftBlockData$data.get(blockData);
|
||||
return (int) Reflections.method$IdMapper$getId.invoke(Reflections.instance$BLOCK_STATE_REGISTRY, blockState);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Key getBlockOwnerId(Block block) {
|
||||
BlockData data = block.getBlockData();
|
||||
Object blockState = blockDataToBlockState(data);
|
||||
return getBlockOwnerIdFromState(blockState);
|
||||
}
|
||||
|
||||
public static Key getBlockOwnerIdFromState(Object blockState) {
|
||||
String id = blockState.toString();
|
||||
int first = id.indexOf('{');
|
||||
int last = id.indexOf('}');
|
||||
if (first != -1 && last != -1 && last > first) {
|
||||
String blockId = id.substring(first + 1, last);
|
||||
return Key.of(blockId);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid block ID format: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object blockDataToBlockState(BlockData blockData) {
|
||||
try {
|
||||
return Reflections.field$CraftBlockData$data.get(blockData);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object idToBlockState(int id) {
|
||||
try {
|
||||
return Reflections.method$IdMapper$byId.invoke(Reflections.instance$BLOCK_STATE_REGISTRY, id);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static int blockStateToId(Object blockState) {
|
||||
try {
|
||||
return (int) Reflections.method$IdMapper$getId.invoke(Reflections.instance$BLOCK_STATE_REGISTRY, blockState);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object getBlockOwner(Object blockState) {
|
||||
try {
|
||||
return Reflections.field$StateHolder$owner.get(blockState);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static int physicsEventToId(BlockPhysicsEvent event) throws ReflectiveOperationException {
|
||||
Object blockData = Reflections.field$BlockPhysicsEvent$changed.get(event);
|
||||
Object blockState = Reflections.field$CraftBlockData$data.get(blockData);
|
||||
return (int) Reflections.method$IdMapper$getId.invoke(Reflections.instance$BLOCK_STATE_REGISTRY, blockState);
|
||||
}
|
||||
|
||||
public static Object physicsEventToState(BlockPhysicsEvent event) throws ReflectiveOperationException {
|
||||
Object blockData = Reflections.field$BlockPhysicsEvent$changed.get(event);
|
||||
return Reflections.field$CraftBlockData$data.get(blockData);
|
||||
}
|
||||
|
||||
public static void setLightEmission(Object state, int emission) throws ReflectiveOperationException {
|
||||
Reflections.field$BlockStateBase$lightEmission.set(state, emission);
|
||||
}
|
||||
|
||||
public static int getLightEmission(Object state) throws ReflectiveOperationException {
|
||||
return (int) Reflections.field$BlockStateBase$lightEmission.get(state);
|
||||
}
|
||||
|
||||
public static void setMapColor(Object state, MapColor color) throws ReflectiveOperationException {
|
||||
Object mcMapColor = Reflections.method$MapColor$byId.invoke(null, color.id);
|
||||
Reflections.field$BlockStateBase$mapColor.set(state, mcMapColor);
|
||||
}
|
||||
|
||||
public static void setInstrument(Object state, Instrument instrument) throws ReflectiveOperationException {
|
||||
Object mcInstrument = ((Object[]) Reflections.method$NoteBlockInstrument$values.invoke(null))[instrument.ordinal()];
|
||||
Reflections.field$BlockStateBase$instrument.set(state, mcInstrument);
|
||||
}
|
||||
|
||||
public static void setHardness(Object state, float hardness) throws ReflectiveOperationException {
|
||||
Reflections.field$BlockStateBase$hardness.set(state, hardness);
|
||||
}
|
||||
|
||||
public static void setBurnable(Object state, boolean burnable) throws ReflectiveOperationException {
|
||||
Reflections.field$BlockStateBase$burnable.set(state, burnable);
|
||||
}
|
||||
|
||||
public static void setPushReaction(Object state, PushReaction reaction) throws ReflectiveOperationException {
|
||||
Object pushReaction = ((Object[]) Reflections.method$PushReaction$values.invoke(null))[reaction.ordinal()];
|
||||
Reflections.field$BlockStateBase$pushReaction.set(state, pushReaction);
|
||||
}
|
||||
|
||||
public static void setIsRandomlyTicking(Object state, boolean randomlyTicking) throws ReflectiveOperationException {
|
||||
Reflections.field$BlockStateBase$isRandomlyTicking.set(state, randomlyTicking);
|
||||
}
|
||||
|
||||
public static void setReplaceable(Object state, boolean replaceable) throws ReflectiveOperationException {
|
||||
Reflections.field$BlockStateBase$replaceable.set(state, replaceable);
|
||||
}
|
||||
|
||||
public static boolean isReplaceable(Object state) throws ReflectiveOperationException {
|
||||
return (boolean) Reflections.field$BlockStateBase$replaceable.get(state);
|
||||
}
|
||||
|
||||
public static void setCanOcclude(Object state, boolean canOcclude) throws ReflectiveOperationException {
|
||||
Reflections.field$BlockStateBase$canOcclude.set(state, canOcclude);
|
||||
}
|
||||
|
||||
public static boolean isOcclude(Object state) throws ReflectiveOperationException {
|
||||
return (boolean) Reflections.field$BlockStateBase$canOcclude.get(state);
|
||||
}
|
||||
|
||||
public static void setIsRedstoneConductor(Object state, Object predicate) throws ReflectiveOperationException {
|
||||
Reflections.field$BlockStateBase$isRedstoneConductor.set(state, predicate);
|
||||
}
|
||||
|
||||
public static void setIsSuffocating(Object state, Object predicate) throws ReflectiveOperationException {
|
||||
Reflections.field$BlockStateBase$isSuffocating.set(state, predicate);
|
||||
}
|
||||
|
||||
public static void setIsViewBlocking(Object state, Object predicate) throws ReflectiveOperationException {
|
||||
Reflections.field$BlockStateBase$isViewBlocking.set(state, predicate);
|
||||
}
|
||||
|
||||
public static boolean isClientSideNoteBlock(Object state) {
|
||||
return CLIENT_SIDE_NOTE_BLOCKS.containsKey(state);
|
||||
}
|
||||
|
||||
public static boolean isVanillaBlock(Object state) {
|
||||
int id = blockStateToId(state);
|
||||
return id >= 0 && id < vanillaStateSize;
|
||||
}
|
||||
|
||||
public static boolean isVanillaBlock(int id) {
|
||||
return id >= 0 && id < vanillaStateSize;
|
||||
}
|
||||
|
||||
public static int vanillaStateSize() {
|
||||
return vanillaStateSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.momirealms.craftengine.bukkit.util;
|
||||
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BlockTags {
|
||||
private static final Map<Key, Object> CACHE = new HashMap<>();
|
||||
|
||||
private BlockTags() {}
|
||||
|
||||
public static Object getOrCreate(Key key) {
|
||||
Object value = CACHE.get(key);
|
||||
if (value == null) {
|
||||
try {
|
||||
value = Reflections.method$TagKey$create.invoke(null, Reflections.instance$Registries$BLOCK, Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, key.namespace(), key.value()));
|
||||
CACHE.put(key, value);
|
||||
return value;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create block tag: " + key, e);
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package net.momirealms.craftengine.bukkit.util;
|
||||
|
||||
import net.momirealms.craftengine.core.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class BukkitReflectionUtils {
|
||||
private static final String PREFIX_MC = "net.minecraft.";
|
||||
private static final String PREFIX_CRAFTBUKKIT = "org.bukkit.craftbukkit";
|
||||
private static final String CRAFT_SERVER = "CraftServer";
|
||||
private static final String CB_PKG_VERSION;
|
||||
public static final int MAJOR_REVISION;
|
||||
|
||||
private BukkitReflectionUtils() {}
|
||||
|
||||
static {
|
||||
final Class<?> serverClass;
|
||||
if (Bukkit.getServer() == null) {
|
||||
// Paper plugin Bootstrapper 1.20.6+
|
||||
serverClass = Objects.requireNonNull(ReflectionUtils.getClazz("org.bukkit.craftbukkit.CraftServer"));
|
||||
} else {
|
||||
serverClass = Bukkit.getServer().getClass();
|
||||
}
|
||||
final String pkg = serverClass.getPackage().getName();
|
||||
final String nmsVersion = pkg.substring(pkg.lastIndexOf(".") + 1);
|
||||
if (!nmsVersion.contains("_")) {
|
||||
int fallbackVersion = -1;
|
||||
if (Bukkit.getServer() != null) {
|
||||
try {
|
||||
final Method getMinecraftVersion = serverClass.getDeclaredMethod("getMinecraftVersion");
|
||||
fallbackVersion = Integer.parseInt(getMinecraftVersion.invoke(Bukkit.getServer()).toString().split("\\.")[1]);
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
} else {
|
||||
// Paper plugin bootstrapper 1.20.6+
|
||||
try {
|
||||
final Class<?> sharedConstants = Objects.requireNonNull(ReflectionUtils.getClazz("net.minecraft.SharedConstants"));
|
||||
final Method getCurrentVersion = sharedConstants.getDeclaredMethod("getCurrentVersion");
|
||||
final Object currentVersion = getCurrentVersion.invoke(null);
|
||||
final Method getName = currentVersion.getClass().getDeclaredMethod("getName");
|
||||
final String versionName = (String) getName.invoke(currentVersion);
|
||||
try {
|
||||
fallbackVersion = Integer.parseInt(versionName.split("\\.")[1]);
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
} catch (final ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
MAJOR_REVISION = fallbackVersion;
|
||||
} else {
|
||||
MAJOR_REVISION = Integer.parseInt(nmsVersion.split("_")[1]);
|
||||
}
|
||||
String name = serverClass.getName();
|
||||
name = name.substring(PREFIX_CRAFTBUKKIT.length());
|
||||
name = name.substring(0, name.length() - CRAFT_SERVER.length());
|
||||
CB_PKG_VERSION = name;
|
||||
}
|
||||
|
||||
public static String assembleCBClass(String className) {
|
||||
return PREFIX_CRAFTBUKKIT + CB_PKG_VERSION + className;
|
||||
}
|
||||
|
||||
public static String assembleMCClass(String className) {
|
||||
return PREFIX_MC + className;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.momirealms.craftengine.bukkit.util;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.core.util.AdventureHelper;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
|
||||
public class ComponentUtils {
|
||||
|
||||
private ComponentUtils() {}
|
||||
|
||||
public static Object adventureToMinecraft(Component component) {
|
||||
String json = AdventureHelper.componentToJson(component);
|
||||
if (VersionHelper.isVersionNewerThan1_20_5()) {
|
||||
try {
|
||||
return Reflections.method$Component$Serializer$fromJson.invoke(null, json, Reflections.instance$MinecraftRegistry);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return Reflections.method$CraftChatMessage$fromJSON.invoke(null, json);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.momirealms.craftengine.bukkit.util;
|
||||
|
||||
import net.momirealms.craftengine.core.util.Direction;
|
||||
import org.bukkit.block.BlockFace;
|
||||
|
||||
public class DirectionUtils {
|
||||
|
||||
private DirectionUtils() {}
|
||||
|
||||
public static Direction toDirection(BlockFace face) {
|
||||
return switch (face) {
|
||||
case UP -> Direction.UP;
|
||||
case DOWN -> Direction.DOWN;
|
||||
case NORTH -> Direction.NORTH;
|
||||
case SOUTH -> Direction.SOUTH;
|
||||
case WEST -> Direction.WEST;
|
||||
case EAST -> Direction.EAST;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + face);
|
||||
};
|
||||
}
|
||||
|
||||
public static BlockFace toBlockFace(Direction direction) {
|
||||
return switch (direction) {
|
||||
case UP -> BlockFace.UP;
|
||||
case DOWN -> BlockFace.DOWN;
|
||||
case NORTH -> BlockFace.NORTH;
|
||||
case SOUTH -> BlockFace.SOUTH;
|
||||
case WEST -> BlockFace.WEST;
|
||||
case EAST -> BlockFace.EAST;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.momirealms.craftengine.bukkit.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class EnchantmentUtils {
|
||||
|
||||
private EnchantmentUtils() {}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, Integer> toMap(Object itemEnchantments) throws ReflectiveOperationException {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
Map<Object, Integer> enchantments = (Map<Object, Integer>) Reflections.field$ItemEnchantments$enchantments.get(itemEnchantments);
|
||||
|
||||
for (Map.Entry<Object, Integer> entry : enchantments.entrySet()) {
|
||||
Object holder = entry.getKey();
|
||||
String name = (String) Reflections.method$Holder$getRegisteredName.invoke(holder);
|
||||
int level = entry.getValue();
|
||||
map.put(name, level);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package net.momirealms.craftengine.bukkit.util;
|
||||
|
||||
public class EntityDataUtils {
|
||||
|
||||
private EntityDataUtils() {}
|
||||
|
||||
private static final int HAS_SHADOW = 0x01; // 1
|
||||
private static final int IS_SEE_THROUGH = 0x02; // 2
|
||||
private static final int USE_DEFAULT_BACKGROUND = 0x04; // 4
|
||||
private static final int LEFT_ALIGNMENT = 0x08; // 8
|
||||
private static final int RIGHT_ALIGNMENT = 0x10; // 16
|
||||
|
||||
public static byte encodeTextDisplayMask(boolean hasShadow, boolean isSeeThrough, boolean useDefaultBackground, int alignment) {
|
||||
int bitMask = 0;
|
||||
if (hasShadow) {
|
||||
bitMask |= HAS_SHADOW;
|
||||
}
|
||||
if (isSeeThrough) {
|
||||
bitMask |= IS_SEE_THROUGH;
|
||||
}
|
||||
if (useDefaultBackground) {
|
||||
bitMask |= USE_DEFAULT_BACKGROUND;
|
||||
}
|
||||
switch (alignment) {
|
||||
case 0: // CENTER
|
||||
break;
|
||||
case 1: // LEFT
|
||||
bitMask |= LEFT_ALIGNMENT;
|
||||
break;
|
||||
case 2: // RIGHT
|
||||
bitMask |= RIGHT_ALIGNMENT;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid alignment value");
|
||||
}
|
||||
return (byte) bitMask;
|
||||
}
|
||||
|
||||
private static final int IS_ON_FIRE = 0x01; // 1
|
||||
private static final int IS_CROUCHING = 0x02; // 2
|
||||
private static final int UNUSED = 0x04; // 4
|
||||
private static final int IS_SPRINTING = 0x08; // 8
|
||||
private static final int IS_SWIMMING = 0x10; // 16
|
||||
private static final int IS_INVISIBLE = 0x20; // 32
|
||||
private static final int HAS_GLOWING_EFFECT = 0x40; // 64
|
||||
private static final int IS_FLYING_WITH_ELYTRA = 0x80; // 128
|
||||
|
||||
public static byte encodeCommonMask(boolean isOnFire, boolean isCrouching, boolean isUnused,
|
||||
boolean isSprinting, boolean isSwimming, boolean isInvisible,
|
||||
boolean hasGlowingEffect, boolean isFlyingWithElytra) {
|
||||
int bitMask = 0;
|
||||
|
||||
if (isOnFire) {
|
||||
bitMask |= IS_ON_FIRE;
|
||||
}
|
||||
if (isCrouching) {
|
||||
bitMask |= IS_CROUCHING;
|
||||
}
|
||||
if (isUnused) {
|
||||
bitMask |= UNUSED;
|
||||
}
|
||||
if (isSprinting) {
|
||||
bitMask |= IS_SPRINTING;
|
||||
}
|
||||
if (isSwimming) {
|
||||
bitMask |= IS_SWIMMING;
|
||||
}
|
||||
if (isInvisible) {
|
||||
bitMask |= IS_INVISIBLE;
|
||||
}
|
||||
if (hasGlowingEffect) {
|
||||
bitMask |= HAS_GLOWING_EFFECT;
|
||||
}
|
||||
if (isFlyingWithElytra) {
|
||||
bitMask |= IS_FLYING_WITH_ELYTRA;
|
||||
}
|
||||
|
||||
return (byte) bitMask;
|
||||
}
|
||||
|
||||
public static boolean isCrouching(byte mask) {
|
||||
return (mask & IS_CROUCHING) != 0;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user