9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-19 15:09:15 +00:00

Merge branch 'dev' into refactor/snbt

This commit is contained in:
XiaoMoMi
2025-12-05 01:03:52 +08:00
committed by GitHub
163 changed files with 3947 additions and 2452 deletions

View File

@@ -41,8 +41,8 @@ dependencies {
compileOnly("io.github.toxicity188:bettermodel:1.14.0")
compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}")
// MMOItems
compileOnly("net.Indyuce:MMOItems-API:6.10-SNAPSHOT")
compileOnly("io.lumine:MythicLib-dist:1.6.2-SNAPSHOT")
compileOnly("net.Indyuce:MMOItems-API:6.10.1-SNAPSHOT")
compileOnly("io.lumine:MythicLib-dist:1.7.1-SNAPSHOT")
// Nexo
compileOnly("com.nexomc:nexo:1.13.0")
// LuckPerms
@@ -62,7 +62,7 @@ dependencies {
// McMMO
compileOnly("com.gmail.nossr50.mcMMO:mcMMO:2.2.038")
// MMOCore
compileOnly("net.Indyuce:MMOCore-API:1.12.1-SNAPSHOT")
compileOnly("net.Indyuce:MMOCore-API:1.13.1-SNAPSHOT")
// JobsReborn
compileOnly("com.github.Zrips:Jobs:v5.2.2.3")
// CustomFishing

View File

@@ -37,6 +37,7 @@ import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.condition.AlwaysFalseCondition;
import net.momirealms.craftengine.core.plugin.context.event.EventConditions;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.plugin.text.minimessage.FormattedLine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
@@ -181,7 +182,7 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
}
private void logHook(String plugin) {
this.plugin.logger().info("[Compatibility] " + plugin + " hooked");
this.plugin.logger().info(TranslationManager.instance().translateLog("info.compatibility", plugin));
}
@Override
@@ -252,8 +253,8 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
if (VersionHelper.isOrAbove1_20_3()) {
this.plugin.logger().severe("");
if (Locale.getDefault() == Locale.SIMPLIFIED_CHINESE) {
this.plugin.logger().severe("[Compatibility] 插件需要更新 FastAsyncWorldEdit 到 2.13.0 或更高版本,以获得更好的兼容性。(当前版本: " + version + ")");
this.plugin.logger().severe("[Compatibility] 请前往 https://ci.athion.net/job/FastAsyncWorldEdit/ 下载最新版本");
this.plugin.logger().severe("[兼容性] 插件需要更新 FastAsyncWorldEdit 到 2.13.0 或更高版本,以获得更好的兼容性。(当前版本: " + version + ")");
this.plugin.logger().severe("[兼容性] 请前往 https://ci.athion.net/job/FastAsyncWorldEdit/ 下载最新版本");
} else {
this.plugin.logger().severe("[Compatibility] Update FastAsyncWorldEdit to v2.13.0+ for better compatibility (Current: " + version + ")");
this.plugin.logger().severe("[Compatibility] Download latest version: https://ci.athion.net/job/FastAsyncWorldEdit/");

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.compatibility.model.bettermodel;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
@@ -59,13 +58,12 @@ public class BetterModelBlockEntityElementConfig implements BlockEntityElementCo
return BetterModelBlockEntityElement.class;
}
public static class Factory implements BlockEntityElementConfigFactory {
public static class Factory implements BlockEntityElementConfigFactory<BetterModelBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public BetterModelBlockEntityElementConfig create(Map<String, Object> arguments) {
String model = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("model"), "warning.config.block.state.entity_renderer.better_model.missing_model");
return (BlockEntityElementConfig<E>) new BetterModelBlockEntityElementConfig(
return new BetterModelBlockEntityElementConfig(
model,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.compatibility.model.modelengine;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
@@ -49,13 +48,12 @@ public class ModelEngineBlockEntityElementConfig implements BlockEntityElementCo
return ModelEngineBlockEntityElement.class;
}
public static class Factory implements BlockEntityElementConfigFactory {
public static class Factory implements BlockEntityElementConfigFactory<ModelEngineBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public ModelEngineBlockEntityElementConfig create(Map<String, Object> arguments) {
String model = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("model"), "warning.config.block.state.entity_renderer.model_engine.missing_model");
return (BlockEntityElementConfig<E>) new ModelEngineBlockEntityElementConfig(
return new ModelEngineBlockEntityElementConfig(
model,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),

View File

@@ -25,7 +25,7 @@ public class CondIsFurniture extends Condition {
@Override
public boolean check(Event event) {
return entities.check(event, entity -> {
BukkitFurniture baseEntity = CraftEngineFurniture.getLoadedFurnitureByBaseEntity(entity);
BukkitFurniture baseEntity = CraftEngineFurniture.getLoadedFurnitureByMetaEntity(entity);
return baseEntity != null;
}, isNegated());
}

View File

@@ -22,7 +22,7 @@ public class EffRemoveFurniture extends Effect {
@Override
protected void execute(Event e) {
for (Entity entity : entities.getArray(e)) {
Furniture bukkitFurniture = CraftEngineFurniture.getLoadedFurnitureByBaseEntity(entity);
Furniture bukkitFurniture = CraftEngineFurniture.getLoadedFurnitureByMetaEntity(entity);
if (bukkitFurniture != null) {
bukkitFurniture.destroy();
}

View File

@@ -49,7 +49,7 @@ public class EvtCustomClick extends SkriptEvent {
EventValues.registerEventValue(FurnitureInteractEvent.class, Location.class, FurnitureInteractEvent::location, EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureInteractEvent.class, Player.class, FurnitureInteractEvent::player, EventValues.TIME_NOW);
EventValues.registerEventValue(CustomBlockInteractEvent.class, Block.class, event -> null, EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureInteractEvent.class, Entity.class, event -> event.furniture().baseEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureInteractEvent.class, Entity.class, event -> event.furniture().getBukkitEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureInteractEvent.class, World.class, event -> event.location().getWorld(), EventValues.TIME_NOW);
}

View File

@@ -30,14 +30,14 @@ public class EvtCustomFurniture extends SkriptEvent {
.description("Called when a furniture is broken by a player.");
EventValues.registerEventValue(FurnitureBreakEvent.class, Location.class, FurnitureBreakEvent::location, EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureBreakEvent.class, Player.class, FurnitureBreakEvent::player, EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureBreakEvent.class, Entity.class, event -> event.furniture().baseEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureBreakEvent.class, Entity.class, event -> event.furniture().getBukkitEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureBreakEvent.class, World.class, event -> event.location().getWorld(), EventValues.TIME_NOW);
Skript.registerEvent("Place Furniture", EvtCustomFurniture.class, FurniturePlaceEvent.class, "(plac(e|ing)|build[ing]) of [(custom|ce|craft-engine)] furniture[s] [[of] %-strings%]")
.description("Called when a player places a furniture.");
EventValues.registerEventValue(FurniturePlaceEvent.class, Location.class, FurniturePlaceEvent::location, EventValues.TIME_NOW);
EventValues.registerEventValue(FurniturePlaceEvent.class, Player.class, FurniturePlaceEvent::player, EventValues.TIME_NOW);
EventValues.registerEventValue(FurniturePlaceEvent.class, Entity.class, event -> event.furniture().baseEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurniturePlaceEvent.class, Entity.class, event -> event.furniture().getBukkitEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurniturePlaceEvent.class, World.class, event -> event.location().getWorld(), EventValues.TIME_NOW);
}

View File

@@ -16,7 +16,7 @@ public class ExprEntityFurnitureID extends SimplePropertyExpression<Object, Stri
@Override
public @Nullable String convert(Object object) {
if (object instanceof Entity entity) {
return Optional.ofNullable(CraftEngineFurniture.getLoadedFurnitureByBaseEntity(entity))
return Optional.ofNullable(CraftEngineFurniture.getLoadedFurnitureByMetaEntity(entity))
.map(it -> it.id().toString())
.orElse(null);
}

View File

@@ -119,6 +119,11 @@ public final class BukkitAdvancementManager extends AbstractAdvancementManager {
return LoadingSequence.ADVANCEMENT;
}
@Override
public int count() {
return 0;
}
@Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (advancements.containsKey(id)) {

View File

@@ -6,11 +6,12 @@ import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeatManager;
import net.momirealms.craftengine.bukkit.nms.CollisionEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureExtraData;
import net.momirealms.craftengine.core.entity.furniture.FurnitureConfig;
import net.momirealms.craftengine.core.entity.furniture.FurnitureDataAccessor;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.LootTable;
@@ -20,11 +21,13 @@ import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.FluidCollisionMode;
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.bukkit.util.RayTraceResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -46,7 +49,7 @@ public final class CraftEngineFurniture {
* @return a non-null map containing all loaded custom furniture
*/
@NotNull
public static Map<Key, CustomFurniture> loadedFurniture() {
public static Map<Key, FurnitureConfig> loadedFurniture() {
return BukkitFurnitureManager.instance().loadedFurniture();
}
@@ -56,10 +59,49 @@ public final class CraftEngineFurniture {
* @param id id
* @return the custom furniture
*/
public static CustomFurniture byId(@NotNull Key id) {
public static FurnitureConfig byId(@NotNull Key id) {
return BukkitFurnitureManager.instance().furnitureById(id).orElse(null);
}
/**
* Performs ray tracing to find the furniture entity that the player is currently targeting
*
* @param player The player performing the ray trace
* @param maxDistance Maximum ray trace distance (in blocks)
* @return The furniture being targeted by the player, or null if no furniture is found
*/
@Nullable
public static BukkitFurniture rayTrace(Player player, double maxDistance) {
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
Location eyeLocation = serverPlayer.getEyeLocation();
RayTraceResult result = player.getWorld().rayTrace(eyeLocation, eyeLocation.getDirection(), maxDistance, FluidCollisionMode.NEVER, true, 0d, CraftEngineFurniture::isCollisionEntity);
if (result == null)
return null;
Entity hitEntity = result.getHitEntity();
if (hitEntity == null)
return null;
return getLoadedFurnitureByCollider(hitEntity);
}
/**
* Performs ray tracing to find the furniture entity that the player is currently targeting
*
* @param player The player performing the ray trace
* @return The furniture being targeted by the player, or null if no furniture is found
*/
@Nullable
public static BukkitFurniture rayTrace(Player player) {
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
Location eyeLocation = serverPlayer.getEyeLocation();
RayTraceResult result = player.getWorld().rayTrace(eyeLocation, eyeLocation.getDirection(), serverPlayer.getCachedInteractionRange(), FluidCollisionMode.NEVER, true, 0d, CraftEngineFurniture::isCollisionEntity);
if (result == null)
return null;
Entity hitEntity = result.getHitEntity();
if (hitEntity == null)
return null;
return getLoadedFurnitureByCollider(hitEntity);
}
/**
* Places furniture at certain location
*
@@ -69,9 +111,9 @@ public final class CraftEngineFurniture {
*/
@Nullable
public static BukkitFurniture place(Location location, Key furnitureId) {
CustomFurniture furniture = byId(furnitureId);
FurnitureConfig furniture = byId(furnitureId);
if (furniture == null) return null;
return place(location, furnitureId, furniture.getAnyAnchorType());
return place(location, furniture, furniture.anyVariantName(), false);
}
/**
@@ -83,10 +125,24 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@Nullable
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, Key furnitureId, AnchorType anchorType) {
CustomFurniture furniture = byId(furnitureId);
return place(location, furnitureId, anchorType.variantName());
}
/**
* Places furniture at certain location
*
* @param location location
* @param furnitureId furniture to place
* @param variant variant type
* @return the loaded furniture
*/
@Nullable
public static BukkitFurniture place(Location location, Key furnitureId, String variant) {
FurnitureConfig furniture = byId(furnitureId);
if (furniture == null) return null;
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), true);
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureDataAccessor.ofVariant(variant), true);
}
/**
@@ -98,8 +154,9 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@NotNull
public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType) {
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), true);
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, FurnitureConfig furniture, AnchorType anchorType) {
return place(location, furniture, anchorType.variantName(), true);
}
/**
@@ -112,10 +169,27 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@Nullable
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, Key furnitureId, AnchorType anchorType, boolean playSound) {
CustomFurniture furniture = byId(furnitureId);
FurnitureConfig furniture = byId(furnitureId);
if (furniture == null) return null;
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), playSound);
return place(location, furniture, anchorType.variantName(), playSound);
}
/**
* Places furniture at certain location
*
* @param location location
* @param furnitureId furniture to place
* @param variant variant
* @param playSound whether to play place sounds
* @return the loaded furniture
*/
@Nullable
public static BukkitFurniture place(Location location, Key furnitureId, String variant, boolean playSound) {
FurnitureConfig furniture = byId(furnitureId);
if (furniture == null) return null;
return place(location, furniture, variant, playSound);
}
/**
@@ -128,8 +202,51 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@NotNull
public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType, boolean playSound) {
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), playSound);
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, FurnitureConfig furniture, AnchorType anchorType, boolean playSound) {
return place(location, furniture, anchorType.variantName(), playSound);
}
/**
* Places furniture at certain location
*
* @param location location
* @param furniture furniture to place
* @param variant variant
* @param playSound whether to play place sounds
* @return the loaded furniture
*/
@NotNull
public static BukkitFurniture place(Location location, FurnitureConfig furniture, String variant, boolean playSound) {
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureDataAccessor.ofVariant(variant), playSound);
}
/**
* Places furniture at certain location
*
* @param location location
* @param furniture furniture to place
* @param data furniture data
* @param playSound whether to play place sounds
* @return the loaded furniture
*/
@NotNull
public static BukkitFurniture place(Location location, FurnitureConfig furniture, CompoundTag data, boolean playSound) {
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureDataAccessor.of(data), playSound);
}
/**
* Places furniture at certain location
*
* @param location location
* @param furniture furniture to place
* @param dataAccessor furniture data accessor
* @param playSound whether to play place sounds
* @return the loaded furniture
*/
@NotNull
public static BukkitFurniture place(Location location, FurnitureConfig furniture, FurnitureDataAccessor dataAccessor, boolean playSound) {
return BukkitFurnitureManager.instance().place(location, furniture, dataAccessor, playSound);
}
/**
@@ -165,18 +282,30 @@ public final class CraftEngineFurniture {
}
/**
* Gets the base furniture by the base entity
* Gets the furniture by the meta entity
*
* @param baseEntity base entity
* @return the loaded furniture
*/
@Nullable
public static BukkitFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) {
return BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(baseEntity.getEntityId());
public static BukkitFurniture getLoadedFurnitureByMetaEntity(@NotNull Entity baseEntity) {
return BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(baseEntity.getEntityId());
}
/**
* Gets the base furniture by the seat entity
* Gets the furniture by the meta entity
*
* @param baseEntity base entity
* @return the loaded furniture
*/
@Nullable
@Deprecated(since = "0.0.66")
public static BukkitFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) {
return getLoadedFurnitureByMetaEntity(baseEntity);
}
/**
* Gets the furniture by the seat entity
*
* @param seat seat entity
* @return the loaded furniture
@@ -186,7 +315,22 @@ public final class CraftEngineFurniture {
if (isSeat(seat)) {
CompoundTag seatExtraData = BukkitSeatManager.instance().getSeatExtraData(seat);
int entityId = seatExtraData.getInt("entity_id");
BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entityId);
BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entityId);
}
return null;
}
/**
* Gets the furniture by the collider entity
*
* @param collider collider entity
* @return the loaded furniture
*/
@Nullable
public static BukkitFurniture getLoadedFurnitureByCollider(@NotNull Entity collider) {
Object nmsEntity = FastNMS.INSTANCE.method$CraftEntity$getHandle(collider);
if (nmsEntity instanceof CollisionEntity collisionEntity) {
return BukkitFurnitureManager.instance().loadedFurnitureByColliderEntityId(collisionEntity.getEntityId());
}
return null;
}
@@ -199,7 +343,7 @@ public final class CraftEngineFurniture {
*/
public static boolean remove(@NotNull Entity entity) {
if (!isFurniture(entity)) return false;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId());
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false;
furniture.destroy();
return true;
@@ -217,7 +361,7 @@ public final class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!isFurniture(entity)) return false;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId());
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false;
remove(furniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound);
return true;
@@ -237,7 +381,7 @@ public final class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!isFurniture(entity)) return false;
Furniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId());
Furniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false;
remove(furniture, player, dropLoot, playSound);
return true;
@@ -286,16 +430,16 @@ public final class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!furniture.isValid()) return;
Location location = ((BukkitFurniture) furniture).dropLocation();
Location location = ((BukkitFurniture) furniture).getDropLocation();
furniture.destroy();
LootTable<ItemStack> lootTable = (LootTable<ItemStack>) furniture.config().lootTable();
LootTable<ItemStack> lootTable = (LootTable<ItemStack>) furniture.config.lootTable();
World world = new BukkitWorld(location.getWorld());
WorldPosition position = new WorldPosition(world, location.getX(), location.getY(), location.getZ());
if (dropLoot && lootTable != null) {
ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(DirectContextParameters.POSITION, position)
.withParameter(DirectContextParameters.FURNITURE, furniture)
.withOptionalParameter(DirectContextParameters.FURNITURE_ITEM, furniture.extraData().item().orElse(null));
.withOptionalParameter(DirectContextParameters.FURNITURE_ITEM, furniture.dataAccessor.item().orElse(null));
if (player != null) {
Item<?> itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND);
builder.withParameter(DirectContextParameters.PLAYER, player)
@@ -307,7 +451,7 @@ public final class CraftEngineFurniture {
}
}
if (playSound) {
world.playBlockSound(position, furniture.config().settings().sounds().breakSound());
world.playBlockSound(position, furniture.config.settings().sounds().breakSound());
}
}
}

View File

@@ -1,11 +1,10 @@
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.furniture.FurnitureConfig;
import net.momirealms.craftengine.core.entity.furniture.FurnitureVariant;
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;
@@ -15,25 +14,22 @@ import org.jetbrains.annotations.NotNull;
public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private boolean cancelled;
private final CustomFurniture furniture;
private final FurnitureConfig furniture;
private final Location location;
private final AnchorType anchorType;
private final BlockFace clickedFace;
private final FurnitureVariant variant;
private final Block clickedBlock;
private final InteractionHand hand;
public FurnitureAttemptPlaceEvent(@NotNull Player player,
@NotNull CustomFurniture furniture,
@NotNull AnchorType anchorType,
@NotNull FurnitureConfig furniture,
@NotNull FurnitureVariant variant,
@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.variant = variant;
this.clickedBlock = clickedBlock;
this.hand = hand;
}
@@ -48,19 +44,14 @@ public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Can
return hand;
}
@NotNull
public BlockFace clickedFace() {
return clickedFace;
}
@NotNull
public Player player() {
return getPlayer();
}
@NotNull
public AnchorType anchorType() {
return anchorType;
public FurnitureVariant variant() {
return variant;
}
@NotNull
@@ -69,7 +60,7 @@ public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Can
}
@NotNull
public CustomFurniture furniture() {
public FurnitureConfig furniture() {
return furniture;
}

View File

@@ -1,7 +1,7 @@
package net.momirealms.craftengine.bukkit.api.event;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture;
import net.momirealms.craftengine.core.entity.furniture.HitBox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -16,23 +16,23 @@ public final class FurnitureInteractEvent extends PlayerEvent implements Cancell
private final BukkitFurniture furniture;
private final InteractionHand hand;
private final Location interactionPoint;
private final HitBox hitBox;
private final FurnitureHitBox furnitureHitBox;
public FurnitureInteractEvent(@NotNull Player player,
@NotNull BukkitFurniture furniture,
@NotNull InteractionHand hand,
@NotNull Location interactionPoint,
@NotNull HitBox hitBox) {
@NotNull FurnitureHitBox furnitureHitBox) {
super(player);
this.furniture = furniture;
this.hand = hand;
this.interactionPoint = interactionPoint;
this.hitBox = hitBox;
this.furnitureHitBox = furnitureHitBox;
}
@NotNull
public HitBox hitBox() {
return hitBox;
public FurnitureHitBox hitBox() {
return furnitureHitBox;
}
@NotNull

View File

@@ -21,6 +21,7 @@ import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.sound.SoundSource;
import net.momirealms.craftengine.core.util.Cancellable;
import net.momirealms.craftengine.core.util.ItemUtils;
@@ -38,6 +39,7 @@ import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.world.GenericGameEvent;
import org.bukkit.inventory.ItemStack;
@@ -233,12 +235,14 @@ public final class BlockEventListener implements Listener {
@EventHandler(priority = EventPriority.LOW)
public void onStep(GenericGameEvent event) {
if (event.getEvent() != GameEvent.STEP) return;
GameEvent gameEvent = event.getEvent();
// 只处理落地和走路
if (gameEvent != GameEvent.STEP) return;
Entity entity = event.getEntity();
if (!(entity instanceof Player player)) return;
BlockPos pos = EntityUtils.getOnPos(player);
Block block = player.getWorld().getBlockAt(pos.x(), pos.y(), pos.z());
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(block.getWorld()), LocationUtils.toBlockPos(block.getX(), block.getY(), block.getZ()));
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(player.getWorld()), LocationUtils.toBlockPos(pos));
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState);
if (optionalCustomState.isPresent()) {
Location location = player.getLocation();
@@ -253,7 +257,8 @@ public final class BlockEventListener implements Listener {
if (cancellable.isCancelled() && !Config.processCancelledStep()) {
return;
}
player.playSound(location, state.settings().sounds().stepSound().id().toString(), SoundCategory.BLOCKS, state.settings().sounds().stepSound().volume().get(), state.settings().sounds().stepSound().pitch().get());
SoundData soundData = state.settings().sounds().stepSound();
player.playSound(location, soundData.id().toString(), SoundCategory.BLOCKS, soundData.volume().get(), soundData.pitch().get());
} else if (Config.enableSoundSystem()) {
if (event.isCancelled() && !Config.processCancelledStep()) {
return;
@@ -267,6 +272,32 @@ public final class BlockEventListener implements Listener {
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
public void onFall(EntityDamageEvent event) {
if (event.getCause() != EntityDamageEvent.DamageCause.FALL)
return;
if (!(event.getEntity() instanceof Player player)) return;
BlockPos pos = EntityUtils.getOnPos(player);
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(player.getWorld()), LocationUtils.toBlockPos(pos));
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState);
if (optionalCustomState.isPresent()) {
Location location = player.getLocation();
ImmutableBlockState state = optionalCustomState.get();
SoundData soundData = state.settings().sounds().fallSound();
player.playSound(location, soundData.id().toString(), SoundCategory.BLOCKS, soundData.volume().get(), soundData.pitch().get());
} else if (Config.enableSoundSystem()) {
if (event.isCancelled() && !Config.processCancelledStep()) {
return;
}
Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState);
Object soundEvent = FastNMS.INSTANCE.field$SoundType$fallSound(soundType);
Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent);
if (this.manager.isStepSoundMissing(soundId)) {
player.playSound(player.getLocation(), soundId.toString(), SoundCategory.BLOCKS, 0.15f, 1f);
}
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onBlockPhysics(BlockPhysicsEvent event) {
// for vanilla blocks

View File

@@ -47,6 +47,8 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key SURFACE_SPREADING_BLOCK = Key.from("craftengine:surface_spreading_block");
public static final Key SNOWY_BLOCK = Key.from("craftengine:snowy_block");
public static final Key HANGABLE_BLOCK = Key.from("craftengine:hangable_block");
public static final Key DROP_EXPERIENCE_BLOCK = Key.from("craftengine:drop_experience_block");
public static final Key DROP_EXP_BLOCK = Key.from("craftengine:drop_exp_block");
public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
@@ -92,5 +94,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(SURFACE_SPREADING_BLOCK, SurfaceSpreadingBlockBehavior.FACTORY);
register(SNOWY_BLOCK, SnowyBlockBehavior.FACTORY);
register(HANGABLE_BLOCK, HangableBlockBehavior.FACTORY);
register(DROP_EXPERIENCE_BLOCK, DropExperienceBlockBehavior.FACTORY);
register(DROP_EXP_BLOCK, DropExperienceBlockBehavior.FACTORY);
}
}

View File

@@ -0,0 +1,104 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.BlockSettings;
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.LootContext;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.condition.AllOfCondition;
import net.momirealms.craftengine.core.plugin.context.event.EventConditions;
import net.momirealms.craftengine.core.plugin.context.number.NumberProvider;
import net.momirealms.craftengine.core.plugin.context.number.NumberProviders;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
public class DropExperienceBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final NumberProvider amount;
private final Condition<Context> conditions;
public DropExperienceBlockBehavior(CustomBlock customBlock, NumberProvider amount, Condition<Context> conditions) {
super(customBlock);
this.amount = amount;
this.conditions = conditions;
}
@Override
public void spawnAfterBreak(Object thisBlock, Object[] args, Callable<Object> superMethod) {
boolean dropExperience = (boolean) args[4]; // 通常来说是 false
Item<ItemStack> item = BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(args[3]));
if (!dropExperience) {
ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null);
if (state == null) {
return;
}
BlockSettings settings = state.settings();
if (settings.requireCorrectTool()) {
if (item.isEmpty()) {
return;
}
boolean cannotBreak = !settings.isCorrectTool(item.id())
&& (!settings.respectToolComponent()
|| !FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(args[3], state.customBlockState().literalObject()));
if (cannotBreak) {
return;
}
}
}
World world = BukkitWorldManager.instance().wrap(FastNMS.INSTANCE.method$Level$getCraftWorld(args[1]));
BlockPos pos = LocationUtils.fromBlockPos(args[2]);
tryDropExperience(world, pos, item);
}
private void tryDropExperience(World world, BlockPos pos, Item<ItemStack> item) {
Vec3d dropPos = Vec3d.atCenterOf(pos);
ContextHolder holder = ContextHolder.builder()
.withParameter(DirectContextParameters.POSITION, new WorldPosition(world, dropPos))
.withParameter(DirectContextParameters.ITEM_IN_HAND, item)
.build();
LootContext context = new LootContext(world, null, 1.0f, holder);
if (this.conditions != null && !this.conditions.test(context)) {
return;
}
int finalAmount = this.amount.getInt(context);
if (finalAmount <= 0) {
return;
}
world.dropExp(dropPos, finalAmount);
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
NumberProvider amount = NumberProviders.fromObject(ResourceConfigUtils.get(arguments, "amount", "count"));
Condition<Context> conditions = null;
List<Condition<Context>> conditionList = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(arguments, "conditions", "condition"), EventConditions::fromMap);
if (conditionList.size() == 1) {
conditions = conditionList.getFirst();
} else if (!conditionList.isEmpty()) {
conditions = new AllOfCondition<>(conditionList);
}
return new DropExperienceBlockBehavior(block, amount, conditions);
}
}
}

View File

@@ -172,8 +172,10 @@ public class SimpleStorageBlockEntity extends BlockEntity {
public void updateOpenBlockState(boolean open) {
ImmutableBlockState state = super.world.getBlockStateAtIfLoaded(this.pos);
if (state == null || state.behavior() != this.behavior) return;
Property<Boolean> property = this.behavior.openProperty();
if (state == null) return;
SimpleStorageBlockBehavior behavior = state.behavior().getAs(SimpleStorageBlockBehavior.class).orElse(null);
if (behavior == null) return;
Property<Boolean> property = behavior.openProperty();
if (property == null) return;
super.world.world().setBlockState(this.pos.x(), this.pos.y(), this.pos.z(), state.with(property, open), UpdateOption.UPDATE_ALL.flags());
}

View File

@@ -2,7 +2,6 @@ package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
import net.momirealms.craftengine.bukkit.entity.data.ItemEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.player.Player;
@@ -76,13 +75,12 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig<It
return this.position.equals(that.position);
}
public static class Factory implements BlockEntityElementConfigFactory {
public static class Factory implements BlockEntityElementConfigFactory<ItemBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public ItemBlockEntityElementConfig create(Map<String, Object> arguments) {
Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item"));
return (BlockEntityElementConfig<E>) new ItemBlockEntityElementConfig(
return new ItemBlockEntityElementConfig(
player -> BukkitItemManager.instance().createWrappedItem(itemId, player),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position")
);

View File

@@ -3,11 +3,10 @@ package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
import com.google.common.base.Objects;
import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.display.Billboard;
import net.momirealms.craftengine.core.entity.display.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.util.Key;
@@ -19,7 +18,6 @@ import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
@@ -159,13 +157,12 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
Objects.equal(rotation, that.rotation);
}
public static class Factory implements BlockEntityElementConfigFactory {
public static class Factory implements BlockEntityElementConfigFactory<ItemDisplayBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public ItemDisplayBlockEntityElementConfig create(Map<String, Object> arguments) {
Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item"));
return (BlockEntityElementConfig<E>) new ItemDisplayBlockEntityElementConfig(
return new ItemDisplayBlockEntityElementConfig(
player -> BukkitItemManager.instance().createWrappedItem(itemId, player),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
@@ -173,8 +170,8 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),
ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"),
ItemDisplayContext.valueOf(arguments.getOrDefault("display-context", "none").toString().toUpperCase(Locale.ROOT)),
Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)),
ResourceConfigUtils.getAsEnum(ResourceConfigUtils.get(arguments, "display-context", "display-transform"), ItemDisplayContext.class, ItemDisplayContext.NONE),
ResourceConfigUtils.getAsEnum(arguments.get("billboard"), Billboard.class, Billboard.FIXED),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-radius", 0f), "shadow-radius"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-strength", 1f), "shadow-strength")
);

View File

@@ -4,10 +4,9 @@ import com.google.common.base.Objects;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.display.Billboard;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.util.AdventureHelper;
@@ -135,13 +134,12 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
Objects.equal(rotation, that.rotation);
}
public static class Factory implements BlockEntityElementConfigFactory {
public static class Factory implements BlockEntityElementConfigFactory<TextDisplayBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public TextDisplayBlockEntityElementConfig create(Map<String, Object> arguments) {
String text = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("text"), "warning.config.block.state.entity_renderer.text_display.missing_text");
return (BlockEntityElementConfig<E>) new TextDisplayBlockEntityElementConfig(
return new TextDisplayBlockEntityElementConfig(
text,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),

View File

@@ -2,12 +2,14 @@ package net.momirealms.craftengine.bukkit.entity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.EntityUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.entity.AbstractEntity;
import net.momirealms.craftengine.core.entity.data.EntityData;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import java.lang.ref.WeakReference;
import java.util.UUID;
@@ -38,6 +40,11 @@ public class BukkitEntity extends AbstractEntity {
public void tick() {
}
@Override
public WorldPosition position() {
return LocationUtils.toWorldPosition(platformEntity().getLocation());
}
@Override
public int entityID() {
return platformEntity().getEntityId();
@@ -78,6 +85,11 @@ public class BukkitEntity extends AbstractEntity {
return EntityUtils.getEntityType(platformEntity());
}
@Override
public boolean isValid() {
return platformEntity().isValid();
}
@Override
public String name() {
return platformEntity().getName();

View File

@@ -1,7 +1,7 @@
package net.momirealms.craftengine.bukkit.entity;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.entity.ItemEntity;
import net.momirealms.craftengine.core.entity.item.ItemEntity;
import org.bukkit.entity.Item;
public class BukkitItemEntity extends BukkitEntity implements ItemEntity {

View File

@@ -39,7 +39,7 @@ public class BukkitEntityData<T> implements EntityData<T> {
}
@Override
public Object create(Object entityDataAccessor, Object value) {
public Object create(Object entityDataAccessor, T value) {
return EntityDataValue.create(entityDataAccessor, value);
}
}

View File

@@ -21,7 +21,7 @@ public class BukkitCollider implements Collider {
@Override
public int entityId() {
return this.collisionEntity.getId();
return this.collisionEntity.getEntityId();
}
@Override

View File

@@ -1,74 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.core.entity.furniture.AbstractCustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureSettings;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
public class BukkitCustomFurniture extends AbstractCustomFurniture {
protected BukkitCustomFurniture(@NotNull Key id,
@NotNull FurnitureSettings settings,
@NotNull Map<AnchorType, Placement> placements,
@NotNull Map<EventTrigger, List<Function<Context>>> events,
@Nullable LootTable<?> lootTable) {
super(id, settings, placements, events, lootTable);
}
public static Builder builder() {
return new BuilderImpl();
}
public static class BuilderImpl implements Builder {
private Key id;
private Map<AnchorType, Placement> placements;
private FurnitureSettings settings;
private Map<EventTrigger, List<Function<Context>>> events;
private LootTable<?> lootTable;
@Override
public CustomFurniture build() {
return new BukkitCustomFurniture(id, settings, placements, events, lootTable);
}
@Override
public Builder id(Key id) {
this.id = id;
return this;
}
@Override
public Builder placement(Map<AnchorType, Placement> placements) {
this.placements = placements;
return this;
}
@Override
public Builder settings(FurnitureSettings settings) {
this.settings = settings;
return this;
}
@Override
public Builder lootTable(LootTable<?> lootTable) {
this.lootTable = lootTable;
return this;
}
@Override
public Builder events(Map<EventTrigger, List<Function<Context>>> events) {
this.events = events;
return this;
}
}
}

View File

@@ -1,290 +1,69 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.entity.BukkitEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureConfig;
import net.momirealms.craftengine.core.entity.furniture.FurnitureDataAccessor;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.Optional;
public class BukkitFurniture implements Furniture {
private final CustomFurniture furniture;
private final CustomFurniture.Placement placement;
private FurnitureExtraData extraData;
// location
private final Location location;
// base entity
private final WeakReference<Entity> baseEntity;
private final int baseEntityId;
// colliders
private final Collider[] colliderEntities;
// cache
private final List<Integer> fakeEntityIds;
private final List<Integer> entityIds;
private final Map<Integer, BukkitHitBox> hitBoxes = new Int2ObjectArrayMap<>();
private final Map<Integer, HitBoxPart> hitBoxParts = new Int2ObjectArrayMap<>();
private final boolean minimized;
private final boolean hasExternalModel;
// cached spawn packet
private Object cachedSpawnPacket;
private Object cachedMinimizedSpawnPacket;
public class BukkitFurniture extends Furniture {
private final WeakReference<ItemDisplay> metaEntity;
private Location location;
public BukkitFurniture(Entity baseEntity,
CustomFurniture furniture,
FurnitureExtraData extraData) {
this.extraData = extraData;
this.baseEntityId = baseEntity.getEntityId();
this.location = baseEntity.getLocation();
this.baseEntity = new WeakReference<>(baseEntity);
this.furniture = furniture;
this.minimized = furniture.settings().minimized();
this.placement = furniture.getValidPlacement(extraData.anchorType().orElseGet(furniture::getAnyAnchorType));
List<Integer> fakeEntityIds = new IntArrayList();
List<Integer> mainEntityIds = new IntArrayList();
mainEntityIds.add(this.baseEntityId);
// 绑定外部模型
Optional<ExternalModel> optionalExternal = placement.externalModel();
if (optionalExternal.isPresent()) {
try {
optionalExternal.get().bindModel(new BukkitEntity(baseEntity));
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to load external model for furniture " + id(), e);
}
this.hasExternalModel = true;
} else {
this.hasExternalModel = false;
}
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate();
List<Object> packets = new ArrayList<>();
List<Object> minimizedPackets = new ArrayList<>();
List<Collider> colliders = new ArrayList<>(4);
WorldPosition position = position();
// 初始化家具的元素
for (FurnitureElement element : placement.elements()) {
int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
fakeEntityIds.add(entityId);
element.initPackets(this, entityId, conjugated, packet -> {
packets.add(packet);
if (this.minimized) minimizedPackets.add(packet);
});
}
// 初始化碰撞箱
for (HitBoxConfig hitBoxConfig : this.placement.hitBoxConfigs()) {
int[] ids = hitBoxConfig.acquireEntityIds(CoreReflections.instance$Entity$ENTITY_COUNTER::incrementAndGet);
List<HitBoxPart> aabbs = new ArrayList<>();
hitBoxConfig.initPacketsAndColliders(ids, position, conjugated, (packet, canBeMinimized) -> {
packets.add(packet);
if (this.minimized && !canBeMinimized) {
minimizedPackets.add(packet);
}
}, colliders::add, part -> {
this.hitBoxParts.put(part.entityId(), part);
aabbs.add(part);
});
BukkitHitBox hitBox = new BukkitHitBox(this, hitBoxConfig, aabbs.toArray(new HitBoxPart[0]));
for (int entityId : ids) {
fakeEntityIds.add(entityId);
mainEntityIds.add(entityId);
this.hitBoxes.put(entityId, hitBox);
}
}
// 初始化缓存的家具包
try {
this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
if (this.minimized) {
this.cachedMinimizedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(minimizedPackets);
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to init spawn packets for furniture " + id(), e);
}
this.fakeEntityIds = fakeEntityIds;
this.entityIds = mainEntityIds;
this.colliderEntities = colliders.toArray(new Collider[0]);
public BukkitFurniture(ItemDisplay metaEntity, FurnitureConfig config, FurnitureDataAccessor data) {
super(new BukkitEntity(metaEntity), data, config);
this.metaEntity = new WeakReference<>(metaEntity);
this.location = metaEntity.getLocation();
}
@Override
public void initializeColliders() {
public void addCollidersToWorld() {
Object world = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(this.location.getWorld());
for (Collider entity : this.colliderEntities) {
FastNMS.INSTANCE.method$LevelWriter$addFreshEntity(world, entity.handle());
for (Collider entity : super.colliders) {
Entity bukkitEntity = FastNMS.INSTANCE.method$Entity$getBukkitEntity(entity.handle());
bukkitEntity.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_COLLISION, PersistentDataType.BYTE, (byte) 1);
bukkitEntity.setPersistent(false);
if (!bukkitEntity.isValid()) {
FastNMS.INSTANCE.method$LevelWriter$addFreshEntity(world, entity.handle());
}
}
@NotNull
public Object spawnPacket(Player player) {
// TODO hasPermission might be slow, can we use a faster way in the future?
// TODO Make it based on conditions. So we can dynamically control which furniture should be sent to the player
if (!this.minimized || player.hasPermission(FurnitureManager.FURNITURE_ADMIN_NODE)) {
return this.cachedSpawnPacket;
} else {
return this.cachedMinimizedSpawnPacket;
}
}
@Override
public WorldPosition position() {
return LocationUtils.toWorldPosition(this.location);
public void destroy() {
Optional.ofNullable(this.metaEntity.get()).ifPresent(Entity::remove);
for (Collider entity : super.colliders) {
entity.destroy();
}
}
@NotNull
public Location location() {
return this.location.clone();
}
@NotNull
public Entity baseEntity() {
Entity entity = this.baseEntity.get();
if (entity == null) {
throw new RuntimeException("Base entity not found. It might be unloaded.");
}
return entity;
}
@Override
public boolean isValid() {
return baseEntity().isValid();
}
@NotNull
public Location dropLocation() {
Optional<Vector3f> dropOffset = this.placement.dropOffset();
// 获取掉落物的位置,受到家具变种的影响
public Location getDropLocation() {
Optional<Vector3f> dropOffset = this.getCurrentVariant().dropOffset();
if (dropOffset.isEmpty()) {
return location();
return this.location;
}
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate();
Vector3f offset = conjugated.transform(new Vector3f(dropOffset.get()));
return new Location(this.location.getWorld(), this.location.getX() + offset.x, this.location.getY() + offset.y, this.location.getZ() - offset.z);
}
@Override
public void destroy() {
if (!isValid()) {
return;
}
this.baseEntity().remove();
this.destroyColliders();
this.destroySeats();
public Location location() {
return location;
}
@Override
public void destroyColliders() {
for (Collider entity : this.colliderEntities) {
if (entity != null)
entity.destroy();
}
}
@Override
public void destroySeats() {
for (HitBox hitBox : this.hitBoxes.values()) {
for (Seat<HitBox> seat : hitBox.seats()) {
seat.destroy();
}
}
}
@Override
public UUID uuid() {
return this.baseEntity().getUniqueId();
}
@Override
public int baseEntityId() {
return this.baseEntityId;
}
@NotNull
public List<Integer> entityIds() {
return Collections.unmodifiableList(this.entityIds);
}
@NotNull
public List<Integer> fakeEntityIds() {
return Collections.unmodifiableList(this.fakeEntityIds);
}
public Collider[] collisionEntities() {
return this.colliderEntities;
}
@Override
public @Nullable HitBox hitBoxByEntityId(int id) {
return this.hitBoxes.get(id);
}
@Override
public @Nullable HitBoxPart hitBoxPartByEntityId(int id) {
return this.hitBoxParts.get(id);
}
@Override
public @NotNull AnchorType anchorType() {
return this.placement.anchorType();
}
@Override
public @NotNull Key id() {
return this.furniture.id();
}
@Override
public @NotNull CustomFurniture config() {
return this.furniture;
}
@Override
public boolean hasExternalModel() {
return hasExternalModel;
}
@Override
public FurnitureExtraData extraData() {
return this.extraData;
}
@Override
public void setExtraData(FurnitureExtraData extraData) {
this.extraData = extraData;
this.save();
}
@Override
public void save() {
try {
this.baseEntity().getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_EXTRA_DATA_KEY, PersistentDataType.BYTE_ARRAY, this.extraData.toBytes());
} catch (IOException e) {
CraftEngine.instance().logger().warn("Failed to save furniture data.", e);
}
public Entity getBukkitEntity() {
return this.metaEntity.get();
}
}

View File

@@ -1,177 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.furniture.AbstractFurnitureElement;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureElement;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.data.FireworkExplosion;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class BukkitFurnitureElement extends AbstractFurnitureElement {
private final List<Object> commonValues;
public BukkitFurnitureElement(Key item,
Billboard billboard,
ItemDisplayContext transform,
Vector3f scale,
Vector3f translation,
Vector3f position,
Quaternionf rotation,
float shadowRadius,
float shadowStrength,
boolean applyDyedColor) {
super(item, billboard, transform, scale, translation, position, rotation, shadowRadius, shadowStrength, applyDyedColor);
this.commonValues = new ArrayList<>();
ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(scale(), this.commonValues);
ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(rotation(), this.commonValues);
ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(billboard().id(), this.commonValues);
ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(translation(), this.commonValues);
ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(transform().id(), this.commonValues);
ItemDisplayEntityData.ShadowRadius.addEntityDataIfNotDefaultValue(shadowRadius, this.commonValues);
ItemDisplayEntityData.ShadowStrength.addEntityDataIfNotDefaultValue(shadowStrength, this.commonValues);
}
@Override
public void initPackets(Furniture furniture, int entityId, @NotNull Quaternionf conjugated, Consumer<Object> packets) {
WorldPosition position = furniture.position();
Vector3f offset = conjugated.transform(new Vector3f(position()));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId, UUID.randomUUID(), position.x() + offset.x, position.y() + offset.y, position.z() - offset.z, 0, position.yRot(),
MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
));
if (applyDyedColor()) {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId, getCachedValues(
furniture.extraData().dyedColor().orElse(null),
furniture.extraData().fireworkExplosionColors().orElse(null)
)));
} else {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId, getCachedValues(null, null)));
}
}
private synchronized List<Object> getCachedValues(@Nullable Color color, int @Nullable [] colors) {
List<Object> cachedValues = new ArrayList<>(this.commonValues);
Item<ItemStack> item = BukkitItemManager.instance().createWrappedItem(item(), null);
if (item == null) {
item = BukkitItemManager.instance().wrap(new ItemStack(Material.BARRIER));
} else {
if (color != null) {
item.dyedColor(color);
}
if (colors != null) {
item.fireworkExplosion(new FireworkExplosion(
FireworkExplosion.Shape.SMALL_BALL,
new IntArrayList(colors),
new IntArrayList(),
false,
false
));
}
}
ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.getLiteralObject(), cachedValues);
return cachedValues;
}
public static Builder builder() {
return new BuilderImpl();
}
public static class BuilderImpl implements Builder {
private boolean applyDyedColor;
private Key item;
private Billboard billboard;
private ItemDisplayContext transform;
private Vector3f scale;
private Vector3f translation;
private Vector3f position;
private Quaternionf rotation;
private float shadowRadius;
private float shadowStrength;
@Override
public Builder applyDyedColor(boolean applyDyedColor) {
this.applyDyedColor = applyDyedColor;
return this;
}
@Override
public Builder item(Key item) {
this.item = item;
return this;
}
@Override
public Builder billboard(Billboard billboard) {
this.billboard = billboard;
return this;
}
@Override
public Builder transform(ItemDisplayContext transform) {
this.transform = transform;
return this;
}
@Override
public Builder scale(Vector3f scale) {
this.scale = scale;
return this;
}
@Override
public Builder translation(Vector3f translation) {
this.translation = translation;
return this;
}
@Override
public Builder position(Vector3f position) {
this.position = position;
return this;
}
@Override
public Builder rotation(Quaternionf rotation) {
this.rotation = rotation;
return this;
}
@Override
public Builder shadowStrength(float shadowStrength) {
this.shadowStrength = shadowStrength;
return this;
}
@Override
public Builder shadowRadius(float shadowRadius) {
this.shadowRadius = shadowRadius;
return this;
}
@Override
public FurnitureElement build() {
return new BukkitFurnitureElement(item, billboard, transform, scale, translation, position, rotation, shadowRadius, shadowStrength, applyDyedColor);
}
}
}

View File

@@ -1,22 +1,24 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.InteractionHitBoxConfig;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.InteractionFurnitureHitboxConfig;
import net.momirealms.craftengine.bukkit.nms.CollisionEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.network.handler.FurniturePacketHandler;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.EntityUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.chunk.CEChunk;
import org.bukkit.*;
import org.bukkit.entity.*;
import org.bukkit.event.HandlerList;
@@ -34,13 +36,17 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
public static final NamespacedKey FURNITURE_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_KEY);
public static final NamespacedKey FURNITURE_EXTRA_DATA_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_EXTRA_DATA_KEY);
public static final NamespacedKey FURNITURE_COLLISION = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_COLLISION);
private static BukkitFurnitureManager instance;
public static Class<?> COLLISION_ENTITY_CLASS = Interaction.class;
public static Object NMS_COLLISION_ENTITY_TYPE = MEntityTypes.INTERACTION;
public static ColliderType COLLISION_ENTITY_TYPE = ColliderType.INTERACTION;
private static BukkitFurnitureManager instance;
private final BukkitCraftEngine plugin;
private final Map<Integer, BukkitFurniture> furnitureByRealEntityId = new ConcurrentHashMap<>(256, 0.5f);
private final Map<Integer, BukkitFurniture> furnitureByEntityId = new ConcurrentHashMap<>(512, 0.5f);
private final Map<Integer, BukkitFurniture> byMetaEntityId = new ConcurrentHashMap<>(256, 0.5f);
private final Map<Integer, BukkitFurniture> byVirtualEntityId = new ConcurrentHashMap<>(512, 0.5f);
private final Map<Integer, BukkitFurniture> byColliderEntityId = new ConcurrentHashMap<>(512, 0.5f);
// Event listeners
private final FurnitureEventListener furnitureEventListener;
@@ -52,53 +58,54 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
super(plugin);
instance = this;
this.plugin = plugin;
this.furnitureEventListener = new FurnitureEventListener(this);
this.furnitureEventListener = new FurnitureEventListener(this, plugin.worldManager());
}
@Override
public Furniture place(WorldPosition position, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound) {
return this.place(LocationUtils.toLocation(position), furniture, extraData, playSound);
public Furniture place(WorldPosition position, FurnitureConfig furniture, FurnitureDataAccessor dataAccessor, boolean playSound) {
return this.place(LocationUtils.toLocation(position), furniture, dataAccessor, playSound);
}
public BukkitFurniture place(Location location, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound) {
Optional<AnchorType> optionalAnchorType = extraData.anchorType();
if (optionalAnchorType.isEmpty() || !furniture.isAllowedPlacement(optionalAnchorType.get())) {
extraData.anchorType(furniture.getAnyAnchorType());
}
public BukkitFurniture place(Location location, FurnitureConfig furniture, FurnitureDataAccessor data, 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());
try {
display.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_EXTRA_DATA_KEY, PersistentDataType.BYTE_ARRAY, extraData.toBytes());
display.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_EXTRA_DATA_KEY, PersistentDataType.BYTE_ARRAY, data.toBytes());
} catch (IOException e) {
this.plugin.logger().warn("Failed to set furniture PDC for " + furniture.id().toString(), e);
}
handleBaseEntityLoadEarly(display);
handleMetaEntityDuringChunkLoad(display);
});
if (playSound) {
SoundData data = furniture.settings().sounds().placeSound();
location.getWorld().playSound(location, data.id().toString(), SoundCategory.BLOCKS, data.volume().get(), data.pitch().get());
SoundData sound = furniture.settings().sounds().placeSound();
location.getWorld().playSound(location, sound.id().toString(), SoundCategory.BLOCKS, sound.volume().get(), sound.pitch().get());
}
return loadedFurnitureByRealEntityId(furnitureEntity.getEntityId());
return loadedFurnitureByMetaEntityId(furnitureEntity.getEntityId());
}
@Override
public void delayedInit() {
// 确定碰撞箱实体类型
COLLISION_ENTITY_TYPE = Config.colliderType();
COLLISION_ENTITY_CLASS = Config.colliderType() == ColliderType.INTERACTION ? Interaction.class : Boat.class;
NMS_COLLISION_ENTITY_TYPE = Config.colliderType() == ColliderType.INTERACTION ? MEntityTypes.INTERACTION : MEntityTypes.OAK_BOAT;
COLLISION_ENTITY_TYPE = Config.colliderType();
// 注册事件
Bukkit.getPluginManager().registerEvents(this.furnitureEventListener, this.plugin.javaPlugin());
// 对世界上已有实体的记录
if (VersionHelper.isFolia()) {
BiConsumer<Entity, Runnable> taskExecutor = (entity, runnable) -> entity.getScheduler().run(this.plugin.javaPlugin(), (t) -> runnable.run(), () -> {});
for (World world : Bukkit.getWorlds()) {
List<Entity> entities = world.getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay display) {
taskExecutor.accept(entity, () -> handleBaseEntityLoadEarly(display));
taskExecutor.accept(entity, () -> handleMetaEntityDuringChunkLoad(display));
} else if (entity instanceof Interaction interaction) {
taskExecutor.accept(entity, () -> handleCollisionEntityLoadOnEntitiesLoad(interaction));
taskExecutor.accept(entity, () -> handleCollisionEntityDuringChunkLoad(interaction));
} else if (entity instanceof Boat boat) {
taskExecutor.accept(entity, () -> handleCollisionEntityLoadOnEntitiesLoad(boat));
taskExecutor.accept(entity, () -> handleCollisionEntityDuringChunkLoad(boat));
}
}
}
@@ -107,11 +114,11 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
List<Entity> entities = world.getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay display) {
handleBaseEntityLoadEarly(display);
handleMetaEntityDuringChunkLoad(display);
} else if (entity instanceof Interaction interaction) {
handleCollisionEntityLoadOnEntitiesLoad(interaction);
handleCollisionEntityDuringChunkLoad(interaction);
} else if (entity instanceof Boat boat) {
handleCollisionEntityLoadOnEntitiesLoad(boat);
handleCollisionEntityDuringChunkLoad(boat);
}
}
}
@@ -125,150 +132,168 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
}
@Override
public boolean isFurnitureRealEntity(int entityId) {
return this.furnitureByRealEntityId.containsKey(entityId);
public boolean isFurnitureMetaEntity(int entityId) {
return this.byMetaEntityId.containsKey(entityId);
}
@Nullable
@Override
public BukkitFurniture loadedFurnitureByRealEntityId(int entityId) {
return this.furnitureByRealEntityId.get(entityId);
public BukkitFurniture loadedFurnitureByMetaEntityId(int entityId) {
return this.byMetaEntityId.get(entityId);
}
@Override
@Nullable
public BukkitFurniture loadedFurnitureByEntityId(int entityId) {
return this.furnitureByEntityId.get(entityId);
}
@Override
protected CustomFurniture.Builder furnitureBuilder() {
return BukkitCustomFurniture.builder();
public BukkitFurniture loadedFurnitureByVirtualEntityId(int entityId) {
return this.byVirtualEntityId.get(entityId);
}
@Nullable
@Override
protected FurnitureElement.Builder furnitureElementBuilder() {
return BukkitFurnitureElement.builder();
public BukkitFurniture loadedFurnitureByColliderEntityId(int entityId) {
return this.byColliderEntityId.get(entityId);
}
protected void handleBaseEntityUnload(Entity entity) {
// 当元数据实体被卸载了
protected void handleMetaEntityUnload(ItemDisplay entity) {
// 不是持久化的
if (!entity.isPersistent()) {
return;
}
int id = entity.getEntityId();
BukkitFurniture furniture = this.furnitureByRealEntityId.remove(id);
BukkitFurniture furniture = this.byMetaEntityId.remove(id);
if (furniture != null) {
Location location = entity.getLocation();
// 区块还在加载的时候,就重复卸载了。为极其特殊情况
boolean isPreventing = FastNMS.INSTANCE.method$ServerLevel$isPreventingStatusUpdates(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(location.getWorld()), location.getBlockX() >> 4, location.getBlockZ() >> 4);
if (!isPreventing) {
furniture.destroySeats();
}
for (int sub : furniture.entityIds()) {
this.furnitureByEntityId.remove(sub);
for (int sub : furniture.virtualEntityIds()) {
this.byVirtualEntityId.remove(sub);
}
for (int sub : furniture.colliderEntityIds()) {
this.byColliderEntityId.remove(sub);
}
}
}
// 保险起见collision实体卸载也移除一下
protected void handleCollisionEntityUnload(Entity entity) {
int id = entity.getEntityId();
this.furnitureByRealEntityId.remove(id);
this.byColliderEntityId.remove(id);
}
@SuppressWarnings("deprecation") // just a misleading name `getTrackedPlayers`
protected void handleBaseEntityLoadLate(ItemDisplay display, int depth) {
// must be a furniture item
String id = display.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
// 检查这个区块的实体是否已经被加载了
private boolean isEntitiesLoaded(Location location) {
CEWorld ceWorld = this.plugin.worldManager().getWorld(location.getWorld());
CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(location.getBlockX() >> 4, location.getBlockZ() >> 4);
if (ceChunk == null) return false;
return ceChunk.isEntitiesLoaded();
}
protected void handleMetaEntityDuringChunkLoad(ItemDisplay entity) {
// 实体可能不是持久的
if (!entity.isPersistent()) {
return;
}
// 获取家具pdc
String id = entity.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
if (id == null) return;
Key key = Key.of(id);
Optional<CustomFurniture> optionalFurniture = furnitureById(key);
if (optionalFurniture.isEmpty()) return;
CustomFurniture customFurniture = optionalFurniture.get();
BukkitFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId());
if (previous != null) return;
Location location = display.getLocation();
boolean above1_20_1 = VersionHelper.isOrAbove1_20_2();
boolean preventChange = FastNMS.INSTANCE.method$ServerLevel$isPreventingStatusUpdates(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(location.getWorld()), location.getBlockX() >> 4, location.getBlockZ() >> 4);
if (above1_20_1) {
if (!preventChange) {
BukkitFurniture furniture = addNewFurniture(display, customFurniture);
furniture.initializeColliders();
for (Player player : display.getTrackedPlayers()) {
BukkitAdaptors.adapt(player).entityPacketHandlers().computeIfAbsent(furniture.baseEntityId(), k -> new FurniturePacketHandler(furniture.fakeEntityIds()));
this.plugin.networkManager().sendPacket(BukkitAdaptors.adapt(player), furniture.spawnPacket(player));
}
}
} else {
BukkitFurniture furniture = addNewFurniture(display, customFurniture);
for (Player player : display.getTrackedPlayers()) {
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
serverPlayer.entityPacketHandlers().computeIfAbsent(furniture.baseEntityId(), k -> new FurniturePacketHandler(furniture.fakeEntityIds()));
this.plugin.networkManager().sendPacket(serverPlayer, furniture.spawnPacket(player));
}
if (preventChange) {
this.plugin.scheduler().sync().runLater(furniture::initializeColliders, 1, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4);
} else {
furniture.initializeColliders();
}
}
if (depth > 2) return;
this.plugin.scheduler().sync().runLater(() -> handleBaseEntityLoadLate(display, depth + 1), 1, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
protected void handleCollisionEntityLoadLate(Entity entity, int depth) {
// remove the entity if it's not a collision entity, it might be wrongly copied by WorldEdit
if (FastNMS.INSTANCE.method$CraftEntity$getHandle(entity) instanceof CollisionEntity) {
return;
}
// not a collision entity
Byte flag = entity.getPersistentDataContainer().get(FURNITURE_COLLISION, PersistentDataType.BYTE);
if (flag == null || flag != 1) {
return;
}
Location location = entity.getLocation();
World world = location.getWorld();
int chunkX = location.getBlockX() >> 4;
int chunkZ = location.getBlockZ() >> 4;
if (!FastNMS.INSTANCE.method$ServerLevel$isPreventingStatusUpdates(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(world), chunkX, chunkZ)) {
entity.remove();
return;
}
if (depth > 2) return;
plugin.scheduler().sync().runLater(() -> {
handleCollisionEntityLoadLate(entity, depth + 1);
}, 1, world, chunkX, chunkZ);
}
public void handleBaseEntityLoadEarly(ItemDisplay display) {
String id = display.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
if (id == null) return;
// Remove the entity if it's not a valid furniture
// 处理无效的家具
if (Config.handleInvalidFurniture()) {
String mapped = Config.furnitureMappings().get(id);
if (mapped != null) {
if (mapped.isEmpty()) {
display.remove();
entity.remove();
return;
} else {
id = mapped;
display.getPersistentDataContainer().set(FURNITURE_KEY, PersistentDataType.STRING, id);
entity.getPersistentDataContainer().set(FURNITURE_KEY, PersistentDataType.STRING, id);
}
}
}
// 获取家具配置
Key key = Key.of(id);
Optional<CustomFurniture> optionalFurniture = furnitureById(key);
if (optionalFurniture.isPresent()) {
CustomFurniture customFurniture = optionalFurniture.get();
BukkitFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId());
Optional<FurnitureConfig> optionalFurniture = furnitureById(key);
if (optionalFurniture.isEmpty()) return;
// 只对1.20.2及以上生效1.20.1比较特殊
if (!VersionHelper.isOrAbove1_20_2()) {
return;
}
// 已经在其他事件里加载过了
FurnitureConfig customFurniture = optionalFurniture.get();
BukkitFurniture previous = this.byMetaEntityId.get(entity.getEntityId());
if (previous != null) return;
BukkitFurniture furniture = addNewFurniture(display, customFurniture);
furniture.initializeColliders(); // safely do it here
// 创建新的家具
createFurnitureInstance(entity, customFurniture);
}
@SuppressWarnings("deprecation")
protected void handleMetaEntityAfterChunkLoad(ItemDisplay entity) {
// 实体可能不是持久的
if (!entity.isPersistent()) {
return;
}
// 获取家具pdc
String id = entity.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
if (id == null) return;
// 这个区块还处于加载实体中这个时候不处理1.20.1需要特殊处理)
Location location = entity.getLocation();
if (VersionHelper.isOrAbove1_20_2() && !isEntitiesLoaded(location)) {
return;
}
// 获取家具配置
Key key = Key.of(id);
Optional<FurnitureConfig> optionalFurniture = furnitureById(key);
if (optionalFurniture.isEmpty()) return;
// 已经在其他事件里加载过了
FurnitureConfig customFurniture = optionalFurniture.get();
BukkitFurniture previous = this.byMetaEntityId.get(entity.getEntityId());
if (previous != null) return;
createFurnitureInstance(entity, customFurniture);
// 补发一次包,修复
for (Player player : entity.getTrackedPlayers()) {
BukkitAdaptors.adapt(player).sendPacket(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entity.getEntityId(), entity.getUniqueId(), location.getX(), location.getY(), location.getZ(), location.getPitch(), location.getYaw(),
MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
), false);
}
}
public void handleCollisionEntityLoadOnEntitiesLoad(Entity collisionEntity) {
protected void handleCollisionEntityAfterChunkLoad(Entity entity) {
// 如果是碰撞实体,那么就忽略
if (FastNMS.INSTANCE.method$CraftEntity$getHandle(entity) instanceof CollisionEntity) {
return;
}
// 看看有没有碰撞实体的pdc
Byte flag = entity.getPersistentDataContainer().get(FURNITURE_COLLISION, PersistentDataType.BYTE);
if (flag == null || flag != 1) {
return;
}
// 实体未加载
Location location = entity.getLocation();
if (!isEntitiesLoaded(location)) {
return;
}
// 移除被WorldEdit错误复制的碰撞实体
runSafeEntityOperation(location, entity::remove);
}
public void handleCollisionEntityDuringChunkLoad(Entity collisionEntity) {
// faster
if (FastNMS.INSTANCE.method$CraftEntity$getHandle(collisionEntity) instanceof CollisionEntity) {
collisionEntity.remove();
@@ -284,34 +309,43 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
collisionEntity.remove();
}
private FurnitureExtraData getFurnitureExtraData(Entity baseEntity) throws IOException {
private FurnitureDataAccessor getFurnitureDataAccessor(Entity baseEntity) {
byte[] extraData = baseEntity.getPersistentDataContainer().get(FURNITURE_EXTRA_DATA_KEY, PersistentDataType.BYTE_ARRAY);
if (extraData == null) return FurnitureExtraData.builder().build();
return FurnitureExtraData.fromBytes(extraData);
if (extraData == null) return new FurnitureDataAccessor(null);
try {
return FurnitureDataAccessor.fromBytes(extraData);
} catch (IOException e) {
// 损坏了?一般不会
return new FurnitureDataAccessor(null);
}
}
private synchronized BukkitFurniture addNewFurniture(ItemDisplay display, CustomFurniture furniture) {
FurnitureExtraData extraData;
try {
extraData = getFurnitureExtraData(display);
} catch (IOException e) {
extraData = FurnitureExtraData.builder().build();
plugin.logger().warn("Furniture extra data could not be loaded", e);
// 创建家具实例,并初始化碰撞实体
private BukkitFurniture createFurnitureInstance(ItemDisplay display, FurnitureConfig furniture) {
BukkitFurniture bukkitFurniture = new BukkitFurniture(display, furniture, getFurnitureDataAccessor(display));
this.byMetaEntityId.put(display.getEntityId(), bukkitFurniture);
for (int entityId : bukkitFurniture.virtualEntityIds()) {
this.byVirtualEntityId.put(entityId, bukkitFurniture);
}
BukkitFurniture bukkitFurniture = new BukkitFurniture(display, furniture, extraData);
this.furnitureByRealEntityId.put(bukkitFurniture.baseEntityId(), bukkitFurniture);
for (int entityId : bukkitFurniture.entityIds()) {
this.furnitureByEntityId.put(entityId, bukkitFurniture);
}
for (Collider collisionEntity : bukkitFurniture.collisionEntities()) {
int collisionEntityId = FastNMS.INSTANCE.method$Entity$getId(collisionEntity.handle());
this.furnitureByRealEntityId.put(collisionEntityId, bukkitFurniture);
for (Collider collisionEntity : bukkitFurniture.colliders()) {
this.byColliderEntityId.put(collisionEntity.entityId(), bukkitFurniture);
}
Location location = display.getLocation();
runSafeEntityOperation(location, bukkitFurniture::addCollidersToWorld);
return bukkitFurniture;
}
private void runSafeEntityOperation(Location location, Runnable action) {
boolean preventChange = FastNMS.INSTANCE.method$ServerLevel$isPreventingStatusUpdates(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(location.getWorld()), location.getBlockX() >> 4, location.getBlockZ() >> 4);
if (preventChange) {
this.plugin.scheduler().sync().runLater(action, 1, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4);
} else {
action.run();
}
}
@Override
protected HitBoxConfig defaultHitBox() {
return InteractionHitBoxConfig.DEFAULT;
protected FurnitureHitBoxConfig<?> defaultHitBox() {
return InteractionFurnitureHitboxConfig.DEFAULT;
}
}

View File

@@ -1,70 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeat;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.HitBox;
import net.momirealms.craftengine.core.entity.furniture.HitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.HitBoxPart;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.world.EntityHitResult;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.sparrow.nbt.CompoundTag;
import java.util.Optional;
public class BukkitHitBox implements HitBox {
private final Furniture furniture;
private final HitBoxConfig config;
private final HitBoxPart[] parts;
private final Seat<HitBox>[] seats;
public BukkitHitBox(Furniture furniture, HitBoxConfig config, HitBoxPart[] parts) {
this.parts = parts;
this.config = config;
this.furniture = furniture;
this.seats = createSeats(config);
}
@SuppressWarnings("unchecked")
private Seat<HitBox>[] createSeats(HitBoxConfig config) {
SeatConfig[] seatConfigs = config.seats();
Seat<HitBox>[] seats = new Seat[seatConfigs.length];
for (int i = 0; i < seatConfigs.length; i++) {
seats[i] = new BukkitSeat<>(this, seatConfigs[i]);
}
return seats;
}
@Override
public HitBoxPart[] parts() {
return this.parts;
}
@Override
public HitBoxConfig config() {
return this.config;
}
@Override
public Seat<HitBox>[] seats() {
return this.seats;
}
@Override
public Optional<EntityHitResult> clip(Vec3d min, Vec3d max) {
for (HitBoxPart hbe : this.parts) {
Optional<EntityHitResult> result = hbe.aabb().clip(min, max);
if (result.isPresent()) {
return result;
}
}
return Optional.empty();
}
@Override
public void saveCustomData(CompoundTag data) {
data.putString("type", "furniture");
data.putInt("entity_id", this.furniture.baseEntityId());
}
}

View File

@@ -2,6 +2,9 @@ package net.momirealms.craftengine.bukkit.entity.furniture;
import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent;
import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent;
import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.chunk.CEChunk;
import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.event.EventHandler;
@@ -16,9 +19,11 @@ import java.util.List;
public class FurnitureEventListener implements Listener {
private final BukkitFurnitureManager manager;
private final BukkitWorldManager worldManager;
public FurnitureEventListener(final BukkitFurnitureManager manager) {
public FurnitureEventListener(final BukkitFurnitureManager manager, final BukkitWorldManager worldManager) {
this.manager = manager;
this.worldManager = worldManager;
}
/*
@@ -29,21 +34,26 @@ public class FurnitureEventListener implements Listener {
List<Entity> entities = event.getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadEarly(itemDisplay);
this.manager.handleMetaEntityDuringChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadOnEntitiesLoad(entity);
this.manager.handleCollisionEntityDuringChunkLoad(entity);
}
}
CEWorld world = this.worldManager.getWorld(event.getWorld());
CEChunk ceChunk = world.getChunkAtIfLoaded(event.getChunk().getChunkKey());
if (ceChunk != null) {
ceChunk.setEntitiesLoaded(true);
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
public void onWorldLoad(WorldLoadEvent event) {
List<Entity> entities = event.getWorld().getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadEarly(itemDisplay);
this.manager.handleMetaEntityDuringChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadOnEntitiesLoad(entity);
this.manager.handleCollisionEntityDuringChunkLoad(entity);
}
}
}
@@ -52,9 +62,9 @@ public class FurnitureEventListener implements Listener {
public void onEntityLoad(EntityAddToWorldEvent event) {
Entity entity = event.getEntity();
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadLate(itemDisplay, 0);
this.manager.handleMetaEntityAfterChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadLate(entity, 0);
this.manager.handleCollisionEntityAfterChunkLoad(entity);
}
}
@@ -65,8 +75,8 @@ public class FurnitureEventListener implements Listener {
public void onChunkUnload(ChunkUnloadEvent event) {
Entity[] entities = event.getChunk().getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay) {
this.manager.handleBaseEntityUnload(entity);
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity);
}
@@ -77,8 +87,8 @@ public class FurnitureEventListener implements Listener {
public void onWorldUnload(WorldUnloadEvent event) {
List<Entity> entities = event.getWorld().getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay) {
this.manager.handleBaseEntityUnload(entity);
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity);
}
@@ -88,8 +98,8 @@ public class FurnitureEventListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onEntityUnload(EntityRemoveFromWorldEvent event) {
Entity entity = event.getEntity();
if (entity instanceof ItemDisplay) {
this.manager.handleBaseEntityUnload(entity);
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity);
}

View File

@@ -0,0 +1,15 @@
package net.momirealms.craftengine.bukkit.entity.furniture.element;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigs;
public class BukkitFurnitureElementConfigs extends FurnitureElementConfigs {
static {
register(ITEM_DISPLAY, ItemDisplayFurnitureElementConfig.FACTORY);
}
private BukkitFurnitureElementConfigs() {}
public static void init() {
}
}

View File

@@ -0,0 +1,61 @@
package net.momirealms.craftengine.bukkit.entity.furniture.element;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureColorSource;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElement;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class ItemDisplayFurnitureElement implements FurnitureElement {
private final ItemDisplayFurnitureElementConfig config;
private final WorldPosition position;
private final int entityId;
private final Object despawnPacket;
private final FurnitureColorSource colorSource;
public ItemDisplayFurnitureElement(Furniture furniture, ItemDisplayFurnitureElementConfig config) {
this.config = config;
this.entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
WorldPosition furniturePos = furniture.position();
Vec3d position = Furniture.getRelativePosition(furniturePos, config.position());
this.position = new WorldPosition(furniturePos.world, position.x, position.y, position.z, furniturePos.xRot, furniturePos.yRot);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList() {{ add(entityId); }});
this.colorSource = furniture.dataAccessor.getColorSource();
}
@Override
public void show(Player player) {
player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundBundlePacket(List.of(
FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
this.entityId, UUID.randomUUID(),
this.position.x, this.position.y, this.position.z, 0, this.position.yRot,
MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
),
FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadata.apply(player, this.colorSource))
)), false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
@Override
public int[] virtualEntityIds() {
return new int[] {this.entityId};
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
collector.accept(this.entityId);
}
}

View File

@@ -0,0 +1,191 @@
package net.momirealms.craftengine.bukkit.entity.furniture.element;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.entity.display.Billboard;
import net.momirealms.craftengine.core.entity.display.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureColorSource;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigFactory;
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.data.FireworkExplosion;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
public class ItemDisplayFurnitureElementConfig implements FurnitureElementConfig<ItemDisplayFurnitureElement> {
public static final Factory FACTORY = new Factory();
public final BiFunction<Player, FurnitureColorSource, List<Object>> metadata;
public final Key itemId;
public final Vector3f scale;
public final Vector3f position;
public final Vector3f translation;
public final float xRot;
public final float yRot;
public final Quaternionf rotation;
public final ItemDisplayContext displayContext;
public final Billboard billboard;
public final float shadowRadius;
public final float shadowStrength;
public final boolean applyDyedColor;
public ItemDisplayFurnitureElementConfig(Key itemId,
Vector3f scale,
Vector3f position,
Vector3f translation,
float xRot,
float yRot,
Quaternionf rotation,
ItemDisplayContext displayContext,
Billboard billboard,
float shadowRadius,
float shadowStrength,
boolean applyDyedColor) {
this.scale = scale;
this.position = position;
this.translation = translation;
this.xRot = xRot;
this.yRot = yRot;
this.rotation = rotation;
this.displayContext = displayContext;
this.billboard = billboard;
this.shadowRadius = shadowRadius;
this.shadowStrength = shadowStrength;
this.applyDyedColor = applyDyedColor;
this.itemId = itemId;
BiFunction<Player, FurnitureColorSource, Item<?>> itemFunction = (player, colorSource) -> {
Item<ItemStack> wrappedItem = BukkitItemManager.instance().createWrappedItem(itemId, player);
if (applyDyedColor && colorSource != null && wrappedItem != null) {
Optional.ofNullable(colorSource.dyedColor()).ifPresent(wrappedItem::dyedColor);
Optional.ofNullable(colorSource.fireworkColors()).ifPresent(colors -> wrappedItem.fireworkExplosion(new FireworkExplosion(
FireworkExplosion.Shape.SMALL_BALL,
new IntArrayList(colors),
new IntArrayList(),
false,
false
)));
}
return Optional.ofNullable(wrappedItem).orElseGet(() -> BukkitItemManager.instance().createWrappedItem(ItemKeys.BARRIER, null));
};
this.metadata = (player, source) -> {
List<Object> dataValues = new ArrayList<>();
ItemDisplayEntityData.DisplayedItem.addEntityData(itemFunction.apply(player, source).getLiteralObject(), dataValues);
ItemDisplayEntityData.Scale.addEntityData(this.scale, dataValues);
ItemDisplayEntityData.RotationLeft.addEntityData(this.rotation, dataValues);
ItemDisplayEntityData.BillboardConstraints.addEntityData(this.billboard.id(), dataValues);
ItemDisplayEntityData.Translation.addEntityData(this.translation, dataValues);
ItemDisplayEntityData.DisplayType.addEntityData(this.displayContext.id(), dataValues);
ItemDisplayEntityData.ShadowRadius.addEntityData(this.shadowRadius, dataValues);
ItemDisplayEntityData.ShadowStrength.addEntityData(this.shadowStrength, dataValues);
return dataValues;
};
}
public Vector3f scale() {
return this.scale;
}
public Vector3f position() {
return this.position;
}
public Vector3f translation() {
return this.translation;
}
public float xRot() {
return this.xRot;
}
public float yRot() {
return this.yRot;
}
public Quaternionf rotation() {
return this.rotation;
}
public ItemDisplayContext displayContext() {
return this.displayContext;
}
public Billboard billboard() {
return this.billboard;
}
public float shadowRadius() {
return this.shadowRadius;
}
public float shadowStrength() {
return this.shadowStrength;
}
public boolean applyDyedColor() {
return this.applyDyedColor;
}
public Key itemId() {
return this.itemId;
}
@Override
public ItemDisplayFurnitureElement create(@NotNull Furniture furniture) {
return new ItemDisplayFurnitureElement(furniture, this);
}
public static class Factory implements FurnitureElementConfigFactory<ItemDisplayFurnitureElement> {
@Override
public ItemDisplayFurnitureElementConfig create(Map<String, Object> arguments) {
Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.furniture.element.item_display.missing_item"));
boolean applyDyedColor = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("apply-dyed-color", true), "apply-dyed-color");
return new ItemDisplayFurnitureElementConfig(
itemId,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0f), "position"),
ResourceConfigUtils.getAsVector3f(arguments.get("translation"), "translation"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),
ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"),
ResourceConfigUtils.getAsEnum(ResourceConfigUtils.get(arguments, "display-context", "display-transform"), ItemDisplayContext.class, ItemDisplayContext.NONE),
ResourceConfigUtils.getAsEnum(arguments.get("billboard"), Billboard.class, Billboard.FIXED),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-radius", 0f), "shadow-radius"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-strength", 1f), "shadow-strength"),
applyDyedColor
);
}
}
@Override
public String toString() {
return "ItemDisplayFurnitureElementConfig{" +
"metadata=" + metadata +
", itemId=" + itemId +
", scale=" + scale +
", position=" + position +
", translation=" + translation +
", xRot=" + xRot +
", yRot=" + yRot +
", rotation=" + rotation +
", displayContext=" + displayContext +
", billboard=" + billboard +
", shadowRadius=" + shadowRadius +
", shadowStrength=" + shadowStrength +
", applyDyedColor=" + applyDyedColor +
'}';
}
}

View File

@@ -0,0 +1,52 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitCollider;
import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeat;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.collision.AABB;
import net.momirealms.sparrow.nbt.CompoundTag;
public abstract class AbstractFurnitureHitBox implements FurnitureHitBox {
protected final Furniture furniture;
protected Seat<FurnitureHitBox>[] seats;
public AbstractFurnitureHitBox(Furniture furniture, FurnitureHitBoxConfig<?> config) {
this.furniture = furniture;
this.seats = createSeats(config);
}
@SuppressWarnings("unchecked")
private Seat<FurnitureHitBox>[] createSeats(FurnitureHitBoxConfig<?> config) {
SeatConfig[] seatConfigs = config.seats();
Seat<FurnitureHitBox>[] seats = new Seat[seatConfigs.length];
for (int i = 0; i < seatConfigs.length; i++) {
seats[i] = new BukkitSeat<>(this, seatConfigs[i]);
}
return seats;
}
@Override
public void saveCustomData(CompoundTag data) {
data.putString("type", "furniture");
// 用于通过座椅找到原始家具
data.putInt("entity_id", this.furniture.entityId());
}
@Override
public Seat<FurnitureHitBox>[] seats() {
return this.seats;
}
protected Collider createCollider(World world, Vec3d position, AABB ceAABB, boolean canCollide, boolean blocksBuilding, boolean canBeHitByProjectile) {
Object nmsAABB = FastNMS.INSTANCE.constructor$AABB(ceAABB.minX, ceAABB.minY, ceAABB.minZ, ceAABB.maxX, ceAABB.maxY, ceAABB.maxZ);
return new BukkitCollider(world.serverWorld(), nmsAABB, position.x, position.y, position.z, canBeHitByProjectile, canCollide, blocksBuilding);
}
}

View File

@@ -0,0 +1,18 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxTypes;
import net.momirealms.craftengine.core.util.VersionHelper;
public class BukkitFurnitureHitboxTypes extends FurnitureHitBoxTypes {
public static void init() {}
static {
register(INTERACTION, InteractionFurnitureHitboxConfig.FACTORY);
register(SHULKER, ShulkerFurnitureHitboxConfig.FACTORY);
register(CUSTOM, CustomFurnitureHitboxConfig.FACTORY);
if (VersionHelper.isOrAbove1_21_6()) {
register(HAPPY_GHAST, HappyGhastFurnitureHitboxConfig.FACTORY);
}
}
}

View File

@@ -1,15 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.core.entity.furniture.HitBoxTypes;
public class BukkitHitBoxTypes extends HitBoxTypes {
public static void init() {}
static {
register(INTERACTION, InteractionHitBoxConfig.FACTORY);
register(SHULKER, ShulkerHitBoxConfig.FACTORY);
register(HAPPY_GHAST, HappyGhastHitBoxConfig.FACTORY);
register(CUSTOM, CustomHitBoxConfig.FACTORY);
}
}

View File

@@ -0,0 +1,90 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class CustomFurnitureHitbox extends AbstractFurnitureHitBox {
private final CustomFurnitureHitboxConfig config;
private final Collider collider;
private final Object spawnPacket;
private final Object despawnPacket;
private final FurnitureHitboxPart part;
private final int entityId;
public CustomFurnitureHitbox(Furniture furniture, CustomFurnitureHitboxConfig config) {
super(furniture, config);
this.config = config;
WorldPosition position = furniture.position();
Vec3d pos = Furniture.getRelativePosition(position, config.position());
AABB aabb = AABB.makeBoundingBox(pos, config.width(), config.height());
this.collider = createCollider(furniture.world(), pos, aabb, false, config.blocksBuilding(), config.canBeHitByProjectile());
int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
List<Object> packets = new ArrayList<>(3);
packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId, UUID.randomUUID(), position.x, position.y, position.z, 0, position.yRot,
config.entityType(), 0, CoreReflections.instance$Vec3$Zero, 0
));
packets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId, config.cachedValues()));
if (VersionHelper.isOrAbove1_20_5()) {
try {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, config.scale());
packets.add(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityId, Collections.singletonList(attributeInstance)));
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to apply scale attribute", e);
}
}
this.spawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
this.part = new FurnitureHitboxPart(entityId, aabb, pos, false);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList() {{ add(entityId); }});
this.entityId = entityId;
}
@Override
public List<Collider> colliders() {
return List.of(this.collider);
}
@Override
public List<FurnitureHitboxPart> parts() {
return List.of(this.part);
}
@Override
public void show(Player player) {
player.sendPacket(this.spawnPacket, false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
collector.accept(this.entityId);
}
@Override
public CustomFurnitureHitboxConfig config() {
return this.config;
}
}

View File

@@ -0,0 +1,114 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.AbstractFurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class CustomFurnitureHitboxConfig extends AbstractFurnitureHitBoxConfig<CustomFurnitureHitbox> {
public static final Factory FACTORY = new Factory();
private final float scale;
private final Object entityType;
private final List<Object> cachedValues = new ArrayList<>();
private final float width;
private final float height;
public CustomFurnitureHitboxConfig(SeatConfig[] seats,
Vector3f position,
boolean canUseItemOn,
boolean blocksBuilding,
boolean canBeHitByProjectile,
float width,
float height,
boolean fixed,
float scale,
Object type) {
super(seats, position, canUseItemOn, blocksBuilding, canBeHitByProjectile);
this.scale = scale;
this.entityType = type;
this.width = fixed ? width : width * scale;
this.height = fixed ? height : height * scale;
BaseEntityData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedValues);
BaseEntityData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedValues);
BaseEntityData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues);
}
public float scale() {
return this.scale;
}
public Object entityType() {
return this.entityType;
}
public List<Object> cachedValues() {
return this.cachedValues;
}
public float width() {
return width;
}
public float height() {
return height;
}
@Override
public void prepareForPlacement(WorldPosition targetPos, Consumer<AABB> aabbConsumer) {
if (this.blocksBuilding) {
Vec3d relativePosition = Furniture.getRelativePosition(targetPos, this.position);
aabbConsumer.accept(AABB.makeBoundingBox(relativePosition, this.width, this.height));
}
}
@Override
public CustomFurnitureHitbox create(Furniture furniture) {
return new CustomFurnitureHitbox(furniture, this);
}
public static class Factory implements FurnitureHitBoxConfigFactory<CustomFurnitureHitbox> {
@Override
public CustomFurnitureHitboxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
float scale = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1), "scale");
String type = (String) arguments.getOrDefault("entity-type", "slime");
Object nmsEntityType = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.ENTITY_TYPE, KeyUtils.toResourceLocation(Key.of(type)));
if (nmsEntityType == null) {
throw new LocalizedResourceConfigException("warning.config.furniture.hitbox.custom.invalid_entity", new IllegalArgumentException("EntityType not found: " + type), type);
}
float width;
float height;
boolean fixed;
try {
Object dimensions = CoreReflections.field$EntityType$dimensions.get(nmsEntityType);
width = CoreReflections.field$EntityDimensions$width.getFloat(dimensions);
height = CoreReflections.field$EntityDimensions$height.getFloat(dimensions);
fixed = CoreReflections.field$EntityDimensions$fixed.getBoolean(dimensions);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to get dimensions for " + nmsEntityType, e);
}
boolean canUseItemOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", false), "can-use-item-on");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", false), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new CustomFurnitureHitboxConfig(SeatConfig.fromObj(arguments.get("seats")), position, canUseItemOn, blocksBuilding, canBeHitByProjectile, width, height, fixed, scale, nmsEntityType);
}
}
}

View File

@@ -1,99 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.entity.EntityType;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class CustomHitBoxConfig extends AbstractHitBoxConfig {
public static final Factory FACTORY = new Factory();
private final float scale;
private final EntityType entityType;
private final List<Object> cachedValues = new ArrayList<>();
public CustomHitBoxConfig(SeatConfig[] seats, Vector3f position, EntityType type, float scale, boolean blocksBuilding, boolean canBeHitByProjectile) {
super(seats, position, false, blocksBuilding, canBeHitByProjectile);
this.scale = scale;
this.entityType = type;
BaseEntityData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedValues);
BaseEntityData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedValues);
BaseEntityData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues);
}
public EntityType entityType() {
return this.entityType;
}
public float scale() {
return this.scale;
}
@Override
public Key type() {
return HitBoxTypes.CUSTOM;
}
@Override
public void initPacketsAndColliders(int[] entityId, WorldPosition position, Quaternionf conjugated, BiConsumer<Object, Boolean> packets, Consumer<Collider> collider, Consumer<HitBoxPart> aabb) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
try {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId[0], UUID.randomUUID(), position.x() + offset.x, position.y() + offset.y, position.z() - offset.z, 0, position.yRot(),
FastNMS.INSTANCE.method$CraftEntityType$toNMSEntityType(this.entityType), 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId[0], List.copyOf(this.cachedValues)), true);
if (VersionHelper.isOrAbove1_20_5() && this.scale != 1) {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale);
packets.accept(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityId[0], Collections.singletonList(attributeInstance)), false);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct custom hitbox spawn packet", e);
}
}
@Override
public void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<AABB> aabbs) {
}
@Override
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
return new int[] {entityIdSupplier.get()};
}
public static class Factory implements HitBoxConfigFactory {
@Override
public HitBoxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
float scale = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1), "scale");
String type = (String) arguments.getOrDefault("entity-type", "slime");
EntityType entityType = Registry.ENTITY_TYPE.get(new NamespacedKey("minecraft", type));
if (entityType == null) {
throw new LocalizedResourceConfigException("warning.config.furniture.hitbox.custom.invalid_entity", new IllegalArgumentException("EntityType not found: " + type), type);
}
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", false), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new CustomHitBoxConfig(SeatConfig.fromObj(arguments.get("seats")), position, entityType, scale, blocksBuilding, canBeHitByProjectile);
}
}
}

View File

@@ -0,0 +1,95 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class HappyGhastFurnitureHitbox extends AbstractFurnitureHitBox {
private final HappyGhastFurnitureHitboxConfig config;
private final Collider collider;
private final Object despawnPacket;
private final FurnitureHitboxPart part;
private final Vec3d pos;
private final List<Object> packets;
private final int entityId;
private final float yaw;
public HappyGhastFurnitureHitbox(Furniture furniture, HappyGhastFurnitureHitboxConfig config) {
super(furniture, config);
this.config = config;
WorldPosition position = furniture.position();
this.pos = Furniture.getRelativePosition(position, config.position());
double bbSize = 4 * config.scale();
AABB aabb = AABB.makeBoundingBox(this.pos, bbSize, bbSize);
this.yaw = position.yRot;
this.entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
this.packets = new ArrayList<>(3);
this.packets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, config.cachedValues()));
if (config.scale() != 1) {
try {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, config.scale());
this.packets.add(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(this.entityId, Collections.singletonList(attributeInstance)));
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to apply scale attribute", e);
}
}
this.packets.add(FastNMS.INSTANCE.constructor$ClientboundEntityPositionSyncPacket(this.entityId, this.pos.x, this.pos.y, this.pos.z, 0, position.yRot, false));
this.collider = createCollider(furniture.world(), this.pos, aabb, config.hardCollision(), config.blocksBuilding(), config.canBeHitByProjectile());
this.part = new FurnitureHitboxPart(this.entityId, aabb, this.pos, false);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList() {{ add(entityId); }});
}
@Override
public List<Collider> colliders() {
return List.of(this.collider);
}
@Override
public List<FurnitureHitboxPart> parts() {
return List.of(this.part);
}
@Override
public void show(Player player) {
List<Object> packets = new ArrayList<>();
packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
this.entityId, UUID.randomUUID(), this.pos.x, player.y() - (this.config.scale() * 4 + 16), this.pos.z, 0, this.yaw,
MEntityTypes.HAPPY_GHAST, 0, CoreReflections.instance$Vec3$Zero, 0
));
packets.addAll(this.packets);
player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets), false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
collector.accept(this.entityId);
}
@Override
public HappyGhastFurnitureHitboxConfig config() {
return this.config;
}
}

View File

@@ -0,0 +1,83 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.HappyGhastData;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.AbstractFurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class HappyGhastFurnitureHitboxConfig extends AbstractFurnitureHitBoxConfig<HappyGhastFurnitureHitbox> {
public static final Factory FACTORY = new Factory();
private final double scale;
private final boolean hardCollision;
private final List<Object> cachedValues = new ArrayList<>(3);
public HappyGhastFurnitureHitboxConfig(SeatConfig[] seats,
Vector3f position,
boolean canUseItemOn,
boolean blocksBuilding,
boolean canBeHitByProjectile,
double scale,
boolean hardCollision) {
super(seats, position, canUseItemOn, blocksBuilding, canBeHitByProjectile);
this.scale = scale;
this.hardCollision = hardCollision;
HappyGhastData.StaysStill.addEntityDataIfNotDefaultValue(hardCollision, this.cachedValues);
HappyGhastData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedValues); // NO AI
HappyGhastData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues); // Invisible
}
public double scale() {
return scale;
}
public boolean hardCollision() {
return hardCollision;
}
public List<Object> cachedValues() {
return cachedValues;
}
@Override
public HappyGhastFurnitureHitbox create(Furniture furniture) {
return new HappyGhastFurnitureHitbox(furniture, this);
}
@Override
public void prepareForPlacement(WorldPosition targetPos, Consumer<AABB> aabbConsumer) {
if (this.blocksBuilding) {
Vec3d relativePosition = Furniture.getRelativePosition(targetPos, this.position);
aabbConsumer.accept(AABB.makeBoundingBox(relativePosition, 4 * this.scale, 4 * this.scale));
}
}
public static class Factory implements FurnitureHitBoxConfigFactory<HappyGhastFurnitureHitbox> {
@Override
public FurnitureHitBoxConfig<HappyGhastFurnitureHitbox> create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0), "position");
boolean canUseItemOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", true), "can-use-item-on");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", true), "can-be-hit-by-projectile");
double scale = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("scale", 1), "scale");
boolean hardCollision = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("hard-collision", true), "hard-collision");
return new HappyGhastFurnitureHitboxConfig(
SeatConfig.fromObj(arguments.get("seats")),
position, canUseItemOn, blocksBuilding, canBeHitByProjectile,
scale, hardCollision
);
}
}
}

View File

@@ -1,138 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.HappyGhastData;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitCollider;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class HappyGhastHitBoxConfig extends AbstractHitBoxConfig {
public static final Factory FACTORY = new Factory();
private final double scale;
private final boolean hardCollision;
private final List<Object> cachedValues = new ArrayList<>();
public HappyGhastHitBoxConfig(SeatConfig[] seats, Vector3f position, double scale, boolean canUseOn, boolean blocksBuilding, boolean canBeHitByProjectile, boolean hardCollision) {
super(seats, position, canUseOn, blocksBuilding, canBeHitByProjectile);
this.scale = scale;
this.hardCollision = hardCollision;
HappyGhastData.StaysStill.addEntityDataIfNotDefaultValue(hardCollision, this.cachedValues);
HappyGhastData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedValues); // NO AI
HappyGhastData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues); // Invisible
}
@Override
public Key type() {
return HitBoxTypes.HAPPY_GHAST;
}
public double scale() {
return this.scale;
}
public boolean hardCollision() {
return this.hardCollision;
}
@Override
public void initPacketsAndColliders(int[] entityIds,
WorldPosition position,
Quaternionf conjugated,
BiConsumer<Object, Boolean> packets,
Consumer<Collider> collider,
Consumer<HitBoxPart> aabb) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
try {
double x = position.x();
double y = position.y();
double z = position.z();
float yaw = position.yRot();
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[0], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw,
MEntityTypes.HAPPY_GHAST, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[0], List.copyOf(this.cachedValues)), true);
if (VersionHelper.isOrAbove1_20_5() && this.scale != 1) {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale);
packets.accept(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[0], Collections.singletonList(attributeInstance)), false);
}
if (this.hardCollision) {
collider.accept(this.createCollider(position.world(), offset, x, y, z, entityIds[0], aabb));
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct custom hitbox spawn packet", e);
}
}
public Collider createCollider(World world, Vector3f offset, double x, double y, double z, int entityId, Consumer<HitBoxPart> aabb) {
AABB ceAABB = createAABB(offset, x, y, z);
Object level = world.serverWorld();
Object nmsAABB = FastNMS.INSTANCE.constructor$AABB(ceAABB.minX, ceAABB.minY, ceAABB.minZ, ceAABB.maxX, ceAABB.maxY, ceAABB.maxZ);
aabb.accept(new HitBoxPart(entityId, ceAABB, new Vec3d(x, y, z)));
return new BukkitCollider(level, nmsAABB, x, y, z, this.canBeHitByProjectile(), true, this.blocksBuilding());
}
public AABB createAABB(Vector3f offset, double x, double y, double z) {
double baseSize = 4.0 * this.scale;
double halfSize = baseSize * 0.5;
double minX = x - halfSize + offset.x();
double maxX = x + halfSize + offset.x();
double minY = y + offset.y();
double maxY = y + baseSize + offset.y();
double minZ = z - halfSize - offset.z();
double maxZ = z + halfSize - offset.z();
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
@Override
public void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<AABB> aabbs) {
if (!this.hardCollision) return;
Vector3f offset = conjugated.transform(new Vector3f(position()));
AABB aabb = createAABB(offset, x, y, z);
aabbs.accept(aabb);
}
@Override
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
return new int[] {entityIdSupplier.get()};
}
public static class Factory implements HitBoxConfigFactory {
@Override
public HitBoxConfig create(Map<String, Object> arguments) {
if (!VersionHelper.isOrAbove1_21_6()) {
throw new UnsupportedOperationException("HappyGhastHitBox is only supported on 1.21.6+");
}
double scale = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("scale", 1), "scale");
boolean hardCollision = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("hard-collision", true), "hard-collision");
boolean canUseOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", true), "can-use-item-on");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", true), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new HappyGhastHitBoxConfig(
SeatConfig.fromObj(arguments.get("seats")),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"),
scale, canUseOn, blocksBuilding, canBeHitByProjectile, hardCollision
);
}
}
}

View File

@@ -0,0 +1,76 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class InteractionFurnitureHitbox extends AbstractFurnitureHitBox {
private final InteractionFurnitureHitboxConfig config;
private final Collider collider;
private final Object spawnPacket;
private final Object despawnPacket;
private final FurnitureHitboxPart part;
private final int entityId;
public InteractionFurnitureHitbox(Furniture furniture, InteractionFurnitureHitboxConfig config) {
super(furniture, config);
this.config = config;
WorldPosition position = furniture.position();
Vec3d pos = Furniture.getRelativePosition(position, config.position());
AABB aabb = AABB.makeBoundingBox(pos, config.size().x, config.size().y);
this.collider = createCollider(furniture.world(), pos, aabb, false, config.blocksBuilding(), config.canBeHitByProjectile());
int interactionId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
this.spawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(List.of(
FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
interactionId, UUID.randomUUID(), pos.x, pos.y, pos.z, 0, position.yRot,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
),
FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(interactionId, config.cachedValues())
));
this.part = new FurnitureHitboxPart(interactionId, aabb, pos, config.responsive());
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList() {{ add(interactionId); }});
this.entityId = interactionId;
}
@Override
public List<Collider> colliders() {
return List.of(this.collider);
}
@Override
public List<FurnitureHitboxPart> parts() {
return List.of(this.part);
}
@Override
public InteractionFurnitureHitboxConfig config() {
return this.config;
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
collector.accept(this.entityId);
}
@Override
public void show(Player player) {
player.sendPacket(this.spawnPacket, false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
}

View File

@@ -0,0 +1,113 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.entity.data.InteractionEntityData;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.AbstractFurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class InteractionFurnitureHitboxConfig extends AbstractFurnitureHitBoxConfig<InteractionFurnitureHitbox> {
public static final Factory FACTORY = new Factory();
public static final InteractionFurnitureHitboxConfig DEFAULT = new InteractionFurnitureHitboxConfig();
private final Vector3f size;
private final boolean responsive;
private final boolean invisible;
private final List<Object> cachedValues = new ArrayList<>(4);
public InteractionFurnitureHitboxConfig(SeatConfig[] seats,
Vector3f position,
boolean canUseItemOn,
boolean blocksBuilding,
boolean canBeHitByProjectile,
boolean invisible,
Vector3f size,
boolean interactive) {
super(seats, position, canUseItemOn, blocksBuilding, canBeHitByProjectile);
this.size = size;
this.responsive = interactive;
this.invisible = invisible;
InteractionEntityData.Height.addEntityDataIfNotDefaultValue(size.y, cachedValues);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(size.x, cachedValues);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedValues);
if (invisible) {
BaseEntityData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, cachedValues);
}
}
private InteractionFurnitureHitboxConfig() {
super(new SeatConfig[0], new Vector3f(), false, false, false);
this.size = new Vector3f(1);
this.responsive = true;
this.invisible = false;
}
public Vector3f size() {
return size;
}
public boolean responsive() {
return responsive;
}
public boolean invisible() {
return invisible;
}
public List<Object> cachedValues() {
return cachedValues;
}
@Override
public void prepareForPlacement(WorldPosition targetPos, Consumer<AABB> aabbConsumer) {
if (this.blocksBuilding) {
Vec3d relativePosition = Furniture.getRelativePosition(targetPos, this.position);
aabbConsumer.accept(AABB.makeBoundingBox(relativePosition, size.x, size.y));
}
}
@Override
public InteractionFurnitureHitbox create(Furniture furniture) {
return new InteractionFurnitureHitbox(furniture, this);
}
public static class Factory implements FurnitureHitBoxConfigFactory<InteractionFurnitureHitbox> {
@Override
public InteractionFurnitureHitboxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0), "position");
float width;
float height;
if (arguments.containsKey("scale")) {
String[] split = arguments.get("scale").toString().split(",");
width = Float.parseFloat(split[0]);
height = Float.parseFloat(split[1]);
} else {
width = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("width", 1), "width");
height = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("height", 1), "height");
}
boolean canUseOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", false), "can-use-item-on");
boolean interactive = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interactive", true), "interactive");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", false), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
boolean invisible = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("invisible", false), "invisible");
return new InteractionFurnitureHitboxConfig(
SeatConfig.fromObj(arguments.get("seats")),
position, canUseOn, blocksBuilding, canBeHitByProjectile, invisible,
new Vector3f(width, height, width),
interactive
);
}
}
}

View File

@@ -1,123 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.InteractionEntityData;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitCollider;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class InteractionHitBoxConfig extends AbstractHitBoxConfig {
public static final Factory FACTORY = new Factory();
public static final InteractionHitBoxConfig DEFAULT = new InteractionHitBoxConfig(new SeatConfig[0], new Vector3f(), new Vector3f(1,1,1), true, false, false, false);
private final Vector3f size;
private final boolean responsive;
private final List<Object> cachedValues = new ArrayList<>();
public InteractionHitBoxConfig(SeatConfig[] seats, Vector3f position, Vector3f size, boolean responsive, boolean canUseOn, boolean blocksBuilding, boolean canBeHitByProjectile) {
super(seats, position, canUseOn, blocksBuilding, canBeHitByProjectile);
this.size = size;
this.responsive = responsive;
InteractionEntityData.Height.addEntityDataIfNotDefaultValue(size.y, cachedValues);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(size.x, cachedValues);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(responsive, cachedValues);
}
public boolean responsive() {
return this.responsive;
}
public Vector3f size() {
return this.size;
}
@Override
public Key type() {
return HitBoxTypes.INTERACTION;
}
@Override
public void initPacketsAndColliders(int[] entityId,
WorldPosition position,
Quaternionf conjugated,
BiConsumer<Object, Boolean> packets,
Consumer<Collider> collider,
Consumer<HitBoxPart> aabb) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
double x = position.x();
double y = position.y();
double z = position.z();
float yaw = position.yRot();
Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y, z - offset.z);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId[0], UUID.randomUUID(), vec3d.x, vec3d.y, vec3d.z, 0, yaw,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId[0], List.copyOf(this.cachedValues)), true);
aabb.accept(new HitBoxPart(entityId[0], AABB.fromInteraction(vec3d, this.size.x, this.size.y), vec3d));
if (blocksBuilding() || this.canBeHitByProjectile()) {
AABB ceAABB = AABB.fromInteraction(vec3d, this.size.x, this.size.y);
Object nmsAABB = FastNMS.INSTANCE.constructor$AABB(ceAABB.minX, ceAABB.minY, ceAABB.minZ, ceAABB.maxX, ceAABB.maxY, ceAABB.maxZ);
collider.accept(new BukkitCollider(position.world().serverWorld(), nmsAABB, x, y, z, this.canBeHitByProjectile(), false, this.blocksBuilding()));
}
}
@Override
public void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<AABB> aabbs) {
if (blocksBuilding()) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
AABB ceAABB = AABB.fromInteraction(new Vec3d(x + offset.x, y + offset.y, z - offset.z), this.size.x, this.size.y);
aabbs.accept(ceAABB);
}
}
@Override
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
return new int[] {entityIdSupplier.get()};
}
public static class Factory implements HitBoxConfigFactory {
@Override
public HitBoxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
float width;
float height;
if (arguments.containsKey("scale")) {
String[] split = arguments.get("scale").toString().split(",");
width = Float.parseFloat(split[0]);
height = Float.parseFloat(split[1]);
} else {
width = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("width", 1), "width");
height = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("height", 1), "height");
}
boolean canUseOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", false), "can-use-item-on");
boolean interactive = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interactive", true), "interactive");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", false), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new InteractionHitBoxConfig(
SeatConfig.fromObj(arguments.get("seats")),
position,
new Vector3f(width, height, width),
interactive, canUseOn, blocksBuilding, canBeHitByProjectile
);
}
}
}

View File

@@ -0,0 +1,139 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class ShulkerFurnitureHitbox extends AbstractFurnitureHitBox {
private final ShulkerFurnitureHitboxConfig config;
private final List<FurnitureHitboxPart> parts;
private final List<Collider> colliders;
private final Object spawnPacket;
private final Object despawnPacket;
private final int[] entityIds;
public ShulkerFurnitureHitbox(Furniture furniture, ShulkerFurnitureHitboxConfig config) {
super(furniture, config);
this.config = config;
this.entityIds = acquireEntityIds(CoreReflections.instance$Entity$ENTITY_COUNTER::incrementAndGet);
WorldPosition position = furniture.position();
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0f, (float) Math.toRadians(180 - position.yRot()), 0f).conjugate();
Vector3f offset = conjugated.transform(new Vector3f(config.position()));
double x = position.x();
double y = position.y();
double z = position.z();
float yaw = position.yRot();
double originalY = y + offset.y;
double integerPart = Math.floor(originalY);
double fractionalPart = originalY - integerPart;
double processedY = (fractionalPart >= 0.5) ? integerPart + 1 : originalY;
List<Object> packets = new ArrayList<>();
List<Collider> colliders = new ArrayList<>();
List<FurnitureHitboxPart> parts = new ArrayList<>();
packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[0], UUID.randomUUID(), x + offset.x, originalY, z - offset.z, 0, yaw,
MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
));
packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[1], UUID.randomUUID(), x + offset.x, processedY, z - offset.z, 0, yaw,
MEntityTypes.SHULKER, 0, CoreReflections.instance$Vec3$Zero, 0
));
packets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], config.cachedShulkerValues()));
packets.add(FastNMS.INSTANCE.constructor$ClientboundSetPassengersPacket(entityIds[0], entityIds[1]));
// fix some special occasions
if (originalY != processedY) {
double deltaY = originalY - processedY;
short ya = (short) (deltaY * 8192);
try {
packets.add(NetworkReflections.constructor$ClientboundMoveEntityPacket$Pos.newInstance(
entityIds[1], (short) 0, ya, (short) 0, true
));
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to construct ClientboundMoveEntityPacket$Pos", e);
}
}
if (VersionHelper.isOrAbove1_20_5() && config.scale() != 1) {
try {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, config.scale());
packets.add(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[1], Collections.singletonList(attributeInstance)));
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to apply scale attribute", e);
}
}
config.spawner().accept(entityIds, position.world(), x, y, z, yaw, offset, packets::add, colliders::add, parts::add);
this.parts = parts;
this.colliders = colliders;
this.spawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList(entityIds));
}
@Override
public List<Collider> colliders() {
return this.colliders;
}
@Override
public List<FurnitureHitboxPart> parts() {
return this.parts;
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
for (int entityId : entityIds) {
collector.accept(entityId);
}
}
@Override
public void show(Player player) {
player.sendPacket(this.spawnPacket, false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
@Override
public ShulkerFurnitureHitboxConfig config() {
return this.config;
}
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
if (config.interactionEntity()) {
if (config.direction().stepY() != 0) {
// 展示实体 // 潜影贝 // 交互实体
return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()};
} else {
// 展示实体 // 潜影贝 // 交互实体1 // 交互实体2
return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()};
}
} else {
// 展示实体 // 潜影贝
return new int[] {entityIdSupplier.get(), entityIdSupplier.get()};
}
}
}

View File

@@ -5,13 +5,18 @@ import net.momirealms.craftengine.bukkit.entity.data.ShulkerData;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitCollider;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.AbstractFurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
@@ -19,30 +24,39 @@ import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
public class ShulkerFurnitureHitboxConfig extends AbstractFurnitureHitBoxConfig<ShulkerFurnitureHitbox> {
public static final Factory FACTORY = new Factory();
// 1.20.6+
private final float scale;
private final byte peek;
private final boolean interactive;
private final boolean interactionEntity;
private final Direction direction;
private final List<Object> cachedShulkerValues = new ArrayList<>();
private final DirectionalShulkerSpawner spawner;
private final List<Object> cachedShulkerValues = new ArrayList<>(6);
private final AABBCreator aabbCreator;
public ShulkerHitBoxConfig(SeatConfig[] seats, Vector3f position, Direction direction, float scale, byte peek, boolean interactionEntity, boolean interactive, boolean canUseOn, boolean blocksBuilding, boolean canBeHitByProjectile) {
super(seats, position, canUseOn, blocksBuilding, canBeHitByProjectile);
this.direction = direction;
public ShulkerFurnitureHitboxConfig(SeatConfig[] seats,
Vector3f position,
boolean canUseItemOn,
boolean blocksBuilding,
boolean canBeHitByProjectile,
float scale,
byte peek,
boolean interactive,
boolean interactionEntity,
Direction direction) {
super(seats, position, canUseItemOn, blocksBuilding, canBeHitByProjectile);
this.scale = scale;
this.peek = peek;
this.interactive = interactive;
this.interactionEntity = interactionEntity;
this.direction = direction;
ShulkerData.Peek.addEntityDataIfNotDefaultValue(peek, this.cachedShulkerValues);
ShulkerData.Color.addEntityDataIfNotDefaultValue((byte) 0, this.cachedShulkerValues);
@@ -51,9 +65,10 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
ShulkerData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedShulkerValues); // NO AI
ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedShulkerValues); // Invisible
float shulkerHeight = (getPhysicalPeek(peek * 0.01F) + 1) * scale;
List<Object> cachedInteractionValues = new ArrayList<>();
if (this.direction == Direction.UP) {
InteractionEntityData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, cachedInteractionValues);
float shulkerHeight = (getPhysicalPeek(peek * 0.01F) + 1) * scale;
if (direction == Direction.UP) {
InteractionEntityData.Height.addEntityDataIfNotDefaultValue(shulkerHeight + 0.01f, cachedInteractionValues);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues);
@@ -63,32 +78,28 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)), true);
if (canUseOn) {
));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)));
Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y, z - offset.z);
aabb.accept(new HitBoxPart(entityIds[2], AABB.fromInteraction(vec3d, scale, shulkerHeight), vec3d));
}
aabb.accept(new FurnitureHitboxPart(entityIds[2], AABB.makeBoundingBox(vec3d, scale, shulkerHeight), vec3d, interactive));
}
};
this.aabbCreator = (x, y, z, yaw, offset) -> createAABB(Direction.UP, offset, x, y, z);
} else if (this.direction == Direction.DOWN) {
} else if (direction == Direction.DOWN) {
InteractionEntityData.Height.addEntityDataIfNotDefaultValue(shulkerHeight + 0.01f, cachedInteractionValues);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues);
this.spawner = (entityIds, world, x, y, z, yaw, offset, packets, collider, aabb) -> {
collider.accept(this.createCollider(Direction.DOWN, world, offset, x, y, z, entityIds[1], aabb));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(CoreReflections.instance$Direction$UP))), false);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(CoreReflections.instance$Direction$UP))));
if (interactionEntity) {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f - shulkerHeight + scale, z - offset.z, 0, yaw,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)), true);
if (canUseOn) {
));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)));
Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y - shulkerHeight + scale, z - offset.z);
aabb.accept(new HitBoxPart(entityIds[2], AABB.fromInteraction(vec3d, scale, shulkerHeight), vec3d));
}
aabb.accept(new FurnitureHitboxPart(entityIds[2], AABB.makeBoundingBox(vec3d, scale, shulkerHeight), vec3d, interactive));
}
};
this.aabbCreator = (x, y, z, yaw, offset) -> createAABB(Direction.DOWN, offset, x, y, z);
@@ -100,27 +111,25 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
Direction shulkerAnchor = getOriginalDirection(direction, Direction.fromYaw(yaw));
Direction shulkerDirection = shulkerAnchor.opposite();
collider.accept(this.createCollider(shulkerDirection, world, offset, x, y, z, entityIds[1], aabb));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(shulkerAnchor)))), false);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(shulkerAnchor)))));
if (interactionEntity) {
// first interaction
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)), true);
));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)));
// second interaction
double distance = shulkerHeight - scale;
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[3], UUID.randomUUID(), x + offset.x + shulkerDirection.stepX() * distance, y + offset.y - 0.005f, z - offset.z + shulkerDirection.stepZ() * distance, 0, yaw,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[3], List.copyOf(cachedInteractionValues)), true);
if (canUseOn) {
));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[3], List.copyOf(cachedInteractionValues)));
Vec3d vec3d1 = new Vec3d(x + offset.x, y + offset.y, z - offset.z);
Vec3d vec3d2 = new Vec3d(x + offset.x + shulkerDirection.stepX() * distance, y + offset.y, z - offset.z + shulkerDirection.stepZ() * distance);
aabb.accept(new HitBoxPart(entityIds[2], AABB.fromInteraction(vec3d1, scale, scale), vec3d1));
aabb.accept(new HitBoxPart(entityIds[3], AABB.fromInteraction(vec3d2, scale, scale), vec3d2));
}
aabb.accept(new FurnitureHitboxPart(entityIds[2], AABB.makeBoundingBox(vec3d1, scale, scale), vec3d1, interactive));
aabb.accept(new FurnitureHitboxPart(entityIds[3], AABB.makeBoundingBox(vec3d2, scale, scale), vec3d2, interactive));
}
};
this.aabbCreator = (x, y, z, yaw, offset) -> {
@@ -131,16 +140,86 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
}
}
public Collider createCollider(Direction direction, World world, Vector3f offset, double x, double y, double z, int entityId, Consumer<HitBoxPart> aabb) {
public static float getPhysicalPeek(float peek) {
return 0.5F - MiscUtils.sin((0.5F + peek) * 3.1415927F) * 0.5F;
}
@Override
public void prepareForPlacement(WorldPosition targetPos, Consumer<AABB> aabbConsumer) {
if (this.blocksBuilding) {
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0f, (float) Math.toRadians(180 - targetPos.yRot()), 0f).conjugate();
Vector3f offset = conjugated.transform(new Vector3f(position()));
aabbConsumer.accept(this.aabbCreator.create(targetPos.x, targetPos.y, targetPos.z, targetPos.yRot, offset));
}
}
public float scale() {
return this.scale;
}
public byte peek() {
return this.peek;
}
public boolean interactive() {
return this.interactive;
}
public boolean interactionEntity() {
return this.interactionEntity;
}
public Direction direction() {
return this.direction;
}
public DirectionalShulkerSpawner spawner() {
return this.spawner;
}
public List<Object> cachedShulkerValues() {
return this.cachedShulkerValues;
}
@Override
public ShulkerFurnitureHitbox create(Furniture furniture) {
return new ShulkerFurnitureHitbox(furniture, this);
}
@FunctionalInterface
public interface AABBCreator {
AABB create(double x, double y, double z, float yaw, Vector3f offset);
}
@FunctionalInterface
public interface DirectionalShulkerSpawner {
void accept(int[] entityIds,
World world,
double x,
double y,
double z,
float yaw,
Vector3f offset,
Consumer<Object> packets,
Consumer<Collider> collider,
Consumer<FurnitureHitboxPart> aabb);
}
public Collider createCollider(Direction direction, World world,
Vector3f offset, double x, double y, double z,
int entityId,
Consumer<FurnitureHitboxPart> aabb) {
AABB ceAABB = createAABB(direction, offset, x, y, z);
Object level = world.serverWorld();
Object nmsAABB = FastNMS.INSTANCE.constructor$AABB(ceAABB.minX, ceAABB.minY, ceAABB.minZ, ceAABB.maxX, ceAABB.maxY, ceAABB.maxZ);
aabb.accept(new HitBoxPart(entityId, ceAABB, new Vec3d(x, y, z)));
aabb.accept(new FurnitureHitboxPart(entityId, ceAABB, new Vec3d(x, y, z), false));
return new BukkitCollider(level, nmsAABB, x, y, z, this.canBeHitByProjectile(), true, this.blocksBuilding());
}
public AABB createAABB(Direction direction, Vector3f offset, double x, double y, double z) {
float peek = getPhysicalPeek(this.peek() * 0.01F);
public AABB createAABB(Direction direction, Vector3f relativePos, double x, double y, double z) {
float peek = getPhysicalPeek(this.peek * 0.01F);
double x1 = -this.scale * 0.5;
double y1 = 0.0;
double z1 = -this.scale * 0.5;
@@ -166,156 +245,15 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
} else if (dz < 0) {
z1 += dz;
}
double minX = x + x1 + offset.x();
double maxX = x + x2 + offset.x();
double minY = y + y1 + offset.y();
double maxY = y + y2 + offset.y();
double minZ = z + z1 - offset.z();
double maxZ = z + z2 - offset.z();
double minX = x + x1 + relativePos.x();
double maxX = x + x2 + relativePos.x();
double minY = y + y1 + relativePos.y();
double maxY = y + y2 + relativePos.y();
double minZ = z + z1 - relativePos.z();
double maxZ = z + z2 - relativePos.z();
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
private static float getPhysicalPeek(float peek) {
return 0.5F - MiscUtils.sin((0.5F + peek) * 3.1415927F) * 0.5F;
}
public boolean interactionEntity() {
return interactionEntity;
}
public boolean interactive() {
return interactive;
}
public Direction direction() {
return direction;
}
public byte peek() {
return peek;
}
public float scale() {
return scale;
}
@Override
public Key type() {
return HitBoxTypes.SHULKER;
}
@Override
public void initPacketsAndColliders(int[] entityIds,
WorldPosition position,
Quaternionf conjugated,
BiConsumer<Object, Boolean> packets,
Consumer<Collider> collider,
Consumer<HitBoxPart> aabb) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
try {
double x = position.x();
double y = position.y();
double z = position.z();
float yaw = position.yRot();
double originalY = y + offset.y;
double integerPart = Math.floor(originalY);
double fractionalPart = originalY - integerPart;
double processedY = (fractionalPart >= 0.5) ? integerPart + 1 : originalY;
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[0], UUID.randomUUID(), x + offset.x, originalY, z - offset.z, 0, yaw,
MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
), false);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[1], UUID.randomUUID(), x + offset.x, processedY, z - offset.z, 0, yaw,
MEntityTypes.SHULKER, 0, CoreReflections.instance$Vec3$Zero, 0
), false);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.copyOf(this.cachedShulkerValues)), false);
// add passengers
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetPassengersPacket(entityIds[0], entityIds[1]), false);
// fix some special occasions
if (originalY != processedY) {
double deltaY = originalY - processedY;
short ya = (short) (deltaY * 8192);
packets.accept(NetworkReflections.constructor$ClientboundMoveEntityPacket$Pos.newInstance(
entityIds[1], (short) 0, ya, (short) 0, true
), false);
}
// set shulker scale
if (VersionHelper.isOrAbove1_20_5() && this.scale != 1) {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale);
packets.accept(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[1], Collections.singletonList(attributeInstance)), false);
}
this.spawner.accept(entityIds, position.world(), x, y, z, yaw, offset, packets, collider, aabb);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct shulker hitbox spawn packet", e);
}
}
@Override
public void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<AABB> aabbs) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
aabbs.accept(this.aabbCreator.create(x, y, z, yaw, offset));
}
@FunctionalInterface
interface DirectionalShulkerSpawner {
void accept(int[] entityIds,
World world,
double x,
double y,
double z,
float yaw,
Vector3f offset,
BiConsumer<Object, Boolean> packets,
Consumer<Collider> collider,
Consumer<HitBoxPart> aabb);
}
@FunctionalInterface
interface AABBCreator {
AABB create(double x, double y, double z, float yaw, Vector3f offset);
}
@Override
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
if (this.interactionEntity) {
if (this.direction.stepY() != 0) {
// 展示实体 // 潜影贝 // 交互实体
return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()};
} else {
// 展示实体 // 潜影贝 // 交互实体1 // 交互实体2
return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()};
}
} else {
// 展示实体 // 潜影贝
return new int[] {entityIdSupplier.get(), entityIdSupplier.get()};
}
}
public static class Factory implements HitBoxConfigFactory {
@Override
public HitBoxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
float scale = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", "1"), "scale");
byte peek = (byte) ResourceConfigUtils.getAsInt(arguments.getOrDefault("peek", 0), "peek");
Direction directionEnum = Optional.ofNullable(arguments.get("direction")).map(it -> Direction.valueOf(it.toString().toUpperCase(Locale.ENGLISH))).orElse(Direction.UP);
boolean interactive = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interactive", true), "interactive");
boolean interactionEntity = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interaction-entity", true), "interaction-entity");
boolean canUseItemOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", true), "can-use-item-on");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", true), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new ShulkerHitBoxConfig(
SeatConfig.fromObj(arguments.get("seats")),
position, directionEnum,
scale, peek, interactionEntity, interactive, canUseItemOn, blocksBuilding, canBeHitByProjectile
);
}
}
public static Direction getOriginalDirection(Direction newDirection, Direction oldDirection) {
switch (newDirection) {
case NORTH -> {
@@ -357,4 +295,26 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
default -> throw new IllegalStateException("Unexpected value: " + newDirection);
}
}
public static class Factory implements FurnitureHitBoxConfigFactory<ShulkerFurnitureHitbox> {
@Override
public ShulkerFurnitureHitboxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0), "position");
float scale = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1), "scale");
byte peek = (byte) ResourceConfigUtils.getAsInt(arguments.getOrDefault("peek", 0), "peek");
Direction directionEnum = ResourceConfigUtils.getAsEnum(arguments.get("direction"), Direction.class, Direction.UP);
boolean interactive = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interactive", true), "interactive");
boolean interactionEntity = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interaction-entity", true), "interaction-entity");
boolean canUseItemOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", true), "can-use-item-on");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", true), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new ShulkerFurnitureHitboxConfig(
SeatConfig.fromObj(arguments.get("seats")),
position,
canUseItemOn, blocksBuilding, canBeHitByProjectile,
scale, peek, interactive, interactionEntity, directionEnum
);
}
}
}

View File

@@ -6,13 +6,10 @@ import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
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.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureExtraData;
import net.momirealms.craftengine.core.entity.furniture.HitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
@@ -27,28 +24,31 @@ import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.Location;
import org.bukkit.World;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
public class FurnitureItemBehavior extends ItemBehavior {
public static final Factory FACTORY = new Factory();
private static final Set<String> ALLOWED_ANCHOR_TYPES = Set.of("wall", "ceiling", "ground");
private final Key id;
private final Map<AnchorType, Rule> rules;
public FurnitureItemBehavior(Key id) {
public FurnitureItemBehavior(Key id, Map<AnchorType, Rule> rules) {
this.id = id;
this.rules = rules;
}
public Key furnitureId() {
return id;
return this.id;
}
@Override
@@ -57,12 +57,11 @@ public class FurnitureItemBehavior extends ItemBehavior {
}
public InteractionResult place(UseOnContext context) {
Optional<CustomFurniture> optionalCustomFurniture = BukkitFurnitureManager.instance().furnitureById(this.id);
Optional<FurnitureConfig> optionalCustomFurniture = BukkitFurnitureManager.instance().furnitureById(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) {
@@ -71,78 +70,90 @@ public class FurnitureItemBehavior extends ItemBehavior {
case DOWN -> AnchorType.CEILING;
};
CustomFurniture.Placement placement = customFurniture.getPlacement(anchorType);
if (placement == null) {
FurnitureConfig customFurniture = optionalCustomFurniture.get();
FurnitureVariant variant = customFurniture.getVariant(anchorType.variantName());
if (variant == null) {
return InteractionResult.FAIL;
}
Rule rule = this.rules.get(anchorType);
if (rule == null) {
rule = Rule.DEFAULT;
}
Player player = context.getPlayer();
// todo adventure check
if (player != null && player.isAdventureMode()) {
return InteractionResult.FAIL;
}
Vec3d clickedPosition = context.getClickLocation();
// trigger event
org.bukkit.entity.Player bukkitPlayer = player != null ? (org.bukkit.entity.Player) player.platformPlayer() : null;
World world = (World) context.getLevel().platformWorld();
// 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()));
Pair<Double, Double> xz = rule.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()));
Pair<Double, Double> xz = rule.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.y()));
finalPlacePosition = new Vec3d(xz.left(), xz.right(), clickedPosition.z());
}
} else {
furnitureYaw = placement.rotationRule().apply(180 + (player != null ? player.yRot() : 0));
Pair<Double, Double> xz = placement.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.z()));
furnitureYaw = rule.rotationRule().apply(180 + (player != null ? player.yRot() : 0));
Pair<Double, Double> xz = rule.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.z()));
finalPlacePosition = new Vec3d(xz.left(), clickedPosition.y(), xz.right());
}
// trigger event
org.bukkit.entity.Player bukkitPlayer = player != null ? (org.bukkit.entity.Player) player.platformPlayer() : null;
World world = (World) context.getLevel().platformWorld();
Location furnitureLocation = new Location(world, finalPlacePosition.x(), finalPlacePosition.y(), finalPlacePosition.z(), (float) furnitureYaw, 0);
WorldPosition furniturePos = LocationUtils.toWorldPosition(furnitureLocation);
List<AABB> aabbs = new ArrayList<>();
for (HitBoxConfig hitBoxConfig : placement.hitBoxConfigs()) {
hitBoxConfig.initShapeForPlacement(finalPlacePosition.x(), finalPlacePosition.y(), finalPlacePosition.z(), (float) furnitureYaw, QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - furnitureYaw), 0).conjugate(), aabbs::add);
// 收集阻挡的碰撞箱
for (FurnitureHitBoxConfig<?> hitBoxConfig : variant.hitBoxConfigs()) {
hitBoxConfig.prepareForPlacement(furniturePos, aabbs::add);
}
// 检查方块、实体阻挡
if (!aabbs.isEmpty()) {
if (!FastNMS.INSTANCE.checkEntityCollision(context.getLevel().serverWorld(), aabbs.stream().map(it -> FastNMS.INSTANCE.constructor$AABB(it.minX, it.minY, it.minZ, it.maxX, it.maxY, it.maxZ)).toList())) {
if (player != null && player.enableFurnitureDebug() && VersionHelper.isPaper()) {
player.playSound(Key.of("minecraft:entity.villager.no"));
Key flame = Key.of("flame");
for (AABB aabb : aabbs) {
List<Vec3d> edgePoints = aabb.getEdgePoints(0.125);
for (Vec3d edgePoint : edgePoints) {
player.playParticle(flame, edgePoint.x(), edgePoint.y(), edgePoint.z());
}
}
}
return InteractionResult.FAIL;
}
}
// 检查其他插件兼容性
if (!BukkitCraftEngine.instance().antiGriefProvider().canPlace(bukkitPlayer, furnitureLocation)) {
return InteractionResult.FAIL;
}
// 触发尝试放置的事件
if (player != null) {
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()));
FurnitureAttemptPlaceEvent attemptPlaceEvent = new FurnitureAttemptPlaceEvent(bukkitPlayer, customFurniture, variant, furnitureLocation.clone(), context.getHand(), world.getBlockAt(context.getClickedPos().x(), context.getClickedPos().y(), context.getClickedPos().z()));
if (EventUtils.fireAndCheckCancel(attemptPlaceEvent)) {
return InteractionResult.FAIL;
}
}
Item<?> item = context.getItem();
// 不可能
if (ItemUtils.isEmpty(item)) return InteractionResult.FAIL;
BukkitFurniture bukkitFurniture = BukkitFurnitureManager.instance().place(
furnitureLocation.clone(), customFurniture,
FurnitureExtraData.builder()
.item(item.copyWithCount(1))
.anchorType(anchorType)
.dyedColor(item.dyedColor().orElse(null))
.fireworkExplosionColors(item.fireworkExplosion().map(explosion -> explosion.colors().toIntArray()).orElse(null))
.build(), false);
// 获取家具物品的一些属性
FurnitureDataAccessor dataAccessor = FurnitureDataAccessor.of(new CompoundTag());
dataAccessor.setVariant(variant.name());
dataAccessor.setItem(item.copyWithCount(1));
dataAccessor.setDyedColor(item.dyedColor().orElse(null));
dataAccessor.setFireworkExplosionColors(item.fireworkExplosion().map(explosion -> explosion.colors().toIntArray()).orElse(null));
// 放置家具
BukkitFurniture bukkitFurniture = BukkitFurnitureManager.instance().place(furnitureLocation.clone(), customFurniture, dataAccessor, false);
// 触发放置事件
if (player != null) {
FurniturePlaceEvent placeEvent = new FurniturePlaceEvent(bukkitPlayer, bukkitFurniture, furnitureLocation, context.getHand());
if (EventUtils.fireAndCheckCancel(placeEvent)) {
@@ -150,7 +161,7 @@ public class FurnitureItemBehavior extends ItemBehavior {
return InteractionResult.FAIL;
}
}
// 触发ce事件
Cancellable dummy = Cancellable.dummy();
PlayerOptionalContext functionContext = PlayerOptionalContext.of(player, ContextHolder.builder()
.withParameter(DirectContextParameters.FURNITURE, bukkitFurniture)
@@ -163,14 +174,13 @@ public class FurnitureItemBehavior extends ItemBehavior {
if (dummy.isCancelled()) {
return InteractionResult.SUCCESS_AND_CANCEL;
}
// 后续处理
if (player != null) {
if (!player.canInstabuild()) {
item.count(item.count() - 1);
}
player.swingHand(context.getHand());
}
context.getLevel().playBlockSound(finalPlacePosition, customFurniture.settings().sounds().placeSound());
return InteractionResult.SUCCESS;
}
@@ -183,17 +193,59 @@ public class FurnitureItemBehavior extends ItemBehavior {
if (id == null) {
throw new LocalizedResourceConfigException("warning.config.item.behavior.furniture.missing_furniture", new IllegalArgumentException("Missing required parameter 'furniture' for furniture_item behavior"));
}
Map<String, Object> rulesMap = ResourceConfigUtils.getAsMapOrNull(arguments.get("rules"), "rules");
Key furnitureId;
if (id instanceof Map<?,?> map) {
Map<String, Object> furnitureSection;
if (map.containsKey(key.toString())) {
// 防呆
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)));
furnitureSection = MiscUtils.castToMap(map.get(key.toString()), false);
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, furnitureSection));
} else {
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map, false)));
furnitureSection = MiscUtils.castToMap(map, false);
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, furnitureSection));
}
furnitureId = key;
// 兼容老版本
if (rulesMap == null) {
Map<String, Object> placementSection = ResourceConfigUtils.getAsMapOrNull(furnitureSection.get("placement"), "placement");
if (placementSection != null) {
rulesMap = new HashMap<>();
for (Map.Entry<String, Object> entry : placementSection.entrySet()) {
if (entry.getValue() instanceof Map<?, ?> innerMap) {
if (innerMap.containsKey("rules")) {
Map<String, Object> rules = ResourceConfigUtils.getAsMap(innerMap.get("rules"), "rules");
if (ALLOWED_ANCHOR_TYPES.contains(entry.getKey())) {
rulesMap.put(entry.getKey(), rules);
}
}
}
}
}
}
return new FurnitureItemBehavior(key);
} else {
return new FurnitureItemBehavior(Key.of(id.toString()));
furnitureId = Key.of(id.toString());
}
Map<AnchorType, Rule> rules = new EnumMap<>(AnchorType.class);
if (rulesMap != null) {
for (Map.Entry<String, Object> entry : rulesMap.entrySet()) {
try {
AnchorType type = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ROOT));
Map<String, Object> ruleSection = MiscUtils.castToMap(entry.getValue(), true);
rules.put(type, new Rule(
ResourceConfigUtils.getAsEnum(ruleSection.get("alignment"), AlignmentRule.class, AlignmentRule.ANY),
ResourceConfigUtils.getAsEnum(ruleSection.get("rotation"), RotationRule.class, RotationRule.ANY)
));
} catch (IllegalArgumentException ignored) {
Debugger.FURNITURE.debug(() -> "Invalid anchor type: " + entry.getKey());
}
}
}
return new FurnitureItemBehavior(furnitureId, rules);
}
}
public record Rule(AlignmentRule alignmentRule, RotationRule rotationRule) {
public static final Rule DEFAULT = new Rule(AlignmentRule.ANY, RotationRule.ANY);
}
}

View File

@@ -457,6 +457,7 @@ public class ItemEventListener implements Listener {
}
} else {
// fixme 如何取消堆叠数量>1的物品的默认replacement
if (replacement != null) {
Item<ItemStack> replacementItem = this.plugin.itemManager().createWrappedItem(replacement, serverPlayer);
if (replacementItem != null) {
PlayerUtils.giveItem(serverPlayer, 1, replacementItem);
@@ -464,6 +465,7 @@ public class ItemEventListener implements Listener {
}
}
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
public void onFoodLevelChange(FoodLevelChangeEvent event) {

View File

@@ -93,6 +93,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme
public class VanillaLootParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"vanilla-loots", "vanilla-loot"};
private int count;
@Override
public int loadingSequence() {
@@ -104,6 +105,16 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme
return CONFIG_SECTION_NAME;
}
@Override
public int count() {
return this.count;
}
@Override
public void preProcess() {
this.count = 0;
}
@Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("type"), "warning.config.vanilla_loot.missing_type");
@@ -147,6 +158,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme
}
}
}
this.count++;
}
}
}

View File

@@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.pack.obfuscation.ObfA;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.util.Base64Utils;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.Bukkit;
@@ -94,7 +95,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
return;
}
if (!Config.sendPackOnUpload()) return;
CraftEngine.instance().logger().info("Completed uploading resource pack");
CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.resource_pack.upload"));
for (BukkitServerPlayer player : this.plugin.networkManager().onlineUsers()) {
sendResourcePack(player);
}

View File

@@ -7,7 +7,8 @@ import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors;
import net.momirealms.craftengine.bukkit.block.entity.renderer.element.BukkitBlockEntityElementConfigs;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.BukkitHitBoxTypes;
import net.momirealms.craftengine.bukkit.entity.furniture.element.BukkitFurnitureElementConfigs;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.BukkitFurnitureHitboxTypes;
import net.momirealms.craftengine.bukkit.entity.projectile.BukkitProjectileManager;
import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeatManager;
import net.momirealms.craftengine.bukkit.font.BukkitFontManager;
@@ -160,8 +161,9 @@ public class BukkitCraftEngine extends CraftEngine {
super.onPluginLoad();
BukkitBlockBehaviors.init();
BukkitItemBehaviors.init();
BukkitHitBoxTypes.init();
BukkitFurnitureHitboxTypes.init();
BukkitBlockEntityElementConfigs.init();
BukkitFurnitureElementConfigs.init();
// 初始化 onload 阶段的兼容性
super.compatibilityManager().onLoad();
// 创建网络管理器
@@ -369,6 +371,11 @@ public class BukkitCraftEngine extends CraftEngine {
return (BukkitFontManager) fontManager;
}
@Override
public BukkitWorldManager worldManager() {
return (BukkitWorldManager) worldManager;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
public void saveResource(String resourcePath) {

View File

@@ -43,12 +43,14 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new TestCommand(this, plugin),
new SetLocaleCommand(this, plugin),
new SetEntityViewDistanceScaleCommand(this, plugin),
new ToggleEntityCullingCommand(this, plugin),
new UnsetLocaleCommand(this, plugin),
new DebugGetBlockStateRegistryIdCommand(this, plugin),
new DebugGetBlockInternalIdCommand(this, plugin),
new DebugAppearanceStateUsageCommand(this, plugin),
new DebugClearCooldownCommand(this, plugin),
new DebugEntityIdCommand(this, plugin),
new DebugFurnitureCommand(this, plugin),
new DebugRealStateUsageCommand(this, plugin),
new DebugItemDataCommand(this, plugin),
new DebugSetBlockCommand(this, plugin),

View File

@@ -0,0 +1,36 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
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 DebugFurnitureCommand extends BukkitCommandFeature<CommandSender> {
public DebugFurnitureCommand(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 -> {
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(context.sender());
boolean b = !serverPlayer.enableFurnitureDebug();
serverPlayer.setEnableFurnitureDebug(b);
serverPlayer.sendMessage(Component.text("Furniture Debug Mode: ").append(Component.text(b ? "ON" : "OFF").color(b ? NamedTextColor.GREEN : NamedTextColor.RED)), false);
});
}
@Override
public String getFeatureID() {
return "debug_furniture";
}
}

View File

@@ -4,8 +4,7 @@ import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureConfig;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
@@ -19,10 +18,11 @@ import org.incendo.cloud.bukkit.parser.NamespacedKeyParser;
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.EnumParser;
import org.incendo.cloud.parser.standard.StringParser;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -42,21 +42,30 @@ public class DebugSpawnFurnitureCommand extends BukkitCommandFeature<CommandSend
return CompletableFuture.completedFuture(plugin().furnitureManager().cachedSuggestions());
}
}))
.optional("anchor-type", EnumParser.enumParser(AnchorType.class))
.optional("variant", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
NamespacedKey namespacedKey = context.get("id");
Key id = KeyUtils.namespacedKey2Key(namespacedKey);
BukkitFurnitureManager furnitureManager = BukkitFurnitureManager.instance();
Optional<FurnitureConfig> optionalCustomFurniture = furnitureManager.furnitureById(id);
return optionalCustomFurniture.<CompletableFuture<? extends Iterable<? extends Suggestion>>>map(config -> CompletableFuture.completedFuture(config.variants().keySet().stream().map(Suggestion::suggestion).toList())).orElseGet(() -> CompletableFuture.completedFuture(List.of()));
}
}))
.flag(FlagKeys.SILENT_FLAG)
.handler(context -> {
NamespacedKey namespacedKey = context.get("id");
Key id = KeyUtils.namespacedKey2Key(namespacedKey);
BukkitFurnitureManager furnitureManager = BukkitFurnitureManager.instance();
Optional<CustomFurniture> optionalCustomFurniture = furnitureManager.furnitureById(id);
Optional<FurnitureConfig> optionalCustomFurniture = furnitureManager.furnitureById(id);
if (optionalCustomFurniture.isEmpty()) {
return;
}
Location location = context.get("location");
CustomFurniture customFurniture = optionalCustomFurniture.get();
AnchorType anchorType = (AnchorType) context.optional("anchor-type").orElse(customFurniture.getAnyAnchorType());
FurnitureConfig customFurniture = optionalCustomFurniture.get();
String variant = (String) context.optional("variant").orElse(customFurniture.anyVariantName());
boolean playSound = context.flags().hasFlag("silent");
CraftEngineFurniture.place(location, customFurniture, anchorType, playSound);
CraftEngineFurniture.place(location, customFurniture, variant, playSound);
});
}

View File

@@ -1,12 +1,14 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
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 net.momirealms.craftengine.core.plugin.command.FlagKeys;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -27,6 +29,14 @@ public class SetEntityViewDistanceScaleCommand extends BukkitCommandFeature<Comm
.required("player", PlayerParser.playerParser())
.required("scale", DoubleParser.doubleParser(0.125, 8))
.handler(context -> {
if (!Config.enableEntityCulling()) {
plugin().senderFactory().wrap(context.sender()).sendMessage(Component.text("Entity culling is not enabled on this server").color(NamedTextColor.RED));
return;
}
if (Config.entityCullingViewDistance() <= 0) {
plugin().senderFactory().wrap(context.sender()).sendMessage(Component.text("View distance is not enabled on this server").color(NamedTextColor.RED));
return;
}
Player player = context.get("player");
double scale = context.get("scale");
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);

View File

@@ -0,0 +1,56 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
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 net.momirealms.craftengine.core.plugin.command.FlagKeys;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.incendo.cloud.Command;
import org.incendo.cloud.bukkit.parser.PlayerParser;
import org.incendo.cloud.parser.standard.BooleanParser;
import java.util.Optional;
public class ToggleEntityCullingCommand extends BukkitCommandFeature<CommandSender> {
public ToggleEntityCullingCommand(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("player", PlayerParser.playerParser())
.optional("state", BooleanParser.booleanParser())
.handler(context -> {
if (!Config.enableEntityCulling()) {
plugin().senderFactory().wrap(context.sender()).sendMessage(Component.text("Entity culling is not enabled on this server").color(NamedTextColor.RED));
return;
}
Player player = context.get("player");
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
Optional<Boolean> state = context.optional("state");
boolean isEnabled = serverPlayer.enableEntityCulling();
if (state.isPresent()) {
serverPlayer.setEnableEntityCulling(state.get());
handleFeedback(context, MessageConstants.COMMAND_TOGGLE_ENTITY_CULLING_SUCCESS, Component.text(state.get()), Component.text(player.getName()));
} else {
serverPlayer.setEnableEntityCulling(!isEnabled);
handleFeedback(context, MessageConstants.COMMAND_TOGGLE_ENTITY_CULLING_SUCCESS, Component.text(!isEnabled), Component.text(player.getName()));
}
});
}
@Override
public String getFeatureID() {
return "toggle_entity_culling";
}
}

View File

@@ -59,8 +59,8 @@ import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
import net.momirealms.craftengine.core.advancement.network.AdvancementHolder;
import net.momirealms.craftengine.core.advancement.network.AdvancementProgress;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.entity.furniture.HitBox;
import net.momirealms.craftengine.core.entity.furniture.HitBoxPart;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.font.FontManager;
@@ -359,8 +359,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
registerNMSPacketConsumer(new EntityEventListener(), NetworkReflections.clazz$ClientboundEntityEventPacket);
registerNMSPacketConsumer(new MovePosAndRotateEntityListener(), NetworkReflections.clazz$ClientboundMoveEntityPacket$PosRot);
registerNMSPacketConsumer(new MovePosEntityListener(), NetworkReflections.clazz$ClientboundMoveEntityPacket$Pos);
registerNMSPacketConsumer(new RotateHeadListener(), NetworkReflections.clazz$ClientboundRotateHeadPacket);
registerNMSPacketConsumer(new SetEntityMotionListener(), NetworkReflections.clazz$ClientboundSetEntityMotionPacket);
registerNMSPacketConsumer(new FinishConfigurationListener(), NetworkReflections.clazz$ClientboundFinishConfigurationPacket);
registerNMSPacketConsumer(new LoginFinishedListener(), NetworkReflections.clazz$ClientboundLoginFinishedPacket);
registerNMSPacketConsumer(new UpdateTagsListener(), NetworkReflections.clazz$ClientboundUpdateTagsPacket);
@@ -1248,7 +1246,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
CraftEngine.instance().logger().warn("Failed to get entityId from ServerboundPickItemFromEntityPacket", e);
return;
}
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId);
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByVirtualEntityId(entityId);
if (furniture == null) return;
Player player = (Player) user.platformPlayer();
if (player == null) return;
@@ -1274,7 +1272,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
private static void handlePickItemFromEntityOnMainThread(Player player, BukkitFurniture furniture) throws Throwable {
Key itemId = furniture.config().settings().itemId();
if (itemId == null) return;
pickItem(player, itemId, null, FastNMS.INSTANCE.method$CraftEntity$getHandle(furniture.baseEntity()));
pickItem(player, itemId, null, FastNMS.INSTANCE.method$CraftEntity$getHandle(furniture.getBukkitEntity()));
}
}
@@ -1470,6 +1468,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
player.setClientSideWorld(BukkitAdaptors.adapt(world));
player.clearTrackedChunks();
player.clearTrackedBlockEntities();
player.clearTrackedFurniture();
} else {
CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist");
}
@@ -1746,9 +1745,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
@Override
public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) {
int entityId = ProtectedFieldVisitor.get().field$ClientboundMoveEntityPacket$entityId(packet);
if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) {
event.setCancelled(true);
}
EntityPacketHandler handler = user.entityPacketHandlers().get(entityId);
if (handler != null) {
handler.handleMoveAndRotate(user, event, packet);
@@ -1768,41 +1764,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
}
public static class RotateHeadListener implements NMSPacketListener {
@Override
public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) {
int entityId;
try {
entityId = (int) NetworkReflections.methodHandle$ClientboundRotateHeadPacket$entityIdGetter.invokeExact(packet);
} catch (Throwable t) {
CraftEngine.instance().logger().warn("Failed to get entity id from ClientboundRotateHeadPacket", t);
return;
}
if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) {
event.setCancelled(true);
}
}
}
public static class SetEntityMotionListener implements NMSPacketListener {
@Override
public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) {
if (!VersionHelper.isOrAbove1_21_6()) return;
int entityId;
try {
entityId = (int) NetworkReflections.methodHandle$ClientboundSetEntityMotionPacket$idGetter.invokeExact(packet);
} catch (Throwable t) {
CraftEngine.instance().logger().warn("Failed to get entity id from ClientboundSetEntityMotionPacket", t);
return;
}
if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) {
event.setCancelled(true);
}
}
}
public static class FinishConfigurationListener implements NMSPacketListener {
@SuppressWarnings("unchecked")
@@ -3352,7 +3313,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
for (int i = 0, size = intList.size(); i < size; i++) {
int entityId = intList.getInt(i);
EntityPacketHandler handler = user.entityPacketHandlers().remove(entityId);
if (handler != null && handler.handleEntitiesRemove(intList)) {
if (handler != null && handler.handleEntitiesRemove(user, intList)) {
changed = true;
}
}
@@ -3701,36 +3662,42 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
public void onPacketReceive(NetWorkUser user, ByteBufPacketEvent event) {
FriendlyByteBuf buf = event.getBuffer();
int entityId = hasModelEngine() ? plugin.compatibilityManager().interactionToBaseEntity(buf.readVarInt()) : buf.readVarInt();
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId);
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByVirtualEntityId(entityId);
if (furniture == null) return;
int actionType = buf.readVarInt();
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
if (serverPlayer.isSpectatorMode()) return;
Player platformPlayer = serverPlayer.platformPlayer();
Location location = furniture.baseEntity().getLocation();
Location location = furniture.location();
Runnable mainThreadTask;
if (actionType == 1) {
// ATTACK
boolean usingSecondaryAction = buf.readBoolean();
if (entityId != furniture.baseEntityId()) {
if (entityId != furniture.entityId()) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeVarInt(furniture.baseEntityId());
buf.writeVarInt(furniture.entityId());
buf.writeVarInt(actionType);
buf.writeBoolean(usingSecondaryAction);
}
mainThreadTask = () -> {
// todo 冒险模式破坏工具白名单
if (serverPlayer.isAdventureMode() ||
!furniture.isValid()) return;
if (serverPlayer.isAdventureMode() || !furniture.isValid()) return;
// todo 重构家具时候注意需要准备加载好的hitbox类以获取hitbox坐标
if (!serverPlayer.canInteractPoint(new Vec3d(location.getX(), location.getY(), location.getZ()), 16d)) {
// 先检查碰撞箱部分是否存在
FurnitureHitBox hitBox = furniture.hitboxByEntityId(entityId);
if (hitBox == null) return;
for (FurnitureHitboxPart part : hitBox.parts()) {
if (part.entityId() == entityId) {
// 检查玩家是否能破坏此点
if (!serverPlayer.canInteractPoint(part.pos(), 16d)) {
return;
}
}
}
FurnitureAttemptBreakEvent preBreakEvent = new FurnitureAttemptBreakEvent(serverPlayer.platformPlayer(), furniture);
if (EventUtils.fireAndCheckCancel(preBreakEvent))
@@ -3767,11 +3734,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
float z = buf.readFloat();
InteractionHand hand = buf.readVarInt() == 0 ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
boolean usingSecondaryAction = buf.readBoolean();
if (entityId != furniture.baseEntityId()) {
if (entityId != furniture.entityId()) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeVarInt(furniture.baseEntityId());
buf.writeVarInt(furniture.entityId());
buf.writeVarInt(actionType);
buf.writeFloat(x).writeFloat(y).writeFloat(z);
buf.writeVarInt(hand == InteractionHand.MAIN_HAND ? 0 : 1);
@@ -3784,34 +3751,39 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
// 先检查碰撞箱部分是否存在
HitBoxPart hitBoxPart = furniture.hitBoxPartByEntityId(entityId);
if (hitBoxPart == null) return;
Vec3d pos = hitBoxPart.pos();
FurnitureHitBox hitBox = furniture.hitboxByEntityId(entityId);
if (hitBox == null) return;
FurnitureHitboxPart part = null;
for (FurnitureHitboxPart p : hitBox.parts()) {
if (p.entityId() == entityId) {
Vec3d pos = p.pos();
// 检测距离
if (!serverPlayer.canInteractPoint(pos, 16d)) {
return;
}
// 检测
part = p;
}
}
if (part == null) {
return;
}
// 检测能否交互碰撞箱
Location eyeLocation = platformPlayer.getEyeLocation();
Vector direction = eyeLocation.getDirection();
Location endLocation = eyeLocation.clone();
endLocation.add(direction.multiply(serverPlayer.getCachedInteractionRange()));
Optional<EntityHitResult> result = hitBoxPart.aabb().clip(LocationUtils.toVec3d(eyeLocation), LocationUtils.toVec3d(endLocation));
Optional<EntityHitResult> result = part.aabb().clip(LocationUtils.toVec3d(eyeLocation), LocationUtils.toVec3d(endLocation));
if (result.isEmpty()) {
return;
}
EntityHitResult hitResult = result.get();
Vec3d hitLocation = hitResult.hitLocation();
// 获取正确的交互点
Location interactionPoint = new Location(platformPlayer.getWorld(), hitLocation.x, hitLocation.y, hitLocation.z);
HitBox hitbox = furniture.hitBoxByEntityId(entityId);
if (hitbox == null) {
return;
}
// 触发事件
FurnitureInteractEvent interactEvent = new FurnitureInteractEvent(serverPlayer.platformPlayer(), furniture, hand, interactionPoint, hitbox);
FurnitureInteractEvent interactEvent = new FurnitureInteractEvent(serverPlayer.platformPlayer(), furniture, hand, interactionPoint, hitBox);
if (EventUtils.fireAndCheckCancel(interactEvent)) {
return;
}
@@ -3833,7 +3805,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
// 必须从网络包层面处理,否则无法获取交互的具体实体
if (serverPlayer.isSecondaryUseActive() && !itemInHand.isEmpty() && hitbox.config().canUseItemOn()) {
if (serverPlayer.isSecondaryUseActive() && !itemInHand.isEmpty() && hitBox.config().canUseItemOn()) {
Optional<CustomItem<ItemStack>> optionalCustomItem = itemInHand.getCustomItem();
if (optionalCustomItem.isPresent() && !optionalCustomItem.get().behaviors().isEmpty()) {
for (ItemBehavior behavior : optionalCustomItem.get().behaviors()) {
@@ -3853,9 +3825,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
);
} else {
if (!serverPlayer.isSecondaryUseActive()) {
for (Seat<HitBox> seat : hitbox.seats()) {
for (Seat<FurnitureHitBox> seat : hitBox.seats()) {
if (!seat.isOccupied()) {
if (seat.spawnSeat(serverPlayer, furniture.position())) {
if (!part.interactive()) {
serverPlayer.swingHand(InteractionHand.MAIN_HAND);
}
break;
}
}
@@ -3866,11 +3841,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
} else if (actionType == 0) {
int hand = buf.readVarInt();
boolean usingSecondaryAction = buf.readBoolean();
if (entityId != furniture.baseEntityId()) {
if (entityId != furniture.entityId()) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeVarInt(furniture.baseEntityId());
buf.writeVarInt(furniture.entityId());
buf.writeVarInt(actionType);
buf.writeVarInt(hand);
buf.writeBoolean(usingSecondaryAction);
@@ -3978,10 +3953,18 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
this.handlers[MEntityTypes.ITEM_DISPLAY$registryId] = (user, event) -> {
FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt();
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id);
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(id);
if (furniture != null) {
user.entityPacketHandlers().put(id, new FurniturePacketHandler(furniture.fakeEntityIds()));
user.sendPacket(furniture.spawnPacket((Player) user.platformPlayer()), false);
EntityPacketHandler previous = serverPlayer.entityPacketHandlers().put(id, new FurniturePacketHandler(id, furniture.virtualEntityIds()));
if (Config.enableEntityCulling()) {
serverPlayer.addTrackedFurniture(id, furniture);
} else {
// 修复addEntityToWorld包比事件先发的问题 (WE)
if (previous == null || previous instanceof ItemDisplayPacketHandler) {
furniture.show(serverPlayer);
}
}
if (Config.hideBaseEntity() && !furniture.hasExternalModel()) {
event.setCancelled(true);
}
@@ -3994,7 +3977,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt();
// Cancel collider entity packet
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id);
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByColliderEntityId(id);
if (furniture != null) {
event.setCancelled(true);
user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE);
@@ -4005,7 +3988,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt();
// Cancel collider entity packet
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id);
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByColliderEntityId(id);
if (furniture != null) {
event.setCancelled(true);
user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE);

View File

@@ -1,22 +1,26 @@
package net.momirealms.craftengine.bukkit.plugin.network.handler;
import it.unimi.dsi.fastutil.ints.IntList;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
import net.momirealms.craftengine.core.plugin.network.NMSPacketEvent;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import java.util.List;
public class FurniturePacketHandler implements EntityPacketHandler {
private final List<Integer> fakeEntities;
private final int metaEntityId;
private final int[] virtualHitboxEntities;
public FurniturePacketHandler(List<Integer> fakeEntities) {
this.fakeEntities = fakeEntities;
public FurniturePacketHandler(int metaEntityId, int[] virtualHitboxEntities) {
this.virtualHitboxEntities = virtualHitboxEntities;
this.metaEntityId = metaEntityId;
}
@Override
public boolean handleEntitiesRemove(IntList entityIds) {
entityIds.addAll(this.fakeEntities);
public boolean handleEntitiesRemove(NetWorkUser user, IntList entityIds) {
((Player) user).removeTrackedFurniture(this.metaEntityId);
for (int entityId : this.virtualHitboxEntities) {
entityIds.add(entityId);
}
return true;
}

View File

@@ -4594,4 +4594,27 @@ public final class CoreReflections {
throw new ReflectionInitException("Failed to init EmptyBlockGetter$INSTANCE", e);
}
}
public static final Class<?> clazz$EntityDimensions = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.entity.EntitySize",
"world.entity.EntityDimensions"
)
);
public static final Field field$EntityType$dimensions = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$EntityType, clazz$EntityDimensions, 0)
);
public static final Field field$EntityDimensions$width = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$EntityDimensions, float.class, 0)
);
public static final Field field$EntityDimensions$height = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$EntityDimensions, float.class, 1)
);
public static final Field field$EntityDimensions$fixed = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$EntityDimensions, boolean.class, 0)
);
}

View File

@@ -7,7 +7,9 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture;
import net.momirealms.craftengine.bukkit.block.entity.BlockEntityHolder;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
@@ -25,6 +27,9 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer;
import net.momirealms.craftengine.core.entity.data.EntityData;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureVariant;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.player.GameMode;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.Player;
@@ -74,6 +79,8 @@ import java.util.concurrent.ConcurrentHashMap;
public class BukkitServerPlayer extends Player {
public static final Key SELECTED_LOCALE_KEY = Key.of("craftengine:locale");
public static final Key ENTITY_CULLING_VIEW_DISTANCE_SCALE = Key.of("craftengine:entity_culling_view_distance_scale");
public static final Key ENABLE_ENTITY_CULLING = Key.of("craftengine:enable_entity_culling");
public static final Key ENABLE_FURNITURE_DEBUG = Key.of("craftengine:enable_furniture_debug");
private final BukkitCraftEngine plugin;
// connection state
@@ -143,11 +150,20 @@ public class BukkitServerPlayer extends Player {
private int lastStopMiningTick;
// 跟踪到的方块实体渲染器
private final Map<BlockPos, VirtualCullableObject> trackedBlockEntityRenderers = new ConcurrentHashMap<>();
private final Map<Integer, VirtualCullableObject> trackedFurniture = new ConcurrentHashMap<>();
private final EntityCulling culling;
private Vec3d firstPersonCameraVec3;
private Vec3d thirdPersonCameraVec3;
// 是否启用实体剔除
private boolean enableEntityCulling;
// 玩家眼睛所在位置
private Location eyeLocation;
// 是否启用家具调试
private boolean enableFurnitureDebug;
// 上一次对准的家具
private BukkitFurniture lastHitFurniture;
// 缓存的tick
private int lastHitFurnitureTick;
public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) {
this.channel = channel;
@@ -174,6 +190,8 @@ public class BukkitServerPlayer extends Player {
byte[] bytes = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY);
String locale = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(SELECTED_LOCALE_KEY), PersistentDataType.STRING);
Double scale = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(ENTITY_CULLING_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE);
this.enableEntityCulling = Optional.ofNullable(player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(ENABLE_ENTITY_CULLING), PersistentDataType.BOOLEAN)).orElse(true);
this.enableFurnitureDebug = Optional.ofNullable(player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(ENABLE_FURNITURE_DEBUG), PersistentDataType.BOOLEAN)).orElse(false);
this.culling.setDistanceScale(Optional.ofNullable(scale).orElse(1.0));
this.selectedLocale = TranslationManager.parseLocale(locale);
this.trackedChunks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(512, 0.5f);
@@ -532,6 +550,33 @@ public class BukkitServerPlayer extends Player {
this.updateGUI();
}
if (this.enableFurnitureDebug) {
BukkitFurniture furniture = CraftEngineFurniture.rayTrace(platformPlayer());
boolean forceShow = furniture != this.lastHitFurniture;
if (forceShow) {
this.lastHitFurnitureTick = 0;
} else {
this.lastHitFurnitureTick++;
if (this.lastHitFurnitureTick % 30 == 0) {
forceShow = true;
}
}
this.lastHitFurniture = furniture;
if (furniture != null && forceShow) {
FurnitureVariant currentVariant = furniture.getCurrentVariant();
List<AABB> aabbs = new ArrayList<>();
for (FurnitureHitBoxConfig<?> config : currentVariant.hitBoxConfigs()) {
config.prepareForPlacement(furniture.position(), aabbs::add);
}
Key endRod = Key.of("soul_fire_flame");
for (AABB aabb : aabbs) {
for (Vec3d point : aabb.getEdgePoints(0.125)) {
this.playParticle(endRod, point.x, point.y, point.z);
}
}
}
}
// 更新眼睛位置
{
Location unsureEyeLocation = bukkitPlayer.getEyeLocation();
@@ -540,7 +585,7 @@ public class BukkitServerPlayer extends Player {
Object mountPos = FastNMS.INSTANCE.method$Entity$getPassengerRidingPosition(FastNMS.INSTANCE.method$CraftEntity$getHandle(vehicle), serverPlayer);
unsureEyeLocation.set(FastNMS.INSTANCE.field$Vec3$x(mountPos), FastNMS.INSTANCE.field$Vec3$y(mountPos) + bukkitPlayer.getEyeHeight(), FastNMS.INSTANCE.field$Vec3$z(mountPos));
}
if (Config.predictBreaking() && !this.isDestroyingCustomBlock && !unsureEyeLocation.equals(this.eyeLocation)) {
if (Config.predictBreaking() && this.eyeLocation != null && !this.isDestroyingCustomBlock && !unsureEyeLocation.equals(this.eyeLocation)) {
// if it's not destroying blocks, we do predict
if ((gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) {
this.predictNextBlockToMine();
@@ -596,8 +641,28 @@ public class BukkitServerPlayer extends Player {
@Override
public void entityCullingTick() {
this.culling.restoreTokenOnTick();
if (this.firstPersonCameraVec3 == null || this.thirdPersonCameraVec3 == null) {
return;
}
boolean useRayTracing = Config.entityCullingRayTracing();
if (this.enableEntityCulling) {
for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) {
cullEntity(useRayTracing, cullableObject);
}
for (VirtualCullableObject cullableObject : this.trackedFurniture.values()) {
cullEntity(useRayTracing, cullableObject);
}
} else {
for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) {
cullableObject.setShown(this, true);
}
for (VirtualCullableObject cullableObject : this.trackedFurniture.values()) {
cullableObject.setShown(this, true);
}
}
}
private void cullEntity(boolean useRayTracing, VirtualCullableObject cullableObject) {
CullingData cullingData = cullableObject.cullable.cullingData();
if (cullingData != null) {
boolean firstPersonVisible = this.culling.isVisible(cullingData, this.firstPersonCameraVec3, useRayTracing);
@@ -614,15 +679,15 @@ public class BukkitServerPlayer extends Player {
if (firstPersonVisible) {
// 下次再说
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
continue;
return;
}
cullableObject.setShown(this, true);
continue;
return;
}
if (this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing)) {
// 下次再说
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
continue;
return;
}
cullableObject.setShown(this, true);
}
@@ -632,7 +697,6 @@ public class BukkitServerPlayer extends Player {
cullableObject.setShown(this, true);
}
}
}
private void updateGUI() {
org.bukkit.inventory.Inventory top = !VersionHelper.isOrAbove1_21() ? LegacyInventoryUtils.getTopInventory(platformPlayer()) : platformPlayer().getOpenInventory().getTopInventory();
@@ -1341,6 +1405,28 @@ public class BukkitServerPlayer extends Player {
platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(ENTITY_CULLING_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE, value);
}
@Override
public void setEnableEntityCulling(boolean enable) {
this.enableEntityCulling = enable;
platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(ENABLE_ENTITY_CULLING), PersistentDataType.BOOLEAN, enable);
}
@Override
public boolean enableEntityCulling() {
return this.enableEntityCulling;
}
@Override
public void setEnableFurnitureDebug(boolean enable) {
this.enableFurnitureDebug = enable;
platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(ENABLE_FURNITURE_DEBUG), PersistentDataType.BOOLEAN, enable);
}
@Override
public boolean enableFurnitureDebug() {
return enableFurnitureDebug;
}
@Override
public void giveExperiencePoints(int xpPoints) {
platformPlayer().giveExp(xpPoints);
@@ -1406,11 +1492,37 @@ public class BukkitServerPlayer extends Player {
this.trackedBlockEntityRenderers.clear();
}
@Override
public void addTrackedFurniture(int entityId, Furniture furniture) {
this.trackedFurniture.put(entityId, new VirtualCullableObject(furniture));
}
@Override
public void removeTrackedFurniture(int entityId) {
VirtualCullableObject remove = this.trackedFurniture.remove(entityId);
if (remove != null && remove.isShown()) {
remove.cullable().hide(this);
}
}
@Override
public void clearTrackedFurniture() {
this.trackedFurniture.clear();
}
@Override
public WorldPosition eyePosition() {
return LocationUtils.toWorldPosition(this.getEyeLocation());
}
@Override
public void playParticle(Key particleId, double x, double y, double z) {
Particle particle = Registry.PARTICLE_TYPE.get(KeyUtils.toNamespacedKey(particleId));
if (particle != null) {
platformPlayer().getWorld().spawnParticle(particle, List.of(platformPlayer()), null, x, y, z, 1, 0, 0,0, 0, null, false);
}
}
public Location getEyeLocation() {
org.bukkit.entity.Player player = platformPlayer();
Location eyeLocation = player.getEyeLocation();

View File

@@ -26,13 +26,9 @@ public final class EntityUtils {
}
public static BlockPos getOnPos(Player player) {
try {
Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player);
Object blockPos = CoreReflections.method$Entity$getOnPos.invoke(serverPlayer, 1.0E-5F);
Object blockPos = FastNMS.INSTANCE.method$Entity$getOnPos(serverPlayer);
return LocationUtils.fromBlockPos(blockPos);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Entity spawnEntity(World world, Location loc, EntityType type, Consumer<Entity> function) {

View File

@@ -101,6 +101,10 @@ public class BukkitWorldManager implements WorldManager, Listener {
injectChunkGenerator(ceWorld);
for (Chunk chunk : world.getLoadedChunks()) {
handleChunkLoad(ceWorld, chunk, false);
CEChunk loadedChunk = ceWorld.getChunkAtIfLoaded(chunk.getChunkKey());
if (loadedChunk != null) {
loadedChunk.setEntitiesLoaded(true);
}
}
ceWorld.setTicking(true);
} catch (Exception e) {
@@ -154,6 +158,10 @@ public class BukkitWorldManager implements WorldManager, Listener {
CEWorld ceWorld = this.worlds.get(uuid);
for (Chunk chunk : world.getLoadedChunks()) {
handleChunkLoad(ceWorld, chunk, true);
CEChunk loadedChunk = ceWorld.getChunkAtIfLoaded(chunk.getChunkKey());
if (loadedChunk != null) {
loadedChunk.setEntitiesLoaded(true);
}
}
ceWorld.setTicking(true);
} else {

View File

@@ -135,6 +135,12 @@ set_entity_view_distance_scale:
usage:
- /ce feature entity-view-distance-scale set
toggle_entity_culling:
enable: true
permission: ce.command.admin.toggle_entity_culling
usage:
- /ce feature toggle-entity-culling
# Debug commands
debug_set_block:
enable: true
@@ -264,6 +270,13 @@ debug_generate_internal_assets:
- /craftengine debug generate-internal-assets
- /ce debug generate-internal-assets
debug_furniture:
enable: true
permission: ce.command.debug.furniture
usage:
- /craftengine debug furniture
- /ce debug furniture
debug_test:
enable: true
permission: ce.command.debug.test

View File

@@ -197,8 +197,8 @@ resource-pack:
deny-non-minecraft-request: true
# Generates a single-use, time-limited download link for each player.
one-time-token: true
# Enhances validation for deny-non-minecraft-request and one-time-token.
strict-validation: true
# Improved validation for deny-non-minecraft-request and one-time-token; do not enable on offline servers or validation will fail.
strict-validation: false
rate-limiting:
# Maximum bandwidth per second to prevent server instability for other players during resource pack downloads
max-bandwidth-per-second: 5_000_000 # 5MB/s
@@ -558,7 +558,7 @@ chunk-system:
client-optimization:
# Requires a restart to fully apply.
entity-culling:
enable: true
enable: false
# Using server-side ray tracing algorithms to hide block entities/furniture and reduce client-side rendering pressure.
ray-tracing: true
# Cull entities based on distance

View File

@@ -27,13 +27,17 @@ items:
texture: minecraft:item/custom/topaz
blocks:
default:topaz_ore:
behavior:
type: drop_experience_block
amount: 3~7
condition:
type: enchantment
predicate: minecraft:silk_touch<=0
loot:
template: default:loot_table/ore
arguments:
ore_drop: default:topaz
ore_block: default:topaz_ore
min_exp: 3
max_exp: 7
settings:
template: default:settings/ore
arguments:
@@ -45,13 +49,17 @@ blocks:
arguments:
path: minecraft:block/custom/topaz_ore
default:deepslate_topaz_ore:
behavior:
type: drop_experience_block
amount: 3~7
condition:
type: enchantment
predicate: minecraft:silk_touch<=0
loot:
template: default:loot_table/ore
arguments:
ore_drop: default:topaz
ore_block: default:deepslate_topaz_ore
min_exp: 3
max_exp: 7
settings:
template: default:settings/deepslate_ore
arguments:

View File

@@ -6,22 +6,23 @@ items:
model: minecraft:item/custom/bench
behavior:
type: furniture_item
rules:
ground:
rotation: four
alignment: center
furniture:
settings:
item: default:bench
sounds:
break: minecraft:block.bamboo_wood.break
place: minecraft:block.bamboo_wood.place
placement:
variants:
ground:
loot-spawn-offset: 0.5,0.5,0
rules:
rotation: FOUR
alignment: CENTER
elements:
- item: default:bench
display-transform: NONE
billboard: FIXED
display-transform: none
billboard: fixed
position: 0.5,0,0
translation: 0,0.5,0
shadow-radius: 1

View File

@@ -7,15 +7,28 @@ items:
behavior:
type: furniture_item
furniture: default:flower_basket
rules:
ground:
rotation: any
alignment: any
wall:
rotation: any
alignment: any
ceiling:
rotation: any
alignment: any
default:flower_basket_ground:
material: nether_brick
model: minecraft:item/custom/flower_basket_ground
item-name: <!i><l10n:item.flower_basket>
default:flower_basket_wall:
material: nether_brick
model: minecraft:item/custom/flower_basket_wall
item-name: <!i><l10n:item.flower_basket>
default:flower_basket_ceiling:
material: nether_brick
model: minecraft:item/custom/flower_basket_ceiling
item-name: <!i><l10n:item.flower_basket>
furniture:
default:flower_basket:
settings:
@@ -27,15 +40,12 @@ furniture:
template: default:loot_table/furniture
arguments:
item: default:flower_basket
placement:
variants:
ground:
rules:
rotation: ANY
alignment: ANY
elements:
- item: default:flower_basket_ground
display-transform: NONE
billboard: FIXED
display-transform: none
billboard: fixed
position: 0,0,0
translation: 0,0.5,0
shadow-radius: 0.5
@@ -49,13 +59,12 @@ furniture:
width: 0.7
height: 0.5
interactive: true
invisible: true
wall:
rules:
alignment: ANY
elements:
- item: default:flower_basket_wall
display-transform: NONE
billboard: FIXED
display-transform: none
billboard: fixed
position: 0,0,0.2
translation: 0,0,0
hitboxes:
@@ -67,6 +76,7 @@ furniture:
width: 0.46
height: 0.75
interactive: true
invisible: true
- type: interaction
can-use-item-on: true
can-be-hit-by-projectile: true
@@ -75,14 +85,12 @@ furniture:
width: 0.46
height: 0.75
interactive: true
invisible: true
ceiling:
rules:
rotation: ANY
alignment: ANY
elements:
- item: default:flower_basket_ceiling
display-transform: NONE
billboard: FIXED
display-transform: none
billboard: fixed
position: 0,-0.46,0
translation: 0,0,0
hitboxes:
@@ -94,3 +102,4 @@ furniture:
width: 0.7
height: 0.7
interactive: true
invisible: true

View File

@@ -6,18 +6,19 @@ items:
model: minecraft:item/custom/wooden_chair
behavior:
type: furniture_item
rules:
ground:
rotation: any
alignment: any
furniture:
settings:
item: default:wooden_chair
sounds:
break: minecraft:block.bamboo_wood.break
place: minecraft:block.bamboo_wood.place
placement:
variants:
ground:
loot-spawn-offset: 0,0.4,0
rules:
rotation: ANY
alignment: ANY
elements:
- item: default:wooden_chair
display-transform: NONE
@@ -26,9 +27,20 @@ items:
billboard: FIXED
translation: 0,0.5,0
hitboxes:
$$>=1.20.5:
- position: 0,0,0
type: custom
entity-type: slime
invisible: true
can-use-item-on: true
blocks-building: true
seats:
- 0,0,-0.1 0
$$fallback:
- position: 0,0,0
type: interaction
blocks-building: true
invisible: true
width: 0.7
height: 1.2
interactive: true

View File

@@ -180,7 +180,6 @@ templates:
# ore_block: the ore block
# ore_drop: the drops of the ore material
# ore_drop_count: the amount of the ore materials
# exp: the exp to drop
default:loot_table/ore:
pools:
- rolls: 1
@@ -203,8 +202,6 @@ templates:
formula:
type: ore_drops
- type: explosion_decay
- type: drop_exp
count: ${exp:-2~4}
# Using Silk Touch or shears will cause the leaves block itself to drop.
# Using Fortune, however, increases the drop rates of sticks and saplings.

View File

@@ -160,8 +160,8 @@ warning.config.sound.missing_name: "<yellow>Problem in Datei <arg:0> gefunden -
warning.config.jukebox_song.duplicate: "<yellow>Problem in Datei <arg:0> gefunden - Doppelter Jukebox-Song '<arg:1>'. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Problem in Datei <arg:0> gefunden - Beim Jukebox-Song '<arg:1>' fehlt das erforderliche 'sound'-Argument.</yellow>"
warning.config.furniture.duplicate: "<yellow>Problem in Datei <arg:0> gefunden - Doppeltes Furniture '<arg:1>'. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Problem in Datei <arg:0> gefunden - Beim Furniture '<arg:1>' fehlt das erforderliche 'placement'-Argument.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Problem in Datei <arg:0> gefunden - Beim Furniture '<arg:1>' fehlt das erforderliche 'item'-Argument für eines seiner Elemente.</yellow>"
warning.config.furniture.missing_variants: "<yellow>Problem in Datei <arg:0> gefunden - Beim Furniture '<arg:1>' fehlt das erforderliche 'variants'-Argument.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>Problem in Datei <arg:0> gefunden - Beim Furniture '<arg:1>' fehlt das erforderliche 'item'-Argument für eines seiner Elemente.</yellow>"
warning.config.furniture.settings.unknown: "<yellow>Problem in Datei <arg:0> gefunden - Das Furniture '<arg:1>' verwendet einen unbekannten Einstellungs-Typ '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>Problem in Datei <arg:0> gefunden - Das Furniture '<arg:1>' verwendet einen ungültigen Hitbox-Typ '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>Problem in Datei <arg:0> gefunden - Das Furniture '<arg:1>' verwendet eine benutzerdefinierte Hitbox mit ungültigem Entity-Typ '<arg:2>'.</yellow>"

View File

@@ -38,6 +38,20 @@ argument.parse.failure.aggregate.missing: "<red>Missing component '<arg:0>'</red
argument.parse.failure.aggregate.failure: "<red>Invalid component '<arg:0>': <arg:1></red>"
argument.parse.failure.either: "<red>Could not resolve <arg:1> or <arg:2> from '<arg:0>'</red>"
argument.parse.failure.namedtextcolor: "<red>'<arg:0>' is not a named text color</red>"
info.pack.load: "Loaded pack: <arg:0>. Default namespace: <arg:1>"
info.resource.load: "Loaded <arg:0> in <arg:1>ms (<arg:2>)"
info.resource_pack.start: "Generating resource pack..."
info.resource_pack.generate: "Generated resource pack in <arg:0>ms"
info.resource_pack.validate: "Validated resource pack in <arg:0>ms"
info.resource_pack.optimize: "Optimized resource pack in <arg:0>ms"
info.resource_pack.optimize.json: "> Optimizing json files..."
info.resource_pack.optimize.texture: "> Optimizing textures..."
info.resource_pack.optimize.result: "□ Before/After/Ratio: <arg:0> KB/<arg:1> KB/<arg:2>%"
info.resource_pack.create: "Created resource pack zip in <arg:0>ms"
info.resource_pack.upload: "Completed uploading resource pack"
info.host.self.netty_server: "Netty HTTP server started on port: <arg:0>"
info.host.cache.load: "[<arg:0>] Loaded cached resource pack metadata"
info.compatibility: "[Compatibility] <arg:0> hooked"
command.reload.config.success: "<white>Configs reloaded in <green><arg:0></green> ms.</white> <gray>(Async: <arg:1>ms | Sync: <arg:2>ms)</gray>"
command.reload.config.failure: "<red>Config reload failed. Check console logs.</red>"
command.reload.pack.success: "<white>Resource pack reloaded in <green><arg:0></green> ms.</white>"
@@ -72,9 +86,10 @@ command.upload.on_progress: "<white>Started uploading progress. Check the consol
command.send_resource_pack.success.single: "<white>Sent resource pack to <arg:0>.</white>"
command.send_resource_pack.success.multiple: "<white>Send resource packs to <arg:0> players.</white>"
command.locale.set.failure: "<red>Invalid locale format: <arg:0></red>"
command.locale.set.success: "<white>Updated selected locale to <arg:0> for <arg:1></white>"
command.locale.set.success: "<white>Selected locale has been set to <arg:0> for <arg:1></white>"
command.locale.unset.success: "<white>Cleared selected locale for <arg:0></white>"
command.entity_view_distance_scale.set.success: "<white>Updated entity view distance scale to <arg:0> for <arg:1></white>"
command.entity_view_distance_scale.set.success: "<white>Entity view distance scale updated to <arg:0> for <arg:1></white>"
command.entity_culling.toggle.success: "<white>Entity culling status updated to <arg:0> for <arg:1></white>"
warning.network.resource_pack.unverified_uuid: "<yellow>Player <arg:0> is attempting to request a resource pack using a UUID (<arg:1>) that is not authenticated by the server.</yellow>"
warning.config.pack.duplicated_files: "<red>Duplicated files Found. Please resolve them through config.yml 'resource-pack.duplicated-files-handler' section.</red>"
warning.config.yaml.duplicated_key: "<red>Issue found in file <arg:0> - Found duplicated key '<arg:1>' at line <arg:2>, this might cause unexpected results.</red>"
@@ -187,6 +202,7 @@ warning.config.recipe.smithing_transform.post_processor.missing_type: "<yellow>I
warning.config.recipe.smithing_transform.post_processor.invalid_type: "<yellow>Issue found in file <arg:0> - The smithing transform recipe '<arg:1>' is using an invalid post processor type '<arg:2>'.</yellow>"
warning.config.recipe.smithing_transform.post_processor.keep_component.missing_components: "<yellow>Issue found in file <arg:0> - The smithing transform recipe '<arg:1>' is missing the required argument 'components' for post-processors 'keep_components'.</yellow>"
warning.config.recipe.smithing_transform.post_processor.keep_component.missing_tags: "<yellow>Issue found in file <arg:0> - The smithing transform recipe '<arg:1>' is missing the required argument 'tags' for post-processors 'keep_tags'.</yellow>"
warning.config.recipe.smithing_transform.post_processor.keep_custom_data.missing_paths: "<yellow>Issue found in file <arg:0> - The smithing transform recipe '<arg:1>' is missing the required argument 'paths' for post-processors 'keep_custom_data'.</yellow>"
warning.config.recipe.smithing_transform.missing_base: "<yellow>Issue found in file <arg:0> - The smithing transform recipe '<arg:1>' is missing the required 'base' argument.</yellow>"
warning.config.recipe.smithing_trim.missing_base: "<yellow>Issue found in file <arg:0> - The smithing trim recipe '<arg:1>' is missing the required 'base' argument.</yellow>"
warning.config.recipe.smithing_trim.missing_template_type: "<yellow>Issue found in file <arg:0> - The smithing trim recipe '<arg:1>' is missing the required 'template-type' argument.</yellow>"
@@ -215,8 +231,9 @@ warning.config.sound.missing_name: "<yellow>Issue found in file <arg:0> - The so
warning.config.jukebox_song.duplicate: "<yellow>Issue found in file <arg:0> - Duplicated jukebox song '<arg:1>'. Please check if there is the same configuration in other files.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Issue found in file <arg:0> - The jukebox song '<arg:1>' is missing the required 'sound' argument.</yellow>"
warning.config.furniture.duplicate: "<yellow>Issue found in file <arg:0> - Duplicated furniture '<arg:1>'. Please check if there is the same configuration in other files.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'placement' argument.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'item' argument for one of its elements.</yellow>"
warning.config.furniture.missing_variants: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'variants' argument.</yellow>"
warning.config.furniture.element.invalid_type: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is using an invalid element type '<arg:2>'.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'item' argument for 'item_display' element.</yellow>"
warning.config.furniture.settings.unknown: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is using an unknown setting type '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is using an invalid hitbox type '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is using a custom hitbox with invalid entity type '<arg:2>'.</yellow>"

View File

@@ -111,8 +111,8 @@ warning.config.sound.missing_name: "<yellow>Problema encontrado en el archivo <a
warning.config.jukebox_song.duplicate: "<yellow>Problema encontrado en el archivo <arg:0> - Canción de tocadiscos duplicada '<arg:1>'. Verifica si hay la misma configuración en otros archivos.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Problema encontrado en el archivo <arg:0> - La canción de tocadiscos '<arg:1>' carece del argumento requerido 'sound'.</yellow>"
warning.config.furniture.duplicate: "<yellow>Problema encontrado en el archivo <arg:0> - Mueble duplicado '<arg:1>'. Verifica si hay la misma configuración en otros archivos.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' carece del argumento requerido 'placement'.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' carece del argumento requerido 'item' para uno de sus elementos.</yellow>"
warning.config.furniture.missing_variants: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' carece del argumento requerido 'variants'.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' carece del argumento requerido 'item' para uno de sus elementos.</yellow>"
warning.config.furniture.settings.unknown: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' está usando un tipo de configuración desconocido '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' está usando un tipo de hitbox inválido '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' está usando un hitbox personalizado con un tipo de entidad inválido '<arg:2>'.</yellow>"

View File

@@ -175,8 +175,8 @@ warning.config.sound.missing_name: "<yellow>Problème trouvé dans le fichier <a
warning.config.jukebox_song.duplicate: "<yellow>Problème trouvé dans le fichier <arg:0> - Chanson de jukebox dupliquée '<arg:1>'. Vérifiez sil existe la même configuration dans dautres fichiers.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Problème trouvé dans le fichier <arg:0> - La chanson de jukebox '<arg:1>' manque largument obligatoire 'sound'.</yellow>"
warning.config.furniture.duplicate: "<yellow>Problème trouvé dans le fichier <arg:0> - Meuble dupliqué '<arg:1>'. Vérifiez sil existe la même configuration dans dautres fichiers.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' manque largument obligatoire 'placement'.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' manque largument obligatoire 'item' pour un de ses éléments.</yellow>"
warning.config.furniture.missing_variants: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' manque largument obligatoire 'variants'.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' manque largument obligatoire 'item' pour un de ses éléments.</yellow>"
warning.config.furniture.settings.unknown: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' utilise un type de paramètre inconnu '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' utilise un type de hitbox invalide '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' utilise un hitbox personnalisé avec un type dentité invalide '<arg:2>'.</yellow>"

View File

@@ -148,8 +148,8 @@ warning.config.sound.missing_name: "<yellow>Проблема найдена в
warning.config.jukebox_song.duplicate: "<yellow>Проблема найдена в файле <arg:0> - Дублированная песня музыкального автомата '<arg:1>'. Проверьте, есть ли такая же конфигурация в других файлах.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Проблема найдена в файле <arg:0> - В песне музыкального автомата '<arg:1>' отсутствует необходимый 'sound' аргумент.</yellow>"
warning.config.furniture.duplicate: "<yellow>Проблема найдена в файле <arg:0> - Дублированная мебель '<arg:1>'. Проверьте, есть ли такая же конфигурация в других файлах.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'placement' аргумент.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'item' аргумент для одного из его элементов.</yellow>"
warning.config.furniture.missing_variants: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'variants' аргумент.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'item' аргумент для одного из его элементов.</yellow>"
warning.config.furniture.settings.unknown: "<yellow>Проблема найдена в файле <arg:0> - Мебель '<arg:1>' использует неизвестный тип настройки '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>Проблема найдена в файле <arg:0> - Мебель '<arg:1>' использует недопустимый тип хитбокса '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>Проблема найдена в файле <arg:0> - Мебель '<arg:1>' использует пользовательский хитбокс с недопустимым типом сущности '<arg:2>'.</yellow>"

View File

@@ -110,8 +110,8 @@ warning.config.sound.missing_name: "<yellow><arg:0> dosyasında sorun bulundu -
warning.config.jukebox_song.duplicate: "<yellow><arg:0> dosyasında sorun bulundu - Yinelenen müzik çalar şarkısı '<arg:1>'. Diğer dosyalarda aynı yapılandırmanın olup olmadığını kontrol edin.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' müzik çalar şarkısı gerekli 'sound' argümanı eksik.</yellow>"
warning.config.furniture.duplicate: "<yellow><arg:0> dosyasında sorun bulundu - Yinelenen mobilya '<arg:1>'. Diğer dosyalarda aynı yapılandırmanın olup olmadığını kontrol edin.</yellow>"
warning.config.furniture.missing_placement: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası gerekli 'placement' argümanı eksik.</yellow>"
warning.config.furniture.element.missing_item: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası, elementlerinden biri için gerekli 'item' argümanı eksik.</yellow>"
warning.config.furniture.missing_variants: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası gerekli 'variants' argümanı eksik.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası, elementlerinden biri için gerekli 'item' argümanı eksik.</yellow>"
warning.config.furniture.settings.unknown: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası bilinmeyen bir ayar türü '<arg:2>' kullanıyor.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası geçersiz bir hitbox türü '<arg:2>' kullanıyor.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası, geçersiz varlık türü '<arg:2>' olan özel bir hitbox kullanıyor.</yellow>"

View File

@@ -38,6 +38,20 @@ argument.parse.failure.aggregate.missing: "<red>缺少组件 '<arg:0>'</red>"
argument.parse.failure.aggregate.failure: "<red>无效的组件 '<arg:0>': <arg:1></red>"
argument.parse.failure.either: "<red>无法从 '<arg:0>' 解析 <arg:1> 或 <arg:2></red>"
argument.parse.failure.namedtextcolor: "<red>'<arg:0>' 不是颜色代码</red>"
info.pack.load: "已加载包: <arg:0>. 默认命名空间: <arg:1>"
info.resource.load: "加载 <arg:0> 耗时 <arg:1>ms (<arg:2>)"
info.resource_pack.start: "正在开始生成资源包..."
info.resource_pack.generate: "生成资源包耗时 <arg:0>ms"
info.resource_pack.validate: "验证资源包耗时 <arg:0>ms"
info.resource_pack.optimize: "优化资源包耗时 <arg:0>ms"
info.resource_pack.optimize.json: "> 正在优化json文件..."
info.resource_pack.optimize.texture: "> 正在优化贴图文件..."
info.resource_pack.optimize.result: "□ 优化前/优化后/比例: <arg:0> KB/<arg:1> KB/<arg:2>%"
info.resource_pack.create: "创建资源包文件耗时 <arg:0>ms"
info.resource_pack.upload: "资源包上传完成"
info.host.self.netty_server: "Netty HTTP 服务已在端口 <arg:0> 开启"
info.host.cache.load: "[<arg:0>] 已加载缓存的资源包元数据"
info.compatibility: "[兼容性] 已挂钩 <arg:0>"
command.reload.config.success: "<white>重新加载配置完成. 耗时 <green><arg:0></green> 毫秒</white> <gray>(异步: <arg:1>ms | 同步: <arg:2>ms)</gray>"
command.reload.config.failure: "<red>重新加载配置失败, 请检查控制台日志</red>"
command.reload.pack.success: "<white>资源包重新加载完成. 耗时 <green><arg:0></green> 毫秒</white>"
@@ -185,6 +199,7 @@ warning.config.recipe.smithing_transform.post_processor.missing_type: "<yellow>
warning.config.recipe.smithing_transform.post_processor.invalid_type: "<yellow>在文件 <arg:0> 发现问题 - 锻造升级配方 '<arg:1>' 使用了无效的后处理器类型 '<arg:2>'</yellow>"
warning.config.recipe.smithing_transform.post_processor.keep_component.missing_components: "<yellow>在文件 <arg:0> 发现问题 - 锻造升级配方 '<arg:1>' 的 'keep_components' 后处理器缺少必需的 'components' 参数</yellow>"
warning.config.recipe.smithing_transform.post_processor.keep_component.missing_tags: "<yellow>在文件 <arg:0> 发现问题 - 锻造升级配方 '<arg:1>' 的 'keep_tags' 后处理器缺少必需的 'tags' 参数</yellow>"
warning.config.recipe.smithing_transform.post_processor.keep_custom_data.missing_paths: "<yellow>在文件 <arg:0> 发现问题 - 锻造升级配方 '<arg:1>' 的 'keep_custom_data' 后处理器缺少必需的 'paths' 参数</yellow>"
warning.config.recipe.smithing_transform.missing_base: "<yellow>在文件 <arg:0> 发现问题 - 锻造升级配方 '<arg:1>' 缺少必需的 'base' 参数</yellow>"
warning.config.recipe.smithing_trim.missing_base: "<yellow>在文件 <arg:0> 发现问题 - 锻造纹饰配方 '<arg:1>' 缺少必需的 'base' 参数</yellow>"
warning.config.recipe.smithing_trim.missing_template_type: "<yellow>在文件 <arg:0> 发现问题 - 锻造纹饰配方 '<arg:1>' 缺少必需的 'template-type' 参数</yellow>"
@@ -209,8 +224,8 @@ warning.config.sound.missing_name: "<yellow>在文件 <arg:0> 发现问题 - 音
warning.config.jukebox_song.duplicate: "<yellow>在文件 <arg:0> 发现问题 - 重复的唱片机歌曲 '<arg:1>' 请检查其他文件中是否存在相同配置</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>在文件 <arg:0> 发现问题 - 唱片机歌曲 '<arg:1>' 缺少必需的 'sound' 参数</yellow>"
warning.config.furniture.duplicate: "<yellow>在文件 <arg:0> 发现问题 - 重复的家具 '<arg:1>' 请检查其他文件中是否存在相同配置</yellow>"
warning.config.furniture.missing_placement: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 缺少必需的 'placement' 参数</yellow>"
warning.config.furniture.element.missing_item: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 的某个元素缺少必需的 'item' 参数</yellow>"
warning.config.furniture.missing_variants: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 缺少必需的 'variants' 参数</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 的 'item_display' 元素缺少必需的 'item' 参数</yellow>"
warning.config.furniture.settings.unknown: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 使用了未知的设置类型 '<arg:2>'</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 使用了无效的碰撞箱类型 '<arg:2>'</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 的自定义碰撞箱使用了无效的实体类型 '<arg:2>'</yellow>"

View File

@@ -261,6 +261,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
public class BlockStateMappingParser extends SectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[]{"block-state-mappings", "block-state-mapping"};
private int count;
@Override
public String[] sectionId() {
@@ -272,6 +273,16 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
return LoadingSequence.BLOCK_STATE_MAPPING;
}
@Override
public int count() {
return this.count;
}
@Override
public void preProcess() {
this.count = 0;
}
@Override
public void parseSection(Pack pack, Path path, Map<String, Object> section) throws LocalizedException {
ExceptionCollector<LocalizedResourceConfigException> exceptionCollector = new ExceptionCollector<>();
@@ -302,6 +313,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
List<BlockStateWrapper> blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(blockOwnerId, k -> new ArrayList<>());
blockStateWrappers.add(beforeState);
AbstractBlockManager.this.autoVisualBlockStateCandidates[beforeState.registryId()] = createVisualBlockCandidate(beforeState);
this.count++;
}
exceptionCollector.throwIfPresent();
}
@@ -331,6 +343,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
this.visualBlockStateAllocator = new VisualBlockStateAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("visual-block-states.json"), candidates, AbstractBlockManager.this::createVanillaBlockState);
}
@Override
public int count() {
return AbstractBlockManager.this.byId.size();
}
public void addPendingConfigSection(PendingConfigSection section) {
this.pendingConfigSections.add(section);
}
@@ -711,12 +728,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
}
private CullingData parseCullingData(Object arguments) {
if (arguments instanceof Boolean b && !b) {
return null;
}
if (!(arguments instanceof Map)) {
return new CullingData(DEFAULT_BLOCK_ENTITY_AABB, Config.entityCullingViewDistance(), 0.5, true);
}
if (arguments instanceof Boolean b && !b) return null;
if (!(arguments instanceof Map)) return new CullingData(DEFAULT_BLOCK_ENTITY_AABB, Config.entityCullingViewDistance(), 0.5, true);
Map<String, Object> argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling");
return new CullingData(
ResourceConfigUtils.getAsAABB(argumentsMap.getOrDefault("aabb", 1), "aabb"),

View File

@@ -41,6 +41,7 @@ public class BlockSettings {
float friction = 0.6f;
float speedFactor = 1f;
float jumpFactor = 1f;
Map<CustomDataType<?>, Object> customData = new IdentityHashMap<>(4);
private BlockSettings() {}
@@ -107,9 +108,29 @@ public class BlockSettings {
newSettings.speedFactor = settings.speedFactor;
newSettings.jumpFactor = settings.jumpFactor;
newSettings.friction = settings.friction;
newSettings.customData = new IdentityHashMap<>(settings.customData);
return newSettings;
}
@SuppressWarnings("unchecked")
public <T> T getCustomData(CustomDataType<T> type) {
return (T) this.customData.get(type);
}
public void clearCustomData() {
this.customData.clear();
}
@Nullable
@SuppressWarnings("unchecked")
public <T> T removeCustomData(CustomDataType<?> type) {
return (T) this.customData.remove(type);
}
public <T> void addCustomData(CustomDataType<T> key, T value) {
this.customData.put(key, value);
}
public Set<Key> tags() {
return tags;
}
@@ -542,7 +563,7 @@ public class BlockSettings {
}));
}
private static void registerFactory(String id, Modifier.Factory factory) {
public static void registerFactory(String id, Modifier.Factory factory) {
FACTORIES.put(id, factory);
}
}

View File

@@ -24,7 +24,7 @@ public abstract class BlockEntity {
this.type = type;
}
public final CompoundTag saveAsTag() {
public CompoundTag saveAsTag() {
CompoundTag tag = new CompoundTag();
this.saveId(tag);
this.savePos(tag);

View File

@@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.util.Key;
public final class BlockEntityTypeKeys {
private BlockEntityTypeKeys() {}
public static final Key INACTIVE = Key.of("craftengine:inactive");
public static final Key UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite");
public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage");
public static final Key SIMPLE_PARTICLE = Key.of("craftengine:simple_particle");

View File

@@ -7,6 +7,7 @@ import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceKey;
public abstract class BlockEntityTypes {
public static final BlockEntityType<InactiveBlockEntity> INACTIVE = register(BlockEntityTypeKeys.INACTIVE);
public static <T extends BlockEntity> BlockEntityType<T> register(Key id) {
BlockEntityType<T> type = new BlockEntityType<>(id);

View File

@@ -0,0 +1,21 @@
package net.momirealms.craftengine.core.block.entity;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.sparrow.nbt.CompoundTag;
public class InactiveBlockEntity extends BlockEntity {
private final CompoundTag tag;
public InactiveBlockEntity(BlockPos pos,
ImmutableBlockState blockState,
CompoundTag tag) {
super(BlockEntityTypes.INACTIVE, pos, blockState);
this.tag = tag;
}
@Override
public CompoundTag saveAsTag() {
return this.tag;
}
}

View File

@@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.block.entity.render.element;
import java.util.Map;
@FunctionalInterface
public interface BlockEntityElementConfigFactory {
public interface BlockEntityElementConfigFactory<E extends BlockEntityElement> {
<E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> args);
BlockEntityElementConfig<E> create(Map<String, Object> args);
}

View File

@@ -15,14 +15,15 @@ public abstract class BlockEntityElementConfigs {
public static final Key TEXT_DISPLAY = Key.of("craftengine:text_display");
public static final Key ITEM = Key.of("craftengine:item");
public static void register(Key key, BlockEntityElementConfigFactory type) {
((WritableRegistry<BlockEntityElementConfigFactory>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE)
public static void register(Key key, BlockEntityElementConfigFactory<?> type) {
((WritableRegistry<BlockEntityElementConfigFactory<?>>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE)
.register(ResourceKey.create(Registries.BLOCK_ENTITY_ELEMENT_TYPE.location(), key), type);
}
public static <E extends BlockEntityElement> BlockEntityElementConfig<E> fromMap(Map<String, Object> arguments) {
Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(it -> Key.withDefaultNamespace(it, "craftengine")).orElse(ITEM_DISPLAY);
BlockEntityElementConfigFactory factory = BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type);
@SuppressWarnings("unchecked")
BlockEntityElementConfigFactory<E> factory = (BlockEntityElementConfigFactory<E>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type);
if (factory == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.entity_renderer.invalid_type", type.toString());
}

View File

@@ -11,6 +11,8 @@ import java.util.UUID;
public interface Entity {
Key type();
boolean isValid();
double x();
double y();

View File

@@ -12,14 +12,14 @@ public interface EntityData<T> {
Object entityDataAccessor();
Object create(Object entityDataAccessor, Object value);
Object create(Object entityDataAccessor, T value);
default Object createEntityDataIfNotDefaultValue(T value) {
if (defaultValue().equals(value)) return null;
return create(entityDataAccessor(), value);
}
default Object createEntityData(Object value) {
default Object createEntityData(T value) {
return create(entityDataAccessor(), value);
}

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.core.entity;
package net.momirealms.craftengine.core.entity.display;
public enum Billboard {
FIXED(0),

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.core.entity;
package net.momirealms.craftengine.core.entity.display;
public enum ItemDisplayContext {
NONE(0),

View File

@@ -1,89 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public abstract class AbstractCustomFurniture implements CustomFurniture {
private final Key id;
private final FurnitureSettings settings;
private final Map<AnchorType, Placement> placements;
private final Map<EventTrigger, List<Function<Context>>> events;
@Nullable
private final LootTable<?> lootTable;
private final AnchorType anyType;
protected AbstractCustomFurniture(@NotNull Key id,
@NotNull FurnitureSettings settings,
@NotNull Map<AnchorType, Placement> placements,
@NotNull Map<EventTrigger, List<Function<Context>>> events,
@Nullable LootTable<?> lootTable) {
this.id = id;
this.settings = settings;
this.placements = placements;
this.lootTable = lootTable;
this.events = events;
this.anyType = placements.keySet().stream().findFirst().orElse(null);
}
@Override
public void execute(Context context, EventTrigger trigger) {
for (Function<Context> function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) {
function.run(context);
}
}
@Override
public Key id() {
return this.id;
}
@Override
public Map<AnchorType, Placement> placements() {
return this.placements;
}
@Override
public FurnitureSettings settings() {
return this.settings;
}
@Override
public @Nullable LootTable<?> lootTable() {
return this.lootTable;
}
@Override
public AnchorType getAnyAnchorType() {
return this.anyType;
}
@Override
public boolean isAllowedPlacement(AnchorType anchorType) {
return this.placements.containsKey(anchorType);
}
@Override
public Placement getPlacement(AnchorType anchorType) {
return this.placements.get(anchorType);
}
@Override
public Placement getValidPlacement(AnchorType anchorType) {
Placement placement = this.placements.get(anchorType);
if (placement == null) {
return this.placements.get(getAnyAnchorType());
}
return placement;
}
}

View File

@@ -1,92 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.util.Key;
import org.joml.Quaternionf;
import org.joml.Vector3f;
public abstract class AbstractFurnitureElement implements FurnitureElement {
private final Key item;
private final Billboard billboard;
private final ItemDisplayContext transform;
private final Vector3f scale;
private final Vector3f translation;
private final Vector3f position;
private final Quaternionf rotation;
private final boolean applyDyedColor;
private final float shadowRadius;
private final float shadowStrength;
public AbstractFurnitureElement(Key item,
Billboard billboard,
ItemDisplayContext transform,
Vector3f scale,
Vector3f translation,
Vector3f position,
Quaternionf rotation,
float shadowRadius,
float shadowStrength,
boolean applyDyedColor) {
this.billboard = billboard;
this.transform = transform;
this.scale = scale;
this.translation = translation;
this.item = item;
this.rotation = rotation;
this.position = position;
this.applyDyedColor = applyDyedColor;
this.shadowRadius = shadowRadius;
this.shadowStrength = shadowStrength;
}
@Override
public float shadowRadius() {
return shadowRadius;
}
@Override
public float shadowStrength() {
return shadowStrength;
}
@Override
public boolean applyDyedColor() {
return applyDyedColor;
}
@Override
public Quaternionf rotation() {
return rotation;
}
@Override
public Key item() {
return item;
}
@Override
public Billboard billboard() {
return billboard;
}
@Override
public ItemDisplayContext transform() {
return transform;
}
@Override
public Vector3f scale() {
return scale;
}
@Override
public Vector3f translation() {
return translation;
}
@Override
public Vector3f position() {
return position;
}
}

View File

@@ -1,14 +1,18 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigs;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxTypes;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.PendingConfigSection;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser;
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions;
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.Key;
@@ -21,7 +25,7 @@ import java.nio.file.Path;
import java.util.*;
public abstract class AbstractFurnitureManager implements FurnitureManager {
protected final Map<Key, CustomFurniture> byId = new HashMap<>();
protected final Map<Key, FurnitureConfig> byId = new HashMap<>();
private final CraftEngine plugin;
private final FurnitureParser furnitureParser;
// Cached command suggestions
@@ -56,12 +60,12 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
}
@Override
public Optional<CustomFurniture> furnitureById(Key id) {
public Optional<FurnitureConfig> furnitureById(Key id) {
return Optional.ofNullable(this.byId.get(id));
}
@Override
public Map<Key, CustomFurniture> loadedFurniture() {
public Map<Key, FurnitureConfig> loadedFurniture() {
return Collections.unmodifiableMap(this.byId);
}
@@ -70,11 +74,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
this.byId.clear();
}
protected abstract HitBoxConfig defaultHitBox();
protected abstract FurnitureElement.Builder furnitureElementBuilder();
protected abstract CustomFurniture.Builder furnitureBuilder();
protected abstract FurnitureHitBoxConfig<?> defaultHitBox();
public class FurnitureParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" };
@@ -107,91 +107,76 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
return LoadingSequence.FURNITURE;
}
@SuppressWarnings("unchecked")
@Override
public int count() {
return AbstractFurnitureManager.this.byId.size();
}
@Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (AbstractFurnitureManager.this.byId.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.furniture.duplicate");
}
EnumMap<AnchorType, CustomFurniture.Placement> placements = new EnumMap<>(AnchorType.class);
Object placementObj = section.get("placement");
Map<String, Object> placementMap = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(placementObj, "warning.config.furniture.missing_placement"), false);
if (placementMap.isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.furniture.missing_placement");
}
for (Map.Entry<String, Object> entry : placementMap.entrySet()) {
// anchor type
AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH));
Map<String, Object> placementArguments = MiscUtils.castToMap(entry.getValue(), false);
Optional<Vector3f> optionalLootSpawnOffset = Optional.ofNullable(placementArguments.get("loot-spawn-offset")).map(it -> ResourceConfigUtils.getAsVector3f(it, "loot-spawn-offset"));
// furniture display elements
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) {
FurnitureElement furnitureElement = furnitureElementBuilder()
.item(Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(element.get("item"), "warning.config.furniture.element.missing_item")))
.applyDyedColor(ResourceConfigUtils.getAsBoolean(element.getOrDefault("apply-dyed-color", true), "apply-dyed-color"))
.billboard(ResourceConfigUtils.getOrDefault(element.get("billboard"), o -> Billboard.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), Billboard.FIXED))
.transform(ResourceConfigUtils.getOrDefault(ResourceConfigUtils.get(element, "transform", "display-transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE))
.scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale"))
.position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position"))
.translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation"))
.rotation(ResourceConfigUtils.getAsQuaternionf(element.getOrDefault("rotation", "0"), "rotation"))
.shadowRadius(ResourceConfigUtils.getAsFloat(element.getOrDefault("shadow-radius", 0f), "shadow-radius"))
.shadowStrength(ResourceConfigUtils.getAsFloat(element.getOrDefault("shadow-strength", 1f), "shadow-strength"))
.build();
elements.add(furnitureElement);
Map<String, Object> variantsMap = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "variants", "placement", "variant"), "warning.config.furniture.missing_variants"), "variants");
if (variantsMap.isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.furniture.missing_variants");
}
// external model providers
Map<String, FurnitureVariant> variants = new HashMap<>();
for (Map.Entry<String, Object> e0 : variantsMap.entrySet()) {
String variantName = e0.getKey();
Map<String, Object> variantArguments = ResourceConfigUtils.getAsMap(e0.getValue(), variantName);
Optional<Vector3f> optionalLootSpawnOffset = Optional.ofNullable(variantArguments.get("loot-spawn-offset")).map(it -> ResourceConfigUtils.getAsVector3f(it, "loot-spawn-offset"));
List<FurnitureElementConfig<?>> elements = ResourceConfigUtils.parseConfigAsList(variantArguments.get("elements"), FurnitureElementConfigs::fromMap);
// fixme 外部模型不应该在这
Optional<ExternalModel> externalModel;
if (placementArguments.containsKey("model-engine")) {
externalModel = Optional.of(plugin.compatibilityManager().createModel("ModelEngine", placementArguments.get("model-engine").toString()));
} else if (placementArguments.containsKey("better-model")) {
externalModel = Optional.of(plugin.compatibilityManager().createModel("BetterModel", placementArguments.get("better-model").toString()));
if (variantArguments.containsKey("model-engine")) {
externalModel = Optional.of(plugin.compatibilityManager().createModel("ModelEngine", variantArguments.get("model-engine").toString()));
} else if (variantArguments.containsKey("better-model")) {
externalModel = Optional.of(plugin.compatibilityManager().createModel("BetterModel", variantArguments.get("better-model").toString()));
} else {
externalModel = Optional.empty();
}
// add hitboxes
List<HitBoxConfig> hitboxes = ResourceConfigUtils.parseConfigAsList(placementArguments.get("hitboxes"), HitBoxTypes::fromMap);
List<FurnitureHitBoxConfig<?>> hitboxes = ResourceConfigUtils.parseConfigAsList(variantArguments.get("hitboxes"), FurnitureHitBoxTypes::fromMap);
if (hitboxes.isEmpty() && externalModel.isEmpty()) {
hitboxes = List.of(defaultHitBox());
}
// rules
Map<String, Object> ruleSection = MiscUtils.castToMap(placementArguments.get("rules"), true);
if (ruleSection != null) {
placements.put(anchorType, new CustomFurniture.Placement(
anchorType,
elements.toArray(new FurnitureElement[0]),
hitboxes.toArray(new HitBoxConfig[0]),
ResourceConfigUtils.getOrDefault(ruleSection.get("rotation"), o -> RotationRule.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), RotationRule.ANY),
ResourceConfigUtils.getOrDefault(ruleSection.get("alignment"), o -> AlignmentRule.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), AlignmentRule.CENTER),
variants.put(variantName, new FurnitureVariant(
variantName,
parseCullingData(section.get("entity-culling")),
elements.toArray(new FurnitureElementConfig[0]),
hitboxes.toArray(new FurnitureHitBoxConfig[0]),
externalModel,
optionalLootSpawnOffset
));
} else {
placements.put(anchorType, new CustomFurniture.Placement(
anchorType,
elements.toArray(new FurnitureElement[0]),
hitboxes.toArray(new HitBoxConfig[0]),
RotationRule.ANY,
AlignmentRule.CENTER,
externalModel,
optionalLootSpawnOffset
));
}
}
CustomFurniture furniture = furnitureBuilder()
FurnitureConfig furniture = FurnitureConfig.builder()
.id(id)
.settings(FurnitureSettings.fromMap(MiscUtils.castToMap(section.get("settings"), true)))
.placement(placements)
.variants(variants)
.events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")))
.lootTable(LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), true)))
.build();
AbstractFurnitureManager.this.byId.put(id, furniture);
}
private CullingData parseCullingData(Object arguments) {
if (arguments instanceof Boolean b && !b)
return null;
if (!(arguments instanceof Map))
return new CullingData(null, Config.entityCullingViewDistance(), 0.25, true);
Map<String, Object> argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling");
return new CullingData(
ResourceConfigUtils.getOrDefault(argumentsMap.get("aabb"), it -> ResourceConfigUtils.getAsAABB(it, "aabb"), null),
ResourceConfigUtils.getAsInt(argumentsMap.getOrDefault("view-distance", Config.entityCullingViewDistance()), "view-distance"),
ResourceConfigUtils.getAsDouble(argumentsMap.getOrDefault("aabb-expansion", 0.25), "aabb-expansion"),
ResourceConfigUtils.getAsBoolean(argumentsMap.getOrDefault("ray-tracing", true), "ray-tracing")
);
}
}
}

View File

@@ -1,20 +1,26 @@
package net.momirealms.craftengine.core.entity.furniture;
public enum AnchorType {
GROUND(0),
WALL(1),
CEILING(2);
GROUND(0, "ground"),
WALL(1, "wall"),
CEILING(2, "ceiling");
private final int id;
private final String variantName;
AnchorType(int id) {
AnchorType(int id, String variantName) {
this.id = id;
this.variantName = variantName;
}
public int getId() {
return id;
}
public String variantName() {
return variantName;
}
public static AnchorType byId(int id) {
return values()[id];
}

View File

@@ -1,60 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import java.util.List;
import java.util.Map;
import java.util.Optional;
// TODO 家具的设计存在问题。家具也应该存在不同的状态,而不是根据放置规则直接决定状态类型
public interface CustomFurniture {
void execute(Context context, EventTrigger trigger);
Key id();
Map<AnchorType, Placement> placements();
FurnitureSettings settings();
@Nullable
LootTable<?> lootTable();
AnchorType getAnyAnchorType();
boolean isAllowedPlacement(AnchorType anchorType);
Placement getPlacement(AnchorType anchorType);
Placement getValidPlacement(AnchorType anchorType);
interface Builder {
Builder id(Key id);
Builder placement(Map<AnchorType, Placement> placements);
Builder settings(FurnitureSettings settings);
Builder lootTable(LootTable<?> lootTable);
Builder events(Map<EventTrigger, List<Function<Context>>> events);
CustomFurniture build();
}
record Placement(AnchorType anchorType,
FurnitureElement[] elements,
HitBoxConfig[] hitBoxConfigs,
RotationRule rotationRule,
AlignmentRule alignmentRule,
Optional<ExternalModel> externalModel,
Optional<Vector3f> dropOffset) {
}
}

View File

@@ -1,48 +1,274 @@
package net.momirealms.craftengine.core.entity.furniture;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.momirealms.craftengine.core.entity.AbstractEntity;
import net.momirealms.craftengine.core.entity.Entity;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElement;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import net.momirealms.craftengine.core.world.Cullable;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface Furniture {
void initializeColliders();
public abstract class Furniture implements Cullable {
public final FurnitureConfig config;
public final FurnitureDataAccessor dataAccessor;
public final Entity metaDataEntity;
WorldPosition position();
protected CullingData cullingData;
protected FurnitureVariant currentVariant;
protected FurnitureElement[] elements;
protected Collider[] colliders;
protected FurnitureHitBox[] hitboxes;
protected Int2ObjectMap<FurnitureHitBox> hitboxMap;
protected int[] virtualEntityIds;
protected int[] colliderEntityIds;
boolean isValid();
private boolean hasExternalModel;
void destroy();
protected Furniture(Entity metaDataEntity, FurnitureDataAccessor data, FurnitureConfig config) {
this.config = config;
this.dataAccessor = data;
this.metaDataEntity = metaDataEntity;
this.setVariant(config.getVariant(data));
}
void destroyColliders();
public Entity metaDataEntity() {
return this.metaDataEntity;
}
void destroySeats();
public FurnitureVariant getCurrentVariant() {
return this.currentVariant;
}
UUID uuid();
public void setVariant(FurnitureVariant variant) {
this.currentVariant = variant;
this.hitboxMap = new Int2ObjectOpenHashMap<>();
// 初始化家具元素
IntList virtualEntityIds = new IntArrayList();
FurnitureElementConfig<?>[] elementConfigs = variant.elementConfigs();
this.elements = new FurnitureElement[elementConfigs.length];
for (int i = 0; i < elementConfigs.length; i++) {
FurnitureElement element = elementConfigs[i].create(this);
this.elements[i] = element;
element.collectVirtualEntityId(virtualEntityIds::addLast);
}
// 初始化碰撞箱
FurnitureHitBoxConfig<?>[] furnitureHitBoxConfigs = variant.hitBoxConfigs();
ObjectArrayList<Collider> colliders = new ObjectArrayList<>(furnitureHitBoxConfigs.length);
this.hitboxes = new FurnitureHitBox[furnitureHitBoxConfigs.length];
for (int i = 0; i < furnitureHitBoxConfigs.length; i++) {
FurnitureHitBox hitbox = furnitureHitBoxConfigs[i].create(this);
this.hitboxes[i] = hitbox;
for (FurnitureHitboxPart part : hitbox.parts()) {
this.hitboxMap.put(part.entityId(), hitbox);
}
hitbox.collectVirtualEntityId(virtualEntityIds::addLast);
colliders.addAll(hitbox.colliders());
}
// 虚拟碰撞箱的实体id
this.virtualEntityIds = virtualEntityIds.toIntArray();
this.colliders = colliders.toArray(new Collider[0]);
this.colliderEntityIds = colliders.stream().mapToInt(Collider::entityId).toArray();
this.cullingData = createCullingData(variant.cullingData());
// 外部模型
Optional<ExternalModel> externalModel = variant.externalModel();
if (externalModel.isPresent()) {
this.hasExternalModel = true;
try {
externalModel.get().bindModel((AbstractEntity) this.metaDataEntity);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to load external model for furniture " + id(), e);
}
} else {
this.hasExternalModel = false;
}
}
int baseEntityId();
private CullingData createCullingData(CullingData parent) {
if (parent == null) return null;
AABB aabb = parent.aabb;
WorldPosition position = position();
if (aabb == null) {
List<AABB> aabbs = new ArrayList<>();
for (FurnitureHitBoxConfig<?> hitBoxConfig : this.currentVariant.hitBoxConfigs()) {
hitBoxConfig.prepareForPlacement(position, aabbs::add);
}
return new CullingData(getMaxAABB(aabbs), parent.maxDistance, parent.aabbExpansion, parent.rayTracing);
} else {
Vector3f[] vertices = new Vector3f[] {
// 底面两个对角点
new Vector3f((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ),
new Vector3f((float) aabb.maxX, (float) aabb.minY, (float) aabb.maxZ),
// 顶面两个对角点
new Vector3f((float) aabb.minX, (float) aabb.maxY, (float) aabb.minZ),
new Vector3f((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ)
};
double minX = Double.MAX_VALUE, minY = aabb.minY; // Y方向不变
double maxX = -Double.MAX_VALUE, maxY = aabb.maxY; // Y方向不变
double minZ = Double.MAX_VALUE, maxZ = -Double.MAX_VALUE;
for (Vector3f vertex : vertices) {
Vec3d rotatedPos = getRelativePosition(position, vertex);
minX = Math.min(minX, rotatedPos.x);
minZ = Math.min(minZ, rotatedPos.z);
maxX = Math.max(maxX, rotatedPos.x);
maxZ = Math.max(maxZ, rotatedPos.z);
}
return new CullingData(new AABB(minX, minY, minZ, maxX, maxY, maxZ),
parent.maxDistance, parent.aabbExpansion, parent.rayTracing);
}
}
private static @NotNull AABB getMaxAABB(List<AABB> aabbs) {
double minX = 0;
double minY = 0;
double minZ = 0;
double maxX = 0;
double maxY = 0;
double maxZ = 0;
for (int i = 0; i < aabbs.size(); i++) {
AABB aabb = aabbs.get(i);
if (i == 0) {
minX = aabb.minX;
minY = aabb.minY;
minZ = aabb.minZ;
maxX = aabb.maxX;
maxY = aabb.maxY;
maxZ = aabb.maxZ;
} else {
minX = Math.min(minX, aabb.minX);
minY = Math.min(minY, aabb.minY);
minZ = Math.min(minZ, aabb.minZ);
maxX = Math.max(maxX, aabb.maxX);
maxY = Math.max(maxY, aabb.maxY);
maxZ = Math.max(maxZ, aabb.maxZ);
}
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
@Nullable
HitBox hitBoxByEntityId(int id);
@Nullable HitBoxPart hitBoxPartByEntityId(int id);
@NotNull
AnchorType anchorType();
@NotNull
Key id();
@NotNull
CustomFurniture config();
boolean hasExternalModel();
FurnitureExtraData extraData();
void setExtraData(FurnitureExtraData extraData);
void save();
public FurnitureHitBox hitboxByEntityId(int entityId) {
return this.hitboxMap.get(entityId);
}
@Nullable
@Override
public CullingData cullingData() {
return this.cullingData;
}
public Key id() {
return this.config.id();
}
// 会发给玩家的包
public int[] virtualEntityIds() {
return this.virtualEntityIds;
}
public int[] colliderEntityIds() {
return colliderEntityIds;
}
public UUID uuid() {
return this.metaDataEntity.uuid();
}
@Override
public void show(Player player) {
for (FurnitureElement element : this.elements) {
element.show(player);
}
for (FurnitureHitBox hitbox : this.hitboxes) {
hitbox.show(player);
}
}
@Override
public void hide(Player player) {
for (FurnitureElement element : this.elements) {
element.hide(player);
}
for (FurnitureHitBox hitbox : this.hitboxes) {
hitbox.hide(player);
}
}
public abstract void addCollidersToWorld();
public void destroySeats() {
for (FurnitureHitBox hitbox : this.hitboxes) {
for (Seat<FurnitureHitBox> seat : hitbox.seats()) {
seat.destroy();
}
}
}
public boolean isValid() {
return this.metaDataEntity.isValid();
}
public abstract void destroy();
public FurnitureConfig config() {
return this.config;
}
public FurnitureDataAccessor dataAccessor() {
return this.dataAccessor;
}
public Collider[] colliders() {
return this.colliders;
}
public WorldPosition position() {
return this.metaDataEntity.position();
}
public int entityId() {
return this.metaDataEntity.entityID();
}
public boolean hasExternalModel() {
return hasExternalModel;
}
public Vec3d getRelativePosition(Vector3f position) {
return getRelativePosition(this.position(), position);
}
public static Vec3d getRelativePosition(WorldPosition location, Vector3f position) {
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0f, (float) Math.toRadians(180 - location.yRot()), 0f).conjugate();
Vector3f offset = conjugated.transform(new Vector3f(position));
return new Vec3d(location.x + offset.x, location.y + offset.y, location.z - offset.z);
}
public World world() {
return this.metaDataEntity.world();
}
}

View File

@@ -0,0 +1,6 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.util.Color;
public record FurnitureColorSource(Color dyedColor, int[] fireworkColors) {
}

View File

@@ -0,0 +1,90 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.furniture.behavior.FurnitureBehavior;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
public interface FurnitureConfig {
void execute(Context context, EventTrigger trigger);
Key id();
FurnitureSettings settings();
@Nullable
LootTable<?> lootTable();
Map<String, FurnitureVariant> variants();
default FurnitureVariant anyVariant() {
return variants().values().stream().findFirst().get();
}
default String anyVariantName() {
return variants().keySet().stream().findFirst().get();
}
@Nullable
FurnitureVariant getVariant(String variantName);
@NotNull
FurnitureBehavior behavior();
@NotNull
default FurnitureVariant getVariant(FurnitureDataAccessor accessor) {
Optional<String> optionalVariant = accessor.variant();
String variantName = null;
if (optionalVariant.isPresent()) {
variantName = optionalVariant.get();
} else {
@SuppressWarnings("deprecation")
Optional<AnchorType> optionalAnchorType = accessor.anchorType();
if (optionalAnchorType.isPresent()) {
variantName = optionalAnchorType.get().name().toLowerCase(Locale.ROOT);
accessor.setVariant(variantName);
accessor.removeCustomData(FurnitureDataAccessor.ANCHOR_TYPE);
}
}
if (variantName == null) {
return anyVariant();
}
FurnitureVariant variant = getVariant(variantName);
if (variant == null) {
return anyVariant();
}
return variant;
}
static Builder builder() {
return new FurnitureConfigImpl.BuilderImpl();
}
interface Builder {
Builder id(Key id);
Builder variants(Map<String, FurnitureVariant> variants);
Builder settings(FurnitureSettings settings);
Builder lootTable(LootTable<?> lootTable);
Builder events(Map<EventTrigger, List<Function<Context>>> events);
Builder behavior(FurnitureBehavior behavior);
FurnitureConfig build();
}
}

View File

@@ -0,0 +1,129 @@
package net.momirealms.craftengine.core.entity.furniture;
import com.google.common.collect.ImmutableMap;
import net.momirealms.craftengine.core.entity.furniture.behavior.EmptyFurnitureBehavior;
import net.momirealms.craftengine.core.entity.furniture.behavior.FurnitureBehavior;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
class FurnitureConfigImpl implements FurnitureConfig {
private final Key id;
private final FurnitureSettings settings;
private final Map<String, FurnitureVariant> variants;
private final Map<EventTrigger, List<Function<Context>>> events;
private final FurnitureBehavior behavior;
@Nullable
private final LootTable<?> lootTable;
private FurnitureConfigImpl(@NotNull Key id,
@NotNull FurnitureSettings settings,
@NotNull Map<String, FurnitureVariant> variants,
@NotNull Map<EventTrigger, List<Function<Context>>> events,
@NotNull FurnitureBehavior behavior,
@Nullable LootTable<?> lootTable) {
this.id = id;
this.settings = settings;
this.variants = ImmutableMap.copyOf(variants);
this.lootTable = lootTable;
this.behavior = behavior;
this.events = events;
}
@Override
public void execute(Context context, EventTrigger trigger) {
for (Function<Context> function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) {
function.run(context);
}
}
@Override
public Key id() {
return this.id;
}
@Override
public FurnitureSettings settings() {
return this.settings;
}
@Override
public @Nullable LootTable<?> lootTable() {
return this.lootTable;
}
@Override
public Map<String, FurnitureVariant> variants() {
return this.variants;
}
@Override
public @NotNull FurnitureBehavior behavior() {
return this.behavior;
}
@Nullable
@Override
public FurnitureVariant getVariant(String variantName) {
return this.variants.get(variantName);
}
public static class BuilderImpl implements Builder {
private Key id;
private Map<String, FurnitureVariant> variants;
private FurnitureSettings settings;
private Map<EventTrigger, List<Function<Context>>> events;
private LootTable<?> lootTable;
private FurnitureBehavior behavior = EmptyFurnitureBehavior.INSTANCE;
@Override
public FurnitureConfig build() {
return new FurnitureConfigImpl(this.id, this.settings, this.variants, this.events, this.behavior, this.lootTable);
}
@Override
public Builder id(Key id) {
this.id = id;
return this;
}
@Override
public Builder variants(Map<String, FurnitureVariant> variants) {
this.variants = variants;
return this;
}
@Override
public Builder settings(FurnitureSettings settings) {
this.settings = settings;
return this;
}
@Override
public Builder lootTable(LootTable<?> lootTable) {
this.lootTable = lootTable;
return this;
}
@Override
public Builder events(Map<EventTrigger, List<Function<Context>>> events) {
this.events = events;
return this;
}
@Override
public Builder behavior(FurnitureBehavior behavior) {
this.behavior = behavior;
return this;
}
}
}

View File

@@ -0,0 +1,139 @@
package 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.plugin.logger.Debugger;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.NBT;
import net.momirealms.sparrow.nbt.Tag;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Optional;
public class FurnitureDataAccessor {
public static final String ITEM = "item";
public static final String DYED_COLOR = "dyed_color";
public static final String FIREWORK_EXPLOSION_COLORS = "firework_explosion_colors";
public static final String VARIANT = "variant";
@ApiStatus.Obsolete
public static final String ANCHOR_TYPE = "anchor_type";
private final CompoundTag data;
public FurnitureDataAccessor(CompoundTag data) {
this.data = data == null ? new CompoundTag() : data;
}
public static FurnitureDataAccessor of(CompoundTag data) {
return new FurnitureDataAccessor(data);
}
public static FurnitureDataAccessor ofVariant(String variant) {
FurnitureDataAccessor accessor = new FurnitureDataAccessor(new CompoundTag());
accessor.setVariant(variant);
return accessor;
}
public CompoundTag copyTag() {
return this.data.copy();
}
@ApiStatus.Internal
public CompoundTag unsafeTag() {
return this.data;
}
public void addCustomData(String key, Tag value) {
this.data.put(key, value);
}
@Nullable
public Tag getCustomData(String key) {
return this.data.get(key);
}
public void removeCustomData(String key) {
this.data.remove(key);
}
public Optional<Item<?>> item() {
byte[] data = this.data.getByteArray(ITEM);
if (data == null) return Optional.empty();
try {
return Optional.of(CraftEngine.instance().itemManager().fromByteArray(data));
} catch (Exception e) {
Debugger.FURNITURE.warn(() -> "Failed to read furniture item data", e);
return Optional.empty();
}
}
public void setItem(Item<?> item) {
this.data.putByteArray(ITEM, item.toByteArray());
}
public FurnitureColorSource getColorSource() {
return new FurnitureColorSource(dyedColor().orElse(null), fireworkExplosionColors().orElse(null));
}
public Optional<int[]> fireworkExplosionColors() {
if (this.data.containsKey(FIREWORK_EXPLOSION_COLORS)) return Optional.of(this.data.getIntArray(FIREWORK_EXPLOSION_COLORS));
return Optional.empty();
}
public void setFireworkExplosionColors(int[] colors) {
if (colors == null) {
this.data.remove(FIREWORK_EXPLOSION_COLORS);
return;
}
this.data.putIntArray(FIREWORK_EXPLOSION_COLORS, colors);
}
public Optional<Color> dyedColor() {
if (this.data.containsKey(DYED_COLOR)) return Optional.of(Color.fromDecimal(this.data.getInt(DYED_COLOR)));
return Optional.empty();
}
public void setDyedColor(@Nullable Color color) {
if (color == null) {
this.data.remove(DYED_COLOR);
return;
}
this.data.putInt(DYED_COLOR, color.color());
}
public Optional<String> variant() {
return Optional.ofNullable(this.data.getString(VARIANT));
}
public void setVariant(String variant) {
this.data.putString(VARIANT, variant);
}
@SuppressWarnings("deprecation")
@ApiStatus.Obsolete
public Optional<AnchorType> anchorType() {
if (this.data.containsKey(ANCHOR_TYPE)) return Optional.of(AnchorType.byId(this.data.getInt(ANCHOR_TYPE)));
return Optional.empty();
}
@ApiStatus.Obsolete
public FurnitureDataAccessor anchorType(@SuppressWarnings("deprecation") AnchorType type) {
this.data.putInt(ANCHOR_TYPE, type.getId());
return this;
}
public static FurnitureDataAccessor fromBytes(final byte[] data) throws IOException {
return new FurnitureDataAccessor(NBT.fromBytes(data));
}
public static byte[] toBytes(final FurnitureDataAccessor data) throws IOException {
return NBT.toBytes(data.data);
}
public byte[] toBytes() throws IOException {
return toBytes(this);
}
}

View File

@@ -1,59 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.function.Consumer;
public interface FurnitureElement {
Quaternionf rotation();
Key item();
Billboard billboard();
ItemDisplayContext transform();
float shadowRadius();
float shadowStrength();
boolean applyDyedColor();
Vector3f scale();
Vector3f translation();
Vector3f position();
void initPackets(Furniture furniture, int entityId, @NotNull Quaternionf conjugated, Consumer<Object> packets);
interface Builder {
Builder item(Key item);
Builder billboard(Billboard billboard);
Builder transform(ItemDisplayContext transform);
Builder scale(Vector3f scale);
Builder translation(Vector3f translation);
Builder position(Vector3f position);
Builder rotation(Quaternionf rotation);
Builder applyDyedColor(boolean applyDyedColor);
Builder shadowStrength(float shadowStrength);
Builder shadowRadius(float shadowRadius);
FurnitureElement build();
}
}

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