diff --git a/src/main/java/com/github/zly2006/reden/access/TranslationStorageAccess.kt b/src/main/java/com/github/zly2006/reden/access/TranslationStorageAccess.kt new file mode 100644 index 00000000..0b48df1c --- /dev/null +++ b/src/main/java/com/github/zly2006/reden/access/TranslationStorageAccess.kt @@ -0,0 +1,10 @@ +package com.github.zly2006.reden.access + +import net.minecraft.text.Text + +interface TranslationStorageAccess { + + @Suppress("INAPPLICABLE_JVM_NAME") + @get:JvmName("getTextMap\$reden") + val textMap: Map +} diff --git a/src/main/java/com/github/zly2006/reden/exceptions/RedenException.kt b/src/main/java/com/github/zly2006/reden/exceptions/RedenException.kt index 4555fd2f..2f2ebc67 100644 --- a/src/main/java/com/github/zly2006/reden/exceptions/RedenException.kt +++ b/src/main/java/com/github/zly2006/reden/exceptions/RedenException.kt @@ -23,4 +23,8 @@ class RedenException : Exception { constructor(message: Text, cause: Throwable) : super(message.string, cause) { this.displayMessage = message } + + override fun toString(): String { + return displayMessage.string + } } diff --git a/src/main/java/com/github/zly2006/reden/malilib/GuiConfigs.kt b/src/main/java/com/github/zly2006/reden/malilib/GuiConfigs.kt index 6b05de52..b60503bf 100644 --- a/src/main/java/com/github/zly2006/reden/malilib/GuiConfigs.kt +++ b/src/main/java/com/github/zly2006/reden/malilib/GuiConfigs.kt @@ -32,7 +32,13 @@ class GuiConfigs(parent: Screen? = null): GuiConfigsBase( button.width + x + 2 } val creditsButton = ButtonGeneric(finalX, 26, -1, 20, "Credits") - val sponsorsButton = ButtonGeneric(finalX, 26, -1, 20, StringUtils.translate("reden.widget.config.sponsor")) + val sponsorsButton = ButtonGeneric( + finalX + creditsButton.width + 2, + 26, + -1, + 20, + StringUtils.translate("reden.widget.config.sponsor") + ) addButton(creditsButton) { _, _ -> onFunctionUsed("credits_malilibConfigScreen") client!!.setScreen(CreditScreen(this)) diff --git a/src/main/java/com/github/zly2006/reden/mixin/debugger/MixinServer.java b/src/main/java/com/github/zly2006/reden/mixin/debugger/MixinServer.java index b94fe2de..4b4f3361 100644 --- a/src/main/java/com/github/zly2006/reden/mixin/debugger/MixinServer.java +++ b/src/main/java/com/github/zly2006/reden/mixin/debugger/MixinServer.java @@ -3,25 +3,8 @@ import com.github.zly2006.reden.Reden; import com.github.zly2006.reden.access.ServerData; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.ServerNetworkIo; -import net.minecraft.server.function.CommandFunctionManager; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -import java.util.List; @Mixin(value = MinecraftServer.class, priority = Reden.REDEN_HIGHEST_MIXIN_PRIORITY) public abstract class MixinServer implements ServerData.ServerDataAccess { - @Shadow - @Final - private CommandFunctionManager commandFunctionManager; - - @Shadow - @Final - private List serverGuiTickables; - - @Shadow - @Final - private ServerNetworkIo networkIo; } diff --git a/src/main/java/com/github/zly2006/reden/mixin/debugger/MixinWorldTickScheduler.java b/src/main/java/com/github/zly2006/reden/mixin/debugger/MixinWorldTickScheduler.java deleted file mode 100644 index 943e40fc..00000000 --- a/src/main/java/com/github/zly2006/reden/mixin/debugger/MixinWorldTickScheduler.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.zly2006.reden.mixin.debugger; - -import com.github.zly2006.reden.Reden; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import net.minecraft.world.tick.ChunkTickScheduler; -import net.minecraft.world.tick.WorldTickScheduler; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -@Mixin(value = WorldTickScheduler.class, priority = Reden.REDEN_HIGHEST_MIXIN_PRIORITY) -public abstract class MixinWorldTickScheduler { - @Shadow - @Final - private Long2ObjectMap> chunkTickSchedulers; -} diff --git a/src/main/java/com/github/zly2006/reden/mixin/render/MixinWorldRenderer.java b/src/main/java/com/github/zly2006/reden/mixin/render/MixinWorldRenderer.java index 415867cd..451cd825 100644 --- a/src/main/java/com/github/zly2006/reden/mixin/render/MixinWorldRenderer.java +++ b/src/main/java/com/github/zly2006/reden/mixin/render/MixinWorldRenderer.java @@ -63,6 +63,7 @@ private boolean forceOutline(boolean x) { return x || !BlockOutline.INSTANCE.getBlocks().isEmpty(); } + @SuppressWarnings("deprecation") @Inject( method = "render", at = @At( diff --git a/src/main/java/com/github/zly2006/reden/mixin/richTranslation/MixinTranslatable.java b/src/main/java/com/github/zly2006/reden/mixin/richTranslation/MixinTranslatable.java new file mode 100644 index 00000000..16174dd5 --- /dev/null +++ b/src/main/java/com/github/zly2006/reden/mixin/richTranslation/MixinTranslatable.java @@ -0,0 +1,49 @@ +package com.github.zly2006.reden.mixin.richTranslation; + +import com.github.zly2006.reden.utils.richTranslation.RichTranslationKt; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.text.StringVisitable; +import net.minecraft.text.TranslatableTextContent; +import net.minecraft.util.Language; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Collections; +import java.util.List; + +// overwrite owo +@Mixin(value = TranslatableTextContent.class, priority = 10) +public class MixinTranslatable { + @Shadow + @Final + private String key; + + @Shadow + @Final + private Object[] args; + + @Shadow + private List translations; + + @Inject( + method = "updateTranslations", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/text/TranslatableTextContent;languageCache:Lnet/minecraft/util/Language;", + ordinal = 1, + shift = At.Shift.AFTER + ), + cancellable = true + ) + private void translate(CallbackInfo ci, @Local Language language) { + var text = RichTranslationKt.processTranslate(language, key, args); + if (text != null) { + translations = Collections.singletonList(text); + ci.cancel(); + } + } +} diff --git a/src/main/java/com/github/zly2006/reden/mixin/richTranslation/MixinTranslationStorage.java b/src/main/java/com/github/zly2006/reden/mixin/richTranslation/MixinTranslationStorage.java new file mode 100644 index 00000000..13a2eaa9 --- /dev/null +++ b/src/main/java/com/github/zly2006/reden/mixin/richTranslation/MixinTranslationStorage.java @@ -0,0 +1,68 @@ +package com.github.zly2006.reden.mixin.richTranslation; + +import com.github.zly2006.reden.access.TranslationStorageAccess; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.client.resource.language.TranslationStorage; +import net.minecraft.resource.ResourceManager; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Mixin(TranslationStorage.class) +public class MixinTranslationStorage implements com.github.zly2006.reden.access.TranslationStorageAccess { + @Unique + private final Map textMap = new HashMap<>(); + @Unique + private static final Map tempTextMap = new HashMap<>(); + + @Override + public @NotNull Map getTextMap$reden() { + return textMap; + } + + @Inject( + method = "load(Lnet/minecraft/resource/ResourceManager;Ljava/util/List;Z)Lnet/minecraft/client/resource/language/TranslationStorage;", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/resource/language/TranslationStorage;load(Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)V" + ) + ) + private static void loadCustomText(ResourceManager resourceManager, List definitions, boolean rightToLeft, CallbackInfoReturnable cir, @Local Identifier identifier) { + Gson gson = new Gson(); + resourceManager.getAllResources(identifier).forEach(resource -> { + try { + var jo = gson.fromJson(new InputStreamReader(resource.getInputStream()), JsonObject.class); + jo.entrySet().stream().filter(it -> it.getValue() instanceof JsonArray).forEach(it -> { + MutableText text = Text.Serialization.fromJsonTree(it.getValue()); + tempTextMap.put(it.getKey(), text); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + @Inject( + method = "load(Lnet/minecraft/resource/ResourceManager;Ljava/util/List;Z)Lnet/minecraft/client/resource/language/TranslationStorage;", + at = @At("TAIL") + ) + private static void finish(ResourceManager resourceManager, List definitions, boolean rightToLeft, CallbackInfoReturnable cir) { + ((TranslationStorageAccess) cir.getReturnValue()).getTextMap$reden().putAll(tempTextMap); + tempTextMap.clear(); + } +} diff --git a/src/main/java/com/github/zly2006/reden/rvc/CuboidStructure.kt b/src/main/java/com/github/zly2006/reden/rvc/CuboidStructure.kt index c8115a02..d13ab07a 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/CuboidStructure.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/CuboidStructure.kt @@ -1,7 +1,6 @@ package com.github.zly2006.reden.rvc -import net.minecraft.util.math.BlockPos -import net.minecraft.world.World +import com.github.zly2006.reden.rvc.tracking.PlacementInfo class CuboidStructure( name: String, @@ -34,8 +33,7 @@ class CuboidStructure( && pos.y in 0 until ySize && pos.z in 0 until zSize } - - override fun createPlacement(world: World, origin: BlockPos): IPlacement { + override fun createPlacement(placementInfo: PlacementInfo): IPlacement { TODO() } } diff --git a/src/main/java/com/github/zly2006/reden/rvc/IStructure.kt b/src/main/java/com/github/zly2006/reden/rvc/IStructure.kt index 329d3354..1cea983b 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/IStructure.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/IStructure.kt @@ -1,6 +1,8 @@ package com.github.zly2006.reden.rvc import com.github.zly2006.reden.rvc.io.StructureIO +import com.github.zly2006.reden.rvc.tracking.PlacementInfo +import com.github.zly2006.reden.rvc.tracking.WorldInfo import net.minecraft.block.BlockState import net.minecraft.nbt.NbtCompound import net.minecraft.util.math.BlockPos @@ -30,7 +32,10 @@ interface IStructure { */ fun load(path: Path) fun isInArea(pos: RelativeCoordinate): Boolean - fun createPlacement(world: World, origin: BlockPos): IPlacement + fun createPlacement(world: World, origin: BlockPos) = + createPlacement(PlacementInfo(WorldInfo.of(world), origin)) + + fun createPlacement(placementInfo: PlacementInfo): IPlacement fun getBlockState(pos: RelativeCoordinate): BlockState fun getBlockEntityData(pos: RelativeCoordinate): NbtCompound? fun getOrCreateBlockEntityData(pos: RelativeCoordinate): NbtCompound diff --git a/src/main/java/com/github/zly2006/reden/rvc/IWritableStructure.kt b/src/main/java/com/github/zly2006/reden/rvc/IWritableStructure.kt index 51c012f3..309d5af5 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/IWritableStructure.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/IWritableStructure.kt @@ -7,6 +7,12 @@ import java.util.* interface IWritableStructure: IStructure { fun setBlockState(pos: RelativeCoordinate, state: BlockState) fun setBlockEntityData(pos: RelativeCoordinate, nbt: NbtCompound) + override fun getOrCreateBlockEntityData(pos: RelativeCoordinate): NbtCompound { + getBlockEntityData(pos)?.let { return it } + val nbt = NbtCompound() + setBlockEntityData(pos, nbt) + return nbt + } override val entities: MutableMap @@ -35,4 +41,4 @@ interface IWritableStructure: IStructure { /** * grammar sugar to [IWritableStructure.assign] */ -operator fun IWritableStructure.remAssign(another: IStructure) = assign(another) \ No newline at end of file +operator fun IWritableStructure.remAssign(another: IStructure) = assign(another) diff --git a/src/main/java/com/github/zly2006/reden/rvc/ReadWriteStructure.kt b/src/main/java/com/github/zly2006/reden/rvc/ReadWriteStructure.kt index 56ccbb3f..12e04db4 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/ReadWriteStructure.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/ReadWriteStructure.kt @@ -13,9 +13,9 @@ abstract class ReadWriteStructure(override var name: String) : IWritableStructur override var xSize: Int = 0; protected set override var ySize: Int = 0; protected set override var zSize: Int = 0; protected set - protected var minX: Int = Int.MAX_VALUE - protected var minY: Int = Int.MAX_VALUE - protected var minZ: Int = Int.MAX_VALUE + protected open var minX: Int = Int.MAX_VALUE + protected open var minY: Int = Int.MAX_VALUE + protected open var minZ: Int = Int.MAX_VALUE private fun checkSize(pos: RelativeCoordinate) { if (pos.x < minX) minX = pos.x if (pos.y < minY) minY = pos.y diff --git a/src/main/java/com/github/zly2006/reden/rvc/RelativeCoordinate.kt b/src/main/java/com/github/zly2006/reden/rvc/RelativeCoordinate.kt index 61326e77..4a27295f 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/RelativeCoordinate.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/RelativeCoordinate.kt @@ -1,5 +1,9 @@ package com.github.zly2006.reden.rvc +import kotlinx.serialization.Serializable +import net.minecraft.util.math.BlockPos + +@Serializable data class RelativeCoordinate( override val x: Int, override val y: Int, @@ -8,4 +12,12 @@ data class RelativeCoordinate( override fun getForOrigin(origin: Coordinate): Coordinate { return AbsoluteCoordinate(x + origin.x, y + origin.y, z + origin.z) } + + companion object { + fun origin(origin: BlockPos) = RelativeBuilder(origin) + + class RelativeBuilder(private val origin: BlockPos) { + fun block(pos: BlockPos) = RelativeCoordinate(pos.x - origin.x, pos.y - origin.y, pos.z - origin.z) + } + } } diff --git a/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionExportScreen.kt b/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionExportScreen.kt index d1982d3c..464716b2 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionExportScreen.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionExportScreen.kt @@ -9,18 +9,15 @@ import com.github.zly2006.reden.rvc.tracking.TrackedStructure import com.github.zly2006.reden.utils.red import io.wispforest.owo.ui.base.BaseOwoScreen import io.wispforest.owo.ui.component.ButtonComponent -import io.wispforest.owo.ui.component.CheckboxComponent import io.wispforest.owo.ui.component.Components -import io.wispforest.owo.ui.component.TextureComponent import io.wispforest.owo.ui.container.Containers import io.wispforest.owo.ui.container.FlowLayout import io.wispforest.owo.ui.container.ScrollContainer import io.wispforest.owo.ui.core.* import net.minecraft.client.gui.screen.Screen +import net.minecraft.structure.StructureTemplate import net.minecraft.text.Text import net.minecraft.util.Identifier -import net.minecraft.util.Util -import org.lwjgl.glfw.GLFW import java.awt.Color import java.io.File import java.nio.file.Path @@ -86,30 +83,6 @@ class SelectionExportScreen( lateinit var selectedButton: ButtonComponent var selectedLine: FileLine? = null - private val multiBoxCheckBox: CheckboxComponent = - Components.checkbox(ExportType.LitematicaMultiBox.displayName).apply { - checked(false) - onChanged { - selectedType = if (it) { -// statusLabel.text(Text.literal("Export to ${ExportType.LitematicaMultiBox.displayName.string} type")) - ExportType.LitematicaMultiBox - } else { -// statusLabel.text(Text.literal("Export to ${ExportType.Litematica.displayName.string} type")) - ExportType.Litematica - } - onNameFieldChanged() - } - } - val multiBoxHelp: TextureComponent = - Components.texture(Reden.identifier("help_icon.png"), 0, 0, 16, 16, 16, 16).apply { - mouseDown().subscribe { _: Double, _: Double, b: Int -> - if (b == GLFW.GLFW_MOUSE_BUTTON_LEFT) { - Util.getOperatingSystem().open(WIKI) - true - } else false - } - cursorStyle(CursorStyle.HAND) - } private fun refreshOptions() { optionsPanel = (Containers.verticalFlow(Sizing.fill(50), Sizing.fill()).apply { @@ -123,13 +96,6 @@ class SelectionExportScreen( }) child(exportButton) child(statusLabel) - if (selectedType == ExportType.LitematicaMultiBox || selectedType == ExportType.Litematica) { - child(Containers.horizontalFlow(Sizing.content(), Sizing.content()).apply { - gap(5) - child(multiBoxCheckBox) - child(multiBoxHelp) - }) - } }) } @@ -148,9 +114,7 @@ class SelectionExportScreen( gap(5) alignment(HorizontalAlignment.LEFT, VerticalAlignment.CENTER) child(Components.label(Text.literal("Export RVC Structure to:"))) - ExportType.entries - // Note: it is the same format as [Litematica] mode - .filterNot { it == ExportType.LitematicaMultiBox }.forEach { type: ExportType -> + ExportType.entries.forEach { type: ExportType -> child(Components.button(type.displayName) { it.active(false) selectedButton.active(true) @@ -239,6 +203,7 @@ class SelectionExportScreen( StructureBlock(Text.literal("Structure Block"), Text.empty(), SelectionImportScreen.EXTENSION_NBT) { override fun export(path: Path, head: TrackedStructure) { val identifier = Identifier(head.name) + StructureTemplate() TODO() } }, @@ -249,16 +214,7 @@ class SelectionExportScreen( }, Litematica(ModNames.litematicaName, Text.empty(), SelectionImportScreen.EXTENSION_LITEMATICA) { override fun export(path: Path, head: TrackedStructure) { - LitematicaIO.save(path, head, false) - } - }, - LitematicaMultiBox( - Text.literal("Litematica Multi-Box"), - Text.empty(), - SelectionImportScreen.EXTENSION_LITEMATICA - ) { - override fun export(path: Path, head: TrackedStructure) { - LitematicaIO.save(path, head, true) + LitematicaIO.save(path, head) } }, RVCArchive(Text.literal("RVC Archive"), Text.empty(), SelectionImportScreen.EXTENSION_RVC_ARCHIVE) { diff --git a/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionImportScreen.kt b/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionImportScreen.kt index 208b3bec..a7968035 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionImportScreen.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionImportScreen.kt @@ -4,11 +4,9 @@ import com.github.zly2006.reden.ModNames import com.github.zly2006.reden.Reden import com.github.zly2006.reden.access.ClientData.Companion.data import com.github.zly2006.reden.report.onFunctionUsed -import com.github.zly2006.reden.rvc.blockPos import com.github.zly2006.reden.rvc.io.LitematicaIO import com.github.zly2006.reden.rvc.io.SchematicStructure import com.github.zly2006.reden.rvc.tracking.RvcRepository -import com.github.zly2006.reden.rvc.tracking.TrackPredicate import com.github.zly2006.reden.rvc.tracking.TrackedStructure import com.github.zly2006.reden.utils.red import com.github.zly2006.reden.utils.server @@ -179,7 +177,7 @@ class SelectionImportScreen( val repository = RvcRepository.create(file.nameWithoutExtension, null, NetworkSide.CLIENTBOUND) val structure = TrackedStructure(file.nameWithoutExtension, repository) LitematicaIO.load(file.toPath(), structure) - val blocksBefore = structure.blocks.size + val blocksBefore = structure.totalBlocks repository.startPlacing(structure) { afterPlaced(structure, file, blocksBefore) } @@ -253,24 +251,14 @@ class SelectionImportScreen( protected fun afterPlaced(structure: TrackedStructure, file: File, blocksBefore: Int) { val mc = MinecraftClient.getInstance() - val center = structure.blockBox().center - val centerBlock = - structure.blocks.keys.minBy { it.blockPos(structure.origin).getSquaredDistance(center) } - structure.trackPoints.add( - TrackedStructure.TrackPoint( - centerBlock, - TrackPredicate.QC, - TrackPredicate.TrackMode.TRACK, - ) - ) - @Suppress("DeferredResultUnused") - structure.networkWorker?.async { + structure.networkWorker?.launch { + structure.autoTrack() structure.collectAllFromWorld() - if (structure.blocks.size != blocksBefore) { + if (structure.totalBlocks != blocksBefore) { mc.player?.sendMessage( Text.literal( "Failed to automatically set trackpoints, please fix it and commit " + - "(expected=$blocksBefore, got=${structure.blocks.size})" + "(expected=$blocksBefore, got=${structure.totalBlocks})" ).red() ) } @@ -297,7 +285,7 @@ class SelectionImportScreen( val structure = TrackedStructure(file.nameWithoutExtension, repository) val nbt = NbtIo.readCompressed(file.toPath(), NbtSizeTracker.ofUnlimitedBytes()) structure.assign(SchematicStructure().readFromNBT(nbt)) - val blocksBefore = structure.blocks.size + val blocksBefore = structure.totalBlocks repository.startPlacing(structure) { afterPlaced(structure, file, blocksBefore) } diff --git a/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionInfoScreen.kt b/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionInfoScreen.kt index dcbffb1f..157916ff 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionInfoScreen.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionInfoScreen.kt @@ -2,10 +2,10 @@ package com.github.zly2006.reden.rvc.gui import com.github.zly2006.reden.Reden import com.github.zly2006.reden.access.ClientData.Companion.data +import com.github.zly2006.reden.exceptions.RedenException import com.github.zly2006.reden.report.onFunctionUsed import com.github.zly2006.reden.rvc.gui.git.RvcCommitScreen import com.github.zly2006.reden.rvc.gui.git.RvcManageRemotesScreen -import com.github.zly2006.reden.rvc.remote.IRemoteRepository import com.github.zly2006.reden.rvc.tracking.RvcRepository import com.github.zly2006.reden.rvc.tracking.TrackedStructure import com.github.zly2006.reden.utils.red @@ -54,15 +54,8 @@ class SelectionInfoScreen( }!! private val pushButton = Components.button(Text.literal("Push")) { onFunctionUsed("push_rvcStructure") - val remote = object : IRemoteRepository { - override fun deleteRepo() { - TODO("Not yet implemented") - } - - override val gitUrl = repository.git.repository.config.getString("remote", "origin", "url") - } try { - repository.push(remote, ChatScreen.hasShiftDown()) + repository.push(repository.remote, ChatScreen.hasShiftDown()) } catch (e: Exception) { Reden.LOGGER.error("Failed to push ${repository.name}", e) UIErrorToast.report(e) @@ -74,6 +67,31 @@ class SelectionInfoScreen( onFunctionUsed("pull_rvcStructure") TODO() }!! + private val createLicenseMenu = Components.dropdown(Sizing.content()).apply { + listOf( + "All rights reserved", + "CC 0", + "CC 4.0 BY", + "CC 4.0 BY SA", + "CC 4.0 BY NC", + "CC 4.0 BY NC SA", + "CC 4.0 BY NC ND", + ).map { it to it.replace(" ", "-").lowercase() }.forEach { pair -> + button(Text.literal(pair.first)) { + try { + repository.createLicense( + "assets/rvc/licenses/${pair.second}.txt", + client!!.player!!.nameForScoreboard + ) + } catch (e: RedenException) { + UIErrorToast.report(e) + } + } + } + } + private val createLicenseButton = Components.button(Text.literal("Create License")) { + + } override fun close() { client!!.setScreen(SelectionListScreen()) @@ -149,4 +167,8 @@ class SelectionInfoScreen( ) .child(Containers.verticalScroll(Sizing.fill(), Sizing.fill(80), commits)) } + + fun refresh() { + + } } diff --git a/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionListScreen.kt b/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionListScreen.kt index f8dc4a8c..0ce7e176 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionListScreen.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionListScreen.kt @@ -60,7 +60,7 @@ class SelectionListScreen : BaseOwoScreen() { placementInfo = PlacementInfo(client!!.getWorldInfo()) } value.repository.head().run { - networkWorker?.async { + networkWorker?.launch { refreshPositions() } } @@ -69,9 +69,11 @@ class SelectionListScreen : BaseOwoScreen() { fun childTr(key: String, vararg args: Any) = child(Components.label(Text.translatable(key, *args))) selectedStructure?.run { childTr("reden.widget.rvc.structure.name", name) - childTr("reden.widget.rvc.structure.block_count", blocks.count()) + childTr("reden.widget.rvc.structure.block_count", totalBlocks) childTr("reden.widget.rvc.structure.entity_count", entities.count()) - if (fluidScheduledTicks.isNotEmpty() || blockScheduledTicks.isNotEmpty() || blockEvents.isNotEmpty()) { + if (regions.values.any { it.fluidScheduledTicks.isNotEmpty() } + || regions.values.any { it.blockScheduledTicks.isNotEmpty() } + || regions.values.any { it.blockEvents.isNotEmpty() }) { childTr("reden.widget.rvc.structure.scheduled_tick_unstable") } } diff --git a/src/main/java/com/github/zly2006/reden/rvc/gui/git/RvcCommitScreen.kt b/src/main/java/com/github/zly2006/reden/rvc/gui/git/RvcCommitScreen.kt index aecdec47..137f690d 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/gui/git/RvcCommitScreen.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/gui/git/RvcCommitScreen.kt @@ -1,7 +1,6 @@ package com.github.zly2006.reden.rvc.gui.git import com.github.zly2006.reden.rvc.gui.SelectionInfoScreen -import com.github.zly2006.reden.rvc.remote.IRemoteRepository import com.github.zly2006.reden.rvc.tracking.RvcRepository import com.github.zly2006.reden.rvc.tracking.TrackedStructure import io.wispforest.owo.ui.base.BaseOwoScreen @@ -26,7 +25,7 @@ class RvcCommitScreen(val repository: RvcRepository, val structure: TrackedStruc UIErrorToast.report("Commit message cannot be empty") return@button } - structure.networkWorker?.async { + structure.networkWorker?.launch { repository.commit(structure, message, client!!.player) client!!.execute { client!!.setScreen(SelectionInfoScreen(repository, structure)) @@ -39,16 +38,9 @@ class RvcCommitScreen(val repository: RvcRepository, val structure: TrackedStruc UIErrorToast.report("Commit message cannot be empty") return@button } - structure.networkWorker?.async { + structure.networkWorker?.launch { repository.commit(structure, message, client!!.player) - val remote = object : IRemoteRepository { - override fun deleteRepo() { - TODO("Not yet implemented") - } - - override val gitUrl = repository.git.repository.config.getString("remote", "origin", "url") - } - repository.push(remote, false) + repository.push(repository.remote, false) client!!.execute { client!!.setScreen(SelectionInfoScreen(repository, structure)) } diff --git a/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/RvcMoveStructureLitematicaTask.kt b/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/RvcMoveStructureLitematicaTask.kt index fe5d847b..015b101c 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/RvcMoveStructureLitematicaTask.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/RvcMoveStructureLitematicaTask.kt @@ -3,6 +3,7 @@ package com.github.zly2006.reden.rvc.gui.hud.gameplay import com.github.zly2006.reden.Reden import com.github.zly2006.reden.rvc.IPlacement import com.github.zly2006.reden.rvc.IStructure +import com.github.zly2006.reden.rvc.tracking.WorldInfo import com.github.zly2006.reden.task.Task import com.github.zly2006.reden.task.taskStack import com.github.zly2006.reden.utils.litematicaInstalled @@ -15,11 +16,10 @@ import net.minecraft.text.Text import net.minecraft.util.math.BlockBox import net.minecraft.util.math.BlockPos import net.minecraft.util.math.ChunkPos -import net.minecraft.world.World import net.minecraft.world.chunk.WorldChunk class RvcMoveStructureLitematicaTask( - world: World, placingStructure: IStructure, successCallback: (Task) -> Unit = {} + world: WorldInfo, placingStructure: IStructure, successCallback: (Task) -> Unit = {} ) : RvcMoveStructureTask(world, placingStructure, "move_structure_litematica", successCallback) { companion object { @JvmStatic diff --git a/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/RvcMoveStructureTask.kt b/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/RvcMoveStructureTask.kt index d266d280..f22fea64 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/RvcMoveStructureTask.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/RvcMoveStructureTask.kt @@ -4,7 +4,9 @@ import com.github.zly2006.reden.access.PlayerData import com.github.zly2006.reden.malilib.RVC_CONFIRM_KEY import com.github.zly2006.reden.rvc.IStructure import com.github.zly2006.reden.rvc.gui.RvcHudRenderer +import com.github.zly2006.reden.rvc.tracking.PlacementInfo import com.github.zly2006.reden.rvc.tracking.TrackedStructure +import com.github.zly2006.reden.rvc.tracking.WorldInfo import com.github.zly2006.reden.task.Task import com.github.zly2006.reden.utils.server import kotlinx.coroutines.GlobalScope @@ -13,10 +15,9 @@ import kotlinx.coroutines.launch import net.minecraft.client.MinecraftClient import net.minecraft.text.Text import net.minecraft.util.math.BlockPos -import net.minecraft.world.World open class RvcMoveStructureTask( - private val world: World, + private val world: WorldInfo, val placingStructure: IStructure, id: String = "move_structure", val successCallback: (Task) -> Unit = {} @@ -55,12 +56,14 @@ open class RvcMoveStructureTask( override fun onConfirm(): Boolean { val pos = currentOrigin ?: return false currentOrigin = null - placingStructure.createPlacement(world, pos).apply { + placingStructure.createPlacement(PlacementInfo(world, pos)).apply { if (placingStructure is TrackedStructure) { // todo multiple player GlobalScope.launch(server.asCoroutineDispatcher()) { placingStructure.networkWorker!!.startUndoRecord(PlayerData.UndoRecord.Cause.RVC_MOVE) - placingStructure.networkWorker!!.paste() + placingStructure.regions.values.forEach { + placingStructure.networkWorker!!.paste(it) + } placingStructure.networkWorker!!.stopUndoRecord() placingStructure.networkWorker!!.execute { placingStructure.setPlaced() diff --git a/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/SelectModeHud.kt b/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/SelectModeHud.kt index f5bbf0b6..e01638ef 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/SelectModeHud.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/gui/hud/gameplay/SelectModeHud.kt @@ -1,18 +1,13 @@ package com.github.zly2006.reden.rvc.gui.hud.gameplay -import com.github.zly2006.reden.Reden import com.github.zly2006.reden.rvc.gui.RvcHudRenderer import com.github.zly2006.reden.rvc.gui.selectedStructure +import com.github.zly2006.reden.rvc.tracking.StructureTracker import com.github.zly2006.reden.rvc.tracking.TrackPredicate import com.github.zly2006.reden.utils.holdingToolItem import com.github.zly2006.reden.utils.red import net.minecraft.client.MinecraftClient import net.minecraft.text.Text -import net.minecraft.util.Formatting -import net.minecraft.util.Identifier - - -val hudId = Identifier(Reden.MOD_ID, "select_mode_hud") fun registerHud() { RvcHudRenderer.supplierMap["select_mode_hud"] = { @@ -24,15 +19,12 @@ fun registerHud() { } else { list.add(Text.translatable("reden.widget.rvc.hud.selected", selectedStructure!!.name)) - val trackCount = selectedStructure!!.trackPoints.count { it.mode == TrackPredicate.TrackMode.TRACK } - val ignoreCount = selectedStructure!!.trackPoints.count { it.mode == TrackPredicate.TrackMode.IGNORE } - list.add( - Text.translatable("reden.widget.rvc.hud.trackpoints") - .append(" ") - .append(Text.translatable("reden.widget.rvc.hud.trackpoints.tracking", trackCount).formatted(Formatting.GREEN)) - .append(" ") - .append(Text.translatable("reden.widget.rvc.hud.trackpoints.ignoring", ignoreCount).formatted(Formatting.RED)) - ) + val allTrackpoint = selectedStructure!!.regions.values.map { it.tracker } + .filterIsInstance() + .flatMap { it.trackpoints } + val trackCount = allTrackpoint.count { it.mode == TrackPredicate.TrackMode.TRACK } + val ignoreCount = allTrackpoint.count { it.mode == TrackPredicate.TrackMode.IGNORE } + list.add(Text.translatable("reden.widget.rvc.hud.trackpoints", trackCount, ignoreCount)) if (selectedStructure!!.placementInfo == null) { list.add(Text.literal("[Warning] Not placed").red()) } diff --git a/src/main/java/com/github/zly2006/reden/rvc/io/LitematicaIO.kt b/src/main/java/com/github/zly2006/reden/rvc/io/LitematicaIO.kt index 7e402100..8c5f1d49 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/io/LitematicaIO.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/io/LitematicaIO.kt @@ -1,7 +1,7 @@ package com.github.zly2006.reden.rvc.io import com.github.zly2006.reden.rvc.* -import com.github.zly2006.reden.rvc.tracking.TrackedStructure +import com.github.zly2006.reden.rvc.tracking.* import fi.dy.masa.litematica.schematic.LitematicaSchematic import fi.dy.masa.litematica.selection.AreaSelection import fi.dy.masa.litematica.selection.Box @@ -13,105 +13,49 @@ import kotlin.io.path.createDirectories import kotlin.io.path.exists import kotlin.io.path.name -open class LitematicaIO: StructureIO { +open class LitematicaIO : StructureIO { override fun save(path: Path, structure: IStructure) { - save(path, structure, false) - } - - fun save(path: Path, structure: IStructure, multiBox: Boolean) { val litematica: LitematicaSchematic if (!path.exists()) { path.createDirectories() } - if (structure is TrackedStructure && multiBox) { - val boxes = structure.splitCuboids() - litematica = LitematicaSchematic.createEmptySchematic( - AreaSelection().apply { - boxes.mapIndexed { index, blockBox -> - addSubRegionBox( - Box( - blockBox.minPos, - blockBox.maxPos, - index.toString() - ), - false - ) + val boxName = "RVC Structure" + litematica = LitematicaSchematic.createEmptySchematic( + AreaSelection().apply { + addSubRegionBox( + Box( + BlockPos.ORIGIN, + BlockPos(structure.xSize, structure.ySize, structure.zSize), + boxName + ), + false + ) + }, + "RVC" + ) + val subRegionContainer = litematica.getSubRegionContainer(boxName)!! + val blockEntityMap = litematica.getBlockEntityMapForRegion(boxName)!! + val entityInfos = litematica.getEntityListForRegion(boxName)!! + for (x in 0 until structure.xSize) { + for (y in 0 until structure.ySize) { + for (z in 0 until structure.zSize) { + val pos = RelativeCoordinate(x, y, z) + val state = structure.getBlockState(pos) + subRegionContainer.set(x, y, z, state) + val be = structure.getBlockEntityData(pos) + if (be != null) { + blockEntityMap[pos.blockPos(BlockPos.ORIGIN)] = be } - }, - "RVC" - ) ?: return - boxes.mapIndexed { index, box -> - val subRegionContainer = litematica.getSubRegionContainer(index.toString())!! - val blockEntityMap = litematica.getBlockEntityMapForRegion(index.toString())!! - val blockTicks = litematica.getScheduledBlockTicksForRegion(index.toString())!! - val fluidTicks = litematica.getScheduledFluidTicksForRegion(index.toString())!! - structure.blockScheduledTicks.addAll(blockTicks.map { TrackedStructure.TickInfo.wrap(it.value, structure.world) }) - structure.fluidScheduledTicks.addAll(fluidTicks.map { TrackedStructure.TickInfo.wrap(it.value, structure.world) }) - val entityInfos = litematica.getEntityListForRegion(index.toString())!! - structure.blocks.filter { it.key.blockPos(BlockPos.ORIGIN) in box }.forEach { - subRegionContainer.set( - it.key.x - box.minX, - it.key.y - box.minY, - it.key.z - box.minZ, - it.value - ) - } - structure.blockEntities.keys.filter { it.blockPos(BlockPos.ORIGIN) in box }.forEach { pos -> - blockEntityMap[pos.blockPos(BlockPos.ORIGIN)] = structure.blockEntities[pos] } - structure.entities.forEach { - entityInfos.add( - LitematicaSchematic.EntityInfo( - Vec3d.ZERO, - it.value - ) - ) - } - subRegionContainer } } - else { - val structure = if (structure is TrackedStructure) { - structure.asCuboid() - } else structure - val boxName = "RVC Structure" - litematica = LitematicaSchematic.createEmptySchematic( - AreaSelection().apply { - addSubRegionBox( - Box( - BlockPos.ORIGIN, - BlockPos(structure.xSize, structure.ySize, structure.zSize), - boxName - ), - false - ) - }, - "RVC" - ) - val subRegionContainer = litematica.getSubRegionContainer(boxName)!! - val blockEntityMap = litematica.getBlockEntityMapForRegion(boxName)!! - val entityInfos = litematica.getEntityListForRegion(boxName)!! - for (x in 0 until structure.xSize) { - for (y in 0 until structure.ySize) { - for (z in 0 until structure.zSize) { - val pos = RelativeCoordinate(x, y, z) - val state = structure.getBlockState(pos) - subRegionContainer.set(x, y, z, state) - val be = structure.getBlockEntityData(pos) - if (be != null) { - blockEntityMap[pos.blockPos(BlockPos.ORIGIN)] = be - } - } - } - } - structure.entities.forEach { - entityInfos.add( - LitematicaSchematic.EntityInfo( - Vec3d.ZERO, - it.value - ) + structure.entities.forEach { + entityInfos.add( + LitematicaSchematic.EntityInfo( + Vec3d.ZERO, + it.value ) - } + ) } litematica.metadata.description = "Reden Exported to Litematica" litematica.metadata.name = structure.name @@ -137,6 +81,19 @@ open class LitematicaIO: StructureIO { val blockEntityMap = schematic.getBlockEntityMapForRegion(regionName)!! val entityInfos = schematic.getEntityListForRegion(regionName)!! val basePos = schematic.getSubRegionPosition(regionName)!! + if (structure is TrackedStructure) { + structure.placementInfo = PlacementInfo(WorldInfo()) + structure.regions[regionName] = TrackedStructurePart( + regionName, + structure, + StructureTracker.Cuboid( + basePos, + basePos.toImmutable().add(subRegionContainer.size) + ) + ).apply { + createPlacement(structure.placementInfo!!.copy(origin = basePos)) + } + } for (x in 0 until subRegionContainer.size.x) { for (y in 0 until subRegionContainer.size.y) { for (z in 0 until subRegionContainer.size.z) { @@ -164,7 +121,7 @@ open class LitematicaIO: StructureIO { } } - companion object Default: LitematicaIO() { + companion object Default : LitematicaIO() { } } diff --git a/src/main/java/com/github/zly2006/reden/rvc/io/SchematicIO.kt b/src/main/java/com/github/zly2006/reden/rvc/io/SchematicIO.kt index 571ecd71..bd5a4e88 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/io/SchematicIO.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/io/SchematicIO.kt @@ -1,6 +1,7 @@ package com.github.zly2006.reden.rvc.io import com.github.zly2006.reden.rvc.* +import com.github.zly2006.reden.rvc.tracking.PlacementInfo import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtIo @@ -10,7 +11,6 @@ import net.minecraft.structure.StructureTemplate import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Vec3d import net.minecraft.util.math.Vec3i -import net.minecraft.world.World import java.nio.file.Path import kotlin.io.path.extension @@ -41,8 +41,8 @@ class SchematicImpl( && pos.z in 0 until zSize } - override fun createPlacement(world: World, origin: BlockPos): IPlacement { - return DefaultPlacement(this, world, origin) + override fun createPlacement(placementInfo: PlacementInfo): IPlacement { + return DefaultPlacement(this, placementInfo.worldInfo.getWorld()!!, placementInfo.origin) } } diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/IRvcFileReader.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/IRvcFileReader.kt index b20ccdc5..3deb43d3 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/IRvcFileReader.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/IRvcFileReader.kt @@ -11,7 +11,7 @@ import java.util.* /** * Interface that defines the data reading methods for - * reading RVC data and transform to [TrackedStructure] structures. + * reading RVC data and transform to [TrackedStructurePart] structures. */ interface IRvcFileReader { val header: RvcHeader @@ -19,48 +19,32 @@ interface IRvcFileReader { /** * Read blocks data from RVC files to maps for structure * @param data Lines contains data read form RVC files - * @return [Map]<[BlockPos], [BlockState]> data that can be saved to a [TrackedStructure] + * @return [Map]<[BlockPos], [BlockState]> data that can be saved to a [TrackedStructurePart] */ fun readBlocksData(data: List, palette: Palette): Map /** * Read blocks entities data from RVC files to maps for structure * @param data Lines contains data read form RVC files - * @return [Map]<[BlockPos], [NbtCompound]> data that can be saved to a [TrackedStructure] + * @return [Map]<[BlockPos], [NbtCompound]> data that can be saved to a [TrackedStructurePart] */ fun readBlockEntitiesData(data: List, palette: Palette): Map /** * Read entities data from RVC files to maps for structure * @param data Lines contains data read form RVC files - * @return [Map]<[UUID], [NbtCompound]> data that can be saved to a [TrackedStructure] + * @return [Map]<[UUID], [NbtCompound]> data that can be saved to a [TrackedStructurePart] */ fun readEntitiesData(data: List): Map - /** - * Read track point data from RVC files to list for structure - * @param data Lines contains data read form RVC files - * @return [List]<[TrackedStructure.TrackPoint]> data that can be saved to a [TrackedStructure] - */ - fun readTrackPointData(data: List): List - /** * Read block events data from RVC files to list for structure * @param data Lines contains data read form RVC files - * @return [List]<[BlockEvent]> data that can be saved to a [TrackedStructure] - */ - fun readBlockEventsData(data: List): List - - /** - * Read scheduled ticks data from RVC files to list for structure. - * (This is used for both block scheduled ticks and fluid scheduled ticks.) - * @param data Lines contains data read form RVC files - * @return [List]<[NbtCompound]> data that can be saved to a [TrackedStructure] + * @return [List]<[BlockEvent]> data that can be saved to a [TrackedStructurePart] */ - @Deprecated("Scheduled ticks data is not supported yet.") - fun readScheduledTicksData(data: List): List + fun readBlockEventsData(data: List): List - fun readScheduledTicksData(data: List, registry: Registry): List> + fun readScheduledTicksData(data: List, registry: Registry): List> /** * RVC File with a [IRvcFileReader] (for the corresponding data version) diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcFileIO.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcFileIO.kt index d3c231b6..a01ed6ff 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcFileIO.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcFileIO.kt @@ -6,11 +6,15 @@ import com.github.zly2006.reden.rvc.io.Palette import com.github.zly2006.reden.rvc.io.StructureIO import com.github.zly2006.reden.rvc.tracking.reader.RvcReaderV1 import com.github.zly2006.reden.utils.Utils +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import net.minecraft.block.BlockState import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.visitor.StringNbtWriter import net.minecraft.registry.Registries +import java.io.FileFilter import java.nio.file.Path +import kotlin.io.path.createDirectories import kotlin.io.path.notExists /** @@ -91,85 +95,89 @@ object RvcFileIO : StructureIO { if (path.notExists()) { path.toFile().mkdirs() } - val usePalette = palette ?: (structure.blocks.size > 1000) - - @Suppress("NAME_SHADOWING") - val palette = Palette() - - // ======================================== Save Blocks ======================================== - // public final val blocks: MutableMap - // com.github.zly2006.reden.rvc.ReadWriteStructure - structure.blocks.entries.joinToString("\n") { (pos, state) -> - if (usePalette) - "${pos.x},${pos.y},${pos.z},${palette.getId(toNbtString(state))}" - else - "${pos.x},${pos.y},${pos.z},${toNbtString(state)}" - }.let { data -> - writeRvcFile( - path, "blocks", IRvcFileReader.RvcHeader( - mutableMapOf( - "Version" to CURRENT_VERSION, - "Platform" to "MCMod/Reden", - "Palette" to usePalette.toString() - ) - ), data + structure.regions.values.forEach { part -> + val path = path.resolve(part.name) + path.createDirectories() + val usePalette = palette ?: (part.blocks.size > 4096) + + @Suppress("NAME_SHADOWING") + val palette = Palette() + + writeRvcFile(path, "index", RVC_HEADER, Json.encodeToString(part.tracker)) + + // ======================================== Save Blocks ======================================== + // public final val blocks: MutableMap + // com.github.zly2006.reden.rvc.ReadWriteStructure + part.blocks.entries.joinToString("\n") { (pos, state) -> + if (usePalette) + "${pos.x},${pos.y},${pos.z},${palette.getId(toNbtString(state))}" + else + "${pos.x},${pos.y},${pos.z},${toNbtString(state)}" + }.let { data -> + writeRvcFile( + path, "blocks", IRvcFileReader.RvcHeader( + mutableMapOf( + "Version" to CURRENT_VERSION, + "Platform" to "MCMod/Reden", + "Palette" to usePalette.toString() + ) + ), data + ) + } + + // ==================================== Save Block Entities ==================================== + // public final val blockEntities: MutableMap + // com.github.zly2006.reden.rvc.ReadWriteStructure + part.blockEntities.entries.joinToString("\n") { (pos, nbt) -> + if (usePalette) + "${pos.x},${pos.y},${pos.z},${palette.getId(toNbtString(nbt))}" + else + "${pos.x},${pos.y},${pos.z},${toNbtString(nbt)}" + }.let { data -> + writeRvcFile( + path, "blockEntities", IRvcFileReader.RvcHeader( + mutableMapOf( + "Version" to CURRENT_VERSION, + "Platform" to "MCMod/Reden", + "Palette" to usePalette.toString() + ) + ), data + ) + } + + // ======================================= Save Entities ======================================= + // public open val entities: MutableMap + // com.github.zly2006.reden.rvc.ReadWriteStructure + part.entities.entries.joinToString("\n") { (uuid, nbt) -> + "$uuid,${toNbtString(nbt)}" + }.let { data -> writeRvcFile(path, "entities", RVC_HEADER, data) } + + // ===================================== Save Block Events ===================================== + // public final val blockEvents: MutableList + // com.github.zly2006.reden.rvc.tracking.TrackedStructure + part.blockEvents.joinToString("\n") { blockEvent -> + blockEvent.toRvcDataString() + }.let { data -> writeRvcFile(path, "blockEvents", RVC_HEADER, data) } + + // ================================ Save Block Scheduled Ticks ================================= + part.blockScheduledTicks.joinToString( + "\n", + transform = TrackedStructurePart.TickInfo<*>::toRvcDataString ) - } + .let { data -> writeRvcFile(path, "blockScheduledTicks", RVC_HEADER, data) } - // ==================================== Save Block Entities ==================================== - // public final val blockEntities: MutableMap - // com.github.zly2006.reden.rvc.ReadWriteStructure - structure.blockEntities.entries.joinToString("\n") { (pos, nbt) -> - if (usePalette) - "${pos.x},${pos.y},${pos.z},${palette.getId(toNbtString(nbt))}" - else - "${pos.x},${pos.y},${pos.z},${toNbtString(nbt)}" - }.let { data -> - writeRvcFile( - path, "blockEntities", IRvcFileReader.RvcHeader( - mutableMapOf( - "Version" to CURRENT_VERSION, - "Platform" to "MCMod/Reden", - "Palette" to usePalette.toString() - ) - ), data + // ================================ Save Fluid Scheduled Ticks ================================= + part.fluidScheduledTicks.joinToString( + "\n", + transform = TrackedStructurePart.TickInfo<*>::toRvcDataString ) - } - - // ======================================= Save Entities ======================================= - // public open val entities: MutableMap - // com.github.zly2006.reden.rvc.ReadWriteStructure - structure.entities.entries.joinToString("\n") { (uuid, nbt) -> - "$uuid,${toNbtString(nbt)}" - }.let { data -> writeRvcFile(path, "entities", RVC_HEADER, data) } - - // ===================================== Save Track Points ===================================== - // public final val trackPoints: MutableList - // com.github.zly2006.reden.rvc.tracking.TrackedStructure - structure.trackPoints.joinToString("\n") { trackPoint -> - val coordinate = structure.getRelativeCoordinate(trackPoint.pos) - "${coordinate.x},${coordinate.y},${coordinate.z},${trackPoint.predicate},${trackPoint.mode}" - }.let { data -> writeRvcFile(path, "trackPoints", RVC_HEADER, data) } - - // ===================================== Save Block Events ===================================== - // public final val blockEvents: MutableList - // com.github.zly2006.reden.rvc.tracking.TrackedStructure - structure.blockEvents.joinToString("\n") { blockEvent -> - blockEvent.toRvcDataString() - }.let { data -> writeRvcFile(path, "blockEvents", RVC_HEADER, data) } + .let { data -> writeRvcFile(path, "fluidScheduledTicks", RVC_HEADER, data) } - // ================================ Save Block Scheduled Ticks ================================= - structure.blockScheduledTicks.joinToString("\n", transform = TrackedStructure.TickInfo<*>::toRvcDataString) - .let { data -> writeRvcFile(path, "blockScheduledTicks", RVC_HEADER, data) } - - // ================================ Save Fluid Scheduled Ticks ================================= - structure.fluidScheduledTicks.joinToString("\n", transform = TrackedStructure.TickInfo<*>::toRvcDataString) - .let { data -> writeRvcFile(path, "fluidScheduledTicks", RVC_HEADER, data) } - - if (usePalette) { - palette.idToName.entries.joinToString("\n") { (id, name) -> - "$id,$name" - }.let { data -> writeRvcFile(path, "palette", RVC_HEADER, data) } + if (usePalette) { + palette.idToName.entries.joinToString("\n") { (id, name) -> + "$id,$name" + }.let { data -> writeRvcFile(path, "palette", RVC_HEADER, data) } + } } } @@ -197,67 +205,40 @@ object RvcFileIO : StructureIO { * please let me know or make a pull request. */ override fun load(path: Path, structure: IWritableStructure) { - // =============================== Check Loading Structure Type ================================ - if (structure !is TrackedStructure) { - throw IllegalArgumentException("Structure is not a TrackedStructure") - } - structure.dirty = true // mark it as dirty caz we have no cache of positions - - // ======================================== Load Blocks ======================================== - // public final val blocks: MutableMap - // com.github.zly2006.reden.rvc.ReadWriteStructure - structure.blocks.clear() - val palette = Palette.load(loadRvcFile(path, "palette")) - loadRvcFile(path, "blocks")?.let { rvcFile -> - structure.blocks.putAll(rvcFile.reader.readBlocksData(rvcFile.data, palette)) - } - - // ==================================== Load Block Entities ==================================== - // public final val blockEntities: MutableMap - // com.github.zly2006.reden.rvc.ReadWriteStructure - structure.blockEntities.clear() - loadRvcFile(path, "blockEntities")?.let { rvcFile -> - structure.blockEntities.putAll(rvcFile.reader.readBlockEntitiesData(rvcFile.data, palette)) - } - - // ======================================= Load Entities ======================================= - // public open val entities: MutableMap - // com.github.zly2006.reden.rvc.ReadWriteStructure - structure.entities.clear() - loadRvcFile(path, "entities")?.let { rvcFile -> - structure.entities.putAll(rvcFile.reader.readEntitiesData(rvcFile.data)) - } - - // ===================================== Load Track Points ===================================== - // public final val trackPoints: MutableList - // com.github.zly2006.reden.rvc.tracking.TrackedStructure - structure.trackPoints.clear() - loadRvcFile(path, "trackPoints")?.let { rvcFile -> - structure.trackPoints.addAll(rvcFile.reader.readTrackPointData(rvcFile.data)) - } - - // ===================================== Load Block Events ===================================== - // public final val blockEvents: MutableList - // com.github.zly2006.reden.rvc.tracking.TrackedStructure - structure.blockEvents.clear() - loadRvcFile(path, "blockEvents")?.let { rvcFile -> - structure.blockEvents.addAll(rvcFile.reader.readBlockEventsData(rvcFile.data)) - } - - // ================================ Load Block Scheduled Ticks ================================= - // public final val blockScheduledTicks: MutableList - // com.github.zly2006.reden.rvc.tracking.TrackedStructure - structure.blockScheduledTicks.clear() - loadRvcFile(path, "blockScheduledTicks")?.let { rvcFile -> - structure.blockScheduledTicks.addAll(rvcFile.reader.readScheduledTicksData(rvcFile.data, Registries.BLOCK)) - } - - // ================================ Load Fluid Scheduled Ticks ================================= - // public final val fluidScheduledTicks: MutableList - // com.github.zly2006.reden.rvc.tracking.TrackedStructure - structure.fluidScheduledTicks.clear() - loadRvcFile(path, "fluidScheduledTicks")?.let { rvcFile -> - structure.fluidScheduledTicks.addAll(rvcFile.reader.readScheduledTicksData(rvcFile.data, Registries.FLUID)) + require(structure is TrackedStructure) { "Structure is not a TrackedStructure" } + structure.regions.clear() + val paths = path.toFile().listFiles(FileFilter { it.isDirectory && it.resolve("index.rvc").exists() })?.toList() + .orEmpty() + (paths + path.toFile()).forEach { + val partPath = it.toPath() + + val tracker = loadRvcFile(partPath, "index")?.let { rvcFile -> + Json.decodeFromString(rvcFile.data[0]) + } ?: StructureTracker.Trackpoint() + + val partName = if (partPath == path) "" else it.name + val part = TrackedStructurePart(partName, structure, tracker) + structure.regions[partName] = part + part.dirty = true // mark it as dirty caz we have no cache of positions + val palette = Palette.load(loadRvcFile(partPath, "palette")) + loadRvcFile(partPath, "blocks")?.let { rvcFile -> + part.blocks.putAll(rvcFile.reader.readBlocksData(rvcFile.data, palette)) + } + loadRvcFile(partPath, "blockEntities")?.let { rvcFile -> + part.blockEntities.putAll(rvcFile.reader.readBlockEntitiesData(rvcFile.data, palette)) + } + loadRvcFile(partPath, "entities")?.let { rvcFile -> + part.entities.putAll(rvcFile.reader.readEntitiesData(rvcFile.data)) + } + loadRvcFile(partPath, "blockEvents")?.let { rvcFile -> + part.blockEvents.addAll(rvcFile.reader.readBlockEventsData(rvcFile.data)) + } + loadRvcFile(partPath, "blockScheduledTicks")?.let { rvcFile -> + part.blockScheduledTicks.addAll(rvcFile.reader.readScheduledTicksData(rvcFile.data, Registries.BLOCK)) + } + loadRvcFile(partPath, "fluidScheduledTicks")?.let { rvcFile -> + part.fluidScheduledTicks.addAll(rvcFile.reader.readScheduledTicksData(rvcFile.data, Registries.FLUID)) + } } } } diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcRepository.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcRepository.kt index 8f3a449b..ccf7d81a 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcRepository.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcRepository.kt @@ -4,6 +4,7 @@ import com.github.zly2006.reden.Reden import com.github.zly2006.reden.gui.LoginRedenScreen import com.github.zly2006.reden.report.httpClient import com.github.zly2006.reden.report.ua +import com.github.zly2006.reden.rvc.gui.SelectionInfoScreen import com.github.zly2006.reden.rvc.gui.hud.gameplay.RvcMoveStructureLitematicaTask import com.github.zly2006.reden.rvc.gui.hud.gameplay.RvcMoveStructureTask import com.github.zly2006.reden.rvc.remote.IRemoteRepository @@ -77,6 +78,13 @@ class RvcRepository( ) { var headCache: TrackedStructure? = null private set + var remote = object : IRemoteRepository { + override fun deleteRepo() { + TODO("Not yet implemented") + } + + override val gitUrl = git.repository.config.getString("remote", "origin", "url") + } fun clearCache() { headCache = null } @@ -118,14 +126,9 @@ class RvcRepository( this.createReadmeIfNotExists() val path = git.repository.workTree.toPath() structure.refreshPositions() - val minPos = BlockPos( - structure.cachedPositions.keys.minOfOrNull { it.x } ?: 0, - structure.cachedPositions.keys.minOfOrNull { it.y } ?: 0, - structure.cachedPositions.keys.minOfOrNull { it.z } ?: 0 - ) if (git.branchList().call().isEmpty()) { // if this is the first commit, reset the origin - structure.placementInfo = structure.placementInfo!!.copy(origin = minPos) + structure.placementInfo = structure.placementInfo!!.copy(origin = structure.minPos) structure.repository.placementInfo = structure.placementInfo } structure.collectAllFromWorld() @@ -144,7 +147,7 @@ class RvcRepository( cmd.setMessage("$message\n\nUser-Agent: Reden-RVC/${Reden.MOD_VERSION} Minecraft/${SharedConstants.getGameVersion().name}") cmd.setSign(false) val commit = cmd.call() - return CommitResult(structure.blocks.size, commit.name) + return CommitResult(structure.totalBlocks, commit.name) } fun push(remote: IRemoteRepository, force: Boolean = false) { @@ -161,7 +164,10 @@ class RvcRepository( fun fetch() { headCache = null git.fetch().call() - TODO() // Note: currently we have no gui for this + val screen = MinecraftClient.getInstance().currentScreen + if (screen is SelectionInfoScreen) { + screen.refresh() + } } @Contract(pure = true) @@ -234,7 +240,7 @@ class RvcRepository( fun createLicense(license: String, author: String? = null) { val year = ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("yyyy")) - val content = ResourceLoader.loadString("assets/rvc/licenses/$license.txt") + val content = ResourceLoader.loadString(license) .replace("\${year}", year) .replace("\${year-start}", year) .replace("\${author}", author ?: "") @@ -243,8 +249,6 @@ class RvcRepository( redenError("LICENSE already exists") } git.repository.workTree.resolve("LICENSE").writeText(content) - - TODO() } fun setWorld() { @@ -266,20 +270,18 @@ class RvcRepository( fun startPlacing(structure: TrackedStructure, successCallback: (Task) -> Unit = {}) { clearCache() val mc = MinecraftClient.getInstance() - // todo singleplayer only - val world = mc.server!!.getWorld(mc.world!!.registryKey)!! Task.all().forEach { it.onCancel() } // Use temporary placement info to initialize the structure and its network worker - placementInfo = PlacementInfo(world.info, BlockPos.ORIGIN) + placementInfo = PlacementInfo(mc.getWorldInfo(), BlockPos.ORIGIN) configure(structure) placementInfo = null taskStack.add( if (litematicaInstalled) - RvcMoveStructureLitematicaTask(world, structure, successCallback) + RvcMoveStructureLitematicaTask(mc.getWorldInfo(), structure, successCallback) else - RvcMoveStructureTask(world, structure, successCallback = successCallback) + RvcMoveStructureTask(mc.getWorldInfo(), structure, successCallback = successCallback) ) } diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/SpreadEntry.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/SpreadEntry.kt new file mode 100644 index 00000000..e9a4260e --- /dev/null +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/SpreadEntry.kt @@ -0,0 +1,33 @@ +package com.github.zly2006.reden.rvc.tracking + +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World + +@Serializable +open class SpreadEntry( + val predicate: TrackPredicate, + val mode: TrackPredicate.TrackMode, +) { + @Transient + lateinit var pos: BlockPos + + @Transient + var part: TrackedStructurePart? = null + fun spreadAround( + world: World, + successConsumer: (BlockPos) -> Unit, + failConsumer: ((BlockPos) -> Unit)? = null + ) { + val structurePart = requireNotNull(part) + predicate.blocks(pos).forEach { + if (predicate.match(world, it, pos, mode, structurePart)) { + successConsumer(it) + } + else { + failConsumer?.invoke(it) + } + } + } +} diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/StructureTracker.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/StructureTracker.kt new file mode 100644 index 00000000..1461f104 --- /dev/null +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/StructureTracker.kt @@ -0,0 +1,237 @@ +package com.github.zly2006.reden.rvc.tracking + +import com.github.zly2006.reden.Reden +import com.github.zly2006.reden.debugger.breakpoint.BlockPosSerializer +import com.github.zly2006.reden.rvc.RelativeCoordinate +import com.github.zly2006.reden.rvc.blockPos +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import net.minecraft.util.math.BlockPos +import java.util.* + +/** + * track a structure's part, listen to block changes and update the positions of the tracked blocks. + * provide a way to iterate through the tracked blocks. + */ +@Serializable +sealed class StructureTracker { + /** + * iterator of the tracked blocks + * + * if tracked was modified, this iterator should be updated by calling [refreshPositions] + */ + abstract val blockIterator: Iterator + + /** + * cached origin of the structure part + */ + @Transient + protected var origin: BlockPos = BlockPos.ORIGIN + + @Serializable + class Cuboid( + @Serializable(BlockPosSerializer::class) + var first: BlockPos, + @Serializable(BlockPosSerializer::class) + var second: BlockPos + ) : StructureTracker() { + @Transient + override val blockIterator: Iterator = BlockPos.iterate(first, second).asSequence().map { + RelativeCoordinate.origin(origin).block(it) + }.iterator() + + override fun isInArea(part: TrackedStructurePart, pos: RelativeCoordinate) = + pos.blockPos(part.origin).run { + x in first.x..second.x + && y in first.y..second.y + && z in first.z..second.z + } + + override suspend fun refreshPositions(part: TrackedStructurePart) { + updateOrigin(part) + } + + override fun onBlockAdded(part: TrackedStructurePart, pos: BlockPos) { + TODO("whether to expand the cuboid?") + } + + override fun onBlockRemoved(part: TrackedStructurePart, pos: BlockPos) {} + } + + @Serializable + class Trackpoint( + val trackpoints: MutableList = mutableListOf() + ) : StructureTracker() { + @Transient + val trackpointMap = mutableMapOf() + + @Transient + var cachedPositions = HashMap() + + @Transient + var cachedIgnoredPositions = HashMap() + + @Transient + override val blockIterator: Iterator = cachedPositions.keys.asSequence().map { + RelativeCoordinate.origin(origin).block(it) + }.iterator() + + override fun updateOrigin(part: TrackedStructurePart) { + origin = part.origin + trackpoints.forEach { it.updateOrigin(part) } + trackpointMap.clear() + trackpoints.forEach { trackpointMap[it.pos] = it } + } + + override fun onBlockAdded(part: TrackedStructurePart, pos: BlockPos) { + part.dirty = true + val trackpoint = TrackPredicate.entries.firstNotNullOfOrNull { predicate -> + predicate.blocks(pos).firstOrNull { pos2 -> + trackpoints.firstOrNull { it.pos == pos2 }?.predicate == predicate && + predicate.match(part.world, pos, pos2, TrackPredicate.TrackMode.TRACK, part) + }?.let { trackpointMap[it] } + } ?: return + val readPos = mutableSetOf() + val queue = LinkedList() + queue.add(trackpoint) + var maxElements = 8000 + while (queue.isNotEmpty() && maxElements > 0) { + maxElements-- + val entry = queue.removeFirst() + if (entry.pos in cachedIgnoredPositions || part.world.isAir(entry.pos)) continue + entry.spreadAround(part.world, { newPos -> + if (readPos.add(newPos)) { + if (newPos in cachedPositions) return@spreadAround + if (!part.world.isAir(newPos)) { + cachedPositions[newPos] = trackpoint + } + queue.add(SpreadEntry(entry.predicate, trackpoint.mode).apply { + this.pos = newPos + this.part = part + }) + } + }) + } + } + + override fun onBlockRemoved(part: TrackedStructurePart, pos: BlockPos) { + part.dirty = true + removeTrackpoint(pos) + cachedPositions -= pos + } + + fun removeTrackpoint(pos: BlockPos) { + val existing = trackpointMap[pos] ?: return + cachedPositions.entries.removeIf { it.value == existing } + trackpoints -= existing + } + + fun addTrackPoint(trackPoint: TrackPoint) { + removeTrackpoint(trackPoint.pos) + trackpointMap[trackPoint.pos] = trackPoint + trackpoints.add(trackPoint) + } + + override suspend fun refreshPositions(part: TrackedStructurePart) { + cachedIgnoredPositions = hashMapOf() + cachedPositions = hashMapOf() + updateOrigin(part) + val readPos = hashSetOf() + requireNotNull(part.structure.networkWorker).launch { + trackpoints.filter { it.mode == TrackPredicate.TrackMode.IGNORE }.forEach { trackPoint -> + // first, add all blocks recursively + val queue = LinkedList() + queue.add(trackPoint) + var maxElements = trackPoint.maxElements + while (queue.isNotEmpty() && maxElements > 0) { + maxElements-- + val entry = queue.removeFirst() + if (entry.pos in cachedIgnoredPositions) continue + if (part.world.isAir(entry.pos)) { + continue + } + cachedIgnoredPositions[entry.pos] = trackPoint + entry.spreadAround(part.world, { newPos -> + if (readPos.add(newPos)) { + queue.add(SpreadEntry(entry.predicate, trackPoint.mode).apply { + this.pos = newPos + this.part = part + }) + } + }) + } + } + trackpoints.filter { it.mode == TrackPredicate.TrackMode.TRACK }.forEach { trackPoint -> + // first, add all blocks recursively + val queue = LinkedList() + queue.add(trackPoint) + var maxElements = trackPoint.maxElements + while (queue.isNotEmpty() && maxElements > 0) { + maxElements-- + val entry = queue.removeFirst() + if (entry.pos in cachedIgnoredPositions) continue + if (part.world.isAir(entry.pos)) { + continue + } + if (!trackPoint.pos.isWithinDistance(entry.pos, 200.0)) { + Reden.LOGGER.error("Track point ${trackPoint.pos} is too far away from ${entry.pos}") + continue + } + entry.spreadAround(part.world, { newPos -> + if (readPos.add(newPos)) { + if (newPos in cachedPositions) return@spreadAround + if (!part.world.isAir(newPos)) { + cachedPositions[newPos] = trackPoint + } + queue.add(SpreadEntry(entry.predicate, trackPoint.mode).apply { + this.pos = newPos + this.part = part + }) + } + }) + } + } + part.structure.networkWorker?.trackpointUpdated(part) + } + } + + override fun isInArea(part: TrackedStructurePart, pos: RelativeCoordinate) = + pos.blockPos(origin) in cachedPositions.keys + } + + @Serializable + data object Entire : StructureTracker() { + override val blockIterator: Iterator + get() = TODO("Not yet implemented") + + override fun updateOrigin(part: TrackedStructurePart) = error("entire structure cannot be moved") + override fun isInArea(part: TrackedStructurePart, pos: RelativeCoordinate) = true + override suspend fun refreshPositions(part: TrackedStructurePart) {} + override fun onBlockAdded(part: TrackedStructurePart, pos: BlockPos) {} + override fun onBlockRemoved(part: TrackedStructurePart, pos: BlockPos) {} + } + + /** + * update the origin of the structure part + * usually called by [TrackedStructurePart.createPlacement] + */ + open fun updateOrigin(part: TrackedStructurePart) { + origin = part.origin + } + + abstract fun onBlockAdded(part: TrackedStructurePart, pos: BlockPos) + abstract fun onBlockRemoved(part: TrackedStructurePart, pos: BlockPos) + + /** + * check if the given position is in the area of the structure part + * @param part the structure part + * @param pos the position to check, relative to the structure part + */ + abstract fun isInArea(part: TrackedStructurePart, pos: RelativeCoordinate): Boolean + + /** + * refresh the positions of the tracked blocks, after called this method, + * the [blockIterator] should return the correct positions + */ + abstract suspend fun refreshPositions(part: TrackedStructurePart) +} diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackPoint.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackPoint.kt new file mode 100644 index 00000000..e08d48a9 --- /dev/null +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackPoint.kt @@ -0,0 +1,68 @@ +package com.github.zly2006.reden.rvc.tracking + +import com.github.zly2006.reden.rvc.RelativeCoordinate +import com.github.zly2006.reden.rvc.blockPos +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.* + +@Serializable(TrackPoint.Companion.TrackPointSerializer::class) +class TrackPoint( + private val relativeCoordinate: RelativeCoordinate, + predicate: TrackPredicate, + mode: TrackPredicate.TrackMode +) : SpreadEntry(predicate, mode) { + companion object { + object TrackPointSerializer : KSerializer { + override val descriptor = buildClassSerialDescriptor("TrackPoint") { + element("relativeCoordinate") + element("predicate") + element("mode") + } + + override fun deserialize(decoder: Decoder): TrackPoint = + decoder.decodeStructure(descriptor) { + var relativeCoordinate: RelativeCoordinate? = null + var predicate: TrackPredicate? = null + var mode: TrackPredicate.TrackMode? = null + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> relativeCoordinate = decoder.decodeSerializableValue(RelativeCoordinate.serializer()) + 1 -> predicate = decoder.decodeSerializableValue(TrackPredicate.serializer()) + 2 -> mode = + decodeSerializableElement(descriptor, index, TrackPredicate.TrackMode.serializer()) + + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + return@decodeStructure TrackPoint(relativeCoordinate!!, predicate!!, mode!!) + } + + override fun serialize(encoder: Encoder, value: TrackPoint) { + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, RelativeCoordinate.serializer(), value.relativeCoordinate) + encodeSerializableElement(descriptor, 1, TrackPredicate.serializer(), value.predicate) + encodeSerializableElement(descriptor, 2, TrackPredicate.TrackMode.serializer(), value.mode) + } + } + } + } + val maxElements: Int + get() = when (mode) { + TrackPredicate.TrackMode.IGNORE -> 5000 + TrackPredicate.TrackMode.TRACK -> 8000 + TrackPredicate.TrackMode.NOOP -> 0 + } + + fun updateOrigin(structure: TrackedStructurePart) { + this.part = structure + pos = relativeCoordinate.blockPos(structure.origin) + } + + override fun toString(): String { + return "TrackPoint(${pos.toShortString()}, $predicate, $mode)" + } +} diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackPredicate.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackPredicate.kt index 1948aa42..78cdb2bd 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackPredicate.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackPredicate.kt @@ -1,8 +1,10 @@ package com.github.zly2006.reden.rvc.tracking +import kotlinx.serialization.Serializable import net.minecraft.util.math.BlockPos import net.minecraft.world.World +@Serializable enum class TrackPredicate(val distance: Int, val same: Boolean) { Same(2, true), Near(1, false), @@ -12,18 +14,14 @@ enum class TrackPredicate(val distance: Int, val same: Boolean) { pos1: BlockPos, pos2: BlockPos, mode: TrackMode, - structure: TrackedStructure + structure: TrackedStructurePart ): Boolean { if (!super.match(world, pos1, pos2, mode, structure)) return false - if (mode == TrackMode.TRACK && pos1.getManhattanDistance(pos2) == 2) { - val center = pos1.add(pos2).mutableCopy().apply { - x /= 2 - y /= 2 - z /= 2 - } - if (center in structure.cachedIgnoredPositions) { - return false - } + if (mode == TrackMode.TRACK && pos1.getSquaredDistance(pos2) >= 3.9) { // Euclidean distance = 2 + // check if the middle block is air. + // if not, then there may be some block ignored between [pos1] and [pos2] + // in that case we should not track this block + return world.isAir(BlockPos((pos1.x + pos2.x) / 2, (pos1.y + pos2.y) / 2, (pos1.z + pos2.z) / 2)) } return true } @@ -35,7 +33,7 @@ enum class TrackPredicate(val distance: Int, val same: Boolean) { pos1: BlockPos, pos2: BlockPos, mode: TrackMode, - structure: TrackedStructure + structure: TrackedStructurePart ): Boolean { // Note: we have checked this condition in [spreadAround] if (pos1.getManhattanDistance(pos2) > this.distance) { @@ -47,6 +45,7 @@ enum class TrackPredicate(val distance: Int, val same: Boolean) { return true } + @Serializable enum class TrackMode { NOOP, TRACK, @@ -56,4 +55,20 @@ enum class TrackPredicate(val distance: Int, val same: Boolean) { return this == TRACK } } + + fun blocks(pos: BlockPos) = sequence { + val x = pos.x + val y = pos.y + val z = pos.z + val deltaRange = -distance..distance + for (dx in deltaRange) { + for (dy in deltaRange) { + for (dz in deltaRange) { + val pos2 = BlockPos(x + dx, y + dy, z + dz) + if (pos2.getManhattanDistance(pos) > distance) continue + yield(pos2) + } + } + } + } } diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructure.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructure.kt index 7d112cf8..858371a9 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructure.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructure.kt @@ -1,43 +1,38 @@ package com.github.zly2006.reden.rvc.tracking -import com.github.zly2006.reden.rvc.* +import com.github.zly2006.reden.rvc.IPlacement +import com.github.zly2006.reden.rvc.IWritableStructure +import com.github.zly2006.reden.rvc.RelativeCoordinate import com.github.zly2006.reden.rvc.tracking.network.NetworkWorker import com.github.zly2006.reden.utils.redenError -import com.github.zly2006.reden.utils.setBlockNoPP -import net.minecraft.block.Block -import net.minecraft.block.BlockEntityProvider import net.minecraft.block.BlockState -import net.minecraft.block.Blocks -import net.minecraft.client.world.ClientWorld -import net.minecraft.entity.Entity -import net.minecraft.entity.EntityType -import net.minecraft.entity.SpawnReason -import net.minecraft.entity.mob.MobEntity -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.fluid.Fluid import net.minecraft.nbt.NbtCompound -import net.minecraft.registry.Registries -import net.minecraft.registry.Registry -import net.minecraft.server.world.ServerWorld -import net.minecraft.util.math.* +import net.minecraft.util.math.BlockPos import net.minecraft.world.World -import net.minecraft.world.tick.ChunkTickScheduler -import net.minecraft.world.tick.OrderedTick -import net.minecraft.world.tick.TickPriority -import org.jetbrains.annotations.Contract import java.nio.file.Path import java.util.* -/** - * todo: see #81 - */ class TrackedStructure( - name: String, + override var name: String, val repository: RvcRepository?, -) : ReadWriteStructure(name), IPlacement, PositionIterable { +) : IWritableStructure, IPlacement { + override val entities: MutableMap + get() = regions.values.flatMap { it.entities.entries }.associate { it.toPair() }.toMutableMap() var networkWorker: NetworkWorker? = null override var enabled: Boolean = true override val structure get() = this + val regions = mutableMapOf() + + init { + regions[""] = TrackedStructurePart("", this) + } + + val minX get() = regions.values.minOf { it.minX } + val minY get() = regions.values.minOf { it.minY } + val minZ get() = regions.values.minOf { it.minZ } + override val xSize get() = regions.values.maxOf { it.minX + it.xSize } - minX + override val ySize get() = regions.values.maxOf { it.minY + it.ySize } - minY + override val zSize get() = regions.values.maxOf { it.minZ + it.zSize } - minZ /** * This is stored in the file `.git/placement_info.json`. @@ -51,334 +46,10 @@ class TrackedStructure( get() = if (networkWorker?.world?.info == placementInfo?.worldInfo) networkWorker?.world ?: redenError("World is not set for $name") else placementInfo?.worldInfo?.getWorld() ?: redenError("World is not found: $name") - override val origin: BlockPos get() = placementInfo?.origin?.toImmutable() ?: redenError("getting origin but PlacementInfo not set for $name") - override fun createPlacement(world: World, origin: BlockPos) = apply { - placementInfo = PlacementInfo(WorldInfo.of(world), origin) - trackPoints.forEach { it.updateOrigin(this) } - } - - var cachedPositions = HashMap() - var cachedIgnoredPositions = HashMap() - val trackPoints = mutableListOf() - val blockEvents = mutableListOf() // order sensitive - val blockScheduledTicks = mutableListOf>() // order sensitive - val fluidScheduledTicks = mutableListOf>() // order sensitive - var dirty = true - - data class TickInfo( - val pos: RelativeCoordinate, - val type: T, - val delay: Long, - val priority: TickPriority, - val registry: Registry - ) { - fun toRvcDataString(): String { - return "${pos.x},${pos.y},${pos.z},${registry.getId(type)},$delay,${priority.ordinal}" - } - - companion object { - fun wrap(orderedTick: OrderedTick, world: World): TickInfo { - @Suppress("UNCHECKED_CAST") - return TickInfo( - pos = RelativeCoordinate(orderedTick.pos.x, orderedTick.pos.y, orderedTick.pos.z), - type = orderedTick.type as T, - delay = orderedTick.triggerTick - world.time, - priority = orderedTick.priority, - registry = when (orderedTick.type) { - is Block -> Registries.BLOCK - is Fluid -> Registries.FLUID - else -> throw IllegalArgumentException("Unknown type ${orderedTick.type}") - } as Registry - ).apply { - this.priority.index - } - } - } - } - - data class BlockEventInfo( - val pos: RelativeCoordinate, - val type: Int, - val data: Int, - val block: Block - ) { - fun toRvcDataString(): String { - return "${pos.x},${pos.y},${pos.z},$type,$data,${Registries.BLOCK.getId(block)}" - } - } - - - fun splitCuboids( - includeUntracked: Boolean = true, - ): List { - class SplitingContext( - var cuboid: BlockBox?, - val points: SortedSet = sortedSetOf() - ) { - constructor(points: Collection) : this( - null, - points.toSortedSet() - ) { - shrinkCuboid() - } - - fun shrinkCuboid() { - if (points.isEmpty()) return - val minX = points.minOf { it.x } - val minY = points.minOf { it.y } - val minZ = points.minOf { it.z } - val maxX = points.maxOf { it.x } - val maxY = points.maxOf { it.y } - val maxZ = points.maxOf { it.z } - cuboid = BlockBox(minX, minY, minZ, maxX, maxY, maxZ) - } - } - - val result: MutableList - - if (includeUntracked) { - result = mutableListOf(SplitingContext(cachedPositions.keys)) - cachedIgnoredPositions.forEach { ignoredPos -> - val iter = result.listIterator() - while (iter.hasNext()) { - val entry = iter.next() - fun splitByAxis( - entry: SplitingContext, - axis: BlockPos.() -> Int - ) { - iter.add( - SplitingContext( - entry.points.filter { it.axis() < ignoredPos.key.axis() } - ) - ) - iter.add( - SplitingContext( - entry.points.filter { it.axis() > ignoredPos.key.axis() } - ) - ) - } - if (entry.cuboid?.contains(ignoredPos.key) == true) { - // Note: in this block element[i] is always removing - // select if we can split by an axis without add more cuboids - if (entry.points.none { it.x == ignoredPos.key.x }) { - iter.remove() - splitByAxis(entry) { x } - } - else if (entry.points.none { it.y == ignoredPos.key.y }) { - iter.remove() - splitByAxis(entry) { y } - } - else if (entry.points.none { it.z == ignoredPos.key.z }) { - iter.remove() - splitByAxis(entry) { z } - } - else { - var entryToSplit = entry - iter.remove() - // first, split by x - splitByAxis(entryToSplit) { x } - // then add same x points to the new cuboids - entryToSplit = SplitingContext(entryToSplit.points.filter { it.x == ignoredPos.key.x }) - if (entryToSplit.cuboid?.contains(ignoredPos.key) != true) { - iter.add(entryToSplit) - } - // second, split by z - splitByAxis(entryToSplit) { z } - // then add same z points to the new cuboids - entryToSplit = SplitingContext(entryToSplit.points.filter { it.y == ignoredPos.key.y }) - if (entryToSplit.cuboid?.contains(ignoredPos.key) != true) { - iter.add(entryToSplit) - } - // third, split by y - splitByAxis(entryToSplit) { y } - } - } - } - result.removeIf { it.points.isEmpty() || it.cuboid == null } - } - } - else - result = (cachedPositions.keys).map { SplitingContext(listOf(it)) }.toMutableList() - - return result.mapNotNull { it.cuboid } - } - - open class SpreadEntry( - var pos: BlockPos, - val predicate: TrackPredicate, - val mode: TrackPredicate.TrackMode, - var structure: TrackedStructure? - ) { - fun spreadAround( - world: World, - successConsumer: (BlockPos) -> Unit, - failConsumer: ((BlockPos) -> Unit)? = null - ) { - val x = pos.x - val y = pos.y - val z = pos.z - val deltaRange = -predicate.distance..predicate.distance - for (dx in deltaRange) { - for (dy in deltaRange) { - for (dz in deltaRange) { - val pos = BlockPos(x + dx, y + dy, z + dz) - if (pos.getManhattanDistance(this.pos) > predicate.distance) continue - if (predicate.match(world, this.pos, pos, mode, structure!!)) { - successConsumer(pos) - } - else { - failConsumer?.invoke(pos) - } - } - } - } - } - } - - class TrackPoint( - private val relativeCoordinate: RelativeCoordinate, - predicate: TrackPredicate, - mode: TrackPredicate.TrackMode - ) : SpreadEntry(BlockPos.ORIGIN, predicate, mode, null) { - val maxElements: Int - get() = when (mode) { - TrackPredicate.TrackMode.IGNORE -> 5000 - TrackPredicate.TrackMode.TRACK -> 8000 - TrackPredicate.TrackMode.NOOP -> 0 - } - - fun updateOrigin(structure: TrackedStructure) { - this.structure = structure - pos = relativeCoordinate.blockPos(structure.origin) - } - - override fun toString(): String { - return "TrackPoint(${pos.toShortString()}, $predicate, $mode)" - } - } - - fun onBlockAdded(pos: BlockPos) { - dirty = true - val trackPoint = Direction.entries.map(pos::offset).map { cachedIgnoredPositions[it] }.firstOrNull() - if (trackPoint != null) { - val readPos = mutableSetOf() - val queue = LinkedList() - queue.add(SpreadEntry(pos, trackPoint.predicate, trackPoint.mode, this)) - var maxElements = 8000 - while (queue.isNotEmpty() && maxElements > 0) { - maxElements-- - val entry = queue.removeFirst() - if (entry.pos in cachedIgnoredPositions || world.isAir(entry.pos)) continue - entry.spreadAround(world, { newPos -> - if (readPos.add(newPos)) { - if (newPos in cachedPositions) return@spreadAround - if (!world.isAir(newPos)) { - cachedPositions[newPos] = trackPoint - } - queue.add(SpreadEntry(newPos, entry.predicate, trackPoint.mode, this)) - } - }) - } - } - } - - fun onBlockRemoved(pos: BlockPos) { - dirty = true - removeTrackpoint(pos) - cachedPositions -= pos - } - - init { - io = RvcFileIO - } - - override fun isInArea(pos: RelativeCoordinate): Boolean { - return cachedPositions.contains(pos.blockPos(origin)) - } - - fun refreshPositionsAsync() = networkWorker?.async { - refreshPositions() - } - - suspend fun refreshPositions() { - trackPoints.filter { it.structure != this }.forEach { - it.updateOrigin(this) - dirty = true - } - if (dirty) { - cachedIgnoredPositions = hashMapOf() - cachedPositions = hashMapOf() - requireNotNull(networkWorker).refreshPositions() - dirty = false - } - requireNotNull(networkWorker).debugRender() - } - - override val blockIterator: Iterator - get() = - cachedPositions.keys.asSequence().map { getRelativeCoordinate(it) }.iterator() - - override fun clearArea() { - clearSchedules() - blockIterator.forEach { - world.setBlockNoPP(it.blockPos(origin), Blocks.AIR.defaultState, Block.NOTIFY_LISTENERS) - } - blocks.keys.forEach { - world.setBlockNoPP(it.blockPos(origin), Blocks.AIR.defaultState, Block.NOTIFY_LISTENERS) - } - entities.forEach { - (world as? ClientWorld)?.entityLookup?.get(it.key)?.discard() - (world as? ServerWorld)?.getEntity(it.key)?.discard() - } - } - - override fun paste() { - blocks.forEach { (pos, state) -> - world.setBlockNoPP(pos.blockPos(origin), state, Block.NOTIFY_LISTENERS) - } - blockEntities.forEach { (pos, nbt) -> - val be = (blocks[pos]?.block as? BlockEntityProvider)?.createBlockEntity(pos.blockPos(origin), blocks[pos]) - ?.apply { - readNbt(nbt) - markDirty() - } ?: redenError("Failed to load block entity") - world.addBlockEntity(be) - } - blocks.keys.forEach { - world.markDirty(it.blockPos(origin)) - } - entities.forEach { - (world as? ServerWorld)?.getEntity(it.key)?.discard() - val entity = EntityType.getEntityFromNbt(it.value, world).get() - world.spawnEntity(entity) - entity.refreshPositionAndAngles( - entity.x + origin.x, - entity.y + origin.y, - entity.z + origin.z, - entity.yaw, - entity.pitch - ) - if (world is ServerWorld) { - (entity as? MobEntity)?.initialize( - world as ServerWorld, - world.getLocalDifficulty(entity.blockPos), - SpawnReason.STRUCTURE, - null, - it.value - ) - (world as ServerWorld).spawnEntityAndPassengers(entity) - } - else { - world.spawnEntity(entity) - } - } - // todo - } - fun setPlaced() { require(repository != null) { "Repository is null" } repository.placed = true @@ -390,18 +61,11 @@ class TrackedStructure( repository.placed = false } - override fun blockBox(): BlockBox { - if (blocks.isEmpty()) { - return BlockBox(BlockPos.ORIGIN) + fun refreshPositionsAsync() = networkWorker?.launch { refreshPositions() } + suspend fun refreshPositions() { + regions.values.forEach { + it.refreshPositions() } - return BlockBox( - origin.x + minX, - origin.y + minY, - origin.z + minZ, - origin.x + minX + xSize, - origin.y + minY + ySize, - origin.z + minZ + zSize - ) } /** @@ -414,168 +78,73 @@ class TrackedStructure( clearArea() } - fun clearSchedules() { - blockIterator.forEach { relative -> - val pos = relative.blockPos(origin) - (world as? ServerWorld)?.run { - syncedBlockEventQueue.removeIf { it.pos == pos } - val blockTickScheduler = getChunk(pos).blockTickScheduler as ChunkTickScheduler - val fluidTickScheduler = getChunk(pos).fluidTickScheduler as ChunkTickScheduler - blockTickScheduler.removeTicksIf { it.pos == pos } - fluidTickScheduler.removeTicksIf { it.pos == pos } - } + suspend fun collectAllFromWorld() { + regions.values.forEach { + it.collectAllFromWorld() } } - fun collectSchedules() { - blockEvents.clear() - blockScheduledTicks.clear() - fluidScheduledTicks.clear() + val totalBlocks: Int get() = regions.values.sumOf { it.blocks.size } + val minPos get() = BlockPos(minX, minY, minZ) - (world as? ServerWorld)?.run { - blockEvents.addAll(syncedBlockEventQueue.filter { isInArea(getRelativeCoordinate(it.pos)) }.map { - BlockEventInfo( - pos = getRelativeCoordinate(it.pos), - block = it.block, - type = it.type, - data = it.data - ) - }) - val chunks = cachedPositions.keys.asSequence() - .map(ChunkPos::toLong) - .toList().distinct() - .map { getChunk(ChunkPos.getPackedX(it), ChunkPos.getPackedZ(it)) } - val blockTickSchedulers = chunks.asSequence().map { it.blockTickScheduler as ChunkTickScheduler } - val fluidTickSchedulers = chunks.asSequence().map { it.fluidTickScheduler as ChunkTickScheduler } - blockScheduledTicks.addAll(blockTickSchedulers - .flatMap { it.queuedTicks.filter { isInArea(getRelativeCoordinate(it.pos)) } } - .map { - @Suppress("UNCHECKED_CAST") - TickInfo.wrap(it, world) as TickInfo - } - ) - fluidScheduledTicks.addAll(fluidTickSchedulers - .flatMap { it.queuedTicks.filter { isInArea(getRelativeCoordinate(it.pos)) } } - .map { - @Suppress("UNCHECKED_CAST") - TickInfo.wrap(it, world) as TickInfo - } - ) + private fun getPartsFor(pos: RelativeCoordinate) = + regions.values.filter { + it.placementInfo != null && + it.isInArea( + RelativeCoordinate( + pos.x + origin.x - it.origin.x, + pos.y + origin.y - it.origin.y, + pos.z + origin.z - it.origin.z + ) + ) } + + override fun setBlockEntityData(pos: RelativeCoordinate, nbt: NbtCompound) { + val count = getPartsFor(pos).onEach { it.setBlockEntityData(pos, nbt) }.count() + require(count > 0) { "No region contains $pos" } } - @Contract(pure = true) - fun getRelativeCoordinate(pos: BlockPos): RelativeCoordinate { - return RelativeCoordinate(pos.x - origin.x, pos.y - origin.y, pos.z - origin.z) + override fun setBlockState(pos: RelativeCoordinate, state: BlockState) { + val count = getPartsFor(pos).onEach { it.setBlockState(pos, state) }.count() + require(count > 0) { "No region contains $pos" } } - suspend fun collectAllFromWorld() { - blocks.clear() - blockEntities.clear() - entities.clear() - refreshPositions() - collectSchedules() - val minPos = BlockPos.Mutable() - val maxPos = BlockPos.Mutable() - blockIterator.forEach { pos -> - if (pos.x < minPos.x) minPos.x = pos.x - if (pos.y < minPos.y) minPos.y = pos.y - if (pos.z < minPos.z) minPos.z = pos.z - if (pos.x > maxPos.x) maxPos.x = pos.x - if (pos.y > maxPos.y) maxPos.y = pos.y - if (pos.z > maxPos.z) maxPos.z = pos.z - val state = world.getBlockState(pos.blockPos(origin)) - val beData = world.getBlockEntity(pos.blockPos(origin))?.createNbtWithId() - blocks[pos] = state - if (beData != null) blockEntities[pos] = beData + override fun getBlockEntityData(pos: RelativeCoordinate) = + regions.values.firstNotNullOfOrNull { it.getBlockEntityData(pos) } + + override fun getBlockState(pos: RelativeCoordinate) = + regions.values.firstNotNullOf { + it.getBlockState(pos).takeIf { state -> !state.isAir } } - xSize = maxPos.x - minPos.x + 1 - ySize = maxPos.y - minPos.y + 1 - zSize = maxPos.z - minPos.z + 1 - world.getNonSpectatingEntities(Entity::class.java, Box.enclosing(minPos, maxPos)).asSequence() - .filter { - it !is PlayerEntity - }.forEach { - it.refreshPositionAndAngles( - it.x - origin.x, - it.y - origin.y, - it.z - origin.z, - it.yaw, - it.pitch - ) - entities[it.uuid] = NbtCompound().apply(it::saveSelfNbt) - it.refreshPositionAndAngles( - it.x + origin.x, - it.y + origin.y, - it.z + origin.z, - it.yaw, - it.pitch - ) - } - } - fun removeTrackpoint(pos: BlockPos) { - dirty = true - val existing = trackPoints.find { it.pos == pos } - if (existing != null) { - cachedPositions.entries.removeIf { it.value == existing } - trackPoints -= existing + override fun isInArea(pos: RelativeCoordinate) = regions.any { it.value.isInArea(pos) } + override fun createPlacement(placementInfo: PlacementInfo) = apply { + val oldOffsets = regions.mapValues { it.value.placementInfo?.origin?.subtract(this.origin) } + this.placementInfo = placementInfo + regions.forEach { (k, v) -> + val partOrigin = oldOffsets[k]?.add(origin) ?: origin + v.createPlacement(placementInfo.copy(origin = partOrigin)) } } - fun addTrackPoint(trackPoint: TrackPoint) { - dirty = true - removeTrackpoint(trackPoint.pos) - trackPoints.add(trackPoint) + override fun save(path: Path) = RvcFileIO.save(path, this) + override fun load(path: Path) = RvcFileIO.load(path, this) + suspend fun autoTrack() { + // todo } - fun asCuboid(): IStructure { - return object : IStructure { - override var name = this@TrackedStructure.name - override val xSize: Int get() = this@TrackedStructure.xSize - override val ySize: Int get() = this@TrackedStructure.ySize - override val zSize: Int get() = this@TrackedStructure.zSize - override fun save(path: Path) {} - override fun load(path: Path) {} - override fun isInArea(pos: RelativeCoordinate): Boolean { - return this@TrackedStructure.isInArea( - RelativeCoordinate( - pos.x + minX, - pos.y + minY, - pos.z + minZ - ) - ) - } - - override fun createPlacement(world: World, origin: BlockPos): IPlacement { - return this@TrackedStructure - } - - override fun getBlockEntityData(pos: RelativeCoordinate): NbtCompound? { - return this@TrackedStructure.getBlockEntityData( - RelativeCoordinate( - pos.x + minX, - pos.y + minY, - pos.z + minZ - ) - ) - } - - override fun getBlockState(pos: RelativeCoordinate): BlockState { - return this@TrackedStructure.getBlockState(RelativeCoordinate(pos.x + minX, pos.y + minY, pos.z + minZ)) - } + fun getRelativeCoordinate(pos: BlockPos): RelativeCoordinate { + require(regions.isNotEmpty()) { + "No region in this structure" + } + return RelativeCoordinate(pos.x - minX, pos.y - minY, pos.z - minZ) + } - override fun getOrCreateBlockEntityData(pos: RelativeCoordinate): NbtCompound { - return this@TrackedStructure.getOrCreateBlockEntityData( - RelativeCoordinate( - pos.x + minX, - pos.y + minY, - pos.z + minZ - ) - ) - } + fun onBlockRemoved(pos: BlockPos) { + regions.values.forEach { it.onBlockRemoved(pos) } + } - override val entities = this@TrackedStructure.entities - } + fun onBlockAdded(pos: BlockPos) { + regions.values.forEach { it.onBlockAdded(pos) } } } diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructurePart.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructurePart.kt new file mode 100644 index 00000000..c8645153 --- /dev/null +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructurePart.kt @@ -0,0 +1,395 @@ +package com.github.zly2006.reden.rvc.tracking + +import com.github.zly2006.reden.rvc.* +import com.github.zly2006.reden.utils.redenError +import com.github.zly2006.reden.utils.setBlockNoPP +import net.minecraft.block.Block +import net.minecraft.block.BlockEntityProvider +import net.minecraft.block.Blocks +import net.minecraft.client.world.ClientWorld +import net.minecraft.entity.Entity +import net.minecraft.entity.EntityType +import net.minecraft.entity.SpawnReason +import net.minecraft.entity.mob.MobEntity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.fluid.Fluid +import net.minecraft.nbt.NbtCompound +import net.minecraft.registry.Registries +import net.minecraft.registry.Registry +import net.minecraft.server.world.ServerWorld +import net.minecraft.util.math.BlockBox +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import net.minecraft.util.math.ChunkPos +import net.minecraft.world.World +import net.minecraft.world.tick.ChunkTickScheduler +import net.minecraft.world.tick.OrderedTick +import net.minecraft.world.tick.TickPriority +import org.jetbrains.annotations.Contract + +class TrackedStructurePart( + name: String, + override val structure: TrackedStructure, + val tracker: StructureTracker = StructureTracker.Trackpoint() +) : ReadWriteStructure(name), IPlacement, PositionIterable { + public override var minX: Int = super.minX + public override var minY: Int = super.minY + public override var minZ: Int = super.minZ + override var enabled: Boolean = true + + /** + * This is stored in the file `.git/placement_info.json`. + * + * When we cloned or created a repository, remember to create this file. + * + * @see RvcRepository.placementInfo + */ + var placementInfo: PlacementInfo? = null + override val world get() = structure.world + + override val origin: BlockPos + get() = placementInfo?.origin?.toImmutable() + ?: redenError("getting origin but PlacementInfo not set for subregion: ${structure.name}/$name") + + override fun createPlacement(placementInfo: PlacementInfo) = apply { + this.placementInfo = placementInfo + tracker.updateOrigin(this) + } + + val blockEvents = mutableListOf() // order sensitive + val blockScheduledTicks = mutableListOf>() // order sensitive + val fluidScheduledTicks = mutableListOf>() // order sensitive + var dirty = true + + data class TickInfo( + val pos: RelativeCoordinate, + val type: T, + val delay: Long, + val priority: TickPriority, + val registry: Registry + ) { + fun toRvcDataString(): String { + return "${pos.x},${pos.y},${pos.z},${registry.getId(type)},$delay,${priority.ordinal}" + } + + companion object { + fun wrap(orderedTick: OrderedTick, world: World): TickInfo { + @Suppress("UNCHECKED_CAST") + return TickInfo( + pos = RelativeCoordinate(orderedTick.pos.x, orderedTick.pos.y, orderedTick.pos.z), + type = orderedTick.type as T, + delay = orderedTick.triggerTick - world.time, + priority = orderedTick.priority, + registry = when (orderedTick.type) { + is Block -> Registries.BLOCK + is Fluid -> Registries.FLUID + else -> throw IllegalArgumentException("Unknown type ${orderedTick.type}") + } as Registry + ).apply { + this.priority.index + } + } + } + } + + data class BlockEventInfo( + val pos: RelativeCoordinate, + val type: Int, + val data: Int, + val block: Block + ) { + fun toRvcDataString(): String { + return "${pos.x},${pos.y},${pos.z},$type,$data,${Registries.BLOCK.getId(block)}" + } + } + + +// fun splitCuboids( +// includeUntracked: Boolean = true, +// ): List { +// class SplitingContext( +// var cuboid: BlockBox?, +// val points: SortedSet = sortedSetOf() +// ) { +// constructor(points: Collection) : this( +// null, +// points.toSortedSet() +// ) { +// shrinkCuboid() +// } +// +// fun shrinkCuboid() { +// if (points.isEmpty()) return +// val minX = points.minOf { it.x } +// val minY = points.minOf { it.y } +// val minZ = points.minOf { it.z } +// val maxX = points.maxOf { it.x } +// val maxY = points.maxOf { it.y } +// val maxZ = points.maxOf { it.z } +// cuboid = BlockBox(minX, minY, minZ, maxX, maxY, maxZ) +// } +// } +// +// val result: MutableList +// +// if (includeUntracked) { +// result = mutableListOf(SplitingContext(cachedPositions.keys)) +// cachedIgnoredPositions.forEach { ignoredPos -> +// val iter = result.listIterator() +// while (iter.hasNext()) { +// val entry = iter.next() +// fun splitByAxis( +// entry: SplitingContext, +// axis: BlockPos.() -> Int +// ) { +// iter.add( +// SplitingContext( +// entry.points.filter { it.axis() < ignoredPos.key.axis() } +// ) +// ) +// iter.add( +// SplitingContext( +// entry.points.filter { it.axis() > ignoredPos.key.axis() } +// ) +// ) +// } +// if (entry.cuboid?.contains(ignoredPos.key) == true) { +// // Note: in this block element[i] is always removing +// // select if we can split by an axis without add more cuboids +// if (entry.points.none { it.x == ignoredPos.key.x }) { +// iter.remove() +// splitByAxis(entry) { x } +// } +// else if (entry.points.none { it.y == ignoredPos.key.y }) { +// iter.remove() +// splitByAxis(entry) { y } +// } +// else if (entry.points.none { it.z == ignoredPos.key.z }) { +// iter.remove() +// splitByAxis(entry) { z } +// } +// else { +// var entryToSplit = entry +// iter.remove() +// // first, split by x +// splitByAxis(entryToSplit) { x } +// // then add same x points to the new cuboids +// entryToSplit = SplitingContext(entryToSplit.points.filter { it.x == ignoredPos.key.x }) +// if (entryToSplit.cuboid?.contains(ignoredPos.key) != true) { +// iter.add(entryToSplit) +// } +// // second, split by z +// splitByAxis(entryToSplit) { z } +// // then add same z points to the new cuboids +// entryToSplit = SplitingContext(entryToSplit.points.filter { it.y == ignoredPos.key.y }) +// if (entryToSplit.cuboid?.contains(ignoredPos.key) != true) { +// iter.add(entryToSplit) +// } +// // third, split by y +// splitByAxis(entryToSplit) { y } +// } +// } +// } +// result.removeIf { it.points.isEmpty() || it.cuboid == null } +// } +// } +// else +// result = (cachedPositions.keys).map { SplitingContext(listOf(it)) }.toMutableList() +// +// return result.mapNotNull { it.cuboid } +// } + + fun onBlockAdded(pos: BlockPos) = tracker.onBlockAdded(this, pos) + + fun onBlockRemoved(pos: BlockPos) = tracker.onBlockRemoved(this, pos) + + init { + io = RvcFileIO + } + + override fun isInArea(pos: RelativeCoordinate): Boolean { + return tracker.isInArea(this, pos) + } + + suspend fun refreshPositions() { + if (dirty) { + tracker.refreshPositions(this) + dirty = false + } + requireNotNull(structure.networkWorker).debugRender(this) + } + + override val blockIterator: Iterator + get() = tracker.blockIterator + + override fun clearArea() { + clearSchedules() + blockIterator.forEach { + world.setBlockNoPP(it.blockPos(origin), Blocks.AIR.defaultState, Block.NOTIFY_LISTENERS) + } + blocks.keys.forEach { + world.setBlockNoPP(it.blockPos(origin), Blocks.AIR.defaultState, Block.NOTIFY_LISTENERS) + } + entities.forEach { + (world as? ClientWorld)?.entityLookup?.get(it.key)?.discard() + (world as? ServerWorld)?.getEntity(it.key)?.discard() + } + } + + override fun paste() { + blocks.forEach { (pos, state) -> + world.setBlockNoPP(pos.blockPos(origin), state, Block.NOTIFY_LISTENERS) + } + blockEntities.forEach { (pos, nbt) -> + val be = (blocks[pos]?.block as? BlockEntityProvider)?.createBlockEntity(pos.blockPos(origin), blocks[pos]) + ?.apply { + readNbt(nbt) + markDirty() + } ?: redenError("Failed to load block entity") + world.addBlockEntity(be) + } + blocks.keys.forEach { + world.markDirty(it.blockPos(origin)) + } + entities.forEach { + (world as? ServerWorld)?.getEntity(it.key)?.discard() + val entity = EntityType.getEntityFromNbt(it.value, world).get() + world.spawnEntity(entity) + entity.refreshPositionAndAngles( + entity.x + origin.x, + entity.y + origin.y, + entity.z + origin.z, + entity.yaw, + entity.pitch + ) + if (world is ServerWorld) { + (entity as? MobEntity)?.initialize( + world as ServerWorld, + world.getLocalDifficulty(entity.blockPos), + SpawnReason.STRUCTURE, + null, + it.value + ) + (world as ServerWorld).spawnEntityAndPassengers(entity) + } + else { + world.spawnEntity(entity) + } + } + // todo + } + + override fun blockBox(): BlockBox { + if (blocks.isEmpty()) { + return BlockBox(BlockPos.ORIGIN) + } + return BlockBox( + origin.x + minX, + origin.y + minY, + origin.z + minZ, + origin.x + minX + xSize, + origin.y + minY + ySize, + origin.z + minZ + zSize + ) + } + + fun clearSchedules() { + blockIterator.forEach { relative -> + val pos = relative.blockPos(origin) + (world as? ServerWorld)?.run { + syncedBlockEventQueue.removeIf { it.pos == pos } + val blockTickScheduler = getChunk(pos).blockTickScheduler as ChunkTickScheduler + val fluidTickScheduler = getChunk(pos).fluidTickScheduler as ChunkTickScheduler + blockTickScheduler.removeTicksIf { it.pos == pos } + fluidTickScheduler.removeTicksIf { it.pos == pos } + } + } + } + + fun collectSchedules() { + blockEvents.clear() + blockScheduledTicks.clear() + fluidScheduledTicks.clear() + + (world as? ServerWorld)?.run { + blockEvents.addAll(syncedBlockEventQueue.filter { isInArea(getRelativeCoordinate(it.pos)) }.map { + BlockEventInfo( + pos = getRelativeCoordinate(it.pos), + block = it.block, + type = it.type, + data = it.data + ) + }) + val chunks = blockIterator.asSequence().map { it.blockPos(origin) } + .map(ChunkPos::toLong) + .toList().distinct() + .map { getChunk(ChunkPos.getPackedX(it), ChunkPos.getPackedZ(it)) } + val blockTickSchedulers = chunks.asSequence().map { it.blockTickScheduler as ChunkTickScheduler } + val fluidTickSchedulers = chunks.asSequence().map { it.fluidTickScheduler as ChunkTickScheduler } + blockScheduledTicks.addAll(blockTickSchedulers + .flatMap { it.queuedTicks.filter { isInArea(getRelativeCoordinate(it.pos)) } } + .map { + @Suppress("UNCHECKED_CAST") + TickInfo.wrap(it, world) as TickInfo + } + ) + fluidScheduledTicks.addAll(fluidTickSchedulers + .flatMap { it.queuedTicks.filter { isInArea(getRelativeCoordinate(it.pos)) } } + .map { + @Suppress("UNCHECKED_CAST") + TickInfo.wrap(it, world) as TickInfo + } + ) + } + } + + @Contract(pure = true) + fun getRelativeCoordinate(pos: BlockPos): RelativeCoordinate { + return RelativeCoordinate(pos.x - origin.x, pos.y - origin.y, pos.z - origin.z) + } + + suspend fun collectAllFromWorld() { + blocks.clear() + blockEntities.clear() + entities.clear() + refreshPositions() + collectSchedules() + val minPos = BlockPos.Mutable() + val maxPos = BlockPos.Mutable() + blockIterator.forEach { pos -> + if (pos.x < minPos.x) minPos.x = pos.x + if (pos.y < minPos.y) minPos.y = pos.y + if (pos.z < minPos.z) minPos.z = pos.z + if (pos.x > maxPos.x) maxPos.x = pos.x + if (pos.y > maxPos.y) maxPos.y = pos.y + if (pos.z > maxPos.z) maxPos.z = pos.z + val state = world.getBlockState(pos.blockPos(origin)) + val beData = world.getBlockEntity(pos.blockPos(origin))?.createNbtWithId() + blocks[pos] = state + if (beData != null) blockEntities[pos] = beData + } + xSize = maxPos.x - minPos.x + 1 + ySize = maxPos.y - minPos.y + 1 + zSize = maxPos.z - minPos.z + 1 + world.getNonSpectatingEntities(Entity::class.java, Box.enclosing(minPos, maxPos)).asSequence() + .filter { + it !is PlayerEntity + }.forEach { + it.refreshPositionAndAngles( + it.x - origin.x, + it.y - origin.y, + it.z - origin.z, + it.yaw, + it.pitch + ) + entities[it.uuid] = NbtCompound().apply(it::saveSelfNbt) + it.refreshPositionAndAngles( + it.x + origin.x, + it.y + origin.y, + it.z + origin.z, + it.yaw, + it.pitch + ) + } + } +} diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/client/ClientTracking.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/client/ClientTracking.kt index 296559e5..f692146b 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/client/ClientTracking.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/client/ClientTracking.kt @@ -1,14 +1,16 @@ package com.github.zly2006.reden.rvc.tracking.client import com.github.zly2006.reden.rvc.gui.selectedStructure +import com.github.zly2006.reden.rvc.tracking.StructureTracker +import com.github.zly2006.reden.rvc.tracking.TrackPoint import com.github.zly2006.reden.rvc.tracking.TrackPredicate -import com.github.zly2006.reden.rvc.tracking.TrackedStructure import com.github.zly2006.reden.utils.holdingToolItem import fi.dy.masa.malilib.event.InputEventHandler import fi.dy.masa.malilib.hotkeys.IMouseInputHandler import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job import net.minecraft.client.MinecraftClient +import net.minecraft.text.Text import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.hit.HitResult import org.lwjgl.glfw.GLFW @@ -27,27 +29,45 @@ fun registerSelectionTool() { val blockResult = raycast as BlockHitResult if (selectedStructure != null && selectedStructure!!.placementInfo != null) { val structure = selectedStructure!! - structure.networkWorker?.async { - if (eventButton == GLFW.GLFW_MOUSE_BUTTON_LEFT) { - structure.addTrackPoint( - TrackedStructure.TrackPoint( - structure.getRelativeCoordinate(blockResult.blockPos), - TrackPredicate.QC, - TrackPredicate.TrackMode.TRACK - ) - ) - } - else { - structure.addTrackPoint( - TrackedStructure.TrackPoint( - structure.getRelativeCoordinate(blockResult.blockPos), - TrackPredicate.Same, - TrackPredicate.TrackMode.IGNORE, - ) - ) + structure.networkWorker?.launch { + val region = structure.regions.values.first() + when (val tracker = region.tracker) { + is StructureTracker.Trackpoint -> { + if (eventButton == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + tracker.addTrackPoint( + TrackPoint( + region.getRelativeCoordinate(blockResult.blockPos), + TrackPredicate.QC, + TrackPredicate.TrackMode.TRACK + ) + ) + } + else { + tracker.addTrackPoint( + TrackPoint( + region.getRelativeCoordinate(blockResult.blockPos), + TrackPredicate.Same, + TrackPredicate.TrackMode.IGNORE, + ) + ) + } + } + + is StructureTracker.Cuboid -> { + if (eventButton == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + tracker.first = blockResult.blockPos + mc.player?.sendMessage(Text.literal("First point set"), true) + } + else { + tracker.second = blockResult.blockPos + mc.player?.sendMessage(Text.literal("Second point set"), true) + } + } + + is StructureTracker.Entire -> {} } structure.refreshPositions() - }?.ignore() + } } } return true diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/network/ClientNetworkWorker.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/network/ClientNetworkWorker.kt index 46bbd5a1..facce308 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/network/ClientNetworkWorker.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/network/ClientNetworkWorker.kt @@ -3,8 +3,10 @@ package com.github.zly2006.reden.rvc.tracking.network import com.github.zly2006.reden.access.PlayerData import com.github.zly2006.reden.render.BlockBorder import com.github.zly2006.reden.render.BlockOutline +import com.github.zly2006.reden.rvc.tracking.StructureTracker import com.github.zly2006.reden.rvc.tracking.TrackedStructure -import kotlinx.coroutines.* +import com.github.zly2006.reden.rvc.tracking.TrackedStructurePart +import kotlinx.coroutines.asCoroutineDispatcher import net.minecraft.client.MinecraftClient import net.minecraft.util.math.BlockPos import net.minecraft.world.World @@ -13,8 +15,13 @@ open class ClientNetworkWorker( override val structure: TrackedStructure, override val world: World ) : NetworkWorker { - var renderPositions = listOf() - override suspend fun debugRender() = execute { + private var renderPositions = listOf() + override fun trackpointUpdated(part: TrackedStructurePart) { + if (part.tracker is StructureTracker.Trackpoint) { + renderPositions = part.tracker.cachedPositions.keys.toList() + } + } + override suspend fun debugRender(part: TrackedStructurePart) = execute { BlockOutline.blocks = mapOf() BlockBorder.tags = mapOf() BlockOutline.blocks = renderPositions.mapNotNull { @@ -22,28 +29,19 @@ open class ClientNetworkWorker( it to world.getBlockState(it) else null }.toMap() - structure.trackPoints.forEach { - if (!world.isAir(it.pos)) - BlockBorder[it.pos] = if (it.mode.isTrack()) 1 else 2 + if (part.tracker is StructureTracker.Trackpoint) { + part.tracker.trackpoints.forEach { + if (!world.isAir(it.pos)) + BlockBorder[it.pos] = if (it.mode.isTrack()) 1 else 2 + } } } - override suspend fun startUndoRecord(cause: PlayerData.UndoRecord.Cause) { - TODO("Not yet implemented") - } - - override suspend fun stopUndoRecord() { + override suspend fun startUndoRecord(cause: PlayerData.UndoRecord.Cause) {} + override suspend fun stopUndoRecord() {} + override suspend fun paste(part: TrackedStructurePart) { TODO("Not yet implemented") } - override suspend fun paste() { - TODO("Not yet implemented") - } - - override suspend fun execute(function: suspend () -> T): T = - withContext(MinecraftClient.getInstance().asCoroutineDispatcher()) { function() } - - @OptIn(DelicateCoroutinesApi::class) - override fun async(function: suspend () -> T) = - GlobalScope.async(MinecraftClient.getInstance().asCoroutineDispatcher()) { function() } + override val coroutineDispatcher = MinecraftClient.getInstance().asCoroutineDispatcher() } diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/network/LocalNetworkWorker.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/network/LocalNetworkWorker.kt index 131ece11..b212a9e0 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/network/LocalNetworkWorker.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/network/LocalNetworkWorker.kt @@ -2,6 +2,8 @@ package com.github.zly2006.reden.rvc.tracking.network import com.github.zly2006.reden.access.PlayerData import com.github.zly2006.reden.rvc.tracking.TrackedStructure +import com.github.zly2006.reden.rvc.tracking.TrackedStructurePart +import kotlinx.coroutines.CoroutineDispatcher import net.minecraft.server.world.ServerWorld import net.minecraft.world.World @@ -14,13 +16,10 @@ class LocalNetworkWorker( private val player = world.server.playerManager.getPlayer(world.server.hostProfile!!.id)!! private val serverWorker = ServerNetworkWorker(structure, world, player) - override suspend fun refreshPositions() = execute { - serverWorker.refreshPositions() - clientWorker.renderPositions = structure.cachedPositions.keys.toList() - } + override fun trackpointUpdated(part: TrackedStructurePart) = clientWorker.trackpointUpdated(part) - override suspend fun debugRender() = execute { - clientWorker.debugRender() + override suspend fun debugRender(part: TrackedStructurePart) = execute { + clientWorker.debugRender(part) } override suspend fun startUndoRecord(cause: PlayerData.UndoRecord.Cause) = execute { @@ -31,11 +30,9 @@ class LocalNetworkWorker( serverWorker.stopUndoRecord() } - override suspend fun paste() = execute { - serverWorker.paste() + override suspend fun paste(part: TrackedStructurePart) = execute { + serverWorker.paste(part) } - override suspend fun execute(function: suspend () -> T) = serverWorker.execute(function) - - override fun async(function: suspend () -> T) = serverWorker.async(function) + override val coroutineDispatcher: CoroutineDispatcher get() = serverWorker.coroutineDispatcher } diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/network/NetworkWorker.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/network/NetworkWorker.kt index 8ee93319..b64f26f0 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/network/NetworkWorker.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/network/NetworkWorker.kt @@ -1,75 +1,34 @@ package com.github.zly2006.reden.rvc.tracking.network -import com.github.zly2006.reden.Reden import com.github.zly2006.reden.access.PlayerData -import com.github.zly2006.reden.rvc.tracking.TrackPredicate +import com.github.zly2006.reden.rvc.tracking.StructureTracker import com.github.zly2006.reden.rvc.tracking.TrackedStructure -import kotlinx.coroutines.Deferred -import net.minecraft.util.math.BlockPos +import com.github.zly2006.reden.rvc.tracking.TrackedStructurePart +import kotlinx.coroutines.* import net.minecraft.world.World -import java.util.* interface NetworkWorker { - suspend fun debugRender() + suspend fun debugRender(part: TrackedStructurePart) val structure: TrackedStructure val world: World - suspend fun refreshPositions() { + suspend fun refreshPositions(part: TrackedStructurePart) { + if (part.tracker !is StructureTracker.Trackpoint) return val timeStart = System.currentTimeMillis() - val readPos = hashSetOf() - structure.trackPoints.filter { it.mode == TrackPredicate.TrackMode.IGNORE }.forEach { trackPoint -> - // first, add all blocks recursively - val queue = LinkedList() - queue.add(trackPoint) - var maxElements = trackPoint.maxElements - while (queue.isNotEmpty() && maxElements > 0) { - maxElements-- - val entry = queue.removeFirst() - if (entry.pos in structure.cachedIgnoredPositions) continue - if (world.isAir(entry.pos)) { - continue - } - structure.cachedIgnoredPositions[entry.pos] = trackPoint - entry.spreadAround(world, { newPos -> - if (readPos.add(newPos)) { - queue.add(TrackedStructure.SpreadEntry(newPos, entry.predicate, trackPoint.mode, structure)) - } - }) - } - } - structure.trackPoints.filter { it.mode == TrackPredicate.TrackMode.TRACK }.forEach { trackPoint -> - // first, add all blocks recursively - val queue = LinkedList() - queue.add(trackPoint) - var maxElements = trackPoint.maxElements - while (queue.isNotEmpty() && maxElements > 0) { - maxElements-- - val entry = queue.removeFirst() - if (entry.pos in structure.cachedIgnoredPositions) continue - if (world.isAir(entry.pos)) { - continue - } - if (!trackPoint.pos.isWithinDistance(entry.pos, 200.0)) { - Reden.LOGGER.error("Track point ${trackPoint.pos} is too far away from ${entry.pos}") - continue - } - entry.spreadAround(world, { newPos -> - if (readPos.add(newPos)) { - if (newPos in structure.cachedPositions) return@spreadAround - if (!world.isAir(newPos)) { - structure.cachedPositions[newPos] = trackPoint - } - queue.add(TrackedStructure.SpreadEntry(newPos, entry.predicate, trackPoint.mode, structure)) - } - }) - } - } val timeEnd = System.currentTimeMillis() println("${this::class.java.simpleName}#refreshPositions: ${timeEnd - timeStart}ms") } suspend fun startUndoRecord(cause: PlayerData.UndoRecord.Cause) suspend fun stopUndoRecord() - suspend fun paste() - suspend fun execute(function: suspend () -> T): T - fun async(function: suspend () -> T): Deferred + suspend fun paste(part: TrackedStructurePart) + val coroutineDispatcher: CoroutineDispatcher + suspend fun execute(function: suspend CoroutineScope.() -> T): T = + withContext(coroutineDispatcher, block = function) + + @OptIn(DelicateCoroutinesApi::class) + fun async(function: suspend CoroutineScope.() -> T) = GlobalScope.async(coroutineDispatcher, block = function) + + @OptIn(DelicateCoroutinesApi::class) + fun launch(function: suspend CoroutineScope.() -> Unit) = GlobalScope.launch(coroutineDispatcher, block = function) + fun trackpointUpdated(part: TrackedStructurePart) {} } diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/network/ServerNetworkWorker.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/network/ServerNetworkWorker.kt index 4eaceb2b..58ae5b19 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/network/ServerNetworkWorker.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/network/ServerNetworkWorker.kt @@ -4,7 +4,9 @@ import com.github.zly2006.reden.access.PlayerData import com.github.zly2006.reden.access.WorldData.Companion.data import com.github.zly2006.reden.mixinhelper.UpdateMonitorHelper import com.github.zly2006.reden.rvc.tracking.TrackedStructure -import kotlinx.coroutines.* +import com.github.zly2006.reden.rvc.tracking.TrackedStructurePart +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.asCoroutineDispatcher import net.minecraft.server.network.ServerPlayerEntity import net.minecraft.server.world.ServerWorld @@ -15,7 +17,7 @@ class ServerNetworkWorker( override val world: ServerWorld, val owner: ServerPlayerEntity ) : NetworkWorker { - override suspend fun debugRender() { /* NOOP */ + override suspend fun debugRender(part: TrackedStructurePart) { /* NOOP */ } override suspend fun startUndoRecord(cause: PlayerData.UndoRecord.Cause) { @@ -28,17 +30,12 @@ class ServerNetworkWorker( UpdateMonitorHelper.playerStopRecording(owner) } - override suspend fun paste() { + override suspend fun paste(part: TrackedStructurePart) { require(world.server.isOnThread) { OFF_THREAD_MESSAGE } structure.world.data!!.updatesDisabled = true structure.paste() structure.world.data!!.updatesDisabled = false } - override suspend fun execute(function: suspend () -> T): T = - withContext(world.server.asCoroutineDispatcher()) { function() } - - @OptIn(DelicateCoroutinesApi::class) - override fun async(function: suspend () -> T) = - GlobalScope.async(world.server.asCoroutineDispatcher()) { function() } + override val coroutineDispatcher: CoroutineDispatcher = world.server.asCoroutineDispatcher() } diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/reader/RvcReaderV1.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/reader/RvcReaderV1.kt index a5423629..911162a9 100644 --- a/src/main/java/com/github/zly2006/reden/rvc/tracking/reader/RvcReaderV1.kt +++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/reader/RvcReaderV1.kt @@ -3,10 +3,7 @@ package com.github.zly2006.reden.rvc.tracking.reader import com.github.zly2006.reden.Reden import com.github.zly2006.reden.rvc.RelativeCoordinate import com.github.zly2006.reden.rvc.io.Palette -import com.github.zly2006.reden.rvc.tracking.IRvcFileReader -import com.github.zly2006.reden.rvc.tracking.RvcDataReader -import com.github.zly2006.reden.rvc.tracking.TrackPredicate -import com.github.zly2006.reden.rvc.tracking.TrackedStructure +import com.github.zly2006.reden.rvc.tracking.* import net.minecraft.block.BlockState import net.minecraft.command.argument.BlockArgumentParser import net.minecraft.nbt.NbtCompound @@ -63,17 +60,18 @@ class RvcReaderV1( return entities } - override fun readTrackPointData( + @Deprecated("#101") + fun readTrackPointData( data: List - ): List { - val trackPoints = mutableListOf() + ): List { + val trackPoints = mutableListOf() data.forEach { val rvcData = RvcDataReader(it, ",") val blockPos = RelativeCoordinate(rvcData.next().toInt(), rvcData.next().toInt(), rvcData.next().toInt()) val predicate = rvcData.next() val mode = rvcData.next() trackPoints.add( - TrackedStructure.TrackPoint( + TrackPoint( relativeCoordinate = blockPos, predicate = TrackPredicate.valueOf(predicate), mode = TrackPredicate.TrackMode.valueOf(mode) @@ -83,8 +81,8 @@ class RvcReaderV1( return trackPoints } - override fun readBlockEventsData(data: List): List { - val blockEvents = mutableListOf() + override fun readBlockEventsData(data: List): List { + val blockEvents = mutableListOf() data.forEach { val rvcData = RvcDataReader(it, ",") val relativeCoordinate = @@ -93,7 +91,7 @@ class RvcReaderV1( val data = rvcData.next().toInt() val block = Registries.BLOCK.get(Identifier(rvcData.readGreedy())) blockEvents.add( - TrackedStructure.BlockEventInfo( + TrackedStructurePart.BlockEventInfo( pos = relativeCoordinate, type = type, data = data, @@ -104,22 +102,17 @@ class RvcReaderV1( return blockEvents } - @Deprecated("Scheduled ticks data is not supported yet.") - override fun readScheduledTicksData(data: List): List { - throw UnsupportedOperationException() - } - override fun readScheduledTicksData( data: List, registry: Registry - ): List> { - val blockTicks = mutableListOf>() + ): List> { + val blockTicks = mutableListOf>() data.forEach { val rvcData = RvcDataReader(it, ",") val relativeCoordinate = RelativeCoordinate(rvcData.next().toInt(), rvcData.next().toInt(), rvcData.next().toInt()) blockTicks.add( - TrackedStructure.TickInfo( + TrackedStructurePart.TickInfo( pos = relativeCoordinate, type = registry.get(Identifier(rvcData.next()))!! as T, delay = rvcData.next().toLong(), diff --git a/src/main/java/com/github/zly2006/reden/utils/richTranslation/RichTranslation.kt b/src/main/java/com/github/zly2006/reden/utils/richTranslation/RichTranslation.kt new file mode 100644 index 00000000..f1b34562 --- /dev/null +++ b/src/main/java/com/github/zly2006/reden/utils/richTranslation/RichTranslation.kt @@ -0,0 +1,83 @@ +package com.github.zly2006.reden.utils.richTranslation + +import com.github.zly2006.reden.access.TranslationStorageAccess +import net.minecraft.text.MutableText +import net.minecraft.text.PlainTextContent +import net.minecraft.text.Text +import net.minecraft.util.Language + +/** + * Note: + * + out rich translation is inspired by owo lib, and we did not do some work to skip vanilla processing, thus, this + * function depends on owo, although it is entirely different form the owo method. + * + not implemented all specifications, %s and %d etc are all accepted + * + use %s for sequence format, and %1$s for index format + * + */ +private enum class VisitMode { + Index, + Sequence, + Unknown +} + +private val indexFormatRegex = Regex("""%[0-9]+\$[a-zA-Z]""") +private val sequenceFormatRegex = Regex("""%[a-zA-Z]""") + +private fun visitLiteral(text: MutableText, mode: VisitMode, args: MutableList): MutableText { + val literal = (text.content as PlainTextContent.Literal).string + var detectedMode = mode + if (detectedMode == VisitMode.Unknown) detectedMode = + if (indexFormatRegex.containsMatchIn(literal)) VisitMode.Index + else if (sequenceFormatRegex.containsMatchIn(literal)) VisitMode.Sequence + else VisitMode.Unknown + return when (detectedMode) { + VisitMode.Index -> { + require(!sequenceFormatRegex.containsMatchIn(literal)) { + "Cannot mix index and sequence format in the same text" + } + val strings = indexFormatRegex.split(literal) + val matches = indexFormatRegex.findAll(literal).map { + it.value.substringBefore("$").substringAfter("%").toIntOrNull() + } + val newText = Text.literal(strings[0]).setStyle(text.style) + matches.forEachIndexed { index, i -> + val value = i?.let { args.getOrNull(it - 1) } ?: "" + newText.append(value.toString()) + newText.append(strings[index + 1]) + } + newText + } + + VisitMode.Sequence -> { + require(!indexFormatRegex.containsMatchIn(literal)) { + "Cannot mix index and sequence format in the same text" + } + val strings = sequenceFormatRegex.split(literal) + val newText = Text.literal(strings[0]).setStyle(text.style) + strings.drop(1).forEach { + newText.append(args.removeFirst().toString()) + newText.append(it) + } + newText + } + + VisitMode.Unknown -> Text.literal(literal).setStyle(text.style) + } +} + +private fun visitContent(text: MutableText, args: MutableList, mode: VisitMode): MutableText { + val mutableText = + if (text.content is PlainTextContent.Literal) visitLiteral(text, mode, args) + else text.copy() + text.siblings.forEach { + mutableText.append(visitContent(it.copy(), args, mode)) + } + return mutableText +} + +fun processTranslate(language: Language, key: String, args: Array): Text? { + val copy = (language as? TranslationStorageAccess?)?.textMap?.get(key)?.copy() + ?: return null + val argList = args.toMutableList() + return visitContent(copy, argList, VisitMode.Unknown) +} diff --git a/src/main/resources/assets/reden/lang/en_us.yml b/src/main/resources/assets/reden/lang/en_us.yml index 6d5187c0..e8dbca86 100644 --- a/src/main/resources/assets/reden/lang/en_us.yml +++ b/src/main/resources/assets/reden/lang/en_us.yml @@ -71,9 +71,11 @@ reden: selected: "Selected: %s" selected_nothing: [ { text: "Selected: Nothing", color: red } ] trackpoints: - .: "Trackpoints: " - tracking: "%s Tracking" - ignoring: "%s Ignoring" + - text: "Trackpoints: " + - text: "%s Tracking " + color: green + - text: "%s Ignoring " + color: red structure: export: litematica: Export to Litematica diff --git a/src/main/resources/assets/reden/lang/zh_cn.yml b/src/main/resources/assets/reden/lang/zh_cn.yml index a741463f..810a42d4 100644 --- a/src/main/resources/assets/reden/lang/zh_cn.yml +++ b/src/main/resources/assets/reden/lang/zh_cn.yml @@ -41,9 +41,11 @@ reden: selected: "已选择:%s" selected_nothing: "已选择:无" trackpoints: - .: "追踪点数:" - tracking: "%s 追踪中" - ignoring: "%s 忽略中" + - text: "追踪点 " + - text: "追踪中: %s " + color: green + - text: "忽略中: %s " + color: red structure: export: litematica: 导出至 Litematica diff --git a/src/main/resources/reden.mixins.json b/src/main/resources/reden.mixins.json index 7083c58c..eab25d1c 100644 --- a/src/main/resources/reden.mixins.json +++ b/src/main/resources/reden.mixins.json @@ -20,7 +20,6 @@ "debugger.MixinServerPlayNetwork", "debugger.MixinServerWorld", "debugger.MixinWorld", - "debugger.MixinWorldTickScheduler", "debugger.crash.MixinSystemDetails", "debugger.events.MixinChunk", "debugger.network.MixinNetworkIo", @@ -44,6 +43,7 @@ "realFakePlayer.MixinCarpetFakePlayer", "realFakePlayer.MixinEntityPlayerActionPack", "realFakePlayer.MixinServerNetworkIo", + "richTranslation.MixinTranslatable", "sctuctureBlock.MixinStructureBlock", "sctuctureBlock.MixinStructureBlockEntity", "undo.MixinBlockEntity", @@ -65,7 +65,7 @@ "undo.MixinServerWorld", "undo.MixinServerWorldChunk", "undo.MixinTntEntity", - "undo.data.MixinChunkSection", + "undo.data.MixinChunkSection" ], "client": [ "MixinClientMain", @@ -92,6 +92,7 @@ "render.MixinBlockModelRenderer", "render.MixinMinecraftClient", "render.MixinWorldRenderer", + "richTranslation.MixinTranslationStorage", "rvc.MixinWorldChunk", "superRight.block.MixinClientPlayerInteractionManager", "superRight.chat.ChatHudMixin", diff --git a/src/test/kotlin/com/github/zly2006/reden/LitematicaIOTest.kt b/src/test/kotlin/com/github/zly2006/reden/LitematicaIOTest.kt index d9cd363b..f6c00cbf 100644 --- a/src/test/kotlin/com/github/zly2006/reden/LitematicaIOTest.kt +++ b/src/test/kotlin/com/github/zly2006/reden/LitematicaIOTest.kt @@ -41,14 +41,14 @@ class LitematicaIOTest { val structure = TrackedStructure("test", repository) LitematicaIO.load(file, structure) RvcFileIO.save(file.parent, structure) - structure.blockEntities.keys.map { structure.blocks[it] }.forEach { + structure.regions[""]!!.blockEntities.keys.map { structure.regions[""]!!.blocks[it] }.forEach { assert(it!!.hasBlockEntity()) { "Expected block with entity, got $it" } } - assert(structure.blocks.size == litematica.metadata.totalBlocks) { - "Expected ${litematica.metadata.totalBlocks} blocks, got ${structure.blocks.size}" + assert(structure.totalBlocks == litematica.metadata.totalBlocks) { + "Expected ${litematica.metadata.totalBlocks} blocks, got ${structure.totalBlocks}" } }