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

Merge branch 'dev' into dev

This commit is contained in:
Halogly
2025-10-02 01:53:46 +08:00
committed by GitHub
512 changed files with 26088 additions and 13983 deletions

View File

@@ -54,10 +54,10 @@ The code you contribute will be open-sourced under the GPLv3 license. If you pre
3. Once done, submit a **pull request** to **dev** branch for review. We appreciate your contributions!
## Differences Between Versions
| Version | Official Support | Max Players | Dev Builds |
|-------------------|------------------|-------------|------------|
| Community Edition | ❌ No | 30 | ❌ No |
| Premium Edition | ✔️ Yes | Unlimited | ✔️ Yes |
| Version | Official Support | Exclusive Features | Dev Builds |
|-------------------|------------------|--------------------|------------|
| Community Edition | ❌ No | ❌ No | ❌ No |
| Premium Edition | ✔️ Yes | ✔️ Yes | ✔️ Yes |
### 💖 Support the Developer
Help sustain CraftEngine's development by going Premium!
@@ -75,7 +75,7 @@ repositories {
```
```kotlin
dependencies {
compileOnly("net.momirealms:craft-engine-core:0.0.63")
compileOnly("net.momirealms:craft-engine-bukkit:0.0.63")
compileOnly("net.momirealms:craft-engine-core:0.0.64")
compileOnly("net.momirealms:craft-engine-bukkit:0.0.64")
}
```

View File

@@ -101,6 +101,10 @@ tasks {
relocate("com.google.common.jimfs", "net.momirealms.craftengine.libraries.jimfs")
relocate("org.apache.commons", "net.momirealms.craftengine.libraries.commons")
relocate("io.leangen.geantyref", "net.momirealms.craftengine.libraries.geantyref")
relocate("io.netty.handler.codec.http", "net.momirealms.craftengine.libraries.netty.handler.codec.http")
relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp")
relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy")
relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
}
}

View File

@@ -15,6 +15,7 @@ repositories {
maven("https://repo.dmulloy2.net/repository/public/") // mcmmo required
maven("https://repo.auxilor.io/repository/maven-public/") // eco
maven("https://repo.hiusers.com/releases") // zaphkiel
maven("https://jitpack.io") // sxitem slimefun
}
dependencies {
@@ -70,6 +71,14 @@ dependencies {
compileOnly("com.github.Archy-X:AureliumSkills:Beta1.3.21")
// Zaphkiel
compileOnly("ink.ptms:ZaphkielAPI:2.1.0")
// WorldGuard
compileOnly(files("${rootProject.rootDir}/libs/worldguard-bukkit-7.0.14-dist.jar"))
// HeadDatabase
compileOnly("com.arcaniax:HeadDatabase-API:1.3.2")
// SXItem
compileOnly("com.github.Saukiya:SX-Item:4.4.6")
// Slimefun
compileOnly("io.github.Slimefun:Slimefun4:RC-32")
}
java {

View File

@@ -5,12 +5,14 @@ import net.momirealms.craftengine.bukkit.compatibility.item.*;
import net.momirealms.craftengine.bukkit.compatibility.legacy.slimeworld.LegacySlimeFormatStorageAdaptor;
import net.momirealms.craftengine.bukkit.compatibility.leveler.*;
import net.momirealms.craftengine.bukkit.compatibility.model.bettermodel.BetterModelModel;
import net.momirealms.craftengine.bukkit.compatibility.model.bettermodel.BetterModelUtils;
import net.momirealms.craftengine.bukkit.compatibility.model.modelengine.ModelEngineModel;
import net.momirealms.craftengine.bukkit.compatibility.model.modelengine.ModelEngineUtils;
import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicItemDropListener;
import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicSkillHelper;
import net.momirealms.craftengine.bukkit.compatibility.papi.PlaceholderAPIUtils;
import net.momirealms.craftengine.bukkit.compatibility.permission.LuckPermsEventListeners;
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.viaversion.ViaVersionUtils;
@@ -18,11 +20,16 @@ import net.momirealms.craftengine.bukkit.compatibility.worldedit.WorldEditBlockR
import net.momirealms.craftengine.bukkit.font.BukkitFontManager;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.core.block.BlockManager;
import net.momirealms.craftengine.core.entity.furniture.ExternalModel;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.loot.LootConditions;
import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager;
import net.momirealms.craftengine.core.plugin.compatibility.LevelerProvider;
import net.momirealms.craftengine.core.plugin.compatibility.ModelProvider;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.context.condition.AlwaysFalseCondition;
import net.momirealms.craftengine.core.plugin.context.event.EventConditions;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.WorldManager;
@@ -117,6 +124,23 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
new MythicItemDropListener(this.plugin);
logHook("MythicMobs");
}
Key worldGuardRegion = Key.of("worldguard:region");
if (this.isPluginEnabled("WorldGuard")) {
EventConditions.register(worldGuardRegion, new WorldGuardRegionCondition.FactoryImpl<>());
LootConditions.register(worldGuardRegion, new WorldGuardRegionCondition.FactoryImpl<>());
logHook("WorldGuard");
} else {
EventConditions.register(worldGuardRegion, new AlwaysFalseCondition.FactoryImpl<>());
LootConditions.register(worldGuardRegion, new AlwaysFalseCondition.FactoryImpl<>());
}
if (this.isPluginEnabled("BetterModel")) {
BetterModelUtils.registerConstantBlockEntityRender();
logHook("BetterModel");
}
if (this.isPluginEnabled("ModelEngine")) {
ModelEngineUtils.registerConstantBlockEntityRender();
logHook("ModelEngine");
}
}
@Override
@@ -224,8 +248,8 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
private void initWorldEditHook() {
WorldEditBlockRegister weBlockRegister = new WorldEditBlockRegister(BukkitBlockManager.instance(), false);
try {
for (Key newBlockId : BukkitBlockManager.instance().blockRegisterOrder()) {
weBlockRegister.register(newBlockId);
for (int i = 0; i < Config.serverSideBlocks(); i++) {
weBlockRegister.register(BlockManager.createCustomBlockKey(i));
}
} catch (Exception e) {
this.plugin.logger().warn("Failed to initialize world edit hook", e);
@@ -250,6 +274,18 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
itemManager.registerExternalItemSource(new ZaphkielSource());
logHook("Zaphkiel");
}
if (this.isPluginEnabled("HeadDatabase")) {
itemManager.registerExternalItemSource(new HeadDatabaseSource());
logHook("HeadDatabase");
}
if (this.isPluginEnabled("SX-Item")) {
itemManager.registerExternalItemSource(new SXItemSource());
logHook("SX-Item");
}
if (this.isPluginEnabled("Slimefun")) {
itemManager.registerExternalItemSource(new SlimefunSource());
logHook("Slimefun");
}
}
private Plugin getPlugin(String name) {

View File

@@ -0,0 +1,33 @@
package net.momirealms.craftengine.bukkit.compatibility.item;
import me.arcaniax.hdb.api.HeadDatabaseAPI;
import net.momirealms.craftengine.core.item.ExternalItemSource;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
public class HeadDatabaseSource implements ExternalItemSource<ItemStack> {
private HeadDatabaseAPI api;
@Override
public String plugin() {
return "headdatabase";
}
@Nullable
@Override
public ItemStack build(String id, ItemBuildContext context) {
if (api == null) {
api = new HeadDatabaseAPI();
}
return api.getItemHead(id);
}
@Override
public String id(ItemStack item) {
if (api == null) {
api = new HeadDatabaseAPI();
}
return api.getItemID(item);
}
}

View File

@@ -1,11 +1,18 @@
package net.momirealms.craftengine.bukkit.compatibility.item;
import io.lumine.mythic.api.adapters.AbstractPlayer;
import io.lumine.mythic.api.skills.SkillCaster;
import io.lumine.mythic.bukkit.BukkitAdapter;
import io.lumine.mythic.bukkit.MythicBukkit;
import io.lumine.mythic.core.drops.DropMetadataImpl;
import net.momirealms.craftengine.core.item.ExternalItemSource;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public class MythicMobsSource implements ExternalItemSource<ItemStack> {
private MythicBukkit mythicBukkit;
@@ -20,7 +27,18 @@ public class MythicMobsSource implements ExternalItemSource<ItemStack> {
if (mythicBukkit == null || mythicBukkit.isClosed()) {
this.mythicBukkit = MythicBukkit.inst();
}
return mythicBukkit.getItemManager().getItemStack(id);
return Optional.ofNullable(context.player())
.map(p -> (Player) p.platformPlayer())
.map(p -> {
AbstractPlayer target = BukkitAdapter.adapt(p);
SkillCaster caster = mythicBukkit.getSkillManager().getCaster(target);
DropMetadataImpl meta = new DropMetadataImpl(caster, target);
return mythicBukkit.getItemManager().getItem(id)
.map(i -> i.generateItemStack(meta, 1))
.map(BukkitAdapter::adapt)
.orElse(null);
})
.orElseGet(() -> mythicBukkit.getItemManager().getItemStack(id));
}
@Override

View File

@@ -0,0 +1,29 @@
package net.momirealms.craftengine.bukkit.compatibility.item;
import github.saukiya.sxitem.SXItem;
import net.momirealms.craftengine.core.item.ExternalItemSource;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public class SXItemSource implements ExternalItemSource<ItemStack> {
@Override
public String plugin() {
return "sx-item";
}
@Nullable
@Override
public ItemStack build(String id, ItemBuildContext context) {
return SXItem.getItemManager().getItem(id, Optional.ofNullable(context.player()).map(p -> (Player) p.platformPlayer()).orElse(null));
}
@Override
public String id(ItemStack item) {
return SXItem.getItemManager().getItemKey(item);
}
}

View File

@@ -0,0 +1,28 @@
package net.momirealms.craftengine.bukkit.compatibility.item;
import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem;
import net.momirealms.craftengine.core.item.ExternalItemSource;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public class SlimefunSource implements ExternalItemSource<ItemStack> {
@Override
public String plugin() {
return "slimefun";
}
@Nullable
@Override
public ItemStack build(String id, ItemBuildContext context) {
return Optional.ofNullable(SlimefunItem.getById(id)).map(SlimefunItem::getItem).orElse(null);
}
@Override
public String id(ItemStack item) {
return Optional.ofNullable(SlimefunItem.getByItem(item)).map(SlimefunItem::getId).orElse(null);
}
}

View File

@@ -0,0 +1,62 @@
package net.momirealms.craftengine.bukkit.compatibility.model.bettermodel;
import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.data.renderer.ModelRenderer;
import kr.toxicity.model.api.tracker.DummyTracker;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import org.bukkit.Location;
import org.joml.Vector3f;
public class BetterModelBlockEntityElement implements BlockEntityElement {
private DummyTracker dummyTracker;
private final Location location;
private final BetterModelBlockEntityElementConfig config;
public BetterModelBlockEntityElement(World world, BlockPos pos, BetterModelBlockEntityElementConfig config) {
this.config = config;
Vector3f position = config.position();
this.location = new Location((org.bukkit.World) world.platformWorld(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, config.yaw(), config.pitch());
this.dummyTracker = createDummyTracker();
}
private DummyTracker createDummyTracker() {
ModelRenderer modelRenderer = BetterModel.plugin().modelManager().renderer(this.config.model());
if (modelRenderer == null) {
return null;
} else {
return modelRenderer.create(this.location);
}
}
@Override
public void hide(Player player) {
if (this.dummyTracker != null) {
this.dummyTracker.remove((org.bukkit.entity.Player) player.platformPlayer());
}
}
@Override
public void show(Player player) {
if (this.dummyTracker != null) {
this.dummyTracker.spawn((org.bukkit.entity.Player) player.platformPlayer());
}
}
@Override
public void deactivate() {
if (this.dummyTracker != null) {
this.dummyTracker.close();
this.dummyTracker = null;
}
}
@Override
public void activate() {
if (this.dummyTracker == null) {
this.dummyTracker = createDummyTracker();
}
}
}

View File

@@ -0,0 +1,61 @@
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;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import org.joml.Vector3f;
import java.util.Map;
public class BetterModelBlockEntityElementConfig implements BlockEntityElementConfig<BetterModelBlockEntityElement> {
private final Vector3f position;
private final float yaw;
private final float pitch;
private final String model;
public BetterModelBlockEntityElementConfig(String model, Vector3f position, float yaw, float pitch) {
this.pitch = pitch;
this.position = position;
this.yaw = yaw;
this.model = model;
}
public String model() {
return model;
}
public float pitch() {
return pitch;
}
public Vector3f position() {
return position;
}
public float yaw() {
return yaw;
}
@Override
public BetterModelBlockEntityElement create(World world, BlockPos pos) {
return new BetterModelBlockEntityElement(world, pos, this);
}
public static class Factory implements BlockEntityElementConfigFactory {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> 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(
model,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch")
);
}
}
}

View File

@@ -2,6 +2,8 @@ package net.momirealms.craftengine.bukkit.compatibility.model.bettermodel;
import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.data.renderer.ModelRenderer;
import net.momirealms.craftengine.bukkit.block.entity.renderer.element.BukkitBlockEntityElementConfigs;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.entity.Entity;
public class BetterModelUtils {
@@ -13,4 +15,8 @@ public class BetterModelUtils {
}
renderer.create(base);
}
public static void registerConstantBlockEntityRender() {
BukkitBlockEntityElementConfigs.register(Key.of("craftengine:better_model"), new BetterModelBlockEntityElementConfig.Factory());
}
}

View File

@@ -0,0 +1,69 @@
package net.momirealms.craftengine.bukkit.compatibility.model.modelengine;
import com.ticxo.modelengine.api.ModelEngineAPI;
import com.ticxo.modelengine.api.entity.Dummy;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import org.bukkit.Location;
import org.joml.Vector3f;
// TODO not tested yet
public class ModelEngineBlockEntityElement implements BlockEntityElement {
private Dummy<?> dummy;
private final Location location;
private final ModelEngineBlockEntityElementConfig config;
public ModelEngineBlockEntityElement(World world, BlockPos pos, ModelEngineBlockEntityElementConfig config) {
this.config = config;
Vector3f position = config.position();
this.location = new Location((org.bukkit.World) world.platformWorld(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, config.yaw(), config.pitch());
this.dummy = createDummy();
}
private Dummy<?> createDummy() {
ActiveModel activeModel = ModelEngineAPI.createActiveModel(config.model());
if (activeModel == null) {
return null;
} else {
Dummy<?> dummy = new Dummy<>();
dummy.setLocation(this.location);
dummy.setDetectingPlayers(false);
ModeledEntity modeledEntity = ModelEngineAPI.createModeledEntity(dummy);
modeledEntity.addModel(activeModel, false);
return dummy;
}
}
@Override
public void hide(Player player) {
if (this.dummy != null) {
this.dummy.setForceViewing((org.bukkit.entity.Player) player.platformPlayer(), true);
}
}
@Override
public void show(Player player) {
if (this.dummy != null) {
this.dummy.setForceHidden((org.bukkit.entity.Player) player.platformPlayer(), true);
}
}
@Override
public void deactivate() {
if (this.dummy != null) {
this.dummy.setRemoved(true);
this.dummy = null;
}
}
@Override
public void activate() {
if (this.dummy == null) {
this.dummy = createDummy();
}
}
}

View File

@@ -0,0 +1,61 @@
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;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import org.joml.Vector3f;
import java.util.Map;
public class ModelEngineBlockEntityElementConfig implements BlockEntityElementConfig<ModelEngineBlockEntityElement> {
private final Vector3f position;
private final float yaw;
private final float pitch;
private final String model;
public ModelEngineBlockEntityElementConfig(String model, Vector3f position, float yaw, float pitch) {
this.pitch = pitch;
this.position = position;
this.yaw = yaw;
this.model = model;
}
public String model() {
return model;
}
public float pitch() {
return pitch;
}
public Vector3f position() {
return position;
}
public float yaw() {
return yaw;
}
@Override
public ModelEngineBlockEntityElement create(World world, BlockPos pos) {
return new ModelEngineBlockEntityElement(world, pos, this);
}
public static class Factory implements BlockEntityElementConfigFactory {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> 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(
model,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch")
);
}
}
}

View File

@@ -3,6 +3,8 @@ package net.momirealms.craftengine.bukkit.compatibility.model.modelengine;
import com.ticxo.modelengine.api.ModelEngineAPI;
import com.ticxo.modelengine.api.model.ActiveModel;
import com.ticxo.modelengine.api.model.ModeledEntity;
import net.momirealms.craftengine.bukkit.block.entity.renderer.element.BukkitBlockEntityElementConfigs;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.entity.Entity;
public class ModelEngineUtils {
@@ -24,4 +26,8 @@ public class ModelEngineUtils {
}
return entityId;
}
public static void registerConstantBlockEntityRender() {
BukkitBlockEntityElementConfigs.register(Key.of("craftengine:model_engine"), new ModelEngineBlockEntityElementConfig.Factory());
}
}

View File

@@ -13,7 +13,7 @@ import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.MCUtils;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
@@ -33,7 +33,7 @@ public class MythicItemDrop extends ItemDrop implements IItemDrop {
@Override
public AbstractItemStack getDrop(DropMetadata dropMetadata, double amount) {
ItemBuildContext context = ItemBuildContext.EMPTY;
ItemBuildContext context = ItemBuildContext.empty();
SkillCaster caster = dropMetadata.getCaster();
if (caster != null && caster.getEntity() instanceof AbstractPlayer abstractPlayer) {
Entity bukkitEntity = abstractPlayer.getBukkitEntity();
@@ -42,7 +42,7 @@ public class MythicItemDrop extends ItemDrop implements IItemDrop {
context = ItemBuildContext.of(player);
}
}
int amountInt = MCUtils.fastFloor(amount + 0.5F);
int amountInt = MiscUtils.fastFloor(amount + 0.5F);
ItemStack itemStack = this.customItem.buildItemStack(context, amountInt);
return adapt(itemStack).amount(amountInt);
}

View File

@@ -0,0 +1,98 @@
package net.momirealms.craftengine.bukkit.compatibility.region;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
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.WorldPosition;
import org.bukkit.World;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Predicate;
public class WorldGuardRegionCondition<CTX extends Context> implements Condition<CTX> {
private static final Key TYPE = Key.of("worldguard:region");
private final MatchMode mode;
private final List<String> regions;
public WorldGuardRegionCondition(MatchMode mode, List<String> regions) {
this.mode = mode;
this.regions = regions;
}
@Override
public boolean test(CTX ctx) {
if (this.regions.isEmpty()) return false;
Optional<WorldPosition> optionalPos = ctx.getOptionalParameter(DirectContextParameters.POSITION);
if (optionalPos.isEmpty()) {
return false;
}
WorldPosition position = optionalPos.get();
RegionManager regionManager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt((World) position.world().platformWorld()));
if (regionManager != null) {
ApplicableRegionSet set = regionManager.getApplicableRegions(BlockVector3.at(position.x(), position.y(), position.z()));
List<String> regionsAtThisPos = new ArrayList<>(set.size());
for (ProtectedRegion region : set) {
String id = region.getId();
regionsAtThisPos.add(id);
}
Predicate<String> predicate = regionsAtThisPos::contains;
return this.mode.matcher.apply(predicate, this.regions);
}
return false;
}
@Override
public Key type() {
return TYPE;
}
public enum MatchMode {
ANY((p, regions) -> {
for (String region : regions) {
if (p.test(region)) {
return true;
}
}
return false;
}),
ALL((p, regions) -> {
for (String region : regions) {
if (!p.test(region)) {
return false;
}
}
return true;
});
private final BiFunction<Predicate<String>, List<String>, Boolean> matcher;
MatchMode(BiFunction<Predicate<String>, List<String>, Boolean> matcher) {
this.matcher = matcher;
}
}
public static class FactoryImpl<CTX extends Context> implements ConditionFactory<CTX> {
@Override
public Condition<CTX> create(Map<String, Object> arguments) {
int mode = ResourceConfigUtils.getAsInt(arguments.getOrDefault("mode", 1), "mode") - 1;
MatchMode matchMode = MatchMode.values()[mode];
List<String> regions = MiscUtils.getAsStringList(arguments.get("regions"));
return new WorldGuardRegionCondition<>(matchMode, regions);
}
}
}

View File

@@ -33,7 +33,6 @@ public class ExprCustomItem extends SimpleExpression<ItemType> {
private Expression<?> itemIds;
@Override
@SuppressWarnings("unchecked")
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) {
itemIds = exprs[0];
return true;
@@ -49,7 +48,7 @@ public class ExprCustomItem extends SimpleExpression<ItemType> {
if (object instanceof String string) {
CustomItem<ItemStack> customItem = CraftEngineItems.byId(Key.of(string));
if (customItem != null) {
ItemType itemType = new ItemType(customItem.buildItemStack(ItemBuildContext.EMPTY));
ItemType itemType = new ItemType(customItem.buildItemStack(ItemBuildContext.empty()));
items.add(itemType);
}
}

View File

@@ -10,6 +10,8 @@ repositories {
dependencies {
// Platform
compileOnly("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT")
// authlib
compileOnly("com.mojang:authlib:6.0.58")
}
java {

View File

@@ -0,0 +1,16 @@
package net.momirealms.craftengine.bukkit.util;
import com.mojang.authlib.GameProfile;
import java.util.UUID;
public class LegacyAuthLibUtils {
public static String getName(GameProfile profile) {
return profile.getName();
}
public static UUID getId(GameProfile profile) {
return profile.getId();
}
}

View File

@@ -50,7 +50,7 @@ bukkit {
name = "CraftEngine"
apiVersion = "1.20"
authors = listOf("XiaoMoMi")
contributors = listOf("jhqwqmc", "iqtesterr", "WhiteProject1", "Catnies", "xiaozhangup", "TamashiiMon", "Halogly", "ArubikU", "Maxsh001", "Sasha2294", "MrPanda8")
contributors = listOf("https://github.com/Xiao-MoMi/craft-engine/graphs/contributors")
softDepend = listOf("PlaceholderAPI", "WorldEdit", "FastAsyncWorldEdit", "Skript")
foliaSupported = true
}
@@ -81,5 +81,9 @@ tasks {
relocate("org.apache.commons", "net.momirealms.craftengine.libraries.commons")
relocate("io.leangen.geantyref", "net.momirealms.craftengine.libraries.geantyref")
relocate("ca.spottedleaf.concurrentutil", "net.momirealms.craftengine.libraries.concurrentutil")
relocate("io.netty.handler.codec.http", "net.momirealms.craftengine.libraries.netty.handler.codec.http")
relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp")
relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy")
relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
}
}

View File

@@ -8,7 +8,7 @@ public class BukkitCraftEnginePlugin extends JavaPlugin {
public BukkitCraftEnginePlugin() {
this.plugin = new BukkitCraftEngine(this);
this.plugin.applyDependencies();
this.plugin.setUpConfig();
this.plugin.setUpConfigAndLocale();
}
@Override

View File

@@ -53,7 +53,7 @@ paper {
name = "CraftEngine"
apiVersion = "1.20"
authors = listOf("XiaoMoMi")
contributors = listOf("jhqwqmc", "iqtesterrr", "WhiteProject1", "Catnies", "xiaozhangup", "TamashiiMon", "Halogly", "ArubikU", "Maxsh001", "Sasha2294", "MrPanda8")
contributors = listOf("https://github.com/Xiao-MoMi/craft-engine/graphs/contributors")
foliaSupported = true
serverDependencies {
register("PlaceholderAPI") {
@@ -86,6 +86,9 @@ paper {
register("MythicMobs") { required = false }
register("CustomFishing") { required = false }
register("Zaphkiel") { required = false }
register("HeadDatabase") { required = false }
register("SX-Item") { required = false }
register("Slimefun") { required = false }
// leveler
register("AuraSkills") { required = false }
@@ -154,5 +157,9 @@ tasks {
relocate("org.apache.commons", "net.momirealms.craftengine.libraries.commons")
relocate("io.leangen.geantyref", "net.momirealms.craftengine.libraries.geantyref")
relocate("ca.spottedleaf.concurrentutil", "net.momirealms.craftengine.libraries.concurrentutil")
relocate("io.netty.handler.codec.http", "net.momirealms.craftengine.libraries.netty.handler.codec.http")
relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp")
relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy")
relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
}
}

View File

@@ -5,11 +5,11 @@ import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import net.momirealms.craftengine.bukkit.plugin.agent.RuntimePatcher;
import net.momirealms.craftengine.bukkit.plugin.classpath.PaperClassPathAppender;
import net.momirealms.craftengine.bukkit.plugin.classpath.BukkitClassPathAppender;
import net.momirealms.craftengine.bukkit.plugin.classpath.PaperPluginClassPathAppender;
import net.momirealms.craftengine.core.plugin.logger.PluginLogger;
import net.momirealms.craftengine.core.plugin.logger.Slf4jPluginLogger;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
@@ -39,14 +39,24 @@ public class PaperCraftEngineBootstrap implements PluginBootstrap {
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to getLogger", e);
}
this.plugin = new BukkitCraftEngine(
logger,
context.getDataDirectory(),
new PaperClassPathAppender(this.getClass().getClassLoader())
);
try {
this.plugin = new BukkitCraftEngine(
logger,
context.getDataDirectory(),
new BukkitClassPathAppender(),
new PaperPluginClassPathAppender(this.getClass().getClassLoader())
);
} catch (UnsupportedOperationException e) {
this.plugin = new BukkitCraftEngine(
logger,
context.getDataDirectory(),
new PaperPluginClassPathAppender(this.getClass().getClassLoader()),
new PaperPluginClassPathAppender(this.getClass().getClassLoader())
);
}
this.plugin.applyDependencies();
this.plugin.setUpConfig();
if (VersionHelper.isOrAbove1_21_4()) {
this.plugin.setUpConfigAndLocale();
if (isDatapackDiscoveryAvailable()) {
new ModernEventHandler(context, this.plugin).register();
} else {
try {
@@ -58,6 +68,16 @@ public class PaperCraftEngineBootstrap implements PluginBootstrap {
}
}
private static boolean isDatapackDiscoveryAvailable() {
try {
Class<?> eventsClass = Class.forName("io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents");
eventsClass.getField("DATAPACK_DISCOVERY");
return true;
} catch (ClassNotFoundException | NoSuchFieldException e) {
return false;
}
}
@Override
public @NotNull JavaPlugin createPlugin(@NotNull PluginProviderContext context) {
return new PaperCraftEnginePlugin(this);

View File

@@ -1,17 +1,26 @@
package net.momirealms.craftengine.bukkit.advancement;
import com.google.gson.JsonElement;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.advancement.AbstractAdvancementManager;
import net.momirealms.craftengine.core.advancement.AdvancementType;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
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.VersionHelper;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
public class BukkitAdvancementManager extends AbstractAdvancementManager {
private final BukkitCraftEngine plugin;
@@ -33,7 +42,71 @@ public class BukkitAdvancementManager extends AbstractAdvancementManager {
return this.advancementParser;
}
public class AdvancementParser implements ConfigParser {
@Override
public void sendToast(Player player, Item<?> icon, Component message, AdvancementType type) {
try {
Object displayInfo = CoreReflections.constructor$DisplayInfo.newInstance(
icon.getLiteralObject(),
ComponentUtils.adventureToMinecraft(message), // title
CoreReflections.instance$Component$empty, // description
VersionHelper.isOrAbove1_20_3() ? Optional.empty() : null, // background
CoreReflections.instance$AdvancementType$values[type.ordinal()],
true, // show toast
false, // announce to chat
true // hidden
);
if (VersionHelper.isOrAbove1_20_2()) {
displayInfo = Optional.of(displayInfo);
}
Object resourceLocation = KeyUtils.toResourceLocation(Key.of("craftengine", "toast"));
Object criterion = VersionHelper.isOrAbove1_20_2() ?
CoreReflections.constructor$Criterion.newInstance(CoreReflections.constructor$ImpossibleTrigger.newInstance(), CoreReflections.constructor$ImpossibleTrigger$TriggerInstance.newInstance()) :
CoreReflections.constructor$Criterion.newInstance(CoreReflections.constructor$ImpossibleTrigger$TriggerInstance.newInstance());
Map<String, Object> criteria = Map.of("impossible", criterion);
Object advancementProgress = CoreReflections.constructor$AdvancementProgress.newInstance();
Object advancement;
if (VersionHelper.isOrAbove1_20_2()) {
Object advancementRequirements = VersionHelper.isOrAbove1_20_3() ?
CoreReflections.constructor$AdvancementRequirements.newInstance(List.of(List.of("impossible"))) :
CoreReflections.constructor$AdvancementRequirements.newInstance((Object) new String[][] {{"impossible"}});
advancement = CoreReflections.constructor$Advancement.newInstance(
Optional.empty(),
displayInfo,
CoreReflections.instance$AdvancementRewards$EMPTY,
criteria,
advancementRequirements,
false
);
CoreReflections.method$AdvancementProgress$update.invoke(advancementProgress, advancementRequirements);
advancement = CoreReflections.constructor$AdvancementHolder.newInstance(resourceLocation, advancement);
} else {
advancement = CoreReflections.constructor$Advancement.newInstance(
resourceLocation,
null, // parent
displayInfo,
CoreReflections.instance$AdvancementRewards$EMPTY,
criteria,
new String[][] {{"impossible"}},
false
);
CoreReflections.method$AdvancementProgress$update.invoke(advancementProgress, criteria, new String[][] {{"impossible"}});
}
CoreReflections.method$AdvancementProgress$grantProgress.invoke(advancementProgress, "impossible");
Map<Object, Object> advancementsToGrant = new HashMap<>();
advancementsToGrant.put(resourceLocation, advancementProgress);
Object grantPacket = VersionHelper.isOrAbove1_21_5() ?
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<>());
player.sendPackets(List.of(grantPacket, removePacket), false);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to send toast for player " + player.name(), e);
}
}
public class AdvancementParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"advancements", "advancement"};
@Override
@@ -47,7 +120,7 @@ public class BukkitAdvancementManager extends AbstractAdvancementManager {
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (advancements.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.advancement.duplicate", path, id);
}

View File

@@ -3,37 +3,63 @@ package net.momirealms.craftengine.bukkit.api;
import net.momirealms.craftengine.bukkit.entity.BukkitEntity;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public final class BukkitAdaptors {
private BukkitAdaptors() {}
public static BukkitServerPlayer adapt(final Player player) {
/**
* Adapts a Bukkit Player to a CraftEngine BukkitServerPlayer.
* This provides access to CraftEngine-specific player functionality and data.
*
* @param player the Bukkit Player to adapt, must not be null
* @return a non-null BukkitServerPlayer instance wrapping the provided player
*/
@NotNull
public static BukkitServerPlayer adapt(@NotNull final Player player) {
return BukkitCraftEngine.instance().adapt(player);
}
public static BukkitWorld adapt(final World world) {
/**
* Adapts a Bukkit World to a CraftEngine BukkitWorld.
* This enables CraftEngine world operations on Bukkit world instances.
*
* @param world the Bukkit World to adapt, must not be null
* @return a non-null BukkitWorld instance wrapping the provided world
*/
@NotNull
public static BukkitWorld adapt(@NotNull final World world) {
return new BukkitWorld(world);
}
public static BukkitEntity adapt(final Entity entity) {
/**
* Adapts a Bukkit Entity to a CraftEngine BukkitEntity.
* This provides CraftEngine entity functionality for Bukkit entities.
*
* @param entity the Bukkit Entity to adapt, must not be null
* @return a non-null BukkitEntity instance wrapping the provided entity
*/
@NotNull
public static BukkitEntity adapt(@NotNull final Entity entity) {
return new BukkitEntity(entity);
}
public static BukkitExistingBlock adapt(final Block block) {
/**
* Adapts a Bukkit Block to a CraftEngine BukkitExistingBlock.
* This enables CraftEngine block operations on Bukkit block instances.
*
* @param block the Bukkit Block to adapt, must not be null
* @return a non-null BukkitExistingBlock instance wrapping the provided block
*/
@NotNull
public static BukkitExistingBlock adapt(@NotNull final Block block) {
return new BukkitExistingBlock(block);
}
public static Location toLocation(WorldPosition position) {
return LocationUtils.toLocation(position);
}
}

View File

@@ -27,10 +27,28 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public final class CraftEngineBlocks {
private CraftEngineBlocks() {}
/**
*
* Returns an unmodifiable map of all currently loaded custom blocks.
* The map keys represent unique identifiers, and the values are the corresponding CustomBlock instances.
*
* <p><strong>Important:</strong> Do not attempt to access this method during the onEnable phase
* as it will be empty. Instead, listen for the {@code CraftEngineReloadEvent} and use this method
* after the event is fired to obtain the complete block list.
*
* @return a non-null map containing all loaded custom blocks
*/
@NotNull
public static Map<Key, CustomBlock> loadedBlocks() {
return BukkitBlockManager.instance().loadedBlocks();
}
/**
* Gets a custom block by ID
*
@@ -170,16 +188,14 @@ public final class CraftEngineBlocks {
* @param player player who breaks the block
* @param dropLoot whether to drop block loots
* @param isMoving is moving
* @param playSound whether to play break sounds
* @param sendParticles whether to send break particles
* @param sendLevelEvent whether to send break particles and sounds
* @return success or not
*/
public static boolean remove(@NotNull Block block,
@Nullable Player player,
boolean isMoving,
boolean dropLoot,
boolean playSound,
boolean sendParticles) {
boolean sendLevelEvent) {
ImmutableBlockState state = getCustomBlockState(block);
if (state == null || state.isEmpty()) return false;
World world = new BukkitWorld(block.getWorld());
@@ -188,24 +204,43 @@ public final class CraftEngineBlocks {
if (dropLoot) {
ContextHolder.Builder builder = new ContextHolder.Builder()
.withParameter(DirectContextParameters.POSITION, position);
BukkitServerPlayer serverPlayer = BukkitCraftEngine.instance().adapt(player);
BukkitServerPlayer serverPlayer = null;
if (player != null) {
builder.withParameter(DirectContextParameters.PLAYER, serverPlayer);
serverPlayer = BukkitCraftEngine.instance().adapt(player);
builder.withOptionalParameter(DirectContextParameters.PLAYER, serverPlayer);
}
for (Item<?> item : state.getDrops(builder, world, serverPlayer)) {
world.dropItemNaturally(position, item);
}
}
if (playSound) {
world.playBlockSound(position, state.settings().sounds().breakSound());
}
if (sendParticles) {
if (sendLevelEvent) {
FastNMS.INSTANCE.method$LevelAccessor$levelEvent(world.serverWorld(), WorldEvents.BLOCK_BREAK_EFFECT, LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), state.customBlockState().registryId());
}
FastNMS.INSTANCE.method$Level$removeBlock(world.serverWorld(), LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), isMoving);
return true;
}
/**
* Removes a block from the world if it's custom
*
* @param block block to remove
* @param player player who breaks the block
* @param dropLoot whether to drop block loots
* @param isMoving is moving
* @param playSound whether to play break sounds
* @param sendParticles whether to send break particles
* @return success or not
*/
@Deprecated(forRemoval = true)
public static boolean remove(@NotNull Block block,
@Nullable Player player,
boolean isMoving,
boolean dropLoot,
boolean playSound,
boolean sendParticles) {
return remove(block, player, dropLoot, isMoving, playSound || sendParticles);
}
/**
* Checks if a block is custom
*
@@ -251,4 +286,14 @@ public final class CraftEngineBlocks {
public static BlockData getBukkitBlockData(@NotNull ImmutableBlockState blockState) {
return BlockStateUtils.fromBlockData(blockState.customBlockState().literalObject());
}
/**
* Checks if the block state is a vanilla block state
*
* @param id state id
* @return is vanilla block or not
*/
public static boolean isVanillaBlockState(int id) {
return BukkitBlockManager.instance().isVanillaBlockState(id);
}
}

View File

@@ -27,11 +27,27 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
public final class CraftEngineFurniture {
private CraftEngineFurniture() {}
/**
* Returns an unmodifiable map of all currently loaded custom furniture.
* The map keys represent unique identifiers, and the values are the corresponding CustomFurniture instances.
*
* <p><strong>Important:</strong> Do not attempt to access this method during the onEnable phase
* as it will be empty. Instead, listen for the {@code CraftEngineReloadEvent} and use this method
* after the event is fired to obtain the complete furniture list.
*
* @return a non-null map containing all loaded custom furniture
*/
@NotNull
public static Map<Key, CustomFurniture> loadedFurniture() {
return BukkitFurnitureManager.instance().loadedFurniture();
}
/**
* Gets custom furniture by ID
*

View File

@@ -0,0 +1,40 @@
package net.momirealms.craftengine.bukkit.api;
import net.momirealms.craftengine.bukkit.font.BukkitFontManager;
import net.momirealms.craftengine.core.font.BitmapImage;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public final class CraftEngineImages {
private CraftEngineImages() {}
/**
* Returns an unmodifiable map of all currently loaded custom images.
* The map keys represent unique identifiers, and the values are the corresponding BitmapImage instances.
*
* <p><strong>Important:</strong> Do not attempt to access this method during the onEnable phase
* as it will be empty. Instead, listen for the {@code CraftEngineReloadEvent} and use this method
* after the event is fired to obtain the complete image list.
*
* @return a non-null map containing all loaded custom images
*/
@NotNull
public static Map<Key, BitmapImage> loadedImages() {
return BukkitFontManager.instance().loadedImages();
}
/**
* Gets a custom image by ID
*
* @param id id
* @return the custom image
*/
@Nullable
public static BitmapImage byId(@NotNull Key id) {
return BukkitFontManager.instance().loadedImages().get(id);
}
}

View File

@@ -8,10 +8,28 @@ import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public final class CraftEngineItems {
private CraftEngineItems() {}
/**
* Returns an unmodifiable map of all currently loaded custom items.
* The map keys represent unique identifiers, and the values are the corresponding CustomItem instances.
*
* <p><strong>Important:</strong> Do not attempt to access this method during the onEnable phase
* as it will be empty. Instead, listen for the {@code CraftEngineReloadEvent} and use this method
* after the event is fired to obtain the complete item list.
*
* @return a non-null map containing all loaded custom items
* @throws IllegalStateException if the BukkitItemManager instance is not available
*/
@NotNull
public static Map<Key, CustomItem<ItemStack>> loadedItems() {
return BukkitItemManager.instance().loadedItems();
}
/**
* Gets a custom item by ID
*

View File

@@ -0,0 +1,67 @@
package net.momirealms.craftengine.bukkit.api.event;
import net.momirealms.craftengine.core.pack.PackCacheData;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* This event is triggered when a user executes the "/ce reload pack" command.
* <p>
* The event initiates a process that caches all resource content into a virtual file system
* to ensure optimal build performance. To add your resource pack through this event,
* you must use the {@link #registerExternalResourcePack(Path)} method every time this event is called.
* </p>
* <p>
* Important: The caching system will not update your resource pack if its file size or
* last modification time remains unchanged between reloads. Ensure these attributes change
* if you need the cache to recognize updates.
* </p>
*/
public class AsyncResourcePackCacheEvent extends Event {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final PackCacheData cacheData;
public AsyncResourcePackCacheEvent(@NotNull PackCacheData cacheData) {
super(true);
this.cacheData = cacheData;
}
@NotNull
public PackCacheData cacheData() {
return this.cacheData;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
@NotNull
public HandlerList getHandlers() {
return getHandlerList();
}
/**
* Adds an external resource pack to the cache.
* <p>
* This method accepts either a .zip file or a directory path representing a resource pack.
* The resource pack will be added to the appropriate cache collection based on its type.
* </p>
*
* @param path the file system path to the resource pack. Must be either a .zip file or a directory.
* @throws IllegalArgumentException if the provided path is neither a .zip file nor a directory.
*/
public void registerExternalResourcePack(@NotNull final Path path) {
if (Files.isRegularFile(path) && path.getFileName().endsWith(".zip")) {
this.cacheData.externalZips().add(path);
} else if (Files.isDirectory(path)) {
this.cacheData.externalFolders().add(path);
} else {
throw new IllegalArgumentException("Illegal resource pack path: " + path);
}
}
}

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.SoundSource;
import net.momirealms.craftengine.core.util.Cancellable;
import net.momirealms.craftengine.core.util.ItemUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
@@ -44,13 +45,11 @@ import java.util.Optional;
public final class BlockEventListener implements Listener {
private final BukkitCraftEngine plugin;
private final boolean enableNoteBlockCheck;
private final BukkitBlockManager manager;
public BlockEventListener(BukkitCraftEngine plugin, BukkitBlockManager manager, boolean enableNoteBlockCheck) {
public BlockEventListener(BukkitCraftEngine plugin, BukkitBlockManager manager) {
this.plugin = plugin;
this.manager = manager;
this.enableNoteBlockCheck = enableNoteBlockCheck;
}
@EventHandler(ignoreCancelled = true)
@@ -74,18 +73,14 @@ public final class BlockEventListener implements Listener {
// send sound if the placed block's sounds are removed
if (Config.enableSoundSystem()) {
Block block = event.getBlock();
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
if (blockState != MBlocks.AIR$defaultState) {
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
if (this.manager.isBlockSoundRemoved(ownerBlock)) {
Object blockState = BlockStateUtils.getBlockState(block);
if (blockState != MBlocks.AIR$defaultState && BlockStateUtils.isVanillaBlock(blockState)) {
Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState);
Object soundEvent = FastNMS.INSTANCE.field$SoundType$placeSound(soundType);
Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent);
if (this.manager.isPlaceSoundMissing(soundId)) {
if (player.getInventory().getItemInMainHand().getType() != Material.DEBUG_STICK) {
try {
Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock);
Object placeSound = CoreReflections.field$SoundType$placeSound.get(soundType);
player.playSound(block.getLocation().add(0.5, 0.5, 0.5), FastNMS.INSTANCE.field$SoundEvent$location(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to get sound type", e);
}
player.playSound(block.getLocation().add(0.5, 0.5, 0.5), soundId.toString(), SoundCategory.BLOCKS, 1f, 0.8f);
}
return;
}
@@ -93,23 +88,19 @@ public final class BlockEventListener implements Listener {
}
// resend sound if the clicked block is interactable on client side
if (serverPlayer.shouldResendSound()) {
try {
Block block = event.getBlock();
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock);
Object placeSound = CoreReflections.field$SoundType$placeSound.get(soundType);
player.playSound(block.getLocation().add(0.5, 0.5, 0.5), FastNMS.INSTANCE.field$SoundEvent$location(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to get sound type", e);
}
Block block = event.getBlock();
Object blockState = BlockStateUtils.getBlockState(block);
Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState);
Object soundEvent = FastNMS.INSTANCE.field$SoundType$placeSound(soundType);
Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent);
player.playSound(block.getLocation().add(0.5, 0.5, 0.5), soundId.toString(), SoundCategory.BLOCKS, 1f, 0.8f);
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPlayerBreak(BlockBreakEvent event) {
org.bukkit.block.Block block = event.getBlock();
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
Object blockState = BlockStateUtils.getBlockState(block);
int stateId = BlockStateUtils.blockStateToId(blockState);
Player player = event.getPlayer();
Location location = block.getLocation();
@@ -168,12 +159,15 @@ public final class BlockEventListener implements Listener {
}
// play sound
world.playBlockSound(position, state.settings().sounds().breakSound());
serverPlayer.playSound(position, state.settings().sounds().breakSound(), SoundSource.BLOCK);
}
} else {
// override vanilla block loots
if (player.getGameMode() != GameMode.CREATIVE) {
this.plugin.vanillaLootManager().getBlockLoot(stateId).ifPresent(it -> {
if (!event.isDropItems()) {
return;
}
if (it.override()) {
event.setDropItems(false);
event.setExpToDrop(0);
@@ -190,18 +184,13 @@ public final class BlockEventListener implements Listener {
}
});
}
// sound system
if (Config.enableSoundSystem()) {
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
if (this.manager.isBlockSoundRemoved(ownerBlock)) {
try {
Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock);
Object breakSound = CoreReflections.field$SoundType$breakSound.get(soundType);
block.getWorld().playSound(block.getLocation().add(0.5, 0.5, 0.5), FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to get sound type", e);
}
Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState);
Object soundEvent = FastNMS.INSTANCE.field$SoundType$breakSound(soundType);
Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent);
if (this.manager.isBreakSoundMissing(soundId)) {
player.playSound(block.getLocation().add(0.5, 0.5, 0.5), soundId.toString(), SoundCategory.BLOCKS, 1f, 0.8f);
}
}
}
@@ -258,22 +247,17 @@ public final class BlockEventListener implements Listener {
}
player.playSound(location, state.settings().sounds().stepSound().id().toString(), SoundCategory.BLOCKS, state.settings().sounds().stepSound().volume().get(), state.settings().sounds().stepSound().pitch().get());
} else if (Config.enableSoundSystem()) {
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
if (this.manager.isBlockSoundRemoved(ownerBlock)) {
try {
Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock);
Object stepSound = CoreReflections.field$SoundType$stepSound.get(soundType);
player.playSound(player.getLocation(), FastNMS.INSTANCE.field$SoundEvent$location(stepSound).toString(), SoundCategory.BLOCKS, 0.15f, 1f);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to get sound type", e);
}
Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState);
Object soundEvent = FastNMS.INSTANCE.field$SoundType$stepSound(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) {
if (!this.enableNoteBlockCheck) return;
// for vanilla blocks
if (event.getChangedType() == Material.NOTE_BLOCK) {
Block block = event.getBlock();

View File

@@ -1,23 +0,0 @@
package net.momirealms.craftengine.bukkit.block;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
public class BukkitBlockStateWrapper implements BlockStateWrapper {
private final Object blockState;
private final int registryId;
public BukkitBlockStateWrapper(Object blockState, int registryId) {
this.blockState = blockState;
this.registryId = registryId;
}
@Override
public Object literalObject() {
return this.blockState;
}
@Override
public int registryId() {
return this.registryId;
}
}

View File

@@ -1,263 +1,27 @@
package net.momirealms.craftengine.bukkit.block;
import net.momirealms.craftengine.bukkit.block.behavior.UnsafeCompositeBlockBehavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.bukkit.util.SoundUtils;
import net.momirealms.craftengine.core.block.*;
import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviors;
import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.block.AbstractCustomBlock;
import net.momirealms.craftengine.core.block.BlockStateVariantProvider;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.registry.WritableRegistry;
import net.momirealms.craftengine.core.util.*;
import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.List;
import java.util.Map;
public final class BukkitCustomBlock extends AbstractCustomBlock {
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 BukkitCustomBlock(
@NotNull Key id,
public BukkitCustomBlock(
@NotNull Holder.Reference<CustomBlock> holder,
@NotNull Map<String, Property<?>> properties,
@NotNull Map<String, Integer> appearances,
@NotNull Map<String, BlockStateVariant> variantMapper,
@NotNull BlockSettings settings,
@NotNull BlockStateVariantProvider variantProvider,
@NotNull Map<EventTrigger, List<Function<PlayerOptionalContext>>> events,
@Nullable List<Map<String, Object>> behavior,
@Nullable LootTable<?> lootTable
) {
super(id, holder, properties, appearances, variantMapper, settings, events, behavior, lootTable);
}
@Override
protected BlockBehavior setupBehavior(List<Map<String, Object>> behaviorConfig) {
if (behaviorConfig == null || behaviorConfig.isEmpty()) {
return new EmptyBlockBehavior();
} else if (behaviorConfig.size() == 1) {
return BlockBehaviors.fromMap(this, behaviorConfig.getFirst());
} else {
List<AbstractBlockBehavior> behaviors = new ArrayList<>();
for (Map<String, Object> config : behaviorConfig) {
behaviors.add((AbstractBlockBehavior) BlockBehaviors.fromMap(this, config));
}
return new UnsafeCompositeBlockBehavior(this, behaviors);
}
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public LootTable<ItemStack> lootTable() {
return (LootTable<ItemStack>) super.lootTable();
}
@Override
protected void applyPlatformSettings() {
try {
for (ImmutableBlockState immutableBlockState : variantProvider().states()) {
if (immutableBlockState.vanillaBlockState() == null) {
CraftEngine.instance().logger().warn("Could not find vanilla block immutableBlockState for " + immutableBlockState + ". This might cause errors!");
continue;
} else if (immutableBlockState.customBlockState() == null) {
CraftEngine.instance().logger().warn("Could not find custom block immutableBlockState for " + immutableBlockState + ". This might cause errors!");
continue;
}
DelegatingBlockState nmsState = (DelegatingBlockState) immutableBlockState.customBlockState().literalObject();
nmsState.setBlockState(immutableBlockState);
BlockSettings settings = immutableBlockState.settings();
// set block properties
CoreReflections.field$BlockStateBase$lightEmission.set(nmsState, settings.luminance());
CoreReflections.field$BlockStateBase$burnable.set(nmsState, settings.burnable());
CoreReflections.field$BlockStateBase$hardness.set(nmsState, settings.hardness());
CoreReflections.field$BlockStateBase$replaceable.set(nmsState, settings.replaceable());
Object mcMapColor = CoreReflections.method$MapColor$byId.invoke(null, settings.mapColor().id);
CoreReflections.field$BlockStateBase$mapColor.set(nmsState, mcMapColor);
Object mcInstrument = ((Object[]) CoreReflections.method$NoteBlockInstrument$values.invoke(null))[settings.instrument().ordinal()];
CoreReflections.field$BlockStateBase$instrument.set(nmsState, mcInstrument);
Object pushReaction = ((Object[]) CoreReflections.method$PushReaction$values.invoke(null))[settings.pushReaction().ordinal()];
CoreReflections.field$BlockStateBase$pushReaction.set(nmsState, pushReaction);
boolean canOcclude = settings.canOcclude() == Tristate.UNDEFINED ? BlockStateUtils.isOcclude(immutableBlockState.vanillaBlockState().literalObject()) : settings.canOcclude().asBoolean();
CoreReflections.field$BlockStateBase$canOcclude.set(nmsState, canOcclude);
boolean useShapeForLightOcclusion = settings.useShapeForLightOcclusion() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.getBoolean(immutableBlockState.vanillaBlockState().literalObject()) : settings.useShapeForLightOcclusion().asBoolean();
CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.set(nmsState, useShapeForLightOcclusion);
CoreReflections.field$BlockStateBase$isRedstoneConductor.set(nmsState, settings.isRedstoneConductor().asBoolean() ? 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));
// set parent block properties
DelegatingBlock nmsBlock = (DelegatingBlock) BlockStateUtils.getBlockOwner(nmsState);
ObjectHolder<BlockShape> shapeHolder = nmsBlock.shapeDelegate();
shapeHolder.bindValue(new BukkitBlockShape(immutableBlockState.vanillaBlockState().literalObject(), Optional.ofNullable(immutableBlockState.settings().supportShapeBlockState()).map(it -> {
try {
Object blockState = BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(it));
if (!BlockStateUtils.isVanillaBlock(blockState)) {
throw new IllegalArgumentException("BlockState is not a Vanilla block");
}
return blockState;
} catch (IllegalArgumentException e) {
CraftEngine.instance().logger().warn("Illegal shape block state: " + it, e);
return null;
}
}).orElse(null)));
// bind behavior
ObjectHolder<BlockBehavior> behaviorHolder = nmsBlock.behaviorDelegate();
behaviorHolder.bindValue(super.behavior);
// set block side properties
CoreReflections.field$BlockBehaviour$explosionResistance.set(nmsBlock, settings.resistance());
CoreReflections.field$BlockBehaviour$friction.set(nmsBlock, settings.friction());
CoreReflections.field$BlockBehaviour$speedFactor.set(nmsBlock, settings.speedFactor());
CoreReflections.field$BlockBehaviour$jumpFactor.set(nmsBlock, settings.jumpFactor());
CoreReflections.field$BlockBehaviour$soundType.set(nmsBlock, SoundUtils.toSoundType(settings.sounds()));
// init cache
CoreReflections.method$BlockStateBase$initCache.invoke(nmsState);
boolean isConditionallyFullOpaque = canOcclude & useShapeForLightOcclusion;
if (!VersionHelper.isOrAbove1_21_2()) {
CoreReflections.field$BlockStateBase$isConditionallyFullOpaque.set(nmsState, isConditionallyFullOpaque);
}
// modify cache
if (VersionHelper.isOrAbove1_21_2()) {
int blockLight = settings.blockLight() != -1 ? settings.blockLight() : CoreReflections.field$BlockStateBase$lightBlock.getInt(immutableBlockState.vanillaBlockState().literalObject());
// set block light
CoreReflections.field$BlockStateBase$lightBlock.set(nmsState, blockLight);
// set propagates skylight
if (settings.propagatesSkylightDown() == Tristate.TRUE) {
CoreReflections.field$BlockStateBase$propagatesSkylightDown.set(nmsState, true);
} else if (settings.propagatesSkylightDown() == Tristate.FALSE) {
CoreReflections.field$BlockStateBase$propagatesSkylightDown.set(nmsState, false);
} else {
CoreReflections.field$BlockStateBase$propagatesSkylightDown.set(nmsState, CoreReflections.field$BlockStateBase$propagatesSkylightDown.getBoolean(immutableBlockState.vanillaBlockState().literalObject()));
}
} else {
Object cache = CoreReflections.field$BlockStateBase$cache.get(nmsState);
int blockLight = settings.blockLight() != -1 ? settings.blockLight() : CoreReflections.field$BlockStateBase$Cache$lightBlock.getInt(CoreReflections.field$BlockStateBase$cache.get(immutableBlockState.vanillaBlockState().literalObject()));
// set block light
CoreReflections.field$BlockStateBase$Cache$lightBlock.set(cache, blockLight);
// set propagates skylight
if (settings.propagatesSkylightDown() == Tristate.TRUE) {
CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.set(cache, true);
} else if (settings.propagatesSkylightDown() == Tristate.FALSE) {
CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.set(cache, false);
} else {
CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.set(cache, CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.getBoolean(CoreReflections.field$BlockStateBase$cache.get(immutableBlockState.vanillaBlockState().literalObject())));
}
if (!isConditionallyFullOpaque) {
CoreReflections.field$BlockStateBase$opacityIfCached.set(nmsState, blockLight);
}
}
// set fluid later
if (settings.fluidState()) {
CoreReflections.field$BlockStateBase$fluidState.set(nmsState, CoreReflections.method$FlowingFluid$getSource.invoke(MFluids.WATER, false));
} else {
CoreReflections.field$BlockStateBase$fluidState.set(nmsState, MFluids.EMPTY$defaultState);
}
// set random tick later
CoreReflections.field$BlockStateBase$isRandomlyTicking.set(nmsState, settings.isRandomlyTicking());
// bind tags
Object holder = BukkitCraftEngine.instance().blockManager().getMinecraftBlockHolder(immutableBlockState.customBlockState().registryId());
Set<Object> tags = new HashSet<>();
for (Key tag : settings.tags()) {
tags.add(CoreReflections.method$TagKey$create.invoke(null, MRegistries.BLOCK, KeyUtils.toResourceLocation(tag)));
}
CoreReflections.field$Holder$Reference$tags.set(holder, tags);
// set burning properties
if (settings.burnable()) {
CoreReflections.method$FireBlock$setFlammable.invoke(MBlocks.FIRE, nmsBlock, settings.burnChance(), settings.fireSpreadChance());
}
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to init block settings", e);
}
}
public static Builder builder(Key id) {
return new BuilderImpl(id);
}
public static class BuilderImpl implements Builder {
protected final Key id;
protected Map<String, Property<?>> properties;
protected Map<String, Integer> appearances;
protected Map<String, BlockStateVariant> variantMapper;
protected BlockSettings settings;
protected List<Map<String, Object>> behavior;
protected LootTable<?> lootTable;
protected Map<EventTrigger, List<Function<PlayerOptionalContext>>> events;
public BuilderImpl(Key id) {
this.id = id;
}
@Override
public Builder events(Map<EventTrigger, List<Function<PlayerOptionalContext>>> events) {
this.events = events;
return this;
}
@Override
public Builder appearances(Map<String, Integer> appearances) {
this.appearances = appearances;
return this;
}
@Override
public Builder behavior(List<Map<String, Object>> behavior) {
this.behavior = behavior;
return this;
}
@Override
public Builder lootTable(LootTable<?> lootTable) {
this.lootTable = lootTable;
return this;
}
@Override
public Builder properties(Map<String, Property<?>> properties) {
this.properties = properties;
return this;
}
@Override
public Builder settings(BlockSettings settings) {
this.settings = settings;
return this;
}
@Override
public Builder variantMapper(Map<String, BlockStateVariant> variantMapper) {
this.variantMapper = variantMapper;
return this;
}
@Override
public @NotNull CustomBlock build() {
// create or get block holder
Holder.Reference<CustomBlock> holder = ((WritableRegistry<CustomBlock>) BuiltInRegistries.BLOCK).getOrRegisterForHolder(ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), this.id));
return new BukkitCustomBlock(this.id, holder, this.properties, this.appearances, this.variantMapper, this.settings, this.events, this.behavior, this.lootTable);
}
super(holder, variantProvider, events, lootTable);
}
}

View File

@@ -0,0 +1,46 @@
package net.momirealms.craftengine.bukkit.block;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.block.AbstractBlockStateWrapper;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.Key;
import java.util.Optional;
public class BukkitCustomBlockStateWrapper extends AbstractBlockStateWrapper {
public BukkitCustomBlockStateWrapper(Object blockState, int registryId) {
super(blockState, registryId);
}
@Override
public Key ownerId() {
return getImmutableBlockState().map(state -> state.owner().value().id()).orElseGet(() -> BlockStateUtils.getBlockOwnerIdFromState(super.blockState));
}
@SuppressWarnings("unchecked")
@Override
public <T> T getProperty(String propertyName) {
return (T) getImmutableBlockState().map(state -> {
Property<?> property = state.owner().value().getProperty(propertyName);
if (property == null)
return null;
return state.getNullable(property);
}).orElse(null);
}
@Override
public boolean hasProperty(String propertyName) {
return getImmutableBlockState().map(state -> state.owner().value().getProperty(propertyName) != null).orElse(false);
}
@Override
public String getAsString() {
return getImmutableBlockState().map(ImmutableBlockState::toString).orElseGet(() -> BlockStateUtils.fromBlockData(super.blockState).getAsString());
}
public Optional<ImmutableBlockState> getImmutableBlockState() {
return BlockStateUtils.getOptionalCustomBlockState(super.blockState);
}
}

View File

@@ -0,0 +1,36 @@
package net.momirealms.craftengine.bukkit.block;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.block.AbstractBlockStateWrapper;
import net.momirealms.craftengine.core.block.StatePropertyAccessor;
import net.momirealms.craftengine.core.util.Key;
public class BukkitVanillaBlockStateWrapper extends AbstractBlockStateWrapper {
private final StatePropertyAccessor accessor;
public BukkitVanillaBlockStateWrapper(Object blockState, int registryId) {
super(blockState, registryId);
this.accessor = FastNMS.INSTANCE.createStatePropertyAccessor(blockState);
}
@Override
public Key ownerId() {
return BlockStateUtils.getBlockOwnerIdFromState(super.blockState);
}
@Override
public <T> T getProperty(String propertyName) {
return this.accessor.getPropertyValue(propertyName);
}
@Override
public boolean hasProperty(String propertyName) {
return this.accessor.hasProperty(propertyName);
}
@Override
public String getAsString() {
return BlockStateUtils.fromBlockData(super.blockState).getAsString();
}
}

View File

@@ -1,51 +0,0 @@
package net.momirealms.craftengine.bukkit.block;
import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.bukkit.entity.FallingBlock;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import java.util.Optional;
@SuppressWarnings("DuplicatedCode")
public final class FallingBlockRemoveListener implements Listener {
@EventHandler
public void onFallingBlockBreak(org.bukkit.event.entity.EntityRemoveEvent event) {
if (event.getCause() == org.bukkit.event.entity.EntityRemoveEvent.Cause.DROP && event.getEntity() instanceof FallingBlock fallingBlock) {
try {
Object fallingBlockEntity = CraftBukkitReflections.field$CraftEntity$entity.get(fallingBlock);
boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity);
if (cancelDrop) return;
Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity);
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState);
if (optionalCustomState.isEmpty()) return;
ImmutableBlockState customState = optionalCustomState.get();
net.momirealms.craftengine.core.world.World world = new BukkitWorld(fallingBlock.getWorld());
WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity));
ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(DirectContextParameters.FALLING_BLOCK, true)
.withParameter(DirectContextParameters.POSITION, position);
for (Item<Object> item : customState.getDrops(builder, world, null)) {
world.dropItemNaturally(position, item);
}
Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity);
boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT);
if (!isSilent) {
world.playBlockSound(position, customState.settings().sounds().destroySound());
}
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to handle EntityRemoveEvent", e);
}
}
}
}

View File

@@ -7,7 +7,6 @@ import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldEvents;
import net.momirealms.craftengine.core.world.WorldPosition;
@@ -69,13 +68,8 @@ public abstract class AbstractCanSurviveBlockBehavior extends BukkitBlockBehavio
FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, blockPos, thisBlock, this.delay);
return state;
}
if (!canSurvive(thisBlock, new Object[] {state, level, blockPos}, () -> true)) {
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
ImmutableBlockState customState = optionalCustomState.get();
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos));
world.playBlockSound(position, customState.settings().sounds().breakSound());
FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, customState.customBlockState().registryId());
if (!FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state, level, blockPos)) {
FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, optionalCustomState.get().customBlockState().registryId());
return MBlocks.AIR$defaultState;
}
return state;

View File

@@ -0,0 +1,99 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
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.block.properties.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class AttachedStemBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<HorizontalDirection> facingProperty;
private final Key fruit;
private final Key stem;
public AttachedStemBlockBehavior(CustomBlock customBlock,
Property<HorizontalDirection> facingProperty,
Key fruit,
Key stem) {
super(customBlock);
this.facingProperty = facingProperty;
this.fruit = fruit;
this.stem = stem;
}
@Override
public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
return (VersionHelper.isOrAbove1_20_5() ? args[1] : args[3]).equals(CoreReflections.instance$PathComputationType$AIR)
&& !FastNMS.INSTANCE.field$BlockBehavior$hasCollision(thisBlock) || (boolean) superMethod.call();
}
@Override
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object state = args[0];
HorizontalDirection direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]).toHorizontalDirection();
Object neighborState = args[updateShape$neighborState];
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state);
if (optionalCustomState.isEmpty() || direction != optionalCustomState.get().get(this.facingProperty)) {
return super.updateShape(thisBlock, args, superMethod);
}
Optional<ImmutableBlockState> optionalCustomNeighborState = BlockStateUtils.getOptionalCustomBlockState(neighborState);
if (optionalCustomNeighborState.isPresent()) {
ImmutableBlockState customNeighborState = optionalCustomNeighborState.get();
if (!customNeighborState.owner().value().id().equals(this.fruit)) {
Object stemBlock = resetStemBlock();
if (stemBlock != null) return stemBlock;
}
} else {
if (this.stem.namespace().equals("minecraft")) {
Key neighborBlockId = BlockStateUtils.getBlockOwnerIdFromState(neighborState);
if (!neighborBlockId.equals(this.fruit)) {
Object stemBlock = resetStemBlock();
if (stemBlock != null) return stemBlock;
}
} else {
Object stemBlock = resetStemBlock();
if (stemBlock != null) return stemBlock;
}
}
return super.updateShape(thisBlock, args, superMethod);
}
private Object resetStemBlock() {
Optional<CustomBlock> optionalStemBlock = BukkitBlockManager.instance().blockById(this.stem);
if (optionalStemBlock.isPresent()) {
CustomBlock stemBlock = optionalStemBlock.get();
IntegerProperty ageProperty = (IntegerProperty) stemBlock.getProperty("age");
if (ageProperty == null) return stemBlock.defaultState().customBlockState().literalObject();
return stemBlock.defaultState().with(ageProperty, ageProperty.max).customBlockState().literalObject();
}
return null;
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
@SuppressWarnings("unchecked")
Property<HorizontalDirection> facingProperty = (Property<HorizontalDirection>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.attached_stem.missing_facing");
Key fruit = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("fruit"), "warning.config.block.behavior.attached_stem.missing_fruit"));
Key stem = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("stem"), "warning.config.block.behavior.attached_stem.missing_stem"));
return new AttachedStemBlockBehavior(block, facingProperty, fruit, stem);
}
}
}

View File

@@ -0,0 +1,90 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.special.FallOnBlockBehavior;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.Vec3d;
import org.bukkit.entity.Entity;
import java.util.Map;
import java.util.concurrent.Callable;
public class BouncingBlockBehavior extends BukkitBlockBehavior implements FallOnBlockBehavior {
public static final Factory FACTORY = new Factory();
private final double bounceHeight;
private final boolean syncPlayerPosition;
private final double fallDamageMultiplier;
public BouncingBlockBehavior(CustomBlock customBlock, double bounceHeight, boolean syncPlayerPosition, double fallDamageMultiplier) {
super(customBlock);
this.bounceHeight = bounceHeight;
this.syncPlayerPosition = syncPlayerPosition;
this.fallDamageMultiplier = fallDamageMultiplier;
}
@Override
public void fallOn(Object thisBlock, Object[] args, Callable<Object> superMethod) {
if (this.fallDamageMultiplier <= 0.0) return;
Object entity = args[3];
Number fallDistance = (Number) args[4];
FastNMS.INSTANCE.method$Entity$causeFallDamage(
entity, fallDistance.doubleValue() * this.fallDamageMultiplier, 1.0F,
FastNMS.INSTANCE.method$DamageSources$fall(FastNMS.INSTANCE.method$Entity$damageSources(entity))
);
}
@Override
public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object entity = args[1];
if (FastNMS.INSTANCE.method$Entity$getSharedFlag(entity, 1)) {
superMethod.call();
} else {
bounceUp(entity);
}
}
private void bounceUp(Object entity) {
Vec3d deltaMovement = LocationUtils.fromVec(FastNMS.INSTANCE.method$Entity$getDeltaMovement(entity));
if (deltaMovement.y < 0.0) {
double d = CoreReflections.clazz$LivingEntity.isInstance(entity) ? 1.0 : 0.8;
double y = -deltaMovement.y * this.bounceHeight * d;
FastNMS.INSTANCE.method$Entity$setDeltaMovement(entity, deltaMovement.x, y, deltaMovement.z);
if (CoreReflections.clazz$Player.isInstance(entity) && this.syncPlayerPosition
&& /* 防抖 -> */ y > 0.035 /* <- 防抖 */
) {
// 这里一定要延迟 1t 不然就会出问题
if (VersionHelper.isFolia()) {
Entity bukkitEntity = FastNMS.INSTANCE.method$Entity$getBukkitEntity(entity);
bukkitEntity.getScheduler().runDelayed(BukkitCraftEngine.instance().javaPlugin(),
r -> FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true),
null, 1L
);
} else {
CraftEngine.instance().scheduler().sync().runLater(
() -> FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true),
1L
);
}
}
}
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
double bounceHeight = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("bounce-height", 0.66), "bounce-height");
boolean syncPlayerPosition = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("sync-player-position", true), "sync-player-position");
double fallDamageMultiplier = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("fall-damage-multiplier", 0.5), "fall-damage-multiplier");
return new BouncingBlockBehavior(block, bounceHeight, syncPlayerPosition, fallDamageMultiplier);
}
}
}

View File

@@ -0,0 +1,99 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.*;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
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.block.properties.BooleanProperty;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
public class BuddingBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final float growthChance;
private final List<Key> blocks;
public BuddingBlockBehavior(CustomBlock customBlock, float growthChance, List<Key> blocks) {
super(customBlock);
this.growthChance = growthChance;
this.blocks = blocks;
}
@Override
public void randomTick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
if (RandomUtils.generateRandomFloat(0, 1) >= growthChance) return;
Object nmsDirection = CoreReflections.instance$Direction$values[RandomUtils.generateRandomInt(0, 6)];
Direction direction = DirectionUtils.fromNMSDirection(nmsDirection);
Object blockPos = FastNMS.INSTANCE.method$BlockPos$relative(args[2], nmsDirection);
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], blockPos);
if (canClusterGrowAtState(blockState)) {
Key blockId = blocks.getFirst();
CustomBlock firstBlock = BukkitBlockManager.instance().blockById(blockId).orElse(null);
placeWithPropertyBlock(firstBlock, blockId, direction, nmsDirection, args[1], blockPos, blockState);
} else {
Key blockId = BlockStateUtils.getOptionalCustomBlockState(blockState)
.map(it -> it.owner().value().id())
.orElseGet(() -> BlockStateUtils.getBlockOwnerIdFromState(blockState));
int blockIdIndex = blocks.indexOf(blockId);
if (blockIdIndex < 0 || blockIdIndex == blocks.size() - 1) return;
Key nextBlockId = blocks.get(blockIdIndex + 1);
CustomBlock nextBlock = BukkitBlockManager.instance().blockById(nextBlockId).orElse(null);
placeWithPropertyBlock(nextBlock, nextBlockId, direction, nmsDirection, args[1], blockPos, blockState);
}
}
@SuppressWarnings("unchecked")
private void placeWithPropertyBlock(CustomBlock customBlock, Key blockId, Direction direction, Object nmsDirection, Object level, Object blockPos, Object blockState) {
if (customBlock != null) {
ImmutableBlockState newState = customBlock.defaultState();
Property<?> facing = customBlock.getProperty("facing");
if (facing != null) {
if (facing.valueClass() == Direction.class) {
newState = newState.with((Property<Direction>) facing, direction);
} else if (facing.valueClass() == HorizontalDirection.class) {
if (!direction.axis().isHorizontal()) return;
newState = newState.with((Property<HorizontalDirection>) facing, direction.toHorizontalDirection());
}
}
BooleanProperty waterlogged = (BooleanProperty) customBlock.getProperty("waterlogged");
if (waterlogged != null) {
newState = newState.with(waterlogged, FastNMS.INSTANCE.method$FluidState$getType(FastNMS.INSTANCE.field$BlockBehaviour$BlockStateBase$fluidState(blockState)) == MFluids.WATER);
}
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, newState.customBlockState().literalObject(), 3);
} else if (blockId.namespace().equals("minecraft")) {
Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", blockId.value()));
if (block == null) return;
Object newState = FastNMS.INSTANCE.method$Block$defaultState(block);
newState = FastNMS.INSTANCE.method$StateHolder$trySetValue(newState, MBlockStateProperties.WATERLOGGED, FastNMS.INSTANCE.method$FluidState$getType(FastNMS.INSTANCE.field$BlockBehaviour$BlockStateBase$fluidState(blockState)) == MFluids.WATER);
newState = FastNMS.INSTANCE.method$StateHolder$trySetValue(newState, MBlockStateProperties.FACING, (Comparable<?>) nmsDirection);
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, newState, 3);
}
}
public static boolean canClusterGrowAtState(Object state) {
return FastNMS.INSTANCE.method$BlockStateBase$isAir(state)
|| FastNMS.INSTANCE.method$BlockStateBase$isBlock(state, MBlocks.WATER)
&& FastNMS.INSTANCE.field$FluidState$amount(FastNMS.INSTANCE.field$BlockBehaviour$BlockStateBase$fluidState(state)) == 8;
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
float growthChance = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("growth-chance", 0.2), "growth-chance");
List<Key> blocks = new ObjectArrayList<>();
MiscUtils.getAsStringList(arguments.get("blocks")).forEach(s -> blocks.add(Key.of(s)));
return new BuddingBlockBehavior(block, growthChance, blocks);
}
}
}

View File

@@ -28,6 +28,21 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key PRESSURE_PLATE_BLOCK = Key.from("craftengine:pressure_plate_block");
public static final Key DOUBLE_HIGH_BLOCK = Key.from("craftengine:double_high_block");
public static final Key CHANGE_OVER_TIME_BLOCK = Key.from("craftengine:change_over_time_block");
public static final Key SIMPLE_STORAGE_BLOCK = Key.from("craftengine:simple_storage_block");
public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block");
public static final Key SOFA_BLOCK = Key.from("craftengine:sofa_block");
public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block");
public static final Key DIRECTIONAL_ATTACHED_BLOCK = Key.from("craftengine:directional_attached_block");
public static final Key LIQUID_FLOWABLE_BLOCK = Key.from("craftengine:liquid_flowable_block");
public static final Key SIMPLE_PARTICLE_BLOCK = Key.from("craftengine:simple_particle_block");
public static final Key WALL_TORCH_PARTICLE_BLOCK = Key.from("craftengine:wall_torch_particle_block");
public static final Key FENCE_BLOCK = Key.from("craftengine:fence_block");
public static final Key BUTTON_BLOCK = Key.from("craftengine:button_block");
public static final Key FACE_ATTACHED_HORIZONTAL_DIRECTIONAL_BLOCK = Key.from("craftengine:face_attached_horizontal_directional_block");
public static final Key STEM_BLOCK = Key.from("craftengine:stem_block");
public static final Key ATTACHED_STEM_BLOCK = Key.from("craftengine:attached_stem_block");
public static final Key CHIME_BLOCK = Key.from("craftengine:chime_block");
public static final Key BUDDING_BLOCK = Key.from("craftengine:budding_block");
public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
@@ -54,5 +69,20 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(PRESSURE_PLATE_BLOCK, PressurePlateBlockBehavior.FACTORY);
register(DOUBLE_HIGH_BLOCK, DoubleHighBlockBehavior.FACTORY);
register(CHANGE_OVER_TIME_BLOCK, ChangeOverTimeBlockBehavior.FACTORY);
register(SIMPLE_STORAGE_BLOCK, SimpleStorageBlockBehavior.FACTORY);
register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY);
register(SOFA_BLOCK, SofaBlockBehavior.FACTORY);
register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY);
register(DIRECTIONAL_ATTACHED_BLOCK, DirectionalAttachedBlockBehavior.FACTORY);
register(LIQUID_FLOWABLE_BLOCK, LiquidFlowableBlockBehavior.FACTORY);
register(SIMPLE_PARTICLE_BLOCK, SimpleParticleBlockBehavior.FACTORY);
register(WALL_TORCH_PARTICLE_BLOCK, WallTorchParticleBlockBehavior.FACTORY);
register(FENCE_BLOCK, FenceBlockBehavior.FACTORY);
register(BUTTON_BLOCK, ButtonBlockBehavior.FACTORY);
register(FACE_ATTACHED_HORIZONTAL_DIRECTIONAL_BLOCK, FaceAttachedHorizontalDirectionalBlockBehavior.FACTORY);
register(STEM_BLOCK, StemBlockBehavior.FACTORY);
register(ATTACHED_STEM_BLOCK, AttachedStemBlockBehavior.FACTORY);
register(CHIME_BLOCK, ChimeBlockBehavior.FACTORY);
register(BUDDING_BLOCK, BuddingBlockBehavior.FACTORY);
}
}

View File

@@ -26,11 +26,13 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior {
protected final Set<String> customBlocksCansSurviveOn;
protected final boolean blacklistMode;
protected final boolean stackable;
protected final int maxHeight;
public BushBlockBehavior(CustomBlock block, int delay, boolean blacklist, boolean stackable, List<Object> tagsCanSurviveOn, Set<Object> blockStatesCanSurviveOn, Set<String> customBlocksCansSurviveOn) {
public BushBlockBehavior(CustomBlock block, int delay, boolean blacklist, boolean stackable, int maxHeight, List<Object> tagsCanSurviveOn, Set<Object> blockStatesCanSurviveOn, Set<String> customBlocksCansSurviveOn) {
super(block, delay);
this.blacklistMode = blacklist;
this.stackable = stackable;
this.maxHeight = maxHeight;
this.tagsCanSurviveOn = tagsCanSurviveOn;
this.blockStatesCanSurviveOn = blockStatesCanSurviveOn;
this.customBlocksCansSurviveOn = customBlocksCansSurviveOn;
@@ -42,9 +44,10 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior {
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments, false);
boolean stackable = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("stackable", false), "stackable");
int maxHeight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-height", 0), "max-height");
int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay");
boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", false), "blacklist");
return new BushBlockBehavior(block, delay, blacklistMode, stackable, tuple.left(), tuple.mid(), tuple.right());
return new BushBlockBehavior(block, delay, blacklistMode, stackable, maxHeight,tuple.left(), tuple.mid(), tuple.right());
}
}
@@ -62,7 +65,7 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior {
if (material != null) {
if (index == -1) {
// vanilla
mcBlocks.addAll(BlockStateUtils.getAllVanillaBlockStates(blockType));
mcBlocks.addAll(BlockStateUtils.getPossibleBlockStates(blockType));
} else {
mcBlocks.add(BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(blockStateStr)));
}
@@ -96,7 +99,11 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior {
} else {
ImmutableBlockState belowCustomState = optionalCustomState.get();
if (belowCustomState.owner().value() == super.customBlock) {
return this.stackable;
if (!this.stackable || this.maxHeight == 1) return false;
if (this.maxHeight > 1) {
return mayStackOn(world, belowPos);
}
return true;
}
if (this.customBlocksCansSurviveOn.contains(belowCustomState.owner().value().id().toString())) {
return !this.blacklistMode;
@@ -107,4 +114,21 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior {
}
return this.blacklistMode;
}
protected boolean mayStackOn(Object world, Object belowPos) {
int count = 1;
Object cursorPos = LocationUtils.below(belowPos);
while (count < this.maxHeight) {
Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, cursorPos);
Optional<ImmutableBlockState> belowCustomState = BlockStateUtils.getOptionalCustomBlockState(belowState);
if (belowCustomState.isPresent() && belowCustomState.get().owner().value() == super.customBlock) {
count++;
cursorPos = LocationUtils.below(cursorPos);
} else {
break;
}
}
return count < this.maxHeight;
}
}

View File

@@ -0,0 +1,217 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntitySelectors;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MGameEvents;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.properties.BooleanProperty;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class ButtonBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final BooleanProperty poweredProperty;
private final int ticksToStayPressed;
private final boolean canButtonBeActivatedByArrows;
private final SoundData buttonClickOnSound;
private final SoundData buttonClickOffSound;
public ButtonBlockBehavior(CustomBlock customBlock,
BooleanProperty powered,
int ticksToStayPressed,
boolean canButtonBeActivatedByArrows,
SoundData buttonClickOnSound,
SoundData buttonClickOffSound) {
super(customBlock);
this.poweredProperty = powered;
this.ticksToStayPressed = ticksToStayPressed;
this.canButtonBeActivatedByArrows = canButtonBeActivatedByArrows;
this.buttonClickOnSound = buttonClickOnSound;
this.buttonClickOffSound = buttonClickOffSound;
}
@Override
public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) {
if (!state.get(this.poweredProperty)) {
press(BlockStateUtils.getBlockOwner(state.customBlockState().literalObject()),
state, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()),
context.getPlayer() != null ? context.getPlayer().serverPlayer() : null);
return InteractionResult.SUCCESS_AND_CANCEL;
}
return InteractionResult.PASS;
}
@Override
public void onExplosionHit(Object thisBlock, Object[] args, Callable<Object> superMethod) {
ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null);
if (blockState == null) return;
if (FastNMS.INSTANCE.method$Explosion$canTriggerBlocks(args[3]) && !blockState.get(this.poweredProperty)) {
press(thisBlock, blockState, args[1], args[2], null);
}
}
@Override
public void affectNeighborsAfterRemoval(Object thisBlock, Object[] args, Callable<Object> superMethod) {
ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null);
if (blockState == null) return;
if (!(boolean) args[3] && blockState.get(this.poweredProperty)) {
updateNeighbours(thisBlock, blockState, args[1], args[2]);
}
}
@Override
public void onRemove(Object thisBlock, Object[] args, Callable<Object> superMethod) {
ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null);
if (blockState == null) return;
if (!(boolean) args[4] && blockState.get(this.poweredProperty)) {
updateNeighbours(thisBlock, blockState, args[1], args[2]);
}
}
@Override
public int getSignal(Object thisBlock, Object[] args, Callable<Object> superMethod) {
ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null);
if (blockState == null) return 0;
return blockState.get(this.poweredProperty) ? 15 : 0;
}
@Override
public int getDirectSignal(Object thisBlock, Object[] args, Callable<Object> superMethod) {
ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null);
if (blockState == null) return 0;
return blockState.get(this.poweredProperty)
&& FaceAttachedHorizontalDirectionalBlockBehavior.getConnectedDirection(blockState)
== DirectionUtils.fromNMSDirection(args[3]) ? 15 : 0;
}
@Override
public boolean isSignalSource(Object thisBlock, Object[] args, Callable<Object> superMethod) {
return true;
}
@Override
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) {
Object state = args[0];
Object level = args[1];
Object pos = args[2];
ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null);
if (blockState == null) return;
if (blockState.get(this.poweredProperty)) {
checkPressed(thisBlock, state, level, pos);
}
}
@Override
public void entityInside(Object thisBlock, Object[] args, Callable<Object> superMethod) {
Object state = args[0];
Object level = args[1];
Object pos = args[2];
ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null);
if (blockState == null) return;
if (this.canButtonBeActivatedByArrows && !blockState.get(this.poweredProperty)) {
checkPressed(thisBlock, state, level, pos);
}
}
private void checkPressed(Object thisBlock, Object state, Object level, Object pos) {
Object arrow = this.canButtonBeActivatedByArrows ? FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass(
level, CoreReflections.clazz$AbstractArrow, FastNMS.INSTANCE.method$AABB$move(
FastNMS.INSTANCE.method$VoxelShape$bounds(FastNMS.INSTANCE.method$BlockState$getShape(
state, level, pos, CoreReflections.instance$CollisionContext$empty
)), pos), MEntitySelectors.NO_SPECTATORS).stream().findFirst().orElse(null) : null;
boolean on = arrow != null;
ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null);
if (blockState == null) return;
boolean poweredValue = blockState.get(this.poweredProperty);
if (on != poweredValue) {
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, on).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags());
updateNeighbours(thisBlock, blockState, level, pos);
playSound(level, pos, on);
Object gameEvent = VersionHelper.isOrAbove1_20_5()
? FastNMS.INSTANCE.method$Holder$direct(on ? MGameEvents.BLOCK_ACTIVATE : MGameEvents.BLOCK_DEACTIVATE)
: on ? MGameEvents.BLOCK_ACTIVATE : MGameEvents.BLOCK_DEACTIVATE;
FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, arrow, gameEvent, pos);
}
if (on) {
FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed);
}
}
private void updateNeighbours(Object thisBlock, ImmutableBlockState state, Object level, Object pos) {
Direction direction = FaceAttachedHorizontalDirectionalBlockBehavior.getConnectedDirection(state);
if (direction == null) return;
Direction opposite = direction.opposite();
Object nmsDirection = DirectionUtils.toNMSDirection(opposite);
Object orientation = null;
if (VersionHelper.isOrAbove1_21_2()) {
@SuppressWarnings("unchecked")
Property<HorizontalDirection> facing = (Property<HorizontalDirection>) state.owner().value().getProperty("facing");
if (facing != null) {
orientation = FastNMS.INSTANCE.method$ExperimentalRedstoneUtils$initialOrientation(
level, nmsDirection, opposite.axis().isHorizontal() ? CoreReflections.instance$Direction$UP : DirectionUtils.toNMSDirection(state.get(facing).toDirection())
);
}
}
FastNMS.INSTANCE.method$Level$updateNeighborsAt(level, pos, thisBlock, orientation);
FastNMS.INSTANCE.method$Level$updateNeighborsAt(level, FastNMS.INSTANCE.method$BlockPos$relative(pos, nmsDirection), thisBlock, orientation);
}
private void playSound(Object level, Object pos, boolean on) {
SoundData soundData = getSound(on);
if (soundData == null) return;
Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(soundData.id()), Optional.empty());
FastNMS.INSTANCE.method$LevelAccessor$playSound(level, null, pos, sound, CoreReflections.instance$SoundSource$BLOCKS, soundData.volume().get(), soundData.pitch().get());
}
private SoundData getSound(boolean on) {
return on ? this.buttonClickOnSound : this.buttonClickOffSound;
}
private void press(Object thisBlock, ImmutableBlockState state, Object level, Object pos, @Nullable Object player) {
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, state.with(this.poweredProperty, true).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags());
FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed);
playSound(level, pos, true);
Object gameEvent = VersionHelper.isOrAbove1_20_5() ? FastNMS.INSTANCE.method$Holder$direct(MGameEvents.BLOCK_ACTIVATE) : MGameEvents.BLOCK_ACTIVATE;
FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, player, gameEvent, pos);
}
public static class Factory implements BlockBehaviorFactory {
@SuppressWarnings({"unchecked", "DuplicatedCode"})
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
BooleanProperty powered = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.button.missing_powered");
int ticksToStayPressed = ResourceConfigUtils.getAsInt(arguments.getOrDefault("ticks-to-stay-pressed", 30), "ticks-to-stay-pressed");
boolean canButtonBeActivatedByArrows = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-activated-by-arrows", true), "can-be-activated-by-arrows");
Map<String, Object> sounds = (Map<String, Object>) arguments.get("sounds");
SoundData buttonClickOnSound = null;
SoundData buttonClickOffSound = null;
if (sounds != null) {
buttonClickOnSound = Optional.ofNullable(sounds.get("on")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null);
buttonClickOffSound = Optional.ofNullable(sounds.get("off")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null);
}
return new ButtonBlockBehavior(block, powered, ticksToStayPressed, canButtonBeActivatedByArrows, buttonClickOnSound, buttonClickOffSound);
}
}
}

View File

@@ -0,0 +1,45 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class ChimeBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final SoundData hitSound;
public ChimeBlockBehavior(CustomBlock customBlock, SoundData hitSound) {
super(customBlock);
this.hitSound = hitSound;
}
@Override
public void onProjectileHit(Object thisBlock, Object[] args, Callable<Object> superMethod) {
Object blockPos = FastNMS.INSTANCE.field$BlockHitResult$blockPos(args[2]);
Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(hitSound.id()), Optional.empty());
FastNMS.INSTANCE.method$LevelAccessor$playSound(args[0], null, blockPos, sound, CoreReflections.instance$SoundSource$BLOCKS, hitSound.volume().get(), hitSound.pitch().get());
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
SoundData hitSound = SoundData.create(ResourceConfigUtils.requireNonNullOrThrow(
Optional.ofNullable(ResourceConfigUtils.getAsMap(arguments.get("sounds"), "sounds"))
.map(sounds -> ResourceConfigUtils.get(sounds, "projectile-hit", "chime"))
.orElse(null),
"warning.config.block.behavior.chime.missing_sounds_projectile_hit"
), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f));
return new ChimeBlockBehavior(block, hitSound);
}
}
}

View File

@@ -129,7 +129,7 @@ public class ConcretePowderBlockBehavior extends BukkitBlockBehavior {
Object mutablePos = CoreReflections.method$BlockPos$mutable.invoke(pos);
int j = Direction.values().length;
for (int k = 0; k < j; k++) {
Object direction = CoreReflections.instance$Directions[k];
Object direction = CoreReflections.instance$Direction$values[k];
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, mutablePos);
if (direction != CoreReflections.instance$Direction$DOWN || canSolidify(blockState)) {
CoreReflections.method$MutableBlockPos$setWithOffset.invoke(mutablePos, pos, direction);

View File

@@ -77,7 +77,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior {
return minGrowLight;
}
private static int getRawBrightness(Object level, Object pos) throws InvocationTargetException, IllegalAccessException {
public static int getRawBrightness(Object level, Object pos) throws InvocationTargetException, IllegalAccessException {
return (int) CoreReflections.method$BlockAndTintGetter$getRawBrightness.invoke(level, pos, 0);
}

View File

@@ -0,0 +1,177 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.BlockTags;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
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.block.properties.Property;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import java.util.*;
import java.util.concurrent.Callable;
public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<?> facingProperty;
private final boolean isSixDirection;
private final List<Object> tagsCanSurviveOn;
private final Set<Object> blockStatesCanSurviveOn;
private final Set<String> customBlocksCansSurviveOn;
private final boolean blacklistMode;
public DirectionalAttachedBlockBehavior(CustomBlock customBlock,
Property<?> facingProperty,
boolean isSixDirection,
boolean blacklist,
List<Object> tagsCanSurviveOn,
Set<Object> blockStatesCanSurviveOn,
Set<String> customBlocksCansSurviveOn) {
super(customBlock);
this.facingProperty = facingProperty;
this.isSixDirection = isSixDirection;
this.tagsCanSurviveOn = tagsCanSurviveOn;
this.blockStatesCanSurviveOn = blockStatesCanSurviveOn;
this.customBlocksCansSurviveOn = customBlocksCansSurviveOn;
this.blacklistMode = blacklist;
}
@Override
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null);
if (state == null) return args[0];
DirectionalAttachedBlockBehavior behavior = state.behavior().getAs(DirectionalAttachedBlockBehavior.class).orElse(null);
if (behavior == null) return state;
boolean flag;
if (isSixDirection) {
Direction direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]).opposite();
flag = direction == state.get(behavior.facingProperty);
} else {
HorizontalDirection direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]).opposite().toHorizontalDirection();
flag = direction == state.get(behavior.facingProperty);
}
return flag && !FastNMS.INSTANCE.method$BlockStateBase$canSurvive(args[0], args[updateShape$level], args[updateShape$blockPos]) ? MBlocks.AIR$defaultState : args[0];
}
@Override
public boolean canSurvive(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null);
if (state == null) return false;
DirectionalAttachedBlockBehavior behavior = state.behavior().getAs(DirectionalAttachedBlockBehavior.class).orElse(null);
if (behavior == null) return false;
Direction direction;
if (isSixDirection) {
direction = ((Direction) state.get(behavior.facingProperty)).opposite();
} else {
direction = ((HorizontalDirection) state.get(behavior.facingProperty)).opposite().toDirection();
}
BlockPos blockPos = LocationUtils.fromBlockPos(args[2]).relative(direction);
Object nmsPos = LocationUtils.toBlockPos(blockPos);
Object nmsState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], nmsPos);
return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, args[1], nmsPos, DirectionUtils.toNMSDirection(direction), CoreReflections.instance$SupportType$FULL)
&& mayPlaceOn(nmsState);
}
private boolean mayPlaceOn(Object state) {
for (Object tag : this.tagsCanSurviveOn) {
if (FastNMS.INSTANCE.method$BlockStateBase$is(state, tag)) {
return !this.blacklistMode;
}
}
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state);
if (optionalCustomState.isEmpty()) {
if (!this.blockStatesCanSurviveOn.isEmpty() && this.blockStatesCanSurviveOn.contains(state)) {
return !this.blacklistMode;
}
} else {
ImmutableBlockState belowCustomState = optionalCustomState.get();
if (this.customBlocksCansSurviveOn.contains(belowCustomState.owner().value().id().toString())) {
return !this.blacklistMode;
}
if (this.customBlocksCansSurviveOn.contains(belowCustomState.toString())) {
return !this.blacklistMode;
}
}
return this.blacklistMode;
}
@SuppressWarnings("unchecked")
@Override
public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) {
DirectionalAttachedBlockBehavior behavior = state.behavior().getAs(DirectionalAttachedBlockBehavior.class).orElse(null);
if (behavior == null) return null;
World level = context.getLevel();
BlockPos clickedPos = context.getClickedPos();
for (Direction direction : context.getNearestLookingDirections()) {
if (isSixDirection) {
state = state.with((Property<Direction>) behavior.facingProperty, direction.opposite());
if (FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state.customBlockState().literalObject(), level.serverWorld(), LocationUtils.toBlockPos(clickedPos))) {
return state;
}
} else if (direction.axis().isHorizontal()) {
state = state.with((Property<HorizontalDirection>) behavior.facingProperty, direction.opposite().toHorizontalDirection());
if (FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state.customBlockState().literalObject(), level.serverWorld(), LocationUtils.toBlockPos(clickedPos))) {
return state;
}
}
}
return null;
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Property<?> facing = ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.directional_attached.missing_facing");
boolean isHorizontalDirection = facing.valueClass() == HorizontalDirection.class;
boolean isDirection = facing.valueClass() == Direction.class;
if (!(isHorizontalDirection || isDirection)) {
throw new LocalizedResourceConfigException("warning.config.block.behavior.directional_attached.missing_facing");
}
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", true), "blacklist");
return new DirectionalAttachedBlockBehavior(block, facing, isDirection, blacklistMode, tuple.left(), tuple.mid(), tuple.right());
}
}
@SuppressWarnings("DuplicatedCode")
public static Tuple<List<Object>, Set<Object>, Set<String>> readTagsAndState(Map<String, Object> arguments) {
List<Object> mcTags = new ArrayList<>();
for (String tag : MiscUtils.getAsStringList(arguments.getOrDefault("attached-block-tags", List.of()))) {
mcTags.add(BlockTags.getOrCreate(Key.of(tag)));
}
Set<Object> mcBlocks = new HashSet<>();
Set<String> customBlocks = new HashSet<>();
for (String blockStateStr : MiscUtils.getAsStringList(arguments.getOrDefault("attached-blocks", List.of()))) {
int index = blockStateStr.indexOf('[');
Key blockType = index != -1 ? Key.from(blockStateStr.substring(0, index)) : Key.from(blockStateStr);
Material material = Registry.MATERIAL.get(new NamespacedKey(blockType.namespace(), blockType.value()));
if (material != null) {
if (index == -1) {
// vanilla
mcBlocks.addAll(BlockStateUtils.getPossibleBlockStates(blockType));
} else {
mcBlocks.add(BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(blockStateStr)));
}
} else {
// custom maybe
customBlocks.add(blockStateStr);
}
}
return new Tuple<>(mcTags, mcBlocks, customBlocks);
}
}

View File

@@ -16,8 +16,8 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.block.state.properties.DoorHinge;
import net.momirealms.craftengine.core.block.state.properties.DoubleBlockHalf;
import net.momirealms.craftengine.core.block.properties.type.DoorHinge;
import net.momirealms.craftengine.core.block.properties.type.DoubleBlockHalf;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;

View File

@@ -6,15 +6,16 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflect
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.*;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.block.state.properties.DoubleBlockHalf;
import net.momirealms.craftengine.core.block.properties.type.DoubleBlockHalf;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.*;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldEvents;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -50,10 +51,6 @@ public class DoubleHighBlockBehavior extends BukkitBlockBehavior {
if (anotherHalfCustomState != null && !anotherHalfCustomState.isEmpty()) return blockState;
// 破坏
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos));
world.playBlockSound(position, customState.settings().sounds().breakSound());
FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, customState.customBlockState().registryId());
return MBlocks.AIR$defaultState;
}

View File

@@ -0,0 +1,150 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
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.block.properties.Property;
import net.momirealms.craftengine.core.block.properties.type.AnchorType;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.Tuple;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
public class FaceAttachedHorizontalDirectionalBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<AnchorType> anchorTypeProperty;
private final Property<HorizontalDirection> facingProperty;
private final List<Object> tagsCanSurviveOn;
private final Set<Object> blockStatesCanSurviveOn;
private final Set<String> customBlocksCansSurviveOn;
private final boolean blacklistMode;
public FaceAttachedHorizontalDirectionalBlockBehavior(CustomBlock customBlock,
boolean blacklist,
List<Object> tagsCanSurviveOn,
Set<Object> blockStatesCanSurviveOn,
Set<String> customBlocksCansSurviveOn,
Property<AnchorType> anchorType,
Property<HorizontalDirection> facing) {
super(customBlock);
this.tagsCanSurviveOn = tagsCanSurviveOn;
this.blockStatesCanSurviveOn = blockStatesCanSurviveOn;
this.customBlocksCansSurviveOn = customBlocksCansSurviveOn;
this.blacklistMode = blacklist;
this.anchorTypeProperty = anchorType;
this.facingProperty = facing;
}
@Override
public boolean canSurvive(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Direction direction = getConnectedDirection(BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null));
if (direction == null) return false;
direction = direction.opposite();
Object nmsDirection = DirectionUtils.toNMSDirection(direction);
Object targetPos = FastNMS.INSTANCE.method$BlockPos$relative(args[2], nmsDirection);
Object targetState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], targetPos);
return canAttach(args[1], targetPos, nmsDirection, targetState) && mayPlaceOn(targetState);
}
@SuppressWarnings("unchecked")
@Override
public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) {
Property<AnchorType> face = (Property<AnchorType>) state.owner().value().getProperty("face");
Property<HorizontalDirection> facing = (Property<HorizontalDirection>) state.owner().value().getProperty("facing");
if (face == null || facing == null) return null;
for (Direction direction : context.getNearestLookingDirections()) {
if (direction.axis() == Direction.Axis.Y) {
state = state
.with(face, direction == Direction.UP ? AnchorType.CEILING : AnchorType.FLOOR)
.with(facing, context.getHorizontalDirection().toHorizontalDirection());
} else {
state = state.with(face, AnchorType.WALL).with(facing, direction.opposite().toHorizontalDirection());
}
if (FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state.customBlockState().literalObject(), context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()))) {
return state;
}
}
return null;
}
@Override
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Direction direction = getConnectedDirection(BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null));
if (direction == null) return MBlocks.AIR$defaultState;
if (DirectionUtils.toNMSDirection(direction.opposite()) == args[updateShape$direction] && !FastNMS.INSTANCE.method$BlockStateBase$canSurvive(args[0], args[updateShape$level], args[updateShape$blockPos])) {
return MBlocks.AIR$defaultState;
}
return superMethod.call();
}
private boolean mayPlaceOn(Object state) {
for (Object tag : this.tagsCanSurviveOn) {
if (FastNMS.INSTANCE.method$BlockStateBase$is(state, tag)) {
return !this.blacklistMode;
}
}
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state);
if (optionalCustomState.isEmpty()) {
if (!this.blockStatesCanSurviveOn.isEmpty() && this.blockStatesCanSurviveOn.contains(state)) {
return !this.blacklistMode;
}
} else {
ImmutableBlockState belowCustomState = optionalCustomState.get();
if (this.customBlocksCansSurviveOn.contains(belowCustomState.owner().value().id().toString())) {
return !this.blacklistMode;
}
if (this.customBlocksCansSurviveOn.contains(belowCustomState.toString())) {
return !this.blacklistMode;
}
}
return this.blacklistMode;
}
public static boolean canAttach(Object level, Object targetPos, Object direction, Object targetState) {
return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(
targetState, level, targetPos,
FastNMS.INSTANCE.method$Direction$getOpposite(direction),
CoreReflections.instance$SupportType$FULL
);
}
@Nullable
public static Direction getConnectedDirection(ImmutableBlockState state) {
if (state == null) return null;
FaceAttachedHorizontalDirectionalBlockBehavior behavior = state.behavior().getAs(FaceAttachedHorizontalDirectionalBlockBehavior.class).orElse(null);
if (behavior == null) return null;
return switch (state.get(behavior.anchorTypeProperty)) {
case CEILING -> Direction.DOWN;
case FLOOR -> Direction.UP;
default -> state.get(behavior.facingProperty).toDirection();
};
}
public static class Factory implements BlockBehaviorFactory {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Property<AnchorType> anchorType = (Property<AnchorType>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("face"), "warning.config.block.behavior.face_attached_horizontal_directional.missing_face");
Property<HorizontalDirection> facing = (Property<HorizontalDirection>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.face_attached_horizontal_directional.missing_facing");
Tuple<List<Object>, Set<Object>, Set<String>> tuple = DirectionalAttachedBlockBehavior.readTagsAndState(arguments);
boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", true), "blacklist");
return new FaceAttachedHorizontalDirectionalBlockBehavior(block, blacklistMode, tuple.left(), tuple.mid(), tuple.right(), anchorType, facing);
}
}
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
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.util.BlockStateUtils;
@@ -10,11 +11,8 @@ import net.momirealms.craftengine.core.block.BlockBehavior;
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.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.sound.SoundData;
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.WorldPosition;
@@ -26,11 +24,15 @@ public class FallingBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final float hurtAmount;
private final int maxHurt;
private final SoundData landSound;
private final SoundData destroySound;
public FallingBlockBehavior(CustomBlock block, float hurtAmount, int maxHurt) {
public FallingBlockBehavior(CustomBlock block, float hurtAmount, int maxHurt, SoundData landSound, SoundData destroySound) {
super(block);
this.hurtAmount = hurtAmount;
this.maxHurt = maxHurt;
this.landSound = landSound;
this.destroySound = destroySound;
}
@Override
@@ -67,7 +69,7 @@ public class FallingBlockBehavior extends BukkitBlockBehavior {
return;
}
Object blockState = args[0];
Object fallingBlockEntity = CoreReflections.method$FallingBlockEntity$fall.invoke(null, world, blockPos, blockState);
Object fallingBlockEntity = FastNMS.INSTANCE.createInjectedFallingBlockEntity(world, blockPos, blockState);
if (this.hurtAmount > 0 && this.maxHurt > 0) {
CoreReflections.method$FallingBlockEntity$setHurtsEntities.invoke(fallingBlockEntity, this.hurtAmount, this.maxHurt);
}
@@ -76,28 +78,18 @@ public class FallingBlockBehavior extends BukkitBlockBehavior {
@SuppressWarnings("DuplicatedCode")
@Override
public void onBrokenAfterFall(Object thisBlock, Object[] args) throws Exception {
// Use EntityRemoveEvent for 1.20.3+
if (VersionHelper.isOrAbove1_20_3()) return;
Object level = args[0];
Object fallingBlockEntity = args[2];
boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity);
if (cancelDrop) return;
Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity);
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState);
if (optionalCustomState.isEmpty()) return;
ImmutableBlockState customState = optionalCustomState.get();
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity));
ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(DirectContextParameters.FALLING_BLOCK, true)
.withParameter(DirectContextParameters.POSITION, position);
for (Item<Object> item : customState.getDrops(builder, world, null)) {
world.dropItemNaturally(position, item);
}
Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity);
boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT);
if (!isSilent) {
world.playBlockSound(position, customState.settings().sounds().destroySound());
if (!BaseEntityData.Silent.get(entityData)) {
Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity);
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState);
if (optionalCustomState.isEmpty()) return;
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity));
if (this.destroySound != null) {
world.playBlockSound(position, this.destroySound);
}
}
}
@@ -107,23 +99,33 @@ public class FallingBlockBehavior extends BukkitBlockBehavior {
Object level = args[0];
Object pos = args[1];
Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlock);
boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT);
Object blockState = args[2];
int stateId = BlockStateUtils.blockStateToId(blockState);
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(stateId);
if (immutableBlockState == null || immutableBlockState.isEmpty()) return;
if (!isSilent) {
if (!BaseEntityData.Silent.get(entityData)) {
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
world.playBlockSound(Vec3d.atCenterOf(LocationUtils.fromBlockPos(pos)), immutableBlockState.settings().sounds().landSound());
if (this.landSound != null) {
world.playBlockSound(Vec3d.atCenterOf(LocationUtils.fromBlockPos(pos)), this.landSound);
}
}
}
public static class Factory implements BlockBehaviorFactory {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
float hurtAmount = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("hurt-amount", -1f), "hurt-amount");
int hurtMax = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-hurt", -1), "max-hurt");
return new FallingBlockBehavior(block, hurtAmount, hurtMax);
Map<String, Object> sounds = (Map<String, Object>) arguments.get("sounds");
SoundData fallSound = null;
SoundData destroySound = null;
if (sounds != null) {
fallSound = Optional.ofNullable(sounds.get("fall")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null);
destroySound = Optional.ofNullable(sounds.get("destroy")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null);
}
return new FallingBlockBehavior(block, hurtAmount, hurtMax, fallSound, destroySound);
}
}
}

View File

@@ -0,0 +1,150 @@
package net.momirealms.craftengine.bukkit.block.behavior;
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.plugin.reflection.minecraft.MRegistries;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MTagKeys;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.core.block.*;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.properties.BooleanProperty;
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.context.BlockPlaceContext;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.HorizontalDirection;
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 java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class FenceBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final BooleanProperty northProperty;
private final BooleanProperty eastProperty;
private final BooleanProperty southProperty;
private final BooleanProperty westProperty;
private final Object connectableBlockTag;
private final boolean canLeash;
public FenceBlockBehavior(CustomBlock customBlock,
BooleanProperty northProperty,
BooleanProperty eastProperty,
BooleanProperty southProperty,
BooleanProperty westProperty,
Object connectableBlockTag,
boolean canLeash) {
super(customBlock);
this.northProperty = northProperty;
this.eastProperty = eastProperty;
this.southProperty = southProperty;
this.westProperty = westProperty;
this.connectableBlockTag = connectableBlockTag;
this.canLeash = canLeash;
}
@Override
public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) {
return false;
}
public boolean connectsTo(BlockStateWrapper state, boolean isSideSolid, HorizontalDirection direction) {
boolean isSameFence = this.isSameFence(state);
boolean flag = CoreReflections.clazz$FenceGateBlock.isInstance(BlockStateUtils.getBlockOwner(state.literalObject()))
? FastNMS.INSTANCE.method$FenceGateBlock$connectsToDirection(state.literalObject(), DirectionUtils.toNMSDirection(direction.toDirection()))
: FenceGateBlockBehavior.connectsToDirection(state, direction);
return !BlockUtils.isExceptionForConnection(state) && isSideSolid || isSameFence || flag;
}
private boolean isSameFence(BlockStateWrapper state) {
Object blockState = state.literalObject();
return FastNMS.INSTANCE.method$BlockStateBase$is(blockState, MTagKeys.Block$FENCES)
&& FastNMS.INSTANCE.method$BlockStateBase$is(blockState, this.connectableBlockTag)
== FastNMS.INSTANCE.method$BlockStateBase$is(this.customBlock.defaultState().customBlockState().literalObject(), this.connectableBlockTag);
}
@Override
public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) {
if (!this.canLeash) return InteractionResult.PASS;
Player player = context.getPlayer();
if (player == null) return InteractionResult.PASS;
if (FastNMS.INSTANCE.method$LeadItem$bindPlayerMobs(player.serverPlayer(), context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()))) {
player.swingHand(InteractionHand.MAIN_HAND);
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
@Override
public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) {
World level = context.getLevel();
BlockPos clickedPos = context.getClickedPos();
Object fluidState = FastNMS.INSTANCE.method$BlockGetter$getFluidState(level.serverWorld(), LocationUtils.toBlockPos(clickedPos));
BlockPos blockPos = clickedPos.north();
BlockPos blockPos1 = clickedPos.east();
BlockPos blockPos2 = clickedPos.south();
BlockPos blockPos3 = clickedPos.west();
BlockStateWrapper blockState = level.getBlockAt(blockPos).blockState();
BlockStateWrapper blockState1 = level.getBlockAt(blockPos1).blockState();
BlockStateWrapper blockState2 = level.getBlockAt(blockPos2).blockState();
BlockStateWrapper blockState3 = level.getBlockAt(blockPos3).blockState();
BooleanProperty waterlogged = (BooleanProperty) state.owner().value().getProperty("waterlogged");
if (waterlogged != null) {
state = state.with(waterlogged, FastNMS.INSTANCE.method$FluidState$getType(fluidState) == MFluids.WATER);
}
return state
.with(this.northProperty, this.connectsTo(blockState, FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(blockState.literalObject(), level.serverWorld(), LocationUtils.toBlockPos(blockPos), CoreReflections.instance$Direction$SOUTH, CoreReflections.instance$SupportType$FULL), HorizontalDirection.SOUTH))
.with(this.eastProperty, this.connectsTo(blockState1, FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(blockState1.literalObject(), level.serverWorld(), LocationUtils.toBlockPos(blockPos1), CoreReflections.instance$Direction$WEST, CoreReflections.instance$SupportType$FULL), HorizontalDirection.WEST))
.with(this.southProperty, this.connectsTo(blockState2, FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(blockState2.literalObject(), level.serverWorld(), LocationUtils.toBlockPos(blockPos2), CoreReflections.instance$Direction$NORTH, CoreReflections.instance$SupportType$FULL), HorizontalDirection.NORTH))
.with(this.westProperty, this.connectsTo(blockState3, FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(blockState3.literalObject(), level.serverWorld(), LocationUtils.toBlockPos(blockPos3), CoreReflections.instance$Direction$EAST, CoreReflections.instance$SupportType$FULL), HorizontalDirection.EAST));
}
@Override
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Optional<ImmutableBlockState> optionalState = BlockStateUtils.getOptionalCustomBlockState(args[0]);
BooleanProperty waterlogged = (BooleanProperty) optionalState
.map(BlockStateHolder::owner)
.map(Holder::value)
.map(block -> block.getProperty("waterlogged"))
.orElse(null);
if (waterlogged != null) {
FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleFluidTick(args[updateShape$level], args[updateShape$blockPos], MFluids.WATER, 5);
}
if (DirectionUtils.fromNMSDirection(args[updateShape$direction]).axis().isHorizontal() && optionalState.isPresent()) {
Direction direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]);
ImmutableBlockState state = optionalState.get();
if (state.owner() != null) {
BooleanProperty booleanProperty = (BooleanProperty) state.owner().value().getProperty(direction.name().toLowerCase(Locale.ROOT));
if (booleanProperty != null) {
BlockStateWrapper wrapper = BlockStateUtils.toBlockStateWrapper(args[updateShape$neighborState]);
return state.with(booleanProperty, this.connectsTo(wrapper, FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(wrapper.literalObject(), args[updateShape$level], args[5], DirectionUtils.toNMSDirection(direction.opposite()), CoreReflections.instance$SupportType$FULL), direction.opposite().toHorizontalDirection())).customBlockState().literalObject();
}
}
}
return superMethod.call();
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
BooleanProperty north = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("north"), "warning.config.block.behavior.fence.missing_north");
BooleanProperty east = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("east"), "warning.config.block.behavior.fence.missing_east");
BooleanProperty south = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("south"), "warning.config.block.behavior.fence.missing_south");
BooleanProperty west = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("west"), "warning.config.block.behavior.fence.missing_west");
Object connectableBlockTag = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("connectable-block-tag", "minecraft:wooden_fences").toString())));
connectableBlockTag = connectableBlockTag != null ? connectableBlockTag : MTagKeys.Block$WOODEN_FENCES;
boolean canLeash = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-leash", false), "can-leash");
return new FenceBlockBehavior(block, north, east, south, west, connectableBlockTag, canLeash);
}
}
}

View File

@@ -10,10 +10,7 @@ import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.InteractUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.BlockBehavior;
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.*;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
@@ -262,6 +259,22 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior {
}
}
public static boolean connectsToDirection(BlockStateWrapper state, HorizontalDirection direction) {
FenceGateBlockBehavior fence = BlockStateUtils.getOptionalCustomBlockState(state.literalObject())
.map(ImmutableBlockState::behavior)
.flatMap(behavior -> behavior.getAs(FenceGateBlockBehavior.class))
.orElse(null);
if (fence == null) return false;
Direction facing = null;
ImmutableBlockState customState = BlockStateUtils.getOptionalCustomBlockState(state.literalObject()).orElse(null);
if (customState == null) return false;
Property<?> facingProperty = customState.owner().value().getProperty("facing");
if (facingProperty != null && facingProperty.valueClass() == HorizontalDirection.class) {
facing = ((HorizontalDirection) customState.get(facingProperty)).toDirection();
}
return facing != null && facing.axis() == direction.toDirection().clockWise().axis();
}
public static class Factory implements BlockBehaviorFactory {
@Override

View File

@@ -146,7 +146,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
}
if (FastNMS.INSTANCE.method$BlockStateBase$isAir(currentState)) {
Object chunkGenerator = CoreReflections.method$ServerChunkCache$getGenerator.invoke(FastNMS.INSTANCE.method$ServerLevel$getChunkSource(world));
Object placedFeature = CoreReflections.method$Holder$value.invoke(holder.get());
Object placedFeature = FastNMS.INSTANCE.method$Holder$value(holder.get());
CoreReflections.method$PlacedFeature$place.invoke(placedFeature, world, chunkGenerator, random, nmsCurrentPos);
}
}

View File

@@ -16,7 +16,7 @@ public class HangingBlockBehavior extends BushBlockBehavior {
public static final Factory FACTORY = new Factory();
public HangingBlockBehavior(CustomBlock block, int delay, boolean blacklist, boolean stackable, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn) {
super(block, delay, blacklist, stackable, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
super(block, delay, blacklist, stackable, -1, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
}
@Override

View File

@@ -132,7 +132,7 @@ public class LeavesBlockBehavior extends BukkitBlockBehavior {
Object mutablePos = CoreReflections.constructor$MutableBlockPos.newInstance();
int j = Direction.values().length;
for (int k = 0; k < j; ++k) {
Object direction = CoreReflections.instance$Directions[k];
Object direction = CoreReflections.instance$Direction$values[k];
CoreReflections.method$MutableBlockPos$setWithOffset.invoke(mutablePos, blockPos, direction);
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, mutablePos);
i = Math.min(i, getDistanceAt(blockState) + 1);

View File

@@ -0,0 +1,49 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.special.PlaceLiquidBlockBehavior;
import net.momirealms.craftengine.core.world.WorldEvents;
import java.util.Map;
import java.util.concurrent.Callable;
public class LiquidFlowableBlockBehavior extends BukkitBlockBehavior implements PlaceLiquidBlockBehavior {
public static final Factory FACTORY = new Factory();
public LiquidFlowableBlockBehavior(CustomBlock customBlock) {
super(customBlock);
}
@Override
public boolean canPlaceLiquid(Object thisBlock, Object[] args, Callable<Object> superMethod) {
return true;
}
@Override
public boolean placeLiquid(Object thisBlock, Object[] args, Callable<Object> superMethod) {
Object level = args[0];
Object pos = args[1];
Object blockState = args[2];
Object fluidState = args[3];
Object fluidType = FastNMS.INSTANCE.method$FluidState$getType(fluidState);
if (fluidType == MFluids.LAVA || fluidType == MFluids.FLOWING_LAVA) {
FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.LAVA_CONVERTS_BLOCK, pos, 0);
} else {
FastNMS.INSTANCE.method$Block$dropResources(blockState, level, pos);
}
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, FastNMS.INSTANCE.method$FluidState$createLegacyBlock(fluidState), UpdateOption.UPDATE_ALL.flags());
return true;
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
return new LiquidFlowableBlockBehavior(block);
}
}
}

View File

@@ -4,11 +4,11 @@ import io.papermc.paper.event.entity.EntityInsideBlockEvent;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntitySelectors;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
@@ -20,7 +20,8 @@ import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.PressurePlateSensitivity;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.*;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldEvents;
import org.bukkit.GameEvent;
import org.bukkit.util.Vector;
@@ -66,10 +67,6 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior {
return MBlocks.AIR$defaultState;
}
ImmutableBlockState customState = optionalCustomState.get();
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos));
world.playBlockSound(position, customState.settings().sounds().breakSound());
FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, customState.customBlockState().registryId());
return MBlocks.AIR$defaultState;
}
@@ -104,8 +101,6 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior {
int signalForState = this.getSignalForState(state);
if (signalForState == 0) {
this.checkPressed(args[3], args[1], args[2], state, signalForState, thisBlock);
} else {
FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(args[1], args[2], thisBlock, this.pressedTime);
}
}
@@ -115,7 +110,10 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior {
case MOBS -> CoreReflections.clazz$LivingEntity;
};
Object box = FastNMS.INSTANCE.method$AABB$move(CoreReflections.instance$BasePressurePlateBlock$TOUCH_AABB, pos);
return FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass(level, box, clazz) > 0 ? 15 : 0;
return !FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass(
level, clazz, box,
MEntitySelectors.NO_SPECTATORS.and(entity -> !FastNMS.INSTANCE.method$Entity$isIgnoringBlockTriggers(entity))
).isEmpty() ? 15 : 0;
}
private Object setSignalForState(Object state, int strength) {

View File

@@ -88,7 +88,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior {
return;
}
Object chunkGenerator = CoreReflections.method$ServerChunkCache$getGenerator.invoke(FastNMS.INSTANCE.method$ServerLevel$getChunkSource(world));
Object configuredFeature = CoreReflections.method$Holder$value.invoke(holder.get());
Object configuredFeature = FastNMS.INSTANCE.method$Holder$value(holder.get());
Object fluidState = FastNMS.INSTANCE.method$BlockGetter$getFluidState(world, blockPos);
Object legacyState = CoreReflections.method$FluidState$createLegacyBlock.invoke(fluidState);
FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, legacyState, UpdateOption.UPDATE_NONE.flags());

View File

@@ -0,0 +1,64 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes;
import net.momirealms.craftengine.bukkit.block.entity.SimpleParticleBlockEntity;
import net.momirealms.craftengine.core.block.BlockBehavior;
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.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.particle.ParticleConfig;
import java.util.List;
import java.util.Map;
public class SimpleParticleBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior {
public static final Factory FACTORY = new Factory();
public final ParticleConfig[] particles;
public final int tickInterval;
public SimpleParticleBlockBehavior(CustomBlock customBlock, ParticleConfig[] particles, int tickInterval) {
super(customBlock);
this.particles = particles;
this.tickInterval = tickInterval;
}
public ParticleConfig[] particles() {
return this.particles;
}
public int tickInterval() {
return tickInterval;
}
@Override
public <T extends BlockEntity> BlockEntityType<T> blockEntityType() {
return EntityBlockBehavior.blockEntityTypeHelper(BukkitBlockEntityTypes.SIMPLE_PARTICLE);
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) {
return new SimpleParticleBlockEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> createAsyncBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
if (this.particles.length == 0) return null;
return EntityBlockBehavior.createTickerHelper(SimpleParticleBlockEntity::tick);
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
List<ParticleConfig> particles = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(arguments, "particles", "particle"), ParticleConfig::fromMap$blockEntity);
int tickInterval = ResourceConfigUtils.getAsInt(arguments.getOrDefault("tick-interval", 10), "tick-interval");
return new SimpleParticleBlockBehavior(block, particles.toArray(new ParticleConfig[0]), tickInterval);
}
}
}

View File

@@ -0,0 +1,212 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes;
import net.momirealms.craftengine.bukkit.block.entity.SimpleStorageBlockEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.gui.BukkitInventory;
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.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.AdventureHelper;
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.CEWorld;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior {
public static final Factory FACTORY = new Factory();
private final String containerTitle;
private final int rows;
private final SoundData openSound;
private final SoundData closeSound;
private final boolean hasAnalogOutputSignal;
private final boolean canPlaceItem;
private final boolean canTakeItem;
@Nullable
private final Property<Boolean> openProperty;
public SimpleStorageBlockBehavior(CustomBlock customBlock,
String containerTitle,
int rows,
SoundData openSound,
SoundData closeSound,
boolean hasAnalogOutputSignal,
boolean canPlaceItem,
boolean canTakeItem,
@Nullable Property<Boolean> openProperty) {
super(customBlock);
this.containerTitle = containerTitle;
this.rows = rows;
this.openSound = openSound;
this.closeSound = closeSound;
this.hasAnalogOutputSignal = hasAnalogOutputSignal;
this.canPlaceItem = canPlaceItem;
this.canTakeItem = canTakeItem;
this.openProperty = openProperty;
}
@Override
public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) {
CEWorld world = context.getLevel().storageWorld();
net.momirealms.craftengine.core.entity.player.Player player = context.getPlayer();
BlockEntity blockEntity = world.getBlockEntityAtIfLoaded(context.getClickedPos());
if (player != null && blockEntity instanceof SimpleStorageBlockEntity entity) {
Player bukkitPlayer = (Player) player.platformPlayer();
Optional.ofNullable(entity.inventory()).ifPresent(inventory -> {
entity.onPlayerOpen(player);
bukkitPlayer.openInventory(inventory);
new BukkitInventory(inventory).open(player, AdventureHelper.miniMessage().deserialize(this.containerTitle, PlayerOptionalContext.of(player).tagResolvers()));
});
}
return InteractionResult.SUCCESS_AND_CANCEL;
}
// 1.21.5+
@Override
public void affectNeighborsAfterRemoval(Object thisBlock, Object[] args, Callable<Object> superMethod) {
Object level = args[1];
Object pos = args[2];
Object blockState = args[0];
FastNMS.INSTANCE.method$Level$updateNeighbourForOutputSignal(level, pos, BlockStateUtils.getBlockOwner(blockState));
}
@Override
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object world = args[1];
Object blockPos = args[2];
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
World bukkitWorld = FastNMS.INSTANCE.method$Level$getCraftWorld(world);
CEWorld ceWorld = BukkitWorldManager.instance().getWorld(bukkitWorld.getUID());
BlockEntity blockEntity = ceWorld.getBlockEntityAtIfLoaded(pos);
if (blockEntity instanceof SimpleStorageBlockEntity entity) {
entity.checkOpeners(world, blockPos, args[0]);
}
}
@Override
public <T extends BlockEntity> BlockEntityType<T> blockEntityType() {
return EntityBlockBehavior.blockEntityTypeHelper(BukkitBlockEntityTypes.SIMPLE_STORAGE);
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) {
return new SimpleStorageBlockEntity(pos, state);
}
@NotNull
public String containerTitle() {
return this.containerTitle;
}
@Nullable
public SoundData closeSound() {
return this.closeSound;
}
@Nullable
public SoundData openSound() {
return this.openSound;
}
public int rows() {
return this.rows;
}
public boolean canPlaceItem() {
return this.canPlaceItem;
}
public boolean canTakeItem() {
return this.canTakeItem;
}
public @Nullable Property<Boolean> openProperty() {
return openProperty;
}
@Override
public int getAnalogOutputSignal(Object thisBlock, Object[] args) {
if (!this.hasAnalogOutputSignal) return 0;
Object world = args[1];
Object blockPos = args[2];
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
World bukkitWorld = FastNMS.INSTANCE.method$Level$getCraftWorld(world);
CEWorld ceWorld = BukkitWorldManager.instance().getWorld(bukkitWorld.getUID());
BlockEntity blockEntity = ceWorld.getBlockEntityAtIfLoaded(pos);
if (blockEntity instanceof SimpleStorageBlockEntity entity) {
Inventory inventory = entity.inventory();
if (inventory != null) {
float signal = 0.0F;
for (int i = 0; i < inventory.getSize(); i++) {
ItemStack item = inventory.getItem(i);
if (item != null) {
signal += (float) item.getAmount() / (float) (Math.min(inventory.getMaxStackSize(), item.getMaxStackSize()));
}
}
signal /= (float) inventory.getSize();
return MiscUtils.lerpDiscrete(signal, 0, 15);
}
}
return 0;
}
@Override
public boolean hasAnalogOutputSignal(Object thisBlock, Object[] args) {
return this.hasAnalogOutputSignal;
}
@Override
public Object getContainer(Object thisBlock, Object[] args) {
CEWorld ceWorld = BukkitWorldManager.instance().getWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(args[1]));
BlockPos blockPos = LocationUtils.fromBlockPos(args[2]);
BlockEntity blockEntity = ceWorld.getBlockEntityAtIfLoaded(blockPos);
if (blockEntity instanceof SimpleStorageBlockEntity entity) {
return FastNMS.INSTANCE.method$CraftInventory$getInventory(entity.inventory());
}
return null;
}
public static class Factory implements BlockBehaviorFactory {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
String title = arguments.getOrDefault("title", "").toString();
int rows = MiscUtils.clamp(ResourceConfigUtils.getAsInt(arguments.getOrDefault("rows", 1), "rows"), 1, 6);
Map<String, Object> sounds = (Map<String, Object>) arguments.get("sounds");
boolean hasAnalogOutputSignal = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("has-signal", true), "has-signal");
SoundData openSound = null;
SoundData closeSound = null;
if (sounds != null) {
openSound = Optional.ofNullable(sounds.get("open")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null);
closeSound = Optional.ofNullable(sounds.get("close")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null);
}
boolean canPlaceItem = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("allow-input", true), "allow-input");
boolean canTakeItem = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("allow-output", true), "allow-output");
Property<Boolean> property = (Property<Boolean>) block.getProperty("open");
return new SimpleStorageBlockBehavior(block, title, rows, openSound, closeSound, hasAnalogOutputSignal, canPlaceItem, canTakeItem, property);
}
}
}

View File

@@ -10,7 +10,7 @@ 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.block.properties.Property;
import net.momirealms.craftengine.core.block.state.properties.SlabType;
import net.momirealms.craftengine.core.block.properties.type.SlabType;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.behavior.BlockBoundItemBehavior;

View File

@@ -0,0 +1,112 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
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.block.properties.Property;
import net.momirealms.craftengine.core.block.properties.type.SofaShape;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.BlockPos;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class SofaBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<HorizontalDirection> facingProperty;
private final Property<SofaShape> shapeProperty;
public SofaBlockBehavior(CustomBlock block, Property<HorizontalDirection> facing, Property<SofaShape> shape) {
super(block);
this.facingProperty = facing;
this.shapeProperty = shape;
}
@Override
public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) {
BlockPos clickedPos = context.getClickedPos();
ImmutableBlockState blockState = state.owner().value().defaultState()
.with(this.facingProperty, context.getHorizontalDirection().toHorizontalDirection());
if (super.waterloggedProperty != null) {
Object fluidState = FastNMS.INSTANCE.method$BlockGetter$getFluidState(context.getLevel().serverWorld(), LocationUtils.toBlockPos(clickedPos));
blockState = blockState.with(this.waterloggedProperty, FastNMS.INSTANCE.method$FluidState$getType(fluidState) == MFluids.WATER);
}
return blockState.with(this.shapeProperty, getSofaShape(blockState, context.getLevel().serverWorld(), clickedPos));
}
@Override
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object level = args[updateShape$level];
Object blockPos = args[updateShape$blockPos];
Object blockState = args[0];
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState);
if (optionalCustomState.isEmpty()) return blockState;
ImmutableBlockState customState = optionalCustomState.get();
if (super.waterloggedProperty != null && customState.get(this.waterloggedProperty)) {
FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleFluidTick(args[updateShape$level], args[updateShape$blockPos], MFluids.WATER, 5);
}
Direction direction = DirectionUtils.fromNMSDirection(VersionHelper.isOrAbove1_21_2() ? args[4] : args[1]);
SofaShape sofaShape = getSofaShape(customState, level, LocationUtils.fromBlockPos(blockPos));
return direction.axis().isHorizontal()
? customState.with(this.shapeProperty, sofaShape).customBlockState().literalObject()
: superMethod.call();
}
private SofaShape getSofaShape(ImmutableBlockState state, Object level, BlockPos pos) {
Direction direction = state.get(this.facingProperty).toDirection();
Object relativeBlockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.toBlockPos(pos.relative(direction.opposite())));
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(relativeBlockState);
if (optionalCustomState.isPresent()) {
ImmutableBlockState customState = optionalCustomState.get();
Optional<SofaBlockBehavior> optionalStairsBlockBehavior = customState.behavior().getAs(SofaBlockBehavior.class);
if (optionalStairsBlockBehavior.isPresent()) {
SofaBlockBehavior stairsBlockBehavior = optionalStairsBlockBehavior.get();
Direction direction1 = customState.get(stairsBlockBehavior.facingProperty).toDirection();
if (direction1.axis() != state.get(this.facingProperty).toDirection().axis() && canTakeShape(state, level, pos, direction1)) {
if (direction1 == direction.counterClockWise()) {
return SofaShape.INNER_LEFT;
}
return SofaShape.INNER_RIGHT;
}
}
}
return SofaShape.STRAIGHT;
}
private boolean canTakeShape(ImmutableBlockState state, Object level, BlockPos pos, Direction face) {
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.toBlockPos(pos.relative(face)));
Optional<ImmutableBlockState> optionalAnotherState = BlockStateUtils.getOptionalCustomBlockState(blockState);
if (optionalAnotherState.isEmpty()) {
return true;
}
ImmutableBlockState anotherState = optionalAnotherState.get();
Optional<SofaBlockBehavior> optionalBehavior = anotherState.behavior().getAs(SofaBlockBehavior.class);
if (optionalBehavior.isEmpty()) {
return true;
}
SofaBlockBehavior anotherBehavior = optionalBehavior.get();
return anotherState.get(anotherBehavior.facingProperty) != state.get(this.facingProperty);
}
public static class Factory implements BlockBehaviorFactory {
@Override
@SuppressWarnings("unchecked")
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Property<HorizontalDirection> facing = (Property<HorizontalDirection>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.sofa.missing_facing");
Property<SofaShape> shape = (Property<SofaShape>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("shape"), "warning.config.block.behavior.sofa.missing_shape");
return new SofaBlockBehavior(block, facing, shape);
}
}
}

View File

@@ -10,8 +10,8 @@ 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.block.properties.Property;
import net.momirealms.craftengine.core.block.state.properties.SingleBlockHalf;
import net.momirealms.craftengine.core.block.state.properties.StairsShape;
import net.momirealms.craftengine.core.block.properties.type.SingleBlockHalf;
import net.momirealms.craftengine.core.block.properties.type.StairsShape;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.HorizontalDirection;

View File

@@ -0,0 +1,139 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
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.plugin.reflection.minecraft.MRegistries;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.properties.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.*;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class StemBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final IntegerProperty ageProperty;
private final Key fruit;
private final Key attachedStem;
private final int minGrowLight;
private final Object tagMayPlaceFruit;
private final Object blockMayPlaceFruit;
public StemBlockBehavior(CustomBlock customBlock,
IntegerProperty ageProperty,
Key fruit,
Key attachedStem,
int minGrowLight,
Object tagMayPlaceFruit,
Object blockMayPlaceFruit) {
super(customBlock);
this.ageProperty = ageProperty;
this.fruit = fruit;
this.attachedStem = attachedStem;
this.minGrowLight = minGrowLight;
this.tagMayPlaceFruit = tagMayPlaceFruit;
this.blockMayPlaceFruit = blockMayPlaceFruit;
}
@Override
public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
return (VersionHelper.isOrAbove1_20_5() ? args[1] : args[3]).equals(CoreReflections.instance$PathComputationType$AIR)
&& !FastNMS.INSTANCE.field$BlockBehavior$hasCollision(thisBlock) || (boolean) superMethod.call();
}
@Override
public void randomTick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object state = args[0];
Object level = args[1];
Object pos = args[2];
if (CropBlockBehavior.getRawBrightness(level, pos) < this.minGrowLight) return;
ImmutableBlockState customState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null);
if (customState == null || customState.isEmpty()) return;
int age = customState.get(ageProperty);
if (age < ageProperty.max) {
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, customState.with(ageProperty, age + 1).customBlockState().literalObject(), 2);
return;
}
Object randomDirection = CoreReflections.instance$Direction$values[RandomUtils.generateRandomInt(2, 6)];
Object blockPos = FastNMS.INSTANCE.method$BlockPos$relative(pos, randomDirection);
if (!FastNMS.INSTANCE.method$BlockStateBase$isAir(FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, blockPos)))
return;
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, FastNMS.INSTANCE.method$BlockPos$relative(blockPos, CoreReflections.instance$Direction$DOWN));
if (mayPlaceFruit(blockState)) {
Optional<CustomBlock> optionalFruit = BukkitBlockManager.instance().blockById(this.fruit);
Object fruitState = null;
if (optionalFruit.isPresent()) {
fruitState = optionalFruit.get().defaultState().customBlockState().literalObject();
} else if (fruit.namespace().equals("minecraft")) {
fruitState = FastNMS.INSTANCE.method$Block$defaultState(FastNMS.INSTANCE.method$Registry$getValue(
MBuiltInRegistries.BLOCK,
FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", fruit.value())
));
}
Optional<CustomBlock> optionalAttachedStem = BukkitBlockManager.instance().blockById(this.attachedStem);
if (fruitState == null || optionalAttachedStem.isEmpty()) return;
CustomBlock attachedStem = optionalAttachedStem.get();
@SuppressWarnings("unchecked")
Property<HorizontalDirection> facing = (Property<HorizontalDirection>) attachedStem.getProperty("facing");
if (facing == null) return;
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, fruitState, UpdateOption.UPDATE_ALL.flags());
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, attachedStem.defaultState().with(facing, DirectionUtils.fromNMSDirection(randomDirection).toHorizontalDirection()).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags());
}
}
@Override
public boolean isValidBoneMealTarget(Object thisBlock, Object[] args) {
ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[2]).orElse(null);
if (state == null || state.isEmpty()) return false;
return state.get(ageProperty) != ageProperty.max;
}
@Override
public boolean isBoneMealSuccess(Object thisBlock, Object[] args) {
return true;
}
@Override
public void performBoneMeal(Object thisBlock, Object[] args) {
ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[3]).orElse(null);
if (state == null || state.isEmpty()) return;
int min = Math.min(7, state.get(ageProperty) + RandomUtils.generateRandomInt(Math.min(ageProperty.min + 2, ageProperty.max), Math.min(ageProperty.max - 2, ageProperty.max)));
Object blockState = state.with(ageProperty, min).customBlockState().literalObject();
FastNMS.INSTANCE.method$LevelWriter$setBlock(args[0], args[2], blockState, 2);
if (min >= ageProperty.max) {
FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$randomTick(blockState, args[0], args[2]);
}
}
private boolean mayPlaceFruit(Object blockState) {
boolean flag1 = tagMayPlaceFruit != null && FastNMS.INSTANCE.method$BlockStateBase$is(blockState, tagMayPlaceFruit);
boolean flag2 = blockMayPlaceFruit != null && FastNMS.INSTANCE.method$BlockStateBase$isBlock(blockState, blockMayPlaceFruit);
if (tagMayPlaceFruit == null && blockMayPlaceFruit == null) return true;
return flag1 || flag2;
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
IntegerProperty ageProperty = (IntegerProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.stem.missing_age");
Key fruit = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("fruit"), "warning.config.block.behavior.stem.missing_fruit"));
Key attachedStem = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("attached-stem"), "warning.config.block.behavior.stem.missing_attached_stem"));
int minGrowLight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("light-requirement", 9), "light-requirement");
Object tagMayPlaceFruit = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("may-place-fruit", "minecraft:dirt").toString())));
Object blockMayPlaceFruit = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("may-place-fruit", "minecraft:farmland").toString())));
return new StemBlockBehavior(block, ageProperty, fruit, attachedStem, minGrowLight, tagMayPlaceFruit, blockMayPlaceFruit);
}
}
}

View File

@@ -0,0 +1,100 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
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.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class ToggleableLampBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<Boolean> litProperty;
private final Property<Boolean> poweredProperty;
private final boolean canOpenWithHand;
public ToggleableLampBlockBehavior(CustomBlock block, Property<Boolean> litProperty, Property<Boolean> poweredProperty, boolean canOpenWithHand) {
super(block);
this.litProperty = litProperty;
this.poweredProperty = poweredProperty;
this.canOpenWithHand = canOpenWithHand;
}
@Override
public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) {
if (!this.canOpenWithHand) {
return InteractionResult.PASS;
}
ToggleableLampBlockBehavior behavior = state.behavior().getAs(ToggleableLampBlockBehavior.class).orElse(null);
if (behavior == null) return InteractionResult.PASS;
FastNMS.INSTANCE.method$LevelWriter$setBlock(
context.getLevel().serverWorld(),
LocationUtils.toBlockPos(context.getClickedPos()),
state.cycle(behavior.litProperty).customBlockState().literalObject(),
2
);
Optional.ofNullable(context.getPlayer()).ifPresent(p -> p.swingHand(context.getHand()));
return InteractionResult.SUCCESS_AND_CANCEL;
}
@Override
public void onPlace(Object thisBlock, Object[] args, Callable<Object> superMethod) {
if (this.poweredProperty == null) return;
Object state = args[0];
Object level = args[1];
Object pos = args[2];
Object oldState = args[3];
if (FastNMS.INSTANCE.method$BlockState$getBlock(oldState) != FastNMS.INSTANCE.method$BlockState$getBlock(state) && CoreReflections.clazz$ServerLevel.isInstance(level)) {
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state);
if (optionalCustomState.isEmpty()) return;
checkAndFlip(optionalCustomState.get(), level, pos);
}
}
@Override
public void neighborChanged(Object thisBlock, Object[] args, Callable<Object> superMethod) {
if (this.poweredProperty == null) return;
Object blockState = args[0];
Object world = args[1];
if (!CoreReflections.clazz$ServerLevel.isInstance(world)) return;
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState);
if (optionalCustomState.isEmpty()) return;
Object blockPos = args[2];
ImmutableBlockState customState = optionalCustomState.get();
checkAndFlip(customState, world, blockPos);
}
private void checkAndFlip(ImmutableBlockState customState, Object level, Object pos) {
boolean hasNeighborSignal = FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, pos);
boolean isPowered = customState.get(this.poweredProperty);
if (hasNeighborSignal != isPowered) {
ImmutableBlockState blockState = customState;
if (!isPowered) {
blockState = blockState.cycle(this.litProperty);
}
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, hasNeighborSignal).customBlockState().literalObject(), 3);
}
}
@SuppressWarnings("unchecked")
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
boolean canOpenWithHand = ResourceConfigUtils.getAsBoolean(ResourceConfigUtils.get(arguments, "can-open-with-hand", "can-toggle-with-hand"), "can-toggle-with-hand");
Property<Boolean> lit = (Property<Boolean>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.toggleable_lamp.missing_lit");
Property<Boolean> powered = (Property<Boolean>) (canOpenWithHand ? block.getProperty("powered") : ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.toggleable_lamp.missing_powered"));
return new ToggleableLampBlockBehavior(block, lit, powered, canOpenWithHand);
}
}
}

View File

@@ -15,7 +15,7 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.block.state.properties.SingleBlockHalf;
import net.momirealms.craftengine.core.block.properties.type.SingleBlockHalf;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;

View File

@@ -4,15 +4,20 @@ import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior;
import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.block.behavior.special.FallOnBlockBehavior;
import net.momirealms.craftengine.core.block.behavior.special.PlaceLiquidBlockBehavior;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior {
public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior
implements FallOnBlockBehavior, PlaceLiquidBlockBehavior {
private final AbstractBlockBehavior[] behaviors;
public UnsafeCompositeBlockBehavior(CustomBlock customBlock, List<AbstractBlockBehavior> behaviors) {
@@ -20,6 +25,26 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior {
this.behaviors = behaviors.toArray(new AbstractBlockBehavior[0]);
}
@Override
public boolean canPlaceLiquid(Object thisBlock, Object[] args, Callable<Object> superMethod) {
for (AbstractBlockBehavior behavior : behaviors) {
if (behavior instanceof PlaceLiquidBlockBehavior) {
return behavior.canPlaceLiquid(thisBlock, args, superMethod);
}
}
return super.canPlaceLiquid(thisBlock, args, superMethod);
}
@Override
public boolean placeLiquid(Object thisBlock, Object[] args, Callable<Object> superMethod) {
for (AbstractBlockBehavior behavior : behaviors) {
if (behavior instanceof PlaceLiquidBlockBehavior) {
return behavior.placeLiquid(thisBlock, args, superMethod);
}
}
return super.placeLiquid(thisBlock, args, superMethod);
}
@SuppressWarnings("unchecked")
@Override
public <T extends BlockBehavior> Optional<T> getAs(Class<T> tClass) {
@@ -31,6 +56,22 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior {
return Optional.empty();
}
@Nullable
@Override
public EntityBlockBehavior getEntityBehavior() {
EntityBlockBehavior target = null;
for (AbstractBlockBehavior behavior : this.behaviors) {
if (behavior instanceof EntityBlockBehavior entityBehavior) {
if (target == null) {
target = entityBehavior;
} else {
throw new IllegalArgumentException("Multiple entity block behaviors are not allowed");
}
}
}
return target;
}
@Override
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
for (AbstractBlockBehavior behavior : this.behaviors) {
@@ -74,6 +115,18 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior {
return previous;
}
@Override
public Object getContainer(Object thisBlock, Object[] args) throws Exception {
for (AbstractBlockBehavior behavior : this.behaviors) {
Object container = behavior.getContainer(thisBlock, args);
if (container != null) {
return container;
}
}
return null;
}
@Override
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
for (AbstractBlockBehavior behavior : this.behaviors) {
@@ -264,6 +317,30 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior {
return false;
}
@Override
public boolean hasAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception {
for (AbstractBlockBehavior behavior : this.behaviors) {
if (behavior.hasAnalogOutputSignal(thisBlock, args)) {
return true;
}
}
return false;
}
@Override
public int getAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception {
int signal = 0;
int count = 0;
for (AbstractBlockBehavior behavior : this.behaviors) {
int s = behavior.getAnalogOutputSignal(thisBlock, args);
if (s != 0) {
signal += s;
count++;
}
}
return count == 0 ? 0 : signal / count;
}
@Override
public Object playerWillDestroy(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object previous = args[0];
@@ -282,4 +359,40 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior {
behavior.spawnAfterBreak(thisBlock, args, superMethod);
}
}
@Override
public void fallOn(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
for (AbstractBlockBehavior behavior : this.behaviors) {
if (behavior instanceof FallOnBlockBehavior f) {
f.fallOn(thisBlock, args, superMethod);
return;
}
}
FallOnBlockBehavior.super.fallOn(thisBlock, args, superMethod);
}
@Override
public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
for (AbstractBlockBehavior behavior : this.behaviors) {
if (behavior instanceof FallOnBlockBehavior f) {
f.updateEntityMovementAfterFallOn(thisBlock, args, superMethod);
return;
}
}
FallOnBlockBehavior.super.updateEntityMovementAfterFallOn(thisBlock, args, superMethod);
}
@Override
public void stepOn(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
for (AbstractBlockBehavior behavior : this.behaviors) {
behavior.stepOn(thisBlock, args, superMethod);
}
}
@Override
public void onProjectileHit(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
for (AbstractBlockBehavior behavior : this.behaviors) {
behavior.onProjectileHit(thisBlock, args, superMethod);
}
}
}

View File

@@ -83,7 +83,7 @@ public class VerticalCropBlockBehavior extends BukkitBlockBehavior {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Property<Integer> ageProperty = (Property<Integer>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.sugar_cane.missing_age");
Property<Integer> ageProperty = (Property<Integer>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.vertical_crop.missing_age");
int maxHeight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-height", 3), "max-height");
boolean direction = arguments.getOrDefault("direction", "up").toString().equalsIgnoreCase("up");
return new VerticalCropBlockBehavior(block, ageProperty, maxHeight,

View File

@@ -0,0 +1,78 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes;
import net.momirealms.craftengine.bukkit.block.entity.WallTorchParticleBlockEntity;
import net.momirealms.craftengine.core.block.BlockBehavior;
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.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.particle.ParticleConfig;
import java.util.List;
import java.util.Map;
public class WallTorchParticleBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior {
public static final Factory FACTORY = new Factory();
public final ParticleConfig[] particles;
public final int tickInterval;
public final Property<HorizontalDirection> facingProperty;
public WallTorchParticleBlockBehavior(CustomBlock customBlock, ParticleConfig[] particles, int tickInterval, Property<HorizontalDirection> facingProperty) {
super(customBlock);
this.particles = particles;
this.tickInterval = tickInterval;
this.facingProperty = facingProperty;
}
public ParticleConfig[] particles() {
return this.particles;
}
public int tickInterval() {
return tickInterval;
}
public Property<HorizontalDirection> facingProperty() {
return facingProperty;
}
@Override
public <T extends BlockEntity> BlockEntityType<T> blockEntityType() {
return EntityBlockBehavior.blockEntityTypeHelper(BukkitBlockEntityTypes.WALL_TORCH_PARTICLE);
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) {
return new WallTorchParticleBlockEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> createAsyncBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
if (this.particles.length == 0) return null;
return EntityBlockBehavior.createTickerHelper(WallTorchParticleBlockEntity::tick);
}
public static class Factory implements BlockBehaviorFactory {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
List<ParticleConfig> particles = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(arguments, "particles", "particle"), ParticleConfig::fromMap$blockEntity);
int tickInterval = ResourceConfigUtils.getAsInt(arguments.getOrDefault("tick-interval", 10), "tick-interval");
Property<HorizontalDirection> directionProperty = (Property<HorizontalDirection>) block.getProperty("facing");
if (directionProperty == null) {
throw new LocalizedResourceConfigException("warning.config.block.behavior.wall_torch_particle.missing_facing");
}
return new WallTorchParticleBlockBehavior(block, particles.toArray(new ParticleConfig[0]), tickInterval, directionProperty);
}
}
}

View File

@@ -0,0 +1,14 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.world.BlockPos;
public abstract class AbstractAnimateTickBlockEntity extends BlockEntity {
protected int tickCount;
public AbstractAnimateTickBlockEntity(BlockEntityType<? extends BlockEntity> type, BlockPos pos, ImmutableBlockState blockState) {
super(type, pos, blockState);
}
}

View File

@@ -0,0 +1,28 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
public class BlockEntityHolder implements InventoryHolder {
private final BlockEntity blockEntity;
private Inventory inventory;
public BlockEntityHolder(BlockEntity entity) {
this.blockEntity = entity;
}
public BlockEntity blockEntity() {
return blockEntity;
}
public void setInventory(Inventory inventory) {
this.inventory = inventory;
}
@Override
public @NotNull Inventory getInventory() {
return this.inventory;
}
}

View File

@@ -0,0 +1,11 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.block.entity.BlockEntityTypeKeys;
import net.momirealms.craftengine.core.block.entity.BlockEntityTypes;
public class BukkitBlockEntityTypes extends BlockEntityTypes {
public static final BlockEntityType<SimpleStorageBlockEntity> SIMPLE_STORAGE = register(BlockEntityTypeKeys.SIMPLE_STORAGE, SimpleStorageBlockEntity::new);
public static final BlockEntityType<SimpleParticleBlockEntity> SIMPLE_PARTICLE = register(BlockEntityTypeKeys.SIMPLE_PARTICLE, SimpleParticleBlockEntity::new);
public static final BlockEntityType<WallTorchParticleBlockEntity> WALL_TORCH_PARTICLE = register(BlockEntityTypeKeys.WALL_TORCH_PARTICLE, WallTorchParticleBlockEntity::new);
}

View File

@@ -0,0 +1,45 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.bukkit.block.behavior.SimpleParticleBlockBehavior;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.SimpleContext;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.particle.ParticleConfig;
public class SimpleParticleBlockEntity extends AbstractAnimateTickBlockEntity {
private final SimpleParticleBlockBehavior behavior;
private final Context context = SimpleContext.of(ContextHolder.empty());
public SimpleParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) {
super(BukkitBlockEntityTypes.SIMPLE_PARTICLE, pos, blockState);
this.behavior = blockState.behavior().getAs(SimpleParticleBlockBehavior.class).orElseThrow();
}
public void animateTick(ImmutableBlockState state, World level, BlockPos pos) {
for (ParticleConfig particle : this.behavior.particles) {
Vec3d location = new Vec3d(super.pos.x() + particle.x.getDouble(context), super.pos.y() + particle.y.getDouble(context), super.pos.z() + particle.z.getDouble(context));
level.spawnParticle(
location,
particle.particleType,
particle.count.getInt(context),
particle.xOffset.getDouble(context),
particle.yOffset.getDouble(context),
particle.zOffset.getDouble(context),
particle.speed.getDouble(context),
particle.particleData,
context
);
}
}
public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, SimpleParticleBlockEntity particle) {
particle.tickCount++;
if (particle.tickCount % particle.behavior.tickInterval != 0) return;
particle.animateTick(state, ceWorld.world(), blockPos);
}
}

View File

@@ -0,0 +1,218 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.block.behavior.SimpleStorageBlockBehavior;
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.MRegistryOps;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.ListTag;
import org.bukkit.GameEvent;
import org.bukkit.GameMode;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional;
public class SimpleStorageBlockEntity extends BlockEntity {
private final SimpleStorageBlockBehavior behavior;
private final Inventory inventory;
private double maxInteractionDistance;
private boolean openState = false;
public SimpleStorageBlockEntity(BlockPos pos, ImmutableBlockState blockState) {
super(BukkitBlockEntityTypes.SIMPLE_STORAGE, pos, blockState);
this.behavior = super.blockState.behavior().getAs(SimpleStorageBlockBehavior.class).orElseThrow();
BlockEntityHolder holder = new BlockEntityHolder(this);
this.inventory = FastNMS.INSTANCE.createSimpleStorageContainer(holder, this.behavior.rows() * 9, this.behavior.canPlaceItem(), this.behavior.canTakeItem());
holder.setInventory(this.inventory);
}
@Override
protected void saveCustomData(CompoundTag tag) {
// 保存前先把所有打开此容器的玩家界面关闭
this.inventory.close();
ListTag itemsTag = new ListTag();
@Nullable ItemStack[] storageContents = this.inventory.getStorageContents();
for (int i = 0; i < storageContents.length; i++) {
if (storageContents[i] != null) {
if (VersionHelper.isOrAbove1_20_5()) {
int slot = i;
CoreReflections.instance$ItemStack$CODEC.encodeStart(MRegistryOps.SPARROW_NBT, FastNMS.INSTANCE.field$CraftItemStack$handle(storageContents[i]))
.ifSuccess(success -> {
CompoundTag itemTag = (CompoundTag) success;
itemTag.putInt("slot", slot);
itemsTag.add(itemTag);
})
.ifError(error -> CraftEngine.instance().logger().severe("Error while saving storage item: " + error));
} else {
Object nmsTag = FastNMS.INSTANCE.method$itemStack$save(FastNMS.INSTANCE.field$CraftItemStack$handle(storageContents[i]), FastNMS.INSTANCE.constructor$CompoundTag());
CompoundTag itemTag = (CompoundTag) MRegistryOps.NBT.convertTo(MRegistryOps.SPARROW_NBT, nmsTag);
itemTag.putInt("slot", i);
itemsTag.add(itemTag);
}
}
}
tag.put("items", itemsTag);
}
@Override
public void loadCustomData(CompoundTag tag) {
ListTag itemsTag = Optional.ofNullable(tag.getList("items")).orElseGet(ListTag::new);
ItemStack[] storageContents = new ItemStack[this.behavior.rows() * 9];
for (int i = 0; i < itemsTag.size(); i++) {
CompoundTag itemTag = itemsTag.getCompound(i);
int slot = itemTag.getInt("slot");
if (slot < 0 || slot >= storageContents.length) {
continue;
}
if (VersionHelper.isOrAbove1_20_5()) {
CoreReflections.instance$ItemStack$CODEC.parse(MRegistryOps.SPARROW_NBT, itemTag)
.resultOrPartial((s) -> CraftEngine.instance().logger().severe("Tried to load invalid item: '" + itemTag + "'. " + s))
.ifPresent(nmsStack -> storageContents[slot] = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsStack));
} else {
Object nmsTag = MRegistryOps.SPARROW_NBT.convertTo(MRegistryOps.NBT, itemTag);
Object itemStack = FastNMS.INSTANCE.method$ItemStack$of(nmsTag);
storageContents[slot] = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack);
}
}
this.inventory.setStorageContents(storageContents);
}
public Inventory inventory() {
if (!isValid()) return null;
return this.inventory;
}
public void onPlayerOpen(Player player) {
if (!isValidContainer()) return;
if (!player.isSpectatorMode()) {
// 有非观察者的人,那么就不触发开启音效和事件
if (!hasNoViewer(this.inventory.getViewers())) return;
this.maxInteractionDistance = Math.max(player.getCachedInteractionRange(), this.maxInteractionDistance);
this.setOpen(player);
FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(super.world.world().serverWorld(), LocationUtils.toBlockPos(this.pos), BlockStateUtils.getBlockOwner(this.blockState.customBlockState().literalObject()), 5);
}
}
public void onPlayerClose(Player player) {
if (!isValidContainer()) return;
if (!player.isSpectatorMode()) {
// 有非观察者的人,那么就不触发关闭音效和事件
for (HumanEntity viewer : this.inventory.getViewers()) {
if (viewer.getGameMode() == GameMode.SPECTATOR || viewer == player.platformPlayer()) {
continue;
}
return;
}
this.maxInteractionDistance = 0;
this.setClose(player);
}
}
private void setOpen(@Nullable Player player) {
this.updateOpenBlockState(true);
org.bukkit.World bukkitWorld = (org.bukkit.World) super.world.world().platformWorld();
if (player != null) {
bukkitWorld.sendGameEvent((org.bukkit.entity.Player) player.platformPlayer(), GameEvent.CONTAINER_OPEN, new Vector(this.pos.x(), this.pos.y(), this.pos.z()));
} else {
bukkitWorld.sendGameEvent(null, GameEvent.CONTAINER_OPEN, new Vector(this.pos.x(), this.pos.y(), this.pos.z()));
}
this.openState = true;
SoundData soundData = this.behavior.openSound();
if (soundData != null) {
super.world.world().playBlockSound(Vec3d.atCenterOf(this.pos), soundData);
}
}
private void setClose(@Nullable Player player) {
this.updateOpenBlockState(false);
org.bukkit.World bukkitWorld = (org.bukkit.World) super.world.world().platformWorld();
if (player != null) {
bukkitWorld.sendGameEvent((org.bukkit.entity.Player) player.platformPlayer(), GameEvent.CONTAINER_CLOSE, new Vector(this.pos.x(), this.pos.y(), this.pos.z()));
} else {
bukkitWorld.sendGameEvent(null, GameEvent.CONTAINER_CLOSE, new Vector(this.pos.x(), this.pos.y(), this.pos.z()));
}
this.openState = false;
SoundData soundData = this.behavior.closeSound();
if (soundData != null) {
super.world.world().playBlockSound(Vec3d.atCenterOf(this.pos), soundData);
}
}
private boolean hasNoViewer(List<HumanEntity> viewers) {
for (HumanEntity viewer : viewers) {
if (viewer.getGameMode() != GameMode.SPECTATOR) {
return false;
}
}
return true;
}
private boolean isValidContainer() {
return this.isValid() && this.inventory != null && this.behavior != null;
}
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 (property == null) return;
super.world.world().setBlockAt(this.pos.x(), this.pos.y(), this.pos.z(), state.with(property, open), UpdateOption.UPDATE_ALL.flags());
}
public void checkOpeners(Object level, Object pos, Object blockState) {
if (!this.isValidContainer()) return;
double maxInteractionDistance = 0d;
List<HumanEntity> viewers = this.inventory.getViewers();
int validViewers = 0;
for (HumanEntity viewer : viewers) {
if (viewer instanceof org.bukkit.entity.Player player) {
maxInteractionDistance = Math.max(BukkitAdaptors.adapt(player).getCachedInteractionRange(), maxInteractionDistance);
if (player.getGameMode() != GameMode.SPECTATOR) {
validViewers++;
}
}
}
boolean shouldOpen = validViewers != 0;
if (shouldOpen && !this.openState) {
this.setOpen(null);
} else if (!shouldOpen && this.openState) {
this.setClose(null);
}
this.maxInteractionDistance = maxInteractionDistance;
if (!viewers.isEmpty()) {
FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(blockState), 5);
}
}
@Override
public void preRemove() {
this.inventory.close();
Vec3d pos = Vec3d.atCenterOf(this.pos);
for (ItemStack stack : this.inventory.getContents()) {
if (stack != null) {
super.world.world().dropItemNaturally(pos, BukkitItemManager.instance().wrap(stack));
}
}
this.inventory.clear();
}
}

View File

@@ -0,0 +1,54 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.bukkit.block.behavior.WallTorchParticleBlockBehavior;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.SimpleContext;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.particle.ParticleConfig;
public class WallTorchParticleBlockEntity extends AbstractAnimateTickBlockEntity {
private final WallTorchParticleBlockBehavior behavior;
private final Context context = SimpleContext.of(ContextHolder.empty());
public WallTorchParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) {
super(BukkitBlockEntityTypes.WALL_TORCH_PARTICLE, pos, blockState);
this.behavior = blockState.behavior().getAs(WallTorchParticleBlockBehavior.class).orElseThrow();
}
public void animateTick(ImmutableBlockState state, World level, BlockPos pos) {
HorizontalDirection direction = state.get(this.behavior.facingProperty);
if (direction == null) return;
Vec3d center = Vec3d.atCenterOf(pos);
HorizontalDirection opposite = direction.opposite();
for (ParticleConfig particle : this.behavior.particles) {
Vec3d location = new Vec3d(
center.x() + particle.x.getDouble(context) * opposite.stepX(),
center.y() + particle.y.getDouble(context),
center.z() + particle.z.getDouble(context) * opposite.stepZ()
);
level.spawnParticle(
location,
particle.particleType,
particle.count.getInt(context),
particle.xOffset.getDouble(context),
particle.yOffset.getDouble(context),
particle.zOffset.getDouble(context),
particle.speed.getDouble(context),
particle.particleData,
context
);
}
}
public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, WallTorchParticleBlockEntity particle) {
particle.tickCount++;
if (particle.tickCount % particle.behavior.tickInterval != 0) return;
particle.animateTick(state, ceWorld.world(), blockPos);
}
}

View File

@@ -0,0 +1,14 @@
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigs;
public class BukkitBlockEntityElementConfigs extends BlockEntityElementConfigs {
static {
register(ITEM_DISPLAY, ItemDisplayBlockEntityElementConfig.FACTORY);
register(TEXT_DISPLAY, TextDisplayBlockEntityElementConfig.FACTORY);
}
public static void init() {
}
}

View File

@@ -0,0 +1,42 @@
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
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.MEntityTypes;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.world.BlockPos;
import org.joml.Vector3f;
import java.util.List;
import java.util.UUID;
public class ItemDisplayBlockEntityElement implements BlockEntityElement {
private final ItemDisplayBlockEntityElementConfig config;
private final Object cachedSpawnPacket;
private final Object cachedDespawnPacket;
private final int entityId;
public ItemDisplayBlockEntityElement(ItemDisplayBlockEntityElementConfig config, BlockPos pos) {
int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
Vector3f position = config.position();
this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId, UUID.randomUUID(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z,
config.xRot(), config.yRot(), MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
);
this.config = config;
this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(IntList.of(entityId));
this.entityId = entityId;
}
@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))), true);
}
}

View File

@@ -0,0 +1,132 @@
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
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.player.Player;
import net.momirealms.craftengine.core.item.Item;
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.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.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;
public ItemDisplayBlockEntityElementConfig(Function<Player, Item<?>> item,
Vector3f scale,
Vector3f position,
Vector3f translation,
float xRot,
float yRot,
Quaternionf rotation,
ItemDisplayContext displayContext,
Billboard billboard) {
this.item = item;
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.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);
return dataValues;
};
}
@Override
public ItemDisplayBlockEntityElement create(World world, BlockPos pos) {
return new ItemDisplayBlockEntityElement(this, pos);
}
public Item<?> item(Player player) {
return this.item.apply(player);
}
public Vector3f scale() {
return this.scale;
}
public Vector3f translation() {
return this.translation;
}
public Vector3f position() {
return this.position;
}
public float yRot() {
return this.yRot;
}
public float xRot() {
return this.xRot;
}
public Billboard billboard() {
return billboard;
}
public ItemDisplayContext displayContext() {
return displayContext;
}
public Quaternionf rotation() {
return rotation;
}
public List<Object> metadataValues(Player player) {
return this.lazyMetadataPacket.apply(player);
}
public static class Factory implements BlockEntityElementConfigFactory {
@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),
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))
);
}
}
}

View File

@@ -0,0 +1,42 @@
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
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.MEntityTypes;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.world.BlockPos;
import org.joml.Vector3f;
import java.util.List;
import java.util.UUID;
public class TextDisplayBlockEntityElement implements BlockEntityElement {
private final TextDisplayBlockEntityElementConfig config;
private final Object cachedSpawnPacket;
private final Object cachedDespawnPacket;
private final int entityId;
public TextDisplayBlockEntityElement(TextDisplayBlockEntityElementConfig config, BlockPos pos) {
int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
Vector3f position = config.position();
this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId, UUID.randomUUID(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z,
config.xRot(), config.yRot(), MEntityTypes.TEXT_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
);
this.config = config;
this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(IntList.of(entityId));
this.entityId = entityId;
}
@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))), true);
}
}

View File

@@ -0,0 +1,123 @@
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
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.player.Player;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
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.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 TextDisplayBlockEntityElementConfig(String text,
Vector3f scale,
Vector3f position,
Vector3f translation,
float xRot,
float yRot,
Quaternionf rotation,
Billboard billboard) {
this.text = text;
this.scale = scale;
this.position = position;
this.translation = translation;
this.xRot = xRot;
this.yRot = yRot;
this.rotation = rotation;
this.billboard = billboard;
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);
return dataValues;
};
}
@Override
public TextDisplayBlockEntityElement create(World world, BlockPos pos) {
return new TextDisplayBlockEntityElement(this, pos);
}
public Component text(Player player) {
return AdventureHelper.miniMessage().deserialize(this.text, PlayerOptionalContext.of(player).tagResolvers());
}
public Vector3f scale() {
return this.scale;
}
public Vector3f translation() {
return this.translation;
}
public Vector3f position() {
return this.position;
}
public float yRot() {
return this.yRot;
}
public float xRot() {
return this.xRot;
}
public Billboard billboard() {
return billboard;
}
public Quaternionf rotation() {
return rotation;
}
public List<Object> metadataValues(Player player) {
return this.lazyMetadataPacket.apply(player);
}
public static class Factory implements BlockEntityElementConfigFactory {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> 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(
text,
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"),
Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT))
);
}
}
}

View File

@@ -15,10 +15,10 @@ public class DisplayEntityData<T> extends BaseEntityData<T> {
public static final DisplayEntityData<Integer> TransformationInterpolationDuration = of(DisplayEntityData.class, EntityDataValue.Serializers$INT, 0, VersionHelper.isOrAbove1_20_2());
public static final DisplayEntityData<Integer> PositionRotationInterpolationDuration = of(DisplayEntityData.class, EntityDataValue.Serializers$INT, 0, VersionHelper.isOrAbove1_20_2());
public static final DisplayEntityData<Object> Translation = of(DisplayEntityData.class, EntityDataValue.Serializers$VECTOR3, new Vector3f(0f), true);
public static final DisplayEntityData<Object> Scale = of(DisplayEntityData.class, EntityDataValue.Serializers$VECTOR3, new Vector3f(1f), true);
public static final DisplayEntityData<Object> RotationLeft = of(DisplayEntityData.class, EntityDataValue.Serializers$QUATERNION, new Quaternionf(0f, 0f, 0f, 1f), true);
public static final DisplayEntityData<Object> RotationRight = of(DisplayEntityData.class, EntityDataValue.Serializers$QUATERNION, new Quaternionf(0f, 0f, 0f, 1f), true);
public static final DisplayEntityData<Vector3f> Translation = of(DisplayEntityData.class, EntityDataValue.Serializers$VECTOR3, new Vector3f(0f), true);
public static final DisplayEntityData<Vector3f> Scale = of(DisplayEntityData.class, EntityDataValue.Serializers$VECTOR3, new Vector3f(1f), true);
public static final DisplayEntityData<Quaternionf> RotationLeft = of(DisplayEntityData.class, EntityDataValue.Serializers$QUATERNION, new Quaternionf(0f, 0f, 0f, 1f), true);
public static final DisplayEntityData<Quaternionf> RotationRight = of(DisplayEntityData.class, EntityDataValue.Serializers$QUATERNION, new Quaternionf(0f, 0f, 0f, 1f), true);
/**
* Billboard Constraints (0 = FIXED, 1 = VERTICAL, 2 = HORIZONTAL, 3 = CENTER)
*/

View File

@@ -1,5 +1,7 @@
package net.momirealms.craftengine.bukkit.entity.data;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import java.util.List;
public interface EntityData<T> {
@@ -27,6 +29,11 @@ public interface EntityData<T> {
list.add(EntityDataValue.create(id(), serializer(), entityDataAccessor(), value));
}
@SuppressWarnings("unchecked")
default T get(Object entityData) {
return (T) FastNMS.INSTANCE.method$SynchedEntityData$get(entityData, entityDataAccessor());
}
static <T> EntityData<T> of(Class<?> clazz, Object serializer, T defaultValue) {
return new SimpleEntityData<>(clazz, serializer, defaultValue);
}

View File

@@ -71,7 +71,8 @@ public class EntityDataValue {
if (VersionHelper.isOrAbove1_21_5()) Serializers$OPTIONAL_LIVING_ENTITY_REFERENCE = initSerializersByName("OPTIONAL_LIVING_ENTITY_REFERENCE");
else Serializers$OPTIONAL_LIVING_ENTITY_REFERENCE = null;
Serializers$OPTIONAL_GLOBAL_POS = initSerializersByName("OPTIONAL_GLOBAL_POS");
Serializers$COMPOUND_TAG = initSerializersByName("COMPOUND_TAG");
if (!VersionHelper.isOrAbove1_21_9()) Serializers$COMPOUND_TAG = initSerializersByName("COMPOUND_TAG");
else Serializers$COMPOUND_TAG = null;
Serializers$VILLAGER_DATA = initSerializersByName("VILLAGER_DATA");
Serializers$OPTIONAL_UNSIGNED_INT = initSerializersByName("OPTIONAL_UNSIGNED_INT");
Serializers$POSE = initSerializersByName("POSE");

View File

@@ -15,7 +15,7 @@ import net.momirealms.craftengine.core.entity.furniture.*;
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.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.bukkit.*;
@@ -337,6 +337,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
plugin.scheduler().sync().runDelayed(() -> tryLeavingSeat(player, entity), player.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
@SuppressWarnings("DuplicatedCode")
protected void tryLeavingSeat(@NotNull Player player, @NotNull Entity vehicle) {
Integer baseFurniture = vehicle.getPersistentDataContainer().get(FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER);
if (baseFurniture == null) return;
@@ -350,7 +351,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
plugin.logger().warn("Failed to get vector3f for player " + player.getName() + "'s seat");
return;
}
Vector3f seatPos = MiscUtils.getAsVector3f(vector3f, "seat");
Vector3f seatPos = ResourceConfigUtils.getAsVector3f(vector3f, "seat");
furniture.removeOccupiedSeat(seatPos);
if (player.getVehicle() != null) return;
@@ -375,6 +376,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
return (entity instanceof ArmorStand || entity instanceof ItemDisplay);
}
@SuppressWarnings("DuplicatedCode")
private boolean isSafeLocation(Location location) {
World world = location.getWorld();
if (world == null) return false;
@@ -386,6 +388,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
return world.getBlockAt(x, y + 1, z).isPassable();
}
@SuppressWarnings("DuplicatedCode")
@Nullable
private Location findSafeLocationNearby(Location center) {
World world = center.getWorld();

View File

@@ -8,7 +8,6 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkRefl
import net.momirealms.craftengine.core.entity.furniture.*;
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.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.WorldPosition;
@@ -84,7 +83,7 @@ public class CustomHitBox extends AbstractHitBox {
@Override
public HitBox create(Map<String, Object> arguments) {
Vector3f position = MiscUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
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));

View File

@@ -9,7 +9,6 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityType
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.World;
@@ -124,7 +123,7 @@ public class HappyGhastHitBox extends AbstractHitBox {
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new HappyGhastHitBox(
HitBoxFactory.getSeats(arguments),
MiscUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"),
scale, canUseOn, blocksBuilding, canBeHitByProjectile, hardCollision
);
}

View File

@@ -7,7 +7,6 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflect
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
@@ -93,7 +92,7 @@ public class InteractionHitBox extends AbstractHitBox {
@Override
public HitBox create(Map<String, Object> arguments) {
Vector3f position = MiscUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
float width;
float height;
if (arguments.containsKey("scale")) {

View File

@@ -171,7 +171,7 @@ public class ShulkerHitBox extends AbstractHitBox {
}
private static float getPhysicalPeek(float peek) {
return 0.5F - MCUtils.sin((0.5F + peek) * 3.1415927F) * 0.5F;
return 0.5F - MiscUtils.sin((0.5F + peek) * 3.1415927F) * 0.5F;
}
public boolean interactionEntity() {
@@ -280,7 +280,7 @@ public class ShulkerHitBox extends AbstractHitBox {
@Override
public HitBox create(Map<String, Object> arguments) {
Vector3f position = MiscUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
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);

View File

@@ -36,11 +36,17 @@ import java.lang.reflect.InvocationTargetException;
import java.util.*;
public class BukkitFontManager extends AbstractFontManager implements Listener {
private static BukkitFontManager instance;
private final BukkitCraftEngine plugin;
public BukkitFontManager(BukkitCraftEngine plugin) {
super(plugin);
this.plugin = plugin;
instance = this;
}
public static BukkitFontManager instance() {
return instance;
}
@Override

View File

@@ -20,7 +20,6 @@ import net.momirealms.craftengine.core.item.recipe.DatapackRecipeResult;
import net.momirealms.craftengine.core.item.recipe.UniqueIdItem;
import net.momirealms.craftengine.core.pack.AbstractPackManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.util.*;
@@ -337,7 +336,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
@Override
public ItemStack buildCustomItemStack(Key id, Player player) {
return Optional.ofNullable(this.customItemsById.get(id)).map(it -> it.buildItemStack(new ItemBuildContext(player, ContextHolder.EMPTY), 1)).orElse(null);
return Optional.ofNullable(this.customItemsById.get(id)).map(it -> it.buildItemStack(ItemBuildContext.of(player), 1)).orElse(null);
}
@Override

View File

@@ -131,7 +131,7 @@ public class AxeItemBehavior extends ItemBehavior {
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
return INSTANCE;
}
}

View File

@@ -23,6 +23,7 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.PendingConfigSection;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
@@ -73,7 +74,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
return InteractionResult.FAIL;
}
if (!context.canPlace()) {
return InteractionResult.FAIL;
return InteractionResult.PASS;
}
Player player = context.getPlayer();
@@ -89,7 +90,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
ImmutableBlockState blockStateToPlace = getPlacementState(context, block);
if (blockStateToPlace == null) {
return InteractionResult.FAIL;
return InteractionResult.PASS;
}
BlockPos againstPos = context.getAgainstPos();
@@ -232,20 +233,24 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
return this.blockId;
}
static void addPendingSection(Pack pack, Path path, String node, Key key, Map<?, ?> map) {
if (map.containsKey(key.toString())) {
// 防呆
BukkitBlockManager.instance().blockParser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)));
} else {
BukkitBlockManager.instance().blockParser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map, false)));
}
}
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
Object id = arguments.get("block");
if (id == null) {
throw new LocalizedResourceConfigException("warning.config.item.behavior.block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for block_item behavior"));
}
if (id instanceof Map<?, ?> map) {
if (map.containsKey(key.toString())) {
// 防呆
BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false));
} else {
BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
}
addPendingSection(pack, path, node, key, map);
return new BlockItemBehavior(key);
} else {
return new BlockItemBehavior(Key.of(id.toString()));

View File

@@ -12,6 +12,7 @@ public class BukkitItemBehaviors extends ItemBehaviors {
public static final Key COMPOSTABLE_ITEM = Key.from("craftengine:compostable_item");
public static final Key AXE_ITEM = Key.from("craftengine:axe_item");
public static final Key DOUBLE_HIGH_BLOCK_ITEM = Key.from("craftengine:double_high_block_item");
public static final Key WALL_BLOCK_ITEM = Key.from("craftengine:wall_block_item");
public static final Key COMPASS_ITEM = Key.from("craftengine:compass_item");
public static final Key ENDER_EYE_ITEM = Key.from("craftengine:ender_eye_item");
public static final Key END_CRYSTAL_ITEM = Key.from("craftengine:end_crystal_item");
@@ -25,6 +26,7 @@ public class BukkitItemBehaviors extends ItemBehaviors {
register(COMPOSTABLE_ITEM, CompostableItemBehavior.FACTORY);
register(AXE_ITEM, AxeItemBehavior.FACTORY);
register(DOUBLE_HIGH_BLOCK_ITEM, DoubleHighBlockItemBehavior.FACTORY);
register(WALL_BLOCK_ITEM, WallBlockItemBehavior.FACTORY);
register(COMPASS_ITEM, CompassItemBehavior.FACTORY);
register(ENDER_EYE_ITEM, EnderEyeItemBehavior.FACTORY);
register(END_CRYSTAL_ITEM, EndCrystalItemBehavior.FACTORY);

View File

@@ -79,7 +79,7 @@ public class CompostableItemBehavior extends ItemBehavior {
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
double chance = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("chance", 0.55), "chance");
return new CompostableItemBehavior(chance);
}

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.item.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids;
@@ -11,7 +10,6 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import org.bukkit.Location;
import org.bukkit.block.BlockState;
@@ -40,18 +38,13 @@ public class DoubleHighBlockItemBehavior extends BlockItemBehavior {
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
Object id = arguments.get("block");
if (id == null) {
throw new LocalizedResourceConfigException("warning.config.item.behavior.double_high.missing_block", new IllegalArgumentException("Missing required parameter 'block' for double_high_block_item behavior"));
throw new LocalizedResourceConfigException("warning.config.item.behavior.double_high.missing_block");
}
if (id instanceof Map<?, ?> map) {
if (map.containsKey(key.toString())) {
// 防呆
BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false));
} else {
BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
}
addPendingSection(pack, path, node, key, map);
return new DoubleHighBlockItemBehavior(key);
} else {
return new DoubleHighBlockItemBehavior(Key.of(id.toString()));

View File

@@ -90,7 +90,7 @@ public class FlintAndSteelItemBehavior extends ItemBehavior {
}
// 且没有shift或者忽略潜行的可交互方块
if (!player.isSecondaryUseActive()) {
player.playSound(FLINT_SOUND, firePos, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f));
player.playSound(firePos, FLINT_SOUND, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f));
}
} else {
// 玩家觉得自定义方块不可燃,且点击了侧面,那么就要判断火源下方的方块是否可燃,如果不可燃,则补发声音
@@ -113,16 +113,16 @@ public class FlintAndSteelItemBehavior extends ItemBehavior {
if (player.isSecondaryUseActive()) {
// 如果底部不能燃烧,则燃烧点位为侧面,需要补发
if (!belowCanBurn) {
player.playSound(FLINT_SOUND, firePos, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f));
player.playSound(firePos, FLINT_SOUND, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f));
player.swingHand(context.getHand());
}
} else {
player.playSound(FLINT_SOUND, firePos, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f));
player.playSound(firePos, FLINT_SOUND, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f));
}
} else {
// 如果底部方块不可燃烧才补发
if (!belowCanBurn) {
player.playSound(FLINT_SOUND, firePos, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f));
player.playSound(firePos, FLINT_SOUND, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f));
player.swingHand(context.getHand());
}
}
@@ -153,7 +153,7 @@ public class FlintAndSteelItemBehavior extends ItemBehavior {
}
}
}
player.playSound(FLINT_SOUND, firePos, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f));
player.playSound(firePos, FLINT_SOUND, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f));
player.swingHand(context.getHand());
}
return InteractionResult.PASS;
@@ -161,7 +161,7 @@ public class FlintAndSteelItemBehavior extends ItemBehavior {
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments) {
public ItemBehavior create(Pack pack, Path path, String node, Key id, Map<String, Object> arguments) {
return INSTANCE;
}
}

View File

@@ -20,6 +20,7 @@ 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.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
@@ -177,7 +178,7 @@ public class FurnitureItemBehavior extends ItemBehavior {
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
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"));
@@ -185,9 +186,9 @@ public class FurnitureItemBehavior extends ItemBehavior {
if (id instanceof Map<?,?> map) {
if (map.containsKey(key.toString())) {
// 防呆
BukkitFurnitureManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false));
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)));
} else {
BukkitFurnitureManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map, false)));
}
return new FurnitureItemBehavior(key);
} else {

View File

@@ -3,6 +3,8 @@ package net.momirealms.craftengine.bukkit.item.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
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.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
@@ -45,15 +47,19 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior {
try {
if (player == null) return InteractionResult.FAIL;
Object blockHitResult = CoreReflections.method$Item$getPlayerPOVHitResult.invoke(null, world.serverWorld(), player.serverPlayer(), CoreReflections.instance$ClipContext$Fluid$SOURCE_ONLY);
Object blockPos = CoreReflections.field$BlockHitResul$blockPos.get(blockHitResult);
Object blockPos = FastNMS.INSTANCE.field$BlockHitResult$blockPos(blockHitResult);
BlockPos above = new BlockPos(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos) + offsetY, FastNMS.INSTANCE.field$Vec3i$z(blockPos));
Direction direction = Direction.values()[(int) CoreReflections.method$Direction$ordinal.invoke(CoreReflections.field$BlockHitResul$direction.get(blockHitResult))];
boolean miss = CoreReflections.field$BlockHitResul$miss.getBoolean(blockHitResult);
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.WATER && fluidType != MFluids.LAVA) {
return InteractionResult.PASS;
}
if (miss) {
return super.useOnBlock(new UseOnContext(player, hand, BlockHitResult.miss(hitPos, direction, above)));
} else {
boolean inside = CoreReflections.field$BlockHitResul$inside.getBoolean(blockHitResult);
boolean inside = CoreReflections.field$BlockHitResult$inside.getBoolean(blockHitResult);
return super.useOnBlock(new UseOnContext(player, hand, new BlockHitResult(hitPos, direction, above, inside)));
}
} catch (Exception e) {
@@ -64,7 +70,7 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior {
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
Object id = arguments.get("block");
if (id == null) {
throw new LocalizedResourceConfigException("warning.config.item.behavior.liquid_collision.missing_block", new IllegalArgumentException("Missing required parameter 'block' for liquid_collision_block_item behavior"));
@@ -73,9 +79,9 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior {
if (id instanceof Map<?, ?> map) {
if (map.containsKey(key.toString())) {
// 防呆
BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false));
BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false));
} else {
BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false));
}
return new LiquidCollisionBlockItemBehavior(key, offset);
} else {

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