mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-31 04:46:37 +00:00
外观方块分配 未测试
This commit is contained in:
@@ -18,9 +18,9 @@ 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.BlockStateAllocator;
|
||||
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;
|
||||
@@ -60,8 +60,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
protected final List<Suggestion> cachedSuggestions = new ArrayList<>();
|
||||
// 缓存的使用中的命名空间
|
||||
protected final Set<String> namespacesInUse = new HashSet<>();
|
||||
// 用于检测单个外观方块状态是否被绑定了不同模型
|
||||
protected final Map<Integer, JsonElement> tempVanillaBlockStateModels = new Int2ObjectOpenHashMap<>();
|
||||
// Map<方块类型, Map<方块状态NBT,模型>>,用于生成block state json
|
||||
protected final Map<Key, Map<String, JsonElement>> blockStateOverrides = new HashMap<>();
|
||||
// 用于生成mod使用的block state json
|
||||
@@ -81,7 +79,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
// 自定义状态列表,会随着重载变化
|
||||
protected final ImmutableBlockState[] immutableBlockStates;
|
||||
// 倒推缓存
|
||||
protected final BlockStateCandidate[] reversedBlockStateArranger;
|
||||
protected final BlockStateCandidate[] autoVisualBlockStateCandidates;
|
||||
// 用于检测单个外观方块状态是否被绑定了不同模型
|
||||
protected final JsonElement[] tempVanillaBlockStateModels;
|
||||
// 临时存储哪些视觉方块被使用了
|
||||
protected final Set<BlockStateWrapper> tempVisualBlockStatesInUse = new HashSet<>();
|
||||
protected final Set<Key> tempVisualBlocksInUse = new HashSet<>();
|
||||
@@ -91,14 +91,15 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
protected AbstractBlockManager(CraftEngine plugin, int vanillaBlockStateCount, int customBlockCount) {
|
||||
super(plugin);
|
||||
this.vanillaBlockStateCount = vanillaBlockStateCount;
|
||||
this.blockParser = new BlockParser();
|
||||
this.blockStateMappingParser = new BlockStateMappingParser();
|
||||
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.reversedBlockStateArranger = new BlockStateCandidate[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);
|
||||
}
|
||||
|
||||
@@ -130,7 +131,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
this.appearanceToRealState.clear();
|
||||
Arrays.fill(this.blockStateMappings, -1);
|
||||
Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE);
|
||||
Arrays.fill(this.reversedBlockStateArranger, null);
|
||||
Arrays.fill(this.autoVisualBlockStateCandidates, null);
|
||||
for (AutoStateGroup autoStateGroup : AutoStateGroup.values()) {
|
||||
autoStateGroup.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -188,7 +192,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
}
|
||||
|
||||
protected void clearCache() {
|
||||
this.tempVanillaBlockStateModels.clear();
|
||||
Arrays.fill(this.tempVanillaBlockStateModels, null);
|
||||
this.tempVisualBlockStatesInUse.clear();
|
||||
this.tempVisualBlocksInUse.clear();
|
||||
}
|
||||
@@ -275,26 +279,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
Key blockOwnerId = getBlockOwnerId(beforeState);
|
||||
List<BlockStateWrapper> blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(blockOwnerId, k -> new ArrayList<>());
|
||||
blockStateWrappers.add(beforeState);
|
||||
AbstractBlockManager.this.reversedBlockStateArranger[beforeState.registryId()] = blockParser.createVisualBlockCandidate(beforeState);
|
||||
|
||||
AbstractBlockManager.this.autoVisualBlockStateCandidates[beforeState.registryId()] = createVisualBlockCandidate(beforeState);
|
||||
}
|
||||
exceptionCollector.throwIfPresent();
|
||||
}
|
||||
}
|
||||
|
||||
public class BlockParser extends IdSectionConfigParser {
|
||||
public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"};
|
||||
private final IdAllocator internalIdAllocator;
|
||||
private final List<PendingConfigSection> pendingConfigSections = new ArrayList<>();
|
||||
private final BlockStateAllocator[] visualBlockStateAllocators = new BlockStateAllocator[AutoStateGroup.values().length];
|
||||
|
||||
public BlockParser() {
|
||||
this.internalIdAllocator = new IdAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("custom-block-states.json"));
|
||||
}
|
||||
|
||||
public void addPendingConfigSection(PendingConfigSection section) {
|
||||
this.pendingConfigSections.add(section);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockStateCandidate createVisualBlockCandidate(BlockStateWrapper blockState) {
|
||||
@@ -302,40 +290,58 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
if (!groups.isEmpty()) {
|
||||
BlockStateCandidate candidate = new BlockStateCandidate(blockState);
|
||||
for (AutoStateGroup group : groups) {
|
||||
getOrCreateBlockStateAllocator(group).addCandidate(candidate);
|
||||
group.addCandidate(candidate);
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private BlockStateAllocator getOrCreateBlockStateAllocator(AutoStateGroup group) {
|
||||
int index = group.ordinal();
|
||||
BlockStateAllocator visualBlockStateAllocator = this.visualBlockStateAllocators[index];
|
||||
if (visualBlockStateAllocator == null) {
|
||||
visualBlockStateAllocator = new BlockStateAllocator();
|
||||
this.visualBlockStateAllocators[index] = visualBlockStateAllocator;
|
||||
}
|
||||
return visualBlockStateAllocator;
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess() {
|
||||
this.visualBlockStateAllocator.processPendingAllocations();
|
||||
try {
|
||||
this.visualBlockStateAllocator.saveToCache();
|
||||
} catch (IOException e) {
|
||||
AbstractBlockManager.this.plugin.logger().warn("Error while saving visual block states allocation", e);
|
||||
}
|
||||
this.internalIdAllocator.processPendingAllocations();
|
||||
try {
|
||||
this.internalIdAllocator.saveToCache();
|
||||
} catch (IOException e) {
|
||||
AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block state allocation", e);
|
||||
AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block states allocation", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preProcess() {
|
||||
this.visualBlockStateAllocator.reset();
|
||||
try {
|
||||
this.visualBlockStateAllocator.loadFromCache();
|
||||
} catch (IOException e) {
|
||||
AbstractBlockManager.this.plugin.logger().warn("Error while loading visual block states allocation cache", e);
|
||||
}
|
||||
this.internalIdAllocator.reset(0, Config.serverSideBlocks() - 1);
|
||||
try {
|
||||
this.internalIdAllocator.loadFromCache();
|
||||
} catch (IOException e) {
|
||||
AbstractBlockManager.this.plugin.logger().warn("Error while loading custom block state allocation cache", e);
|
||||
AbstractBlockManager.this.plugin.logger().warn("Error while loading custom block states allocation cache", e);
|
||||
}
|
||||
for (PendingConfigSection section : this.pendingConfigSections) {
|
||||
ResourceConfigUtils.runCatching(
|
||||
@@ -437,9 +443,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFutures.allOf(internalIdAllocators).whenComplete((v, t) -> ResourceConfigUtils.runCatching(path, node, () -> {
|
||||
if (t != null) {
|
||||
if (t instanceof CompletionException e) {
|
||||
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) {
|
||||
@@ -449,7 +455,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Unknown error occurred", t);
|
||||
throw new RuntimeException("Unknown error occurred", t1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < internalIdAllocators.size(); i++) {
|
||||
@@ -472,110 +478,164 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
);
|
||||
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) {
|
||||
BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(stateSection.get("state"), "warning.config.block.state.missing_state"));
|
||||
this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(stateSection, "model", "models"));
|
||||
ImmutableBlockState onlyState = states.getFirst();
|
||||
// 为唯一的状态绑定外观
|
||||
onlyState.setVanillaBlockState(appearanceState);
|
||||
parseBlockEntityRender(stateSection.get("entity-renderer")).ifPresent(onlyState::setConstantRenderers);
|
||||
appearanceConfigs = Map.of("", stateSection);
|
||||
} else {
|
||||
BlockStateWrapper anyAppearanceState = null;
|
||||
Map<String, Object> appearancesSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances");
|
||||
// 也不能为空
|
||||
if (appearancesSection.isEmpty()) {
|
||||
throw new LocalizedResourceConfigException("warning.config.block.state.missing_appearances");
|
||||
}
|
||||
Map<String, BlockStateAppearance> appearances = Maps.newHashMap();
|
||||
// 先解析所有的外观
|
||||
for (Map.Entry<String, Object> entry : appearancesSection.entrySet()) {
|
||||
Map<String, Object> appearanceSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey());
|
||||
// 解析对应的视觉方块
|
||||
BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(appearanceSection.get("state"), "warning.config.block.state.missing_state"));
|
||||
this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(appearanceSection, "model", "models"));
|
||||
appearances.put(entry.getKey(), new BlockStateAppearance(appearanceState, parseBlockEntityRender(appearanceSection.get("entity-renderer"))));
|
||||
if (anyAppearanceState == null) {
|
||||
anyAppearanceState = appearanceState;
|
||||
}
|
||||
}
|
||||
// 解析变体
|
||||
Map<String, Object> variantsSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), "variants");
|
||||
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) {
|
||||
throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", variantNBT);
|
||||
}
|
||||
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) {
|
||||
throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearanceName);
|
||||
}
|
||||
for (ImmutableBlockState possibleState : possibleStates) {
|
||||
possibleState.setVanillaBlockState(appearance.blockState());
|
||||
appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 为没有外观的方块状态填充
|
||||
for (ImmutableBlockState blockState : states) {
|
||||
if (blockState.vanillaBlockState() == null) {
|
||||
blockState.setVanillaBlockState(anyAppearanceState);
|
||||
}
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
// 获取方块实体行为
|
||||
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();
|
||||
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.get(appearanceId))
|
||||
.orElseGet(() -> {
|
||||
// 如果未指定模型,说明复用原版模型?但是部分模型是多部位模型,无法使用变体解决问题
|
||||
// 未来需要靠mod重构彻底解决问题
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("model", "minecraft:block/air");
|
||||
return json;
|
||||
}));
|
||||
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 (stateSection.containsKey("auto-state")) {
|
||||
String autoStateId = stateSection.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");
|
||||
}
|
||||
}
|
||||
|
||||
// 一定要到最后再绑定
|
||||
customBlock.setBehavior(blockBehavior);
|
||||
holder.bindValue(customBlock);
|
||||
CompletableFutures.allOf(futureVisualStates.values()).whenComplete((v2, t2) -> {
|
||||
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);
|
||||
}
|
||||
|
||||
// 添加方块
|
||||
AbstractBlockManager.this.byId.put(customBlock.id(), customBlock);
|
||||
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)));
|
||||
}
|
||||
|
||||
@@ -622,12 +682,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
// 结合variants
|
||||
JsonElement combinedVariant = GsonHelper.combine(variants);
|
||||
Map<String, JsonElement> overrideMap = AbstractBlockManager.this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>());
|
||||
AbstractBlockManager.this.tempVanillaBlockStateModels.put(blockStateWrapper.registryId(), combinedVariant);
|
||||
JsonElement previous = overrideMap.get(propertyNBT);
|
||||
if (previous != null && !previous.equals(combinedVariant)) {
|
||||
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) {
|
||||
|
||||
@@ -23,4 +23,16 @@ public abstract class AbstractBlockStateWrapper implements BlockStateWrapper {
|
||||
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,6 +1,8 @@
|
||||
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.*;
|
||||
@@ -9,37 +11,68 @@ 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"), 0
|
||||
(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"), 0
|
||||
(w) -> w.getProperty("waterlogged")
|
||||
),
|
||||
TRIPWIRE("tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> true, 1),
|
||||
LOWER_TRIPWIRE("lower_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> w.getProperty("attached"), 0),
|
||||
HIGHER_TRIPWIRE("higher_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> !(boolean) w.getProperty("attached"), 0),
|
||||
NOTE_BLOCK("note_block", Set.of(BlockKeys.NOTE_BLOCK), (w) -> true, 0),
|
||||
BROWN_MUSHROOM("brown_mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK), (w) -> true, 0),
|
||||
RED_MUSHROOM("red_mushroom", Set.of(BlockKeys.RED_MUSHROOM_BLOCK), (w) -> true, 0),
|
||||
MUSHROOM_STEM("mushroom_stem", Set.of(BlockKeys.MUSHROOM_STEM), (w) -> true, 0),
|
||||
MUSHROOM("mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM), (w) -> true, 1),
|
||||
SOLID("solid", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM, BlockKeys.NOTE_BLOCK), (w) -> true, 2);
|
||||
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 int priority;
|
||||
private final List<BlockStateCandidate> candidates = new ArrayList<>();
|
||||
private int pointer;
|
||||
|
||||
AutoStateGroup(String id, Set<Key> blocks, Predicate<BlockStateWrapper> predicate, int priority) {
|
||||
AutoStateGroup(String id, Set<Key> blocks, Predicate<BlockStateWrapper> predicate) {
|
||||
this.id = id;
|
||||
this.blocks = blocks;
|
||||
this.predicate = predicate;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public int priority() {
|
||||
return priority;
|
||||
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() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.List;
|
||||
public final class BlockKeys {
|
||||
private BlockKeys() {}
|
||||
|
||||
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 CRAFTING_TABLE = Key.of("minecraft:crafting_table");
|
||||
@@ -262,6 +263,15 @@ public final class BlockKeys {
|
||||
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 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);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package net.momirealms.craftengine.core.block;
|
||||
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface BlockStateWrapper {
|
||||
public interface BlockStateWrapper extends Comparable<BlockStateWrapper> {
|
||||
|
||||
Object literalObject();
|
||||
|
||||
@@ -15,4 +16,9 @@ public interface BlockStateWrapper {
|
||||
boolean hasProperty(String propertyName);
|
||||
|
||||
String getAsString();
|
||||
|
||||
@Override
|
||||
default int compareTo(@NotNull BlockStateWrapper o) {
|
||||
return Integer.compare(registryId(), o.registryId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,16 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
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();
|
||||
|
||||
@@ -48,6 +48,10 @@ public interface FontManager extends Manageable {
|
||||
|
||||
OffsetFont offsetFont();
|
||||
|
||||
Map<Key, BitmapImage> loadedImages();
|
||||
|
||||
Map<Key, Emoji> emojis();
|
||||
|
||||
ConfigParser[] parsers();
|
||||
|
||||
default EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, @Nullable Player player) {
|
||||
|
||||
@@ -69,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);
|
||||
|
||||
@@ -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;
|
||||
@@ -27,7 +26,9 @@ import net.momirealms.craftengine.core.pack.obfuscation.ObfA;
|
||||
import net.momirealms.craftengine.core.pack.revision.Revision;
|
||||
import net.momirealms.craftengine.core.pack.revision.Revisions;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.*;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
|
||||
import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor;
|
||||
import net.momirealms.craftengine.core.plugin.locale.I18NData;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
@@ -615,7 +616,10 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
long o2 = System.nanoTime();
|
||||
this.plugin.logger().info("Loaded packs. Took " + String.format("%.2f", ((o2 - o1) / 1_000_000.0)) + " ms");
|
||||
for (ConfigParser parser : this.sortedParsers) {
|
||||
if (!predicate.test(parser)) continue;
|
||||
if (!predicate.test(parser)) {
|
||||
parser.clear();
|
||||
continue;
|
||||
}
|
||||
long t1 = System.nanoTime();
|
||||
parser.preProcess();
|
||||
parser.loadAll();
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package net.momirealms.craftengine.core.pack.allocator;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BlockStateAllocator {
|
||||
private final List<BlockStateCandidate> blockStates = new ArrayList<>();
|
||||
private int pointer = 0;
|
||||
private int max = -1;
|
||||
|
||||
public void addCandidate(BlockStateCandidate state) {
|
||||
this.blockStates.add(state);
|
||||
this.max = this.blockStates.size() - 1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockStateCandidate findNext() {
|
||||
while (this.pointer < this.max) {
|
||||
final BlockStateCandidate state = this.blockStates.get(this.pointer);
|
||||
if (!state.isUsed()) {
|
||||
return state;
|
||||
}
|
||||
this.pointer++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void processPendingAllocations() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public class IdAllocator {
|
||||
* @param shouldRemove 判断是否应该移除的谓词
|
||||
* @return 被移除的ID数量
|
||||
*/
|
||||
public int cleanupUnusedIds(Predicate<String> shouldRemove) {
|
||||
public List<String> cleanupUnusedIds(Predicate<String> shouldRemove) {
|
||||
List<String> idsToRemove = new ArrayList<>();
|
||||
for (String id : this.cachedIdMap.keySet()) {
|
||||
if (shouldRemove.test(id)) {
|
||||
@@ -160,15 +160,13 @@ public class IdAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
int removedCount = 0;
|
||||
for (String id : idsToRemove) {
|
||||
Integer removedId = this.cachedIdMap.remove(id);
|
||||
if (removedId != null && !this.forcedIdMap.containsValue(removedId)) {
|
||||
this.occupiedIdSet.clear(removedId);
|
||||
removedCount++;
|
||||
}
|
||||
}
|
||||
return removedCount;
|
||||
return idsToRemove;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package net.momirealms.craftengine.core.pack.allocator;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import net.momirealms.craftengine.core.block.AutoStateGroup;
|
||||
import net.momirealms.craftengine.core.block.BlockStateWrapper;
|
||||
import net.momirealms.craftengine.core.util.FileUtils;
|
||||
import net.momirealms.craftengine.core.util.GsonHelper;
|
||||
import net.momirealms.craftengine.core.util.Pair;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class VisualBlockStateAllocator {
|
||||
private final Path cacheFilePath;
|
||||
private final Map<String, BlockStateWrapper> cachedBlockStates = new HashMap<>();
|
||||
private final Map<String, Pair<AutoStateGroup, CompletableFuture<BlockStateWrapper>>> pendingAllocations = new LinkedHashMap<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
private final List<Pair<String, CompletableFuture<BlockStateWrapper>>>[] pendingAllocationFutures = new List[AutoStateGroup.values().length];
|
||||
private final BlockStateCandidate[] candidates;
|
||||
private final Function<String, BlockStateWrapper> factory;
|
||||
|
||||
public VisualBlockStateAllocator(Path cacheFilePath, BlockStateCandidate[] candidates, Function<String, BlockStateWrapper> factory) {
|
||||
this.cacheFilePath = cacheFilePath;
|
||||
this.candidates = candidates;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
Arrays.fill(this.pendingAllocationFutures, new ArrayList<>());
|
||||
this.cachedBlockStates.clear();
|
||||
this.pendingAllocations.clear();
|
||||
}
|
||||
|
||||
public CompletableFuture<BlockStateWrapper> assignFixedBlockState(String name, BlockStateWrapper state) {
|
||||
this.cachedBlockStates.remove(name);
|
||||
BlockStateCandidate candidate = this.candidates[state.registryId()];
|
||||
if (candidate != null) {
|
||||
candidate.setUsed();
|
||||
}
|
||||
return CompletableFuture.completedFuture(state);
|
||||
}
|
||||
|
||||
public CompletableFuture<BlockStateWrapper> requestAutoState(String name, AutoStateGroup group) {
|
||||
CompletableFuture<BlockStateWrapper> future = new CompletableFuture<>();
|
||||
this.pendingAllocations.put(name, new Pair<>(group, future));
|
||||
this.pendingAllocationFutures[group.ordinal()].add(Pair.of(name, future));
|
||||
return future;
|
||||
}
|
||||
|
||||
public void processPendingAllocations() {
|
||||
// 先处理缓存的
|
||||
for (Map.Entry<String, BlockStateWrapper> entry : this.cachedBlockStates.entrySet()) {
|
||||
int registryId = entry.getValue().registryId();
|
||||
// 检查候选方块是否可用
|
||||
BlockStateCandidate candidate = this.candidates[registryId];
|
||||
if (candidate != null) {
|
||||
// 未被使用
|
||||
if (!candidate.isUsed()) {
|
||||
// 获取当前的安排任务
|
||||
Pair<AutoStateGroup, CompletableFuture<BlockStateWrapper>> pair = this.pendingAllocations.get(entry.getKey());
|
||||
// 如果候选满足组,那么直接允许起飞
|
||||
if (pair != null && pair.left().test(candidate.blockState())) {
|
||||
pair.right().complete(candidate.blockState());
|
||||
}
|
||||
// 尽管未被使用,该槽位也应该被占用,以避免被自动分配到
|
||||
candidate.setUsed();
|
||||
}
|
||||
// 被使用了就随他去
|
||||
}
|
||||
// 没有候选也随他去
|
||||
}
|
||||
|
||||
this.pendingAllocations.clear();
|
||||
|
||||
for (AutoStateGroup group : AutoStateGroup.values()) {
|
||||
List<Pair<String, CompletableFuture<BlockStateWrapper>>> pendingAllocationFuture = this.pendingAllocationFutures[group.ordinal()];
|
||||
for (Pair<String, CompletableFuture<BlockStateWrapper>> pair : pendingAllocationFuture) {
|
||||
BlockStateCandidate nextCandidate = group.findNextCandidate();
|
||||
if (nextCandidate != null) {
|
||||
nextCandidate.setUsed();
|
||||
this.cachedBlockStates.put(pair.left(), nextCandidate.blockState());
|
||||
pair.right().complete(nextCandidate.blockState());
|
||||
} else {
|
||||
pair.right().completeExceptionally(new StateExhaustedException(group));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class StateExhaustedException extends RuntimeException {
|
||||
private final AutoStateGroup group;
|
||||
|
||||
public StateExhaustedException(AutoStateGroup group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public AutoStateGroup group() {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件加载缓存
|
||||
*/
|
||||
public void loadFromCache() throws IOException {
|
||||
if (!Files.exists(this.cacheFilePath)) {
|
||||
return;
|
||||
}
|
||||
JsonElement element = GsonHelper.readJsonFile(this.cacheFilePath);
|
||||
if (element instanceof JsonObject jsonObject) {
|
||||
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
|
||||
if (entry.getValue() instanceof JsonPrimitive primitive) {
|
||||
String id = primitive.getAsString();
|
||||
BlockStateWrapper state = this.factory.apply(id);
|
||||
if (state != null) {
|
||||
this.cachedBlockStates.put(entry.getKey(), state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存缓存到文件
|
||||
*/
|
||||
public void saveToCache() throws IOException {
|
||||
// 创建按ID排序的TreeMap
|
||||
Map<BlockStateWrapper, String> sortedById = new TreeMap<>();
|
||||
for (Map.Entry<String, BlockStateWrapper> entry : this.cachedBlockStates.entrySet()) {
|
||||
sortedById.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
// 创建有序的JSON对象
|
||||
JsonObject sortedJsonObject = new JsonObject();
|
||||
for (Map.Entry<BlockStateWrapper, String> entry : sortedById.entrySet()) {
|
||||
sortedJsonObject.addProperty(entry.getValue(), entry.getKey().getAsString());
|
||||
}
|
||||
if (sortedJsonObject.isEmpty()) {
|
||||
if (Files.exists(this.cacheFilePath)) {
|
||||
Files.delete(this.cacheFilePath);
|
||||
}
|
||||
} else {
|
||||
FileUtils.createDirectoriesSafe(this.cacheFilePath.getParent());
|
||||
GsonHelper.writeJsonFile(sortedJsonObject, this.cacheFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
62
core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/AllocationCacheFile.java
vendored
Normal file
62
core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/AllocationCacheFile.java
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package net.momirealms.craftengine.core.pack.allocator.cache;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class AllocationCacheFile<T extends Comparable<T>, A> {
|
||||
private final Map<String, T> cache;
|
||||
private final CacheStorage<A> cacheStorage;
|
||||
private final CacheSerializer<T, A> serializer;
|
||||
|
||||
public AllocationCacheFile(CacheStorage<A> cacheStorage, CacheSerializer<T, A> serializer) {
|
||||
this.cache = new HashMap<>();
|
||||
this.cacheStorage = cacheStorage;
|
||||
this.serializer = serializer;
|
||||
}
|
||||
|
||||
public Map<String, T> cache() {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> load() {
|
||||
return this.cacheStorage.load().thenAccept(a -> {
|
||||
Map<String, T> deserialized = this.serializer.deserialize(a);
|
||||
this.cache.putAll(deserialized);
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> save() {
|
||||
Map<T, String> sortedById = new TreeMap<>();
|
||||
for (Map.Entry<String, T> entry : this.cache.entrySet()) {
|
||||
sortedById.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
return this.cacheStorage.save(this.serializer.serialize(sortedById));
|
||||
}
|
||||
|
||||
public Iterable<Map.Entry<String, T>> entrySet() {
|
||||
return this.cache.entrySet();
|
||||
}
|
||||
|
||||
public Set<String> keySet() {
|
||||
return this.cache.keySet();
|
||||
}
|
||||
|
||||
public void put(String name, T newId) {
|
||||
this.cache.put(name, newId);
|
||||
}
|
||||
|
||||
public T remove(String name) {
|
||||
return this.cache.remove(name);
|
||||
}
|
||||
|
||||
public T get(String name) {
|
||||
return this.cache.get(name);
|
||||
}
|
||||
}
|
||||
98
core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileStorage.java
vendored
Normal file
98
core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileStorage.java
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
package net.momirealms.craftengine.core.pack.allocator.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface CacheFileStorage<A> {
|
||||
|
||||
CompletableFuture<A> load();
|
||||
|
||||
CompletableFuture<Void> save(A value);
|
||||
|
||||
boolean needForceUpdate();
|
||||
|
||||
static <A> LocalFileCacheStorage<A> local(Path path, CacheFileType<A> type) {
|
||||
return new LocalFileCacheStorage<>(path, type);
|
||||
}
|
||||
|
||||
abstract class AbstractRemoteFileCacheStorage<A> implements CacheFileStorage<A> {
|
||||
|
||||
@Override
|
||||
public boolean needForceUpdate() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class LocalFileCacheStorage<A> implements CacheFileStorage<A> {
|
||||
private final CacheFileType<A> fileType;
|
||||
private final Path filePath;
|
||||
private long lastModified = 0L;
|
||||
|
||||
public LocalFileCacheStorage(Path filePath, CacheFileType<A> type) {
|
||||
this.filePath = filePath;
|
||||
this.fileType = type;
|
||||
updateLastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needForceUpdate() {
|
||||
try {
|
||||
if (!Files.exists(this.filePath)) {
|
||||
return this.lastModified != 0L; // 文件被删除了,需要更新
|
||||
}
|
||||
long currentModified = Files.getLastModifiedTime(this.filePath).toMillis();
|
||||
if (currentModified > this.lastModified) {
|
||||
this.lastModified = currentModified;
|
||||
return true; // 文件被修改了,需要强制更新
|
||||
}
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
// 如果无法读取文件信息,保守起见返回 true 强制更新
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<A> load() {
|
||||
if (!Files.exists(this.filePath)) {
|
||||
this.lastModified = 0L; // 重置最后修改时间
|
||||
return CompletableFuture.completedFuture(this.fileType.create());
|
||||
}
|
||||
try {
|
||||
A result = this.fileType.read(this.filePath);
|
||||
updateLastModified(); // 加载成功后更新最后修改时间
|
||||
return CompletableFuture.completedFuture(result);
|
||||
} catch (Exception e) {
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> save(A value) {
|
||||
try {
|
||||
this.fileType.write(this.filePath, value);
|
||||
updateLastModified(); // 保存成功后更新最后修改时间
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} catch (Exception e) {
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新最后修改时间
|
||||
*/
|
||||
private void updateLastModified() {
|
||||
try {
|
||||
if (Files.exists(this.filePath)) {
|
||||
this.lastModified = Files.getLastModifiedTime(filePath).toMillis();
|
||||
} else {
|
||||
this.lastModified = 0L;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
this.lastModified = 0L; // 出错时重置
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileType.java
vendored
Normal file
39
core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileType.java
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package net.momirealms.craftengine.core.pack.allocator.cache;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import net.momirealms.craftengine.core.util.GsonHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface CacheFileType<T> {
|
||||
JsonCacheFileType JSON = new JsonCacheFileType();
|
||||
|
||||
T read(Path path) throws IOException;
|
||||
|
||||
void write(Path path, T value) throws IOException;
|
||||
|
||||
T create();
|
||||
|
||||
class JsonCacheFileType implements CacheFileType<JsonObject> {
|
||||
|
||||
@Override
|
||||
public JsonObject read(Path path) throws IOException {
|
||||
if (Files.exists(path)) {
|
||||
return GsonHelper.readJsonFile(path).getAsJsonObject();
|
||||
}
|
||||
return new JsonObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Path path, JsonObject value) throws IOException {
|
||||
GsonHelper.writeJsonFile(value, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject create() {
|
||||
return new JsonObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
51
core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheSerializer.java
vendored
Normal file
51
core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheSerializer.java
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package net.momirealms.craftengine.core.pack.allocator.cache;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public interface CacheSerializer<T extends Comparable<T>, A> {
|
||||
|
||||
A serialize(Map<T, String> obj);
|
||||
|
||||
Map<String, T> deserialize(A obj);
|
||||
|
||||
static <T extends Comparable<T>> CacheSerializer<T, JsonObject> json() {
|
||||
return new JsonSerializer<>();
|
||||
}
|
||||
|
||||
class JsonSerializer<T extends Comparable<T>> implements CacheSerializer<T, JsonObject> {
|
||||
|
||||
@Override
|
||||
public JsonObject serialize(Map<T, String> obj) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
for (Map.Entry<T, String> entry : obj.entrySet()) {
|
||||
if (entry.getKey() instanceof Integer i) {
|
||||
jsonObject.addProperty(entry.getValue(), i);
|
||||
} else if (entry.getKey() instanceof String s) {
|
||||
jsonObject.addProperty(entry.getValue(), s);
|
||||
}
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map<String, T> deserialize(JsonObject obj) {
|
||||
Map<String, T> map = new HashMap<>();
|
||||
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
|
||||
if (entry.getValue() instanceof JsonPrimitive primitive) {
|
||||
if (primitive.isNumber()) {
|
||||
map.put(entry.getKey(), (T) (Integer) primitive.getAsInt());
|
||||
} else if (primitive.isString()) {
|
||||
map.put(entry.getKey(), (T) primitive.getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheStorage.java
vendored
Normal file
32
core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheStorage.java
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package net.momirealms.craftengine.core.pack.allocator.cache;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class CacheStorage<A> {
|
||||
private final CacheFileStorage<A> storage;
|
||||
private A lastReadValue;
|
||||
|
||||
public CacheStorage(CacheFileStorage<A> storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> save(@NotNull final A value) {
|
||||
if (!value.equals(this.lastReadValue) || this.storage.needForceUpdate()) {
|
||||
this.lastReadValue = value;
|
||||
return this.storage.save(value);
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
public CompletableFuture<A> load() {
|
||||
if (this.lastReadValue != null && !this.storage.needForceUpdate()) {
|
||||
return CompletableFuture.completedFuture(this.lastReadValue);
|
||||
}
|
||||
return this.storage.load().thenApply(a -> {
|
||||
this.lastReadValue = a;
|
||||
return a;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user