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

Merge branch 'Xiao-MoMi:dev' into dev

This commit is contained in:
jhqwqmc
2025-12-05 01:27:43 +08:00
committed by GitHub
203 changed files with 7110 additions and 2785 deletions

View File

@@ -41,8 +41,8 @@ dependencies {
compileOnly("io.github.toxicity188:bettermodel:1.14.0") compileOnly("io.github.toxicity188:bettermodel:1.14.0")
compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}") compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}")
// MMOItems // MMOItems
compileOnly("net.Indyuce:MMOItems-API:6.10-SNAPSHOT") compileOnly("net.Indyuce:MMOItems-API:6.10.1-SNAPSHOT")
compileOnly("io.lumine:MythicLib-dist:1.6.2-SNAPSHOT") compileOnly("io.lumine:MythicLib-dist:1.7.1-SNAPSHOT")
// Nexo // Nexo
compileOnly("com.nexomc:nexo:1.13.0") compileOnly("com.nexomc:nexo:1.13.0")
// LuckPerms // LuckPerms
@@ -62,7 +62,7 @@ dependencies {
// McMMO // McMMO
compileOnly("com.gmail.nossr50.mcMMO:mcMMO:2.2.038") compileOnly("com.gmail.nossr50.mcMMO:mcMMO:2.2.038")
// MMOCore // MMOCore
compileOnly("net.Indyuce:MMOCore-API:1.12.1-SNAPSHOT") compileOnly("net.Indyuce:MMOCore-API:1.13.1-SNAPSHOT")
// JobsReborn // JobsReborn
compileOnly("com.github.Zrips:Jobs:v5.2.2.3") compileOnly("com.github.Zrips:Jobs:v5.2.2.3")
// CustomFishing // 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.Context;
import net.momirealms.craftengine.core.plugin.context.condition.AlwaysFalseCondition; import net.momirealms.craftengine.core.plugin.context.condition.AlwaysFalseCondition;
import net.momirealms.craftengine.core.plugin.context.event.EventConditions; 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.plugin.text.minimessage.FormattedLine;
import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.util.VersionHelper;
@@ -181,7 +182,7 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
} }
private void logHook(String plugin) { private void logHook(String plugin) {
this.plugin.logger().info("[Compatibility] " + plugin + " hooked"); this.plugin.logger().info(TranslationManager.instance().translateLog("info.compatibility", plugin));
} }
@Override @Override
@@ -252,8 +253,8 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
if (VersionHelper.isOrAbove1_20_3()) { if (VersionHelper.isOrAbove1_20_3()) {
this.plugin.logger().severe(""); this.plugin.logger().severe("");
if (Locale.getDefault() == Locale.SIMPLIFIED_CHINESE) { if (Locale.getDefault() == Locale.SIMPLIFIED_CHINESE) {
this.plugin.logger().severe("[Compatibility] 插件需要更新 FastAsyncWorldEdit 到 2.13.0 或更高版本,以获得更好的兼容性。(当前版本: " + version + ")"); this.plugin.logger().severe("[兼容性] 插件需要更新 FastAsyncWorldEdit 到 2.13.0 或更高版本,以获得更好的兼容性。(当前版本: " + version + ")");
this.plugin.logger().severe("[Compatibility] 请前往 https://ci.athion.net/job/FastAsyncWorldEdit/ 下载最新版本"); this.plugin.logger().severe("[兼容性] 请前往 https://ci.athion.net/job/FastAsyncWorldEdit/ 下载最新版本");
} else { } else {
this.plugin.logger().severe("[Compatibility] Update FastAsyncWorldEdit to v2.13.0+ for better compatibility (Current: " + version + ")"); 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/"); 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; 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.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils;
@@ -59,13 +58,12 @@ public class BetterModelBlockEntityElementConfig implements BlockEntityElementCo
return BetterModelBlockEntityElement.class; return BetterModelBlockEntityElement.class;
} }
public static class Factory implements BlockEntityElementConfigFactory { public static class Factory implements BlockEntityElementConfigFactory<BetterModelBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override @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"); 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, model,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.compatibility.model.modelengine; 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.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils;
@@ -49,13 +48,12 @@ public class ModelEngineBlockEntityElementConfig implements BlockEntityElementCo
return ModelEngineBlockEntityElement.class; return ModelEngineBlockEntityElement.class;
} }
public static class Factory implements BlockEntityElementConfigFactory { public static class Factory implements BlockEntityElementConfigFactory<ModelEngineBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override @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"); 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, model,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),

View File

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

View File

@@ -22,7 +22,7 @@ public class EffRemoveFurniture extends Effect {
@Override @Override
protected void execute(Event e) { protected void execute(Event e) {
for (Entity entity : entities.getArray(e)) { for (Entity entity : entities.getArray(e)) {
Furniture bukkitFurniture = CraftEngineFurniture.getLoadedFurnitureByBaseEntity(entity); Furniture bukkitFurniture = CraftEngineFurniture.getLoadedFurnitureByMetaEntity(entity);
if (bukkitFurniture != null) { if (bukkitFurniture != null) {
bukkitFurniture.destroy(); 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, Location.class, FurnitureInteractEvent::location, EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureInteractEvent.class, Player.class, FurnitureInteractEvent::player, 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(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); 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."); .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, Location.class, FurnitureBreakEvent::location, EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureBreakEvent.class, Player.class, FurnitureBreakEvent::player, 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); 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%]") 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."); .description("Called when a player places a furniture.");
EventValues.registerEventValue(FurniturePlaceEvent.class, Location.class, FurniturePlaceEvent::location, EventValues.TIME_NOW); 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, 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); 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 @Override
public @Nullable String convert(Object object) { public @Nullable String convert(Object object) {
if (object instanceof Entity entity) { if (object instanceof Entity entity) {
return Optional.ofNullable(CraftEngineFurniture.getLoadedFurnitureByBaseEntity(entity)) return Optional.ofNullable(CraftEngineFurniture.getLoadedFurnitureByMetaEntity(entity))
.map(it -> it.id().toString()) .map(it -> it.id().toString())
.orElse(null); .orElse(null);
} }

View File

@@ -119,6 +119,11 @@ public final class BukkitAdvancementManager extends AbstractAdvancementManager {
return LoadingSequence.ADVANCEMENT; return LoadingSequence.ADVANCEMENT;
} }
@Override
public int count() {
return 0;
}
@Override @Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) { public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (advancements.containsKey(id)) { 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.CollisionEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; 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.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.entity.furniture.AnchorType; 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.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.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.LootTable; 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.World;
import net.momirealms.craftengine.core.world.WorldPosition; import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.RayTraceResult;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -46,7 +49,7 @@ public final class CraftEngineFurniture {
* @return a non-null map containing all loaded custom furniture * @return a non-null map containing all loaded custom furniture
*/ */
@NotNull @NotNull
public static Map<Key, CustomFurniture> loadedFurniture() { public static Map<Key, FurnitureConfig> loadedFurniture() {
return BukkitFurnitureManager.instance().loadedFurniture(); return BukkitFurnitureManager.instance().loadedFurniture();
} }
@@ -56,10 +59,49 @@ public final class CraftEngineFurniture {
* @param id id * @param id id
* @return the custom furniture * @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); 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 * Places furniture at certain location
* *
@@ -69,9 +111,9 @@ public final class CraftEngineFurniture {
*/ */
@Nullable @Nullable
public static BukkitFurniture place(Location location, Key furnitureId) { public static BukkitFurniture place(Location location, Key furnitureId) {
CustomFurniture furniture = byId(furnitureId); FurnitureConfig furniture = byId(furnitureId);
if (furniture == null) return null; 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 * @return the loaded furniture
*/ */
@Nullable @Nullable
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, Key furnitureId, AnchorType anchorType) { 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; 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 * @return the loaded furniture
*/ */
@NotNull @NotNull
public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType) { @Deprecated(since = "0.0.66", forRemoval = true)
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), 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 * @return the loaded furniture
*/ */
@Nullable @Nullable
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, Key furnitureId, AnchorType anchorType, boolean playSound) { 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; 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 * @return the loaded furniture
*/ */
@NotNull @NotNull
public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType, boolean playSound) { @Deprecated(since = "0.0.66", forRemoval = true)
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), playSound); 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 * @param baseEntity base entity
* @return the loaded furniture * @return the loaded furniture
*/ */
@Nullable @Nullable
public static BukkitFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) { public static BukkitFurniture getLoadedFurnitureByMetaEntity(@NotNull Entity baseEntity) {
return BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(baseEntity.getEntityId()); 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 * @param seat seat entity
* @return the loaded furniture * @return the loaded furniture
@@ -186,7 +315,22 @@ public final class CraftEngineFurniture {
if (isSeat(seat)) { if (isSeat(seat)) {
CompoundTag seatExtraData = BukkitSeatManager.instance().getSeatExtraData(seat); CompoundTag seatExtraData = BukkitSeatManager.instance().getSeatExtraData(seat);
int entityId = seatExtraData.getInt("entity_id"); 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; return null;
} }
@@ -199,7 +343,7 @@ public final class CraftEngineFurniture {
*/ */
public static boolean remove(@NotNull Entity entity) { public static boolean remove(@NotNull Entity entity) {
if (!isFurniture(entity)) return false; if (!isFurniture(entity)) return false;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId()); BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false; if (furniture == null) return false;
furniture.destroy(); furniture.destroy();
return true; return true;
@@ -217,7 +361,7 @@ public final class CraftEngineFurniture {
boolean dropLoot, boolean dropLoot,
boolean playSound) { boolean playSound) {
if (!isFurniture(entity)) return false; if (!isFurniture(entity)) return false;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId()); BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false; if (furniture == null) return false;
remove(furniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound); remove(furniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound);
return true; return true;
@@ -237,7 +381,7 @@ public final class CraftEngineFurniture {
boolean dropLoot, boolean dropLoot,
boolean playSound) { boolean playSound) {
if (!isFurniture(entity)) return false; if (!isFurniture(entity)) return false;
Furniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId()); Furniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false; if (furniture == null) return false;
remove(furniture, player, dropLoot, playSound); remove(furniture, player, dropLoot, playSound);
return true; return true;
@@ -286,16 +430,16 @@ public final class CraftEngineFurniture {
boolean dropLoot, boolean dropLoot,
boolean playSound) { boolean playSound) {
if (!furniture.isValid()) return; if (!furniture.isValid()) return;
Location location = ((BukkitFurniture) furniture).dropLocation(); Location location = ((BukkitFurniture) furniture).getDropLocation();
furniture.destroy(); furniture.destroy();
LootTable<ItemStack> lootTable = (LootTable<ItemStack>) furniture.config().lootTable(); LootTable<ItemStack> lootTable = (LootTable<ItemStack>) furniture.config.lootTable();
World world = new BukkitWorld(location.getWorld()); World world = new BukkitWorld(location.getWorld());
WorldPosition position = new WorldPosition(world, location.getX(), location.getY(), location.getZ()); WorldPosition position = new WorldPosition(world, location.getX(), location.getY(), location.getZ());
if (dropLoot && lootTable != null) { if (dropLoot && lootTable != null) {
ContextHolder.Builder builder = ContextHolder.builder() ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(DirectContextParameters.POSITION, position) .withParameter(DirectContextParameters.POSITION, position)
.withParameter(DirectContextParameters.FURNITURE, furniture) .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) { if (player != null) {
Item<?> itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND); Item<?> itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND);
builder.withParameter(DirectContextParameters.PLAYER, player) builder.withParameter(DirectContextParameters.PLAYER, player)
@@ -307,7 +451,7 @@ public final class CraftEngineFurniture {
} }
} }
if (playSound) { 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; package net.momirealms.craftengine.bukkit.api.event;
import net.momirealms.craftengine.core.entity.furniture.AnchorType; import net.momirealms.craftengine.core.entity.furniture.FurnitureConfig;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture; import net.momirealms.craftengine.core.entity.furniture.FurnitureVariant;
import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionHand;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable; import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
@@ -15,25 +14,22 @@ import org.jetbrains.annotations.NotNull;
public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Cancellable { public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList(); private static final HandlerList HANDLER_LIST = new HandlerList();
private boolean cancelled; private boolean cancelled;
private final CustomFurniture furniture; private final FurnitureConfig furniture;
private final Location location; private final Location location;
private final AnchorType anchorType; private final FurnitureVariant variant;
private final BlockFace clickedFace;
private final Block clickedBlock; private final Block clickedBlock;
private final InteractionHand hand; private final InteractionHand hand;
public FurnitureAttemptPlaceEvent(@NotNull Player player, public FurnitureAttemptPlaceEvent(@NotNull Player player,
@NotNull CustomFurniture furniture, @NotNull FurnitureConfig furniture,
@NotNull AnchorType anchorType, @NotNull FurnitureVariant variant,
@NotNull Location location, @NotNull Location location,
@NotNull BlockFace clickedFace,
@NotNull InteractionHand hand, @NotNull InteractionHand hand,
@NotNull Block clickedBlock) { @NotNull Block clickedBlock) {
super(player); super(player);
this.furniture = furniture; this.furniture = furniture;
this.location = location; this.location = location;
this.anchorType = anchorType; this.variant = variant;
this.clickedFace = clickedFace;
this.clickedBlock = clickedBlock; this.clickedBlock = clickedBlock;
this.hand = hand; this.hand = hand;
} }
@@ -48,19 +44,14 @@ public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Can
return hand; return hand;
} }
@NotNull
public BlockFace clickedFace() {
return clickedFace;
}
@NotNull @NotNull
public Player player() { public Player player() {
return getPlayer(); return getPlayer();
} }
@NotNull @NotNull
public AnchorType anchorType() { public FurnitureVariant variant() {
return anchorType; return variant;
} }
@NotNull @NotNull
@@ -69,7 +60,7 @@ public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Can
} }
@NotNull @NotNull
public CustomFurniture furniture() { public FurnitureConfig furniture() {
return furniture; return furniture;
} }

View File

@@ -1,7 +1,7 @@
package net.momirealms.craftengine.bukkit.api.event; package net.momirealms.craftengine.bukkit.api.event;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; 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 net.momirealms.craftengine.core.entity.player.InteractionHand;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -16,23 +16,23 @@ public final class FurnitureInteractEvent extends PlayerEvent implements Cancell
private final BukkitFurniture furniture; private final BukkitFurniture furniture;
private final InteractionHand hand; private final InteractionHand hand;
private final Location interactionPoint; private final Location interactionPoint;
private final HitBox hitBox; private final FurnitureHitBox furnitureHitBox;
public FurnitureInteractEvent(@NotNull Player player, public FurnitureInteractEvent(@NotNull Player player,
@NotNull BukkitFurniture furniture, @NotNull BukkitFurniture furniture,
@NotNull InteractionHand hand, @NotNull InteractionHand hand,
@NotNull Location interactionPoint, @NotNull Location interactionPoint,
@NotNull HitBox hitBox) { @NotNull FurnitureHitBox furnitureHitBox) {
super(player); super(player);
this.furniture = furniture; this.furniture = furniture;
this.hand = hand; this.hand = hand;
this.interactionPoint = interactionPoint; this.interactionPoint = interactionPoint;
this.hitBox = hitBox; this.furnitureHitBox = furnitureHitBox;
} }
@NotNull @NotNull
public HitBox hitBox() { public FurnitureHitBox hitBox() {
return hitBox; return furnitureHitBox;
} }
@NotNull @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.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; 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.sound.SoundSource;
import net.momirealms.craftengine.core.util.Cancellable; import net.momirealms.craftengine.core.util.Cancellable;
import net.momirealms.craftengine.core.util.ItemUtils; 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.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.world.GenericGameEvent; import org.bukkit.event.world.GenericGameEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@@ -233,12 +235,14 @@ public final class BlockEventListener implements Listener {
@EventHandler(priority = EventPriority.LOW) @EventHandler(priority = EventPriority.LOW)
public void onStep(GenericGameEvent event) { public void onStep(GenericGameEvent event) {
if (event.getEvent() != GameEvent.STEP) return; GameEvent gameEvent = event.getEvent();
// 只处理落地和走路
if (gameEvent != GameEvent.STEP) return;
Entity entity = event.getEntity(); Entity entity = event.getEntity();
if (!(entity instanceof Player player)) return; if (!(entity instanceof Player player)) return;
BlockPos pos = EntityUtils.getOnPos(player); BlockPos pos = EntityUtils.getOnPos(player);
Block block = player.getWorld().getBlockAt(pos.x(), pos.y(), pos.z()); 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); Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState);
if (optionalCustomState.isPresent()) { if (optionalCustomState.isPresent()) {
Location location = player.getLocation(); Location location = player.getLocation();
@@ -253,7 +257,8 @@ public final class BlockEventListener implements Listener {
if (cancellable.isCancelled() && !Config.processCancelledStep()) { if (cancellable.isCancelled() && !Config.processCancelledStep()) {
return; 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()) { } else if (Config.enableSoundSystem()) {
if (event.isCancelled() && !Config.processCancelledStep()) { if (event.isCancelled() && !Config.processCancelledStep()) {
return; 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) @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onBlockPhysics(BlockPhysicsEvent event) { public void onBlockPhysics(BlockPhysicsEvent event) {
// for vanilla blocks // 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 SURFACE_SPREADING_BLOCK = Key.from("craftengine:surface_spreading_block");
public static final Key SNOWY_BLOCK = Key.from("craftengine:snowy_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 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() { public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
@@ -92,5 +94,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(SURFACE_SPREADING_BLOCK, SurfaceSpreadingBlockBehavior.FACTORY); register(SURFACE_SPREADING_BLOCK, SurfaceSpreadingBlockBehavior.FACTORY);
register(SNOWY_BLOCK, SnowyBlockBehavior.FACTORY); register(SNOWY_BLOCK, SnowyBlockBehavior.FACTORY);
register(HANGABLE_BLOCK, HangableBlockBehavior.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

@@ -33,7 +33,7 @@ public class SeatBlockBehavior extends BukkitBlockBehavior implements EntityBloc
@Override @Override
public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) {
return new SeatBlockEntity(pos, state, this.seats, this.directionProperty); return new SeatBlockEntity(pos, state, this.seats);
} }
@Override @Override

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.bukkit.block.entity; package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.bukkit.block.behavior.SeatBlockBehavior;
import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeat; import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeat;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.BlockEntity;
@@ -13,14 +14,14 @@ import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.WorldPosition; import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.CompoundTag;
import java.util.Optional;
public class SeatBlockEntity extends BlockEntity implements SeatOwner { public class SeatBlockEntity extends BlockEntity implements SeatOwner {
private final Seat<SeatBlockEntity>[] seats; private final Seat<SeatBlockEntity>[] seats;
private final Property<HorizontalDirection> facing;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public SeatBlockEntity(BlockPos pos, ImmutableBlockState blockState, SeatConfig[] seats, Property<HorizontalDirection> directionProperty) { public SeatBlockEntity(BlockPos pos, ImmutableBlockState blockState, SeatConfig[] seats) {
super(BukkitBlockEntityTypes.SEAT, pos, blockState); super(BukkitBlockEntityTypes.SEAT, pos, blockState);
this.facing = directionProperty;
this.seats = new Seat[seats.length]; this.seats = new Seat[seats.length];
for (int i = 0; i < seats.length; i++) { for (int i = 0; i < seats.length; i++) {
this.seats[i] = new BukkitSeat<>(this, seats[i]); this.seats[i] = new BukkitSeat<>(this, seats[i]);
@@ -41,7 +42,10 @@ public class SeatBlockEntity extends BlockEntity implements SeatOwner {
public boolean spawnSeat(Player player) { public boolean spawnSeat(Player player) {
int yRot = 0; int yRot = 0;
if (this.facing != null) { Optional<SeatBlockBehavior> behavior = super.blockState.behavior().getAs(SeatBlockBehavior.class);
if (behavior.isEmpty()) return false;
Property<HorizontalDirection> facing = behavior.get().directionProperty();
if (facing != null) {
HorizontalDirection direction = super.blockState.get(facing); HorizontalDirection direction = super.blockState.get(facing);
yRot = switch (direction) { yRot = switch (direction) {
case NORTH -> 0; case NORTH -> 0;

View File

@@ -172,8 +172,10 @@ public class SimpleStorageBlockEntity extends BlockEntity {
public void updateOpenBlockState(boolean open) { public void updateOpenBlockState(boolean open) {
ImmutableBlockState state = super.world.getBlockStateAtIfLoaded(this.pos); ImmutableBlockState state = super.world.getBlockStateAtIfLoaded(this.pos);
if (state == null || state.behavior() != this.behavior) return; if (state == null) return;
Property<Boolean> property = this.behavior.openProperty(); SimpleStorageBlockBehavior behavior = state.behavior().getAs(SimpleStorageBlockBehavior.class).orElse(null);
if (behavior == null) return;
Property<Boolean> property = behavior.openProperty();
if (property == null) return; 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()); 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.entity.data.ItemEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager; 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.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.entity.player.Player;
@@ -29,8 +28,8 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig<It
this.position = position; this.position = position;
this.lazyMetadataPacket = player -> { this.lazyMetadataPacket = player -> {
List<Object> dataValues = new ArrayList<>(); List<Object> dataValues = new ArrayList<>();
ItemEntityData.Item.addEntityDataIfNotDefaultValue(item.apply(player).getLiteralObject(), dataValues); ItemEntityData.Item.addEntityData(item.apply(player).getLiteralObject(), dataValues);
ItemEntityData.NoGravity.addEntityDataIfNotDefaultValue(true, dataValues); ItemEntityData.NoGravity.addEntityData(true, dataValues);
return dataValues; return dataValues;
}; };
} }
@@ -76,13 +75,12 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig<It
return this.position.equals(that.position); return this.position.equals(that.position);
} }
public static class Factory implements BlockEntityElementConfigFactory { public static class Factory implements BlockEntityElementConfigFactory<ItemBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override @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")); 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), player -> BukkitItemManager.instance().createWrappedItem(itemId, player),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position") 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 com.google.common.base.Objects;
import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData; import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager; 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.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; 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.ItemDisplayContext; import net.momirealms.craftengine.core.entity.display.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Key;
@@ -19,7 +18,6 @@ import org.joml.Vector3f;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
@@ -159,13 +157,12 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
Objects.equal(rotation, that.rotation); Objects.equal(rotation, that.rotation);
} }
public static class Factory implements BlockEntityElementConfigFactory { public static class Factory implements BlockEntityElementConfigFactory<ItemDisplayBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override @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")); 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), player -> BukkitItemManager.instance().createWrappedItem(itemId, player),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), 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("pitch", 0f), "pitch"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),
ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"), ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"),
ItemDisplayContext.valueOf(arguments.getOrDefault("display-context", "none").toString().toUpperCase(Locale.ROOT)), ResourceConfigUtils.getAsEnum(ResourceConfigUtils.get(arguments, "display-context", "display-transform"), ItemDisplayContext.class, ItemDisplayContext.NONE),
Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)), ResourceConfigUtils.getAsEnum(arguments.get("billboard"), Billboard.class, Billboard.FIXED),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-radius", 0f), "shadow-radius"), ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-radius", 0f), "shadow-radius"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-strength", 1f), "shadow-strength") 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.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData; import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData;
import net.momirealms.craftengine.bukkit.util.ComponentUtils; 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.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; 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.entity.player.Player;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.AdventureHelper;
@@ -135,13 +134,12 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
Objects.equal(rotation, that.rotation); Objects.equal(rotation, that.rotation);
} }
public static class Factory implements BlockEntityElementConfigFactory { public static class Factory implements BlockEntityElementConfigFactory<TextDisplayBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override @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"); 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, text,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), 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.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.EntityUtils; 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.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.entity.AbstractEntity; import net.momirealms.craftengine.core.entity.AbstractEntity;
import net.momirealms.craftengine.core.entity.data.EntityData; import net.momirealms.craftengine.core.entity.data.EntityData;
import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.UUID; import java.util.UUID;
@@ -38,6 +40,11 @@ public class BukkitEntity extends AbstractEntity {
public void tick() { public void tick() {
} }
@Override
public WorldPosition position() {
return LocationUtils.toWorldPosition(platformEntity().getLocation());
}
@Override @Override
public int entityID() { public int entityID() {
return platformEntity().getEntityId(); return platformEntity().getEntityId();
@@ -78,6 +85,11 @@ public class BukkitEntity extends AbstractEntity {
return EntityUtils.getEntityType(platformEntity()); return EntityUtils.getEntityType(platformEntity());
} }
@Override
public boolean isValid() {
return platformEntity().isValid();
}
@Override @Override
public String name() { public String name() {
return platformEntity().getName(); return platformEntity().getName();

View File

@@ -1,7 +1,7 @@
package net.momirealms.craftengine.bukkit.entity; package net.momirealms.craftengine.bukkit.entity;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager; 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; import org.bukkit.entity.Item;
public class BukkitItemEntity extends BukkitEntity implements ItemEntity { public class BukkitItemEntity extends BukkitEntity implements ItemEntity {

View File

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

View File

@@ -21,7 +21,7 @@ public class BukkitCollider implements Collider {
@Override @Override
public int entityId() { public int entityId() {
return this.collisionEntity.getId(); return this.collisionEntity.getEntityId();
} }
@Override @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,152 @@
package net.momirealms.craftengine.bukkit.entity.furniture; package net.momirealms.craftengine.bukkit.entity.furniture;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.entity.BukkitEntity; import net.momirealms.craftengine.bukkit.entity.BukkitEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.seat.Seat; import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.QuaternionUtils; import net.momirealms.craftengine.core.util.QuaternionUtils;
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.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf; import org.joml.Quaternionf;
import org.joml.Vector3f; import org.joml.Vector3f;
import java.io.IOException;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.*; import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class BukkitFurniture implements Furniture { public class BukkitFurniture extends Furniture {
private final CustomFurniture furniture; private final WeakReference<ItemDisplay> metaEntity;
private final CustomFurniture.Placement placement; private Location location;
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 BukkitFurniture(Entity baseEntity, public BukkitFurniture(ItemDisplay metaEntity, FurnitureConfig config, FurnitureDataAccessor data) {
CustomFurniture furniture, super(new BukkitEntity(metaEntity), data, config);
FurnitureExtraData extraData) { this.metaEntity = new WeakReference<>(metaEntity);
this.extraData = extraData; this.location = metaEntity.getLocation();
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]);
} }
@Override @Override
public void initializeColliders() { public void addCollidersToWorld() {
Object world = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(this.location.getWorld()); Object world = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(this.location.getWorld());
for (Collider entity : this.colliderEntities) { for (Collider entity : super.colliders) {
FastNMS.INSTANCE.method$LevelWriter$addFreshEntity(world, entity.handle());
Entity bukkitEntity = FastNMS.INSTANCE.method$Entity$getBukkitEntity(entity.handle()); Entity bukkitEntity = FastNMS.INSTANCE.method$Entity$getBukkitEntity(entity.handle());
bukkitEntity.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_COLLISION, PersistentDataType.BYTE, (byte) 1); 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 @Override
public WorldPosition position() { public boolean setVariant(String variantName) {
return LocationUtils.toWorldPosition(this.location); FurnitureVariant variant = this.config.getVariant(variantName);
if (variant == null) return false;
if (this.currentVariant == variant) return false;
BukkitFurnitureManager.instance().invalidateFurniture(this);
super.clearColliders();
super.setVariantInternal(variant);
BukkitFurnitureManager.instance().initFurniture(this);
this.addCollidersToWorld();
this.refresh();
return true;
} }
@NotNull @SuppressWarnings("deprecation")
public Location location() { @Override
return this.location.clone(); public CompletableFuture<Boolean> moveTo(WorldPosition position) {
} ItemDisplay itemDisplay = this.metaEntity.get();
if (itemDisplay == null) return CompletableFuture.completedFuture(false);
@NotNull // 检查新位置是否可用
public Entity baseEntity() { List<AABB> aabbs = new ArrayList<>();
Entity entity = this.baseEntity.get(); for (FurnitureHitBoxConfig<?> hitBoxConfig : getCurrentVariant().hitBoxConfigs()) {
if (entity == null) { hitBoxConfig.prepareForPlacement(position, aabbs::add);
throw new RuntimeException("Base entity not found. It might be unloaded."); }
if (!aabbs.isEmpty()) {
if (!FastNMS.INSTANCE.checkEntityCollision(position.world.serverWorld(), aabbs.stream().map(it -> FastNMS.INSTANCE.constructor$AABB(it.minX, it.minY, it.minZ, it.maxX, it.maxY, it.maxZ)).toList())) {
return CompletableFuture.completedFuture(false);
}
}
// 准备传送
CompletableFuture<Boolean> future = new CompletableFuture<>();
BukkitFurnitureManager.instance().invalidateFurniture(this);
super.clearColliders();
this.location = LocationUtils.toLocation(position);
Object removePacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList() {{ add(itemDisplay.getEntityId()); }});
for (Player player : itemDisplay.getTrackedPlayers()) {
BukkitAdaptors.adapt(player).sendPacket(removePacket, false);
}
itemDisplay.teleportAsync(this.location).thenAccept(result -> {
if (result) {
super.setVariantInternal(getCurrentVariant());
BukkitFurnitureManager.instance().initFurniture(this);
this.addCollidersToWorld();
Object addPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(itemDisplay.getEntityId(), itemDisplay.getUniqueId(),
itemDisplay.getX(), itemDisplay.getY(), itemDisplay.getZ(), itemDisplay.getPitch(), itemDisplay.getYaw(), MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0);
for (Player player : itemDisplay.getTrackedPlayers()) {
BukkitAdaptors.adapt(player).sendPacket(addPacket, false);
}
future.complete(true);
} else {
future.complete(false);
}
});
return future;
}
@SuppressWarnings("deprecation")
@Override
protected void refresh() {
ItemDisplay itemDisplay = this.metaEntity.get();
if (itemDisplay == null) return;
Object removePacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList() {{ add(itemDisplay.getEntityId()); }});
Object addPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(itemDisplay.getEntityId(), itemDisplay.getUniqueId(),
itemDisplay.getX(), itemDisplay.getY(), itemDisplay.getZ(), itemDisplay.getPitch(), itemDisplay.getYaw(), MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0);
for (Player player : itemDisplay.getTrackedPlayers()) {
BukkitAdaptors.adapt(player).sendPacket(removePacket, false);
BukkitAdaptors.adapt(player).sendPacket(addPacket, false);
} }
return entity;
} }
@Override @Override
public boolean isValid() { public void destroy() {
return baseEntity().isValid(); Optional.ofNullable(this.metaEntity.get()).ifPresent(Entity::remove);
for (Collider entity : super.colliders) {
entity.destroy();
}
} }
@NotNull // 获取掉落物的位置,受到家具变种的影响
public Location dropLocation() { public Location getDropLocation() {
Optional<Vector3f> dropOffset = this.placement.dropOffset(); Optional<Vector3f> dropOffset = this.getCurrentVariant().dropOffset();
if (dropOffset.isEmpty()) { if (dropOffset.isEmpty()) {
return location(); return this.location;
} }
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate(); Quaternionf conjugated = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate();
Vector3f offset = conjugated.transform(new Vector3f(dropOffset.get())); 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); return new Location(this.location.getWorld(), this.location.getX() + offset.x, this.location.getY() + offset.y, this.location.getZ() - offset.z);
} }
@Override public Location location() {
public void destroy() { return location;
if (!isValid()) {
return;
}
this.baseEntity().remove();
this.destroyColliders();
this.destroySeats();
} }
@Override public Entity getBukkitEntity() {
public void destroyColliders() { return this.metaEntity.get();
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);
}
} }
} }

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; package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; 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.CollisionEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; 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.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.EntityUtils; import net.momirealms.craftengine.bukkit.util.EntityUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.*; 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.plugin.config.Config;
import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper; 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.WorldPosition;
import net.momirealms.craftengine.core.world.chunk.CEChunk;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.entity.*; import org.bukkit.entity.*;
import org.bukkit.event.HandlerList; 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_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_EXTRA_DATA_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_EXTRA_DATA_KEY);
public static final NamespacedKey FURNITURE_COLLISION = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_COLLISION); 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 Class<?> COLLISION_ENTITY_CLASS = Interaction.class;
public static Object NMS_COLLISION_ENTITY_TYPE = MEntityTypes.INTERACTION; public static Object NMS_COLLISION_ENTITY_TYPE = MEntityTypes.INTERACTION;
public static ColliderType COLLISION_ENTITY_TYPE = ColliderType.INTERACTION; public static ColliderType COLLISION_ENTITY_TYPE = ColliderType.INTERACTION;
private static BukkitFurnitureManager instance;
private final BukkitCraftEngine plugin; 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 // Event listeners
private final FurnitureEventListener furnitureEventListener; private final FurnitureEventListener furnitureEventListener;
@@ -52,53 +58,54 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
super(plugin); super(plugin);
instance = this; instance = this;
this.plugin = plugin; this.plugin = plugin;
this.furnitureEventListener = new FurnitureEventListener(this); this.furnitureEventListener = new FurnitureEventListener(this, plugin.worldManager());
} }
@Override @Override
public Furniture place(WorldPosition position, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound) { public Furniture place(WorldPosition position, FurnitureConfig furniture, FurnitureDataAccessor dataAccessor, boolean playSound) {
return this.place(LocationUtils.toLocation(position), furniture, extraData, playSound); return this.place(LocationUtils.toLocation(position), furniture, dataAccessor, playSound);
} }
public BukkitFurniture place(Location location, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound) { public BukkitFurniture place(Location location, FurnitureConfig furniture, FurnitureDataAccessor data, boolean playSound) {
Optional<AnchorType> optionalAnchorType = extraData.anchorType();
if (optionalAnchorType.isEmpty() || !furniture.isAllowedPlacement(optionalAnchorType.get())) {
extraData.anchorType(furniture.getAnyAnchorType());
}
Entity furnitureEntity = EntityUtils.spawnEntity(location.getWorld(), location, EntityType.ITEM_DISPLAY, entity -> { Entity furnitureEntity = EntityUtils.spawnEntity(location.getWorld(), location, EntityType.ITEM_DISPLAY, entity -> {
ItemDisplay display = (ItemDisplay) entity; ItemDisplay display = (ItemDisplay) entity;
display.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_KEY, PersistentDataType.STRING, furniture.id().toString()); display.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_KEY, PersistentDataType.STRING, furniture.id().toString());
try { 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) { } catch (IOException e) {
this.plugin.logger().warn("Failed to set furniture PDC for " + furniture.id().toString(), e); this.plugin.logger().warn("Failed to set furniture PDC for " + furniture.id().toString(), e);
} }
handleBaseEntityLoadEarly(display); handleMetaEntityDuringChunkLoad(display);
}); });
if (playSound) { if (playSound) {
SoundData data = furniture.settings().sounds().placeSound(); SoundData sound = furniture.settings().sounds().placeSound();
location.getWorld().playSound(location, data.id().toString(), SoundCategory.BLOCKS, data.volume().get(), data.pitch().get()); location.getWorld().playSound(location, sound.id().toString(), SoundCategory.BLOCKS, sound.volume().get(), sound.pitch().get());
} }
return loadedFurnitureByRealEntityId(furnitureEntity.getEntityId()); return loadedFurnitureByMetaEntityId(furnitureEntity.getEntityId());
} }
@Override @Override
public void delayedInit() { public void delayedInit() {
// 确定碰撞箱实体类型
COLLISION_ENTITY_TYPE = Config.colliderType();
COLLISION_ENTITY_CLASS = Config.colliderType() == ColliderType.INTERACTION ? Interaction.class : Boat.class; COLLISION_ENTITY_CLASS = Config.colliderType() == ColliderType.INTERACTION ? Interaction.class : Boat.class;
NMS_COLLISION_ENTITY_TYPE = Config.colliderType() == ColliderType.INTERACTION ? MEntityTypes.INTERACTION : MEntityTypes.OAK_BOAT; 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()); Bukkit.getPluginManager().registerEvents(this.furnitureEventListener, this.plugin.javaPlugin());
// 对世界上已有实体的记录
if (VersionHelper.isFolia()) { if (VersionHelper.isFolia()) {
BiConsumer<Entity, Runnable> taskExecutor = (entity, runnable) -> entity.getScheduler().run(this.plugin.javaPlugin(), (t) -> runnable.run(), () -> {}); BiConsumer<Entity, Runnable> taskExecutor = (entity, runnable) -> entity.getScheduler().run(this.plugin.javaPlugin(), (t) -> runnable.run(), () -> {});
for (World world : Bukkit.getWorlds()) { for (World world : Bukkit.getWorlds()) {
List<Entity> entities = world.getEntities(); List<Entity> entities = world.getEntities();
for (Entity entity : entities) { for (Entity entity : entities) {
if (entity instanceof ItemDisplay display) { if (entity instanceof ItemDisplay display) {
taskExecutor.accept(entity, () -> handleBaseEntityLoadEarly(display)); taskExecutor.accept(entity, () -> handleMetaEntityDuringChunkLoad(display));
} else if (entity instanceof Interaction interaction) { } else if (entity instanceof Interaction interaction) {
taskExecutor.accept(entity, () -> handleCollisionEntityLoadOnEntitiesLoad(interaction)); taskExecutor.accept(entity, () -> handleCollisionEntityDuringChunkLoad(interaction));
} else if (entity instanceof Boat boat) { } 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(); List<Entity> entities = world.getEntities();
for (Entity entity : entities) { for (Entity entity : entities) {
if (entity instanceof ItemDisplay display) { if (entity instanceof ItemDisplay display) {
handleBaseEntityLoadEarly(display); handleMetaEntityDuringChunkLoad(display);
} else if (entity instanceof Interaction interaction) { } else if (entity instanceof Interaction interaction) {
handleCollisionEntityLoadOnEntitiesLoad(interaction); handleCollisionEntityDuringChunkLoad(interaction);
} else if (entity instanceof Boat boat) { } else if (entity instanceof Boat boat) {
handleCollisionEntityLoadOnEntitiesLoad(boat); handleCollisionEntityDuringChunkLoad(boat);
} }
} }
} }
@@ -125,150 +132,168 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
} }
@Override @Override
public boolean isFurnitureRealEntity(int entityId) { public boolean isFurnitureMetaEntity(int entityId) {
return this.furnitureByRealEntityId.containsKey(entityId); return this.byMetaEntityId.containsKey(entityId);
} }
@Nullable @Nullable
@Override @Override
public BukkitFurniture loadedFurnitureByRealEntityId(int entityId) { public BukkitFurniture loadedFurnitureByMetaEntityId(int entityId) {
return this.furnitureByRealEntityId.get(entityId); return this.byMetaEntityId.get(entityId);
} }
@Override
@Nullable @Nullable
public BukkitFurniture loadedFurnitureByEntityId(int entityId) {
return this.furnitureByEntityId.get(entityId);
}
@Override @Override
protected CustomFurniture.Builder furnitureBuilder() { public BukkitFurniture loadedFurnitureByVirtualEntityId(int entityId) {
return BukkitCustomFurniture.builder(); return this.byVirtualEntityId.get(entityId);
} }
@Nullable
@Override @Override
protected FurnitureElement.Builder furnitureElementBuilder() { public BukkitFurniture loadedFurnitureByColliderEntityId(int entityId) {
return BukkitFurnitureElement.builder(); return this.byColliderEntityId.get(entityId);
} }
protected void handleBaseEntityUnload(Entity entity) { // 当元数据实体被卸载了
protected void handleMetaEntityUnload(ItemDisplay entity) {
// 不是持久化的
if (!entity.isPersistent()) {
return;
}
int id = entity.getEntityId(); int id = entity.getEntityId();
BukkitFurniture furniture = this.furnitureByRealEntityId.remove(id); BukkitFurniture furniture = this.byMetaEntityId.remove(id);
if (furniture != null) { if (furniture != null) {
Location location = entity.getLocation(); Location location = entity.getLocation();
// 区块还在加载的时候,就重复卸载了。为极其特殊情况
boolean isPreventing = FastNMS.INSTANCE.method$ServerLevel$isPreventingStatusUpdates(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(location.getWorld()), location.getBlockX() >> 4, location.getBlockZ() >> 4); boolean isPreventing = FastNMS.INSTANCE.method$ServerLevel$isPreventingStatusUpdates(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(location.getWorld()), location.getBlockX() >> 4, location.getBlockZ() >> 4);
if (!isPreventing) { if (!isPreventing) {
furniture.destroySeats(); furniture.destroySeats();
} }
for (int sub : furniture.entityIds()) { for (int sub : furniture.virtualEntityIds()) {
this.furnitureByEntityId.remove(sub); this.byVirtualEntityId.remove(sub);
}
for (int sub : furniture.colliderEntityIds()) {
this.byColliderEntityId.remove(sub);
} }
} }
} }
// 保险起见collision实体卸载也移除一下
protected void handleCollisionEntityUnload(Entity entity) { protected void handleCollisionEntityUnload(Entity entity) {
int id = entity.getEntityId(); 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) { private boolean isEntitiesLoaded(Location location) {
// must be a furniture item CEWorld ceWorld = this.plugin.worldManager().getWorld(location.getWorld());
String id = display.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING); 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; 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()) { if (Config.handleInvalidFurniture()) {
String mapped = Config.furnitureMappings().get(id); String mapped = Config.furnitureMappings().get(id);
if (mapped != null) { if (mapped != null) {
if (mapped.isEmpty()) { if (mapped.isEmpty()) {
display.remove(); entity.remove();
return; return;
} else { } else {
id = mapped; id = mapped;
display.getPersistentDataContainer().set(FURNITURE_KEY, PersistentDataType.STRING, id); entity.getPersistentDataContainer().set(FURNITURE_KEY, PersistentDataType.STRING, id);
} }
} }
} }
// 获取家具配置
Key key = Key.of(id); Key key = Key.of(id);
Optional<CustomFurniture> optionalFurniture = furnitureById(key); Optional<FurnitureConfig> optionalFurniture = furnitureById(key);
if (optionalFurniture.isPresent()) { if (optionalFurniture.isEmpty()) return;
CustomFurniture customFurniture = optionalFurniture.get();
BukkitFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId()); // 只对1.20.2及以上生效1.20.1比较特殊
if (previous != null) return; if (!VersionHelper.isOrAbove1_20_2()) {
BukkitFurniture furniture = addNewFurniture(display, customFurniture); return;
furniture.initializeColliders(); // safely do it here }
// 已经在其他事件里加载过了
FurnitureConfig customFurniture = optionalFurniture.get();
BukkitFurniture previous = this.byMetaEntityId.get(entity.getEntityId());
if (previous != null) return;
// 创建新的家具
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 // faster
if (FastNMS.INSTANCE.method$CraftEntity$getHandle(collisionEntity) instanceof CollisionEntity) { if (FastNMS.INSTANCE.method$CraftEntity$getHandle(collisionEntity) instanceof CollisionEntity) {
collisionEntity.remove(); collisionEntity.remove();
@@ -284,34 +309,57 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
collisionEntity.remove(); 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); byte[] extraData = baseEntity.getPersistentDataContainer().get(FURNITURE_EXTRA_DATA_KEY, PersistentDataType.BYTE_ARRAY);
if (extraData == null) return FurnitureExtraData.builder().build(); if (extraData == null) return new FurnitureDataAccessor(null);
return FurnitureExtraData.fromBytes(extraData); try {
return FurnitureDataAccessor.fromBytes(extraData);
} catch (IOException e) {
// 损坏了?一般不会
return new FurnitureDataAccessor(null);
}
} }
private synchronized BukkitFurniture addNewFurniture(ItemDisplay display, CustomFurniture furniture) { // 创建家具实例,并初始化碰撞实体
FurnitureExtraData extraData; private BukkitFurniture createFurnitureInstance(ItemDisplay display, FurnitureConfig furniture) {
try { BukkitFurniture bukkitFurniture = new BukkitFurniture(display, furniture, getFurnitureDataAccessor(display));
extraData = getFurnitureExtraData(display); initFurniture(bukkitFurniture);
} catch (IOException e) { Location location = display.getLocation();
extraData = FurnitureExtraData.builder().build(); runSafeEntityOperation(location, bukkitFurniture::addCollidersToWorld);
plugin.logger().warn("Furniture extra data could not be loaded", e);
}
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);
}
return bukkitFurniture; return bukkitFurniture;
} }
protected void initFurniture(BukkitFurniture furniture) {
this.byMetaEntityId.put(furniture.entityId(), furniture);
for (int entityId : furniture.virtualEntityIds()) {
this.byVirtualEntityId.put(entityId, furniture);
}
for (Collider collisionEntity : furniture.colliders()) {
this.byColliderEntityId.put(collisionEntity.entityId(), furniture);
}
}
protected void invalidateFurniture(BukkitFurniture furniture) {
this.byMetaEntityId.remove(furniture.entityId());
for (int entityId : furniture.virtualEntityIds()) {
this.byVirtualEntityId.remove(entityId);
}
for (Collider collisionEntity : furniture.colliders()) {
this.byColliderEntityId.remove(collisionEntity.entityId());
}
}
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 @Override
protected HitBoxConfig defaultHitBox() { protected FurnitureHitBoxConfig<?> defaultHitBox() {
return InteractionHitBoxConfig.DEFAULT; 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,8 +2,26 @@ package net.momirealms.craftengine.bukkit.entity.furniture;
import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent; import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent;
import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent; import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.api.event.FurnitureInteractEvent;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
import net.momirealms.craftengine.core.entity.furniture.FurnitureVariant;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.RandomUtils;
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.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemDisplay; import org.bukkit.entity.ItemDisplay;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -11,14 +29,20 @@ import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.event.world.EntitiesLoadEvent; import org.bukkit.event.world.EntitiesLoadEvent;
import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
public class FurnitureEventListener implements Listener { public class FurnitureEventListener implements Listener {
private final BukkitFurnitureManager manager; 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.manager = manager;
this.worldManager = worldManager;
} }
/* /*
@@ -29,21 +53,26 @@ public class FurnitureEventListener implements Listener {
List<Entity> entities = event.getEntities(); List<Entity> entities = event.getEntities();
for (Entity entity : entities) { for (Entity entity : entities) {
if (entity instanceof ItemDisplay itemDisplay) { if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadEarly(itemDisplay); this.manager.handleMetaEntityDuringChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) { } 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) { public void onWorldLoad(WorldLoadEvent event) {
List<Entity> entities = event.getWorld().getEntities(); List<Entity> entities = event.getWorld().getEntities();
for (Entity entity : entities) { for (Entity entity : entities) {
if (entity instanceof ItemDisplay itemDisplay) { if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadEarly(itemDisplay); this.manager.handleMetaEntityDuringChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) { } else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadOnEntitiesLoad(entity); this.manager.handleCollisionEntityDuringChunkLoad(entity);
} }
} }
} }
@@ -52,9 +81,9 @@ public class FurnitureEventListener implements Listener {
public void onEntityLoad(EntityAddToWorldEvent event) { public void onEntityLoad(EntityAddToWorldEvent event) {
Entity entity = event.getEntity(); Entity entity = event.getEntity();
if (entity instanceof ItemDisplay itemDisplay) { if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadLate(itemDisplay, 0); this.manager.handleMetaEntityAfterChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) { } else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadLate(entity, 0); this.manager.handleCollisionEntityAfterChunkLoad(entity);
} }
} }
@@ -65,8 +94,8 @@ public class FurnitureEventListener implements Listener {
public void onChunkUnload(ChunkUnloadEvent event) { public void onChunkUnload(ChunkUnloadEvent event) {
Entity[] entities = event.getChunk().getEntities(); Entity[] entities = event.getChunk().getEntities();
for (Entity entity : entities) { for (Entity entity : entities) {
if (entity instanceof ItemDisplay) { if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityUnload(entity); this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) { } else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity); this.manager.handleCollisionEntityUnload(entity);
} }
@@ -77,8 +106,8 @@ public class FurnitureEventListener implements Listener {
public void onWorldUnload(WorldUnloadEvent event) { public void onWorldUnload(WorldUnloadEvent event) {
List<Entity> entities = event.getWorld().getEntities(); List<Entity> entities = event.getWorld().getEntities();
for (Entity entity : entities) { for (Entity entity : entities) {
if (entity instanceof ItemDisplay) { if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityUnload(entity); this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) { } else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity); this.manager.handleCollisionEntityUnload(entity);
} }
@@ -88,10 +117,50 @@ public class FurnitureEventListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onEntityUnload(EntityRemoveFromWorldEvent event) { public void onEntityUnload(EntityRemoveFromWorldEvent event) {
Entity entity = event.getEntity(); Entity entity = event.getEntity();
if (entity instanceof ItemDisplay) { if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityUnload(entity); this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) { } else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity); this.manager.handleCollisionEntityUnload(entity);
} }
} }
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onInteractFurniture(FurnitureInteractEvent event) {
Player bukkitPlayer = event.getPlayer();
BukkitServerPlayer player = BukkitAdaptors.adapt(bukkitPlayer);
if (!(player.canInstabuild() && player.hasPermission("minecraft.debugstick")) && !player.hasPermission("minecraft.debugstick.always")) {
return;
}
Item<ItemStack> itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND);
if (!itemInHand.vanillaId().equals(ItemKeys.DEBUG_STICK)) return;
BukkitFurniture furniture = event.furniture();
List<String> variants = new ArrayList<>(furniture.config.variants().keySet());
if (variants.size() == 1) {
try {
Object systemChatPacket = NetworkReflections.constructor$ClientboundSystemChatPacket.newInstance(
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.empty").arguments(Component.text(furniture.id().asString()))), true);
player.sendPacket(systemChatPacket, false);
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Could not create system chat packet", e);
}
} else {
String variantName = furniture.getCurrentVariant().name();
int index = variants.indexOf(variantName) + 1;
if (index >= variants.size()) {
index = 0;
}
furniture.setVariant(variants.get(index));
try {
Object systemChatPacket = NetworkReflections.constructor$ClientboundSystemChatPacket.newInstance(
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.update")
.arguments(
Component.text("variant"),
Component.text(variants.get(index))
)), true);
player.sendPacket(systemChatPacket, false);
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Could not create system chat packet", e);
}
}
}
} }

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.entity.furniture.BukkitCollider;
import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; 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.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.util.DirectionUtils; 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.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.Vec3d;
import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition; 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.Quaternionf;
import org.joml.Vector3f; import org.joml.Vector3f;
import java.util.*; import java.util.ArrayList;
import java.util.function.BiConsumer; import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer; 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(); public static final Factory FACTORY = new Factory();
// 1.20.6+
private final float scale; private final float scale;
private final byte peek; private final byte peek;
private final boolean interactive; private final boolean interactive;
private final boolean interactionEntity; private final boolean interactionEntity;
private final Direction direction; private final Direction direction;
private final List<Object> cachedShulkerValues = new ArrayList<>();
private final DirectionalShulkerSpawner spawner; private final DirectionalShulkerSpawner spawner;
private final List<Object> cachedShulkerValues = new ArrayList<>(6);
private final AABBCreator aabbCreator; 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) { public ShulkerFurnitureHitboxConfig(SeatConfig[] seats,
super(seats, position, canUseOn, blocksBuilding, canBeHitByProjectile); Vector3f position,
this.direction = direction; 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.scale = scale;
this.peek = peek; this.peek = peek;
this.interactive = interactive; this.interactive = interactive;
this.interactionEntity = interactionEntity; this.interactionEntity = interactionEntity;
this.direction = direction;
ShulkerData.Peek.addEntityDataIfNotDefaultValue(peek, this.cachedShulkerValues); ShulkerData.Peek.addEntityDataIfNotDefaultValue(peek, this.cachedShulkerValues);
ShulkerData.Color.addEntityDataIfNotDefaultValue((byte) 0, 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.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedShulkerValues); // NO AI
ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedShulkerValues); // Invisible ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedShulkerValues); // Invisible
float shulkerHeight = (getPhysicalPeek(peek * 0.01F) + 1) * scale;
List<Object> cachedInteractionValues = new ArrayList<>(); 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.Height.addEntityDataIfNotDefaultValue(shulkerHeight + 0.01f, cachedInteractionValues);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues); InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues); InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues);
@@ -63,32 +78,28 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw, 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 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)));
if (canUseOn) { Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y, z - offset.z);
Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y, z - offset.z); aabb.accept(new FurnitureHitboxPart(entityIds[2], AABB.makeBoundingBox(vec3d, scale, shulkerHeight), vec3d, interactive));
aabb.accept(new HitBoxPart(entityIds[2], AABB.fromInteraction(vec3d, scale, shulkerHeight), vec3d));
}
} }
}; };
this.aabbCreator = (x, y, z, yaw, offset) -> createAABB(Direction.UP, offset, x, y, z); 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.Height.addEntityDataIfNotDefaultValue(shulkerHeight + 0.01f, cachedInteractionValues);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues); InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues); InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues);
this.spawner = (entityIds, world, x, y, z, yaw, offset, packets, collider, aabb) -> { 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)); 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) { if (interactionEntity) {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( 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, 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 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)));
if (canUseOn) { Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y - shulkerHeight + scale, z - offset.z);
Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y - shulkerHeight + scale, z - offset.z); aabb.accept(new FurnitureHitboxPart(entityIds[2], AABB.makeBoundingBox(vec3d, scale, shulkerHeight), vec3d, interactive));
aabb.accept(new HitBoxPart(entityIds[2], AABB.fromInteraction(vec3d, scale, shulkerHeight), vec3d));
}
} }
}; };
this.aabbCreator = (x, y, z, yaw, offset) -> createAABB(Direction.DOWN, offset, x, y, z); 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 shulkerAnchor = getOriginalDirection(direction, Direction.fromYaw(yaw));
Direction shulkerDirection = shulkerAnchor.opposite(); Direction shulkerDirection = shulkerAnchor.opposite();
collider.accept(this.createCollider(shulkerDirection, world, offset, x, y, z, entityIds[1], aabb)); 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) { if (interactionEntity) {
// first interaction // first interaction
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw, 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 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 // second interaction
double distance = shulkerHeight - scale; double distance = shulkerHeight - scale;
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( 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, 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 MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true); ));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[3], List.copyOf(cachedInteractionValues)), true); packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[3], List.copyOf(cachedInteractionValues)));
if (canUseOn) { Vec3d vec3d1 = new Vec3d(x + offset.x, y + offset.y, z - offset.z);
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);
Vec3d vec3d2 = new Vec3d(x + offset.x + shulkerDirection.stepX() * distance, y + offset.y, z - offset.z + shulkerDirection.stepZ() * distance); aabb.accept(new FurnitureHitboxPart(entityIds[2], AABB.makeBoundingBox(vec3d1, scale, scale), vec3d1, interactive));
aabb.accept(new HitBoxPart(entityIds[2], AABB.fromInteraction(vec3d1, scale, scale), vec3d1)); aabb.accept(new FurnitureHitboxPart(entityIds[3], AABB.makeBoundingBox(vec3d2, scale, scale), vec3d2, interactive));
aabb.accept(new HitBoxPart(entityIds[3], AABB.fromInteraction(vec3d2, scale, scale), vec3d2));
}
} }
}; };
this.aabbCreator = (x, y, z, yaw, offset) -> { 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); AABB ceAABB = createAABB(direction, offset, x, y, z);
Object level = world.serverWorld(); Object level = world.serverWorld();
Object nmsAABB = FastNMS.INSTANCE.constructor$AABB(ceAABB.minX, ceAABB.minY, ceAABB.minZ, ceAABB.maxX, ceAABB.maxY, ceAABB.maxZ); 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()); 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) { public AABB createAABB(Direction direction, Vector3f relativePos, double x, double y, double z) {
float peek = getPhysicalPeek(this.peek() * 0.01F); float peek = getPhysicalPeek(this.peek * 0.01F);
double x1 = -this.scale * 0.5; double x1 = -this.scale * 0.5;
double y1 = 0.0; double y1 = 0.0;
double z1 = -this.scale * 0.5; double z1 = -this.scale * 0.5;
@@ -166,156 +245,15 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
} else if (dz < 0) { } else if (dz < 0) {
z1 += dz; z1 += dz;
} }
double minX = x + x1 + offset.x(); double minX = x + x1 + relativePos.x();
double maxX = x + x2 + offset.x(); double maxX = x + x2 + relativePos.x();
double minY = y + y1 + offset.y(); double minY = y + y1 + relativePos.y();
double maxY = y + y2 + offset.y(); double maxY = y + y2 + relativePos.y();
double minZ = z + z1 - offset.z(); double minZ = z + z1 - relativePos.z();
double maxZ = z + z2 - offset.z(); double maxZ = z + z2 - relativePos.z();
return new AABB(minX, minY, minZ, maxX, maxY, maxZ); 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) { public static Direction getOriginalDirection(Direction newDirection, Direction oldDirection) {
switch (newDirection) { switch (newDirection) {
case NORTH -> { case NORTH -> {
@@ -357,4 +295,26 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
default -> throw new IllegalStateException("Unexpected value: " + newDirection); 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.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; 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.EventUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.AnchorType; import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture; import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.FurnitureExtraData;
import net.momirealms.craftengine.core.entity.furniture.HitBoxConfig;
import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item; 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.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; 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.util.*;
import net.momirealms.craftengine.core.world.Vec3d; 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.craftengine.core.world.collision.AABB;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class FurnitureItemBehavior extends ItemBehavior { public class FurnitureItemBehavior extends ItemBehavior {
public static final Factory FACTORY = new Factory(); 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 Key id;
private final Map<AnchorType, Rule> rules;
public FurnitureItemBehavior(Key id) { public FurnitureItemBehavior(Key id, Map<AnchorType, Rule> rules) {
this.id = id; this.id = id;
this.rules = rules;
} }
public Key furnitureId() { public Key furnitureId() {
return id; return this.id;
} }
@Override @Override
@@ -57,12 +57,11 @@ public class FurnitureItemBehavior extends ItemBehavior {
} }
public InteractionResult place(UseOnContext context) { public InteractionResult place(UseOnContext context) {
Optional<CustomFurniture> optionalCustomFurniture = BukkitFurnitureManager.instance().furnitureById(this.id); Optional<FurnitureConfig> optionalCustomFurniture = BukkitFurnitureManager.instance().furnitureById(this.id);
if (optionalCustomFurniture.isEmpty()) { if (optionalCustomFurniture.isEmpty()) {
CraftEngine.instance().logger().warn("Furniture " + this.id + " not found"); CraftEngine.instance().logger().warn("Furniture " + this.id + " not found");
return InteractionResult.FAIL; return InteractionResult.FAIL;
} }
CustomFurniture customFurniture = optionalCustomFurniture.get();
Direction clickedFace = context.getClickedFace(); Direction clickedFace = context.getClickedFace();
AnchorType anchorType = switch (clickedFace) { AnchorType anchorType = switch (clickedFace) {
@@ -71,78 +70,90 @@ public class FurnitureItemBehavior extends ItemBehavior {
case DOWN -> AnchorType.CEILING; case DOWN -> AnchorType.CEILING;
}; };
CustomFurniture.Placement placement = customFurniture.getPlacement(anchorType); FurnitureConfig customFurniture = optionalCustomFurniture.get();
if (placement == null) { FurnitureVariant variant = customFurniture.getVariant(anchorType.variantName());
if (variant == null) {
return InteractionResult.FAIL; return InteractionResult.FAIL;
} }
Rule rule = this.rules.get(anchorType);
if (rule == null) {
rule = Rule.DEFAULT;
}
Player player = context.getPlayer(); Player player = context.getPlayer();
// todo adventure check
if (player != null && player.isAdventureMode()) { if (player != null && player.isAdventureMode()) {
return InteractionResult.FAIL; return InteractionResult.FAIL;
} }
Vec3d clickedPosition = context.getClickLocation(); 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 // get position and rotation for placement
Vec3d finalPlacePosition; Vec3d finalPlacePosition;
double furnitureYaw; double furnitureYaw;
if (anchorType == AnchorType.WALL) { if (anchorType == AnchorType.WALL) {
furnitureYaw = Direction.getYaw(clickedFace); furnitureYaw = Direction.getYaw(clickedFace);
if (clickedFace == Direction.EAST || clickedFace == Direction.WEST) { 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()); finalPlacePosition = new Vec3d(clickedPosition.x(), xz.left(), xz.right());
} else { } 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()); finalPlacePosition = new Vec3d(xz.left(), xz.right(), clickedPosition.z());
} }
} else { } else {
furnitureYaw = placement.rotationRule().apply(180 + (player != null ? player.yRot() : 0)); furnitureYaw = rule.rotationRule().apply(180 + (player != null ? player.yRot() : 0));
Pair<Double, Double> xz = placement.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.z())); Pair<Double, Double> xz = rule.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.z()));
finalPlacePosition = new Vec3d(xz.left(), clickedPosition.y(), xz.right()); 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); Location furnitureLocation = new Location(world, finalPlacePosition.x(), finalPlacePosition.y(), finalPlacePosition.z(), (float) furnitureYaw, 0);
WorldPosition furniturePos = LocationUtils.toWorldPosition(furnitureLocation);
List<AABB> aabbs = new ArrayList<>(); 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 (!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 (!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; return InteractionResult.FAIL;
} }
} }
// 检查其他插件兼容性
if (!BukkitCraftEngine.instance().antiGriefProvider().canPlace(bukkitPlayer, furnitureLocation)) { if (!BukkitCraftEngine.instance().antiGriefProvider().canPlace(bukkitPlayer, furnitureLocation)) {
return InteractionResult.FAIL; return InteractionResult.FAIL;
} }
// 触发尝试放置的事件
if (player != null) { if (player != null) {
FurnitureAttemptPlaceEvent attemptPlaceEvent = new FurnitureAttemptPlaceEvent(bukkitPlayer, customFurniture, anchorType, furnitureLocation.clone(), FurnitureAttemptPlaceEvent attemptPlaceEvent = new FurnitureAttemptPlaceEvent(bukkitPlayer, customFurniture, variant, furnitureLocation.clone(), context.getHand(), world.getBlockAt(context.getClickedPos().x(), context.getClickedPos().y(), context.getClickedPos().z()));
DirectionUtils.toBlockFace(clickedFace), context.getHand(), world.getBlockAt(context.getClickedPos().x(), context.getClickedPos().y(), context.getClickedPos().z()));
if (EventUtils.fireAndCheckCancel(attemptPlaceEvent)) { if (EventUtils.fireAndCheckCancel(attemptPlaceEvent)) {
return InteractionResult.FAIL; return InteractionResult.FAIL;
} }
} }
Item<?> item = context.getItem(); Item<?> item = context.getItem();
// 不可能
if (ItemUtils.isEmpty(item)) return InteractionResult.FAIL; if (ItemUtils.isEmpty(item)) return InteractionResult.FAIL;
// 获取家具物品的一些属性
BukkitFurniture bukkitFurniture = BukkitFurnitureManager.instance().place( FurnitureDataAccessor dataAccessor = FurnitureDataAccessor.of(new CompoundTag());
furnitureLocation.clone(), customFurniture, dataAccessor.setVariant(variant.name());
FurnitureExtraData.builder() dataAccessor.setItem(item.copyWithCount(1));
.item(item.copyWithCount(1)) dataAccessor.setDyedColor(item.dyedColor().orElse(null));
.anchorType(anchorType) dataAccessor.setFireworkExplosionColors(item.fireworkExplosion().map(explosion -> explosion.colors().toIntArray()).orElse(null));
.dyedColor(item.dyedColor().orElse(null)) // 放置家具
.fireworkExplosionColors(item.fireworkExplosion().map(explosion -> explosion.colors().toIntArray()).orElse(null)) BukkitFurniture bukkitFurniture = BukkitFurnitureManager.instance().place(furnitureLocation.clone(), customFurniture, dataAccessor, false);
.build(), false); // 触发放置事件
if (player != null) { if (player != null) {
FurniturePlaceEvent placeEvent = new FurniturePlaceEvent(bukkitPlayer, bukkitFurniture, furnitureLocation, context.getHand()); FurniturePlaceEvent placeEvent = new FurniturePlaceEvent(bukkitPlayer, bukkitFurniture, furnitureLocation, context.getHand());
if (EventUtils.fireAndCheckCancel(placeEvent)) { if (EventUtils.fireAndCheckCancel(placeEvent)) {
@@ -150,7 +161,7 @@ public class FurnitureItemBehavior extends ItemBehavior {
return InteractionResult.FAIL; return InteractionResult.FAIL;
} }
} }
// 触发ce事件
Cancellable dummy = Cancellable.dummy(); Cancellable dummy = Cancellable.dummy();
PlayerOptionalContext functionContext = PlayerOptionalContext.of(player, ContextHolder.builder() PlayerOptionalContext functionContext = PlayerOptionalContext.of(player, ContextHolder.builder()
.withParameter(DirectContextParameters.FURNITURE, bukkitFurniture) .withParameter(DirectContextParameters.FURNITURE, bukkitFurniture)
@@ -163,14 +174,13 @@ public class FurnitureItemBehavior extends ItemBehavior {
if (dummy.isCancelled()) { if (dummy.isCancelled()) {
return InteractionResult.SUCCESS_AND_CANCEL; return InteractionResult.SUCCESS_AND_CANCEL;
} }
// 后续处理
if (player != null) { if (player != null) {
if (!player.canInstabuild()) { if (!player.canInstabuild()) {
item.count(item.count() - 1); item.count(item.count() - 1);
} }
player.swingHand(context.getHand()); player.swingHand(context.getHand());
} }
context.getLevel().playBlockSound(finalPlacePosition, customFurniture.settings().sounds().placeSound()); context.getLevel().playBlockSound(finalPlacePosition, customFurniture.settings().sounds().placeSound());
return InteractionResult.SUCCESS; return InteractionResult.SUCCESS;
} }
@@ -183,17 +193,59 @@ public class FurnitureItemBehavior extends ItemBehavior {
if (id == null) { if (id == null) {
throw new LocalizedResourceConfigException("warning.config.item.behavior.furniture.missing_furniture", new IllegalArgumentException("Missing required parameter 'furniture' for furniture_item behavior")); 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) { if (id instanceof Map<?,?> map) {
Map<String, Object> furnitureSection;
if (map.containsKey(key.toString())) { 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 { } 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 { } 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

@@ -16,7 +16,9 @@ import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.MiscUtils;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
@@ -44,12 +46,10 @@ public class DebugStickListener implements Listener {
public void onUseDebugStick(PlayerInteractEvent event) { public void onUseDebugStick(PlayerInteractEvent event) {
Block clickedBlock = event.getClickedBlock(); Block clickedBlock = event.getClickedBlock();
if (clickedBlock == null) return; if (clickedBlock == null) return;
ItemStack itemInHand = event.getItem();
if (ItemStackUtils.isEmpty(itemInHand)) return;
Material material = itemInHand.getType();
if (material != Material.DEBUG_STICK) return;
Player bukkitPlayer = event.getPlayer(); Player bukkitPlayer = event.getPlayer();
BukkitServerPlayer player = BukkitAdaptors.adapt(bukkitPlayer); BukkitServerPlayer player = BukkitAdaptors.adapt(bukkitPlayer);
Item<ItemStack> itemInHand = player.getItemInHand(event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND);
if (!itemInHand.vanillaId().equals(ItemKeys.DEBUG_STICK)) return;
if (!(player.canInstabuild() && player.hasPermission("minecraft.debugstick")) && !player.hasPermission("minecraft.debugstick.always")) { if (!(player.canInstabuild() && player.hasPermission("minecraft.debugstick")) && !player.hasPermission("minecraft.debugstick.always")) {
return; return;
} }
@@ -73,8 +73,7 @@ public class DebugStickListener implements Listener {
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.empty").arguments(Component.text(blockId))), true); ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.empty").arguments(Component.text(blockId))), true);
player.sendPacket(systemChatPacket, false); player.sendPacket(systemChatPacket, false);
} else { } else {
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(itemInHand); Object storedData = itemInHand.getJavaTag("craftengine:debug_stick_state");
Object storedData = wrapped.getJavaTag("craftengine:debug_stick_state");
if (storedData == null) storedData = new HashMap<>(); if (storedData == null) storedData = new HashMap<>();
if (storedData instanceof Map<?,?> map) { if (storedData instanceof Map<?,?> map) {
Map<String, Object> data = new HashMap<>(MiscUtils.castToMap(map, false)); Map<String, Object> data = new HashMap<>(MiscUtils.castToMap(map, false));
@@ -96,7 +95,7 @@ public class DebugStickListener implements Listener {
} else { } else {
currentProperty = getRelative(properties, currentProperty, player.isSecondaryUseActive()); currentProperty = getRelative(properties, currentProperty, player.isSecondaryUseActive());
data.put(blockId, currentProperty.name()); data.put(blockId, currentProperty.name());
wrapped.setTag(data, "craftengine:debug_stick_state"); itemInHand.setTag(data, "craftengine:debug_stick_state");
Object systemChatPacket = NetworkReflections.constructor$ClientboundSystemChatPacket.newInstance( Object systemChatPacket = NetworkReflections.constructor$ClientboundSystemChatPacket.newInstance(
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.select") ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.select")
.arguments( .arguments(

View File

@@ -457,9 +457,11 @@ public class ItemEventListener implements Listener {
} }
} else { } else {
// fixme 如何取消堆叠数量>1的物品的默认replacement // fixme 如何取消堆叠数量>1的物品的默认replacement
Item<ItemStack> replacementItem = this.plugin.itemManager().createWrappedItem(replacement, serverPlayer); if (replacement != null) {
if (replacementItem != null) { Item<ItemStack> replacementItem = this.plugin.itemManager().createWrappedItem(replacement, serverPlayer);
PlayerUtils.giveItem(serverPlayer, 1, replacementItem); if (replacementItem != null) {
PlayerUtils.giveItem(serverPlayer, 1, replacementItem);
}
} }
} }
} }

View File

@@ -93,6 +93,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme
public class VanillaLootParser extends IdSectionConfigParser { public class VanillaLootParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"vanilla-loots", "vanilla-loot"}; public static final String[] CONFIG_SECTION_NAME = new String[] {"vanilla-loots", "vanilla-loot"};
private int count;
@Override @Override
public int loadingSequence() { public int loadingSequence() {
@@ -104,6 +105,16 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme
return CONFIG_SECTION_NAME; return CONFIG_SECTION_NAME;
} }
@Override
public int count() {
return this.count;
}
@Override
public void preProcess() {
this.count = 0;
}
@Override @Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) { 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"); 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.pack.obfuscation.ObfA;
import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config; 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.Base64Utils;
import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -94,7 +95,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
return; return;
} }
if (!Config.sendPackOnUpload()) 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()) { for (BukkitServerPlayer player : this.plugin.networkManager().onlineUsers()) {
sendResourcePack(player); 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.behavior.BukkitBlockBehaviors;
import net.momirealms.craftengine.bukkit.block.entity.renderer.element.BukkitBlockEntityElementConfigs; 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.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.projectile.BukkitProjectileManager;
import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeatManager; import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeatManager;
import net.momirealms.craftengine.bukkit.font.BukkitFontManager; import net.momirealms.craftengine.bukkit.font.BukkitFontManager;
@@ -160,8 +161,9 @@ public class BukkitCraftEngine extends CraftEngine {
super.onPluginLoad(); super.onPluginLoad();
BukkitBlockBehaviors.init(); BukkitBlockBehaviors.init();
BukkitItemBehaviors.init(); BukkitItemBehaviors.init();
BukkitHitBoxTypes.init(); BukkitFurnitureHitboxTypes.init();
BukkitBlockEntityElementConfigs.init(); BukkitBlockEntityElementConfigs.init();
BukkitFurnitureElementConfigs.init();
// 初始化 onload 阶段的兼容性 // 初始化 onload 阶段的兼容性
super.compatibilityManager().onLoad(); super.compatibilityManager().onLoad();
// 创建网络管理器 // 创建网络管理器
@@ -369,6 +371,11 @@ public class BukkitCraftEngine extends CraftEngine {
return (BukkitFontManager) fontManager; return (BukkitFontManager) fontManager;
} }
@Override
public BukkitWorldManager worldManager() {
return (BukkitWorldManager) worldManager;
}
@SuppressWarnings("ResultOfMethodCallIgnored") @SuppressWarnings("ResultOfMethodCallIgnored")
@Override @Override
public void saveResource(String resourcePath) { public void saveResource(String resourcePath) {

View File

@@ -31,34 +31,11 @@ public class BukkitPlatform implements Platform {
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command); Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command);
} }
@SuppressWarnings("unchecked")
@Override
public Object snbtToJava(String nbt) {
try {
Object tag = FastNMS.INSTANCE.method$TagParser$parseCompoundFully("{\"root\":" + nbt + "}");
Map<String, Object> map = (Map<String, Object>) MRegistryOps.NBT.convertTo(MRegistryOps.JAVA, tag);
return map.get("root");
} catch (CommandSyntaxException e) {
throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e, nbt);
}
}
@Override @Override
public Tag jsonToSparrowNBT(JsonElement json) { public Tag jsonToSparrowNBT(JsonElement json) {
return MRegistryOps.JSON.convertTo(MRegistryOps.SPARROW_NBT, json); return MRegistryOps.JSON.convertTo(MRegistryOps.SPARROW_NBT, json);
} }
@Override
public Tag snbtToSparrowNBT(String nbt) {
try {
Object tag = FastNMS.INSTANCE.method$TagParser$parseCompoundFully("{\"root\":" + nbt + "}");
CompoundTag map = (CompoundTag) MRegistryOps.NBT.convertTo(MRegistryOps.SPARROW_NBT, tag);
return map.get("root");
} catch (CommandSyntaxException e) {
throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e, nbt);
}
}
@Override @Override
public Tag javaToSparrowNBT(Object object) { public Tag javaToSparrowNBT(Object object) {
return MRegistryOps.JAVA.convertTo(MRegistryOps.SPARROW_NBT, object); return MRegistryOps.JAVA.convertTo(MRegistryOps.SPARROW_NBT, object);

View File

@@ -43,12 +43,14 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new TestCommand(this, plugin), new TestCommand(this, plugin),
new SetLocaleCommand(this, plugin), new SetLocaleCommand(this, plugin),
new SetEntityViewDistanceScaleCommand(this, plugin), new SetEntityViewDistanceScaleCommand(this, plugin),
new ToggleEntityCullingCommand(this, plugin),
new UnsetLocaleCommand(this, plugin), new UnsetLocaleCommand(this, plugin),
new DebugGetBlockStateRegistryIdCommand(this, plugin), new DebugGetBlockStateRegistryIdCommand(this, plugin),
new DebugGetBlockInternalIdCommand(this, plugin), new DebugGetBlockInternalIdCommand(this, plugin),
new DebugAppearanceStateUsageCommand(this, plugin), new DebugAppearanceStateUsageCommand(this, plugin),
new DebugClearCooldownCommand(this, plugin), new DebugClearCooldownCommand(this, plugin),
new DebugEntityIdCommand(this, plugin), new DebugEntityIdCommand(this, plugin),
new DebugFurnitureCommand(this, plugin),
new DebugRealStateUsageCommand(this, plugin), new DebugRealStateUsageCommand(this, plugin),
new DebugItemDataCommand(this, plugin), new DebugItemDataCommand(this, plugin),
new DebugSetBlockCommand(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.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.entity.furniture.AnchorType; import net.momirealms.craftengine.core.entity.furniture.FurnitureConfig;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys; 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.bukkit.parser.location.LocationParser;
import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput; 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.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider; import org.incendo.cloud.suggestion.SuggestionProvider;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -42,21 +42,30 @@ public class DebugSpawnFurnitureCommand extends BukkitCommandFeature<CommandSend
return CompletableFuture.completedFuture(plugin().furnitureManager().cachedSuggestions()); 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) .flag(FlagKeys.SILENT_FLAG)
.handler(context -> { .handler(context -> {
NamespacedKey namespacedKey = context.get("id"); NamespacedKey namespacedKey = context.get("id");
Key id = KeyUtils.namespacedKey2Key(namespacedKey); Key id = KeyUtils.namespacedKey2Key(namespacedKey);
BukkitFurnitureManager furnitureManager = BukkitFurnitureManager.instance(); BukkitFurnitureManager furnitureManager = BukkitFurnitureManager.instance();
Optional<CustomFurniture> optionalCustomFurniture = furnitureManager.furnitureById(id); Optional<FurnitureConfig> optionalCustomFurniture = furnitureManager.furnitureById(id);
if (optionalCustomFurniture.isEmpty()) { if (optionalCustomFurniture.isEmpty()) {
return; return;
} }
Location location = context.get("location"); Location location = context.get("location");
CustomFurniture customFurniture = optionalCustomFurniture.get(); FurnitureConfig customFurniture = optionalCustomFurniture.get();
AnchorType anchorType = (AnchorType) context.optional("anchor-type").orElse(customFurniture.getAnyAnchorType()); String variant = (String) context.optional("variant").orElse(customFurniture.anyVariantName());
boolean playSound = context.flags().hasFlag("silent"); 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; package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component; 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.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys; 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 net.momirealms.craftengine.core.plugin.locale.MessageConstants;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -27,6 +29,14 @@ public class SetEntityViewDistanceScaleCommand extends BukkitCommandFeature<Comm
.required("player", PlayerParser.playerParser()) .required("player", PlayerParser.playerParser())
.required("scale", DoubleParser.doubleParser(0.125, 8)) .required("scale", DoubleParser.doubleParser(0.125, 8))
.handler(context -> { .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"); Player player = context.get("player");
double scale = context.get("scale"); double scale = context.get("scale");
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); 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

@@ -60,14 +60,15 @@ import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
import net.momirealms.craftengine.core.advancement.network.AdvancementHolder; import net.momirealms.craftengine.core.advancement.network.AdvancementHolder;
import net.momirealms.craftengine.core.advancement.network.AdvancementProgress; import net.momirealms.craftengine.core.advancement.network.AdvancementProgress;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.entity.furniture.HitBox; import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
import net.momirealms.craftengine.core.entity.furniture.HitBoxPart; import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.seat.Seat; import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.font.FontManager; import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult; import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult;
import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.item.recipe.network.legacy.LegacyRecipeHolder; import net.momirealms.craftengine.core.item.recipe.network.legacy.LegacyRecipeHolder;
@@ -361,8 +362,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
registerNMSPacketConsumer(new EntityEventListener(), NetworkReflections.clazz$ClientboundEntityEventPacket); registerNMSPacketConsumer(new EntityEventListener(), NetworkReflections.clazz$ClientboundEntityEventPacket);
registerNMSPacketConsumer(new MovePosAndRotateEntityListener(), NetworkReflections.clazz$ClientboundMoveEntityPacket$PosRot); registerNMSPacketConsumer(new MovePosAndRotateEntityListener(), NetworkReflections.clazz$ClientboundMoveEntityPacket$PosRot);
registerNMSPacketConsumer(new MovePosEntityListener(), NetworkReflections.clazz$ClientboundMoveEntityPacket$Pos); 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 FinishConfigurationListener(), NetworkReflections.clazz$ClientboundFinishConfigurationPacket);
registerNMSPacketConsumer(new LoginFinishedListener(), NetworkReflections.clazz$ClientboundLoginFinishedPacket); registerNMSPacketConsumer(new LoginFinishedListener(), NetworkReflections.clazz$ClientboundLoginFinishedPacket);
registerNMSPacketConsumer(new UpdateTagsListener(), NetworkReflections.clazz$ClientboundUpdateTagsPacket); registerNMSPacketConsumer(new UpdateTagsListener(), NetworkReflections.clazz$ClientboundUpdateTagsPacket);
@@ -1250,7 +1249,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
CraftEngine.instance().logger().warn("Failed to get entityId from ServerboundPickItemFromEntityPacket", e); CraftEngine.instance().logger().warn("Failed to get entityId from ServerboundPickItemFromEntityPacket", e);
return; return;
} }
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId); BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByVirtualEntityId(entityId);
if (furniture == null) return; if (furniture == null) return;
Player player = (Player) user.platformPlayer(); Player player = (Player) user.platformPlayer();
if (player == null) return; if (player == null) return;
@@ -1276,7 +1275,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
private static void handlePickItemFromEntityOnMainThread(Player player, BukkitFurniture furniture) throws Throwable { private static void handlePickItemFromEntityOnMainThread(Player player, BukkitFurniture furniture) throws Throwable {
Key itemId = furniture.config().settings().itemId(); Key itemId = furniture.config().settings().itemId();
if (itemId == null) return; 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()));
} }
} }
@@ -1472,6 +1471,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
player.setClientSideWorld(BukkitAdaptors.adapt(world)); player.setClientSideWorld(BukkitAdaptors.adapt(world));
player.clearTrackedChunks(); player.clearTrackedChunks();
player.clearTrackedBlockEntities(); player.clearTrackedBlockEntities();
player.clearTrackedFurniture();
} else { } else {
CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist"); CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist");
} }
@@ -1748,9 +1748,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
@Override @Override
public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) {
int entityId = ProtectedFieldVisitor.get().field$ClientboundMoveEntityPacket$entityId(packet); int entityId = ProtectedFieldVisitor.get().field$ClientboundMoveEntityPacket$entityId(packet);
if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) {
event.setCancelled(true);
}
EntityPacketHandler handler = user.entityPacketHandlers().get(entityId); EntityPacketHandler handler = user.entityPacketHandlers().get(entityId);
if (handler != null) { if (handler != null) {
handler.handleMoveAndRotate(user, event, packet); handler.handleMoveAndRotate(user, event, packet);
@@ -1770,41 +1767,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 { public static class FinishConfigurationListener implements NMSPacketListener {
private void returnToWorld(NetWorkUser user, Queue<Object> configurationTasks, Object packetListener) { private void returnToWorld(NetWorkUser user, Queue<Object> configurationTasks, Object packetListener) {
@@ -2228,7 +2190,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
if (Config.entityCullingRayTracing()) { if (Config.entityCullingRayTracing()) {
SectionPos sectionPos = SectionPos.of(sPos); SectionPos sectionPos = SectionPos.of(sPos);
ClientChunk trackedChunk = user.getTrackedChunk(sectionPos.asChunkPos().longKey); ClientChunk trackedChunk = user.getTrackedChunk(sectionPos.asChunkPos().longKey);
clientSection = trackedChunk.sectionById(sectionPos.y); if (trackedChunk != null) {
clientSection = trackedChunk.sectionById(sectionPos.y);
}
} }
for (int i = 0; i < blocks; i++) { for (int i = 0; i < blocks; i++) {
@@ -3361,7 +3325,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
for (int i = 0, size = intList.size(); i < size; i++) { for (int i = 0, size = intList.size(); i < size; i++) {
int entityId = intList.getInt(i); int entityId = intList.getInt(i);
EntityPacketHandler handler = user.entityPacketHandlers().remove(entityId); EntityPacketHandler handler = user.entityPacketHandlers().remove(entityId);
if (handler != null && handler.handleEntitiesRemove(intList)) { if (handler != null && handler.handleEntitiesRemove(user, intList)) {
changed = true; changed = true;
} }
} }
@@ -3710,35 +3674,41 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
public void onPacketReceive(NetWorkUser user, ByteBufPacketEvent event) { public void onPacketReceive(NetWorkUser user, ByteBufPacketEvent event) {
FriendlyByteBuf buf = event.getBuffer(); FriendlyByteBuf buf = event.getBuffer();
int entityId = hasModelEngine() ? plugin.compatibilityManager().interactionToBaseEntity(buf.readVarInt()) : buf.readVarInt(); 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; if (furniture == null) return;
int actionType = buf.readVarInt(); int actionType = buf.readVarInt();
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user; BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
if (serverPlayer.isSpectatorMode()) return; if (serverPlayer.isSpectatorMode()) return;
Player platformPlayer = serverPlayer.platformPlayer(); Player platformPlayer = serverPlayer.platformPlayer();
Location location = furniture.baseEntity().getLocation(); Location location = furniture.location();
Runnable mainThreadTask; Runnable mainThreadTask;
if (actionType == 1) { if (actionType == 1) {
// ATTACK // ATTACK
boolean usingSecondaryAction = buf.readBoolean(); boolean usingSecondaryAction = buf.readBoolean();
if (entityId != furniture.baseEntityId()) { if (entityId != furniture.entityId()) {
event.setChanged(true); event.setChanged(true);
buf.clear(); buf.clear();
buf.writeVarInt(event.packetID()); buf.writeVarInt(event.packetID());
buf.writeVarInt(furniture.baseEntityId()); buf.writeVarInt(furniture.entityId());
buf.writeVarInt(actionType); buf.writeVarInt(actionType);
buf.writeBoolean(usingSecondaryAction); buf.writeBoolean(usingSecondaryAction);
} }
mainThreadTask = () -> { mainThreadTask = () -> {
// todo 冒险模式破坏工具白名单 // todo 冒险模式破坏工具白名单
if (serverPlayer.isAdventureMode() || if (serverPlayer.isAdventureMode() || !furniture.isValid()) return;
!furniture.isValid()) return;
// todo 重构家具时候注意需要准备加载好的hitbox类以获取hitbox坐标 // 先检查碰撞箱部分是否存在
if (!serverPlayer.canInteractPoint(new Vec3d(location.getX(), location.getY(), location.getZ()), 16d)) { FurnitureHitBox hitBox = furniture.hitboxByEntityId(entityId);
return; 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); FurnitureAttemptBreakEvent preBreakEvent = new FurnitureAttemptBreakEvent(serverPlayer.platformPlayer(), furniture);
@@ -3776,11 +3746,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
float z = buf.readFloat(); float z = buf.readFloat();
InteractionHand hand = buf.readVarInt() == 0 ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND; InteractionHand hand = buf.readVarInt() == 0 ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
boolean usingSecondaryAction = buf.readBoolean(); boolean usingSecondaryAction = buf.readBoolean();
if (entityId != furniture.baseEntityId()) { if (entityId != furniture.entityId()) {
event.setChanged(true); event.setChanged(true);
buf.clear(); buf.clear();
buf.writeVarInt(event.packetID()); buf.writeVarInt(event.packetID());
buf.writeVarInt(furniture.baseEntityId()); buf.writeVarInt(furniture.entityId());
buf.writeVarInt(actionType); buf.writeVarInt(actionType);
buf.writeFloat(x).writeFloat(y).writeFloat(z); buf.writeFloat(x).writeFloat(y).writeFloat(z);
buf.writeVarInt(hand == InteractionHand.MAIN_HAND ? 0 : 1); buf.writeVarInt(hand == InteractionHand.MAIN_HAND ? 0 : 1);
@@ -3793,34 +3763,39 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
} }
// 先检查碰撞箱部分是否存在 // 先检查碰撞箱部分是否存在
HitBoxPart hitBoxPart = furniture.hitBoxPartByEntityId(entityId); FurnitureHitBox hitBox = furniture.hitboxByEntityId(entityId);
if (hitBoxPart == null) return; if (hitBox == null) return;
Vec3d pos = hitBoxPart.pos(); FurnitureHitboxPart part = null;
// 检测距离 for (FurnitureHitboxPart p : hitBox.parts()) {
if (!serverPlayer.canInteractPoint(pos, 16d)) { if (p.entityId() == entityId) {
Vec3d pos = p.pos();
// 检测距离
if (!serverPlayer.canInteractPoint(pos, 16d)) {
return;
}
part = p;
}
}
if (part == null) {
return; return;
} }
// 检测
// 检测能否交互碰撞箱
Location eyeLocation = platformPlayer.getEyeLocation(); Location eyeLocation = platformPlayer.getEyeLocation();
Vector direction = eyeLocation.getDirection(); Vector direction = eyeLocation.getDirection();
Location endLocation = eyeLocation.clone(); Location endLocation = eyeLocation.clone();
endLocation.add(direction.multiply(serverPlayer.getCachedInteractionRange())); 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()) { if (result.isEmpty()) {
return; return;
} }
EntityHitResult hitResult = result.get(); EntityHitResult hitResult = result.get();
Vec3d hitLocation = hitResult.hitLocation(); Vec3d hitLocation = hitResult.hitLocation();
// 获取正确的交互点 // 获取正确的交互点
Location interactionPoint = new Location(platformPlayer.getWorld(), hitLocation.x, hitLocation.y, hitLocation.z); 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)) { if (EventUtils.fireAndCheckCancel(interactEvent)) {
return; return;
} }
@@ -3841,8 +3816,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
return; return;
} }
// 不处理调试棒
if (itemInHand.vanillaId().equals(ItemKeys.DEBUG_STICK)) {
return;
}
// 必须从网络包层面处理,否则无法获取交互的具体实体 // 必须从网络包层面处理,否则无法获取交互的具体实体
if (serverPlayer.isSecondaryUseActive() && !itemInHand.isEmpty() && hitbox.config().canUseItemOn()) { if (serverPlayer.isSecondaryUseActive() && !itemInHand.isEmpty() && hitBox.config().canUseItemOn()) {
Optional<CustomItem<ItemStack>> optionalCustomItem = itemInHand.getCustomItem(); Optional<CustomItem<ItemStack>> optionalCustomItem = itemInHand.getCustomItem();
if (optionalCustomItem.isPresent() && !optionalCustomItem.get().behaviors().isEmpty()) { if (optionalCustomItem.isPresent() && !optionalCustomItem.get().behaviors().isEmpty()) {
for (ItemBehavior behavior : optionalCustomItem.get().behaviors()) { for (ItemBehavior behavior : optionalCustomItem.get().behaviors()) {
@@ -3862,9 +3842,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
); );
} else { } else {
if (!serverPlayer.isSecondaryUseActive()) { if (!serverPlayer.isSecondaryUseActive()) {
for (Seat<HitBox> seat : hitbox.seats()) { for (Seat<FurnitureHitBox> seat : hitBox.seats()) {
if (!seat.isOccupied()) { if (!seat.isOccupied()) {
if (seat.spawnSeat(serverPlayer, furniture.position())) { if (seat.spawnSeat(serverPlayer, furniture.position())) {
if (!part.interactive()) {
serverPlayer.swingHand(InteractionHand.MAIN_HAND);
}
break; break;
} }
} }
@@ -3875,11 +3858,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
} else if (actionType == 0) { } else if (actionType == 0) {
int hand = buf.readVarInt(); int hand = buf.readVarInt();
boolean usingSecondaryAction = buf.readBoolean(); boolean usingSecondaryAction = buf.readBoolean();
if (entityId != furniture.baseEntityId()) { if (entityId != furniture.entityId()) {
event.setChanged(true); event.setChanged(true);
buf.clear(); buf.clear();
buf.writeVarInt(event.packetID()); buf.writeVarInt(event.packetID());
buf.writeVarInt(furniture.baseEntityId()); buf.writeVarInt(furniture.entityId());
buf.writeVarInt(actionType); buf.writeVarInt(actionType);
buf.writeVarInt(hand); buf.writeVarInt(hand);
buf.writeBoolean(usingSecondaryAction); buf.writeBoolean(usingSecondaryAction);
@@ -3987,10 +3970,18 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
this.handlers[MEntityTypes.ITEM_DISPLAY$registryId] = (user, event) -> { this.handlers[MEntityTypes.ITEM_DISPLAY$registryId] = (user, event) -> {
FriendlyByteBuf buf = event.getBuffer(); FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt(); int id = buf.readVarInt();
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id); BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(id);
if (furniture != null) { if (furniture != null) {
user.entityPacketHandlers().put(id, new FurniturePacketHandler(furniture.fakeEntityIds())); EntityPacketHandler previous = serverPlayer.entityPacketHandlers().put(id, new FurniturePacketHandler(id, furniture.virtualEntityIds()));
user.sendPacket(furniture.spawnPacket((Player) user.platformPlayer()), false); if (Config.enableEntityCulling()) {
serverPlayer.addTrackedFurniture(id, furniture);
} else {
// 修复addEntityToWorld包比事件先发的问题 (WE)
if (previous == null || previous instanceof ItemDisplayPacketHandler) {
furniture.show(serverPlayer);
}
}
if (Config.hideBaseEntity() && !furniture.hasExternalModel()) { if (Config.hideBaseEntity() && !furniture.hasExternalModel()) {
event.setCancelled(true); event.setCancelled(true);
} }
@@ -4003,7 +3994,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
FriendlyByteBuf buf = event.getBuffer(); FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt(); int id = buf.readVarInt();
// Cancel collider entity packet // Cancel collider entity packet
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id); BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByColliderEntityId(id);
if (furniture != null) { if (furniture != null) {
event.setCancelled(true); event.setCancelled(true);
user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE); user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE);
@@ -4014,7 +4005,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
FriendlyByteBuf buf = event.getBuffer(); FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt(); int id = buf.readVarInt();
// Cancel collider entity packet // Cancel collider entity packet
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id); BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByColliderEntityId(id);
if (furniture != null) { if (furniture != null) {
event.setCancelled(true); event.setCancelled(true);
user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE); user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE);

View File

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

View File

@@ -4615,4 +4615,27 @@ public final class CoreReflections {
throw new ReflectionInitException("Failed to init EmptyBlockGetter$INSTANCE", e); 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 io.netty.channel.ChannelHandler;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; 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.block.entity.BlockEntityHolder;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; 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.BlockEntity;
import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer;
import net.momirealms.craftengine.core.entity.data.EntityData; 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.GameMode;
import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.entity.player.Player;
@@ -74,6 +79,8 @@ import java.util.concurrent.ConcurrentHashMap;
public class BukkitServerPlayer extends Player { public class BukkitServerPlayer extends Player {
public static final Key SELECTED_LOCALE_KEY = Key.of("craftengine:locale"); 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 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; private final BukkitCraftEngine plugin;
// connection state // connection state
@@ -143,11 +150,20 @@ public class BukkitServerPlayer extends Player {
private int lastStopMiningTick; private int lastStopMiningTick;
// 跟踪到的方块实体渲染器 // 跟踪到的方块实体渲染器
private final Map<BlockPos, VirtualCullableObject> trackedBlockEntityRenderers = new ConcurrentHashMap<>(); private final Map<BlockPos, VirtualCullableObject> trackedBlockEntityRenderers = new ConcurrentHashMap<>();
private final Map<Integer, VirtualCullableObject> trackedFurniture = new ConcurrentHashMap<>();
private final EntityCulling culling; private final EntityCulling culling;
private Vec3d firstPersonCameraVec3; private Vec3d firstPersonCameraVec3;
private Vec3d thirdPersonCameraVec3; private Vec3d thirdPersonCameraVec3;
// 是否启用实体剔除
private boolean enableEntityCulling;
// 玩家眼睛所在位置 // 玩家眼睛所在位置
private Location eyeLocation; private Location eyeLocation;
// 是否启用家具调试
private boolean enableFurnitureDebug;
// 上一次对准的家具
private BukkitFurniture lastHitFurniture;
// 缓存的tick
private int lastHitFurnitureTick;
public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) {
this.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); 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); 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); 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.culling.setDistanceScale(Optional.ofNullable(scale).orElse(1.0));
this.selectedLocale = TranslationManager.parseLocale(locale); this.selectedLocale = TranslationManager.parseLocale(locale);
this.trackedChunks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(512, 0.5f); this.trackedChunks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(512, 0.5f);
@@ -532,6 +550,33 @@ public class BukkitServerPlayer extends Player {
this.updateGUI(); 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(); 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); 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)); 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 it's not destroying blocks, we do predict
if ((gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) { if ((gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) {
this.predictNextBlockToMine(); this.predictNextBlockToMine();
@@ -596,41 +641,60 @@ public class BukkitServerPlayer extends Player {
@Override @Override
public void entityCullingTick() { public void entityCullingTick() {
this.culling.restoreTokenOnTick(); this.culling.restoreTokenOnTick();
if (this.firstPersonCameraVec3 == null || this.thirdPersonCameraVec3 == null) {
return;
}
boolean useRayTracing = Config.entityCullingRayTracing(); boolean useRayTracing = Config.entityCullingRayTracing();
for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) { if (this.enableEntityCulling) {
CullingData cullingData = cullableObject.cullable.cullingData(); for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) {
if (cullingData != null) { cullEntity(useRayTracing, cullableObject);
boolean firstPersonVisible = this.culling.isVisible(cullingData, this.firstPersonCameraVec3, useRayTracing); }
// 之前可见 for (VirtualCullableObject cullableObject : this.trackedFurniture.values()) {
if (cullableObject.isShown) { cullEntity(useRayTracing, cullableObject);
boolean thirdPersonVisible = this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing); }
if (!firstPersonVisible && !thirdPersonVisible) { } else {
cullableObject.setShown(this, false); for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) {
}
}
// 之前不可见
else {
// 但是第一人称可见了
if (firstPersonVisible) {
// 下次再说
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
continue;
}
cullableObject.setShown(this, true);
continue;
}
if (this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing)) {
// 下次再说
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
continue;
}
cullableObject.setShown(this, true);
}
// 仍然不可见
}
} else {
cullableObject.setShown(this, true); 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);
// 之前可见
if (cullableObject.isShown) {
boolean thirdPersonVisible = this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing);
if (!firstPersonVisible && !thirdPersonVisible) {
cullableObject.setShown(this, false);
}
}
// 之前不可见
else {
// 但是第一人称可见了
if (firstPersonVisible) {
// 下次再说
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
return;
}
cullableObject.setShown(this, true);
return;
}
if (this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing)) {
// 下次再说
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
return;
}
cullableObject.setShown(this, true);
}
// 仍然不可见
}
} else {
cullableObject.setShown(this, true);
} }
} }
@@ -1341,6 +1405,28 @@ public class BukkitServerPlayer extends Player {
platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(ENTITY_CULLING_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE, value); 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 @Override
public void giveExperiencePoints(int xpPoints) { public void giveExperiencePoints(int xpPoints) {
platformPlayer().giveExp(xpPoints); platformPlayer().giveExp(xpPoints);
@@ -1406,11 +1492,37 @@ public class BukkitServerPlayer extends Player {
this.trackedBlockEntityRenderers.clear(); 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 @Override
public WorldPosition eyePosition() { public WorldPosition eyePosition() {
return LocationUtils.toWorldPosition(this.getEyeLocation()); 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() { public Location getEyeLocation() {
org.bukkit.entity.Player player = platformPlayer(); org.bukkit.entity.Player player = platformPlayer();
Location eyeLocation = player.getEyeLocation(); Location eyeLocation = player.getEyeLocation();

View File

@@ -26,13 +26,9 @@ public final class EntityUtils {
} }
public static BlockPos getOnPos(Player player) { public static BlockPos getOnPos(Player player) {
try { Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player);
Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player); Object blockPos = FastNMS.INSTANCE.method$Entity$getOnPos(serverPlayer);
Object blockPos = CoreReflections.method$Entity$getOnPos.invoke(serverPlayer, 1.0E-5F); return LocationUtils.fromBlockPos(blockPos);
return LocationUtils.fromBlockPos(blockPos);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
} }
public static Entity spawnEntity(World world, Location loc, EntityType type, Consumer<Entity> function) { 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); injectChunkGenerator(ceWorld);
for (Chunk chunk : world.getLoadedChunks()) { for (Chunk chunk : world.getLoadedChunks()) {
handleChunkLoad(ceWorld, chunk, false); handleChunkLoad(ceWorld, chunk, false);
CEChunk loadedChunk = ceWorld.getChunkAtIfLoaded(chunk.getChunkKey());
if (loadedChunk != null) {
loadedChunk.setEntitiesLoaded(true);
}
} }
ceWorld.setTicking(true); ceWorld.setTicking(true);
} catch (Exception e) { } catch (Exception e) {
@@ -154,6 +158,10 @@ public class BukkitWorldManager implements WorldManager, Listener {
CEWorld ceWorld = this.worlds.get(uuid); CEWorld ceWorld = this.worlds.get(uuid);
for (Chunk chunk : world.getLoadedChunks()) { for (Chunk chunk : world.getLoadedChunks()) {
handleChunkLoad(ceWorld, chunk, true); handleChunkLoad(ceWorld, chunk, true);
CEChunk loadedChunk = ceWorld.getChunkAtIfLoaded(chunk.getChunkKey());
if (loadedChunk != null) {
loadedChunk.setEntitiesLoaded(true);
}
} }
ceWorld.setTicking(true); ceWorld.setTicking(true);
} else { } else {

View File

@@ -135,6 +135,12 @@ set_entity_view_distance_scale:
usage: usage:
- /ce feature entity-view-distance-scale set - /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 commands
debug_set_block: debug_set_block:
enable: true enable: true
@@ -264,6 +270,13 @@ debug_generate_internal_assets:
- /craftengine debug generate-internal-assets - /craftengine debug generate-internal-assets
- /ce 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: debug_test:
enable: true enable: true
permission: ce.command.debug.test permission: ce.command.debug.test

View File

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

View File

@@ -53,6 +53,8 @@ items:
behavior: behavior:
type: block_item type: block_item
block: block:
events:
- template: default:rotatable_block
loot: loot:
template: default:loot_table/self template: default:loot_table/self
settings: settings:

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,31 @@
templates:
default:rotatable_block:
on: right_click
conditions:
- type: expression
expression: <arg:player.is_sneaking>
functions:
- type: update_interaction_tick
- type: play_sound
sound: ${rotate_sound:-'minecraft:block.bamboo.place'}
- type: swing_hand
- type: cycle_block_property
property: facing
rules:
north: east
east: south
south: west
west: north
default:rotatable_furniture:
on: right_click
conditions:
- type: expression
expression: <arg:player.is_sneaking>
functions:
- type: rotate_furniture
degree: 90
on-success:
- type: swing_hand
- type: play_sound
sound: ${rotate_sound:-'minecraft:block.bamboo.place'}
on-failure: []

View File

@@ -180,7 +180,6 @@ templates:
# ore_block: the ore block # ore_block: the ore block
# ore_drop: the drops of the ore material # ore_drop: the drops of the ore material
# ore_drop_count: the amount of the ore materials # ore_drop_count: the amount of the ore materials
# exp: the exp to drop
default:loot_table/ore: default:loot_table/ore:
pools: pools:
- rolls: 1 - rolls: 1
@@ -203,8 +202,6 @@ templates:
formula: formula:
type: ore_drops type: ore_drops
- type: explosion_decay - type: explosion_decay
- type: drop_exp
count: ${exp:-2~4}
# Using Silk Touch or shears will cause the leaves block itself to drop. # Using Silk Touch or shears will cause the leaves block itself to drop.
# Using Fortune, however, increases the drop rates of sticks and saplings. # 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.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.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.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.missing_variants: "<yellow>Problem in Datei <arg:0> gefunden - Beim Furniture '<arg:1>' fehlt das erforderliche 'variants'-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.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.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.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>" 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.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.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>" 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.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.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>" 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.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.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.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.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.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.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>" 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>"
@@ -90,6 +105,32 @@ warning.config.type.vec3d: "<yellow>Issue found in file <arg:0> - Failed to load
warning.config.type.map: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Map type for option '<arg:3>'.</yellow>" warning.config.type.map: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Map type for option '<arg:3>'.</yellow>"
warning.config.type.aabb: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to AABB type for option '<arg:3>'.</yellow>" warning.config.type.aabb: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to AABB type for option '<arg:3>'.</yellow>"
warning.config.type.snbt.invalid_syntax: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Invalid snbt syntax '<arg:2>'.</yellow>" warning.config.type.snbt.invalid_syntax: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Invalid snbt syntax '<arg:2>'.</yellow>"
warning.config.type.snbt.invalid_syntax.parse_error: "<yellow><arg:0> at position <arg:1>: <arg:2></yellow>"
warning.config.type.snbt.invalid_syntax.here: "<yellow><--[HERE]</yellow>"
warning.config.type.snbt.parser.expected_string_uuid: "<yellow>Expected a string representing a valid UUID</yellow>"
warning.config.type.snbt.parser.expected_number_or_boolean: "<yellow>Expected a number or a boolean</yellow>"
warning.config.type.snbt.parser.trailing: "<yellow>Unexpected trailing data</yellow>"
warning.config.type.snbt.parser.expected.compound: "<yellow>Expected compound tag</yellow>"
warning.config.type.snbt.parser.number_parse_failure: "<yellow>Failed to parse number: <arg:0></yellow>"
warning.config.type.snbt.parser.expected_hex_escape: "<yellow>Expected a character literal of length <arg:0></yellow>"
warning.config.type.snbt.parser.invalid_codepoint: "<yellow>Invalid Unicode character value: <arg:0></yellow>"
warning.config.type.snbt.parser.no_such_operation: "<yellow>No such operation: <arg:0></yellow>"
warning.config.type.snbt.parser.expected_integer_type: "<yellow>Expected an integer number</yellow>"
warning.config.type.snbt.parser.expected_float_type: "<yellow>Expected a floating point number</yellow>"
warning.config.type.snbt.parser.expected_non_negative_number: "<yellow>Expected a non-negative number</yellow>"
warning.config.type.snbt.parser.invalid_character_name: "<yellow>Invalid Unicode character name</yellow>"
warning.config.type.snbt.parser.invalid_array_element_type: "<yellow>Invalid array element type</yellow>"
warning.config.type.snbt.parser.invalid_unquoted_start: "<yellow>Unquoted strings can't start with digits 0-9, + or -</yellow>"
warning.config.type.snbt.parser.expected_unquoted_string: "<yellow>Expected a valid unquoted string</yellow>"
warning.config.type.snbt.parser.invalid_string_contents: "<yellow>Invalid string contents</yellow>"
warning.config.type.snbt.parser.expected_binary_numeral: "<yellow>Expected a binary number</yellow>"
warning.config.type.snbt.parser.underscore_not_allowed: "<yellow>Underscore is not allowed in binary numerals</yellow>"
warning.config.type.snbt.parser.expected_decimal_numeral: "<yellow>Expected a decimal number</yellow>"
warning.config.type.snbt.parser.expected_hex_numeral: "<yellow>Expected a hexadecimal number</yellow>"
warning.config.type.snbt.parser.empty_key: "<yellow>Key cannot be empty</yellow>"
warning.config.type.snbt.parser.leading_zero_not_allowed: "<yellow>Decimal numbers can't start with 0</yellow>"
warning.config.type.snbt.parser.infinity_not_allowed: "<yellow>Non-finite numbers are not allowed</yellow>"
warning.config.type.snbt.parser.incorrect: "<yellow>Expected literal <arg:0></yellow>"
warning.config.number.missing_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'type' argument for number argument.</yellow>" warning.config.number.missing_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'type' argument for number argument.</yellow>"
warning.config.number.invalid_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid number argument type '<arg:2>'.</yellow>" warning.config.number.invalid_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid number argument type '<arg:2>'.</yellow>"
warning.config.number.missing_argument: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the argument for 'number'.</yellow>" warning.config.number.missing_argument: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the argument for 'number'.</yellow>"
@@ -161,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.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_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_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_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_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>" 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>"
@@ -189,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.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.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.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.missing_variants: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'variants' 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.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.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.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>" 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.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.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.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.missing_variants: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' carece del argumento requerido 'variants'.</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.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.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.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>" 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.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.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.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.missing_variants: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' manque largument obligatoire 'variants'.</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.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.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.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>" 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.duplicate: "<yellow>Проблема найдена в файле <arg:0> - Дублированная песня музыкального автомата '<arg:1>'. Проверьте, есть ли такая же конфигурация в других файлах.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Проблема найдена в файле <arg:0> - В песне музыкального автомата '<arg:1>' отсутствует необходимый 'sound' аргумент.</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.duplicate: "<yellow>Проблема найдена в файле <arg:0> - Дублированная мебель '<arg:1>'. Проверьте, есть ли такая же конфигурация в других файлах.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'placement' аргумент.</yellow>" warning.config.furniture.missing_variants: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'variants' аргумент.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'item' аргумент для одного из его элементов.</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.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.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>" 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.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.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.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.missing_variants: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası gerekli 'variants' 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.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.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.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>" 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.aggregate.failure: "<red>无效的组件 '<arg:0>': <arg:1></red>"
argument.parse.failure.either: "<red>无法从 '<arg:0>' 解析 <arg:1> 或 <arg:2></red>" argument.parse.failure.either: "<red>无法从 '<arg:0>' 解析 <arg:1> 或 <arg:2></red>"
argument.parse.failure.namedtextcolor: "<red>'<arg:0>' 不是颜色代码</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.success: "<white>重新加载配置完成. 耗时 <green><arg:0></green> 毫秒</white> <gray>(异步: <arg:1>ms | 同步: <arg:2>ms)</gray>"
command.reload.config.failure: "<red>重新加载配置失败, 请检查控制台日志</red>" command.reload.config.failure: "<red>重新加载配置失败, 请检查控制台日志</red>"
command.reload.pack.success: "<white>资源包重新加载完成. 耗时 <green><arg:0></green> 毫秒</white>" command.reload.pack.success: "<white>资源包重新加载完成. 耗时 <green><arg:0></green> 毫秒</white>"
@@ -89,6 +103,32 @@ warning.config.type.vector3f: "<yellow>在文件 <arg:0> 发现问题 - 无法
warning.config.type.vec3d: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为双精度浮点数三维向量类型 (选项 '<arg:3>')</yellow>" warning.config.type.vec3d: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为双精度浮点数三维向量类型 (选项 '<arg:3>')</yellow>"
warning.config.type.map: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为映射类型 (选项 '<arg:3>')</yellow>" warning.config.type.map: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为映射类型 (选项 '<arg:3>')</yellow>"
warning.config.type.snbt.invalid_syntax: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无效的 SNBT 语法 '<arg:2>'</yellow>" warning.config.type.snbt.invalid_syntax: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无效的 SNBT 语法 '<arg:2>'</yellow>"
warning.config.type.snbt.invalid_syntax.parse_error: "<yellow><arg:0>,位于第<arg:1>个字符:<arg:2></yellow>"
warning.config.type.snbt.invalid_syntax.here: "<yellow><--[此处]</yellow>"
warning.config.type.snbt.parser.expected_string_uuid: "<yellow>应为表示有效UUID的字符串</yellow>"
warning.config.type.snbt.parser.expected_number_or_boolean: "<yellow>应为数字或布尔型</yellow>"
warning.config.type.snbt.parser.trailing: "<yellow>多余的尾随数据</yellow>"
warning.config.type.snbt.parser.expected.compound: "<yellow>应为复合标签</yellow>"
warning.config.type.snbt.parser.number_parse_failure: "<yellow>解析数字失败:<arg:0></yellow>"
warning.config.type.snbt.parser.expected_hex_escape: "<yellow>字符字面量长度应为<arg:0></yellow>"
warning.config.type.snbt.parser.invalid_codepoint: "<yellow>无效的Unicode字符码位<arg:0></yellow>"
warning.config.type.snbt.parser.no_such_operation: "<yellow>不存在的操作: <arg:0></yellow>"
warning.config.type.snbt.parser.expected_integer_type: "<yellow>应为整数</yellow>"
warning.config.type.snbt.parser.expected_float_type: "<yellow>应为浮点数</yellow>"
warning.config.type.snbt.parser.expected_non_negative_number: "<yellow>应为非负数</yellow>"
warning.config.type.snbt.parser.invalid_character_name: "<yellow>无效的Unicode字符名称</yellow>"
warning.config.type.snbt.parser.invalid_array_element_type: "<yellow>无效的数组元素类型</yellow>"
warning.config.type.snbt.parser.invalid_unquoted_start: "<yellow>无引号字符串不能以数字0-9、+或-开头</yellow>"
warning.config.type.snbt.parser.expected_unquoted_string: "<yellow>应为有效的无引号字符串</yellow>"
warning.config.type.snbt.parser.invalid_string_contents: "<yellow>无效的字符串内容</yellow>"
warning.config.type.snbt.parser.expected_binary_numeral: "<yellow>应为二进制数</yellow>"
warning.config.type.snbt.parser.underscore_not_allowed: "<yellow>数字的开头和结尾不允许使用下划线字符</yellow>"
warning.config.type.snbt.parser.expected_decimal_numeral: "<yellow>应为十进制数</yellow>"
warning.config.type.snbt.parser.expected_hex_numeral: "<yellow>应为十六进制数</yellow>"
warning.config.type.snbt.parser.empty_key: "<yellow>键不能为空</yellow>"
warning.config.type.snbt.parser.leading_zero_not_allowed: "<yellow>十进制数不能以0开头</yellow>"
warning.config.type.snbt.parser.infinity_not_allowed: "<yellow>不允许使用非有限数的数值</yellow>"
warning.config.type.snbt.parser.incorrect: "<yellow>应为字面量<arg:0></yellow>"
warning.config.number.missing_type: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 缺少数字类型所需的 'type' 参数</yellow>" warning.config.number.missing_type: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 缺少数字类型所需的 'type' 参数</yellow>"
warning.config.number.invalid_type: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 使用了无效的数字类型 '<arg:2>'</yellow>" warning.config.number.invalid_type: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 使用了无效的数字类型 '<arg:2>'</yellow>"
warning.config.number.missing_argument: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 缺少数字参数</yellow>" warning.config.number.missing_argument: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 缺少数字参数</yellow>"
@@ -160,6 +200,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.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_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_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_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_base: "<yellow>在文件 <arg:0> 发现问题 - 锻造纹饰配方 '<arg:1>' 缺少必需的 'base' 参数</yellow>"
warning.config.recipe.smithing_trim.missing_template_type: "<yellow>在文件 <arg:0> 发现问题 - 锻造纹饰配方 '<arg:1>' 缺少必需的 'template-type' 参数</yellow>" warning.config.recipe.smithing_trim.missing_template_type: "<yellow>在文件 <arg:0> 发现问题 - 锻造纹饰配方 '<arg:1>' 缺少必需的 'template-type' 参数</yellow>"
@@ -188,8 +229,8 @@ warning.config.sound.missing_name: "<yellow>在文件 <arg:0> 发现问题 - 音
warning.config.jukebox_song.duplicate: "<yellow>在文件 <arg:0> 发现问题 - 重复的唱片机歌曲 '<arg:1>' 请检查其他文件中是否存在相同配置</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.jukebox_song.missing_sound: "<yellow>在文件 <arg:0> 发现问题 - 唱片机歌曲 '<arg:1>' 缺少必需的 'sound' 参数</yellow>"
warning.config.furniture.duplicate: "<yellow>在文件 <arg:0> 发现问题 - 重复的家具 '<arg:1>' 请检查其他文件中是否存在相同配置</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.missing_variants: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 缺少必需的 'variants' 参数</yellow>"
warning.config.furniture.element.missing_item: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 的某个元素缺少必需的 'item' 参数</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.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.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>" 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 class BlockStateMappingParser extends SectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[]{"block-state-mappings", "block-state-mapping"}; public static final String[] CONFIG_SECTION_NAME = new String[]{"block-state-mappings", "block-state-mapping"};
private int count;
@Override @Override
public String[] sectionId() { public String[] sectionId() {
@@ -272,6 +273,16 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
return LoadingSequence.BLOCK_STATE_MAPPING; return LoadingSequence.BLOCK_STATE_MAPPING;
} }
@Override
public int count() {
return this.count;
}
@Override
public void preProcess() {
this.count = 0;
}
@Override @Override
public void parseSection(Pack pack, Path path, Map<String, Object> section) throws LocalizedException { public void parseSection(Pack pack, Path path, Map<String, Object> section) throws LocalizedException {
ExceptionCollector<LocalizedResourceConfigException> exceptionCollector = new ExceptionCollector<>(); 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<>()); List<BlockStateWrapper> blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(blockOwnerId, k -> new ArrayList<>());
blockStateWrappers.add(beforeState); blockStateWrappers.add(beforeState);
AbstractBlockManager.this.autoVisualBlockStateCandidates[beforeState.registryId()] = createVisualBlockCandidate(beforeState); AbstractBlockManager.this.autoVisualBlockStateCandidates[beforeState.registryId()] = createVisualBlockCandidate(beforeState);
this.count++;
} }
exceptionCollector.throwIfPresent(); 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); 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) { public void addPendingConfigSection(PendingConfigSection section) {
this.pendingConfigSections.add(section); this.pendingConfigSections.add(section);
} }
@@ -711,12 +728,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
} }
private CullingData parseCullingData(Object arguments) { private CullingData parseCullingData(Object arguments) {
if (arguments instanceof Boolean b && !b) { if (arguments instanceof Boolean b && !b) return null;
return null; if (!(arguments instanceof Map)) return new CullingData(DEFAULT_BLOCK_ENTITY_AABB, Config.entityCullingViewDistance(), 0.5, true);
}
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"); Map<String, Object> argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling");
return new CullingData( return new CullingData(
ResourceConfigUtils.getAsAABB(argumentsMap.getOrDefault("aabb", 1), "aabb"), ResourceConfigUtils.getAsAABB(argumentsMap.getOrDefault("aabb", 1), "aabb"),

View File

@@ -41,6 +41,7 @@ public class BlockSettings {
float friction = 0.6f; float friction = 0.6f;
float speedFactor = 1f; float speedFactor = 1f;
float jumpFactor = 1f; float jumpFactor = 1f;
Map<CustomDataType<?>, Object> customData = new IdentityHashMap<>(4);
private BlockSettings() {} private BlockSettings() {}
@@ -107,9 +108,29 @@ public class BlockSettings {
newSettings.speedFactor = settings.speedFactor; newSettings.speedFactor = settings.speedFactor;
newSettings.jumpFactor = settings.jumpFactor; newSettings.jumpFactor = settings.jumpFactor;
newSettings.friction = settings.friction; newSettings.friction = settings.friction;
newSettings.customData = new IdentityHashMap<>(settings.customData);
return newSettings; 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() { public Set<Key> tags() {
return 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); FACTORIES.put(id, factory);
} }
} }

View File

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

View File

@@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.util.Key;
public final class BlockEntityTypeKeys { public final class BlockEntityTypeKeys {
private 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 UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite");
public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage"); public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage");
public static final Key SIMPLE_PARTICLE = Key.of("craftengine:simple_particle"); 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; import net.momirealms.craftengine.core.util.ResourceKey;
public abstract class BlockEntityTypes { public abstract class BlockEntityTypes {
public static final BlockEntityType<InactiveBlockEntity> INACTIVE = register(BlockEntityTypeKeys.INACTIVE);
public static <T extends BlockEntity> BlockEntityType<T> register(Key id) { public static <T extends BlockEntity> BlockEntityType<T> register(Key id) {
BlockEntityType<T> type = new BlockEntityType<>(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; import java.util.Map;
@FunctionalInterface @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 TEXT_DISPLAY = Key.of("craftengine:text_display");
public static final Key ITEM = Key.of("craftengine:item"); public static final Key ITEM = Key.of("craftengine:item");
public static void register(Key key, BlockEntityElementConfigFactory type) { public static void register(Key key, BlockEntityElementConfigFactory<?> type) {
((WritableRegistry<BlockEntityElementConfigFactory>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE) ((WritableRegistry<BlockEntityElementConfigFactory<?>>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE)
.register(ResourceKey.create(Registries.BLOCK_ENTITY_ELEMENT_TYPE.location(), key), type); .register(ResourceKey.create(Registries.BLOCK_ENTITY_ELEMENT_TYPE.location(), key), type);
} }
public static <E extends BlockEntityElement> BlockEntityElementConfig<E> fromMap(Map<String, Object> arguments) { 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); 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) { if (factory == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.entity_renderer.invalid_type", type.toString()); 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 { public interface Entity {
Key type(); Key type();
boolean isValid();
double x(); double x();
double y(); double y();

View File

@@ -12,14 +12,14 @@ public interface EntityData<T> {
Object entityDataAccessor(); Object entityDataAccessor();
Object create(Object entityDataAccessor, Object value); Object create(Object entityDataAccessor, T value);
default Object createEntityDataIfNotDefaultValue(T value) { default Object createEntityDataIfNotDefaultValue(T value) {
if (defaultValue().equals(value)) return null; if (defaultValue().equals(value)) return null;
return create(entityDataAccessor(), value); return create(entityDataAccessor(), value);
} }
default Object createEntityData(Object value) { default Object createEntityData(T value) {
return create(entityDataAccessor(), 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 { public enum Billboard {
FIXED(0), FIXED(0),

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.core.entity; package net.momirealms.craftengine.core.entity.display;
public enum ItemDisplayContext { public enum ItemDisplayContext {
NONE(0), 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; package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.Billboard; import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
import net.momirealms.craftengine.core.entity.ItemDisplayContext; 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.loot.LootTable;
import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.pack.PendingConfigSection;
import net.momirealms.craftengine.core.plugin.CraftEngine; 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.config.IdSectionConfigParser;
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; 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.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Key;
@@ -21,7 +25,7 @@ import java.nio.file.Path;
import java.util.*; import java.util.*;
public abstract class AbstractFurnitureManager implements FurnitureManager { 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 CraftEngine plugin;
private final FurnitureParser furnitureParser; private final FurnitureParser furnitureParser;
// Cached command suggestions // Cached command suggestions
@@ -56,12 +60,12 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
} }
@Override @Override
public Optional<CustomFurniture> furnitureById(Key id) { public Optional<FurnitureConfig> furnitureById(Key id) {
return Optional.ofNullable(this.byId.get(id)); return Optional.ofNullable(this.byId.get(id));
} }
@Override @Override
public Map<Key, CustomFurniture> loadedFurniture() { public Map<Key, FurnitureConfig> loadedFurniture() {
return Collections.unmodifiableMap(this.byId); return Collections.unmodifiableMap(this.byId);
} }
@@ -70,11 +74,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
this.byId.clear(); this.byId.clear();
} }
protected abstract HitBoxConfig defaultHitBox(); protected abstract FurnitureHitBoxConfig<?> defaultHitBox();
protected abstract FurnitureElement.Builder furnitureElementBuilder();
protected abstract CustomFurniture.Builder furnitureBuilder();
public class FurnitureParser extends IdSectionConfigParser { public class FurnitureParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" }; public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" };
@@ -107,91 +107,76 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
return LoadingSequence.FURNITURE; return LoadingSequence.FURNITURE;
} }
@SuppressWarnings("unchecked") @Override
public int count() {
return AbstractFurnitureManager.this.byId.size();
}
@Override @Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) { public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (AbstractFurnitureManager.this.byId.containsKey(id)) { if (AbstractFurnitureManager.this.byId.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.furniture.duplicate"); 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);
}
// external model providers 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");
}
Map<String, FurnitureVariant> variants = new LinkedHashMap<>();
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; Optional<ExternalModel> externalModel;
if (placementArguments.containsKey("model-engine")) { if (variantArguments.containsKey("model-engine")) {
externalModel = Optional.of(plugin.compatibilityManager().createModel("ModelEngine", placementArguments.get("model-engine").toString())); externalModel = Optional.of(plugin.compatibilityManager().createModel("ModelEngine", variantArguments.get("model-engine").toString()));
} else if (placementArguments.containsKey("better-model")) { } else if (variantArguments.containsKey("better-model")) {
externalModel = Optional.of(plugin.compatibilityManager().createModel("BetterModel", placementArguments.get("better-model").toString())); externalModel = Optional.of(plugin.compatibilityManager().createModel("BetterModel", variantArguments.get("better-model").toString()));
} else { } else {
externalModel = Optional.empty(); externalModel = Optional.empty();
} }
// add hitboxes List<FurnitureHitBoxConfig<?>> hitboxes = ResourceConfigUtils.parseConfigAsList(variantArguments.get("hitboxes"), FurnitureHitBoxTypes::fromMap);
List<HitBoxConfig> hitboxes = ResourceConfigUtils.parseConfigAsList(placementArguments.get("hitboxes"), HitBoxTypes::fromMap);
if (hitboxes.isEmpty() && externalModel.isEmpty()) { if (hitboxes.isEmpty() && externalModel.isEmpty()) {
hitboxes = List.of(defaultHitBox()); hitboxes = List.of(defaultHitBox());
} }
// rules variants.put(variantName, new FurnitureVariant(
Map<String, Object> ruleSection = MiscUtils.castToMap(placementArguments.get("rules"), true); variantName,
if (ruleSection != null) { parseCullingData(section.get("entity-culling")),
placements.put(anchorType, new CustomFurniture.Placement( elements.toArray(new FurnitureElementConfig[0]),
anchorType, hitboxes.toArray(new FurnitureHitBoxConfig[0]),
elements.toArray(new FurnitureElement[0]), externalModel,
hitboxes.toArray(new HitBoxConfig[0]), optionalLootSpawnOffset
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),
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) .id(id)
.settings(FurnitureSettings.fromMap(MiscUtils.castToMap(section.get("settings"), true))) .settings(FurnitureSettings.fromMap(MiscUtils.castToMap(section.get("settings"), true)))
.placement(placements) .variants(variants)
.events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"))) .events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")))
.lootTable(LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), true))) .lootTable(LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), true)))
.build(); .build();
AbstractFurnitureManager.this.byId.put(id, furniture); 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; package net.momirealms.craftengine.core.entity.furniture;
public enum AnchorType { public enum AnchorType {
GROUND(0), GROUND(0, "ground"),
WALL(1), WALL(1, "wall"),
CEILING(2); CEILING(2, "ceiling");
private final int id; private final int id;
private final String variantName;
AnchorType(int id) { AnchorType(int id, String variantName) {
this.id = id; this.id = id;
this.variantName = variantName;
} }
public int getId() { public int getId() {
return id; return id;
} }
public String variantName() {
return variantName;
}
public static AnchorType byId(int id) { public static AnchorType byId(int id) {
return values()[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) {
}
}

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