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

Merge branch 'dev' into dev

This commit is contained in:
Halogly
2025-10-02 01:53:46 +08:00
committed by GitHub
512 changed files with 26088 additions and 13983 deletions

View File

@@ -21,7 +21,7 @@ dependencies {
implementation("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}")
implementation("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}")
// S3
implementation("net.momirealms:craft-engine-s3:0.4")
implementation("net.momirealms:craft-engine-s3:0.5")
// Util
compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}")
// Adventure
@@ -30,6 +30,8 @@ dependencies {
compileOnly("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") {
exclude("com.google.code.gson", "gson")
}
compileOnly("net.kyori:adventure-text-serializer-legacy:${rootProject.properties["adventure_bundle_version"]}")
compileOnly("net.kyori:adventure-text-serializer-json-legacy-impl:${rootProject.properties["adventure_bundle_version"]}")
// Command
compileOnly("org.incendo:cloud-core:${rootProject.properties["cloud_core_version"]}")
@@ -101,6 +103,10 @@ tasks {
relocate("com.google.common.jimfs", "net.momirealms.craftengine.libraries.jimfs")
relocate("org.apache.commons", "net.momirealms.craftengine.libraries.commons")
relocate("io.leangen.geantyref", "net.momirealms.craftengine.libraries.geantyref")
relocate("io.netty.handler.codec.http", "net.momirealms.craftengine.libraries.netty.handler.codec.http")
relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp")
relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy")
relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
}
}

View File

@@ -8,4 +8,6 @@ public abstract class AbstractAdvancementManager implements AdvancementManager {
public AbstractAdvancementManager(CraftEngine plugin) {
this.plugin = plugin;
}
}

View File

@@ -1,9 +1,14 @@
package net.momirealms.craftengine.core.advancement;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
public interface AdvancementManager extends Manageable {
ConfigParser parser();
void sendToast(Player player, Item<?> icon, Component message, AdvancementType type);
}

View File

@@ -1,66 +1,120 @@
package net.momirealms.craftengine.core.block;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigs;
import net.momirealms.craftengine.core.block.parser.BlockNbtParser;
import net.momirealms.craftengine.core.block.properties.Properties;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.PendingConfigSection;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.pack.allocator.BlockStateCandidate;
import net.momirealms.craftengine.core.pack.allocator.IdAllocator;
import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator;
import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator;
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser;
import net.momirealms.craftengine.core.plugin.config.SectionConfigParser;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.registry.WritableRegistry;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.incendo.cloud.suggestion.Suggestion;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
public abstract class AbstractBlockManager extends AbstractModelGenerator implements BlockManager {
protected final BlockParser blockParser;
// CraftEngine objects
protected final BlockStateMappingParser blockStateMappingParser;
// 根据id获取自定义方块
protected final Map<Key, CustomBlock> byId = new HashMap<>();
// Cached command suggestions
// 缓存的指令建议
protected final List<Suggestion> cachedSuggestions = new ArrayList<>();
// Cached Namespace
// 缓存的使用中的命名空间
protected final Set<String> namespacesInUse = new HashSet<>();
// for mod, real block id -> state models
protected final Map<Key, JsonElement> modBlockStates = new HashMap<>();
// A temporary map that stores the model path of a certain vanilla block state
protected final Map<Integer, JsonElement> tempVanillaBlockStateModels = new Int2ObjectOpenHashMap<>();
// A temporary map used to detect whether the same block state corresponds to multiple models.
protected final Map<Integer, Key> tempRegistryIdConflictMap = new Int2ObjectOpenHashMap<>();
// A temporary map that converts the custom block registered on the server to the vanilla block ID.
protected final Map<Integer, Integer> tempBlockAppearanceConvertor = new Int2IntOpenHashMap();
// Used to store override information of json files
// Map<方块类型, Map<方块状态NBT,模型>>用于生成block state json
protected final Map<Key, Map<String, JsonElement>> blockStateOverrides = new HashMap<>();
// a reverted mapper
// 用于生成mod使用的block state json
protected final Map<Key, JsonElement> modBlockStateOverrides = new HashMap<>();
// 根据外观查找真实状态用于debug指令
protected final Map<Integer, List<Integer>> appearanceToRealState = new Int2ObjectOpenHashMap<>();
// 用于note_block:0这样格式的自动分配
protected final Map<Key, List<BlockStateWrapper>> blockStateArranger = new HashMap<>();
// 全方块状态映射文件,用于网络包映射
protected final int[] blockStateMappings;
// 原版方块状态数量
protected final int vanillaBlockStateCount;
// 注册的大宝贝
protected final DelegatingBlock[] customBlocks;
protected final DelegatingBlockState[] customBlockStates;
protected final Object[] customBlockHolders;
// 自定义状态列表,会随着重载变化
protected final ImmutableBlockState[] immutableBlockStates;
// 倒推缓存
protected final BlockStateCandidate[] autoVisualBlockStateCandidates;
// 用于检测单个外观方块状态是否被绑定了不同模型
protected final JsonElement[] tempVanillaBlockStateModels;
// 临时存储哪些视觉方块被使用了
protected final Set<BlockStateWrapper> tempVisualBlockStatesInUse = new HashSet<>();
protected final Set<Key> tempVisualBlocksInUse = new HashSet<>();
// 声音映射表,和使用了哪些视觉方块有关
protected Map<Key, Key> soundReplacements = Map.of();
// client side block tags
protected Map<Integer, List<String>> clientBoundTags = Map.of();
protected Map<Integer, List<String>> previousClientBoundTags = Map.of();
// Used to automatically arrange block states for strings such as minecraft:note_block:0
protected Map<Key, List<Integer>> blockAppearanceArranger;
protected Map<Key, List<Integer>> realBlockArranger;
protected Map<Key, Integer> internalId2StateId;
protected Map<Key, DelegatingBlock> registeredBlocks;
protected AbstractBlockManager(CraftEngine plugin) {
protected AbstractBlockManager(CraftEngine plugin, int vanillaBlockStateCount, int customBlockCount) {
super(plugin);
this.blockParser = new BlockParser();
this.vanillaBlockStateCount = vanillaBlockStateCount;
this.customBlocks = new DelegatingBlock[customBlockCount];
this.customBlockHolders = new Object[customBlockCount];
this.customBlockStates = new DelegatingBlockState[customBlockCount];
this.immutableBlockStates = new ImmutableBlockState[customBlockCount];
this.blockStateMappings = new int[customBlockCount + vanillaBlockStateCount];
this.autoVisualBlockStateCandidates = new BlockStateCandidate[vanillaBlockStateCount];
this.tempVanillaBlockStateModels = new JsonElement[vanillaBlockStateCount];
this.blockParser = new BlockParser(this.autoVisualBlockStateCandidates);
this.blockStateMappingParser = new BlockStateMappingParser();
Arrays.fill(this.blockStateMappings, -1);
}
@NotNull
@Override
public ImmutableBlockState getImmutableBlockStateUnsafe(int stateId) {
return this.immutableBlockStates[stateId - this.vanillaBlockStateCount];
}
@Nullable
@Override
public ImmutableBlockState getImmutableBlockState(int stateId) {
if (!isVanillaBlockState(stateId)) {
return this.immutableBlockStates[stateId - this.vanillaBlockStateCount];
}
return null;
}
@Override
@@ -68,23 +122,30 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
super.clearModelsToGenerate();
this.clearCache();
this.cachedSuggestions.clear();
this.namespacesInUse.clear();
this.blockStateOverrides.clear();
this.modBlockStates.clear();
this.modBlockStateOverrides.clear();
this.byId.clear();
this.previousClientBoundTags = this.clientBoundTags;
this.clientBoundTags = new HashMap<>();
this.blockStateArranger.clear();
this.appearanceToRealState.clear();
Arrays.fill(this.blockStateMappings, -1);
Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE);
Arrays.fill(this.autoVisualBlockStateCandidates, null);
for (AutoStateGroup autoStateGroup : AutoStateGroup.values()) {
autoStateGroup.reset();
}
}
@Override
public void delayedLoad() {
this.initSuggestions();
this.clearCache();
this.resendTags();
this.processSounds();
this.clearCache();
}
@Override
public Map<Key, CustomBlock> blocks() {
public Map<Key, CustomBlock> loadedBlocks() {
return Collections.unmodifiableMap(this.byId);
}
@@ -93,24 +154,20 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
return Optional.ofNullable(this.byId.get(id));
}
protected void addBlockInternal(Key id, CustomBlock customBlock) {
this.byId.put(id, customBlock);
// generate mod assets
if (Config.generateModAssets()) {
for (ImmutableBlockState state : customBlock.variantProvider().states()) {
this.modBlockStates.put(getBlockOwnerId(state.customBlockState()), this.tempVanillaBlockStateModels.get(state.vanillaBlockState().registryId()));
}
}
public Map<Key, List<BlockStateWrapper>> blockStateArranger() {
return this.blockStateArranger;
}
protected abstract void applyPlatformSettings(ImmutableBlockState state);
@Override
public ConfigParser parser() {
return this.blockParser;
public ConfigParser[] parsers() {
return new ConfigParser[]{this.blockParser, this.blockStateMappingParser};
}
@Override
public Map<Key, JsonElement> modBlockStates() {
return Collections.unmodifiableMap(this.modBlockStates);
return Collections.unmodifiableMap(this.modBlockStateOverrides);
}
@Override
@@ -123,14 +180,24 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
return Collections.unmodifiableCollection(this.cachedSuggestions);
}
@Nullable
public Key replaceSoundIfExist(Key id) {
return this.soundReplacements.get(id);
}
@Override
public Map<Key, Key> soundReplacements() {
return Collections.unmodifiableMap(this.soundReplacements);
}
public Set<String> namespacesInUse() {
return Collections.unmodifiableSet(this.namespacesInUse);
}
protected void clearCache() {
this.tempRegistryIdConflictMap.clear();
this.tempBlockAppearanceConvertor.clear();
this.tempVanillaBlockStateModels.clear();
Arrays.fill(this.tempVanillaBlockStateModels, null);
this.tempVisualBlockStatesInUse.clear();
this.tempVisualBlocksInUse.clear();
}
protected void initSuggestions() {
@@ -154,20 +221,149 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
return Optional.ofNullable(this.appearanceToRealState.get(appearanceStateId)).orElse(List.of());
}
public abstract BlockBehavior createBlockBehavior(CustomBlock customBlock, List<Map<String, Object>> behaviorConfig);
protected abstract void resendTags();
protected abstract boolean isVanillaBlock(Key id);
protected abstract int getBlockRegistryId(Key id);
protected abstract String stateRegistryIdToStateSNBT(int id);
protected abstract Key getBlockOwnerId(int id);
protected abstract CustomBlock.Builder platformBuilder(Key id);
protected abstract void setVanillaBlockTags(Key id, List<String> tags);
public class BlockParser implements ConfigParser {
protected abstract int vanillaBlockStateCount();
protected abstract void processSounds();
protected abstract CustomBlock createCustomBlock(@NotNull Holder.Reference<CustomBlock> holder,
@NotNull BlockStateVariantProvider variantProvider,
@NotNull Map<EventTrigger, List<Function<PlayerOptionalContext>>> events,
@Nullable LootTable<?> lootTable);
public class BlockStateMappingParser extends SectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[]{"block-state-mappings", "block-state-mapping"};
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
public int loadingSequence() {
return LoadingSequence.BLOCK_STATE_MAPPING;
}
@Override
public void parseSection(Pack pack, Path path, Map<String, Object> section) throws LocalizedException {
ExceptionCollector<LocalizedResourceConfigException> exceptionCollector = new ExceptionCollector<>();
for (Map.Entry<String, Object> entry : section.entrySet()) {
String before = entry.getKey();
String after = entry.getValue().toString();
// 先解析为唯一的wrapper
BlockStateWrapper beforeState = createVanillaBlockState(before);
BlockStateWrapper afterState = createVanillaBlockState(after);
if (beforeState == null) {
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block_state_mapping.invalid_state", before));
continue;
}
if (afterState == null) {
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block_state_mapping.invalid_state", after));
continue;
}
int previous = AbstractBlockManager.this.blockStateMappings[beforeState.registryId()];
if (previous != -1 && previous != afterState.registryId()) {
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block_state_mapping.conflict",
beforeState.toString(),
afterState.toString(),
BlockRegistryMirror.byId(previous).toString()));
continue;
}
AbstractBlockManager.this.blockStateMappings[beforeState.registryId()] = afterState.registryId();
Key blockOwnerId = getBlockOwnerId(beforeState);
List<BlockStateWrapper> blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(blockOwnerId, k -> new ArrayList<>());
blockStateWrappers.add(beforeState);
AbstractBlockManager.this.autoVisualBlockStateCandidates[beforeState.registryId()] = createVisualBlockCandidate(beforeState);
}
exceptionCollector.throwIfPresent();
}
@Nullable
public BlockStateCandidate createVisualBlockCandidate(BlockStateWrapper blockState) {
List<AutoStateGroup> groups = AutoStateGroup.findGroups(blockState);
if (!groups.isEmpty()) {
BlockStateCandidate candidate = new BlockStateCandidate(blockState);
for (AutoStateGroup group : groups) {
group.addCandidate(candidate);
}
return candidate;
}
return null;
}
}
public class BlockParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"};
private final IdAllocator internalIdAllocator;
private final VisualBlockStateAllocator visualBlockStateAllocator;
private final List<PendingConfigSection> pendingConfigSections = new ArrayList<>();
public BlockParser(BlockStateCandidate[] candidates) {
this.internalIdAllocator = new IdAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("custom-block-states.json"));
this.visualBlockStateAllocator = new VisualBlockStateAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("visual-block-states.json"), candidates, AbstractBlockManager.this::createVanillaBlockState);
}
public void addPendingConfigSection(PendingConfigSection section) {
this.pendingConfigSections.add(section);
}
public IdAllocator internalIdAllocator() {
return internalIdAllocator;
}
public VisualBlockStateAllocator visualBlockStateAllocator() {
return visualBlockStateAllocator;
}
@Override
public void postProcess() {
this.internalIdAllocator.processPendingAllocations();
try {
this.internalIdAllocator.saveToCache();
} catch (IOException e) {
AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block states allocation", e);
}
this.visualBlockStateAllocator.processPendingAllocations();
try {
this.visualBlockStateAllocator.saveToCache();
} catch (IOException e) {
AbstractBlockManager.this.plugin.logger().warn("Error while saving visual block states allocation", e);
}
}
@Override
public void preProcess() {
this.internalIdAllocator.reset(0, Config.serverSideBlocks() - 1);
this.visualBlockStateAllocator.reset();
try {
this.visualBlockStateAllocator.loadFromCache();
} catch (IOException e) {
AbstractBlockManager.this.plugin.logger().warn("Error while loading visual block states allocation cache", e);
}
try {
this.internalIdAllocator.loadFromCache();
} catch (IOException e) {
AbstractBlockManager.this.plugin.logger().warn("Error while loading custom block states allocation cache", e);
}
for (PendingConfigSection section : this.pendingConfigSections) {
ResourceConfigUtils.runCatching(
section.path(),
section.node(),
() -> parseSection(section.pack(), section.path(), section.node(), section.id(), section.config()),
() -> GsonHelper.get().toJson(section.config())
);
}
this.pendingConfigSections.clear();
}
@Override
public String[] sectionId() {
@@ -180,114 +376,286 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (isVanillaBlock(id)) {
parseVanillaBlock(pack, path, id, section);
parseVanillaBlock(id, section);
} else {
// check duplicated config
if (AbstractBlockManager.this.byId.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.block.duplicate");
}
parseCustomBlock(pack, path, id, section);
parseCustomBlock(path, node, id, section);
}
}
private void parseVanillaBlock(Pack pack, Path path, Key id, Map<String, Object> section) {
private void parseVanillaBlock(Key id, Map<String, Object> section) {
Map<String, Object> settings = MiscUtils.castToMap(section.get("settings"), true);
if (settings != null) {
Object clientBoundTags = settings.get("client-bound-tags");
if (clientBoundTags instanceof List<?> list) {
List<String> clientSideTags = MiscUtils.getAsStringList(list).stream().filter(ResourceLocation::isValid).toList();
AbstractBlockManager.this.clientBoundTags.put(getBlockRegistryId(id), clientSideTags);
AbstractBlockManager.this.setVanillaBlockTags(id, clientSideTags);
}
}
}
private void parseCustomBlock(Pack pack, Path path, Key id, Map<String, Object> section) {
// 获取方块设置
private void parseCustomBlock(Path path, String node, Key id, Map<String, Object> section) {
// 获取共享方块设置
BlockSettings settings = BlockSettings.fromMap(id, MiscUtils.castToMap(section.get("settings"), true));
// 读取基础外观配置
Map<String, Property<?>> properties;
Map<String, Integer> appearances;
Map<String, BlockStateVariant> variants;
// 读取states区域
Map<String, Object> stateSection = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(
ResourceConfigUtils.get(section, "state", "states"), "warning.config.block.missing_state"), true);
Map<String, Object> stateSection = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "state", "states"), "warning.config.block.missing_state"), true);
boolean singleState = !stateSection.containsKey("properties");
// 单方块状态
if (singleState) {
int internalId = ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(
stateSection.get("id"), "warning.config.block.state.missing_real_id"), "id");
// 获取原版外观的注册表id
int appearanceId = pluginFormattedBlockStateToRegistryId(ResourceConfigUtils.requireNonEmptyStringOrThrow(
stateSection.get("state"), "warning.config.block.state.missing_state"));
// 为原版外观赋予外观模型并检查模型冲突
this.arrangeModelForStateAndVerify(appearanceId, ResourceConfigUtils.get(stateSection, "model", "models"));
// 设置参数
properties = Map.of();
appearances = Map.of("", appearanceId);
variants = Map.of("", new BlockStateVariant("", settings, getInternalBlockId(internalId, appearanceId)));
}
// 多方块状态
else {
properties = parseBlockProperties(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("properties"), "warning.config.block.state.missing_properties"), "properties"));
appearances = parseBlockAppearances(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances"));
variants = parseBlockVariants(
ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), "variants"),
it -> appearances.getOrDefault(it, -1), settings
);
}
// 读取方块的property通过property决定
Map<String, Property<?>> properties = singleState ? Map.of() : parseBlockProperties(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("properties"), "warning.config.block.state.missing_properties"), "properties"));
// 注册方块容器
Holder.Reference<CustomBlock> holder = ((WritableRegistry<CustomBlock>) BuiltInRegistries.BLOCK).getOrRegisterForHolder(ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), id));
// 先绑定无效方块
holder.bindValue(new InactiveCustomBlock(holder));
addBlockInternal(id, platformBuilder(id)
.appearances(appearances)
.variantMapper(variants)
.properties(properties)
.settings(settings)
.lootTable(LootTable.fromMap(ResourceConfigUtils.getAsMapOrNull(section.get("loot"), "loot")))
.behavior(MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors")))
.events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")))
.build());
}
// 根据properties生成variant provider
BlockStateVariantProvider variantProvider = new BlockStateVariantProvider(holder, (owner, propertyMap) -> {
ImmutableBlockState blockState = new ImmutableBlockState(owner, propertyMap);
blockState.setSettings(settings);
return blockState;
}, properties);
private Map<String, BlockStateVariant> parseBlockVariants(Map<String, Object> variantsSection,
Function<String, Integer> appearanceValidator,
BlockSettings parentSettings) {
Map<String, BlockStateVariant> variants = new HashMap<>();
for (Map.Entry<String, Object> entry : variantsSection.entrySet()) {
Map<String, Object> variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey());
String variantNBT = entry.getKey();
String appearance = ResourceConfigUtils.requireNonEmptyStringOrThrow(variantSection.get("appearance"), "warning.config.block.state.variant.missing_appearance");
int appearanceId = appearanceValidator.apply(appearance);
if (appearanceId == -1) {
throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearance);
ImmutableList<ImmutableBlockState> states = variantProvider.states();
List<CompletableFuture<Integer>> internalIdAllocators = new ArrayList<>(states.size());
// 如果用户指定了起始id
if (stateSection.containsKey("id")) {
int startingId = ResourceConfigUtils.getAsInt(stateSection.get("id"), "id");
int endingId = startingId + states.size() - 1;
if (startingId < 0 || endingId >= Config.serverSideBlocks()) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_id", startingId + "~" + endingId, String.valueOf(Config.serverSideBlocks() - 1));
}
// 先检测范围冲突
List<Pair<String, Integer>> conflicts = this.internalIdAllocator.getFixedIdsBetween(startingId, endingId);
if (!conflicts.isEmpty()) {
ExceptionCollector<LocalizedResourceConfigException> exceptionCollector = new ExceptionCollector<>();
for (Pair<String, Integer> conflict : conflicts) {
int internalId = conflict.right();
int index = internalId - startingId;
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.id.conflict", states.get(index).toString(), conflict.left(), BlockManager.createCustomBlockKey(internalId).toString()));
}
exceptionCollector.throwIfPresent();
}
// 强行分配id
for (ImmutableBlockState blockState : states) {
String blockStateId = blockState.toString();
internalIdAllocators.add(this.internalIdAllocator.assignFixedId(blockStateId, startingId++));
}
int internalId = getInternalBlockId(ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(variantSection.get("id"), "warning.config.block.state.missing_real_id"), "id"), appearanceId);
Map<String, Object> anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings");
variants.put(variantNBT, new BlockStateVariant(appearance, anotherSetting == null ? parentSettings : BlockSettings.ofFullCopy(parentSettings, anotherSetting), internalId));
}
return variants;
// 未指定,则使用自动分配
else {
for (ImmutableBlockState blockState : states) {
String blockStateId = blockState.toString();
internalIdAllocators.add(this.internalIdAllocator.requestAutoId(blockStateId));
}
}
CompletableFutures.allOf(internalIdAllocators).whenComplete((v1, t1) -> ResourceConfigUtils.runCatching(path, node, () -> {
if (t1 != null) {
if (t1 instanceof CompletionException e) {
Throwable cause = e.getCause();
// 这里不会有conflict了因为之前已经判断过了
if (cause instanceof IdAllocator.IdExhaustedException) {
throw new LocalizedResourceConfigException("warning.config.block.state.id.exhausted");
} else {
Debugger.BLOCK.warn(() -> "Unknown error while allocating internal block state id.", cause);
return;
}
}
throw new RuntimeException("Unknown error occurred", t1);
}
for (int i = 0; i < internalIdAllocators.size(); i++) {
CompletableFuture<Integer> future = internalIdAllocators.get(i);
try {
int internalId = future.get();
states.get(i).setCustomBlockState(BlockRegistryMirror.byId(internalId + AbstractBlockManager.this.vanillaBlockStateCount));
} catch (InterruptedException | ExecutionException e) {
AbstractBlockManager.this.plugin.logger().warn("Interrupted while allocating internal block state for block " + id.asString(), e);
return;
}
}
// 创建自定义方块
AbstractCustomBlock customBlock = (AbstractCustomBlock) createCustomBlock(
holder,
variantProvider,
EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")),
LootTable.fromMap(ResourceConfigUtils.getAsMapOrNull(section.get("loot"), "loot"))
);
BlockBehavior blockBehavior = createBlockBehavior(customBlock, MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors")));
Map<String, Map<String, Object>> appearanceConfigs;
Map<String, CompletableFuture<BlockStateWrapper>> futureVisualStates = new HashMap<>();
if (singleState) {
appearanceConfigs = Map.of("", stateSection);
} else {
Map<String, Object> rawAppearancesSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances");
appearanceConfigs = new LinkedHashMap<>(4);
for (Map.Entry<String, Object> entry : rawAppearancesSection.entrySet()) {
appearanceConfigs.put(entry.getKey(), ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()));
}
}
for (Map.Entry<String, Map<String, Object>> entry : appearanceConfigs.entrySet()) {
Map<String, Object> appearanceSection = entry.getValue();
if (appearanceSection.containsKey("state")) {
String appearanceName = entry.getKey();
futureVisualStates.put(
appearanceName,
this.visualBlockStateAllocator.assignFixedBlockState(appearanceName.isEmpty() ? id.asString() : id.asString() + ":" + appearanceName, parsePluginFormattedBlockState(appearanceSection.get("state").toString()))
);
} else if (appearanceSection.containsKey("auto-state")) {
String autoStateId = appearanceSection.get("auto-state").toString();
AutoStateGroup group = AutoStateGroup.byId(autoStateId);
if (group == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_auto_state", autoStateId, EnumUtils.toString(AutoStateGroup.values()));
}
String appearanceName = entry.getKey();
futureVisualStates.put(
appearanceName,
this.visualBlockStateAllocator.requestAutoState(appearanceName.isEmpty() ? id.asString() : id.asString() + ":" + appearanceName, group)
);
} else {
throw new LocalizedResourceConfigException("warning.config.block.state.missing_state");
}
}
CompletableFutures.allOf(futureVisualStates.values()).whenComplete((v2, t2) -> ResourceConfigUtils.runCatching(path, node, () -> {
if (t2 != null) {
if (t2 instanceof CompletionException e) {
Throwable cause = e.getCause();
if (cause instanceof VisualBlockStateAllocator.StateExhaustedException exhausted) {
throw new LocalizedResourceConfigException("warning.config.block.state.auto_state.exhausted", exhausted.group().id(), String.valueOf(exhausted.group().candidateCount()));
} else {
Debugger.BLOCK.warn(() -> "Unknown error while allocating visual block state.", cause);
return;
}
}
throw new RuntimeException("Unknown error occurred", t2);
}
BlockStateAppearance anyAppearance = null;
Map<String, BlockStateAppearance> appearances = new HashMap<>();
for (Map.Entry<String, Map<String, Object>> entry : appearanceConfigs.entrySet()) {
String appearanceName = entry.getKey();
Map<String, Object> appearanceSection = entry.getValue();
BlockStateWrapper visualBlockState;
try {
visualBlockState = futureVisualStates.get(appearanceName).get();
} catch (InterruptedException | ExecutionException e) {
AbstractBlockManager.this.plugin.logger().warn("Interrupted while allocating visual block state for block " + id.asString(), e);
return;
}
this.arrangeModelForStateAndVerify(visualBlockState, ResourceConfigUtils.get(appearanceSection, "model", "models"));
BlockStateAppearance blockStateAppearance = new BlockStateAppearance(visualBlockState, parseBlockEntityRender(appearanceSection.get("entity-renderer")));
appearances.put(appearanceName, blockStateAppearance);
if (anyAppearance == null) {
anyAppearance = blockStateAppearance;
}
}
// 至少有一个外观吧
Objects.requireNonNull(anyAppearance, "any appearance should not be null");
ExceptionCollector<LocalizedResourceConfigException> exceptionCollector = new ExceptionCollector<>();
if (!singleState) {
Map<String, Object> variantsSection = ResourceConfigUtils.getAsMapOrNull(stateSection.get("variants"), "variants");
if (variantsSection != null) {
for (Map.Entry<String, Object> entry : variantsSection.entrySet()) {
Map<String, Object> variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey());
String variantNBT = entry.getKey();
// 先解析nbt找到需要修改的方块状态
CompoundTag tag = BlockNbtParser.deserialize(variantProvider, variantNBT);
if (tag == null) {
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", variantNBT));
continue;
}
List<ImmutableBlockState> possibleStates = variantProvider.getPossibleStates(tag);
Map<String, Object> anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings");
if (anotherSetting != null) {
for (ImmutableBlockState possibleState : possibleStates) {
possibleState.setSettings(BlockSettings.ofFullCopy(possibleState.settings(), anotherSetting));
}
}
String appearanceName = ResourceConfigUtils.getAsString(variantSection.get("appearance"));
if (appearanceName != null) {
BlockStateAppearance appearance = appearances.get(appearanceName);
if (appearance == null) {
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearanceName));
continue;
}
for (ImmutableBlockState possibleState : possibleStates) {
possibleState.setVanillaBlockState(appearance.blockState());
appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers);
}
}
}
}
}
// 获取方块实体行为
EntityBlockBehavior entityBlockBehavior = blockBehavior.getEntityBehavior();
boolean isEntityBlock = entityBlockBehavior != null;
// 绑定行为
for (ImmutableBlockState state : states) {
if (isEntityBlock) {
state.setBlockEntityType(entityBlockBehavior.blockEntityType());
}
state.setBehavior(blockBehavior);
int internalId = state.customBlockState().registryId();
BlockStateWrapper visualState = state.vanillaBlockState();
// 校验,为未绑定外观的强行添加外观
if (visualState == null) {
visualState = anyAppearance.blockState();
state.setVanillaBlockState(visualState);
anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers);
}
int appearanceId = visualState.registryId();
int index = internalId - AbstractBlockManager.this.vanillaBlockStateCount;
AbstractBlockManager.this.immutableBlockStates[index] = state;
AbstractBlockManager.this.blockStateMappings[internalId] = appearanceId;
AbstractBlockManager.this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId);
AbstractBlockManager.this.tempVisualBlockStatesInUse.add(visualState);
AbstractBlockManager.this.tempVisualBlocksInUse.add(getBlockOwnerId(visualState));
AbstractBlockManager.this.applyPlatformSettings(state);
// generate mod assets
if (Config.generateModAssets()) {
AbstractBlockManager.this.modBlockStateOverrides.put(BlockManager.createCustomBlockKey(index), Optional.ofNullable(AbstractBlockManager.this.tempVanillaBlockStateModels[appearanceId])
.orElseGet(() -> {
// 如果未指定模型,说明复用原版模型?但是部分模型是多部位模型,无法使用变体解决问题
// 未来需要靠mod重构彻底解决问题
JsonObject json = new JsonObject();
json.addProperty("model", "minecraft:block/air");
return json;
}));
}
}
// 一定要到最后再绑定
customBlock.setBehavior(blockBehavior);
holder.bindValue(customBlock);
// 添加方块
AbstractBlockManager.this.byId.put(customBlock.id(), customBlock);
// 抛出次要警告
exceptionCollector.throwIfPresent();
}, () -> GsonHelper.get().toJson(section)));
}, () -> GsonHelper.get().toJson(section)));
}
private int getInternalBlockId(int internalId, int appearanceId) {
Key baseBlock = getBlockOwnerId(appearanceId);
Key internalBlockId = Key.of(Key.DEFAULT_NAMESPACE, baseBlock.value() + "_" + internalId);
int internalBlockRegistryId = Optional.ofNullable(AbstractBlockManager.this.internalId2StateId.get(internalBlockId)).orElse(-1);
if (internalBlockRegistryId == -1) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", internalBlockId.toString(), String.valueOf(availableAppearances(baseBlock) - 1));
}
return internalBlockRegistryId;
}
private Map<String, Integer> parseBlockAppearances(Map<String, Object> appearancesSection) {
Map<String, Integer> appearances = new HashMap<>();
for (Map.Entry<String, Object> entry : appearancesSection.entrySet()) {
Map<String, Object> appearanceSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey());
int appearanceId = pluginFormattedBlockStateToRegistryId(ResourceConfigUtils.requireNonEmptyStringOrThrow(
appearanceSection.get("state"), "warning.config.block.state.missing_state"));
this.arrangeModelForStateAndVerify(appearanceId, ResourceConfigUtils.get(appearanceSection, "model", "models"));
appearances.put(entry.getKey(), appearanceId);
}
return appearances;
@SuppressWarnings("unchecked")
private Optional<BlockEntityElementConfig<? extends BlockEntityElement>[]> parseBlockEntityRender(Object arguments) {
if (arguments == null) return Optional.empty();
List<BlockEntityElementConfig<? extends BlockEntityElement>> blockEntityElementConfigs = ResourceConfigUtils.parseConfigAsList(arguments, BlockEntityElementConfigs::fromMap);
if (blockEntityElementConfigs.isEmpty()) return Optional.empty();
return Optional.of(blockEntityElementConfigs.toArray(new BlockEntityElementConfig[0]));
}
@NotNull
@@ -300,7 +668,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
return properties;
}
private void arrangeModelForStateAndVerify(int registryId, Object modelOrModels) {
private void arrangeModelForStateAndVerify(BlockStateWrapper blockStateWrapper, Object modelOrModels) {
// 如果没有配置models
if (modelOrModels == null) {
return;
@@ -318,9 +686,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
}
}
// 拆分方块id与属性
String blockState = stateRegistryIdToStateSNBT(registryId);
Key blockId = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}')));
String propertyNBT = blockState.substring(blockState.indexOf('[') + 1, blockState.lastIndexOf(']'));
String blockState = blockStateWrapper.getAsString();
int firstIndex = blockState.indexOf('[');
Key blockId = firstIndex == -1 ? Key.of(blockState) : Key.of(blockState.substring(0, firstIndex));
String propertyNBT = firstIndex == -1 ? "" : blockState.substring(firstIndex + 1, blockState.lastIndexOf(']'));
// 结合variants
JsonElement combinedVariant = GsonHelper.combine(variants);
Map<String, JsonElement> overrideMap = AbstractBlockManager.this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>());
@@ -329,6 +698,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
throw new LocalizedResourceConfigException("warning.config.block.state.model.conflict", GsonHelper.get().toJson(combinedVariant), blockState, GsonHelper.get().toJson(previous));
}
overrideMap.put(propertyNBT, combinedVariant);
AbstractBlockManager.this.tempVanillaBlockStateModels[blockStateWrapper.registryId()] = combinedVariant;
}
private JsonObject parseAppearanceModelSectionAsJson(Map<String, Object> section) {
@@ -353,7 +723,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
}
// 从方块外观的state里获取其原版方块的state id
private int pluginFormattedBlockStateToRegistryId(String blockState) {
private BlockStateWrapper parsePluginFormattedBlockState(String blockState) {
// 五种合理情况
// minecraft:note_block:10
// note_block:10
@@ -364,7 +734,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
if (split.length >= 4) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState);
}
int registryId;
BlockStateWrapper wrapper;
String stateOrId = split[split.length - 1];
boolean isId = false;
int arrangerIndex = 0;
@@ -384,14 +754,14 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
// 获取原版方块的id
Key block = split.length == 2 ? Key.of(split[0]) : Key.of(split[0], split[1]);
try {
List<Integer> arranger = blockAppearanceArranger.get(block);
List<BlockStateWrapper> arranger = AbstractBlockManager.this.blockStateArranger.get(block);
if (arranger == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.unavailable_vanilla", blockState);
}
if (arrangerIndex >= arranger.size()) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla_id", blockState, String.valueOf(arranger.size() - 1));
}
registryId = arranger.get(arrangerIndex);
wrapper = arranger.get(arrangerIndex);
} catch (NumberFormatException e) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", e, blockState);
}
@@ -401,9 +771,21 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
if (packedBlockState == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState);
}
registryId = packedBlockState.registryId();
wrapper = packedBlockState;
}
return registryId;
return wrapper;
}
}
public boolean isVanillaBlockState(int id) {
return id < this.vanillaBlockStateCount && id >= 0;
}
public BlockParser blockParser() {
return blockParser;
}
public BlockStateMappingParser blockStateMappingParser() {
return blockStateMappingParser;
}
}

View File

@@ -0,0 +1,38 @@
package net.momirealms.craftengine.core.block;
public abstract class AbstractBlockStateWrapper implements BlockStateWrapper {
protected final Object blockState;
protected final int registryId;
protected AbstractBlockStateWrapper(Object blockState, int registryId) {
this.blockState = blockState;
this.registryId = registryId;
}
@Override
public Object literalObject() {
return this.blockState;
}
@Override
public int registryId() {
return this.registryId;
}
@Override
public String toString() {
return this.blockState.toString();
}
@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BlockStateWrapper that)) return false;
return this.registryId == that.registryId();
}
@Override
public int hashCode() {
return this.registryId;
}
}

View File

@@ -1,8 +1,6 @@
package net.momirealms.craftengine.core.block;
import com.google.common.collect.ImmutableMap;
import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior;
import net.momirealms.craftengine.core.block.parser.BlockNbtParser;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.loot.LootTable;
@@ -10,7 +8,6 @@ import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.sparrow.nbt.CompoundTag;
@@ -22,76 +19,33 @@ import java.util.*;
import java.util.function.BiFunction;
public abstract class AbstractCustomBlock implements CustomBlock {
protected final Holder<CustomBlock> holder;
protected final Key id;
protected final Holder.Reference<CustomBlock> holder;
protected final BlockStateVariantProvider variantProvider;
protected final Map<String, Property<?>> properties;
protected final BlockBehavior behavior;
protected final BiFunction<BlockPlaceContext, ImmutableBlockState, ImmutableBlockState> placementFunction;
protected final ImmutableBlockState defaultState;
protected final Map<EventTrigger, List<Function<PlayerOptionalContext>>> events;
@Nullable
protected final LootTable<?> lootTable;
protected BlockBehavior behavior = EmptyBlockBehavior.INSTANCE;
protected AbstractCustomBlock(
@NotNull Key id,
@NotNull Holder.Reference<CustomBlock> holder,
@NotNull Map<String, Property<?>> properties,
@NotNull Map<String, Integer> appearances,
@NotNull Map<String, BlockStateVariant> variantMapper,
@NotNull BlockSettings settings,
@NotNull BlockStateVariantProvider variantProvider,
@NotNull Map<EventTrigger, List<Function<PlayerOptionalContext>>> events,
@Nullable List<Map<String, Object>> behaviorConfig,
@Nullable LootTable<?> lootTable
) {
holder.bindValue(this);
this.id = holder.key().location();
this.holder = holder;
this.id = id;
this.lootTable = lootTable;
this.properties = ImmutableMap.copyOf(properties);
this.events = events;
this.variantProvider = new BlockStateVariantProvider(holder, ImmutableBlockState::new, properties);
this.variantProvider = variantProvider;
this.defaultState = this.variantProvider.getDefaultState();
this.behavior = setupBehavior(behaviorConfig);
List<BiFunction<BlockPlaceContext, ImmutableBlockState, ImmutableBlockState>> placements = new ArrayList<>(4);
for (Map.Entry<String, Property<?>> propertyEntry : this.properties.entrySet()) {
for (Map.Entry<String, Property<?>> propertyEntry : this.variantProvider.properties().entrySet()) {
placements.add(Property.createStateForPlacement(propertyEntry.getKey(), propertyEntry.getValue()));
}
this.placementFunction = composite(placements);
for (Map.Entry<String, BlockStateVariant> entry : variantMapper.entrySet()) {
String nbtString = entry.getKey();
CompoundTag tag = BlockNbtParser.deserialize(this, nbtString);
if (tag == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString);
}
List<ImmutableBlockState> possibleStates = this.getPossibleStates(tag);
if (possibleStates.size() != 1) {
throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString);
}
BlockStateVariant blockStateVariant = entry.getValue();
int vanillaStateRegistryId = appearances.getOrDefault(blockStateVariant.appearance(), -1);
// This should never happen
if (vanillaStateRegistryId == -1) {
vanillaStateRegistryId = appearances.values().iterator().next();
}
// Late init states
ImmutableBlockState state = possibleStates.getFirst();
state.setSettings(blockStateVariant.settings());
state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId));
state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId()));
}
// double check if there's any invalid state
for (ImmutableBlockState state : this.variantProvider().states()) {
state.setBehavior(this.behavior);
if (state.settings() == null) {
state.setSettings(settings);
}
}
this.applyPlatformSettings();
}
protected BlockBehavior setupBehavior(List<Map<String, Object>> behaviorConfig) {
return EmptyBlockBehavior.INSTANCE;
}
private static BiFunction<BlockPlaceContext, ImmutableBlockState, ImmutableBlockState> composite(List<BiFunction<BlockPlaceContext, ImmutableBlockState, ImmutableBlockState>> placements) {
@@ -112,8 +66,6 @@ public abstract class AbstractCustomBlock implements CustomBlock {
};
}
protected abstract void applyPlatformSettings();
@Override
public @Nullable LootTable<?> lootTable() {
return this.lootTable;
@@ -138,25 +90,13 @@ public abstract class AbstractCustomBlock implements CustomBlock {
return this.id;
}
public void setBehavior(@Nullable BlockBehavior behavior) {
this.behavior = behavior;
}
@Override
public List<ImmutableBlockState> getPossibleStates(CompoundTag nbt) {
List<ImmutableBlockState> tempStates = new ArrayList<>();
tempStates.add(defaultState());
for (Property<?> property : this.variantProvider.getDefaultState().getProperties()) {
Tag value = nbt.get(property.name());
if (value != null) {
tempStates.replaceAll(immutableBlockState -> ImmutableBlockState.with(immutableBlockState, property, property.unpack(value)));
} else {
List<ImmutableBlockState> newStates = new ArrayList<>();
for (ImmutableBlockState state : tempStates) {
for (Object possibleValue : property.possibleValues()) {
newStates.add(ImmutableBlockState.with(state, property, possibleValue));
}
}
tempStates = newStates;
}
}
return tempStates;
return this.variantProvider.getPossibleStates(nbt);
}
@Override
@@ -177,12 +117,12 @@ public abstract class AbstractCustomBlock implements CustomBlock {
@Override
public @Nullable Property<?> getProperty(String name) {
return this.properties.get(name);
return this.variantProvider.getProperty(name);
}
@Override
public @NotNull Collection<Property<?>> properties() {
return this.properties.values();
return this.variantProvider.properties().values();
}
@Override

View File

@@ -0,0 +1,117 @@
package net.momirealms.craftengine.core.block;
import net.momirealms.craftengine.core.pack.allocator.BlockStateCandidate;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Predicate;
public enum AutoStateGroup {
LEAVES("leaves",
Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES),
(w) -> !(boolean) w.getProperty("waterlogged")
),
WATERLOGGED_LEAVES(
"waterlogged_leaves",
Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES),
(w) -> w.getProperty("waterlogged")
),
LOWER_TRIPWIRE("lower_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> w.getProperty("attached")),
HIGHER_TRIPWIRE("higher_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> !(boolean) w.getProperty("attached")),
NOTE_BLOCK("note_block", Set.of(BlockKeys.NOTE_BLOCK), (w) -> true),
BROWN_MUSHROOM("brown_mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK), (w) -> true),
RED_MUSHROOM("red_mushroom", Set.of(BlockKeys.RED_MUSHROOM_BLOCK), (w) -> true),
MUSHROOM_STEM("mushroom_stem", Set.of(BlockKeys.MUSHROOM_STEM), (w) -> true),
TRIPWIRE("tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> true),
SUGAR_CANE("sugar_cane", Set.of(BlockKeys.SUGAR_CANE), (w) -> true),
CACTUS("cactus", Set.of(BlockKeys.CACTUS), (w) -> true),
SAPLING("sapling", Set.of(BlockKeys.OAK_SAPLING, BlockKeys.SPRUCE_SAPLING, BlockKeys.BIRCH_SAPLING, BlockKeys.JUNGLE_SAPLING, BlockKeys.ACACIA_SAPLING, BlockKeys.DARK_OAK_SAPLING, BlockKeys.CHERRY_SAPLING, BlockKeys.PALE_OAK_SAPLING), (w) -> true),
MUSHROOM("mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM), (w) -> true),
SOLID("solid", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM, BlockKeys.NOTE_BLOCK), (w) -> true);
private final Set<Key> blocks;
private final String id;
private final Predicate<BlockStateWrapper> predicate;
private final List<BlockStateCandidate> candidates = new ArrayList<>();
private int pointer;
AutoStateGroup(String id, Set<Key> blocks, Predicate<BlockStateWrapper> predicate) {
this.id = id;
this.blocks = blocks;
this.predicate = predicate;
}
public void reset() {
this.pointer = 0;
this.candidates.clear();
}
public void addCandidate(@NotNull BlockStateCandidate candidate) {
this.candidates.add(candidate);
}
public int candidateCount() {
return candidates.size();
}
@Nullable
public BlockStateCandidate findNextCandidate() {
while (this.pointer < this.candidates.size()) {
final BlockStateCandidate state = this.candidates.get(this.pointer);
if (!state.isUsed()) {
return state;
}
this.pointer++;
}
return null;
}
public boolean test(BlockStateWrapper state) {
if (!this.blocks.contains(state.ownerId())) {
return false;
}
return this.predicate.test(state);
}
public Set<Key> blocks() {
return blocks;
}
public String id() {
return id;
}
private static final Map<String, AutoStateGroup> BY_ID = new HashMap<>();
private static final Map<Key, List<AutoStateGroup>> BY_BLOCKS = new HashMap<>();
static {
for (AutoStateGroup group : AutoStateGroup.values()) {
BY_ID.put(group.id(), group);
BY_ID.put(group.id().toUpperCase(Locale.ROOT), group);
for (Key key : group.blocks) {
BY_BLOCKS.computeIfAbsent(key, k -> new ArrayList<>(4)).add(group);
}
}
}
@Nullable
public static AutoStateGroup byId(String id) {
return BY_ID.get(id);
}
public static List<AutoStateGroup> findGroups(BlockStateWrapper wrapper) {
return findGroups(wrapper.ownerId(), wrapper);
}
public static List<AutoStateGroup> findGroups(Key id, BlockStateWrapper wrapper) {
List<AutoStateGroup> groups = BY_BLOCKS.get(id);
if (groups == null) return Collections.emptyList();
List<AutoStateGroup> result = new ArrayList<>(groups.size());
for (AutoStateGroup group : groups) {
if (group.predicate.test(wrapper)) result.add(group);
}
return result;
}
}

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.block;
import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item;
@@ -10,6 +11,7 @@ import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.concurrent.Callable;
@@ -24,6 +26,14 @@ public abstract class BlockBehavior {
return Optional.empty();
}
@Nullable
public EntityBlockBehavior getEntityBehavior() {
if (this instanceof EntityBlockBehavior behavior) {
return behavior;
}
return null;
}
// BlockState state, Rotation rotation
public Object rotate(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
return superMethod.call();
@@ -45,18 +55,18 @@ public abstract class BlockBehavior {
superMethod.call();
}
// ServerLevel level, BlockPos pos, RandomSource random
// BlockState state, ServerLevel level, BlockPos pos, RandomSource random
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
superMethod.call();
}
// ServerLevel level, BlockPos pos, RandomSource random
// BlockState state, ServerLevel level, BlockPos pos, RandomSource random
public void randomTick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
superMethod.call();
}
// 1.20-1.20.4 BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify, UseOnContext context
// 1.20.5+ Level level, BlockPos pos, BlockState oldState, boolean movedByPiston
// 1.20.5+ BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston
public void onPlace(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
superMethod.call();
}
@@ -85,6 +95,22 @@ public abstract class BlockBehavior {
return false;
}
// BlockState state
public boolean hasAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception {
return false;
}
// 1.20.1~1.21.8 BlockState state, Level level, BlockPos pos
// 1.21.9+ BlockState state, Level level, BlockPos pos
public int getAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception {
return 0;
}
// BlockState state, LevelAccessor level, BlockPos pos
public Object getContainer(Object thisBlock, Object[] args) throws Exception {
return null;
}
// Level level, RandomSource random, BlockPos pos, BlockState state
public boolean isBoneMealSuccess(Object thisBlock, Object[] args) throws Exception {
return false;
@@ -153,6 +179,14 @@ public abstract class BlockBehavior {
public void spawnAfterBreak(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
}
// Level level, BlockPos pos, BlockState state, Entity entity
public void stepOn(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
}
// Level level, BlockState state, BlockHitResult hit, Projectile projectile
public void onProjectileHit(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
}
public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) {
return state;
}

View File

@@ -1,17 +0,0 @@
package net.momirealms.craftengine.core.block;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
public class BlockEntityState {
private final CompoundTag nbt;
public BlockEntityState(CompoundTag nbt) {
this.nbt = nbt.deepClone();
}
public CompoundTag nbt() {
return this.nbt;
}
}

View File

@@ -2,10 +2,15 @@ package net.momirealms.craftengine.core.block;
import net.momirealms.craftengine.core.util.Key;
import java.util.List;
public final class BlockKeys {
private BlockKeys() {}
// 特殊
public static final Key AIR = Key.of("minecraft:air");
public static final Key SUGAR_CANE = Key.of("minecraft:sugar_cane");
public static final Key NOTE_BLOCK = Key.of("minecraft:note_block");
public static final Key TRIPWIRE = Key.of("minecraft:tripwire");
public static final Key CACTUS = Key.of("minecraft:cactus");
public static final Key POWDER_SNOW = Key.of("minecraft:powder_snow");
@@ -299,6 +304,33 @@ public final class BlockKeys {
public static final Key BAMBOO_WALL_HANGING_SIGN = Key.of("minecraft:bamboo_wall_hanging_sign");
public static final Key CRIMSON_WALL_HANGING_SIGN = Key.of("minecraft:crimson_wall_hanging_sign");
public static final Key WARPED_WALL_HANGING_SIGN = Key.of("minecraft:warped_wall_hanging_sign");
public static final Key CACTUS = Key.of("minecraft:cactus");
public static final Key BROWN_MUSHROOM_BLOCK = Key.of("minecraft:brown_mushroom_block");
public static final Key RED_MUSHROOM_BLOCK = Key.of("minecraft:red_mushroom_block");
public static final Key MUSHROOM_STEM = Key.of("minecraft:mushroom_stem");
public static final Key OAK_LEAVES = Key.of("minecraft:oak_leaves");
public static final Key SPRUCE_LEAVES = Key.of("minecraft:spruce_leaves");
public static final Key BIRCH_LEAVES = Key.of("minecraft:birch_leaves");
public static final Key JUNGLE_LEAVES = Key.of("minecraft:jungle_leaves");
public static final Key ACACIA_LEAVES = Key.of("minecraft:acacia_leaves");
public static final Key DARK_OAK_LEAVES = Key.of("minecraft:dark_oak_leaves");
public static final Key MANGROVE_LEAVES = Key.of("minecraft:mangrove_leaves");
public static final Key CHERRY_LEAVES = Key.of("minecraft:cherry_leaves");
public static final Key PALE_OAK_LEAVES = Key.of("minecraft:pale_oak_leaves");
public static final Key AZALEA_LEAVES = Key.of("minecraft:azalea_leaves");
public static final Key FLOWERING_AZALEA_LEAVES = Key.of("minecraft:flowering_azalea_leaves");
public static final Key OAK_SAPLING = Key.of("minecraft:oak_sapling");
public static final Key SPRUCE_SAPLING = Key.of("minecraft:spruce_sapling");
public static final Key BIRCH_SAPLING = Key.of("minecraft:birch_sapling");
public static final Key JUNGLE_SAPLING = Key.of("minecraft:jungle_sapling");
public static final Key DARK_OAK_SAPLING = Key.of("minecraft:dark_oak_sapling");
public static final Key ACACIA_SAPLING = Key.of("minecraft:acacia_sapling");
public static final Key CHERRY_SAPLING = Key.of("minecraft:cherry_sapling");
public static final Key PALE_OAK_SAPLING = Key.of("minecraft:pale_oak_sapling");
public static final Key[] BUTTONS = new Key[]{
OAK_BUTTON, SPRUCE_BUTTON, BIRCH_BUTTON, JUNGLE_BUTTON, ACACIA_BUTTON, DARK_OAK_BUTTON, MANGROVE_BUTTON, CHERRY_BUTTON,
@@ -316,4 +348,33 @@ public final class BlockKeys {
REDSTONE_WIRE, REDSTONE_TORCH, REDSTONE_BLOCK, REPEATER, COMPARATOR, TARGET, LEVER, SCULK_SENSOR, CALIBRATED_SCULK_SENSOR,
TRIPWIRE_HOOK, LECTERN, DAYLIGHT_DETECTOR, LIGHTNING_ROD, TRAPPED_CHEST, JUKEBOX, OBSERVER, DETECTOR_RAIL
};
public static final List<Key> WOODEN_TRAPDOORS = List.of(OAK_TRAPDOOR, SPRUCE_TRAPDOOR, BIRCH_TRAPDOOR,
ACACIA_TRAPDOOR, PALE_OAK_TRAPDOOR, DARK_OAK_TRAPDOOR, MANGROVE_TRAPDOOR, JUNGLE_TRAPDOOR);
public static final List<Key> CHERRY_TRAPDOORS = List.of(CHERRY_TRAPDOOR);
public static final List<Key> BAMBOO_TRAPDOORS = List.of(BAMBOO_TRAPDOOR);
public static final List<Key> NETHER_TRAPDOORS = List.of(WARPED_TRAPDOOR, CRIMSON_TRAPDOOR);
public static final List<Key> COPPER_TRAPDOORS = List.of(COPPER_TRAPDOOR, EXPOSED_COPPER_TRAPDOOR, WEATHERED_COPPER_TRAPDOOR, OXIDIZED_COPPER_DOOR,
WAXED_COPPER_TRAPDOOR, WAXED_EXPOSED_COPPER_TRAPDOOR, WAXED_WEATHERED_COPPER_TRAPDOOR, WAXED_OXIDIZED_COPPER_TRAPDOOR);
public static final List<Key> WOODEN_DOORS = List.of(OAK_DOOR, SPRUCE_DOOR, BIRCH_DOOR,
ACACIA_DOOR, PALE_OAK_DOOR, DARK_OAK_DOOR, MANGROVE_DOOR, JUNGLE_DOOR);
public static final List<Key> CHERRY_DOORS = List.of(CHERRY_DOOR);
public static final List<Key> BAMBOO_DOORS = List.of(BAMBOO_DOOR);
public static final List<Key> NETHER_DOORS = List.of(WARPED_DOOR, CRIMSON_DOOR);
public static final List<Key> COPPER_DOORS = List.of(COPPER_DOOR, EXPOSED_COPPER_DOOR, WEATHERED_COPPER_DOOR, OXIDIZED_COPPER_DOOR,
WAXED_COPPER_DOOR, WAXED_EXPOSED_COPPER_DOOR, WAXED_WEATHERED_COPPER_DOOR, WAXED_OXIDIZED_COPPER_DOOR);
public static final List<Key> WOODEN_FENCE_GATES = List.of(OAK_FENCE_GATE, SPRUCE_FENCE_GATE, BIRCH_FENCE_GATE,
ACACIA_FENCE_GATE, PALE_OAK_FENCE_GATE, DARK_OAK_FENCE_GATE, MANGROVE_FENCE_GATE, JUNGLE_FENCE_GATE);
public static final List<Key> CHERRY_FENCE_GATES = List.of(CHERRY_FENCE_GATE);
public static final List<Key> BAMBOO_FENCE_GATES = List.of(BAMBOO_FENCE_GATE);
public static final List<Key> NETHER_FENCE_GATES = List.of(WARPED_FENCE_GATE, CRIMSON_FENCE_GATE);
public static final List<Key> WOODEN_BUTTONS = List.of(OAK_BUTTON, SPRUCE_BUTTON, BIRCH_BUTTON, JUNGLE_BUTTON,
ACACIA_BUTTON, DARK_OAK_BUTTON, PALE_OAK_BUTTON, MANGROVE_BUTTON);
public static final List<Key> CHERRY_BUTTONS = List.of(CHERRY_BUTTON);
public static final List<Key> BAMBOO_BUTTONS = List.of(BAMBOO_BUTTON);
public static final List<Key> NETHER_BUTTONS = List.of(CRIMSON_BUTTON, WARPED_BUTTON);
public static final List<Key> STONE_BUTTONS = List.of(STONE_BUTTON, POLISHED_BLACKSTONE_BUTTON);
}

View File

@@ -16,7 +16,7 @@ import java.util.Optional;
public interface BlockManager extends Manageable, ModelGenerator {
ConfigParser parser();
ConfigParser[] parsers();
Collection<ModelGeneration> modelsToGenerate();
@@ -24,15 +24,18 @@ public interface BlockManager extends Manageable, ModelGenerator {
Map<Key, JsonElement> modBlockStates();
Map<Key, CustomBlock> blocks();
Map<Key, CustomBlock> loadedBlocks();
@Deprecated(forRemoval = true)
default Map<Key, CustomBlock> blocks() {
return loadedBlocks();
}
Optional<CustomBlock> blockById(Key key);
Collection<Suggestion> cachedSuggestions();
Map<Key, Key> soundMapper();
int availableAppearances(Key blockType);
Map<Key, Key> soundReplacements();
Key getBlockOwnerId(BlockStateWrapper state);
@@ -44,4 +47,11 @@ public interface BlockManager extends Manageable, ModelGenerator {
@Nullable
BlockStateWrapper createBlockState(String blockState);
@Nullable
BlockStateWrapper createVanillaBlockState(String blockState);
static Key createCustomBlockKey(int id) {
return Key.of("craftengine", "custom_" + id);
}
}

View File

@@ -5,14 +5,14 @@ public final class BlockRegistryMirror {
private static BlockStateWrapper stoneState;
public static void init(BlockStateWrapper[] states, BlockStateWrapper state) {
if (blockStates != null) throw new IllegalStateException("block states are already set");
if (blockStates != null) throw new IllegalStateException("block states have already been set");
blockStates = states;
stoneState = state;
}
public static BlockStateWrapper stateByRegistryId(int vanillaId) {
if (vanillaId < 0) return stoneState;
return blockStates[vanillaId];
public static BlockStateWrapper byId(int stateId) {
if (stateId < 0) return stoneState;
return blockStates[stateId];
}
public static int size() {

View File

@@ -1,7 +1,6 @@
package net.momirealms.craftengine.core.block;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.Key;
import java.util.Map;
@@ -15,25 +14,20 @@ public final class BlockSounds {
Land 0.3 1
Destroy 1 1
*/
public static final SoundData EMPTY_SOUND = new SoundData(Key.of("minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_1);
public static final BlockSounds EMPTY = new BlockSounds(EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND);
public static final BlockSounds EMPTY = new BlockSounds(SoundData.EMPTY, SoundData.EMPTY, SoundData.EMPTY, SoundData.EMPTY, SoundData.EMPTY);
private final SoundData breakSound;
private final SoundData stepSound;
private final SoundData placeSound;
private final SoundData hitSound;
private final SoundData fallSound;
private final SoundData landSound;
private final SoundData destroySound;
public BlockSounds(SoundData breakSound, SoundData stepSound, SoundData placeSound, SoundData hitSound, SoundData fallSound, SoundData landSound, SoundData destroySound) {
public BlockSounds(SoundData breakSound, SoundData stepSound, SoundData placeSound, SoundData hitSound, SoundData fallSound) {
this.breakSound = breakSound;
this.stepSound = stepSound;
this.placeSound = placeSound;
this.hitSound = hitSound;
this.fallSound = fallSound;
this.landSound = landSound;
this.destroySound = destroySound;
}
public static BlockSounds fromMap(Map<String, Object> map) {
@@ -43,16 +37,10 @@ public final class BlockSounds {
SoundData.create(map.getOrDefault("step", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_0_15, SoundData.SoundValue.FIXED_1),
SoundData.create(map.getOrDefault("place", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8),
SoundData.create(map.getOrDefault("hit", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.FIXED_0_5),
SoundData.create(map.getOrDefault("fall", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.FIXED_0_75),
SoundData.create(map.getOrDefault("land", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_0_3, SoundData.SoundValue.FIXED_1),
SoundData.create(map.getOrDefault("destroy", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_1)
SoundData.create(map.getOrDefault("fall", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.FIXED_0_75)
);
}
public SoundData destroySound() {
return destroySound;
}
public SoundData breakSound() {
return breakSound;
}
@@ -69,10 +57,6 @@ public final class BlockSounds {
return hitSound;
}
public SoundData landSound() {
return landSound;
}
public SoundData fallSound() {
return fallSound;
}

View File

@@ -0,0 +1,9 @@
package net.momirealms.craftengine.core.block;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import java.util.Optional;
public record BlockStateAppearance(BlockStateWrapper blockState, Optional<BlockEntityElementConfig<? extends BlockEntityElement>[]> blockEntityRenderer) {
}

View File

@@ -9,11 +9,11 @@ import java.util.*;
import java.util.stream.Collectors;
public class BlockStateHolder {
protected final Holder<CustomBlock> owner;
protected final Holder.Reference<CustomBlock> owner;
private final Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap;
private Map<Property<?>, ImmutableBlockState[]> withMap;
public BlockStateHolder(Holder<CustomBlock> owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap) {
public BlockStateHolder(Holder.Reference<CustomBlock> owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap) {
this.owner = owner;
this.propertyMap = new Reference2ObjectArrayMap<>(propertyMap);
}
@@ -39,9 +39,9 @@ public class BlockStateHolder {
@Override
public String toString() {
if (this.propertyMap.isEmpty()) {
return this.owner.value().id().toString();
return this.owner.key().location().toString();
}
return this.owner.value().id() + "[" + getPropertiesAsString() + "]";
return this.owner.key().location() + "[" + getPropertiesAsString() + "]";
}
public String getPropertiesAsString() {

View File

@@ -1,25 +0,0 @@
package net.momirealms.craftengine.core.block;
public class BlockStateVariant {
private final String appearance;
private final BlockSettings settings;
private final int internalId;
public BlockStateVariant(String appearance, BlockSettings settings, int internalId) {
this.appearance = appearance;
this.settings = settings;
this.internalId = internalId;
}
public String appearance() {
return appearance;
}
public BlockSettings settings() {
return settings;
}
public int internalRegistryId() {
return internalId;
}
}

View File

@@ -8,6 +8,8 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Pair;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.Tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -23,7 +25,7 @@ public final class BlockStateVariantProvider {
private final ImmutableList<ImmutableBlockState> states;
private final Holder<CustomBlock> owner;
public BlockStateVariantProvider(Holder<CustomBlock> owner, Factory<Holder<CustomBlock>, ImmutableBlockState> factory, Map<String, Property<?>> propertiesMap) {
public BlockStateVariantProvider(Holder.Reference<CustomBlock> owner, Factory<Holder.Reference<CustomBlock>, ImmutableBlockState> factory, Map<String, Property<?>> propertiesMap) {
this.owner = owner;
this.properties = ImmutableSortedMap.copyOf(propertiesMap);
@@ -59,6 +61,27 @@ public final class BlockStateVariantProvider {
this.states = ImmutableList.copyOf(list);
}
public List<ImmutableBlockState> getPossibleStates(CompoundTag nbt) {
List<ImmutableBlockState> tempStates = new ArrayList<>();
ImmutableBlockState defaultState = getDefaultState();
tempStates.add(defaultState);
for (Property<?> property : defaultState.getProperties()) {
Tag value = nbt.get(property.name());
if (value != null) {
tempStates.replaceAll(immutableBlockState -> ImmutableBlockState.with(immutableBlockState, property, property.unpack(value)));
} else {
List<ImmutableBlockState> newStates = new ArrayList<>();
for (ImmutableBlockState state : tempStates) {
for (Object possibleValue : property.possibleValues()) {
newStates.add(ImmutableBlockState.with(state, property, possibleValue));
}
}
tempStates = newStates;
}
}
return tempStates;
}
public interface Factory<O, S> {
S create(O owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap);
}
@@ -80,6 +103,11 @@ public final class BlockStateVariantProvider {
return this.owner;
}
@NotNull
public ImmutableSortedMap<String, Property<?>> properties() {
return this.properties;
}
@Nullable
public Property<?> getProperty(String name) {
return this.properties.get(name);

View File

@@ -1,8 +1,24 @@
package net.momirealms.craftengine.core.block;
public interface BlockStateWrapper {
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
public interface BlockStateWrapper extends Comparable<BlockStateWrapper> {
Object literalObject();
int registryId();
Key ownerId();
<T> T getProperty(String propertyName);
boolean hasProperty(String propertyName);
String getAsString();
@Override
default int compareTo(@NotNull BlockStateWrapper o) {
return Integer.compare(registryId(), o.registryId());
}
}

View File

@@ -5,7 +5,6 @@ import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
@@ -13,17 +12,18 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public interface CustomBlock {
Key id();
@Nullable LootTable<?> lootTable();
@Nullable
LootTable<?> lootTable();
void execute(PlayerOptionalContext context, EventTrigger trigger);
@NotNull BlockStateVariantProvider variantProvider();
@NotNull
BlockStateVariantProvider variantProvider();
List<ImmutableBlockState> getPossibleStates(CompoundTag nbt);
@@ -38,23 +38,4 @@ public interface CustomBlock {
ImmutableBlockState getStateForPlacement(BlockPlaceContext context);
void setPlacedBy(BlockPlaceContext context, ImmutableBlockState state);
interface Builder {
Builder events(Map<EventTrigger, List<Function<PlayerOptionalContext>>> events);
Builder appearances(Map<String, Integer> appearances);
Builder behavior(List<Map<String, Object>> behavior);
Builder lootTable(LootTable<?> lootTable);
Builder properties(Map<String, Property<?>> properties);
Builder settings(BlockSettings settings);
Builder variantMapper(Map<String, BlockStateVariant> variantMapper);
@NotNull CustomBlock build();
}
}

View File

@@ -32,9 +32,11 @@ public interface DelegatingBlock {
*/
ObjectHolder<BlockBehavior> behaviorDelegate();
// 其实是错误的做法
@Deprecated
boolean isNoteBlock();
// 其实是错误的做法
@Deprecated
boolean isTripwire();
}

View File

@@ -1,22 +1,32 @@
package net.momirealms.craftengine.core.block;
import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.registry.WritableRegistry;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceKey;
import java.util.List;
import java.util.Map;
public final class EmptyBlock extends AbstractCustomBlock {
public static EmptyBlock INSTANCE;
public static ImmutableBlockState STATE;
public static final EmptyBlock INSTANCE;
public static final ImmutableBlockState STATE;
public EmptyBlock(Key id, Holder.Reference<CustomBlock> holder) {
super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), Map.of(), List.of(), null);
INSTANCE = this;
STATE = defaultState();
static {
Holder.Reference<CustomBlock> holder = ((WritableRegistry<CustomBlock>) BuiltInRegistries.BLOCK)
.registerForHolder(ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), Key.withDefaultNamespace("empty")));
INSTANCE = new EmptyBlock(holder);
holder.bindValue(INSTANCE);
STATE = INSTANCE.defaultState();
STATE.setSettings(BlockSettings.of());
STATE.setBehavior(EmptyBlockBehavior.INSTANCE);
}
@Override
protected void applyPlatformSettings() {
private EmptyBlock(Holder.Reference<CustomBlock> holder) {
super(holder, new BlockStateVariantProvider(holder, ImmutableBlockState::new, Map.of()), Map.of(), null);
}
public static void initialize() {
}
}

View File

@@ -1,6 +1,12 @@
package net.momirealms.craftengine.core.block;
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
@@ -8,6 +14,7 @@ import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.NBT;
@@ -22,11 +29,13 @@ public final class ImmutableBlockState extends BlockStateHolder {
private BlockStateWrapper customBlockState;
private BlockStateWrapper vanillaBlockState;
private BlockBehavior behavior;
private Integer hashCode;
private BlockSettings settings;
private BlockEntityType<? extends BlockEntity> blockEntityType;
@Nullable
private BlockEntityElementConfig<? extends BlockEntityElement>[] renderers;
ImmutableBlockState(
Holder<CustomBlock> owner,
Holder.Reference<CustomBlock> owner,
Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap
) {
super(owner, propertyMap);
@@ -48,23 +57,32 @@ public final class ImmutableBlockState extends BlockStateHolder {
this.settings = settings;
}
public BlockEntityType<? extends BlockEntity> blockEntityType() {
return blockEntityType;
}
public void setBlockEntityType(BlockEntityType<? extends BlockEntity> blockEntityType) {
this.blockEntityType = blockEntityType;
}
public boolean isEmpty() {
return this == EmptyBlock.STATE;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ImmutableBlockState state)) return false;
return state.owner == this.owner && state.tag.equals(this.tag);
public BlockEntityElementConfig<? extends BlockEntityElement>[] constantRenderers() {
return this.renderers;
}
@Override
public int hashCode() {
if (this.hashCode == null) {
this.hashCode = getNbtToSave().hashCode();
}
return this.hashCode;
public void setConstantRenderers(BlockEntityElementConfig<? extends BlockEntityElement>[] renderers) {
this.renderers = renderers;
}
public boolean hasBlockEntity() {
return this.blockEntityType != null;
}
public boolean hasConstantBlockEntityRenderer() {
return this.renderers != null;
}
public BlockStateWrapper customBlockState() {
@@ -111,7 +129,7 @@ public final class ImmutableBlockState extends BlockStateHolder {
public CompoundTag toNbtToSave(CompoundTag properties) {
CompoundTag tag = new CompoundTag();
tag.put("properties", properties);
tag.put("id", NBT.createString(this.owner.value().id().asString()));
tag.put("id", NBT.createString(this.owner.key().location().asString()));
return tag;
}
@@ -132,4 +150,18 @@ public final class ImmutableBlockState extends BlockStateHolder {
if (lootTable == null) return List.of();
return lootTable.getRandomItems(builder.withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, this).build(), world, player);
}
@SuppressWarnings("unchecked")
public <T extends BlockEntity> BlockEntityTicker<T> createSyncBlockEntityTicker(CEWorld world, BlockEntityType<? extends BlockEntity> type) {
EntityBlockBehavior blockBehavior = this.behavior.getEntityBehavior();
if (blockBehavior == null) return null;
return (BlockEntityTicker<T>) blockBehavior.createSyncBlockEntityTicker(world, this, type);
}
@SuppressWarnings("unchecked")
public <T extends BlockEntity> BlockEntityTicker<T> createAsyncBlockEntityTicker(CEWorld world, BlockEntityType<? extends BlockEntity> type) {
EntityBlockBehavior blockBehavior = this.behavior.getEntityBehavior();
if (blockBehavior == null) return null;
return (BlockEntityTicker<T>) blockBehavior.createAsyncBlockEntityTicker(world, this, type);
}
}

View File

@@ -1,29 +1,25 @@
package net.momirealms.craftengine.core.block;
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.sparrow.nbt.CompoundTag;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class InactiveCustomBlock extends AbstractCustomBlock {
private final Map<CompoundTag, ImmutableBlockState> cachedData = new HashMap<>();
public InactiveCustomBlock(Key id, Holder.Reference<CustomBlock> holder) {
super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), Map.of(), List.of(), null);
}
@Override
protected void applyPlatformSettings() {
public InactiveCustomBlock(Holder.Reference<CustomBlock> holder) {
super(holder, new BlockStateVariantProvider(holder, ImmutableBlockState::new, Map.of()), Map.of(), null);
}
@Override
public ImmutableBlockState getBlockState(CompoundTag nbt) {
return this.cachedData.computeIfAbsent(nbt, k -> {
ImmutableBlockState state = new ImmutableBlockState(super.holder, new Reference2ObjectArrayMap<>());
state.setBehavior(EmptyBlockBehavior.INSTANCE);
state.setNbtToSave(state.toNbtToSave(nbt));
return state;
});

View File

@@ -5,5 +5,7 @@ public enum PushReaction {
DESTROY,
BLOCK,
IGNORE,
PUSH_ONLY
PUSH_ONLY;
public static final PushReaction[] VALUES = values();
}

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.core.block.state;
package net.momirealms.craftengine.core.block;
import java.util.Collection;

View File

@@ -0,0 +1,35 @@
package net.momirealms.craftengine.core.block.behavior;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
public interface EntityBlockBehavior {
<T extends BlockEntity> BlockEntityType<T> blockEntityType();
BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state);
default <T extends BlockEntity> BlockEntityTicker<T> createSyncBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
return null;
}
default <T extends BlockEntity> BlockEntityTicker<T> createAsyncBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
return null;
}
@SuppressWarnings("unchecked")
static <E extends BlockEntity, T extends BlockEntity> BlockEntityTicker<E> createTickerHelper(BlockEntityTicker<? super T> ticker) {
return (BlockEntityTicker<E>) ticker;
}
@SuppressWarnings("unchecked")
static <E extends BlockEntity> BlockEntityType<E> blockEntityTypeHelper(BlockEntityType<?> type) {
return (BlockEntityType<E>) type;
}
}

View File

@@ -0,0 +1,17 @@
package net.momirealms.craftengine.core.block.behavior.special;
import java.util.concurrent.Callable;
public interface FallOnBlockBehavior {
// 1.20.1~1.21.4 Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance
// 1.21.5+ Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance
default void fallOn(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
superMethod.call();
}
// BlockGetter level, Entity entity
default void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
superMethod.call();
}
}

View File

@@ -0,0 +1,10 @@
package net.momirealms.craftengine.core.block.behavior.special;
import java.util.concurrent.Callable;
public interface PlaceLiquidBlockBehavior {
boolean placeLiquid(Object thisBlock, Object[] args, Callable<Object> superMethod);
boolean canPlaceLiquid(Object thisBlock, Object[] args, Callable<Object> superMethod);
}

View File

@@ -0,0 +1,115 @@
package net.momirealms.craftengine.core.block.entity;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.render.DynamicBlockEntityRenderer;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.ChunkPos;
import net.momirealms.craftengine.core.world.SectionPos;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.jetbrains.annotations.Nullable;
public abstract class BlockEntity {
protected final BlockPos pos;
protected ImmutableBlockState blockState;
protected BlockEntityType<? extends BlockEntity> type;
public CEWorld world;
protected boolean valid;
@Nullable
protected DynamicBlockEntityRenderer blockEntityRenderer;
protected BlockEntity(BlockEntityType<? extends BlockEntity> type, BlockPos pos, ImmutableBlockState blockState) {
this.pos = pos;
this.blockState = blockState;
this.type = type;
}
public final CompoundTag saveAsTag() {
CompoundTag tag = new CompoundTag();
this.saveId(tag);
this.savePos(tag);
this.saveCustomData(tag);
return tag;
}
private void saveId(CompoundTag tag) {
tag.putString("id", this.type.id().asString());
}
public void setBlockState(ImmutableBlockState blockState) {
this.blockState = blockState;
}
public ImmutableBlockState blockState() {
return blockState;
}
public CEWorld world() {
return world;
}
public void setWorld(CEWorld world) {
this.world = world;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
private void savePos(CompoundTag tag) {
tag.putInt("x", this.pos.x());
tag.putInt("y", this.pos.y());
tag.putInt("z", this.pos.z());
}
protected void saveCustomData(CompoundTag tag) {
}
public void loadCustomData(CompoundTag tag) {
}
public void preRemove() {
}
public static BlockPos readPos(CompoundTag tag) {
return new BlockPos(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
}
public BlockEntityType<? extends BlockEntity> type() {
return this.type;
}
public @Nullable DynamicBlockEntityRenderer blockEntityRenderer() {
return blockEntityRenderer;
}
public static BlockPos readPosAndVerify(CompoundTag tag, ChunkPos chunkPos) {
int x = tag.getInt("x", 0);
int y = tag.getInt("y", 0);
int z = tag.getInt("z", 0);
int sectionX = SectionPos.blockToSectionCoord(x);
int sectionZ = SectionPos.blockToSectionCoord(z);
if (sectionX != chunkPos.x || sectionZ != chunkPos.z) {
x = chunkPos.x * 16 + SectionPos.sectionRelative(x);
z = chunkPos.z * 16 + SectionPos.sectionRelative(z);
}
return new BlockPos(x, y, z);
}
public BlockPos pos() {
return this.pos;
}
public boolean isValidBlockState(ImmutableBlockState blockState) {
return this.type == blockState.blockEntityType();
}
public interface Factory<T extends BlockEntity> {
T create(BlockPos pos, ImmutableBlockState state);
}
}

View File

@@ -0,0 +1,6 @@
package net.momirealms.craftengine.core.block.entity;
import net.momirealms.craftengine.core.util.Key;
public record BlockEntityType<T extends BlockEntity>(Key id, BlockEntity.Factory<T> factory) {
}

View File

@@ -0,0 +1,12 @@
package net.momirealms.craftengine.core.block.entity;
import net.momirealms.craftengine.core.util.Key;
public final class BlockEntityTypeKeys {
private BlockEntityTypeKeys() {}
public static final Key UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite");
public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage");
public static final Key SIMPLE_PARTICLE = Key.of("craftengine:simple_particle");
public static final Key WALL_TORCH_PARTICLE = Key.of("craftengine:wall_torch_particle");
}

View File

@@ -0,0 +1,17 @@
package net.momirealms.craftengine.core.block.entity;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
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.ResourceKey;
public class BlockEntityTypes {
public static <T extends BlockEntity> BlockEntityType<T> register(Key id, BlockEntity.Factory<T> factory) {
BlockEntityType<T> type = new BlockEntityType<>(id, factory);
((WritableRegistry<BlockEntityType<?>>) BuiltInRegistries.BLOCK_ENTITY_TYPE)
.register(ResourceKey.create(Registries.BLOCK_ENTITY_TYPE.location(), id), type);
return type;
}
}

View File

@@ -0,0 +1,38 @@
package net.momirealms.craftengine.core.block.entity.render;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.entity.player.Player;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
public class ConstantBlockEntityRenderer {
private final BlockEntityElement[] elements;
public ConstantBlockEntityRenderer(BlockEntityElement[] elements) {
this.elements = elements;
}
public void show(Player player) {
for (BlockEntityElement element : this.elements) {
element.show(player);
}
}
public void hide(Player player) {
for (BlockEntityElement element : this.elements) {
element.hide(player);
}
}
public void deactivate() {
for (BlockEntityElement element : this.elements) {
element.deactivate();
}
}
public void activate() {
for (BlockEntityElement element : this.elements) {
element.activate();
}
}
}

View File

@@ -0,0 +1,14 @@
package net.momirealms.craftengine.core.block.entity.render;
import net.momirealms.craftengine.core.entity.player.Player;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
public interface DynamicBlockEntityRenderer {
void show(Player player);
void hide(Player player);
void update(Player player);
}

View File

@@ -0,0 +1,16 @@
package net.momirealms.craftengine.core.block.entity.render.element;
import net.momirealms.craftengine.core.entity.player.Player;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
public interface BlockEntityElement {
void show(Player player);
void hide(Player player);
default void deactivate() {}
default void activate() {}
}

View File

@@ -0,0 +1,9 @@
package net.momirealms.craftengine.core.block.entity.render.element;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
public interface BlockEntityElementConfig<E extends BlockEntityElement> {
E create(World world, BlockPos pos);
}

View File

@@ -0,0 +1,9 @@
package net.momirealms.craftengine.core.block.entity.render.element;
import java.util.Map;
@FunctionalInterface
public interface BlockEntityElementConfigFactory {
<E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> args);
}

View File

@@ -0,0 +1,30 @@
package net.momirealms.craftengine.core.block.entity.render.element;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
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.ResourceKey;
import java.util.Map;
import java.util.Optional;
public class BlockEntityElementConfigs {
public static final Key ITEM_DISPLAY = Key.of("craftengine:item_display");
public static final Key TEXT_DISPLAY = Key.of("craftengine:text_display");
public static void register(Key key, BlockEntityElementConfigFactory type) {
((WritableRegistry<BlockEntityElementConfigFactory>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE)
.register(ResourceKey.create(Registries.BLOCK_ENTITY_ELEMENT_TYPE.location(), key), type);
}
public static <E extends BlockEntityElement> BlockEntityElementConfig<E> fromMap(Map<String, Object> arguments) {
Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(it -> Key.withDefaultNamespace(it, "craftengine")).orElse(ITEM_DISPLAY);
BlockEntityElementConfigFactory factory = BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type);
if (factory == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.entity_renderer.invalid_type", type.toString());
}
return factory.create(arguments);
}
}

View File

@@ -0,0 +1,11 @@
package net.momirealms.craftengine.core.block.entity.tick;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
public interface BlockEntityTicker<T extends BlockEntity> {
void tick(CEWorld world, BlockPos pos, ImmutableBlockState state, T blockEntity);
}

View File

@@ -0,0 +1,21 @@
package net.momirealms.craftengine.core.block.entity.tick;
import net.momirealms.craftengine.core.world.BlockPos;
public class DummyTickingBlockEntity implements TickingBlockEntity {
public static final DummyTickingBlockEntity INSTANCE = new DummyTickingBlockEntity();
@Override
public boolean isValid() {
return false;
}
@Override
public void tick() {
}
@Override
public BlockPos pos() {
return BlockPos.ZERO;
}
}

View File

@@ -0,0 +1,34 @@
package net.momirealms.craftengine.core.block.entity.tick;
import net.momirealms.craftengine.core.world.BlockPos;
public class ReplaceableTickingBlockEntity implements TickingBlockEntity {
private TickingBlockEntity target;
public ReplaceableTickingBlockEntity(TickingBlockEntity target) {
this.target = target;
}
public TickingBlockEntity target() {
return target;
}
public void setTicker(TickingBlockEntity target) {
this.target = target;
}
@Override
public BlockPos pos() {
return this.target.pos();
}
@Override
public void tick() {
this.target.tick();
}
@Override
public boolean isValid() {
return this.target.isValid();
}
}

View File

@@ -0,0 +1,12 @@
package net.momirealms.craftengine.core.block.entity.tick;
import net.momirealms.craftengine.core.world.BlockPos;
public interface TickingBlockEntity {
void tick();
boolean isValid();
BlockPos pos();
}

View File

@@ -0,0 +1,49 @@
package net.momirealms.craftengine.core.block.entity.tick;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.chunk.CEChunk;
public class TickingBlockEntityImpl<T extends BlockEntity> implements TickingBlockEntity {
private final T blockEntity;
private final BlockEntityTicker<T> ticker;
private final CEChunk chunk;
public TickingBlockEntityImpl(CEChunk chunk, T blockEntity, BlockEntityTicker<T> ticker) {
this.blockEntity = blockEntity;
this.ticker = ticker;
this.chunk = chunk;
}
@Override
public BlockPos pos() {
return this.blockEntity.pos();
}
@Override
public void tick() {
// 还没加载完全
if (this.blockEntity.world == null) return;
BlockPos pos = pos();
ImmutableBlockState state = this.chunk.getBlockState(pos);
// 不是合法方块
if (!this.blockEntity.isValidBlockState(state)) {
this.chunk.removeBlockEntity(pos);
Debugger.BLOCK_ENTITY.warn(() -> "Invalid block entity(" + this.blockEntity.getClass().getSimpleName() + ") with state " + state + " found at world " + this.chunk.world().name() + " " + pos, null);
return;
}
try {
this.ticker.tick(this.chunk.world(), pos, state, this.blockEntity);
} catch (Throwable t) {
CraftEngine.instance().logger().warn("Failed to tick block entity(" + this.blockEntity.getClass().getSimpleName() + ") at world " + this.chunk.world().name() + " " + pos, t);
}
}
@Override
public boolean isValid() {
return this.blockEntity.isValid();
}
}

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.block.parser;
import net.momirealms.craftengine.core.block.BlockStateVariantProvider;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.StringReader;
@@ -7,11 +8,13 @@ import net.momirealms.sparrow.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Function;
public final class BlockNbtParser {
private BlockNbtParser() {}
@Nullable
public static CompoundTag deserialize(@NotNull CustomBlock block, @NotNull String data) {
public static CompoundTag deserialize(@NotNull Function<String, Property<?>> propertyProvider, @NotNull String data) {
StringReader reader = StringReader.simple(data);
CompoundTag properties = new CompoundTag();
while (reader.canRead()) {
@@ -24,7 +27,7 @@ public final class BlockNbtParser {
if (propertyValue.isEmpty()) {
return null;
}
Property<?> property = block.getProperty(propertyName);
Property<?> property = propertyProvider.apply(propertyName);
if (property != null) {
property.createOptionalTag(propertyValue).ifPresent(tag -> {
properties.put(propertyName, tag);
@@ -38,4 +41,14 @@ public final class BlockNbtParser {
}
return properties;
}
@Nullable
public static CompoundTag deserialize(@NotNull CustomBlock block, @NotNull String data) {
return deserialize(block::getProperty, data);
}
@Nullable
public static CompoundTag deserialize(@NotNull BlockStateVariantProvider variantProvider, @NotNull String data) {
return deserialize(variantProvider::getProperty, data);
}
}

View File

@@ -1,6 +1,6 @@
package net.momirealms.craftengine.core.block.properties;
import net.momirealms.craftengine.core.block.state.properties.*;
import net.momirealms.craftengine.core.block.properties.type.*;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Registries;
@@ -21,6 +21,8 @@ public final class Properties {
public static final Key HINGE = Key.of("craftengine:hinge");
public static final Key STAIRS_SHAPE = Key.of("craftengine:stairs_shape");
public static final Key SLAB_TYPE = Key.of("craftengine:slab_type");
public static final Key SOFA_SHAPE = Key.of("craftengine:sofa_shape");
public static final Key ANCHOR_TYPE = Key.of("craftengine:anchor_type");
static {
register(BOOLEAN, BooleanProperty.FACTORY);
@@ -36,6 +38,8 @@ public final class Properties {
register(HINGE, new EnumProperty.Factory<>(DoorHinge.class));
register(STAIRS_SHAPE, new EnumProperty.Factory<>(StairsShape.class));
register(SLAB_TYPE, new EnumProperty.Factory<>(SlabType.class));
register(SOFA_SHAPE, new EnumProperty.Factory<>(SofaShape.class));
register(ANCHOR_TYPE, new EnumProperty.Factory<>(AnchorType.class));
}
public static void register(Key key, PropertyFactory factory) {

View File

@@ -167,4 +167,9 @@ public abstract class Property<T extends Comparable<T>> {
public static <T extends Comparable<T>> String formatValue(Property<T> property, Comparable<?> value) {
return property.valueName((T) value);
}
@Override
public String toString() {
return this.getClass().getSimpleName() + "{clazz=" + this.clazz + ", name='" + this.name + "', values=" + this.possibleValues() + '}';
}
}

View File

@@ -0,0 +1,7 @@
package net.momirealms.craftengine.core.block.properties.type;
public enum AnchorType {
FLOOR,
WALL,
CEILING
}

View File

@@ -0,0 +1,5 @@
package net.momirealms.craftengine.core.block.properties.type;
public enum DoorHinge {
LEFT, RIGHT
}

View File

@@ -0,0 +1,5 @@
package net.momirealms.craftengine.core.block.properties.type;
public enum DoubleBlockHalf {
UPPER, LOWER
}

View File

@@ -0,0 +1,5 @@
package net.momirealms.craftengine.core.block.properties.type;
public enum SingleBlockHalf {
TOP, BOTTOM
}

View File

@@ -0,0 +1,7 @@
package net.momirealms.craftengine.core.block.properties.type;
public enum SlabType {
TOP,
BOTTOM,
DOUBLE
}

View File

@@ -0,0 +1,7 @@
package net.momirealms.craftengine.core.block.properties.type;
public enum SofaShape {
STRAIGHT,
INNER_LEFT,
INNER_RIGHT,
}

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.core.block.state.properties;
package net.momirealms.craftengine.core.block.properties.type;
public enum StairsShape {
STRAIGHT,

View File

@@ -1,5 +0,0 @@
package net.momirealms.craftengine.core.block.state.properties;
public enum DoorHinge {
LEFT, RIGHT
}

View File

@@ -1,5 +0,0 @@
package net.momirealms.craftengine.core.block.state.properties;
public enum DoubleBlockHalf {
UPPER, LOWER
}

View File

@@ -1,5 +0,0 @@
package net.momirealms.craftengine.core.block.state.properties;
public enum SingleBlockHalf {
TOP, BOTTOM
}

View File

@@ -1,7 +0,0 @@
package net.momirealms.craftengine.core.block.state.properties;
public enum SlabType {
TOP,
BOTTOM,
DOUBLE
}

View File

@@ -5,10 +5,12 @@ import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.PendingConfigSection;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser;
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
@@ -31,7 +33,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
}
@Override
public ConfigParser parser() {
public FurnitureParser parser() {
return this.furnitureParser;
}
@@ -58,6 +60,11 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
return Optional.ofNullable(this.byId.get(id));
}
@Override
public Map<Key, CustomFurniture> loadedFurniture() {
return Collections.unmodifiableMap(this.byId);
}
@Override
public void unload() {
this.byId.clear();
@@ -69,8 +76,26 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
protected abstract CustomFurniture.Builder furnitureBuilder();
public class FurnitureParser implements ConfigParser {
public class FurnitureParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" };
private final List<PendingConfigSection> pendingConfigSections = new ArrayList<>();
public void addPendingConfigSection(PendingConfigSection section) {
this.pendingConfigSections.add(section);
}
@Override
public void preProcess() {
for (PendingConfigSection section : this.pendingConfigSections) {
ResourceConfigUtils.runCatching(
section.path(),
section.node(),
() -> parseSection(section.pack(), section.path(), section.node(), section.id(), section.config()),
() -> GsonHelper.get().toJson(section.config())
);
}
this.pendingConfigSections.clear();
}
@Override
public String[] sectionId() {
@@ -84,8 +109,8 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
@SuppressWarnings("unchecked")
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
if (byId.containsKey(id)) {
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (AbstractFurnitureManager.this.byId.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.furniture.duplicate");
}
EnumMap<AnchorType, CustomFurniture.Placement> placements = new EnumMap<>(AnchorType.class);
@@ -98,7 +123,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
// anchor type
AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH));
Map<String, Object> placementArguments = MiscUtils.castToMap(entry.getValue(), false);
Optional<Vector3f> optionalLootSpawnOffset = Optional.ofNullable(placementArguments.get("loot-spawn-offset")).map(it -> MiscUtils.getAsVector3f(it, "loot-spawn-offset"));
Optional<Vector3f> optionalLootSpawnOffset = Optional.ofNullable(placementArguments.get("loot-spawn-offset")).map(it -> ResourceConfigUtils.getAsVector3f(it, "loot-spawn-offset"));
// furniture display elements
List<FurnitureElement> elements = new ArrayList<>();
List<Map<String, Object>> elementConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("elements", List.of());
@@ -108,10 +133,10 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
.applyDyedColor(ResourceConfigUtils.getAsBoolean(element.getOrDefault("apply-dyed-color", true), "apply-dyed-color"))
.billboard(ResourceConfigUtils.getOrDefault(element.get("billboard"), o -> Billboard.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), Billboard.FIXED))
.transform(ResourceConfigUtils.getOrDefault(element.get("transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE))
.scale(MiscUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale"))
.position(MiscUtils.getAsVector3f(element.getOrDefault("position", "0"), "position"))
.translation(MiscUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation"))
.rotation(MiscUtils.getAsQuaternionf(element.getOrDefault("rotation", "0"), "rotation"))
.scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale"))
.position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position"))
.translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation"))
.rotation(ResourceConfigUtils.getAsQuaternionf(element.getOrDefault("rotation", "0"), "rotation"))
.build();
elements.add(furnitureElement);
}

View File

@@ -9,6 +9,7 @@ import org.incendo.cloud.suggestion.Suggestion;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
public interface FurnitureManager extends Manageable {
@@ -30,6 +31,8 @@ public interface FurnitureManager extends Manageable {
Optional<CustomFurniture> furnitureById(Key id);
Map<Key, CustomFurniture> loadedFurniture();
boolean isFurnitureRealEntity(int entityId);
@Nullable

View File

@@ -1,6 +1,6 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import java.util.List;
import java.util.Map;
@@ -15,8 +15,8 @@ public interface HitBoxFactory {
return seats.stream()
.map(arg -> {
String[] split = arg.split(" ");
if (split.length == 1) return new Seat(MiscUtils.getAsVector3f(split[0], "seats"), 0, false);
return new Seat(MiscUtils.getAsVector3f(split[0], "seats"), Float.parseFloat(split[1]), true);
if (split.length == 1) return new Seat(ResourceConfigUtils.getAsVector3f(split[0], "seats"), 0, false);
return new Seat(ResourceConfigUtils.getAsVector3f(split[0], "seats"), Float.parseFloat(split[1]), true);
})
.toArray(Seat[]::new);
}

View File

@@ -1,13 +1,18 @@
package net.momirealms.craftengine.core.entity.player;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.core.advancement.AdvancementType;
import net.momirealms.craftengine.core.entity.AbstractEntity;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.context.CooldownData;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.sound.SoundSource;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Position;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.jetbrains.annotations.NotNull;
public abstract class Player extends AbstractEntity implements NetWorkUser {
@@ -62,6 +67,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
public abstract boolean canPlace(BlockPos pos, Object state);
public abstract void sendToast(Component text, Item<?> icon, AdvancementType type);
public abstract void sendActionBar(Component text);
public abstract void sendMessage(Component text, boolean overlay);
@@ -96,7 +103,19 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
public abstract void playSound(Key sound, SoundSource source, float volume, float pitch);
public abstract void playSound(Key sound, BlockPos pos, SoundSource source, float volume, float pitch);
public abstract void playSound(Position pos, Key sound, SoundSource source, float volume, float pitch);
public void playSound(BlockPos pos, Key sound, SoundSource source, float volume, float pitch) {
this.playSound(Vec3d.atCenterOf(pos), sound, source, volume, pitch);
}
public void playSound(BlockPos pos, SoundData data, SoundSource source) {
this.playSound(pos, data.id(), source, data.volume().get(), data.pitch().get());
}
public void playSound(Position pos, SoundData data, SoundSource source) {
this.playSound(pos, data.id(), source, data.volume().get(), data.pitch().get());
}
public abstract void giveItem(Item<?> item);
@@ -148,4 +167,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
public abstract void clearPotionEffects();
public abstract CooldownData cooldown();
public abstract void teleport(WorldPosition worldPosition);
public abstract void damage(double amount, Key damageType);
}

View File

@@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.entity.projectile;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
@@ -13,6 +12,5 @@ public record ProjectileMeta(Key item,
Vector3f scale,
Vector3f translation,
Quaternionf rotation,
double range,
@Nullable ProjectileType type) {
double range) {
}

View File

@@ -1,5 +0,0 @@
package net.momirealms.craftengine.core.entity.projectile;
public enum ProjectileType {
TRIDENT
}

View File

@@ -5,8 +5,11 @@ import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.pack.allocator.IdAllocator;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
@@ -23,6 +26,9 @@ import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -40,7 +46,7 @@ public abstract class AbstractFontManager implements FontManager {
private final EmojiParser emojiParser;
private OffsetFont offsetFont;
protected Trie imageTagTrie;
protected Trie networkTagTrie;
protected Trie emojiKeywordTrie;
protected Map<String, ComponentProvider> networkTagMapper;
protected Map<String, Emoji> emojiMapper;
@@ -53,6 +59,14 @@ public abstract class AbstractFontManager implements FontManager {
this.emojiParser = new EmojiParser();
}
public ImageParser imageParser() {
return imageParser;
}
public EmojiParser emojiParser() {
return emojiParser;
}
@Override
public void load() {
this.offsetFont = Optional.ofNullable(plugin.config().settings().getSection("image.offset-characters"))
@@ -61,15 +75,35 @@ public abstract class AbstractFontManager implements FontManager {
this.networkTagMapper = new HashMap<>(1024);
}
@Override
public OffsetFont offsetFont() {
return offsetFont;
}
@Override
public Map<Key, BitmapImage> loadedImages() {
return Collections.unmodifiableMap(this.images);
}
@Override
public Map<Key, Emoji> emojis() {
return Collections.unmodifiableMap(this.emojis);
}
@Override
public void unload() {
this.fonts.clear();
this.images.clear();
this.illegalChars.clear();
this.emojis.clear();
this.networkTagTrie = null;
this.emojiKeywordTrie = null;
if (this.networkTagMapper != null) {
this.networkTagMapper.clear();
}
if (this.emojiMapper != null) {
this.emojiMapper.clear();
}
}
@Override
@@ -88,7 +122,7 @@ public abstract class AbstractFontManager implements FontManager {
this.registerImageTags();
this.registerShiftTags();
this.registerGlobalTags();
this.buildImageTagTrie();
this.buildNetworkTagTrie();
this.buildEmojiKeywordsTrie();
this.emojiList = new ArrayList<>(this.emojis.values());
this.allEmojiSuggestions = this.emojis.values().stream()
@@ -131,11 +165,11 @@ public abstract class AbstractFontManager implements FontManager {
@Override
public Map<String, ComponentProvider> matchTags(String json) {
if (this.imageTagTrie == null) {
if (this.networkTagTrie == null) {
return Collections.emptyMap();
}
Map<String, ComponentProvider> tags = new HashMap<>();
for (Token token : this.imageTagTrie.tokenize(json)) {
for (Token token : this.networkTagTrie.tokenize(json)) {
if (token.isMatch()) {
tags.put(token.getFragment(), this.networkTagMapper.get(token.getFragment()));
}
@@ -208,7 +242,7 @@ public abstract class AbstractFontManager implements FontManager {
emoji.content(),
PlayerOptionalContext.of(player, ContextHolder.builder()
.withOptionalParameter(EmojiParameters.EMOJI, emoji.emojiImage())
.withParameter(EmojiParameters.KEYWORD, emoji.keywords().get(0))
.withParameter(EmojiParameters.KEYWORD, emoji.keywords().getFirst())
).tagResolvers())
);
if (emojis.size() >= maxTimes) break;
@@ -305,8 +339,8 @@ public abstract class AbstractFontManager implements FontManager {
.build();
}
private void buildImageTagTrie() {
this.imageTagTrie = Trie.builder()
private void buildNetworkTagTrie() {
this.networkTagTrie = Trie.builder()
.ignoreOverlaps()
.addKeywords(this.networkTagMapper.keySet())
.build();
@@ -366,7 +400,7 @@ public abstract class AbstractFontManager implements FontManager {
return this.fonts.computeIfAbsent(key, Font::new);
}
public class EmojiParser implements ConfigParser {
public class EmojiParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"emoji", "emojis"};
@Override
@@ -380,18 +414,18 @@ public abstract class AbstractFontManager implements FontManager {
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (emojis.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.emoji.duplicate", path, id);
throw new LocalizedResourceConfigException("warning.config.emoji.duplicate");
}
String permission = (String) section.get("permission");
Object keywordsRaw = section.get("keywords");
if (keywordsRaw == null) {
throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords", path, id);
throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords");
}
List<String> keywords = MiscUtils.getAsStringList(keywordsRaw);
if (keywords.isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords", path, id);
throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords");
}
Object rawContent = section.getOrDefault("content", "<white><arg:emoji></white>");
String content;
@@ -410,7 +444,7 @@ public abstract class AbstractFontManager implements FontManager {
if (bitmapImage.isPresent()) {
image = bitmapImage.get().miniMessageAt(0, 0);
} else {
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage);
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage);
}
} else if (split.length == 4) {
Key imageId = new Key(split[0], split[1]);
@@ -419,13 +453,13 @@ public abstract class AbstractFontManager implements FontManager {
try {
image = bitmapImage.get().miniMessageAt(Integer.parseInt(split[2]), Integer.parseInt(split[3]));
} catch (ArrayIndexOutOfBoundsException e) {
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage);
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage);
}
} else {
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage);
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage);
}
} else {
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage);
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage);
}
}
Emoji emoji = new Emoji(content, permission, image, keywords);
@@ -433,8 +467,9 @@ public abstract class AbstractFontManager implements FontManager {
}
}
public class ImageParser implements ConfigParser {
public class ImageParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"images", "image"};
private final Map<Key, IdAllocator> idAllocators = new HashMap<>();
@Override
public String[] sectionId() {
@@ -447,129 +482,219 @@ public abstract class AbstractFontManager implements FontManager {
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
if (images.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.image.duplicate", path, id);
public void postProcess() {
for (Map.Entry<Key, IdAllocator> entry : this.idAllocators.entrySet()) {
entry.getValue().processPendingAllocations();
try {
entry.getValue().saveToCache();
} catch (IOException e) {
AbstractFontManager.this.plugin.logger().warn("Error while saving codepoint allocation for font " + entry.getKey().asString(), e);
}
}
}
@Override
public void preProcess() {
this.idAllocators.clear();
}
public IdAllocator getOrCreateIdAllocator(Key key) {
return this.idAllocators.computeIfAbsent(key, k -> {
IdAllocator newAllocator = new IdAllocator(plugin.dataFolderPath().resolve("cache").resolve("font").resolve(k.namespace()).resolve(k.value() + ".json"));
newAllocator.reset(Config.codepointStartingValue(k), 1114111); // utf16
try {
newAllocator.loadFromCache();
} catch (IOException e) {
AbstractFontManager.this.plugin.logger().warn("Error while loading chars data from cache for font " + k.asString(), e);
}
return newAllocator;
});
}
@Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (AbstractFontManager.this.images.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.image.duplicate");
}
Object file = section.get("file");
if (file == null) {
throw new LocalizedResourceConfigException("warning.config.image.missing_file", path, id);
throw new LocalizedResourceConfigException("warning.config.image.missing_file");
}
String resourceLocation = CharacterUtils.replaceBackslashWithSlash(file.toString());
String resourceLocation = MiscUtils.make(CharacterUtils.replaceBackslashWithSlash(file.toString()), s -> s.endsWith(".png") ? s : s + ".png");
if (!ResourceLocation.isValid(resourceLocation)) {
throw new LocalizedResourceConfigException("warning.config.image.invalid_file_chars", path, id, resourceLocation);
throw new LocalizedResourceConfigException("warning.config.image.invalid_file_chars", resourceLocation);
}
String fontName = section.getOrDefault("font", "minecraft:default").toString();
String fontName = section.getOrDefault("font", pack.namespace()+ ":default").toString();
if (!ResourceLocation.isValid(fontName)) {
throw new LocalizedResourceConfigException("warning.config.image.invalid_font_chars", path, id, fontName);
throw new LocalizedResourceConfigException("warning.config.image.invalid_font_chars", fontName);
}
Key fontKey = Key.withDefaultNamespace(fontName, id.namespace());
Font font = getOrCreateFont(fontKey);
List<char[]> chars;
Key fontId = Key.withDefaultNamespace(fontName, id.namespace());
Font font = getOrCreateFont(fontId);
IdAllocator allocator = getOrCreateIdAllocator(fontId);
int rows;
int columns;
List<CompletableFuture<Integer>> futureCodepoints = new ArrayList<>();
Object charsObj = ResourceConfigUtils.get(section, "chars", "char");
// 自动分配
if (charsObj == null) {
throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id);
}
if (charsObj instanceof List<?> list) {
chars = MiscUtils.getAsStringList(list).stream().map(it -> {
if (it.startsWith("\\u")) {
return CharacterUtils.decodeUnicodeToChars(it);
} else {
return it.toCharArray();
Object grid = section.get("grid-size");
if (grid != null) {
String gridString = grid.toString();
String[] split = gridString.split(",");
if (split.length != 2) {
throw new LocalizedResourceConfigException("warning.config.image.invalid_grid_size", gridString);
}
}).toList();
if (chars.isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id);
rows = Integer.parseInt(split[0]);
columns = Integer.parseInt(split[1]);
int chars = rows * columns;
if (chars <= 0) {
throw new LocalizedResourceConfigException("warning.config.image.invalid_grid_size", gridString);
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
futureCodepoints.add(allocator.requestAutoId(id.asString() + ":" + i + ":" + j));
}
}
} else {
rows = 1;
columns = 1;
futureCodepoints.add(allocator.requestAutoId(id.asString()));
}
} else {
if (charsObj instanceof Integer integer) {
chars = List.of(new char[]{(char) integer.intValue()});
}
// 使用了list
else if (charsObj instanceof List<?> list) {
List<String> charsList = MiscUtils.getAsStringList(list);
if (charsList.isEmpty() || charsList.getFirst().isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.image.missing_char");
}
int tempColumns = -1;
rows = charsList.size();
for (int i = 0; i < charsList.size(); i++) {
String charString = charsList.get(i);
int[] codepoints;
if (charString.startsWith("\\u")) {
codepoints = CharacterUtils.charsToCodePoints(CharacterUtils.decodeUnicodeToChars(charString));
} else {
codepoints = CharacterUtils.charsToCodePoints(charString.toCharArray());
}
for (int j = 0; j < codepoints.length; j++) {
if (codepoints[j] == 0) {
futureCodepoints.add(CompletableFuture.completedFuture(0));
} else {
futureCodepoints.add(allocator.assignFixedId(id.asString() + ":" + i + ":" + j, codepoints[j]));
}
}
if (tempColumns == -1) {
tempColumns = codepoints.length;
} else if (tempColumns != codepoints.length) {
throw new LocalizedResourceConfigException("warning.config.image.invalid_codepoint_grid");
}
}
columns = tempColumns;
}
// 使用了具体的值
else {
if (charsObj instanceof Integer codepoint) {
futureCodepoints.add(allocator.assignFixedId(id.asString(), codepoint));
rows = 1;
columns = 1;
} else {
String character = charsObj.toString();
if (character.isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id);
throw new LocalizedResourceConfigException("warning.config.image.missing_char");
}
if (character.length() == 1) {
chars = List.of(character.toCharArray());
rows = 1;
int[] codepoints;
if (character.startsWith("\\u")) {
codepoints = CharacterUtils.charsToCodePoints(CharacterUtils.decodeUnicodeToChars(character));
} else {
if (character.startsWith("\\u")) {
chars = List.of(CharacterUtils.decodeUnicodeToChars(character));
codepoints = CharacterUtils.charsToCodePoints(character.toCharArray());
}
columns = codepoints.length;
for (int i = 0; i < codepoints.length; i++) {
if (codepoints[i] == 0) {
futureCodepoints.add(CompletableFuture.completedFuture(0));
} else {
// ??? TODO 需要测试特殊字符集
// if (CharacterUtils.containsCombinedCharacter(character)) {
// TranslationManager.instance().log("warning.config.image.invalid_char", path.toString(), id.toString());
// }
chars = List.of(character.toCharArray());
futureCodepoints.add(allocator.assignFixedId(id.asString() + ":0:" + i, codepoints[i]));
}
}
}
}
int size = -1;
int[][] codepointGrid = new int[chars.size()][];
for (int i = 0; i < chars.size(); ++i) {
int[] codepoints = CharacterUtils.charsToCodePoints(chars.get(i));
for (int codepoint : codepoints) {
if (font.isCodepointInUse(codepoint)) {
BitmapImage image = font.bitmapImageByCodepoint(codepoint);
throw new LocalizedResourceConfigException("warning.config.image.codepoint_conflict", path, id,
fontKey.toString(),
CharacterUtils.encodeCharsToUnicode(Character.toChars(codepoint)),
new String(Character.toChars(codepoint)),
image.id().toString());
CompletableFutures.allOf(futureCodepoints).whenComplete((v, t) -> ResourceConfigUtils.runCatching(path, node, () -> {
if (t != null) {
if (t instanceof CompletionException e) {
Throwable cause = e.getCause();
if (cause instanceof IdAllocator.IdConflictException conflict) {
throw new LocalizedResourceConfigException("warning.config.image.codepoint.conflict",
fontId.toString(),
CharacterUtils.encodeCharsToUnicode(Character.toChars(conflict.id())),
new String(Character.toChars(conflict.id())),
conflict.previousOwner()
);
} else if (cause instanceof IdAllocator.IdExhaustedException) {
throw new LocalizedResourceConfigException("warning.config.image.codepoint.exhausted", fontId.asString());
}
}
throw new RuntimeException("Unknown error occurred", t);
}
int[][] codepointGrid = new int[rows][columns];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
try {
int codepoint = futureCodepoints.get(i * columns + j).get();
codepointGrid[i][j] = codepoint;
} catch (InterruptedException | ExecutionException e) {
AbstractFontManager.this.plugin.logger().warn("Interrupted while allocating codepoint for image " + id.asString(), e);
return;
}
}
}
if (codepoints.length == 0) {
throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id);
}
codepointGrid[i] = codepoints;
if (size == -1) size = codepoints.length;
if (size != codepoints.length) {
throw new LocalizedResourceConfigException("warning.config.image.invalid_codepoint_grid", path, id);
}
}
Object heightObj = section.get("height");
if (!resourceLocation.endsWith(".png")) resourceLocation += ".png";
if (heightObj == null) {
Key namespacedPath = Key.of(resourceLocation);
Path targetImagePath = pack.resourcePackFolder()
.resolve("assets")
.resolve(namespacedPath.namespace())
.resolve("textures")
.resolve(namespacedPath.value());
if (Files.exists(targetImagePath)) {
try (InputStream in = Files.newInputStream(targetImagePath)) {
BufferedImage image = ImageIO.read(in);
heightObj = image.getHeight() / codepointGrid.length;
} catch (IOException e) {
plugin.logger().warn("Failed to load image " + targetImagePath, e);
return;
Object heightObj = section.get("height");
if (heightObj == null) {
Key namespacedPath = Key.of(resourceLocation);
Path targetImagePath = pack.resourcePackFolder()
.resolve("assets")
.resolve(namespacedPath.namespace())
.resolve("textures")
.resolve(namespacedPath.value());
if (Files.exists(targetImagePath)) {
try (InputStream in = Files.newInputStream(targetImagePath)) {
BufferedImage image = ImageIO.read(in);
heightObj = image.getHeight() / codepointGrid.length;
} catch (IOException e) {
plugin.logger().warn("Failed to load image " + targetImagePath, e);
return;
}
} else {
throw new LocalizedResourceConfigException("warning.config.image.missing_height");
}
} else {
throw new LocalizedResourceConfigException("warning.config.image.missing_height", path, id);
}
}
int height = ResourceConfigUtils.getAsInt(heightObj, "height");
int ascent = ResourceConfigUtils.getAsInt(section.getOrDefault("ascent", height - 1), "ascent");
if (height < ascent) {
throw new LocalizedResourceConfigException("warning.config.image.height_ascent_conflict", path, id, String.valueOf(height), String.valueOf(ascent));
}
BitmapImage bitmapImage = new BitmapImage(id, fontKey, height, ascent, resourceLocation, codepointGrid);
for (int[] y : codepointGrid) {
for (int x : y) {
font.addBitmapImage(x, bitmapImage);
int height = ResourceConfigUtils.getAsInt(heightObj, "height");
int ascent = ResourceConfigUtils.getAsInt(section.getOrDefault("ascent", height - 1), "ascent");
if (height < ascent) {
throw new LocalizedResourceConfigException("warning.config.image.height_ascent_conflict", String.valueOf(height), String.valueOf(ascent));
}
}
images.put(id, bitmapImage);
BitmapImage bitmapImage = new BitmapImage(id, fontId, height, ascent, resourceLocation, codepointGrid);
for (int[] y : codepointGrid) {
for (int x : y) {
font.addBitmapImage(x, bitmapImage);
}
}
AbstractFontManager.this.images.put(id, bitmapImage);
}, () -> GsonHelper.get().toJson(section)));
}
}
}

View File

@@ -46,6 +46,12 @@ public interface FontManager extends Manageable {
IllegalCharacterProcessResult processIllegalCharacters(String raw, char replacement);
OffsetFont offsetFont();
Map<Key, BitmapImage> loadedImages();
Map<Key, Emoji> emojis();
ConfigParser[] parsers();
default EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, @Nullable Player player) {

View File

@@ -12,7 +12,8 @@ import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
public class OffsetFont {
private final String font;
private static OffsetFont instance;
private final net.momirealms.craftengine.core.util.Key font;
private final Key fontKey;
private final String NEG_16;
@@ -39,10 +40,14 @@ public class OffsetFont {
.maximumSize(256)
.build();
public OffsetFont(Section section) {
font = section.getString("font", "minecraft:default");
fontKey = Key.key(font);
public net.momirealms.craftengine.core.util.Key font() {
return font;
}
@SuppressWarnings("all")
public OffsetFont(Section section) {
font = net.momirealms.craftengine.core.util.Key.of(section.getString("font", "minecraft:default"));
fontKey = Key.key(font.namespace(), font.value());
NEG_16 = convertIfUnicode(section.getString("-16", ""));
NEG_24 = convertIfUnicode(section.getString("-24", ""));
NEG_32 = convertIfUnicode(section.getString("-32", ""));
@@ -71,7 +76,7 @@ public class OffsetFont {
public String createOffset(int offset, BiFunction<String, String, String> tagDecorator) {
if (offset == 0) return "";
return tagDecorator.apply(this.fastLookup.get(offset, k -> k > 0 ? createPos(k) : createNeg(-k)), this.font);
return tagDecorator.apply(this.fastLookup.get(offset, k -> k > 0 ? createPos(k) : createNeg(-k)), this.font.asString());
}
@SuppressWarnings("DuplicatedCode")
@@ -148,7 +153,7 @@ public class OffsetFont {
private String convertIfUnicode(String s) {
if (s.startsWith("\\u")) {
return new String(CharacterUtils.decodeUnicodeToChars(font));
return new String(CharacterUtils.decodeUnicodeToChars(font.asString()));
} else {
return s;
}

View File

@@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.pack.AbstractPackManager;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.pack.allocator.IdAllocator;
import net.momirealms.craftengine.core.pack.model.*;
import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator;
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
@@ -22,17 +23,21 @@ import net.momirealms.craftengine.core.pack.model.select.TrimMaterialSelectPrope
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.util.*;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.type.Either;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -48,7 +53,6 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
protected final Map<Key, CustomItem<I>> customItemsById = new HashMap<>();
protected final Map<String, CustomItem<I>> customItemsByPath = new HashMap<>();
protected final Map<Key, List<UniqueKey>> customItemTags = new HashMap<>();
protected final Map<Key, Map<Integer, Key>> cmdConflictChecker = new HashMap<>();
protected final Map<Key, ModernItemModel> modernItemModels1_21_4 = new HashMap<>();
protected final Map<Key, TreeSet<LegacyOverridesModel>> modernItemModels1_21_2 = new HashMap<>();
protected final Map<Key, TreeSet<LegacyOverridesModel>> legacyOverrides = new HashMap<>();
@@ -65,6 +69,14 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
ItemDataModifiers.init();
}
public ItemParser itemParser() {
return itemParser;
}
public EquipmentParser equipmentParser() {
return equipmentParser;
}
protected static void registerVanillaItemExtraBehavior(ItemBehavior behavior, Key... items) {
for (Key key : items) {
VANILLA_ITEM_EXTRA_BEHAVIORS.computeIfAbsent(key, k -> new ArrayList<>()).add(behavior);
@@ -124,7 +136,6 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
this.modernOverrides.clear();
this.customItemTags.clear();
this.equipments.clear();
this.cmdConflictChecker.clear();
this.modernItemModels1_21_4.clear();
this.modernItemModels1_21_2.clear();
}
@@ -229,8 +240,8 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
}
@Override
public Collection<Key> items() {
return Collections.unmodifiableCollection(this.customItemsById.keySet());
public Map<Key, CustomItem<I>> loadedItems() {
return Collections.unmodifiableMap(this.customItemsById);
}
@Override
@@ -267,7 +278,7 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
protected abstract void registerArmorTrimPattern(Collection<Key> equipments);
public class EquipmentParser implements ConfigParser {
public class EquipmentParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"equipments", "equipment"};
@Override
@@ -281,7 +292,7 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (AbstractItemManager.this.equipments.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.equipment.duplicate");
}
@@ -310,8 +321,9 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
}
}
public class ItemParser implements ConfigParser {
public class ItemParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"};
private final Map<Key, IdAllocator> idAllocators = new HashMap<>();
private boolean isModernFormatRequired() {
return Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_4);
@@ -321,6 +333,18 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
return Config.packMinVersion().isBelow(MinecraftVersions.V1_21_4);
}
private boolean needsCustomModelDataCompatibility() {
return Config.packMinVersion().isBelow(MinecraftVersions.V1_21_2);
}
private boolean needsItemModelCompatibility() {
return Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2) && VersionHelper.isOrAbove1_21_2(); //todo 能否通过客户端包解决问题
}
public Map<Key, IdAllocator> idAllocators() {
return this.idAllocators;
}
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
@@ -332,218 +356,311 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
public void preProcess() {
this.idAllocators.clear();
}
@Override
public void postProcess() {
for (Map.Entry<Key, IdAllocator> entry : this.idAllocators.entrySet()) {
entry.getValue().processPendingAllocations();
try {
entry.getValue().saveToCache();
} catch (IOException e) {
AbstractItemManager.this.plugin.logger().warn("Error while saving custom model data allocation for material " + entry.getKey().asString(), e);
}
}
}
// 创建或获取已有的自动分配器
private IdAllocator getOrCreateIdAllocator(Key key) {
return this.idAllocators.computeIfAbsent(key, k -> {
IdAllocator newAllocator = new IdAllocator(plugin.dataFolderPath().resolve("cache").resolve("custom-model-data").resolve(k.value() + ".json"));
newAllocator.reset(Config.customModelDataStartingValue(k), 16_777_216);
try {
newAllocator.loadFromCache();
} catch (IOException e) {
AbstractItemManager.this.plugin.logger().warn("Error while loading custom model data from cache for material " + k.asString(), e);
}
return newAllocator;
});
}
@Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (AbstractItemManager.this.customItemsById.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.item.duplicate");
}
// 创建UniqueKey仅缓存用
UniqueKey uniqueId = UniqueKey.create(id);
// 判断是不是原版物品
boolean isVanillaItem = isVanillaItem(id);
Key material = Key.from(isVanillaItem ? id.value() : ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("material"), "warning.config.item.missing_material").toLowerCase(Locale.ENGLISH));
Key clientBoundMaterial = section.containsKey("client-bound-material") ? Key.from(section.get("client-bound-material").toString().toLowerCase(Locale.ENGLISH)) : material;
// 如果是原版物品那么custom-model-data只能是0即使用户设置了其他值
int customModelData = isVanillaItem ? 0 : ResourceConfigUtils.getAsInt(section.getOrDefault("custom-model-data", 0), "custom-model-data");
boolean clientBoundModel = section.containsKey("client-bound-model") ? ResourceConfigUtils.getAsBoolean(section.get("client-bound-model"), "client-bound-model") : Config.globalClientboundModel();
if (customModelData < 0) {
throw new LocalizedResourceConfigException("warning.config.item.invalid_custom_model_data", String.valueOf(customModelData));
}
if (customModelData > 16_777_216) {
throw new LocalizedResourceConfigException("warning.config.item.bad_custom_model_data", String.valueOf(customModelData));
}
// item-model值
Key itemModelKey = null;
// 读取服务端侧材质
Key material = isVanillaItem ? id : Key.from(ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("material"), "warning.config.item.missing_material").toLowerCase(Locale.ROOT));
// 读取客户端侧材质
Key clientBoundMaterial = VersionHelper.PREMIUM && section.containsKey("client-bound-material") ? Key.from(section.get("client-bound-material").toString().toLowerCase(Locale.ROOT)) : material;
CustomItem.Builder<I> itemBuilder = createPlatformItemBuilder(uniqueId, material, clientBoundMaterial);
boolean hasItemModelSection = section.containsKey("item-model");
// custom model data
CompletableFuture<Integer> customModelDataFuture;
boolean forceCustomModelData;
// 如果custom-model-data不为0
if (customModelData > 0) {
if (clientBoundModel) itemBuilder.clientBoundDataModifier(new CustomModelDataModifier<>(customModelData));
else itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData));
}
// 如果没有item-model选项被配置同时这个物品又含有 model 区域
else if (!hasItemModelSection && section.containsKey("model") && VersionHelper.isOrAbove1_21_2()) {
// 那么使用物品id当成item-model的值
itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString());
// 但是有个前提id必须是有效的resource location
if (ResourceLocation.isValid(itemModelKey.toString())) {
if (clientBoundModel) itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModelKey));
else itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey));
} else {
itemModelKey = null;
if (!isVanillaItem) {
// 如果用户指定了,说明要手动分配,不管他是什么版本,都强制设置模型值
if (section.containsKey("custom-model-data")) {
int customModelData = ResourceConfigUtils.getAsInt(section.getOrDefault("custom-model-data", 0), "custom-model-data");
if (customModelData < 0) {
throw new LocalizedResourceConfigException("warning.config.item.invalid_custom_model_data", String.valueOf(customModelData));
}
if (customModelData > 16_777_216) {
throw new LocalizedResourceConfigException("warning.config.item.bad_custom_model_data", String.valueOf(customModelData));
}
customModelDataFuture = getOrCreateIdAllocator(clientBoundMaterial).assignFixedId(id.asString(), customModelData);
forceCustomModelData = true;
}
}
// 如果有item-model
if (hasItemModelSection && VersionHelper.isOrAbove1_21_2()) {
itemModelKey = Key.from(section.get("item-model").toString());
if (clientBoundModel) itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModelKey));
else itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey));
}
// 对于不重要的配置,可以仅警告,不返回
ExceptionCollector<LocalizedResourceConfigException> collector = new ExceptionCollector<>();
// 应用物品数据
try {
applyDataModifiers(MiscUtils.castToMap(section.get("data"), true), itemBuilder::dataModifier);
} catch (LocalizedResourceConfigException e) {
collector.add(e);
}
// 应用客户端侧数据
try {
if (VersionHelper.PREMIUM) {
applyDataModifiers(MiscUtils.castToMap(section.get("client-bound-data"), true), itemBuilder::clientBoundDataModifier);
}
} catch (LocalizedResourceConfigException e) {
collector.add(e);
}
// 如果不是原版物品那么加入ce的标识符
if (!isVanillaItem)
itemBuilder.dataModifier(new IdModifier<>(id));
// 事件
Map<EventTrigger, List<net.momirealms.craftengine.core.plugin.context.function.Function<PlayerOptionalContext>>> eventTriggerListMap;
try {
eventTriggerListMap = EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"));
} catch (LocalizedResourceConfigException e) {
collector.add(e);
eventTriggerListMap = Map.of();
}
// 设置
ItemSettings settings;
try {
settings = Optional.ofNullable(ResourceConfigUtils.get(section, "settings"))
.map(map -> ItemSettings.fromMap(MiscUtils.castToMap(map, true)))
.map(it -> isVanillaItem ? it.canPlaceRelatedVanillaBlock(true) : it)
.orElse(ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem));
} catch (LocalizedResourceConfigException e) {
collector.add(e);
settings = ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem);
}
// 行为
List<ItemBehavior> behaviors;
try {
behaviors = ItemBehaviors.fromObj(pack, path, id, ResourceConfigUtils.get(section, "behavior", "behaviors"));
} catch (LocalizedResourceConfigException e) {
collector.add(e);
behaviors = Collections.emptyList();
}
// 如果有物品更新器
if (section.containsKey("updater")) {
Map<String, Object> updater = ResourceConfigUtils.getAsMap(section.get("updater"), "updater");
List<ItemUpdateConfig.Version> versions = new ArrayList<>(2);
for (Map.Entry<String, Object> entry : updater.entrySet()) {
try {
int version = Integer.parseInt(entry.getKey());
versions.add(new ItemUpdateConfig.Version(
version,
ResourceConfigUtils.parseConfigAsList(entry.getValue(), map -> ItemUpdaters.fromMap(id, map)).toArray(new ItemUpdater[0])
));
} catch (NumberFormatException ignored) {
// 用户没指定custom-model-data则看当前资源包版本兼容需求
else {
forceCustomModelData = false;
// 如果最低版本要1.21.1以下支持
if (needsCustomModelDataCompatibility()) {
customModelDataFuture = getOrCreateIdAllocator(clientBoundMaterial).requestAutoId(id.asString());
}
// 否则不主动分配模型值
else {
customModelDataFuture = CompletableFuture.completedFuture(0);
}
}
ItemUpdateConfig config = new ItemUpdateConfig(versions);
itemBuilder.updater(config);
itemBuilder.dataModifier(new ItemVersionModifier<>(config.maxVersion()));
} else {
forceCustomModelData = false;
// 原版物品不应该有这个
customModelDataFuture = CompletableFuture.completedFuture(0);
}
// 构建自定义物品
CustomItem<I> customItem = itemBuilder
.isVanillaItem(isVanillaItem)
.behaviors(behaviors)
.settings(settings)
.events(eventTriggerListMap)
.build();
// 添加到缓存
addCustomItem(customItem);
// 如果有类别,则添加
if (section.containsKey("category")) {
AbstractItemManager.this.plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList());
}
// 模型配置区域如果这里被配置了那么用户必须要配置custom-model-data或item-model
Map<String, Object> modelSection = MiscUtils.castToMap(section.get("model"), true);
Map<String, Object> legacyModelSection = MiscUtils.castToMap(section.get("legacy-model"), true);
if (modelSection == null && legacyModelSection == null) {
collector.throwIfPresent();
return;
}
boolean needsModelSection = isModernFormatRequired() || (needsLegacyCompatibility() && legacyModelSection == null);
// 只对自定义物品有这个限制
if (!isVanillaItem) {
// 既没有模型值也没有item-model
if (customModelData == 0 && itemModelKey == null) {
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model_id"));
// 当模型值完成分配的时候
customModelDataFuture.whenComplete((cmd, throwable) -> ResourceConfigUtils.runCatching(path, node, () -> {
int customModelData;
if (throwable != null) {
// 检测custom model data 冲突
if (throwable instanceof IdAllocator.IdConflictException exception) {
if (section.containsKey("model") || section.containsKey("legacy-model")) {
throw new LocalizedResourceConfigException("warning.config.item.custom_model_data.conflict", String.valueOf(exception.id()), exception.previousOwner());
}
customModelData = exception.id();
}
// custom model data 已被用尽,不太可能
else if (throwable instanceof IdAllocator.IdExhaustedException) {
throw new LocalizedResourceConfigException("warning.config.item.custom_model_data.exhausted", clientBoundMaterial.asString());
}
// 未知错误
else {
Debugger.ITEM.warn(() -> "Unknown error while allocating custom model data.", throwable);
return;
}
} else {
customModelData = cmd;
}
}
// 新版格式
ItemModel modernModel = null;
// 旧版格式
TreeSet<LegacyOverridesModel> legacyOverridesModels = null;
// 如果需要支持新版item model 或者用户需要旧版本兼容但是没配置legacy-model
if (needsModelSection) {
// 1.21.4+必须要配置model区域如果不需要高版本兼容则可以只写legacy-model
if (modelSection == null) {
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model"));
return;
// item model
Key itemModel = null;
boolean forceItemModel = false;
// 如果这个版本可以使用 item model
if (!isVanillaItem && needsItemModelCompatibility()) {
// 如果用户主动设定了item model那么肯定要设置
if (section.containsKey("item-model")) {
itemModel = Key.from(section.get("item-model").toString());
forceItemModel = true;
}
// 用户没设置item model也没设置custom model data那么为他生成一个基于物品id的item model
else if (customModelData == 0 || Config.alwaysUseItemModel()) {
itemModel = id;
}
// 用户没设置item model但是有custom model data那么就使用custom model data
}
// 是否使用客户端侧模型
boolean clientBoundModel = VersionHelper.PREMIUM && (section.containsKey("client-bound-model") ? ResourceConfigUtils.getAsBoolean(section.get("client-bound-model"), "client-bound-model") : Config.globalClientboundModel());
CustomItem.Builder<I> itemBuilder = createPlatformItemBuilder(uniqueId, material, clientBoundMaterial);
// 模型配置区域如果这里被配置了那么用户必须要配置custom-model-data或item-model
Map<String, Object> modelSection = MiscUtils.castToMap(section.get("model"), true);
Map<String, Object> legacyModelSection = MiscUtils.castToMap(section.get("legacy-model"), true);
boolean hasModelSection = modelSection != null || legacyModelSection != null;
if (customModelData > 0 && (hasModelSection || forceCustomModelData)) {
if (clientBoundModel) itemBuilder.clientBoundDataModifier(new CustomModelDataModifier<>(customModelData));
else itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData));
}
if (itemModel != null && (hasModelSection || forceItemModel)) {
if (clientBoundModel) itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModel));
else itemBuilder.dataModifier(new ItemModelModifier<>(itemModel));
}
// 对于不重要的配置,可以仅警告,不返回
ExceptionCollector<LocalizedResourceConfigException> collector = new ExceptionCollector<>();
// 应用物品数据
try {
modernModel = ItemModels.fromMap(modelSection);
for (ModelGeneration generation : modernModel.modelsToGenerate()) {
prepareModelGeneration(generation);
applyDataModifiers(MiscUtils.castToMap(section.get("data"), true), itemBuilder::dataModifier);
} catch (LocalizedResourceConfigException e) {
collector.add(e);
}
// 应用客户端侧数据
try {
if (VersionHelper.PREMIUM) {
applyDataModifiers(MiscUtils.castToMap(section.get("client-bound-data"), true), itemBuilder::clientBoundDataModifier);
}
} catch (LocalizedResourceConfigException e) {
collector.addAndThrow(e);
collector.add(e);
}
}
// 如果需要旧版本兼容
if (needsLegacyCompatibility()) {
if (legacyModelSection != null) {
// 如果不是原版物品那么加入ce的标识符
if (!isVanillaItem)
itemBuilder.dataModifier(new IdModifier<>(id));
// 事件
Map<EventTrigger, List<net.momirealms.craftengine.core.plugin.context.function.Function<PlayerOptionalContext>>> eventTriggerListMap;
try {
eventTriggerListMap = EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"));
} catch (LocalizedResourceConfigException e) {
collector.add(e);
eventTriggerListMap = Map.of();
}
// 设置
ItemSettings settings;
try {
settings = Optional.ofNullable(ResourceConfigUtils.get(section, "settings"))
.map(map -> ItemSettings.fromMap(MiscUtils.castToMap(map, true)))
.map(it -> isVanillaItem ? it.canPlaceRelatedVanillaBlock(true) : it)
.orElse(ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem));
} catch (LocalizedResourceConfigException e) {
collector.add(e);
settings = ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem);
}
// 行为
List<ItemBehavior> behaviors;
try {
behaviors = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(section, "behavior", "behaviors"), map -> ItemBehaviors.fromMap(pack, path, node, id, map));
} catch (LocalizedResourceConfigException e) {
collector.add(e);
behaviors = Collections.emptyList();
}
// 如果有物品更新器
if (section.containsKey("updater")) {
Map<String, Object> updater = ResourceConfigUtils.getAsMap(section.get("updater"), "updater");
List<ItemUpdateConfig.Version> versions = new ArrayList<>(2);
for (Map.Entry<String, Object> entry : updater.entrySet()) {
try {
int version = Integer.parseInt(entry.getKey());
versions.add(new ItemUpdateConfig.Version(
version,
ResourceConfigUtils.parseConfigAsList(entry.getValue(), map -> ItemUpdaters.fromMap(id, map)).toArray(new ItemUpdater[0])
));
} catch (NumberFormatException ignored) {
}
}
ItemUpdateConfig config = new ItemUpdateConfig(versions);
itemBuilder.updater(config);
itemBuilder.dataModifier(new ItemVersionModifier<>(config.maxVersion()));
}
// 构建自定义物品
CustomItem<I> customItem = itemBuilder
.isVanillaItem(isVanillaItem)
.behaviors(behaviors)
.settings(settings)
.events(eventTriggerListMap)
.build();
// 添加到缓存
addCustomItem(customItem);
// 如果有类别,则添加
if (section.containsKey("category")) {
AbstractItemManager.this.plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList());
}
/*
* ========================
*
* 模型配置分界线
*
* ========================
*/
// 原版物品还改模型自己替换json去
if (isVanillaItem) {
return;
}
if (!hasModelSection) {
collector.throwIfPresent();
return;
}
// 只对自定义物品有这个限制既没有模型值也没有item-model
if (customModelData == 0 && itemModel == null) {
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model_id"));
}
// 新版格式
ItemModel modernModel = null;
// 旧版格式
TreeSet<LegacyOverridesModel> legacyOverridesModels = null;
// 如果需要支持新版item model 或者用户需要旧版本兼容但是没配置legacy-model
if (isModernFormatRequired() || (needsLegacyCompatibility() && legacyModelSection == null)) {
// 1.21.4+必须要配置model区域如果不需要高版本兼容则可以只写legacy-model
if (modelSection == null) {
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model"));
return;
}
try {
LegacyItemModel legacyItemModel = LegacyItemModel.fromMap(legacyModelSection, customModelData);
for (ModelGeneration generation : legacyItemModel.modelsToGenerate()) {
modernModel = ItemModels.fromMap(modelSection);
for (ModelGeneration generation : modernModel.modelsToGenerate()) {
prepareModelGeneration(generation);
}
legacyOverridesModels = new TreeSet<>(legacyItemModel.overrides());
} catch (LocalizedResourceConfigException e) {
collector.addAndThrow(e);
}
} else {
legacyOverridesModels = new TreeSet<>();
processModelRecursively(modernModel, new LinkedHashMap<>(), legacyOverridesModels, clientBoundMaterial, customModelData);
if (legacyOverridesModels.isEmpty()) {
collector.add(new LocalizedResourceConfigException("warning.config.item.legacy_model.cannot_convert", path.toString(), id.asString()));
}
// 如果需要旧版本兼容
if (needsLegacyCompatibility()) {
if (legacyModelSection != null) {
try {
LegacyItemModel legacyItemModel = LegacyItemModel.fromMap(legacyModelSection, customModelData);
for (ModelGeneration generation : legacyItemModel.modelsToGenerate()) {
prepareModelGeneration(generation);
}
legacyOverridesModels = new TreeSet<>(legacyItemModel.overrides());
} catch (LocalizedResourceConfigException e) {
collector.addAndThrow(e);
}
} else {
legacyOverridesModels = new TreeSet<>();
processModelRecursively(modernModel, new LinkedHashMap<>(), legacyOverridesModels, clientBoundMaterial, customModelData);
if (legacyOverridesModels.isEmpty()) {
collector.add(new LocalizedResourceConfigException("warning.config.item.legacy_model.cannot_convert"));
}
}
}
}
// 自定义物品的model处理
if (!isVanillaItem) {
boolean hasLegacyModel = legacyOverridesModels != null && !legacyOverridesModels.isEmpty();
boolean hasModernModel = modernModel != null;
// 自定义物品的model处理
// 这个item-model是否存在且是原版item-model
boolean isVanillaItemModel = itemModelKey != null && AbstractPackManager.PRESET_ITEMS.containsKey(itemModelKey);
boolean isVanillaItemModel = itemModel != null && AbstractPackManager.PRESET_ITEMS.containsKey(itemModel);
// 使用了自定义模型值
if (customModelData != 0) {
// 如果用户主动设置了item-model且为原版物品则使用item-model为基础模型否则使用其视觉材质对应的item-model
Key finalBaseModel = isVanillaItemModel ? itemModelKey : clientBoundMaterial;
// 检查cmd冲突
Map<Integer, Key> conflict = AbstractItemManager.this.cmdConflictChecker.computeIfAbsent(finalBaseModel, k -> new HashMap<>());
if (conflict.containsKey(customModelData)) {
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(customModelData), conflict.get(customModelData).toString()));
}
conflict.put(customModelData, id);
Key finalBaseModel = isVanillaItemModel ? itemModel : clientBoundMaterial;
// 添加新版item model
if (isModernFormatRequired() && modernModel != null) {
if (isModernFormatRequired() && hasModernModel) {
TreeMap<Integer, ModernItemModel> map = AbstractItemManager.this.modernOverrides.computeIfAbsent(finalBaseModel, k -> new TreeMap<>());
map.put(customModelData, new ModernItemModel(
modernModel,
@@ -552,41 +669,33 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
));
}
// 添加旧版 overrides
if (needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) {
if (needsLegacyCompatibility() && hasLegacyModel) {
TreeSet<LegacyOverridesModel> lom = AbstractItemManager.this.legacyOverrides.computeIfAbsent(finalBaseModel, k -> new TreeSet<>());
lom.addAll(legacyOverridesModels);
}
} else if (isVanillaItemModel) {
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.item_model.conflict", itemModelKey.asString()));
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.item_model.conflict", itemModel.asString()));
}
// 使用了item-model组件且不是原版物品的
if (itemModelKey != null && !isVanillaItemModel) {
if (isModernFormatRequired() && modernModel != null) {
AbstractItemManager.this.modernItemModels1_21_4.put(itemModelKey, new ModernItemModel(
if (itemModel != null && !isVanillaItemModel) {
if (isModernFormatRequired() && hasModernModel) {
AbstractItemManager.this.modernItemModels1_21_4.put(itemModel, new ModernItemModel(
modernModel,
ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"),
ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap")
));
}
if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2) && needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) {
TreeSet<LegacyOverridesModel> lom = AbstractItemManager.this.modernItemModels1_21_2.computeIfAbsent(itemModelKey, k -> new TreeSet<>());
if (needsItemModelCompatibility() && needsLegacyCompatibility() && hasLegacyModel) {
TreeSet<LegacyOverridesModel> lom = AbstractItemManager.this.modernItemModels1_21_2.computeIfAbsent(itemModel, k -> new TreeSet<>());
lom.addAll(legacyOverridesModels);
}
}
} else {
// 原版物品的item model覆写
if (isModernFormatRequired()) {
AbstractItemManager.this.modernItemModels1_21_4.put(id, new ModernItemModel(
modernModel,
ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"),
ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap")
));
}
}
// 抛出异常
collector.throwIfPresent();
// 抛出异常
collector.throwIfPresent();
}, () -> GsonHelper.get().toJson(section)));
}
}

View File

@@ -1,7 +1,6 @@
package net.momirealms.craftengine.core.item;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.util.Key;
public interface BuildableItem<I> {
@@ -27,18 +26,18 @@ public interface BuildableItem<I> {
}
default I buildItemStack() {
return buildItemStack(ItemBuildContext.EMPTY, 1);
return buildItemStack(ItemBuildContext.empty(), 1);
}
default I buildItemStack(int count) {
return buildItemStack(ItemBuildContext.EMPTY, count);
return buildItemStack(ItemBuildContext.empty(), count);
}
default I buildItemStack(Player player) {
return this.buildItemStack(new ItemBuildContext(player, ContextHolder.EMPTY), 1);
return this.buildItemStack(ItemBuildContext.of(player), 1);
}
default I buildItemStack(Player player, int count) {
return this.buildItemStack(new ItemBuildContext(player, ContextHolder.EMPTY), count);
return this.buildItemStack(ItemBuildContext.of(player), count);
}
}

View File

@@ -16,6 +16,10 @@ import java.util.Optional;
public interface CustomItem<I> extends BuildableItem<I> {
/**
* Since CraftEngine allows users to add certain functionalities to vanilla items, this custom item might actually be a vanilla item.
* This will be refactored before the 1.0 release, but no changes will be made for now to ensure compatibility.
*/
boolean isVanillaItem();
Key id();

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.item;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
@@ -10,12 +11,23 @@ import org.jetbrains.annotations.Nullable;
import java.util.Map;
public class ItemBuildContext extends PlayerOptionalContext {
public static final ItemBuildContext EMPTY = new ItemBuildContext(null, ContextHolder.EMPTY);
/**
* Use {@link #empty()} instead
*/
@Deprecated(since = "0.0.63", forRemoval = true)
public static final ItemBuildContext EMPTY = new ItemBuildContext(null, ContextHolder.empty());
public static final TagResolver[] EMPTY_RESOLVERS = empty().tagResolvers();
public ItemBuildContext(@Nullable Player player, @NotNull ContextHolder contexts) {
super(player, contexts);
}
@NotNull
public static ItemBuildContext empty() {
return new ItemBuildContext(null, ContextHolder.empty());
}
@NotNull
public static ItemBuildContext of(@Nullable Player player, @NotNull ContextHolder contexts) {
return new ItemBuildContext(player, contexts);
@@ -29,7 +41,7 @@ public class ItemBuildContext extends PlayerOptionalContext {
@NotNull
public static ItemBuildContext of(@Nullable Player player) {
if (player == null) return new ItemBuildContext(null, ContextHolder.EMPTY);
if (player == null) return new ItemBuildContext(null, ContextHolder.empty());
return new ItemBuildContext(player, new ContextHolder(Map.of(DirectContextParameters.PLAYER, () -> player)));
}
}

View File

@@ -66,13 +66,9 @@ public final class ItemKeys {
public static final Key MAGENTA_DYE = Key.of("minecraft:magenta_dye");
public static final Key PINK_DYE = Key.of("minecraft:pink_dye");
public static final Key[] AXES = new Key[] {
WOODEN_AXE, STONE_AXE, IRON_AXE, GOLDEN_AXE, DIAMOND_AXE, NETHERITE_AXE
};
public static final Key[] AXES = new Key[] {WOODEN_AXE, STONE_AXE, IRON_AXE, GOLDEN_AXE, DIAMOND_AXE, NETHERITE_AXE};
public static final Key[] WATER_BUCKETS = new Key[] {
WATER_BUCKET, COD_BUCKET, SALMON_BUCKET, TROPICAL_FISH_BUCKET, TADPOLE_BUCKET, PUFFERFISH_BUCKET, AXOLOTL_BUCKET
};
public static final Key[] WATER_BUCKETS = new Key[] {WATER_BUCKET, COD_BUCKET, SALMON_BUCKET, TROPICAL_FISH_BUCKET, TADPOLE_BUCKET, PUFFERFISH_BUCKET, AXOLOTL_BUCKET};
public static final Key[] DYES = new Key[] {
WHITE_DYE, LIGHT_GRAY_DYE, GRAY_DYE, BLACK_DYE, BROWN_DYE, RED_DYE, ORANGE_DYE, YELLOW_DYE, LIME_DYE,

View File

@@ -54,7 +54,12 @@ public interface ItemManager<T> extends Manageable, ModelGenerator {
Item<T> fromByteArray(byte[] bytes);
Collection<Key> items();
Map<Key, CustomItem<T>> loadedItems();
@Deprecated(forRemoval = true)
default Collection<Key> items() {
return loadedItems().keySet();
}
ExternalItemSource<T> getExternalItemSource(String name);

View File

@@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.item;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.projectile.ProjectileMeta;
import net.momirealms.craftengine.core.entity.projectile.ProjectileType;
import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment;
import net.momirealms.craftengine.core.item.equipment.Equipment;
import net.momirealms.craftengine.core.item.modifier.EquippableModifier;
@@ -25,7 +24,7 @@ import java.util.stream.Collectors;
public class ItemSettings {
int fuelTime;
Set<Key> tags = Set.of();
Tristate canRepair = Tristate.UNDEFINED;
Repairable repairable = Repairable.UNDEFINED;
List<AnvilRepairItem> anvilRepairItems = List.of();
boolean renameable = true;
boolean canPlaceRelatedVanillaBlock = false;
@@ -89,7 +88,7 @@ public class ItemSettings {
newSettings.fuelTime = settings.fuelTime;
newSettings.tags = settings.tags;
newSettings.equipment = settings.equipment;
newSettings.canRepair = settings.canRepair;
newSettings.repairable = settings.repairable;
newSettings.anvilRepairItems = settings.anvilRepairItems;
newSettings.renameable = settings.renameable;
newSettings.canPlaceRelatedVanillaBlock = settings.canPlaceRelatedVanillaBlock;
@@ -128,8 +127,8 @@ public class ItemSettings {
return canPlaceRelatedVanillaBlock;
}
public Tristate canRepair() {
return canRepair;
public Repairable repairable() {
return repairable;
}
public int fuelTime() {
@@ -233,8 +232,8 @@ public class ItemSettings {
return this;
}
public ItemSettings canRepair(Tristate canRepair) {
this.canRepair = canRepair;
public ItemSettings repairable(Repairable repairable) {
this.repairable = repairable;
return this;
}
@@ -315,8 +314,14 @@ public class ItemSettings {
static {
registerFactory("repairable", (value -> {
boolean bool = ResourceConfigUtils.getAsBoolean(value, "repairable");
return settings -> settings.canRepair(bool ? Tristate.TRUE : Tristate.FALSE);
if (value instanceof Map<?,?> mapValue) {
Map<String, Object> repairableData = ResourceConfigUtils.getAsMap(mapValue, "repairable");
Repairable repairable = Repairable.fromMap(repairableData);
return settings -> settings.repairable(repairable);
} else {
boolean bool = ResourceConfigUtils.getAsBoolean(value, "repairable");
return settings -> settings.repairable(bool ? Repairable.TRUE : Repairable.FALSE);
}
}));
registerFactory("enchantable", (value -> {
boolean bool = ResourceConfigUtils.getAsBoolean(value, "enchantable");
@@ -399,12 +404,11 @@ public class ItemSettings {
Key customTridentItemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("item"), "warning.config.item.settings.projectile.missing_item"));
ItemDisplayContext displayType = ItemDisplayContext.valueOf(args.getOrDefault("display-transform", "NONE").toString().toUpperCase(Locale.ENGLISH));
Billboard billboard = Billboard.valueOf(args.getOrDefault("billboard", "FIXED").toString().toUpperCase(Locale.ENGLISH));
Vector3f translation = MiscUtils.getAsVector3f(args.getOrDefault("translation", "0"), "translation");
Vector3f scale = MiscUtils.getAsVector3f(args.getOrDefault("scale", "1"), "scale");
Quaternionf rotation = MiscUtils.getAsQuaternionf(ResourceConfigUtils.get(args, "rotation-left", "rotation"), "rotation-left");
ProjectileType type = Optional.ofNullable(args.get("type")).map(String::valueOf).map(it -> ProjectileType.valueOf(it.toUpperCase(Locale.ENGLISH))).orElse(null);
Vector3f translation = ResourceConfigUtils.getAsVector3f(args.getOrDefault("translation", "0"), "translation");
Vector3f scale = ResourceConfigUtils.getAsVector3f(args.getOrDefault("scale", "1"), "scale");
Quaternionf rotation = ResourceConfigUtils.getAsQuaternionf(ResourceConfigUtils.get(args, "rotation"), "rotation");
double range = ResourceConfigUtils.getAsDouble(args.getOrDefault("range", 1), "range");
return settings -> settings.projectileMeta(new ProjectileMeta(customTridentItemId, displayType, billboard, scale, translation, rotation, range, type));
return settings -> settings.projectileMeta(new ProjectileMeta(customTridentItemId, displayType, billboard, scale, translation, rotation, range));
}));
registerFactory("helmet", (value -> {
Map<String, Object> args = MiscUtils.castToMap(value, false);
@@ -426,14 +430,14 @@ public class ItemSettings {
if (value instanceof Integer i) {
return settings -> settings.dyeColor(Color.fromDecimal(i));
} else {
return settings -> settings.dyeColor(Color.fromVector3f(MiscUtils.getAsVector3f(value, "dye-color")));
return settings -> settings.dyeColor(Color.fromVector3f(ResourceConfigUtils.getAsVector3f(value, "dye-color")));
}
}));
registerFactory("firework-color", (value -> {
if (value instanceof Integer i) {
return settings -> settings.fireworkColor(Color.fromDecimal(i));
} else {
return settings -> settings.fireworkColor(Color.fromVector3f(MiscUtils.getAsVector3f(value, "firework-color")));
return settings -> settings.fireworkColor(Color.fromVector3f(ResourceConfigUtils.getAsVector3f(value, "firework-color")));
}
}));
registerFactory("food", (value -> {

View File

@@ -13,7 +13,7 @@ public class EmptyItemBehavior extends ItemBehavior {
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments) {
public ItemBehavior create(Pack pack, Path path, String node, Key id, Map<String, Object> arguments) {
return INSTANCE;
}
}

View File

@@ -8,5 +8,5 @@ import java.util.Map;
public interface ItemBehaviorFactory {
ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments);
ItemBehavior create(Pack pack, Path path, String node, Key id, Map<String, Object> arguments);
}

View File

@@ -6,13 +6,10 @@ import net.momirealms.craftengine.core.registry.BuiltInRegistries;
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.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.ResourceKey;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ItemBehaviors {
@@ -23,7 +20,7 @@ public class ItemBehaviors {
.register(ResourceKey.create(Registries.ITEM_BEHAVIOR_FACTORY.location(), key), factory);
}
public static ItemBehavior fromMap(Pack pack, Path path, Key id, Map<String, Object> map) {
public static ItemBehavior fromMap(Pack pack, Path path, String node, Key id, Map<String, Object> map) {
if (map == null || map.isEmpty()) return EmptyItemBehavior.INSTANCE;
String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("type"), "warning.config.item.behavior.missing_type");
Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE);
@@ -31,25 +28,6 @@ public class ItemBehaviors {
if (factory == null) {
throw new LocalizedResourceConfigException("warning.config.item.behavior.invalid_type", type);
}
return factory.create(pack, path, id, map);
}
public static List<ItemBehavior> fromList(Pack pack, Path path, Key id, List<Map<String, Object>> list) {
List<ItemBehavior> behaviors = new ArrayList<>(list.size());
for (Map<String, Object> map : list) {
behaviors.add(fromMap(pack, path, id, map));
}
return behaviors;
}
@SuppressWarnings("unchecked")
public static List<ItemBehavior> fromObj(Pack pack, Path path, Key id, Object behaviorObj) {
if (behaviorObj instanceof Map<?,?>) {
return List.of(fromMap(pack, path, id, MiscUtils.castToMap(behaviorObj, false)));
} else if (behaviorObj instanceof List<?>) {
return fromList(pack, path, id, (List<Map<String, Object>>) behaviorObj);
} else {
return List.of();
}
return factory.create(pack, path, node, id, map);
}
}

View File

@@ -48,4 +48,23 @@ public class BlockPlaceContext extends UseOnContext {
public Direction getNearestLookingDirection() {
return Direction.orderedByNearest(this.getPlayer())[0];
}
public Direction[] getNearestLookingDirections() {
Direction[] directions = Direction.orderedByNearest(this.getPlayer());
if (!this.replaceClicked) {
Direction clickedFace = this.getClickedFace();
int i = 0;
while (i < directions.length && directions[i] != clickedFace.opposite()) {
i++;
}
if (i > 0) {
System.arraycopy(directions, 0, directions, 1, i);
directions[0] = clickedFace.opposite();
}
}
return directions;
}
}

View File

@@ -131,7 +131,7 @@ public class ComponentBasedEquipment extends AbstractEquipment implements Suppli
return new DyeableData(ResourceConfigUtils.getAsInt(data.get("color-when-undyed"), "color-when-undyed"));
}
}
return new DyeableData(null);
return null;
}
@Override

View File

@@ -6,7 +6,7 @@ import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.ItemDataModifierFactory;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
@@ -54,7 +54,7 @@ public class DyedColorModifier<I> implements SimpleNetworkItemDataModifier<I> {
if (arg instanceof Integer integer) {
return new DyedColorModifier<>(Color.fromDecimal(integer));
} else {
Vector3f vector3f = MiscUtils.getAsVector3f(arg, "dyed-color");
Vector3f vector3f = ResourceConfigUtils.getAsVector3f(arg, "dyed-color");
return new DyedColorModifier<>(Color.fromVector3f(vector3f));
}
}

View File

@@ -8,12 +8,13 @@ import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
import java.util.Map;
import java.util.*;
public class ExternalModifier<I> implements ItemDataModifier<I> {
public static final Factory<?> FACTORY = new Factory<>();
private static final ThreadLocal<Set<Dependency>> BUILD_STACK = ThreadLocal.withInitial(LinkedHashSet::new);
private final String id;
private final ExternalItemSource<I> provider;
@@ -38,14 +39,36 @@ public class ExternalModifier<I> implements ItemDataModifier<I> {
@SuppressWarnings("unchecked")
@Override
public Item<I> apply(Item<I> item, ItemBuildContext context) {
I another = this.provider.build(id, context);
if (another == null) {
CraftEngine.instance().logger().warn("'" + id + "' could not be found in " + provider.plugin());
Dependency dependency = new Dependency(provider.plugin(), id);
Set<Dependency> buildStack = BUILD_STACK.get();
if (buildStack.contains(dependency)) {
StringJoiner dependencyChain = new StringJoiner(" -> ");
buildStack.forEach(element -> dependencyChain.add(element.asString()));
dependencyChain.add(dependency.asString());
CraftEngine.instance().logger().warn(
"Failed to build '" + this.id + "' from plugin '" + provider.plugin() + "' due to dependency loop: " + dependencyChain
);
return item;
}
Item<I> anotherWrapped = (Item<I>) CraftEngine.instance().itemManager().wrap(another);
item.merge(anotherWrapped);
return item;
buildStack.add(dependency);
try {
I another = this.provider.build(this.id, context);
if (another == null) {
CraftEngine.instance().logger().warn("'" + this.id + "' could not be found in " + provider.plugin());
return item;
}
Item<I> anotherWrapped = (Item<I>) CraftEngine.instance().itemManager().wrap(another);
item.merge(anotherWrapped);
return item;
} catch (Throwable e) {
CraftEngine.instance().logger().warn("Failed to build item '" + this.id + "' from plugin '" + provider.plugin() + "'", e);
return item;
} finally {
buildStack.remove(dependency);
BUILD_STACK.remove();
}
}
public static class Factory<I> implements ItemDataModifierFactory<I> {
@@ -60,4 +83,11 @@ public class ExternalModifier<I> implements ItemDataModifier<I> {
return new ExternalModifier<>(id, ResourceConfigUtils.requireNonNullOrThrow(provider, () -> new LocalizedResourceConfigException("warning.config.item.data.external.invalid_source", plugin)));
}
}
private record Dependency(String source, String id) {
public @NotNull String asString() {
return source + "[id=" + id + "]";
}
}
}

View File

@@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.UniqueKey;
@@ -106,19 +107,21 @@ public abstract class AbstractRecipeManager<T> implements RecipeManager<T> {
if (recipe instanceof AbstractedFixedResultRecipe<?> fixedResult) {
this.byResult.computeIfAbsent(fixedResult.result().item().id(), k -> new ArrayList<>()).add(recipe);
}
HashSet<Key> usedKeys = new HashSet<>();
for (Ingredient<T> ingredient : recipe.ingredientsInUse()) {
for (UniqueKey holder : ingredient.items()) {
Key key = holder.key();
if (usedKeys.add(key)) {
this.byIngredient.computeIfAbsent(key, k -> new ArrayList<>()).add(recipe);
if (recipe.canBeSearchedByIngredients()) {
HashSet<Key> usedKeys = new HashSet<>();
for (Ingredient<T> ingredient : recipe.ingredientsInUse()) {
for (UniqueKey holder : ingredient.items()) {
Key key = holder.key();
if (usedKeys.add(key)) {
this.byIngredient.computeIfAbsent(key, k -> new ArrayList<>()).add(recipe);
}
}
}
}
return true;
}
public class RecipeParser implements ConfigParser {
public class RecipeParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"recipes", "recipe"};
@Override
@@ -132,10 +135,10 @@ public abstract class AbstractRecipeManager<T> implements RecipeManager<T> {
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (!Config.enableRecipeSystem()) return;
if (AbstractRecipeManager.this.byId.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.recipe.duplicate", path, id);
throw new LocalizedResourceConfigException("warning.config.recipe.duplicate");
}
Recipe<T> recipe = RecipeSerializers.fromMap(id, section);
try {

View File

@@ -9,6 +9,12 @@ import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult;
import net.momirealms.craftengine.core.item.recipe.result.PostProcessor;
import net.momirealms.craftengine.core.item.recipe.result.PostProcessors;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.condition.AllOfCondition;
import net.momirealms.craftengine.core.plugin.context.event.EventConditions;
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.*;
import org.jetbrains.annotations.NotNull;
@@ -24,6 +30,23 @@ public abstract class AbstractRecipeSerializer<T, R extends Recipe<T>> implement
new VanillaRecipeReader1_20_5() :
new VanillaRecipeReader1_20();
@SuppressWarnings("unchecked")
protected Function<PlayerOptionalContext>[] functions(Map<String, Object> arguments) {
Object functions = ResourceConfigUtils.get(arguments, "functions", "function");
if (functions == null) return null;
List<Function<PlayerOptionalContext>> functionList = ResourceConfigUtils.parseConfigAsList(functions, EventFunctions::fromMap);
return functionList.toArray(new Function[0]);
}
protected Condition<PlayerOptionalContext> conditions(Map<String, Object> arguments) {
Object conditions = ResourceConfigUtils.get(arguments, "conditions", "condition");
if (conditions == null) return null;
List<Condition<PlayerOptionalContext>> conditionList = ResourceConfigUtils.parseConfigAsList(conditions, EventConditions::fromMap);
if (conditionList.isEmpty()) return null;
if (conditionList.size() == 1) return conditionList.getFirst();
return new AllOfCondition<>(conditionList);
}
protected boolean showNotification(Map<String, Object> arguments) {
return ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("show-notification", true), "show-notification");
}

View File

@@ -0,0 +1,8 @@
package net.momirealms.craftengine.core.item.recipe;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
public interface ConditionalRecipe {
boolean canUse(final PlayerOptionalContext context);
}

View File

@@ -4,17 +4,37 @@ import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.recipe.input.RecipeInput;
import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.Nullable;
public abstract class CustomCraftingTableRecipe<T> extends AbstractGroupedRecipe<T> {
public abstract class CustomCraftingTableRecipe<T> extends AbstractGroupedRecipe<T> implements ConditionalRecipe {
protected final CraftingRecipeCategory category;
private final CustomRecipeResult<T> visualResult;
private final Function<PlayerOptionalContext>[] craftingFunctions;
private final Condition<PlayerOptionalContext> craftingCondition;
protected CustomCraftingTableRecipe(Key id, boolean showNotification, CustomRecipeResult<T> result, @Nullable CustomRecipeResult<T> visualResult, String group, CraftingRecipeCategory category) {
protected CustomCraftingTableRecipe(Key id,
boolean showNotification,
CustomRecipeResult<T> result,
@Nullable CustomRecipeResult<T> visualResult,
String group,
CraftingRecipeCategory category,
Function<PlayerOptionalContext>[] craftingFunctions,
Condition<PlayerOptionalContext> craftingCondition) {
super(id, showNotification, result, group);
this.category = category == null ? CraftingRecipeCategory.MISC : category;
this.visualResult = visualResult;
this.craftingFunctions = craftingFunctions;
this.craftingCondition = craftingCondition;
}
@Override
public boolean canUse(PlayerOptionalContext context) {
if (this.craftingCondition == null) return true;
return this.craftingCondition.test(context);
}
public CraftingRecipeCategory category() {
@@ -49,4 +69,8 @@ public abstract class CustomCraftingTableRecipe<T> extends AbstractGroupedRecipe
return super.result.buildItem(context);
}
}
public Function<PlayerOptionalContext>[] craftingFunctions() {
return craftingFunctions;
}
}

View File

@@ -5,6 +5,9 @@ import com.google.gson.JsonObject;
import net.momirealms.craftengine.core.item.recipe.input.CraftingInput;
import net.momirealms.craftengine.core.item.recipe.input.RecipeInput;
import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
@@ -24,8 +27,10 @@ public class CustomShapedRecipe<T> extends CustomCraftingTableRecipe<T> {
CustomRecipeResult<T> visualResult,
String group,
CraftingRecipeCategory category,
Pattern<T> pattern) {
super(id, showNotification, result, visualResult, group, category);
Pattern<T> pattern,
Function<PlayerOptionalContext>[] craftingFunctions,
Condition<PlayerOptionalContext> craftingCondition) {
super(id, showNotification, result, visualResult, group, category, craftingFunctions, craftingCondition);
this.pattern = pattern;
this.parsedPattern = pattern.parse();
}
@@ -169,7 +174,9 @@ public class CustomShapedRecipe<T> extends CustomCraftingTableRecipe<T> {
parseResult(arguments),
parseVisualResult(arguments),
arguments.containsKey("group") ? arguments.get("group").toString() : null, craftingRecipeCategory(arguments),
new Pattern<>(pattern.toArray(new String[0]), ingredients)
new Pattern<>(pattern.toArray(new String[0]), ingredients),
functions(arguments),
conditions(arguments)
);
}
@@ -182,7 +189,9 @@ public class CustomShapedRecipe<T> extends CustomCraftingTableRecipe<T> {
null,
VANILLA_RECIPE_HELPER.readGroup(json),
VANILLA_RECIPE_HELPER.craftingCategory(json),
new Pattern<>(VANILLA_RECIPE_HELPER.craftingShapedPattern(json), ingredients)
new Pattern<>(VANILLA_RECIPE_HELPER.craftingShapedPattern(json), ingredients),
null,
null
);
}

View File

@@ -4,6 +4,9 @@ import com.google.gson.JsonObject;
import net.momirealms.craftengine.core.item.recipe.input.CraftingInput;
import net.momirealms.craftengine.core.item.recipe.input.RecipeInput;
import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import org.jetbrains.annotations.NotNull;
@@ -23,8 +26,10 @@ public class CustomShapelessRecipe<T> extends CustomCraftingTableRecipe<T> {
CustomRecipeResult<T> visualResult,
String group,
CraftingRecipeCategory category,
List<Ingredient<T>> ingredients) {
super(id, showNotification, result, visualResult, group, category);
List<Ingredient<T>> ingredients,
Function<PlayerOptionalContext>[] craftingFunctions,
Condition<PlayerOptionalContext> craftingCondition) {
super(id, showNotification, result, visualResult, group, category, craftingFunctions, craftingCondition);
this.ingredients = ingredients;
this.placementInfo = PlacementInfo.create(ingredients);
}
@@ -88,7 +93,9 @@ public class CustomShapelessRecipe<T> extends CustomCraftingTableRecipe<T> {
parseResult(arguments),
parseVisualResult(arguments),
arguments.containsKey("group") ? arguments.get("group").toString() : null, craftingRecipeCategory(arguments),
ingredients
ingredients,
functions(arguments),
conditions(arguments)
);
}
@@ -99,7 +106,9 @@ public class CustomShapelessRecipe<T> extends CustomCraftingTableRecipe<T> {
parseResult(VANILLA_RECIPE_HELPER.craftingResult(json.getAsJsonObject("result"))),
null,
VANILLA_RECIPE_HELPER.readGroup(json), VANILLA_RECIPE_HELPER.craftingCategory(json),
VANILLA_RECIPE_HELPER.shapelessIngredients(json.getAsJsonArray("ingredients")).stream().map(this::toIngredient).toList()
VANILLA_RECIPE_HELPER.shapelessIngredients(json.getAsJsonArray("ingredients")).stream().map(this::toIngredient).toList(),
null,
null
);
}
}

View File

@@ -7,6 +7,8 @@ import net.momirealms.craftengine.core.item.recipe.input.RecipeInput;
import net.momirealms.craftengine.core.item.recipe.input.SmithingInput;
import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Registries;
@@ -20,13 +22,14 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
public class CustomSmithingTransformRecipe<T> extends AbstractedFixedResultRecipe<T> {
public class CustomSmithingTransformRecipe<T> extends AbstractedFixedResultRecipe<T> implements ConditionalRecipe {
public static final Serializer<?> SERIALIZER = new Serializer<>();
private final Ingredient<T> base;
private final Ingredient<T> template;
private final Ingredient<T> addition;
private final boolean mergeComponents;
private final List<ItemDataProcessor> processors;
private final Condition<PlayerOptionalContext> condition;
public CustomSmithingTransformRecipe(Key id,
boolean showNotification,
@@ -35,7 +38,8 @@ public class CustomSmithingTransformRecipe<T> extends AbstractedFixedResultRecip
@Nullable Ingredient<T> addition,
CustomRecipeResult<T> result,
List<ItemDataProcessor> processors,
boolean mergeComponents
boolean mergeComponents,
Condition<PlayerOptionalContext> condition
) {
super(id, showNotification, result);
this.base = base;
@@ -43,6 +47,13 @@ public class CustomSmithingTransformRecipe<T> extends AbstractedFixedResultRecip
this.addition = addition;
this.processors = processors;
this.mergeComponents = mergeComponents;
this.condition = condition;
}
@Override
public boolean canUse(PlayerOptionalContext context) {
if (this.condition != null) return this.condition.test(context);
return true;
}
@SuppressWarnings("unchecked")
@@ -140,14 +151,23 @@ public class CustomSmithingTransformRecipe<T> extends AbstractedFixedResultRecip
toIngredient(addition),
parseResult(arguments),
ItemDataProcessors.fromMapList(processors),
mergeComponents
mergeComponents,
conditions(arguments)
);
}
@Override
public CustomSmithingTransformRecipe<A> readJson(Key id, JsonObject json) {
return new CustomSmithingTransformRecipe<>(id,
true, toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("template"))), Objects.requireNonNull(toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("base")))), toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("addition"))), parseResult(VANILLA_RECIPE_HELPER.smithingResult(json.getAsJsonObject("result"))), null, true
return new CustomSmithingTransformRecipe<>(
id,
true,
toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("template"))),
Objects.requireNonNull(toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("base")))),
toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("addition"))),
parseResult(VANILLA_RECIPE_HELPER.smithingResult(json.getAsJsonObject("result"))),
null,
true,
null
);
}
}

View File

@@ -6,6 +6,8 @@ import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.recipe.input.RecipeInput;
import net.momirealms.craftengine.core.item.recipe.input.SmithingInput;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
@@ -18,31 +20,41 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
public class CustomSmithingTrimRecipe<T> extends AbstractRecipe<T> {
public class CustomSmithingTrimRecipe<T> extends AbstractRecipe<T> implements ConditionalRecipe {
public static final Serializer<?> SERIALIZER = new Serializer<>();
private final Ingredient<T> base;
private final Ingredient<T> template;
private final Ingredient<T> addition;
@Nullable // 1.21.5
private final Key pattern;
@Nullable
private final Condition<PlayerOptionalContext> condition;
public CustomSmithingTrimRecipe(@NotNull Key id,
boolean showNotification,
@NotNull Ingredient<T> template,
@NotNull Ingredient<T> base,
@NotNull Ingredient<T> addition,
@Nullable Key pattern
@Nullable Key pattern,
@Nullable Condition<PlayerOptionalContext> condition
) {
super(id, showNotification);
this.base = base;
this.template = template;
this.addition = addition;
this.pattern = pattern;
this.condition = condition;
if (pattern == null && VersionHelper.isOrAbove1_21_5()) {
throw new IllegalStateException("SmithingTrimRecipe cannot have a null pattern on 1.21.5 and above.");
}
}
@Override
public boolean canUse(PlayerOptionalContext context) {
if (this.condition != null) return this.condition.test(context);
return true;
}
@SuppressWarnings("unchecked")
@Override
public T assemble(RecipeInput input, ItemBuildContext context) {
@@ -103,6 +115,11 @@ public class CustomSmithingTrimRecipe<T> extends AbstractRecipe<T> {
return pattern;
}
@Override
public boolean canBeSearchedByIngredients() {
return false;
}
@SuppressWarnings({"DuplicatedCode"})
public static class Serializer<A> extends AbstractRecipeSerializer<A, CustomSmithingTrimRecipe<A>> {
@@ -117,7 +134,8 @@ public class CustomSmithingTrimRecipe<T> extends AbstractRecipe<T> {
ResourceConfigUtils.requireNonNullOrThrow(toIngredient(template), "warning.config.recipe.smithing_trim.missing_template_type"),
ResourceConfigUtils.requireNonNullOrThrow(toIngredient(base), "warning.config.recipe.smithing_trim.missing_base"),
ResourceConfigUtils.requireNonNullOrThrow(toIngredient(addition), "warning.config.recipe.smithing_trim.missing_addition"),
pattern
pattern,
conditions(arguments)
);
}
@@ -128,7 +146,8 @@ public class CustomSmithingTrimRecipe<T> extends AbstractRecipe<T> {
Objects.requireNonNull(toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("template")))),
Objects.requireNonNull(toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("base")))),
Objects.requireNonNull(toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("addition")))),
VersionHelper.isOrAbove1_21_5() ? Key.of(json.get("pattern").getAsString()) : null
VersionHelper.isOrAbove1_21_5() ? Key.of(json.get("pattern").getAsString()) : null,
null
);
}
}

View File

@@ -25,4 +25,8 @@ public interface Recipe<T> {
default boolean showNotification() {
return true;
}
default boolean canBeSearchedByIngredients() {
return true;
}
}

View File

@@ -35,7 +35,10 @@ public final class LegacyRecipeTypes {
public static final Key SMITHING_TRIM = Key.of("smithing_trim");
public static final Key DECORATED_POT_RECIPE = Key.of("crafting_decorated_pot");
public static void register() {
public static void init() {
}
static {
register(SHAPED_RECIPE, new LegacyRecipe.Type(LegacyShapedRecipe::read));
register(SHAPELESS_RECIPE, new LegacyRecipe.Type(LegacyShapelessRecipe::read));
register(ARMOR_DYE, new LegacyRecipe.Type(LegacyCustomRecipe::read));

View File

@@ -15,7 +15,10 @@ public final class RecipeDisplayTypes {
public static final Key STONECUTTER = Key.of("stonecutter");
public static final Key SMITHING = Key.of("smithing");
public static void register() {
public static void init() {
}
static {
register(CRAFTING_SHAPELESS, new RecipeDisplay.Type(ShapelessCraftingRecipeDisplay::read));
register(CRAFTING_SHAPED, new RecipeDisplay.Type(ShapedCraftingRecipeDisplay::read));
register(FURNACE, new RecipeDisplay.Type(FurnaceRecipeDisplay::read));

View File

@@ -18,7 +18,10 @@ public final class SlotDisplayTypes {
public static final Key WITH_REMAINDER = Key.of("with_remainder");
public static final Key COMPOSITE = Key.of("composite");
public static void register() {
public static void init() {
}
static {
register(EMPTY, new SlotDisplay.Type(EmptySlotDisplay::read));
register(ANY_FUEL, new SlotDisplay.Type(AnyFuelDisplay::read));
register(ITEM, new SlotDisplay.Type(ItemSlotDisplay::read));

View File

@@ -0,0 +1,20 @@
package net.momirealms.craftengine.core.item.setting;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.Tristate;
import java.util.Map;
public record Repairable(Tristate craftingTable, Tristate anvilRepair, Tristate anvilCombine) {
public static final Repairable UNDEFINED = new Repairable(Tristate.UNDEFINED, Tristate.UNDEFINED, Tristate.UNDEFINED);
public static final Repairable TRUE = new Repairable(Tristate.TRUE, Tristate.TRUE, Tristate.TRUE);
public static final Repairable FALSE = new Repairable(Tristate.FALSE, Tristate.FALSE, Tristate.FALSE);
public static Repairable fromMap(Map<String, Object> map) {
Tristate craftingTable = map.containsKey("crafting-table") ? Tristate.of(ResourceConfigUtils.getAsBoolean(map.get("crafting-table"), "crafting-table")) : Tristate.UNDEFINED;
Tristate anvilRepair = map.containsKey("anvil-repair") ? Tristate.of(ResourceConfigUtils.getAsBoolean(map.get("anvil-repair"), "anvil-repair")) : Tristate.UNDEFINED;
Tristate anvilCombine = map.containsKey("anvil-combine") ? Tristate.of(ResourceConfigUtils.getAsBoolean(map.get("anvil-combine"), "anvil-combine")) : Tristate.UNDEFINED;
return new Repairable(craftingTable, anvilRepair, anvilCombine);
}
}

View File

@@ -8,7 +8,7 @@ import java.util.Optional;
public abstract class AbstractVanillaLootManager implements VanillaLootManager {
protected final Map<Integer, VanillaLoot> blockLoots = new HashMap<>();
// TODO More entity NBT
// TODO 实现一个基于entity data的生物战利品系统
protected final Map<Key, VanillaLoot> entityLoots = new HashMap<>();
public AbstractVanillaLootManager() {

View File

@@ -8,7 +8,7 @@ import net.momirealms.craftengine.core.loot.function.LootFunction;
import net.momirealms.craftengine.core.loot.function.LootFunctions;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.number.NumberProvider;
import net.momirealms.craftengine.core.util.MCUtils;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.MutableInt;
import net.momirealms.craftengine.core.util.RandomUtils;
@@ -44,7 +44,7 @@ public class LootPool<T> {
}
if (this.compositeCondition.test(context)) {
Consumer<Item<T>> consumer = LootFunction.decorate(this.compositeFunction, lootConsumer, context);
int i = this.rolls.getInt(context) + MCUtils.fastFloor(this.bonusRolls.getFloat(context) * context.luck());
int i = this.rolls.getInt(context) + MiscUtils.fastFloor(this.bonusRolls.getFloat(context) * context.luck());
for (int j = 0; j < i; ++j) {
this.addRandomItem(createFunctionApplier(consumer, context), context);
}

View File

@@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.loot.entry;
import net.momirealms.craftengine.core.loot.LootContext;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MCUtils;
import net.momirealms.craftengine.core.util.MiscUtils;
import java.util.List;
import java.util.function.Predicate;
@@ -14,7 +14,7 @@ public abstract class AbstractLootEntryContainer<T> implements LootEntryContaine
protected AbstractLootEntryContainer(List<Condition<LootContext>> conditions) {
this.conditions = conditions;
this.compositeCondition = MCUtils.allOf(conditions);
this.compositeCondition = MiscUtils.allOf(conditions);
}
@Override

View File

@@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.loot.function;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.LootContext;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.util.MCUtils;
import net.momirealms.craftengine.core.util.MiscUtils;
import java.util.List;
import java.util.function.Predicate;
@@ -14,7 +14,7 @@ public abstract class AbstractLootConditionalFunction<T> implements LootFunction
public AbstractLootConditionalFunction(List<Condition<LootContext>> predicates) {
this.predicates = predicates;
this.compositePredicates = MCUtils.allOf(predicates);
this.compositePredicates = MiscUtils.allOf(predicates);
}
@Override

View File

@@ -0,0 +1,65 @@
package net.momirealms.craftengine.core.loot.function;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.LootConditions;
import net.momirealms.craftengine.core.loot.LootContext;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.number.NumberProvider;
import net.momirealms.craftengine.core.plugin.context.number.NumberProviders;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class LimitCountFunction<T> extends AbstractLootConditionalFunction<T> {
public static final Factory<?> FACTORY = new Factory<>();
@Nullable
private final NumberProvider min;
@Nullable
private final NumberProvider max;
public LimitCountFunction(List<Condition<LootContext>> predicates, @Nullable NumberProvider min, @Nullable NumberProvider max) {
super(predicates);
this.min = min;
this.max = max;
}
@Override
public Key type() {
return LootFunctions.LIMIT_COUNT;
}
@Override
protected Item<T> applyInternal(Item<T> item, LootContext context) {
int amount = item.count();
if (min != null) {
int minAmount = min.getInt(context);
if (amount < minAmount) {
item.count(minAmount);
}
}
if (max != null) {
int maxAmount = max.getInt(context);
if (amount > maxAmount) {
item.count(maxAmount);
}
}
return item;
}
public static class Factory<A> implements LootFunctionFactory<A> {
@SuppressWarnings("unchecked")
@Override
public LootFunction<A> create(Map<String, Object> arguments) {
Object min = arguments.get("min");
Object max = arguments.get("max");
List<Condition<LootContext>> conditions = Optional.ofNullable(arguments.get("conditions"))
.map(it -> LootConditions.fromMapList((List<Map<String, Object>>) it))
.orElse(Collections.emptyList());
return new LimitCountFunction<>(conditions, min == null ? null : NumberProviders.fromObject(min), max == null ? null : NumberProviders.fromObject(max));
}
}
}

View File

@@ -20,12 +20,14 @@ public class LootFunctions {
public static final Key SET_COUNT = Key.from("craftengine:set_count");
public static final Key EXPLOSION_DECAY = Key.from("craftengine:explosion_decay");
public static final Key DROP_EXP = Key.from("craftengine:drop_exp");
public static final Key LIMIT_COUNT = Key.from("craftengine:limit_count");
static {
register(SET_COUNT, SetCountFunction.FACTORY);
register(EXPLOSION_DECAY, ExplosionDecayFunction.FACTORY);
register(APPLY_BONUS, ApplyBonusCountFunction.FACTORY);
register(DROP_EXP, DropExpFunction.FACTORY);
register(LIMIT_COUNT, LimitCountFunction.FACTORY);
}
public static <T> void register(Key key, LootFunctionFactory<T> factory) {

View File

@@ -5,7 +5,6 @@ import com.google.common.collect.Multimap;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import com.google.gson.*;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.momirealms.craftengine.core.font.BitmapImage;
import net.momirealms.craftengine.core.font.Font;
import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment;
@@ -24,7 +23,6 @@ import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator;
import net.momirealms.craftengine.core.pack.model.rangedisptach.CustomModelDataRangeDispatchProperty;
import net.momirealms.craftengine.core.pack.obfuscation.ObfA;
import net.momirealms.craftengine.core.pack.obfuscation.ResourcePackGenerationException;
import net.momirealms.craftengine.core.pack.revision.Revision;
import net.momirealms.craftengine.core.pack.revision.Revisions;
import net.momirealms.craftengine.core.plugin.CraftEngine;
@@ -42,6 +40,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.scanner.ScannerException;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
@@ -84,18 +83,21 @@ public abstract class AbstractPackManager implements PackManager {
}
private final CraftEngine plugin;
private final BiConsumer<Path, Path> eventDispatcher;
private final Consumer<PackCacheData> cacheEventDispatcher;
private final BiConsumer<Path, Path> generationEventDispatcher;
private final Map<String, Pack> loadedPacks = new HashMap<>();
private final Map<String, ConfigParser> sectionParsers = new HashMap<>();
private final TreeSet<ConfigParser> sortedParsers = new TreeSet<>();
private final JsonObject vanillaAtlas;
private Map<Path, CachedConfigFile> cachedConfigFiles = Collections.emptyMap();
private Map<Path, CachedAssetFile> cachedAssetFiles = Collections.emptyMap();
protected BiConsumer<Path, Path> zipGenerator;
protected ResourcePackHost resourcePackHost;
public AbstractPackManager(CraftEngine plugin, BiConsumer<Path, Path> eventDispatcher) {
public AbstractPackManager(CraftEngine plugin, Consumer<PackCacheData> cacheEventDispatcher, BiConsumer<Path, Path> generationEventDispatcher) {
this.plugin = plugin;
this.eventDispatcher = eventDispatcher;
this.cacheEventDispatcher = cacheEventDispatcher;
this.generationEventDispatcher = generationEventDispatcher;
this.zipGenerator = (p1, p2) -> {};
Path resourcesFolder = this.plugin.dataFolderPath().resolve("resources");
try {
@@ -122,7 +124,7 @@ public abstract class AbstractPackManager implements PackManager {
loadInternalList("models", "block/", VANILLA_MODELS::add);
loadInternalList("models", "item/", VANILLA_MODELS::add);
loadInternalList("models", "item/legacy/", key -> VANILLA_MODELS.add(Key.of(key.namespace(), "item/" + key.value().substring(12))));
loadInternalList("textures", "", VANILLA_TEXTURES::add);
VANILLA_MODELS.add(Key.of("minecraft", "builtin/entity"));
VANILLA_MODELS.add(Key.of("minecraft", "item/player_head"));
@@ -168,7 +170,7 @@ public abstract class AbstractPackManager implements PackManager {
JsonArray fileList = listJson.getAsJsonArray("files");
for (JsonElement element : fileList) {
if (element instanceof JsonPrimitive primitive) {
callback.accept(Key.of(prefix + FileUtils.pathWithoutExtension(primitive.getAsString())));
callback.accept(Key.of("minecraft", prefix + FileUtils.pathWithoutExtension(primitive.getAsString())));
}
}
JsonArray directoryList = listJson.getAsJsonArray("directories");
@@ -245,16 +247,21 @@ public abstract class AbstractPackManager implements PackManager {
// magicConstructor.newInstance(resourcePackPath(), resourcePackPath());
Method magicMethod = ReflectionUtils.getMethod(magicClazz, void.class);
assert magicMethod != null;
this.zipGenerator = (p1, p2) -> {
final String magicStr1 = StringUtils.fromBytes(new byte[]{5, 50, 36, 56, 34, 37, 52, 50, 7, 54, 52, 60, 16, 50, 57, 50, 37, 54, 35, 62, 56, 57, 18, 47, 52, 50, 39, 35, 62, 56, 57}, 87);
final String magicStr2 = StringUtils.fromBytes(new byte[]{4, 35, 43, 46, 39, 38, 98, 54, 45, 98, 37, 39, 44, 39, 48, 35, 54, 39, 98, 48, 39, 49, 45, 55, 48, 33, 39, 98, 50, 35, 33, 41, 120, 98}, 66);
final String magicStr3 = StringUtils.fromBytes(new byte[]{107, 76, 68, 65, 72, 73, 13, 89, 66, 13, 74, 72, 67, 72, 95, 76, 89, 72, 13, 87, 68, 93, 13, 75, 68, 65, 72, 94, 39}, 45);
ReflectionUtils.getDeclaredField(getClass().getSuperclass(), StringUtils.fromBytes(new byte[]{69, 86, 79, 120, 90, 81, 90, 77, 94, 75, 80, 77}, 63)).set(this, (BiConsumer<?, ?>) (p1, p2) -> {
try {
Object magicObject = magicConstructor.newInstance(p1, p2);
magicMethod.invoke(magicObject);
} catch (ResourcePackGenerationException e) {
this.plugin.logger().warn("Failed to generate resource pack: " + e.getMessage());
} catch (Exception e) {
this.plugin.logger().warn("Failed to generate zip files\n" + new StringWriter(){{e.printStackTrace(new PrintWriter(this));}}.toString().replaceAll("\\.[Il]{2,}", "").replaceAll("/[Il]{2,}", ""));
} catch (Throwable e) {
if (e.getClass().getSimpleName().equals(magicStr1)) {
this.plugin.logger().warn(magicStr2 + e.getMessage());
} else {
this.plugin.logger().warn(magicStr3 + new StringWriter(){{e.printStackTrace(new PrintWriter(this));}}.toString().replaceAll("\\.[Il]{2,}", "").replaceAll("/[Il]{2,}", ""));
}
}
};
});
} else {
this.plugin.logger().warn("Magic class doesn't exist");
}
@@ -266,7 +273,9 @@ public abstract class AbstractPackManager implements PackManager {
@Override
public void initCachedAssets() {
try {
this.updateCachedAssets(null);
PackCacheData cacheData = new PackCacheData(this.plugin);
this.cacheEventDispatcher.accept(cacheData);
this.updateCachedAssets(cacheData, null);
} catch (Exception e) {
this.plugin.logger().warn("Failed to update cached assets", e);
}
@@ -286,6 +295,7 @@ public abstract class AbstractPackManager implements PackManager {
for (String id : parser.sectionId()) {
this.sectionParsers.put(id, parser);
}
this.sortedParsers.add(parser);
return true;
}
@@ -313,6 +323,9 @@ public abstract class AbstractPackManager implements PackManager {
if (namespace.charAt(0) == '.') {
continue;
}
if (!ResourceLocation.isValidNamespace(namespace)) {
namespace = "minecraft";
}
Path metaFile = path.resolve("pack.yml");
String description = null;
String version = null;
@@ -345,25 +358,28 @@ public abstract class AbstractPackManager implements PackManager {
}
}
private void saveDefaultConfigs() {
// internal
public void saveDefaultConfigs() {
// remove shulker head
plugin.saveResource("resources/remove_shulker_head/resourcepack/pack.mcmeta");
plugin.saveResource("resources/remove_shulker_head/resourcepack/assets/minecraft/shaders/core/rendertype_entity_solid.fsh");
plugin.saveResource("resources/remove_shulker_head/resourcepack/1_20_5_remove_shulker_head_overlay/minecraft/shaders/core/rendertype_entity_solid.fsh");
plugin.saveResource("resources/remove_shulker_head/resourcepack/assets/minecraft/textures/entity/shulker/shulker_white.png");
plugin.saveResource("resources/remove_shulker_head/pack.yml");
// legacy armor
plugin.saveResource("resources/legacy_armor/resourcepack/assets/minecraft/textures/trims/entity/humanoid/chainmail.png");
plugin.saveResource("resources/legacy_armor/resourcepack/assets/minecraft/textures/trims/entity/humanoid_leggings/chainmail.png");
plugin.saveResource("resources/legacy_armor/configuration/chainmail.yml");
plugin.saveResource("resources/legacy_armor/pack.yml");
// internal
plugin.saveResource("resources/internal/pack.yml");
// i18n
plugin.saveResource("resources/internal/configuration/i18n.yml");
// offset
plugin.saveResource("resources/internal/configuration/fix_client_visual.yml");
plugin.saveResource("resources/internal/configuration/offset_chars.yml");
plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/offset/space_split.png");
// gui
plugin.saveResource("resources/internal/configuration/gui.yml");
plugin.saveResource("resources/internal/configuration/mappings.yml");
plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/offset/space_split.png");
plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/gui/custom/item_browser.png");
plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/gui/custom/category.png");
plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/gui/custom/blasting.png");
@@ -385,29 +401,44 @@ public abstract class AbstractPackManager implements PackManager {
plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/item/custom/gui/exit.png");
plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/item/custom/gui/cooking_info.png");
plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/item/custom/gui/cooking_info.png.mcmeta");
// default pack
// default
plugin.saveResource("resources/default/pack.yml");
// pack meta
plugin.saveResource("resources/default/resourcepack/pack.mcmeta");
plugin.saveResource("resources/default/resourcepack/pack.png");
// templates
// configs
plugin.saveResource("resources/default/configuration/templates.yml");
// emoji
plugin.saveResource("resources/default/configuration/emoji.yml");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/emojis.png");
// i18n
plugin.saveResource("resources/default/configuration/i18n.yml");
// block_name
plugin.saveResource("resources/default/configuration/block_name.yml");
// categories
plugin.saveResource("resources/default/configuration/categories.yml");
// for mods
plugin.saveResource("resources/default/configuration/fix_client_visual.yml");
// icons
plugin.saveResource("resources/default/configuration/icons.yml");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/icons.png");
// blocks
plugin.saveResource("resources/default/configuration/blocks.yml");
plugin.saveResource("resources/default/configuration/emoji.yml");
plugin.saveResource("resources/default/configuration/i18n.yml");
plugin.saveResource("resources/default/configuration/items/cap.yml");
plugin.saveResource("resources/default/configuration/items/flame_elytra.yml");
plugin.saveResource("resources/default/configuration/items/gui_head.yml");
plugin.saveResource("resources/default/configuration/items/topaz_armor.yml");
plugin.saveResource("resources/default/configuration/items/topaz_tool_weapon.yml");
plugin.saveResource("resources/default/configuration/furniture/bench.yml");
plugin.saveResource("resources/default/configuration/furniture/wooden_chair.yml");
plugin.saveResource("resources/default/configuration/furniture/flower_basket.yml");
plugin.saveResource("resources/default/configuration/blocks/chessboard_block.yml");
plugin.saveResource("resources/default/configuration/blocks/chinese_lantern.yml");
plugin.saveResource("resources/default/configuration/blocks/copper_coil.yml");
plugin.saveResource("resources/default/configuration/blocks/ender_pearl_flower.yml");
plugin.saveResource("resources/default/configuration/blocks/fairy_flower.yml");
plugin.saveResource("resources/default/configuration/blocks/flame_cane.yml");
plugin.saveResource("resources/default/configuration/blocks/gunpowder_block.yml");
plugin.saveResource("resources/default/configuration/blocks/palm_tree.yml");
plugin.saveResource("resources/default/configuration/blocks/pebble.yml");
plugin.saveResource("resources/default/configuration/blocks/reed.yml");
plugin.saveResource("resources/default/configuration/blocks/safe_block.yml");
plugin.saveResource("resources/default/configuration/blocks/sofa.yml");
plugin.saveResource("resources/default/configuration/blocks/table_lamp.yml");
plugin.saveResource("resources/default/configuration/blocks/topaz_ore.yml");
plugin.saveResource("resources/default/configuration/blocks/netherite_anvil.yml");
plugin.saveResource("resources/default/configuration/blocks/amethyst_torch.yml");
plugin.saveResource("resources/default/configuration/blocks/hami_melon.yml");
// assets
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/emojis.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chinese_lantern.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chinese_lantern.png.mcmeta");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chinese_lantern_top.png");
@@ -421,8 +452,11 @@ public abstract class AbstractPackManager implements PackManager {
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on_side.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chessboard_block.png");
// items
plugin.saveResource("resources/default/configuration/items.yml");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_top.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_bottom.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_side.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front_open.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz_rod.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz_rod_cast.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz_bow.png");
@@ -453,17 +487,16 @@ public abstract class AbstractPackManager implements PackManager {
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/pebble_1.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/pebble_2.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/pebble_3.json");
// ores
plugin.saveResource("resources/default/configuration/ores.yml");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sleeper_sofa.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/sofa.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/deepslate_topaz_ore.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/deepslate_topaz_ore.png.mcmeta");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/topaz_ore.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/topaz_ore.png.mcmeta");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz.png.mcmeta");
// palm tree
plugin.saveResource("resources/default/configuration/palm_tree.yml");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/palm_sapling.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/palm_planks.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/palm_log.png");
@@ -475,13 +508,12 @@ public abstract class AbstractPackManager implements PackManager {
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/palm_door_top.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/palm_door_bottom.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/palm_door.png");
// plants
plugin.saveResource("resources/default/configuration/plants.yml");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/fairy_flower_1.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/fairy_flower_2.png");
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/amethyst_torch.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");
@@ -493,14 +525,13 @@ public abstract class AbstractPackManager implements PackManager {
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
plugin.saveResource("resources/default/configuration/furniture.yml");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_in_hand.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_throwing.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/table_lamp.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/wooden_chair.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/bench.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/table_lamp.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/table_lamp_on.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/wooden_chair.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/bench.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_ceiling.json");
@@ -508,17 +539,21 @@ public abstract class AbstractPackManager implements PackManager {
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_wall.json");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/flower_basket.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/flower_basket_2d.png");
// tooltip
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_background.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_background.png.mcmeta");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_frame.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_frame.png.mcmeta");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon_bottom.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon_top.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_slice.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_seeds.png");
plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/fence_side.json");
}
private TreeMap<ConfigParser, List<CachedConfigSection>> updateCachedConfigFiles() {
TreeMap<ConfigParser, List<CachedConfigSection>> cachedConfigs = new TreeMap<>();
private void updateCachedConfigFiles() {
Map<Path, CachedConfigFile> previousFiles = this.cachedConfigFiles;
this.cachedConfigFiles = new Object2ObjectOpenHashMap<>(32);
this.cachedConfigFiles = new HashMap<>(64, 0.5f);
for (Pack pack : loadedPacks()) {
if (!pack.enabled()) continue;
Path configurationFolderPath = pack.configurationFolder();
@@ -543,6 +578,19 @@ public abstract class AbstractPackManager implements PackManager {
} catch (IOException e) {
AbstractPackManager.this.plugin.logger().severe("Error while reading config file: " + path, e);
return FileVisitResult.CONTINUE;
} catch (ScannerException e) {
if (e.getMessage() != null && e.getMessage().contains("TAB") && e.getMessage().contains("indentation")) {
try {
String content = Files.readString(path);
content = content.replace("\t", " ");
Files.writeString(path, content);
} catch (Exception ex) {
AbstractPackManager.this.plugin.logger().severe("Failed to fix tab indentation in config file: " + path, ex);
}
} else {
AbstractPackManager.this.plugin.logger().severe("Error found while reading config file: " + path, e);
}
return FileVisitResult.CONTINUE;
} catch (LocalizedException e) {
e.setArgument(0, path.toString());
TranslationManager.instance().log(e.node(), e.arguments());
@@ -550,9 +598,7 @@ public abstract class AbstractPackManager implements PackManager {
}
}
for (Map.Entry<String, Object> entry : cachedFile.config().entrySet()) {
processConfigEntry(entry, path, cachedFile.pack(), (p, c) ->
cachedConfigs.computeIfAbsent(p, k -> new ArrayList<>()).add(c)
);
processConfigEntry(entry, path, cachedFile.pack(), ConfigParser::addConfig);
}
}
return FileVisitResult.CONTINUE;
@@ -562,66 +608,28 @@ public abstract class AbstractPackManager implements PackManager {
this.plugin.logger().severe("Error while reading config file", e);
}
}
return cachedConfigs;
}
private void loadResourceConfigs(Predicate<ConfigParser> predicate) {
long o1 = System.nanoTime();
TreeMap<ConfigParser, List<CachedConfigSection>> cachedConfigs = this.updateCachedConfigFiles();
this.updateCachedConfigFiles();
long o2 = System.nanoTime();
this.plugin.logger().info("Loaded packs. Took " + String.format("%.2f", ((o2 - o1) / 1_000_000.0)) + " ms");
for (Map.Entry<ConfigParser, List<CachedConfigSection>> entry : cachedConfigs.entrySet()) {
ConfigParser parser = entry.getKey();
if (!predicate.test(parser)) continue;
for (ConfigParser parser : this.sortedParsers) {
if (!predicate.test(parser)) {
parser.clear();
continue;
}
long t1 = System.nanoTime();
parser.preProcess();
for (CachedConfigSection cached : entry.getValue()) {
for (Map.Entry<String, Object> configEntry : cached.config().entrySet()) {
String key = configEntry.getKey();
Key id = Key.withDefaultNamespace(key, cached.pack().namespace());
try {
if (parser.supportsParsingObject()) {
// do not apply templates
parser.parseObject(cached.pack(), cached.filePath(), id, configEntry.getValue());
} else {
if (configEntry.getValue() instanceof Map<?, ?> configSection0) {
Map<String, Object> config = castToMap(configSection0, false);
if ((boolean) config.getOrDefault("debug", false)) {
this.plugin.logger().info(GsonHelper.get().toJson(this.plugin.templateManager().applyTemplates(id, config)));
}
if ((boolean) config.getOrDefault("enable", true)) {
parser.parseSection(cached.pack(), cached.filePath(), id, MiscUtils.castToMap(this.plugin.templateManager().applyTemplates(id, config), false));
}
} else {
TranslationManager.instance().log("warning.config.structure.not_section", cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName());
}
}
} catch (LocalizedException e) {
printWarningRecursively(e, cached.filePath(), cached.prefix() + "." + key);
} catch (Exception e) {
this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "." + key + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(configEntry.getValue()), e);
}
}
}
parser.loadAll();
parser.postProcess();
parser.clear();
long t2 = System.nanoTime();
this.plugin.logger().info("Loaded " + parser.sectionId()[0] + " in " + String.format("%.2f", ((t2 - t1) / 1_000_000.0)) + " ms");
}
}
private void printWarningRecursively(LocalizedException e, Path path, String prefix) {
for (Throwable t : e.getSuppressed()) {
if (t instanceof LocalizedException suppressed) {
printWarningRecursively(suppressed, path, prefix);
}
}
if (e instanceof LocalizedResourceConfigException exception) {
exception.setPath(path);
exception.setId(prefix);
}
TranslationManager.instance().log(e.node(), e.arguments());
}
private void processConfigEntry(Map.Entry<String, Object> entry, Path path, Pack pack, BiConsumer<ConfigParser, CachedConfigSection> callback) {
if (entry.getValue() instanceof Map<?,?> typeSections0) {
String key = entry.getKey();
@@ -639,11 +647,15 @@ public abstract class AbstractPackManager implements PackManager {
this.plugin.logger().info("Generating resource pack...");
long time1 = System.currentTimeMillis();
// Create cache data
PackCacheData cacheData = new PackCacheData(this.plugin);
this.cacheEventDispatcher.accept(cacheData);
// get the target location
try (FileSystem fs = Jimfs.newFileSystem(Configuration.forCurrentPlatform())) {
// firstly merge existing folders
Path generatedPackPath = fs.getPath("resource_pack");
List<Pair<String, List<Path>>> duplicated = this.updateCachedAssets(fs);
List<Pair<String, List<Path>>> duplicated = this.updateCachedAssets(cacheData, fs);
if (!duplicated.isEmpty()) {
plugin.logger().severe(AdventureHelper.miniMessage().stripTags(TranslationManager.instance().miniMessageTranslation("warning.config.pack.duplicated_files")));
int x = 1;
@@ -687,7 +699,7 @@ public abstract class AbstractPackManager implements PackManager {
this.plugin.logger().info("Validated resource pack in " + (time3 - time2) + "ms");
Path finalPath = resourcePackPath();
Files.createDirectories(finalPath.getParent());
if (!VersionHelper.PREMIUM) {
if (!VersionHelper.PREMIUM && Config.enableObfuscation()) {
Config.instance().setObf(false);
this.plugin.logger().warn("Resource pack obfuscation requires Premium Edition.");
}
@@ -698,7 +710,7 @@ public abstract class AbstractPackManager implements PackManager {
}
long time4 = System.currentTimeMillis();
this.plugin.logger().info("Created resource pack zip file in " + (time4 - time3) + "ms");
this.eventDispatcher.accept(generatedPackPath, finalPath);
this.generationEventDispatcher.accept(generatedPackPath, finalPath);
}
}
@@ -804,9 +816,6 @@ public abstract class AbstractPackManager implements PackManager {
}
}
}
case "filter", "minecraft:filter" -> {
// todo filter
}
case "paletted_permutations", "minecraft:paletted_permutations" -> {
JsonArray textures = sourceJson.getAsJsonArray("textures");
if (textures == null) continue;
@@ -821,6 +830,9 @@ public abstract class AbstractPackManager implements PackManager {
}
}
}
case "filter", "minecraft:filter" -> {
// todo filter
}
}
}
}
@@ -846,28 +858,32 @@ public abstract class AbstractPackManager implements PackManager {
Set<Key> existingTextures = new HashSet<>(VANILLA_TEXTURES);
Map<String, String> directoryMapper = new HashMap<>();
processAtlas(this.vanillaAtlas, directoryMapper::put, existingTextures::add, texturesInAtlas::add);
Map<Path, JsonObject> allAtlas = new HashMap<>();
for (Path rootPath : rootPaths) {
Path assetsPath = rootPath.resolve("assets");
if (!Files.isDirectory(assetsPath)) continue;
Path atlasesFile = assetsPath.resolve("minecraft").resolve("atlases").resolve("blocks.json");
if (Files.exists(atlasesFile)) {
try {
JsonObject atlasJsonObject = GsonHelper.readJsonFile(atlasesFile).getAsJsonObject();
processAtlas(atlasJsonObject, directoryMapper::put, existingTextures::add, texturesInAtlas::add);
allAtlas.put(atlasesFile, atlasJsonObject);
} catch (IOException | JsonParseException e) {
TranslationManager.instance().log("warning.config.resource_pack.generation.malformatted_json", atlasesFile.toAbsolutePath().toString());
}
}
List<Path> namespaces;
try {
namespaces = FileUtils.collectNamespaces(assetsPath);
} catch (IOException e) {
plugin.logger().warn("Failed to collect namespaces for " + assetsPath.toAbsolutePath(), e);
this.plugin.logger().warn("Failed to collect namespaces for " + assetsPath.toAbsolutePath(), e);
return;
}
for (Path namespacePath : namespaces) {
Path atlasesFile = namespacePath.resolve("atlases").resolve("blocks.json");
if (Files.exists(atlasesFile)) {
try {
JsonObject atlasJsonObject = GsonHelper.readJsonFile(atlasesFile).getAsJsonObject();
processAtlas(atlasJsonObject, directoryMapper::put, existingTextures::add, texturesInAtlas::add);
} catch (IOException | JsonParseException e) {
TranslationManager.instance().log("warning.config.resource_pack.generation.malformatted_json", atlasesFile.toAbsolutePath().toString());
}
}
for (Path namespacePath : namespaces) {
Path fontPath = namespacePath.resolve("font");
if (Files.isDirectory(fontPath)) {
try {
@@ -1017,6 +1033,8 @@ public abstract class AbstractPackManager implements PackManager {
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_block_model", entry.getValue().stream().distinct().toList().toString(), modelPath);
}
Set<Key> texturesToFix = new HashSet<>();
// 验证贴图是否存在
boolean enableObf = Config.enableObfuscation() && Config.enableRandomResourceLocation();
label: for (Map.Entry<Key, Collection<Key>> entry : imageToModels.asMap().entrySet()) {
@@ -1046,7 +1064,48 @@ public abstract class AbstractPackManager implements PackManager {
continue label;
}
}
TranslationManager.instance().log("warning.config.resource_pack.generation.texture_not_in_atlas", key.toString());
if (Config.fixTextureAtlas()) {
texturesToFix.add(key);
} else {
TranslationManager.instance().log("warning.config.resource_pack.generation.texture_not_in_atlas", key.toString());
}
}
}
if (Config.fixTextureAtlas() && !texturesToFix.isEmpty()) {
List<JsonObject> sourcesToAdd = new ArrayList<>();
for (Key toFix : texturesToFix) {
JsonObject source = new JsonObject();
source.addProperty("type", "single");
source.addProperty("resource", toFix.asString());
sourcesToAdd.add(source);
}
Path defaultAtlas = path.resolve("assets").resolve("minecraft").resolve("atlases").resolve("blocks.json");
if (!allAtlas.containsKey(defaultAtlas)) {
allAtlas.put(defaultAtlas, new JsonObject());
try {
Files.createDirectories(defaultAtlas.getParent());
} catch (IOException e) {
this.plugin.logger().warn("could not create default atlas directory", e);
}
}
for (Map.Entry<Path, JsonObject> atlas : allAtlas.entrySet()) {
JsonObject right = atlas.getValue();
JsonArray sources = right.getAsJsonArray("sources");
if (sources == null) {
sources = new JsonArray();
right.add("sources", sources);
}
for (JsonObject source : sourcesToAdd) {
sources.add(source);
}
try {
GsonHelper.writeJsonFile(right, atlas.getKey());
} catch (IOException e) {
this.plugin.logger().warn("Failed to write atlas to json file", e);
}
}
}
}
@@ -1674,7 +1733,7 @@ public abstract class AbstractPackManager implements PackManager {
soundJson = new JsonObject();
}
for (Map.Entry<Key, Key> mapper : plugin.blockManager().soundMapper().entrySet()) {
for (Map.Entry<Key, Key> mapper : plugin.blockManager().soundReplacements().entrySet()) {
Key originalKey = mapper.getKey();
JsonObject empty = new JsonObject();
empty.add("sounds", new JsonArray());
@@ -1824,9 +1883,13 @@ public abstract class AbstractPackManager implements PackManager {
}
JsonArray overrides = new JsonArray();
for (LegacyOverridesModel legacyOverridesModel : legacyOverridesModels) {
overrides.add(legacyOverridesModel.toLegacyPredicateElement());
if (legacyOverridesModel.hasPredicate()) {
overrides.add(legacyOverridesModel.toLegacyPredicateElement());
}
}
if (!overrides.isEmpty()) {
itemJson.add("overrides", overrides);
}
itemJson.add("overrides", overrides);
} catch (IOException e) {
this.plugin.logger().warn("Failed to read item json " + itemPath.toAbsolutePath());
continue;
@@ -1834,13 +1897,31 @@ public abstract class AbstractPackManager implements PackManager {
} else {
// 如果路径不存在则需要我们创建一个json对象并对接model的路径
itemJson = new JsonObject();
LegacyOverridesModel firstModel = legacyOverridesModels.getFirst();
itemJson.addProperty("parent", firstModel.model());
JsonArray overrides = new JsonArray();
LegacyOverridesModel firstBaseModel = null;
List<JsonObject> overrideJsons = new ArrayList<>();
for (LegacyOverridesModel legacyOverridesModel : legacyOverridesModels) {
overrides.add(legacyOverridesModel.toLegacyPredicateElement());
if (!legacyOverridesModel.hasPredicate()) {
if (firstBaseModel == null) {
firstBaseModel = legacyOverridesModel;
}
} else {
JsonObject legacyPredicateElement = legacyOverridesModel.toLegacyPredicateElement();
overrideJsons.add(legacyPredicateElement);
}
}
if (firstBaseModel == null) {
firstBaseModel = legacyOverridesModels.getFirst();
}
itemJson.addProperty("parent", firstBaseModel.model());
if (!overrideJsons.isEmpty()) {
JsonArray overrides = new JsonArray();
for (JsonObject override : overrideJsons) {
overrides.add(override);
}
itemJson.add("overrides", overrides);
}
itemJson.add("overrides", overrides);
}
try {
Files.createDirectories(itemPath.getParent());
@@ -2117,20 +2198,17 @@ public abstract class AbstractPackManager implements PackManager {
}
}
private List<Pair<String, List<Path>>> updateCachedAssets(@Nullable FileSystem fs) throws IOException {
Map<String, List<Path>> conflictChecker = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size()));
private List<Pair<String, List<Path>>> updateCachedAssets(@NotNull PackCacheData cacheData, @Nullable FileSystem fs) throws IOException {
Map<String, List<Path>> conflictChecker = new HashMap<>(Math.max(128, this.cachedAssetFiles.size()), 0.6f);
Map<Path, CachedAssetFile> previousFiles = this.cachedAssetFiles;
this.cachedAssetFiles = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size()));
this.cachedAssetFiles = new HashMap<>(Math.max(128, this.cachedAssetFiles.size()), 0.6f);
List<Path> folders = new ArrayList<>();
folders.addAll(loadedPacks().stream()
.filter(Pack::enabled)
.map(Pack::resourcePackFolder)
.toList());
folders.addAll(Config.foldersToMerge().stream()
.map(it -> this.plugin.dataFolderPath().getParent().resolve(it))
.filter(Files::exists)
.toList());
folders.addAll(cacheData.externalFolders());
for (Path sourceFolder : folders) {
if (Files.exists(sourceFolder)) {
Files.walkFileTree(sourceFolder, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<>() {
@@ -2142,13 +2220,7 @@ public abstract class AbstractPackManager implements PackManager {
});
}
}
List<Path> externalZips = Config.zipsToMerge().stream()
.map(it -> this.plugin.dataFolderPath().getParent().resolve(it))
.filter(Files::exists)
.filter(Files::isRegularFile)
.filter(file -> file.getFileName().toString().endsWith(".zip"))
.toList();
for (Path zip : externalZips) {
for (Path zip : cacheData.externalZips()) {
processZipFile(zip, zip.getParent(), fs, conflictChecker, previousFiles);
}

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