2021-05-22 21:56:01 +02:00
|
|
|
package net.minestom.server.instance.block;
|
|
|
|
|
2021-07-10 18:42:02 +02:00
|
|
|
import com.github.benmanes.caffeine.cache.Cache;
|
|
|
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
2022-01-25 07:43:52 +01:00
|
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
|
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
|
2021-06-23 17:41:46 +02:00
|
|
|
import net.minestom.server.registry.Registry;
|
|
|
|
import net.minestom.server.tag.Tag;
|
2021-12-30 12:07:16 +01:00
|
|
|
import net.minestom.server.utils.ArrayUtils;
|
2021-07-30 17:16:52 +02:00
|
|
|
import net.minestom.server.utils.block.BlockUtils;
|
2022-02-04 22:28:05 +01:00
|
|
|
import net.minestom.server.utils.collection.MergedMap;
|
2022-03-07 13:49:56 +01:00
|
|
|
import net.minestom.server.utils.collection.ObjectArray;
|
2021-06-23 17:41:46 +02:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
2022-01-25 07:43:52 +01:00
|
|
|
import org.jetbrains.annotations.Unmodifiable;
|
2021-06-23 17:41:46 +02:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
2021-12-13 16:41:30 +01:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
2021-05-22 21:56:01 +02:00
|
|
|
|
2021-07-10 20:26:30 +02:00
|
|
|
import java.time.Duration;
|
2022-01-25 13:33:14 +01:00
|
|
|
import java.util.*;
|
2021-07-24 03:31:03 +02:00
|
|
|
import java.util.function.Function;
|
2021-05-22 21:56:01 +02:00
|
|
|
|
2021-12-22 01:30:10 +01:00
|
|
|
record BlockImpl(@NotNull Registry.BlockEntry registry,
|
2022-02-04 22:30:30 +01:00
|
|
|
byte @NotNull [] propertiesArray,
|
2021-12-22 01:30:10 +01:00
|
|
|
@Nullable NBTCompound nbt,
|
|
|
|
@Nullable BlockHandler handler) implements Block {
|
2021-07-30 17:16:52 +02:00
|
|
|
// Block state -> block object
|
2021-11-06 14:07:42 +01:00
|
|
|
private static final ObjectArray<Block> BLOCK_STATE_MAP = new ObjectArray<>();
|
2022-01-25 07:43:52 +01:00
|
|
|
// Block id -> valid property keys (order is important for lookup)
|
2022-03-07 13:49:56 +01:00
|
|
|
private static final ObjectArray<PropertyType[]> PROPERTIES_TYPE = new ObjectArray<>();
|
2022-01-25 07:43:52 +01:00
|
|
|
// Block id -> Map<PropertiesValues, Block>
|
|
|
|
private static final ObjectArray<Map<PropertiesHolder, BlockImpl>> POSSIBLE_STATES = new ObjectArray<>();
|
2022-01-14 10:45:44 +01:00
|
|
|
private static final Registry.Container<Block> CONTAINER = Registry.createContainer(Registry.Resource.BLOCKS,
|
2022-02-04 22:28:05 +01:00
|
|
|
(namespace, properties) -> {
|
|
|
|
final int blockId = properties.getInt("id");
|
|
|
|
final Registry.Properties stateObject = properties.section("states");
|
2022-01-25 07:43:52 +01:00
|
|
|
|
|
|
|
// Retrieve properties
|
2022-03-07 13:49:56 +01:00
|
|
|
PropertyType[] propertyTypes;
|
2022-01-25 07:43:52 +01:00
|
|
|
{
|
2022-02-04 22:28:05 +01:00
|
|
|
Registry.Properties stateProperties = properties.section("properties");
|
|
|
|
if (stateProperties != null) {
|
|
|
|
final int stateCount = stateProperties.size();
|
2022-03-07 13:49:56 +01:00
|
|
|
propertyTypes = new PropertyType[stateCount];
|
2022-01-25 07:43:52 +01:00
|
|
|
int i = 0;
|
2022-02-04 22:28:05 +01:00
|
|
|
for (var entry : stateProperties) {
|
2022-03-07 13:49:56 +01:00
|
|
|
final var k = entry.getKey();
|
2022-01-25 13:33:14 +01:00
|
|
|
final var v = (List<String>) entry.getValue();
|
2022-03-07 13:49:56 +01:00
|
|
|
propertyTypes[i++] = new PropertyType(k, v);
|
2022-01-25 07:43:52 +01:00
|
|
|
}
|
2022-03-07 13:49:56 +01:00
|
|
|
} else {
|
|
|
|
propertyTypes = new PropertyType[0];
|
2022-01-25 07:43:52 +01:00
|
|
|
}
|
|
|
|
}
|
2022-03-07 13:49:56 +01:00
|
|
|
PROPERTIES_TYPE.set(blockId, propertyTypes);
|
2022-01-25 07:43:52 +01:00
|
|
|
|
|
|
|
// Retrieve block states
|
2021-12-30 12:07:16 +01:00
|
|
|
{
|
2022-02-04 22:28:05 +01:00
|
|
|
final int propertiesCount = stateObject.size();
|
2022-01-25 07:43:52 +01:00
|
|
|
PropertiesHolder[] propertiesKeys = new PropertiesHolder[propertiesCount];
|
|
|
|
BlockImpl[] blocksValues = new BlockImpl[propertiesCount];
|
2021-12-30 12:07:16 +01:00
|
|
|
int propertiesOffset = 0;
|
2022-02-04 22:28:05 +01:00
|
|
|
for (var stateEntry : stateObject) {
|
2021-12-30 12:07:16 +01:00
|
|
|
final String query = stateEntry.getKey();
|
|
|
|
final var stateOverride = (Map<String, Object>) stateEntry.getValue();
|
|
|
|
final var propertyMap = BlockUtils.parseProperties(query);
|
2022-03-07 13:49:56 +01:00
|
|
|
assert propertyTypes.length == propertyMap.size();
|
|
|
|
byte[] propertiesArray = new byte[propertyTypes.length];
|
2022-01-25 07:43:52 +01:00
|
|
|
for (var entry : propertyMap.entrySet()) {
|
2022-03-07 13:49:56 +01:00
|
|
|
final byte keyIndex = findKeyIndex(propertyTypes, entry.getKey(), null);
|
|
|
|
final byte valueIndex = findValueIndex(propertyTypes[keyIndex], entry.getValue(), null);
|
2022-01-25 14:14:01 +01:00
|
|
|
propertiesArray[keyIndex] = valueIndex;
|
2022-01-25 07:43:52 +01:00
|
|
|
}
|
|
|
|
|
2022-03-09 17:51:03 +01:00
|
|
|
var mainProperties = Registry.Properties.fromMap(new MergedMap<>(stateOverride, properties.asMap()));
|
2022-02-04 22:28:05 +01:00
|
|
|
final BlockImpl block = new BlockImpl(Registry.block(namespace, mainProperties),
|
2022-01-25 07:43:52 +01:00
|
|
|
propertiesArray, null, null);
|
2021-12-30 12:07:16 +01:00
|
|
|
BLOCK_STATE_MAP.set(block.stateId(), block);
|
2022-01-25 07:43:52 +01:00
|
|
|
propertiesKeys[propertiesOffset] = new PropertiesHolder(propertiesArray);
|
2021-12-30 12:07:16 +01:00
|
|
|
blocksValues[propertiesOffset++] = block;
|
|
|
|
}
|
2022-01-25 07:43:52 +01:00
|
|
|
POSSIBLE_STATES.set(blockId, ArrayUtils.toMap(propertiesKeys, blocksValues, propertiesOffset));
|
2021-07-30 17:34:39 +02:00
|
|
|
}
|
|
|
|
// Register default state
|
2022-02-04 22:28:05 +01:00
|
|
|
final int defaultState = properties.getInt("defaultStateId");
|
2022-01-14 10:45:44 +01:00
|
|
|
return getState(defaultState);
|
2021-07-30 17:16:52 +02:00
|
|
|
});
|
2021-07-30 17:34:39 +02:00
|
|
|
private static final Cache<NBTCompound, NBTCompound> NBT_CACHE = Caffeine.newBuilder()
|
|
|
|
.expireAfterWrite(Duration.ofMinutes(5))
|
|
|
|
.weakValues()
|
|
|
|
.build();
|
2021-07-30 17:16:52 +02:00
|
|
|
|
2021-10-17 14:29:27 +02:00
|
|
|
static {
|
2022-03-07 13:49:56 +01:00
|
|
|
PROPERTIES_TYPE.trim();
|
2021-10-17 14:29:27 +02:00
|
|
|
BLOCK_STATE_MAP.trim();
|
2021-12-22 01:30:10 +01:00
|
|
|
POSSIBLE_STATES.trim();
|
2021-10-17 14:29:27 +02:00
|
|
|
}
|
|
|
|
|
2021-07-30 17:16:52 +02:00
|
|
|
static Block get(@NotNull String namespace) {
|
|
|
|
return CONTAINER.get(namespace);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Block getSafe(@NotNull String namespace) {
|
|
|
|
return CONTAINER.getSafe(namespace);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Block getId(int id) {
|
|
|
|
return CONTAINER.getId(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Block getState(int stateId) {
|
|
|
|
return BLOCK_STATE_MAP.get(stateId);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Collection<Block> values() {
|
|
|
|
return CONTAINER.values();
|
|
|
|
}
|
|
|
|
|
2021-06-23 17:41:46 +02:00
|
|
|
@Override
|
|
|
|
public @NotNull Block withProperty(@NotNull String property, @NotNull String value) {
|
2022-03-07 13:49:56 +01:00
|
|
|
final PropertyType[] propertyTypes = PROPERTIES_TYPE.get(id());
|
|
|
|
assert propertyTypes != null;
|
|
|
|
final byte keyIndex = findKeyIndex(propertyTypes, property, this);
|
|
|
|
final byte valueIndex = findValueIndex(propertyTypes[keyIndex], value, this);
|
2022-01-25 07:43:52 +01:00
|
|
|
var properties = this.propertiesArray.clone();
|
2022-01-25 13:33:14 +01:00
|
|
|
properties[keyIndex] = valueIndex;
|
2021-07-23 16:14:42 +02:00
|
|
|
return compute(properties);
|
2021-07-19 23:59:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NotNull Block withProperties(@NotNull Map<@NotNull String, @NotNull String> properties) {
|
2021-09-20 18:09:16 +02:00
|
|
|
if (properties.isEmpty()) return this;
|
2022-03-07 13:49:56 +01:00
|
|
|
final PropertyType[] propertyTypes = PROPERTIES_TYPE.get(id());
|
|
|
|
assert propertyTypes != null;
|
2022-02-04 22:30:30 +01:00
|
|
|
byte[] result = this.propertiesArray.clone();
|
2022-01-25 07:43:52 +01:00
|
|
|
for (var entry : properties.entrySet()) {
|
2022-03-07 13:49:56 +01:00
|
|
|
final byte keyIndex = findKeyIndex(propertyTypes, entry.getKey(), this);
|
|
|
|
final byte valueIndex = findValueIndex(propertyTypes[keyIndex], entry.getValue(), this);
|
2022-01-25 13:33:14 +01:00
|
|
|
result[keyIndex] = valueIndex;
|
2021-07-19 23:59:40 +02:00
|
|
|
}
|
2022-01-25 07:43:52 +01:00
|
|
|
return compute(result);
|
2021-06-23 17:41:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-06-26 20:23:56 +02:00
|
|
|
public @NotNull <T> Block withTag(@NotNull Tag<T> tag, @Nullable T value) {
|
2021-12-13 16:41:30 +01:00
|
|
|
var temporaryNbt = new MutableNBTCompound(Objects.requireNonNullElse(nbt, NBTCompound.EMPTY));
|
2021-07-24 03:31:03 +02:00
|
|
|
tag.write(temporaryNbt, value);
|
2021-12-13 16:41:30 +01:00
|
|
|
final var finalNbt = temporaryNbt.getSize() > 0 ? NBT_CACHE.get(temporaryNbt.toCompound(), Function.identity()) : null;
|
2022-01-25 07:43:52 +01:00
|
|
|
return new BlockImpl(registry, propertiesArray, finalNbt, handler);
|
2021-06-23 17:41:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NotNull Block withHandler(@Nullable BlockHandler handler) {
|
2022-01-25 07:43:52 +01:00
|
|
|
return new BlockImpl(registry, propertiesArray, nbt, handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @Unmodifiable @NotNull Map<String, String> properties() {
|
2022-03-07 13:49:56 +01:00
|
|
|
final PropertyType[] propertyTypes = PROPERTIES_TYPE.get(id());
|
|
|
|
assert propertyTypes != null;
|
|
|
|
final int length = propertyTypes.length;
|
|
|
|
String[] keys = new String[length];
|
|
|
|
String[] values = new String[length];
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
|
|
var property = propertyTypes[i];
|
|
|
|
keys[i] = property.key();
|
|
|
|
values[i] = property.values().get(propertiesArray[i]);
|
2022-01-25 13:33:14 +01:00
|
|
|
}
|
2022-03-07 13:49:56 +01:00
|
|
|
return Map.class.cast(Object2ObjectMaps.unmodifiable(new Object2ObjectArrayMap<>(keys, values, length)));
|
2021-06-23 17:41:46 +02:00
|
|
|
}
|
|
|
|
|
2021-10-25 16:17:12 +02:00
|
|
|
@Override
|
|
|
|
public @NotNull Collection<@NotNull Block> possibleStates() {
|
2022-01-25 07:43:52 +01:00
|
|
|
return Collection.class.cast(possibleProperties().values());
|
2021-10-25 16:17:12 +02:00
|
|
|
}
|
|
|
|
|
2021-06-23 17:41:46 +02:00
|
|
|
@Override
|
|
|
|
public <T> @Nullable T getTag(@NotNull Tag<T> tag) {
|
2022-01-02 06:08:28 +01:00
|
|
|
return tag.read(Objects.requireNonNullElse(nbt, NBTCompound.EMPTY));
|
2021-07-23 15:44:53 +02:00
|
|
|
}
|
|
|
|
|
2022-01-25 07:43:52 +01:00
|
|
|
private Map<PropertiesHolder, BlockImpl> possibleProperties() {
|
2021-12-22 01:30:10 +01:00
|
|
|
return POSSIBLE_STATES.get(id());
|
2021-08-11 21:28:13 +02:00
|
|
|
}
|
|
|
|
|
2021-08-01 13:01:56 +02:00
|
|
|
@Override
|
|
|
|
public String toString() {
|
2022-01-25 07:43:52 +01:00
|
|
|
return String.format("%s{properties=%s, nbt=%s, handler=%s}", name(), properties(), nbt, handler);
|
2021-08-01 13:01:56 +02:00
|
|
|
}
|
|
|
|
|
2022-01-25 07:43:52 +01:00
|
|
|
@Override
|
|
|
|
public boolean equals(Object o) {
|
|
|
|
if (this == o) return true;
|
|
|
|
if (!(o instanceof BlockImpl block)) return false;
|
|
|
|
return stateId() == block.stateId() && Objects.equals(nbt, block.nbt) && Objects.equals(handler, block.handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
|
|
|
return Objects.hash(stateId(), nbt, handler);
|
|
|
|
}
|
|
|
|
|
2022-02-04 22:30:30 +01:00
|
|
|
private Block compute(byte[] properties) {
|
2022-01-25 07:43:52 +01:00
|
|
|
if (Arrays.equals(propertiesArray, properties)) return this;
|
2022-03-07 22:18:03 +01:00
|
|
|
final BlockImpl block = possibleProperties().get(new PropertiesHolder(properties));
|
|
|
|
assert block != null;
|
2022-01-25 07:43:52 +01:00
|
|
|
return nbt == null && handler == null ? block : new BlockImpl(block.registry(), block.propertiesArray, nbt, handler);
|
|
|
|
}
|
|
|
|
|
2022-03-07 13:49:56 +01:00
|
|
|
private static byte findKeyIndex(PropertyType[] properties, String key, BlockImpl block) {
|
|
|
|
for (byte i = 0; i < properties.length; i++) {
|
|
|
|
if (properties[i].key().equals(key)) return i;
|
|
|
|
}
|
|
|
|
if (block != null) {
|
|
|
|
throw new IllegalArgumentException("Property " + key + " is not valid for block " + block);
|
|
|
|
} else {
|
|
|
|
throw new IllegalArgumentException("Unknown property key: " + key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static byte findValueIndex(PropertyType propertyType, String value, BlockImpl block) {
|
|
|
|
final List<String> values = propertyType.values();
|
|
|
|
final byte index = (byte) values.indexOf(value);
|
|
|
|
if (index != -1) return index;
|
|
|
|
if (block != null) {
|
|
|
|
throw new IllegalArgumentException("Property " + propertyType.key() + " value " + value + " is not valid for block " + block);
|
|
|
|
} else {
|
|
|
|
throw new IllegalArgumentException("Unknown property value: " + value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private record PropertyType(String key, List<String> values) {
|
|
|
|
}
|
|
|
|
|
2022-01-25 07:43:52 +01:00
|
|
|
private static final class PropertiesHolder {
|
2022-02-04 22:30:30 +01:00
|
|
|
private final byte[] properties;
|
2022-01-25 07:43:52 +01:00
|
|
|
private final int hashCode;
|
|
|
|
|
2022-02-04 22:30:30 +01:00
|
|
|
public PropertiesHolder(byte[] properties) {
|
2022-01-25 07:43:52 +01:00
|
|
|
this.properties = properties;
|
|
|
|
this.hashCode = Arrays.hashCode(properties);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object o) {
|
|
|
|
if (this == o) return true;
|
|
|
|
if (!(o instanceof PropertiesHolder that)) return false;
|
|
|
|
return Arrays.equals(properties, that.properties);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
|
|
|
return hashCode;
|
|
|
|
}
|
2021-05-22 21:56:01 +02:00
|
|
|
}
|
2021-06-23 17:41:46 +02:00
|
|
|
}
|