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

Merge branch 'Xiao-MoMi:dev' into dev

This commit is contained in:
jhqwqmc
2025-04-04 21:49:35 +08:00
committed by GitHub
14 changed files with 170 additions and 129 deletions

View File

@@ -37,7 +37,7 @@ public final class CraftEngineBlocks {
*/
@Nullable
public static CustomBlock byId(@NotNull Key id) {
return BukkitBlockManager.instance().getBlock(id).orElse(null);
return BukkitBlockManager.instance().blockById(id).orElse(null);
}
/**

View File

@@ -36,7 +36,6 @@ import org.bukkit.NamespacedKey;
import org.bukkit.block.data.BlockData;
import org.bukkit.event.HandlerList;
import org.bukkit.inventory.ItemStack;
import org.incendo.cloud.suggestion.Suggestion;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
@@ -59,11 +58,7 @@ public class BukkitBlockManager extends AbstractBlockManager {
// The total amount of blocks registered
private int customBlockCount;
// CraftEngine objects
private final Map<Key, CustomBlock> byId = new HashMap<>();
private final ImmutableBlockState[] stateId2ImmutableBlockStates;
protected final ImmutableBlockState[] stateId2ImmutableBlockStates;
// Minecraft objects
// Cached new blocks $ holders
private ImmutableMap<Key, Integer> internalId2StateId;
@@ -87,14 +82,9 @@ public class BukkitBlockManager extends AbstractBlockManager {
private final Map<Key, Map<String, JsonElement>> blockStateOverrides = new HashMap<>();
// for mod, real block id -> state models
private final Map<Key, JsonElement> modBlockStates = new HashMap<>();
// Cached command suggestions
private final List<Suggestion> cachedSuggestions = new ArrayList<>();
// Cached Namespace
private final Set<String> namespacesInUse = new HashSet<>();
// Event listeners
private final BlockEventListener blockEventListener;
private final FallingBlockRemoveListener fallingBlockRemoveListener;
private WorldEditCommandHelper weCommandHelper;
public BukkitBlockManager(BukkitCraftEngine plugin) {
@@ -106,7 +96,7 @@ public class BukkitBlockManager extends AbstractBlockManager {
if (plugin.hasMod() && plugin.requiresRestart()) {
blockEventListener = null;
fallingBlockRemoveListener = null;
stateId2ImmutableBlockStates = null;
stateId2ImmutableBlockStates = new ImmutableBlockState[]{};
return;
}
this.registerBlocks();
@@ -152,11 +142,9 @@ public class BukkitBlockManager extends AbstractBlockManager {
@Override
public void unload() {
super.clearModelsToGenerate();
super.unload();
this.clearCache();
this.appearanceToRealState.clear();
this.byId.clear();
this.cachedSuggestions.clear();
this.blockStateOverrides.clear();
this.modBlockStates.clear();
if (EmptyBlock.INSTANCE != null)
@@ -236,41 +224,6 @@ public class BukkitBlockManager extends AbstractBlockManager {
return Collections.unmodifiableMap(this.blockStateOverrides);
}
@Override
public Map<Key, CustomBlock> blocks() {
return Collections.unmodifiableMap(this.byId);
}
@Override
public Optional<CustomBlock> getBlock(Key key) {
return Optional.ofNullable(this.byId.get(key));
}
@Override
public Collection<Suggestion> cachedSuggestions() {
return Collections.unmodifiableCollection(this.cachedSuggestions);
}
private void initSuggestions() {
this.cachedSuggestions.clear();
this.namespacesInUse.clear();
Set<String> states = new HashSet<>();
for (CustomBlock block : this.byId.values()) {
states.add(block.id().toString());
this.namespacesInUse.add(block.id().namespace());
for (ImmutableBlockState state : block.variantProvider().states()) {
states.add(state.toString());
}
}
for (String state : states) {
this.cachedSuggestions.add(Suggestion.suggestion(state));
}
}
public Set<String> namespacesInUse() {
return Collections.unmodifiableSet(this.namespacesInUse);
}
public ImmutableMap<Key, List<Integer>> blockAppearanceArranger() {
return blockAppearanceArranger;
}
@@ -392,11 +345,11 @@ public class BukkitBlockManager extends AbstractBlockManager {
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
// check duplicated config
if (byId.containsKey(id)) {
TranslationManager.instance().log("warning.config.block.duplicated", path.toString(), id.toString());
return;
}
// read block settings
BlockSettings settings = BlockSettings.fromMap(MiscUtils.castToMap(section.getOrDefault("settings", Map.of()), false));
// read loot table
@@ -410,38 +363,46 @@ public class BukkitBlockManager extends AbstractBlockManager {
properties = Map.of();
int internalId = MiscUtils.getAsInt(stateSection.getOrDefault("id", -1));
if (internalId < 0) {
TranslationManager.instance().log("warning.config.block.lack_real_id", path.toString(), id.toString());
TranslationManager.instance().log("warning.config.block.state.lack_real_id", path.toString(), id.toString());
return;
}
Pair<Key, Integer> pair = parseAppearanceSection(path, id, stateSection);
if (pair == null) return;
appearances = Map.of("default", pair.right());
Key internalBlockId = Key.of(CraftEngine.NAMESPACE, pair.left().value() + "_" + internalId);
int internalBlockRegistryId = MiscUtils.getAsInt(internalId2StateId.getOrDefault(internalBlockId, -1));
if (internalBlockRegistryId == -1) {
plugin.logger().warn(path, "Failed to register " + id + " because id " + internalId + " is not a value between 0~" + (MiscUtils.getAsInt(registeredRealBlockSlots.get(pair.left()))-1) +
". Consider editing additional-real-blocks.yml if the number of real block IDs is insufficient while there are still available appearances");
TranslationManager.instance().log("warning.config.block.state.invalid_real_state_id",
path.toString(),
id.toString(),
pair.left().value() + "_" + internalId,
String.valueOf(MiscUtils.getAsInt(registeredRealBlockSlots.get(pair.left()))-1)
);
return;
}
variants = Map.of("", new VariantState("default", settings, internalBlockRegistryId));
} else {
// states
Map<String, Object> statesSection = MiscUtils.castToMap(section.get("states"), true);
if (statesSection == null) {
TranslationManager.instance().log("warning.config.block.lack_state", path.toString(), id.toString());
return;
}
// properties
Map<String, Object> propertySection = MiscUtils.castToMap(statesSection.get("properties"), true);
if (propertySection == null) {
TranslationManager.instance().log("warning.config.block.state.lack_properties", path.toString(), id.toString());
return;
}
properties = parseProperties(path, propertySection);
properties = parseProperties(path, id, propertySection);
// appearance
Map<String, Object> appearancesSection = MiscUtils.castToMap(statesSection.get("appearances"), true);
if (appearancesSection == null) {
TranslationManager.instance().log("warning.config.block.state.lack_appearances", path.toString(), id.toString());
return;
}
appearances = new HashMap<>();
Map<String, Key> tempTypeMap = new HashMap<>();
for (Map.Entry<String, Object> appearanceEntry : appearancesSection.entrySet()) {
@@ -452,13 +413,12 @@ public class BukkitBlockManager extends AbstractBlockManager {
tempTypeMap.put(appearanceEntry.getKey(), pair.left());
}
}
// variants
Map<String, Object> variantsSection = MiscUtils.castToMap(statesSection.get("variants"), true);
if (variantsSection == null) {
TranslationManager.instance().log("warning.config.block.state.lack_variants", path.toString(), id.toString());
return;
}
variants = new HashMap<>();
for (Map.Entry<String, Object> variantEntry : variantsSection.entrySet()) {
if (variantEntry.getValue() instanceof Map<?, ?> variantSection0) {
@@ -478,8 +438,12 @@ public class BukkitBlockManager extends AbstractBlockManager {
Key internalBlockId = Key.of(CraftEngine.NAMESPACE, baseBlock.value() + "_" + internalId);
int internalBlockRegistryId = MiscUtils.getAsInt(internalId2StateId.getOrDefault(internalBlockId, -1));
if (internalBlockRegistryId == -1) {
plugin.logger().warn(path, "Failed to register " + id + " because id " + internalId + " is not a value between 0~" + (MiscUtils.getAsInt(registeredRealBlockSlots.getOrDefault(baseBlock, 1))-1) +
". Consider editing additional-real-blocks.yml if the number of real block IDs is insufficient while there are still available appearances");
TranslationManager.instance().log("warning.config.block.state.invalid_real_state_id",
path.toString(),
id.toString(),
internalBlockId.toString(),
String.valueOf(MiscUtils.getAsInt(registeredRealBlockSlots.getOrDefault(baseBlock, 1)) - 1)
);
return;
}
Map<String, Object> anotherSetting = MiscUtils.castToMap(variantSection.get("settings"), true);
@@ -487,18 +451,27 @@ public class BukkitBlockManager extends AbstractBlockManager {
}
}
}
// create or get block holder
Holder.Reference<CustomBlock> holder = BuiltInRegistries.BLOCK.get(id).orElseGet(() ->
((WritableRegistry<CustomBlock>) BuiltInRegistries.BLOCK).registerForHolder(new ResourceKey<>(BuiltInRegistries.BLOCK.key().location(), id)));
// create block
Map<String, Object> behaviorSection = MiscUtils.castToMap(section.getOrDefault("behavior", Map.of()), false);
BukkitCustomBlock block = new BukkitCustomBlock(id, holder, properties, appearances, variants, settings, behaviorSection, lootTable);
// bind appearance
bindAppearance(block);
byId.put(id, block);
// bind appearance and real state
for (ImmutableBlockState state : block.variantProvider().states()) {
ImmutableBlockState previous = stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()];
if (previous != null && !previous.isEmpty()) {
TranslationManager.instance().log("warning.config.block.bind_real_state", path.toString(), id.toString(), state.toString(), previous.toString());
continue;
}
stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state;
tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId());
appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new ArrayList<>()).add(state.customBlockState().registryId());
}
byId.put(id, block);
// generate mod assets
if (Config.generateModAssets()) {
for (ImmutableBlockState state : block.variantProvider().states()) {
@@ -509,27 +482,18 @@ public class BukkitBlockManager extends AbstractBlockManager {
}
}
private void bindAppearance(CustomBlock block) {
for (ImmutableBlockState state : block.variantProvider().states()) {
ImmutableBlockState previous = this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()];
if (previous != null && !previous.isEmpty()) {
this.plugin.logger().severe("[Fatal] Failed to bind real block state for " + state + ": the state is already occupied by " + previous);
continue;
}
this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state;
this.tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId());
this.appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new ArrayList<>()).add(state.customBlockState().registryId());
}
}
private Map<String, Property<?>> parseProperties(Path path, Map<String, Object> propertiesSection) {
private Map<String, Property<?>> parseProperties(Path path, Key id, Map<String, Object> propertiesSection) {
Map<String, Property<?>> properties = new HashMap<>();
for (Map.Entry<String, Object> entry : propertiesSection.entrySet()) {
if (entry.getValue() instanceof Map<?, ?> params) {
Property<?> property = Properties.fromMap(entry.getKey(), MiscUtils.castToMap(params, false));
properties.put(entry.getKey(), property);
try {
Property<?> property = Properties.fromMap(entry.getKey(), MiscUtils.castToMap(params, false));
properties.put(entry.getKey(), property);
} catch (Exception e) {
TranslationManager.instance().log("warning.config.block.state.invalid_property", path.toString(), id.toString(), entry.getKey(), e.getMessage());
}
} else {
this.plugin.logger().warn(path, "Invalid property format: " + entry.getKey());
TranslationManager.instance().log("warning.config.block.state.invalid_property_structure", path.toString(), id.toString(), entry.getKey());
}
}
return properties;
@@ -537,25 +501,41 @@ public class BukkitBlockManager extends AbstractBlockManager {
@Nullable
private Pair<Key, Integer> parseAppearanceSection(Path path, Key id, Map<String, Object> section) {
// require state non null
String vanillaStateString = (String) section.get("state");
if (vanillaStateString == null) {
TranslationManager.instance().log("warning.config.block.state.lack_state", path.toString(), id.toString());
return null;
}
// get its registry id
int vanillaStateRegistryId;
try {
vanillaStateRegistryId = parseVanillaStateRegistryId(vanillaStateString);
} catch (BlockStateArrangeException e) {
this.plugin.logger().warn(path, "Failed to load " + id + " - " + e.getMessage(), e);
VanillaStateParseResult parseResult = parseVanillaStateRegistryId(vanillaStateString);
if (parseResult.success()) {
vanillaStateRegistryId = parseResult.result;
} else {
String[] args = new String[parseResult.args.length + 2];
args[0] = path.toString();
args[1] = id.toString();
System.arraycopy(parseResult.args, 0, args, 2, parseResult.args.length);
TranslationManager.instance().log(parseResult.reason(), args);
return null;
}
// check conflict
Key ifAny = this.tempRegistryIdConflictMap.get(vanillaStateRegistryId);
if (ifAny != null && !ifAny.equals(id)) {
this.plugin.logger().warn(path, "Can't use " + BlockStateUtils.idToBlockState(vanillaStateRegistryId) + " as the base block for " + id + " because the state has already been used by " + ifAny);
TranslationManager.instance().log("warning.config.block.state.conflict", path.toString(), id.toString(), BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(vanillaStateRegistryId)).getAsString(), ifAny.toString());
return null;
}
// require models not to be null
Object models = section.getOrDefault("models", section.get("model"));
if (models == null) {
TranslationManager.instance().log("warning.config.block.state.no_model_set", path.toString(), id.toString());
return null;
}
List<JsonObject> variants = new ArrayList<>();
if (models instanceof Map<?, ?> singleModelSection) {
loadVariantModel(variants, MiscUtils.castToMap(singleModelSection, false));
@@ -565,11 +545,9 @@ public class BukkitBlockManager extends AbstractBlockManager {
loadVariantModel(variants, MiscUtils.castToMap(singleModelMap, false));
}
}
} else {
this.plugin.logger().warn(path, "No model set for " + id);
return null;
}
if (variants.isEmpty()) return null;
this.tempRegistryIdConflictMap.put(vanillaStateRegistryId, id);
String blockState = BlockStateUtils.idToBlockState(vanillaStateRegistryId).toString();
Key block = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}')));
@@ -604,46 +582,50 @@ public class BukkitBlockManager extends AbstractBlockManager {
variants.add(json);
}
private int parseVanillaStateRegistryId(String blockState) throws BlockStateArrangeException {
private VanillaStateParseResult parseVanillaStateRegistryId(String blockState) {
String[] split = blockState.split(":", 3);
PreConditions.runIfTrue(split.length >= 4, () -> {
throw new BlockStateArrangeException("Invalid vanilla block state: " + blockState);
});
if (split.length >= 4) {
return VanillaStateParseResult.failure("warning.config.block.state.invalid_state", new String[]{blockState});
}
int registryId;
// minecraft:xxx:0
// xxx:0
String stateOrId = split[split.length - 1];
boolean isId = !stateOrId.contains("[") && !stateOrId.contains("]");
if (isId) {
if (split.length == 1) {
throw new BlockStateArrangeException("Invalid vanilla block state: " + blockState);
}
if (split.length == 1) return VanillaStateParseResult.failure("warning.config.block.state.invalid_state", new String[]{blockState});
Key block = split.length == 2 ? Key.of(split[0]) : Key.of(split[0], split[1]);
try {
int id = split.length == 2 ? Integer.parseInt(split[1]) : Integer.parseInt(split[2]);
PreConditions.runIfTrue(id < 0, () -> {
throw new BlockStateArrangeException("Invalid block state: " + blockState);
});
if (id < 0) return VanillaStateParseResult.failure("warning.config.block.state.invalid_state", new String[]{blockState});
List<Integer> arranger = this.blockAppearanceArranger.get(block);
if (arranger == null) {
throw new BlockStateArrangeException("No freed block state is available for block " + block);
}
if (id >= arranger.size()) {
throw new BlockStateArrangeException(blockState + " is not a valid block state because " + id + " is outside of the range (0~" + (arranger.size() - 1) + ")");
}
if (arranger == null) return VanillaStateParseResult.failure("warning.config.block.state.unavailable_state", new String[]{blockState});
if (id >= arranger.size()) return VanillaStateParseResult.failure("warning.config.block.state.invalid_vanilla_state_id", new String[]{blockState, String.valueOf(arranger.size() - 1)});
registryId = arranger.get(id);
} catch (NumberFormatException e) {
throw new BlockStateArrangeException("Invalid block state: " + blockState);
return VanillaStateParseResult.failure("warning.config.block.state.invalid_state", new String[]{blockState});
}
} else {
try {
BlockData blockData = Bukkit.createBlockData(blockState);
registryId = BlockStateUtils.blockDataToId(blockData);
if (!this.blockAppearanceMapper.containsKey(registryId)) {
return VanillaStateParseResult.failure("warning.config.block.state.unavailable_state", new String[]{blockState});
}
} catch (IllegalArgumentException e) {
throw new BlockStateArrangeException("Invalid block state: " + blockState);
return VanillaStateParseResult.failure("warning.config.block.state.invalid_state", new String[]{blockState});
}
}
return registryId;
return VanillaStateParseResult.success(registryId);
}
public record VanillaStateParseResult(boolean success, int result, String reason, String[] args) {
public static VanillaStateParseResult success(int result) {
return new VanillaStateParseResult(true, result, null, null);
}
public static VanillaStateParseResult failure(String reason, String[] args) {
return new VanillaStateParseResult(false, -1, reason, args);
}
}
private void loadMappingsAndAdditionalBlocks() {

View File

@@ -48,7 +48,7 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior {
if (this.defaultBlockState != null) {
return this.defaultBlockState;
}
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().getBlock(this.targetBlock);
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().blockById(this.targetBlock);
if (optionalCustomBlock.isPresent()) {
CustomBlock customBlock = optionalCustomBlock.get();
this.defaultBlockState = customBlock.defaultState().customBlockState().handle();

View File

@@ -60,7 +60,7 @@ public class AxeItemBehavior extends ItemBehavior {
return InteractionResult.PASS;
}
Optional<CustomBlock> optionalNewCustomBlock = BukkitBlockManager.instance().getBlock(blockBehavior.stripped());
Optional<CustomBlock> optionalNewCustomBlock = BukkitBlockManager.instance().blockById(blockBehavior.stripped());
if (optionalNewCustomBlock.isEmpty()) {
CraftEngine.instance().logger().warn("stripped block " + blockBehavior.stripped() + " does not exist");
return InteractionResult.FAIL;

View File

@@ -61,7 +61,7 @@ public class BlockItemBehavior extends ItemBehavior {
if (!context.canPlace()) {
return InteractionResult.FAIL;
}
Optional<CustomBlock> optionalBlock = BukkitBlockManager.instance().getBlock(this.blockId);
Optional<CustomBlock> optionalBlock = BukkitBlockManager.instance().blockById(this.blockId);
if (optionalBlock.isEmpty()) {
CraftEngine.instance().logger().warn("Failed to place unknown block " + this.blockId);
return InteractionResult.FAIL;

View File

@@ -39,7 +39,7 @@ public class BlockStateUtils {
} else {
String blockTypeString = blockState.substring(0, index);
Key block = Key.of(blockTypeString);
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().getBlock(block);
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().blockById(block);
if (optionalCustomBlock.isPresent()) {
ImmutableBlockState state = BlockStateParser.deserialize(blockState);
if (state == null) {
@@ -55,7 +55,7 @@ public class BlockStateUtils {
}
public static List<Object> getAllBlockStates(Key block) {
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().getBlock(block);
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().blockById(block);
return optionalCustomBlock.map(customBlock -> customBlock.variantProvider().states().stream().map(it -> it.customBlockState().handle()).toList())
.orElseGet(() -> getAllVanillaBlockStates(block));
}
@@ -80,7 +80,7 @@ public class BlockStateUtils {
}
public static BlockData fromBlockData(Object blockState) {
return (BlockData) FastNMS.INSTANCE.method$CraftBlockData$fromData(blockState);
return FastNMS.INSTANCE.method$CraftBlockData$fromData(blockState);
}
public static int blockDataToId(BlockData blockData) {