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
3 changes: 0 additions & 3 deletions src/main/java/cn/nukkit/Player.java
Expand Up @@ -2699,9 +2699,6 @@ 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)
);
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
122 changes: 109 additions & 13 deletions src/main/java/cn/nukkit/block/customblock/CustomBlock.java
@@ -1,8 +1,13 @@
package cn.nukkit.block.customblock;

import cn.nukkit.block.Block;
import cn.nukkit.block.BlockFallableMeta;
import cn.nukkit.block.BlockMeta;
import cn.nukkit.block.BlockSolidMeta;
import cn.nukkit.blockproperty.*;
import cn.nukkit.item.Item;
import cn.nukkit.nbt.tag.CompoundTag;
import cn.nukkit.nbt.tag.*;
import org.jetbrains.annotations.Nullable;

import java.util.Locale;

Expand All @@ -19,7 +24,10 @@ public interface CustomBlock {
/**
* 控制自定义方块所用的材质名称<br>(例如材质图片test.png设置test)
*/
String getTexture();
@Nullable
default String getTexture() {
return null;
}

/* 以下几个方法需要被手动覆写 */
double getFrictionFactor();
Expand All @@ -38,6 +46,14 @@ 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 Down Expand Up @@ -73,6 +89,69 @@ default String getGeometry() {
return "";
}

/**
* 控制自定义方块的客户端状态
*
* @return Permutations NBT Tag
*/
@Nullable
default ListTag<CompoundTag> getPermutations() {
return null;
}

/**
* 获取方块属性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 int getId() {
return Block.CUSTOM_BLOCK_ID_MAP.get(getNamespace().toLowerCase(Locale.ENGLISH));
Expand All @@ -82,7 +161,6 @@ default String getName() {
return this.getNamespace().split(":")[1].toLowerCase(Locale.ENGLISH);
}


default BlockPropertyData getBlockPropertyData() {
var compoundTag = new CompoundTag()
.putCompound("minecraft:creative_category", new CompoundTag()
Expand All @@ -100,22 +178,40 @@ default BlockPropertyData getBlockPropertyData() {
.putFloat("y", 0)
.putFloat("z", 0))
.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", new CompoundTag()
.putCompound("*", new CompoundTag()
.putBoolean("ambient_occlusion", true)
.putBoolean("face_dimming", true)
.putString("render_method", this.getRenderMethod())
.putString("texture", this.getTexture()))));
}
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()));
} else {
compoundTag.putCompound("minecraft:geometry", new CompoundTag()
.putString("value", "geometry.blocks"));
}
compoundTag = componentNBTProcessor(compoundTag);
var nbt = new CompoundTag().putCompound("components", compoundTag);
if (getPermutations() != null) {
var permutations = getPermutations();
permutations.setName("permutations");
nbt.putList(permutations);
}
nbt.putInt("molangVersion", 0);
var propertiesNBT = getPropertiesNBT();
if (propertiesNBT != null) {
propertiesNBT.setName("properties");
nbt.putList(propertiesNBT);
}
return new BlockPropertyData(this.getNamespace().toLowerCase(Locale.ENGLISH), new CompoundTag().putCompound("components", compoundTag));
return new BlockPropertyData(this.getNamespace().toLowerCase(Locale.ENGLISH), nbt);
}
}
1 change: 0 additions & 1 deletion src/main/java/cn/nukkit/blockentity/BlockEntity.java
Expand Up @@ -135,7 +135,6 @@ public static BlockEntity createBlockEntity(String type, Position pos, CompoundT
}

public static BlockEntity createBlockEntity(String type, FullChunk chunk, CompoundTag nbt, Object... args) {
type = type.replaceFirst("BlockEntity", ""); //TODO: Remove this after the first release
BlockEntity blockEntity = null;

Class<? extends BlockEntity> clazz = knownBlockEntities.get(type);
Expand Down