├── jitpack.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── resources │ ├── assets │ │ └── syncmatica │ │ │ ├── icon │ │ │ └── logo.png │ │ │ └── lang │ │ │ ├── zh_cn.json │ │ │ ├── zh_tw.json │ │ │ ├── ko_kr.json │ │ │ ├── en_us.json │ │ │ ├── ru_ru.json │ │ │ └── uk_ua.json │ ├── syncmatica.litematica_mixin.json │ ├── syncmatica.mixin.json │ └── fabric.mod.json │ └── java │ └── ch │ └── endte │ └── syncmatica │ ├── communication │ ├── MessageType.java │ ├── exchange │ │ ├── FeatureExchange.java │ │ ├── Exchange.java │ │ ├── AbstractExchange.java │ │ ├── ModifyExchangeServer.java │ │ ├── VersionHandshakeServer.java │ │ ├── VersionHandshakeClient.java │ │ ├── ShareLitematicExchange.java │ │ ├── UploadExchange.java │ │ ├── DownloadExchange.java │ │ └── ModifyExchangeClient.java │ ├── FeatureSet.java │ ├── ExchangeTarget.java │ └── ClientCommunicationManager.java │ ├── litematica │ ├── IIDContainer.java │ ├── gui │ │ ├── IGuiBase.java │ │ ├── IButtonType.java │ │ ├── MainMenuButtonType.java │ │ ├── ButtonListenerChangeMenu.java │ │ ├── BaseButtonType.java │ │ ├── GuiSyncmaticaServerPlacementList.java │ │ ├── ButtonListenerShare.java │ │ └── MultiTypeButton.java │ ├── MovingFinisher.java │ ├── schematic │ │ ├── SchematicSchema.java │ │ └── FileType.java │ ├── ScreenHelper.java │ └── PlacementEventHandler.java │ ├── litematica_mixin │ ├── MixinSchematicPlacementManager.java │ ├── MixinButtonBase.java │ ├── MixinSchematicHolder.java │ ├── MixinSubregionPlacement.java │ ├── MixinGuiBase.java │ ├── MixinWidgetListSchematicPlacement.java │ ├── MixinGuiMainMenu.java │ ├── MixinSchematicPlacement.java │ ├── MixinWidgetSchematicPlacement.java │ └── MixinGuiPlacementConfiguration.java │ ├── ModInit.java │ ├── data │ ├── IFileStorage.java │ ├── LocalLitematicState.java │ ├── ServerPosition.java │ ├── RedirectFileStorage.java │ └── FileStorage.java │ ├── service │ ├── AbstractService.java │ ├── IServiceConfiguration.java │ ├── IService.java │ ├── DebugService.java │ ├── JsonConfiguration.java │ └── QuotaService.java │ ├── network │ ├── actor │ │ ├── IServerPlay.java │ │ ├── IClientPlay.java │ │ └── ActorClientPlayHandler.java │ ├── SyncmaticaPacket.java │ ├── handler │ │ ├── ServerPlayHandler.java │ │ └── ClientPlayHandler.java │ └── PacketType.java │ ├── material │ ├── DeliveryPosition.java │ ├── SyncmaticaMaterialList.java │ └── SyncmaticaMaterialEntry.java │ ├── command │ ├── IServerCommand.java │ └── PermsWrap.java │ ├── util │ └── StringTools.java │ ├── Feature.java │ ├── extended_core │ ├── PlayerIdentifier.java │ ├── PlayerIdentifierProvider.java │ ├── SubRegionPlacementModification.java │ └── SubRegionData.java │ ├── mixin │ ├── MixinServerCommonNetworkHandler.java │ ├── MixinIntegratedServer.java │ ├── MixinClientCommonNetworkHandler.java │ ├── MixinCommandManager.java │ ├── MixinMinecraftClient.java │ ├── MixinPlayerManager.java │ ├── MixinMinecraftServer.java │ ├── MixinClientPlayNetworkHandler.java │ └── MixinServerPlayNetworkHandler.java │ ├── Reference.java │ └── Syncmatica.java ├── settings.gradle ├── .gitignore ├── gradle.properties ├── .github └── workflows │ └── build.yml ├── CONFIG.md ├── gradlew.bat ├── README.md └── LICENSE /jitpack.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - sdk install java 21.0.9-tem 3 | - sdk use java 21.0.9-tem 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakura-ryoko/syncmatica/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/assets/syncmatica/icon/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakura-ryoko/syncmatica/HEAD/src/main/resources/assets/syncmatica/icon/logo.png -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/MessageType.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication; 2 | 3 | public enum MessageType { 4 | SUCCESS, 5 | INFO, 6 | WARNING, 7 | ERROR 8 | } 9 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/IIDContainer.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica; 2 | 3 | import java.util.UUID; 4 | 5 | public interface IIDContainer { 6 | void syncmatica$setServerId(UUID i); 7 | 8 | UUID syncmatica$getServerId(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/gui/IGuiBase.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica.gui; 2 | 3 | import java.util.List; 4 | 5 | import fi.dy.masa.malilib.gui.button.ButtonBase; 6 | 7 | public interface IGuiBase 8 | { 9 | List getButtons(); 10 | } 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/MovingFinisher.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica; 2 | 3 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacementManager; 4 | 5 | public interface MovingFinisher { 6 | void onFinishedMoving(String subRegionName, SchematicPlacementManager manager); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica_mixin/MixinSchematicPlacementManager.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica_mixin; 2 | 3 | //@Mixin(SchematicPlacementManager.class) 4 | public interface MixinSchematicPlacementManager 5 | { 6 | // @Invoker(value = "onPrePlacementChange", remap = false) 7 | // void preSubregionChange(SchematicPlacement schematicPlacement); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/ModInit.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica; 2 | 3 | import net.fabricmc.api.ModInitializer; 4 | 5 | /** 6 | * This helps Syncmatica init the Channel Registrations at game launch. 7 | */ 8 | public class ModInit implements ModInitializer 9 | { 10 | @Override 11 | public void onInitialize() 12 | { 13 | Syncmatica.preInit(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/data/IFileStorage.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.data; 2 | 3 | import java.nio.file.Path; 4 | 5 | import ch.endte.syncmatica.Context; 6 | 7 | public interface IFileStorage 8 | { 9 | LocalLitematicState getLocalState(ServerPlacement placement); 10 | 11 | Path createLocalLitematic(ServerPlacement placement); 12 | 13 | Path getLocalLitematic(ServerPlacement placement); 14 | 15 | void setContext(Context con); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/service/AbstractService.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.service; 2 | 3 | import ch.endte.syncmatica.Context; 4 | 5 | abstract class AbstractService implements IService { 6 | 7 | Context context; 8 | 9 | @Override 10 | public void setContext(final Context context) { 11 | this.context = context; 12 | } 13 | 14 | @Override 15 | public Context getContext() { 16 | return context; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # local buildscript 2 | buildAndTest.sh 3 | 4 | # logs ? 5 | 6 | logs/ 7 | 8 | # gradle 9 | 10 | .gradle/ 11 | build/ 12 | out/ 13 | classes/ 14 | libs/ 15 | 16 | # dependencies 17 | 18 | dependencies/ 19 | 20 | # eclipse 21 | 22 | *.launch 23 | 24 | # idea 25 | 26 | .idea/ 27 | *.iml 28 | *.ipr 29 | *.iws 30 | 31 | # vscode 32 | 33 | .settings/ 34 | .vscode/ 35 | bin/ 36 | .classpath 37 | .project 38 | 39 | # fabric 40 | 41 | run/ 42 | 43 | # macOS 44 | 45 | .DS_Store -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/network/actor/IServerPlay.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.network.actor; 2 | 3 | import ch.endte.syncmatica.communication.ExchangeTarget; 4 | import ch.endte.syncmatica.communication.ServerCommunicationManager; 5 | 6 | import java.util.function.Consumer; 7 | 8 | public interface IServerPlay 9 | { 10 | void syncmatica$operateComms(final Consumer operation); 11 | 12 | ExchangeTarget syncmatica$getExchangeTarget(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/service/IServiceConfiguration.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.service; 2 | 3 | import java.util.function.Consumer; 4 | import java.util.function.IntConsumer; 5 | 6 | public interface IServiceConfiguration { 7 | 8 | void loadBoolean(String key, Consumer loader); 9 | 10 | void saveBoolean(String key, Boolean value); 11 | 12 | void loadInteger(String key, IntConsumer loader); 13 | 14 | void saveInteger(String key, Integer value); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/service/IService.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.service; 2 | 3 | import ch.endte.syncmatica.Context; 4 | 5 | public interface IService { 6 | 7 | void setContext(Context context); 8 | 9 | Context getContext(); 10 | 11 | void getDefaultConfiguration(IServiceConfiguration configuration); 12 | 13 | String getConfigKey(); 14 | 15 | void configure(IServiceConfiguration configuration); 16 | 17 | void startup(); 18 | 19 | void shutdown(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/schematic/SchematicSchema.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica.schematic; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * Cloned from Litematica 1.21.5 -- Sakura 7 | */ 8 | public record SchematicSchema(int litematicVersion, int minecraftDataVersion) 9 | { 10 | @Override 11 | public @Nonnull String toString() 12 | { 13 | return "V" + this.litematicVersion() + " / DataVersion " + this.minecraftDataVersion(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica_mixin/MixinButtonBase.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica_mixin; 2 | 3 | import fi.dy.masa.malilib.gui.button.ButtonBase; 4 | import fi.dy.masa.malilib.gui.button.IButtonActionListener; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(ButtonBase.class) 9 | public interface MixinButtonBase { 10 | @Accessor(value = "actionListener", remap = false) 11 | IButtonActionListener getActionListener(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica_mixin/MixinSchematicHolder.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica_mixin; 2 | 3 | //@Mixin(SchematicHolder.class) 4 | public abstract class MixinSchematicHolder { 5 | 6 | public MixinSchematicHolder() { 7 | } 8 | 9 | // @Inject(method = "removeSchematic", at = @At("RETURN"), remap = false) 10 | // public void unloadSyncmatic(final LitematicaSchematic schematic, final CallbackInfoReturnable ci) 11 | // { 12 | // LitematicManager.getInstance().unrenderSchematic(schematic); 13 | // } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/material/DeliveryPosition.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.material; 2 | 3 | import ch.endte.syncmatica.data.ServerPosition; 4 | import net.minecraft.core.BlockPos; 5 | 6 | public class DeliveryPosition extends ServerPosition { 7 | 8 | private final int amount; 9 | 10 | public DeliveryPosition(final BlockPos pos, final String dim, final int amount) { 11 | super(pos, dim); 12 | this.amount = amount; 13 | } 14 | 15 | public int getAmount() { 16 | return amount; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/syncmatica.litematica_mixin.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "ch.endte.syncmatica.litematica_mixin", 5 | "compatibilityLevel": "JAVA_21", 6 | "mixins": [], 7 | "client": [ 8 | "MixinGuiMainMenu", 9 | "MixinWidgetListSchematicPlacement", 10 | "MixinWidgetSchematicPlacement", 11 | "MixinGuiBase", 12 | "MixinButtonBase", 13 | "MixinWidgetSchematicPlacement", 14 | "MixinGuiPlacementConfiguration" 15 | ], 16 | "server": [], 17 | "injectors": { 18 | "defaultRequire": 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/command/IServerCommand.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.command; 2 | 3 | import com.mojang.brigadier.CommandDispatcher; 4 | import net.minecraft.commands.CommandBuildContext; 5 | import net.minecraft.commands.CommandSourceStack; 6 | import net.minecraft.commands.Commands; 7 | 8 | public interface IServerCommand 9 | { 10 | /** 11 | * Register a Server Side command 12 | */ 13 | void register(CommandDispatcher dispatcher, 14 | CommandBuildContext registryAccess, 15 | Commands.CommandSelection environment); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/network/actor/IClientPlay.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.network.actor; 2 | 3 | import ch.endte.syncmatica.communication.ClientCommunicationManager; 4 | import ch.endte.syncmatica.communication.ExchangeTarget; 5 | 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * I set up a Client version of this interface in case we need to move the "onCustomPayload" call to "ClientCommonNetworkHandler" 10 | * ... You know, like in case Mojang removes the current method 11 | */ 12 | public interface IClientPlay 13 | { 14 | void syncmatica$operateComms(final Consumer operation); 15 | 16 | ExchangeTarget syncmatica$getExchangeTarget(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/syncmatica.mixin.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "ch.endte.syncmatica.mixin", 5 | "compatibilityLevel": "JAVA_21", 6 | "mixins": [ 7 | "MixinCommandManager", 8 | "MixinMinecraftServer", 9 | "MixinPlayerManager", 10 | "MixinServerCommonNetworkHandler", 11 | "MixinServerPlayNetworkHandler" 12 | ], 13 | "client": [ 14 | "MixinClientCommonNetworkHandler", 15 | "MixinClientPlayNetworkHandler", 16 | "MixinIntegratedServer", 17 | "MixinMinecraftClient" 18 | ], 19 | "server": [ 20 | ], 21 | "injectors": { 22 | "defaultRequire": 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica_mixin/MixinSubregionPlacement.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica_mixin; 2 | 3 | //@Mixin(SubRegionPlacement.class) 4 | public interface MixinSubregionPlacement { 5 | // @Accessor(value = "defaultPos", remap = false) 6 | // BlockPos getDefaultPosition(); 7 | // 8 | // @Accessor(value = "pos", remap = false) 9 | // void setBlockPosition(BlockPos pos); 10 | // 11 | // @Accessor(value = "rotation", remap = false) 12 | // void setBlockRotation(BlockRotation pos); 13 | // 14 | // @Accessor(value = "mirror", remap = false) 15 | // void setBlockMirror(BlockMirror pos); 16 | // 17 | // @Invoker(value = "resetToOriginalValues", remap = false) 18 | // void reset(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica_mixin/MixinGuiBase.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica_mixin; 2 | 3 | import ch.endte.syncmatica.litematica.gui.IGuiBase; 4 | import fi.dy.masa.malilib.gui.GuiBase; 5 | import fi.dy.masa.malilib.gui.button.ButtonBase; 6 | import org.spongepowered.asm.mixin.Final; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | 10 | import java.util.List; 11 | 12 | @Mixin(GuiBase.class) 13 | public abstract class MixinGuiBase implements IGuiBase { 14 | 15 | @Final 16 | @Shadow(remap = false) 17 | private List buttons; 18 | 19 | @Override 20 | public List getButtons() { 21 | return buttons; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle Properties 2 | org.gradle.jvmargs = -Xmx3G 3 | org.gradle.daemon = false 4 | org.gradle.cache.cleanup = false 5 | 6 | # Mod Properties 7 | maven_group = ch.endte 8 | mod_id = syncmatica 9 | mod_name = Syncmatica 10 | mod_file_name = syncmatica-fabric 11 | 12 | # Mod Version 13 | mod_version = 0.3.16 14 | 15 | # Dependencies (malilib / litematica) 16 | malilib_version = 28fd4dd03a 17 | litematica_version = f735104983 18 | 19 | # Minecraft, Fabric Loader and API and mappings versions 20 | minecraft_version_out = 1.21.11 21 | minecraft_version = 1.21.11 22 | mappings_version = 1.21.11+build.1 23 | 24 | fabric_loader_version = 0.18.2 25 | mod_menu_version = 17.0.0-alpha.1 26 | fabric_api_version = 0.139.4+1.21.11 27 | 28 | # Luck Permissions API 29 | fabric_permissions_api_version = 0.6.1 30 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/command/PermsWrap.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.command; 2 | 3 | import java.util.function.Predicate; 4 | import javax.annotation.Nonnull; 5 | import me.lucko.fabric.api.permissions.v0.Permissions; 6 | 7 | import net.minecraft.commands.CommandSourceStack; 8 | import net.minecraft.server.permissions.PermissionLevel; 9 | import net.minecraft.util.Mth; 10 | 11 | public class PermsWrap 12 | { 13 | public static Predicate check(@Nonnull String node, PermissionLevel level) 14 | { 15 | return Permissions.require(node, level); 16 | } 17 | 18 | public static Predicate check(@Nonnull String node, int level) 19 | { 20 | return Permissions.require(node, PermissionLevel.byId(Mth.clamp(level, 0, PermissionLevel.OWNERS.id()))); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/data/LocalLitematicState.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.data; 2 | 3 | public enum LocalLitematicState 4 | { 5 | NO_LOCAL_LITEMATIC(true, false), 6 | LOCAL_LITEMATIC_DESYNC(true, false), 7 | DOWNLOADING_LITEMATIC(false, false), 8 | LOCAL_LITEMATIC_PRESENT(false, true); 9 | 10 | private final boolean downloadReady; 11 | private final boolean fileReady; 12 | 13 | LocalLitematicState(final boolean downloadReady, final boolean fileReady) 14 | { 15 | this.downloadReady = downloadReady; 16 | this.fileReady = fileReady; 17 | } 18 | 19 | public boolean isReadyForDownload() 20 | { 21 | return downloadReady; 22 | } 23 | 24 | public boolean isLocalFileReady() 25 | { 26 | return fileReady; 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/gui/IButtonType.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica.gui; 2 | 3 | import java.util.List; 4 | 5 | import fi.dy.masa.malilib.gui.button.IButtonActionListener; 6 | import fi.dy.masa.malilib.gui.interfaces.IGuiIcon; 7 | 8 | // Represents a type of button 9 | // e.g. the onClickListener / whether it is active or not 10 | // its text and icon 11 | 12 | public interface IButtonType 13 | { 14 | IGuiIcon getIcon(); 15 | 16 | // returns the key when it is already translated 17 | // allows for the most freedom in the implementation 18 | String getTranslatedKey(); 19 | 20 | List getHoverStrings(); 21 | 22 | IButtonActionListener getButtonListener(); 23 | 24 | // returns whether a button type is active or not 25 | // for MultiTypeButtons this check essentially checks which type is selected 26 | boolean isActive(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/util/StringTools.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import net.fabricmc.loader.api.FabricLoader; 7 | import net.fabricmc.loader.api.ModContainer; 8 | 9 | public class StringTools 10 | { 11 | public static String getHexString(byte[] bytes) { 12 | List list = new ArrayList<>(); 13 | for (byte b : bytes) { 14 | list.add(String.format("%02x", b)); 15 | } 16 | return String.join(" ", list); 17 | } 18 | 19 | public static String getModVersion(String modid) 20 | { 21 | final Optional CONTAINER = FabricLoader.getInstance().getModContainer(modid); 22 | if (CONTAINER.isPresent()) 23 | { 24 | return CONTAINER.get().getMetadata().getVersion().getFriendlyString(); 25 | } 26 | else return "?"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "syncmatica", 4 | "version": "${mod_version}", 5 | "name": "Syncmatica", 6 | "description": "Server-wide shared litematics.", 7 | "authors": [ 8 | "nnnik", 9 | "samipourquoi" 10 | ], 11 | "contact": { 12 | "sources": "https://github.com/endtech/syncmatica" 13 | }, 14 | "license": "CC0-1.0", 15 | "environment": "*", 16 | "entrypoints": { 17 | "main": [ 18 | "ch.endte.syncmatica.ModInit" 19 | ] 20 | }, 21 | "mixins": [ 22 | "syncmatica.mixin.json", 23 | "syncmatica.litematica_mixin.json" 24 | ], 25 | "depends": { 26 | "minecraft": "1.21.11", 27 | "fabric-networking-api-v1": ">=5.1.4" 28 | }, 29 | "suggests": { 30 | "malilib": ">=0.27.0- <0.28.0", 31 | "litematica": ">=0.25.0- <0.26.0" 32 | }, 33 | "breaks": { 34 | "malilib": "<0.27.0-", 35 | "litematica": "<0.25.0-" 36 | }, 37 | 38 | "icon": "assets/syncmatica/icon/logo.png", 39 | "custom": { 40 | "modmenu": { 41 | "links": { 42 | "modmenu.discord": "https://discord.gg/6NPDVNMZ3T" 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/Feature.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica; 2 | 3 | public enum Feature 4 | { 5 | CORE, // every feature that's part of 0.1.0 - it doesn't make sense to divide those further since compatibility with 0.0 of future versions 6 | // cannot be maintained and the version is very alpha. 7 | FEATURE, // the possibility of reporting on ones own features during version exchange 8 | MODIFY, // commands to modify the placement of a syncmatic placement on the server 9 | MESSAGE, // ability to send messages to display from server to client 10 | QUOTA, // quota on client uploads to the server 11 | DEBUG, // ability to configure debugging 12 | CORE_EX, // extended basic features - such as who owns a placement and subregion sharing 13 | VERSION, // extended version metadata 14 | DISPLAY_NAME, // extended file / display name feature for saving and loading files 15 | ; 16 | 17 | public static Feature fromString(final String s) 18 | { 19 | for (final Feature f : Feature.values()) 20 | { 21 | if (f.toString().equals(s)) 22 | { 23 | return f; 24 | } 25 | } 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/extended_core/PlayerIdentifier.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.extended_core; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonPrimitive; 5 | 6 | import java.util.UUID; 7 | 8 | public class PlayerIdentifier { 9 | public static final UUID MISSING_PLAYER_UUID = UUID.fromString("4c1b738f-56fa-4011-8273-498c972424ea"); 10 | public static final PlayerIdentifier MISSING_PLAYER = new PlayerIdentifier(MISSING_PLAYER_UUID, "No Player"); 11 | 12 | public final UUID uuid; 13 | private String bufferedPlayerName; 14 | 15 | PlayerIdentifier(final UUID uuid, final String bufferedPlayerName) { 16 | this.uuid = uuid; 17 | this.bufferedPlayerName = bufferedPlayerName; 18 | } 19 | 20 | public String getName() { 21 | return bufferedPlayerName; 22 | } 23 | 24 | public void updatePlayerName(final String name) { 25 | bufferedPlayerName = name; 26 | } 27 | 28 | public JsonObject toJson() { 29 | final JsonObject jsonObject = new JsonObject(); 30 | 31 | jsonObject.add("uuid", new JsonPrimitive(uuid.toString())); 32 | jsonObject.add("name", new JsonPrimitive(bufferedPlayerName)); 33 | 34 | return jsonObject; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/gui/MainMenuButtonType.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica.gui; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import fi.dy.masa.malilib.gui.button.IButtonActionListener; 7 | import fi.dy.masa.malilib.gui.interfaces.IGuiIcon; 8 | import fi.dy.masa.malilib.util.StringUtils; 9 | 10 | public enum MainMenuButtonType implements IButtonType 11 | { 12 | VIEW_SYNCMATICS("syncmatica.gui.button.view_syncmatics"), 13 | MATERIAL_GATHERINGS("syncmatica.gui.button.material_gatherings"); 14 | 15 | private final String labelKey; 16 | 17 | MainMenuButtonType(final String labelKey) 18 | { 19 | this.labelKey = labelKey; 20 | } 21 | 22 | @Override 23 | public IGuiIcon getIcon() 24 | { 25 | return null; 26 | } 27 | 28 | @Override 29 | public String getTranslatedKey() 30 | { 31 | return StringUtils.translate(labelKey); 32 | } 33 | 34 | @Override 35 | public List getHoverStrings() 36 | { 37 | return Collections.emptyList(); 38 | } 39 | 40 | @Override 41 | public IButtonActionListener getButtonListener() 42 | { 43 | return null; 44 | } 45 | 46 | @Override 47 | public boolean isActive() 48 | { 49 | return false; 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/material/SyncmaticaMaterialList.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.material; 2 | 3 | import ch.endte.syncmatica.data.ServerPosition; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.Optional; 8 | 9 | public class SyncmaticaMaterialList { 10 | private ArrayList list; 11 | private ServerPosition deliveryPoint; 12 | 13 | public SyncmaticaMaterialEntry getUnclaimedEntry() { 14 | final Optional unclaimed = list.parallelStream().filter(SyncmaticaMaterialEntry.UNFINISHED).filter(SyncmaticaMaterialEntry.UNCLAIMED).findFirst(); 15 | if (unclaimed.isPresent()) { 16 | return unclaimed.get(); 17 | } 18 | return null; 19 | } 20 | 21 | public Collection getDeliveryPosition(final SyncmaticaMaterialEntry entry) { 22 | if (!list.contains(entry)) { 23 | throw new IllegalArgumentException(); 24 | } 25 | final DeliveryPosition delivery = new DeliveryPosition(deliveryPoint.getBlockPosition(), deliveryPoint.getDimensionId(), entry.getAmountMissing()); 26 | final ArrayList deliveryList = new ArrayList<>(); 27 | deliveryList.add(delivery); 28 | return deliveryList; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Automatically build the project and run any configured tests for every push 2 | # and submitted pull request. This can help catch issues that only occur on 3 | # certain platforms or Java versions, and provides a first line of defence 4 | # against bad commits. 5 | 6 | name: build 7 | on: [ pull_request, push ] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | # Use these Java versions 14 | java: [ 21 ] 15 | distro: [ temurin ] 16 | # and run on both Linux and Windows 17 | os: [ ubuntu-latest ] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - name: checkout repository 21 | uses: actions/checkout@v4 22 | - name: validate gradle wrapper 23 | uses: gradle/actions/wrapper-validation@v4 24 | - name: setup jdk ${{ matrix.java }} 25 | uses: actions/setup-java@v4 26 | with: 27 | distribution: ${{ matrix.distro }} 28 | java-version: ${{ matrix.java }} 29 | - name: make gradle wrapper executable 30 | if: ${{ runner.os != 'Windows' }} 31 | run: chmod +x ./gradlew 32 | - name: build 33 | run: ./gradlew build 34 | - name: capture build artifacts 35 | if: ${{ runner.os == 'Linux' }} 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: Artifacts 39 | path: build/libs/ 40 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/material/SyncmaticaMaterialEntry.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.material; 2 | 3 | import java.util.function.Predicate; 4 | 5 | public class SyncmaticaMaterialEntry { 6 | private int amountRequired; 7 | private int amountPresent; 8 | private String claimedBy; 9 | 10 | public static final Unclaimed UNCLAIMED = new Unclaimed(); 11 | public static final Unfinished UNFINISHED = new Unfinished(); 12 | 13 | public int getAmountRequired() { 14 | return amountRequired; 15 | } 16 | 17 | public int getAmountPresent() { 18 | return amountPresent; 19 | } 20 | 21 | public int getAmountMissing() { 22 | return amountRequired - amountPresent; 23 | } 24 | 25 | public boolean isClaimed() { 26 | return claimedBy != null; 27 | } 28 | 29 | public boolean isFinished() { 30 | return amountPresent >= amountRequired; 31 | } 32 | 33 | public static class Unclaimed implements Predicate { 34 | @Override 35 | public boolean test(final SyncmaticaMaterialEntry arg0) { 36 | return !arg0.isClaimed(); 37 | } 38 | } 39 | 40 | public static class Unfinished implements Predicate { 41 | @Override 42 | public boolean test(final SyncmaticaMaterialEntry arg0) { 43 | return !arg0.isFinished(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/gui/ButtonListenerChangeMenu.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica.gui; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import fi.dy.masa.malilib.gui.GuiBase; 5 | import fi.dy.masa.malilib.gui.button.ButtonBase; 6 | import fi.dy.masa.malilib.gui.button.IButtonActionListener; 7 | import net.minecraft.client.gui.screens.Screen; 8 | 9 | public class ButtonListenerChangeMenu implements IButtonActionListener 10 | { 11 | private final MainMenuButtonType type; 12 | private final Screen parent; 13 | 14 | public ButtonListenerChangeMenu(final MainMenuButtonType type, final Screen parent) 15 | { 16 | this.type = type; 17 | this.parent = parent; 18 | } 19 | 20 | @Override 21 | public void actionPerformedWithButton(final ButtonBase arg0, final int arg1) 22 | { 23 | GuiBase gui = null; 24 | switch (type) 25 | { 26 | case MATERIAL_GATHERINGS: 27 | LogManager.getLogger().info("Opened Material Gatherings GUI - currently unsupported operation"); 28 | break; 29 | case VIEW_SYNCMATICS: 30 | gui = new GuiSyncmaticaServerPlacementList(); 31 | break; 32 | default: 33 | break; 34 | } 35 | if (gui != null) 36 | { 37 | gui.setParent(parent); 38 | GuiBase.openGui(gui); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/service/DebugService.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.service; 2 | 3 | import ch.endte.syncmatica.Syncmatica; 4 | import ch.endte.syncmatica.network.PacketType; 5 | 6 | public class DebugService extends AbstractService 7 | { 8 | private boolean doPacketLogging = true; 9 | 10 | public void logReceivePacket(final PacketType packetType) 11 | { 12 | if (doPacketLogging) 13 | { 14 | Syncmatica.LOGGER.info("Syncmatica - received packet:[type={}]", packetType.toString()); 15 | } 16 | } 17 | 18 | public void logSendPacket(final PacketType packetType, final String targetIdentifier) 19 | { 20 | if (doPacketLogging) 21 | { 22 | Syncmatica.LOGGER.info( 23 | "Sending packet[type={}] to ExchangeTarget[id={}]", 24 | packetType.toString(), 25 | targetIdentifier 26 | ); 27 | } 28 | } 29 | 30 | @Override 31 | public void getDefaultConfiguration(final IServiceConfiguration configuration) 32 | { 33 | configuration.saveBoolean("doPackageLogging", false); 34 | } 35 | 36 | @Override 37 | public String getConfigKey() 38 | { 39 | return "debug"; 40 | } 41 | 42 | @Override 43 | public void configure(final IServiceConfiguration configuration) 44 | { 45 | configuration.loadBoolean("doPackageLogging", b -> doPacketLogging = b); 46 | } 47 | 48 | @Override 49 | public void startup() 50 | { //NOSONAR 51 | } 52 | 53 | @Override 54 | public void shutdown() 55 | { //NOSONAR 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/assets/syncmatica/lang/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "syncmatica.error.create_from_schematic": "从原理图创建同步结构时出错: {}", 3 | "syncmatica.error.failed_to_load": "加载投影文件失败 {}", 4 | "syncmatica.error.invalid_file": "不支持分享此文件类型", 5 | "syncmatica.error.modification_deny": "操作冲突 - 该结构正在被其他玩家编辑中", 6 | "syncmatica.error.player_dimension_mismatch": "请进入与结构相同的维度后进行加载", 7 | "syncmatica.error.share_incompatible_schematic": "请通过主菜单的原理图管理器转换此文件后再分享", 8 | "syncmatica.error.share_modified_subregions": "分享失败 - 服务器暂不支持修改后的子区域分享,请更新服务端模组", 9 | "syncmatica.error.share_without_shift": "按住 Shift 键进行分享", 10 | "syncmatica.gui.button.download": "下载", 11 | "syncmatica.gui.button.downloading": "下载中...", 12 | "syncmatica.gui.button.load": "加载", 13 | "syncmatica.gui.button.material_gathering_placement": "材料清单", 14 | "syncmatica.gui.button.material_gatherings": "材料收集清单", 15 | "syncmatica.gui.button.remove": "移除", 16 | "syncmatica.gui.button.share": "分享", 17 | "syncmatica.gui.button.unload": "卸载", 18 | "syncmatica.gui.button.view_syncmatics": "共享投影图", 19 | "syncmatica.gui.label.placement_info.dimension_id": "维度", 20 | "syncmatica.gui.label.placement_info.display_name": "名称", 21 | "syncmatica.gui.label.placement_info.file_name": "文件名", 22 | "syncmatica.gui.label.placement_info.last_modified": "最后修改者", 23 | "syncmatica.gui.label.placement_info.owner": "初始分享者", 24 | "syncmatica.gui.label.placement_info.position": "原点坐标 (xyz)", 25 | "syncmatica.gui.label.placement_info.schema": "游戏版本: §f%s§r [结构版本 §f%d§r]", 26 | "syncmatica.gui.label.placement_info.version": "投影版本: §f%d§r", 27 | "syncmatica.gui.title.manage_server_placements": "管理服务器结构放置 - %s", 28 | "syncmatica.success.modification_accepted": "修改已接受 - 请重新锁定结构位置以同步到服务器" 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/mixin/MixinServerCommonNetworkHandler.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.mixin; 2 | 3 | import ch.endte.syncmatica.Reference; 4 | import ch.endte.syncmatica.network.SyncmaticaPacket; 5 | import ch.endte.syncmatica.network.handler.ServerPlayHandler; 6 | import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; 7 | import net.minecraft.server.network.ServerCommonPacketListenerImpl; 8 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | @Mixin(ServerCommonPacketListenerImpl.class) 15 | public class MixinServerCommonNetworkHandler 16 | { 17 | // This exists because of the Communications Manager / Exchange Target system, 18 | // and FAPI networking is too slow to register the receivers 19 | @Inject(method = "handleCustomPayload", at = @At("HEAD"), cancellable = true) 20 | private void syncmatica$handlePacket(ServerboundCustomPayloadPacket packet, CallbackInfo ci) 21 | { 22 | if (packet.payload().type().id().getNamespace().equals(Reference.MOD_ID)) 23 | { 24 | SyncmaticaPacket.Payload payload = (SyncmaticaPacket.Payload) packet.payload(); 25 | Object thiss = this; 26 | 27 | if (thiss instanceof ServerGamePacketListenerImpl handler) 28 | { 29 | ServerPlayHandler.decodeSyncData(payload.data(), handler); 30 | } 31 | 32 | // Cancel unnecessary processing if a PacketType we own is caught 33 | if (ci.isCancellable()) 34 | ci.cancel(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/service/JsonConfiguration.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.service; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | 6 | import java.util.function.Consumer; 7 | import java.util.function.IntConsumer; 8 | 9 | public class JsonConfiguration implements IServiceConfiguration { 10 | 11 | public final JsonObject configuration; 12 | private Boolean wasError; 13 | 14 | public JsonConfiguration(final JsonObject configuration) { 15 | this.configuration = configuration; 16 | wasError = false; 17 | } 18 | 19 | @Override 20 | public void loadBoolean(final String key, final Consumer loader) { 21 | try { 22 | final JsonElement elem = configuration.get(key); 23 | if (elem != null) { 24 | loader.accept(elem.getAsBoolean()); 25 | } 26 | } catch (final Exception ignored) { 27 | wasError = true; 28 | } 29 | } 30 | 31 | @Override 32 | public void saveBoolean(final String key, final Boolean value) { 33 | configuration.addProperty(key, value); 34 | } 35 | 36 | @Override 37 | public void loadInteger(final String key, final IntConsumer loader) { 38 | try { 39 | final JsonElement elem = configuration.get(key); 40 | if (elem != null) { 41 | loader.accept(elem.getAsInt()); 42 | } 43 | } catch (final Exception ignored) { 44 | wasError = true; 45 | } 46 | } 47 | 48 | @Override 49 | public void saveInteger(final String key, final Integer value) { 50 | configuration.addProperty(key, value); 51 | } 52 | 53 | public Boolean hadError() { 54 | return wasError; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/resources/assets/syncmatica/lang/zh_tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "syncmatica.error.create_from_schematic": "從原理圖建立同步時出錯: {}", 3 | "syncmatica.error.failed_to_load": "無法載入 Litematica 模組 {}", 4 | "syncmatica.error.invalid_file": "無法分享此類型的檔案。", 5 | "syncmatica.error.modification_deny": "修改請求被拒絕 - 其他用戶已修改此檔案的展示位置。", 6 | "syncmatica.error.player_dimension_mismatch": "玩家需要與要載入的原理圖處於同一維度。", 7 | "syncmatica.error.share_incompatible_schematic": "請使用主選單中的原理圖管理員轉換此檔案以分享。", 8 | "syncmatica.error.share_modified_subregions": "分享錯誤:伺服器不支持共享已修改的子區域,需要更新。", 9 | "syncmatica.error.share_without_shift": "按下 Shift 鍵以分享原理圖", 10 | "syncmatica.gui.button.download": "下載", 11 | "syncmatica.gui.button.downloading": "下載中...", 12 | "syncmatica.gui.button.load": "載入", 13 | "syncmatica.gui.button.material_gathering_placement": "材料", 14 | "syncmatica.gui.button.material_gatherings": "材料收集", 15 | "syncmatica.gui.button.remove": "刪除", 16 | "syncmatica.gui.button.share": "分享", 17 | "syncmatica.gui.button.unload": "卸載", 18 | "syncmatica.gui.button.view_syncmatics": "Syncmatics 選單", 19 | "syncmatica.gui.label.placement_info.dimension_id": "維度", 20 | "syncmatica.gui.label.placement_info.display_name": "名稱", 21 | "syncmatica.gui.label.placement_info.file_name": "檔案名稱", 22 | "syncmatica.gui.label.placement_info.last_modified": "最後修改", 23 | "syncmatica.gui.label.placement_info.owner": "上傳者", 24 | "syncmatica.gui.label.placement_info.position": "原點 (xyz)", 25 | "syncmatica.gui.label.placement_info.schema": "遊戲版本:§f%s§r [Schema §f%d§r]", 26 | "syncmatica.gui.label.placement_info.version": "投影版本: §f%d§r", 27 | "syncmatica.gui.title.manage_server_placements": "管理伺服器配置 - %s", 28 | "syncmatica.success.modification_accepted": "修改請求已允許 - 請再次鎖定展示位置以更新。" 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica_mixin/MixinWidgetListSchematicPlacement.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica_mixin; 2 | 3 | import ch.endte.syncmatica.data.ServerPlacement; 4 | import ch.endte.syncmatica.litematica.ScreenHelper; 5 | import fi.dy.masa.litematica.gui.GuiSchematicPlacementsList; 6 | import fi.dy.masa.litematica.gui.widgets.WidgetListSchematicPlacements; 7 | import fi.dy.masa.litematica.gui.widgets.WidgetSchematicPlacement; 8 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacement; 9 | import fi.dy.masa.malilib.gui.interfaces.ISelectionListener; 10 | import fi.dy.masa.malilib.gui.widgets.WidgetListBase; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Unique; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | import java.util.function.Consumer; 18 | 19 | @Mixin(WidgetListSchematicPlacements.class) 20 | public abstract class MixinWidgetListSchematicPlacement extends WidgetListBase { 21 | 22 | @Unique 23 | Consumer updateListener; 24 | 25 | public MixinWidgetListSchematicPlacement(final int x, final int y, final int width, final int height, 26 | final ISelectionListener selectionListener) { 27 | super(x, y, width, height, selectionListener); 28 | } 29 | 30 | @Inject(method = "", at = @At("TAIL"), remap = false) 31 | public void setupListener(final int x, final int y, final int width, final int height, final GuiSchematicPlacementsList parent, final CallbackInfo ci) { 32 | ScreenHelper.ifPresent((s) -> s.setCurrentGui(parent)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/gui/BaseButtonType.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica.gui; 2 | 3 | import java.util.List; 4 | import java.util.function.Supplier; 5 | 6 | import fi.dy.masa.malilib.gui.button.IButtonActionListener; 7 | import fi.dy.masa.malilib.gui.interfaces.IGuiIcon; 8 | import fi.dy.masa.malilib.util.StringUtils; 9 | 10 | public class BaseButtonType implements IButtonType 11 | { 12 | private final String translatedKey; 13 | private final IButtonActionListener listener; 14 | private final Supplier activeFunction; 15 | private final List hoverString; 16 | 17 | public BaseButtonType(final String untranslatedKey, final Supplier activeFunction, final IButtonActionListener listener, final List hoverString) 18 | { 19 | translatedKey = StringUtils.translate(untranslatedKey); 20 | this.listener = listener; 21 | this.activeFunction = activeFunction; 22 | this.hoverString = hoverString; 23 | } 24 | 25 | public BaseButtonType(final String untranslatedKey, final Supplier activeFunction, final IButtonActionListener listener) 26 | { 27 | this(untranslatedKey, activeFunction, listener, null); 28 | } 29 | 30 | @Override 31 | public String getTranslatedKey() 32 | { 33 | return translatedKey; 34 | } 35 | 36 | @Override 37 | public IButtonActionListener getButtonListener() 38 | { 39 | return listener; 40 | } 41 | 42 | @Override 43 | public boolean isActive() 44 | { 45 | return activeFunction.get(); 46 | } 47 | 48 | @Override 49 | public IGuiIcon getIcon() 50 | { 51 | return null; 52 | } 53 | 54 | @Override 55 | public List getHoverStrings() 56 | { 57 | return hoverString; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/mixin/MixinIntegratedServer.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.mixin; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.Reference; 5 | import ch.endte.syncmatica.Syncmatica; 6 | import net.minecraft.client.server.IntegratedServer; 7 | import net.minecraft.world.level.GameType; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 12 | 13 | @Mixin(value = IntegratedServer.class) 14 | public class MixinIntegratedServer 15 | { 16 | @Inject(method = "initServer", at = @At("RETURN")) 17 | private void syncmatica$setupServer(CallbackInfoReturnable cir) 18 | { 19 | if (cir.getReturnValue()) 20 | { 21 | Syncmatica.debug("syncmatica$setupServer(): Integrated Server detected"); 22 | Reference.setIntegratedServer(true); 23 | Reference.setOpenToLan(false); 24 | Reference.setDedicatedServer(false); 25 | } 26 | } 27 | 28 | @Inject(method = "publishServer", at = @At("RETURN")) 29 | private void syncmatica$checkOpenToLan(GameType gameMode, boolean cheatsAllowed, int port, CallbackInfoReturnable cir) 30 | { 31 | if (cir.getReturnValue()) 32 | { 33 | Syncmatica.debug("syncmatica$checkOpenToLan(): OpenToLan detected"); 34 | Reference.setIntegratedServer(true); 35 | Reference.setOpenToLan(true); 36 | Reference.setDedicatedServer(false); 37 | 38 | Context ctx = Syncmatica.getContext(Syncmatica.SERVER_CONTEXT); 39 | if (ctx != null) 40 | { 41 | ctx.registerReceivers(); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CONFIG.md: -------------------------------------------------------------------------------- 1 | # Syncmatica Configuration Manual 2 | 3 | This document explains what the possibilities, options and restrictions of configuring syncmatica are. 4 | 5 | ## Configuration 6 | 7 | syncmatica is configured by a `config.json` which can be found in `config/syncmatica/config.json`. The json inside 8 | configures several settings on the Server/Client which are grouped together into categories. Here is an example of the 9 | current (As of this files last update time current) `config.json`: 10 | 11 | ### Server config.json 12 | 13 | ```json 14 | { 15 | "quota": { 16 | "enabled": false, 17 | "limit": 40000000 18 | }, 19 | "debug": { 20 | "doPackageLogging": false 21 | } 22 | } 23 | ``` 24 | 25 | ### Client config.json 26 | 27 | ```json 28 | { 29 | "debug": { 30 | "doPackageLogging": false 31 | } 32 | } 33 | ``` 34 | 35 | If the file is missing or otherwise cannot be read, the file might completely reset itself during startup. If parts of 36 | the configuration are damaged/unreadable, the file will try to reset the portions during startup. Extra entries are 37 | ignored. 38 | 39 | #### Quota 40 | 41 | The key "quota" configures the quota feature of the server. 42 | 43 | * `enabled` defines whether the quota feature is enabled on the server. The feature blocks uploads from clients if the 44 | client exceeds a limit for file uploads. How much the client already uploaded resets itself when the server shuts 45 | down. Can be `true` or `false` 46 | * `limit` defines the limit that a client is able to upload in bytes. 47 | 48 | #### Debug 49 | 50 | The key "debug" configures the debug feature of the mod. 51 | 52 | * `doPackageLogging` configures whether the client/server should add a debug log for all outgoing and incoming packets. 53 | The type of the packet and the target of the packet gets logged for outgoing packets - for incoming only the type of 54 | the packet gets logged. 55 | -------------------------------------------------------------------------------- /src/main/resources/assets/syncmatica/lang/ko_kr.json: -------------------------------------------------------------------------------- 1 | { 2 | "syncmatica.error.create_from_schematic": "스키매틱으로 싱크매틱을 생성하는 중 오류가 발생했습니다: {}", 3 | "syncmatica.error.failed_to_load": "라이트매틱 불러오기에 실패했습니다. {}", 4 | "syncmatica.error.invalid_file": "이 유형의 파일은 공유할 수 없습니다.", 5 | "syncmatica.error.modification_deny": "수정 요청 거부 - 다른 사용자가 이미 이 배치를 수정하고 있습니다.", 6 | "syncmatica.error.player_dimension_mismatch": "배치를 불러오기 위해서는 플레이어가 배치와 같은 차원에 있어야 합니다.", 7 | "syncmatica.error.share_incompatible_schematic": "메인 메뉴의 스키매틱 관리자를 이용하여 이 파일을 변환한 후 공유하세요.", 8 | "syncmatica.error.share_modified_subregions": "공유 오류 - 연결된 서버는 수정된 서브 지역 공유를 지원하지 않습니다. - 업데이트가 필요합니다.", 9 | "syncmatica.error.share_without_shift": "공유하려면 shift를 누르세요.", 10 | "syncmatica.gui.button.download": "다운로드", 11 | "syncmatica.gui.button.downloading": "다운로드 중…", 12 | "syncmatica.gui.button.load": "불러오기", 13 | "syncmatica.gui.button.material_gathering_placement": "재료", 14 | "syncmatica.gui.button.material_gatherings": "재료 수집", 15 | "syncmatica.gui.button.remove": "제거", 16 | "syncmatica.gui.button.share": "공유", 17 | "syncmatica.gui.button.unload": "제거하기", 18 | "syncmatica.gui.button.view_syncmatics": "싱크매틱스 보기", 19 | "syncmatica.gui.label.placement_info.dimension_id": "차원", 20 | "syncmatica.gui.label.placement_info.display_name": "이름", 21 | "syncmatica.gui.label.placement_info.file_name": "파일 이름", 22 | "syncmatica.gui.label.placement_info.last_modified": "최종 수정자", 23 | "syncmatica.gui.label.placement_info.owner": "최초 공유자", 24 | "syncmatica.gui.label.placement_info.position": "기준 (xyz)", 25 | "syncmatica.gui.label.placement_info.schema": "마인크래프트: §f%s§r [스키마 §f%d§r]", 26 | "syncmatica.gui.label.placement_info.version": "라이트매틱 버전: §f%d§r", 27 | "syncmatica.gui.title.manage_server_placements": "서버 배치 관리 - %s", 28 | "syncmatica.success.modification_accepted": "수정 요청 수락 - 결과를 서버에 공유하려면 배치를 다시 잠그세요." 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/exchange/FeatureExchange.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication.exchange; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.communication.ExchangeTarget; 5 | import ch.endte.syncmatica.communication.FeatureSet; 6 | import ch.endte.syncmatica.network.PacketType; 7 | import io.netty.buffer.Unpooled; 8 | import net.minecraft.network.FriendlyByteBuf; 9 | 10 | public abstract class FeatureExchange extends AbstractExchange 11 | { 12 | protected FeatureExchange(final ExchangeTarget partner, final Context con) { super(partner, con); } 13 | 14 | @Override 15 | public boolean checkPacket(final PacketType type, final FriendlyByteBuf packetBuf) 16 | { 17 | return type.equals(PacketType.FEATURE_REQUEST) 18 | || type.equals(PacketType.FEATURE); 19 | } 20 | 21 | @Override 22 | public void handle(final PacketType type, final FriendlyByteBuf packetBuf) 23 | { 24 | if (type.equals(PacketType.FEATURE_REQUEST)) 25 | { 26 | sendFeatures(); 27 | } else if (type.equals(PacketType.FEATURE)) 28 | { 29 | final FeatureSet fs = FeatureSet.fromString(packetBuf.readUtf(PACKET_MAX_STRING_SIZE)); 30 | getPartner().setFeatureSet(fs); 31 | onFeatureSetReceive(); 32 | } 33 | } 34 | 35 | protected void onFeatureSetReceive() { succeed(); } 36 | 37 | public void requestFeatureSet() 38 | { 39 | getPartner().sendPacket(PacketType.FEATURE_REQUEST, new FriendlyByteBuf(Unpooled.buffer()), getContext()); 40 | } 41 | 42 | private void sendFeatures() 43 | { 44 | final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); 45 | final FeatureSet fs = getContext().getFeatureSet(); 46 | buf.writeUtf(fs.toString(), PACKET_MAX_STRING_SIZE); 47 | getPartner().sendPacket(PacketType.FEATURE, buf, getContext()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/mixin/MixinClientCommonNetworkHandler.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.mixin; 2 | 3 | import ch.endte.syncmatica.Reference; 4 | import ch.endte.syncmatica.network.SyncmaticaPacket; 5 | import ch.endte.syncmatica.network.handler.ClientPlayHandler; 6 | import net.minecraft.client.multiplayer.ClientCommonPacketListenerImpl; 7 | import net.minecraft.client.multiplayer.ClientPacketListener; 8 | import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | @Mixin(ClientCommonPacketListenerImpl.class) 15 | public class MixinClientCommonNetworkHandler 16 | { 17 | // This exists because of the Communications Manager / Exchange Target system, 18 | // and FAPI networking is too slow to register the receivers 19 | @Inject(method = "handleCustomPayload(Lnet/minecraft/network/protocol/common/ClientboundCustomPayloadPacket;)V", at = @At("HEAD"), cancellable = true) 20 | private void syncmatica$handlePacket(ClientboundCustomPayloadPacket packet, CallbackInfo ci) 21 | { 22 | if (packet.payload().type().id().getNamespace().equals(Reference.MOD_ID)) 23 | { 24 | SyncmaticaPacket.Payload payload = (SyncmaticaPacket.Payload) packet.payload(); 25 | Object thiss = this; 26 | 27 | if (thiss instanceof ClientPacketListener handler) 28 | { 29 | ClientPlayHandler.decodeSyncData(payload.data(), handler); 30 | } 31 | else 32 | { 33 | ClientPlayHandler.receiveSyncPayload(payload.data()); 34 | } 35 | 36 | // Cancel unnecessary processing if a PacketType we own is caught 37 | if (ci.isCancellable()) 38 | ci.cancel(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/service/QuotaService.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.service; 2 | 3 | import ch.endte.syncmatica.communication.ExchangeTarget; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class QuotaService extends AbstractService { 9 | 10 | public static final Boolean IS_ENABLED_DEFAULT = false; 11 | public static final Integer QUOTA_LIMIT_DEFAULT = 40000000; 12 | 13 | Map progress = new HashMap<>(); 14 | Boolean isEnabled = IS_ENABLED_DEFAULT; 15 | Integer limit = QUOTA_LIMIT_DEFAULT; 16 | 17 | public Boolean isOverQuota(final ExchangeTarget sender, final Integer newData) { 18 | if (!Boolean.TRUE.equals(isEnabled)) { 19 | return false; 20 | } 21 | int curValue = progress.getOrDefault(sender.getPersistentName(), 0); 22 | curValue += newData; 23 | return curValue > limit; 24 | } 25 | 26 | public void progressQuota(final ExchangeTarget sender, final Integer newData) { 27 | if (Boolean.TRUE.equals(isEnabled)) { 28 | final int curValue = progress.getOrDefault(sender.getPersistentName(), 0); 29 | progress.put(sender.getPersistentName(), curValue + newData); 30 | } 31 | } 32 | 33 | @Override 34 | public void getDefaultConfiguration(final IServiceConfiguration configuration) { 35 | configuration.saveBoolean("enabled", IS_ENABLED_DEFAULT); 36 | configuration.saveInteger("limit", QUOTA_LIMIT_DEFAULT); 37 | } 38 | 39 | @Override 40 | public String getConfigKey() { 41 | return "quota"; 42 | } 43 | 44 | @Override 45 | public void configure(final IServiceConfiguration configuration) { 46 | configuration.loadBoolean("enabled", b -> isEnabled = b); 47 | configuration.loadInteger("limit", i -> limit = i); 48 | } 49 | 50 | @Override 51 | public void startup() { // NOSONAR 52 | } 53 | 54 | @Override 55 | public void shutdown() { // NOSONAR 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/mixin/MixinCommandManager.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.mixin; 2 | 3 | import ch.endte.syncmatica.command.SyncmaticaCommand; 4 | 5 | import com.mojang.brigadier.CommandDispatcher; 6 | import net.minecraft.commands.CommandBuildContext; 7 | import net.minecraft.commands.CommandSourceStack; 8 | import net.minecraft.commands.Commands; 9 | import org.spongepowered.asm.mixin.Final; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 15 | 16 | @Mixin(Commands.class) 17 | public class MixinCommandManager 18 | { 19 | @Shadow @Final private CommandDispatcher dispatcher; 20 | 21 | @Inject(method = "", at = @At(value = "INVOKE", 22 | target = "Lnet/minecraft/server/commands/WhitelistCommand;register(Lcom/mojang/brigadier/CommandDispatcher;)V", 23 | shift = At.Shift.AFTER)) 24 | private void syncmatica_injectDedicatedCommands(Commands.CommandSelection environment, 25 | CommandBuildContext registryAccess, CallbackInfo ci) 26 | { 27 | SyncmaticaCommand.INSTANCE.register(this.dispatcher, registryAccess, environment); 28 | } 29 | 30 | @Inject(method = "", at = @At(value = "INVOKE", 31 | target = "Lnet/minecraft/server/commands/PublishCommand;register(Lcom/mojang/brigadier/CommandDispatcher;)V", 32 | shift = At.Shift.AFTER)) 33 | private void syncmatica_injectIntegratedCommands(Commands.CommandSelection environment, 34 | CommandBuildContext registryAccess, CallbackInfo ci) 35 | { 36 | SyncmaticaCommand.INSTANCE.register(this.dispatcher, registryAccess, environment); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/mixin/MixinMinecraftClient.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.mixin; 2 | 3 | import ch.endte.syncmatica.Reference; 4 | import ch.endte.syncmatica.Syncmatica; 5 | import ch.endte.syncmatica.litematica.LitematicManager; 6 | import ch.endte.syncmatica.litematica.ScreenHelper; 7 | import ch.endte.syncmatica.network.actor.ActorClientPlayHandler; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraft.server.WorldStem; 10 | import net.minecraft.server.packs.repository.PackRepository; 11 | import net.minecraft.world.level.storage.LevelStorageSource; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | @Mixin(Minecraft.class) 19 | public class MixinMinecraftClient 20 | { 21 | @Shadow private boolean isLocalServer; 22 | 23 | @Inject(method = "doWorldLoad", at = @At("TAIL")) 24 | private void syncmatica$startIntegratedServer(LevelStorageSource.LevelStorageAccess session, PackRepository dataPackManager, WorldStem saveLoader, boolean newWorld, CallbackInfo ci) 25 | { 26 | if (this.isLocalServer) 27 | { 28 | Reference.setIntegratedServer(true); 29 | } 30 | } 31 | 32 | @Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V", at = @At("HEAD")) 33 | private void syncmatica$shutdownPre(final CallbackInfo ci) 34 | { 35 | ActorClientPlayHandler.getInstance().reset(); 36 | Reference.setIntegratedServer(false); 37 | ScreenHelper.close(); 38 | } 39 | 40 | @Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V", at = @At("RETURN")) 41 | private void syncmatica$shutdownPost(final CallbackInfo ci) 42 | { 43 | // This fixes some timing issues between this 44 | // and when LM unloads/loads placements. 45 | Syncmatica.shutdown(); 46 | LitematicManager.clear(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/exchange/Exchange.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication.exchange; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.communication.ExchangeTarget; 5 | import ch.endte.syncmatica.network.PacketType; 6 | import net.minecraft.network.FriendlyByteBuf; 7 | 8 | // an exchange represents a portion of a communication with a specific goal 9 | // that stretches across multiple packages 10 | // an exchange has exactly 2 participants since one of the participants is implied 11 | // to be the vm holding this exchange only the partner has to be noted 12 | // another thing of note is that an Exchange is only one half of the entire exchange 13 | // thus only encompasses half of the entire behavior of an exchange 14 | // e.g. there could be a class receiveData and a class sendData creating 15 | // the 2 halves of a TRANSMIT_DATA_EXCHANGE 16 | 17 | 18 | public interface Exchange 19 | { 20 | // uniquely identifies the partner of this exchange 21 | ExchangeTarget getPartner(); 22 | 23 | // in case an exchange starts another exchange they need to be able to reach for 24 | // the manager 25 | Context getContext(); 26 | 27 | // looks into the received packet and returns 28 | // whether this exchange handles the packet or not 29 | // this test should have no side effects. 30 | // doesn't handle packets directly 31 | boolean checkPacket(PacketType type, FriendlyByteBuf packetBuf); 32 | 33 | // handles the data of this specific packet 34 | void handle(PacketType type, FriendlyByteBuf packetBuf); 35 | 36 | // marks an exchange that has terminated 37 | boolean isFinished(); 38 | 39 | // marks a successfully finished exchange 40 | boolean isSuccessful(); 41 | 42 | // marks an external unsuccessful close 43 | // upon a close the exchange might send a cancel packet for this request 44 | // if the notifyPartner boolean is set to true 45 | // otherwise this will not happen 46 | void close(boolean notifyPartner); 47 | 48 | // initializes the actual Exchange 49 | void init(); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/gui/GuiSyncmaticaServerPlacementList.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica.gui; 2 | 3 | import ch.endte.syncmatica.Reference; 4 | import ch.endte.syncmatica.data.ServerPlacement; 5 | 6 | import fi.dy.masa.malilib.gui.GuiListBase; 7 | import fi.dy.masa.malilib.gui.button.ButtonGeneric; 8 | import fi.dy.masa.malilib.util.StringUtils; 9 | import fi.dy.masa.litematica.gui.GuiMainMenu.ButtonListenerChangeMenu; 10 | 11 | public class GuiSyncmaticaServerPlacementList extends GuiListBase 12 | { 13 | public GuiSyncmaticaServerPlacementList() 14 | { 15 | super(12, 30); 16 | title = StringUtils.translate("syncmatica.gui.title.manage_server_placements", String.format("v%s", Reference.MOD_VERSION)); 17 | } 18 | 19 | @Override 20 | public void initGui() 21 | { 22 | super.initGui(); 23 | // source GuiSchematicLoadedList 24 | final ButtonListenerChangeMenu.ButtonType type = ButtonListenerChangeMenu.ButtonType.MAIN_MENU; 25 | final String label = StringUtils.translate(type.getLabelKey()); 26 | final int buttonWidth = getStringWidth(label) + 20; 27 | final int x = width - buttonWidth - 10; 28 | final int y = height - 26; 29 | final ButtonGeneric button = new ButtonGeneric(x, y, buttonWidth, 20, label); 30 | addButton(button, new ButtonListenerChangeMenu(type, getParent())); 31 | } 32 | 33 | @Override 34 | protected WidgetListSyncmaticaServerPlacement createListWidget(final int listX, final int listY) 35 | { 36 | return new WidgetListSyncmaticaServerPlacement(listX, listY, getBrowserWidth(), getBrowserHeight(), this, null); 37 | } 38 | 39 | @Override 40 | protected int getBrowserHeight() 41 | { 42 | return height - 68; 43 | } 44 | 45 | @Override 46 | protected int getBrowserWidth() 47 | { 48 | return width - 20; 49 | } 50 | 51 | public int getMaxInfoHeight() 52 | { 53 | return getBrowserHeight(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/FeatureSet.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication; 2 | 3 | import ch.endte.syncmatica.Feature; 4 | 5 | import java.util.*; 6 | 7 | // a class representing what kind of features a syncmatica instance supports 8 | // or has enabled/disabled 9 | 10 | public class FeatureSet { 11 | 12 | private static final Map versionFeatures; 13 | private final Collection features; 14 | 15 | public static FeatureSet fromVersionString(String version) { 16 | if (version.matches("^\\d+(\\.\\d+){2,4}$")) { 17 | final int minSize = version.indexOf("."); 18 | while (version.length() > minSize) { 19 | if (versionFeatures.containsKey(version)) { 20 | return versionFeatures.get(version); 21 | } 22 | final int lastDot = version.lastIndexOf("."); 23 | version = version.substring(0, lastDot); 24 | } 25 | } 26 | return null; 27 | } 28 | 29 | public static FeatureSet fromString(final String features) { 30 | final FeatureSet featureSet = new FeatureSet(new ArrayList<>()); 31 | for (final String feature : features.split("\n")) { 32 | final Feature f = Feature.fromString(feature); 33 | if (f != null) { 34 | featureSet.features.add(f); 35 | } 36 | } 37 | return featureSet; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | final StringBuilder output = new StringBuilder(); 43 | boolean b = false; 44 | for (final Feature feature : features) { 45 | output.append(b ? "\n" + feature.toString() : feature.toString()); 46 | b = true; 47 | } 48 | return output.toString(); 49 | } 50 | 51 | public FeatureSet(final Collection features) { 52 | this.features = features; 53 | } 54 | 55 | public boolean hasFeature(final Feature f) { 56 | return features.contains(f); 57 | } 58 | 59 | static { 60 | versionFeatures = new HashMap<>(); 61 | versionFeatures.put("0.1", new FeatureSet(Collections.singletonList(Feature.CORE))); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/resources/assets/syncmatica/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "syncmatica.gui.button.view_syncmatics": "View Syncmatics", 3 | "syncmatica.gui.button.material_gatherings": "Material Collections", 4 | "syncmatica.gui.button.load": "Load", 5 | "syncmatica.gui.button.unload": "Unload", 6 | "syncmatica.gui.button.remove": "Remove", 7 | "syncmatica.gui.button.download": "Download", 8 | "syncmatica.gui.button.downloading": "Downloading...", 9 | "syncmatica.gui.button.share": "Share", 10 | "syncmatica.gui.button.material_gathering_placement": "Materials", 11 | "syncmatica.gui.title.manage_server_placements": "Manage Server Placements - %s", 12 | "syncmatica.gui.label.placement_info.display_name": "Name", 13 | "syncmatica.gui.label.placement_info.file_name": "File Name", 14 | "syncmatica.gui.label.placement_info.position": "Origin (xyz)", 15 | "syncmatica.gui.label.placement_info.dimension_id": "Dimension", 16 | "syncmatica.gui.label.placement_info.owner": "Originally shared by", 17 | "syncmatica.gui.label.placement_info.last_modified": "Last modified by", 18 | "syncmatica.gui.label.placement_info.version": "Litematic Version: §f%d§r", 19 | "syncmatica.gui.label.placement_info.schema": "Minecraft: §f%s§r [Schema §f%d§r]", 20 | "syncmatica.error.share_without_shift": "Press shift to share", 21 | "syncmatica.error.share_modified_subregions": "Share Error - Connected Server does not support sharing of modified subregions - it needs to update.", 22 | "syncmatica.error.modification_deny": "Denied Modification request - Another user is already modifying this placement.", 23 | "syncmatica.error.player_dimension_mismatch": "Player needs to be in the same dimension as placement to load.", 24 | "syncmatica.error.create_from_schematic": "Error creating Syncmatic from schematic: {}", 25 | "syncmatica.error.share_incompatible_schematic": "Convert this file using the Schematic Manager from the main menu to share it", 26 | "syncmatica.error.invalid_file": "Cannot share this type of file", 27 | "syncmatica.error.failed_to_load": "Failed to load litematic {}", 28 | "syncmatica.success.modification_accepted": "Modification request accept - Lock the placement again to share the results to the server" 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/exchange/AbstractExchange.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication.exchange; 2 | 3 | import java.util.UUID; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | import ch.endte.syncmatica.Context; 6 | import ch.endte.syncmatica.communication.CommunicationManager; 7 | import ch.endte.syncmatica.communication.ExchangeTarget; 8 | 9 | public abstract class AbstractExchange implements Exchange 10 | { 11 | protected int PACKET_MAX_STRING_SIZE = FriendlyByteBuf.MAX_STRING_LENGTH; 12 | private boolean success = false; 13 | private boolean finished = false; 14 | private final ExchangeTarget partner; 15 | private final Context context; 16 | protected AbstractExchange(final ExchangeTarget partner, final Context con) 17 | { 18 | this.partner = partner; 19 | context = con; 20 | } 21 | @Override 22 | public ExchangeTarget getPartner() { 23 | return partner; 24 | } 25 | 26 | @Override 27 | public Context getContext() { 28 | return context; 29 | } 30 | 31 | @Override 32 | public boolean isFinished() { 33 | return finished; 34 | } 35 | 36 | @Override 37 | public boolean isSuccessful() { 38 | return success; 39 | } 40 | 41 | @Override 42 | public void close(final boolean notifyPartner) 43 | { 44 | finished = true; 45 | success = false; 46 | onClose(); 47 | if (notifyPartner) 48 | { 49 | sendCancelPacket(); 50 | } 51 | } 52 | 53 | public CommunicationManager getManager() { return context.getCommunicationManager(); } 54 | 55 | protected void sendCancelPacket() { } 56 | 57 | protected void onClose() { } 58 | 59 | protected void succeed() 60 | { 61 | finished = true; 62 | success = true; 63 | // Ctrl+C Ctrl+V and forget to adapt the success state - typical 64 | onClose(); 65 | } 66 | 67 | protected static boolean checkUUID(final FriendlyByteBuf sourceBuf, final UUID targetId) 68 | { 69 | final int r = sourceBuf.readerIndex(); 70 | final UUID sourceId = sourceBuf.readUUID(); 71 | sourceBuf.readerIndex(r); 72 | return sourceId.equals(targetId); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/mixin/MixinPlayerManager.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.mixin; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.Reference; 5 | import ch.endte.syncmatica.Syncmatica; 6 | import ch.endte.syncmatica.network.handler.ServerPlayHandler; 7 | import ch.endte.syncmatica.network.PacketType; 8 | import ch.endte.syncmatica.network.SyncmaticaPacket; 9 | import io.netty.buffer.Unpooled; 10 | import net.minecraft.network.Connection; 11 | import net.minecraft.network.FriendlyByteBuf; 12 | import net.minecraft.server.level.ServerPlayer; 13 | import net.minecraft.server.network.CommonListenerCookie; 14 | import net.minecraft.server.players.PlayerList; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | 20 | @Mixin(PlayerList.class) 21 | public class MixinPlayerManager 22 | { 23 | public MixinPlayerManager() { super(); } 24 | 25 | @Inject(method = "placeNewPlayer", at = @At("TAIL")) 26 | private void syncmatica$eventOnPlayerJoin(Connection connection, ServerPlayer player, CommonListenerCookie clientData, CallbackInfo ci) 27 | { 28 | Syncmatica.debug("MixinPlayerManager#onPlayerJoin(): player {}", player.getName().tryCollapseToString()); 29 | 30 | if (Reference.isServer() || Reference.isDedicatedServer() || Reference.isIntegratedServer() || Reference.isOpenToLan()) 31 | { 32 | Context server = Syncmatica.getContext(Syncmatica.SERVER_CONTEXT); 33 | if (server != null && server.isStarted()) 34 | { 35 | Syncmatica.debug("syncmatica$eventOnPlayerJoin: yeet"); 36 | FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); 37 | buf.writeUtf(Reference.MOD_VERSION); 38 | 39 | ServerPlayHandler.encodeSyncData(new SyncmaticaPacket(PacketType.REGISTER_VERSION.getId(), buf), player); 40 | } 41 | } 42 | } 43 | 44 | @Inject(method = "remove", at = @At("HEAD")) 45 | private void syncmatica$eventOnPlayerLeave(ServerPlayer player, CallbackInfo ci) 46 | { 47 | // Something we need to do here? 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/network/SyncmaticaPacket.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.network; 2 | 3 | import javax.annotation.Nonnull; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | import net.minecraft.network.codec.StreamCodec; 6 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 7 | import net.minecraft.resources.Identifier; 8 | import ch.endte.syncmatica.Syncmatica; 9 | import org.jspecify.annotations.NonNull; 10 | 11 | public class SyncmaticaPacket 12 | { 13 | private final FriendlyByteBuf packet; 14 | private final PacketType type; 15 | private final Identifier channel; 16 | 17 | public SyncmaticaPacket(@Nonnull Identifier channel, @Nonnull FriendlyByteBuf packet) 18 | { 19 | this.channel = channel; 20 | this.packet = packet; 21 | this.type = PacketType.getType(channel); 22 | } 23 | 24 | public PacketType getType() 25 | { 26 | return this.type; 27 | } 28 | 29 | public Identifier getChannel() 30 | { 31 | return this.channel; 32 | } 33 | 34 | public FriendlyByteBuf getPacket() 35 | { 36 | return this.packet; 37 | } 38 | 39 | protected static SyncmaticaPacket fromPacket(FriendlyByteBuf input) 40 | { 41 | return new SyncmaticaPacket(input.readIdentifier(), new FriendlyByteBuf(input.readBytes(input.readableBytes()))); 42 | } 43 | 44 | protected void toPacket(FriendlyByteBuf output) 45 | { 46 | output.writeIdentifier(this.channel); 47 | output.writeBytes(this.packet.copy()); 48 | } 49 | 50 | public record Payload(SyncmaticaPacket data) implements CustomPacketPayload 51 | { 52 | public static final Type ID = new Type<>(Syncmatica.NETWORK_ID); 53 | public static final StreamCodec CODEC = CustomPacketPayload.codec(Payload::write, Payload::new); 54 | 55 | public Payload(FriendlyByteBuf input) 56 | { 57 | this(SyncmaticaPacket.fromPacket(input)); 58 | } 59 | 60 | private void write(FriendlyByteBuf output) 61 | { 62 | data.toPacket(output); 63 | } 64 | 65 | @Override 66 | public @NonNull Type type() 67 | { 68 | return ID; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/gui/ButtonListenerShare.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica.gui; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.Feature; 5 | import ch.endte.syncmatica.communication.ClientCommunicationManager; 6 | import ch.endte.syncmatica.communication.ExchangeTarget; 7 | import ch.endte.syncmatica.communication.exchange.ShareLitematicExchange; 8 | import ch.endte.syncmatica.litematica.LitematicManager; 9 | 10 | import fi.dy.masa.malilib.gui.GuiBase; 11 | import fi.dy.masa.malilib.gui.Message; 12 | import fi.dy.masa.malilib.gui.button.ButtonBase; 13 | import fi.dy.masa.malilib.gui.button.IButtonActionListener; 14 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacement; 15 | 16 | public class ButtonListenerShare implements IButtonActionListener 17 | { 18 | private final SchematicPlacement schematicPlacement; 19 | private final GuiBase messageDisplay; 20 | 21 | public ButtonListenerShare(final SchematicPlacement placement, final GuiBase messageDisplay) 22 | { 23 | schematicPlacement = placement; 24 | this.messageDisplay = messageDisplay; 25 | } 26 | 27 | @Override 28 | public void actionPerformedWithButton(final ButtonBase button, final int mouseButton) 29 | { 30 | if (LitematicManager.getInstance().isSyncmatic(schematicPlacement)) 31 | { 32 | return; 33 | } 34 | if (!GuiBase.isShiftDown()) 35 | { 36 | messageDisplay.addMessage(Message.MessageType.ERROR, "syncmatica.error.share_without_shift"); 37 | return; 38 | } 39 | button.setEnabled(false); 40 | final Context con = LitematicManager.getInstance().getActiveContext(); 41 | final ExchangeTarget server = ((ClientCommunicationManager) con.getCommunicationManager()).getServer(); 42 | if (!server.getFeatureSet().hasFeature(Feature.CORE_EX) && schematicPlacement.isRegionPlacementModified()) 43 | { 44 | messageDisplay.addMessage(Message.MessageType.ERROR, "syncmatica.error.share_modified_subregions"); 45 | return; 46 | } 47 | final ShareLitematicExchange ex = new ShareLitematicExchange(schematicPlacement, server, con); 48 | con.getCommunicationManager().startExchange(ex); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/resources/assets/syncmatica/lang/ru_ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "syncmatica.error.create_from_schematic": "Ошибка создания Синкматики из schematic: {}", 3 | "syncmatica.error.failed_to_load": "Ошибка загрузки Litematic {}", 4 | "syncmatica.error.invalid_file": "Невозможно передать такой тип файла", 5 | "syncmatica.error.modification_deny": "Отклонено в Запросе Модификации - Другой пользователь уже редактирует Размещение.", 6 | "syncmatica.error.player_dimension_mismatch": "Игрок должен быть в том же измерении для загрузки Размещения.", 7 | "syncmatica.error.share_incompatible_schematic": "Конвертируйте этот файл с помощью Schematic Manager из главного меню для передачи", 8 | "syncmatica.error.share_modified_subregions": "Ошибка передачи - Сервер не поддерживает передачу модифицированных субрегионов - ему нужно обновиться.", 9 | "syncmatica.error.share_without_shift": "Нажмите Shift чтобы поделиться", 10 | "syncmatica.gui.button.download": "Скачать", 11 | "syncmatica.gui.button.downloading": "Скачивание...", 12 | "syncmatica.gui.button.load": "Загрузить", 13 | "syncmatica.gui.button.material_gathering_placement": "Материалы", 14 | "syncmatica.gui.button.material_gatherings": "Список Материалов", 15 | "syncmatica.gui.button.remove": "Удалить", 16 | "syncmatica.gui.button.share": "Поделиться", 17 | "syncmatica.gui.button.unload": "Выгрузить", 18 | "syncmatica.gui.button.view_syncmatics": "Просмотр Синкматик", 19 | "syncmatica.gui.label.placement_info.dimension_id": "Измерение", 20 | "syncmatica.gui.label.placement_info.display_name": "Name", 21 | "syncmatica.gui.label.placement_info.file_name": "Имя Файла", 22 | "syncmatica.gui.label.placement_info.last_modified": "Последнее изменение", 23 | "syncmatica.gui.label.placement_info.owner": "Изначально Разместил(а)", 24 | "syncmatica.gui.label.placement_info.position": "Источник (xyz)", 25 | "syncmatica.gui.label.placement_info.schema": "Minecraft: §f%s§r [Schema §f%d§r]", 26 | "syncmatica.gui.label.placement_info.version": "Версия Litematic: §f%d§r", 27 | "syncmatica.gui.title.manage_server_placements": "Управление Размещениями на Сервере - %s", 28 | "syncmatica.success.modification_accepted": "Запрос на Изменение Принят - Заблокируйте Размещение снова чтобы поделиться результатами с сервером" 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/data/ServerPosition.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.data; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonPrimitive; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.core.GlobalPos; 8 | 9 | public class ServerPosition 10 | { 11 | private final BlockPos position; 12 | private final String dimensionId; 13 | 14 | public static final String NETHER_DIMENSION_ID = "minecraft:the_nether"; 15 | public static final String OVERWORLD_DIMENSION_ID = "minecraft:overworld"; 16 | 17 | public ServerPosition(final BlockPos pos, final String dim) 18 | { 19 | position = pos; 20 | dimensionId = dim; 21 | } 22 | 23 | public static ServerPosition fromGlobalPos(GlobalPos pos) 24 | { 25 | return new ServerPosition(pos.pos(), pos.dimension().identifier().toString()); 26 | } 27 | 28 | public BlockPos getBlockPosition() 29 | { 30 | return position; 31 | } 32 | 33 | public String getDimensionId() 34 | { 35 | return dimensionId; 36 | } 37 | 38 | public JsonObject toJson() 39 | { 40 | final JsonObject obj = new JsonObject(); 41 | final JsonArray arr = new JsonArray(); 42 | arr.add(new JsonPrimitive(position.getX())); 43 | arr.add(new JsonPrimitive(position.getY())); 44 | arr.add(new JsonPrimitive(position.getZ())); 45 | obj.add("position", arr); 46 | obj.add("dimension", new JsonPrimitive(dimensionId)); 47 | return obj; 48 | } 49 | 50 | public static ServerPosition fromJson(final JsonObject obj) 51 | { 52 | if (obj.has("position") && obj.has("dimension")) 53 | { 54 | final int x; 55 | final int y; 56 | final int z; 57 | final JsonArray arr = obj.get("position").getAsJsonArray(); 58 | x = arr.get(0).getAsInt(); 59 | y = arr.get(1).getAsInt(); 60 | z = arr.get(2).getAsInt(); 61 | final BlockPos pos = new BlockPos(x, y, z); 62 | return new ServerPosition(pos, obj.get("dimension").getAsString()); 63 | } 64 | // could try to throw an exception and catch it or maybe log the thing idk 65 | // TODO: Decide 66 | return null; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/ScreenHelper.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica; 2 | 3 | import java.util.function.Consumer; 4 | import ch.endte.syncmatica.Context; 5 | import ch.endte.syncmatica.data.ServerPlacement; 6 | 7 | import fi.dy.masa.malilib.gui.GuiBase; 8 | import fi.dy.masa.malilib.gui.Message; 9 | import fi.dy.masa.malilib.util.InfoUtils; 10 | 11 | public class ScreenHelper 12 | { 13 | private static ScreenHelper instance; 14 | 15 | private Consumer updateListener; 16 | private GuiBase currentGui = null; 17 | private Context context; 18 | 19 | public static void ifPresent(final Consumer callable) 20 | { 21 | if (instance != null) 22 | { 23 | callable.accept(instance); 24 | } 25 | } 26 | 27 | public static void init() 28 | { 29 | if (instance != null) 30 | { 31 | instance.detach(); 32 | } 33 | instance = new ScreenHelper(); 34 | } 35 | 36 | public static void close() 37 | { 38 | if (instance != null) 39 | { 40 | instance.detach(); 41 | } 42 | instance = null; 43 | } 44 | 45 | private ScreenHelper() 46 | { 47 | } 48 | 49 | public void setActiveContext(final Context con) 50 | { 51 | detach(); 52 | context = con; 53 | attach(); 54 | updateCurrentScreen(); 55 | } 56 | 57 | public void setCurrentGui(final GuiBase gui) 58 | { 59 | currentGui = gui; 60 | } 61 | 62 | public void addMessage(final Message.MessageType type, final String messageKey, final Object... args) 63 | { 64 | InfoUtils.showGuiOrInGameMessage(type, messageKey, args); 65 | } 66 | 67 | private void updateCurrentScreen() 68 | { 69 | if (currentGui != null) 70 | { 71 | currentGui.initGui(); 72 | } 73 | } 74 | 75 | private void attach() 76 | { 77 | updateListener = p -> updateCurrentScreen(); 78 | context.getSyncmaticManager().addServerPlacementConsumer(updateListener); 79 | } 80 | 81 | private void detach() 82 | { 83 | if (context != null) 84 | { 85 | context.getSyncmaticManager().removeServerPlacementConsumer(updateListener); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/resources/assets/syncmatica/lang/uk_ua.json: -------------------------------------------------------------------------------- 1 | { 2 | "syncmatica.gui.button.view_syncmatics": "Переглянути синхроматики", 3 | "syncmatica.gui.button.material_gatherings": "Зібрано матеріалів", 4 | "syncmatica.gui.button.load": "Завантажити", 5 | "syncmatica.gui.button.unload": "Розвантажити", 6 | "syncmatica.gui.button.remove": "Видалити", 7 | "syncmatica.gui.button.download": "Завантажити", 8 | "syncmatica.gui.button.downloading": "Завантаження…", 9 | "syncmatica.gui.button.share": "Поділитися", 10 | "syncmatica.gui.button.material_gathering_placement": "Матеріали", 11 | "syncmatica.gui.title.manage_server_placements": "Manage Server Placements — %s", 12 | "syncmatica.gui.label.placement_info.display_name": "Назва", 13 | "syncmatica.gui.label.placement_info.file_name": "Назва файлу", 14 | "syncmatica.gui.label.placement_info.position": "Походженння (xyz)", 15 | "syncmatica.gui.label.placement_info.dimension_id": "Вимір", 16 | "syncmatica.gui.label.placement_info.owner": "Уперше поширив", 17 | "syncmatica.gui.label.placement_info.last_modified": "Востаннє змінив", 18 | "syncmatica.gui.label.placement_info.version": "Версія Litematic: §f%d§r", 19 | "syncmatica.gui.label.placement_info.schema": "Minecraft: §f%s§r [Схема §f%d§r]", 20 | "syncmatica.error.share_without_shift": "Натисніть Shift, щоб поділитися", 21 | "syncmatica.error.share_modified_subregions": "Помилка спільного доступу — підключений сервер не підтримує спільний доступ до змінених підрегіонів — його потрібно оновити.", 22 | "syncmatica.error.modification_deny": "Запит на зміну відхилено — інший користувач уже змінює це розміщення.", 23 | "syncmatica.error.player_dimension_mismatch": "Для завантаження гравець має бути в тому ж розмірі, що й місце розміщення.", 24 | "syncmatica.error.create_from_schematic": "Помилка створення синхромматики зі схематики: {}", 25 | "syncmatica.error.share_incompatible_schematic": "Перетворіть цей файл за допомогою менеджера схематик із головного меню, щоб поділитися ним", 26 | "syncmatica.error.invalid_file": "Неможливо поділитися цим типом файлу", 27 | "syncmatica.error.failed_to_load": "Не вдалося завантажити litematic {}", 28 | "syncmatica.success.modification_accepted": "Запит на зміну прийнято — знову заблокуйте розміщення, щоб надіслати результати на сервер" 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/extended_core/PlayerIdentifierProvider.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.extended_core; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.communication.ExchangeTarget; 5 | import ch.endte.syncmatica.communication.ServerCommunicationManager; 6 | import com.google.gson.JsonObject; 7 | import com.mojang.authlib.GameProfile; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.UUID; 12 | 13 | public class PlayerIdentifierProvider { 14 | private final Map identifiers = new HashMap<>(); 15 | private final Context context; 16 | 17 | public PlayerIdentifierProvider(final Context context) { 18 | this.context = context; 19 | identifiers.put(PlayerIdentifier.MISSING_PLAYER_UUID, PlayerIdentifier.MISSING_PLAYER); 20 | } 21 | 22 | public PlayerIdentifier createOrGet(final ExchangeTarget exchangeTarget) { 23 | final ServerCommunicationManager profileProvider = (ServerCommunicationManager) context.getCommunicationManager(); 24 | return createOrGet(profileProvider.getGameProfile(exchangeTarget)); 25 | } 26 | 27 | public PlayerIdentifier createOrGet(final GameProfile gameProfile) { 28 | return createOrGet(gameProfile.id(), gameProfile.name()); 29 | } 30 | 31 | public PlayerIdentifier createOrGet(final UUID uuid, final String playerName) { 32 | final PlayerIdentifier playerIdentifier = identifiers.computeIfAbsent(uuid, id -> new PlayerIdentifier(uuid, playerName)); 33 | if (!context.isServer()) { 34 | playerIdentifier.updatePlayerName(playerName); // trust that the latest value is up-to-date on the client 35 | } 36 | return playerIdentifier; 37 | } 38 | 39 | public void updateName(final UUID uuid, final String playerName) { 40 | createOrGet(uuid, playerName).updatePlayerName(playerName); 41 | } 42 | 43 | public PlayerIdentifier fromJson(final JsonObject obj) { 44 | if (!obj.has("uuid") || !obj.has("name")) { 45 | return PlayerIdentifier.MISSING_PLAYER; 46 | } 47 | 48 | final UUID jsonUUID = UUID.fromString(obj.get("uuid").getAsString()); 49 | 50 | return identifiers.computeIfAbsent(jsonUUID, 51 | key -> new PlayerIdentifier(jsonUUID, obj.get("name").getAsString()) 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/Reference.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica; 2 | 3 | import java.nio.file.Path; 4 | import ch.endte.syncmatica.util.StringTools; 5 | import net.fabricmc.api.EnvType; 6 | import net.fabricmc.loader.api.FabricLoader; 7 | import net.minecraft.SharedConstants; 8 | 9 | /** 10 | * Main (Generic) Reference Calls -- 11 | * Should use the "Context" versions of some of these for their respective context. 12 | * These are used to help control what contexts get registered and when (Server versus Client) 13 | */ 14 | public class Reference 15 | { 16 | public static final String MOD_ID = "syncmatica"; 17 | public static final String MOD_NAME = "Syncmatica"; 18 | public static final String MOD_VERSION = StringTools.getModVersion(MOD_ID); // No more manually typing in the version # :) 19 | public static final String MC_VERSION = SharedConstants.getCurrentVersion().id(); 20 | private static final EnvType MOD_ENV = FabricLoader.getInstance().getEnvironmentType(); 21 | public static final boolean MOD_DEBUG = false; 22 | 23 | // Fixes various file saving problems by using the FAPI GameDir/ConfigDir instead of "." 24 | public static final Path GAME_ROOT = FabricLoader.getInstance().getGameDir(); 25 | public static final Path CONFIG_ROOT = FabricLoader.getInstance().getConfigDir(); 26 | 27 | private static boolean DEDICATED_SERVER = false; 28 | private static boolean INTEGRATED_SERVER = false; 29 | private static boolean OPEN_TO_LAN = false; 30 | 31 | public static boolean isClient() 32 | { 33 | return MOD_ENV == EnvType.CLIENT; 34 | } 35 | public static boolean isServer() 36 | { 37 | return MOD_ENV == EnvType.SERVER; 38 | } 39 | public static boolean isDedicatedServer() 40 | { 41 | return DEDICATED_SERVER; 42 | } 43 | public static boolean isIntegratedServer() 44 | { 45 | return INTEGRATED_SERVER; 46 | } 47 | public static boolean isOpenToLan() 48 | { 49 | return OPEN_TO_LAN; 50 | } 51 | 52 | public static void setDedicatedServer(boolean toggle) 53 | { 54 | DEDICATED_SERVER = toggle; 55 | } 56 | public static void setIntegratedServer(boolean toggle) 57 | { 58 | INTEGRATED_SERVER = toggle; 59 | } 60 | public static void setOpenToLan(boolean toggle) 61 | { 62 | OPEN_TO_LAN = toggle; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica_mixin/MixinGuiMainMenu.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica_mixin; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.litematica.LitematicManager; 5 | import ch.endte.syncmatica.litematica.gui.ButtonListenerChangeMenu; 6 | import ch.endte.syncmatica.litematica.gui.MainMenuButtonType; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | import fi.dy.masa.malilib.gui.GuiBase; 13 | import fi.dy.masa.malilib.gui.button.ButtonGeneric; 14 | import fi.dy.masa.malilib.util.StringUtils; 15 | import fi.dy.masa.litematica.gui.GuiMainMenu; 16 | import fi.dy.masa.litematica.selection.SelectionMode; 17 | 18 | @Mixin(GuiMainMenu.class) 19 | public class MixinGuiMainMenu extends GuiBase { 20 | 21 | @Inject(method = "initGui", at = @At("RETURN"), remap = false) 22 | public void initGui(final CallbackInfo ci) { 23 | final int width = getButtonWidth(); 24 | final int x = 52 + 2 * width; 25 | int y = 30; 26 | createChangeMenuButton(x, y, width, MainMenuButtonType.VIEW_SYNCMATICS); 27 | y += 22; 28 | createChangeMenuButton(x, y, width, MainMenuButtonType.MATERIAL_GATHERINGS).setEnabled(false); 29 | } 30 | 31 | @Unique 32 | private ButtonGeneric createChangeMenuButton(final int x, final int y, final int width, final MainMenuButtonType type) { 33 | final ButtonGeneric button = new ButtonGeneric(x, y, width, 20, type.getTranslatedKey(), type.getIcon()); 34 | final Context con = LitematicManager.getInstance().getActiveContext(); 35 | button.setEnabled(con != null && con.isStarted()); 36 | addButton(button, new ButtonListenerChangeMenu(type, this)); 37 | return button; 38 | } 39 | 40 | @Unique 41 | private int getButtonWidth() { 42 | int width = 0; 43 | 44 | for (final MainMenuButtonType type : MainMenuButtonType.values()) { 45 | width = Math.max(width, getStringWidth(type.getTranslatedKey()) + 30); 46 | } 47 | 48 | for (final SelectionMode mode : SelectionMode.values()) { 49 | final String label = StringUtils.translate("litematica.gui.button.area_selection_mode", mode.getDisplayName()); 50 | width = Math.max(width, getStringWidth(label) + 10); 51 | } 52 | 53 | return width; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/schematic/FileType.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica.schematic; 2 | 3 | import java.io.File; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import net.minecraft.util.StringRepresentable; 7 | import com.google.common.collect.ImmutableList; 8 | import org.jspecify.annotations.NonNull; 9 | 10 | import com.mojang.serialization.Codec; 11 | 12 | /** 13 | * Cloned from Litematica 1.21.5 -- Sakura 14 | */ 15 | public enum FileType implements StringRepresentable 16 | { 17 | INVALID, 18 | UNKNOWN, 19 | JSON, 20 | LITEMATICA_SCHEMATIC, 21 | SCHEMATICA_SCHEMATIC, 22 | SPONGE_SCHEMATIC, 23 | VANILLA_STRUCTURE; 24 | 25 | public static final EnumCodec CODEC = StringRepresentable.fromEnum(FileType::values); 26 | public static final ImmutableList VALUES = ImmutableList.copyOf(values()); 27 | 28 | public Codec codec() 29 | { 30 | return CODEC; 31 | } 32 | 33 | public static FileType fromName(String fileName) 34 | { 35 | if (fileName.endsWith(".litematic")) 36 | { 37 | return LITEMATICA_SCHEMATIC; 38 | } 39 | else if (fileName.endsWith(".schematic")) 40 | { 41 | return SCHEMATICA_SCHEMATIC; 42 | } 43 | else if (fileName.endsWith(".nbt")) 44 | { 45 | return VANILLA_STRUCTURE; 46 | } 47 | else if (fileName.endsWith(".schem")) 48 | { 49 | return SPONGE_SCHEMATIC; 50 | } 51 | else if (fileName.endsWith(".json")) 52 | { 53 | return JSON; 54 | } 55 | 56 | return UNKNOWN; 57 | } 58 | 59 | @Deprecated 60 | public static FileType fromFile(File file) 61 | { 62 | if (file.isFile() && file.canRead()) 63 | { 64 | return fromName(file.getName()); 65 | } 66 | else 67 | { 68 | return INVALID; 69 | } 70 | } 71 | 72 | public static FileType fromFile(Path file) 73 | { 74 | if (Files.exists(file) && Files.isReadable(file)) 75 | { 76 | return fromName(file.getFileName().toString()); 77 | } 78 | else 79 | { 80 | return INVALID; 81 | } 82 | } 83 | 84 | public static String getString(FileType type) 85 | { 86 | return switch (type) 87 | { 88 | case LITEMATICA_SCHEMATIC -> "litematic"; 89 | case SCHEMATICA_SCHEMATIC -> "schematic"; 90 | case SPONGE_SCHEMATIC -> "sponge"; 91 | case VANILLA_STRUCTURE -> "vanilla_nbt"; 92 | case JSON -> "JSON"; 93 | case INVALID -> "invalid"; 94 | case UNKNOWN -> "unknown"; 95 | }; 96 | } 97 | 98 | @Override 99 | public @NonNull String getSerializedName() 100 | { 101 | return getString(this); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/extended_core/SubRegionPlacementModification.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.extended_core; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonPrimitive; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.world.level.block.Mirror; 8 | import net.minecraft.world.level.block.Rotation; 9 | 10 | public class SubRegionPlacementModification { 11 | public final String name; 12 | public final BlockPos position; 13 | public final Rotation rotation; 14 | public final Mirror mirror; 15 | 16 | SubRegionPlacementModification(final String name, final BlockPos position, final Rotation rotation, final Mirror mirror) { 17 | this.name = name; 18 | this.position = position; 19 | this.rotation = rotation; 20 | this.mirror = mirror; 21 | } 22 | 23 | public JsonObject toJson() { 24 | final JsonObject obj = new JsonObject(); 25 | 26 | final JsonArray arr = new JsonArray(); 27 | arr.add(position.getX()); 28 | arr.add(position.getY()); 29 | arr.add(position.getZ()); 30 | obj.add("position", arr); 31 | 32 | obj.add("name", new JsonPrimitive(name)); 33 | obj.add("rotation", new JsonPrimitive(rotation.name())); 34 | obj.add("mirror", new JsonPrimitive(mirror.name())); 35 | 36 | return obj; 37 | } 38 | 39 | public static SubRegionPlacementModification fromJson(final JsonObject obj) { 40 | if ( 41 | !obj.has("name") 42 | || !obj.has("position") 43 | || !obj.has("rotation") 44 | || !obj.has("mirror") 45 | ) { 46 | 47 | return null; 48 | } 49 | final String name = obj.get("name").getAsString(); 50 | 51 | final JsonArray arr = obj.get("position").getAsJsonArray(); 52 | if (arr.size() != 3) { 53 | 54 | return null; 55 | } 56 | final BlockPos position = new BlockPos( 57 | arr.get(0).getAsInt(), 58 | arr.get(1).getAsInt(), 59 | arr.get(2).getAsInt() 60 | ); 61 | 62 | final Rotation rotation = Rotation.valueOf(obj.get("rotation").getAsString()); 63 | final Mirror mirror = Mirror.valueOf(obj.get("mirror").getAsString()); 64 | 65 | return new SubRegionPlacementModification(name, position, rotation, mirror); 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return String.format("[name=%s, position=%s, rotation=%s, mirror=%s]", name, position, rotation, mirror); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/network/handler/ServerPlayHandler.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.network.handler; 2 | 3 | import javax.annotation.Nonnull; 4 | import ch.endte.syncmatica.network.actor.IServerPlay; 5 | import ch.endte.syncmatica.network.SyncmaticaPacket; 6 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 7 | import net.minecraft.network.protocol.Packet; 8 | import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; 9 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 10 | import net.minecraft.server.level.ServerPlayer; 11 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 12 | 13 | /** 14 | * Network packet senders / receivers (Server Context) 15 | */ 16 | public abstract class ServerPlayHandler 17 | { 18 | public static void decodeSyncData(@Nonnull SyncmaticaPacket data, @Nonnull ServerGamePacketListenerImpl handler) 19 | { 20 | IServerPlay iDo = ((IServerPlay) handler); 21 | 22 | iDo.syncmatica$operateComms(sm -> sm.onPacket(iDo.syncmatica$getExchangeTarget(), data.getType(), data.getPacket())); 23 | } 24 | 25 | public static void decodeSyncData(@Nonnull SyncmaticaPacket data, @Nonnull IServerPlay iDo) 26 | { 27 | iDo.syncmatica$operateComms(sm -> sm.onPacket(iDo.syncmatica$getExchangeTarget(), data.getType(), data.getPacket())); 28 | } 29 | 30 | public static void encodeSyncData(@Nonnull SyncmaticaPacket data, @Nonnull ServerGamePacketListenerImpl handler) 31 | { 32 | sendSyncPacket(new SyncmaticaPacket.Payload(data), handler); 33 | } 34 | 35 | public static void encodeSyncData(@Nonnull SyncmaticaPacket data, @Nonnull ServerPlayer player) 36 | { 37 | sendSyncPacket(new SyncmaticaPacket.Payload(data), player); 38 | } 39 | 40 | public static void receiveSyncPayload(SyncmaticaPacket.Payload payload, ServerPlayNetworking.Context context) 41 | { 42 | decodeSyncData(payload.data(), context.player().connection); 43 | } 44 | 45 | public static void sendSyncPacket(@Nonnull T payload, @Nonnull ServerPlayer player) 46 | { 47 | if (ServerPlayNetworking.canSend(player, payload.type())) 48 | { 49 | ServerPlayNetworking.send(player, payload); 50 | } 51 | } 52 | 53 | public static void sendSyncPacket(@Nonnull T payload, @Nonnull ServerGamePacketListenerImpl handler) 54 | { 55 | Packet packet = new ClientboundCustomPayloadPacket(payload); 56 | 57 | if (handler.shouldHandleMessage(packet)) 58 | { 59 | handler.send(packet); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/mixin/MixinMinecraftServer.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.mixin; 2 | 3 | import ch.endte.syncmatica.Reference; 4 | import ch.endte.syncmatica.Syncmatica; 5 | import ch.endte.syncmatica.communication.ServerCommunicationManager; 6 | import ch.endte.syncmatica.data.FileStorage; 7 | import ch.endte.syncmatica.data.SyncmaticManager; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | import net.minecraft.server.MinecraftServer; 13 | import net.minecraft.world.level.storage.LevelResource; 14 | 15 | @Mixin(MinecraftServer.class) 16 | public class MixinMinecraftServer 17 | { 18 | @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;initServer()Z"), method = "runServer") 19 | private void syncmatica$onServerStarting(CallbackInfo ci) 20 | { 21 | final MinecraftServer server = (MinecraftServer) (Object) this; 22 | Syncmatica.debug("MixinMinecraftServer#onServerStarting()"); 23 | 24 | if (server.isDedicatedServer()) 25 | { 26 | Reference.setDedicatedServer(true); 27 | Reference.setOpenToLan(false); 28 | } 29 | if (server.isSingleplayer()) 30 | { 31 | Reference.setOpenToLan(false); 32 | } 33 | } 34 | 35 | @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;buildServerStatus()Lnet/minecraft/network/protocol/status/ServerStatus;", ordinal = 0), method = "runServer") 36 | private void syncmatica$onServerStarted(CallbackInfo ci) 37 | { 38 | final MinecraftServer server = (MinecraftServer) (Object) this; 39 | Syncmatica.debug("MixinMinecraftServer#onServerStarted()"); 40 | 41 | if (server.isDedicatedServer()) 42 | { 43 | Reference.setDedicatedServer(true); 44 | Reference.setOpenToLan(false); 45 | } 46 | if (server.isSingleplayer()) 47 | { 48 | Reference.setOpenToLan(false); 49 | } 50 | 51 | // Process Syncmatica Server Context 52 | Syncmatica.initServer( 53 | new ServerCommunicationManager(), 54 | new FileStorage(), 55 | new SyncmaticManager(), 56 | !server.isDedicatedServer(), 57 | server.getWorldPath(LevelResource.ROOT).normalize() 58 | ).startup(); 59 | } 60 | 61 | @Inject(at = @At("TAIL"), method = "stopServer") 62 | private void syncmatica$onServerStopped(CallbackInfo info) 63 | { 64 | //final MinecraftServer server = (MinecraftServer) (Object) this; 65 | Syncmatica.debug("MixinMinecraftServer#onServerStopped()"); 66 | 67 | Reference.setIntegratedServer(false); 68 | Syncmatica.shutdown(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica_mixin/MixinSchematicPlacement.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica_mixin; 2 | 3 | import ch.endte.syncmatica.litematica.IIDContainer; 4 | import ch.endte.syncmatica.litematica.MovingFinisher; 5 | 6 | //@Mixin(SchematicPlacement.class) 7 | public abstract class MixinSchematicPlacement implements IIDContainer, MovingFinisher { 8 | 9 | // unsure if I can just make an assignment here so I mixin to the constructor 10 | // @Unique 11 | // UUID serverId; 12 | 13 | private MixinSchematicPlacement() { 14 | } 15 | 16 | // @Inject(method = "toJson", at = @At("RETURN"), remap = false) 17 | // public void saveUuid(final CallbackInfoReturnable cir) { 18 | // final JsonObject saveData = cir.getReturnValue(); 19 | // if (saveData != null) { 20 | // if (serverId != null) { 21 | // saveData.add("syncmatica_uuid", new JsonPrimitive(serverId.toString())); 22 | // } 23 | // } 24 | // } 25 | 26 | // @Inject(method = "fromJson", at = @At("RETURN"), remap = false, cancellable = true) 27 | // private static void loadSyncmatic(final JsonObject obj, final CallbackInfoReturnable cir) { 28 | // if (JsonUtils.hasString(obj, "syncmatica_uuid")) { 29 | // final SchematicPlacement newInstance = cir.getReturnValue(); 30 | // if (newInstance != null) { 31 | // ((IIDContainer) newInstance).syncmatica$setServerId(UUID.fromString(obj.get("syncmatica_uuid").getAsString())); 32 | // cir.setReturnValue(null); 33 | // LitematicManager.getInstance().preLoad(newInstance); 34 | // } 35 | // } 36 | // } 37 | 38 | /* 39 | @Inject(method = "(Lfi/dy/masa/litematica/schematic/LitematicaSchematic;Lnet/minecraft/util/math/BlockPos;Ljava/lang/String;ZZLfi/dy/masa/litematica/schematic/placement/SchematicPlacementManager;)V", at = @At("TAIL"), remap = false) 40 | public void setNull(LitematicaSchematic schematic, BlockPos origin, String name, boolean enabled, boolean enableRender, SchematicPlacementManager placementManager, CallbackInfo ci) { 41 | serverId = null; 42 | } 43 | */ 44 | 45 | // @Inject(method = "*", at = @At("TAIL"), remap = false) 46 | // public void setNull(final LitematicaSchematic schematic, final BlockPos origin, final String name, final boolean enabled, final boolean enableRender, final CallbackInfo ci) 47 | // { 48 | // serverId = null; 49 | // } 50 | // 51 | // @Override 52 | // public void syncmatica$setServerId(final UUID i) { 53 | // serverId = i; 54 | // } 55 | // 56 | // @Override 57 | // public UUID syncmatica$getServerId() { 58 | // return serverId; 59 | // } 60 | 61 | // @Override 62 | // @Invoker(value = "onModified", remap = false) 63 | // public abstract void onFinishedMoving(String subRegionName, SchematicPlacementManager manager); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/network/handler/ClientPlayHandler.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.network.handler; 2 | 3 | import javax.annotation.Nonnull; 4 | import ch.endte.syncmatica.network.SyncmaticaPacket; 5 | import ch.endte.syncmatica.network.actor.ActorClientPlayHandler; 6 | 7 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraft.client.multiplayer.ClientPacketListener; 10 | import net.minecraft.network.protocol.Packet; 11 | import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; 12 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | /** 16 | * Network packet senders / receivers (Client Context) 17 | */ 18 | public class ClientPlayHandler 19 | { 20 | public static void decodeSyncData(@Nonnull SyncmaticaPacket data, ClientPacketListener handler) 21 | { 22 | CallbackInfo ci = new CallbackInfo("receiveSyncPacket", false); 23 | ActorClientPlayHandler.getInstance().packetEvent(data.getType(), data.getPacket(), handler, ci); 24 | } 25 | 26 | public static void encodeSyncData(@Nonnull SyncmaticaPacket data, ClientPacketListener handler) 27 | { 28 | SyncmaticaPacket.Payload payload = new SyncmaticaPacket.Payload(data); 29 | if (handler != null) 30 | { 31 | sendSyncPacket(payload, handler); 32 | } 33 | else 34 | { 35 | sendSyncPacket(payload); 36 | } 37 | } 38 | 39 | public static void receiveSyncPayload(@Nonnull SyncmaticaPacket data) 40 | { 41 | CallbackInfo ci = new CallbackInfo("receiveSyncPacket", false); 42 | ActorClientPlayHandler.getInstance().packetEvent(data.getType(), data.getPacket(), Minecraft.getInstance().getConnection(), ci); 43 | } 44 | 45 | public static void receiveSyncPayload(SyncmaticaPacket.Payload payload, ClientPlayNetworking.Context context) 46 | { 47 | // Has threading issues ? 48 | if (context.client().getConnection() != null) 49 | { 50 | decodeSyncData(payload.data(), context.client().getConnection()); 51 | } 52 | else 53 | { 54 | decodeSyncData(payload.data(), Minecraft.getInstance().getConnection()); 55 | } 56 | } 57 | 58 | public static void sendSyncPacket(@Nonnull T payload) 59 | { 60 | if (ClientPlayNetworking.canSend(payload.type())) 61 | { 62 | ClientPlayNetworking.send(payload); 63 | } 64 | } 65 | 66 | public static void sendSyncPacket(@Nonnull T payload, @Nonnull ClientPacketListener handler) 67 | { 68 | Packet packet = new ServerboundCustomPayloadPacket(payload); 69 | 70 | if (handler.shouldHandleMessage(packet)) 71 | { 72 | handler.send(packet); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/mixin/MixinClientPlayNetworkHandler.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.mixin; 2 | 3 | import java.util.function.Consumer; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.multiplayer.ClientPacketListener; 6 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 7 | import ch.endte.syncmatica.Context; 8 | import ch.endte.syncmatica.Reference; 9 | import ch.endte.syncmatica.Syncmatica; 10 | import ch.endte.syncmatica.communication.ClientCommunicationManager; 11 | import ch.endte.syncmatica.communication.ExchangeTarget; 12 | import ch.endte.syncmatica.network.actor.IClientPlay; 13 | import ch.endte.syncmatica.network.handler.ClientPlayHandler; 14 | import ch.endte.syncmatica.network.SyncmaticaPacket; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.Unique; 17 | import org.spongepowered.asm.mixin.injection.At; 18 | import org.spongepowered.asm.mixin.injection.Inject; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 20 | 21 | @Mixin(value = ClientPacketListener.class, priority = 1001) 22 | public abstract class MixinClientPlayNetworkHandler implements IClientPlay 23 | { 24 | @Unique 25 | public ExchangeTarget exTarget = null; 26 | @Unique 27 | private ClientCommunicationManager comManager = null; 28 | 29 | // This exists because of the Communications Manager / Exchange Target system, 30 | // and FAPI networking is too slow to register the receivers 31 | @Inject(method = "handleCustomPayload", at = @At("HEAD"), cancellable = true) 32 | private void syncmatica$handlePacket(CustomPacketPayload packet, CallbackInfo ci) 33 | { 34 | if (!Minecraft.getInstance().isSameThread()) 35 | { 36 | return; //only execute packet on main thread 37 | } 38 | 39 | if (packet.type().id().getNamespace().equals(Reference.MOD_ID)) 40 | { 41 | SyncmaticaPacket.Payload payload = (SyncmaticaPacket.Payload) packet; 42 | ClientPlayHandler.decodeSyncData(payload.data(), (ClientPacketListener) (Object) this); 43 | 44 | // Cancel unnecessary processing if a PacketType we own is caught 45 | if (ci.isCancellable()) 46 | ci.cancel(); 47 | } 48 | } 49 | 50 | @Override 51 | public void syncmatica$operateComms(final Consumer operation) 52 | { 53 | if (comManager == null) 54 | { 55 | final Context con = Syncmatica.getContext(Syncmatica.CLIENT_CONTEXT); 56 | if (con != null) 57 | { 58 | comManager = (ClientCommunicationManager) con.getCommunicationManager(); 59 | } 60 | } 61 | if (comManager != null) 62 | { 63 | operation.accept(comManager); 64 | } 65 | } 66 | 67 | @Override 68 | public ExchangeTarget syncmatica$getExchangeTarget() 69 | { 70 | if (exTarget == null) 71 | { 72 | exTarget = new ExchangeTarget((ClientPacketListener) (Object) this); 73 | } 74 | return exTarget; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/exchange/ModifyExchangeServer.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication.exchange; 2 | 3 | import java.util.UUID; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | import ch.endte.syncmatica.Context; 6 | import ch.endte.syncmatica.communication.ExchangeTarget; 7 | import ch.endte.syncmatica.data.ServerPlacement; 8 | import ch.endte.syncmatica.extended_core.PlayerIdentifier; 9 | import ch.endte.syncmatica.network.PacketType; 10 | import io.netty.buffer.Unpooled; 11 | 12 | public class ModifyExchangeServer extends AbstractExchange 13 | { 14 | private final ServerPlacement placement; 15 | UUID placementId; 16 | 17 | public ModifyExchangeServer(final UUID placeId, final ExchangeTarget partner, final Context con) 18 | { 19 | super(partner, con); 20 | placementId = placeId; 21 | placement = con.getSyncmaticManager().getPlacement(placementId); 22 | } 23 | 24 | @Override 25 | public boolean checkPacket(final PacketType type, final FriendlyByteBuf packetBuf) 26 | { 27 | return type.equals(PacketType.MODIFY_FINISH) && checkUUID(packetBuf, placement.getId()); 28 | } 29 | 30 | @Override 31 | public void handle(final PacketType type, final FriendlyByteBuf packetBuf) 32 | { 33 | packetBuf.readUUID(); // consume uuid 34 | if (type.equals(PacketType.MODIFY_FINISH)) 35 | { 36 | getContext().getCommunicationManager().receivePositionData(placement, packetBuf, getPartner()); 37 | 38 | final PlayerIdentifier identifier = getContext().getPlayerIdentifierProvider().createOrGet( 39 | getPartner() 40 | ); 41 | placement.setLastModifiedBy(identifier); 42 | getContext().getSyncmaticManager().updateServerPlacement(placement); 43 | succeed(); 44 | } 45 | } 46 | 47 | @Override 48 | public void init() 49 | { 50 | if (getPlacement() == null || getContext().getCommunicationManager().getModifier(placement) != null) 51 | { 52 | close(true); // equivalent to deny 53 | } 54 | else 55 | { 56 | accept(); 57 | } 58 | } 59 | 60 | private void accept() 61 | { 62 | final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); 63 | buf.writeUUID(placement.getId()); 64 | getPartner().sendPacket(PacketType.MODIFY_REQUEST_ACCEPT, buf, getContext()); 65 | getContext().getCommunicationManager().setModifier(placement, this); 66 | } 67 | 68 | @Override 69 | protected void sendCancelPacket() 70 | { 71 | final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); 72 | buf.writeUUID(placementId); 73 | getPartner().sendPacket(PacketType.MODIFY_REQUEST_DENY, buf, getContext()); 74 | } 75 | 76 | public ServerPlacement getPlacement() { return placement; } 77 | 78 | @Override 79 | protected void onClose() 80 | { 81 | if (getContext().getCommunicationManager().getModifier(placement) == this) 82 | { 83 | getContext().getCommunicationManager().setModifier(placement, null); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/exchange/VersionHandshakeServer.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication.exchange; 2 | 3 | import java.util.Collection; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | import ch.endte.syncmatica.Context; 6 | import ch.endte.syncmatica.Reference; 7 | import ch.endte.syncmatica.Syncmatica; 8 | import ch.endte.syncmatica.communication.ExchangeTarget; 9 | import ch.endte.syncmatica.communication.FeatureSet; 10 | import ch.endte.syncmatica.data.ServerPlacement; 11 | import ch.endte.syncmatica.network.PacketType; 12 | import io.netty.buffer.Unpooled; 13 | 14 | public class VersionHandshakeServer extends FeatureExchange 15 | { 16 | private String partnerVersion; 17 | public VersionHandshakeServer(final ExchangeTarget partner, final Context con) { super(partner, con); } 18 | 19 | @Override 20 | public boolean checkPacket(final PacketType type, final FriendlyByteBuf packetBuf) 21 | { 22 | return type.equals(PacketType.REGISTER_VERSION) 23 | || super.checkPacket(type, packetBuf); 24 | } 25 | 26 | @Override 27 | public void handle(final PacketType type, final FriendlyByteBuf packetBuf) 28 | { 29 | if (type.equals(PacketType.REGISTER_VERSION)) 30 | { 31 | partnerVersion = packetBuf.readUtf(PACKET_MAX_STRING_SIZE); 32 | if (!getContext().checkPartnerVersion(partnerVersion)) 33 | { 34 | Syncmatica.LOGGER.warn("Denying syncmatica join due to outdated client with local version {} and client version {} from partner {}", Reference.MOD_VERSION, partnerVersion, getPartner().getPersistentName()); 35 | // same as client - avoid further packets 36 | close(false); 37 | return; 38 | } 39 | final FeatureSet fs = FeatureSet.fromVersionString(partnerVersion); 40 | if (fs == null) 41 | { 42 | requestFeatureSet(); 43 | } 44 | else 45 | { 46 | getPartner().setFeatureSet(fs); 47 | onFeatureSetReceive(); 48 | } 49 | } 50 | else 51 | { 52 | super.handle(type, packetBuf); 53 | } 54 | } 55 | 56 | @Override 57 | public void onFeatureSetReceive() 58 | { 59 | Syncmatica.LOGGER.info("Syncmatica client joining with local version {} and client version {}", Reference.MOD_VERSION, partnerVersion); 60 | final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); 61 | final Collection l = getContext().getSyncmaticManager().getAll(); 62 | newBuf.writeInt(l.size()); 63 | for (final ServerPlacement p : l) 64 | { 65 | getManager().putMetaData(p, newBuf, getPartner()); 66 | } 67 | getPartner().sendPacket(PacketType.CONFIRM_USER, newBuf, getContext()); 68 | succeed(); 69 | } 70 | 71 | @Override 72 | public void init() 73 | { 74 | final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); 75 | newBuf.writeUtf(Reference.MOD_VERSION); 76 | getPartner().sendPacket(PacketType.REGISTER_VERSION, newBuf, getContext()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/extended_core/SubRegionData.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.extended_core; 2 | 3 | 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonElement; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import net.minecraft.core.BlockPos; 9 | import net.minecraft.world.level.block.Mirror; 10 | import net.minecraft.world.level.block.Rotation; 11 | 12 | public class SubRegionData { 13 | private boolean isModified; 14 | private Map modificationData; // is null when isModified is false 15 | 16 | public SubRegionData() { 17 | this(false, null); 18 | } 19 | 20 | public SubRegionData(final boolean isModified, final Map modificationData) { 21 | this.isModified = isModified; 22 | this.modificationData = modificationData; 23 | } 24 | 25 | public void reset() { 26 | isModified = false; 27 | modificationData = null; 28 | } 29 | 30 | public void modify( 31 | final String name, 32 | final BlockPos position, 33 | final Rotation rotation, 34 | final Mirror mirror 35 | ) { 36 | modify( 37 | new SubRegionPlacementModification( 38 | name, 39 | position, 40 | rotation, 41 | mirror 42 | ) 43 | ); 44 | } 45 | 46 | public void modify(final SubRegionPlacementModification subRegionPlacementModification) { 47 | if (subRegionPlacementModification == null) { 48 | 49 | return; 50 | } 51 | isModified = true; 52 | if (modificationData == null) { 53 | modificationData = new HashMap<>(); 54 | } 55 | modificationData.put(subRegionPlacementModification.name, subRegionPlacementModification); 56 | } 57 | 58 | public boolean isModified() { 59 | return isModified; 60 | } 61 | 62 | public Map getModificationData() { 63 | return modificationData; 64 | } 65 | 66 | public JsonElement toJson() { 67 | 68 | return modificationDataToJson(); 69 | } 70 | 71 | private JsonElement modificationDataToJson() { 72 | final JsonArray arr = new JsonArray(); 73 | 74 | for (final Map.Entry entry : modificationData.entrySet()) { 75 | arr.add(entry.getValue().toJson()); 76 | } 77 | 78 | return arr; 79 | } 80 | 81 | public static SubRegionData fromJson(final JsonElement obj) { 82 | final SubRegionData newSubRegionData = new SubRegionData(); 83 | 84 | newSubRegionData.isModified = true; 85 | 86 | for (final JsonElement modification : obj.getAsJsonArray()) { 87 | newSubRegionData.modify(SubRegionPlacementModification.fromJson(modification.getAsJsonObject())); 88 | } 89 | 90 | return newSubRegionData; 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | if (!isModified) { 96 | 97 | return "[]"; 98 | } 99 | 100 | return modificationData == null ? "[ERROR:null]" : modificationData.toString(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/exchange/VersionHandshakeClient.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication.exchange; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.Reference; 5 | import ch.endte.syncmatica.Syncmatica; 6 | import ch.endte.syncmatica.communication.ExchangeTarget; 7 | import ch.endte.syncmatica.communication.FeatureSet; 8 | import ch.endte.syncmatica.data.ServerPlacement; 9 | import ch.endte.syncmatica.litematica.LitematicManager; 10 | import ch.endte.syncmatica.network.PacketType; 11 | import io.netty.buffer.Unpooled; 12 | import net.minecraft.network.FriendlyByteBuf; 13 | 14 | public class VersionHandshakeClient extends FeatureExchange 15 | { 16 | private String partnerVersion; 17 | public VersionHandshakeClient(final ExchangeTarget partner, final Context con) { super(partner, con); } 18 | 19 | @Override 20 | public boolean checkPacket(final PacketType type, final FriendlyByteBuf packetBuf) 21 | { 22 | return type.equals(PacketType.CONFIRM_USER) 23 | || type.equals(PacketType.REGISTER_VERSION) 24 | || super.checkPacket(type, packetBuf); 25 | } 26 | 27 | @Override 28 | public void handle(final PacketType type, final FriendlyByteBuf packetBuf) 29 | { 30 | if (type.equals(PacketType.REGISTER_VERSION)) 31 | { 32 | final String version = packetBuf.readUtf(PACKET_MAX_STRING_SIZE); 33 | if (!getContext().checkPartnerVersion(version)) 34 | { 35 | // any further packets are risky so no further packets should get send 36 | Syncmatica.LOGGER.warn("Denying syncmatica join due to outdated server with local version {} and server version {}", Reference.MOD_VERSION, version); 37 | close(false); 38 | } 39 | else 40 | { 41 | Syncmatica.LOGGER.info("Accepting version {} from partner {}", version, getPartner().getPersistentName()); 42 | partnerVersion = version; 43 | final FeatureSet fs = FeatureSet.fromVersionString(version); 44 | if (fs == null) 45 | { 46 | requestFeatureSet(); 47 | } 48 | else 49 | { 50 | getPartner().setFeatureSet(fs); 51 | onFeatureSetReceive(); 52 | } 53 | } 54 | } 55 | else if (type.equals(PacketType.CONFIRM_USER)) 56 | { 57 | final int placementCount = packetBuf.readInt(); 58 | for (int i = 0; i < placementCount; i++) 59 | { 60 | final ServerPlacement p = getManager().receiveMetaData(packetBuf, getPartner()); 61 | getContext().getSyncmaticManager().addPlacement(p); 62 | } 63 | Syncmatica.LOGGER.info("Joining syncmatica server with local version {}", Reference.MOD_VERSION); 64 | LitematicManager.getInstance().commitLoad(); 65 | getContext().startup(); 66 | succeed(); 67 | } 68 | else 69 | { 70 | super.handle(type, packetBuf); 71 | } 72 | } 73 | 74 | @Override 75 | public void onFeatureSetReceive() 76 | { 77 | final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); 78 | newBuf.writeUtf(Reference.MOD_VERSION); 79 | getPartner().sendPacket(PacketType.REGISTER_VERSION, newBuf, getContext()); 80 | } 81 | 82 | @Override 83 | public void init() 84 | { 85 | // Not required - just await message from the server 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica_mixin/MixinWidgetSchematicPlacement.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica_mixin; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.litematica.LitematicManager; 5 | import ch.endte.syncmatica.litematica.gui.ButtonListenerShare; 6 | import fi.dy.masa.litematica.gui.widgets.WidgetListSchematicPlacements; 7 | import fi.dy.masa.litematica.gui.widgets.WidgetSchematicPlacement; 8 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacement; 9 | import fi.dy.masa.malilib.gui.GuiBase; 10 | import fi.dy.masa.malilib.gui.button.ButtonBase; 11 | import fi.dy.masa.malilib.gui.button.ButtonGeneric; 12 | import fi.dy.masa.malilib.gui.button.IButtonActionListener; 13 | import fi.dy.masa.malilib.gui.widgets.WidgetBase; 14 | import fi.dy.masa.malilib.gui.widgets.WidgetListEntryBase; 15 | import org.spongepowered.asm.mixin.Final; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Shadow; 18 | import org.spongepowered.asm.mixin.Unique; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 22 | 23 | @Mixin(WidgetSchematicPlacement.class) 24 | public abstract class MixinWidgetSchematicPlacement extends WidgetListEntryBase { 25 | @Shadow(remap = false) 26 | public int buttonsStartX; 27 | @Final 28 | @Shadow(remap = false) 29 | public SchematicPlacement placement; 30 | 31 | protected MixinWidgetSchematicPlacement(final int x, final int y, final int width, final int height, final SchematicPlacement entry, final int listIndex) { 32 | super(x, y, width, height, entry, listIndex); 33 | } 34 | 35 | @Inject(method = "", at = @At("TAIL"), remap = false) 36 | public void addUploadButton(final int x, final int y, final int width, final int height, final boolean isOdd, 37 | final SchematicPlacement placement, final int listIndex, final WidgetListSchematicPlacements parent, final CallbackInfo ci) { 38 | int i = 0; 39 | if (LitematicManager.getInstance().isSyncmatic(placement)) { 40 | for (final WidgetBase base : subWidgets) { 41 | if (base instanceof ButtonBase) { 42 | final ButtonBase button = (ButtonBase) base; 43 | if (++i == 1) { 44 | final IButtonActionListener oldAction = ((MixinButtonBase) button).getActionListener(); 45 | button.setActionListener((b, k) -> { 46 | if (GuiBase.isShiftDown()) { 47 | LitematicManager.getInstance().unrenderSchematicPlacement(placement); 48 | return; 49 | } 50 | oldAction.actionPerformedWithButton(b, k); 51 | }); 52 | 53 | } 54 | } 55 | } 56 | } 57 | 58 | final ButtonGeneric shareButton = new ButtonGeneric(buttonsStartX, y + 1, -1, true, "syncmatica.gui.button.share"); 59 | final Context con = LitematicManager.getInstance().getActiveContext(); 60 | final boolean buttonEnabled = con != null && con.isStarted() && !LitematicManager.getInstance().isSyncmatic(placement); 61 | shareButton.setEnabled(buttonEnabled); 62 | addButton(shareButton, new ButtonListenerShare(placement, parent.parent)); 63 | buttonsStartX = shareButton.getX() - 1; 64 | } 65 | 66 | @Unique 67 | public SchematicPlacement getPlacement() { 68 | return placement; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/gui/MultiTypeButton.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica.gui; 2 | 3 | import java.util.List; 4 | 5 | import fi.dy.masa.malilib.gui.button.ButtonBase; 6 | import fi.dy.masa.malilib.gui.button.ButtonGeneric; 7 | import fi.dy.masa.malilib.gui.button.IButtonActionListener; 8 | 9 | // Not sure how to add Icon support to this without a problematic implementation 10 | // Icon assumes final in its field - we cannot change the fact that the rendering probably 11 | // executes based on this assumption 12 | // for now I will leave the icons alone and pretend they don't exist 13 | // actually upon reading the code there might not be a problem with this. 14 | public class MultiTypeButton extends ButtonGeneric 15 | { 16 | IButtonType activeType; 17 | List types; 18 | 19 | public MultiTypeButton(final int x, final int y, final boolean rightAligned, final List types) 20 | { 21 | super(x, y, 1, 20, ""); 22 | this.types = types; 23 | activeType = types.get(0); 24 | update(); 25 | width = calculateWidth(); 26 | if (rightAligned) 27 | { 28 | this.x -= width; 29 | } 30 | actionListener = new MultiTypeListener(); 31 | } 32 | 33 | public void update() 34 | { 35 | updateType(); 36 | displayString = activeType.getTranslatedKey(); 37 | if (activeType.getHoverStrings() != null) 38 | { 39 | final List hoverStrings = activeType.getHoverStrings(); 40 | setHoverStrings(hoverStrings.toArray(new String[0])); 41 | } 42 | setEnabled(activeType.getButtonListener() != null); 43 | } 44 | 45 | private void updateType() 46 | { 47 | for (final IButtonType type : types) 48 | { 49 | if (type.isActive()) 50 | { 51 | activeType = type; 52 | return; 53 | } 54 | } 55 | // default type is 0 56 | activeType = types.get(0); 57 | } 58 | 59 | public IButtonType getActiveType() 60 | { 61 | return activeType; 62 | } 63 | 64 | private int calculateWidth() 65 | { 66 | int wMax = -1; 67 | for (final IButtonType type : types) 68 | { 69 | final int wType = calculateWidth(type); 70 | if (wType > wMax) 71 | { 72 | wMax = wType; 73 | } 74 | } 75 | return wMax; 76 | } 77 | 78 | private int calculateWidth(final IButtonType type) 79 | { 80 | int w = 0; 81 | if (type.getTranslatedKey() != null) 82 | { 83 | // is it really 30px?? need to check 84 | w += getStringWidth(type.getTranslatedKey()) + 10; 85 | } 86 | if (type.getIcon() != null) 87 | { 88 | w += type.getIcon().getWidth() + 8; 89 | } 90 | return w; 91 | } 92 | 93 | // gracefully ignored 94 | @Override 95 | public MultiTypeButton setActionListener(final IButtonActionListener listener) 96 | { 97 | return this; 98 | } 99 | 100 | public class MultiTypeListener implements IButtonActionListener 101 | { 102 | @Override 103 | public void actionPerformedWithButton(final ButtonBase button, final int mouseButton) 104 | { 105 | update(); 106 | if (activeType.getButtonListener() != null) 107 | { 108 | activeType.getButtonListener().actionPerformedWithButton(button, mouseButton); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Syncmatica 2 | 3 | Syncmatica is a mod which aims to mod into litematica so that schematics and their placements can be easily shared. 4 | 5 | ### Notice Please use with caution 6 | 7 | Syncmatica is a mod that gives its users a lot of power and can have consequences for the server. Only use this mod if 8 | you feel confident that your users won't abuse it too heavily. 9 | 10 | ## Setup 11 | 12 | Syncmatica is a mod for both Minecraft client and server. 13 | The mod works for Minecraft 1.18+. It's made for [Minecraft Fabric](https://fabricmc.net/). It relies 14 | on [litematica and malilib](https://masa.dy.fi/mcmods/client_mods/) to provide all client features. Please make sure to 15 | update litematica, malilib and other potentially conflicting mods like Multiconnect before making a bug report about 16 | Syncmaticas functionality :) 17 | 18 | Use [v0.3.11-1.18.2](https://github.com/End-Tech/syncmatica/releases/tag/v0.3.11-1.18.2) for 1.18 or 1.19. 19 | Use [v0.3.11-1.20.1](https://github.com/End-Tech/syncmatica/releases/tag/v0.3.11-1.20.1) for 1.20+. 20 | 21 | ### Client 22 | 23 | You first need to install fabric and add the litematica and malilib mods to your client. The next step is to move the 24 | Syncmatica mod file to the mod folder. Now you are ready to go. 25 | 26 | Versions as old as v0.0.0-dev.20210106.181551 appear to cause issues due to a field renaming or not existing or being 27 | invisible. If you have versions as old as that you will have to update or Syncmatica may not function properly. 28 | 29 | ### Server 30 | 31 | For the server you only need to install fabric and put Syncmatic in the mods folder, and you are good to go. 32 | 33 | After running the mod once it will create a configuration file that you can use to configure the mod as you please. 34 | See [Config Doku](https://github.com/End-Tech/syncmatica/blob/master/CONFIG.md) for more information. 35 | 36 | ## Usage 37 | 38 | Once installed on your client, you can join every server normally. For servers which have Syncmatica installed you will 39 | get access to a few extra buttons. 2 of them are in the main menu and allow you to see the placements that are shared on 40 | the server and download them. Another is in your schematic placement overview and allows you to share your own 41 | litematics with the server. 42 | 43 | You need to be in the same dimension as a syncmatic to load it. 44 | 45 | To modify a placement just unlock a placement on your client. Lock it again after making changes to share the changes 46 | with everyone. 47 | 48 | ## Project Status & Road Map 49 | v0.3.11 is a compatability fix with 50 | 51 | v0.3.10 is a quick hotfix for compatability issues with pca and other masa mods. 52 | 53 | v0.3.9 better chinese translation adds minor fixes and compatability with 1.20 54 | This was mostly done by other people. I want to thank them for their work. A thanks here to kpzip, whitecat346 and s-yh-china. (Especially kpzip <3) 55 | We drop compatability with 1.17 and 1.16 since it doesn't compile anymore and I do not feel like fixing that 56 | If you play 1.16 feel free to drop by in the discord to let me know. 57 | 58 | I have also seen that many servers have a problem with the amount of syncmatics that are persistently shared. 59 | The ownership is a pre-requirement for the change that is supposed to fix this. 60 | Adding a better fix for this is also on the roadmap, but I'm debating the priority since I don't get any feedback like this anymore. 61 | 62 | The MaterialGatherings button is supposed, to aid with collecting the materials as a group. It should synchronize and 63 | simplify the collection of material across the server. As of now it does nothing. 64 | 65 | ## Contact 66 | 67 | Feel free to join me on [Discord](https://discord.gg/6NPDVNMZ3T) for more information and help on the mod. 68 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/exchange/ShareLitematicExchange.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication.exchange; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.nio.file.Path; 5 | import net.minecraft.network.FriendlyByteBuf; 6 | import ch.endte.syncmatica.Context; 7 | import ch.endte.syncmatica.communication.ClientCommunicationManager; 8 | import ch.endte.syncmatica.communication.ExchangeTarget; 9 | import ch.endte.syncmatica.data.RedirectFileStorage; 10 | import ch.endte.syncmatica.data.ServerPlacement; 11 | import ch.endte.syncmatica.litematica.LitematicManager; 12 | import ch.endte.syncmatica.network.PacketType; 13 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacement; 14 | 15 | public class ShareLitematicExchange extends AbstractExchange 16 | { 17 | private final SchematicPlacement schematicPlacement; 18 | private final ServerPlacement toShare; 19 | private final Path toUpload; 20 | 21 | public ShareLitematicExchange(final SchematicPlacement schematicPlacement, final ExchangeTarget partner, final Context con) { this(schematicPlacement, partner, con, null); } 22 | 23 | public ShareLitematicExchange(final SchematicPlacement schematicPlacement, final ExchangeTarget partner, final Context con, final ServerPlacement p) 24 | { 25 | super(partner, con); 26 | this.schematicPlacement = schematicPlacement; 27 | toShare = p == null ? LitematicManager.getInstance().syncmaticFromSchematic(schematicPlacement) : p; 28 | toUpload = schematicPlacement.getSchematicFile(); 29 | } 30 | 31 | @Override 32 | public boolean checkPacket(final PacketType type, final FriendlyByteBuf packetBuf) 33 | { 34 | if (type.equals(PacketType.REQUEST_LITEMATIC) 35 | || type.equals(PacketType.REGISTER_METADATA) 36 | || type.equals(PacketType.CANCEL_SHARE)) 37 | { 38 | return AbstractExchange.checkUUID(packetBuf, toShare.getId()); 39 | } 40 | return false; 41 | } 42 | 43 | @Override 44 | public void handle(final PacketType type, final FriendlyByteBuf packetBuf) 45 | { 46 | if (type.equals(PacketType.REQUEST_LITEMATIC)) 47 | { 48 | packetBuf.readUUID(); 49 | final UploadExchange upload; 50 | try 51 | { 52 | upload = new UploadExchange(toShare, toUpload, getPartner(), getContext()); 53 | } 54 | catch (final FileNotFoundException e) 55 | { 56 | e.printStackTrace(); 57 | 58 | return; 59 | } 60 | getManager().startExchange(upload); 61 | return; 62 | } 63 | if (type.equals(PacketType.REGISTER_METADATA)) 64 | { 65 | final RedirectFileStorage redirect = (RedirectFileStorage) getContext().getFileStorage(); 66 | redirect.addRedirect(toUpload); 67 | LitematicManager.getInstance().renderSyncmatic(toShare, schematicPlacement, false); 68 | getContext().getSyncmaticManager().addPlacement(toShare); 69 | return; 70 | } 71 | if (type.equals(PacketType.CANCEL_SHARE)) 72 | { 73 | close(false); 74 | } 75 | } 76 | 77 | @Override 78 | public void init() 79 | { 80 | if (toShare == null) 81 | { 82 | close(false); 83 | return; 84 | } 85 | ((ClientCommunicationManager) getManager()).setSharingState(toShare, true); 86 | getContext().getSyncmaticManager().updateServerPlacement(toShare); 87 | getManager().sendMetaData(toShare, getPartner()); 88 | } 89 | 90 | @Override 91 | public void onClose() { ((ClientCommunicationManager) getManager()).setSharingState(toShare, false); } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica_mixin/MixinGuiPlacementConfiguration.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica_mixin; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.Feature; 5 | import ch.endte.syncmatica.data.ServerPlacement; 6 | import ch.endte.syncmatica.communication.ClientCommunicationManager; 7 | import ch.endte.syncmatica.communication.ExchangeTarget; 8 | import ch.endte.syncmatica.communication.exchange.ModifyExchangeClient; 9 | import ch.endte.syncmatica.litematica.LitematicManager; 10 | import ch.endte.syncmatica.litematica.ScreenHelper; 11 | import ch.endte.syncmatica.litematica.gui.IGuiBase; 12 | import fi.dy.masa.litematica.gui.GuiPlacementConfiguration; 13 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacement; 14 | import fi.dy.masa.malilib.gui.GuiBase; 15 | import fi.dy.masa.malilib.gui.Message; 16 | import fi.dy.masa.malilib.gui.button.ButtonBase; 17 | import org.spongepowered.asm.mixin.Final; 18 | import org.spongepowered.asm.mixin.Mixin; 19 | import org.spongepowered.asm.mixin.Shadow; 20 | import org.spongepowered.asm.mixin.Unique; 21 | import org.spongepowered.asm.mixin.injection.At; 22 | import org.spongepowered.asm.mixin.injection.Inject; 23 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 24 | 25 | import java.util.List; 26 | 27 | @Mixin(GuiPlacementConfiguration.class) 28 | public abstract class MixinGuiPlacementConfiguration extends GuiBase { 29 | 30 | @Final 31 | @Shadow(remap = false) 32 | public SchematicPlacement placement; 33 | 34 | @Inject(method = "initGui", at = @At("RETURN"), remap = false) 35 | public void initGui(final CallbackInfo ci) { 36 | if (!LitematicManager.getInstance().isSyncmatic(placement)) { 37 | return; 38 | } 39 | final List buttons = ((IGuiBase) this).getButtons(); 40 | final ButtonBase button = buttons.get(6); // unlock button 41 | button.setActionListener((b, k) -> { 42 | if (placement.isLocked()) { 43 | requestModification(); 44 | } else { 45 | finishModification(); 46 | } 47 | }); 48 | ScreenHelper.ifPresent(s -> s.setCurrentGui(this)); 49 | } 50 | 51 | @Unique 52 | private void requestModification() { 53 | final Context context = LitematicManager.getInstance().getActiveContext(); 54 | final ExchangeTarget server = ((ClientCommunicationManager) context.getCommunicationManager()).getServer(); 55 | final ServerPlacement serverPlacement = LitematicManager.getInstance().syncmaticFromSchematic(placement); 56 | if (!server.getFeatureSet().hasFeature(Feature.CORE_EX) && placement.isRegionPlacementModified()) { 57 | addMessage(Message.MessageType.ERROR, "syncmatica.error.share_modified_subregions"); 58 | return; 59 | } 60 | final ModifyExchangeClient modifyExchange = new ModifyExchangeClient(serverPlacement, server, context); 61 | context.getCommunicationManager().startExchange(modifyExchange); 62 | } 63 | 64 | @Unique 65 | private void finishModification() { 66 | final Context context = LitematicManager.getInstance().getActiveContext(); 67 | final ExchangeTarget server = ((ClientCommunicationManager) context.getCommunicationManager()).getServer(); 68 | if (!server.getFeatureSet().hasFeature(Feature.CORE_EX) && placement.isRegionPlacementModified()) { 69 | addMessage(Message.MessageType.ERROR, "syncmatica.error.share_modified_subregions"); 70 | return; 71 | } 72 | final ServerPlacement serverPlacement = LitematicManager.getInstance().syncmaticFromSchematic(placement); 73 | final ModifyExchangeClient modifyExchange = (ModifyExchangeClient) context.getCommunicationManager().getModifier(serverPlacement); 74 | if (modifyExchange != null) { 75 | modifyExchange.conclude(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/network/actor/ActorClientPlayHandler.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.network.actor; 2 | 3 | import java.util.Objects; 4 | import net.minecraft.client.multiplayer.ClientPacketListener; 5 | import net.minecraft.network.FriendlyByteBuf; 6 | import ch.endte.syncmatica.Syncmatica; 7 | import ch.endte.syncmatica.communication.ClientCommunicationManager; 8 | import ch.endte.syncmatica.communication.CommunicationManager; 9 | import ch.endte.syncmatica.communication.ExchangeTarget; 10 | import ch.endte.syncmatica.data.IFileStorage; 11 | import ch.endte.syncmatica.data.RedirectFileStorage; 12 | import ch.endte.syncmatica.data.SyncmaticManager; 13 | import ch.endte.syncmatica.litematica.LitematicManager; 14 | import ch.endte.syncmatica.litematica.ScreenHelper; 15 | import ch.endte.syncmatica.network.PacketType; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | import static ch.endte.syncmatica.Syncmatica.CLIENT_CONTEXT; 19 | import static ch.endte.syncmatica.Syncmatica.getContext; 20 | 21 | /** 22 | * If I can get this to work, so be it. 23 | */ 24 | public class ActorClientPlayHandler 25 | { 26 | private static ActorClientPlayHandler instance; 27 | private static ClientPacketListener clientPlayNetworkHandler; 28 | private CommunicationManager clientCommunication; 29 | private ExchangeTarget exTarget; 30 | 31 | public static ActorClientPlayHandler getInstance() 32 | { 33 | if (instance == null) 34 | { 35 | instance = new ActorClientPlayHandler(); 36 | } 37 | 38 | return instance; 39 | } 40 | 41 | public void startEvent(final ClientPacketListener handler) 42 | { 43 | Syncmatica.debug("ActorClientPlayHandler#startEvent()"); 44 | if (clientPlayNetworkHandler == null) 45 | { 46 | setClientContext(handler); 47 | } 48 | startClient(); 49 | } 50 | 51 | public void startClient() 52 | { 53 | Syncmatica.debug("ActorClientPlayHandler#startClient()"); 54 | /* 55 | if (clientPlayNetworkHandler == null) 56 | { 57 | throw new RuntimeException("Tried to start client before receiving a connection"); 58 | } 59 | */ 60 | final IFileStorage data = new RedirectFileStorage(); 61 | final SyncmaticManager man = new SyncmaticManager(); 62 | exTarget = new ExchangeTarget(clientPlayNetworkHandler); 63 | final CommunicationManager comms = new ClientCommunicationManager(exTarget); 64 | Syncmatica.initClient(comms, data, man); 65 | clientCommunication = comms; 66 | ScreenHelper.init(); 67 | LitematicManager.getInstance().setActiveContext(Objects.requireNonNull(getContext(CLIENT_CONTEXT))); 68 | } 69 | 70 | public void packetEvent(final PacketType type, final FriendlyByteBuf data, final ClientPacketListener clientContext, CallbackInfo ci) 71 | { 72 | if (clientCommunication == null) 73 | { 74 | ActorClientPlayHandler.getInstance().startEvent(clientContext); 75 | } 76 | if (packetEvent(type, data)) 77 | if (ci.isCancellable()) 78 | ci.cancel(); 79 | } 80 | 81 | public boolean packetEvent(final PacketType type, final FriendlyByteBuf bufSupplier) 82 | { 83 | if (clientCommunication.handlePacket(type)) 84 | { 85 | clientCommunication.onPacket(exTarget, type, bufSupplier); 86 | return true; 87 | } 88 | return false; 89 | } 90 | 91 | public void reset() 92 | { 93 | Syncmatica.debug("ActorClientPlayHandler#reset()"); 94 | clientCommunication = null; 95 | exTarget = null; 96 | clientPlayNetworkHandler = null; 97 | } 98 | 99 | private static void setClientContext(final ClientPacketListener clientHandler) { ActorClientPlayHandler.clientPlayNetworkHandler = clientHandler; } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/mixin/MixinServerPlayNetworkHandler.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.mixin; 2 | 3 | import java.util.function.Consumer; 4 | import ch.endte.syncmatica.Context; 5 | import ch.endte.syncmatica.Reference; 6 | import ch.endte.syncmatica.Syncmatica; 7 | import ch.endte.syncmatica.communication.ExchangeTarget; 8 | import ch.endte.syncmatica.communication.ServerCommunicationManager; 9 | import ch.endte.syncmatica.network.actor.IServerPlay; 10 | import ch.endte.syncmatica.network.handler.ServerPlayHandler; 11 | import ch.endte.syncmatica.network.SyncmaticaPacket; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.Unique; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | import net.minecraft.network.Connection; 19 | import net.minecraft.network.DisconnectionDetails; 20 | import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; 21 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 22 | import net.minecraft.server.MinecraftServer; 23 | import net.minecraft.server.level.ServerPlayer; 24 | import net.minecraft.server.network.CommonListenerCookie; 25 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 26 | 27 | @Mixin(value = ServerGamePacketListenerImpl.class, priority = 1001) 28 | public abstract class MixinServerPlayNetworkHandler implements IServerPlay 29 | { 30 | @Shadow public abstract ServerPlayer getPlayer(); 31 | 32 | @Unique 33 | private ExchangeTarget exTarget = null; 34 | @Unique 35 | private ServerCommunicationManager comManager = null; 36 | 37 | @Inject(method = "", at = @At("TAIL")) 38 | public void syncmatica$onConnect(MinecraftServer server, Connection clientConnection, ServerPlayer player, CommonListenerCookie clientData, CallbackInfo ci) 39 | { 40 | syncmatica$operateComms(sm -> sm.onPlayerJoin(syncmatica$getExchangeTarget(), player)); 41 | } 42 | 43 | @Inject(method = "onDisconnect", at = @At("HEAD")) 44 | public void syncmatica$onDisconnected(DisconnectionDetails info, CallbackInfo ci) 45 | { 46 | syncmatica$operateComms(sm -> sm.onPlayerLeave(syncmatica$getExchangeTarget())); 47 | } 48 | 49 | // This exists because of the Communications Manager / Exchange Target system, 50 | // and FAPI networking is too slow to register the receivers 51 | @Inject(method = "handleCustomPayload", at = @At("HEAD"), cancellable = true) 52 | private void syncmatica$onCustomPayload(ServerboundCustomPayloadPacket packet, CallbackInfo ci) 53 | { 54 | CustomPacketPayload thisPayload = packet.payload(); 55 | 56 | if (thisPayload.type().id().getNamespace().equals(Reference.MOD_ID)) 57 | { 58 | SyncmaticaPacket.Payload payload = (SyncmaticaPacket.Payload) thisPayload; 59 | ServerPlayHandler.decodeSyncData(payload.data(), this); 60 | 61 | // Cancel unnecessary processing if a PacketType we own is caught 62 | if (ci.isCancellable()) 63 | ci.cancel(); 64 | } 65 | } 66 | 67 | @Unique 68 | public void syncmatica$operateComms(final Consumer operation) 69 | { 70 | if (comManager == null) 71 | { 72 | final Context con = Syncmatica.getContext(Syncmatica.SERVER_CONTEXT); 73 | if (con != null) 74 | { 75 | comManager = (ServerCommunicationManager) con.getCommunicationManager(); 76 | } 77 | } 78 | if (comManager != null) 79 | { 80 | operation.accept(comManager); 81 | } 82 | } 83 | 84 | @Unique 85 | public ExchangeTarget syncmatica$getExchangeTarget() 86 | { 87 | if (exTarget == null) 88 | { 89 | exTarget = new ExchangeTarget((ServerGamePacketListenerImpl) (Object) this); 90 | } 91 | return exTarget; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/exchange/UploadExchange.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication.exchange; 2 | 3 | import java.io.*; 4 | import java.nio.file.Path; 5 | import net.minecraft.network.FriendlyByteBuf; 6 | import ch.endte.syncmatica.Context; 7 | import ch.endte.syncmatica.communication.ExchangeTarget; 8 | import ch.endte.syncmatica.data.ServerPlacement; 9 | import ch.endte.syncmatica.network.PacketType; 10 | import io.netty.buffer.Unpooled; 11 | 12 | // uploading part of transmit data exchange 13 | // pairs with Download Exchange 14 | 15 | public class UploadExchange extends AbstractExchange 16 | { 17 | // The maximum buffer size for CustomPayloadPackets is actually 32767 18 | // so 32768 is a bad value to send - thus adjusted it to 16384 - exactly halved 19 | private static final int BUFFER_SIZE = 16384; 20 | private final ServerPlacement toUpload; 21 | private final InputStream inputStream; 22 | private final byte[] buffer = new byte[BUFFER_SIZE]; 23 | 24 | public UploadExchange(final ServerPlacement syncmatic, final Path uploadFile, final ExchangeTarget partner, final Context con) throws FileNotFoundException 25 | { 26 | super(partner, con); 27 | toUpload = syncmatic; 28 | inputStream = new FileInputStream(uploadFile.toFile()); 29 | } 30 | 31 | @Override 32 | public boolean checkPacket(final PacketType type, final FriendlyByteBuf packetBuf) 33 | { 34 | if (type.equals(PacketType.RECEIVED_LITEMATIC) 35 | || type.equals(PacketType.CANCEL_LITEMATIC)) 36 | { 37 | return checkUUID(packetBuf, toUpload.getId()); 38 | } 39 | return false; 40 | } 41 | 42 | @Override 43 | public void handle(final PacketType type, final FriendlyByteBuf packetBuf) 44 | { 45 | 46 | packetBuf.readUUID(); // uncertain if the data has to be consumed 47 | if (type.equals(PacketType.RECEIVED_LITEMATIC)) 48 | { 49 | send(); 50 | } 51 | if (type.equals(PacketType.CANCEL_LITEMATIC)) 52 | { 53 | close(false); 54 | } 55 | } 56 | 57 | private void send() 58 | { 59 | // might fail when an empty file is attempted to be transmitted 60 | final int bytesRead; 61 | try 62 | { 63 | bytesRead = inputStream.read(buffer); 64 | } 65 | catch (final IOException e) 66 | { 67 | close(true); 68 | e.printStackTrace(); 69 | return; 70 | } 71 | if (bytesRead == -1) 72 | { 73 | sendFinish(); 74 | } 75 | else 76 | { 77 | sendData(bytesRead); 78 | } 79 | } 80 | 81 | private void sendData(final int bytesRead) 82 | { 83 | final FriendlyByteBuf packetByteBuf = new FriendlyByteBuf(Unpooled.buffer()); 84 | packetByteBuf.writeUUID(toUpload.getId()); 85 | packetByteBuf.writeInt(bytesRead); 86 | packetByteBuf.writeBytes(buffer, 0, bytesRead); 87 | getPartner().sendPacket(PacketType.SEND_LITEMATIC, packetByteBuf, getContext()); 88 | } 89 | 90 | private void sendFinish() 91 | { 92 | final FriendlyByteBuf packetByteBuf = new FriendlyByteBuf(Unpooled.buffer()); 93 | packetByteBuf.writeUUID(toUpload.getId()); 94 | getPartner().sendPacket(PacketType.FINISHED_LITEMATIC, packetByteBuf, getContext()); 95 | succeed(); 96 | } 97 | 98 | @Override 99 | public void init() { send(); } 100 | 101 | @Override 102 | protected void onClose() 103 | { 104 | try 105 | { 106 | inputStream.close(); 107 | } 108 | catch (final IOException e) 109 | { 110 | e.printStackTrace(); 111 | } 112 | } 113 | 114 | @Override 115 | protected void sendCancelPacket() 116 | { 117 | final FriendlyByteBuf packetByteBuf = new FriendlyByteBuf(Unpooled.buffer()); 118 | packetByteBuf.writeUUID(toUpload.getId()); 119 | getPartner().sendPacket(PacketType.CANCEL_LITEMATIC, packetByteBuf, getContext()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/ExchangeTarget.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import net.minecraft.client.multiplayer.ClientPacketListener; 7 | import net.minecraft.network.FriendlyByteBuf; 8 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 9 | import ch.endte.syncmatica.Context; 10 | import ch.endte.syncmatica.Syncmatica; 11 | import ch.endte.syncmatica.communication.exchange.Exchange; 12 | import ch.endte.syncmatica.network.handler.ClientPlayHandler; 13 | import ch.endte.syncmatica.network.handler.ServerPlayHandler; 14 | import ch.endte.syncmatica.network.PacketType; 15 | import ch.endte.syncmatica.network.SyncmaticaPacket; 16 | import fi.dy.masa.malilib.util.StringUtils; 17 | 18 | // since Client/Server PlayNetworkHandler are 2 different classes, but I want to use exchanges 19 | // on both without having to recode them individually, I have an adapter class here 20 | public class ExchangeTarget 21 | { 22 | public final ClientPacketListener clientPlayNetworkHandler; 23 | public final ServerGamePacketListenerImpl serverPlayNetworkHandler; 24 | private final String persistentName; 25 | 26 | private FeatureSet features; 27 | private final List ongoingExchanges = new ArrayList<>(); // implicitly relies on priority 28 | 29 | public ExchangeTarget(ClientPacketListener clientPlayContext) 30 | { 31 | this.clientPlayNetworkHandler = clientPlayContext; 32 | this.serverPlayNetworkHandler = null; 33 | this.persistentName = StringUtils.getWorldOrServerName(); 34 | } 35 | 36 | public ExchangeTarget(ServerGamePacketListenerImpl serverPlayContext) 37 | { 38 | this.clientPlayNetworkHandler = null; 39 | this.serverPlayNetworkHandler = serverPlayContext; 40 | this.persistentName = serverPlayContext.getPlayer().getStringUUID(); 41 | } 42 | 43 | // this application exclusively communicates in CustomPayLoad packets 44 | // this class handles the sending of either S2C or C2S packets 45 | /** 46 | * The Fabric API call mode sometimes fails here, because the channels might not be registered in PLAY mode, especially for Single Player. 47 | */ 48 | public void sendPacket(final PacketType type, final FriendlyByteBuf byteBuf, final Context context) 49 | { 50 | //SyncLog.debug("ExchangeTarget#sendPacket(): invoked."); 51 | if (context != null) { 52 | context.getDebugService().logSendPacket(type, persistentName); 53 | } 54 | final SyncmaticaPacket newPacket = new SyncmaticaPacket(type.getId(), byteBuf); 55 | 56 | if (newPacket.getType() == null) 57 | { 58 | Syncmatica.LOGGER.error("ExchangeTarget#sendPacket(): error, PacketType {} resulted in a null Payload", type.toString()); 59 | return; 60 | } 61 | if (clientPlayNetworkHandler != null) 62 | { 63 | //SyncLog.debug("ExchangeTarget#sendPacket(): in Client Context, packet type: {}, size in bytes: {}", type.getId().toString(), buf.readableBytes()); 64 | ClientPlayHandler.encodeSyncData(newPacket, clientPlayNetworkHandler); 65 | } 66 | if (serverPlayNetworkHandler != null) 67 | { 68 | //ServerPlayerEntity player = serverPlayNetworkHandler.getPlayer(); 69 | //SyncLog.debug("ExchangeTarget#sendPacket(): in Server Context, packet type: {}, size in bytes: {} to player: {}", type.getId().toString(), buf.readableBytes(), player.getName().getLiteralString()); 70 | ServerPlayHandler.encodeSyncData(newPacket, serverPlayNetworkHandler); 71 | } 72 | } 73 | 74 | // removed equals code due to issues with Collection.contains 75 | public FeatureSet getFeatureSet() { return features; } 76 | 77 | public void setFeatureSet(final FeatureSet f) { features = f; } 78 | 79 | public Collection getExchanges() { return ongoingExchanges; } 80 | 81 | public String getPersistentName() { return persistentName; } 82 | 83 | public boolean isServer() { return serverPlayNetworkHandler != null; } 84 | 85 | public boolean isClient() { return clientPlayNetworkHandler != null; } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/data/RedirectFileStorage.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.data; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.UUID; 10 | 11 | import ch.endte.syncmatica.Context; 12 | import ch.endte.syncmatica.Syncmatica; 13 | import ch.endte.syncmatica.util.SyncmaticaUtil; 14 | 15 | // pretty sure this is some kind of pattern 16 | // dont remember the name though 17 | public class RedirectFileStorage implements IFileStorage 18 | { 19 | 20 | private final IFileStorage fs; 21 | 22 | 23 | // stores the redirects 24 | // will associate a hash with a specific file on the disk 25 | // not sure how to solve storage and make everything convenient for the user 26 | private final Map redirect = new HashMap<>(); 27 | 28 | public RedirectFileStorage() 29 | { 30 | fs = new FileStorage(); 31 | } 32 | 33 | public void addRedirect(final Path file) 34 | { 35 | final RedirectData red = new RedirectData(file); 36 | redirect.put(red.getHash(), red); 37 | } 38 | 39 | @Override 40 | public LocalLitematicState getLocalState(final ServerPlacement placement) 41 | { 42 | final UUID hashId = placement.getHash(); 43 | if (redirect.containsKey(hashId) && hashId.equals(redirect.get(hashId).getHash())) 44 | { 45 | return LocalLitematicState.LOCAL_LITEMATIC_PRESENT; 46 | } 47 | else 48 | { 49 | return fs.getLocalState(placement); 50 | } 51 | } 52 | 53 | // todo 54 | @Override 55 | public Path createLocalLitematic(final ServerPlacement placement) 56 | { 57 | return fs.createLocalLitematic(placement); 58 | } 59 | 60 | @Override 61 | public Path getLocalLitematic(final ServerPlacement placement) 62 | { 63 | final UUID hashId = placement.getHash(); 64 | if (redirect.containsKey(hashId)) 65 | { 66 | final RedirectData red = redirect.get(hashId); 67 | if (red.exists() && hashId.equals(red.getHash())) 68 | { 69 | return red.redirect; 70 | } 71 | else 72 | { 73 | redirect.remove(hashId); 74 | } 75 | } 76 | return fs.getLocalLitematic(placement); 77 | } 78 | 79 | @Override 80 | public void setContext(final Context con) 81 | { 82 | fs.setContext(con); 83 | } 84 | 85 | private class RedirectData 86 | { 87 | Path redirect = null; 88 | UUID hash = null; 89 | long hashTimeStamp; 90 | 91 | RedirectData(Path file) 92 | { 93 | redirect = file; 94 | getHash(); 95 | if (hash == null) 96 | { 97 | file = null; 98 | } 99 | } 100 | 101 | UUID getHash() 102 | { 103 | // if (hashTimeStamp == redirect.lastModified()) { 104 | if (hashTimeStamp == this.getModifiedTime()) 105 | { 106 | return hash; 107 | } 108 | try 109 | { 110 | hash = SyncmaticaUtil.createChecksum(new FileInputStream(redirect.toFile())); 111 | } 112 | catch (final Exception e) 113 | { 114 | e.printStackTrace(); 115 | return null; 116 | } 117 | // hashTimeStamp = redirect.lastModified(); 118 | hashTimeStamp = this.getModifiedTime(); 119 | return hash; 120 | } 121 | 122 | long getModifiedTime() 123 | { 124 | try 125 | { 126 | return Files.getLastModifiedTime(redirect).toMillis(); 127 | } 128 | catch (IOException e) 129 | { 130 | Syncmatica.LOGGER.warn("Exception getting last modified time of file [{}]", 131 | redirect.getFileName().toString()); 132 | } 133 | 134 | // default to now 135 | return System.currentTimeMillis(); 136 | } 137 | 138 | boolean exists() 139 | { 140 | // return redirect.exists() && redirect.canRead(); 141 | return Files.exists(redirect) && Files.isReadable(redirect); 142 | } 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/network/PacketType.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.network; 2 | 3 | import net.minecraft.resources.Identifier; 4 | 5 | public enum PacketType 6 | { 7 | REGISTER_METADATA("syncmatica", "register_metadata"), 8 | // one packet will be responsible for sending the entire metadata of a syncmatic 9 | // it marks the creation of a syncmatic - for now it also is responsible 10 | // for changing the syncmatic server and client side 11 | 12 | CANCEL_SHARE("syncmatica", "cancel_share"), 13 | // send to a client when a share failed, 14 | // the client can cancel the upload or upon finishing send a removal packet 15 | 16 | REQUEST_LITEMATIC("syncmatica", "request_download"), 17 | // another group of packets will be responsible for downloading the entire 18 | // litematic starting with a download request 19 | 20 | SEND_LITEMATIC("syncmatica", "send_litematic"), 21 | // a packet responsible for sending a bit of a litematic (16 kilobytes to be precise (half of what minecraft can send in one packet at most)) 22 | 23 | RECEIVED_LITEMATIC("syncmatica", "received_litematic"), 24 | // a packet responsible for triggering another send for a litematic 25 | // by waiting until a response is sent I hope we can ensure 26 | // that we do not overwhelm the clients' connection to the server 27 | 28 | FINISHED_LITEMATIC("syncmatica", "finished_litematic"), 29 | // a packet responsible for marking the end of a litematic 30 | // transmission 31 | 32 | CANCEL_LITEMATIC("syncmatica", "cancel_litematic"), 33 | // a packet responsible for cancelling an ongoing upload/download 34 | // will be sent in several cases - upon errors mostly 35 | 36 | REMOVE_SYNCMATIC("syncmatica", "remove_syncmatic"), 37 | // a packet that will be sent to clients when a syncmatic got removed 38 | // send to the server by a client if a specific client intends to remove a litematic from the server 39 | 40 | REGISTER_VERSION("syncmatica", "register_version"), 41 | // this packet will be sent to the client when it joins the server 42 | // upon receiving this packet the client will check the server version 43 | // initializes syncmatica on the clients end 44 | // if it can function with the version on the server then it will respond with a version of its own 45 | // if the server can handle the client version the server will send 46 | 47 | CONFIRM_USER("syncmatica", "confirm_user"), 48 | // the confirm-user packet 49 | // send after a successful version exchange 50 | // fully starts up syncmatica on the clients end 51 | // sends all server placements along to the client 52 | 53 | FEATURE_REQUEST("syncmatica", "feature_request"), 54 | // requests the partner to send a list of its features 55 | // does not require a fully finished handshake 56 | 57 | FEATURE("syncmatica", "feature"), 58 | // sends feature set to the partner 59 | // send during a version exchange to check if the 2 versions are compatible, and there is no 60 | // default feature set available for the transmitted version 61 | // afterwards the feature set is used to communicate to the partner 62 | 63 | MODIFY("syncmatica", "modify"), 64 | // sends updated placement data to the client or vice versa 65 | 66 | MODIFY_REQUEST("syncmatica", "modify_request"), 67 | // send from client to server to request the editing of placement values 68 | // used to ensure that only one person can edit at a time thus preventing all kinds of stuff 69 | 70 | MODIFY_REQUEST_DENY("syncmatica", "modify_request_deny"), 71 | MODIFY_REQUEST_ACCEPT("syncmatica", "modify_request_accept"), 72 | 73 | MODIFY_FINISH("syncmatica", "modify_finish"), 74 | // send from client to server to mark that the editing of placement values has concluded 75 | // sends along the final data of the placement 76 | 77 | MESSAGE("syncmatica", "mesage"); 78 | // sends a message from client to server - allows for future compatibility 79 | // can't fix the typo here lol 80 | 81 | public final Identifier identifier; 82 | 83 | PacketType(final String id, final String channel) {identifier = Identifier.fromNamespaceAndPath(id, channel);} 84 | 85 | public static boolean containsIdentifier(final Identifier id) 86 | { 87 | for (final PacketType p : PacketType.values()) 88 | { 89 | if (id.equals(p.identifier)) 90 | { // this took I kid you not 4-5 hours to find 91 | return true; 92 | } 93 | } 94 | return false; 95 | } 96 | 97 | public static boolean containsType(final PacketType type) 98 | { 99 | for (final PacketType p : PacketType.values()) 100 | { 101 | if (type.equals(p)) 102 | { 103 | return true; 104 | } 105 | } 106 | return false; 107 | } 108 | 109 | public static PacketType getType(final Identifier id) 110 | { 111 | for (final PacketType p : PacketType.values()) 112 | { 113 | if (id.equals(p.identifier)) 114 | { 115 | return p; 116 | } 117 | } 118 | return null; 119 | } 120 | 121 | public Identifier getId() {return this.identifier;} 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/Syncmatica.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica; 2 | 3 | import java.nio.file.Path; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.UUID; 7 | import ch.endte.syncmatica.command.SyncmaticaCommand; 8 | import ch.endte.syncmatica.communication.CommunicationManager; 9 | import ch.endte.syncmatica.data.IFileStorage; 10 | import ch.endte.syncmatica.data.SyncmaticManager; 11 | import ch.endte.syncmatica.network.SyncmaticaPacket; 12 | import ch.endte.syncmatica.network.actor.ActorClientPlayHandler; 13 | import org.apache.logging.log4j.LogManager; 14 | import org.apache.logging.log4j.Logger; 15 | import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; 16 | import net.minecraft.resources.Identifier; 17 | 18 | // could probably turn this into a singleton 19 | public class Syncmatica 20 | { 21 | public static Logger LOGGER = LogManager.getLogger(Reference.MOD_ID); 22 | 23 | // protected static final String SERVER_PATH = "." + File.separator + "syncmatics"; 24 | // protected static final String CLIENT_PATH = "." + File.separator + "schematics" + File.separator + "sync"; 25 | protected static final Path SERVER_PATH = Reference.GAME_ROOT.resolve("syncmatics"); 26 | protected static final Path CLIENT_PATH = Reference.GAME_ROOT.resolve("schematics").resolve("sync"); 27 | public static final Identifier CLIENT_CONTEXT = Identifier.fromNamespaceAndPath(Reference.MOD_ID, "client_context"); 28 | public static final Identifier SERVER_CONTEXT = Identifier.fromNamespaceAndPath(Reference.MOD_ID, "server_context"); 29 | public static final Identifier NETWORK_ID = Identifier.fromNamespaceAndPath(Reference.MOD_ID, "main"); 30 | public static final UUID syncmaticaId = UUID.fromString("4c1b738f-56fa-4011-8273-498c972424ea"); 31 | protected static Map contexts = null; 32 | protected static boolean context_init = false; 33 | 34 | /** 35 | * Tasks to be run at Mod Init, such as register Play Channels 36 | */ 37 | public static void preInit() 38 | { 39 | Syncmatica.debug("Syncmatica#preInit(): registering play channel(s)"); 40 | PayloadTypeRegistry.playC2S().register(SyncmaticaPacket.Payload.ID, SyncmaticaPacket.Payload.CODEC); 41 | PayloadTypeRegistry.playS2C().register(SyncmaticaPacket.Payload.ID, SyncmaticaPacket.Payload.CODEC); 42 | // These need to be registered ASAP at launch. 43 | } 44 | 45 | /** 46 | * Streamlined debugging tool via MOD_DEBUG boolean 47 | * @param msg (message content) 48 | * @param args (variable args) 49 | */ 50 | public static void debug(String msg, Object... args) 51 | { 52 | if (Reference.MOD_DEBUG) 53 | { 54 | LOGGER.info(msg, args); 55 | } 56 | } 57 | 58 | public static Context getContext(final Identifier id) 59 | { 60 | if (context_init) 61 | return contexts.get(id); 62 | else return null; 63 | } 64 | 65 | static void init(final Context con, final Identifier contextId) { 66 | Syncmatica.debug("Syncmatica#init()"); 67 | 68 | if (contexts == null) { 69 | contexts = new HashMap<>(); 70 | } 71 | if (!contexts.containsKey(contextId)) { 72 | contexts.put(contextId, con); 73 | } 74 | context_init = true; 75 | } 76 | 77 | public static void shutdown() { 78 | Syncmatica.debug("Syncmatica#shutdown()"); 79 | 80 | if (contexts != null) { 81 | for (final Context con : contexts.values()) { 82 | if (con.isStarted()) { 83 | con.shutdown(); 84 | } 85 | } 86 | } 87 | deinit(); 88 | } 89 | 90 | private static void deinit() { 91 | Syncmatica.debug("Syncmatica#deinit()"); 92 | 93 | contexts = null; 94 | context_init = false; 95 | } 96 | 97 | public static Context initClient(final CommunicationManager comms, final IFileStorage fileStorage, final SyncmaticManager schematics) 98 | { 99 | Syncmatica.debug("Syncmatica#initClient()"); 100 | 101 | final Context clientContext = new Context( 102 | fileStorage, 103 | comms, 104 | schematics, 105 | // new File(CLIENT_PATH) 106 | CLIENT_PATH 107 | ); 108 | Syncmatica.init(clientContext, CLIENT_CONTEXT); 109 | return clientContext; 110 | } 111 | public static void restartClient() { 112 | Syncmatica.debug("Syncmatica#restartClient()"); 113 | 114 | final Context oldClient = getContext(CLIENT_CONTEXT); 115 | if (oldClient != null) { 116 | if (oldClient.isStarted()) { 117 | oldClient.shutdown(); 118 | } 119 | 120 | contexts.remove(CLIENT_CONTEXT); 121 | } 122 | 123 | ActorClientPlayHandler.getInstance().startClient(); 124 | } 125 | 126 | public static Context initServer(final CommunicationManager comms, final IFileStorage fileStorage, final SyncmaticManager schematics, 127 | final boolean isIntegratedServer, final Path worldPath) 128 | { 129 | Syncmatica.debug("Syncmatica#initServer()"); 130 | 131 | final Context serverContext = new Context( 132 | fileStorage, 133 | comms, 134 | schematics, 135 | true, 136 | // new File(SERVER_PATH), 137 | SERVER_PATH, 138 | isIntegratedServer, 139 | worldPath 140 | ); 141 | Syncmatica.debug("INIT:Server Context; world path: '{}'", worldPath.toAbsolutePath().toString()); 142 | Syncmatica.init(serverContext, SERVER_CONTEXT); 143 | SyncmaticaCommand.INSTANCE.updateSyncmaticDir(serverContext); 144 | return serverContext; 145 | } 146 | 147 | protected Syncmatica() {} 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/ClientCommunicationManager.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication; 2 | 3 | import java.util.Collection; 4 | import java.util.HashSet; 5 | import java.util.UUID; 6 | import net.minecraft.network.FriendlyByteBuf; 7 | import ch.endte.syncmatica.Context; 8 | import ch.endte.syncmatica.Feature; 9 | import ch.endte.syncmatica.Syncmatica; 10 | import ch.endte.syncmatica.communication.exchange.DownloadExchange; 11 | import ch.endte.syncmatica.communication.exchange.Exchange; 12 | import ch.endte.syncmatica.communication.exchange.VersionHandshakeClient; 13 | import ch.endte.syncmatica.data.ServerPlacement; 14 | import ch.endte.syncmatica.extended_core.PlayerIdentifier; 15 | import ch.endte.syncmatica.litematica.LitematicManager; 16 | import ch.endte.syncmatica.litematica.ScreenHelper; 17 | import ch.endte.syncmatica.network.actor.ActorClientPlayHandler; 18 | import ch.endte.syncmatica.network.PacketType; 19 | import fi.dy.masa.malilib.gui.Message; 20 | 21 | public class ClientCommunicationManager extends CommunicationManager 22 | { 23 | private final ExchangeTarget server; 24 | private final Collection sharing; 25 | 26 | public ClientCommunicationManager(final ExchangeTarget server) { 27 | super(); 28 | this.server = server; 29 | broadcastTargets.add(server); 30 | sharing = new HashSet<>(); 31 | } 32 | 33 | public ExchangeTarget getServer() { 34 | return server; 35 | } 36 | 37 | @Override 38 | protected void handle(final ExchangeTarget source, final PacketType type, final FriendlyByteBuf packetBuf) 39 | { 40 | if (type.equals(PacketType.REGISTER_METADATA)) { 41 | final ServerPlacement placement = receiveMetaData(packetBuf, source); 42 | context.getSyncmaticManager().addPlacement(placement); 43 | return; 44 | } 45 | if (type.equals(PacketType.REMOVE_SYNCMATIC)) { 46 | final UUID placementId = packetBuf.readUUID(); 47 | final ServerPlacement placement = context.getSyncmaticManager().getPlacement(placementId); 48 | if (placement != null) { 49 | final Exchange modifier = getModifier(placement); 50 | if (modifier != null) { 51 | modifier.close(false); 52 | notifyClose(modifier); 53 | } 54 | context.getSyncmaticManager().removePlacement(placement); 55 | if (LitematicManager.getInstance().isRendered(placement)) { 56 | LitematicManager.getInstance().unrenderSyncmatic(placement); 57 | } 58 | } 59 | return; 60 | } 61 | if (type.equals(PacketType.MODIFY)) { 62 | final UUID placementId = packetBuf.readUUID(); 63 | final ServerPlacement toModify = context.getSyncmaticManager().getPlacement(placementId); 64 | receivePositionData(toModify, packetBuf, source); 65 | if (source.getFeatureSet().hasFeature(Feature.CORE_EX)) { 66 | final PlayerIdentifier lastModifiedBy = context.getPlayerIdentifierProvider().createOrGet( 67 | packetBuf.readUUID(), 68 | packetBuf.readUtf(PACKET_MAX_STRING_SIZE) 69 | ); 70 | 71 | toModify.setLastModifiedBy(lastModifiedBy); 72 | } 73 | LitematicManager.getInstance().updateRendered(toModify); 74 | context.getSyncmaticManager().updateServerPlacement(toModify); 75 | return; 76 | } 77 | if (type.equals(PacketType.MESSAGE)) { 78 | final Message.MessageType msgType = mapMessageType(MessageType.valueOf(packetBuf.readUtf(PACKET_MAX_STRING_SIZE))); 79 | final String text = packetBuf.readUtf(PACKET_MAX_STRING_SIZE); 80 | ScreenHelper.ifPresent(s -> s.addMessage(msgType, text)); 81 | return; 82 | } 83 | if (type.equals(PacketType.REGISTER_VERSION)) { 84 | LitematicManager.clear(); 85 | Syncmatica.restartClient(); 86 | 87 | ActorClientPlayHandler.getInstance().packetEvent(type, packetBuf); 88 | } 89 | } 90 | 91 | @Override 92 | protected void handleExchange(final Exchange exchange) { 93 | if (exchange instanceof DownloadExchange && exchange.isSuccessful()) { 94 | LitematicManager.getInstance().renderSyncmatic(((DownloadExchange) exchange).getPlacement()); 95 | } 96 | } 97 | 98 | @Override 99 | public void setDownloadState(final ServerPlacement syncmatic, final boolean state) { 100 | downloadState.put(syncmatic.getHash(), state); 101 | if (state || LitematicManager.getInstance().isRendered(syncmatic)) { //change client behavior so that the Load button doesn't show up naturally 102 | context.getSyncmaticManager().updateServerPlacement(syncmatic); 103 | } 104 | } 105 | 106 | public void setSharingState(final ServerPlacement placement, final boolean state) { 107 | if (state) { 108 | sharing.add(placement); 109 | } else { 110 | sharing.remove(placement); 111 | } 112 | } 113 | 114 | public boolean getSharingState(final ServerPlacement placement) { 115 | return sharing.contains(placement); 116 | } 117 | 118 | @Override 119 | public void setContext(final Context con) { 120 | super.setContext(con); 121 | final VersionHandshakeClient hi = new VersionHandshakeClient(server, context); 122 | startExchangeUnchecked(hi); 123 | } 124 | 125 | private Message.MessageType mapMessageType(final MessageType m) { 126 | return switch (m) { 127 | case SUCCESS -> Message.MessageType.SUCCESS; 128 | case WARNING -> Message.MessageType.WARNING; 129 | case ERROR -> Message.MessageType.ERROR; 130 | default -> Message.MessageType.INFO; 131 | }; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/data/FileStorage.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.data; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.Syncmatica; 5 | import ch.endte.syncmatica.util.SyncmaticaUtil; 6 | 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.util.HashMap; 12 | import java.util.UUID; 13 | 14 | public class FileStorage implements IFileStorage 15 | { 16 | 17 | private final HashMap buffer = new HashMap<>(); 18 | private Context context = null; 19 | 20 | @Override 21 | public void setContext(final Context con) 22 | { 23 | if (context == null) 24 | { 25 | context = con; 26 | } 27 | else 28 | { 29 | throw new Context.DuplicateContextAssignmentException("Duplicate Context assignment"); 30 | } 31 | } 32 | 33 | @Override 34 | public LocalLitematicState getLocalState(final ServerPlacement placement) 35 | { 36 | final Path localFile = getSchematicPath(placement); 37 | // if (localFile.isFile()) { 38 | if (Files.isRegularFile(localFile)) 39 | { 40 | if (isDownloading(placement)) 41 | { 42 | return LocalLitematicState.DOWNLOADING_LITEMATIC; 43 | } 44 | if ((buffer.containsKey(placement) && buffer.get(placement) == this.getLastModified(localFile)) || hashCompare(localFile, placement)) 45 | { 46 | return LocalLitematicState.LOCAL_LITEMATIC_PRESENT; 47 | } 48 | return LocalLitematicState.LOCAL_LITEMATIC_DESYNC; 49 | } 50 | return LocalLitematicState.NO_LOCAL_LITEMATIC; 51 | } 52 | 53 | private long getLastModified(Path file) 54 | { 55 | if (Files.exists(file)) 56 | { 57 | try 58 | { 59 | return Files.getLastModifiedTime(file).toMillis(); 60 | } 61 | catch (IOException e) 62 | { 63 | Syncmatica.LOGGER.warn("Error getting last modified time of file '{}'; Exception: {}", file.getFileName().toString(), e.getLocalizedMessage()); 64 | } 65 | } 66 | 67 | // use current time 68 | return System.currentTimeMillis(); 69 | } 70 | 71 | private boolean isDownloading(final ServerPlacement placement) 72 | { 73 | if (context == null) 74 | { 75 | throw new RuntimeException("No CommunicationManager has been set yet - cannot get litematic state"); 76 | } 77 | return context.getCommunicationManager().getDownloadState(placement); 78 | } 79 | 80 | @Override 81 | public Path getLocalLitematic(final ServerPlacement placement) 82 | { 83 | if (getLocalState(placement).isLocalFileReady()) 84 | { 85 | return getSchematicPath(placement); 86 | } 87 | else 88 | { 89 | return null; 90 | } 91 | } 92 | 93 | // method for creating an empty file for the litematic data 94 | @Override 95 | public Path createLocalLitematic(final ServerPlacement placement) 96 | { 97 | if (getLocalState(placement).isLocalFileReady()) 98 | { 99 | throw new IllegalArgumentException(""); 100 | } 101 | // final File file = getSchematicPath(placement); 102 | // if (file.exists()) { 103 | // file.delete(); // NOSONAR 104 | // } 105 | // try { 106 | // file.createNewFile(); // NOSONAR 107 | // } catch (final IOException e) { 108 | // e.printStackTrace(); 109 | // } 110 | 111 | final Path file = getSchematicPath(placement); 112 | 113 | try 114 | { 115 | if (Files.exists(file)) 116 | { 117 | Files.deleteIfExists(file); 118 | } 119 | 120 | Files.createFile(file); 121 | return file; 122 | } 123 | catch (IOException e) 124 | { 125 | Syncmatica.LOGGER.error("Exception creating new file: '{}'; Exception: {}", file.getFileName().toString(), e.getLocalizedMessage()); 126 | } 127 | 128 | return file; 129 | } 130 | 131 | private boolean hashCompare(final Path localFile, final ServerPlacement placement) 132 | { 133 | UUID hash = null; 134 | try 135 | { 136 | hash = SyncmaticaUtil.createChecksum(new FileInputStream(localFile.toFile())); 137 | } 138 | catch (final Exception e) 139 | { 140 | // can be safely ignored since we established that file has been found 141 | e.printStackTrace(); 142 | }// wtf just exception? 143 | 144 | if (hash == null) 145 | { 146 | return false; 147 | } 148 | if (hash.equals(placement.getHash())) 149 | { 150 | buffer.put(placement, this.getLastModified(localFile)); 151 | return true; 152 | } 153 | return false; 154 | } 155 | 156 | private Path getSchematicPath(final ServerPlacement placement) 157 | { 158 | final Path litematicPath = context.getLitematicFolder(); 159 | 160 | if (context.isServer()) 161 | { 162 | // return new File(litematicPath, placement.getHash().toString() + ".litematic"); 163 | return litematicPath.resolve(placement.getHash().toString() + ".litematic"); 164 | } 165 | 166 | String fileName = SyncmaticaUtil.sanitizeUnicodeFileName(placement.getNormalFileName()); 167 | Syncmatica.debug("getSchematicPath(): Placement filename: '{}'", fileName); 168 | 169 | if (fileName.endsWith(".litematic")) 170 | { 171 | // return new File(litematicPath, placement.getFileName()); 172 | return litematicPath.resolve(fileName); 173 | } 174 | else 175 | { 176 | // return new File(litematicPath, placement.getFileName() + ".litematic"); 177 | return litematicPath.resolve(fileName+".litematic"); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/exchange/DownloadExchange.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication.exchange; 2 | 3 | import java.io.FileOutputStream; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.security.DigestOutputStream; 9 | import java.security.MessageDigest; 10 | import java.security.NoSuchAlgorithmException; 11 | import java.util.UUID; 12 | import net.minecraft.network.FriendlyByteBuf; 13 | import ch.endte.syncmatica.Context; 14 | import ch.endte.syncmatica.Syncmatica; 15 | import ch.endte.syncmatica.communication.ExchangeTarget; 16 | import ch.endte.syncmatica.communication.MessageType; 17 | import ch.endte.syncmatica.communication.ServerCommunicationManager; 18 | import ch.endte.syncmatica.data.ServerPlacement; 19 | import ch.endte.syncmatica.network.PacketType; 20 | import io.netty.buffer.Unpooled; 21 | 22 | public class DownloadExchange extends AbstractExchange 23 | { 24 | private final ServerPlacement toDownload; 25 | private final OutputStream outputStream; 26 | private final MessageDigest md5; 27 | private final Path downloadFile; 28 | private int bytesSent; 29 | 30 | public DownloadExchange(final ServerPlacement syncmatic, final Path downloadFile, final ExchangeTarget partner, final Context context) throws IOException, NoSuchAlgorithmException 31 | { 32 | super(partner, context); 33 | this.downloadFile = downloadFile; 34 | final OutputStream os = new FileOutputStream(downloadFile.toFile()); //NOSONAR 35 | toDownload = syncmatic; 36 | md5 = MessageDigest.getInstance("MD5"); 37 | outputStream = new DigestOutputStream(os, md5); 38 | } 39 | 40 | @Override 41 | public boolean checkPacket(final PacketType type, final FriendlyByteBuf packetBuf) 42 | { 43 | if (type.equals(PacketType.SEND_LITEMATIC) 44 | || type.equals(PacketType.FINISHED_LITEMATIC) 45 | || type.equals(PacketType.CANCEL_LITEMATIC)) 46 | { 47 | return checkUUID(packetBuf, toDownload.getId()); 48 | } 49 | return false; 50 | } 51 | 52 | @Override 53 | public void handle(final PacketType type, final FriendlyByteBuf packetBuf) 54 | { 55 | packetBuf.readUUID(); //skips the UUID 56 | if (type.equals(PacketType.SEND_LITEMATIC)) 57 | { 58 | final int size = packetBuf.readInt(); 59 | bytesSent += size; 60 | if (getContext().isServer() && getContext().getQuotaService().isOverQuota(getPartner(), bytesSent)) 61 | { 62 | close(true); 63 | ((ServerCommunicationManager) getContext().getCommunicationManager()).sendMessage( 64 | getPartner(), 65 | MessageType.ERROR, 66 | "syncmatica.error.cancelled_transmit_exceed_quota" 67 | ); 68 | } 69 | try 70 | { 71 | packetBuf.readBytes(outputStream, size); 72 | } 73 | catch (final IOException e) 74 | { 75 | close(true); 76 | e.printStackTrace(); 77 | return; 78 | } 79 | final FriendlyByteBuf packetByteBuf = new FriendlyByteBuf(Unpooled.buffer()); 80 | packetByteBuf.writeUUID(toDownload.getId()); 81 | getPartner().sendPacket(PacketType.RECEIVED_LITEMATIC, packetByteBuf, getContext()); 82 | return; 83 | } 84 | if (type.equals(PacketType.FINISHED_LITEMATIC)) 85 | { 86 | try 87 | { 88 | outputStream.flush(); 89 | } 90 | catch (final IOException e) 91 | { 92 | close(false); 93 | e.printStackTrace(); 94 | return; 95 | } 96 | final UUID downloadHash = UUID.nameUUIDFromBytes(md5.digest()); 97 | if (downloadHash.equals(toDownload.getHash())) 98 | { 99 | succeed(); 100 | } 101 | else 102 | { 103 | // no need to notify partner since exchange is closed on partner side 104 | close(false); 105 | } 106 | return; 107 | } 108 | if (type.equals(PacketType.CANCEL_LITEMATIC)) 109 | { 110 | close(false); 111 | } 112 | } 113 | 114 | @Override 115 | public void init() 116 | { 117 | final FriendlyByteBuf packetByteBuf = new FriendlyByteBuf(Unpooled.buffer()); 118 | packetByteBuf.writeUUID(toDownload.getId()); 119 | getPartner().sendPacket(PacketType.REQUEST_LITEMATIC, packetByteBuf, getContext()); 120 | } 121 | 122 | @Override 123 | protected void onClose() 124 | { 125 | getManager().setDownloadState(toDownload, false); 126 | if (getContext().isServer() && isSuccessful()) 127 | { 128 | getContext().getQuotaService().progressQuota(getPartner(), bytesSent); 129 | } 130 | try 131 | { 132 | outputStream.close(); 133 | } 134 | catch (final IOException e) 135 | { 136 | e.printStackTrace(); 137 | } 138 | // if (!isSuccessful() && downloadFile.exists()) 139 | if (!isSuccessful() && Files.exists(downloadFile)) 140 | { 141 | try 142 | { 143 | // if (!downloadFile.delete()) 144 | Files.deleteIfExists(downloadFile); 145 | } 146 | catch (Exception err) { 147 | Syncmatica.LOGGER.error("DownloadExchange#onClose(): failed to delete file: {}; exception {}", downloadFile.toString(), err.getLocalizedMessage()); 148 | } 149 | } 150 | } 151 | 152 | @Override 153 | protected void sendCancelPacket() 154 | { 155 | final FriendlyByteBuf packetByteBuf = new FriendlyByteBuf(Unpooled.buffer()); 156 | packetByteBuf.writeUUID(toDownload.getId()); 157 | getPartner().sendPacket(PacketType.CANCEL_LITEMATIC, packetByteBuf, getContext()); 158 | } 159 | 160 | public ServerPlacement getPlacement() { return toDownload; } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/communication/exchange/ModifyExchangeClient.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.communication.exchange; 2 | 3 | import ch.endte.syncmatica.Context; 4 | import ch.endte.syncmatica.Feature; 5 | import ch.endte.syncmatica.communication.ExchangeTarget; 6 | import ch.endte.syncmatica.data.ServerPlacement; 7 | import ch.endte.syncmatica.litematica.LitematicManager; 8 | import ch.endte.syncmatica.litematica.ScreenHelper; 9 | import ch.endte.syncmatica.network.PacketType; 10 | import io.netty.buffer.Unpooled; 11 | import net.minecraft.network.FriendlyByteBuf; 12 | import fi.dy.masa.malilib.gui.Message; 13 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacement; 14 | 15 | public class ModifyExchangeClient extends AbstractExchange 16 | { 17 | //bad practice but valid for communication with deprecated systems 18 | private boolean expectRemove = false; 19 | private final ServerPlacement placement; 20 | private final SchematicPlacement litematic; 21 | 22 | public ModifyExchangeClient(final ServerPlacement placement, final ExchangeTarget partner, final Context con) 23 | { 24 | super(partner, con); 25 | this.placement = placement; 26 | litematic = LitematicManager.getInstance().schematicFromSyncmatic(placement); 27 | } 28 | 29 | @Override 30 | public boolean checkPacket(final PacketType type, final FriendlyByteBuf packetBuf) 31 | { 32 | if (type.equals(PacketType.MODIFY_REQUEST_DENY) 33 | || type.equals(PacketType.MODIFY_REQUEST_ACCEPT) 34 | || (expectRemove && type.equals(PacketType.REMOVE_SYNCMATIC))) 35 | { 36 | return AbstractExchange.checkUUID(packetBuf, placement.getId()); 37 | } 38 | return false; 39 | } 40 | 41 | @Override 42 | public void handle(final PacketType type, final FriendlyByteBuf packetBuf) 43 | { 44 | if (type.equals(PacketType.MODIFY_REQUEST_DENY)) 45 | { 46 | packetBuf.readUUID(); 47 | close(false); 48 | if (!litematic.isLocked()) 49 | { 50 | litematic.setOrigin(placement.getPosition(), null); 51 | litematic.setRotation(placement.getRotation(), null); 52 | litematic.setMirror(placement.getMirror(), null); 53 | litematic.toggleLocked(); 54 | } 55 | ScreenHelper.ifPresent(s -> s.addMessage(Message.MessageType.SUCCESS, "syncmatica.error.modification_deny")); 56 | } 57 | else if (type.equals(PacketType.MODIFY_REQUEST_ACCEPT)) 58 | { 59 | packetBuf.readUUID(); 60 | acceptModification(); 61 | } 62 | else if (type.equals(PacketType.REMOVE_SYNCMATIC)) 63 | { 64 | packetBuf.readUUID(); 65 | final ShareLitematicExchange legacyModify = new ShareLitematicExchange(litematic, getPartner(), getContext(), placement); 66 | getContext().getCommunicationManager().startExchange(legacyModify); 67 | succeed(); // the adding portion of this is handled by the ShareLitematicExchange 68 | } 69 | } 70 | 71 | @Override 72 | public void init() 73 | { 74 | if (getContext().getCommunicationManager().getModifier(placement) != null) 75 | { 76 | close(false); 77 | return; 78 | } 79 | getContext().getCommunicationManager().setModifier(placement, this); 80 | if (getPartner().getFeatureSet().hasFeature(Feature.MODIFY)) 81 | { 82 | final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); 83 | buf.writeUUID(placement.getId()); 84 | getPartner().sendPacket(PacketType.MODIFY_REQUEST, buf, getContext()); 85 | } 86 | else 87 | { 88 | acceptModification(); 89 | } 90 | } 91 | 92 | private void acceptModification() 93 | { 94 | if (litematic.isLocked()) 95 | { 96 | litematic.toggleLocked(); 97 | } 98 | ScreenHelper.ifPresent(s -> s.addMessage(Message.MessageType.SUCCESS, "syncmatica.success.modification_accepted")); 99 | getContext().getSyncmaticManager().updateServerPlacement(placement); 100 | } 101 | 102 | public void conclude() 103 | { 104 | LitematicManager.getInstance().updateServerPlacement(litematic, placement); 105 | sendFinish(); 106 | if (!litematic.isLocked()) 107 | { 108 | litematic.toggleLocked(); 109 | } 110 | getContext().getSyncmaticManager().updateServerPlacement(placement); 111 | } 112 | 113 | private void sendFinish() { 114 | if (getPartner().getFeatureSet().hasFeature(Feature.MODIFY)) 115 | { 116 | final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); 117 | buf.writeUUID(placement.getId()); 118 | getContext().getCommunicationManager().putPositionData(placement, buf, getPartner()); 119 | getPartner().sendPacket(PacketType.MODIFY_FINISH, buf, getContext()); 120 | succeed(); 121 | getContext().getCommunicationManager().notifyClose(this); 122 | } 123 | else 124 | { 125 | final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); 126 | buf.writeUUID(placement.getId()); 127 | getPartner().sendPacket(PacketType.REMOVE_SYNCMATIC, buf, getContext()); 128 | expectRemove = true; 129 | } 130 | } 131 | 132 | @Override 133 | protected void sendCancelPacket() 134 | { 135 | if (getPartner().getFeatureSet().hasFeature(Feature.MODIFY)) 136 | { 137 | final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); 138 | buf.writeUUID(placement.getId()); 139 | getContext().getCommunicationManager().putPositionData(placement, buf, getPartner()); 140 | getPartner().sendPacket(PacketType.MODIFY_FINISH, buf, getContext()); 141 | } 142 | } 143 | 144 | @Override 145 | protected void onClose() 146 | { 147 | if (getContext().getCommunicationManager().getModifier(placement) == this) 148 | { 149 | getContext().getCommunicationManager().setModifier(placement, null); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/ch/endte/syncmatica/litematica/PlacementEventHandler.java: -------------------------------------------------------------------------------- 1 | package ch.endte.syncmatica.litematica; 2 | 3 | import java.util.HashMap; 4 | import java.util.UUID; 5 | import ch.endte.syncmatica.Syncmatica; 6 | import ch.endte.syncmatica.data.ServerPlacement; 7 | import com.google.gson.JsonObject; 8 | import com.google.gson.JsonPrimitive; 9 | import org.jetbrains.annotations.Nullable; 10 | import net.fabricmc.api.EnvType; 11 | import net.fabricmc.api.Environment; 12 | import net.minecraft.core.BlockPos; 13 | import net.minecraft.nbt.CompoundTag; 14 | import net.minecraft.world.level.block.Mirror; 15 | import net.minecraft.world.level.block.Rotation; 16 | import fi.dy.masa.malilib.util.JsonUtils; 17 | import fi.dy.masa.litematica.interfaces.ISchematicPlacementEventListener; 18 | import fi.dy.masa.litematica.schematic.LitematicaSchematic; 19 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacement; 20 | 21 | @Environment(EnvType.CLIENT) 22 | public class PlacementEventHandler implements ISchematicPlacementEventListener 23 | { 24 | private final HashMap serverIds = new HashMap<>(); 25 | 26 | protected void setServerId(SchematicPlacement placement, UUID id) 27 | { 28 | if (placement != null) 29 | { 30 | Syncmatica.debug("PlacementEventHandler#setServerId() - name: [{}], ID: [{}]", placement.getName(), id.toString()); 31 | this.serverIds.put(placement.getHashId(), id); 32 | } 33 | } 34 | 35 | protected @Nullable UUID getServerId(SchematicPlacement placement) 36 | { 37 | if (placement != null && this.serverIds.containsKey(placement.getHashId())) 38 | { 39 | Syncmatica.debug("PlacementEventHandler#getServerId():A: - name: [{}], ID: [{}]", placement.getName(), this.serverIds.get(placement.getHashId()).toString()); 40 | return this.serverIds.get(placement.getHashId()); 41 | } 42 | else 43 | { 44 | Syncmatica.LOGGER.warn("PlacementEventHandler#getServerId() - name: [{}] --> NOT FOUND!", placement != null ? placement.getName() : ""); 45 | } 46 | 47 | return null; 48 | } 49 | 50 | protected boolean hasServerId(SchematicPlacement placement) 51 | { 52 | return this.serverIds.containsKey(placement.getHashId()); 53 | } 54 | 55 | protected void clearServerId(SchematicPlacement placement) 56 | { 57 | if (placement != null) 58 | { 59 | Syncmatica.debug("PlacementEventHandler#clearServerId() - name: [{}]", placement.getName()); 60 | this.serverIds.remove(placement.getHashId()); 61 | } 62 | } 63 | 64 | protected void clear() 65 | { 66 | Syncmatica.debug("PlacementEventHandler#clear()"); 67 | this.serverIds.clear(); 68 | } 69 | 70 | @Override 71 | public void onPlacementInit(SchematicPlacement placement) 72 | { 73 | // if (placement != null) 74 | // { 75 | // this.clearServerId(placement); 76 | // } 77 | } 78 | 79 | @Override 80 | public void onPlacementCreateFromJson(SchematicPlacement placement, LitematicaSchematic litematicaSchematic, BlockPos blockPos, String s, Rotation blockRotation, Mirror blockMirror, boolean enabled, boolean renderEnabled, JsonObject obj) 81 | { 82 | if (JsonUtils.hasString(obj, "syncmatica_uuid") && placement != null) 83 | { 84 | String id = obj.get("syncmatica_uuid").getAsString(); 85 | Syncmatica.debug("PlacementEventHandler#onPlacementCreateFromJson(): name: [{}], id: [{}]", placement.getName(), id); 86 | this.setServerId(placement, UUID.fromString(id)); 87 | LitematicManager.getInstance().preLoad(placement); 88 | } 89 | } 90 | 91 | @Override 92 | public void onPlacementCreateFromNbt(SchematicPlacement placement, LitematicaSchematic litematicaSchematic, BlockPos blockPos, String s, Rotation blockRotation, Mirror blockMirror, boolean enabled, boolean renderEnabled, CompoundTag nbt) 93 | { 94 | if (nbt.contains("syncmatica_uuid") && placement != null) 95 | { 96 | String id = nbt.getStringOr("syncmatica_uuid", ""); 97 | Syncmatica.debug("PlacementEventHandler#onPlacementCreateFromNbt(): name: [{}], id: [{}]", placement.getName(), id); 98 | this.setServerId(placement, UUID.fromString(id)); 99 | LitematicManager.getInstance().preLoad(placement); 100 | } 101 | } 102 | 103 | @Override 104 | public void onSavePlacementToJson(SchematicPlacement placement, JsonObject obj) 105 | { 106 | UUID serverId = this.getServerId(placement); 107 | 108 | if (serverId != null) 109 | { 110 | Syncmatica.debug("PlacementEventHandler#onSavePlacementToJson(): name: [{}], id: [{}]", placement.getName(), serverId.toString()); 111 | obj.add("syncmatica_uuid", new JsonPrimitive(serverId.toString())); 112 | } 113 | else 114 | { 115 | Syncmatica.LOGGER.warn("PlacementEventHandler#onSavePlacementToJson(): name: [{}] --> NOT FOUND!", placement.getName()); 116 | } 117 | } 118 | 119 | @Override 120 | public void onSavePlacementToNbt(SchematicPlacement placement, CompoundTag nbt) 121 | { 122 | UUID serverId = this.getServerId(placement); 123 | 124 | if (serverId != null) 125 | { 126 | Syncmatica.debug("PlacementEventHandler#onSavePlacementToNbt(): name: [{}], id: [{}]", placement.getName(), serverId.toString()); 127 | nbt.putString("syncmatica_uuid", serverId.toString()); 128 | } 129 | else 130 | { 131 | Syncmatica.LOGGER.warn("PlacementEventHandler#onSavePlacementToNbt(): name: [{}] --> NOT FOUND!", placement.getName()); 132 | } 133 | } 134 | 135 | @Override 136 | public void onPlacementAdded(SchematicPlacement placement) 137 | { 138 | // This is just a failsafe mechanism in case there is some kinda de-sync 139 | if (!this.hasServerId(placement) && 140 | LitematicManager.getInstance().isSyncmatic(placement)) 141 | { 142 | ServerPlacement serverPlacement = LitematicManager.getInstance().getRenderingSyncmatic(placement); 143 | 144 | if (serverPlacement != null) 145 | { 146 | this.setServerId(placement, serverPlacement.getId()); 147 | LitematicManager.getInstance().preLoad(placement); 148 | } 149 | } 150 | } 151 | 152 | @Override 153 | public void onPlacementRemoved(SchematicPlacement placement) 154 | { 155 | LitematicManager.getInstance().unrenderSchematic(placement.getSchematic()); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | --------------------------------------------------------------------------------