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

Merge pull request #68 from Xiao-MoMi/dev

0.0.40
This commit is contained in:
XiaoMoMi
2025-03-29 02:26:58 +08:00
committed by GitHub
77 changed files with 1763 additions and 242 deletions

View File

@@ -5,9 +5,7 @@ plugins {
repositories {
maven("https://jitpack.io/")
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // papi
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://maven.enginehub.org/repo/") // worldguard worldedit
maven("https://repo.momirealms.net/releases/")
mavenCentral()
}
@@ -24,8 +22,6 @@ dependencies {
compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}")
// NMS
compileOnly("net.momirealms:craft-engine-nms-helper:${rootProject.properties["nms_helper_version"]}")
// Placeholder
compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}")
// Platform
compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT")
// OpenGL Math
@@ -56,8 +52,6 @@ dependencies {
compileOnly("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") {
exclude("com.google.code.gson", "gson")
}
compileOnly("com.sk89q.worldedit:worldedit-core:7.2.19")
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.2.19")
// Data Fixer Upper
compileOnly("com.mojang:datafixerupper:${rootProject.properties["datafixerupper_version"]}")
// BStats

View File

@@ -2,14 +2,27 @@ repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://r.irepo.space/maven/")
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // papi
maven("https://maven.enginehub.org/repo/") // worldguard worldedit
maven("https://repo.rapture.pw/repository/maven-releases/") // slime world
maven("https://repo.infernalsuite.com/repository/maven-snapshots/") // slime world
maven("https://repo.momirealms.net/releases/")
}
dependencies {
compileOnly(project(":core"))
compileOnly("net.momirealms:sparrow-nbt:${rootProject.properties["sparrow_nbt_version"]}")
// Platform
compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT")
// NeigeItems
compileOnly("pers.neige.neigeitems:NeigeItems:1.21.42")
// Placeholder
compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}")
// WorldEdit
compileOnly("com.sk89q.worldedit:worldedit-core:7.2.19")
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.2.19")
// SlimeWorld
compileOnly("com.infernalsuite.asp:api:4.0.0-SNAPSHOT")
}
java {

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.bukkit.plugin.papi;
package net.momirealms.craftengine.bukkit.compatibility.papi;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.momirealms.craftengine.core.font.BitmapImage;

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.util;
package net.momirealms.craftengine.bukkit.compatibility.papi;
import me.clip.placeholderapi.PlaceholderAPI;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import org.bukkit.OfflinePlayer;
public class PlaceholderAPIUtils {
@@ -10,4 +11,9 @@ public class PlaceholderAPIUtils {
public static String parse(OfflinePlayer player, String text) {
return PlaceholderAPI.setPlaceholders(player, text);
}
public static void registerExpansions(CraftEngine plugin) {
new ImageExpansion(plugin).register();
new ShiftExpansion(plugin).register();
}
}

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.bukkit.plugin.papi;
package net.momirealms.craftengine.bukkit.compatibility.papi;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.momirealms.craftengine.core.plugin.CraftEngine;

View File

@@ -0,0 +1,63 @@
package net.momirealms.craftengine.bukkit.compatibility.slimeworld;
import com.infernalsuite.asp.api.AdvancedSlimePaperAPI;
import com.infernalsuite.asp.api.events.LoadSlimeWorldEvent;
import com.infernalsuite.asp.api.world.SlimeWorld;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldManager;
import net.momirealms.craftengine.core.world.chunk.storage.DefaultStorageAdaptor;
import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Method;
public class SlimeFormatStorageAdaptor extends DefaultStorageAdaptor implements Listener {
private final WorldManager worldManager;
private final Class<?> byteArrayTagClass = ReflectionUtils.getClazz("net{}kyori{}adventure{}nbt{}ByteArrayBinaryTag".replace("{}", "."));
private final Method method$ByteArrayBinaryTag$byteArrayBinaryTag = ReflectionUtils.getStaticMethod(byteArrayTagClass, byteArrayTagClass, byte.class.arrayType());
private final Method method$ByteArrayBinaryTag$value = ReflectionUtils.getMethod(byteArrayTagClass, byte.class.arrayType());
@EventHandler
public void onWorldLoad(LoadSlimeWorldEvent event) {
org.bukkit.World world = Bukkit.getWorld(event.getSlimeWorld().getName());
this.worldManager.loadWorld(this.worldManager.createWorld(this.worldManager.wrap(world), new SlimeWorldDataStorage(event.getSlimeWorld(), this)));
}
public SlimeFormatStorageAdaptor(WorldManager worldManager) {
this.worldManager = worldManager;
}
public SlimeWorld getWorld(String name) {
return AdvancedSlimePaperAPI.instance().getLoadedWorld(name);
}
// 请注意在加载事件的时候无法通过AdvancedSlimePaperAPI.instance().getLoadedWorld来判断是否为slime世界
@Override
public @NotNull WorldDataStorage adapt(@NotNull World world) {
SlimeWorld slimeWorld = getWorld(world.name());
if (slimeWorld == null) {
return super.adapt(world);
}
return new SlimeWorldDataStorage(slimeWorld, this);
}
public byte[] byteArrayTagToBytes(Object byteArrayTag) {
try {
return (byte[]) method$ByteArrayBinaryTag$value.invoke(byteArrayTag);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to convert byte array tag to byte[]", e);
}
}
public Object bytesToByteArrayTag(byte[] bytes) {
try {
return method$ByteArrayBinaryTag$byteArrayBinaryTag.invoke(null, (Object) bytes);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to convert byte array tag to byte[]", e);
}
}
}

View File

@@ -0,0 +1,70 @@
package net.momirealms.craftengine.bukkit.compatibility.slimeworld;
import com.infernalsuite.asp.api.world.SlimeChunk;
import com.infernalsuite.asp.api.world.SlimeWorld;
import net.momirealms.craftengine.core.world.ChunkPos;
import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.NBT;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Map;
public class SlimeWorldDataStorage implements WorldDataStorage {
private final WeakReference<SlimeWorld> slimeWorld;
private final SlimeFormatStorageAdaptor adaptor;
public SlimeWorldDataStorage(SlimeWorld slimeWorld, SlimeFormatStorageAdaptor adaptor) {
this.slimeWorld = new WeakReference<>(slimeWorld);
this.adaptor = adaptor;
}
public SlimeWorld getWorld() {
return slimeWorld.get();
}
@Nullable
@Override
public CompoundTag readChunkTagAt(ChunkPos pos) {
SlimeChunk slimeChunk = getWorld().getChunk(pos.x, pos.z);
if (slimeChunk == null) return null;
Object tag = slimeChunk.getExtraData().get("craftengine");
if (tag == null) return null;
try {
return NBT.readCompound(new DataInputStream(new ByteArrayInputStream(adaptor.byteArrayTagToBytes(tag))));
} catch (Exception e) {
throw new RuntimeException("Failed to read chunk tag from slime world. " + pos, e);
}
}
@SuppressWarnings("unchecked")
@Override
public void writeChunkTagAt(ChunkPos pos, @Nullable CompoundTag nbt) {
SlimeChunk slimeChunk = getWorld().getChunk(pos.x, pos.z);
if (slimeChunk == null) return;
if (nbt == null) {
slimeChunk.getExtraData().remove("craftengine");
} else {
try {
Object tag = adaptor.bytesToByteArrayTag(NBT.toBytes(nbt));
Map<String, ?> data1 = slimeChunk.getExtraData();
Map<String, Object> data2 = (Map<String, Object>) data1;
data2.put("craftengine", tag);
} catch (Exception e) {
throw new RuntimeException("Failed to write chunk tag to slime world. " + pos, e);
}
}
}
@Override
public void flush() {
}
@Override
public void close() throws IOException {
}
}

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.bukkit.block.worldedit;
package net.momirealms.craftengine.bukkit.compatibility.worldedit;
import com.sk89q.worldedit.bukkit.BukkitBlockRegistry;
import com.sk89q.worldedit.util.concurrency.LazyReference;

View File

@@ -14,9 +14,7 @@
# Q: When do I need to configure this file?
# A: When the number of real block IDs is insufficient, but there are still available appearances.
# By default, the plugin only registers an additional 112 oak leaf block states (for the default configuration needs [>=28 states]).
minecraft:oak_leaves: 112
minecraft:oak_sapling: 1
minecraft:birch_sapling: 1
minecraft:spruce_sapling: 1
@@ -24,5 +22,5 @@ minecraft:jungle_sapling: 1
minecraft:dark_oak_sapling: 1
minecraft:acacia_sapling: 1
minecraft:cherry_sapling: 1
minecraft:anvil: 2
minecraft:anvil: 2
minecraft:sugarcane: 14

View File

@@ -59,5 +59,7 @@ categories:
- default:netherite_anvil
- default:fairy_flower
- default:reed
- default:flame_cane
- default:gunpowder_block
- default:solid_gunpowder_block
- default:solid_gunpowder_block
- default:ender_pearl_flower_seeds

View File

@@ -3,6 +3,8 @@ i18n:
item.chinese_lantern: "Chinese Lantern"
item.fairy_flower: "Fairy Flower"
item.reed: "Reed"
item.flame_cane: "Flame Cane"
item.ender_pearl_flower_seeds: "Ender Pearl Flower Seeds"
item.bench: "Bench"
item.table_lamp: "Table Lamp"
item.wooden_chair: "Wooden Chair"
@@ -41,6 +43,8 @@ i18n:
item.chinese_lantern: "灯笼"
item.fairy_flower: "仙灵花"
item.reed: "芦苇"
item.flame_cane: "烈焰甘蔗"
item.ender_pearl_flower_seeds: "末影珍珠花种子"
item.bench: "长椅"
item.table_lamp: "台灯"
item.wooden_chair: "木椅"

View File

@@ -2,7 +2,7 @@ images:
default:icons:
height: 10
ascent: 9
font: minecraft:default # Consider using other fonts if other plugins support custom font!
font: minecraft:icons # Do not use 'minecraft:default' unless other plugins don't support custom font!
file: minecraft:font/image/icons.png
chars:
- '\ub000\ub001'

View File

@@ -23,6 +23,30 @@ items:
behavior:
type: liquid_collision_block_item
block: default:reed
default:flame_cane:
material: paper
custom-model-data: 4002
data:
item-name: "<!i><i18n:item.flame_cane>"
model:
template: default:model/simplified_generated
arguments:
path: "minecraft:item/custom/flame_cane"
behavior:
type: block_item
block: default:flame_cane
default:ender_pearl_flower_seeds:
material: paper
custom-model-data: 4003
data:
item-name: "<!i><i18n:item.ender_pearl_flower_seeds>"
model:
template: default:model/simplified_generated
arguments:
path: "minecraft:item/custom/ender_pearl_flower_seeds"
behavior:
type: block_item
block: default:ender_pearl_flower
blocks:
default:fairy_flower:
settings:
@@ -32,8 +56,12 @@ blocks:
overrides:
item: default:fairy_flower
push-reaction: DESTROY
map-color: 19
behavior:
type: bush_block
bottom-block-tags:
- minecraft:dirt
- minecraft:farmland
loot:
template: "default:loot_table/basic"
arguments:
@@ -70,6 +98,7 @@ blocks:
overrides:
item: default:reed
push-reaction: DESTROY
map-color: 60
behavior:
type: on_liquid_block
liquid-type: water
@@ -82,6 +111,137 @@ blocks:
state: sugar_cane:1
model:
path: "minecraft:block/custom/reed"
default:flame_cane:
settings:
template:
- default:hardness/none
- default:sound/grass
overrides:
item: default:flame_cane
push-reaction: DESTROY
map-color: 15
is-randomly-ticking: true
behavior:
type: sugar_cane_block
max-height: 4
required-adjacent-liquids: lava
grow-speed: 0.333
bottom-blocks:
- minecraft:netherrack
- minecraft:soul_sand
- minecraft:soul_soil
- minecraft:magma_block
- minecraft:warped_nylium
- minecraft:crimson_nylium
- minecraft:basalt
loot:
template: "default:loot_table/basic"
arguments:
item: default:flame_cane
states:
properties:
age:
type: int
default: 0
range: 0~5
appearances:
default:
state: "sugar_cane:2"
models:
- path: "minecraft:block/custom/flame_cane_1"
weight: 1
generation:
parent: "minecraft:block/sugar_cane"
textures:
"cross": "minecraft:block/custom/flame_cane_1"
- path: "minecraft:block/custom/flame_cane_2"
weight: 1
generation:
parent: "minecraft:block/sugar_cane"
textures:
"cross": "minecraft:block/custom/flame_cane_2"
variants:
age=0:
appearance: default
id: 2
age=1:
appearance: default
id: 3
age=2:
appearance: default
id: 4
age=3:
appearance: default
id: 5
age=4:
appearance: default
id: 6
age=5:
appearance: default
id: 7
default:ender_pearl_flower:
settings:
template:
- default:hardness/none
- default:sound/grass
overrides:
item: default:ender_pearl_flower_seeds
push-reaction: DESTROY
map-color: 24
is-randomly-ticking: true
behavior:
type: crop_block
grow-speed: 0.25
light-requirement: 9
bottom-blocks:
- minecraft:end_stone
loot:
template: default:loot_table/seed_crop
arguments:
crop_item: minecraft:ender_pearl
crop_seed: default:ender_pearl_flower_seeds
ripe_age: 2
states:
properties:
age:
type: int
default: 0
range: 0~2
appearances:
stage_0:
state: "tripwire:1"
models:
- path: "minecraft:block/custom/ender_pearl_flower_stage_0"
generation:
parent: "minecraft:block/cross"
textures:
"cross": "minecraft:block/custom/ender_pearl_flower_stage_0"
stage_1:
state: "tripwire:0"
models:
- path: "minecraft:block/custom/ender_pearl_flower_stage_1"
generation:
parent: "minecraft:block/cross"
textures:
"cross": "minecraft:block/custom/ender_pearl_flower_stage_1"
stage_2:
state: "sugar_cane:3"
models:
- path: "minecraft:block/custom/ender_pearl_flower_stage_2"
generation:
parent: "minecraft:block/cross"
textures:
"cross": "minecraft:block/custom/ender_pearl_flower_stage_2"
variants:
age=0:
appearance: stage_0
id: 0
age=1:
appearance: stage_1
id: 1
age=2:
appearance: stage_2
id: 8
recipes:
default:paper_from_reed:
type: shaped
@@ -91,4 +251,46 @@ recipes:
A: "default:reed"
result:
id: minecraft:paper
count: 3
count: 3
default:magma_cream:
type: shaped
pattern:
- " A "
- "ABA"
- " A "
ingredients:
A: "default:flame_cane"
B: "minecraft:slime_ball"
result:
id: minecraft:magma_cream
count: 1
default:magma_block:
type: shapeless
ingredients:
A1: "minecraft:cobblestone"
A2: "minecraft:cobblestone"
B1: "default:flame_cane"
B2: "default:flame_cane"
result:
id: minecraft:magma_block
count: 2
vanilla-loots:
minecraft:ender_pearl_flower_seeds_from_endermite:
type: entity
target: "minecraft:endermite"
override: false
loot:
pools:
- rolls: 1
conditions:
- type: table_bonus
enchantment: minecraft:looting
chances:
- 0.1
- 0.5
- 0.8
- 1
entries:
- type: item
item: "default:ender_pearl_flower_seeds"

View File

@@ -953,7 +953,12 @@ templates#recipes:
# loot tables
templates#loot_tables:
# drop one item
# template: default:loot_table/basic
# arguments:
# item: the item
default:loot_table/basic:
pools:
- rolls: 1
@@ -962,7 +967,12 @@ templates#loot_tables:
entries:
- type: item
item: "{item}"
# drop with silk touch
# template: default:loot_table/silk_touch
# arguments:
# item: the item
default:loot_table/silk_touch:
pools:
- rolls: 1
@@ -972,7 +982,78 @@ templates#loot_tables:
entries:
- type: item
item: "{item}"
# drop ores
# crop drops
# template: default:loot_table/seed_crop
# arguments:
# crop_item: the ripe crop
# crop_seed: the seed of the crop
# ripe_age: the max age
default:loot_table/seed_crop:
pools:
- rolls: 1
entries:
- type: alternatives
children:
- type: item
item: "{crop_item}"
conditions:
- type: match_block_property
properties:
age: "{ripe_age}"
- type: item
item: "{crop_seed}"
- rolls: 1
conditions:
- type: match_block_property
properties:
age: "{ripe_age}"
entries:
- type: item
item: "{crop_seed}"
functions:
- type: apply_bonus
enchantment: minecraft:fortune
formula:
type: binomial_with_bonus_count
extra: 3
probability: 0.5714286
# template: default:loot_table/crop
# arguments:
# crop_item: the ripe crop
# ripe_age: the max age
default:loot_table/crop:
pools:
- rolls: 1
entries:
- type: item
item: "{crop_item}"
- rolls: 1
conditions:
- type: match_block_property
properties:
age: "{ripe_age}"
entries:
- type: item
item: "{crop_item}"
functions:
- type: apply_bonus
enchantment: minecraft:fortune
formula:
type: binomial_with_bonus_count
extra: 3
probability: 0.5714286
# ore drops
# template: default:loot_table/ore
# arguments:
# ore_block: the ore block
# ore_drop: the drops of the ore
# min_exp: the min exp to drop
# max_exp: the max exp to drop
default:loot_table/ore:
pools:
- rolls: 1
@@ -997,7 +1078,37 @@ templates#loot_tables:
type: uniform
min: "{min_exp}"
max: "{max_exp}"
# leaves
# template: default:loot_table/ore_no_exp
# arguments:
# ore_block: the ore block
# ore_drop: the drops of the ore
default:loot_table/ore_no_exp:
pools:
- rolls: 1
entries:
- type: alternatives
children:
- type: item
item: "{ore_block}"
conditions:
- type: enchantment
predicate: minecraft:silk_touch>=1
- type: item
item: "{ore_drop}"
functions:
- type: apply_bonus
enchantment: minecraft:fortune
formula:
type: ore_drops
- type: explosion_decay
# leaves drops
# template: default:loot_table/leaves
# arguments:
# leaves: the leaves block
# sapling: the sapling item
default:loot_table/leaves:
pools:
- rolls: 1

View File

@@ -0,0 +1,43 @@
{
"ambientocclusion": false,
"textures": {
"1": "item/ender_pearl",
"particle": "block/custom/ender_pearl_flower_stage_3",
"cross": "block/custom/ender_pearl_flower_stage_3"
},
"elements": [
{
"from": [0.8, 0, 8],
"to": [15.2, 16, 8],
"shade": false,
"rotation": {"angle": 45, "axis": "y", "origin": [8, 8, 8], "rescale": true},
"faces": {
"north": {"uv": [0, 0, 16, 16], "texture": "#cross"},
"south": {"uv": [0, 0, 16, 16], "texture": "#cross"}
}
},
{
"from": [8, 0, 0.8],
"to": [8, 16, 15.2],
"shade": false,
"rotation": {"angle": 45, "axis": "y", "origin": [8, 8, 8], "rescale": true},
"faces": {
"east": {"uv": [0, 0, 16, 16], "texture": "#cross"},
"west": {"uv": [0, 0, 16, 16], "texture": "#cross"}
}
},
{
"from": [8, 5, -1],
"to": [8, 21, 15],
"rotation": {"angle": 0, "axis": "y", "origin": [7, 5, 7]},
"faces": {
"north": {"uv": [0, 0, 0, 16], "texture": "#1"},
"east": {"uv": [0, 0, 16, 16], "texture": "#1"},
"south": {"uv": [0, 0, 0, 16], "texture": "#1"},
"west": {"uv": [0, 0, 16, 16], "texture": "#1"},
"up": {"uv": [0, 0, 0, 16], "texture": "#1"},
"down": {"uv": [0, 0, 0, 16], "texture": "#1"}
}
}
]
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.api;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
@@ -108,9 +109,9 @@ public final class CraftEngineBlocks {
boolean success;
try {
Object worldServer = Reflections.field$CraftWorld$ServerLevel.get(location.getWorld());
Object blockPos = Reflections.constructor$BlockPos.newInstance(location.getBlockX(), location.getBlockY(), location.getBlockZ());
Object blockPos = FastNMS.INSTANCE.constructor$BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());
Object blockState = block.customBlockState().handle();
Object oldBlockState = Reflections.method$BlockGetter$getBlockState.invoke(worldServer, blockPos);
Object oldBlockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(worldServer, blockPos);
success = (boolean) Reflections.method$LevelWriter$setBlock.invoke(worldServer, blockPos, blockState, option.flags());
if (success) {
Reflections.method$BlockStateBase$onPlace.invoke(blockState, worldServer, blockPos, oldBlockState, true);

View File

@@ -318,7 +318,7 @@ public class BlockEventListener implements Listener {
if (direction == BlockFace.UP || direction == BlockFace.DOWN) {
Object serverLevel = Reflections.field$CraftWorld$ServerLevel.get(world);
Object chunkSource = Reflections.field$ServerLevel$chunkSource.get(serverLevel);
Object blockPos = Reflections.constructor$BlockPos.newInstance(location.getBlockX(), location.getBlockY(), location.getBlockZ());
Object blockPos = LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());
Reflections.method$ServerChunkCache$blockChanged.invoke(chunkSource, blockPos);
if (direction == BlockFace.UP) {
NoteBlockChainUpdateUtils.noteBlockChainUpdate(serverLevel, chunkSource, Reflections.instance$Direction$UP, blockPos, 0);

View File

@@ -7,8 +7,7 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import dev.dejvokep.boostedyaml.YamlDocument;
import net.momirealms.craftengine.bukkit.block.worldedit.WorldEditBlockRegister;
import net.momirealms.craftengine.bukkit.block.worldedit.WorldEditCommandHelper;
import net.momirealms.craftengine.bukkit.compatibility.worldedit.WorldEditBlockRegister;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers;

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.block.worldedit;
package net.momirealms.craftengine.bukkit.block;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.block.BlockStateParser;

View File

@@ -6,6 +6,7 @@ import net.momirealms.craftengine.shared.block.EmptyBlockBehavior;
public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key BUSH_BLOCK = Key.from("craftengine:bush_block");
public static final Key HANGING_BLOCK = Key.from("craftengine:hanging_block");
public static final Key FALLING_BLOCK = Key.from("craftengine:falling_block");
public static final Key LEAVES_BLOCK = Key.from("craftengine:leaves_block");
public static final Key STRIPPABLE_BLOCK = Key.from("craftengine:strippable_block");
@@ -13,16 +14,21 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key ON_LIQUID_BLOCK = Key.from("craftengine:on_liquid_block");
public static final Key WATERLOGGED_BLOCK = Key.from("craftengine:waterlogged_block");
public static final Key CONCRETE_POWDER_BLOCK = Key.from("craftengine:concrete_powder_block");
public static final Key SUGARCANE_BLOCK = Key.from("craftengine:sugar_cane_block");
public static final Key CROP_BLOCK = Key.from("craftengine:crop_block");
public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
register(FALLING_BLOCK, FallingBlockBehavior.FACTORY);
register(BUSH_BLOCK, BushBlockBehavior.FACTORY);
register(HANGING_BLOCK, HangingBlockBehavior.FACTORY);
register(LEAVES_BLOCK, LeavesBlockBehavior.FACTORY);
register(STRIPPABLE_BLOCK, StrippableBlockBehavior.FACTORY);
register(SAPLING_BLOCK, SaplingBlockBehavior.FACTORY);
register(ON_LIQUID_BLOCK, OnLiquidBlockBehavior.FACTORY);
register(WATERLOGGED_BLOCK, WaterLoggedBlockBehavior.FACTORY);
register(CONCRETE_POWDER_BLOCK, ConcretePowderBlockBehavior.FACTORY);
register(SUGARCANE_BLOCK, SugarCaneBlockBehavior.FACTORY);
register(CROP_BLOCK, CropBlockBehavior.FACTORY);
}
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.BlockTags;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
@@ -14,26 +15,32 @@ import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import org.bukkit.World;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.Callable;
public class BushBlockBehavior extends AbstractBlockBehavior {
public static final Factory FACTORY = new Factory();
protected static final Object DIRT_TAG = BlockTags.getOrCreate(Key.of("minecraft", "dirt"));
protected static final Object FARMLAND = BlockTags.getOrCreate(Key.of("minecraft", "farmland"));
public static final BushBlockBehavior INSTANCE = new BushBlockBehavior(List.of(DIRT_TAG, FARMLAND));
protected final List<Object> tagsCanSurviveOn;
protected final Set<Object> blocksCansSurviveOn;
protected final Set<String> customBlocksCansSurviveOn;
protected final boolean any;
public BushBlockBehavior(List<Object> tagsCanSurviveOn) {
public BushBlockBehavior(List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn) {
this.tagsCanSurviveOn = tagsCanSurviveOn;
this.blocksCansSurviveOn = blocksCansSurviveOn;
this.customBlocksCansSurviveOn = customBlocksCansSurviveOn;
this.any = this.tagsCanSurviveOn.isEmpty() && this.blocksCansSurviveOn.isEmpty() && this.customBlocksCansSurviveOn.isEmpty();
}
@Override
@@ -61,7 +68,7 @@ public class BushBlockBehavior extends AbstractBlockBehavior {
ContextHolder.Builder builder = ContextHolder.builder();
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
Vec3d vec3d = Vec3d.atCenterOf(pos);
net.momirealms.craftengine.core.world.World world = new BukkitWorld((World) Reflections.method$Level$getCraftWorld.invoke(level));
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
builder.withParameter(LootParameters.LOCATION, vec3d);
builder.withParameter(LootParameters.WORLD, world);
for (Item<Object> item : previousState.getDrops(builder, world)) {
@@ -82,33 +89,72 @@ public class BushBlockBehavior extends AbstractBlockBehavior {
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
if (arguments.containsKey("bottom-block-tags")) {
return new BushBlockBehavior(MiscUtils.getAsStringList(arguments.get("bottom-block-tags")).stream().map(it -> BlockTags.getOrCreate(Key.of(it))).toList());
} else if (arguments.containsKey("tags")) {
return new BushBlockBehavior(MiscUtils.getAsStringList(arguments.get("tags")).stream().map(it -> BlockTags.getOrCreate(Key.of(it))).toList());
} else {
return INSTANCE;
}
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
return new BushBlockBehavior(tuple.left(), tuple.mid(), tuple.right());
}
}
private boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException {
int y = Reflections.field$Vec3i$y.getInt(blockPos);
int x = Reflections.field$Vec3i$x.getInt(blockPos);
int z = Reflections.field$Vec3i$z.getInt(blockPos);
Object belowPos = Reflections.constructor$BlockPos.newInstance(x, y - 1, z);
Object belowState = Reflections.method$BlockGetter$getBlockState.invoke(world, belowPos);
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("bottom-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("bottom-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.getAllVanillaBlockStates(blockType));
} else {
mcBlocks.add(BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(blockStateStr)));
}
} else {
// custom maybe
customBlocks.add(blockStateStr);
}
}
return new Tuple<>(mcTags, mcBlocks, customBlocks);
}
protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException {
int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos);
int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos);
int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos);
Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x, y - 1, z);
Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos);
return mayPlaceOn(belowState, world, belowPos);
}
protected boolean mayPlaceOn(Object belowState, Object world, Object belowPos) throws ReflectiveOperationException {
if (this.any) return true;
for (Object tag : this.tagsCanSurviveOn) {
if ((boolean) Reflections.method$BlockStateBase$hasTag.invoke(belowState, tag)) {
return true;
}
}
int id = BlockStateUtils.blockStateToId(belowState);
if (BlockStateUtils.isVanillaBlock(id)) {
if (!this.blocksCansSurviveOn.isEmpty() && this.blocksCansSurviveOn.contains(belowState)) {
return true;
}
} else {
ImmutableBlockState previousState = BukkitBlockManager.instance().getImmutableBlockState(id);
if (previousState != null && !previousState.isEmpty()) {
if (this.customBlocksCansSurviveOn.contains(previousState.owner().value().id().toString())) {
return true;
}
if (this.customBlocksCansSurviveOn.contains(previousState.toString())) {
return true;
}
}
}
return false;
}
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
@@ -65,7 +66,7 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior {
Object level = context.getLevel().serverWorld();
Object blockPos = LocationUtils.toBlockPos(context.getClickedPos());
try {
Object previousState = Reflections.method$BlockGetter$getBlockState.invoke(level, blockPos);
Object previousState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, blockPos);
if (!shouldSolidify(level, blockPos, previousState)) {
return super.updateStateForPlacement(context, state);
} else {
@@ -137,10 +138,10 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior {
int j = Direction.values().length;
for (int k = 0; k < j; k++) {
Object direction = Reflections.instance$Directions[k];
Object blockState = Reflections.method$BlockGetter$getBlockState.invoke(level, mutablePos);
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, mutablePos);
if (direction != Reflections.instance$Direction$DOWN || canSolidify(blockState)) {
Reflections.method$MutableBlockPos$setWithOffset.invoke(mutablePos, pos, direction);
blockState = Reflections.method$BlockGetter$getBlockState.invoke(level, mutablePos);
blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, mutablePos);
if (canSolidify(blockState) && !(boolean) Reflections.method$BlockStateBase$isFaceSturdy.invoke(blockState, level, pos, Reflections.getOppositeDirection(direction), Reflections.instance$SupportType$FULL)) {
flag = true;
break;

View File

@@ -0,0 +1,146 @@
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.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.ParticleUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
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.MiscUtils;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import org.bukkit.World;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
public class CropBlockBehavior extends BushBlockBehavior {
public static final Factory FACTORY = new Factory();
private final IntegerProperty ageProperty;
private final float growSpeed;
private final int minGrowLight;
public CropBlockBehavior(List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn,
Property<Integer> ageProperty, float growSpeed, int minGrowLight) {
super(tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
this.ageProperty = (IntegerProperty) ageProperty;
this.growSpeed = growSpeed;
this.minGrowLight = minGrowLight;
}
public final int getAge(ImmutableBlockState state) {
return state.get(ageProperty);
}
public boolean isMaxAge(ImmutableBlockState state) {
return state.get(ageProperty) == ageProperty.max;
}
private static int getRawBrightness(Object level, Object pos) throws InvocationTargetException, IllegalAccessException {
return (int) Reflections.method$BlockAndTintGetter$getRawBrightness.invoke(level, pos, 0);
}
private boolean hasSufficientLight(Object level, Object pos) throws InvocationTargetException, IllegalAccessException {
return getRawBrightness(level, pos) >= this.minGrowLight - 1;
}
@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 (getRawBrightness(level, pos) >= this.minGrowLight) {
ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(state));
if (currentState != null && !currentState.isEmpty()) {
int age = this.getAge(currentState);
if (age < this.ageProperty.max && RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) {
Reflections.method$Level$setBlock.invoke(level, pos, currentState.with(this.ageProperty, age + 1).customBlockState().handle(), UpdateOption.UPDATE_ALL.flags());
}
}
}
}
@Override
protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException {
return hasSufficientLight(world, blockPos) && super.canSurvive(thisBlock, state, world, blockPos);
}
@Override
public boolean isBoneMealSuccess(Object thisBlock, Object[] args) {
return true;
}
@Override
public boolean isValidBoneMealTarget(Object thisBlock, Object[] args) {
Object state = args[2];
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(state));
if (immutableBlockState != null && !immutableBlockState.isEmpty()) {
return getAge(immutableBlockState) != this.ageProperty.max;
} else {
return false;
}
}
@Override
public void performBoneMeal(Object thisBlock, Object[] args) throws Exception {
this.performBoneMeal(args[0], args[2], args[3]);
}
private void performBoneMeal(Object level, Object pos, Object state) throws InvocationTargetException, IllegalAccessException {
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(state));
if (immutableBlockState == null || immutableBlockState.isEmpty()) {
return;
}
boolean sendParticles = false;
Object visualState = immutableBlockState.vanillaBlockState().handle();
Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState);
if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = (boolean) Reflections.method$BonemealableBlock$isValidBonemealTarget.invoke(visualStateBlock, level, pos, visualState);
if (!is) {
sendParticles = true;
}
} else {
sendParticles = true;
}
int i = this.getAge(immutableBlockState) + RandomUtils.generateRandomInt(2, 5);
int maxAge = this.ageProperty.max;
if (i > maxAge) {
i = maxAge;
}
Reflections.method$Level$setBlock.invoke(level, pos, immutableBlockState.with(this.ageProperty, i).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags());
if (sendParticles) {
World world = FastNMS.INSTANCE.method$Level$getCraftWorld(level);
int x = FastNMS.INSTANCE.field$Vec3i$x(pos);
int y = FastNMS.INSTANCE.field$Vec3i$y(pos);
int z = FastNMS.INSTANCE.field$Vec3i$z(pos);
world.spawnParticle(ParticleUtils.getParticle("HAPPY_VILLAGER"), x + 0.5, y + 0.5, z + 0.5, 12, 0.25, 0.25, 0.25);
}
}
public static class Factory implements BlockBehaviorFactory {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
Property<Integer> ageProperty = (Property<Integer>) block.getProperty("age");
if (ageProperty == null) {
throw new IllegalArgumentException("age property not set for crop");
}
// 存活条件是最小生长亮度-1
int minGrowLight = MiscUtils.getAsInt(arguments.getOrDefault("light-requirement", 9));
float growSpeed = MiscUtils.getAsFloat(arguments.getOrDefault("grow-speed", 0.25f));
return new CropBlockBehavior(tuple.left(), tuple.mid(), tuple.right(), ageProperty, growSpeed, minGrowLight);
}
}
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
@@ -16,7 +17,6 @@ import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import org.bukkit.World;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -56,17 +56,17 @@ public class FallingBlockBehavior extends AbstractBlockBehavior {
@Override
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object blockPos = args[2];
int y = Reflections.field$Vec3i$y.getInt(blockPos);
int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos);
Object world = args[1];
Object dimension = Reflections.method$$LevelReader$dimensionType.invoke(world);
int minY = Reflections.field$DimensionType$minY.getInt(dimension);
if (y < minY) {
return;
}
int x = Reflections.field$Vec3i$x.getInt(blockPos);
int z = Reflections.field$Vec3i$z.getInt(blockPos);
Object belowPos = Reflections.constructor$BlockPos.newInstance(x, y - 1, z);
Object belowState = Reflections.method$BlockGetter$getBlockState.invoke(world, belowPos);
int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos);
int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos);
Object belowPos = LocationUtils.toBlockPos(x, y - 1, z);
Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos);
boolean isFree = (boolean) Reflections.method$FallingBlock$isFree.invoke(null, belowState);
if (!isFree) {
return;
@@ -96,7 +96,7 @@ public class FallingBlockBehavior extends AbstractBlockBehavior {
double y = Reflections.field$Entity$yo.getDouble(fallingBlockEntity);
double z = Reflections.field$Entity$zo.getDouble(fallingBlockEntity);
Vec3d vec3d = new Vec3d(x, y, z);
net.momirealms.craftengine.core.world.World world = new BukkitWorld((World) Reflections.method$Level$getCraftWorld.invoke(level));
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
builder.withParameter(LootParameters.LOCATION, vec3d);
builder.withParameter(LootParameters.WORLD, world);
for (Item<Object> item : immutableBlockState.getDrops(builder, world)) {
@@ -121,7 +121,7 @@ public class FallingBlockBehavior extends AbstractBlockBehavior {
if (immutableBlockState == null || immutableBlockState.isEmpty()) return;
Object level = args[0];
Object pos = args[1];
net.momirealms.craftengine.core.world.World world = new BukkitWorld((World) Reflections.method$Level$getCraftWorld.invoke(level));
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
world.playBlockSound(Vec3d.atCenterOf(LocationUtils.fromBlockPos(pos)), immutableBlockState.sounds().landSound());
}
}

View File

@@ -0,0 +1,38 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class HangingBlockBehavior extends BushBlockBehavior {
public static final Factory FACTORY = new Factory();
public HangingBlockBehavior(List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn) {
super(tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
}
@Override
protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException {
int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos);
int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos);
int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos);
Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x, y + 1, z);
Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos);
return mayPlaceOn(belowState, world, belowPos);
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
return new HangingBlockBehavior(tuple.left(), tuple.mid(), tuple.right());
}
}
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.BlockTags;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
@@ -108,7 +109,7 @@ public class LeavesBlockBehavior extends WaterLoggedBlockBehavior {
Object blockPos = args[2];
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(args[0]));
if (immutableBlockState != null && immutableBlockState.behavior() instanceof LeavesBlockBehavior behavior && behavior.isDecaying(immutableBlockState)) {
World bukkitWorld = (World) Reflections.method$Level$getCraftWorld.invoke(level);
World bukkitWorld = FastNMS.INSTANCE.method$Level$getCraftWorld(level);
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
// call bukkit event
LeavesDecayEvent event = new LeavesDecayEvent(bukkitWorld.getBlockAt(pos.x(), pos.y(), pos.z()));
@@ -142,7 +143,7 @@ public class LeavesBlockBehavior extends WaterLoggedBlockBehavior {
for (int k = 0; k < j; ++k) {
Object direction = Reflections.instance$Directions[k];
Reflections.method$MutableBlockPos$setWithOffset.invoke(mutablePos, blockPos, direction);
Object blockState = Reflections.method$BlockGetter$getBlockState.invoke(world, mutablePos);
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, mutablePos);
i = Math.min(i, getDistanceAt(blockState) + 1);
if (i == 1) {
break;

View File

@@ -9,14 +9,15 @@ import net.momirealms.craftengine.shared.block.BlockBehavior;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class OnLiquidBlockBehavior extends BushBlockBehavior {
public static final Factory FACTORY = new Factory();
private final boolean onWater;
private final boolean onLava;
public OnLiquidBlockBehavior(List<Object> tagsCanSurviveOn, boolean onWater, boolean onLava) {
super(tagsCanSurviveOn);
public OnLiquidBlockBehavior(boolean onWater, boolean onLava) {
super(List.of(), Set.of(), Set.of());
this.onWater = onWater;
this.onLava = onLava;
}
@@ -33,7 +34,7 @@ public class OnLiquidBlockBehavior extends BushBlockBehavior {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
List<String> liquidTypes = MiscUtils.getAsStringList(arguments.getOrDefault("liquid-type", List.of("water")));
return new OnLiquidBlockBehavior(List.of(), liquidTypes.contains("water"), liquidTypes.contains("lava"));
return new OnLiquidBlockBehavior(liquidTypes.contains("water"), liquidTypes.contains("lava"));
}
}

View File

@@ -2,6 +2,7 @@ package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
@@ -12,6 +13,7 @@ import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import org.bukkit.Location;
import org.bukkit.World;
@@ -19,6 +21,7 @@ import org.bukkit.World;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
public class SaplingBlockBehavior extends BushBlockBehavior {
@@ -26,12 +29,14 @@ public class SaplingBlockBehavior extends BushBlockBehavior {
private final Key feature;
private final Property<Integer> stageProperty;
private final double boneMealSuccessChance;
private final float growSpeed;
public SaplingBlockBehavior(Key feature, Property<Integer> stageProperty, List<Object> tagsCanSurviveOn, double boneMealSuccessChance) {
super(tagsCanSurviveOn);
public SaplingBlockBehavior(Key feature, Property<Integer> stageProperty, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn, double boneMealSuccessChance, float growSpeed) {
super(tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
this.feature = feature;
this.stageProperty = stageProperty;
this.boneMealSuccessChance = boneMealSuccessChance;
this.growSpeed = growSpeed;
}
public Key treeFeature() {
@@ -43,11 +48,8 @@ public class SaplingBlockBehavior extends BushBlockBehavior {
Object world = args[1];
Object blockPos = args[2];
Object blockState = args[0];
int x = (int) Reflections.field$Vec3i$x.get(blockPos);
int y = (int) Reflections.field$Vec3i$y.get(blockPos);
int z = (int) Reflections.field$Vec3i$z.get(blockPos);
Object aboveBlockPos = LocationUtils.toBlockPos(x, y + 1, z);
if ((int) Reflections.method$LevelReader$getMaxLocalRawBrightness.invoke(world, aboveBlockPos) >= 9 && (float) Reflections.method$RandomSource$nextFloat.invoke(args[3]) < (1.0f / 7.0f)) {
Object aboveBlockPos = LocationUtils.above(blockPos);
if ((int) Reflections.method$LevelReader$getMaxLocalRawBrightness.invoke(world, aboveBlockPos) >= 9 && (float) RandomUtils.generateRandomFloat(0, 1) < growSpeed) {
increaseStage(world, blockPos, blockState, args[3]);
}
}
@@ -58,10 +60,10 @@ public class SaplingBlockBehavior extends BushBlockBehavior {
int currentStage = immutableBlockState.get(this.stageProperty);
if (currentStage != this.stageProperty.possibleValues().get(this.stageProperty.possibleValues().size() - 1)) {
ImmutableBlockState nextStage = immutableBlockState.cycle(this.stageProperty);
World bukkitWorld = (World) Reflections.method$Level$getCraftWorld.invoke(world);
int x = (int) Reflections.field$Vec3i$x.get(blockPos);
int y = (int) Reflections.field$Vec3i$y.get(blockPos);
int z = (int) Reflections.field$Vec3i$z.get(blockPos);
World bukkitWorld = FastNMS.INSTANCE.method$Level$getCraftWorld(world);
int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos);
int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos);
int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos);
CraftEngineBlocks.place(new Location(bukkitWorld, x, y, z), nextStage, UpdateOption.UPDATE_NONE, false);
} else {
generateTree(world, blockPos, blockState, randomSource);
@@ -83,7 +85,7 @@ public class SaplingBlockBehavior extends BushBlockBehavior {
Object legacyState = Reflections.method$FluidState$createLegacyBlock.invoke(fluidState);
Reflections.method$Level$setBlock.invoke(world, blockPos, legacyState, UpdateOption.UPDATE_NONE.flags());
if ((boolean) Reflections.method$ConfiguredFeature$place.invoke(configuredFeature, world, chunkGenerator, randomSource, blockPos)) {
if (Reflections.method$BlockGetter$getBlockState.invoke(world, blockPos) == legacyState) {
if (FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, blockPos) == legacyState) {
Reflections.method$ServerLevel$sendBlockUpdated.invoke(world, blockPos, blockState, legacyState, 2);
}
} else {
@@ -93,8 +95,34 @@ public class SaplingBlockBehavior extends BushBlockBehavior {
}
@Override
public boolean isBoneMealSuccess(Object thisBlock, Object[] args) {
return RandomUtils.generateRandomDouble(0d, 1d) < this.boneMealSuccessChance;
public boolean isBoneMealSuccess(Object thisBlock, Object[] args) throws Exception {
boolean success = RandomUtils.generateRandomDouble(0d, 1d) < this.boneMealSuccessChance;
Object level = args[0];
Object blockPos = args[2];
Object blockState = args[3];
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
if (immutableBlockState == null || immutableBlockState.isEmpty()) {
return false;
}
boolean sendParticles = false;
Object visualState = immutableBlockState.vanillaBlockState().handle();
Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState);
if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = (boolean) Reflections.method$BonemealableBlock$isValidBonemealTarget.invoke(visualStateBlock, level, blockPos, visualState);
if (!is) {
sendParticles = true;
}
} else {
sendParticles = true;
}
if (sendParticles) {
World world = FastNMS.INSTANCE.method$Level$getCraftWorld(level);
int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos);
int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos);
int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos);
world.spawnParticle(ParticleUtils.getParticle("HAPPY_VILLAGER"), x + 0.5, y + 0.5, z + 0.5, 12, 0.25, 0.25, 0.25);
}
return success;
}
@Override
@@ -121,13 +149,9 @@ public class SaplingBlockBehavior extends BushBlockBehavior {
throw new IllegalArgumentException("stage property not set for sapling");
}
double boneMealSuccessChance = MiscUtils.getAsDouble(arguments.getOrDefault("bone-meal-success-chance", 0.45));
if (arguments.containsKey("bottom-block-tags")) {
return new SaplingBlockBehavior(Key.of(feature), stageProperty, MiscUtils.getAsStringList(arguments.get("bottom-block-tags")).stream().map(it -> BlockTags.getOrCreate(Key.of(it))).toList(), boneMealSuccessChance);
} else if (arguments.containsKey("tags")) {
return new SaplingBlockBehavior(Key.of(feature), stageProperty, MiscUtils.getAsStringList(arguments.get("tags")).stream().map(it -> BlockTags.getOrCreate(Key.of(it))).toList(), boneMealSuccessChance);
} else {
return new SaplingBlockBehavior(Key.of(feature), stageProperty, List.of(DIRT_TAG, FARMLAND), boneMealSuccessChance);
}
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
return new SaplingBlockBehavior(Key.of(feature), stageProperty, tuple.left(), tuple.mid(), tuple.right(), boneMealSuccessChance,
MiscUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1.0 / 7.0)));
}
}
}

View File

@@ -0,0 +1,215 @@
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.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
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.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.item.Item;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
public class SugarCaneBlockBehavior extends BushBlockBehavior {
public static final Factory FACTORY = new Factory();
private static final List<Object> WATER = List.of(Reflections.instance$Fluids$WATER, Reflections.instance$Fluids$FLOWING_WATER);
private static final List<Object> LAVA = List.of(Reflections.instance$Fluids$LAVA, Reflections.instance$Fluids$FLOWING_LAVA);
private static final List<Object> HORIZON_DIRECTIONS = List.of(Reflections.instance$Direction$NORTH, Reflections.instance$Direction$EAST, Reflections.instance$Direction$SOUTH, Reflections.instance$Direction$WEST);
private final int maxHeight;
private final boolean nearWater;
private final boolean nearLava;
private final IntegerProperty ageProperty;
private final float growSpeed;
private final CustomBlock customBlock;
public SugarCaneBlockBehavior(CustomBlock customBlock, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn, Property<Integer> ageProperty,
int maxHeight, boolean nearWater, boolean nearLava, float growSpeed) {
super(tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
this.nearWater = nearWater;
this.nearLava = nearLava;
this.maxHeight = maxHeight;
this.ageProperty = (IntegerProperty) ageProperty;
this.growSpeed = growSpeed;
this.customBlock = customBlock;
}
@Override
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object blockState = args[0];
Object level = args[1];
Object blockPos = args[2];
if (!canSurvive(thisBlock, blockState, level, blockPos)) {
ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
if (currentState != null && !currentState.isEmpty()) {
// break the sugar cane
Reflections.method$Level$removeBlock.invoke(level, blockPos, false);
Vec3d vec3d = Vec3d.atCenterOf(LocationUtils.fromBlockPos(blockPos));
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
// TODO client side particles?
ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(LootParameters.LOCATION, vec3d)
.withParameter(LootParameters.WORLD, world);
for (Item<Object> item : currentState.getDrops(builder, world)) {
world.dropItemNaturally(vec3d, item);
}
}
}
}
@Override
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object world;
Object blockPos;
if (VersionHelper.isVersionNewerThan1_21_2()) {
world = args[1];
blockPos = args[3];
} else {
world = args[3];
blockPos = args[4];
}
Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 1);
// return state, do not call super.
return superMethod.call();
}
@Override
public void randomTick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object blockState = args[0];
Object level = args[1];
Object blockPos = args[2];
// above block is empty
if (FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.above(blockPos)) == Reflections.instance$Blocks$AIR$defaultState) {
int currentHeight = 1;
BlockPos currentPos = LocationUtils.fromBlockPos(blockPos);
ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
if (currentState != null && !currentState.isEmpty()) {
while (true) {
Object belowPos = LocationUtils.toBlockPos(currentPos.x(), currentPos.y() - currentHeight, currentPos.z());
Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, belowPos);
ImmutableBlockState belowImmutableState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(belowState));
if (belowImmutableState != null && !belowImmutableState.isEmpty() && belowImmutableState.owner() == currentState.owner()) {
currentHeight++;
} else {
break;
}
}
} else {
return;
}
if (currentHeight < this.maxHeight) {
int age = currentState.get(ageProperty);
if (age >= this.ageProperty.max || RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) {
Object abovePos = LocationUtils.above(blockPos);
if (VersionHelper.isVersionNewerThan1_21_5()) {
Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, abovePos, customBlock.defaultState().customBlockState().handle(), UpdateOption.UPDATE_ALL.flags());
} else {
Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, abovePos, customBlock.defaultState().customBlockState().handle());
}
Reflections.method$Level$setBlock.invoke(level, blockPos, currentState.with(this.ageProperty, this.ageProperty.min).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags());
} else if (RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) {
Reflections.method$Level$setBlock.invoke(level, blockPos, currentState.with(this.ageProperty, age + 1).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags());
}
}
}
}
@Override
protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException {
int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos);
int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos);
int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos);
Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x, y - 1, z);
Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos);
int id = BlockStateUtils.blockStateToId(belowState);
// 如果下方是同种方块
if (!BlockStateUtils.isVanillaBlock(id)) {
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(id);
if (immutableBlockState.owner().value() == this.customBlock) {
return true;
}
}
if (!super.mayPlaceOn(belowState, world, belowPos)) {
return false;
}
// 如果不需要依靠流体
if (!this.nearWater && !this.nearLava) {
return true;
}
// 需要流体
if (this.nearWater) {
if (hasNearbyLiquid(world, belowPos, true)) {
return true;
}
}
if (this.nearLava) {
if (hasNearbyLiquid(world, belowPos, false)) {
return true;
}
}
return false;
}
private boolean hasNearbyLiquid(Object world, Object blockPos, boolean waterOrLava) throws ReflectiveOperationException {
for (Object direction : HORIZON_DIRECTIONS) {
Object relativePos = Reflections.method$BlockPos$relative.invoke(blockPos, direction);
if (waterOrLava) {
// water
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, relativePos);
if (Reflections.method$BlockStateBase$getBlock.invoke(blockState) == Reflections.instance$Blocks$ICE) {
return true;
}
Object fluidState = Reflections.method$Level$getFluidState.invoke(world, relativePos);
Object fluidType = Reflections.method$FluidState$getType.invoke(fluidState);
if (WATER.contains(fluidType)) {
return true;
}
} else {
// lava
Object fluidState = Reflections.method$Level$getFluidState.invoke(world, relativePos);
Object fluidType = Reflections.method$FluidState$getType.invoke(fluidState);
if (LAVA.contains(fluidType)) {
return true;
}
}
}
return false;
}
public static class Factory implements BlockBehaviorFactory {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
Property<Integer> ageProperty = (Property<Integer>) block.getProperty("age");
if (ageProperty == null) {
throw new IllegalArgumentException("age property not set for sugar cane");
}
int maxHeight = MiscUtils.getAsInt(arguments.getOrDefault("max-height", 3));
List<String> nearbyLiquids = MiscUtils.getAsStringList(arguments.getOrDefault("required-adjacent-liquids", List.of()));
boolean nearWater = nearbyLiquids.contains("water");
boolean nearLava = nearbyLiquids.contains("lava");
return new SugarCaneBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right(), ageProperty, maxHeight, nearWater, nearLava,
MiscUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1)));
}
}
}

View File

@@ -2,6 +2,7 @@ package net.momirealms.craftengine.bukkit.item;
import net.momirealms.craftengine.bukkit.compatibility.item.NeigeItemsProvider;
import net.momirealms.craftengine.bukkit.item.behavior.AxeItemBehavior;
import net.momirealms.craftengine.bukkit.item.behavior.BoneMealItemBehavior;
import net.momirealms.craftengine.bukkit.item.behavior.BucketItemBehavior;
import net.momirealms.craftengine.bukkit.item.behavior.WaterBucketItemBehavior;
import net.momirealms.craftengine.bukkit.item.factory.BukkitItemFactory;
@@ -50,6 +51,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
registerVanillaItemExtraBehavior(AxeItemBehavior.INSTANCE, ItemKeys.AXES);
registerVanillaItemExtraBehavior(WaterBucketItemBehavior.INSTANCE, ItemKeys.WATER_BUCKETS);
registerVanillaItemExtraBehavior(BucketItemBehavior.INSTANCE, ItemKeys.BUCKET);
registerVanillaItemExtraBehavior(BoneMealItemBehavior.INSTANCE, ItemKeys.BONE_MEAL);
}
private static BukkitItemManager instance;

View File

@@ -40,7 +40,6 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
@@ -147,12 +146,7 @@ public class BlockItemBehavior extends ItemBehavior {
protected boolean canPlace(BlockPlaceContext context, ImmutableBlockState state) {
try {
Object player;
try {
player = Reflections.method$CraftPlayer$getHandle.invoke(context.getPlayer().platformPlayer());
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Failed to get server player", e);
}
Object player = context.getPlayer().serverPlayer();
Object blockState = state.customBlockState().handle();
Object blockPos = LocationUtils.toBlockPos(context.getClickedPos());
Object voxelShape = Reflections.method$CollisionContext$of.invoke(null, player);

View File

@@ -0,0 +1,73 @@
package net.momirealms.craftengine.bukkit.item.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.behavior.CropBlockBehavior;
import net.momirealms.craftengine.bukkit.block.behavior.SaplingBlockBehavior;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.world.BukkitWorldBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
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.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.block.Block;
import java.nio.file.Path;
import java.util.Map;
public class BoneMealItemBehavior extends ItemBehavior {
public static final Factory FACTORY = new Factory();
public static final BoneMealItemBehavior INSTANCE = new BoneMealItemBehavior();
@Override
public InteractionResult useOnBlock(UseOnContext context) {
BukkitWorldBlock clicked = (BukkitWorldBlock) context.getLevel().getBlockAt(context.getClickedPos());
Block block = clicked.block();
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData()));
if (state == null || state.isEmpty()) return InteractionResult.PASS;
boolean shouldHandle =false;
if (state.behavior() instanceof CropBlockBehavior blockBehavior) {
if (!blockBehavior.isMaxAge(state)) {
shouldHandle = true;
}
} else if (state.behavior() instanceof SaplingBlockBehavior) {
shouldHandle = true;
}
if (!shouldHandle) return InteractionResult.PASS;
boolean sendSwing = false;
try {
Object visualState = state.vanillaBlockState().handle();
Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState);
if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = (boolean) Reflections.method$BonemealableBlock$isValidBonemealTarget.invoke(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState);
if (!is) {
sendSwing = true;
}
} else {
sendSwing = true;
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to check visual state bone meal state", e);
}
if (sendSwing) {
context.getPlayer().swingHand(context.getHand());
}
return InteractionResult.SUCCESS;
}
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments) {
return INSTANCE;
}
}
}

View File

@@ -11,6 +11,7 @@ public class BukkitItemBehaviors extends ItemBehaviors {
public static final Key AXE_ITEM = Key.from("craftengine:axe_item");
public static final Key WATER_BUCKET_ITEM = Key.from("craftengine:water_bucket_item");
public static final Key BUCKET_ITEM = Key.from("craftengine:bucket_item");
public static final Key BONE_MEAL_ITEM = Key.from("craftengine:bone_meal_item");
public static void init() {
register(EMPTY, EmptyItemBehavior.FACTORY);
@@ -20,5 +21,6 @@ public class BukkitItemBehaviors extends ItemBehaviors {
register(AXE_ITEM, AxeItemBehavior.FACTORY);
register(WATER_BUCKET_ITEM, WaterBucketItemBehavior.FACTORY);
register(BUCKET_ITEM, BucketItemBehavior.FACTORY);
register(BONE_MEAL_ITEM, BoneMealItemBehavior.FACTORY);
}
}

View File

@@ -1,6 +1,7 @@
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.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
@@ -41,7 +42,7 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior {
try {
Object blockHitResult = Reflections.method$Item$getPlayerPOVHitResult.invoke(null, world.serverWorld(), player.serverPlayer(), Reflections.instance$ClipContext$Fluid$SOURCE_ONLY);
Object blockPos = Reflections.field$BlockHitResul$blockPos.get(blockHitResult);
BlockPos above = new BlockPos(Reflections.field$Vec3i$x.getInt(blockPos), Reflections.field$Vec3i$y.getInt(blockPos) + offsetY, Reflections.field$Vec3i$z.getInt(blockPos));
BlockPos above = new BlockPos(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos) + offsetY, FastNMS.INSTANCE.field$Vec3i$z(blockPos));
Direction direction = Direction.values()[(int) Reflections.method$Direction$ordinal.invoke(Reflections.field$BlockHitResul$direction.get(blockHitResult))];
boolean miss = Reflections.field$BlockHitResul$miss.getBoolean(blockHitResult);
Vec3d hitPos = LocationUtils.fromVec(Reflections.field$HitResult$location.get(blockHitResult));

View File

@@ -39,6 +39,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.Reader;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.*;
@@ -988,8 +989,22 @@ public class BukkitRecipeManager implements RecipeManager<ItemStack> {
}
}
// 1.21.5+
private static Object toTransmuteResult(ItemStack item) throws InvocationTargetException, IllegalAccessException, InstantiationException {
Object itemStack = Reflections.method$CraftItemStack$asNMSCopy.invoke(null, item);
Object nmsItem = Reflections.method$ItemStack$getItem.invoke(itemStack);
return Reflections.constructor$TransmuteResult.newInstance(nmsItem);
}
private static Object createMinecraftSmithingTransformRecipe(CustomSmithingTransformRecipe<ItemStack> ceRecipe) throws ReflectiveOperationException {
if (VersionHelper.isVersionNewerThan1_21_2()) {
if (VersionHelper.isVersionNewerThan1_21_5()) {
return Reflections.constructor$SmithingTransformRecipe.newInstance(
toOptionalMinecraftIngredient(ceRecipe.template()),
toMinecraftIngredient(ceRecipe.base()),
toOptionalMinecraftIngredient(ceRecipe.addition()),
toTransmuteResult(ceRecipe.result(ItemBuildContext.EMPTY))
);
} else if (VersionHelper.isVersionNewerThan1_21_2()) {
return Reflections.constructor$SmithingTransformRecipe.newInstance(
toOptionalMinecraftIngredient(ceRecipe.template()),
toOptionalMinecraftIngredient(ceRecipe.base()),

View File

@@ -118,7 +118,7 @@ public class BukkitVanillaLootManager implements VanillaLootManager, Listener {
VanillaLoot vanillaLoot = this.blockLoots.computeIfAbsent(BlockStateUtils.blockStateToId(blockState), k -> new VanillaLoot(VanillaLoot.Type.BLOCK));
vanillaLoot.addLootTable(lootTable);
} else {
for (Object blockState : BlockStateUtils.getAllBlockStates(Key.of(target))) {
for (Object blockState : BlockStateUtils.getAllVanillaBlockStates(Key.of(target))) {
if (blockState == Reflections.instance$Blocks$AIR$defaultState) {
this.plugin.logger().warn(path, "Failed to load " + id + ". Invalid target " + target);
return;

View File

@@ -4,6 +4,7 @@ import net.momirealms.antigrieflib.AntiGriefLib;
import net.momirealms.craftengine.bukkit.api.event.CraftEngineReloadEvent;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors;
import net.momirealms.craftengine.bukkit.compatibility.papi.PlaceholderAPIUtils;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.font.BukkitImageManager;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
@@ -17,13 +18,10 @@ import net.momirealms.craftengine.bukkit.plugin.command.BukkitSenderFactory;
import net.momirealms.craftengine.bukkit.plugin.gui.BukkitGuiManager;
import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager;
import net.momirealms.craftengine.bukkit.plugin.papi.ImageExpansion;
import net.momirealms.craftengine.bukkit.plugin.papi.ShiftExpansion;
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.PlaceholderAPIUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
import net.momirealms.craftengine.core.entity.player.Player;
@@ -176,8 +174,7 @@ public class BukkitCraftEngine extends CraftEngine {
// compatibility
// register expansion
if (this.isPluginEnabled("PlaceholderAPI")) {
new ShiftExpansion(this).register();
new ImageExpansion(this).register();
PlaceholderAPIUtils.registerExpansions(this);
this.hasPlaceholderAPI = true;
}
}

View File

@@ -96,6 +96,8 @@ public class BukkitInjector {
.method(ElementMatchers.any()
.and(ElementMatchers.not(ElementMatchers.is(Reflections.method$PalettedContainer$getAndSet)))
.and(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class)))
// TODO Requires Paper Patch
//.and(ElementMatchers.not(ElementMatchers.named("get").and(ElementMatchers.takesArguments(int.class)).and(ElementMatchers.returns(Object.class))))
)
.intercept(MethodDelegation.toField("target"))
.method(ElementMatchers.is(Reflections.method$PalettedContainer$getAndSet))

View File

@@ -6,7 +6,7 @@ import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.network.impl.*;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
@@ -72,7 +72,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
public BukkitNetworkManager(BukkitCraftEngine plugin) {
this.plugin = plugin;
if (VersionHelper.isVersionNewerThan1_21_2()) {
if (VersionHelper.isVersionNewerThan1_21_5()) {
this.packetIds = new PacketIds1_21_5();
} else if (VersionHelper.isVersionNewerThan1_21_2()) {
this.packetIds = new PacketIds1_21_2();
} else if (VersionHelper.isVersionNewerThan1_20_5()) {
this.packetIds = new PacketIds1_20_5();
@@ -254,7 +256,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
return (Channel) Reflections.field$Channel.get(
Reflections.field$NetworkManager.get(
Reflections.field$ServerPlayer$connection.get(
Reflections.method$CraftPlayer$getHandle.invoke(player)
FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)
)
)
);
@@ -265,7 +267,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
public void sendPacket(@NotNull Player player, @NotNull Object packet) {
try {
Object serverPlayer = Reflections.method$CraftPlayer$getHandle.invoke(player);
Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player);
this.immediatePacketConsumer.accept(serverPlayer, packet);
} catch (Exception e) {
this.plugin.logger().warn("Failed to send packet", e);

View File

@@ -11,6 +11,7 @@ import net.momirealms.craftengine.bukkit.api.event.FurnitureInteractEvent;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.*;
@@ -268,11 +269,7 @@ public class PacketConsumers {
Player platformPlayer = player.platformPlayer();
World world = platformPlayer.getWorld();
Object blockPos = Reflections.field$ServerboundPlayerActionPacket$pos.get(packet);
BlockPos pos = new BlockPos(
(int) Reflections.field$Vec3i$x.get(blockPos),
(int) Reflections.field$Vec3i$y.get(blockPos),
(int) Reflections.field$Vec3i$z.get(blockPos)
);
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
if (VersionHelper.isFolia()) {
BukkitCraftEngine.instance().scheduler().sync().run(() -> {
try {
@@ -293,7 +290,7 @@ public class PacketConsumers {
Object action = Reflections.field$ServerboundPlayerActionPacket$action.get(packet);
if (action == Reflections.instance$ServerboundPlayerActionPacket$Action$START_DESTROY_BLOCK) {
Object serverLevel = Reflections.field$CraftWorld$ServerLevel.get(world);
Object blockState = Reflections.method$BlockGetter$getBlockState.invoke(serverLevel, LocationUtils.toBlockPos(pos));
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(serverLevel, LocationUtils.toBlockPos(pos));
int stateId = BlockStateUtils.blockStateToId(blockState);
// not a custom block
if (BlockStateUtils.isVanillaBlock(stateId)) {
@@ -311,7 +308,7 @@ public class PacketConsumers {
}
if (player.isAdventureMode()) {
Object itemStack = Reflections.method$CraftItemStack$asNMSCopy.invoke(null, player.platformPlayer().getInventory().getItemInMainHand());
Object blockPos = Reflections.constructor$BlockPos.newInstance(pos.x(), pos.y(), pos.z());
Object blockPos = LocationUtils.toBlockPos(pos);
Object blockInWorld = Reflections.constructor$BlockInWorld.newInstance(serverLevel, blockPos, false);
if (VersionHelper.isVersionNewerThan1_20_5()) {
if (Reflections.method$ItemStack$canBreakBlockInAdventureMode != null
@@ -523,8 +520,8 @@ public class PacketConsumers {
if (player == null) return;
Object pos = Reflections.field$ServerboundPickItemFromBlockPacket$pos.get(packet);
if (VersionHelper.isFolia()) {
int x = (int) Reflections.field$Vec3i$x.get(pos);
int z = (int) Reflections.field$Vec3i$z.get(pos);
int x = FastNMS.INSTANCE.field$Vec3i$x(pos);
int z = FastNMS.INSTANCE.field$Vec3i$z(pos);
BukkitCraftEngine.instance().scheduler().sync().run(() -> {
try {
handlePickItemFromBlockPacketOnMainThread(player, pos);
@@ -548,7 +545,7 @@ public class PacketConsumers {
private static void handlePickItemFromBlockPacketOnMainThread(Player player, Object pos) throws Exception {
Object serverLevel = Reflections.field$CraftWorld$ServerLevel.get(player.getWorld());
Object blockState = Reflections.method$BlockGetter$getBlockState.invoke(serverLevel, pos);
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(serverLevel, pos);
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
if (state == null) return;
Key itemId = state.settings().itemId();
@@ -603,7 +600,7 @@ public class PacketConsumers {
}
assert Reflections.method$ServerGamePacketListenerImpl$tryPickItem != null;
Reflections.method$ServerGamePacketListenerImpl$tryPickItem.invoke(
Reflections.field$ServerPlayer$connection.get(Reflections.method$CraftPlayer$getHandle.invoke(player)), Reflections.method$CraftItemStack$asNMSCopy.invoke(null, itemStack));
Reflections.field$ServerPlayer$connection.get(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)), Reflections.method$CraftItemStack$asNMSCopy.invoke(null, itemStack));
}
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> ADD_ENTITY = (user, event, packet) -> {
@@ -909,9 +906,14 @@ public class PacketConsumers {
Object id = Reflections.method$CustomPacketPayload$Type$id.invoke(type);
String channel = id.toString();
if (!channel.equals(NetworkManager.MOD_CHANNEL)) return;
ByteBuf buf = (ByteBuf) Reflections.method$DiscardedPayload$data.invoke(payload);
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
byte[] data;
if (Reflections.method$DiscardedPayload$data != null) {
ByteBuf buf = (ByteBuf) Reflections.method$DiscardedPayload$data.invoke(payload);
data = new byte[buf.readableBytes()];
buf.readBytes(data);
} else {
data = (byte[]) Reflections.method$DiscardedPayload$dataByteArray.invoke(payload);
}
String decodeData = new String(data, StandardCharsets.UTF_8);
if (!decodeData.endsWith("init")) return;
int firstColon = decodeData.indexOf(':');

View File

@@ -0,0 +1,31 @@
package net.momirealms.craftengine.bukkit.plugin.network.impl;
import net.momirealms.craftengine.bukkit.plugin.network.PacketIds;
public class PacketIds1_21_5 implements PacketIds {
@Override
public int clientboundBlockUpdatePacket() {
return 8;
}
@Override
public int clientboundSectionBlocksUpdatePacket() {
return 72;
}
@Override
public int clientboundLevelParticlesPacket() {
return 40;
}
@Override
public int clientboundLevelEventPacket() {
return 39;
}
@Override
public int clientboundAddEntityPacket() {
return 1;
}
}

View File

@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
import io.netty.channel.Channel;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
@@ -75,11 +76,7 @@ public class BukkitServerPlayer extends Player {
public void setPlayer(org.bukkit.entity.Player player) {
playerRef = new WeakReference<>(player);
try {
serverPlayerRef = new WeakReference<>(Reflections.method$CraftPlayer$getHandle.invoke(player));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
serverPlayerRef = new WeakReference<>(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player));
}
@Override
@@ -298,7 +295,7 @@ public class BukkitServerPlayer extends Player {
public float getDestroyProgress(Object blockState, BlockPos pos) {
try {
Object serverPlayer = serverPlayer();
Object blockPos = Reflections.constructor$BlockPos.newInstance(pos.x(), pos.y(), pos.z());
Object blockPos = LocationUtils.toBlockPos(pos.x(), pos.y(), pos.z());
return (float) Reflections.method$BlockStateBase$getDestroyProgress.invoke(blockState, serverPlayer, Reflections.method$Entity$level.invoke(serverPlayer), blockPos);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to get destroy progress for player " + platformPlayer().getName());

View File

@@ -1,18 +1,23 @@
package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.block.BlockStateParser;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.PushReaction;
import net.momirealms.craftengine.core.util.Instrument;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MapColor;
import net.momirealms.craftengine.core.world.BlockPos;
import org.bukkit.Bukkit;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.event.block.BlockPhysicsEvent;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Optional;
public class BlockStateUtils {
public static final IdentityHashMap<Object, Object> CLIENT_SIDE_NOTE_BLOCKS = new IdentityHashMap<>();
@@ -27,8 +32,36 @@ public class BlockStateUtils {
hasInit = true;
}
@SuppressWarnings("unchecked")
public static List<Object> getAllBlockStates(String blockState) {
int index = blockState.indexOf('[');
if (index == -1) {
return getAllBlockStates(Key.of(blockState));
} else {
String blockTypeString = blockState.substring(0, index);
Key block = Key.of(blockTypeString);
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().getBlock(block);
if (optionalCustomBlock.isPresent()) {
ImmutableBlockState state = BlockStateParser.deserialize(blockState);
if (state == null) {
return List.of();
} else {
return List.of(state.customBlockState().handle());
}
} else {
BlockData blockData = Bukkit.createBlockData(blockState);
return List.of(blockDataToBlockState(blockData));
}
}
}
public static List<Object> getAllBlockStates(Key block) {
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().getBlock(block);
return optionalCustomBlock.map(customBlock -> customBlock.variantProvider().states().stream().map(it -> it.customBlockState().handle()).toList())
.orElseGet(() -> getAllVanillaBlockStates(block));
}
@SuppressWarnings("unchecked")
public static List<Object> getAllVanillaBlockStates(Key block) {
try {
Object blockIns = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$BLOCK, Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, block.namespace(), block.value()));
Object definition = Reflections.field$Block$StateDefinition.get(blockIns);

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.world.BlockPos;
import org.bukkit.Location;
import org.bukkit.World;
@@ -14,13 +15,9 @@ public class EntityUtils {
public static BlockPos getOnPos(Player player) {
try {
Object serverPlayer = Reflections.method$CraftPlayer$getHandle.invoke(player);
Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player);
Object blockPos = Reflections.method$Entity$getOnPos.invoke(serverPlayer, 1.0E-5F);
return new BlockPos(
(int) Reflections.field$Vec3i$x.get(blockPos),
(int) Reflections.field$Vec3i$y.get(blockPos),
(int) Reflections.field$Vec3i$z.get(blockPos)
);
return LocationUtils.fromBlockPos(blockPos);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}

View File

@@ -11,6 +11,7 @@ public class LightUtils {
private LightUtils() {}
@SuppressWarnings("unchecked")
public static void updateChunkLight(World world, Map<Long, BitSet> sectionPosSet) {
try {
Object serverLevel = Reflections.field$CraftWorld$ServerLevel.get(world);
@@ -19,8 +20,13 @@ public class LightUtils {
long chunkKey = entry.getKey();
Object chunkHolder = Reflections.method$ServerChunkCache$getVisibleChunkIfPresent.invoke(chunkSource, chunkKey);
if (chunkHolder == null) continue;
@SuppressWarnings("unchecked")
List<Object> players = (List<Object>) Reflections.method$ChunkHolder$getPlayers.invoke(chunkHolder, false);
List<Object> players;
if (Reflections.method$ChunkHolder$getPlayers != null) {
players = (List<Object>) Reflections.method$ChunkHolder$getPlayers.invoke(chunkHolder, false);
} else {
Object chunkHolder$PlayerProvider = Reflections.field$ChunkHolder$playerProvider.get(chunkHolder);
players = (List<Object>) Reflections.method$ChunkHolder$PlayerProvider$getPlayers.invoke(chunkHolder$PlayerProvider, false);
}
if (players.isEmpty()) continue;
Object lightEngine = Reflections.field$ChunkHolder$lightEngine.get(chunkHolder);
BitSet blockChangedLightSectionFilter = (BitSet) Reflections.field$ChunkHolder$blockChangedLightSectionFilter.get(chunkHolder);

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import org.bukkit.Location;
@@ -13,48 +14,39 @@ public class LocationUtils {
return new Vec3d(loc.getX(), loc.getY(), loc.getZ());
}
public static Vec3d fromVec(Object vec) throws ReflectiveOperationException {
public static Vec3d fromVec(Object vec) {
return new Vec3d(
Reflections.field$Vec3$x.getDouble(vec),
Reflections.field$Vec3$y.getDouble(vec),
Reflections.field$Vec3$z.getDouble(vec)
FastNMS.INSTANCE.field$Vec3$x(vec),
FastNMS.INSTANCE.field$Vec3$y(vec),
FastNMS.INSTANCE.field$Vec3$y(vec)
);
}
public static Object toBlockPos(BlockPos pos) {
try {
return Reflections.constructor$BlockPos.newInstance(pos.x(), pos.y(), pos.z());
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to create BlockPos", e);
}
return toBlockPos(pos.x(), pos.y(), pos.z());
}
public static Object above(Object blockPos) throws ReflectiveOperationException {
return toBlockPos(
Reflections.field$Vec3i$x.getInt(blockPos),
Reflections.field$Vec3i$y.getInt(blockPos) + 1,
Reflections.field$Vec3i$z.getInt(blockPos)
FastNMS.INSTANCE.field$Vec3i$x(blockPos),
FastNMS.INSTANCE.field$Vec3i$y(blockPos) + 1,
FastNMS.INSTANCE.field$Vec3i$z(blockPos)
);
}
public static Object toBlockPos(int x, int y, int z) {
try {
return Reflections.constructor$BlockPos.newInstance(x, y, z);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to create BlockPos", e);
}
return FastNMS.INSTANCE.constructor$BlockPos(x, y, z);
}
public static BlockPos toBlockPos(Location pos) {
return new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
}
public static BlockPos fromBlockPos(Object pos) throws ReflectiveOperationException {
return new BlockPos(
Reflections.field$Vec3i$x.getInt(pos),
Reflections.field$Vec3i$y.getInt(pos),
Reflections.field$Vec3i$z.getInt(pos)
FastNMS.INSTANCE.field$Vec3i$x(pos),
FastNMS.INSTANCE.field$Vec3i$y(pos),
FastNMS.INSTANCE.field$Vec3i$z(pos)
);
}

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
public class NoteBlockChainUpdateUtils {
@@ -9,7 +10,7 @@ public class NoteBlockChainUpdateUtils {
public static void noteBlockChainUpdate(Object level, Object chunkSource, Object direction, Object blockPos, int times) throws ReflectiveOperationException {
if (times >= ConfigManager.maxChainUpdate()) return;
Object relativePos = Reflections.method$BlockPos$relative.invoke(blockPos, direction);
Object state = Reflections.method$BlockGetter$getBlockState.invoke(level, relativePos);
Object state = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, relativePos);
if (BlockStateUtils.isClientSideNoteBlock(state)) {
Reflections.method$ServerChunkCache$blockChanged.invoke(chunkSource, relativePos);
noteBlockChainUpdate(level, chunkSource, direction, relativePos, times+1);

View File

@@ -0,0 +1,18 @@
package net.momirealms.craftengine.bukkit.util;
import org.bukkit.Particle;
public class ParticleUtils {
public static Particle getParticle(String particle) {
try {
return Particle.valueOf(particle);
} catch (IllegalArgumentException e) {
return switch (particle) {
case "REDSTONE" -> Particle.valueOf("DUST");
case "VILLAGER_HAPPY" -> Particle.valueOf("HAPPY_VILLAGER");
default -> Particle.valueOf(particle);
};
}
}
}

View File

@@ -471,9 +471,9 @@ public class Reflections {
BukkitReflectionUtils.assembleCBClass("entity.CraftPlayer")
));
public static final Method method$CraftPlayer$getHandle = requireNonNull(
ReflectionUtils.getMethod(clazz$CraftPlayer, new String[] { "getHandle" })
);
// public static final Method method$CraftPlayer$getHandle = requireNonNull(
// ReflectionUtils.getMethod(clazz$CraftPlayer, new String[] { "getHandle" })
// );
public static final Field field$ServerPlayer$connection = requireNonNull(
ReflectionUtils.getInstanceDeclaredField(clazz$ServerPlayer, clazz$ServerGamePacketListenerImpl, 0)
@@ -1263,9 +1263,18 @@ public class Reflections {
)
);
public static final Class<?> clazz$Team$Visibility = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.scores.Team$Visibility"),
BukkitReflectionUtils.assembleMCClass("world.scores.ScoreboardTeamBase$EnumTeamPush")
)
);
public static final Field field$ClientboundSetPlayerTeamPacket$Parameters$nametagVisibility = requireNonNull(
ReflectionUtils.getInstanceDeclaredField(
clazz$ClientboundSetPlayerTeamPacket$Parameters, String.class, 0
clazz$ClientboundSetPlayerTeamPacket$Parameters,
VersionHelper.isVersionNewerThan1_21_5() ? clazz$Team$Visibility : String.class,
0
)
);
@@ -1355,17 +1364,17 @@ public class Reflections {
)
);
public static final Field field$Vec3i$x = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$Vec3i, int.class, 0)
);
public static final Field field$Vec3i$y = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$Vec3i, int.class, 1)
);
public static final Field field$Vec3i$z = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$Vec3i, int.class, 2)
);
// public static final Field field$Vec3i$x = requireNonNull(
// ReflectionUtils.getDeclaredField(clazz$Vec3i, int.class, 0)
// );
//
// public static final Field field$Vec3i$y = requireNonNull(
// ReflectionUtils.getDeclaredField(clazz$Vec3i, int.class, 1)
// );
//
// public static final Field field$Vec3i$z = requireNonNull(
// ReflectionUtils.getDeclaredField(clazz$Vec3i, int.class, 2)
// );
public static final Class<?> clazz$BlockState = requireNonNull(
ReflectionUtils.getClazz(
@@ -1588,11 +1597,11 @@ public class Reflections {
)
);
public static final Constructor<?> constructor$BlockPos = requireNonNull(
ReflectionUtils.getConstructor(
clazz$BlockPos, int.class, int.class, int.class
)
);
// public static final Constructor<?> constructor$BlockPos = requireNonNull(
// ReflectionUtils.getConstructor(
// clazz$BlockPos, int.class, int.class, int.class
// )
// );
public static final Method method$Vec3i$relative = requireNonNull(
ReflectionUtils.getMethod(
@@ -2462,11 +2471,11 @@ public class Reflections {
)
);
public static final Method method$BlockGetter$getBlockState = requireNonNull(
ReflectionUtils.getMethod(
clazz$BlockGetter, clazz$BlockState, clazz$BlockPos
)
);
// public static final Method method$BlockGetter$getBlockState = requireNonNull(
// ReflectionUtils.getMethod(
// clazz$BlockGetter, clazz$BlockState, clazz$BlockPos
// )
// );
public static final Method method$LevelAccessor$scheduleTick = requireNonNull(
ReflectionUtils.getMethod(
@@ -2660,12 +2669,31 @@ public class Reflections {
)
);
public static final Method method$ChunkHolder$getPlayers = requireNonNull(
ReflectionUtils.getMethod(
clazz$ChunkHolder, List.class, boolean.class
public static final Class<?> clazz$ChunkHolder$PlayerProvider = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("server.level.ChunkHolder$PlayerProvider"),
BukkitReflectionUtils.assembleMCClass("server.level.PlayerChunk$d")
)
);
public static final Field field$ChunkHolder$playerProvider = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ChunkHolder, clazz$ChunkHolder$PlayerProvider, 0
)
);
public static final Method method$ChunkHolder$PlayerProvider$getPlayers = requireNonNull(
ReflectionUtils.getMethod(
clazz$ChunkHolder$PlayerProvider, List.class, clazz$ChunkPos, boolean.class
)
);
// 1.20 ~ 1.21.4
public static final Method method$ChunkHolder$getPlayers =
ReflectionUtils.getMethod(
clazz$ChunkHolder, List.class, boolean.class
);
public static final Field field$ChunkHolder$lightEngine = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ChunkHolder, clazz$LevelLightEngine, 0
@@ -3407,6 +3435,7 @@ public class Reflections {
)
);
@Deprecated
public static final Method method$Level$getCraftWorld = requireNonNull(
ReflectionUtils.getMethod(
clazz$Level, clazz$CraftWorld
@@ -3923,9 +3952,9 @@ public class Reflections {
);
public static final Method method$CraftEventFactory$callBlockPlaceEvent = requireNonNull(
ReflectionUtils.getStaticMethod(
clazz$CraftEventFactory, BlockPlaceEvent.class, clazz$ServerLevel, clazz$Player, clazz$InteractionHand, BlockState.class, int.class, int.class, int.class
)
VersionHelper.isVersionNewerThan1_21_5()
? ReflectionUtils.getStaticMethod(clazz$CraftEventFactory, BlockPlaceEvent.class, clazz$ServerLevel, clazz$Player, clazz$InteractionHand, BlockState.class, clazz$BlockPos)
: ReflectionUtils.getStaticMethod(clazz$CraftEventFactory, BlockPlaceEvent.class, clazz$ServerLevel, clazz$Player, clazz$InteractionHand, BlockState.class, int.class, int.class, int.class)
);
public static final Class<?> clazz$Abilities = requireNonNull(
@@ -4816,9 +4845,9 @@ public class Reflections {
);
public static final Method method$ServerLevel$levelEvent = requireNonNull(
ReflectionUtils.getMethod(
clazz$ServerLevel, void.class, clazz$Player, int.class, clazz$BlockPos, int.class
)
VersionHelper.isVersionNewerThan1_21_5()
? ReflectionUtils.getMethod(clazz$ServerLevel, void.class, clazz$Entity, int.class, clazz$BlockPos, int.class)
: ReflectionUtils.getMethod(clazz$ServerLevel, void.class, clazz$Player, int.class, clazz$BlockPos, int.class)
);
public static final Method method$PalettedContainer$getAndSet = Objects.requireNonNull(
@@ -5104,12 +5133,24 @@ public class Reflections {
)
);
// 1.21.5+
public static final Class<?> clazz$TransmuteResult =
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.item.crafting.TransmuteResult")
);
public static final Constructor<?> constructor$TransmuteResult = Optional.ofNullable(clazz$TransmuteResult)
.map(it -> ReflectionUtils.getConstructor(it, clazz$Item))
.orElse(null);
public static final Constructor<?> constructor$SmithingTransformRecipe = requireNonNull(
VersionHelper.isVersionNewerThan1_21_2() ?
ReflectionUtils.getConstructor(clazz$SmithingTransformRecipe, Optional.class, Optional.class, Optional.class, clazz$ItemStack) :
VersionHelper.isVersionNewerThan1_20_2() ?
ReflectionUtils.getConstructor(clazz$SmithingTransformRecipe, clazz$Ingredient, clazz$Ingredient, clazz$Ingredient, clazz$ItemStack) :
ReflectionUtils.getConstructor(clazz$SmithingTransformRecipe, clazz$ResourceLocation, clazz$Ingredient, clazz$Ingredient, clazz$Ingredient, clazz$ItemStack)
VersionHelper.isVersionNewerThan1_21_5()
? ReflectionUtils.getConstructor(clazz$SmithingTransformRecipe, Optional.class, clazz$Ingredient, Optional.class, clazz$TransmuteResult)
: VersionHelper.isVersionNewerThan1_21_2()
? ReflectionUtils.getConstructor(clazz$SmithingTransformRecipe, Optional.class, Optional.class, Optional.class, clazz$ItemStack)
: VersionHelper.isVersionNewerThan1_20_2()
? ReflectionUtils.getConstructor(clazz$SmithingTransformRecipe, clazz$Ingredient, clazz$Ingredient, clazz$Ingredient, clazz$ItemStack)
: ReflectionUtils.getConstructor(clazz$SmithingTransformRecipe, clazz$ResourceLocation, clazz$Ingredient, clazz$Ingredient, clazz$Ingredient, clazz$ItemStack)
);
public static final Method method$RecipeManager$addRecipe = requireNonNull(
@@ -5415,13 +5456,11 @@ public class Reflections {
);
public static final Method method$SimpleWaterloggedBlock$canPlaceLiquid = requireNonNull(
VersionHelper.isVersionNewerThan1_20_2() ?
ReflectionUtils.getMethod(
clazz$SimpleWaterloggedBlock, boolean.class, clazz$Player, clazz$BlockGetter, clazz$BlockPos, clazz$BlockState, clazz$Fluid
) :
ReflectionUtils.getMethod(
clazz$SimpleWaterloggedBlock, boolean.class, clazz$BlockGetter, clazz$BlockPos, clazz$BlockState, clazz$Fluid
)
VersionHelper.isVersionNewerThan1_21_5()
? ReflectionUtils.getMethod(clazz$SimpleWaterloggedBlock, boolean.class, clazz$LivingEntity, clazz$BlockGetter, clazz$BlockPos, clazz$BlockState, clazz$Fluid)
: VersionHelper.isVersionNewerThan1_20_2()
? ReflectionUtils.getMethod(clazz$SimpleWaterloggedBlock, boolean.class, clazz$Player, clazz$BlockGetter, clazz$BlockPos, clazz$BlockState, clazz$Fluid)
: ReflectionUtils.getMethod(clazz$SimpleWaterloggedBlock, boolean.class, clazz$BlockGetter, clazz$BlockPos, clazz$BlockState, clazz$Fluid)
);
public static final Method method$SimpleWaterloggedBlock$placeLiquid = requireNonNull(
@@ -5431,13 +5470,11 @@ public class Reflections {
);
public static final Method method$SimpleWaterloggedBlock$pickupBlock = requireNonNull(
VersionHelper.isVersionNewerThan1_20_2() ?
ReflectionUtils.getMethod(
clazz$SimpleWaterloggedBlock, clazz$ItemStack, clazz$Player, clazz$LevelAccessor, clazz$BlockPos, clazz$BlockState
) :
ReflectionUtils.getMethod(
clazz$SimpleWaterloggedBlock, clazz$ItemStack, clazz$LevelAccessor, clazz$BlockPos, clazz$BlockState
)
VersionHelper.isVersionNewerThan1_21_5()
? ReflectionUtils.getMethod(clazz$SimpleWaterloggedBlock, clazz$ItemStack, clazz$LivingEntity, clazz$LevelAccessor, clazz$BlockPos, clazz$BlockState)
: VersionHelper.isVersionNewerThan1_20_2()
? ReflectionUtils.getMethod(clazz$SimpleWaterloggedBlock, clazz$ItemStack, clazz$Player, clazz$LevelAccessor, clazz$BlockPos, clazz$BlockState)
: ReflectionUtils.getMethod(clazz$SimpleWaterloggedBlock, clazz$ItemStack, clazz$LevelAccessor, clazz$BlockPos, clazz$BlockState)
);
public static final Method method$Fluid$getTickDelay = requireNonNull(
@@ -5667,11 +5704,19 @@ public class Reflections {
.map(it -> ReflectionUtils.getMethod(it, clazz$ResourceLocation))
.orElse(null);
// 1.20.5+
// 1.20.5~1.21.4#221
public static final Method method$DiscardedPayload$data = Optional.ofNullable(clazz$DiscardedPayload)
.map(it -> ReflectionUtils.getMethod(it, ByteBuf.class))
.orElse(null);
// 1.21.4#222+
public static final Method method$DiscardedPayload$dataByteArray = Optional.ofNullable(method$DiscardedPayload$data)
.map(m -> (Method) null)
.orElseGet(() -> Optional.ofNullable(clazz$DiscardedPayload)
.map(clazz -> ReflectionUtils.getMethod(clazz, byte[].class))
.orElse(null)
);
public static final Class<?> clazz$ClientboundDisconnectPacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.common.ClientboundDisconnectPacket"),
@@ -5684,4 +5729,46 @@ public class Reflections {
clazz$ClientboundDisconnectPacket, clazz$Component
)
);
public static final Method method$CraftEventFactory$handleBlockGrowEvent = requireNonNull(
VersionHelper.isVersionNewerThan1_21_5() ?
ReflectionUtils.getStaticMethod(
clazz$CraftEventFactory, boolean.class, clazz$Level, clazz$BlockPos, clazz$BlockState, int.class
) :
ReflectionUtils.getStaticMethod(
clazz$CraftEventFactory, boolean.class, clazz$Level, clazz$BlockPos, clazz$BlockState
)
);
public static final Class<?> clazz$BlockAndTintGetter = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.level.BlockAndTintGetter"),
BukkitReflectionUtils.assembleMCClass("world.level.IBlockLightAccess")
)
);
public static final Method method$BlockAndTintGetter$getRawBrightness = requireNonNull(
ReflectionUtils.getMethod(
clazz$BlockAndTintGetter, int.class, clazz$BlockPos, int.class
)
);
public static final Field field$Level$random = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$Level, clazz$RandomSource, 0
)
);
public static final Class<?> clazz$Mth = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("util.Mth"),
BukkitReflectionUtils.assembleMCClass("util.MathHelper")
)
);
public static final Method method$nextInt = requireNonNull(
ReflectionUtils.getMethod(
clazz$Mth, int.class, clazz$RandomSource, int.class, int.class
)
);
}

View File

@@ -5,11 +5,17 @@ import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.util.SectionPosUtils;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor;
import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage;
public class BukkitCEWorld extends CEWorld {
public BukkitCEWorld(World world) {
super(world);
public BukkitCEWorld(World world, StorageAdaptor adaptor) {
super(world, adaptor);
}
public BukkitCEWorld(World world, WorldDataStorage dataStorage) {
super(world, dataStorage);
}
@Override

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.bukkit.world;
import net.momirealms.craftengine.bukkit.compatibility.slimeworld.SlimeFormatStorageAdaptor;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
@@ -8,6 +9,7 @@ import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.ChunkPos;
import net.momirealms.craftengine.core.world.SectionPos;
@@ -15,6 +17,9 @@ import net.momirealms.craftengine.core.world.WorldManager;
import net.momirealms.craftengine.core.world.chunk.CEChunk;
import net.momirealms.craftengine.core.world.chunk.CESection;
import net.momirealms.craftengine.core.world.chunk.serialization.ChunkSerializer;
import net.momirealms.craftengine.core.world.chunk.storage.DefaultStorageAdaptor;
import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor;
import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
@@ -27,6 +32,7 @@ import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.HashMap;
@@ -38,18 +44,34 @@ public class BukkitWorldManager implements WorldManager, Listener {
private static BukkitWorldManager instance;
private final BukkitCraftEngine plugin;
private final Map<UUID, CEWorld> worlds;
private CEWorld[] worldArray;
private CEWorld[] worldArray = new CEWorld[0];
private final ReentrantReadWriteLock worldMapLock = new ReentrantReadWriteLock();
private SchedulerTask tickTask;
// cache
private UUID lastVisitedUUID;
private CEWorld lastVisitedWorld;
private StorageAdaptor storageAdaptor;
public BukkitWorldManager(BukkitCraftEngine plugin) {
instance = this;
this.plugin = plugin;
this.worlds = new HashMap<>();
resetWorldArray();
if (VersionHelper.isVersionNewerThan1_21_4()) {
try {
Class.forName("com.infernalsuite.asp.api.AdvancedSlimePaperAPI");
SlimeFormatStorageAdaptor adaptor = new SlimeFormatStorageAdaptor(this);
this.storageAdaptor = adaptor;
Bukkit.getPluginManager().registerEvents(adaptor, plugin.bootstrap());
return;
} catch (ClassNotFoundException ignored) {
}
}
this.storageAdaptor = new DefaultStorageAdaptor();
}
@Override
public void setStorageAdaptor(@NotNull StorageAdaptor storageAdaptor) {
this.storageAdaptor = storageAdaptor;
}
public static BukkitWorldManager instance() {
@@ -95,7 +117,7 @@ public class BukkitWorldManager implements WorldManager, Listener {
this.worldMapLock.writeLock().lock();
try {
for (World world : Bukkit.getWorlds()) {
CEWorld ceWorld = new BukkitCEWorld(new BukkitWorld(world));
CEWorld ceWorld = new BukkitCEWorld(new BukkitWorld(world), this.storageAdaptor);
this.worlds.put(world.getUID(), ceWorld);
this.resetWorldArray();
for (Chunk chunk : world.getLoadedChunks()) {
@@ -110,8 +132,11 @@ public class BukkitWorldManager implements WorldManager, Listener {
@Override
public void disable() {
HandlerList.unregisterAll(this);
if (tickTask != null && !tickTask.cancelled()) {
tickTask.cancel();
if (this.storageAdaptor instanceof Listener listener) {
HandlerList.unregisterAll(listener);
}
if (this.tickTask != null && !this.tickTask.cancelled()) {
this.tickTask.cancel();
}
for (World world : Bukkit.getWorlds()) {
@@ -125,14 +150,18 @@ public class BukkitWorldManager implements WorldManager, Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onWorldLoad(WorldLoadEvent event) {
World world = event.getWorld();
CEWorld ceWorld = new BukkitCEWorld(new BukkitWorld(world));
this.loadWorld(new BukkitWorld(event.getWorld()));
}
@Override
public void loadWorld(net.momirealms.craftengine.core.world.World world) {
this.worldMapLock.writeLock().lock();
try {
if (this.worlds.containsKey(world.getUID())) return;
this.worlds.put(event.getWorld().getUID(), ceWorld);
if (this.worlds.containsKey(world.uuid())) return;
CEWorld ceWorld = new BukkitCEWorld(world, this.storageAdaptor);
this.worlds.put(world.uuid(), ceWorld);
this.resetWorldArray();
for (Chunk chunk : world.getLoadedChunks()) {
for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) {
handleChunkLoad(ceWorld, chunk);
}
} finally {
@@ -140,13 +169,37 @@ public class BukkitWorldManager implements WorldManager, Listener {
}
}
@Override
public void loadWorld(CEWorld world) {
this.worldMapLock.writeLock().lock();
try {
if (this.worlds.containsKey(world.world().uuid())) return;
this.worlds.put(world.world().uuid(), world);
this.resetWorldArray();
for (Chunk chunk : ((World) world.world().platformWorld()).getLoadedChunks()) {
handleChunkLoad(world, chunk);
}
} finally {
this.worldMapLock.writeLock().unlock();
}
}
@Override
public CEWorld createWorld(net.momirealms.craftengine.core.world.World world, WorldDataStorage storage) {
return new BukkitCEWorld(world, storage);
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onWorldUnload(WorldUnloadEvent event) {
World world = event.getWorld();
unloadWorld(new BukkitWorld(event.getWorld()));
}
@Override
public void unloadWorld(net.momirealms.craftengine.core.world.World world) {
CEWorld ceWorld;
this.worldMapLock.writeLock().lock();
try {
ceWorld = this.worlds.remove(world.getUID());
ceWorld = this.worlds.remove(world.uuid());
if (ceWorld == null) {
return;
}
@@ -158,11 +211,20 @@ public class BukkitWorldManager implements WorldManager, Listener {
} finally {
this.worldMapLock.writeLock().unlock();
}
for (Chunk chunk : world.getLoadedChunks()) {
for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) {
handleChunkUnload(ceWorld, chunk);
}
}
@Override
public <T> net.momirealms.craftengine.core.world.World wrap(T world) {
if (world instanceof World w) {
return new BukkitWorld(w);
} else {
throw new IllegalArgumentException(world.getClass() + " is not a Bukkit World");
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onChunkLoad(ChunkLoadEvent event) {
this.worldMapLock.readLock().lock();

View File

@@ -6,6 +6,7 @@ import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.filter.AbstractFilter;
public class LoggerFilter {
public static void filter() {
Logger rootLogger = (Logger) LogManager.getRootLogger();
rootLogger.addFilter(new AbstractFilter() {

View File

@@ -14,9 +14,7 @@
# Q: When do I need to configure this file?
# A: When the number of real block IDs is insufficient, but there are still available appearances.
# By default, the plugin only registers an additional 112 oak leaf block states (for the default configuration needs [>=28 states]).
minecraft:oak_leaves: 112
minecraft:oak_sapling: 1
minecraft:birch_sapling: 1
minecraft:spruce_sapling: 1
@@ -24,5 +22,5 @@ minecraft:jungle_sapling: 1
minecraft:dark_oak_sapling: 1
minecraft:acacia_sapling: 1
minecraft:cherry_sapling: 1
minecraft:anvil: 2
minecraft:anvil: 2
minecraft:sugarcane: 14

View File

@@ -10,6 +10,7 @@ import net.momirealms.craftengine.core.loot.function.LootFunction;
import net.momirealms.craftengine.core.loot.function.LootFunctions;
import net.momirealms.craftengine.core.loot.number.NumberProvider;
import net.momirealms.craftengine.core.loot.number.NumberProviders;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.World;
import org.jetbrains.annotations.Nullable;
@@ -43,19 +44,19 @@ public class LootTable<T> {
NumberProvider rolls = NumberProviders.fromObject(pool.getOrDefault("rolls", 1));
NumberProvider bonus_rolls = NumberProviders.fromObject(pool.getOrDefault("bonus_rolls", 0));
List<LootCondition> conditions = Optional.ofNullable(pool.get("conditions"))
.map(it -> LootConditions.fromMapList((List<Map<String, Object>>) it))
.map(it -> LootConditions.fromMapList(MiscUtils.castToMapListOrThrow(it, () -> new RuntimeException("'conditions' should be a map list, current type: " + it.getClass().getSimpleName()))))
.orElse(Lists.newArrayList());
List<LootEntryContainer<T>> containers = Optional.ofNullable(pool.get("entries"))
.map(it -> (List<LootEntryContainer<T>>) new ArrayList<LootEntryContainer<T>>(LootEntryContainers.fromMapList((List<Map<String, Object>>) it)))
.map(it -> (List<LootEntryContainer<T>>) new ArrayList<LootEntryContainer<T>>(LootEntryContainers.fromMapList(MiscUtils.castToMapListOrThrow(it, () -> new RuntimeException("'entries' should be a map list, current type: " + it.getClass().getSimpleName())))))
.orElse(Lists.newArrayList());
List<LootFunction<T>> functions = Optional.ofNullable(pool.get("functions"))
.map(it -> (List<LootFunction<T>>) new ArrayList<LootFunction<T>>(LootFunctions.fromMapList((List<Map<String, Object>>) it)))
.map(it -> (List<LootFunction<T>>) new ArrayList<LootFunction<T>>(LootFunctions.fromMapList(MiscUtils.castToMapListOrThrow(it, () -> new RuntimeException("'functions' should be a map list, current type: " + it.getClass().getSimpleName())))))
.orElse(Lists.newArrayList());
lootPools.add(new LootPool<>(containers, conditions, functions, rolls, bonus_rolls));
}
return new LootTable<>(lootPools,
Optional.ofNullable(map.get("functions"))
.map(it -> (List<LootFunction<T>>) new ArrayList<LootFunction<T>>(LootFunctions.fromMapList((List<Map<String, Object>>) it)))
.map(it -> (List<LootFunction<T>>) new ArrayList<LootFunction<T>>(LootFunctions.fromMapList(MiscUtils.castToMapListOrThrow(it, () -> new RuntimeException("'functions' should be a map list, current type: " + it.getClass().getSimpleName())))))
.orElse(Lists.newArrayList())
);
}

View File

@@ -14,8 +14,10 @@ import java.util.function.Predicate;
public class LootConditions {
public static final Key MATCH_ITEM = Key.from("craftengine:match_item");
public static final Key MATCH_BLOCK_PROPERTY = Key.from("craftengine:match_block_property");
public static final Key TABLE_BONUS = Key.from("craftengine:table_bonus");
public static final Key SURVIVES_EXPLOSION = Key.from("craftengine:survives_explosion");
public static final Key RANDOM = Key.from("craftengine:random");
public static final Key ANY_OF = Key.from("craftengine:any_of");
public static final Key ALL_OF = Key.from("craftengine:all_of");
public static final Key ENCHANTMENT = Key.from("craftengine:enchantment");
@@ -24,6 +26,7 @@ public class LootConditions {
static {
register(MATCH_ITEM, MatchItemCondition.FACTORY);
register(MATCH_BLOCK_PROPERTY, MatchBlockPropertyCondition.FACTORY);
register(TABLE_BONUS, TableBonusCondition.FACTORY);
register(SURVIVES_EXPLOSION, SurvivesExplosionCondition.FACTORY);
register(ANY_OF, AnyOfCondition.FACTORY);
@@ -31,6 +34,7 @@ public class LootConditions {
register(ENCHANTMENT, EnchantmentCondition.FACTORY);
register(INVERTED, InvertedCondition.FACTORY);
register(FALLING_BLOCK, FallingCondition.FACTORY);
register(RANDOM, RandomCondition.FACTORY);
}
public static void register(Key key, LootConditionFactory factory) {

View File

@@ -0,0 +1,61 @@
package net.momirealms.craftengine.core.loot.condition;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.loot.LootContext;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class MatchBlockPropertyCondition implements LootCondition {
public static final Factory FACTORY = new Factory();
private final List<Pair<String, String>> properties;
public MatchBlockPropertyCondition(List<Pair<String, String>> properties) {
this.properties = properties;
}
@Override
public Key type() {
return LootConditions.MATCH_BLOCK_PROPERTY;
}
@Override
public boolean test(LootContext lootContext) {
return lootContext.getOptionalParameter(LootParameters.BLOCK_STATE).map(state -> {
CustomBlock block = state.owner().value();
for (Pair<String, String> property : this.properties) {
Property<?> propertyIns = block.getProperty(property.left());
if (propertyIns == null) {
return false;
}
if (!state.get(propertyIns).toString().toLowerCase(Locale.ENGLISH).equals(property.right())) {
return false;
}
}
return true;
}).orElse(false);
}
public static class Factory implements LootConditionFactory {
@SuppressWarnings("unchecked")
@Override
public LootCondition create(Map<String, Object> arguments) {
Map<String, Object> properties = (Map<String, Object>) arguments.get("properties");
if (properties == null) {
throw new IllegalArgumentException("Missing 'properties' argument for 'match_block_property'");
}
List<Pair<String, String>> propertyList = new ArrayList<>();
for (Map.Entry<String, Object> entry : properties.entrySet()) {
propertyList.add(new Pair<>(entry.getKey(), entry.getValue().toString()));
}
return new MatchBlockPropertyCondition(propertyList);
}
}
}

View File

@@ -0,0 +1,35 @@
package net.momirealms.craftengine.core.loot.condition;
import net.momirealms.craftengine.core.loot.LootContext;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.RandomUtils;
import java.util.Map;
public class RandomCondition implements LootCondition {
public static final Factory FACTORY = new Factory();
private final float chance;
public RandomCondition(float chance) {
this.chance = chance;
}
@Override
public Key type() {
return LootConditions.RANDOM;
}
@Override
public boolean test(LootContext lootContext) {
return RandomUtils.generateRandomFloat(0, 1) < this.chance;
}
public static class Factory implements LootConditionFactory {
@Override
public LootCondition create(Map<String, Object> arguments) {
float chance = MiscUtils.getAsFloat(arguments.getOrDefault("value", 0.5f));
return new RandomCondition(chance);
}
}
}

View File

@@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.registry.Registries;
import net.momirealms.craftengine.core.registry.WritableRegistry;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.ResourceKey;
import java.util.Collections;
@@ -78,9 +79,11 @@ public class ApplyBonusCountFunction<T> extends AbstractLootConditionalFunction<
public static class Formulas {
public static final Key ORE_DROPS = Key.of("craftengine:ore_drops");
public static final Key CROP_DROPS = Key.of("craftengine:binomial_with_bonus_count");
static {
register(ORE_DROPS, OreDrops.FACTORY);
register(CROP_DROPS, CropDrops.FACTORY);
}
public static void register(Key key, FormulaFactory factory) {
@@ -133,4 +136,40 @@ public class ApplyBonusCountFunction<T> extends AbstractLootConditionalFunction<
}
}
}
public static class CropDrops implements Formula {
public static final Factory FACTORY = new Factory();
private final int extra;
private final float probability;
public CropDrops(int extra, float probability) {
this.extra = extra;
this.probability = probability;
}
@Override
public int apply(int initialCount, int enchantmentLevel) {
for (int i = 0; i < enchantmentLevel + this.extra; i++) {
if (RandomUtils.generateRandomFloat(0,1) < this.probability) {
initialCount++;
}
}
return initialCount;
}
@Override
public Key type() {
return Formulas.CROP_DROPS;
}
public static class Factory implements FormulaFactory {
@Override
public Formula create(Map<String, Object> arguments) {
int extra = MiscUtils.getAsInt(arguments.getOrDefault("extra", 1));
float probability = MiscUtils.getAsFloat(arguments.getOrDefault("probability", 0.5f));
return new CropDrops(extra, probability);
}
}
}
}

View File

@@ -331,8 +331,15 @@ public abstract class AbstractPackManager implements PackManager {
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/fairy_flower_3.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/fairy_flower_4.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/reed.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/flame_cane_1.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/flame_cane_2.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/ender_pearl_flower_stage_0.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/ender_pearl_flower_stage_1.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/ender_pearl_flower_stage_2.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/fairy_flower.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/reed.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/flame_cane.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/ender_pearl_flower_seeds.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/fairy_flower_1.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/reed.json");
// furniture

View File

@@ -16,6 +16,7 @@ import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.PluginProperties;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.plugin.logger.filter.DisconnectLogFilter;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ReflectionUtils;
@@ -183,6 +184,7 @@ public class ConfigManager implements Reloadable {
metrics = config.getBoolean("metrics", false);
checkUpdate = config.getBoolean("update-checker", false);
filterConfigurationPhaseDisconnect = config.getBoolean("filter-configuration-phase-disconnect", false);
DisconnectLogFilter.instance().setEnable(filterConfigurationPhaseDisconnect);
// resource pack
resource_pack$override_uniform_font = config.getBoolean("resource-pack.override-uniform-font", false);

View File

@@ -1,7 +1,5 @@
package net.momirealms.craftengine.core.plugin.logger.filter;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Node;
@@ -13,10 +11,24 @@ import org.apache.logging.log4j.core.impl.MutableLogEvent;
public class DisconnectLogFilter extends AbstractFilter {
private static final String TARGET_LOGGER = "net.minecraft.server.network.ServerConfigurationPacketListenerImpl";
private static final String TARGET_MESSAGE_PATTERN = "{} lost connection: {}";
private static DisconnectLogFilter instance;
private boolean enable = false;
public DisconnectLogFilter() {
instance = this;
}
public static DisconnectLogFilter instance() {
return instance;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
@Override
public Result filter(LogEvent event) {
if (!ConfigManager.filterConfigurationPhaseDisconnect()) {
if (!enable) {
return Result.NEUTRAL;
}

View File

@@ -3,8 +3,6 @@ package net.momirealms.craftengine.core.plugin.network;
import io.netty.channel.Channel;
import net.momirealms.craftengine.core.entity.player.Player;
import java.util.Collection;
public interface NetworkManager {
String MOD_CHANNEL = "craftengine:payload";

View File

@@ -6,6 +6,7 @@ import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public class MiscUtils {
@@ -22,6 +23,15 @@ public class MiscUtils {
throw new IllegalArgumentException("Expected Map, got: " + obj.getClass().getSimpleName());
}
@SuppressWarnings("unchecked")
public static List<Map<String, Object>> castToMapListOrThrow(Object obj, Supplier<RuntimeException> exceptionSupplier) {
if (obj instanceof List<?> list) {
return (List<Map<String, Object>>) list;
} else {
throw exceptionSupplier.get();
}
}
@SuppressWarnings("unchecked")
public static List<Object> castToList(Object obj, boolean allowNull) {
if (allowNull && obj == null) {

View File

@@ -26,6 +26,10 @@ public class RandomUtils {
return min + (max - min) * getInstance().random.nextFloat();
}
public static int generateRandomInt(int min, int max) {
return min >= max ? min : getInstance().random.nextInt(max - min + 1) + min;
}
public static boolean generateRandomBoolean() {
return getInstance().random.nextBoolean();
}

View File

@@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.world;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.world.chunk.CEChunk;
import net.momirealms.craftengine.core.world.chunk.storage.DefaultRegionFileStorage;
import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor;
import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage;
import org.jetbrains.annotations.Nullable;
@@ -25,10 +25,18 @@ public abstract class CEWorld {
private CEChunk lastChunk;
private long lastChunkPos;
public CEWorld(World world) {
public CEWorld(World world, StorageAdaptor adaptor) {
this.world = world;
this.loadedChunkMap = new Long2ObjectOpenHashMap<>(1024, 0.5f);
this.worldDataStorage = new DefaultRegionFileStorage(world.directory().resolve(REGION_DIRECTORY));
this.worldDataStorage = adaptor.adapt(world);
this.worldHeightAccessor = world.worldHeight();
this.lastChunkPos = ChunkPos.INVALID_CHUNK_POS;
}
public CEWorld(World world, WorldDataStorage dataStorage) {
this.world = world;
this.loadedChunkMap = new Long2ObjectOpenHashMap<>(1024, 0.5f);
this.worldDataStorage = dataStorage;
this.worldHeightAccessor = world.worldHeight();
this.lastChunkPos = ChunkPos.INVALID_CHUNK_POS;
}

View File

@@ -1,12 +1,27 @@
package net.momirealms.craftengine.core.world;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor;
import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public interface WorldManager extends Reloadable {
void setStorageAdaptor(@NotNull StorageAdaptor storageAdaptor);
CEWorld getWorld(UUID uuid);
void delayedInit();
void loadWorld(World world);
void loadWorld(CEWorld world);
CEWorld createWorld(World world, WorldDataStorage storage);
void unloadWorld(World world);
<T> World wrap(T world);
}

View File

@@ -0,0 +1,13 @@
package net.momirealms.craftengine.core.world.chunk.storage;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.World;
import org.jetbrains.annotations.NotNull;
public class DefaultStorageAdaptor implements StorageAdaptor {
@Override
public @NotNull WorldDataStorage adapt(@NotNull World world) {
return new DefaultRegionFileStorage(world.directory().resolve(CEWorld.REGION_DIRECTORY));
}
}

View File

@@ -0,0 +1,10 @@
package net.momirealms.craftengine.core.world.chunk.storage;
import net.momirealms.craftengine.core.world.World;
import org.jetbrains.annotations.NotNull;
public interface StorageAdaptor {
@NotNull
WorldDataStorage adapt(@NotNull World world);
}

View File

@@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G
# Project settings
# Rule: [major update].[feature update].[bug fix]
project_version=0.0.39
project_version=0.0.40
config_version=18
lang_version=3
project_group=net.momirealms
@@ -40,7 +40,7 @@ geantyref_version=1.3.16
zstd_version=1.5.6-9
commons_io_version=2.17.0
sparrow_nbt_version=0.3
sparrow_util_version=0.33
sparrow_util_version=0.34
fastutil_version=8.5.15
netty_version=4.1.119.Final
joml_version=1.10.8
@@ -49,7 +49,7 @@ mojang_brigadier_version=1.0.18
byte_buddy_version=1.15.11
snake_yaml_version=2.3
anti_grief_version=0.13
nms_helper_version=0.6
nms_helper_version=0.12
# Ignite Dependencies
mixinextras_version=0.4.1
mixin_version=0.15.2+mixin.0.8.7