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:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,4 +8,6 @@ public abstract class AbstractAdvancementManager implements AdvancementManager {
|
||||
public AbstractAdvancementManager(CraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ public interface DelegatingBlock {
|
||||
*/
|
||||
ObjectHolder<BlockBehavior> behaviorDelegate();
|
||||
|
||||
// 其实是错误的做法
|
||||
@Deprecated
|
||||
boolean isNoteBlock();
|
||||
|
||||
// 其实是错误的做法
|
||||
@Deprecated
|
||||
boolean isTripwire();
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -5,5 +5,7 @@ public enum PushReaction {
|
||||
DESTROY,
|
||||
BLOCK,
|
||||
IGNORE,
|
||||
PUSH_ONLY
|
||||
PUSH_ONLY;
|
||||
|
||||
public static final PushReaction[] VALUES = values();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.momirealms.craftengine.core.block.state;
|
||||
package net.momirealms.craftengine.core.block;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.momirealms.craftengine.core.block.properties.type;
|
||||
|
||||
public enum AnchorType {
|
||||
FLOOR,
|
||||
WALL,
|
||||
CEILING
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.momirealms.craftengine.core.block.properties.type;
|
||||
|
||||
public enum DoorHinge {
|
||||
LEFT, RIGHT
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.momirealms.craftengine.core.block.properties.type;
|
||||
|
||||
public enum DoubleBlockHalf {
|
||||
UPPER, LOWER
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.momirealms.craftengine.core.block.properties.type;
|
||||
|
||||
public enum SingleBlockHalf {
|
||||
TOP, BOTTOM
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.momirealms.craftengine.core.block.properties.type;
|
||||
|
||||
public enum SlabType {
|
||||
TOP,
|
||||
BOTTOM,
|
||||
DOUBLE
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.momirealms.craftengine.core.block.properties.type;
|
||||
|
||||
public enum SofaShape {
|
||||
STRAIGHT,
|
||||
INNER_LEFT,
|
||||
INNER_RIGHT,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.momirealms.craftengine.core.block.state.properties;
|
||||
package net.momirealms.craftengine.core.block.properties.type;
|
||||
|
||||
public enum StairsShape {
|
||||
STRAIGHT,
|
||||
@@ -1,5 +0,0 @@
|
||||
package net.momirealms.craftengine.core.block.state.properties;
|
||||
|
||||
public enum DoorHinge {
|
||||
LEFT, RIGHT
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package net.momirealms.craftengine.core.block.state.properties;
|
||||
|
||||
public enum DoubleBlockHalf {
|
||||
UPPER, LOWER
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package net.momirealms.craftengine.core.block.state.properties;
|
||||
|
||||
public enum SingleBlockHalf {
|
||||
TOP, BOTTOM
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package net.momirealms.craftengine.core.block.state.properties;
|
||||
|
||||
public enum SlabType {
|
||||
TOP,
|
||||
BOTTOM,
|
||||
DOUBLE
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package net.momirealms.craftengine.core.entity.projectile;
|
||||
|
||||
public enum ProjectileType {
|
||||
TRIDENT
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,8 @@ public interface Recipe<T> {
|
||||
default boolean showNotification() {
|
||||
return true;
|
||||
}
|
||||
|
||||
default boolean canBeSearchedByIngredients() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user