Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

完善自定义方块 #537

Merged
merged 11 commits into from Aug 17, 2022
27 changes: 9 additions & 18 deletions src/main/java/cn/nukkit/Player.java
Expand Up @@ -2699,30 +2699,21 @@ public void onCompletion(Server server) {
stackPacket.resourcePackStack = this.server.getResourcePackManager().getResourceStack();

if (this.getServer().isEnableExperimentMode()) {
stackPacket.experiments.add(
new ResourcePackStackPacket.ExperimentData("wild_update", true)
);
stackPacket.experiments.add(
new ResourcePackStackPacket.ExperimentData("spectator_mode", true)
);
stackPacket.experiments.add(
new ResourcePackStackPacket.ExperimentData("vanilla_experiments", true)
);
// stackPacket.experiments.add(
// new ResourcePackStackPacket.ExperimentData("spectator_mode", true)
// );
stackPacket.experiments.add(
new ResourcePackStackPacket.ExperimentData("data_driven_items", true)
);
stackPacket.experiments.add(
new ResourcePackStackPacket.ExperimentData("data_driven_biomes", true)
);
// stackPacket.experiments.add(
// new ResourcePackStackPacket.ExperimentData("data_driven_biomes", true)
// );
stackPacket.experiments.add(
new ResourcePackStackPacket.ExperimentData("upcoming_creator_features", true)
);
stackPacket.experiments.add(
new ResourcePackStackPacket.ExperimentData("gametest", true)
);
stackPacket.experiments.add(
new ResourcePackStackPacket.ExperimentData("experimental_custom_ui", true)
);
// stackPacket.experiments.add(
// new ResourcePackStackPacket.ExperimentData("gametest", true)
// );
stackPacket.experiments.add(
new ResourcePackStackPacket.ExperimentData("experimental_molang_features", true)
);
Expand Down
62 changes: 42 additions & 20 deletions src/main/java/cn/nukkit/block/Block.java
Expand Up @@ -34,6 +34,8 @@
import cn.nukkit.utils.MinecraftNamespaceComparator;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.extern.log4j.Log4j2;

import javax.annotation.Nonnegative;
Expand Down Expand Up @@ -147,7 +149,7 @@ public abstract class Block extends Position implements Metadatable, Cloneable,
private static final List<BlockPropertyData> BLOCK_PROPERTY_DATA = new ArrayList<>();

@PowerNukkitXOnly
private static final HashMap<Integer, CustomBlock> ID_TO_CUSTOM_BLOCK = new HashMap<>();
public static final Int2ObjectMap<CustomBlock> ID_TO_CUSTOM_BLOCK = new Int2ObjectOpenHashMap<>();

@PowerNukkitXOnly
public static final ConcurrentHashMap<String, Integer> CUSTOM_BLOCK_ID_MAP = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -885,7 +887,7 @@ public static Block get(int id) {
@DeprecationDetails(reason = "The meta is limited to 32 bits", replaceWith = "BlockState.getBlock()", since = "1.4.0.0-PN")
public static Block get(int id, Integer meta) {
if (id > MAX_BLOCK_ID) {
return ID_TO_CUSTOM_BLOCK.get(id).toCustomBlock();
return ID_TO_CUSTOM_BLOCK.get(id).toCustomBlock(meta);
}
if (id < 0) {
id = 255 - id;
Expand Down Expand Up @@ -918,7 +920,7 @@ public static Block get(int id, Integer meta, Position pos, int layer) {
Block block;

if (id > MAX_BLOCK_ID) {
block = ID_TO_CUSTOM_BLOCK.get(id).toCustomBlock();
block = ID_TO_CUSTOM_BLOCK.get(id).toCustomBlock(meta);
block.x = pos.x;
block.y = pos.y;
block.z = pos.z;
Expand Down Expand Up @@ -952,7 +954,7 @@ public static Block get(int id, Integer meta, Position pos, int layer) {
@DeprecationDetails(reason = "The meta is limited to 32 bits", replaceWith = "BlockState.getBlock()", since = "1.4.0.0-PN")
public static Block get(int id, int data) {
if (id > MAX_BLOCK_ID) {
return ID_TO_CUSTOM_BLOCK.get(id).toCustomBlock();
return ID_TO_CUSTOM_BLOCK.get(id).toCustomBlock(data);
}

if (id < 0) {
Expand All @@ -977,7 +979,17 @@ public static Block get(int fullId, Level level, int x, int y, int z) {
@Deprecated
@DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.3.0.0-PN")
public static Block get(int fullId, Level level, int x, int y, int z, int layer) {
Block block = fullList[fullId].clone();
var id = fullId >> DATA_BITS;
if (id > MAX_BLOCK_ID) {
var block = ID_TO_CUSTOM_BLOCK.get(id).toCustomBlock((~(id << DATA_BITS)) & fullId);
block.x = x;
block.y = y;
block.z = z;
block.level = level;
block.layer = layer;
return block;
}
var block = fullList[fullId].clone();
block.x = x;
block.y = y;
block.z = z;
Expand Down Expand Up @@ -1206,32 +1218,42 @@ public static void registerBlockImplementation(int blockId, @Nonnull Class<? ext
* @param blockClassList 传入自定义方块class List
*/
@PowerNukkitXOnly
@Deprecated(since = "1.19.20-r5")
public static void registerCustomBlock(@Nonnull List<Class<? extends CustomBlock>> blockClassList) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
var map = new HashMap<String, Class<? extends CustomBlock>>(blockClassList.size() + 1, 0.99f);
for (var each : blockClassList) {
map.put(each.getDeclaredConstructor().newInstance().getNamespace(), each);
}
registerCustomBlock(map);
}

/**
* 注册自定义方块
*
* @param blockNamespaceClassMap 传入自定义方块classMap { key: NamespaceID, value: Class }
*/
@PowerNukkitXOnly
public static void registerCustomBlock(@Nonnull Map<String, Class<? extends CustomBlock>> blockNamespaceClassMap) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
if (!Server.getInstance().isEnableExperimentMode()) {
log.warn("The server does not have the experiment mode feature enabled.Unable to register custom block!");
return;
}
//方块升序排序
SortedMap<String, CustomBlock> sortedCustomBlocks = new TreeMap<>(MinecraftNamespaceComparator::compareFNV);
for (var clazz : blockClassList) {
CustomBlock block = clazz.getDeclaredConstructor().newInstance();
sortedCustomBlocks.put(block.getNamespace(), block);
}
SortedMap<String, Class<? extends CustomBlock>> sortedCustomBlockClasses = new TreeMap<>(MinecraftNamespaceComparator::compareFNV);
//监测该方块是否已经注册,如果已经注册将他排除
if (!CUSTOM_BLOCK_ID_MAP.isEmpty()) {
for (var key : sortedCustomBlocks.keySet()) {
if (CUSTOM_BLOCK_ID_MAP.containsKey(key)) {
sortedCustomBlocks.remove(key);
}
for (var entry : blockNamespaceClassMap.entrySet()) {
if (!CUSTOM_BLOCK_ID_MAP.containsKey(entry.getKey())) {
sortedCustomBlockClasses.put(entry.getKey(), entry.getValue());
}
}
//排除后可能为空
if (!sortedCustomBlocks.isEmpty()) {
if (!sortedCustomBlockClasses.isEmpty()) {
//注册各种数据
for (var block : sortedCustomBlocks.values()) {
BLOCK_PROPERTY_DATA.add(block.getBlockPropertyData());//行为包数据
for (var entry : sortedCustomBlockClasses.entrySet()) {
CUSTOM_BLOCK_ID_MAP.put(entry.getKey(), nextBlockId);//自定义方块标识符->自定义方块id
CustomBlock block = entry.getValue().getDeclaredConstructor().newInstance();
ID_TO_CUSTOM_BLOCK.put(nextBlockId, block);//自定义方块id->自定义方块
CUSTOM_BLOCK_ID_MAP.put(block.getNamespace(), nextBlockId);//自定义方块标识符->自定义方块id
BLOCK_PROPERTY_DATA.add(block.getBlockPropertyData());//行为包数据
++nextBlockId;
}
var blocks = ID_TO_CUSTOM_BLOCK.values().stream().toList();
Expand Down Expand Up @@ -1276,7 +1298,7 @@ protected Block() {
@PowerNukkitOnly
@Since("1.4.0.0-PN")
@Nonnull
protected final MutableBlockState getMutableState() {
public final MutableBlockState getMutableState() {
if (mutableState == null) {
mutableState = getProperties().createMutableState(getId());
}
Expand Down
161 changes: 134 additions & 27 deletions src/main/java/cn/nukkit/block/customblock/CustomBlock.java
@@ -1,8 +1,14 @@
package cn.nukkit.block.customblock;

import cn.nukkit.block.Block;
import cn.nukkit.block.BlockFallableMeta;
import cn.nukkit.block.BlockMeta;
import cn.nukkit.block.customblock.type.MaterialsFactory;
import cn.nukkit.blockproperty.*;
import cn.nukkit.item.Item;
import cn.nukkit.nbt.tag.CompoundTag;
import cn.nukkit.math.Vector3;
import cn.nukkit.nbt.tag.*;
import org.jetbrains.annotations.Nullable;

import java.util.Locale;

Expand Down Expand Up @@ -32,12 +38,29 @@ public interface CustomBlock {

double calculateBreakTime();

/* 下面两个方法需要被覆写,请使用接口的定义 */
default int getId() {
return Block.CUSTOM_BLOCK_ID_MAP.get(getNamespace().toLowerCase(Locale.ENGLISH));
}

default String getName() {
return this.getNamespace().split(":")[1].toLowerCase(Locale.ENGLISH);
}

Item toItem();

default Block toCustomBlock() {
return ((Block) this).clone();
}

default Block toCustomBlock(int meta) {
var block = toCustomBlock();
if (block instanceof BlockMeta || block instanceof BlockFallableMeta) {
block.getMutableState().setDataStorageFromInt(meta, true);
}
return block;
}

/**
* 控制自定义方块在创造栏中的分类,默认值construction
*
Expand All @@ -57,65 +80,149 @@ default String getCreativeCategoryGroup() {
}

/**
* 控制自定义方块的渲染方法<p>
* 可选值:<br>opaque<br>alpha_test<br>blend<br>double_sided<p>
* 默认值: "opaque"
* 将几何文件中的face(面)名称映射到实际的材质实例
*/
default String getRenderMethod() {
return "opaque";
default CompoundTag getMaterials() {
return MaterialsFactory.of(new CompoundTag()).process("*", "opaque", getTexture()).build();
}

/**
* 以度为单位设置块围绕立方体中心的旋转,旋转顺序为 xyz.角度必须是90的倍数。
*/
default Vector3 getRotation() {
return new Vector3(0, 0, 0);
}

@Nullable
default ListTag<StringTag> getBlockTags() {
return null;
}

/**
* 控制自定义方块的形状<br>
* Geometry identifier from geo file in 'RP/models/entity' folder
* Geometry identifier from geo file in 'RP/models/blocks' folder
*/
default String getGeometry() {
return "";
}

/* 下面两个方法需要被覆写,请使用接口的定义 */
default int getId() {
return Block.CUSTOM_BLOCK_ID_MAP.get(getNamespace().toLowerCase(Locale.ENGLISH));
/**
* 控制自定义方块的客户端状态
*
* @return Permutations NBT Tag
*/
@Nullable
default ListTag<CompoundTag> getPermutations() {
return null;
}

default String getName() {
return this.getNamespace().split(":")[1].toLowerCase(Locale.ENGLISH);
/**
* 获取方块属性NBT定义
*
* @return BlockProperties in NBT Tag format
*/
@Nullable
default ListTag<CompoundTag> getPropertiesNBT() {
if (this instanceof Block block) {
var properties = block.getProperties();
if (properties == CommonBlockProperties.EMPTY_PROPERTIES || properties.getAllProperties().size() == 0) {
return null;
}
var nbtList = new ListTag<CompoundTag>("properties");
for (var each : properties.getAllProperties()) {
if (each.getProperty() instanceof BooleanBlockProperty booleanBlockProperty) {
nbtList.add(new CompoundTag().putString("name", booleanBlockProperty.getName())
.putList(new ListTag<>("enum")
.add(new IntTag("", 0))
.add(new IntTag("", 1))));
} else if (each.getProperty() instanceof IntBlockProperty intBlockProperty) {
var enumList = new ListTag<IntTag>("enum");
for (int i = intBlockProperty.getMinValue(); i < intBlockProperty.getMaxValue(); i++) {
enumList.add(new IntTag("", i));
}
nbtList.add(new CompoundTag().putString("name", intBlockProperty.getName()).putList(enumList));
} else if (each.getProperty() instanceof UnsignedIntBlockProperty unsignedIntBlockProperty) {
var enumList = new ListTag<LongTag>("enum");
for (long i = unsignedIntBlockProperty.getMinValue(); i < unsignedIntBlockProperty.getMaxValue(); i++) {
enumList.add(new LongTag("", i));
}
nbtList.add(new CompoundTag().putString("name", unsignedIntBlockProperty.getName()).putList(enumList));
} else if (each.getProperty() instanceof ArrayBlockProperty<?> arrayBlockProperty) {
var enumList = new ListTag<StringTag>("enum");
for (var e : arrayBlockProperty.getUniverse()) {
enumList.add(new StringTag("", e.toString()));
}
nbtList.add(new CompoundTag().putString("name", arrayBlockProperty.getName()).putList(enumList));
}
}
return nbtList;
}
return null;
}

/**
* 对自动生成的ComponentNBT进行处理
*
* @param componentNBT 自动生成的component NBT
* @return 处理后的ComponentNBT
*/
default CompoundTag componentNBTProcessor(CompoundTag componentNBT) {
return componentNBT;
}

default BlockPropertyData getBlockPropertyData() {
//内部处理components组件
var compoundTag = new CompoundTag()
.putCompound("minecraft:creative_category", new CompoundTag()
.putString("category", this.getCreativeCategory()))
.putCompound("minecraft:friction", new CompoundTag()
.putFloat("value", (float) this.getFrictionFactor()))
.putCompound("minecraft:explosion_resistance", new CompoundTag()
.putInt("value", (int) this.getResistance()))
.putCompound("minecraft:block_light_absorption", new CompoundTag()
.putFloat("value", (float) this.getLightFilter() / 15))
.putCompound("minecraft:block_light_filter", new CompoundTag()
.putFloat("lightLevel", (byte) this.getLightFilter()))
.putCompound("minecraft:light_emission", new CompoundTag()
.putByte("emission", (byte) this.getLightLevel()))
.putCompound("minecraft:rotation", new CompoundTag()
.putFloat("x", 0)
.putFloat("y", 0)
.putFloat("z", 0))
.putFloat("x", (float) this.getRotation().x)
.putFloat("y", (float) this.getRotation().y)
.putFloat("z", (float) this.getRotation().z))
.putCompound("minecraft:destructible_by_mining", new CompoundTag()
.putFloat("value", (float) (this.calculateBreakTime() * 2 / 3)))
.putCompound("minecraft:material_instances", new CompoundTag()
.putCompound("mappings", new CompoundTag())
.putCompound("materials", new CompoundTag()
.putCompound("*", new CompoundTag()
.putBoolean("ambient_occlusion", true)
.putBoolean("face_dimming", true)
.putString("render_method", this.getRenderMethod())
.putString("texture", this.getTexture()))));
.putFloat("value", (float) (this.calculateBreakTime() * 2 / 3)));
if (this.getTexture() != null) {
compoundTag.putCompound("minecraft:material_instances", new CompoundTag()
.putCompound("mappings", new CompoundTag())
.putCompound("materials", this.getMaterials()));
}
if (!this.getCreativeCategoryGroup().isEmpty()) {
compoundTag.getCompound("minecraft:creative_category").putString("group", this.getCreativeCategoryGroup());
}
//设置方块对应的几何模型,需要在资源包定义
if (!this.getGeometry().isEmpty()) {
compoundTag.putCompound("minecraft:geometry", new CompoundTag()
.putString("value", this.getGeometry()));
}
return new BlockPropertyData(this.getNamespace().toLowerCase(Locale.ENGLISH), new CompoundTag().putCompound("components", compoundTag));
//提供外部操作components组件
compoundTag = componentNBTProcessor(compoundTag);
//方块components
var nbt = new CompoundTag()
.putCompound("components", compoundTag);
//方块BlockTags
if (getBlockTags() != null) nbt.putList(getBlockTags());
//设置方块的permutations
if (getPermutations() != null) {
var permutations = getPermutations();
permutations.setName("permutations");
nbt.putList(permutations);
}
//设置方块的properties
var propertiesNBT = getPropertiesNBT();
if (propertiesNBT != null) {
propertiesNBT.setName("properties");
nbt.putList(propertiesNBT);
}
//molang版本
// nbt.putInt("molangVersion", 6);
return new BlockPropertyData(this.getNamespace(), nbt);
}
}