Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Fabric-1.21.11/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ loader_version=0.18.4
loom_version=1.15-SNAPSHOT

# Mod Properties
mod_version=1.4.2-mc1.21.11
mod_version=1.4.3-mc1.21.11
maven_group=com.box3lab
archives_base_name=box3

Expand Down
163 changes: 163 additions & 0 deletions Fabric-1.21.11/src/main/java/com/box3lab/command/ModCommands.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.box3lab.command;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.box3lab.block.BarrierVoxelBlock;
import com.box3lab.register.VoxelExport;
import com.box3lab.register.VoxelImport;
import com.box3lab.util.Box3ImportFiles;
import com.mojang.brigadier.arguments.BoolArgumentType;
Expand All @@ -16,14 +18,21 @@
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.block.Block;

public final class ModCommands {
private ModCommands() {
}

private static final String DEFAULT_EXPORT_MARKER_BLOCK = "minecraft:redstone_block";
private static final int MAX_MARKER_SCAN_RADIUS = 1024;
private static final int MARKER_Y_TOLERANCE = 512;

private static final SuggestionProvider<CommandSourceStack> BOX3_FILE_SUGGESTIONS = (context, builder) -> {
try {
List<String> files = Box3ImportFiles.listJsonFiles();
Expand Down Expand Up @@ -125,6 +134,16 @@ public static void register() {
.then(literal("toggle")
.executes(context -> toggleBarrierVisible(
context.getSource())))));

dispatcher.register(
literal("box3export")
.executes(context -> showBox3ExportUsage(context.getSource()))
.then(argument("fileName", StringArgumentType.word())
.executes(context -> executeBox3ExportByMarkers(
context.getSource(),
StringArgumentType.getString(
context,
"fileName")))));
});
}

Expand Down Expand Up @@ -158,6 +177,11 @@ private static int listBox3ImportFiles(CommandSourceStack source) {
return 1;
}

private static int showBox3ExportUsage(CommandSourceStack source) {
source.sendFailure(Component.translatable("command.box3.box3export.usage"));
return 0;
}

private static String resolveMapName(String fileName) {
if (fileName != null && fileName.startsWith("Box3-")) {
String suffix = fileName.substring("Box3-".length());
Expand Down Expand Up @@ -221,4 +245,143 @@ private static int toggleBarrierVisible(CommandSourceStack source) {
false);
return 1;
}

private static int executeBox3Export(CommandSourceStack source, String fileName,
int x1, int y1, int z1, int x2, int y2, int z2) {
ServerLevel level = source.getLevel();
BlockPos from = new BlockPos(x1, y1, z1);
BlockPos to = new BlockPos(x2, y2, z2);

try {
VoxelExport.ExportResult result = VoxelExport.exportRegion(level, from, to, fileName);
source.sendSuccess(
() -> Component.translatable(
"command.box3.box3export.success",
result.output().toString(),
result.scannedBlocks(),
result.exportedBlocks()),
false);
} catch (Exception e) {
source.sendFailure(
Component.translatable("command.box3.box3export.failure", e.getMessage()));
}
return 1;
}

private static int executeBox3ExportByMarkers(CommandSourceStack source, String fileName) {
ServerPlayer player = source.getPlayer();
if (player == null) {
source.sendFailure(Component.translatable("command.box3.box3export.player_only"));
return 0;
}

Block markerBlock = resolveMarkerBlock(DEFAULT_EXPORT_MARKER_BLOCK);
if (markerBlock == null) {
source.sendFailure(Component.translatable("command.box3.box3export.marker_invalid",
DEFAULT_EXPORT_MARKER_BLOCK));
return 0;
}

List<BlockPos> positions = findMarkerPositions(source.getLevel(), player.blockPosition(), markerBlock,
MAX_MARKER_SCAN_RADIUS, MARKER_Y_TOLERANCE, 2);
if (positions.size() < 2) {
source.sendFailure(Component.translatable(
"command.box3.box3export.marker_count_invalid",
MAX_MARKER_SCAN_RADIUS,
positions.size(),
BuiltInRegistries.BLOCK.getKey(markerBlock).toString()));
return 0;
}

BlockPos p1 = positions.get(0);
BlockPos p2 = positions.get(1);
return executeBox3Export(source, fileName, p1.getX(), p1.getY(), p1.getZ(), p2.getX(), p2.getY(),
p2.getZ());
}

private static Block resolveMarkerBlock(String blockId) {
Identifier id = Identifier.tryParse(blockId);
if (id == null) {
return null;
}
if (!BuiltInRegistries.BLOCK.containsKey(id)) {
return null;
}
return BuiltInRegistries.BLOCK.get(id).map(holder -> holder.value()).orElse(null);
}

private static List<BlockPos> findMarkerPositions(ServerLevel level, BlockPos center, Block markerBlock,
int maxRadius, int yTolerance, int maxResults) {
List<BlockPos> positions = new ArrayList<>();
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
int cx = center.getX();
int cy = center.getY();
int cz = center.getZ();

for (int radius = 0; radius <= maxRadius; radius++) {
int minX = cx - radius;
int maxX = cx + radius;
int minZ = cz - radius;
int maxZ = cz + radius;

if (radius == 0) {
if (scanMarkerColumn(level, markerBlock, cy, yTolerance, cx, cz, cursor, positions,
maxResults)) {
return positions;
}
continue;
}

for (int x = minX; x <= maxX; x++) {
if (scanMarkerColumn(level, markerBlock, cy, yTolerance, x, minZ, cursor, positions,
maxResults)) {
return positions;
}
if (scanMarkerColumn(level, markerBlock, cy, yTolerance, x, maxZ, cursor, positions,
maxResults)) {
return positions;
}
}

for (int z = minZ + 1; z <= maxZ - 1; z++) {
if (scanMarkerColumn(level, markerBlock, cy, yTolerance, minX, z, cursor, positions,
maxResults)) {
return positions;
}
if (scanMarkerColumn(level, markerBlock, cy, yTolerance, maxX, z, cursor, positions,
maxResults)) {
return positions;
}
}
}
return positions;
}

private static boolean scanMarkerColumn(ServerLevel level, Block markerBlock, int centerY, int yTolerance, int x, int z,
BlockPos.MutableBlockPos cursor, List<BlockPos> positions, int maxResults) {
for (int dy = 0; dy <= yTolerance; dy++) {
int y1 = centerY + dy;
cursor.set(x, y1, z);
if (level.hasChunkAt(cursor) && level.getBlockState(cursor).getBlock() == markerBlock) {
positions.add(cursor.immutable());
if (positions.size() >= maxResults) {
return true;
}
}

if (dy == 0) {
continue;
}

int y2 = centerY - dy;
cursor.set(x, y2, z);
if (level.hasChunkAt(cursor) && level.getBlockState(cursor).getBlock() == markerBlock) {
positions.add(cursor.immutable());
if (positions.size() >= maxResults) {
return true;
}
}
}
return false;
}
}
148 changes: 148 additions & 0 deletions Fabric-1.21.11/src/main/java/com/box3lab/register/VoxelExport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package com.box3lab.register;

import static com.box3lab.Box3.MOD_ID;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPOutputStream;

import com.box3lab.util.BlockIdResolver;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;

public final class VoxelExport {

private VoxelExport() {
}

public static ExportResult exportRegion(ServerLevel level, BlockPos from, BlockPos to, String fileName) throws IOException {
BlockPos min = new BlockPos(
Math.min(from.getX(), to.getX()),
Math.min(from.getY(), to.getY()),
Math.min(from.getZ(), to.getZ()));
BlockPos max = new BlockPos(
Math.max(from.getX(), to.getX()),
Math.max(from.getY(), to.getY()),
Math.max(from.getZ(), to.getZ()));

int sizeX = max.getX() - min.getX() + 1;
int sizeY = max.getY() - min.getY() + 1;
int sizeZ = max.getZ() - min.getZ() + 1;

List<Integer> indices = new ArrayList<>();
List<Integer> data = new ArrayList<>();
List<Integer> rot = new ArrayList<>();

for (int z = 0; z < sizeZ; z++) {
for (int y = 0; y < sizeY; y++) {
for (int x = 0; x < sizeX; x++) {
BlockPos pos = min.offset(x, y, z);
BlockState state = level.getBlockState(pos);
int id = BlockIdResolver.getIdByBlock(state.getBlock());
if (id == 0) {
continue;
}

int idx = x + y * sizeX + z * sizeX * sizeY;
indices.add(idx);
data.add(id);
rot.add(toRotationIndex(state));
}
}
}

JsonObject root = new JsonObject();
root.add("shape", intArray(sizeX, sizeY, sizeZ));
root.add("dir", intArray(1, 1, 1));
root.add("indices", toJsonArray(indices));
root.add("data", toJsonArray(data));
root.add("rot", toJsonArray(rot));

Path output = resolveOutput(fileName);
Files.createDirectories(output.getParent());
writeGzipJson(output, root.toString());

return new ExportResult(output, sizeX * sizeY * sizeZ, indices.size());
}

private static int toRotationIndex(BlockState state) {
Direction dir = null;
if (state.hasProperty(BlockStateProperties.HORIZONTAL_FACING)) {
dir = state.getValue(BlockStateProperties.HORIZONTAL_FACING);
} else if (state.hasProperty(BlockStateProperties.FACING)) {
Direction facing = state.getValue(BlockStateProperties.FACING);
if (facing.getAxis().isHorizontal()) {
dir = facing;
}
}

if (dir == null) {
return 0;
}

Rotation rotation = switch (dir) {
case EAST -> Rotation.CLOCKWISE_90;
case SOUTH -> Rotation.CLOCKWISE_180;
case WEST -> Rotation.COUNTERCLOCKWISE_90;
default -> Rotation.NONE;
};

return switch (rotation) {
case CLOCKWISE_90 -> 1;
case CLOCKWISE_180 -> 2;
case COUNTERCLOCKWISE_90 -> 3;
default -> 0;
};
}

private static JsonArray intArray(int a, int b, int c) {
JsonArray array = new JsonArray();
array.add(a);
array.add(b);
array.add(c);
return array;
}

private static JsonArray toJsonArray(List<Integer> values) {
JsonArray array = new JsonArray();
for (Integer value : values) {
array.add(value);
}
return array;
}

private static Path resolveOutput(String fileName) {
String cleaned = (fileName == null || fileName.isBlank()) ? "export" : fileName.trim();
if (!cleaned.endsWith(".gz")) {
cleaned = cleaned + ".gz";
}
return FabricLoader.getInstance()
.getConfigDir()
.resolve(MOD_ID)
.resolve(cleaned);
}

private static void writeGzipJson(Path outputPath, String json) throws IOException {
try (OutputStream fos = Files.newOutputStream(outputPath);
GZIPOutputStream gos = new GZIPOutputStream(fos)) {
gos.write(json.getBytes(StandardCharsets.UTF_8));
gos.finish();
}
}

public record ExportResult(Path output, int scannedBlocks, int exportedBlocks) {
}
}
Loading