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

Merge pull request #494 from Xiao-MoMi/dev

0.0.66
This commit is contained in:
XiaoMoMi
2025-12-07 00:50:29 +08:00
committed by GitHub
351 changed files with 10305 additions and 4129 deletions

View File

@@ -75,7 +75,7 @@ repositories {
```
```kotlin
dependencies {
compileOnly("net.momirealms:craft-engine-core:0.0.65")
compileOnly("net.momirealms:craft-engine-bukkit:0.0.65")
compileOnly("net.momirealms:craft-engine-core:0.0.66")
compileOnly("net.momirealms:craft-engine-bukkit:0.0.66")
}
```

View File

@@ -18,6 +18,7 @@ repositories {
maven("https://jitpack.io") // sxitem slimefun
maven("https://repo.codemc.io/repository/maven-public/") // quickshop
maven("https://repo.nexomc.com/releases/") // nexo
maven("https://repo.opencollab.dev/main/") // geyser
}
dependencies {
@@ -41,8 +42,8 @@ dependencies {
compileOnly("io.github.toxicity188:bettermodel:1.14.0")
compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}")
// MMOItems
compileOnly("net.Indyuce:MMOItems-API:6.10-SNAPSHOT")
compileOnly("io.lumine:MythicLib-dist:1.6.2-SNAPSHOT")
compileOnly("net.Indyuce:MMOItems-API:6.10.1-SNAPSHOT")
compileOnly("io.lumine:MythicLib-dist:1.7.1-SNAPSHOT")
// Nexo
compileOnly("com.nexomc:nexo:1.13.0")
// LuckPerms
@@ -62,7 +63,7 @@ dependencies {
// McMMO
compileOnly("com.gmail.nossr50.mcMMO:mcMMO:2.2.038")
// MMOCore
compileOnly("net.Indyuce:MMOCore-API:1.12.1-SNAPSHOT")
compileOnly("net.Indyuce:MMOCore-API:1.13.1-SNAPSHOT")
// JobsReborn
compileOnly("com.github.Zrips:Jobs:v5.2.2.3")
// CustomFishing
@@ -88,6 +89,10 @@ dependencies {
compileOnly("io.github.Slimefun:Slimefun4:RC-32")
// QuickShop
compileOnly("com.ghostchu:quickshop-api:6.2.0.10")
// Geyser
compileOnly("org.geysermc.geyser:api:2.9.0-SNAPSHOT")
// Floodgate
compileOnly("org.geysermc.floodgate:api:2.2.4-SNAPSHOT")
}
java {

View File

@@ -3,6 +3,8 @@ package net.momirealms.craftengine.bukkit.compatibility;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.entity.renderer.element.BukkitBlockEntityElementConfigs;
import net.momirealms.craftengine.bukkit.compatibility.bedrock.FloodgateUtils;
import net.momirealms.craftengine.bukkit.compatibility.bedrock.GeyserUtils;
import net.momirealms.craftengine.bukkit.compatibility.item.*;
import net.momirealms.craftengine.bukkit.compatibility.legacy.slimeworld.LegacySlimeFormatStorageAdaptor;
import net.momirealms.craftengine.bukkit.compatibility.leveler.*;
@@ -19,6 +21,7 @@ import net.momirealms.craftengine.bukkit.compatibility.quickshop.QuickShopItemEx
import net.momirealms.craftengine.bukkit.compatibility.region.WorldGuardRegionCondition;
import net.momirealms.craftengine.bukkit.compatibility.skript.SkriptHook;
import net.momirealms.craftengine.bukkit.compatibility.slimeworld.SlimeFormatStorageAdaptor;
import net.momirealms.craftengine.bukkit.compatibility.tag.CustomNameplateHatSettings;
import net.momirealms.craftengine.bukkit.compatibility.tag.CustomNameplateProviders;
import net.momirealms.craftengine.bukkit.compatibility.viaversion.ViaVersionUtils;
import net.momirealms.craftengine.bukkit.compatibility.worldedit.WorldEditBlockRegister;
@@ -37,6 +40,7 @@ import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.condition.AlwaysFalseCondition;
import net.momirealms.craftengine.core.plugin.context.event.EventConditions;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.plugin.text.minimessage.FormattedLine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
@@ -53,6 +57,8 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
private final Map<String, TagResolverProvider> tagResolverProviders;
private TagResolverProvider[] tagResolverProviderArray = null;
private boolean hasPlaceholderAPI;
private boolean hasGeyser;
private boolean hasFloodgate;
public BukkitCompatibilityManager(BukkitCraftEngine plugin) {
this.plugin = plugin;
@@ -96,6 +102,7 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
registerTagResolverProvider(new CustomNameplateProviders.Background());
registerTagResolverProvider(new CustomNameplateProviders.Nameplate());
registerTagResolverProvider(new CustomNameplateProviders.Bubble());
new CustomNameplateHatSettings().register();
logHook("CustomNameplates");
}
Key worldGuardRegion = Key.of("worldguard:region");
@@ -107,6 +114,12 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
EventConditions.register(worldGuardRegion, new AlwaysFalseCondition.FactoryImpl<>());
LootConditions.register(worldGuardRegion, new AlwaysFalseCondition.FactoryImpl<>());
}
if (this.isPluginEnabled("Geyser-Spigot")) {
this.hasGeyser = true;
}
if (this.isPluginEnabled("floodgate")) {
this.hasFloodgate = true;
}
}
@Override
@@ -181,7 +194,7 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
}
private void logHook(String plugin) {
this.plugin.logger().info("[Compatibility] " + plugin + " hooked");
this.plugin.logger().info(TranslationManager.instance().translateLog("info.compatibility", plugin));
}
@Override
@@ -252,8 +265,8 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
if (VersionHelper.isOrAbove1_20_3()) {
this.plugin.logger().severe("");
if (Locale.getDefault() == Locale.SIMPLIFIED_CHINESE) {
this.plugin.logger().severe("[Compatibility] 插件需要更新 FastAsyncWorldEdit 到 2.13.0 或更高版本,以获得更好的兼容性。(当前版本: " + version + ")");
this.plugin.logger().severe("[Compatibility] 请前往 https://ci.athion.net/job/FastAsyncWorldEdit/ 下载最新版本");
this.plugin.logger().severe("[兼容性] 插件需要更新 FastAsyncWorldEdit 到 2.13.0 或更高版本,以获得更好的兼容性。(当前版本: " + version + ")");
this.plugin.logger().severe("[兼容性] 请前往 https://ci.athion.net/job/FastAsyncWorldEdit/ 下载最新版本");
} else {
this.plugin.logger().severe("[Compatibility] Update FastAsyncWorldEdit to v2.13.0+ for better compatibility (Current: " + version + ")");
this.plugin.logger().severe("[Compatibility] Download latest version: https://ci.athion.net/job/FastAsyncWorldEdit/");
@@ -365,4 +378,16 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
}
return resolvers;
}
@Override
public boolean isBedrockPlayer(Player player) {
UUID uuid = player.uuid();
if (this.hasFloodgate) {
return FloodgateUtils.isFloodgatePlayer(uuid);
}
if (this.hasGeyser) {
return GeyserUtils.isGeyserPlayer(uuid);
}
return uuid.version() == 0;
}
}

View File

@@ -0,0 +1,14 @@
package net.momirealms.craftengine.bukkit.compatibility.bedrock;
import org.geysermc.floodgate.api.FloodgateApi;
import java.util.UUID;
public final class FloodgateUtils {
private FloodgateUtils() {}
public static boolean isFloodgatePlayer(UUID uuid) {
return FloodgateApi.getInstance().isFloodgatePlayer(uuid);
}
}

View File

@@ -0,0 +1,14 @@
package net.momirealms.craftengine.bukkit.compatibility.bedrock;
import org.geysermc.api.Geyser;
import java.util.UUID;
public final class GeyserUtils {
private GeyserUtils() {}
public static boolean isGeyserPlayer(UUID uuid) {
return Geyser.api().isBedrockPlayer(uuid);
}
}

View File

@@ -25,7 +25,9 @@ public class MMOItemsSource implements ExternalItemSource<ItemStack> {
split = split[0].split("_", 2);
}
if (split.length == 1) return new ItemStack(Material.AIR);
MMOItem mmoItem = MMOItems.plugin.getMMOItem(Type.get(split[0]), split[1].toUpperCase());
// 这里与使用和mmoitems相同的转换id方法
String mmoItemId = split[1].toUpperCase().replace("-", "_").replace(" ", "_");
MMOItem mmoItem = MMOItems.plugin.getMMOItem(Type.get(split[0]), mmoItemId);
return mmoItem == null ? new ItemStack(Material.AIR) : requireNonNull(mmoItem.newBuilder().build());
}

View File

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

View File

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

View File

@@ -42,7 +42,7 @@ public class MythicItemDrop extends ItemDrop implements IItemDrop {
context = ItemBuildContext.of(player);
}
}
int amountInt = MiscUtils.fastFloor(amount + 0.5F);
int amountInt = MiscUtils.floor(amount + 0.5F);
ItemStack itemStack = this.customItem.buildItemStack(context, amountInt);
return adapt(itemStack).amount(amountInt);
}

View File

@@ -1,10 +1,8 @@
package net.momirealms.craftengine.bukkit.compatibility.papi;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.ItemStackUtils;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.CraftEngine;
@@ -13,8 +11,6 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Predicate;
public class CheckItemExpansion extends PlaceholderExpansion {
private final CraftEngine plugin;
@@ -108,11 +104,6 @@ public class CheckItemExpansion extends PlaceholderExpansion {
}
private int getItemCount(BukkitServerPlayer player, String[] param) {
Key itemId = Key.of(param[0], param[1]);
Predicate<Object> predicate = nmsStack -> this.plugin.itemManager().wrap(ItemStackUtils.asCraftMirror(nmsStack)).id().equals(itemId);
Object inventory = FastNMS.INSTANCE.method$Player$getInventory(player.serverPlayer());
Object inventoryMenu = FastNMS.INSTANCE.field$Player$inventoryMenu(player.serverPlayer());
Object craftSlots = FastNMS.INSTANCE.method$InventoryMenu$getCraftSlots(inventoryMenu);
return FastNMS.INSTANCE.method$Inventory$clearOrCountMatchingItems(inventory, predicate, 0, craftSlots);
return player.clearOrCountMatchingInventoryItems(Key.of(param[0], param[1]), 0);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,84 @@
package net.momirealms.craftengine.bukkit.compatibility.tag;
import io.papermc.paper.event.entity.EntityEquipmentChangedEvent;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemSettings;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.CustomDataType;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.CustomNameplatesAPI;
import net.momirealms.customnameplates.api.feature.tag.TagRenderer;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public final class CustomNameplateHatSettings implements Listener {
public static final CustomDataType<Double> HAT_HEIGHT = new CustomDataType<>();
public void register() {
ItemSettings.Modifiers.registerFactory("hat-height", height -> {
double heightD = ResourceConfigUtils.getAsDouble(height, "hat-height");
return settings -> settings.addCustomData(HAT_HEIGHT, heightD);
});
Bukkit.getPluginManager().registerEvents(this, BukkitCraftEngine.instance().javaPlugin());
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onEquipmentChange(EntityEquipmentChangedEvent event) {
if (!(event.getEntity() instanceof Player player)) return;
Map<EquipmentSlot, EntityEquipmentChangedEvent.EquipmentChange> equipmentChanges = event.getEquipmentChanges();
EntityEquipmentChangedEvent.EquipmentChange equipmentChange = equipmentChanges.get(EquipmentSlot.HEAD);
if (equipmentChange == null) return;
ItemStack newItem = equipmentChange.newItem();
updateHatHeight(player, newItem);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
// 稍微延迟一下,可以等待背包同步插件的处理
if (VersionHelper.isFolia()) {
player.getScheduler().runDelayed(BukkitCraftEngine.instance().javaPlugin(), t1 -> {
if (player.isOnline()) {
updateHatHeight(player, player.getInventory().getItem(EquipmentSlot.HEAD));
}
}, null, 10);
} else {
CraftEngine.instance().scheduler().sync().runLater(() -> {
if (player.isOnline()) {
updateHatHeight(player, player.getInventory().getItem(EquipmentSlot.HEAD));
}
}, 10);
}
}
public void updateHatHeight(Player player, ItemStack newItem) {
CNPlayer cnPlayer = CustomNameplatesAPI.getInstance().getPlayer(player.getUniqueId());
if (cnPlayer == null) return;
TagRenderer tagRender = CustomNameplates.getInstance().getUnlimitedTagManager().getTagRender(cnPlayer);
if (tagRender == null) return;
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(newItem);
Optional<CustomItem<ItemStack>> optionalCustomItem = wrapped.getCustomItem();
if (optionalCustomItem.isEmpty()) {
tagRender.hatOffset(0d);
return;
}
Double customHeight = optionalCustomItem.get().settings().getCustomData(HAT_HEIGHT);
tagRender.hatOffset(Objects.requireNonNullElse(customHeight, 0d));
}
}

View File

@@ -16,6 +16,7 @@ import net.momirealms.craftengine.core.util.ReflectionUtils;
import org.bukkit.Material;
import java.lang.reflect.Field;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Stream;
@@ -63,7 +64,7 @@ public class WorldEditBlockRegister {
}
if (!input.contains(":")) {
String lowerSearch = input.toLowerCase();
String lowerSearch = input.toLowerCase(Locale.ROOT);
return Stream.concat(
namespacesInUse.stream().filter(n -> n.startsWith(lowerSearch)).map(n -> n + ":"),
BlockStateParser.fillSuggestions(input).stream()

View File

@@ -77,6 +77,10 @@ paper {
register("ViaVersion") { required = false }
register("QuickShop-Hikari") { required = false }
// Geyser
register("Geyser-Spigot") { required = false }
register("floodgate") { required = false }
// AdvancedSlimePaper
register("SlimeWorldPlugin") { required = false }
register("SlimeWorldManager") { required = false }

View File

@@ -17,6 +17,7 @@ import net.momirealms.craftengine.core.plugin.config.ConfigParser;
import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import java.nio.file.Path;
@@ -98,8 +99,8 @@ public final class BukkitAdvancementManager extends AbstractAdvancementManager {
NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, Arrays.asList(advancement), new HashSet<>(), advancementsToGrant, true) :
NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, Arrays.asList(advancement), new HashSet<>(), advancementsToGrant);
Object removePacket = VersionHelper.isOrAbove1_21_5() ?
NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, new ArrayList<>(), new HashSet<>() {{add(resourceLocation);}}, new HashMap<>(), true) :
NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, new ArrayList<>(), new HashSet<>() {{add(resourceLocation);}}, new HashMap<>());
NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, new ArrayList<>(), MiscUtils.init(new HashSet<>(), s -> s.add(resourceLocation)), new HashMap<>(), true) :
NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, new ArrayList<>(), MiscUtils.init(new HashSet<>(), s -> s.add(resourceLocation)), new HashMap<>());
player.sendPackets(List.of(grantPacket, removePacket), false);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to send toast for player " + player.name(), e);
@@ -119,6 +120,11 @@ public final class BukkitAdvancementManager extends AbstractAdvancementManager {
return LoadingSequence.ADVANCEMENT;
}
@Override
public int count() {
return 0;
}
@Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (advancements.containsKey(id)) {

View File

@@ -1,14 +1,17 @@
package net.momirealms.craftengine.bukkit.api;
import net.momirealms.craftengine.bukkit.entity.BukkitEntity;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.item.Item;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
public final class BukkitAdaptors {
@@ -62,4 +65,15 @@ public final class BukkitAdaptors {
public static BukkitExistingBlock adapt(@NotNull final Block block) {
return new BukkitExistingBlock(block);
}
/**
* Adapts a Bukkit ItemStack to a CraftEngine wrapped item
*
* @param item the Bukkit ItemStack to adapt, must not be null
* @return a non-null Item instance wrapping the provided item
*/
@NotNull
public static Item<ItemStack> adapt(@NotNull final ItemStack item) {
return BukkitItemManager.instance().wrap(item);
}
}

View File

@@ -6,11 +6,12 @@ import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeatManager;
import net.momirealms.craftengine.bukkit.nms.CollisionEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureExtraData;
import net.momirealms.craftengine.core.entity.furniture.FurnitureDataAccessor;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.LootTable;
@@ -20,11 +21,13 @@ import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.RayTraceResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -60,6 +63,45 @@ public final class CraftEngineFurniture {
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
*
@@ -71,7 +113,7 @@ public final class CraftEngineFurniture {
public static BukkitFurniture place(Location location, Key furnitureId) {
CustomFurniture furniture = byId(furnitureId);
if (furniture == null) return null;
return place(location, furnitureId, furniture.getAnyAnchorType());
return place(location, furniture, furniture.anyVariantName(), false);
}
/**
@@ -83,10 +125,24 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@Nullable
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, Key furnitureId, AnchorType anchorType) {
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) {
CustomFurniture furniture = byId(furnitureId);
if (furniture == null) return null;
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), true);
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureDataAccessor.ofVariant(variant), true);
}
/**
@@ -98,8 +154,9 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@NotNull
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType) {
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), true);
return place(location, furniture, anchorType.variantName(), true);
}
/**
@@ -112,10 +169,27 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@Nullable
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, Key furnitureId, AnchorType anchorType, boolean playSound) {
CustomFurniture furniture = byId(furnitureId);
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) {
CustomFurniture furniture = byId(furnitureId);
if (furniture == null) return null;
return place(location, furniture, variant, playSound);
}
/**
@@ -128,8 +202,51 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@NotNull
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType, boolean playSound) {
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 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, CustomFurniture 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, CustomFurniture 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, CustomFurniture furniture, FurnitureDataAccessor dataAccessor, boolean playSound) {
return BukkitFurnitureManager.instance().place(location, furniture, dataAccessor, playSound);
}
/**
@@ -165,18 +282,30 @@ public final class CraftEngineFurniture {
}
/**
* Gets the base furniture by the base entity
* Gets the furniture by the meta entity
*
* @param baseEntity base entity
* @return the loaded furniture
*/
@Nullable
public static BukkitFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) {
return BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(baseEntity.getEntityId());
public static BukkitFurniture getLoadedFurnitureByMetaEntity(@NotNull Entity baseEntity) {
return BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(baseEntity.getEntityId());
}
/**
* Gets the base furniture by the seat entity
* Gets the furniture by the meta entity
*
* @param baseEntity base entity
* @return the loaded furniture
*/
@Nullable
@Deprecated(since = "0.0.66")
public static BukkitFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) {
return getLoadedFurnitureByMetaEntity(baseEntity);
}
/**
* Gets the furniture by the seat entity
*
* @param seat seat entity
* @return the loaded furniture
@@ -186,7 +315,22 @@ public final class CraftEngineFurniture {
if (isSeat(seat)) {
CompoundTag seatExtraData = BukkitSeatManager.instance().getSeatExtraData(seat);
int entityId = seatExtraData.getInt("entity_id");
BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entityId);
BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entityId);
}
return null;
}
/**
* Gets the furniture by the collider entity
*
* @param collider collider entity
* @return the loaded furniture
*/
@Nullable
public static BukkitFurniture getLoadedFurnitureByCollider(@NotNull Entity collider) {
Object nmsEntity = FastNMS.INSTANCE.method$CraftEntity$getHandle(collider);
if (nmsEntity instanceof CollisionEntity collisionEntity) {
return BukkitFurnitureManager.instance().loadedFurnitureByColliderEntityId(collisionEntity.getEntityId());
}
return null;
}
@@ -199,7 +343,7 @@ public final class CraftEngineFurniture {
*/
public static boolean remove(@NotNull Entity entity) {
if (!isFurniture(entity)) return false;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId());
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false;
furniture.destroy();
return true;
@@ -217,7 +361,7 @@ public final class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!isFurniture(entity)) return false;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId());
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false;
remove(furniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound);
return true;
@@ -237,7 +381,7 @@ public final class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!isFurniture(entity)) return false;
Furniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId());
Furniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false;
remove(furniture, player, dropLoot, playSound);
return true;
@@ -286,16 +430,16 @@ public final class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!furniture.isValid()) return;
Location location = ((BukkitFurniture) furniture).dropLocation();
Location location = ((BukkitFurniture) furniture).getDropLocation();
furniture.destroy();
LootTable<ItemStack> lootTable = (LootTable<ItemStack>) furniture.config().lootTable();
LootTable<ItemStack> lootTable = (LootTable<ItemStack>) furniture.config.lootTable();
World world = new BukkitWorld(location.getWorld());
WorldPosition position = new WorldPosition(world, location.getX(), location.getY(), location.getZ());
if (dropLoot && lootTable != null) {
ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(DirectContextParameters.POSITION, position)
.withParameter(DirectContextParameters.FURNITURE, furniture)
.withOptionalParameter(DirectContextParameters.FURNITURE_ITEM, furniture.extraData().item().orElse(null));
.withOptionalParameter(DirectContextParameters.FURNITURE_ITEM, furniture.dataAccessor.item().orElse(null));
if (player != null) {
Item<?> itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND);
builder.withParameter(DirectContextParameters.PLAYER, player)
@@ -307,7 +451,7 @@ public final class CraftEngineFurniture {
}
}
if (playSound) {
world.playBlockSound(position, furniture.config().settings().sounds().breakSound());
world.playBlockSound(position, furniture.config.settings().sounds().breakSound());
}
}
}

View File

@@ -1,11 +1,10 @@
package net.momirealms.craftengine.bukkit.api.event;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureVariant;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
@@ -17,23 +16,20 @@ public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Can
private boolean cancelled;
private final CustomFurniture furniture;
private final Location location;
private final AnchorType anchorType;
private final BlockFace clickedFace;
private final FurnitureVariant variant;
private final Block clickedBlock;
private final InteractionHand hand;
public FurnitureAttemptPlaceEvent(@NotNull Player player,
@NotNull CustomFurniture furniture,
@NotNull AnchorType anchorType,
@NotNull FurnitureVariant variant,
@NotNull Location location,
@NotNull BlockFace clickedFace,
@NotNull InteractionHand hand,
@NotNull Block clickedBlock) {
super(player);
this.furniture = furniture;
this.location = location;
this.anchorType = anchorType;
this.clickedFace = clickedFace;
this.variant = variant;
this.clickedBlock = clickedBlock;
this.hand = hand;
}
@@ -48,19 +44,14 @@ public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Can
return hand;
}
@NotNull
public BlockFace clickedFace() {
return clickedFace;
}
@NotNull
public Player player() {
return getPlayer();
}
@NotNull
public AnchorType anchorType() {
return anchorType;
public FurnitureVariant variant() {
return variant;
}
@NotNull

View File

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

View File

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

View File

@@ -42,6 +42,7 @@ import java.util.*;
public final class BukkitBlockManager extends AbstractBlockManager {
public static final Set<Object> CLIENT_SIDE_NOTE_BLOCKS = new HashSet<>(2048, 0.6f);
private static final Object BLOCK_POS$ZERO = LocationUtils.toBlockPos(0,0,0);
private static final Object ALWAYS_FALSE = FastNMS.INSTANCE.method$StatePredicate$always(false);
private static final Object ALWAYS_TRUE = FastNMS.INSTANCE.method$StatePredicate$always(true);
private static BukkitBlockManager instance;
@@ -80,12 +81,14 @@ public final class BukkitBlockManager extends AbstractBlockManager {
@Override
public void init() {
super.init();
this.initMirrorRegistry();
this.initFireBlock();
this.deceiveBukkitRegistry();
this.markVanillaNoteBlocks();
this.findViewBlockingVanillaBlocks();
Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState());
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 一定要预先初始化一次预防id超出上限
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 一定要预先初始化一次预防id超出上限
}
public static BukkitBlockManager instance() {
@@ -125,7 +128,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
@Override
public void delayedLoad() {
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 重置方块映射表
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 重置方块映射表
super.delayedLoad();
this.cachedVisualBlockStatePacket = VisualBlockStatePacket.create();
for (BukkitServerPlayer player : BukkitNetworkManager.instance().onlineUsers()) {
@@ -223,7 +226,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
protected void applyPlatformSettings(ImmutableBlockState state) {
DelegatingBlockState nmsState = (DelegatingBlockState) state.customBlockState().literalObject();
nmsState.setBlockState(state);
Object nmsVisualState = state.vanillaBlockState().literalObject();
Object nmsVisualState = state.visualBlockState().literalObject();
BlockSettings settings = state.settings();
try {
@@ -240,8 +243,15 @@ public final class BukkitBlockManager extends AbstractBlockManager {
boolean useShapeForLightOcclusion = settings.useShapeForLightOcclusion() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.getBoolean(nmsVisualState) : settings.useShapeForLightOcclusion().asBoolean();
CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.set(nmsState, useShapeForLightOcclusion);
CoreReflections.field$BlockStateBase$isRedstoneConductor.set(nmsState, settings.isRedstoneConductor().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE);
boolean suffocating = settings.isSuffocating() == Tristate.UNDEFINED ? (canBlockView(state.visualBlockState())) : (settings.isSuffocating().asBoolean());
CoreReflections.field$BlockStateBase$isSuffocating.set(nmsState, suffocating ? ALWAYS_TRUE : ALWAYS_FALSE);
CoreReflections.field$BlockStateBase$isSuffocating.set(nmsState, settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE);
CoreReflections.field$BlockStateBase$isViewBlocking.set(nmsState, settings.isViewBlocking() == Tristate.UNDEFINED ? settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE : (settings.isViewBlocking().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE));
CoreReflections.field$BlockStateBase$isViewBlocking.set(nmsState,
settings.isViewBlocking() == Tristate.UNDEFINED ?
(suffocating ? ALWAYS_TRUE : ALWAYS_FALSE) :
(settings.isViewBlocking().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE)
);
DelegatingBlock nmsBlock = (DelegatingBlock) BlockStateUtils.getBlockOwner(nmsState);
ObjectHolder<BlockShape> shapeHolder = nmsBlock.shapeDelegate();
@@ -291,9 +301,16 @@ public final class BukkitBlockManager extends AbstractBlockManager {
this.burnableBlocks.add(nmsBlock);
}
Key vanillaBlockId = state.vanillaBlockState().ownerId();
Key vanillaBlockId = state.visualBlockState().ownerId();
BlockGenerator.field$CraftEngineBlock$isNoteBlock().set(nmsBlock, vanillaBlockId.equals(BlockKeys.NOTE_BLOCK));
BlockGenerator.field$CraftEngineBlock$isTripwire().set(nmsBlock, vanillaBlockId.equals(BlockKeys.TRIPWIRE));
if (vanillaBlockId.equals(BlockKeys.BARRIER)) {
state.setRestoreBlockState(createBlockState("minecraft:glass"));
} else {
state.setRestoreBlockState(state.visualBlockState());
}
// 根据客户端的状态决定其是否阻挡视线
super.viewBlockingBlocks[state.customBlockState().registryId()] = canBlockView(state.visualBlockState());
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to apply platform block settings for block state " + state, e);
}
@@ -379,6 +396,23 @@ public final class BukkitBlockManager extends AbstractBlockManager {
}
}
public boolean canBlockView(BlockStateWrapper wrapper) {
Object blockState = wrapper.literalObject();
if (!BlockStateUtils.isOcclude(blockState)) {
return false;
}
return FastNMS.INSTANCE.method$BlockStateBase$isCollisionShapeFullBlock(blockState, CoreReflections.instance$EmptyBlockGetter$INSTANCE, BLOCK_POS$ZERO);
}
private void findViewBlockingVanillaBlocks() {
for (int i = 0; i < this.vanillaBlockStateCount; i++) {
BlockStateWrapper blockState = BlockRegistryMirror.byId(i);
if (canBlockView(blockState)) {
this.viewBlockingBlocks[i] = true;
}
}
}
@Override
protected void setVanillaBlockTags(Key id, List<String> tags) {
Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id));

View File

@@ -20,7 +20,7 @@ public class BukkitCustomBlockStateWrapper extends AbstractBlockStateWrapper imp
@Override
public BlockStateWrapper visualBlockState() {
return getImmutableBlockState().map(ImmutableBlockState::vanillaBlockState).orElse(null);
return getImmutableBlockState().map(ImmutableBlockState::visualBlockState).orElse(null);
}
@Override

View File

@@ -13,6 +13,7 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
@@ -22,7 +23,7 @@ import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
public class BukkitBlockBehavior extends AbstractBlockBehavior {
private static final Map<String, BiConsumer<BukkitBlockBehavior, Property<?>>> HARD_CODED_PROPERTY_DATA = new HashMap<>();
private static final Map<String, BiConsumer<@NotNull BukkitBlockBehavior, Property<?>>> HARD_CODED_PROPERTY_DATA = new HashMap<>();
static {
HARD_CODED_PROPERTY_DATA.put("axis", (behavior, property) -> {
@SuppressWarnings("unchecked")
@@ -180,7 +181,7 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior {
public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(args[0]);
if (optionalCustomState.isEmpty()) return false;
BlockStateWrapper vanillaState = optionalCustomState.get().vanillaBlockState();
BlockStateWrapper vanillaState = optionalCustomState.get().visualBlockState();
if (vanillaState == null) return false;
return FastNMS.INSTANCE.method$BlockStateBase$isPathFindable(vanillaState.literalObject(), VersionHelper.isOrAbove1_20_5() ? null : args[1], VersionHelper.isOrAbove1_20_5() ? null : args[2], args[isPathFindable$type]);
}

View File

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

View File

@@ -135,7 +135,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior {
if (isMaxAge(state))
return InteractionResult.PASS;
boolean sendSwing = false;
Object visualState = state.vanillaBlockState().literalObject();
Object visualState = state.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState);
@@ -158,7 +158,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior {
}
ImmutableBlockState customState = optionalCustomState.get();
boolean sendParticles = false;
Object visualState = customState.vanillaBlockState().literalObject();
Object visualState = customState.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, pos, visualState);

View File

@@ -0,0 +1,99 @@
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.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.MiscUtils;
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;
import java.util.function.Predicate;
public class DropExperienceBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final NumberProvider amount;
private final Predicate<Context> condition;
public DropExperienceBlockBehavior(CustomBlock customBlock, NumberProvider amount, Predicate<Context> condition) {
super(customBlock);
this.amount = amount;
this.condition = condition;
}
@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.condition.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"));
List<Condition<Context>> conditionList = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(arguments, "conditions", "condition"), EventConditions::fromMap);
return new DropExperienceBlockBehavior(block, amount, MiscUtils.allOf(conditionList));
}
}
}

View File

@@ -147,7 +147,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior implements IsPat
Player player = context.getPlayer();
if (player == null) return;
this.toggle(state, context.getLevel(), context.getClickedPos(), player);
if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.visualBlockState().literalObject()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
player.swingHand(context.getHand());
}
}

View File

@@ -60,7 +60,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
}
boolean sendParticles = false;
ImmutableBlockState customState = optionalCustomState.get();
Object visualState = customState.vanillaBlockState().literalObject();
Object visualState = customState.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState);
@@ -93,7 +93,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
if (!block.isEmpty())
return InteractionResult.PASS;
boolean sendSwing = false;
Object visualState = state.vanillaBlockState().literalObject();
Object visualState = state.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState);

View File

@@ -115,7 +115,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior {
}
ImmutableBlockState customState = optionalCustomState.get();
boolean sendParticles = false;
Object visualState = customState.vanillaBlockState().literalObject();
Object visualState = customState.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState);
@@ -153,7 +153,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior {
if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || player == null || player.isAdventureMode())
return InteractionResult.PASS;
boolean sendSwing = false;
Object visualState = state.vanillaBlockState().literalObject();
Object visualState = state.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState);

View File

@@ -120,7 +120,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior implements IsPath
Player player = context.getPlayer();
if (player == null) return;
this.toggle(state, context.getLevel(), context.getClickedPos(), player);
if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.visualBlockState().literalObject()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
player.swingHand(context.getHand());
}
}

View File

@@ -1,5 +1,6 @@
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.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
@@ -13,6 +14,8 @@ import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.sparrow.nbt.CompoundTag;
import java.util.Optional;
public class SeatBlockEntity extends BlockEntity implements SeatOwner {
private final Seat<SeatBlockEntity>[] seats;
@@ -38,10 +41,12 @@ public class SeatBlockEntity extends BlockEntity implements SeatOwner {
}
public boolean spawnSeat(Player player) {
Property<?> facing = super.blockState.owner().value().getProperty("facing");
int yRot = 0;
if (facing != null && facing.valueClass() == HorizontalDirection.class) {
HorizontalDirection direction = (HorizontalDirection) super.blockState.get(facing);
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);
yRot = switch (direction) {
case NORTH -> 0;
case SOUTH -> 180;

View File

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

View File

@@ -0,0 +1,98 @@
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.ints.IntList;
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.world.score.BukkitTeamManager;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.BlockPos;
import org.joml.Vector3f;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class ArmorStandBlockEntityElement implements BlockEntityElement {
public final ArmorStandBlockEntityElementConfig config;
public final Object cachedSpawnPacket;
public final Object cachedDespawnPacket;
public final Object cachedUpdatePosPacket;
public final Object cachedScalePacket;
public final Object cachedTeamPacket;
public final int entityId;
public final UUID uuid = UUID.randomUUID();
public ArmorStandBlockEntityElement(ArmorStandBlockEntityElementConfig config, BlockPos pos) {
this(config, pos, CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(), false);
}
public ArmorStandBlockEntityElement(ArmorStandBlockEntityElementConfig config, BlockPos pos, int entityId, boolean posChanged) {
Vector3f position = config.position();
this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId, this.uuid, pos.x() + position.x, pos.y() + position.y, pos.z() + position.z,
config.xRot(), config.yRot(), MEntityTypes.ARMOR_STAND, 0, CoreReflections.instance$Vec3$Zero, config.yRot()
);
this.config = config;
this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(IntList.of(entityId));
this.entityId = entityId;
this.cachedUpdatePosPacket = posChanged ? FastNMS.INSTANCE.constructor$ClientboundEntityPositionSyncPacket(this.entityId, pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, config.yRot(), config.xRot(), false) : null;
if (VersionHelper.isOrAbove1_20_5() && config.scale() != 1) {
Object attributeIns = FastNMS.INSTANCE.constructor$AttributeInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
FastNMS.INSTANCE.method$AttributeInstance$setBaseValue(attributeIns, config.scale());
this.cachedScalePacket = FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(entityId, Collections.singletonList(attributeIns));
} else {
this.cachedScalePacket = null;
}
Object teamPacket = null;
if (config.glowColor != null) {
Object teamByColor = BukkitTeamManager.instance().getTeamByColor(config.glowColor);
if (teamByColor != null) {
teamPacket = FastNMS.INSTANCE.method$ClientboundSetPlayerTeamPacket$createMultiplePlayerPacket(teamByColor, List.of(this.uuid.toString()), true);
}
}
this.cachedTeamPacket = teamPacket;
}
@Override
public void hide(Player player) {
player.sendPacket(this.cachedDespawnPacket, false);
}
@Override
public void show(Player player) {
player.sendPackets(List.of(this.cachedSpawnPacket, FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player))), false);
player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundSetEquipmentPacket(this.entityId, List.of(
Pair.of(CoreReflections.instance$EquipmentSlot$HEAD, this.config.item(player).getLiteralObject())
)), false);
if (this.cachedDespawnPacket != null) {
player.sendPacket(this.cachedDespawnPacket, false);
}
if (this.cachedTeamPacket != null) {
player.sendPacket(this.cachedTeamPacket, false);
}
}
@Override
public void transform(Player player) {
if (this.cachedUpdatePosPacket != null) {
player.sendPackets(List.of(
this.cachedUpdatePosPacket,
FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player)),
FastNMS.INSTANCE.constructor$ClientboundSetEquipmentPacket(this.entityId, List.of(
Pair.of(CoreReflections.instance$EquipmentSlot$HEAD, this.config.item(player).getLiteralObject())
))
), false);
} else {
player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player)), false);
player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundSetEquipmentPacket(this.entityId, List.of(
Pair.of(CoreReflections.instance$EquipmentSlot$HEAD, this.config.item(player).getLiteralObject())
)), false);
}
}
}

View File

@@ -0,0 +1,156 @@
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
import com.google.common.base.Objects;
import net.momirealms.craftengine.bukkit.entity.data.ArmorStandData;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.LegacyChatFormatter;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Glowing;
import net.momirealms.craftengine.core.world.World;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class ArmorStandBlockEntityElementConfig implements BlockEntityElementConfig<ArmorStandBlockEntityElement>, Glowing {
public static final Factory FACTORY = new Factory();
public final Function<Player, List<Object>> lazyMetadataPacket;
public final Key itemId;
public final float scale;
public final Vector3f position;
public final float xRot;
public final float yRot;
public final boolean small;
public final LegacyChatFormatter glowColor;
public ArmorStandBlockEntityElementConfig(Key itemId,
float scale,
Vector3f position,
float xRot,
float yRot,
boolean small,
LegacyChatFormatter glowColor) {
this.itemId = itemId;
this.glowColor = glowColor;
this.scale = scale;
this.position = position;
this.xRot = xRot;
this.yRot = yRot;
this.small = small;
this.lazyMetadataPacket = player -> {
List<Object> dataValues = new ArrayList<>(2);
if (glowColor != null) {
BaseEntityData.SharedFlags.addEntityData((byte) 0x60, dataValues);
} else {
BaseEntityData.SharedFlags.addEntityData((byte) 0x20, dataValues);
}
if (small) {
ArmorStandData.ArmorStandFlags.addEntityData((byte) 0x01, dataValues);
}
return dataValues;
};
}
@Nullable
@Override
public LegacyChatFormatter glowColor() {
return this.glowColor;
}
@Override
public ArmorStandBlockEntityElement create(World world, BlockPos pos) {
return new ArmorStandBlockEntityElement(this, pos);
}
@Override
public ArmorStandBlockEntityElement create(World world, BlockPos pos, ArmorStandBlockEntityElement previous) {
if (previous.config.scale != scale || previous.config.glowColor != glowColor) {
return null;
}
return new ArmorStandBlockEntityElement(this, pos, previous.entityId,
previous.config.yRot != this.yRot ||
previous.config.xRot != this.xRot ||
!previous.config.position.equals(this.position)
);
}
@Override
public ArmorStandBlockEntityElement createExact(World world, BlockPos pos, ArmorStandBlockEntityElement previous) {
if (!previous.config.isSamePosition(this)) {
return null;
}
return new ArmorStandBlockEntityElement(this, pos, previous.entityId, false);
}
@Override
public Class<ArmorStandBlockEntityElement> elementClass() {
return ArmorStandBlockEntityElement.class;
}
public Item<?> item(Player player) {
Item<ItemStack> wrappedItem = BukkitItemManager.instance().createWrappedItem(this.itemId, player);
return wrappedItem == null ? BukkitItemManager.instance().createWrappedItem(ItemKeys.BARRIER, player) : wrappedItem ;
}
public Key itemId() {
return this.itemId;
}
public float scale() {
return this.scale;
}
public Vector3f position() {
return this.position;
}
public float yRot() {
return this.yRot;
}
public float xRot() {
return this.xRot;
}
public boolean small() {
return this.small;
}
public List<Object> metadataValues(Player player) {
return this.lazyMetadataPacket.apply(player);
}
public boolean isSamePosition(ArmorStandBlockEntityElementConfig that) {
return Float.compare(xRot, that.xRot) == 0 &&
Float.compare(yRot, that.yRot) == 0 &&
Objects.equal(position, that.position);
}
public static class Factory implements BlockEntityElementConfigFactory<ArmorStandBlockEntityElement> {
@Override
public ArmorStandBlockEntityElementConfig create(Map<String, Object> arguments) {
return new ArmorStandBlockEntityElementConfig(
Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.armor_stand.missing_item")),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("small", false), "small"),
ResourceConfigUtils.getAsEnum(arguments.get("glow-color"), LegacyChatFormatter.class, null)
);
}
}
}

View File

@@ -8,6 +8,7 @@ public class BukkitBlockEntityElementConfigs extends BlockEntityElementConfigs {
register(ITEM_DISPLAY, ItemDisplayBlockEntityElementConfig.FACTORY);
register(TEXT_DISPLAY, TextDisplayBlockEntityElementConfig.FACTORY);
register(ITEM, ItemBlockEntityElementConfig.FACTORY);
register(ARMOR_STAND, ArmorStandBlockEntityElementConfig.FACTORY);
}
private BukkitBlockEntityElementConfigs() {}

View File

@@ -2,35 +2,41 @@ package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
import net.momirealms.craftengine.bukkit.entity.data.ItemEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import org.bukkit.inventory.ItemStack;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
public class ItemBlockEntityElementConfig implements BlockEntityElementConfig<ItemBlockEntityElement> {
public static final Factory FACTORY = new Factory();
private final Function<Player, List<Object>> lazyMetadataPacket;
private final Function<Player, Item<?>> item;
private final Vector3f position;
public final Function<Player, List<Object>> lazyMetadataPacket;
public final Key itemId;
public final Vector3f position;
public ItemBlockEntityElementConfig(Function<Player, Item<?>> item, Vector3f position) {
this.item = item;
public ItemBlockEntityElementConfig(Key itemId, Vector3f position) {
this.itemId = itemId;
this.position = position;
this.lazyMetadataPacket = player -> {
List<Object> dataValues = new ArrayList<>();
ItemEntityData.Item.addEntityDataIfNotDefaultValue(item.apply(player).getLiteralObject(), dataValues);
ItemEntityData.NoGravity.addEntityDataIfNotDefaultValue(true, dataValues);
Item<ItemStack> wrappedItem = BukkitItemManager.instance().createWrappedItem(itemId, player);
if (wrappedItem == null) {
wrappedItem = Objects.requireNonNull(BukkitItemManager.instance().createWrappedItem(ItemKeys.BARRIER, player));
}
ItemEntityData.Item.addEntityData(wrappedItem.getLiteralObject(), dataValues);
ItemEntityData.NoGravity.addEntityData(true, dataValues);
return dataValues;
};
}
@@ -45,6 +51,14 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig<It
return new ItemBlockEntityElement(this, pos, previous.entityId1, previous.entityId2, !previous.config.position.equals(this.position));
}
@Override
public ItemBlockEntityElement createExact(World world, BlockPos pos, ItemBlockEntityElement previous) {
if (!previous.config.isSamePosition(this)) {
return null;
}
return new ItemBlockEntityElement(this, pos, previous.entityId1, previous.entityId2, false);
}
@Override
public Class<ItemBlockEntityElement> elementClass() {
return ItemBlockEntityElement.class;
@@ -54,22 +68,24 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig<It
return position;
}
public Item<?> item(Player player) {
return this.item.apply(player);
public Key itemId() {
return itemId;
}
public List<Object> metadataValues(Player player) {
return this.lazyMetadataPacket.apply(player);
}
public static class Factory implements BlockEntityElementConfigFactory {
public boolean isSamePosition(ItemBlockEntityElementConfig that) {
return this.position.equals(that.position);
}
public static class Factory implements BlockEntityElementConfigFactory<ItemBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item"));
return (BlockEntityElementConfig<E>) new ItemBlockEntityElementConfig(
player -> BukkitItemManager.instance().createWrappedItem(itemId, player),
public ItemBlockEntityElementConfig create(Map<String, Object> arguments) {
return new ItemBlockEntityElementConfig(
Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item.missing_item")),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position")
);
}

View File

@@ -1,43 +1,51 @@
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
import com.google.common.base.Objects;
import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.display.Billboard;
import net.momirealms.craftengine.core.entity.display.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementConfig<ItemDisplayBlockEntityElement> {
public static final Factory FACTORY = new Factory();
private final Function<Player, List<Object>> lazyMetadataPacket;
private final Function<Player, Item<?>> item;
private final Vector3f scale;
private final Vector3f position;
private final Vector3f translation;
private final float xRot;
private final float yRot;
private final Quaternionf rotation;
private final ItemDisplayContext displayContext;
private final Billboard billboard;
private final float shadowRadius;
private final float shadowStrength;
public final Function<Player, List<Object>> lazyMetadataPacket;
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 Color glowColor;
public final int blockLight;
public final int skyLight;
public final float viewRange;
public ItemDisplayBlockEntityElementConfig(Function<Player, Item<?>> item,
public ItemDisplayBlockEntityElementConfig(Key itemId,
Vector3f scale,
Vector3f position,
Vector3f translation,
@@ -47,8 +55,12 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
ItemDisplayContext displayContext,
Billboard billboard,
float shadowRadius,
float shadowStrength) {
this.item = item;
float shadowStrength,
@Nullable Color glowColor,
int blockLight,
int skyLight,
float viewRange) {
this.itemId = itemId;
this.scale = scale;
this.position = position;
this.translation = translation;
@@ -59,16 +71,37 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
this.billboard = billboard;
this.shadowRadius = shadowRadius;
this.shadowStrength = shadowStrength;
this.glowColor = glowColor;
this.blockLight = blockLight;
this.skyLight = skyLight;
this.viewRange = viewRange;
this.lazyMetadataPacket = player -> {
List<Object> dataValues = new ArrayList<>();
ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.apply(player).getLiteralObject(), dataValues);
ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues);
ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues);
ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues);
ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues);
ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(this.displayContext.id(), dataValues);
ItemDisplayEntityData.ShadowRadius.addEntityDataIfNotDefaultValue(this.shadowRadius, dataValues);
ItemDisplayEntityData.ShadowStrength.addEntityDataIfNotDefaultValue(this.shadowStrength, dataValues);
if (glowColor != null) {
ItemDisplayEntityData.SharedFlags.addEntityData((byte) 0x40, dataValues);
ItemDisplayEntityData.GlowColorOverride.addEntityData(glowColor.color(), dataValues);
} else {
ItemDisplayEntityData.SharedFlags.addEntityData((byte) 0x0, dataValues);
ItemDisplayEntityData.GlowColorOverride.addEntityData(-1, dataValues);
}
Item<ItemStack> wrappedItem = BukkitItemManager.instance().createWrappedItem(itemId, player);
if (wrappedItem == null) {
wrappedItem = java.util.Objects.requireNonNull(BukkitItemManager.instance().createWrappedItem(ItemKeys.BARRIER, player));
}
ItemDisplayEntityData.DisplayedItem.addEntityData(wrappedItem.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);
if (this.blockLight != -1 && this.skyLight != -1) {
ItemDisplayEntityData.BrightnessOverride.addEntityData(this.blockLight << 4 | this.skyLight << 20, dataValues);
} else {
ItemDisplayEntityData.BrightnessOverride.addEntityData(-1, dataValues);
}
ItemDisplayEntityData.ViewRange.addEntityData((float) (this.viewRange * player.displayEntityViewDistance()), dataValues);
return dataValues;
};
}
@@ -80,14 +113,6 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
@Override
public ItemDisplayBlockEntityElement create(World world, BlockPos pos, ItemDisplayBlockEntityElement previous) {
Quaternionf previousRotation = previous.config.rotation;
if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) {
return null;
}
Vector3f translation = previous.config.translation;
if (translation.x != 0 || translation.y != 0 || translation.z != 0) {
return null;
}
return new ItemDisplayBlockEntityElement(this, pos, previous.entityId,
previous.config.yRot != this.yRot ||
previous.config.xRot != this.xRot ||
@@ -95,13 +120,25 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
);
}
@Override
public ItemDisplayBlockEntityElement createExact(World world, BlockPos pos, ItemDisplayBlockEntityElement previous) {
if (!previous.config.isSamePosition(this)) {
return null;
}
return new ItemDisplayBlockEntityElement(this, pos, previous.entityId, false);
}
@Override
public Class<ItemDisplayBlockEntityElement> elementClass() {
return ItemDisplayBlockEntityElement.class;
}
public Item<?> item(Player player) {
return this.item.apply(player);
public Color glowColor() {
return glowColor;
}
public Key itemId() {
return itemId;
}
public Vector3f scale() {
@@ -148,24 +185,35 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
return this.lazyMetadataPacket.apply(player);
}
public static class Factory implements BlockEntityElementConfigFactory {
public boolean isSamePosition(ItemDisplayBlockEntityElementConfig that) {
return Float.compare(xRot, that.xRot) == 0 &&
Float.compare(yRot, that.yRot) == 0 &&
Objects.equal(position, that.position) &&
Objects.equal(translation, that.translation) &&
Objects.equal(rotation, that.rotation);
}
public static class Factory implements BlockEntityElementConfigFactory<ItemDisplayBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item"));
return (BlockEntityElementConfig<E>) new ItemDisplayBlockEntityElementConfig(
player -> BukkitItemManager.instance().createWrappedItem(itemId, player),
public ItemDisplayBlockEntityElementConfig create(Map<String, Object> arguments) {
Map<String, Object> brightness = ResourceConfigUtils.getAsMap(arguments.getOrDefault("brightness", Map.of()), "brightness");
return new ItemDisplayBlockEntityElementConfig(
Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item")),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "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"),
ItemDisplayContext.valueOf(arguments.getOrDefault("display-context", "none").toString().toUpperCase(Locale.ROOT)),
Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)),
ResourceConfigUtils.getAsEnum(ResourceConfigUtils.get(arguments, "display-context", "display-transform"), ItemDisplayContext.class, ItemDisplayContext.NONE),
ResourceConfigUtils.getAsEnum(arguments.get("billboard"), Billboard.class, Billboard.FIXED),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-radius", 0f), "shadow-radius"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-strength", 1f), "shadow-strength")
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-strength", 1f), "shadow-strength"),
Optional.ofNullable(arguments.get("glow-color")).map(it -> Color.fromStrings(it.toString().split(","))).orElse(null),
ResourceConfigUtils.getAsInt(brightness.getOrDefault("block-light", -1), "block-light"),
ResourceConfigUtils.getAsInt(brightness.getOrDefault("sky-light", -1), "sky-light"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("view-range", 1f), "view-range")
);
}
}

View File

@@ -1,38 +1,49 @@
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
import com.google.common.base.Objects;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.display.Billboard;
import net.momirealms.craftengine.core.entity.display.TextDisplayAlignment;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
public class TextDisplayBlockEntityElementConfig implements BlockEntityElementConfig<TextDisplayBlockEntityElement> {
public static final Factory FACTORY = new Factory();
private final Function<Player, List<Object>> lazyMetadataPacket;
private final String text;
private final Vector3f scale;
private final Vector3f position;
private final Vector3f translation;
private final float xRot;
private final float yRot;
private final Quaternionf rotation;
private final Billboard billboard;
public final Function<Player, List<Object>> lazyMetadataPacket;
public final String text;
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 Billboard billboard;
public final Color glowColor;
public final int blockLight;
public final int skyLight;
public final float viewRange;
public final int lineWidth;
public final int backgroundColor;
public final byte opacity;
public final boolean hasShadow;
public final boolean isSeeThrough;
public final boolean useDefaultBackgroundColor;
public final TextDisplayAlignment alignment;
public TextDisplayBlockEntityElementConfig(String text,
Vector3f scale,
@@ -41,7 +52,18 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
float xRot,
float yRot,
Quaternionf rotation,
Billboard billboard) {
Billboard billboard,
@Nullable Color glowColor,
int blockLight,
int skyLight,
float viewRange,
int lineWidth,
int backgroundColor,
byte opacity,
boolean hasShadow,
boolean isSeeThrough,
boolean useDefaultBackgroundColor,
TextDisplayAlignment alignment) {
this.text = text;
this.scale = scale;
this.position = position;
@@ -50,13 +72,41 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
this.yRot = yRot;
this.rotation = rotation;
this.billboard = billboard;
this.glowColor = glowColor;
this.blockLight = blockLight;
this.skyLight = skyLight;
this.viewRange = viewRange;
this.lineWidth = lineWidth;
this.backgroundColor = backgroundColor;
this.opacity = opacity;
this.hasShadow = hasShadow;
this.useDefaultBackgroundColor = useDefaultBackgroundColor;
this.alignment = alignment;
this.isSeeThrough = isSeeThrough;
this.lazyMetadataPacket = player -> {
List<Object> dataValues = new ArrayList<>();
TextDisplayEntityData.Text.addEntityDataIfNotDefaultValue(ComponentUtils.adventureToMinecraft(text(player)), dataValues);
TextDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues);
TextDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues);
TextDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues);
TextDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues);
if (glowColor != null) {
TextDisplayEntityData.SharedFlags.addEntityData((byte) 0x40, dataValues);
TextDisplayEntityData.GlowColorOverride.addEntityData(glowColor.color(), dataValues);
} else {
TextDisplayEntityData.SharedFlags.addEntityData((byte) 0x0, dataValues);
TextDisplayEntityData.GlowColorOverride.addEntityData(-1, dataValues);
}
TextDisplayEntityData.Text.addEntityData(ComponentUtils.adventureToMinecraft(text(player)), dataValues);
TextDisplayEntityData.Scale.addEntityData(this.scale, dataValues);
TextDisplayEntityData.RotationLeft.addEntityData(this.rotation, dataValues);
TextDisplayEntityData.BillboardConstraints.addEntityData(this.billboard.id(), dataValues);
TextDisplayEntityData.Translation.addEntityData(this.translation, dataValues);
TextDisplayEntityData.LineWidth.addEntityData(this.lineWidth, dataValues);
TextDisplayEntityData.BackgroundColor.addEntityData(this.backgroundColor, dataValues);
TextDisplayEntityData.TextOpacity.addEntityData(this.opacity, dataValues);
TextDisplayEntityData.TextDisplayMasks.addEntityData(TextDisplayEntityData.encodeMask(this.hasShadow, this.isSeeThrough, this.useDefaultBackgroundColor, this.alignment), dataValues);
if (this.blockLight != -1 && this.skyLight != -1) {
TextDisplayEntityData.BrightnessOverride.addEntityData(this.blockLight << 4 | this.skyLight << 20, dataValues);
} else {
TextDisplayEntityData.BrightnessOverride.addEntityData(-1, dataValues);
}
TextDisplayEntityData.ViewRange.addEntityData((float) (this.viewRange * player.displayEntityViewDistance()), dataValues);
return dataValues;
};
}
@@ -68,14 +118,6 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
@Override
public TextDisplayBlockEntityElement create(World world, BlockPos pos, TextDisplayBlockEntityElement previous) {
Quaternionf previousRotation = previous.config.rotation;
if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) {
return null;
}
Vector3f translation = previous.config.translation;
if (translation.x != 0 || translation.y != 0 || translation.z != 0) {
return null;
}
return new TextDisplayBlockEntityElement(this, pos, previous.entityId,
previous.config.yRot != this.yRot ||
previous.config.xRot != this.xRot ||
@@ -83,13 +125,25 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
);
}
@Override
public TextDisplayBlockEntityElement createExact(World world, BlockPos pos, TextDisplayBlockEntityElement previous) {
if (!previous.config.isSamePosition(this)) {
return null;
}
return new TextDisplayBlockEntityElement(this, pos, previous.entityId, false);
}
@Override
public Class<TextDisplayBlockEntityElement> elementClass() {
return TextDisplayBlockEntityElement.class;
}
public String text() {
return text;
}
public Component text(Player player) {
return AdventureHelper.miniMessage().deserialize(this.text, PlayerOptionalContext.of(player).tagResolvers());
return AdventureHelper.miniMessage().deserialize(this.text, NetworkTextReplaceContext.of(player).tagResolvers());
}
public Vector3f scale() {
@@ -124,13 +178,21 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
return this.lazyMetadataPacket.apply(player);
}
public static class Factory implements BlockEntityElementConfigFactory {
public boolean isSamePosition(TextDisplayBlockEntityElementConfig that) {
return Float.compare(xRot, that.xRot) == 0 &&
Float.compare(yRot, that.yRot) == 0 &&
Objects.equal(position, that.position) &&
Objects.equal(translation, that.translation) &&
Objects.equal(rotation, that.rotation);
}
public static class Factory implements BlockEntityElementConfigFactory<TextDisplayBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public TextDisplayBlockEntityElementConfig create(Map<String, Object> arguments) {
String text = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("text"), "warning.config.block.state.entity_renderer.text_display.missing_text");
return (BlockEntityElementConfig<E>) new TextDisplayBlockEntityElementConfig(
Map<String, Object> brightness = ResourceConfigUtils.getAsMap(arguments.getOrDefault("brightness", Map.of()), "brightness");
return new TextDisplayBlockEntityElementConfig(
text,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
@@ -138,7 +200,18 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),
ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"),
Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT))
Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)),
Optional.ofNullable(arguments.get("glow-color")).map(it -> Color.fromStrings(it.toString().split(","))).orElse(null),
ResourceConfigUtils.getAsInt(brightness.getOrDefault("block-light", -1), "block-light"),
ResourceConfigUtils.getAsInt(brightness.getOrDefault("sky-light", -1), "sky-light"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("view-range", 1f), "view-range"),
ResourceConfigUtils.getAsInt(arguments.getOrDefault("line-width", 200), "line-width"),
ResourceConfigUtils.getOrDefault(arguments.get("background-color"), o -> Color.fromStrings(o.toString().split(",")).color(), 0x40000000),
(byte) ResourceConfigUtils.getAsInt(arguments.getOrDefault("text-opacity", -1), "text-opacity"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("has-shadow", false), "has-shadow"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("is-see-through", false), "is-see-through"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("use-default-background-color", false), "use-default-background-color"),
ResourceConfigUtils.getAsEnum(arguments.get("alignment"), TextDisplayAlignment.class, TextDisplayAlignment.CENTER)
);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
package net.momirealms.craftengine.bukkit.entity.data;
public class ArmorStandData<T> extends LivingEntityData<T> {
public static final ArmorStandData<Byte> ArmorStandFlags = new ArmorStandData<>(ArmorStandData.class, EntityDataValue.Serializers$BYTE, (byte) 0);
// rotations
public ArmorStandData(Class<?> clazz, Object serializer, T defaultValue) {
super(clazz, serializer, defaultValue);
}
}

View File

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

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.entity.data;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.core.entity.display.TextDisplayAlignment;
public class TextDisplayEntityData<T> extends DisplayEntityData<T> {
public static final TextDisplayEntityData<Object> Text = new TextDisplayEntityData<>(TextDisplayEntityData.class, EntityDataValue.Serializers$COMPONENT, CoreReflections.instance$Component$empty);
@@ -12,4 +13,39 @@ public class TextDisplayEntityData<T> extends DisplayEntityData<T> {
public TextDisplayEntityData(Class<?> clazz, Object serializer, T defaultValue) {
super(clazz, serializer, defaultValue);
}
public static final int HAS_SHADOW = 0x01;
public static final int IS_SEE_THROUGH = 0x02;
public static final int USE_DEFAULT_BG_COLOR = 0x04;
private static final int LEFT_ALIGNMENT = 0x08; // 8
private static final int RIGHT_ALIGNMENT = 0x10; // 16
public static byte encodeMask(boolean hasShadow, boolean isSeeThrough, boolean useDefaultBackground, TextDisplayAlignment alignment) {
int bitMask = 0;
if (hasShadow) {
bitMask |= HAS_SHADOW;
}
if (isSeeThrough) {
bitMask |= IS_SEE_THROUGH;
}
if (useDefaultBackground) {
bitMask |= USE_DEFAULT_BG_COLOR;
}
switch (alignment) {
case CENTER: // CENTER
break;
case LEFT: // LEFT
bitMask |= LEFT_ALIGNMENT;
break;
case RIGHT: // RIGHT
bitMask |= RIGHT_ALIGNMENT;
break;
default:
throw new IllegalArgumentException("Invalid alignment value");
}
return (byte) bitMask;
}
}

View File

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

View File

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

View File

@@ -1,290 +1,181 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.entity.BukkitEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.entity.Player;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class BukkitFurniture implements Furniture {
private final CustomFurniture furniture;
private final CustomFurniture.Placement placement;
private FurnitureExtraData extraData;
// location
private final Location location;
// base entity
private final WeakReference<Entity> baseEntity;
private final int baseEntityId;
// colliders
private final Collider[] colliderEntities;
// cache
private final List<Integer> fakeEntityIds;
private final List<Integer> entityIds;
private final Map<Integer, BukkitHitBox> hitBoxes = new Int2ObjectArrayMap<>();
private final Map<Integer, HitBoxPart> hitBoxParts = new Int2ObjectArrayMap<>();
private final boolean minimized;
private final boolean hasExternalModel;
// cached spawn packet
private Object cachedSpawnPacket;
private Object cachedMinimizedSpawnPacket;
@SuppressWarnings("DuplicatedCode")
public class BukkitFurniture extends Furniture {
private final WeakReference<ItemDisplay> metaEntity;
private Location location;
public BukkitFurniture(Entity baseEntity,
CustomFurniture furniture,
FurnitureExtraData extraData) {
this.extraData = extraData;
this.baseEntityId = baseEntity.getEntityId();
this.location = baseEntity.getLocation();
this.baseEntity = new WeakReference<>(baseEntity);
this.furniture = furniture;
this.minimized = furniture.settings().minimized();
this.placement = furniture.getValidPlacement(extraData.anchorType().orElseGet(furniture::getAnyAnchorType));
List<Integer> fakeEntityIds = new IntArrayList();
List<Integer> mainEntityIds = new IntArrayList();
mainEntityIds.add(this.baseEntityId);
// 绑定外部模型
Optional<ExternalModel> optionalExternal = placement.externalModel();
if (optionalExternal.isPresent()) {
try {
optionalExternal.get().bindModel(new BukkitEntity(baseEntity));
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to load external model for furniture " + id(), e);
}
this.hasExternalModel = true;
} else {
this.hasExternalModel = false;
}
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate();
List<Object> packets = new ArrayList<>();
List<Object> minimizedPackets = new ArrayList<>();
List<Collider> colliders = new ArrayList<>(4);
WorldPosition position = position();
// 初始化家具的元素
for (FurnitureElement element : placement.elements()) {
int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
fakeEntityIds.add(entityId);
element.initPackets(this, entityId, conjugated, packet -> {
packets.add(packet);
if (this.minimized) minimizedPackets.add(packet);
});
}
// 初始化碰撞箱
for (HitBoxConfig hitBoxConfig : this.placement.hitBoxConfigs()) {
int[] ids = hitBoxConfig.acquireEntityIds(CoreReflections.instance$Entity$ENTITY_COUNTER::incrementAndGet);
List<HitBoxPart> aabbs = new ArrayList<>();
hitBoxConfig.initPacketsAndColliders(ids, position, conjugated, (packet, canBeMinimized) -> {
packets.add(packet);
if (this.minimized && !canBeMinimized) {
minimizedPackets.add(packet);
}
}, colliders::add, part -> {
this.hitBoxParts.put(part.entityId(), part);
aabbs.add(part);
});
BukkitHitBox hitBox = new BukkitHitBox(this, hitBoxConfig, aabbs.toArray(new HitBoxPart[0]));
for (int entityId : ids) {
fakeEntityIds.add(entityId);
mainEntityIds.add(entityId);
this.hitBoxes.put(entityId, hitBox);
}
}
// 初始化缓存的家具包
try {
this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
if (this.minimized) {
this.cachedMinimizedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(minimizedPackets);
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to init spawn packets for furniture " + id(), e);
}
this.fakeEntityIds = fakeEntityIds;
this.entityIds = mainEntityIds;
this.colliderEntities = colliders.toArray(new Collider[0]);
public BukkitFurniture(ItemDisplay metaEntity, CustomFurniture config, FurnitureDataAccessor data) {
super(new BukkitEntity(metaEntity), data, config);
this.metaEntity = new WeakReference<>(metaEntity);
this.location = metaEntity.getLocation();
}
@Override
public void initializeColliders() {
public void addCollidersToWorld() {
Object world = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(this.location.getWorld());
for (Collider entity : this.colliderEntities) {
FastNMS.INSTANCE.method$LevelWriter$addFreshEntity(world, entity.handle());
for (Collider entity : super.colliders) {
Entity bukkitEntity = FastNMS.INSTANCE.method$Entity$getBukkitEntity(entity.handle());
bukkitEntity.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_COLLISION, PersistentDataType.BYTE, (byte) 1);
}
}
@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;
bukkitEntity.setPersistent(false);
if (!bukkitEntity.isValid()) {
FastNMS.INSTANCE.method$LevelWriter$addFreshEntity(world, entity.handle());
}
}
}
@Override
public WorldPosition position() {
return LocationUtils.toWorldPosition(this.location);
}
@NotNull
public Location location() {
return this.location.clone();
}
@NotNull
public Entity baseEntity() {
Entity entity = this.baseEntity.get();
if (entity == null) {
throw new RuntimeException("Base entity not found. It might be unloaded.");
public boolean setVariant(String variantName) {
FurnitureVariant variant = this.config.getVariant(variantName);
if (variant == null) return false;
if (this.currentVariant == variant) return false;
// 检查新位置是否可用
List<AABB> aabbs = new ArrayList<>();
WorldPosition position = position();
for (FurnitureHitBoxConfig<?> hitBoxConfig : variant.hitBoxConfigs()) {
hitBoxConfig.prepareBoundingBox(position, aabbs::add, false);
}
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(),
o -> {
for (Collider collider : super.colliders) {
if (o == collider.handle()) {
return false;
}
}
return true;
})) {
return false;
}
}
// 删除椅子
super.destroySeats();
BukkitFurnitureManager.instance().invalidateFurniture(this);
super.clearColliders();
super.setVariantInternal(variant);
BukkitFurnitureManager.instance().initFurniture(this);
this.addCollidersToWorld();
this.refresh();
return true;
}
@SuppressWarnings("deprecation")
@Override
public CompletableFuture<Boolean> moveTo(WorldPosition position) {
ItemDisplay itemDisplay = this.metaEntity.get();
if (itemDisplay == null) return CompletableFuture.completedFuture(false);
// 检查新位置是否可用
List<AABB> aabbs = new ArrayList<>();
for (FurnitureHitBoxConfig<?> hitBoxConfig : getCurrentVariant().hitBoxConfigs()) {
hitBoxConfig.prepareBoundingBox(position, aabbs::add, false);
}
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(),
o -> {
for (Collider collider : super.colliders) {
if (o == collider.handle()) {
return false;
}
}
return true;
})) {
return CompletableFuture.completedFuture(false);
}
}
// 删除椅子
super.destroySeats();
// 准备传送
CompletableFuture<Boolean> future = new CompletableFuture<>();
BukkitFurnitureManager.instance().invalidateFurniture(this);
super.clearColliders();
this.location = LocationUtils.toLocation(position);
Object removePacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(MiscUtils.init(new IntArrayList(), l -> l.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(MiscUtils.init(new IntArrayList(), l -> l.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
public boolean isValid() {
return baseEntity().isValid();
public void destroy() {
Optional.ofNullable(this.metaEntity.get()).ifPresent(Entity::remove);
for (Collider entity : super.colliders) {
entity.destroy();
}
}
@NotNull
public Location dropLocation() {
Optional<Vector3f> dropOffset = this.placement.dropOffset();
// 获取掉落物的位置,受到家具变种的影响
public Location getDropLocation() {
Optional<Vector3f> dropOffset = this.getCurrentVariant().dropOffset();
if (dropOffset.isEmpty()) {
return location();
return this.location;
}
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate();
Vector3f offset = conjugated.transform(new Vector3f(dropOffset.get()));
return new Location(this.location.getWorld(), this.location.getX() + offset.x, this.location.getY() + offset.y, this.location.getZ() - offset.z);
}
@Override
public void destroy() {
if (!isValid()) {
return;
}
this.baseEntity().remove();
this.destroyColliders();
this.destroySeats();
public Location location() {
return location;
}
@Override
public void destroyColliders() {
for (Collider entity : this.colliderEntities) {
if (entity != null)
entity.destroy();
}
}
@Override
public void destroySeats() {
for (HitBox hitBox : this.hitBoxes.values()) {
for (Seat<HitBox> seat : hitBox.seats()) {
seat.destroy();
}
}
}
@Override
public UUID uuid() {
return this.baseEntity().getUniqueId();
}
@Override
public int baseEntityId() {
return this.baseEntityId;
}
@NotNull
public List<Integer> entityIds() {
return Collections.unmodifiableList(this.entityIds);
}
@NotNull
public List<Integer> fakeEntityIds() {
return Collections.unmodifiableList(this.fakeEntityIds);
}
public Collider[] collisionEntities() {
return this.colliderEntities;
}
@Override
public @Nullable HitBox hitBoxByEntityId(int id) {
return this.hitBoxes.get(id);
}
@Override
public @Nullable HitBoxPart hitBoxPartByEntityId(int id) {
return this.hitBoxParts.get(id);
}
@Override
public @NotNull AnchorType anchorType() {
return this.placement.anchorType();
}
@Override
public @NotNull Key id() {
return this.furniture.id();
}
@Override
public @NotNull CustomFurniture config() {
return this.furniture;
}
@Override
public boolean hasExternalModel() {
return hasExternalModel;
}
@Override
public FurnitureExtraData extraData() {
return this.extraData;
}
@Override
public void setExtraData(FurnitureExtraData extraData) {
this.extraData = extraData;
this.save();
}
@Override
public void save() {
try {
this.baseEntity().getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_EXTRA_DATA_KEY, PersistentDataType.BYTE_ARRAY, this.extraData.toBytes());
} catch (IOException e) {
CraftEngine.instance().logger().warn("Failed to save furniture data.", e);
}
public Entity getBukkitEntity() {
return this.metaEntity.get();
}
}

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,22 @@ package net.momirealms.craftengine.bukkit.entity.furniture;
import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent;
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.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.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.world.CEWorld;
import net.momirealms.craftengine.core.world.chunk.CEChunk;
import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@@ -11,14 +25,18 @@ import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.event.world.EntitiesLoadEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
public class FurnitureEventListener implements Listener {
private final BukkitFurnitureManager manager;
private final BukkitWorldManager worldManager;
public FurnitureEventListener(final BukkitFurnitureManager manager) {
public FurnitureEventListener(final BukkitFurnitureManager manager, final BukkitWorldManager worldManager) {
this.manager = manager;
this.worldManager = worldManager;
}
/*
@@ -29,21 +47,26 @@ public class FurnitureEventListener implements Listener {
List<Entity> entities = event.getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadEarly(itemDisplay);
this.manager.handleMetaEntityDuringChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadOnEntitiesLoad(entity);
this.manager.handleCollisionEntityDuringChunkLoad(entity);
}
}
CEWorld world = this.worldManager.getWorld(event.getWorld());
CEChunk ceChunk = world.getChunkAtIfLoaded(event.getChunk().getChunkKey());
if (ceChunk != null) {
ceChunk.setEntitiesLoaded(true);
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
public void onWorldLoad(WorldLoadEvent event) {
List<Entity> entities = event.getWorld().getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadEarly(itemDisplay);
this.manager.handleMetaEntityDuringChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadOnEntitiesLoad(entity);
this.manager.handleCollisionEntityDuringChunkLoad(entity);
}
}
}
@@ -52,9 +75,9 @@ public class FurnitureEventListener implements Listener {
public void onEntityLoad(EntityAddToWorldEvent event) {
Entity entity = event.getEntity();
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadLate(itemDisplay, 0);
this.manager.handleMetaEntityAfterChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadLate(entity, 0);
this.manager.handleCollisionEntityAfterChunkLoad(entity);
}
}
@@ -65,8 +88,8 @@ public class FurnitureEventListener implements Listener {
public void onChunkUnload(ChunkUnloadEvent event) {
Entity[] entities = event.getChunk().getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay) {
this.manager.handleBaseEntityUnload(entity);
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity);
}
@@ -77,8 +100,8 @@ public class FurnitureEventListener implements Listener {
public void onWorldUnload(WorldUnloadEvent event) {
List<Entity> entities = event.getWorld().getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay) {
this.manager.handleBaseEntityUnload(entity);
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity);
}
@@ -88,10 +111,50 @@ public class FurnitureEventListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onEntityUnload(EntityRemoveFromWorldEvent event) {
Entity entity = event.getEntity();
if (entity instanceof ItemDisplay) {
this.manager.handleBaseEntityUnload(entity);
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity);
}
}
@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,89 @@
package net.momirealms.craftengine.bukkit.entity.furniture.element;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.ints.IntList;
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.world.score.BukkitTeamManager;
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.util.VersionHelper;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class ArmorStandFurnitureElement implements FurnitureElement {
private final ArmorStandFurnitureElementConfig config;
private final FurnitureColorSource colorSource;
public final Object cachedSpawnPacket;
public final Object cachedDespawnPacket;
public final Object cachedScalePacket;
public final Object cachedTeamPacket;
public final int entityId;
public final UUID uuid = UUID.randomUUID();
public ArmorStandFurnitureElement(Furniture furniture, ArmorStandFurnitureElementConfig config) {
this.config = config;
this.entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
WorldPosition furniturePos = furniture.position();
Vec3d position = Furniture.getRelativePosition(furniturePos, config.position());
this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
this.entityId, this.uuid, position.x, position.y, position.z,
furniturePos.xRot, furniturePos.yRot, MEntityTypes.ARMOR_STAND, 0, CoreReflections.instance$Vec3$Zero, furniturePos.yRot
);
this.colorSource = furniture.dataAccessor.getColorSource();
this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(IntList.of(this.entityId));
if (VersionHelper.isOrAbove1_20_5() && config.scale != 1) {
Object attributeIns = FastNMS.INSTANCE.constructor$AttributeInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
FastNMS.INSTANCE.method$AttributeInstance$setBaseValue(attributeIns, config.scale());
this.cachedScalePacket = FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(this.entityId, Collections.singletonList(attributeIns));
} else {
this.cachedScalePacket = null;
}
Object teamPacket = null;
if (config.glowColor != null) {
Object teamByColor = BukkitTeamManager.instance().getTeamByColor(config.glowColor);
if (teamByColor != null) {
teamPacket = FastNMS.INSTANCE.method$ClientboundSetPlayerTeamPacket$createMultiplePlayerPacket(teamByColor, List.of(this.uuid.toString()), true);
}
}
this.cachedTeamPacket = teamPacket;
}
@Override
public void show(Player player) {
player.sendPackets(List.of(this.cachedSpawnPacket, FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadata.apply(player))), false);
player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundSetEquipmentPacket(this.entityId, List.of(
Pair.of(CoreReflections.instance$EquipmentSlot$HEAD, this.config.item(player, this.colorSource).getLiteralObject())
)), false);
if (this.cachedScalePacket != null) {
player.sendPacket(this.cachedScalePacket, false);
}
if (this.cachedTeamPacket != null) {
player.sendPacket(this.cachedTeamPacket, false);
}
}
@Override
public void hide(Player player) {
player.sendPacket(this.cachedDespawnPacket, 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,127 @@
package net.momirealms.craftengine.bukkit.entity.furniture.element;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.entity.data.ArmorStandData;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
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.LegacyChatFormatter;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Glowing;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
public class ArmorStandFurnitureElementConfig implements FurnitureElementConfig<ArmorStandFurnitureElement>, Glowing {
public static final Factory FACTORY = new Factory();
public final Function<Player, List<Object>> metadata;
public final Key itemId;
public final float scale;
public final boolean applyDyedColor;
public final Vector3f position;
public final boolean small;
public final LegacyChatFormatter glowColor;
public ArmorStandFurnitureElementConfig(Key itemId,
float scale,
Vector3f position,
boolean applyDyedColor,
boolean small,
LegacyChatFormatter glowColor) {
this.position = position;
this.applyDyedColor = applyDyedColor;
this.small = small;
this.scale = scale;
this.itemId = itemId;
this.glowColor = glowColor;
this.metadata = (player) -> {
List<Object> dataValues = new ArrayList<>(2);
if (glowColor != null) {
BaseEntityData.SharedFlags.addEntityData((byte) 0x60, dataValues);
} else {
BaseEntityData.SharedFlags.addEntityData((byte) 0x20, dataValues);
}
if (small) {
ArmorStandData.ArmorStandFlags.addEntityData((byte) 0x01, dataValues);
}
return dataValues;
};
}
public Item<?> item(Player player, FurnitureColorSource 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));
}
@Nullable
@Override
public LegacyChatFormatter glowColor() {
return this.glowColor;
}
public float scale() {
return scale;
}
public boolean small() {
return small;
}
public Vector3f position() {
return this.position;
}
public boolean applyDyedColor() {
return this.applyDyedColor;
}
public Key itemId() {
return this.itemId;
}
@Override
public ArmorStandFurnitureElement create(@NotNull Furniture furniture) {
return new ArmorStandFurnitureElement(furniture, this);
}
public static class Factory implements FurnitureElementConfigFactory<ArmorStandFurnitureElement> {
@Override
public ArmorStandFurnitureElementConfig create(Map<String, Object> arguments) {
return new ArmorStandFurnitureElementConfig(
Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.furniture.element.armor_stand.missing_item")),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0f), "position"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("apply-dyed-color", true), "apply-dyed-color"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("small", false), "small"),
ResourceConfigUtils.getAsEnum(arguments.get("glow-color"), LegacyChatFormatter.class, null)
);
}
}
}

View File

@@ -0,0 +1,18 @@
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);
register(TEXT_DISPLAY, TextDisplayFurnitureElementConfig.FACTORY);
register(ITEM, ItemFurnitureElementConfig.FACTORY);
register(ARMOR_STAND, ArmorStandFurnitureElementConfig.FACTORY);
}
private BukkitFurnitureElementConfigs() {}
public static void init() {
}
}

View File

@@ -0,0 +1,63 @@
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.util.MiscUtils;
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;
private final UUID uuid = UUID.randomUUID();
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(MiscUtils.init(new IntArrayList(), a -> a.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, this.uuid,
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,218 @@
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.Color;
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.jetbrains.annotations.Nullable;
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 final Color glowColor;
public final int blockLight;
public final int skyLight;
public final float viewRange;
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,
@Nullable Color glowColor,
int blockLight,
int skyLight,
float viewRange) {
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;
this.glowColor = glowColor;
this.blockLight = blockLight;
this.skyLight = skyLight;
this.viewRange = viewRange;
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<>();
if (glowColor != null) {
ItemDisplayEntityData.SharedFlags.addEntityData((byte) 0x40, dataValues);
ItemDisplayEntityData.GlowColorOverride.addEntityData(glowColor.color(), dataValues);
}
ItemDisplayEntityData.DisplayedItem.addEntityData(itemFunction.apply(player, source).getLiteralObject(), dataValues);
ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues);
ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues);
ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues);
ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues);
ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(this.displayContext.id(), dataValues);
ItemDisplayEntityData.ShadowRadius.addEntityDataIfNotDefaultValue(this.shadowRadius, dataValues);
ItemDisplayEntityData.ShadowStrength.addEntityDataIfNotDefaultValue(this.shadowStrength, dataValues);
if (this.blockLight != -1 && this.skyLight != -1) {
ItemDisplayEntityData.BrightnessOverride.addEntityData(this.blockLight << 4 | this.skyLight << 20, dataValues);
}
ItemDisplayEntityData.ViewRange.addEntityDataIfNotDefaultValue((float) (this.viewRange * player.displayEntityViewDistance()), 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 BiFunction<Player, FurnitureColorSource, List<Object>> metadata() {
return this.metadata;
}
public Key itemId() {
return this.itemId;
}
@Nullable
public Color glowColor() {
return this.glowColor;
}
public int blockLight() {
return this.blockLight;
}
public int skyLight() {
return this.skyLight;
}
public float viewRange() {
return this.viewRange;
}
@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) {
Map<String, Object> brightness = ResourceConfigUtils.getAsMap(arguments.getOrDefault("brightness", Map.of()), "brightness");
return new ItemDisplayFurnitureElementConfig(
Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.furniture.element.item_display.missing_item")),
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"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("apply-dyed-color", true), "apply-dyed-color"),
Optional.ofNullable(arguments.get("glow-color")).map(it -> Color.fromStrings(it.toString().split(","))).orElse(null),
ResourceConfigUtils.getAsInt(brightness.getOrDefault("block-light", -1), "block-light"),
ResourceConfigUtils.getAsInt(brightness.getOrDefault("sky-light", -1), "sky-light"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("view-range", 1f), "view-range")
);
}
}
}

View File

@@ -0,0 +1,78 @@
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.util.MiscUtils;
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 ItemFurnitureElement implements FurnitureElement {
private final ItemFurnitureElementConfig config;
public final int entityId1;
public final int entityId2;
private final Object despawnPacket;
private final FurnitureColorSource colorSource;
public final Object cachedSpawnPacket1;
public final Object cachedSpawnPacket2;
public final Object cachedRidePacket;
public ItemFurnitureElement(Furniture furniture, ItemFurnitureElementConfig config) {
this.config = config;
this.entityId1 = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
this.entityId2 = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
WorldPosition furniturePos = furniture.position();
Vec3d position = Furniture.getRelativePosition(furniturePos, config.position());
this.cachedSpawnPacket1 = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId1, UUID.randomUUID(), position.x, position.y, position.z,
0, 0, MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
);
this.cachedSpawnPacket2 = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId2, UUID.randomUUID(), position.x, position.y, position.z,
0, 0, MEntityTypes.ITEM, 0, CoreReflections.instance$Vec3$Zero, 0
);
this.cachedRidePacket = FastNMS.INSTANCE.constructor$ClientboundSetPassengersPacket(entityId1, entityId2);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(MiscUtils.init(new IntArrayList(),
a -> {
a.add(entityId1);
a.add(entityId2);
}
));
this.colorSource = furniture.dataAccessor.getColorSource();
}
@Override
public void show(Player player) {
player.sendPackets(List.of(
this.cachedSpawnPacket1,
this.cachedSpawnPacket2,
this.cachedRidePacket,
FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId2, 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.entityId1, this.entityId2};
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
collector.accept(this.entityId1);
collector.accept(this.entityId2);
}
}

View File

@@ -0,0 +1,93 @@
package net.momirealms.craftengine.bukkit.entity.furniture.element;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.entity.data.ItemEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
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.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
public class ItemFurnitureElementConfig implements FurnitureElementConfig<ItemFurnitureElement> {
public static final Factory FACTORY = new Factory();
public final BiFunction<Player, FurnitureColorSource, List<Object>> metadata;
public final Key itemId;
public final boolean applyDyedColor;
public final Vector3f position;
public ItemFurnitureElementConfig(Key itemId,
Vector3f position,
boolean applyDyedColor) {
this.position = position;
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<>();
ItemEntityData.Item.addEntityData(itemFunction.apply(player, source).getLiteralObject(), dataValues);
ItemEntityData.NoGravity.addEntityData(true, dataValues);
return dataValues;
};
}
public Vector3f position() {
return this.position;
}
public boolean applyDyedColor() {
return this.applyDyedColor;
}
public BiFunction<Player, FurnitureColorSource, List<Object>> metadata() {
return this.metadata;
}
public Key itemId() {
return this.itemId;
}
@Override
public ItemFurnitureElement create(@NotNull Furniture furniture) {
return new ItemFurnitureElement(furniture, this);
}
public static class Factory implements FurnitureElementConfigFactory<ItemFurnitureElement> {
@Override
public ItemFurnitureElementConfig create(Map<String, Object> arguments) {
return new ItemFurnitureElementConfig(
Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.furniture.element.item.missing_item")),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0f), "position"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("apply-dyed-color", true), "apply-dyed-color")
);
}
}
}

View File

@@ -0,0 +1,60 @@
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.element.FurnitureElement;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.util.MiscUtils;
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 TextDisplayFurnitureElement implements FurnitureElement {
private final TextDisplayFurnitureElementConfig config;
private final WorldPosition position;
private final int entityId;
private final Object despawnPacket;
private final UUID uuid = UUID.randomUUID();
public TextDisplayFurnitureElement(Furniture furniture, TextDisplayFurnitureElementConfig 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(MiscUtils.init(new IntArrayList(), a -> a.add(entityId)));
}
@Override
public void show(Player player) {
player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundBundlePacket(List.of(
FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
this.entityId, this.uuid,
this.position.x, this.position.y, this.position.z, 0, this.position.yRot,
MEntityTypes.TEXT_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
),
FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadata.apply(player))
)), 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,251 @@
package net.momirealms.craftengine.bukkit.entity.furniture.element;
import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.core.entity.display.Billboard;
import net.momirealms.craftengine.core.entity.display.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.display.TextDisplayAlignment;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
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.plugin.context.NetworkTextReplaceContext;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
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.Map;
import java.util.Optional;
import java.util.function.Function;
public class TextDisplayFurnitureElementConfig implements FurnitureElementConfig<TextDisplayFurnitureElement> {
public static final Factory FACTORY = new Factory();
public final Function<Player, List<Object>> metadata;
public final String text;
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 Color glowColor;
public final int blockLight;
public final int skyLight;
public final float viewRange;
public final int lineWidth;
public final int backgroundColor;
public final byte opacity;
public final boolean hasShadow;
public final boolean isSeeThrough;
public final boolean useDefaultBackgroundColor;
public final TextDisplayAlignment alignment;
public TextDisplayFurnitureElementConfig(String text,
Vector3f scale,
Vector3f position,
Vector3f translation,
float xRot,
float yRot,
Quaternionf rotation,
ItemDisplayContext displayContext,
Billboard billboard,
float shadowRadius,
float shadowStrength,
@Nullable Color glowColor,
int blockLight,
int skyLight,
float viewRange,
int lineWidth,
int backgroundColor,
byte opacity,
boolean hasShadow,
boolean isSeeThrough,
boolean useDefaultBackgroundColor,
TextDisplayAlignment alignment) {
this.text = text;
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.glowColor = glowColor;
this.blockLight = blockLight;
this.skyLight = skyLight;
this.viewRange = viewRange;
this.lineWidth = lineWidth;
this.backgroundColor = backgroundColor;
this.opacity = opacity;
this.hasShadow = hasShadow;
this.useDefaultBackgroundColor = useDefaultBackgroundColor;
this.alignment = alignment;
this.isSeeThrough = isSeeThrough;
this.metadata = (player) -> {
List<Object> dataValues = new ArrayList<>();
if (glowColor != null) {
TextDisplayEntityData.SharedFlags.addEntityData((byte) 0x40, dataValues);
TextDisplayEntityData.GlowColorOverride.addEntityData(glowColor.color(), dataValues);
}
TextDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues);
TextDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues);
TextDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues);
TextDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues);
TextDisplayEntityData.ShadowRadius.addEntityDataIfNotDefaultValue(this.shadowRadius, dataValues);
TextDisplayEntityData.ShadowStrength.addEntityDataIfNotDefaultValue(this.shadowStrength, dataValues);
TextDisplayEntityData.Text.addEntityData(ComponentUtils.adventureToMinecraft(AdventureHelper.miniMessage().deserialize(this.text, NetworkTextReplaceContext.of(player).tagResolvers())), dataValues);
TextDisplayEntityData.LineWidth.addEntityDataIfNotDefaultValue(this.lineWidth, dataValues);
TextDisplayEntityData.BackgroundColor.addEntityDataIfNotDefaultValue(this.backgroundColor, dataValues);
TextDisplayEntityData.TextOpacity.addEntityDataIfNotDefaultValue(this.opacity, dataValues);
TextDisplayEntityData.TextDisplayMasks.addEntityDataIfNotDefaultValue(TextDisplayEntityData.encodeMask(this.hasShadow, this.isSeeThrough, this.useDefaultBackgroundColor, this.alignment), dataValues);
if (this.blockLight != -1 && this.skyLight != -1) {
TextDisplayEntityData.BrightnessOverride.addEntityData(this.blockLight << 4 | this.skyLight << 20, dataValues);
}
TextDisplayEntityData.ViewRange.addEntityDataIfNotDefaultValue((float) (this.viewRange * player.displayEntityViewDistance()), 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 Function<Player, List<Object>> metadata() {
return this.metadata;
}
@Nullable
public Color glowColor() {
return this.glowColor;
}
public int blockLight() {
return this.blockLight;
}
public int skyLight() {
return this.skyLight;
}
public float viewRange() {
return this.viewRange;
}
public String text() {
return this.text;
}
public int lineWidth() {
return this.lineWidth;
}
public int backgroundColor() {
return this.backgroundColor;
}
public byte opacity() {
return this.opacity;
}
public boolean hasShadow() {
return this.hasShadow;
}
public boolean isSeeThrough() {
return this.isSeeThrough;
}
public boolean useDefaultBackgroundColor() {
return this.useDefaultBackgroundColor;
}
public TextDisplayAlignment alignment() {
return this.alignment;
}
@Override
public TextDisplayFurnitureElement create(@NotNull Furniture furniture) {
return new TextDisplayFurnitureElement(furniture, this);
}
public static class Factory implements FurnitureElementConfigFactory<TextDisplayFurnitureElement> {
@Override
public TextDisplayFurnitureElementConfig create(Map<String, Object> arguments) {
Map<String, Object> brightness = ResourceConfigUtils.getAsMap(arguments.getOrDefault("brightness", Map.of()), "brightness");
return new TextDisplayFurnitureElementConfig(
ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("text"), "warning.config.furniture.element.text_display.missing_text"),
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"),
Optional.ofNullable(arguments.get("glow-color")).map(it -> Color.fromStrings(it.toString().split(","))).orElse(null),
ResourceConfigUtils.getAsInt(brightness.getOrDefault("block-light", -1), "block-light"),
ResourceConfigUtils.getAsInt(brightness.getOrDefault("sky-light", -1), "sky-light"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("view-range", 1f), "view-range"),
ResourceConfigUtils.getAsInt(arguments.getOrDefault("line-width", 200), "line-width"),
ResourceConfigUtils.getOrDefault(arguments.get("background-color"), o -> Color.fromStrings(o.toString().split(",")).color(), 0x40000000),
(byte) ResourceConfigUtils.getAsInt(arguments.getOrDefault("text-opacity", -1), "text-opacity"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("has-shadow", false), "has-shadow"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("is-see-through", false), "is-see-through"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("use-default-background-color", false), "use-default-background-color"),
ResourceConfigUtils.getAsEnum(arguments.get("alignment"), TextDisplayAlignment.class, TextDisplayAlignment.CENTER)
);
}
}
}

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,85 @@
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.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.util.MiscUtils;
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()) {
Object attributeIns = FastNMS.INSTANCE.constructor$AttributeInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
FastNMS.INSTANCE.method$AttributeInstance$setBaseValue(attributeIns, config.scale());
packets.add(FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(entityId, Collections.singletonList(attributeIns)));
}
this.spawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
this.part = new FurnitureHitboxPart(entityId, aabb, pos, false);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(MiscUtils.init(new IntArrayList(), l -> l.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 prepareBoundingBox(WorldPosition targetPos, Consumer<AABB> aabbConsumer, boolean ignoreBlocksBuilding) {
if (this.blocksBuilding || ignoreBlocksBuilding) {
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,89 @@
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.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.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) {
Object attributeIns = FastNMS.INSTANCE.constructor$AttributeInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
FastNMS.INSTANCE.method$AttributeInstance$setBaseValue(attributeIns, config.scale());
this.packets.add(FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(this.entityId, Collections.singletonList(attributeIns)));
}
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 prepareBoundingBox(WorldPosition targetPos, Consumer<AABB> aabbConsumer, boolean ignoreBlocksBuilding) {
if (this.blocksBuilding || ignoreBlocksBuilding) {
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,77 @@
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.util.MiscUtils;
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(MiscUtils.init(new IntArrayList(), l -> l.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 prepareBoundingBox(WorldPosition targetPos, Consumer<AABB> aabbConsumer, boolean ignoreBlocksBuilding) {
if (this.blocksBuilding || ignoreBlocksBuilding) {
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,135 @@
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) {
Object attributeIns = FastNMS.INSTANCE.constructor$AttributeInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
FastNMS.INSTANCE.method$AttributeInstance$setBaseValue(attributeIns, config.scale());
packets.add(FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(this.entityIds[1], Collections.singletonList(attributeIns)));
}
config.spawner().accept(entityIds, position.world(), x, y, z, yaw, offset, packets::add, colliders::add, parts::add);
this.parts = parts;
this.colliders = colliders;
this.spawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList(entityIds));
}
@Override
public List<Collider> colliders() {
return this.colliders;
}
@Override
public List<FurnitureHitboxPart> parts() {
return this.parts;
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
for (int entityId : entityIds) {
collector.accept(entityId);
}
}
@Override
public void show(Player player) {
player.sendPacket(this.spawnPacket, false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
@Override
public ShulkerFurnitureHitboxConfig config() {
return this.config;
}
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
if (config.interactionEntity()) {
if (config.direction().stepY() != 0) {
// 展示实体 // 潜影贝 // 交互实体
return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()};
} else {
// 展示实体 // 潜影贝 // 交互实体1 // 交互实体2
return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()};
}
} else {
// 展示实体 // 潜影贝
return new int[] {entityIdSupplier.get(), entityIdSupplier.get()};
}
}
}

View File

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

View File

@@ -61,6 +61,7 @@ public class BukkitSeatManager implements SeatManager, Listener {
@Override
public void delayedInit() {
Bukkit.getPluginManager().registerEvents(this.dismountListener, this.plugin.javaPlugin());
Bukkit.getPluginManager().registerEvents(this, this.plugin.javaPlugin());
}
@Override

View File

@@ -101,7 +101,7 @@ public class AxeItemBehavior extends ItemBehavior {
// resend swing if it's not interactable on client side
if (!InteractUtils.isInteractable(
bukkitPlayer, BlockStateUtils.fromBlockData(customState.vanillaBlockState().literalObject()),
bukkitPlayer, BlockStateUtils.fromBlockData(customState.visualBlockState().literalObject()),
context.getHitResult(), item
) || player.isSecondaryUseActive()) {
player.swingHand(context.getHand());

View File

@@ -112,7 +112,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
} else {
ImmutableBlockState customState = optionalCustomState.get();
// custom block
if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.vanillaBlockState().literalObject() : againstBlockState)) {
if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.visualBlockState().literalObject() : againstBlockState)) {
return InteractionResult.FAIL;
}
}

View File

@@ -8,6 +8,7 @@ public class BukkitItemBehaviors extends ItemBehaviors {
public static final Key BLOCK_ITEM = Key.from("craftengine:block_item");
public static final Key ON_LIQUID_BLOCK_ITEM = Key.from("craftengine:liquid_collision_block_item");
public static final Key FURNITURE_ITEM = Key.from("craftengine:furniture_item");
public static final Key ON_LIQUID_FURNITURE_ITEM = Key.from("craftengine:liquid_collision_furniture_item");
public static final Key FLINT_AND_STEEL_ITEM = Key.from("craftengine:flint_and_steel_item");
public static final Key COMPOSTABLE_ITEM = Key.from("craftengine:compostable_item");
public static final Key AXE_ITEM = Key.from("craftengine:axe_item");
@@ -21,6 +22,7 @@ public class BukkitItemBehaviors extends ItemBehaviors {
register(BLOCK_ITEM, BlockItemBehavior.FACTORY);
register(ON_LIQUID_BLOCK_ITEM, LiquidCollisionBlockItemBehavior.FACTORY);
register(FURNITURE_ITEM, FurnitureItemBehavior.FACTORY);
register(ON_LIQUID_FURNITURE_ITEM, LiquidCollisionFurnitureItemBehavior.FACTORY);
register(FLINT_AND_STEEL_ITEM, FlintAndSteelItemBehavior.FACTORY);
register(COMPOSTABLE_ITEM, CompostableItemBehavior.FACTORY);
register(AXE_ITEM, AxeItemBehavior.FACTORY);

View File

@@ -77,10 +77,10 @@ public class FlintAndSteelItemBehavior extends ItemBehavior {
// 点击对象为自定义方块
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
// 原版外观也可燃
if (BlockStateUtils.isBurnable(immutableBlockState.vanillaBlockState().literalObject())) {
if (BlockStateUtils.isBurnable(immutableBlockState.visualBlockState().literalObject())) {
return InteractionResult.PASS;
}
BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject());
BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject());
// 点击的是方块上面则只需要判断shift和可交互
if (direction == Direction.UP) {
// 客户端层面必须可交互

View File

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

View File

@@ -0,0 +1,157 @@
package net.momirealms.craftengine.bukkit.item.behavior;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.AlignmentRule;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.RotationRule;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.PendingConfigSection;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockHitResult;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.*;
public class LiquidCollisionFurnitureItemBehavior extends FurnitureItemBehavior {
public static final Factory FACTORY = new Factory();
private final List<String> liquidTypes;
private final boolean sourceOnly;
public LiquidCollisionFurnitureItemBehavior(Key id, Map<AnchorType, Rule> rules, boolean ignorePlacer, boolean ignoreEntities, boolean sourceOnly, List<String> liquidTypes) {
super(id, rules, ignorePlacer, ignoreEntities);
this.liquidTypes = liquidTypes;
this.sourceOnly = sourceOnly;
}
@Override
public InteractionResult useOnBlock(UseOnContext context) {
return use(context.getLevel(), context.getPlayer(), context.getHand());
}
@Override
public InteractionResult use(World world, @Nullable Player player, InteractionHand hand) {
try {
if (player == null) return InteractionResult.FAIL;
Object blockHitResult = CoreReflections.method$Item$getPlayerPOVHitResult.invoke(null, world.serverWorld(), player.serverPlayer(), CoreReflections.instance$ClipContext$Fluid$ANY);
Object blockPos = FastNMS.INSTANCE.field$BlockHitResult$blockPos(blockHitResult);
BlockPos above = new BlockPos(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos), FastNMS.INSTANCE.field$Vec3i$z(blockPos));
Direction direction = DirectionUtils.fromNMSDirection(FastNMS.INSTANCE.field$BlockHitResul$direction(blockHitResult));
boolean miss = FastNMS.INSTANCE.field$BlockHitResul$miss(blockHitResult);
Vec3d hitPos = LocationUtils.fromVec(CoreReflections.field$HitResult$location.get(blockHitResult));
Object fluidType = FastNMS.INSTANCE.method$FluidState$getType(FastNMS.INSTANCE.method$BlockGetter$getFluidState(world.serverWorld(), blockPos));
if (fluidType == MFluids.EMPTY) {
return InteractionResult.PASS;
}
String liquid = null;
if (fluidType == MFluids.LAVA) {
liquid = "lava";
} else if (fluidType == MFluids.WATER) {
liquid = "water";
} else if (fluidType == MFluids.FLOWING_LAVA) {
if (this.sourceOnly) return InteractionResult.PASS;
liquid = "lava";
} else if (fluidType == MFluids.FLOWING_WATER) {
if (this.sourceOnly) return InteractionResult.PASS;
liquid = "water";
}
if (!this.liquidTypes.contains(liquid)) {
return InteractionResult.PASS;
}
if (miss) {
return super.useOnBlock(new UseOnContext(player, hand, BlockHitResult.miss(hitPos, direction, above)));
} else {
boolean inside = CoreReflections.field$BlockHitResult$inside.getBoolean(blockHitResult);
return super.useOnBlock(new UseOnContext(player, hand, new BlockHitResult(hitPos, direction, above, inside)));
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Error handling use", e);
return InteractionResult.FAIL;
}
}
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
Object id = arguments.get("furniture");
if (id == null) {
throw new LocalizedResourceConfigException("warning.config.item.behavior.furniture.missing_furniture", new IllegalArgumentException("Missing required parameter 'furniture' for furniture_item behavior"));
}
Map<String, Object> rulesMap = ResourceConfigUtils.getAsMapOrNull(arguments.get("rules"), "rules");
Key furnitureId;
if (id instanceof Map<?,?> map) {
Map<String, Object> furnitureSection;
if (map.containsKey(key.toString())) {
// 防呆
furnitureSection = MiscUtils.castToMap(map.get(key.toString()), false);
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, furnitureSection));
} else {
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);
}
}
}
}
}
}
} else {
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 LiquidCollisionFurnitureItemBehavior(furnitureId, rules,
ResourceConfigUtils.getAsBoolean(arguments.get("ignore-placer"), "ignore-placer"),
ResourceConfigUtils.getAsBoolean(arguments.get("ignore-entities"), "ignore-entities"),
ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("source-only", true), "source-only"),
MiscUtils.getAsStringList(arguments.get("liquid-type"))
);
}
}
}

View File

@@ -3,22 +3,21 @@ package net.momirealms.craftengine.bukkit.item.listener;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.bukkit.util.ItemStackUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.util.MiscUtils;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -44,12 +43,10 @@ public class DebugStickListener implements Listener {
public void onUseDebugStick(PlayerInteractEvent event) {
Block clickedBlock = event.getClickedBlock();
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();
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")) {
return;
}
@@ -73,8 +70,7 @@ public class DebugStickListener implements Listener {
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.empty").arguments(Component.text(blockId))), true);
player.sendPacket(systemChatPacket, false);
} else {
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(itemInHand);
Object storedData = wrapped.getJavaTag("craftengine:debug_stick_state");
Object storedData = itemInHand.getJavaTag("craftengine:debug_stick_state");
if (storedData == null) storedData = new HashMap<>();
if (storedData instanceof Map<?,?> map) {
Map<String, Object> data = new HashMap<>(MiscUtils.castToMap(map, false));
@@ -96,7 +92,7 @@ public class DebugStickListener implements Listener {
} else {
currentProperty = getRelative(properties, currentProperty, player.isSecondaryUseActive());
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(
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.select")
.arguments(

View File

@@ -21,6 +21,7 @@ import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.ItemSettings;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.item.context.UseOnContext;
@@ -54,20 +55,16 @@ import org.bukkit.event.enchantment.PrepareItemEnchantEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerItemConsumeEvent;
import org.bukkit.inventory.EnchantingInventory;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
public class ItemEventListener implements Listener {
private final BukkitCraftEngine plugin;
@@ -167,7 +164,7 @@ public class ItemEventListener implements Listener {
// fix client side issues
if (action.isRightClick() && hitResult != null &&
InteractUtils.canPlaceVisualBlock(player, BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()), hitResult, itemInHand)) {
InteractUtils.canPlaceVisualBlock(player, BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject()), hitResult, itemInHand)) {
player.updateInventory();
}
@@ -272,13 +269,13 @@ public class ItemEventListener implements Listener {
if (immutableBlockState != null) {
// client won't have sounds if the clientside block is interactable
// so we should check and resend sounds on BlockPlaceEvent
BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject());
BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject());
if (InteractUtils.isInteractable(player, craftBlockData, hitResult, itemInHand)) {
if (!serverPlayer.isSecondaryUseActive()) {
serverPlayer.setResendSound();
}
} else {
if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().literalObject()) && !BlockStateUtils.isReplaceable(immutableBlockState.vanillaBlockState().literalObject())) {
if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().literalObject()) && !BlockStateUtils.isReplaceable(immutableBlockState.visualBlockState().literalObject())) {
serverPlayer.setResendSwing();
}
}
@@ -426,7 +423,7 @@ public class ItemEventListener implements Listener {
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onConsumeItem(PlayerItemConsumeEvent event) {
ItemStack consumedItem = event.getItem();
if (ItemStackUtils.isEmpty(consumedItem)) return;
@@ -435,9 +432,11 @@ public class ItemEventListener implements Listener {
if (optionalCustomItem.isEmpty()) {
return;
}
Player player = event.getPlayer();
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled);
CustomItem<ItemStack> customItem = optionalCustomItem.get();
PlayerOptionalContext context = PlayerOptionalContext.of(BukkitAdaptors.adapt(event.getPlayer()), ContextHolder.builder()
PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder()
.withParameter(DirectContextParameters.ITEM_IN_HAND, wrapped)
.withParameter(DirectContextParameters.EVENT, cancellable)
.withParameter(DirectContextParameters.HAND, event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND)
@@ -448,11 +447,19 @@ public class ItemEventListener implements Listener {
}
if (event.getPlayer().getGameMode() != GameMode.CREATIVE) {
Key replacement = customItem.settings().consumeReplacement();
if (replacement == null) {
event.setReplacement(null);
if (wrapped.count() == 1) {
if (replacement != null) {
ItemStack replacementItem = this.plugin.itemManager().buildItemStack(replacement, serverPlayer);
event.setReplacement(replacementItem);
}
} else {
ItemStack replacementItem = this.plugin.itemManager().buildItemStack(replacement, BukkitAdaptors.adapt(event.getPlayer()));
event.setReplacement(replacementItem);
// fixme 如何取消堆叠数量>1的物品的默认replacement
if (replacement != null) {
Item<ItemStack> replacementItem = this.plugin.itemManager().createWrappedItem(replacement, serverPlayer);
if (replacementItem != null) {
PlayerUtils.giveItem(serverPlayer, 1, replacementItem);
}
}
}
}
}
@@ -620,4 +627,114 @@ public class ItemEventListener implements Listener {
}
event.setCurrentItem((ItemStack) result.finalItem().getItem());
}
@SuppressWarnings("DuplicatedCode")
@EventHandler(ignoreCancelled = true)
public void onPlayerDeath(PlayerDeathEvent event) {
BukkitItemManager instance = BukkitItemManager.instance();
// 处理损毁物品
if (event.getKeepInventory()) {
if (!instance.featureFlag$destroyOnDeathChance()) return;
Random random = ThreadLocalRandom.current();
PlayerInventory inventory = event.getPlayer().getInventory();
for (ItemStack item : inventory.getContents()) {
if (item == null) continue;
Optional<CustomItem<ItemStack>> optional = instance.wrap(item).getCustomItem();
if (optional.isEmpty()) continue;
CustomItem<ItemStack> customItem = optional.get();
ItemSettings settings = customItem.settings();
float destroyChance = settings.destroyOnDeathChance();
if (destroyChance <= 0f) continue;
int totalAmount = item.getAmount();
int destroyCount = 0;
for (int i = 0; i < totalAmount; i++) {
float rand = random.nextFloat();
// 判断是否损毁
if (destroyChance > 0f && rand < destroyChance) {
destroyCount++;
}
}
if (destroyCount != 0) {
item.setAmount(totalAmount - destroyCount);
}
}
}
// 处理保留 + 损毁物品
else {
if (!instance.featureFlag$keepOnDeathChance() && !instance.featureFlag$destroyOnDeathChance()) return;
Random random = ThreadLocalRandom.current();
List<ItemStack> itemsToKeep = event.getItemsToKeep();
List<ItemStack> itemsToDrop = event.getDrops();
Iterator<ItemStack> iterator = itemsToDrop.iterator();
while (iterator.hasNext()) {
ItemStack item = iterator.next();
Optional<CustomItem<ItemStack>> optional = instance.wrap(item).getCustomItem();
if (optional.isEmpty()) continue;
CustomItem<ItemStack> customItem = optional.get();
ItemSettings settings = customItem.settings();
float destroyChance = settings.destroyOnDeathChance();
float keepChance = settings.keepOnDeathChance();
// 如果没有效果,跳过
if (destroyChance <= 0f && keepChance <= 0f) continue;
int totalAmount = item.getAmount();
int keepCount = 0;
int destroyCount = 0;
int dropCount = 0;
for (int i = 0; i < totalAmount; i++) {
float rand = random.nextFloat();
// 先判断是否损毁
if (destroyChance > 0f && rand < destroyChance) {
destroyCount++;
}
// 然后判断是否保留(在未损毁的物品中)
else if (keepChance > 0f && rand < (destroyChance + keepChance)) {
keepCount++;
}
// 否则掉落
else {
dropCount++;
}
}
// 处理结果
if (destroyCount == totalAmount) {
iterator.remove();
continue;
}
if (keepCount == 0 && dropCount == 0) {
// 实际上不会发生这种情况
continue;
}
if (keepCount > 0) {
ItemStack keepItem = item.clone();
keepItem.setAmount(keepCount);
itemsToKeep.add(keepItem);
}
if (dropCount > 0) {
item.setAmount(dropCount);
} else {
iterator.remove();
}
}
}
}
}

View File

@@ -407,8 +407,12 @@ public class RecipeEventListener implements Listener {
}
boolean hasResult = true;
int realDurabilityPerItem = (int) (repairItem.amount() + repairItem.percent() * maxDamage);
if (realDurabilityPerItem == 0) {
return;
}
int consumeMaxAmount = damage / realDurabilityPerItem + 1;
int actualConsumedAmount = Math.min(consumeMaxAmount, wrappedSecond.count());
int actualRepairAmount = actualConsumedAmount * realDurabilityPerItem;

View File

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

View File

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

View File

@@ -7,7 +7,8 @@ import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors;
import net.momirealms.craftengine.bukkit.block.entity.renderer.element.BukkitBlockEntityElementConfigs;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.BukkitHitBoxTypes;
import net.momirealms.craftengine.bukkit.entity.furniture.element.BukkitFurnitureElementConfigs;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.BukkitFurnitureHitboxTypes;
import net.momirealms.craftengine.bukkit.entity.projectile.BukkitProjectileManager;
import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeatManager;
import net.momirealms.craftengine.bukkit.font.BukkitFontManager;
@@ -26,6 +27,7 @@ import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.sound.BukkitSoundManager;
import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
import net.momirealms.craftengine.bukkit.world.score.BukkitTeamManager;
import net.momirealms.craftengine.core.item.ItemManager;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender;
@@ -160,8 +162,9 @@ public class BukkitCraftEngine extends CraftEngine {
super.onPluginLoad();
BukkitBlockBehaviors.init();
BukkitItemBehaviors.init();
BukkitHitBoxTypes.init();
BukkitFurnitureHitboxTypes.init();
BukkitBlockEntityElementConfigs.init();
BukkitFurnitureElementConfigs.init();
// 初始化 onload 阶段的兼容性
super.compatibilityManager().onLoad();
// 创建网络管理器
@@ -190,6 +193,8 @@ public class BukkitCraftEngine extends CraftEngine {
super.seatManager = new BukkitSeatManager(this);
// 初始化家具管理器
super.furnitureManager = new BukkitFurnitureManager(this);
// 初始化队伍管理器
super.teamManager = new BukkitTeamManager(this);
// 注册默认的parser
this.registerDefaultParsers();
// 完成加载
@@ -369,6 +374,11 @@ public class BukkitCraftEngine extends CraftEngine {
return (BukkitFontManager) fontManager;
}
@Override
public BukkitWorldManager worldManager() {
return (BukkitWorldManager) worldManager;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
public void saveResource(String resourcePath) {

View File

@@ -1,24 +1,18 @@
package net.momirealms.craftengine.bukkit.plugin;
import com.google.gson.JsonElement;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistryOps;
import net.momirealms.craftengine.bukkit.util.ParticleUtils;
import net.momirealms.craftengine.bukkit.world.particle.BukkitParticleType;
import net.momirealms.craftengine.core.plugin.Platform;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.particle.ParticleType;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.Tag;
import org.bukkit.Bukkit;
import org.bukkit.Particle;
import java.util.Map;
public class BukkitPlatform implements Platform {
private final BukkitCraftEngine plugin;
@@ -31,34 +25,11 @@ public class BukkitPlatform implements Platform {
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
public Tag jsonToSparrowNBT(JsonElement 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
public Tag javaToSparrowNBT(Object object) {
return MRegistryOps.JAVA.convertTo(MRegistryOps.SPARROW_NBT, object);

View File

@@ -42,12 +42,16 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new SearchUsageAdminCommand(this, plugin),
new TestCommand(this, plugin),
new SetLocaleCommand(this, plugin),
new SetDisplayEntityViewDistanceScaleCommand(this, plugin),
new SetEntityCullingDistanceScaleCommand(this, plugin),
new ToggleEntityCullingCommand(this, plugin),
new UnsetLocaleCommand(this, plugin),
new DebugGetBlockStateRegistryIdCommand(this, plugin),
new DebugGetBlockInternalIdCommand(this, plugin),
new DebugAppearanceStateUsageCommand(this, plugin),
new DebugClearCooldownCommand(this, plugin),
new DebugEntityIdCommand(this, plugin),
new DebugFurnitureCommand(this, plugin),
new DebugRealStateUsageCommand(this, plugin),
new DebugItemDataCommand(this, plugin),
new DebugSetBlockCommand(this, plugin),

View File

@@ -78,7 +78,7 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature<CommandSender>
Set<BlockStateWrapper> ids = new HashSet<>();
for (CustomBlock customBlock : instance.loadedBlocks().values()) {
for (ImmutableBlockState state : customBlock.variantProvider().states()) {
ids.add(state.vanillaBlockState());
ids.add(state.visualBlockState());
}
}
VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator();

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

@@ -13,6 +13,7 @@ import org.incendo.cloud.parser.standard.StringParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
@@ -139,7 +140,7 @@ public class DebugGenerateInternalAssetsCommand extends BukkitCommandFeature<Com
private void collectListJson(Path folder, String prefix, Consumer<String> callback) {
try (InputStream inputStream = Files.newInputStream(folder.resolve("_list.json"))) {
String s = prefix.isEmpty() ? "" : (prefix + "/");
JsonObject listJson = JsonParser.parseReader(new InputStreamReader(inputStream)).getAsJsonObject();
JsonObject listJson = JsonParser.parseReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).getAsJsonObject();
JsonArray fileList = listJson.getAsJsonArray("files");
for (JsonElement element : fileList) {
if (element instanceof JsonPrimitive primitive) {

View File

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

View File

@@ -0,0 +1,42 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
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.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.DoubleParser;
public class SetDisplayEntityViewDistanceScaleCommand extends BukkitCommandFeature<CommandSender> {
public SetDisplayEntityViewDistanceScaleCommand(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())
.required("scale", DoubleParser.doubleParser(0.125, 8))
.handler(context -> {
Player player = context.get("player");
double scale = context.get("scale");
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
serverPlayer.setDisplayEntityViewDistanceScale(scale);
handleFeedback(context, MessageConstants.COMMAND_DISPLAY_ENTITY_VIEW_DISTANCE_SCALE_SET_SUCCESS, Component.text(scale), Component.text(player.getName()));
});
}
@Override
public String getFeatureID() {
return "set_display_entity_view_distance_scale";
}
}

View File

@@ -0,0 +1,42 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
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.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.DoubleParser;
public class SetEntityCullingDistanceScaleCommand extends BukkitCommandFeature<CommandSender> {
public SetEntityCullingDistanceScaleCommand(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())
.required("scale", DoubleParser.doubleParser(0.125, 8))
.handler(context -> {
Player player = context.get("player");
double scale = context.get("scale");
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
serverPlayer.setEntityCullingDistanceScale(scale);
handleFeedback(context, MessageConstants.COMMAND_ENTITY_CULLING_DISTANCE_SCALE_SET_SUCCESS, Component.text(scale), Component.text(player.getName()));
});
}
@Override
public String getFeatureID() {
return "set_entity_culling_distance_scale";
}
}

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

@@ -283,10 +283,10 @@ public final class WorldStorageInjector {
if (Config.enableLightSystem()) {
if (previousImmutableBlockState.isEmpty()) {
// 原版块到自定义块,只需要判断新块是否和客户端视觉一致
updateLight(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, x, y, z);
updateLight(holder, newImmutableBlockState.visualBlockState().literalObject(), newState, x, y, z);
} else {
// 自定义块到自定义块
updateLight$complex(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, previousState, x, y, z);
updateLight$complex(holder, newImmutableBlockState.visualBlockState().literalObject(), newState, previousState, x, y, z);
}
}
} else {
@@ -311,7 +311,7 @@ public final class WorldStorageInjector {
}
if (Config.enableLightSystem()) {
// 自定义块到原版块,只需要判断旧块是否和客户端一直
BlockStateWrapper wrapper = previous.vanillaBlockState();
BlockStateWrapper wrapper = previous.visualBlockState();
if (wrapper != null) {
updateLight(holder, wrapper.literalObject(), previousState, x, y, z);
}

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