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

Merge pull request #402 from Xiao-MoMi/main

更新上游
This commit is contained in:
XiaoMoMi
2025-10-07 00:10:19 +08:00
committed by GitHub
343 changed files with 16995 additions and 12105 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

@@ -1,5 +1,5 @@
plugins {
id("com.gradleup.shadow") version "9.0.0-rc2"
id("com.gradleup.shadow") version "9.2.2"
id("maven-publish")
}

View File

@@ -16,6 +16,7 @@ repositories {
maven("https://repo.auxilor.io/repository/maven-public/") // eco
maven("https://repo.hiusers.com/releases") // zaphkiel
maven("https://jitpack.io") // sxitem slimefun
maven("https://repo.codemc.io/repository/maven-public/") // quickshop
}
dependencies {
@@ -79,6 +80,8 @@ dependencies {
compileOnly("com.github.Saukiya:SX-Item:4.4.6")
// Slimefun
compileOnly("io.github.Slimefun:Slimefun4:RC-32")
// QuickShop
compileOnly("com.ghostchu:quickshop-api:6.2.0.10")
}
java {

View File

@@ -12,6 +12,7 @@ import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicItemDrop
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.quickshop.QuickShopItemExpressionHandler;
import net.momirealms.craftengine.bukkit.compatibility.region.WorldGuardRegionCondition;
import net.momirealms.craftengine.bukkit.compatibility.skript.SkriptHook;
import net.momirealms.craftengine.bukkit.compatibility.slimeworld.SlimeFormatStorageAdaptor;
@@ -20,12 +21,14 @@ 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;
@@ -139,6 +142,10 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
ModelEngineUtils.registerConstantBlockEntityRender();
logHook("ModelEngine");
}
if (this.isPluginEnabled("QuickShop-Hikari")) {
new QuickShopItemExpressionHandler(this.plugin).register();
logHook("QuickShop-Hikari");
}
}
@Override
@@ -246,8 +253,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);

View File

@@ -16,7 +16,7 @@ public class BetterModelModel extends AbstractExternalModel {
@Override
public void bindModel(AbstractEntity entity) {
org.bukkit.entity.Entity bukkitEntity = (org.bukkit.entity.Entity) entity.literalObject();
org.bukkit.entity.Entity bukkitEntity = (org.bukkit.entity.Entity) entity.platformEntity();
BetterModelUtils.bindModel(bukkitEntity, id());
}
}

View File

@@ -16,7 +16,7 @@ public class ModelEngineModel extends AbstractExternalModel {
@Override
public void bindModel(AbstractEntity entity) {
org.bukkit.entity.Entity bukkitEntity = (org.bukkit.entity.Entity) entity.literalObject();
org.bukkit.entity.Entity bukkitEntity = (org.bukkit.entity.Entity) entity.platformEntity();
ModelEngineUtils.bindModel(bukkitEntity, id());
}
}

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;
@@ -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,48 @@
package net.momirealms.craftengine.bukkit.compatibility.quickshop;
import com.ghostchu.quickshop.api.QuickShopAPI;
import com.ghostchu.quickshop.api.event.QSConfigurationReloadEvent;
import com.ghostchu.quickshop.api.registry.BuiltInRegistry;
import com.ghostchu.quickshop.api.registry.Registry;
import com.ghostchu.quickshop.api.registry.builtin.itemexpression.ItemExpressionHandler;
import com.ghostchu.quickshop.api.registry.builtin.itemexpression.ItemExpressionRegistry;
import net.momirealms.craftengine.bukkit.api.CraftEngineItems;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
public class QuickShopItemExpressionHandler implements ItemExpressionHandler, Listener {
private final BukkitCraftEngine plugin;
public QuickShopItemExpressionHandler(BukkitCraftEngine plugin) {
this.plugin = plugin;
}
public void register() {
Registry registry = QuickShopAPI.getInstance().getRegistry().getRegistry(BuiltInRegistry.ITEM_EXPRESSION);
if (!(registry instanceof ItemExpressionRegistry itemExpressionRegistry)) return;
itemExpressionRegistry.registerHandlerSafely(this);
}
@Override
public @NotNull Plugin getPlugin() {
return this.plugin.javaPlugin();
}
@Override
public String getPrefix() {
return "craftengine";
}
@Override
public boolean match(ItemStack itemStack, String id) {
Key customId = CraftEngineItems.getCustomItemId(itemStack);
return customId != null && id.equals(customId.asString());
}
}

View File

@@ -1,5 +1,5 @@
plugins {
id("com.gradleup.shadow") version "9.0.0-rc2"
id("com.gradleup.shadow") version "9.2.2"
}
repositories {
@@ -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

@@ -1,5 +1,5 @@
plugins {
id("com.gradleup.shadow") version "9.0.0-rc2"
id("com.gradleup.shadow") version "9.2.2"
id("de.eldoria.plugin-yml.bukkit") version "0.7.1"
}

View File

@@ -13,7 +13,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

@@ -1,7 +1,7 @@
import net.minecrell.pluginyml.paper.PaperPluginDescription
plugins {
id("com.gradleup.shadow") version "9.0.0-rc2"
id("com.gradleup.shadow") version "9.2.2"
id("de.eldoria.plugin-yml.paper") version "0.7.1"
}
@@ -75,6 +75,7 @@ paper {
}
register("LuckPerms") { required = false }
register("ViaVersion") { required = false }
register("QuickShop-Hikari") { required = false }
// external models
register("ModelEngine") { required = false }

View File

@@ -55,7 +55,7 @@ public class PaperCraftEngineBootstrap implements PluginBootstrap {
);
}
this.plugin.applyDependencies();
this.plugin.setUpConfig();
this.plugin.setUpConfigAndLocale();
if (isDatapackDiscoveryAvailable()) {
new ModernEventHandler(context, this.plugin).register();
} else {

View File

@@ -14,6 +14,7 @@ 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;
@@ -105,7 +106,7 @@ public class BukkitAdvancementManager extends AbstractAdvancementManager {
}
}
public class AdvancementParser implements ConfigParser {
public class AdvancementParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"advancements", "advancement"};
@Override
@@ -119,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

@@ -188,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());
@@ -215,16 +213,34 @@ public final class CraftEngineBlocks {
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
*
@@ -270,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

@@ -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

@@ -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,7 +159,7 @@ 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
@@ -193,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);
}
}
}
@@ -261,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

@@ -2,122 +2,89 @@ package net.momirealms.craftengine.bukkit.block;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
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.injector.BlockGenerator;
import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers;
import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections;
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.MBuiltInRegistries;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.*;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.bukkit.util.RegistryUtils;
import net.momirealms.craftengine.bukkit.util.TagUtils;
import net.momirealms.craftengine.bukkit.util.*;
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.parser.BlockStateParser;
import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
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.plugin.logger.Debugger;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.registry.WritableRegistry;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.sound.Sounds;
import net.momirealms.craftengine.core.sound.SoundSet;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.chunk.PalettedContainer;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.data.BlockData;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public final class BukkitBlockManager extends AbstractBlockManager {
public static final Set<Object> CLIENT_SIDE_NOTE_BLOCKS = new HashSet<>(2048, 0.6f);
private static final Object ALWAYS_FALSE = FastNMS.INSTANCE.method$StatePredicate$always(false);
private static final Object ALWAYS_TRUE = FastNMS.INSTANCE.method$StatePredicate$always(true);
private static BukkitBlockManager instance;
private final BukkitCraftEngine plugin;
// The total amount of blocks registered
private int customBlockCount;
private ImmutableBlockState[] stateId2ImmutableBlockStates;
// Minecraft objects
// Cached new blocks $ holders
private Map<Integer, Object> stateId2BlockHolder;
// This map is used to change the block states that are not necessarily needed into a certain block state
private Map<Integer, Integer> blockAppearanceMapper;
// Record the amount of real blocks by block type
private Map<Key, Integer> registeredRealBlockSlots;
// A set of blocks that sounds have been removed
private Set<Object> affectedSoundBlocks;
private Map<Object, Pair<SoundData, SoundData>> affectedOpenableBlockSounds;
private Map<Key, Key> soundMapper;
// A list to record the order of registration
private List<Key> blockRegisterOrder = new ObjectArrayList<>();
// Event listeners
private BlockEventListener blockEventListener;
// cached tag packet
// 事件监听器
private final BlockEventListener blockEventListener;
// 用于缓存string形式的方块状态到原版方块状态
private final Map<String, BlockStateWrapper> blockStateCache = new HashMap<>(1024);
// 用于临时存储可燃烧自定义方块的列表
private final List<DelegatingBlock> burnableBlocks = new ArrayList<>();
// 可燃烧的方块
private Map<Object, Integer> igniteOdds;
private Map<Object, Integer> burnOdds;
// 自定义客户端侧原版方块标签
private Map<Integer, List<String>> clientBoundTags = Map.of();
private Map<Integer, List<String>> previousClientBoundTags = Map.of();
// 缓存的原版方块tag包
private Object cachedUpdateTagsPacket;
private final List<Tuple<Object, Key, Boolean>> blocksToDeceive = new ArrayList<>();
// 被移除声音的原版方块
private Set<Object> missingPlaceSounds = Set.of();
private Set<Object> missingBreakSounds = Set.of();
private Set<Object> missingHitSounds = Set.of();
private Set<Object> missingStepSounds = Set.of();
private Set<Key> missingInteractSoundBlocks = Set.of();
public BukkitBlockManager(BukkitCraftEngine plugin) {
super(plugin);
instance = this;
super(plugin, RegistryUtils.currentBlockRegistrySize(), Config.serverSideBlocks());
this.plugin = plugin;
this.initVanillaRegistry();
this.loadMappingsAndAdditionalBlocks();
this.registerBlocks();
this.registerEmptyBlock();
this.blockEventListener = new BlockEventListener(plugin, this);
this.registerServerSideCustomBlocks(Config.serverSideBlocks());
EmptyBlock.initialize();
instance = this;
}
@Override
public void init() {
this.initMirrorRegistry();
this.deceiveBukkit();
boolean enableNoteBlocks = this.blockAppearanceArranger.containsKey(BlockKeys.NOTE_BLOCK);
this.blockEventListener = new BlockEventListener(plugin, this, enableNoteBlocks);
if (enableNoteBlocks) {
this.recordVanillaNoteBlocks();
}
this.stateId2ImmutableBlockStates = new ImmutableBlockState[this.customBlockCount];
Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.defaultState());
this.resetPacketConsumers();
}
@Override
public String stateRegistryIdToStateSNBT(int id) {
return BlockStateUtils.idToBlockState(id).toString();
this.initFireBlock();
this.deceiveBukkitRegistry();
this.markVanillaNoteBlocks();
Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState());
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 一定要预先初始化一次预防id超出上限
}
public static BukkitBlockManager instance() {
return instance;
}
public List<Key> blockRegisterOrder() {
return Collections.unmodifiableList(this.blockRegisterOrder);
}
@Override
public void delayedInit() {
Bukkit.getPluginManager().registerEvents(this.blockEventListener, this.plugin.javaPlugin());
@@ -126,9 +93,16 @@ public final class BukkitBlockManager extends AbstractBlockManager {
@Override
public void unload() {
super.unload();
this.previousClientBoundTags = this.clientBoundTags;
this.clientBoundTags = new HashMap<>();
for (DelegatingBlock block : this.burnableBlocks) {
this.igniteOdds.remove(block);
this.burnOdds.remove(block);
}
this.burnableBlocks.clear();
if (EmptyBlock.STATE != null)
Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.STATE);
for (DelegatingBlock block : this.registeredBlocks.values()) {
Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE);
for (DelegatingBlock block : this.customBlocks) {
block.behaviorDelegate().bindValue(EmptyBlockBehavior.INSTANCE);
block.shapeDelegate().bindValue(BukkitBlockShape.STONE);
DelegatingBlockState state = (DelegatingBlockState) FastNMS.INSTANCE.method$Block$defaultState(block);
@@ -143,14 +117,24 @@ public final class BukkitBlockManager extends AbstractBlockManager {
}
@Override
public Map<Key, Key> soundMapper() {
return this.soundMapper;
public void delayedLoad() {
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 重置方块映射表
super.delayedLoad();
}
@Override
public void delayedLoad() {
this.resetPacketConsumers();
super.delayedLoad();
public BlockBehavior createBlockBehavior(CustomBlock customBlock, List<Map<String, Object>> behaviorConfig) {
if (behaviorConfig == null || behaviorConfig.isEmpty()) {
return new EmptyBlockBehavior();
} else if (behaviorConfig.size() == 1) {
return BlockBehaviors.fromMap(customBlock, behaviorConfig.getFirst());
} else {
List<AbstractBlockBehavior> behaviors = new ArrayList<>();
for (Map<String, Object> config : behaviorConfig) {
behaviors.add((AbstractBlockBehavior) BlockBehaviors.fromMap(customBlock, config));
}
return new UnsafeCompositeBlockBehavior(customBlock, behaviors);
}
}
@Override
@@ -180,47 +164,36 @@ public final class BukkitBlockManager extends AbstractBlockManager {
if (state != null) {
return state.customBlockState();
}
return createVanillaBlockState(blockState);
}
@Override
public BlockStateWrapper createVanillaBlockState(String blockState) {
return this.blockStateCache.computeIfAbsent(blockState, k -> {
Object state = parseBlockState(k);
if (state == null) return null;
return BlockStateUtils.toBlockStateWrapper(state);
});
}
@Nullable
private Object parseBlockState(String state) {
try {
BlockData blockData = Bukkit.createBlockData(blockState);
return BlockStateUtils.toBlockStateWrapper(blockData);
} catch (IllegalArgumentException e) {
Object registryOrLookUp = MBuiltInRegistries.BLOCK;
if (CoreReflections.method$Registry$asLookup != null) {
registryOrLookUp = CoreReflections.method$Registry$asLookup.invoke(registryOrLookUp);
}
Object result = CoreReflections.method$BlockStateParser$parseForBlock.invoke(null, registryOrLookUp, state, false);
return CoreReflections.method$BlockStateParser$BlockResult$blockState.invoke(result);
} catch (Exception e) {
Debugger.BLOCK.warn(() -> "Failed to create block state: " + state, e);
return null;
}
}
@Nullable
public Object getMinecraftBlockHolder(int stateId) {
return this.stateId2BlockHolder.get(stateId);
}
@NotNull
@Override
public ImmutableBlockState getImmutableBlockStateUnsafe(int stateId) {
return this.stateId2ImmutableBlockStates[stateId - BlockStateUtils.vanillaStateSize()];
}
@Nullable
@Override
public ImmutableBlockState getImmutableBlockState(int stateId) {
if (!BlockStateUtils.isVanillaBlock(stateId)) {
return this.stateId2ImmutableBlockStates[stateId - BlockStateUtils.vanillaStateSize()];
}
return null;
}
@Override
public void addBlockInternal(Key id, CustomBlock customBlock) {
// bind appearance and real state
for (ImmutableBlockState state : customBlock.variantProvider().states()) {
ImmutableBlockState previous = this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()];
if (previous != null && !previous.isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.block.state.bind_failed", state.toString(), previous.toString(), BlockStateUtils.getBlockOwnerIdFromState(previous.customBlockState().literalObject()).toString());
}
this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state;
this.tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId());
this.appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new IntArrayList()).add(state.customBlockState().registryId());
}
super.addBlockInternal(id, customBlock);
return this.customBlockHolders[stateId - BlockStateUtils.vanillaBlockStateCount()];
}
@Override
@@ -233,449 +206,312 @@ public final class BukkitBlockManager extends AbstractBlockManager {
return BlockStateUtils.getBlockOwnerIdFromState(BlockStateUtils.idToBlockState(id));
}
@SuppressWarnings("unchecked")
private void initFireBlock() {
try {
this.igniteOdds = (Map<Object, Integer>) CoreReflections.field$FireBlock$igniteOdds.get(MBlocks.FIRE);
this.burnOdds = (Map<Object, Integer>) CoreReflections.field$FireBlock$burnOdds.get(MBlocks.FIRE);
} catch (IllegalAccessException e) {
this.plugin.logger().warn("Failed to get ignite odds", e);
}
}
@Override
public int availableAppearances(Key blockType) {
return Optional.ofNullable(this.registeredRealBlockSlots.get(blockType)).orElse(0);
protected void applyPlatformSettings(ImmutableBlockState state) {
DelegatingBlockState nmsState = (DelegatingBlockState) state.customBlockState().literalObject();
nmsState.setBlockState(state);
Object nmsVisualState = state.vanillaBlockState().literalObject();
BlockSettings settings = state.settings();
try {
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);
CoreReflections.field$BlockStateBase$instrument.set(nmsState, CoreReflections.instance$NoteBlockInstrument$values[settings.instrument().ordinal()]);
CoreReflections.field$BlockStateBase$pushReaction.set(nmsState, CoreReflections.instance$PushReaction$values[settings.pushReaction().ordinal()]);
boolean canOcclude = settings.canOcclude() == Tristate.UNDEFINED ? BlockStateUtils.isOcclude(nmsVisualState) : settings.canOcclude().asBoolean();
CoreReflections.field$BlockStateBase$canOcclude.set(nmsState, canOcclude);
boolean useShapeForLightOcclusion = settings.useShapeForLightOcclusion() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.getBoolean(nmsVisualState) : settings.useShapeForLightOcclusion().asBoolean();
CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.set(nmsState, useShapeForLightOcclusion);
CoreReflections.field$BlockStateBase$isRedstoneConductor.set(nmsState, settings.isRedstoneConductor().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE);
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));
DelegatingBlock nmsBlock = (DelegatingBlock) BlockStateUtils.getBlockOwner(nmsState);
ObjectHolder<BlockShape> shapeHolder = nmsBlock.shapeDelegate();
shapeHolder.bindValue(new BukkitBlockShape(nmsVisualState, Optional.ofNullable(state.settings().supportShapeBlockState()).map(it -> Objects.requireNonNull(createVanillaBlockState(it), "Illegal block state: " + it).literalObject()).orElse(null)));
ObjectHolder<BlockBehavior> behaviorHolder = nmsBlock.behaviorDelegate();
behaviorHolder.bindValue(state.behavior());
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()));
CoreReflections.method$BlockStateBase$initCache.invoke(nmsState);
boolean isConditionallyFullOpaque = canOcclude & useShapeForLightOcclusion;
if (!VersionHelper.isOrAbove1_21_2()) {
CoreReflections.field$BlockStateBase$isConditionallyFullOpaque.set(nmsState, isConditionallyFullOpaque);
}
if (VersionHelper.isOrAbove1_21_2()) {
int blockLight = settings.blockLight() != -1 ? settings.blockLight() : CoreReflections.field$BlockStateBase$lightBlock.getInt(nmsVisualState);
CoreReflections.field$BlockStateBase$lightBlock.set(nmsState, blockLight);
boolean propagatesSkylightDown = settings.propagatesSkylightDown() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$propagatesSkylightDown.getBoolean(nmsVisualState) : settings.propagatesSkylightDown().asBoolean();
CoreReflections.field$BlockStateBase$propagatesSkylightDown.set(nmsState, propagatesSkylightDown);
} 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(nmsVisualState));
CoreReflections.field$BlockStateBase$Cache$lightBlock.set(cache, blockLight);
boolean propagatesSkylightDown = settings.propagatesSkylightDown() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.getBoolean(CoreReflections.field$BlockStateBase$cache.get(nmsVisualState)) : settings.propagatesSkylightDown().asBoolean();
CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.set(cache, propagatesSkylightDown);
if (!isConditionallyFullOpaque) {
CoreReflections.field$BlockStateBase$opacityIfCached.set(nmsState, blockLight);
}
}
CoreReflections.field$BlockStateBase$fluidState.set(nmsState, settings.fluidState() ? MFluids.WATER$defaultState : MFluids.EMPTY$defaultState);
CoreReflections.field$BlockStateBase$isRandomlyTicking.set(nmsState, settings.isRandomlyTicking());
Object holder = BukkitCraftEngine.instance().blockManager().getMinecraftBlockHolder(state.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);
if (settings.burnable()) {
this.igniteOdds.put(nmsBlock, settings.burnChance());
this.burnOdds.put(nmsBlock, settings.fireSpreadChance());
this.burnableBlocks.add(nmsBlock);
}
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to apply platform block settings for block state " + state, e);
}
}
@NotNull
public Map<Key, List<Integer>> blockAppearanceArranger() {
return this.blockAppearanceArranger;
private BlockSounds toBlockSounds(Object soundType) throws ReflectiveOperationException {
return new BlockSounds(
toSoundData(CoreReflections.field$SoundType$breakSound.get(soundType), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8),
toSoundData(CoreReflections.field$SoundType$stepSound.get(soundType), SoundData.SoundValue.FIXED_0_15, SoundData.SoundValue.FIXED_1),
toSoundData(CoreReflections.field$SoundType$placeSound.get(soundType), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8),
toSoundData(CoreReflections.field$SoundType$hitSound.get(soundType), SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.FIXED_0_5),
toSoundData(CoreReflections.field$SoundType$fallSound.get(soundType), SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.FIXED_0_75)
);
}
@NotNull
public Map<Key, List<Integer>> realBlockArranger() {
return this.realBlockArranger;
private SoundData toSoundData(Object soundEvent, SoundData.SoundValue volume, SoundData.SoundValue pitch) {
Key soundId = KeyUtils.resourceLocationToKey(FastNMS.INSTANCE.field$SoundEvent$location(soundEvent));
return new SoundData(soundId, volume, pitch);
}
private void initMirrorRegistry() {
int size = RegistryUtils.currentBlockRegistrySize();
BlockStateWrapper[] states = new BlockStateWrapper[size];
for (int i = 0; i < size; i++) {
states[i] = new BukkitBlockStateWrapper(BlockStateUtils.idToBlockState(i), i);
for (int i = 0; i < this.vanillaBlockStateCount; i++) {
states[i] = new BukkitVanillaBlockStateWrapper(BlockStateUtils.idToBlockState(i), i);
}
BlockRegistryMirror.init(states, new BukkitBlockStateWrapper(MBlocks.STONE$defaultState, BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState)));
}
private void registerEmptyBlock() {
Holder.Reference<CustomBlock> holder = ((WritableRegistry<CustomBlock>) BuiltInRegistries.BLOCK).registerForHolder(ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), Key.withDefaultNamespace("empty")));
EmptyBlock emptyBlock = new EmptyBlock(Key.withDefaultNamespace("empty"), holder);
holder.bindValue(emptyBlock);
}
private void resetPacketConsumers() {
Map<Integer, Integer> finalMapping = new HashMap<>(this.blockAppearanceMapper);
int stoneId = BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState);
for (int custom : this.internalId2StateId.values()) {
finalMapping.put(custom, stoneId);
for (int i = this.vanillaBlockStateCount; i < size; i++) {
states[i] = new BukkitCustomBlockStateWrapper(BlockStateUtils.idToBlockState(i), i);
}
finalMapping.putAll(this.tempBlockAppearanceConvertor);
PacketConsumers.initBlocks(finalMapping, RegistryUtils.currentBlockRegistrySize());
BlockRegistryMirror.init(states, states[BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState)]);
}
private void initVanillaRegistry() {
int vanillaStateCount = RegistryUtils.currentBlockRegistrySize();
this.plugin.logger().info("Vanilla block count: " + vanillaStateCount);
BlockStateUtils.init(vanillaStateCount);
}
@Override
protected CustomBlock.Builder platformBuilder(Key id) {
return BukkitCustomBlock.builder(id);
}
@SuppressWarnings("unchecked")
private void registerBlocks() {
this.plugin.logger().info("Registering blocks. Please wait...");
// 注册服务端侧的真实方块
private void registerServerSideCustomBlocks(int count) {
// 这个会影响全局调色盘
if (MiscUtils.ceilLog2(this.vanillaBlockStateCount + count) == MiscUtils.ceilLog2(this.vanillaBlockStateCount)) {
PalettedContainer.NEED_DOWNGRADE = false;
}
try {
ImmutableMap.Builder<Key, Integer> builder1 = ImmutableMap.builder();
ImmutableMap.Builder<Integer, Object> builder2 = ImmutableMap.builder();
ImmutableMap.Builder<Key, List<Integer>> builder3 = ImmutableMap.builder();
ImmutableMap.Builder<Key, DelegatingBlock> builder4 = ImmutableMap.builder();
Set<Object> affectedBlockSounds = new HashSet<>();
Map<Object, Pair<SoundData, SoundData>> affectedDoors = new IdentityHashMap<>();
Set<Object> affectedBlocks = new HashSet<>();
List<Key> order = new ArrayList<>();
unfreezeRegistry();
int counter = 0;
for (Map.Entry<Key, Integer> baseBlockAndItsCount : this.registeredRealBlockSlots.entrySet()) {
counter = registerBlockVariants(baseBlockAndItsCount, counter, builder1, builder2, builder3, builder4, affectedBlockSounds, order);
for (int i = 0; i < count; i++) {
Key customBlockId = BlockManager.createCustomBlockKey(i);
DelegatingBlock customBlock;
try {
customBlock = BlockGenerator.generateBlock(customBlockId);
} catch (Throwable t) {
CraftEngine.instance().logger().warn("Failed to generate custom block " + customBlockId, t);
break;
}
this.customBlocks[i] = customBlock;
try {
Object resourceLocation = KeyUtils.toResourceLocation(customBlockId);
Object blockHolder = CoreReflections.method$Registry$registerForHolder.invoke(null, MBuiltInRegistries.BLOCK, resourceLocation, customBlock);
this.customBlockHolders[i] = blockHolder;
CoreReflections.method$Holder$Reference$bindValue.invoke(blockHolder, customBlock);
CoreReflections.field$Holder$Reference$tags.set(blockHolder, Set.of());
DelegatingBlockState newBlockState = (DelegatingBlockState) FastNMS.INSTANCE.method$Block$defaultState(customBlock);
this.customBlockStates[i] = newBlockState;
CoreReflections.method$IdMapper$add.invoke(CoreReflections.instance$Block$BLOCK_STATE_REGISTRY, newBlockState);
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to register custom block " + customBlockId, e);
}
}
} finally {
freezeRegistry();
this.plugin.logger().info("Registered block count: " + counter);
this.customBlockCount = counter;
this.internalId2StateId = builder1.build();
this.stateId2BlockHolder = builder2.build();
this.realBlockArranger = builder3.build();
this.registeredBlocks = builder4.build();
this.blockRegisterOrder = ImmutableList.copyOf(order);
if (MCUtils.ceilLog2(BlockStateUtils.vanillaStateSize() + counter) == MCUtils.ceilLog2(BlockStateUtils.vanillaStateSize())) {
PalettedContainer.NEED_DOWNGRADE = false;
}
for (Object block : (Iterable<Object>) MBuiltInRegistries.BLOCK) {
Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(block);
if (affectedBlockSounds.contains(soundType)) {
Object state = FastNMS.INSTANCE.method$Block$defaultState(block);
if (BlockStateUtils.isVanillaBlock(state)) {
affectedBlocks.add(block);
}
}
}
affectedBlocks.remove(MBlocks.FIRE);
affectedBlocks.remove(MBlocks.SOUL_FIRE);
this.affectedSoundBlocks = ImmutableSet.copyOf(affectedBlocks);
ImmutableMap.Builder<Key, Key> soundMapperBuilder = ImmutableMap.builder();
for (Object soundType : affectedBlockSounds) {
for (Field field : List.of(CoreReflections.field$SoundType$placeSound, CoreReflections.field$SoundType$fallSound, CoreReflections.field$SoundType$hitSound, CoreReflections.field$SoundType$stepSound, CoreReflections.field$SoundType$breakSound)) {
Object soundEvent = field.get(soundType);
Key previousId = Key.of(FastNMS.INSTANCE.field$SoundEvent$location(soundEvent).toString());
soundMapperBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value()));
}
}
Predicate<Key> predicate = it -> this.realBlockArranger.containsKey(it);
Consumer<Key> soundCallback = s -> soundMapperBuilder.put(s, Key.of("replaced." + s.value()));
BiConsumer<Object, Pair<SoundData, SoundData>> affectedBlockCallback = affectedDoors::put;
Function<Key, SoundData> soundMapper = (k) -> SoundData.of(k, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f));
collectDoorSounds(predicate, Sounds.WOODEN_TRAPDOOR_OPEN, Sounds.WOODEN_TRAPDOOR_CLOSE, Sounds.WOODEN_TRAPDOORS, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.NETHER_WOOD_TRAPDOOR_OPEN, Sounds.NETHER_WOOD_TRAPDOOR_CLOSE, Sounds.NETHER_TRAPDOORS, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.BAMBOO_WOOD_TRAPDOOR_OPEN, Sounds.BAMBOO_WOOD_TRAPDOOR_CLOSE, Sounds.BAMBOO_TRAPDOORS, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.CHERRY_WOOD_TRAPDOOR_OPEN, Sounds.CHERRY_WOOD_TRAPDOOR_CLOSE, Sounds.CHERRY_TRAPDOORS, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.COPPER_TRAPDOOR_OPEN, Sounds.COPPER_TRAPDOOR_CLOSE, Sounds.COPPER_TRAPDOORS, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.WOODEN_DOOR_OPEN, Sounds.WOODEN_DOOR_CLOSE, Sounds.WOODEN_DOORS, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.NETHER_WOOD_DOOR_OPEN, Sounds.NETHER_WOOD_DOOR_CLOSE, Sounds.NETHER_DOORS, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.BAMBOO_WOOD_DOOR_OPEN, Sounds.BAMBOO_WOOD_DOOR_CLOSE, Sounds.BAMBOO_DOORS, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.CHERRY_WOOD_DOOR_OPEN, Sounds.CHERRY_WOOD_DOOR_CLOSE, Sounds.CHERRY_DOORS, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.COPPER_DOOR_OPEN, Sounds.COPPER_DOOR_CLOSE, Sounds.COPPER_DOORS, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.WOODEN_FENCE_GATE_OPEN, Sounds.WOODEN_FENCE_GATE_CLOSE, Sounds.WOODEN_FENCE_GATES, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.NETHER_WOOD_FENCE_GATE_OPEN, Sounds.NETHER_WOOD_FENCE_GATE_CLOSE, Sounds.NETHER_FENCE_GATES, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.BAMBOO_WOOD_FENCE_GATE_OPEN, Sounds.BAMBOO_WOOD_FENCE_GATE_CLOSE, Sounds.BAMBOO_FENCE_GATES, soundMapper, soundCallback, affectedBlockCallback);
collectDoorSounds(predicate, Sounds.CHERRY_WOOD_FENCE_GATE_OPEN, Sounds.CHERRY_WOOD_FENCE_GATE_CLOSE, Sounds.CHERRY_FENCE_GATES, soundMapper, soundCallback, affectedBlockCallback);
this.affectedOpenableBlockSounds = ImmutableMap.copyOf(affectedDoors);
this.soundMapper = soundMapperBuilder.buildKeepingLast();
} catch (Throwable e) {
plugin.logger().warn("Failed to inject blocks.", e);
}
}
private void collectDoorSounds(Predicate<Key> isUsedForCustomBlock,
Key openSound,
Key closeSound,
List<Key> doors,
Function<Key, SoundData> soundMapper,
Consumer<Key> soundCallback,
BiConsumer<Object, Pair<SoundData, SoundData>> affectedBlockCallback) {
for (Key d : doors) {
if (isUsedForCustomBlock.test(d)) {
soundCallback.accept(openSound);
soundCallback.accept(closeSound);
for (Key door : doors) {
Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(door));
if (block != null) {
affectedBlockCallback.accept(block, Pair.of(soundMapper.apply(Key.of("replaced." + openSound.value())), soundMapper.apply(Key.of("replaced." + closeSound.value()))));
}
}
break;
}
}
}
public Object cachedUpdateTagsPacket() {
return cachedUpdateTagsPacket;
return this.cachedUpdateTagsPacket;
}
private void loadMappingsAndAdditionalBlocks() {
this.plugin.logger().info("Loading mappings.yml.");
Path mappingsFile = this.plugin.dataFolderPath().resolve("mappings.yml");
if (!Files.exists(mappingsFile)) {
this.plugin.saveResource("mappings.yml");
}
Path additionalFile = this.plugin.dataFolderPath().resolve("additional-real-blocks.yml");
if (!Files.exists(additionalFile)) {
this.plugin.saveResource("additional-real-blocks.yml");
}
Yaml yaml = new Yaml(new StringKeyConstructor(mappingsFile, new LoaderOptions()));
Map<Key, Integer> blockTypeCounter = new LinkedHashMap<>();
try (InputStream is = Files.newInputStream(mappingsFile)) {
Map<String, String> blockStateMappings = loadBlockStateMappings(yaml.load(is));
this.validateBlockStateMappings(mappingsFile, blockStateMappings);
Map<Integer, String> stateMap = new Int2ObjectOpenHashMap<>();
Map<Integer, Integer> appearanceMapper = new Int2IntOpenHashMap();
Map<Key, List<Integer>> appearanceArranger = new HashMap<>();
for (Map.Entry<String, String> entry : blockStateMappings.entrySet()) {
this.processBlockStateMapping(mappingsFile, entry, stateMap, blockTypeCounter, appearanceMapper, appearanceArranger);
}
this.blockAppearanceMapper = ImmutableMap.copyOf(appearanceMapper);
this.blockAppearanceArranger = ImmutableMap.copyOf(appearanceArranger);
this.plugin.logger().info("Freed " + this.blockAppearanceMapper.size() + " block state appearances.");
} catch (IOException e) {
throw new RuntimeException("Failed to init mappings.yml", e);
}
try (InputStream is = Files.newInputStream(additionalFile)) {
this.registeredRealBlockSlots = this.buildRegisteredRealBlockSlots(blockTypeCounter, yaml.load(is));
} catch (IOException e) {
throw new RuntimeException("Failed to init additional-real-blocks.yml", e);
}
}
private void recordVanillaNoteBlocks() {
private void markVanillaNoteBlocks() {
try {
Object resourceLocation = KeyUtils.toResourceLocation(BlockKeys.NOTE_BLOCK);
Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, resourceLocation);
Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(BlockKeys.NOTE_BLOCK));
Object stateDefinition = CoreReflections.field$Block$StateDefinition.get(block);
@SuppressWarnings("unchecked")
ImmutableList<Object> states = (ImmutableList<Object>) CoreReflections.field$StateDefinition$states.get(stateDefinition);
for (Object state : states) {
BlockStateUtils.CLIENT_SIDE_NOTE_BLOCKS.put(state, new Object());
}
CLIENT_SIDE_NOTE_BLOCKS.addAll(states);
} catch (ReflectiveOperationException e) {
plugin.logger().warn("Failed to init vanilla note block", e);
}
}
@Nullable
public Key replaceSoundIfExist(Key id) {
return this.soundMapper.get(id);
}
public boolean isBlockSoundRemoved(Object block) {
return this.affectedSoundBlocks.contains(block);
}
public boolean isOpenableBlockSoundRemoved(Object block) {
return this.affectedOpenableBlockSounds.containsKey(block);
}
public SoundData getRemovedOpenableBlockSound(Object block, boolean open) {
return open ? this.affectedOpenableBlockSounds.get(block).left() : this.affectedOpenableBlockSounds.get(block).right();
}
private Map<String, String> loadBlockStateMappings(Map<String, Object> mappings) {
Map<String, String> blockStateMappings = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : mappings.entrySet()) {
if (entry.getValue() instanceof String afterValue) {
blockStateMappings.put(entry.getKey(), afterValue);
}
}
return blockStateMappings;
}
private void validateBlockStateMappings(Path mappingFile, Map<String, String> blockStateMappings) {
Map<String, String> temp = new HashMap<>(blockStateMappings);
for (Map.Entry<String, String> entry : temp.entrySet()) {
String state = entry.getValue();
if (blockStateMappings.containsKey(state)) {
String after = blockStateMappings.remove(state);
plugin.logger().warn(mappingFile, "'" + state + ": " + after + "' is invalid because '" + state + "' has already been used as a base block.");
}
}
}
private void processBlockStateMapping(Path mappingFile,
Map.Entry<String, String> entry,
Map<Integer, String> stateMap,
Map<Key, Integer> counter,
Map<Integer, Integer> mapper,
Map<Key, List<Integer>> arranger) {
Object before = createBlockState(mappingFile, entry.getKey());
Object after = createBlockState(mappingFile, entry.getValue());
if (before == null || after == null) return;
int beforeId = BlockStateUtils.blockStateToId(before);
int afterId = BlockStateUtils.blockStateToId(after);
Integer previous = mapper.put(beforeId, afterId);
if (previous == null) {
Key key = blockOwnerFromString(entry.getKey());
counter.compute(key, (k, count) -> count == null ? 1 : count + 1);
stateMap.put(beforeId, entry.getKey());
stateMap.put(afterId, entry.getValue());
arranger.computeIfAbsent(key, (k) -> new IntArrayList()).add(beforeId);
} else {
String previousState = stateMap.get(previous);
plugin.logger().warn(mappingFile, "Duplicate entry: '" + previousState + "' equals '" + entry.getKey() + "'");
}
}
private Key blockOwnerFromString(String stateString) {
int index = stateString.indexOf('[');
if (index == -1) {
return Key.of(stateString);
} else {
return Key.of(stateString.substring(0, index));
}
}
private Object createBlockState(Path mappingFile, String state) {
try {
Object registryOrLookUp = MBuiltInRegistries.BLOCK;
if (CoreReflections.method$Registry$asLookup != null) {
registryOrLookUp = CoreReflections.method$Registry$asLookup.invoke(registryOrLookUp);
}
Object result = CoreReflections.method$BlockStateParser$parseForBlock.invoke(null, registryOrLookUp, state, false);
return CoreReflections.method$BlockStateParser$BlockResult$blockState.invoke(result);
} catch (Exception e) {
this.plugin.logger().warn(mappingFile, "'" + state + "' is not a valid block state.");
return null;
}
}
private LinkedHashMap<Key, Integer> buildRegisteredRealBlockSlots(Map<Key, Integer> counter, Map<String, Object> additionalYaml) {
LinkedHashMap<Key, Integer> map = new LinkedHashMap<>(counter);
for (Map.Entry<String, Object> entry : additionalYaml.entrySet()) {
Key blockType = Key.of(entry.getKey());
if (entry.getValue() instanceof Integer i) {
int previous = map.getOrDefault(blockType, 0);
if (previous == 0) {
map.put(blockType, i);
this.plugin.logger().info("Loaded " + blockType + " with " + i + " real block states");
} else {
map.put(blockType, i + previous);
this.plugin.logger().info("Loaded " + blockType + " with " + previous + " appearances and " + (i + previous) + " real block states");
}
}
}
return map;
}
private void unfreezeRegistry() throws IllegalAccessException {
CoreReflections.field$MappedRegistry$frozen.set(MBuiltInRegistries.BLOCK, false);
CoreReflections.field$MappedRegistry$unregisteredIntrusiveHolders.set(MBuiltInRegistries.BLOCK, new IdentityHashMap<>());
}
private void freezeRegistry() throws IllegalAccessException {
CoreReflections.field$MappedRegistry$frozen.set(MBuiltInRegistries.BLOCK, true);
}
private int registerBlockVariants(Map.Entry<Key, Integer> blockWithCount,
int counter,
ImmutableMap.Builder<Key, Integer> builder1,
ImmutableMap.Builder<Integer, Object> builder2,
ImmutableMap.Builder<Key, List<Integer>> builder3,
ImmutableMap.Builder<Key, DelegatingBlock> builder4,
Set<Object> affectSoundTypes,
List<Key> order) throws Exception {
Key clientSideBlockType = blockWithCount.getKey();
boolean isNoteBlock = clientSideBlockType.equals(BlockKeys.NOTE_BLOCK);
Object clientSideBlock = getBlockFromRegistry(createResourceLocation(clientSideBlockType));
int amount = blockWithCount.getValue();
List<Integer> stateIds = new IntArrayList();
for (int i = 0; i < amount; i++) {
Key realBlockKey = createRealBlockKey(clientSideBlockType, i);
Object blockProperties = createBlockProperties(realBlockKey);
Object newRealBlock;
Object newBlockState;
Object blockHolder;
Object resourceLocation = createResourceLocation(realBlockKey);
try {
newRealBlock = BlockGenerator.generateBlock(clientSideBlockType, clientSideBlock, blockProperties);
} catch (Throwable throwable) {
this.plugin.logger().warn("Failed to generate dynamic block class", throwable);
continue;
}
blockHolder = CoreReflections.method$Registry$registerForHolder.invoke(null, MBuiltInRegistries.BLOCK, resourceLocation, newRealBlock);
CoreReflections.method$Holder$Reference$bindValue.invoke(blockHolder, newRealBlock);
CoreReflections.field$Holder$Reference$tags.set(blockHolder, Set.of());
newBlockState = FastNMS.INSTANCE.method$Block$defaultState(newRealBlock);
CoreReflections.method$IdMapper$add.invoke(CoreReflections.instance$Block$BLOCK_STATE_REGISTRY, newBlockState);
if (isNoteBlock) {
BlockStateUtils.CLIENT_SIDE_NOTE_BLOCKS.put(newBlockState, new Object());
}
int stateId = BlockStateUtils.vanillaStateSize() + counter;
builder1.put(realBlockKey, stateId);
builder2.put(stateId, blockHolder);
builder4.put(realBlockKey, (DelegatingBlock) newRealBlock);
stateIds.add(stateId);
this.blocksToDeceive.add(Tuple.of(newRealBlock, clientSideBlockType, isNoteBlock));
order.add(realBlockKey);
counter++;
}
builder3.put(clientSideBlockType, stateIds);
Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(clientSideBlock);
affectSoundTypes.add(soundType);
return counter;
}
private Object createResourceLocation(Key key) {
return FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath(key.namespace(), key.value());
}
private Object getBlockFromRegistry(Object resourceLocation) {
return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, resourceLocation);
}
private Key createRealBlockKey(Key replacedBlock, int index) {
return Key.of(Key.DEFAULT_NAMESPACE, replacedBlock.value() + "_" + index);
}
private Object createBlockProperties(Key realBlockKey) throws Exception {
Object blockProperties = CoreReflections.method$BlockBehaviour$Properties$of.invoke(null);
Object realBlockResourceLocation = createResourceLocation(realBlockKey);
Object realBlockResourceKey = CoreReflections.method$ResourceKey$create.invoke(null, MRegistries.BLOCK, realBlockResourceLocation);
if (CoreReflections.field$BlockBehaviour$Properties$id != null) {
CoreReflections.field$BlockBehaviour$Properties$id.set(blockProperties, realBlockResourceKey);
}
return blockProperties;
}
@SuppressWarnings("unchecked")
private void deceiveBukkit() {
try {
Map<Object, Material> magicMap = (Map<Object, Material>) CraftBukkitReflections.field$CraftMagicNumbers$BLOCK_MATERIAL.get(null);
Map<Material, Object> factories = (Map<Material, Object>) CraftBukkitReflections.field$CraftBlockStates$FACTORIES.get(null);
for (Tuple<Object, Key, Boolean> tuple : this.blocksToDeceive) {
deceiveBukkit(tuple.left(), tuple.mid(), tuple.right(), magicMap, factories);
}
this.blocksToDeceive.clear();
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to deceive bukkit", e);
}
}
private void deceiveBukkit(Object newBlock, Key replacedBlock, boolean isNoteBlock, Map<Object, Material> magicMap, Map<Material, Object> factories) {
if (isNoteBlock) {
magicMap.put(newBlock, Material.STONE);
} else {
Material material = org.bukkit.Registry.MATERIAL.get(new NamespacedKey(replacedBlock.namespace(), replacedBlock.value()));
if (CraftBukkitReflections.clazz$CraftBlockStates$BlockEntityStateFactory.isInstance(factories.get(material))) {
magicMap.put(newBlock, Material.STONE);
} else {
magicMap.put(newBlock, material);
}
this.plugin.logger().warn("Failed to init vanilla note block", e);
}
}
@Override
protected int getBlockRegistryId(Key id) {
protected void setVanillaBlockTags(Key id, List<String> tags) {
Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id));
return FastNMS.INSTANCE.method$IdMap$getId(MBuiltInRegistries.BLOCK, block).orElseThrow(() -> new IllegalStateException("Block " + id + " not found"));
this.clientBoundTags.put(FastNMS.INSTANCE.method$IdMap$getId(MBuiltInRegistries.BLOCK, block).orElseThrow(() -> new IllegalStateException("Block " + id + " not found")), tags);
}
public boolean isPlaceSoundMissing(Object sound) {
return this.missingPlaceSounds.contains(sound);
}
public boolean isBreakSoundMissing(Object sound) {
return this.missingBreakSounds.contains(sound);
}
public boolean isHitSoundMissing(Object sound) {
return this.missingHitSounds.contains(sound);
}
public boolean isStepSoundMissing(Object sound) {
return this.missingStepSounds.contains(sound);
}
public boolean isInteractSoundMissing(Key blockType) {
return this.missingInteractSoundBlocks.contains(blockType);
}
private void unfreezeRegistry() {
try {
CoreReflections.field$MappedRegistry$frozen.set(MBuiltInRegistries.BLOCK, false);
CoreReflections.field$MappedRegistry$unregisteredIntrusiveHolders.set(MBuiltInRegistries.BLOCK, new IdentityHashMap<>());
} catch (IllegalAccessException e) {
this.plugin.logger().warn("Failed to unfreeze block registry", e);
}
}
private void freezeRegistry() {
try {
CoreReflections.field$MappedRegistry$frozen.set(MBuiltInRegistries.BLOCK, true);
} catch (IllegalAccessException e) {
this.plugin.logger().warn("Failed to freeze block registry", e);
}
}
@SuppressWarnings("unchecked")
private void deceiveBukkitRegistry() {
try {
Map<Object, Material> magicMap = (Map<Object, Material>) CraftBukkitReflections.field$CraftMagicNumbers$BLOCK_MATERIAL.get(null);
for (DelegatingBlock customBlock : this.customBlocks) {
magicMap.put(customBlock, Material.STONE);
}
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to deceive bukkit magic blocks", e);
}
}
@Override
protected boolean isVanillaBlock(Key id) {
if (!id.namespace().equals("minecraft")) {
if (!id.namespace().equals("minecraft"))
return false;
}
if (id.value().equals("air")) {
if (id.value().equals("air"))
return true;
}
return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id)) != MBlocks.AIR;
}
public boolean isBurnable(Object blockState) {
Object blockOwner = BlockStateUtils.getBlockOwner(blockState);
return this.igniteOdds.getOrDefault(blockOwner, 0) > 0;
}
@Override
public int vanillaBlockStateCount() {
return this.vanillaBlockStateCount;
}
@SuppressWarnings("DuplicatedCode")
@Override
protected void processSounds() {
Set<Object> affectedBlockSoundTypes = new HashSet<>();
for (BlockStateWrapper vanillaBlockState : super.tempVisualBlockStatesInUse) {
affectedBlockSoundTypes.add(FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(vanillaBlockState.literalObject()));
}
Set<Object> placeSounds = new HashSet<>();
Set<Object> breakSounds = new HashSet<>();
Set<Object> stepSounds = new HashSet<>();
Set<Object> hitSounds = new HashSet<>();
for (Object soundType : affectedBlockSoundTypes) {
placeSounds.add(FastNMS.INSTANCE.field$SoundEvent$location(FastNMS.INSTANCE.field$SoundType$placeSound(soundType)));
breakSounds.add(FastNMS.INSTANCE.field$SoundEvent$location(FastNMS.INSTANCE.field$SoundType$breakSound(soundType)));
stepSounds.add(FastNMS.INSTANCE.field$SoundEvent$location(FastNMS.INSTANCE.field$SoundType$stepSound(soundType)));
hitSounds.add(FastNMS.INSTANCE.field$SoundEvent$location(FastNMS.INSTANCE.field$SoundType$hitSound(soundType)));
}
ImmutableMap.Builder<Key, Key> soundReplacementBuilder = ImmutableMap.builder();
for (Object soundId : placeSounds) {
Key previousId = KeyUtils.resourceLocationToKey(soundId);
soundReplacementBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value()));
}
for (Object soundId : breakSounds) {
Key previousId = KeyUtils.resourceLocationToKey(soundId);
soundReplacementBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value()));
}
for (Object soundId : stepSounds) {
Key previousId = KeyUtils.resourceLocationToKey(soundId);
soundReplacementBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value()));
}
for (Object soundId : hitSounds) {
Key previousId = KeyUtils.resourceLocationToKey(soundId);
soundReplacementBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value()));
}
this.missingPlaceSounds = placeSounds;
this.missingBreakSounds = breakSounds;
this.missingHitSounds = hitSounds;
this.missingStepSounds = stepSounds;
Set<Key> missingInteractSoundBlocks = new HashSet<>();
for (SoundSet soundSet : SoundSet.getAllSoundSets()) {
for (Key block : soundSet.blocks()) {
if (super.tempVisualBlocksInUse.contains(block)) {
Key openSound = soundSet.openSound();
soundReplacementBuilder.put(openSound, Key.of(openSound.namespace(), "replaced." + openSound.value()));
Key closeSound = soundSet.closeSound();
soundReplacementBuilder.put(closeSound, Key.of(closeSound.namespace(), "replaced." + closeSound.value()));
missingInteractSoundBlocks.addAll(soundSet.blocks());
break;
}
}
}
this.missingInteractSoundBlocks = missingInteractSoundBlocks;
this.soundReplacements = soundReplacementBuilder.buildKeepingLast();
}
@Override
protected CustomBlock createCustomBlock(@NotNull Holder.Reference<CustomBlock> holder,
@NotNull BlockStateVariantProvider variantProvider,
@NotNull Map<EventTrigger, List<Function<PlayerOptionalContext>>> events,
@Nullable LootTable<?> lootTable) {
return new BukkitCustomBlock(holder, variantProvider, events, lootTable);
}
}

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, BlockStateAppearance> 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 visual block state for " + immutableBlockState + ". This might cause errors!");
continue;
} else if (immutableBlockState.customBlockState() == null) {
CraftEngine.instance().logger().warn("Could not find real block state 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, BlockStateAppearance> 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, BlockStateAppearance> 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,62 @@
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.BlockStateWrapper;
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 BlockStateWrapper withProperty(String propertyName, String propertyValue) {
Optional<ImmutableBlockState> immutableBlockState = getImmutableBlockState();
if (immutableBlockState.isPresent()) {
Property<?> property = immutableBlockState.get().owner().value().getProperty(propertyName);
if (property != null) {
Comparable<?> value = property.valueByName(propertyValue);
if (value != null) {
return ImmutableBlockState.with(immutableBlockState.get(), property, value).customBlockState();
}
}
}
return this;
}
@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,45 @@
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.BlockRegistryMirror;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
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();
}
@Override
public BlockStateWrapper withProperty(String propertyName, String propertyValue) {
Object newState = this.accessor.withProperty(propertyName, propertyValue);
if (newState == super.blockState) return this;
return BlockRegistryMirror.byId(BlockStateUtils.blockStateToId(newState));
}
}

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,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

@@ -36,6 +36,13 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
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);
@@ -70,5 +77,12 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
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

@@ -1,37 +1,67 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections;
import net.momirealms.craftengine.core.block.*;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
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.util.Key;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.LazyReference;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.sparrow.nbt.CompoundTag;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class ChangeOverTimeBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final float changeSpeed;
private final Key nextBlock;
private final String nextBlock;
private final LazyReference<BlockStateWrapper> lazyState;
private final List<String> excludedProperties;
public ChangeOverTimeBlockBehavior(CustomBlock customBlock, float changeSpeed, Key nextBlock) {
public ChangeOverTimeBlockBehavior(CustomBlock customBlock, float changeSpeed, String nextBlock, List<String> excludedProperties) {
super(customBlock);
this.changeSpeed = changeSpeed;
this.nextBlock = nextBlock;
this.excludedProperties = excludedProperties;
this.lazyState = LazyReference.lazyReference(() -> CraftEngine.instance().blockManager().createBlockState(this.nextBlock));
}
public String nextBlock() {
return nextBlock;
}
public BlockStateWrapper nextState() {
return this.lazyState.get();
}
public CompoundTag filter(CompoundTag properties) {
for (String property : this.excludedProperties) {
properties.remove(property);
}
return properties;
}
@Override
public void randomTick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws ReflectiveOperationException {
public void randomTick(Object thisBlock, Object[] args, Callable<Object> superMethod) {
if (RandomUtils.generateRandomFloat(0F, 1F) >= this.changeSpeed) return;
Optional<Object> nextState = BukkitBlockManager.instance().blockById(this.nextBlock)
.map(CustomBlock::defaultState)
.map(ImmutableBlockState::customBlockState)
.map(BlockStateWrapper::literalObject);
if (nextState.isEmpty()) return;
CraftBukkitReflections.method$CraftEventFactory$handleBlockFormEvent.invoke(null, args[1], args[2], nextState.get(), UpdateOption.UPDATE_ALL.flags());
Object blockState = args[0];
BlockStateUtils.getOptionalCustomBlockState(blockState).ifPresent(state -> {
BlockStateWrapper nextState = this.nextState();
if (nextState == null) return;
nextState = nextState.withProperties(filter(state.propertiesNbt()));
try {
CraftBukkitReflections.method$CraftEventFactory$handleBlockFormEvent.invoke(null, args[1], args[2], nextState.literalObject(), UpdateOption.UPDATE_ALL.flags());
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to call block form event", e);
}
});
}
public static class Factory implements BlockBehaviorFactory {
@@ -39,8 +69,9 @@ public class ChangeOverTimeBlockBehavior extends BukkitBlockBehavior {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
float changeSpeed = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("change-speed", 0.05688889F), "change-speed");
Key nextBlock = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.getOrDefault("next-block", "minecraft:air"), "warning.config.block.behavior.change_over_time.missing_next_block"));
return new ChangeOverTimeBlockBehavior(block, changeSpeed, nextBlock);
String nextBlock = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.getOrDefault("next-block", "minecraft:air"), "warning.config.block.behavior.change_over_time.missing_next_block");
List<String> excludedProperties = MiscUtils.getAsStringList(arguments.get("excluded-properties"));
return new ChangeOverTimeBlockBehavior(block, changeSpeed, nextBlock, excludedProperties);
}
}
}

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

@@ -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

@@ -4,6 +4,7 @@ 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;
@@ -13,24 +14,40 @@ 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.Direction;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
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.Map;
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) {
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
@@ -65,7 +82,31 @@ public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior {
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);
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")
@@ -95,13 +136,42 @@ public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Property<?> facing = ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.surface_attached.missing_facing");
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.surface_attached.missing_facing");
throw new LocalizedResourceConfigException("warning.config.block.behavior.directional_attached.missing_facing");
}
return new DirectionalAttachedBlockBehavior(block, facing, isDirection);
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,9 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.entity.BukkitEntity;
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,6 +13,7 @@ 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.sound.SoundData;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
@@ -22,11 +26,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
@@ -74,42 +82,52 @@ public class FallingBlockBehavior extends BukkitBlockBehavior {
public void onBrokenAfterFall(Object thisBlock, Object[] args) throws Exception {
Object level = args[0];
Object fallingBlockEntity = args[2];
Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity);
boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT);
if (!isSilent) {
BukkitEntity entity = BukkitAdaptors.adapt(FastNMS.INSTANCE.method$Entity$getBukkitEntity(fallingBlockEntity));
if (!entity.getEntityData(BaseEntityData.Silent)) {
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));
world.playBlockSound(position, customState.settings().sounds().destroySound());
if (this.destroySound != null) {
world.playBlockSound(position, this.destroySound);
}
}
}
@Override
public void onLand(Object thisBlock, Object[] args) throws Exception {
public void onLand(Object thisBlock, Object[] args) {
Object fallingBlock = args[4];
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);
BukkitEntity entity = BukkitAdaptors.adapt(FastNMS.INSTANCE.method$Entity$getBukkitEntity(fallingBlock));
Object blockState = args[2];
int stateId = BlockStateUtils.blockStateToId(blockState);
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(stateId);
if (immutableBlockState == null || immutableBlockState.isEmpty()) return;
if (!isSilent) {
if (!entity.getEntityData(BaseEntityData.Silent)) {
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,153 @@
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.BlockBehavior;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
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.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(ImmutableBlockState::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

@@ -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

@@ -48,7 +48,7 @@ public class SimpleParticleBlockBehavior extends BukkitBlockBehavior implements
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
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);
}

View File

@@ -20,7 +20,7 @@ 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.MCUtils;
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;
@@ -166,7 +166,7 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E
}
}
signal /= (float) inventory.getSize();
return MCUtils.lerpDiscrete(signal, 0, 15);
return MiscUtils.lerpDiscrete(signal, 0, 15);
}
}
return 0;
@@ -194,7 +194,7 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
String title = arguments.getOrDefault("title", "").toString();
int rows = MCUtils.clamp(ResourceConfigUtils.getAsInt(arguments.getOrDefault("rows", 1), "rows"), 1, 6);
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;

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

@@ -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.SofaShape;
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;

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

@@ -1,32 +1,53 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.LazyReference;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.sparrow.nbt.CompoundTag;
import java.util.List;
import java.util.Map;
public class StrippableBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Key stripped;
private final String stripped;
private final LazyReference<BlockStateWrapper> lazyState;
private final List<String> excludedProperties;
public StrippableBlockBehavior(CustomBlock block, Key stripped) {
public StrippableBlockBehavior(CustomBlock block, String stripped, List<String> excludedProperties) {
super(block);
this.stripped = stripped;
this.lazyState = LazyReference.lazyReference(() -> CraftEngine.instance().blockManager().createBlockState(this.stripped));
this.excludedProperties = excludedProperties;
}
public Key stripped() {
public String stripped() {
return this.stripped;
}
public BlockStateWrapper strippedState() {
return this.lazyState.get();
}
public CompoundTag filter(CompoundTag properties) {
for (String property : this.excludedProperties) {
properties.remove(property);
}
return properties;
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
String stripped = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("stripped"), "warning.config.block.behavior.strippable.missing_stripped");
return new StrippableBlockBehavior(block, Key.of(stripped));
List<String> excludedProperties = MiscUtils.getAsStringList(arguments.get("excluded-properties"));
return new StrippableBlockBehavior(block, stripped, excludedProperties);
}
}
}

View File

@@ -91,7 +91,7 @@ public class ToggleableLampBlockBehavior extends BukkitBlockBehavior {
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
boolean canOpenWithHand = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-open-with-hand", false), "can-open-with-hand");
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

@@ -381,4 +381,18 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior
}
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

@@ -57,7 +57,7 @@ public class WallTorchParticleBlockBehavior extends BukkitBlockBehavior implemen
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
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);
}

View File

@@ -5,7 +5,6 @@ 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.Direction;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;

View File

@@ -1,8 +1,10 @@
package net.momirealms.craftengine.bukkit.entity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.EntityUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.entity.AbstractEntity;
import net.momirealms.craftengine.core.entity.data.EntityData;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.World;
@@ -19,17 +21,17 @@ public class BukkitEntity extends AbstractEntity {
@Override
public double x() {
return literalObject().getX();
return platformEntity().getX();
}
@Override
public double y() {
return literalObject().getY();
return platformEntity().getY();
}
@Override
public double z() {
return literalObject().getZ();
return platformEntity().getZ();
}
@Override
@@ -38,22 +40,22 @@ public class BukkitEntity extends AbstractEntity {
@Override
public int entityID() {
return literalObject().getEntityId();
return platformEntity().getEntityId();
}
@Override
public float xRot() {
return literalObject().getYaw();
return platformEntity().getYaw();
}
@Override
public float yRot() {
return literalObject().getPitch();
return platformEntity().getPitch();
}
@Override
public World world() {
return new BukkitWorld(literalObject().getWorld());
return new BukkitWorld(platformEntity().getWorld());
}
@Override
@@ -62,22 +64,43 @@ public class BukkitEntity extends AbstractEntity {
}
@Override
public org.bukkit.entity.Entity literalObject() {
public org.bukkit.entity.Entity platformEntity() {
return this.entity.get();
}
@Override
public Object serverEntity() {
return FastNMS.INSTANCE.method$CraftEntity$getHandle(platformEntity());
}
@Override
public Key type() {
return EntityUtils.getEntityType(literalObject());
return EntityUtils.getEntityType(platformEntity());
}
@Override
public String name() {
return literalObject().getName();
return platformEntity().getName();
}
@Override
public UUID uuid() {
return literalObject().getUniqueId();
return platformEntity().getUniqueId();
}
@Override
public Object entityData() {
return FastNMS.INSTANCE.field$Entity$entityData(serverEntity());
}
@SuppressWarnings("unchecked")
@Override
public <T> T getEntityData(EntityData<T> data) {
return (T) FastNMS.INSTANCE.method$SynchedEntityData$get(entityData(), data.entityDataAccessor());
}
@Override
public <T> void setEntityData(EntityData<T> data, T value, boolean force) {
FastNMS.INSTANCE.method$SynchedEntityData$set(entityData(), data.entityDataAccessor(), value, force);
}
}

View File

@@ -4,7 +4,7 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflect
import java.util.Optional;
public class BaseEntityData<T> extends SimpleEntityData<T> {
public class BaseEntityData<T> extends BukkitEntityData<T> {
public static final BaseEntityData<Byte> SharedFlags = new BaseEntityData<>(BaseEntityData.class, EntityDataValue.Serializers$BYTE, (byte) 0);
public static final BaseEntityData<Integer> AirSupply = new BaseEntityData<>(BaseEntityData.class, EntityDataValue.Serializers$INT, 300);
public static final BaseEntityData<Optional<Object>> CustomName = new BaseEntityData<>(BaseEntityData.class, EntityDataValue.Serializers$OPTIONAL_COMPONENT, Optional.empty());

View File

@@ -2,15 +2,16 @@ package net.momirealms.craftengine.bukkit.entity.data;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.entity.data.ClassTreeIdRegistry;
import net.momirealms.craftengine.core.entity.data.EntityData;
public class SimpleEntityData<T> implements EntityData<T> {
public class BukkitEntityData<T> implements EntityData<T> {
public static final ClassTreeIdRegistry ID_REGISTRY = new ClassTreeIdRegistry();
private final int id;
private final Object serializer;
private final T defaultValue;
private final Object entityDataAccessor;
public SimpleEntityData(Class<?> clazz, Object serializer, T defaultValue) {
public BukkitEntityData(Class<?> clazz, Object serializer, T defaultValue) {
this.id = ID_REGISTRY.define(clazz);
this.serializer = serializer;
this.defaultValue = defaultValue;
@@ -36,4 +37,9 @@ public class SimpleEntityData<T> implements EntityData<T> {
public Object entityDataAccessor() {
return entityDataAccessor;
}
@Override
public Object create(Object entityDataAccessor, Object value) {
return EntityDataValue.create(entityDataAccessor, value);
}
}

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");
@@ -98,7 +99,7 @@ public class EntityDataValue {
throw new IllegalAccessError("Utility class");
}
public static Object create(int id, Object serializer, Object entityDataAccessor, Object value) {
public static Object create(Object entityDataAccessor, Object value) {
return FastNMS.INSTANCE.method$SynchedEntityData$DataValue$create(entityDataAccessor, value);
}
}

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() {

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

@@ -27,6 +27,7 @@ import net.momirealms.craftengine.core.util.*;
import org.bukkit.Bukkit;
import org.bukkit.event.HandlerList;
import org.bukkit.inventory.ItemStack;
import org.incendo.cloud.suggestion.Suggestion;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -402,6 +403,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
Object resourceLocation = FastNMS.INSTANCE.method$Registry$getKey(MBuiltInRegistries.ITEM, item);
Key itemKey = KeyUtils.resourceLocationToKey(resourceLocation);
VANILLA_ITEMS.add(itemKey);
super.cachedVanillaItemSuggestions.add(Suggestion.suggestion(itemKey.asString()));
UniqueKey uniqueKey = UniqueKey.create(itemKey);
Object mcHolder = FastNMS.INSTANCE.method$Registry$getHolderByResourceKey(MBuiltInRegistries.ITEM, FastNMS.INSTANCE.method$ResourceKey$create(MRegistries.ITEM, resourceLocation)).get();
Set<Object> tags = (Set<Object>) CoreReflections.field$Holder$Reference$tags.get(mcHolder);

View File

@@ -1,13 +1,12 @@
package net.momirealms.craftengine.bukkit.item.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.behavior.StrippableBlockBehavior;
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.util.*;
import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
@@ -25,7 +24,6 @@ import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.GameEvent;
import org.bukkit.Material;
import org.bukkit.Statistic;
@@ -66,28 +64,25 @@ public class AxeItemBehavior extends ItemBehavior {
ImmutableBlockState customState = optionalCustomState.get();
Optional<StrippableBlockBehavior> behaviorOptional = customState.behavior().getAs(StrippableBlockBehavior.class);
if (behaviorOptional.isEmpty()) return InteractionResult.PASS;
Key stripped = behaviorOptional.get().stripped();
Item<ItemStack> offHandItem = player != null ? (Item<ItemStack>) player.getItemInHand(InteractionHand.OFF_HAND) : BukkitItemManager.instance().uniqueEmptyItem().item();
// is using a shield
if (context.getHand() == InteractionHand.MAIN_HAND && !ItemUtils.isEmpty(offHandItem) && canBlockAttack(offHandItem) && player != null && !player.isSecondaryUseActive()) {
return InteractionResult.PASS;
}
Optional<CustomBlock> optionalNewCustomBlock = BukkitBlockManager.instance().blockById(stripped);
if (optionalNewCustomBlock.isEmpty()) {
CraftEngine.instance().logger().warn("stripped block " + stripped + " does not exist");
BlockStateWrapper newState = behaviorOptional.get().strippedState();
if (newState == null) {
CraftEngine.instance().logger().warn("stripped block " + behaviorOptional.get().stripped() + " does not exist");
return InteractionResult.FAIL;
}
CustomBlock newCustomBlock = optionalNewCustomBlock.get();
CompoundTag compoundTag = customState.propertiesNbt();
ImmutableBlockState newState = newCustomBlock.getBlockState(compoundTag);
newState = newState.withProperties(behaviorOptional.get().filter(customState.propertiesNbt()));
BukkitExistingBlock clicked = (BukkitExistingBlock) context.getLevel().getBlockAt(context.getClickedPos());
org.bukkit.entity.Player bukkitPlayer = null;
if (player != null) {
bukkitPlayer = ((org.bukkit.entity.Player) player.platformPlayer());
// Call bukkit event
EntityChangeBlockEvent event = new EntityChangeBlockEvent(bukkitPlayer, clicked.block(), BlockStateUtils.fromBlockData(newState.customBlockState().literalObject()));
EntityChangeBlockEvent event = new EntityChangeBlockEvent(bukkitPlayer, clicked.block(), BlockStateUtils.fromBlockData(newState.literalObject()));
if (EventUtils.fireAndCheckCancel(event)) {
return InteractionResult.FAIL;
}
@@ -98,7 +93,7 @@ public class AxeItemBehavior extends ItemBehavior {
if (ItemUtils.isEmpty(item)) return InteractionResult.FAIL;
BlockPos pos = context.getClickedPos();
context.getLevel().playBlockSound(Vec3d.atCenterOf(pos), AXE_STRIP_SOUND, 1, 1);
FastNMS.INSTANCE.method$LevelWriter$setBlock(context.getLevel().serverWorld(), LocationUtils.toBlockPos(pos), newState.customBlockState().literalObject(), UpdateOption.UPDATE_ALL_IMMEDIATE.flags());
FastNMS.INSTANCE.method$LevelWriter$setBlock(context.getLevel().serverWorld(), LocationUtils.toBlockPos(pos), newState.literalObject(), UpdateOption.UPDATE_ALL_IMMEDIATE.flags());
clicked.block().getWorld().sendGameEvent(bukkitPlayer, GameEvent.BLOCK_CHANGE, new Vector(pos.x(), pos.y(), pos.z()));
Material material = MaterialUtils.getMaterial(item.vanillaId());
if (bukkitPlayer != null) {
@@ -126,12 +121,12 @@ public class AxeItemBehavior extends ItemBehavior {
itemStack.damage(1, bukkitPlayer);
}
}
return InteractionResult.SUCCESS;
return InteractionResult.SUCCESS_AND_CANCEL;
}
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;
@@ -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

@@ -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

@@ -47,7 +47,7 @@ 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 = FastNMS.INSTANCE.field$BlockHitResul$blockPos(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 = DirectionUtils.fromNMSDirection(FastNMS.INSTANCE.field$BlockHitResul$direction(blockHitResult));
boolean miss = FastNMS.INSTANCE.field$BlockHitResul$miss(blockHitResult);
@@ -59,7 +59,7 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior {
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) {
@@ -70,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"));
@@ -79,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 {

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.item.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
@@ -9,7 +8,6 @@ import net.momirealms.craftengine.core.item.context.UseOnContext;
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 java.nio.file.Path;
import java.util.Map;
@@ -35,18 +33,13 @@ public class WallBlockItemBehavior 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.wall_block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for wall_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 WallBlockItemBehavior(key);
} else {
return new WallBlockItemBehavior(Key.of(id.toString()));

View File

@@ -49,7 +49,7 @@ public abstract class BukkitItemFactory<W extends ItemWrapper<ItemStack>> extend
case "1.21.4" -> {
return new ComponentItemFactory1_21_4(plugin);
}
case "1.21.5", "1.21.6", "1.21.7", "1.21.8" -> {
case "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10" -> {
return new ComponentItemFactory1_21_5(plugin);
}
default -> throw new IllegalStateException("Unsupported server version: " + plugin.serverVersion());

View File

@@ -17,7 +17,6 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.util.MCUtils;
import net.momirealms.craftengine.core.util.MiscUtils;
import org.bukkit.Material;
import org.bukkit.block.Block;
@@ -119,7 +118,7 @@ public class DebugStickListener implements Listener {
}
private static <T> T getRelative(Iterable<T> elements, @Nullable T current, boolean inverse) {
return inverse ? MCUtils.findPreviousInIterable(elements, current) : MCUtils.findNextInIterable(elements, current);
return inverse ? MiscUtils.findPreviousInIterable(elements, current) : MiscUtils.findNextInIterable(elements, current);
}
private static <T extends Comparable<T>> String getNameHelper(ImmutableBlockState state, Property<T> property) {

View File

@@ -29,7 +29,7 @@ import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.sound.SoundSet;
import net.momirealms.craftengine.core.sound.SoundSource;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.BlockHitResult;
@@ -41,6 +41,7 @@ import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Openable;
import org.bukkit.block.data.Powerable;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -219,14 +220,29 @@ public class ItemEventListener implements Listener {
}
} else {
if (Config.enableSoundSystem() && hitResult != null) {
Object blockOwner = FastNMS.INSTANCE.method$BlockState$getBlock(blockState);
if (this.plugin.blockManager().isOpenableBlockSoundRemoved(blockOwner)) {
Key blockOwner = BlockStateUtils.getBlockOwnerIdFromState(blockState);
if (this.plugin.blockManager().isInteractSoundMissing(blockOwner)) {
boolean hasItem = player.getInventory().getItemInMainHand().getType() != Material.AIR || player.getInventory().getItemInOffHand().getType() != Material.AIR;
boolean flag = player.isSneaking() && hasItem;
if (!flag) {
if (blockData instanceof Openable openable) {
SoundData soundData = this.plugin.blockManager().getRemovedOpenableBlockSound(blockOwner, !openable.isOpen());
serverPlayer.playSound(soundData.id(), SoundSource.BLOCK, soundData.volume().get(), soundData.pitch().get());
SoundSet soundSet = SoundSet.getByBlock(blockOwner);
if (soundSet != null) {
serverPlayer.playSound(
Vec3d.atCenterOf(hitResult.getBlockPos()),
openable.isOpen() ? soundSet.closeSound() : soundSet.openSound(),
SoundSource.BLOCK,
1, RandomUtils.generateRandomFloat(0.9f, 1));
}
} else if (blockData instanceof Powerable powerable && !powerable.isPowered()) {
SoundSet soundSet = SoundSet.getByBlock(blockOwner);
if (soundSet != null) {
serverPlayer.playSound(
Vec3d.atCenterOf(hitResult.getBlockPos()),
soundSet.openSound(),
SoundSource.BLOCK,
1, RandomUtils.generateRandomFloat(0.9f, 1));
}
}
}
}
@@ -458,9 +474,9 @@ public class ItemEventListener implements Listener {
if (foodData == null) return;
event.setCancelled(true);
int oldFoodLevel = player.getFoodLevel();
if (foodData.nutrition() != 0) player.setFoodLevel(MCUtils.clamp(oldFoodLevel + foodData.nutrition(), 0, 20));
if (foodData.nutrition() != 0) player.setFoodLevel(MiscUtils.clamp(oldFoodLevel + foodData.nutrition(), 0, 20));
float oldSaturation = player.getSaturation();
if (foodData.saturation() != 0) player.setSaturation(MCUtils.clamp(oldSaturation, 0, 10));
if (foodData.saturation() != 0) player.setSaturation(MiscUtils.clamp(oldSaturation, 0, 10));
}
private boolean cancelEventIfHasInteraction(PlayerInteractEvent event, BukkitServerPlayer player, InteractionHand hand) {

View File

@@ -258,7 +258,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager<ItemStack> {
// 已经被替换过的数据包配方
private final Set<Key> replacedDatapackRecipes = new HashSet<>();
// 换成的数据包配方
private Map<Key, Recipe<ItemStack>> lastDatapackRecipes = Map.of();
private Map<Key, JsonObject> lastDatapackRecipes = Map.of();
private Object lastRecipeManager = null;
public BukkitRecipeManager(BukkitCraftEngine plugin) {
@@ -386,18 +386,33 @@ public class BukkitRecipeManager extends AbstractRecipeManager<ItemStack> {
}
boolean hasDisabledAny = !Config.disabledVanillaRecipes().isEmpty();
for (Map.Entry<Key, Recipe<ItemStack>> entry : this.lastDatapackRecipes.entrySet()) {
for (Map.Entry<Key, JsonObject> entry : this.lastDatapackRecipes.entrySet()) {
Key id = entry.getKey();
if (hasDisabledAny && Config.disabledVanillaRecipes().contains(entry.getKey())) {
this.recipesToUnregister.add(Pair.of(entry.getKey(), false));
this.recipesToUnregister.add(Pair.of(id, false));
continue;
}
markAsDataPackRecipe(entry.getKey());
registerInternalRecipe(entry.getKey(), entry.getValue());
JsonObject jsonObject = entry.getValue();
Key serializerType = Key.of(jsonObject.get("type").getAsString());
@SuppressWarnings("unchecked")
RecipeSerializer<ItemStack, ? extends Recipe<ItemStack>> serializer = (RecipeSerializer<ItemStack, ? extends Recipe<ItemStack>>) BuiltInRegistries.RECIPE_SERIALIZER.getValue(serializerType);
if (serializer == null) {
continue;
}
try {
Recipe<ItemStack> recipe = serializer.readJson(id, jsonObject);
markAsDataPackRecipe(id);
registerInternalRecipe(id, recipe);
} catch (Exception e) {
this.plugin.logger().warn("Failed to load data pack recipe " + id + ". Json: " + jsonObject, e);
}
}
}
@SuppressWarnings("unchecked")
private Map<Key, Recipe<ItemStack>> scanResources() throws Throwable {
private Map<Key, JsonObject> scanResources() throws Throwable {
Object fileToIdConverter = CoreReflections.methodHandle$FileToIdConverter$json.invokeExact((String) (VersionHelper.isOrAbove1_21() ? "recipe" : "recipes"));
Object minecraftServer = FastNMS.INSTANCE.method$MinecraftServer$getServer();
Object packRepository = CoreReflections.methodHandle$MinecraftServer$getPackRepository.invokeExact(minecraftServer);
@@ -406,7 +421,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager<ItemStack> {
for (Object pack : selected) {
packResources.add(CoreReflections.methodHandle$Pack$open.invokeExact(pack));
}
Map<Key, Recipe<ItemStack>> recipes = new HashMap<>();
Map<Key, JsonObject> recipes = new HashMap<>();
try (AutoCloseable resourceManager = (AutoCloseable) CoreReflections.methodHandle$MultiPackResourceManagerConstructor.invokeExact(CoreReflections.instance$PackType$SERVER_DATA, packResources)) {
Map<Object, Object> scannedResources = (Map<Object, Object>) CoreReflections.methodHandle$FileToIdConverter$listMatchingResources.invokeExact(fileToIdConverter, resourceManager);
@@ -414,17 +429,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager<ItemStack> {
Key id = extractKeyFromResourceLocation(entry.getKey().toString());
Reader reader = (Reader) CoreReflections.methodHandle$Resource$openAsReader.invokeExact(entry.getValue());
JsonObject jsonObject = JsonParser.parseReader(reader).getAsJsonObject();
Key serializerType = Key.of(jsonObject.get("type").getAsString());
RecipeSerializer<ItemStack, ? extends Recipe<ItemStack>> serializer = (RecipeSerializer<ItemStack, ? extends Recipe<ItemStack>>) BuiltInRegistries.RECIPE_SERIALIZER.getValue(serializerType);
if (serializer == null) {
continue;
}
try {
Recipe<ItemStack> recipe = serializer.readJson(id, jsonObject);
recipes.put(id, recipe);
} catch (Exception e) {
this.plugin.logger().warn("Failed to load data pack recipe " + id + ". Json: " + jsonObject, e);
}
recipes.put(id, jsonObject);
}
} catch (Throwable e) {
this.plugin.logger().warn("Unknown error occurred when loading data pack recipes", e);

View File

@@ -23,6 +23,8 @@ import net.momirealms.craftengine.core.item.recipe.input.SmithingInput;
import net.momirealms.craftengine.core.item.setting.AnvilRepairItem;
import net.momirealms.craftengine.core.item.setting.ItemEquipment;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.*;
import org.bukkit.Material;
import org.bukkit.entity.Player;
@@ -608,20 +610,25 @@ public class RecipeEventListener implements Listener {
inventory.setResult(null);
return;
}
CraftingInput<ItemStack> input = getCraftingInput(inventory);
if (input == null) return;
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
if (craftingTableRecipe.hasVisualResult()) {
inventory.setResult(craftingTableRecipe.assembleVisual(input, ItemBuildContext.of(serverPlayer)));
ItemBuildContext itemBuildContext = ItemBuildContext.of(serverPlayer);
if (!craftingTableRecipe.canUse(itemBuildContext)) {
inventory.setResult(null);
return;
}
CraftingInput<ItemStack> input = getCraftingInput(inventory);
if (input == null) return;
if (craftingTableRecipe.hasVisualResult() && VersionHelper.PREMIUM) {
inventory.setResult(craftingTableRecipe.assembleVisual(input, itemBuildContext));
} else {
inventory.setResult(craftingTableRecipe.assemble(input, ItemBuildContext.of(serverPlayer)));
inventory.setResult(craftingTableRecipe.assemble(input, itemBuildContext));
}
}
@EventHandler(ignoreCancelled = true)
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onCraftingFinish(CraftItemEvent event) {
if (!Config.enableRecipeSystem()) return;
if (!Config.enableRecipeSystem() || !VersionHelper.PREMIUM) return;
org.bukkit.inventory.Recipe recipe = event.getRecipe();
if (!(recipe instanceof CraftingRecipe craftingRecipe)) return;
Key recipeId = Key.of(craftingRecipe.getKey().namespace(), craftingRecipe.getKey().value());
@@ -634,14 +641,19 @@ public class RecipeEventListener implements Listener {
if (!(optionalRecipe.get() instanceof CustomCraftingTableRecipe<ItemStack> craftingTableRecipe)) {
return;
}
if (!craftingTableRecipe.hasVisualResult()) {
return;
}
CraftingInput<ItemStack> input = getCraftingInput(inventory);
if (input == null) return;
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
inventory.setResult(craftingTableRecipe.assemble(input, ItemBuildContext.of(serverPlayer)));
if (craftingTableRecipe.hasVisualResult()) {
CraftingInput<ItemStack> input = getCraftingInput(inventory);
inventory.setResult(craftingTableRecipe.assemble(input, ItemBuildContext.of(serverPlayer)));
}
Function<PlayerOptionalContext>[] functions = craftingTableRecipe.craftingFunctions();
if (functions != null) {
PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer);
for (Function<PlayerOptionalContext> function : functions) {
function.run(context);
}
}
}
private CraftingInput<ItemStack> getCraftingInput(CraftingInventory inventory) {
@@ -690,10 +702,16 @@ public class RecipeEventListener implements Listener {
event.setResult(null);
return;
}
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
ItemBuildContext itemBuildContext = ItemBuildContext.of(BukkitAdaptors.adapt(player));
if (!smithingTrimRecipe.canUse(itemBuildContext)) {
event.setResult(null);
return;
}
SmithingInput<ItemStack> input = getSmithingInput(inventory);
if (smithingTrimRecipe.matches(input)) {
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
ItemStack result = smithingTrimRecipe.assemble(getSmithingInput(inventory), ItemBuildContext.of(BukkitAdaptors.adapt(player)));
ItemStack result = smithingTrimRecipe.assemble(getSmithingInput(inventory), itemBuildContext);
event.setResult(result);
} else {
event.setResult(null);

View File

@@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.loot.VanillaLoot;
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.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
@@ -90,8 +91,8 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme
return this.vanillaLootParser;
}
public class VanillaLootParser implements ConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"vanilla-loots", "vanilla-loot", "vanilla_loots", "vanilla_loot"};
public class VanillaLootParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"vanilla-loots", "vanilla-loot"};
@Override
public int loadingSequence() {
@@ -104,7 +105,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme
}
@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) {
String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("type"), "warning.config.vanilla_loot.missing_type");
VanillaLoot.Type typeEnum;
try {
@@ -126,7 +127,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme
VanillaLoot vanillaLoot = blockLoots.computeIfAbsent(BlockStateUtils.blockStateToId(blockState), k -> new VanillaLoot(VanillaLoot.Type.BLOCK));
vanillaLoot.addLootTable(lootTable);
} else {
for (Object blockState : BlockStateUtils.getAllVanillaBlockStates(Key.of(target))) {
for (Object blockState : BlockStateUtils.getPossibleBlockStates(Key.of(target))) {
if (blockState == MBlocks.AIR$defaultState) {
throw new LocalizedResourceConfigException("warning.config.vanilla_loot.block.invalid_target", target);
}

View File

@@ -20,12 +20,10 @@ import net.momirealms.craftengine.bukkit.plugin.command.BukkitSenderFactory;
import net.momirealms.craftengine.bukkit.plugin.gui.BukkitGuiManager;
import net.momirealms.craftengine.bukkit.plugin.injector.*;
import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager;
import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers;
import net.momirealms.craftengine.bukkit.plugin.scheduler.BukkitSchedulerAdapter;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.sound.BukkitSoundManager;
import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.util.RegistryUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
import net.momirealms.craftengine.core.item.ItemManager;
import net.momirealms.craftengine.core.plugin.CraftEngine;
@@ -57,6 +55,7 @@ import org.jspecify.annotations.Nullable;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
@@ -104,9 +103,15 @@ public class BukkitCraftEngine extends CraftEngine {
this.javaPlugin = javaPlugin;
}
protected void setUpConfig() {
this.translationManager = new TranslationManagerImpl(this);
protected void setUpConfigAndLocale() {
this.config = new Config(this);
this.config.updateConfigCache();
// 先读取语言后,再重载语言文件系统
this.config.loadForcedLocale();
this.translationManager = new TranslationManagerImpl(this);
this.translationManager.reload();
// 最后才加载完整的config配置
this.config.loadFullSettings();
}
public void injectRegistries() {
@@ -146,8 +151,8 @@ public class BukkitCraftEngine extends CraftEngine {
throw new InjectionException("Error initializing ProtectedFieldVisitor", e);
}
super.onPluginLoad();
super.blockManager.init();
super.networkManager = new BukkitNetworkManager(this);
super.blockManager.init();
super.itemManager = new BukkitItemManager(this);
this.successfullyLoaded = true;
super.compatibilityManager().onLoad();
@@ -191,7 +196,6 @@ public class BukkitCraftEngine extends CraftEngine {
BukkitItemBehaviors.init();
BukkitHitBoxTypes.init();
BukkitBlockEntityElementConfigs.init();
PacketConsumers.initEntities(RegistryUtils.currentEntityTypeRegistrySize());
super.packManager = new BukkitPackManager(this);
super.senderFactory = new BukkitSenderFactory(this);
super.recipeManager = new BukkitRecipeManager(this);
@@ -207,6 +211,19 @@ public class BukkitCraftEngine extends CraftEngine {
super.furnitureManager = new BukkitFurnitureManager(this);
super.onPluginEnable();
super.compatibilityManager().onEnable();
// todo 未来版本移除
Path legacyFile1 = this.dataFolderPath().resolve("additional-real-blocks.yml");
Path legacyFile2 = this.dataFolderPath().resolve("mappings.yml");
if (Files.exists(legacyFile1)) {
try {
Files.delete(legacyFile1);
Files.deleteIfExists(legacyFile2);
this.saveResource("resources/internal/configuration/mappings.yml");
} catch (IOException e) {
this.logger.warn("Failed to delete legacy files", e);
}
}
}
@Override
@@ -331,6 +348,11 @@ public class BukkitCraftEngine extends CraftEngine {
return (BukkitPackManager) packManager;
}
@Override
public BukkitFontManager fontManager() {
return (BukkitFontManager) fontManager;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
public void saveResource(String resourcePath) {
@@ -379,6 +401,7 @@ public class BukkitCraftEngine extends CraftEngine {
this.antiGrief = AntiGriefLib.builder(this.javaPlugin)
.ignoreOP(true)
.silentLogs(false)
.bypassPermission("craftengine.antigrief.bypass")
.build();
}
return this.antiGrief;

View File

@@ -6,6 +6,8 @@ import net.momirealms.craftengine.bukkit.plugin.command.feature.*;
import net.momirealms.craftengine.core.plugin.command.AbstractCommandManager;
import net.momirealms.craftengine.core.plugin.command.CommandFeature;
import net.momirealms.craftengine.core.plugin.command.sender.Sender;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.command.CommandSender;
import org.incendo.cloud.SenderMapper;
import org.incendo.cloud.bukkit.CloudBukkitCapabilities;
@@ -24,7 +26,11 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
plugin.javaPlugin(),
ExecutionCoordinator.simpleCoordinator(),
SenderMapper.identity()
));
) {{ // TODO等 cloud 修复后移除,绕过 obc.command.BukkitCommandWrapper 类检查,因为这个类在 1.21.9 版本被移除了,并且项目貌似没用到这个
if (VersionHelper.isOrAbove1_21_9() && ReflectionUtils.classExists("com.mojang.brigadier.tree.CommandNode")) {
registerCapability(CloudBukkitCapabilities.BRIGADIER);
}
}});
this.plugin = plugin;
this.index = Index.create(CommandFeature::getFeatureID, List.of(
new ReloadCommand(this, plugin),
@@ -56,7 +62,9 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new ListResourceCommand(this, plugin),
new UploadPackCommand(this, plugin),
new SendResourcePackCommand(this, plugin),
new DebugSaveDefaultResourcesCommand(this, plugin)
new DebugSaveDefaultResourcesCommand(this, plugin),
new DebugCleanCacheCommand(this, plugin)
// new OverrideGiveCommand(this, plugin)
));
final LegacyPaperCommandManager<CommandSender> manager = (LegacyPaperCommandManager<CommandSender>) getCommandManager();
manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true);

View File

@@ -5,7 +5,8 @@ import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.util.Key;
@@ -34,37 +35,39 @@ public class DebugAppearanceStateUsageCommand extends BukkitCommandFeature<Comma
.required("id", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
return CompletableFuture.completedFuture(plugin().blockManager().blockAppearanceArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList());
return CompletableFuture.completedFuture(plugin().blockManager().blockStateArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList());
}
}))
.handler(context -> {
String data = context.get("id");
BukkitBlockManager blockManager = plugin().blockManager();
Key baseBlockId = Key.of(data);
List<Integer> appearances = blockManager.blockAppearanceArranger().get(baseBlockId);
List<BlockStateWrapper> appearances = blockManager.blockStateArranger().get(baseBlockId);
if (appearances == null) return;
int i = 0;
Component block = Component.text(baseBlockId + ": ");
plugin().senderFactory().wrap(context.sender()).sendMessage(block);
VisualBlockStateAllocator allocator = blockManager.blockParser().visualBlockStateAllocator();
List<Component> batch = new ArrayList<>();
for (int appearance : appearances) {
for (BlockStateWrapper appearance : appearances) {
Component text = Component.text("|");
List<Integer> reals = blockManager.appearanceToRealStates(appearance);
if (reals == null || reals.isEmpty()) {
List<Integer> reals = blockManager.appearanceToRealStates(appearance.registryId());
if (reals.isEmpty()) {
Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.GREEN);
hover = hover.append(Component.newline()).append(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.GREEN));
hover = hover.append(Component.newline()).append(Component.text(appearance.getAsString()).color(NamedTextColor.GREEN));
text = text.color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover));
} else {
Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.RED);
boolean isFixed = allocator.isForcedState(appearance);
NamedTextColor namedTextColor = isFixed ? NamedTextColor.RED : NamedTextColor.YELLOW;
Component hover = Component.text(baseBlockId.value() + ":" + i).color(namedTextColor);
List<Component> hoverChildren = new ArrayList<>();
hoverChildren.add(Component.newline());
hoverChildren.add(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.RED));
hoverChildren.add(Component.text(appearance.getAsString()).color(namedTextColor));
for (int real : reals) {
hoverChildren.add(Component.newline());
hoverChildren.add(Component.text(blockManager.getImmutableBlockStateUnsafe(real).toString()).color(NamedTextColor.GRAY));
}
text = text.color(NamedTextColor.RED).hoverEvent(HoverEvent.showText(hover.children(hoverChildren)));
text = text.color(namedTextColor).hoverEvent(HoverEvent.showText(hover.children(hoverChildren)));
}
batch.add(text);
i++;

View File

@@ -0,0 +1,244 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.font.BukkitFontManager;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.font.BitmapImage;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.pack.allocator.IdAllocator;
import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.util.FileUtils;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.command.CommandSender;
import org.bukkit.inventory.ItemStack;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.Command;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.parser.standard.StringParser;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
public class DebugCleanCacheCommand extends BukkitCommandFeature<CommandSender> {
public DebugCleanCacheCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.optional("type", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
return CompletableFuture.completedFuture(List.of(Suggestion.suggestion("custom-model-data"), Suggestion.suggestion("custom-block-states"), Suggestion.suggestion("visual-block-states"), Suggestion.suggestion("font"), Suggestion.suggestion("all")));
}
}))
.handler(context -> {
if (this.plugin().isReloading()) {
context.sender().sendMessage("The plugin is reloading. Please wait until the process is complete.");
return;
}
String type = context.getOrDefault("type", "all");
switch (type) {
case "custom-model-data" -> handleCustomModelData(context);
case "font", "images" -> handleFont(context);
case "custom-block-states" -> handleCustomBlockState(context);
case "visual-block-states" -> handleVisualBlockState(context);
case "all" -> {
handleCustomModelData(context);
handleFont(context);
handleCustomBlockState(context);
handleVisualBlockState(context);
}
}
});
}
@Override
public String getFeatureID() {
return "debug_clean_cache";
}
private void handleVisualBlockState(CommandContext<CommandSender> context) {
BukkitBlockManager instance = BukkitBlockManager.instance();
Set<BlockStateWrapper> ids = new HashSet<>();
for (CustomBlock customBlock : instance.loadedBlocks().values()) {
for (ImmutableBlockState state : customBlock.variantProvider().states()) {
ids.add(state.vanillaBlockState());
}
}
VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator();
List<String> removed = visualBlockStateAllocator.cleanupUnusedIds(i -> !ids.contains(i));
try {
visualBlockStateAllocator.saveToCache();
} catch (IOException e) {
this.plugin().logger().warn("Error while saving visual block states allocation", e);
}
for (String id : removed) {
this.plugin().logger().info("Cleaned unsued block appearance: " + id);
}
context.sender().sendMessage("Cleaned " + removed.size() + " unused block state appearances");
}
private void handleCustomBlockState(CommandContext<CommandSender> context) {
BukkitBlockManager instance = BukkitBlockManager.instance();
Set<String> ids = new HashSet<>();
for (CustomBlock customBlock : instance.loadedBlocks().values()) {
for (ImmutableBlockState state : customBlock.variantProvider().states()) {
ids.add(state.toString());
}
}
IdAllocator idAllocator = instance.blockParser().internalIdAllocator();
List<String> removed = idAllocator.cleanupUnusedIds(i -> !ids.contains(i));
try {
idAllocator.saveToCache();
} catch (IOException e) {
this.plugin().logger().warn("Error while saving custom block states allocation", e);
}
for (String id : removed) {
this.plugin().logger().info("Cleaned unsued block state: " + id);
}
context.sender().sendMessage("Cleaned " + removed.size() + " unused custom block states");
}
private void handleFont(CommandContext<CommandSender> context) {
BukkitFontManager instance = this.plugin().fontManager();
Map<Key, Set<String>> idsMap = new HashMap<>();
for (BitmapImage image : instance.loadedImages().values()) {
Set<String> ids = idsMap.computeIfAbsent(image.font(), k -> new HashSet<>());
String id = image.id().toString();
ids.add(id);
for (int i = 0; i < image.rows(); i++) {
for (int j = 0; j < image.columns(); j++) {
String imageArgs = id + ":" + i + ":" + j;
ids.add(imageArgs);
}
}
}
int total = 0;
for (Map.Entry<Key, IdAllocator> entry : getAllCachedFont().entrySet()) {
Key font = entry.getKey();
Set<String> ids = idsMap.getOrDefault(font, Collections.emptySet());
List<String> removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i));
try {
entry.getValue().saveToCache();
} catch (IOException e) {
this.plugin().logger().warn("Error while saving codepoint allocation for font " + font.asString(), e);
return;
}
for (String id : removed) {
this.plugin().logger().info("Cleaned unsued image: " + id);
}
total += removed.size();
}
context.sender().sendMessage("Cleaned " + total + " unused codepoints");
}
private void handleCustomModelData(CommandContext<CommandSender> context) {
BukkitItemManager instance = BukkitItemManager.instance();
Map<Key, Set<String>> idsMap = new HashMap<>();
for (CustomItem<ItemStack> item : instance.loadedItems().values()) {
Set<String> ids = idsMap.computeIfAbsent(item.clientBoundMaterial(), k -> new HashSet<>());
ids.add(item.id().asString());
}
int total = 0;
for (Map.Entry<Key, IdAllocator> entry : getAllCachedCustomModelData().entrySet()) {
Set<String> ids = idsMap.getOrDefault(entry.getKey(), Collections.emptySet());
List<String> removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i));
total += removed.size();
try {
entry.getValue().saveToCache();
} catch (IOException e) {
this.plugin().logger().warn("Error while saving custom model data allocation for material " + entry.getKey().asString(), e);
return;
}
for (String id : removed) {
this.plugin().logger().info("Cleaned unsued item: " + id);
}
}
context.sender().sendMessage("Cleaned " + total + " unused custom model data");
}
public Map<Key, IdAllocator> getAllCachedCustomModelData() {
Path cacheDir = CraftEngine.instance().dataFolderPath().resolve("cache").resolve("custom-model-data");
if (!Files.exists(cacheDir)) {
return Map.of();
}
Map<Key, IdAllocator> idAllocators = new HashMap<>();
try (Stream<Path> files = Files.list(cacheDir)) {
files.filter(this::isJsonFile)
.forEach(file -> processIdAllocatorFile("minecraft", file, idAllocators));
} catch (IOException e) {
CraftEngine.instance().logger().warn("Failed to process: " + cacheDir.getFileName(), e);
}
return idAllocators;
}
public Map<Key, IdAllocator> getAllCachedFont() {
Path cacheDir = CraftEngine.instance().dataFolderPath().resolve("cache").resolve("font");
try {
List<Path> namespaces = FileUtils.collectNamespaces(cacheDir);
Map<Key, IdAllocator> idAllocators = new HashMap<>();
for (Path namespace : namespaces) {
processNamespace(namespace, idAllocators);
}
return idAllocators;
} catch (IOException e) {
CraftEngine.instance().logger().warn("Failed to load cached id allocators from: " + cacheDir, e);
return Collections.emptyMap();
}
}
private void processNamespace(Path namespace, Map<Key, IdAllocator> idAllocators) {
if (!Files.isDirectory(namespace)) {
return;
}
try (Stream<Path> files = Files.list(namespace)) {
files.filter(this::isJsonFile)
.forEach(file -> processIdAllocatorFile(namespace.getFileName().toString(), file, idAllocators));
} catch (IOException e) {
CraftEngine.instance().logger().warn("Failed to process namespace: " + namespace.getFileName(), e);
}
}
private boolean isJsonFile(Path file) {
return Files.isRegularFile(file) && file.getFileName().toString().endsWith(".json");
}
private void processIdAllocatorFile(String namespaceName, Path file, Map<Key, IdAllocator> idAllocators) {
try {
String fileName = FileUtils.pathWithoutExtension(file.getFileName().toString());
Key font = Key.of(namespaceName, fileName);
IdAllocator allocator = new IdAllocator(file);
allocator.loadFromCache();
idAllocators.put(font, allocator);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to load id allocator from: " + file, e);
}
}
}

View File

@@ -1,5 +1,8 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
@@ -7,6 +10,7 @@ import net.momirealms.craftengine.core.block.parser.BlockStateParser;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
import net.momirealms.craftengine.core.plugin.command.sender.Sender;
import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.Command;
@@ -38,7 +42,11 @@ public class DebugGetBlockInternalIdCommand extends BukkitCommandFeature<Command
String data = context.get("id");
ImmutableBlockState state = BlockStateParser.deserialize(data);
if (state == null) return;
context.sender().sendMessage(BlockStateUtils.getBlockOwnerIdFromState(state.customBlockState().literalObject()).toString());
String id = BlockStateUtils.getBlockOwnerIdFromState(state.customBlockState().literalObject()).toString();
Sender sender = plugin().senderFactory().wrap(context.sender());
sender.sendMessage(Component.text(id)
.hoverEvent(Component.text("Copy", NamedTextColor.YELLOW))
.clickEvent(ClickEvent.suggestCommand(id)));
});
}

View File

@@ -1,13 +1,16 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.sender.Sender;
import org.bukkit.Bukkit;
import org.bukkit.block.data.BlockData;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.incendo.cloud.Command;
import org.incendo.cloud.parser.standard.StringParser;
@@ -20,13 +23,15 @@ public class DebugGetBlockStateRegistryIdCommand extends BukkitCommandFeature<Co
@Override
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.senderType(Player.class)
.required("state", StringParser.greedyStringParser())
.handler(context -> {
String state = context.get("state");
BlockData blockData = Bukkit.createBlockData(state);
int id = BlockStateUtils.blockDataToId(blockData);
context.sender().sendMessage(String.valueOf(id));
Sender sender = plugin().senderFactory().wrap(context.sender());
sender.sendMessage(Component.text(id)
.hoverEvent(Component.text("Copy", NamedTextColor.YELLOW))
.clickEvent(ClickEvent.suggestCommand(String.valueOf(id))));
});
}

View File

@@ -5,22 +5,17 @@ import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.core.block.BlockManager;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.pack.allocator.IdAllocator;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.plugin.config.Config;
import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.Command;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.parser.standard.StringParser;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class DebugRealStateUsageCommand extends BukkitCommandFeature<CommandSender> {
@@ -31,34 +26,22 @@ public class DebugRealStateUsageCommand extends BukkitCommandFeature<CommandSend
@Override
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.required("id", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
return CompletableFuture.completedFuture(plugin().blockManager().blockAppearanceArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList());
}
}))
.handler(context -> {
String data = context.get("id");
BukkitBlockManager blockManager = plugin().blockManager();
Key baseBlockId = Key.of(data);
List<Integer> reals = blockManager.realBlockArranger().get(baseBlockId);
if (reals == null) return;
int i = 0;
Component block = Component.text(baseBlockId + ": ");
plugin().senderFactory().wrap(context.sender()).sendMessage(block);
plugin().senderFactory().wrap(context.sender()).sendMessage(Component.text("Serverside block state usage:"));
List<Component> batch = new ArrayList<>(100);
for (int real : reals) {
ImmutableBlockState state = blockManager.getImmutableBlockStateUnsafe(real);
IdAllocator idAllocator = blockManager.blockParser().internalIdAllocator();
for (int i = 0; i < Config.serverSideBlocks(); i++) {
ImmutableBlockState state = blockManager.getImmutableBlockStateUnsafe(i + blockManager.vanillaBlockStateCount());
if (state.isEmpty()) {
Component hover = Component.text("craftengine:" + baseBlockId.value() + "_" + i).color(NamedTextColor.GREEN);
Component hover = Component.text(BlockManager.createCustomBlockKey(i).asString()).color(NamedTextColor.GREEN);
batch.add(Component.text("|").color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover)));
} else {
Component hover = Component.text("craftengine:" + baseBlockId.value() + "_" + i).color(NamedTextColor.RED);
NamedTextColor namedTextColor = idAllocator.isForced(state.toString()) ? NamedTextColor.RED : NamedTextColor.YELLOW;
Component hover = Component.text(BlockManager.createCustomBlockKey(i).asString()).color(namedTextColor);
hover = hover.append(Component.newline()).append(Component.text(state.toString()).color(NamedTextColor.GRAY));
batch.add(Component.text("|").color(NamedTextColor.RED).hoverEvent(HoverEvent.showText(hover)));
batch.add(Component.text("|").color(namedTextColor).hoverEvent(HoverEvent.showText(hover)));
}
i++;
if (batch.size() == 100) {
plugin().senderFactory().wrap(context.sender())
.sendMessage(Component.text("").children(batch));

View File

@@ -1,6 +1,8 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
@@ -42,17 +44,21 @@ public class DebugTargetBlockCommand extends BukkitCommandFeature<CommandSender>
String bData = block.getBlockData().getAsString();
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
Sender sender = plugin().senderFactory().wrap(context.sender());
sender.sendMessage(Component.text(bData));
sender.sendMessage(Component.text(bData)
.hoverEvent(Component.text("Copy", NamedTextColor.YELLOW))
.clickEvent(ClickEvent.suggestCommand(bData)));
int id = BlockStateUtils.blockStateToId(blockState);
Object holder = BukkitBlockManager.instance().getMinecraftBlockHolder(id);
if (holder != null) {
if (!BlockStateUtils.isVanillaBlock(id)) {
Object holder = BukkitBlockManager.instance().getMinecraftBlockHolder(id);
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(id);
if (immutableBlockState != null) {
sender.sendMessage(Component.text(immutableBlockState.toString()));
String bState = immutableBlockState.toString();
sender.sendMessage(Component.text(bState)
.hoverEvent(Component.text("Copy", NamedTextColor.YELLOW))
.clickEvent(ClickEvent.suggestCommand(bState)));
}
ImmutableBlockState dataInCache = plugin().worldManager().getWorld(block.getWorld().getUID()).getBlockStateAtIfLoaded(LocationUtils.toBlockPos(block.getLocation()));
sender.sendMessage(Component.text("cache-state: " + !dataInCache.isEmpty()));
sender.sendMessage(Component.text("cache-state: " + (dataInCache != null && !dataInCache.isEmpty())));
try {
@SuppressWarnings("unchecked")
Set<Object> tags = (Set<Object>) CoreReflections.field$Holder$Reference$tags.get(holder);

View File

@@ -1,11 +1,14 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.api.CraftEngineItems;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.PlayerUtils;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
@@ -37,18 +40,16 @@ public class GetItemCommand extends BukkitCommandFeature<CommandSender> {
return builder
.senderType(Player.class)
.flag(FlagKeys.SILENT_FLAG)
.flag(FlagKeys.TO_INVENTORY_FLAG)
.required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
return CompletableFuture.completedFuture(plugin().itemManager().cachedSuggestions());
return CompletableFuture.completedFuture(plugin().itemManager().cachedCustomItemSuggestions());
}
}))
.optional("amount", IntegerParser.integerParser(1, 6400))
.optional("amount", IntegerParser.integerParser(1, 9999))
.handler(context -> {
Player player = context.sender();
int amount = context.getOrDefault("amount", 1);
boolean toInv = context.flags().hasFlag(FlagKeys.TO_INVENTORY);
NamespacedKey namespacedKey = context.get("id");
Key itemId = Key.of(namespacedKey.namespace(), namespacedKey.value());
CustomItem<ItemStack> customItem = CraftEngineItems.byId(itemId);
@@ -61,24 +62,17 @@ public class GetItemCommand extends BukkitCommandFeature<CommandSender> {
itemId = customItem.id();
}
}
ItemStack builtItem = customItem.buildItemStack(plugin().adapt(context.sender()));
int amountToGive = amount;
int maxStack = builtItem.getMaxStackSize();
while (amountToGive > 0) {
int perStackSize = Math.min(maxStack, amountToGive);
amountToGive -= perStackSize;
ItemStack more = builtItem.clone();
more.setAmount(perStackSize);
if (toInv) {
PlayerUtils.putItemsToInventory(player.getInventory(), more, more.getAmount());
} else {
PlayerUtils.dropItem(player, more, false, true, false);
}
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
Item<ItemStack> builtItem = customItem.buildItem(serverPlayer);
if (builtItem != null) {
PlayerUtils.giveItem(serverPlayer, amount, builtItem);
}
handleFeedback(context, MessageConstants.COMMAND_ITEM_GET_SUCCESS, Component.text(amount), Component.text(itemId.toString()));
});
}
@Override
public String getFeatureID() {
return "get_item";

View File

@@ -1,17 +1,19 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.api.CraftEngineItems;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.PlayerUtils;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -40,19 +42,17 @@ public class GiveItemCommand extends BukkitCommandFeature<CommandSender> {
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.flag(FlagKeys.SILENT_FLAG)
.flag(FlagKeys.TO_INVENTORY_FLAG)
.required("player", MultiplePlayerSelectorParser.multiplePlayerSelectorParser(true))
.required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
return CompletableFuture.completedFuture(plugin().itemManager().cachedSuggestions());
return CompletableFuture.completedFuture(plugin().itemManager().cachedCustomItemSuggestions());
}
}))
.optional("amount", IntegerParser.integerParser(1, 6400))
.optional("amount", IntegerParser.integerParser(1, 9999))
.handler(context -> {
MultiplePlayerSelector selector = context.get("player");
int amount = context.getOrDefault("amount", 1);
boolean toInv = context.flags().hasFlag(FlagKeys.TO_INVENTORY);
NamespacedKey namespacedKey = context.get("id");
Key itemId = Key.of(namespacedKey.namespace(), namespacedKey.value());
CustomItem<ItemStack> customItem = CraftEngineItems.byId(itemId);
@@ -67,30 +67,10 @@ public class GiveItemCommand extends BukkitCommandFeature<CommandSender> {
}
Collection<Player> players = selector.values();
for (Player player : players) {
ItemStack builtItem = customItem.buildItemStack(plugin().adapt(player));
if (builtItem == null) {
return;
}
int amountToGive = amount;
int maxStack = builtItem.getMaxStackSize();
while (amountToGive > 0) {
int perStackSize = Math.min(maxStack, amountToGive);
amountToGive -= perStackSize;
ItemStack more = builtItem.clone();
more.setAmount(perStackSize);
if (toInv) {
if (VersionHelper.isFolia()) {
player.getScheduler().run(plugin().javaPlugin(), (t) -> PlayerUtils.putItemsToInventory(player.getInventory(), more, more.getAmount()), () -> {});
} else {
PlayerUtils.putItemsToInventory(player.getInventory(), more, more.getAmount());
}
} else {
if (VersionHelper.isFolia()) {
player.getScheduler().run(plugin().javaPlugin(), (t) -> PlayerUtils.dropItem(player, more, false, true, false), () -> {});
} else {
PlayerUtils.dropItem(player, more, false, true, false);
}
}
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
Item<ItemStack> builtItem = customItem.buildItem(serverPlayer);
if (builtItem != null) {
PlayerUtils.giveItem(serverPlayer, amount, builtItem);
}
}
if (players.size() == 1) {

View File

@@ -37,7 +37,7 @@ public class SearchRecipeAdminCommand extends BukkitCommandFeature<CommandSender
.required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
return CompletableFuture.completedFuture(plugin().itemManager().cachedSuggestions());
return CompletableFuture.completedFuture(plugin().itemManager().cachedCustomItemSuggestions());
}
}))
.handler(context -> {

View File

@@ -37,7 +37,7 @@ public class SearchUsageAdminCommand extends BukkitCommandFeature<CommandSender>
.required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
return CompletableFuture.completedFuture(plugin().itemManager().cachedSuggestions());
return CompletableFuture.completedFuture(plugin().itemManager().cachedCustomItemSuggestions());
}
}))
.handler(context -> {

View File

@@ -1,17 +1,23 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.item.ComponentTypes;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MSoundEvents;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.bukkit.util.PlayerUtils;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.NamespacedKey;
@@ -27,14 +33,17 @@ import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.parser.flag.CommandFlag;
import org.incendo.cloud.parser.standard.FloatParser;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.util.List;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class TotemAnimationCommand extends BukkitCommandFeature<CommandSender> {
public static final Object FIX_TOTEM_SOUND_PACKET = FastNMS.INSTANCE.constructor$ClientboundSoundPacket(FastNMS.INSTANCE.method$Holder$direct(MSoundEvents.TOTEM_USE), CoreReflections.instance$SoundSource$MUSIC, 0, Integer.MIN_VALUE, 0, 0, 0, 0);
public TotemAnimationCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
@@ -44,6 +53,7 @@ public class TotemAnimationCommand extends BukkitCommandFeature<CommandSender> {
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.flag(FlagKeys.SILENT_FLAG)
.flag(CommandFlag.builder("no-sound"))
.required("players", MultiplePlayerSelectorParser.multiplePlayerSelectorParser())
.required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
@@ -51,8 +61,16 @@ public class TotemAnimationCommand extends BukkitCommandFeature<CommandSender> {
return CompletableFuture.completedFuture(plugin().itemManager().cachedTotemSuggestions());
}
}))
.flag(CommandFlag.builder("sound_event").withComponent(NamespacedKeyParser.namespacedKeyParser()).build())
.flag(CommandFlag.builder("sound_location").withComponent(NamespacedKeyParser.namespacedKeyParser()).build())
.optional("sound", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
return CompletableFuture.completedFuture(plugin().soundManager().cachedSoundSuggestions());
}
}))
.optional("volume", FloatParser.floatParser(0f))
.optional("pitch", FloatParser.floatParser(0f, 2f))
.optional("min-volume", FloatParser.floatParser(0f))
.optional("min-pitch", FloatParser.floatParser(0f, 2f))
.handler(context -> {
NamespacedKey namespacedKey = context.get("id");
Key key = Key.of(namespacedKey.namespace(), namespacedKey.value());
@@ -61,28 +79,31 @@ public class TotemAnimationCommand extends BukkitCommandFeature<CommandSender> {
handleFeedback(context, MessageConstants.COMMAND_TOTEM_NOT_TOTEM, Component.text(key.toString()));
return;
}
Item<ItemStack> item = customItem.buildItem(ItemBuildContext.empty());
if (VersionHelper.isOrAbove1_21_2()) {
if (context.flags().contains("sound_location")) {
String soundResourceLocation = context.flags().getValue("sound_location").get().toString();
if (soundResourceLocation != null) {
item.setComponent(ComponentTypes.DEATH_PROTECTION, Map.of("death_effects", List.of(Map.of("type", "play_sound", "sound", Map.of(
"sound_id", soundResourceLocation
)))));
}
} else if (context.flags().contains("sound_event")) {
String soundEvent = context.flags().getValue("sound_event").get().toString();
if (soundEvent != null) {
item.setComponent(ComponentTypes.DEATH_PROTECTION, Map.of("death_effects", List.of(Map.of("type", "play_sound", "sound", soundEvent))));
}
} else {
item.setComponent(ComponentTypes.DEATH_PROTECTION, Map.of());
}
Optional<NamespacedKey> soundKey = context.optional("sound");
SoundData soundData = null;
if (soundKey.isPresent()) {
float volume = context.getOrDefault("volume", 1.0f);
float pitch = context.getOrDefault("pitch", 1.0f);
float minVolume = context.getOrDefault("min-volume", 1.0f);
float minPitch = context.getOrDefault("min-pitch", 1.0f);
soundData = SoundData.of(KeyUtils.namespacedKey2Key(soundKey.get()), SoundData.SoundValue.ranged(minVolume, volume), SoundData.SoundValue.ranged(minPitch, pitch));
}
ItemStack totemItem = item.getItem();
boolean removeSound = context.flags().hasFlag("no-sound");
MultiplePlayerSelector selector = context.get("players");
for (Player player : selector.values()) {
PlayerUtils.sendTotemAnimation(player, totemItem);
Collection<Player> players = selector.values();
for (Player player : players) {
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
Item<ItemStack> item = customItem.buildItem(serverPlayer);
if (VersionHelper.isOrAbove1_21_2()) {
item.setJavaComponent(ComponentTypes.DEATH_PROTECTION, Map.of());
}
PlayerUtils.sendTotemAnimation(serverPlayer, item, soundData, removeSound);
}
if (players.size() == 1) {
handleFeedback(context, MessageConstants.COMMAND_TOTEM_SUCCESS_SINGLE, Component.text(namespacedKey.toString()), Component.text(players.iterator().next().getName()));
} else if (players.size() > 1) {
handleFeedback(context, MessageConstants.COMMAND_TOTEM_SUCCESS_MULTIPLE, Component.text(namespacedKey.toString()), Component.text(players.size()));
}
});
}

View File

@@ -12,7 +12,6 @@ import net.momirealms.craftengine.bukkit.util.InventoryUtils;
import net.momirealms.craftengine.bukkit.util.LegacyInventoryUtils;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.gui.*;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -23,11 +22,9 @@ import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.MenuType;
public class BukkitGuiManager implements GuiManager, Listener {
private static final boolean useNewOpenInventory = ReflectionUtils.getDeclaredMethod(InventoryView.class, void.class, new String[]{"open"}) != null;
// private static final boolean useNewOpenInventory = ReflectionUtils.getDeclaredMethod(InventoryView.class, void.class, new String[]{"open"}) != null;
private static BukkitGuiManager instance;
private final BukkitCraftEngine plugin;
@@ -46,21 +43,21 @@ public class BukkitGuiManager implements GuiManager, Listener {
HandlerList.unregisterAll(this);
}
@SuppressWarnings("UnstableApiUsage")
// @SuppressWarnings("UnstableApiUsage")
@Override
public void openInventory(net.momirealms.craftengine.core.entity.player.Player player, GuiType guiType) {
Player bukkitPlayer = (Player) player.platformPlayer();
if (useNewOpenInventory) {
switch (guiType) {
case ANVIL -> MenuType.ANVIL.create(bukkitPlayer).open();
case LOOM -> MenuType.LOOM.create(bukkitPlayer).open();
case ENCHANTMENT -> MenuType.ENCHANTMENT.create(bukkitPlayer).open();
case CRAFTING -> MenuType.CRAFTER_3X3.create(bukkitPlayer).open();
case CARTOGRAPHY -> MenuType.CARTOGRAPHY_TABLE.create(bukkitPlayer).open();
case SMITHING -> MenuType.SMITHING.create(bukkitPlayer).open();
case GRINDSTONE -> MenuType.GRINDSTONE.create(bukkitPlayer).open();
}
} else {
// if (useNewOpenInventory) {
// switch (guiType) {
// case ANVIL -> MenuType.ANVIL.create(bukkitPlayer).open();
// case LOOM -> MenuType.LOOM.create(bukkitPlayer).open();
// case ENCHANTMENT -> MenuType.ENCHANTMENT.create(bukkitPlayer).open();
// case CRAFTING -> MenuType.CRAFTING.create(bukkitPlayer).open();
// case CARTOGRAPHY -> MenuType.CARTOGRAPHY_TABLE.create(bukkitPlayer).open();
// case SMITHING -> MenuType.SMITHING.create(bukkitPlayer).open();
// case GRINDSTONE -> MenuType.GRINDSTONE.create(bukkitPlayer).open();
// }
// } else {
switch (guiType) {
case ANVIL -> LegacyInventoryUtils.openAnvil(bukkitPlayer);
case LOOM -> LegacyInventoryUtils.openLoom(bukkitPlayer);
@@ -70,7 +67,7 @@ public class BukkitGuiManager implements GuiManager, Listener {
case ENCHANTMENT -> LegacyInventoryUtils.openEnchanting(bukkitPlayer);
case CARTOGRAPHY -> LegacyInventoryUtils.openCartographyTable(bukkitPlayer);
}
}
// }
}
@Override

View File

@@ -17,6 +17,8 @@ import net.momirealms.craftengine.bukkit.block.BukkitBlockShape;
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.MRegistries;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.bukkit.util.NoteBlockChainUpdateUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.BlockKeys;
@@ -167,6 +169,12 @@ public final class BlockGenerator {
// updateEntityMovementAfterFallOn
.method(ElementMatchers.is(CoreReflections.method$Block$updateEntityMovementAfterFallOn))
.intercept(MethodDelegation.to(UpdateEntityMovementAfterFallOnInterceptor.INSTANCE))
// stepOn
.method(ElementMatchers.is(CoreReflections.method$Block$stepOn))
.intercept(MethodDelegation.to(StepOnInterceptor.INSTANCE))
// onProjectileHit
.method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$onProjectileHit))
.intercept(MethodDelegation.to(OnProjectileHitInterceptor.INSTANCE))
;
// 1.21.5+
if (CoreReflections.method$BlockBehaviour$affectNeighborsAfterRemoval != null) {
@@ -193,6 +201,30 @@ public final class BlockGenerator {
field$CraftEngineBlock$isTripwire = clazz$CraftEngineBlock.getField("isClientSideTripwire");
}
public static DelegatingBlock generateBlock(Key blockId) throws Throwable {
ObjectHolder<BlockBehavior> behaviorHolder = new ObjectHolder<>(EmptyBlockBehavior.INSTANCE);
ObjectHolder<BlockShape> shapeHolder = new ObjectHolder<>(STONE_SHAPE);
Object newBlockInstance = constructor$CraftEngineBlock.invoke(createEmptyBlockProperties(blockId));
field$CraftEngineBlock$behavior.set(newBlockInstance, behaviorHolder);
field$CraftEngineBlock$shape.set(newBlockInstance, shapeHolder);
Object stateDefinitionBuilder = CoreReflections.constructor$StateDefinition$Builder.newInstance(newBlockInstance);
Object stateDefinition = CoreReflections.method$StateDefinition$Builder$create.invoke(stateDefinitionBuilder,
(Function<Object, Object>) FastNMS.INSTANCE::method$Block$defaultState, BlockStateGenerator.instance$StateDefinition$Factory);
CoreReflections.field$Block$StateDefinition.set(newBlockInstance, stateDefinition);
CoreReflections.field$Block$defaultBlockState.set(newBlockInstance, ((ImmutableList<?>) CoreReflections.field$StateDefinition$states.get(stateDefinition)).getFirst());
return (DelegatingBlock) newBlockInstance;
}
private static Object createEmptyBlockProperties(Key id) throws ReflectiveOperationException {
Object blockProperties = CoreReflections.method$BlockBehaviour$Properties$of.invoke(null);
Object resourceLocation = KeyUtils.toResourceLocation(id);
Object resourceKey = FastNMS.INSTANCE.method$ResourceKey$create(MRegistries.BLOCK, resourceLocation);
if (CoreReflections.field$BlockBehaviour$Properties$id != null) {
CoreReflections.field$BlockBehaviour$Properties$id.set(blockProperties, resourceKey);
}
return blockProperties;
}
public static Object generateBlock(Key replacedBlock, Object ownerBlock, Object properties) throws Throwable {
Object ownerProperties = CoreReflections.field$BlockBehaviour$properties.get(ownerBlock);
CoreReflections.field$BlockBehaviour$Properties$hasCollision.set(properties, CoreReflections.field$BlockBehaviour$Properties$hasCollision.get(ownerProperties));
@@ -708,6 +740,20 @@ public final class BlockGenerator {
}
}
public static class StepOnInterceptor {
public static final StepOnInterceptor INSTANCE = new StepOnInterceptor();
@RuntimeType
public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) {
ObjectHolder<BlockBehavior> holder = ((DelegatingBlock) thisObj).behaviorDelegate();
try {
holder.value().stepOn(thisObj, args, superMethod);
} catch (Exception e) {
CraftEngine.instance().logger().severe("Failed to run stepOn", e);
}
}
}
public static class FallOnInterceptor {
public static final FallOnInterceptor INSTANCE = new FallOnInterceptor();
@@ -743,4 +789,18 @@ public final class BlockGenerator {
}
}
}
public static class OnProjectileHitInterceptor {
public static final OnProjectileHitInterceptor INSTANCE = new OnProjectileHitInterceptor();
@RuntimeType
public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) {
ObjectHolder<BlockBehavior> holder = ((DelegatingBlock) thisObj).behaviorDelegate();
try {
holder.value().onProjectileHit(thisObj, args, superMethod);
} catch (Exception e) {
CraftEngine.instance().logger().severe("Failed to run onProjectileHit", e);
}
}
}
}

View File

@@ -64,15 +64,17 @@ public final class BlockStateGenerator {
.method(ElementMatchers.is(CoreReflections.method$StateHolder$getValue))
.intercept(MethodDelegation.to(GetPropertyValueInterceptor.INSTANCE))
.method(ElementMatchers.is(CoreReflections.method$StateHolder$setValue))
.intercept(MethodDelegation.to(SetPropertyValueInterceptor.INSTANCE));
.intercept(MethodDelegation.to(SetPropertyValueInterceptor.INSTANCE))
.method(ElementMatchers.is(CoreReflections.method$BlockStateBase$isBlock))
.intercept(MethodDelegation.to(IsBlockInterceptor.INSTANCE));
Class<?> clazz$CraftEngineBlock = stateBuilder.make().load(BlockStateGenerator.class.getClassLoader()).getLoaded();
constructor$CraftEngineBlockState = VersionHelper.isOrAbove1_20_5() ?
MethodHandles.publicLookup().in(clazz$CraftEngineBlock)
.findConstructor(clazz$CraftEngineBlock, MethodType.methodType(void.class, CoreReflections.clazz$Block, Reference2ObjectArrayMap.class, MapCodec.class))
.asType(MethodType.methodType(CoreReflections.clazz$BlockState, CoreReflections.clazz$Block, Reference2ObjectArrayMap.class, MapCodec.class)) :
.findConstructor(clazz$CraftEngineBlock, MethodType.methodType(void.class, CoreReflections.clazz$Block, Reference2ObjectArrayMap.class, MapCodec.class))
.asType(MethodType.methodType(CoreReflections.clazz$BlockState, CoreReflections.clazz$Block, Reference2ObjectArrayMap.class, MapCodec.class)) :
MethodHandles.publicLookup().in(clazz$CraftEngineBlock)
.findConstructor(clazz$CraftEngineBlock, MethodType.methodType(void.class, CoreReflections.clazz$Block, ImmutableMap.class, MapCodec.class))
.asType(MethodType.methodType(CoreReflections.clazz$BlockState, CoreReflections.clazz$Block, ImmutableMap.class, MapCodec.class));
.findConstructor(clazz$CraftEngineBlock, MethodType.methodType(void.class, CoreReflections.clazz$Block, ImmutableMap.class, MapCodec.class))
.asType(MethodType.methodType(CoreReflections.clazz$BlockState, CoreReflections.clazz$Block, ImmutableMap.class, MapCodec.class));
String generatedFactoryClassName = packageWithName.substring(0, packageWithName.lastIndexOf('.')) + ".CraftEngineStateFactory";
DynamicType.Builder<?> factoryBuilder = byteBuddy
@@ -183,6 +185,23 @@ public final class BlockStateGenerator {
}
}
public static class IsBlockInterceptor {
public static final IsBlockInterceptor INSTANCE = new IsBlockInterceptor();
@RuntimeType
public boolean intercept(@This Object thisObj, @AllArguments Object[] args) {
DelegatingBlockState customState = (DelegatingBlockState) thisObj;
ImmutableBlockState thisState = customState.blockState();
if (thisState == null) return false;
if (FastNMS.INSTANCE.method$Block$defaultState(args[0]) instanceof DelegatingBlockState holder) {
ImmutableBlockState holderState = holder.blockState();
if (holderState == null) return false;
return holderState.owner().equals(thisState.owner());
}
return false;
}
}
public static class CreateStateInterceptor {
public static final CreateStateInterceptor INSTANCE = new CreateStateInterceptor();

View File

@@ -14,7 +14,6 @@ import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatchers;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.block.DelegatingBlockState;
import net.momirealms.craftengine.core.block.EmptyBlock;
@@ -313,9 +312,7 @@ public final class WorldStorageInjector {
@SuppressWarnings("DuplicatedCode")
private static void updateLight(@This InjectedHolder thisObj, Object clientState, Object serverState, int x, int y, int z) {
CEWorld world = thisObj.ceChunk().world;
Object blockPos = LocationUtils.toBlockPos(x, y, z);
Object serverWorld = world.world().serverWorld();
if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(serverState, clientState, serverWorld, blockPos)) {
if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(serverState, clientState)) {
SectionPos sectionPos = thisObj.cePos();
List<SectionPos> pos = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, 15);
world.sectionLightUpdated(pos);
@@ -325,16 +322,14 @@ public final class WorldStorageInjector {
@SuppressWarnings("DuplicatedCode")
private static void updateLight$complex(@This InjectedHolder thisObj, Object newClientState, Object newServerState, Object oldServerState, int x, int y, int z) {
CEWorld world = thisObj.ceChunk().world;
Object blockPos = LocationUtils.toBlockPos(x, y, z);
Object serverWorld = world.world().serverWorld();
// 如果客户端新状态和服务端新状态光照属性不同
if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(newClientState, newServerState, serverWorld, blockPos)) {
if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(newClientState, newServerState)) {
SectionPos sectionPos = thisObj.cePos();
List<SectionPos> pos = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, 15);
world.sectionLightUpdated(pos);
return;
}
if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(newServerState, oldServerState, serverWorld, blockPos)) {
if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(newServerState, oldServerState)) {
SectionPos sectionPos = thisObj.cePos();
List<SectionPos> pos = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, 15);
world.sectionLightUpdated(pos);

View File

@@ -60,6 +60,8 @@ public interface PacketIds {
int clientboundUpdateAdvancementsPacket();
int clientBoundMerchantOffersPacket();
int serverboundContainerClickPacket();
int serverboundSetCreativeModeSlotPacket();

View File

@@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.entity.data.BlockDisplayEntityData;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers;
import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.core.entity.player.Player;
@@ -36,12 +36,7 @@ public class BlockDisplayPacketHandler implements EntityPacketHandler {
if (entityDataId == BlockDisplayEntityData.DisplayedBlock.id()) {
Object blockState = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
int stateId = BlockStateUtils.blockStateToId(blockState);
int newStateId;
if (!user.clientModEnabled()) {
newStateId = PacketConsumers.remap(stateId);
} else {
newStateId = PacketConsumers.remapMOD(stateId);
}
int newStateId= BukkitNetworkManager.instance().remapBlockState(stateId, user.clientModEnabled());
if (newStateId == stateId) continue;
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(

View File

@@ -41,7 +41,7 @@ public class CommonItemPacketHandler implements EntityPacketHandler {
continue;
}
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(itemStack, (BukkitServerPlayer) user);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(itemStack, user);
if (optional.isEmpty()) continue;
isChanged = true;
itemStack = optional.get();

View File

@@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.entity.data.EnderManData;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers;
import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.core.entity.player.Player;
@@ -38,12 +38,7 @@ public class EndermanPacketHandler implements EntityPacketHandler {
Optional<Object> blockState = (Optional<Object>) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (blockState.isEmpty()) continue;
int stateId = BlockStateUtils.blockStateToId(blockState.get());
int newStateId;
if (!user.clientModEnabled()) {
newStateId = PacketConsumers.remap(stateId);
} else {
newStateId = PacketConsumers.remapMOD(stateId);
}
int newStateId = BukkitNetworkManager.instance().remapBlockState(stateId, user.clientModEnabled());
if (newStateId == stateId) continue;
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(

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