├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── api ├── build.gradle.kts └── src │ └── main │ └── java │ └── xyz │ └── jpenilla │ └── squaremap │ └── api │ ├── BukkitAdapter.java │ ├── HtmlComponentSerializer.java │ ├── HtmlStripper.java │ ├── Key.java │ ├── LayerProvider.java │ ├── MapWorld.java │ ├── Pair.java │ ├── PlayerManager.java │ ├── Point.java │ ├── ProviderHolder.java │ ├── Registry.java │ ├── SimpleLayerProvider.java │ ├── Squaremap.java │ ├── SquaremapProvider.java │ ├── WorldIdentifier.java │ ├── WorldIdentifierImpl.java │ └── marker │ ├── Circle.java │ ├── Ellipse.java │ ├── IPolygon.java │ ├── Icon.java │ ├── Marker.java │ ├── MarkerOptions.java │ ├── MultiPolygon.java │ ├── Polygon.java │ ├── Polyline.java │ └── Rectangle.java ├── build-logic ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── CopyFile.kt │ ├── SquaremapPlatformExtension.kt │ ├── ext.kt │ ├── squaremap.base-conventions.gradle.kts │ ├── squaremap.parent.gradle.kts │ ├── squaremap.platform.gradle.kts │ ├── squaremap.platform.loom.gradle.kts │ ├── squaremap.platform.mdg.gradle.kts │ ├── squaremap.platform.mod.gradle.kts │ └── squaremap.publishing.gradle.kts ├── build.gradle.kts ├── common ├── build.gradle.kts └── src │ └── main │ ├── java │ └── xyz │ │ └── jpenilla │ │ └── squaremap │ │ └── common │ │ ├── AbstractPlayerManager.java │ │ ├── IconRegistry.java │ │ ├── LayerRegistry.java │ │ ├── Logging.java │ │ ├── ServerAccess.java │ │ ├── SquaremapApiProvider.java │ │ ├── SquaremapCommon.java │ │ ├── SquaremapPlatform.java │ │ ├── WorldManager.java │ │ ├── WorldManagerImpl.java │ │ ├── command │ │ ├── BrigadierSetup.java │ │ ├── Commander.java │ │ ├── Commands.java │ │ ├── ExceptionHandler.java │ │ ├── PlatformCommands.java │ │ ├── PlayerCommander.java │ │ ├── SquaremapCommand.java │ │ ├── argument │ │ │ └── parser │ │ │ │ ├── LevelParser.java │ │ │ │ └── MapWorldParser.java │ │ ├── commands │ │ │ ├── CancelRenderCommand.java │ │ │ ├── ConfirmCommand.java │ │ │ ├── FullRenderCommand.java │ │ │ ├── HelpCommand.java │ │ │ ├── HideShowCommands.java │ │ │ ├── LinkCommand.java │ │ │ ├── PauseRenderCommand.java │ │ │ ├── ProgressLoggingCommand.java │ │ │ ├── RadiusRenderCommand.java │ │ │ ├── ReloadCommand.java │ │ │ └── ResetMapCommand.java │ │ └── exception │ │ │ └── CommandCompleted.java │ │ ├── config │ │ ├── AbstractConfig.java │ │ ├── AbstractWorldConfig.java │ │ ├── Advanced.java │ │ ├── Config.java │ │ ├── ConfigManager.java │ │ ├── ConfigUpgrader.java │ │ ├── Messages.java │ │ ├── Transformations.java │ │ ├── WorldAdvanced.java │ │ └── WorldConfig.java │ │ ├── data │ │ ├── BiomeColors.java │ │ ├── BlockColors.java │ │ ├── ChunkCoordinate.java │ │ ├── DirectoryProvider.java │ │ ├── Image.java │ │ ├── LevelBiomeColorData.java │ │ ├── MapWorldInternal.java │ │ ├── RegionCoordinate.java │ │ └── RenderManager.java │ │ ├── httpd │ │ ├── IntegratedServer.java │ │ ├── JsonCache.java │ │ └── ViteRunner.java │ │ ├── inject │ │ ├── SquaremapModulesBuilder.java │ │ ├── annotation │ │ │ └── DataDirectory.java │ │ └── module │ │ │ ├── ApiModule.java │ │ │ ├── PlatformModule.java │ │ │ ├── VanillaChunkSnapshotProviderFactoryModule.java │ │ │ └── VanillaRegionFileDirectoryResolverModule.java │ │ ├── layer │ │ ├── SpawnIconLayer.java │ │ └── WorldBorderLayer.java │ │ ├── network │ │ ├── Constants.java │ │ └── NetworkingHandler.java │ │ ├── task │ │ ├── TaskFactory.java │ │ ├── UpdateMarkers.java │ │ ├── UpdatePlayers.java │ │ ├── UpdateWorldData.java │ │ └── render │ │ │ ├── AbstractRender.java │ │ │ ├── BackgroundRender.java │ │ │ ├── FullRender.java │ │ │ ├── RadiusRender.java │ │ │ ├── RenderFactory.java │ │ │ └── RenderProgress.java │ │ ├── util │ │ ├── AbstractFluidColorExporter.java │ │ ├── CheckedConsumer.java │ │ ├── ChunkHashMapKey.java │ │ ├── ChunkMapAccess.java │ │ ├── ColorBlender.java │ │ ├── Colors.java │ │ ├── CommandUtil.java │ │ ├── Components.java │ │ ├── ConcurrentFIFOLoadingCache.java │ │ ├── EntityScheduler.java │ │ ├── ExceptionLoggingScheduledThreadPoolExecutor.java │ │ ├── FileUtil.java │ │ ├── HtmlComponentSerializerImpl.java │ │ ├── HtmlStripperImpl.java │ │ ├── ImageIOExecutor.java │ │ ├── NamedThreadFactory.java │ │ ├── Numbers.java │ │ ├── ReflectionUtil.java │ │ ├── RegionFileDirectoryResolver.java │ │ ├── SleepBlockingMinecraftServer.java │ │ ├── SpiralIterator.java │ │ ├── SquaremapJarAccess.java │ │ ├── UpdateChecker.java │ │ ├── Util.java │ │ └── chunksnapshot │ │ │ ├── ChunkSnapshot.java │ │ │ ├── ChunkSnapshotImpl.java │ │ │ ├── ChunkSnapshotProvider.java │ │ │ ├── ChunkSnapshotProviderFactory.java │ │ │ ├── HeightmapSnapshot.java │ │ │ ├── VanillaChunkSnapshotProvider.java │ │ │ └── VanillaChunkSnapshotProviderFactory.java │ │ └── visibilitylimit │ │ ├── CircleShape.java │ │ ├── PolygonShape.java │ │ ├── RectangleShape.java │ │ ├── VisibilityLimit.java │ │ ├── VisibilityLimitImpl.java │ │ ├── VisibilityShape.java │ │ ├── VisibilityShapeSerializer.java │ │ └── WorldBorderShape.java │ └── resources │ ├── META-INF │ └── services │ │ ├── xyz.jpenilla.squaremap.api.HtmlComponentSerializer$Provider │ │ └── xyz.jpenilla.squaremap.api.HtmlStripper$Provider │ ├── locale │ ├── lang-bs.yml │ ├── lang-es-es.yml │ ├── lang-fr.yml │ ├── lang-nl.yml │ ├── lang-no.yml │ ├── lang-pl.yml │ ├── lang-pt-br.yml │ ├── lang-ru.yml │ ├── lang-sk.yml │ ├── lang-zh-cn.yml │ └── lang-zh-tw.yml │ └── squaremap-common-at.cfg ├── fabric ├── build.gradle.kts └── src │ └── main │ ├── java │ └── xyz │ │ └── jpenilla │ │ └── squaremap │ │ └── fabric │ │ ├── FabricFluidColorExporter.java │ │ ├── FabricPlayerManager.java │ │ ├── FabricServerAccess.java │ │ ├── FabricSquaremapJarAccess.java │ │ ├── SquaremapComponentInitializer.java │ │ ├── SquaremapFabric.java │ │ ├── SquaremapFabricInitializer.java │ │ ├── command │ │ ├── FabricCommander.java │ │ └── FabricCommands.java │ │ ├── data │ │ └── FabricMapWorld.java │ │ ├── event │ │ ├── MapUpdateEvents.java │ │ └── ServerPlayerEvents.java │ │ ├── inject │ │ └── module │ │ │ └── FabricModule.java │ │ ├── listener │ │ └── FabricMapUpdates.java │ │ ├── mixin │ │ ├── BlockItemMixin.java │ │ ├── ChunkMapAccess.java │ │ ├── CommandSourceStackAccess.java │ │ ├── LiquidBlockAccess.java │ │ ├── PlayerListMixin.java │ │ ├── ServerPlayerMixin.java │ │ ├── SleepBlockingMinecraftServerMixin.java │ │ └── SpriteContentsMixin.java │ │ └── network │ │ └── FabricNetworking.java │ └── resources │ ├── squaremap-fabric.accesswidener │ └── squaremap-fabric.mixins.json ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── neoforge ├── build.gradle.kts └── src │ └── main │ ├── java │ └── xyz │ │ └── jpenilla │ │ └── squaremap │ │ └── forge │ │ ├── ForgeFluidColorExporter.java │ │ ├── ForgePlayerManager.java │ │ ├── ForgeServerAccess.java │ │ ├── ForgeSquaremapJarAccess.java │ │ ├── SquaremapForge.java │ │ ├── command │ │ ├── ForgeCommander.java │ │ └── ForgeCommands.java │ │ ├── data │ │ └── ForgeMapWorld.java │ │ ├── event │ │ └── ForgeMapUpdates.java │ │ ├── inject │ │ └── module │ │ │ └── ForgeModule.java │ │ ├── mixin │ │ ├── ChunkMapAccess.java │ │ └── SleepBlockingMinecraftServerMixin.java │ │ └── network │ │ └── ForgeNetworking.java │ └── resources │ ├── META-INF │ └── accesstransformer.cfg │ ├── pack.mcmeta │ └── squaremap-forge.mixins.json ├── paper ├── build.gradle.kts ├── folia │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── xyz │ │ └── jpenilla │ │ └── squaremap │ │ └── paper │ │ └── folia │ │ └── FoliaInitListener.java └── src │ └── main │ └── java │ └── xyz │ └── jpenilla │ └── squaremap │ └── paper │ ├── PaperPlayerManager.java │ ├── PaperServerAccess.java │ ├── PaperWorldManager.java │ ├── SquaremapPaper.java │ ├── SquaremapPaperBootstrap.java │ ├── command │ ├── PaperCommander.java │ └── PaperCommands.java │ ├── config │ └── PaperAdvanced.java │ ├── data │ └── PaperMapWorld.java │ ├── inject │ └── module │ │ └── PaperModule.java │ ├── listener │ ├── MapUpdateListeners.java │ └── WorldLoadListener.java │ ├── network │ └── PaperNetworking.java │ └── util │ ├── CraftBukkitHelper.java │ ├── Folia.java │ ├── PaperEntityScheduler.java │ ├── PaperRegionFileDirectoryResolver.java │ ├── WorldNameToKeyMigration.java │ └── chunksnapshot │ ├── PaperChunkSnapshotProvider.java │ └── PaperChunkSnapshotProviderFactory.java ├── renovate.json ├── settings.gradle.kts ├── sponge ├── build.gradle.kts └── src │ └── main │ ├── java │ └── xyz │ │ └── jpenilla │ │ └── squaremap │ │ └── sponge │ │ ├── SpongePlayerManager.java │ │ ├── SpongeServerAccess.java │ │ ├── SquaremapSponge.java │ │ ├── SquaremapSpongeBootstrap.java │ │ ├── command │ │ ├── SpongeCommander.java │ │ └── SpongeCommands.java │ │ ├── config │ │ └── SpongeAdvanced.java │ │ ├── data │ │ └── SpongeMapWorld.java │ │ ├── inject │ │ └── module │ │ │ └── SpongeModule.java │ │ ├── listener │ │ ├── MapUpdateListener.java │ │ └── WorldLoadListener.java │ │ ├── mixin │ │ ├── ChunkMapAccess.java │ │ ├── MainMixin.java │ │ └── SleepBlockingMinecraftServerMixin.java │ │ ├── network │ │ └── SpongeNetworking.java │ │ └── util │ │ └── SpongeVectors.java │ └── resources │ ├── squaremap-sponge.accesswidener │ └── squaremap-sponge.mixins.json └── web ├── .gitignore ├── .prettierignore ├── .prettierrc ├── bun.lock ├── eslint.config.js ├── global.d.ts ├── index.html ├── package.json ├── public ├── favicon.ico └── images │ ├── armor │ ├── 0.png │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 17.png │ ├── 18.png │ ├── 19.png │ ├── 2.png │ ├── 20.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png │ ├── clear.png │ ├── end_sky.png │ ├── foliage.png │ ├── grass.png │ ├── health │ ├── 0.png │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 17.png │ ├── 18.png │ ├── 19.png │ ├── 2.png │ ├── 20.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png │ ├── icon │ ├── blue-cube-smol.png │ ├── green-cube-smol.png │ ├── player.png │ ├── purple-cube-smol.png │ ├── red-cube-smol.png │ └── spawn.png │ ├── link.png │ ├── nether_sky.png │ ├── og.png │ ├── overworld_sky.png │ ├── pinned.png │ └── unpinned.png ├── src ├── css │ └── styles.css └── js │ ├── LayerControl.js │ ├── PlayerList.js │ ├── Sidebar.js │ ├── Squaremap.js │ ├── SquaremapTileLayer.js │ ├── UICoordinates.js │ ├── UILink.js │ ├── WorldList.js │ ├── addons │ ├── Ellipse.js │ └── RotateMarker.js │ ├── types.ts │ └── util │ ├── Fieldset.js │ ├── Markers.js │ ├── Pin.js │ ├── Player.js │ └── World.js ├── tsconfig.json └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_size = 4 4 | indent_style = space 5 | insert_final_newline = true 6 | max_line_length = off 7 | 8 | [{*.kt,*.kts}] 9 | indent_size = 2 10 | 11 | [*.java] 12 | ij_java_imports_layout = *,|,$* 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jpenilla 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | 11 | # Eclipse 12 | .classpath 13 | .project 14 | .settings/ 15 | plugin/bin/ 16 | api/bin/ 17 | 18 | # Compiled class file 19 | *.class 20 | 21 | # Log file 22 | *.log 23 | 24 | # BlueJ files 25 | *.ctxt 26 | 27 | # Package Files # 28 | *.war 29 | *.nar 30 | *.ear 31 | *.zip 32 | *.tar.gz 33 | *.rar 34 | 35 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 36 | hs_err_pid* 37 | 38 | *~ 39 | 40 | # temporary files which can be created if a process still has a handle open of a deleted file 41 | .fuse_hidden* 42 | 43 | # KDE directory preferences 44 | .directory 45 | 46 | # Linux trash folder which might appear on any partition or disk 47 | .Trash-* 48 | 49 | # .nfs files are created when an open file is removed but is still being accessed 50 | .nfs* 51 | 52 | # General 53 | .DS_Store 54 | .AppleDouble 55 | .LSOverride 56 | 57 | # Icon must end with two \r 58 | Icon 59 | 60 | # Thumbnails 61 | ._* 62 | 63 | # Files that might appear in the root of a volume 64 | .DocumentRevisions-V100 65 | .fseventsd 66 | .Spotlight-V100 67 | .TemporaryItems 68 | .Trashes 69 | .VolumeIcon.icns 70 | .com.apple.timemachine.donotpresent 71 | 72 | # Directories potentially created on remote AFP share 73 | .AppleDB 74 | .AppleDesktop 75 | Network Trash Folder 76 | Temporary Items 77 | .apdisk 78 | 79 | # Windows thumbnail cache files 80 | Thumbs.db 81 | Thumbs.db:encryptable 82 | ehthumbs.db 83 | ehthumbs_vista.db 84 | 85 | # Dump file 86 | *.stackdump 87 | 88 | # Folder config file 89 | [Dd]esktop.ini 90 | 91 | # Recycle Bin used on file shares 92 | $RECYCLE.BIN/ 93 | 94 | # Windows Installer files 95 | *.cab 96 | *.msi 97 | *.msix 98 | *.msm 99 | *.msp 100 | 101 | # Windows shortcuts 102 | *.lnk 103 | 104 | target/ 105 | 106 | pom.xml.tag 107 | pom.xml.releaseBackup 108 | pom.xml.versionsBackup 109 | pom.xml.next 110 | 111 | release.properties 112 | dependency-reduced-pom.xml 113 | buildNumber.properties 114 | .mvn/timing.properties 115 | .mvn/wrapper/maven-wrapper.jar 116 | .flattened-pom.xml 117 | 118 | # Common working directory 119 | run/ 120 | build 121 | .gradle 122 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2024 Jason Penilla 4 | Contributors 5 | 6 | Copyright (c) 2020-2021 William Blake Galbreath & Contributors 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("squaremap.publishing") 3 | alias(libs.plugins.javadoc.links) 4 | } 5 | 6 | description = "API for extending squaremap, a minimalistic and lightweight world map viewer for Minecraft servers" 7 | 8 | java { 9 | disableAutoTargetJvm() 10 | } 11 | 12 | dependencies { 13 | compileOnly(libs.paperApi) 14 | javadocLinks(libs.paperApi) { 15 | isTransitive = false 16 | } 17 | compileOnlyApi(libs.checkerQual) 18 | compileOnlyApi(platform(libs.adventureBom)) 19 | compileOnlyApi(libs.adventureApi) 20 | } 21 | 22 | indra { 23 | javaVersions { 24 | target(8) 25 | } 26 | } 27 | 28 | tasks.withType(JavaCompile::class).configureEach { 29 | // Don't warn about missing forRemoval in @Deprecated 30 | options.compilerArgs.add("-Xlint:-classfile") 31 | } 32 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/BukkitAdapter.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import java.util.Objects; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.Location; 6 | import org.bukkit.NamespacedKey; 7 | import org.bukkit.World; 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | 11 | /** 12 | * Helper methods for bridging the Bukkit API with squaremap API. 13 | */ 14 | @DefaultQualifier(NonNull.class) 15 | public final class BukkitAdapter { 16 | private BukkitAdapter() { 17 | } 18 | 19 | /** 20 | * Gets the {@link WorldIdentifier} for a Bukkit {@link World}. 21 | * 22 | * @param world Bukkit world 23 | * @return world identifier 24 | */ 25 | public static WorldIdentifier worldIdentifier(final World world) { 26 | return WorldIdentifier.create(world.key().namespace(), world.key().value()); 27 | } 28 | 29 | /** 30 | * Convert the given {@link WorldIdentifier} to a Bukkit {@link NamespacedKey}. 31 | * 32 | * @param worldIdentifier world identifier 33 | * @return namespaced key 34 | */ 35 | public static NamespacedKey namespacedKey(final WorldIdentifier worldIdentifier) { 36 | return Objects.requireNonNull(NamespacedKey.fromString(worldIdentifier.asString())); 37 | } 38 | 39 | /** 40 | * Gets the Bukkit {@link World} for the given {@link MapWorld}. 41 | * 42 | * @param world world identifier 43 | * @return bukkit world 44 | */ 45 | public static World bukkitWorld(final MapWorld world) { 46 | return Objects.requireNonNull(Bukkit.getWorld(namespacedKey(world.identifier()))); 47 | } 48 | 49 | /** 50 | * Create a new point from a Bukkit {@link Location}. Uses block location. 51 | * 52 | * @param location location 53 | * @return point 54 | */ 55 | public static Point point(final Location location) { 56 | return Point.point(location.getBlockX(), location.getBlockZ()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/HtmlComponentSerializer.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.flattener.ComponentFlattener; 5 | import net.kyori.adventure.text.serializer.ComponentEncoder; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | import org.jetbrains.annotations.ApiStatus; 9 | 10 | /** 11 | * Safely encodes {@link Component Components} as HTML text. 12 | * 13 | *

Mostly useful for marker tooltips.

14 | * 15 | * @see Squaremap#htmlComponentSerializer() 16 | * @since 1.2.0 17 | */ 18 | @DefaultQualifier(NonNull.class) 19 | public interface HtmlComponentSerializer extends ComponentEncoder { 20 | 21 | /** 22 | * Create a new {@link HtmlComponentSerializer} using the provided {@link ComponentFlattener}. 23 | * 24 | * @param flattener component flattener 25 | * @return serializer 26 | */ 27 | static HtmlComponentSerializer withFlattener(final ComponentFlattener flattener) { 28 | return ProviderHolder.HTML_SERIALIZER.create(flattener); 29 | } 30 | 31 | @ApiStatus.Internal 32 | interface Provider { 33 | HtmlComponentSerializer create(ComponentFlattener flattener); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/HtmlStripper.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.framework.qual.DefaultQualifier; 5 | import org.jetbrains.annotations.ApiStatus; 6 | 7 | /** 8 | * Strips HTML tags from text. Allows sanitizing untrusted strings before use in 9 | * marker tooltips. 10 | * 11 | * @since 1.2.0 12 | */ 13 | @DefaultQualifier(NonNull.class) 14 | public interface HtmlStripper { 15 | 16 | /** 17 | * Get an {@link HtmlStripper}. 18 | * 19 | * @return HTML stripper 20 | */ 21 | static HtmlStripper htmlStripper() { 22 | return ProviderHolder.HTML_STRIPPER.instance(); 23 | } 24 | 25 | /** 26 | * Strips HTML tags from the provided string. 27 | * 28 | * @param string untrusted string 29 | * @return sanitized string 30 | */ 31 | String stripHtml(String string); 32 | 33 | @ApiStatus.Internal 34 | interface Provider { 35 | HtmlStripper instance(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/LayerProvider.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import java.util.Collection; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import xyz.jpenilla.squaremap.api.marker.Marker; 6 | 7 | /** 8 | * Provides Markers and other metadata which make up a layer. LayerProviders are called on each update of a layer. 9 | */ 10 | public interface LayerProvider { 11 | 12 | /** 13 | * Get the label of this LayerProvider, shown in the control box 14 | * 15 | * @return label 16 | */ 17 | @NonNull String getLabel(); 18 | 19 | /** 20 | * Whether to show this layer in the control box 21 | * 22 | *

Default implementation always returns {@code true}

23 | * 24 | * @return boolean 25 | */ 26 | default boolean showControls() { 27 | return true; 28 | } 29 | 30 | /** 31 | * Whether this layer is hidden by default in the control box 32 | * 33 | *

Default implementation always returns {@code false}

34 | * 35 | * @return boolean 36 | */ 37 | default boolean defaultHidden() { 38 | return false; 39 | } 40 | 41 | /** 42 | * 0-indexed order for this layer in the control box 43 | *

Falls back to alpha-numeric ordering based on name if there are order conflicts

44 | * 45 | * @return arbitrary number 46 | */ 47 | int layerPriority(); 48 | 49 | /** 50 | * 0-indexed z-index for this layer. Used in determining what layers are visually on top of other layers. 51 | * 52 | *

Falls back to alpha-numeric ordering based on name if there are order conflicts

53 | * 54 | *

Default implementation returns {@link #layerPriority()}

55 | * 56 | * @return arbitrary number 57 | */ 58 | default int zIndex() { 59 | return this.layerPriority(); 60 | } 61 | 62 | /** 63 | * Get the markers to display 64 | * 65 | * @return markers 66 | */ 67 | @NonNull Collection getMarkers(); 68 | 69 | } 70 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/MapWorld.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | /** 6 | * Represents a world mapped by squaremap. 7 | */ 8 | public interface MapWorld { 9 | 10 | /** 11 | * Gets the layer registry for this world. 12 | * 13 | * @return the layer registry 14 | */ 15 | @NonNull Registry layerRegistry(); 16 | 17 | /** 18 | * Get the identifier of this world. 19 | * 20 | * @return identifier 21 | */ 22 | @NonNull WorldIdentifier identifier(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/Pair.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import java.util.Objects; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.checker.nullness.qual.Nullable; 6 | 7 | /** 8 | * Data class for holding a pair of non-null objects with generic types. 9 | * 10 | * @param Left generic type argument 11 | * @param Right generic type argument 12 | */ 13 | public final class Pair { 14 | private final L left; 15 | private final R right; 16 | 17 | private Pair(final @NonNull L left, final @NonNull R right) { 18 | this.left = left; 19 | this.right = right; 20 | } 21 | 22 | /** 23 | * Get the left value 24 | * 25 | * @return Left value 26 | */ 27 | public @NonNull L left() { 28 | return this.left; 29 | } 30 | 31 | /** 32 | * Get the right value 33 | * 34 | * @return Right value 35 | */ 36 | public @NonNull R right() { 37 | return this.right; 38 | } 39 | 40 | @Override 41 | public boolean equals(final @Nullable Object o) { 42 | if (this == o) { 43 | return true; 44 | } 45 | if (o == null || this.getClass() != o.getClass()) { 46 | return false; 47 | } 48 | final @Nullable Pair pair = (Pair) o; 49 | return this.left.equals(pair.left) && this.right.equals(pair.right); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | return Objects.hash(this.left, this.right); 55 | } 56 | 57 | /** 58 | * Create a new Pair 59 | * 60 | * @param left Left value 61 | * @param right Right value 62 | * @param Left generic type 63 | * @param Right generic type 64 | * @return new pair 65 | */ 66 | public static @NonNull Pair of(final @NonNull L left, final @NonNull R right) { 67 | return new Pair<>(left, right); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/PlayerManager.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import java.util.UUID; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | 6 | /** 7 | * Interface for interacting with players on the map 8 | */ 9 | public interface PlayerManager { 10 | 11 | /** 12 | * Set a player to be temporarily hidden on the map 13 | *

14 | * The status will last until the server restarts or the plugin reloads 15 | * 16 | * @param uuid player UUID 17 | */ 18 | default void hide(final @NonNull UUID uuid) { 19 | hide(uuid, false); 20 | } 21 | 22 | /** 23 | * Set a player to be hidden on the map 24 | * 25 | * @param uuid player UUID 26 | * @param persistent persist the hide status 27 | */ 28 | void hide(@NonNull UUID uuid, boolean persistent); 29 | 30 | /** 31 | * Set a player to temporarily not be hidden on the map 32 | *

33 | * The status will last until the server restarts or the plugin reloads 34 | * 35 | * @param uuid player UUID 36 | */ 37 | default void show(final @NonNull UUID uuid) { 38 | show(uuid, false); 39 | } 40 | 41 | /** 42 | * Set a player to not be hidden on the map 43 | * 44 | * @param uuid player UUID 45 | * @param persistent persist the show status 46 | */ 47 | void show(@NonNull UUID uuid, boolean persistent); 48 | 49 | /** 50 | * Set whether a player is hidden on the map temporarily 51 | *

52 | * The status will last until the server restarts or the plugin reloads 53 | * 54 | * @param uuid player UUID 55 | * @param hide whether to hide the player 56 | */ 57 | default void hidden(final @NonNull UUID uuid, final boolean hide) { 58 | hidden(uuid, hide, false); 59 | } 60 | 61 | /** 62 | * Set whether a player is hidden on the map 63 | * 64 | * @param uuid player UUID 65 | * @param hide whether to hide the player 66 | * @param persistent persist the hide status 67 | */ 68 | void hidden(@NonNull UUID uuid, boolean hide, boolean persistent); 69 | 70 | /** 71 | * Get whether a player is hidden on the map 72 | * 73 | * @param uuid player UUID 74 | * @return whether the player is hidden 75 | */ 76 | boolean hidden(@NonNull UUID uuid); 77 | 78 | } 79 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/Point.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import java.util.Objects; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.checker.nullness.qual.Nullable; 6 | 7 | /** 8 | * Represents a point on a map in the XZ plane. May be relative or absolute depending on the context 9 | */ 10 | public final class Point { 11 | 12 | private final double x; 13 | private final double z; 14 | 15 | private Point(final double x, final double z) { 16 | this.x = x; 17 | this.z = z; 18 | } 19 | 20 | /** 21 | * Get the x position of this point 22 | * 23 | * @return x 24 | */ 25 | public double x() { 26 | return this.x; 27 | } 28 | 29 | /** 30 | * Get the z position of this point 31 | * 32 | * @return z 33 | */ 34 | public double z() { 35 | return this.z; 36 | } 37 | 38 | /** 39 | * Create a new point from an x and z position 40 | * 41 | * @param x x position 42 | * @param z z position 43 | * @return point 44 | */ 45 | public static @NonNull Point of(final double x, final double z) { 46 | return new Point(x, z); 47 | } 48 | 49 | /** 50 | * Create a new point from an x and z position 51 | * 52 | * @param x x position 53 | * @param z z position 54 | * @return point 55 | */ 56 | public static @NonNull Point point(final double x, final double z) { 57 | return new Point(x, z); 58 | } 59 | 60 | @Override 61 | public boolean equals(final @Nullable Object o) { 62 | if (this == o) { 63 | return true; 64 | } 65 | if (o == null || this.getClass() != o.getClass()) { 66 | return false; 67 | } 68 | final @Nullable Point point = (Point) o; 69 | return Double.compare(point.x, this.x) == 0 && Double.compare(point.z, this.z) == 0; 70 | } 71 | 72 | @Override 73 | public int hashCode() { 74 | return Objects.hash(this.x, this.z); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/ProviderHolder.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import net.kyori.adventure.util.Services; 4 | 5 | final class ProviderHolder { 6 | static final HtmlComponentSerializer.Provider HTML_SERIALIZER = service(HtmlComponentSerializer.Provider.class); 7 | static final HtmlStripper.Provider HTML_STRIPPER = service(HtmlStripper.Provider.class); 8 | 9 | private static T service(final Class clazz) { 10 | return Services.service(clazz) 11 | .orElseThrow(() -> new IllegalStateException("Could not find " + clazz.getName() + " implementation")); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/Registry.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | /** 6 | * Simple registry interface 7 | * 8 | * @param Generic type argument 9 | */ 10 | public interface Registry { 11 | 12 | /** 13 | * Register a new entry with the provided key and value 14 | * 15 | * @param key key 16 | * @param value value 17 | * @throws IllegalArgumentException when there is already an entry for the provided key 18 | */ 19 | void register(@NonNull Key key, @NonNull T value); 20 | 21 | /** 22 | * Unregister the entry for the provided key if present 23 | * 24 | * @param key key 25 | * @throws IllegalArgumentException when there is no entry for the provided key 26 | */ 27 | void unregister(@NonNull Key key); 28 | 29 | /** 30 | * Check whether an entry is present for the provided key 31 | * 32 | * @param key key 33 | * @return whether an entry is present 34 | */ 35 | boolean hasEntry(@NonNull Key key); 36 | 37 | /** 38 | * Get the registered value for a key 39 | * 40 | * @param key key 41 | * @return value 42 | * @throws IllegalArgumentException when there is no value for the provided key 43 | */ 44 | @NonNull T get(@NonNull Key key); 45 | 46 | /** 47 | * Get the registered entries 48 | * 49 | * @return the registered entries 50 | */ 51 | @NonNull Iterable> entries(); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/Squaremap.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.nio.file.Path; 5 | import java.util.Collection; 6 | import java.util.Optional; 7 | import net.kyori.adventure.text.flattener.ComponentFlattener; 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | 10 | /** 11 | * squaremap API 12 | * 13 | *

The API allows other plugins on the server integrate with squaremap.

14 | * 15 | *

This interface represents the base of the API package. All functions are 16 | * accessed via this interface.

17 | * 18 | *

To start using the API, you need to obtain an instance of this interface. 19 | * These are registered by the squaremap plugin to the platforms Services 20 | * Manager. This is the preferred method for obtaining an instance.

21 | * 22 | *

For ease of use, an instance can also be obtained from the static 23 | * singleton accessor in {@link SquaremapProvider}.

24 | */ 25 | public interface Squaremap { 26 | 27 | /** 28 | * Get an unmodifiable view of the enabled worlds 29 | * 30 | * @return The set of worlds 31 | */ 32 | @NonNull Collection mapWorlds(); 33 | 34 | /** 35 | * Get an optional which will either 36 | *
    37 | *
  • A) Be empty, if the world does not exist, or does not have squaremap enabled
  • 38 | *
  • B) Contain the {@link MapWorld} instance for the World associated with the provided {@link WorldIdentifier}, if the world exists and has squaremap enabled
  • 39 | *
40 | * 41 | * @param identifier world identifier 42 | * @return optional 43 | */ 44 | @NonNull Optional getWorldIfEnabled(@NonNull WorldIdentifier identifier); 45 | 46 | /** 47 | * Get the registry of images which can be used with icon markers 48 | * 49 | * @return icon registry 50 | */ 51 | @NonNull Registry iconRegistry(); 52 | 53 | /** 54 | * Get the player manager 55 | * 56 | * @return player manager 57 | */ 58 | @NonNull PlayerManager playerManager(); 59 | 60 | /** 61 | * Get the web directory 62 | * 63 | * @return web directory 64 | */ 65 | @NonNull Path webDir(); 66 | 67 | /** 68 | * Get an {@link HtmlComponentSerializer} using the platform {@link ComponentFlattener}. 69 | * 70 | * @return serializer 71 | * @since 1.2.0 72 | */ 73 | @NonNull HtmlComponentSerializer htmlComponentSerializer(); 74 | 75 | } 76 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/SquaremapProvider.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | /** 6 | * Static singleton for conveniently accessing the squaremap API instance. 7 | * Prefer using the platform's service manager when available. 8 | */ 9 | public final class SquaremapProvider { 10 | private static Squaremap instance = null; 11 | 12 | private SquaremapProvider() { 13 | throw new UnsupportedOperationException("This class cannot be instantiated."); 14 | } 15 | 16 | /** 17 | * Gets an instance of the {@link Squaremap} service, 18 | * throwing {@link IllegalStateException} if an instance is not yet loaded. 19 | * 20 | *

Will never return null.

21 | * 22 | * @return an api instance 23 | * @throws IllegalStateException if the api is not loaded 24 | */ 25 | public static @NonNull Squaremap get() { 26 | if (instance == null) { 27 | throw new IllegalStateException("The squaremap API is not loaded."); 28 | } 29 | return instance; 30 | } 31 | 32 | static void register(final @NonNull Squaremap instance) { 33 | SquaremapProvider.instance = instance; 34 | } 35 | 36 | static void unregister() { 37 | SquaremapProvider.instance = null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/WorldIdentifier.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.framework.qual.DefaultQualifier; 5 | 6 | /** 7 | * Namespaced identifier used to query worlds from squaremap. 8 | * 9 | *

Mirrors Minecraft's {@code ResourceLocation}. This means the same rules apply regarding allowed characters.

10 | * 11 | * @see https://minecraft.wiki/w/Resource_location#Legal_characters 12 | */ 13 | @DefaultQualifier(NonNull.class) 14 | public interface WorldIdentifier { 15 | /** 16 | * Gets the namespace string of this {@link WorldIdentifier}. 17 | * 18 | * @return namespace string 19 | */ 20 | String namespace(); 21 | 22 | /** 23 | * Gets the value string of this {@link WorldIdentifier}. 24 | * 25 | * @return value string 26 | */ 27 | String value(); 28 | 29 | /** 30 | * Get the string representation of this {@link WorldIdentifier}. 31 | * 32 | * @return string representation 33 | */ 34 | String asString(); 35 | 36 | /** 37 | * Create a new {@link WorldIdentifier} from the provided namespace and value strings. 38 | * 39 | * @param namespace namespace string 40 | * @param value value string 41 | * @return new {@link WorldIdentifier} 42 | */ 43 | static WorldIdentifier create(final String namespace, final String value) { 44 | return new WorldIdentifierImpl(namespace, value); 45 | } 46 | 47 | /** 48 | * Parse a colon separated identifier string into a new {@link WorldIdentifier}. 49 | * 50 | * @param identifierString identifier string 51 | * @return new {@link WorldIdentifier} 52 | */ 53 | static WorldIdentifier parse(final String identifierString) { 54 | final String[] split = identifierString.split(":"); 55 | if (split.length != 2) { 56 | throw new IllegalArgumentException("Invalid format for WorldIdentifier string '" + identifierString + "', expected 'namespace:value'."); 57 | } 58 | return create(split[0], split[1]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/marker/Circle.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api.marker; 2 | 3 | import java.util.Objects; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.checker.nullness.qual.Nullable; 6 | import xyz.jpenilla.squaremap.api.Point; 7 | 8 | /** 9 | * Circle marker 10 | */ 11 | public final class Circle extends Marker { 12 | 13 | private Point center; 14 | private double radius; 15 | 16 | Circle(final @NonNull Point center, final double radius) { 17 | this.center = center; 18 | this.radius = radius; 19 | } 20 | 21 | /** 22 | * Get the center point of this circle 23 | * 24 | * @return center point 25 | */ 26 | public @NonNull Point center() { 27 | return this.center; 28 | } 29 | 30 | /** 31 | * Set a new center point for this circle 32 | * 33 | * @param center new center 34 | */ 35 | public void center(final @NonNull Point center) { 36 | this.center = center; 37 | } 38 | 39 | /** 40 | * Get the radius of this circle 41 | * 42 | * @return radius 43 | */ 44 | public double radius() { 45 | return this.radius; 46 | } 47 | 48 | /** 49 | * Set the radius of this circle 50 | * 51 | * @param radius new radius 52 | */ 53 | public void radius(double radius) { 54 | this.radius = radius; 55 | } 56 | 57 | @Override 58 | public boolean equals(final @Nullable Object o) { 59 | if (this == o) { 60 | return true; 61 | } 62 | if (o == null || this.getClass() != o.getClass()) { 63 | return false; 64 | } 65 | final @Nullable Circle circle = (Circle) o; 66 | return this.markerOptionsMatch(circle) 67 | && Double.compare(circle.radius, this.radius) == 0 68 | && this.center.equals(circle.center); 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | return Objects.hash(this.markerOptions(), this.center, this.radius); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/marker/IPolygon.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api.marker; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | import xyz.jpenilla.squaremap.api.Point; 7 | import xyz.jpenilla.squaremap.api.marker.MultiPolygon.MultiPolygonPart; 8 | 9 | /** 10 | * Interface with common methods to {@link Polygon} and {@link MultiPolygonPart} 11 | */ 12 | public interface IPolygon { 13 | 14 | /** 15 | * Get the mutable list of polygons which make up the negative space for this polygon. 16 | * 17 | * @return negative space 18 | */ 19 | @NonNull List> negativeSpace(); 20 | 21 | /** 22 | * Set the negative space for this polygon. This will reset any negative space currently set. 23 | * 24 | * @param points new negative space 25 | */ 26 | @SuppressWarnings("unchecked") 27 | default void negativeSpace(final @NonNull List @NonNull ... points) { 28 | this.negativeSpace(Arrays.asList(points)); 29 | } 30 | 31 | /** 32 | * Set the negative space for this polygon. This will reset any negative space currently set. 33 | * 34 | * @param points new negative space 35 | */ 36 | default void negativeSpace(final @NonNull List> points) { 37 | this.negativeSpace().clear(); 38 | this.negativeSpace().addAll(points); 39 | } 40 | 41 | /** 42 | * Get the mutable list of the points which make up the main polygon 43 | * 44 | * @return main polygon 45 | */ 46 | @NonNull List mainPolygon(); 47 | 48 | /** 49 | * Set the points which make up the main polygon for this polygon. This will reset any currently set points. 50 | * 51 | * @param points new main polygon 52 | */ 53 | default void mainPolygon(final @NonNull Point @NonNull ... points) { 54 | this.mainPolygon(Arrays.asList(points)); 55 | } 56 | 57 | /** 58 | * Set the points which make up the main polygon for this polygon. This will reset any currently set points. 59 | * 60 | * @param points new main polygon 61 | */ 62 | default void mainPolygon(final @NonNull List points) { 63 | this.mainPolygon().clear(); 64 | this.mainPolygon().addAll(points); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/marker/Polygon.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api.marker; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Objects; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.checker.nullness.qual.Nullable; 8 | import xyz.jpenilla.squaremap.api.Point; 9 | 10 | /** 11 | * Polygon marker 12 | */ 13 | public final class Polygon extends Marker implements IPolygon { 14 | 15 | private final List mainPolygon; 16 | private final List> negativeSpace; 17 | 18 | Polygon(final @NonNull List points, final @NonNull List> negativeSpace) { 19 | this.mainPolygon = new ArrayList<>(points); 20 | this.negativeSpace = new ArrayList<>(negativeSpace); 21 | } 22 | 23 | @Override 24 | public @NonNull List> negativeSpace() { 25 | return this.negativeSpace; 26 | } 27 | 28 | @Override 29 | public @NonNull List mainPolygon() { 30 | return this.mainPolygon; 31 | } 32 | 33 | @Override 34 | public boolean equals(final @Nullable Object o) { 35 | if (this == o) { 36 | return true; 37 | } 38 | if (o == null || this.getClass() != o.getClass()) { 39 | return false; 40 | } 41 | final @Nullable Polygon polygon = (Polygon) o; 42 | return this.markerOptionsMatch(polygon) 43 | && this.mainPolygon.equals(polygon.mainPolygon) 44 | && this.negativeSpace.equals(polygon.negativeSpace); 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(this.markerOptions(), this.mainPolygon, this.negativeSpace); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /api/src/main/java/xyz/jpenilla/squaremap/api/marker/Rectangle.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.api.marker; 2 | 3 | import java.util.Objects; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.checker.nullness.qual.Nullable; 6 | import xyz.jpenilla.squaremap.api.Point; 7 | 8 | /** 9 | * Rectangle marker 10 | */ 11 | public final class Rectangle extends Marker { 12 | 13 | private Point point1; 14 | private Point point2; 15 | 16 | Rectangle(final @NonNull Point point1, final @NonNull Point point2) { 17 | this.point1 = point1; 18 | this.point2 = point2; 19 | } 20 | 21 | public void points(final @NonNull Point point1, final @NonNull Point point2) { 22 | this.point1 = point1; 23 | this.point2 = point2; 24 | } 25 | 26 | /** 27 | * Get the first corner point for this rectangle 28 | * 29 | * @return point1 30 | */ 31 | public @NonNull Point point1() { 32 | return this.point1; 33 | } 34 | 35 | /** 36 | * Set the first corner point for this rectangle 37 | * 38 | * @param point new point 39 | */ 40 | public void point1(final @NonNull Point point) { 41 | this.point1 = point; 42 | } 43 | 44 | /** 45 | * Get the second corner point for this rectangle 46 | * 47 | * @return point2 48 | */ 49 | public @NonNull Point point2() { 50 | return this.point2; 51 | } 52 | 53 | /** 54 | * Set the second corner point for this rectangle 55 | * 56 | * @param point new point 57 | */ 58 | public void point2(final @NonNull Point point) { 59 | this.point2 = point; 60 | } 61 | 62 | @Override 63 | public boolean equals(final @Nullable Object o) { 64 | if (this == o) { 65 | return true; 66 | } 67 | if (o == null || this.getClass() != o.getClass()) { 68 | return false; 69 | } 70 | final @Nullable Rectangle rectangle = (Rectangle) o; 71 | return this.markerOptionsMatch(rectangle) 72 | && this.point1.equals(rectangle.point1) 73 | && this.point2.equals(rectangle.point2); 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | return Objects.hash(this.markerOptions(), this.point1, this.point2); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | gradlePluginPortal() 7 | mavenCentral() 8 | maven("https://repo.papermc.io/repository/maven-public/") 9 | maven("https://maven.fabricmc.net/") 10 | maven("https://maven.neoforged.net/releases/") 11 | maven("https://maven.architectury.dev/") 12 | maven("https://repo.jpenilla.xyz/snapshots/") 13 | } 14 | 15 | dependencies { 16 | implementation(libs.mdg) 17 | implementation(libs.indraCommon) 18 | implementation(libs.indraPublishingSonatype) 19 | implementation(libs.shadow) 20 | implementation(libs.mod.publish.plugin) 21 | implementation(libs.loom) 22 | implementation(libs.paperweightUserdev) 23 | } 24 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | versionCatalogs { 3 | create("libs") { 4 | from(files("../gradle/libs.versions.toml")) 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/CopyFile.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.DefaultTask 2 | import org.gradle.api.file.RegularFileProperty 3 | import org.gradle.api.tasks.InputFile 4 | import org.gradle.api.tasks.OutputFile 5 | import org.gradle.api.tasks.TaskAction 6 | import org.gradle.work.DisableCachingByDefault 7 | 8 | @DisableCachingByDefault(because = "Not worth caching") 9 | abstract class CopyFile : DefaultTask() { 10 | @get:InputFile 11 | abstract val fileToCopy: RegularFileProperty 12 | 13 | @get:OutputFile 14 | abstract val destination: RegularFileProperty 15 | 16 | @TaskAction 17 | fun copyFile() { 18 | destination.get().asFile.parentFile.mkdirs() 19 | fileToCopy.get().asFile.copyTo(destination.get().asFile, overwrite = true) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/SquaremapPlatformExtension.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.file.RegularFileProperty 2 | import javax.inject.Inject 3 | 4 | abstract class SquaremapPlatformExtension @Inject constructor() { 5 | abstract val productionJar: RegularFileProperty 6 | } 7 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/ext.kt: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | import net.kyori.indra.git.IndraGitExtension 3 | import org.eclipse.jgit.lib.Repository 4 | import org.gradle.api.Project 5 | import org.gradle.api.file.ProjectLayout 6 | import org.gradle.api.file.RegularFile 7 | import org.gradle.api.plugins.BasePluginExtension 8 | import org.gradle.api.provider.Provider 9 | import org.gradle.api.provider.ProviderFactory 10 | import org.gradle.kotlin.dsl.getByType 11 | 12 | fun runProps(layout: ProjectLayout, providers: ProviderFactory): Map = mapOf( 13 | "squaremap.devFrontend" to providers.gradleProperty("devFrontend").getOrElse("true"), 14 | "squaremap.frontendPath" to layout.settingsDirectory.dir("web").asFile.absolutePath, 15 | ) 16 | 17 | val Project.releaseNotes: Provider 18 | get() = providers.environmentVariable("RELEASE_NOTES") 19 | 20 | val Project.githubUrl: Provider 21 | get() = providers.gradleProperty("githubUrl") 22 | 23 | fun Project.lastCommitHash(): String = extensions.getByType().commit()?.name?.substring(0, 7) 24 | ?: error("Could not determine commit hash") 25 | 26 | fun Project.decorateVersion() { 27 | val versionString = version as String 28 | version = if (versionString.endsWith("-SNAPSHOT")) { 29 | "$versionString+${lastCommitHash()}" 30 | } else { 31 | versionString 32 | } 33 | } 34 | 35 | fun ShadowJar.reloc(pkg: String) { 36 | relocate(pkg, "squaremap.libraries.$pkg") 37 | } 38 | 39 | fun Project.currentBranch(): String { 40 | System.getenv("GITHUB_HEAD_REF")?.takeIf { it.isNotEmpty() } 41 | ?.let { return it } 42 | System.getenv("GITHUB_REF")?.takeIf { it.isNotEmpty() } 43 | ?.let { return it.replaceFirst("refs/heads/", "") } 44 | 45 | val indraGit = extensions.getByType().takeIf { it.isPresent } 46 | 47 | val ref = indraGit?.git()?.repository?.exactRef("HEAD")?.target 48 | ?: return "detached-head" 49 | 50 | return Repository.shortenRefName(ref.name) 51 | } 52 | 53 | fun Project.productionJarName(mcVer: Provider): Provider = extensions.getByType() 54 | .archivesName.zip(mcVer) { archivesName, mc -> "$archivesName-mc$mc-$version.jar" } 55 | 56 | fun Project.productionJarLocation(mcVer: Provider): Provider = 57 | productionJarName(mcVer).flatMap { layout.buildDirectory.file("libs/$it") } 58 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/squaremap.base-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("net.kyori.indra") 4 | id("net.kyori.indra.git") 5 | } 6 | 7 | group = rootProject.group 8 | version = rootProject.version 9 | description = rootProject.description 10 | 11 | indra { 12 | javaVersions { 13 | minimumToolchain(21) 14 | target(21) 15 | } 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | maven("https://repo.jpenilla.xyz/snapshots/") { 21 | mavenContent { 22 | includeModule("org.incendo", "cloud-sponge") 23 | includeGroup("xyz.jpenilla") 24 | snapshotsOnly() 25 | } 26 | } 27 | sonatype.s01Snapshots() 28 | sonatype.ossSnapshots() 29 | maven("https://repo.papermc.io/repository/maven-public/") 30 | maven("https://cursemaven.com") { 31 | content { 32 | includeGroup("curse.maven") 33 | } 34 | } 35 | } 36 | 37 | tasks.withType(JavaCompile::class).configureEach { 38 | // We don't care about annotations being unclaimed by processors, 39 | // missing annotation (values), or Java serialization 40 | options.compilerArgs.add("-Xlint:-processing,-classfile,-serial") 41 | } 42 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/squaremap.parent.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | base 3 | id("net.kyori.indra.publishing.sonatype") 4 | } 5 | 6 | indraSonatype { 7 | useAlternateSonatypeOSSHost("s01") 8 | } 9 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/squaremap.platform.gradle.kts: -------------------------------------------------------------------------------- 1 | import me.modmuss50.mpp.ReleaseType 2 | 3 | plugins { 4 | id("squaremap.base-conventions") 5 | id("com.gradleup.shadow") 6 | id("me.modmuss50.mod-publish-plugin") 7 | } 8 | 9 | val platformExt = extensions.create("squaremapPlatform", SquaremapPlatformExtension::class) 10 | 11 | decorateVersion() 12 | 13 | tasks { 14 | jar { 15 | manifest { 16 | attributes( 17 | "squaremap-version" to project.version, 18 | "squaremap-commit" to lastCommitHash(), 19 | "squaremap-branch" to currentBranch(), 20 | ) 21 | } 22 | } 23 | shadowJar { 24 | from(rootProject.projectDir.resolve("LICENSE")) { 25 | rename("LICENSE", "META-INF/LICENSE_${rootProject.name}") 26 | } 27 | minimize { 28 | exclude { it.moduleName.contains("squaremap") } 29 | exclude(dependency("io.undertow:.*:.*")) // does not like being minimized _or_ relocated (xnio errors) 30 | } 31 | dependencies { 32 | exclude(dependency("com.google.errorprone:.*:.*")) 33 | } 34 | listOf( 35 | "org.owasp.html", 36 | "org.owasp.shim", 37 | "org.spongepowered.configurate", 38 | "org.yaml.snakeyaml" 39 | ).forEach(::reloc) 40 | } 41 | val copyJar = register("copyJar", CopyFile::class) { 42 | fileToCopy = platformExt.productionJar 43 | destination = platformExt.productionJar.flatMap { 44 | rootProject.layout.buildDirectory.file("libs/${it.asFile.name}") 45 | } 46 | } 47 | assemble { 48 | dependsOn(copyJar) 49 | } 50 | javadoc { 51 | enabled = false 52 | } 53 | } 54 | 55 | publishMods.modrinth { 56 | projectId = "PFb7ZqK6" 57 | type = ReleaseType.STABLE 58 | file = platformExt.productionJar 59 | changelog = releaseNotes 60 | accessToken = providers.environmentVariable("MODRINTH_TOKEN") 61 | } 62 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/squaremap.platform.loom.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("squaremap.platform.mod") 3 | id("quiet-fabric-loom") 4 | } 5 | 6 | val platformExt = extensions.getByType() 7 | platformExt.productionJar = tasks.remapJar.flatMap { it.archiveFile } 8 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/squaremap.platform.mdg.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("squaremap.platform.mod") 3 | id("net.neoforged.moddev") 4 | } 5 | 6 | val prod = tasks.register("productionJar") { 7 | destinationDirectory = layout.buildDirectory.dir("libs") 8 | from(zipTree(tasks.shadowJar.flatMap { it.archiveFile })) 9 | // for some reason the inner jars were getting unpacked when from'ing directly to shadowJar...? 10 | from(tasks.jarJar) 11 | } 12 | 13 | val platformExt = extensions.getByType() 14 | platformExt.productionJar = prod.flatMap { it.archiveFile } 15 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/squaremap.platform.mod.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("squaremap.platform") 3 | } 4 | 5 | val shade: Configuration by configurations.creating 6 | configurations.implementation { 7 | extendsFrom(shade) 8 | } 9 | val shadeFiltered: Configuration by configurations.creating { 10 | extendsFrom(shade) 11 | 12 | exclude("org.checkerframework") 13 | } 14 | 15 | tasks { 16 | shadowJar { 17 | configurations = listOf(shadeFiltered) 18 | listOf( 19 | "jakarta.inject", 20 | "com.google.inject", 21 | "org.aopalliance", 22 | ).forEach(::reloc) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/squaremap.publishing.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("squaremap.base-conventions") 3 | id("net.kyori.indra.publishing") 4 | } 5 | 6 | signing { 7 | val signingKey: String? by project 8 | val signingPassword: String? by project 9 | useInMemoryPgpKeys(signingKey, signingPassword) 10 | } 11 | 12 | indra { 13 | github("jpenilla", "squaremap") 14 | mitLicense() 15 | 16 | configurePublications { 17 | pom { 18 | developers { 19 | developer { 20 | id = "jmp" 21 | name = "Jason Penilla" 22 | timezone = "America/Phoenix" 23 | } 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("squaremap.parent") 3 | } 4 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/LayerRegistry.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | import org.checkerframework.framework.qual.DefaultQualifier; 7 | import xyz.jpenilla.squaremap.api.Key; 8 | import xyz.jpenilla.squaremap.api.LayerProvider; 9 | import xyz.jpenilla.squaremap.api.Pair; 10 | import xyz.jpenilla.squaremap.api.Registry; 11 | 12 | @DefaultQualifier(NonNull.class) 13 | public final class LayerRegistry implements Registry { 14 | private final Map layerProviders = new ConcurrentHashMap<>(); 15 | 16 | @Override 17 | public void register(final Key key, final LayerProvider value) { 18 | if (this.hasEntry(key)) { 19 | throw layerAlreadyRegistered(key); 20 | } 21 | this.layerProviders.put(key, value); 22 | } 23 | 24 | @Override 25 | public void unregister(final Key key) { 26 | final LayerProvider removed = this.layerProviders.remove(key); 27 | if (removed == null) { 28 | throw noLayerRegistered(key); 29 | } 30 | } 31 | 32 | @Override 33 | public boolean hasEntry(final Key key) { 34 | return this.layerProviders.containsKey(key); 35 | } 36 | 37 | @Override 38 | public LayerProvider get(final Key key) { 39 | final LayerProvider provider = this.layerProviders.get(key); 40 | if (provider == null) { 41 | throw noLayerRegistered(key); 42 | } 43 | return provider; 44 | } 45 | 46 | @Override 47 | public Iterable> entries() { 48 | return this.layerProviders.entrySet().stream() 49 | .map(entry -> Pair.of(entry.getKey(), entry.getValue())) 50 | .toList(); 51 | } 52 | 53 | private static IllegalArgumentException noLayerRegistered(final Key key) { 54 | return new IllegalArgumentException(String.format("No LayerProvider registered for key '%s'", key.getKey())); 55 | } 56 | 57 | private static IllegalArgumentException layerAlreadyRegistered(final Key key) { 58 | throw new IllegalArgumentException(String.format("LayerProvider already registered for key '%s'", key.getKey())); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/Logging.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common; 2 | 3 | import java.util.function.Supplier; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | import xyz.jpenilla.squaremap.common.config.Config; 9 | 10 | @DefaultQualifier(NonNull.class) 11 | public final class Logging { 12 | private static final Logger LOGGER = LogManager.getLogger("squaremap"); 13 | 14 | private Logging() { 15 | } 16 | 17 | public static Logger logger() { 18 | return LOGGER; 19 | } 20 | 21 | public static void debug(final Supplier msg) { 22 | if (Config.DEBUG_MODE) { 23 | logger().info("[DEBUG] " + msg.get()); 24 | } 25 | } 26 | 27 | public static void error(final String message, final Throwable thr, final Object... replacements) { 28 | logger().error(replace(message, replacements), thr); 29 | } 30 | 31 | public static void info(final String message, final Object... replacements) { 32 | logger().info(replace(message, replacements)); 33 | } 34 | 35 | public static String replace(String message, final Object... replacements) { 36 | if (replacements.length == 0) { 37 | return message; 38 | } 39 | if ((replacements.length & 1) != 0) { 40 | throw new IllegalArgumentException("Invalid length for replacements array (expected to be divisible by 2)"); 41 | } 42 | for (int i = 0; i < replacements.length; i += 2) { 43 | message = message.replace('<' + replacements[i].toString() + '>', replacements[i + 1].toString()); 44 | } 45 | return message; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/ServerAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common; 2 | 3 | import java.util.Collection; 4 | import java.util.UUID; 5 | import net.minecraft.server.level.ServerLevel; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | import org.checkerframework.checker.nullness.qual.Nullable; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | import xyz.jpenilla.squaremap.api.WorldIdentifier; 11 | 12 | @DefaultQualifier(NonNull.class) 13 | public interface ServerAccess { 14 | Collection levels(); 15 | 16 | @Nullable ServerLevel level(WorldIdentifier identifier); 17 | 18 | @Nullable ServerPlayer player(UUID uuid); 19 | 20 | int maxPlayers(); 21 | 22 | /** 23 | * Blocks the server from tick sleeping to ensure chunks unload during renders. 24 | */ 25 | void blockSleep(); 26 | 27 | /** 28 | * Allows the server to tick sleep after a render is done. 29 | */ 30 | void allowSleep(); 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/SquaremapPlatform.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.framework.qual.DefaultQualifier; 5 | 6 | @DefaultQualifier(NonNull.class) 7 | public interface SquaremapPlatform { 8 | void startCallback(); 9 | 10 | void stopCallback(); 11 | 12 | String version(); 13 | 14 | default boolean hasMod(final String id) { 15 | return false; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/WorldManager.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common; 2 | 3 | import java.util.Collection; 4 | import java.util.Optional; 5 | import net.minecraft.server.level.ServerLevel; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | import xyz.jpenilla.squaremap.api.WorldIdentifier; 9 | import xyz.jpenilla.squaremap.common.data.MapWorldInternal; 10 | 11 | @DefaultQualifier(NonNull.class) 12 | public interface WorldManager { 13 | Collection worlds(); 14 | 15 | Optional getWorldIfEnabled(WorldIdentifier worldIdentifier); 16 | 17 | Optional getWorldIfEnabled(ServerLevel level); 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/command/BrigadierSetup.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.command; 2 | 3 | import io.leangen.geantyref.TypeToken; 4 | import net.minecraft.commands.arguments.DimensionArgument; 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | import org.checkerframework.framework.qual.DefaultQualifier; 7 | import org.incendo.cloud.brigadier.BrigadierManagerHolder; 8 | import org.incendo.cloud.brigadier.CloudBrigadierManager; 9 | import xyz.jpenilla.squaremap.common.command.argument.parser.LevelParser; 10 | import xyz.jpenilla.squaremap.common.command.argument.parser.MapWorldParser; 11 | 12 | @DefaultQualifier(NonNull.class) 13 | public final class BrigadierSetup { 14 | private BrigadierSetup() { 15 | } 16 | 17 | public static void setup(final BrigadierManagerHolder manager) { 18 | final CloudBrigadierManager brigManager = manager.brigadierManager(); 19 | 20 | brigManager.registerMapping( 21 | new TypeToken>() {}, 22 | builder -> builder.toConstant(DimensionArgument.dimension()).cloudSuggestions() 23 | ); 24 | brigManager.registerMapping( 25 | new TypeToken>() {}, 26 | builder -> builder.toConstant(DimensionArgument.dimension()).cloudSuggestions() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/command/Commander.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.command; 2 | 3 | import net.kyori.adventure.audience.Audience; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | 7 | @DefaultQualifier(NonNull.class) 8 | public interface Commander extends Audience { 9 | boolean hasPermission(String permission); 10 | 11 | Object commanderId(); 12 | } 13 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/command/PlatformCommands.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.command; 2 | 3 | import java.util.Optional; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | import org.incendo.cloud.CommandManager; 9 | import org.incendo.cloud.context.CommandContext; 10 | import org.incendo.cloud.parser.ParserDescriptor; 11 | import xyz.jpenilla.squaremap.common.command.exception.CommandCompleted; 12 | 13 | @DefaultQualifier(NonNull.class) 14 | public interface PlatformCommands { 15 | /** 16 | * Create the platform command manager. 17 | * 18 | * @return command manager 19 | */ 20 | CommandManager createCommandManager(); 21 | 22 | /** 23 | * Create a column pos parser. 24 | * 25 | * @return built argument 26 | */ 27 | ParserDescriptor columnPosParser(); 28 | 29 | /** 30 | * Create a single player selector parser. 31 | * 32 | * @return built argument 33 | */ 34 | ParserDescriptor singlePlayerSelectorParser(); 35 | 36 | /** 37 | * Extract the result from an argument created with {@link #singlePlayerSelectorParser()}. 38 | * 39 | * @param name argument name 40 | * @param ctx command context 41 | * @return result 42 | * @throws CommandCompleted when no result is found 43 | */ 44 | Optional extractPlayer(String name, CommandContext ctx); 45 | } 46 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/command/PlayerCommander.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.command; 2 | 3 | import net.minecraft.server.level.ServerPlayer; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | 7 | @DefaultQualifier(NonNull.class) 8 | public interface PlayerCommander extends Commander { 9 | ServerPlayer player(); 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/command/SquaremapCommand.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.command; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.framework.qual.DefaultQualifier; 5 | 6 | @DefaultQualifier(NonNull.class) 7 | public abstract class SquaremapCommand { 8 | protected final Commands commands; 9 | 10 | protected SquaremapCommand(final Commands commands) { 11 | this.commands = commands; 12 | } 13 | 14 | public abstract void register(); 15 | } 16 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/command/commands/CancelRenderCommand.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.command.commands; 2 | 3 | import com.google.inject.Inject; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | import org.incendo.cloud.context.CommandContext; 7 | import xyz.jpenilla.squaremap.common.command.Commander; 8 | import xyz.jpenilla.squaremap.common.command.Commands; 9 | import xyz.jpenilla.squaremap.common.command.SquaremapCommand; 10 | import xyz.jpenilla.squaremap.common.config.Messages; 11 | import xyz.jpenilla.squaremap.common.data.MapWorldInternal; 12 | import xyz.jpenilla.squaremap.common.util.CommandUtil; 13 | import xyz.jpenilla.squaremap.common.util.Components; 14 | 15 | import static org.incendo.cloud.minecraft.extras.RichDescription.richDescription; 16 | import static xyz.jpenilla.squaremap.common.command.argument.parser.MapWorldParser.mapWorldParser; 17 | 18 | @DefaultQualifier(NonNull.class) 19 | public final class CancelRenderCommand extends SquaremapCommand { 20 | @Inject 21 | private CancelRenderCommand(final Commands commands) { 22 | super(commands); 23 | } 24 | 25 | @Override 26 | public void register() { 27 | this.commands.registerSubcommand(builder -> 28 | builder.literal("cancelrender") 29 | .optional("world", mapWorldParser(), richDescription(Messages.OPTIONAL_WORLD_ARGUMENT_DESCRIPTION)) 30 | .commandDescription(richDescription(Messages.CANCEL_RENDER_COMMAND_DESCRIPTION)) 31 | .permission("squaremap.command.cancelrender") 32 | .handler(this::executeCancelRender)); 33 | } 34 | 35 | private void executeCancelRender(final CommandContext context) { 36 | final Commander sender = context.sender(); 37 | final MapWorldInternal world = CommandUtil.resolveWorld(context); 38 | if (!world.renderManager().isRendering()) { 39 | sender.sendMessage(Messages.RENDER_NOT_IN_PROGRESS.withPlaceholders(Components.worldPlaceholder(world))); 40 | return; 41 | } 42 | 43 | sender.sendMessage(Messages.CANCELLED_RENDER.withPlaceholders(Components.worldPlaceholder(world))); 44 | world.renderManager().cancelRender(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/command/commands/PauseRenderCommand.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.command.commands; 2 | 3 | import com.google.inject.Inject; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | import org.incendo.cloud.context.CommandContext; 7 | import xyz.jpenilla.squaremap.common.command.Commander; 8 | import xyz.jpenilla.squaremap.common.command.Commands; 9 | import xyz.jpenilla.squaremap.common.command.SquaremapCommand; 10 | import xyz.jpenilla.squaremap.common.config.Messages; 11 | import xyz.jpenilla.squaremap.common.data.MapWorldInternal; 12 | import xyz.jpenilla.squaremap.common.util.CommandUtil; 13 | import xyz.jpenilla.squaremap.common.util.Components; 14 | 15 | import static org.incendo.cloud.minecraft.extras.RichDescription.richDescription; 16 | import static xyz.jpenilla.squaremap.common.command.argument.parser.MapWorldParser.mapWorldParser; 17 | 18 | @DefaultQualifier(NonNull.class) 19 | public final class PauseRenderCommand extends SquaremapCommand { 20 | @Inject 21 | private PauseRenderCommand(final Commands commands) { 22 | super(commands); 23 | } 24 | 25 | @Override 26 | public void register() { 27 | this.commands.registerSubcommand(builder -> 28 | builder.literal("pauserender") 29 | .optional("world", mapWorldParser(), richDescription(Messages.OPTIONAL_WORLD_ARGUMENT_DESCRIPTION)) 30 | .commandDescription(richDescription(Messages.PAUSE_RENDER_COMMAND_DESCRIPTION)) 31 | .permission("squaremap.command.pauserender") 32 | .handler(this::executePauseRender)); 33 | } 34 | 35 | private void executePauseRender(final CommandContext context) { 36 | final Commander sender = context.sender(); 37 | final MapWorldInternal world = CommandUtil.resolveWorld(context); 38 | 39 | world.renderManager().pauseRenders(!world.renderManager().rendersPaused()); 40 | 41 | if (world.renderManager().rendersPaused()) { 42 | sender.sendMessage(Messages.PAUSED_RENDER.withPlaceholders(Components.worldPlaceholder(world))); 43 | } else { 44 | sender.sendMessage(Messages.UNPAUSED_RENDER.withPlaceholders(Components.worldPlaceholder(world))); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/command/commands/ReloadCommand.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.command.commands; 2 | 3 | import com.google.inject.Inject; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | import org.incendo.cloud.context.CommandContext; 7 | import xyz.jpenilla.squaremap.common.SquaremapCommon; 8 | import xyz.jpenilla.squaremap.common.command.Commander; 9 | import xyz.jpenilla.squaremap.common.command.Commands; 10 | import xyz.jpenilla.squaremap.common.command.SquaremapCommand; 11 | import xyz.jpenilla.squaremap.common.config.Messages; 12 | 13 | import static org.incendo.cloud.minecraft.extras.RichDescription.richDescription; 14 | 15 | @DefaultQualifier(NonNull.class) 16 | public final class ReloadCommand extends SquaremapCommand { 17 | private final SquaremapCommon common; 18 | 19 | @Inject 20 | private ReloadCommand( 21 | final Commands commands, 22 | final SquaremapCommon common 23 | ) { 24 | super(commands); 25 | this.common = common; 26 | } 27 | 28 | @Override 29 | public void register() { 30 | this.commands.registerSubcommand(builder -> 31 | builder.literal("reload") 32 | .commandDescription(richDescription(Messages.RELOAD_COMMAND_DESCRIPTION)) 33 | .permission("squaremap.command.reload") 34 | .handler(this::execute)); 35 | } 36 | 37 | public void execute(final CommandContext context) { 38 | this.common.reload(context.sender()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/command/exception/CommandCompleted.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.command.exception; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.ComponentLike; 5 | import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; 6 | import net.kyori.adventure.util.ComponentMessageThrowable; 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | import org.checkerframework.checker.nullness.qual.Nullable; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | 11 | @DefaultQualifier(NonNull.class) 12 | public final class CommandCompleted extends RuntimeException implements ComponentMessageThrowable { 13 | private static final long serialVersionUID = -8318440562349647391L; 14 | 15 | private final @Nullable Component message; 16 | 17 | private CommandCompleted(final @Nullable Component message) { 18 | this.message = message; 19 | } 20 | 21 | public static CommandCompleted withoutMessage() { 22 | return new CommandCompleted(null); 23 | } 24 | 25 | public static CommandCompleted withMessage(final ComponentLike message) { 26 | return new CommandCompleted(message.asComponent()); 27 | } 28 | 29 | @Override 30 | public @Nullable Component componentMessage() { 31 | return this.message; 32 | } 33 | 34 | @Override 35 | public String getMessage() { 36 | return PlainTextComponentSerializer.plainText().serializeOr(this.message, "No message."); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/config/ConfigUpgrader.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.config; 2 | 3 | import java.util.function.Consumer; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | import org.spongepowered.configurate.ConfigurateException; 7 | import org.spongepowered.configurate.ConfigurationNode; 8 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 9 | import xyz.jpenilla.squaremap.common.util.Util; 10 | 11 | @DefaultQualifier(NonNull.class) 12 | public final class ConfigUpgrader { 13 | private final ConfigurationTransformation.Versioned upgrader; 14 | 15 | public ConfigUpgrader(final Consumer op) { 16 | final ConfigurationTransformation.VersionedBuilder builder = ConfigurationTransformation.versionedBuilder(); 17 | op.accept(builder); 18 | this.upgrader = builder.build(); 19 | } 20 | 21 | public UpgradeResult upgrade(final N node) { 22 | final int original = this.upgrader.version(node); 23 | try { 24 | this.upgrader.apply(node); 25 | } catch (final ConfigurateException e) { 26 | Util.rethrow(e); 27 | } 28 | final int newVer = this.upgrader.version(node); 29 | return new UpgradeResult<>(original, newVer, node, original != newVer); 30 | } 31 | 32 | public record UpgradeResult( 33 | int originalVersion, 34 | int newVersion, 35 | N node, 36 | boolean didUpgrade 37 | ) { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/data/ChunkCoordinate.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.data; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.framework.qual.DefaultQualifier; 5 | import xyz.jpenilla.squaremap.common.util.Numbers; 6 | 7 | @DefaultQualifier(NonNull.class) 8 | public record ChunkCoordinate(int x, int z) { 9 | 10 | public int getRegionX() { 11 | return Numbers.chunkToRegion(this.x); 12 | } 13 | 14 | public int getRegionZ() { 15 | return Numbers.chunkToRegion(this.z); 16 | } 17 | 18 | public int getBlockX() { 19 | return Numbers.chunkToBlock(this.x); 20 | } 21 | 22 | public int getBlockZ() { 23 | return Numbers.chunkToBlock(this.z); 24 | } 25 | 26 | public RegionCoordinate regionCoordinate() { 27 | return new RegionCoordinate(this.getRegionX(), this.getRegionZ()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/data/RegionCoordinate.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.data; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.framework.qual.DefaultQualifier; 5 | import xyz.jpenilla.squaremap.common.util.Numbers; 6 | 7 | @DefaultQualifier(NonNull.class) 8 | public record RegionCoordinate(int x, int z) { 9 | 10 | public int getChunkX() { 11 | return Numbers.regionToChunk(this.x); 12 | } 13 | 14 | public int getChunkZ() { 15 | return Numbers.regionToChunk(this.z); 16 | } 17 | 18 | public int getBlockX() { 19 | return Numbers.regionToBlock(this.x); 20 | } 21 | 22 | public int getBlockZ() { 23 | return Numbers.regionToBlock(this.z); 24 | } 25 | 26 | public ChunkCoordinate chunkCoordinate() { 27 | return new ChunkCoordinate(this.getChunkX(), this.getChunkZ()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/inject/annotation/DataDirectory.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.inject.annotation; 2 | 3 | import com.google.inject.BindingAnnotation; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @BindingAnnotation 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target({ElementType.PARAMETER, ElementType.FIELD}) 12 | public @interface DataDirectory { 13 | } 14 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/inject/module/ApiModule.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.inject.module; 2 | 3 | import com.google.inject.AbstractModule; 4 | import xyz.jpenilla.squaremap.api.Squaremap; 5 | import xyz.jpenilla.squaremap.common.SquaremapApiProvider; 6 | 7 | public final class ApiModule extends AbstractModule { 8 | @Override 9 | protected void configure() { 10 | this.bind(Squaremap.class) 11 | .to(SquaremapApiProvider.class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/inject/module/VanillaChunkSnapshotProviderFactoryModule.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.inject.module; 2 | 3 | import com.google.inject.AbstractModule; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | import xyz.jpenilla.squaremap.common.util.chunksnapshot.ChunkSnapshotProviderFactory; 7 | import xyz.jpenilla.squaremap.common.util.chunksnapshot.VanillaChunkSnapshotProviderFactory; 8 | 9 | @DefaultQualifier(NonNull.class) 10 | public final class VanillaChunkSnapshotProviderFactoryModule extends AbstractModule { 11 | @Override 12 | protected void configure() { 13 | this.bind(ChunkSnapshotProviderFactory.class) 14 | .to(VanillaChunkSnapshotProviderFactory.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/inject/module/VanillaRegionFileDirectoryResolverModule.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.inject.module; 2 | 3 | import com.google.inject.AbstractModule; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | import xyz.jpenilla.squaremap.common.util.RegionFileDirectoryResolver; 7 | 8 | @DefaultQualifier(NonNull.class) 9 | public final class VanillaRegionFileDirectoryResolverModule extends AbstractModule { 10 | @Override 11 | protected void configure() { 12 | this.bind(RegionFileDirectoryResolver.class) 13 | .to(RegionFileDirectoryResolver.Vanilla.class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/network/Constants.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.network; 2 | 3 | public final class Constants { 4 | private Constants() { 5 | } 6 | 7 | public static final int SERVER_DATA = 0; 8 | public static final int MAP_DATA = 1; 9 | public static final int UPDATE_WORLD = 2; 10 | 11 | public static final int PROTOCOL = 3; 12 | 13 | public static final int RESPONSE_SUCCESS = 200; 14 | 15 | public static final int ERROR_NO_SUCH_MAP = -1; 16 | public static final int ERROR_NO_SUCH_WORLD = -2; 17 | public static final int ERROR_NOT_VANILLA_MAP = -3; 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/task/TaskFactory.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.task; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.framework.qual.DefaultQualifier; 5 | import xyz.jpenilla.squaremap.common.data.MapWorldInternal; 6 | 7 | @DefaultQualifier(NonNull.class) 8 | public interface TaskFactory { 9 | UpdateMarkers createUpdateMarkers(MapWorldInternal mapWorld); 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/task/render/RenderFactory.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.task.render; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | import xyz.jpenilla.squaremap.common.data.MapWorldInternal; 7 | 8 | @DefaultQualifier(NonNull.class) 9 | public interface RenderFactory { 10 | FullRender createFullRender(MapWorldInternal mapWorld, int wait); 11 | 12 | FullRender createFullRender(MapWorldInternal mapWorld); 13 | 14 | BackgroundRender createBackgroundRender(MapWorldInternal mapWorld); 15 | 16 | RadiusRender createRadiusRender(MapWorldInternal mapWorld, BlockPos center, int radius); 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/CheckedConsumer.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | @FunctionalInterface 4 | public interface CheckedConsumer { 5 | void accept(T t) throws X; 6 | } 7 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/ChunkHashMapKey.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import it.unimi.dsi.fastutil.HashCommon; 4 | import net.minecraft.world.level.ChunkPos; 5 | 6 | public final class ChunkHashMapKey implements Comparable { 7 | public final long key; 8 | 9 | public ChunkHashMapKey(final long key) { 10 | this.key = key; 11 | } 12 | 13 | public ChunkHashMapKey(final ChunkPos pos) { 14 | this(pos.toLong()); 15 | } 16 | 17 | @Override 18 | public int hashCode() { 19 | return (int) HashCommon.mix(this.key); 20 | } 21 | 22 | @Override 23 | public boolean equals(final Object obj) { 24 | if (this == obj) { 25 | return true; 26 | } 27 | 28 | if (!(obj instanceof final ChunkHashMapKey other)) { 29 | return false; 30 | } 31 | 32 | return this.key == other.key; 33 | } 34 | 35 | @Override 36 | public int compareTo(final ChunkHashMapKey other) { 37 | return Long.compare(this.key, other.key); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return new ChunkPos(this.key).toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/ChunkMapAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; 4 | import java.util.Optional; 5 | import java.util.concurrent.CompletableFuture; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.server.level.ChunkHolder; 8 | import net.minecraft.world.level.ChunkPos; 9 | 10 | public interface ChunkMapAccess { 11 | ChunkHolder squaremap$getVisibleChunkIfPresent(long pos); 12 | 13 | CompletableFuture> squaremap$readChunk(ChunkPos pos); 14 | 15 | Long2ObjectLinkedOpenHashMap squaremap$pendingUnloads(); 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/ColorBlender.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.framework.qual.DefaultQualifier; 5 | 6 | @DefaultQualifier(NonNull.class) 7 | public final class ColorBlender { 8 | private int a = 0; 9 | private int r = 0; 10 | private int g = 0; 11 | private int b = 0; 12 | private int count = 0; 13 | 14 | public void addColor(final int color) { 15 | this.a += (color >> 24) & 0xFF; 16 | this.r += (color >> 16) & 0xFF; 17 | this.g += (color >> 8) & 0xFF; 18 | this.b += color & 0xFF; 19 | this.count++; 20 | } 21 | 22 | public int result() { 23 | if (this.count == 0) { 24 | throw new IllegalStateException("Cannot blend 0 colors!"); 25 | } 26 | final int a = this.a / this.count; 27 | final int r = this.r / this.count; 28 | final int g = this.g / this.count; 29 | final int b = this.b / this.count; 30 | return a << 24 | r << 16 | g << 8 | b; 31 | } 32 | 33 | public void reset() { 34 | this.a = 0; 35 | this.r = 0; 36 | this.g = 0; 37 | this.b = 0; 38 | this.count = 0; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/CommandUtil.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import java.util.Optional; 4 | import net.kyori.adventure.text.format.NamedTextColor; 5 | import net.minecraft.server.level.ServerLevel; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.checker.nullness.qual.Nullable; 8 | import org.checkerframework.framework.qual.DefaultQualifier; 9 | import org.incendo.cloud.context.CommandContext; 10 | import xyz.jpenilla.squaremap.common.command.Commander; 11 | import xyz.jpenilla.squaremap.common.command.Commands; 12 | import xyz.jpenilla.squaremap.common.command.PlayerCommander; 13 | import xyz.jpenilla.squaremap.common.command.exception.CommandCompleted; 14 | import xyz.jpenilla.squaremap.common.config.Messages; 15 | import xyz.jpenilla.squaremap.common.data.MapWorldInternal; 16 | 17 | @DefaultQualifier(NonNull.class) 18 | public final class CommandUtil { 19 | private CommandUtil() { 20 | } 21 | 22 | public static MapWorldInternal resolveWorld(final CommandContext context) { 23 | final Commander sender = context.sender(); 24 | final @Nullable MapWorldInternal world = context.getOrDefault("world", null); 25 | if (world != null) { 26 | return world; 27 | } 28 | if (sender instanceof final PlayerCommander player) { 29 | final ServerLevel level = (ServerLevel) player.player().level(); 30 | final Optional mapWorld = context.get(Commands.WORLD_MANAGER).getWorldIfEnabled(level); 31 | if (mapWorld.isPresent()) { 32 | return mapWorld.get(); 33 | } 34 | throw CommandCompleted.withMessage( 35 | Messages.MAP_NOT_ENABLED_FOR_WORLD.withPlaceholders(Components.worldPlaceholder(level)) 36 | .color(NamedTextColor.RED) 37 | ); 38 | } else { 39 | throw CommandCompleted.withMessage(Messages.CONSOLE_MUST_SPECIFY_WORLD); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/ConcurrentFIFOLoadingCache.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import java.util.Map; 4 | import java.util.Queue; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.concurrent.ConcurrentLinkedQueue; 7 | import java.util.function.Function; 8 | import org.checkerframework.checker.nullness.qual.Nullable; 9 | 10 | public final class ConcurrentFIFOLoadingCache { 11 | private final int maximumCapacity; 12 | private final int evictUntil; 13 | private final Function loader; 14 | // lookup map 15 | private final Map map; 16 | // FIFO eviction queue 17 | private final Queue queue; 18 | 19 | public ConcurrentFIFOLoadingCache( 20 | final int maximumCapacity, 21 | final int evictUntil, 22 | final Function loader 23 | ) { 24 | if (maximumCapacity <= evictUntil) { 25 | throw new IllegalArgumentException("maximumCapacity must be larger than evictUntil (%s <= %s)".formatted(maximumCapacity, evictUntil)); 26 | } 27 | this.maximumCapacity = maximumCapacity; 28 | this.evictUntil = evictUntil; 29 | this.loader = loader; 30 | this.map = new ConcurrentHashMap<>(maximumCapacity + Runtime.getRuntime().availableProcessors()); 31 | this.queue = new ConcurrentLinkedQueue<>(); 32 | } 33 | 34 | public V get(final K key) { 35 | final @Nullable V cached = this.map.get(key); 36 | if (cached != null) { 37 | return cached; 38 | } 39 | return this.loadValue(key); 40 | } 41 | 42 | private V loadValue(final K key) { 43 | final V load = this.loader.apply(key); 44 | final @Nullable V prevValue = this.map.putIfAbsent(key, load); 45 | if (prevValue != null) { 46 | // lost race to load entry 47 | return prevValue; 48 | } 49 | this.queue.offer(key); 50 | this.maybeEvictEntries(); 51 | return load; 52 | } 53 | 54 | private void maybeEvictEntries() { 55 | if (this.map.size() > this.maximumCapacity) { 56 | while (this.map.size() > this.evictUntil) { 57 | final @Nullable K remove = this.queue.poll(); 58 | if (remove == null) { 59 | break; 60 | } 61 | this.map.remove(remove); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/EntityScheduler.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import com.google.inject.Inject; 4 | import net.minecraft.world.entity.Entity; 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | import org.checkerframework.framework.qual.DefaultQualifier; 7 | import xyz.jpenilla.squaremap.common.command.Commander; 8 | 9 | @DefaultQualifier(NonNull.class) 10 | public interface EntityScheduler { 11 | void scheduleFor(Entity entity, Runnable task); 12 | 13 | void scheduleFor(Commander commander, Runnable task); 14 | 15 | final class NoneEntityScheduler implements EntityScheduler { 16 | @Inject 17 | private NoneEntityScheduler() { 18 | } 19 | 20 | @Override 21 | public void scheduleFor(final Entity entity, final Runnable task) { 22 | task.run(); 23 | } 24 | 25 | @Override 26 | public void scheduleFor(final Commander commander, final Runnable task) { 27 | task.run(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/ExceptionLoggingScheduledThreadPoolExecutor.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import java.util.concurrent.Callable; 4 | import java.util.concurrent.ScheduledFuture; 5 | import java.util.concurrent.ScheduledThreadPoolExecutor; 6 | import java.util.concurrent.ThreadFactory; 7 | import java.util.concurrent.TimeUnit; 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | import xyz.jpenilla.squaremap.common.Logging; 11 | 12 | @DefaultQualifier(NonNull.class) 13 | public final class ExceptionLoggingScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor { 14 | public ExceptionLoggingScheduledThreadPoolExecutor(final int corePoolSize, final ThreadFactory threadFactory) { 15 | super(corePoolSize, threadFactory); 16 | } 17 | 18 | @Override 19 | public ScheduledFuture schedule(final Runnable command, final long delay, final TimeUnit unit) { 20 | return super.schedule(new ExceptionLoggingRunnable(command), delay, unit); 21 | } 22 | 23 | @Override 24 | public ScheduledFuture schedule(final Callable callable, final long delay, final TimeUnit unit) { 25 | throw new UnsupportedOperationException(); 26 | } 27 | 28 | @Override 29 | public ScheduledFuture scheduleAtFixedRate(final Runnable command, final long initialDelay, final long period, final TimeUnit unit) { 30 | return super.scheduleAtFixedRate(new ExceptionLoggingRunnable(command), initialDelay, period, unit); 31 | } 32 | 33 | @Override 34 | public ScheduledFuture scheduleWithFixedDelay(final Runnable command, final long initialDelay, final long delay, final TimeUnit unit) { 35 | return super.scheduleWithFixedDelay(new ExceptionLoggingRunnable(command), initialDelay, delay, unit); 36 | } 37 | 38 | private record ExceptionLoggingRunnable(Runnable wrapped) implements Runnable { 39 | @Override 40 | public void run() { 41 | try { 42 | this.wrapped.run(); 43 | } catch (final Throwable thr) { 44 | Logging.logger().error("Error executing task '{}'", this.wrapped, thr); 45 | Util.rethrow(thr); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/HtmlStripperImpl.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import java.util.Objects; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | import org.owasp.html.HtmlPolicyBuilder; 7 | import org.owasp.html.PolicyFactory; 8 | import xyz.jpenilla.squaremap.api.HtmlStripper; 9 | 10 | @DefaultQualifier(NonNull.class) 11 | public final class HtmlStripperImpl implements HtmlStripper { 12 | private static final PolicyFactory SANITIZER = new HtmlPolicyBuilder().toFactory(); 13 | 14 | private HtmlStripperImpl() { 15 | } 16 | 17 | @Override 18 | public String stripHtml(final String string) { 19 | Objects.requireNonNull(string, "Parameter 'string' must not be null"); 20 | return SANITIZER.sanitize(string); 21 | } 22 | 23 | public static final class Provider implements HtmlStripper.Provider { 24 | private static final HtmlStripper INSTANCE = new HtmlStripperImpl(); 25 | 26 | @Override 27 | public HtmlStripper instance() { 28 | return INSTANCE; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | import org.checkerframework.framework.qual.DefaultQualifier; 7 | 8 | /** 9 | * Same as DefaultThreadFactory, with a custom name prefix. 10 | */ 11 | @DefaultQualifier(NonNull.class) 12 | public final class NamedThreadFactory implements ThreadFactory { 13 | private final AtomicInteger threadCount = new AtomicInteger(1); 14 | private final String namePrefix; 15 | 16 | public NamedThreadFactory(final String poolName) { 17 | this.namePrefix = poolName + "-"; 18 | } 19 | 20 | public Thread newThread(final Runnable runnable) { 21 | final Thread thread = new Thread( 22 | null, 23 | runnable, 24 | this.namePrefix + this.threadCount.getAndIncrement(), 25 | 0 26 | ); 27 | if (thread.isDaemon()) { 28 | thread.setDaemon(false); 29 | } 30 | if (thread.getPriority() != Thread.NORM_PRIORITY) { 31 | thread.setPriority(Thread.NORM_PRIORITY); 32 | } 33 | return thread; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/Numbers.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | public final class Numbers { 4 | private Numbers() { 5 | } 6 | 7 | public static int regionToBlock(int n) { 8 | return n << 9; 9 | } 10 | 11 | public static int blockToRegion(int n) { 12 | return n >> 9; 13 | } 14 | 15 | public static int regionToChunk(int n) { 16 | return n << 5; 17 | } 18 | 19 | public static int chunkToRegion(int n) { 20 | return n >> 5; 21 | } 22 | 23 | public static int chunkToBlock(int n) { 24 | return n << 4; 25 | } 26 | 27 | public static int blockToChunk(int n) { 28 | return n >> 4; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/RegionFileDirectoryResolver.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Singleton; 5 | import java.nio.file.Path; 6 | import net.minecraft.server.level.ServerLevel; 7 | import net.minecraft.world.level.dimension.DimensionType; 8 | import net.minecraft.world.level.storage.LevelResource; 9 | import org.checkerframework.checker.nullness.qual.NonNull; 10 | import org.checkerframework.framework.qual.DefaultQualifier; 11 | 12 | @DefaultQualifier(NonNull.class) 13 | public interface RegionFileDirectoryResolver { 14 | Path resolveRegionFileDirectory(ServerLevel level); 15 | 16 | @Singleton 17 | class Vanilla implements RegionFileDirectoryResolver { 18 | @Inject 19 | private Vanilla() { 20 | } 21 | 22 | @Override 23 | public Path resolveRegionFileDirectory(final ServerLevel level) { 24 | final Path worldPath = level.getServer().getWorldPath(LevelResource.ROOT); 25 | return DimensionType.getStorageFolder(level.dimension(), worldPath).resolve("region"); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/SleepBlockingMinecraftServer.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | public interface SleepBlockingMinecraftServer { 4 | void squaremap$blockSleep(); 5 | 6 | void squaremap$allowSleep(); 7 | } 8 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/SquaremapJarAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util; 2 | 3 | import com.google.inject.Inject; 4 | import java.io.IOException; 5 | import java.net.URI; 6 | import java.net.URISyntaxException; 7 | import java.net.URL; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import org.checkerframework.checker.nullness.qual.NonNull; 11 | import org.checkerframework.framework.qual.DefaultQualifier; 12 | import xyz.jpenilla.squaremap.common.Logging; 13 | 14 | @DefaultQualifier(NonNull.class) 15 | public interface SquaremapJarAccess { 16 | void useJar(CheckedConsumer consumer) throws IOException, URISyntaxException; 17 | 18 | default void extract(final String inDir, final Path outDir, final boolean replaceExisting) { 19 | try { 20 | this.useJar(root -> FileUtil.specialCopyRecursively(root.resolve(inDir), outDir, replaceExisting)); 21 | } catch (final IOException | URISyntaxException ex) { 22 | Logging.logger().error("Failed to extract directory '{}' from jar to '{}'", inDir, outDir, ex); 23 | } 24 | } 25 | 26 | final class JarFromCodeSource implements SquaremapJarAccess { 27 | @Inject 28 | private JarFromCodeSource() { 29 | } 30 | 31 | @Override 32 | public void useJar(final CheckedConsumer consumer) throws IOException, URISyntaxException { 33 | FileUtil.openJar(jar(), fileSystem -> consumer.accept(fileSystem.getPath("/"))); 34 | } 35 | 36 | private static Path jar() throws URISyntaxException, IOException { 37 | URL sourceUrl = JarFromCodeSource.class.getProtectionDomain().getCodeSource().getLocation(); 38 | // Some class loaders give the full url to the class, some give the URL to its jar. 39 | // We want the containing jar, so we will unwrap jar-schema code sources. 40 | if (sourceUrl.getProtocol().equals("jar")) { 41 | final int exclamationIdx = sourceUrl.getPath().lastIndexOf('!'); 42 | if (exclamationIdx != -1) { 43 | sourceUrl = URI.create(sourceUrl.getPath().substring(0, exclamationIdx)).toURL(); 44 | } 45 | } 46 | return Paths.get(sourceUrl.toURI()); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/chunksnapshot/ChunkSnapshotProvider.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util.chunksnapshot; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.checker.nullness.qual.Nullable; 6 | import org.checkerframework.framework.qual.DefaultQualifier; 7 | 8 | @DefaultQualifier(NonNull.class) 9 | public interface ChunkSnapshotProvider { 10 | CompletableFuture<@Nullable ChunkSnapshot> asyncSnapshot(int x, int z); 11 | } 12 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/chunksnapshot/ChunkSnapshotProviderFactory.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util.chunksnapshot; 2 | 3 | import net.minecraft.server.level.ServerLevel; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | 7 | @DefaultQualifier(NonNull.class) 8 | public interface ChunkSnapshotProviderFactory { 9 | ChunkSnapshotProvider createChunkSnapshotProvider(ServerLevel level); 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/chunksnapshot/HeightmapSnapshot.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util.chunksnapshot; 2 | 3 | import net.minecraft.util.BitStorage; 4 | import net.minecraft.util.Mth; 5 | import net.minecraft.util.SimpleBitStorage; 6 | import net.minecraft.world.level.LevelHeightAccessor; 7 | import net.minecraft.world.level.chunk.ChunkAccess; 8 | import net.minecraft.world.level.levelgen.Heightmap; 9 | 10 | final class HeightmapSnapshot { 11 | private final BitStorage data; 12 | private final LevelHeightAccessor heightAccessor; 13 | 14 | HeightmapSnapshot( 15 | final ChunkAccess chunk, 16 | final LevelHeightAccessor heightAccessor, 17 | final Heightmap.Types heightmapType 18 | ) { 19 | this.data = new SimpleBitStorage( 20 | Mth.ceillog2(heightAccessor.getHeight() + 1), 21 | 256, 22 | chunk.getOrCreateHeightmapUnprimed(heightmapType).getRawData().clone() 23 | ); 24 | this.heightAccessor = heightAccessor; 25 | } 26 | 27 | public int getFirstAvailable(final int x, final int z) { 28 | return this.getFirstAvailable(getIndex(x, z)); 29 | } 30 | 31 | private int getFirstAvailable(final int index) { 32 | return this.data.get(index) + this.heightAccessor.getMinY(); 33 | } 34 | 35 | private static int getIndex(final int x, final int z) { 36 | return x + z * 16; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/util/chunksnapshot/VanillaChunkSnapshotProviderFactory.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.util.chunksnapshot; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Singleton; 5 | import net.minecraft.server.level.ServerLevel; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | import xyz.jpenilla.squaremap.common.SquaremapPlatform; 9 | 10 | @DefaultQualifier(NonNull.class) 11 | @Singleton 12 | public final class VanillaChunkSnapshotProviderFactory implements ChunkSnapshotProviderFactory { 13 | private final SquaremapPlatform platform; 14 | 15 | @Inject 16 | private VanillaChunkSnapshotProviderFactory(final SquaremapPlatform platform) { 17 | this.platform = platform; 18 | } 19 | 20 | @Override 21 | public ChunkSnapshotProvider createChunkSnapshotProvider(final ServerLevel level) { 22 | return new VanillaChunkSnapshotProvider(level, this.platform.hasMod("moonrise")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/visibilitylimit/VisibilityLimit.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.visibilitylimit; 2 | 3 | import java.util.List; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | 6 | /** 7 | * Decides what blocks, chunks and regions are visible on the map. A chunk outside this limit 8 | * will not be displayed regardless of whether it exists. 9 | */ 10 | public interface VisibilityLimit { 11 | 12 | /** 13 | * Gets the shapes used to decide the visibility limit. If one 14 | * of the shapes says that an area can be drawn, it will be drawn. 15 | * 16 | *

If the list is empty, the entire world is drawn.

17 | * 18 | *

This map is mutable, so you can add and remove visibility shapes.

19 | * 20 | * @return The shapes 21 | */ 22 | @NonNull List getShapes(); 23 | 24 | /** 25 | * Returns whether the given block is within any of the visibility limits (see {@link #getShapes()}). 26 | * If there are no visibility limits defined, this method always returns true. 27 | * 28 | * @param blockX X coordinate of the block 29 | * @param blockZ Z coordinate of the block 30 | * @return whether the location is visible on the map 31 | */ 32 | boolean isWithinLimit(int blockX, int blockZ); 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/java/xyz/jpenilla/squaremap/common/visibilitylimit/VisibilityShape.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.common.visibilitylimit; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.framework.qual.DefaultQualifier; 5 | import xyz.jpenilla.squaremap.api.MapWorld; 6 | 7 | @DefaultQualifier(NonNull.class) 8 | public interface VisibilityShape { 9 | VisibilityShape NULL = new VisibilityShape() { 10 | @Override 11 | public boolean shouldRenderChunk(final MapWorld world, final int chunkX, final int chunkZ) { 12 | return true; 13 | } 14 | 15 | @Override 16 | public boolean shouldRenderRegion(final MapWorld world, final int regionX, final int regionZ) { 17 | return true; 18 | } 19 | 20 | @Override 21 | public boolean shouldRenderColumn(final MapWorld world, final int blockX, final int blockZ) { 22 | return true; 23 | } 24 | 25 | @Override 26 | public int countChunksInRegion(final MapWorld world, final int regionX, final int regionZ) { 27 | return 32 * 32; 28 | } 29 | }; 30 | 31 | boolean shouldRenderChunk(MapWorld world, int chunkX, int chunkZ); 32 | 33 | boolean shouldRenderRegion(MapWorld world, int regionX, int regionZ); 34 | 35 | boolean shouldRenderColumn(MapWorld world, int blockX, int blockZ); 36 | 37 | int countChunksInRegion(MapWorld world, int regionX, int regionZ); 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/resources/META-INF/services/xyz.jpenilla.squaremap.api.HtmlComponentSerializer$Provider: -------------------------------------------------------------------------------- 1 | xyz.jpenilla.squaremap.common.util.HtmlComponentSerializerImpl$Provider 2 | -------------------------------------------------------------------------------- /common/src/main/resources/META-INF/services/xyz.jpenilla.squaremap.api.HtmlStripper$Provider: -------------------------------------------------------------------------------- 1 | xyz.jpenilla.squaremap.common.util.HtmlStripperImpl$Provider 2 | -------------------------------------------------------------------------------- /common/src/main/resources/squaremap-common-at.cfg: -------------------------------------------------------------------------------- 1 | # Any ATs declared here must be applied on all platforms (in one way or another) 2 | 3 | public net.minecraft.world.level.chunk.PalettedContainer get(I)Ljava/lang/Object; 4 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/FabricPlayerManager.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Singleton; 5 | import net.kyori.adventure.platform.modcommon.MinecraftServerAudiences; 6 | import net.kyori.adventure.text.Component; 7 | import net.minecraft.server.level.ServerPlayer; 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | import xyz.jpenilla.squaremap.common.AbstractPlayerManager; 11 | 12 | @DefaultQualifier(NonNull.class) 13 | @Singleton 14 | public final class FabricPlayerManager extends AbstractPlayerManager { 15 | @Inject 16 | private FabricPlayerManager(final FabricServerAccess serverAccess) { 17 | super(serverAccess); 18 | } 19 | 20 | @Override 21 | public Component displayName(final ServerPlayer player) { 22 | return MinecraftServerAudiences.of(player.getServer()).asAdventure(player.getDisplayName()); 23 | } 24 | 25 | @Override 26 | protected boolean persistentHidden(final ServerPlayer player) { 27 | return component(player).hidden(); 28 | } 29 | 30 | @Override 31 | protected void persistentHidden(final ServerPlayer player, final boolean value) { 32 | component(player).hidden(value); 33 | } 34 | 35 | private static SquaremapComponentInitializer.PlayerComponent component(final ServerPlayer player) { 36 | return player.getComponent(SquaremapComponentInitializer.SQUAREMAP_PLAYER_COMPONENT); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/FabricSquaremapJarAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric; 2 | 3 | import com.google.inject.Inject; 4 | import java.io.IOException; 5 | import java.nio.file.Path; 6 | import java.util.List; 7 | import net.fabricmc.loader.api.ModContainer; 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | import xyz.jpenilla.squaremap.common.util.CheckedConsumer; 11 | import xyz.jpenilla.squaremap.common.util.SquaremapJarAccess; 12 | 13 | @DefaultQualifier(NonNull.class) 14 | final class FabricSquaremapJarAccess implements SquaremapJarAccess { 15 | private final ModContainer modContainer; 16 | 17 | @Inject 18 | private FabricSquaremapJarAccess(final ModContainer modContainer) { 19 | this.modContainer = modContainer; 20 | } 21 | 22 | @Override 23 | public void useJar(final CheckedConsumer consumer) throws IOException { 24 | final List roots = this.modContainer.getRootPaths(); 25 | if (roots.size() != 1) { 26 | throw new IllegalStateException("Expected one root, got " + roots.size() + "!"); 27 | } 28 | consumer.accept(roots.get(0)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/SquaremapComponentInitializer.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric; 2 | 3 | import net.minecraft.core.HolderLookup; 4 | import net.minecraft.nbt.CompoundTag; 5 | import net.minecraft.resources.ResourceLocation; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | import org.ladysnake.cca.api.v3.component.ComponentKey; 9 | import org.ladysnake.cca.api.v3.component.ComponentRegistryV3; 10 | import org.ladysnake.cca.api.v3.component.ComponentV3; 11 | import org.ladysnake.cca.api.v3.entity.EntityComponentFactoryRegistry; 12 | import org.ladysnake.cca.api.v3.entity.EntityComponentInitializer; 13 | import org.ladysnake.cca.api.v3.entity.RespawnCopyStrategy; 14 | 15 | @DefaultQualifier(NonNull.class) 16 | public class SquaremapComponentInitializer implements EntityComponentInitializer { 17 | public static final ComponentKey SQUAREMAP_PLAYER_COMPONENT = 18 | ComponentRegistryV3.INSTANCE.getOrCreate(ResourceLocation.parse("squaremap:player_component"), PlayerComponent.class); 19 | 20 | @Override 21 | public void registerEntityComponentFactories(final EntityComponentFactoryRegistry registry) { 22 | registry.registerForPlayers(SQUAREMAP_PLAYER_COMPONENT, player -> new PlayerComponentImpl(), RespawnCopyStrategy.ALWAYS_COPY); 23 | } 24 | 25 | public interface PlayerComponent extends ComponentV3 { 26 | boolean hidden(); 27 | 28 | void hidden(boolean hidden); 29 | } 30 | 31 | private static final class PlayerComponentImpl implements PlayerComponent { 32 | private static final String HIDDEN_KEY = "hidden"; 33 | 34 | private boolean hidden; 35 | 36 | @Override 37 | public void readFromNbt(final CompoundTag tag, final HolderLookup.Provider registryLookup) { 38 | this.hidden = tag.getBooleanOr(HIDDEN_KEY, false); 39 | } 40 | 41 | @Override 42 | public void writeToNbt(final CompoundTag tag, final HolderLookup.Provider registryLookup) { 43 | tag.putBoolean(HIDDEN_KEY, this.hidden); 44 | } 45 | 46 | @Override 47 | public boolean hidden() { 48 | return this.hidden; 49 | } 50 | 51 | @Override 52 | public void hidden(final boolean hidden) { 53 | this.hidden = hidden; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/SquaremapFabricInitializer.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric; 2 | 3 | import net.fabricmc.api.ModInitializer; 4 | 5 | public final class SquaremapFabricInitializer implements ModInitializer { 6 | @Override 7 | public void onInitialize() { 8 | new SquaremapFabric(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/command/FabricCommander.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.command; 2 | 3 | import me.lucko.fabric.api.permissions.v0.Permissions; 4 | import net.kyori.adventure.audience.Audience; 5 | import net.kyori.adventure.audience.ForwardingAudience; 6 | import net.minecraft.commands.CommandSourceStack; 7 | import net.minecraft.server.level.ServerPlayer; 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | import xyz.jpenilla.squaremap.common.command.Commander; 11 | import xyz.jpenilla.squaremap.common.command.PlayerCommander; 12 | import xyz.jpenilla.squaremap.fabric.mixin.CommandSourceStackAccess; 13 | 14 | @DefaultQualifier(NonNull.class) 15 | public class FabricCommander implements Commander, ForwardingAudience.Single { 16 | private final CommandSourceStack stack; 17 | 18 | private FabricCommander(final CommandSourceStack stack) { 19 | this.stack = stack; 20 | } 21 | 22 | @Override 23 | public Audience audience() { 24 | return this.stack; 25 | } 26 | 27 | @Override 28 | public boolean hasPermission(final String permission) { 29 | return Permissions.check(this.stack, permission, this.stack.getServer().getOperatorUserPermissionLevel()); 30 | } 31 | 32 | public CommandSourceStack stack() { 33 | return this.stack; 34 | } 35 | 36 | @Override 37 | public Object commanderId() { 38 | return ((CommandSourceStackAccess) this.stack).source(); 39 | } 40 | 41 | public static FabricCommander from(final CommandSourceStack stack) { 42 | if (stack.getEntity() instanceof ServerPlayer) { 43 | return new Player(stack); 44 | } 45 | return new FabricCommander(stack); 46 | } 47 | 48 | public static final class Player extends FabricCommander implements PlayerCommander { 49 | private Player(final CommandSourceStack stack) { 50 | super(stack); 51 | } 52 | 53 | @Override 54 | public ServerPlayer player() { 55 | return (ServerPlayer) this.stack().getEntity(); 56 | } 57 | 58 | @Override 59 | public Object commanderId() { 60 | return this.player().getGameProfile().getId(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/data/FabricMapWorld.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.data; 2 | 3 | import com.google.inject.assistedinject.Assisted; 4 | import com.google.inject.assistedinject.AssistedInject; 5 | import net.minecraft.server.level.ServerLevel; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | import xyz.jpenilla.squaremap.common.config.ConfigManager; 9 | import xyz.jpenilla.squaremap.common.data.DirectoryProvider; 10 | import xyz.jpenilla.squaremap.common.data.MapWorldInternal; 11 | import xyz.jpenilla.squaremap.common.task.TaskFactory; 12 | import xyz.jpenilla.squaremap.common.task.UpdateMarkers; 13 | import xyz.jpenilla.squaremap.common.task.render.RenderFactory; 14 | 15 | @DefaultQualifier(NonNull.class) 16 | public final class FabricMapWorld extends MapWorldInternal { 17 | private final UpdateMarkers updateMarkers; 18 | 19 | @AssistedInject 20 | private FabricMapWorld( 21 | @Assisted final ServerLevel level, 22 | final RenderFactory renderFactory, 23 | final DirectoryProvider directoryProvider, 24 | final ConfigManager configManager, 25 | final TaskFactory taskFactory 26 | ) { 27 | super(level, renderFactory, directoryProvider, configManager); 28 | 29 | this.updateMarkers = taskFactory.createUpdateMarkers(this); 30 | } 31 | 32 | public void tickEachSecond(final long tick) { 33 | if (tick % (this.config().MARKER_API_UPDATE_INTERVAL_SECONDS * 20L) == 0) { 34 | this.updateMarkers.run(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/event/MapUpdateEvents.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.event; 2 | 3 | import net.fabricmc.fabric.api.event.Event; 4 | import net.fabricmc.fabric.api.event.EventFactory; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.server.level.ServerLevel; 7 | import net.minecraft.world.level.ChunkPos; 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | 11 | @DefaultQualifier(NonNull.class) 12 | public final class MapUpdateEvents { 13 | public static final Event> BLOCK_CHANGED = EventFactory.createArrayBacked( 14 | PositionListener.class, 15 | listeners -> (serverLevel, position) -> { 16 | for (final PositionListener listener : listeners) { 17 | listener.updatePosition(serverLevel, position); 18 | } 19 | } 20 | ); 21 | 22 | public static final Event> CHUNK_CHANGED = EventFactory.createArrayBacked( 23 | PositionListener.class, 24 | listeners -> (serverLevel, position) -> { 25 | for (final PositionListener listener : listeners) { 26 | listener.updatePosition(serverLevel, position); 27 | } 28 | } 29 | ); 30 | 31 | private MapUpdateEvents() { 32 | } 33 | 34 | public interface PositionListener { 35 | void updatePosition(ServerLevel serverLevel, T position); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/event/ServerPlayerEvents.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.event; 2 | 3 | import net.fabricmc.fabric.api.event.Event; 4 | import net.fabricmc.fabric.api.event.EventFactory; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | 9 | @DefaultQualifier(NonNull.class) 10 | public final class ServerPlayerEvents { 11 | public static final Event WORLD_CHANGED = EventFactory.createArrayBacked( 12 | WorldChanged.class, 13 | listeners -> player -> { 14 | for (final WorldChanged listener : listeners) { 15 | listener.worldChanged(player); 16 | } 17 | } 18 | ); 19 | 20 | private ServerPlayerEvents() { 21 | } 22 | 23 | public interface WorldChanged { 24 | void worldChanged(ServerPlayer player); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/inject/module/FabricModule.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.inject.module; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.google.inject.Provides; 5 | import java.nio.file.Path; 6 | import net.fabricmc.loader.api.FabricLoader; 7 | import net.fabricmc.loader.api.ModContainer; 8 | import net.kyori.adventure.platform.modcommon.MinecraftServerAudiences; 9 | import net.kyori.adventure.text.flattener.ComponentFlattener; 10 | import org.checkerframework.checker.nullness.qual.NonNull; 11 | import org.checkerframework.framework.qual.DefaultQualifier; 12 | import xyz.jpenilla.squaremap.common.AbstractPlayerManager; 13 | import xyz.jpenilla.squaremap.common.ServerAccess; 14 | import xyz.jpenilla.squaremap.common.command.PlatformCommands; 15 | import xyz.jpenilla.squaremap.common.inject.annotation.DataDirectory; 16 | import xyz.jpenilla.squaremap.fabric.FabricPlayerManager; 17 | import xyz.jpenilla.squaremap.fabric.FabricServerAccess; 18 | import xyz.jpenilla.squaremap.fabric.SquaremapFabric; 19 | import xyz.jpenilla.squaremap.fabric.command.FabricCommands; 20 | 21 | @DefaultQualifier(NonNull.class) 22 | public final class FabricModule extends AbstractModule { 23 | private final SquaremapFabric squaremapFabric; 24 | 25 | public FabricModule(final SquaremapFabric squaremapFabric) { 26 | this.squaremapFabric = squaremapFabric; 27 | } 28 | 29 | @Override 30 | protected void configure() { 31 | this.bind(SquaremapFabric.class) 32 | .toInstance(this.squaremapFabric); 33 | 34 | this.bind(PlatformCommands.class) 35 | .to(FabricCommands.class); 36 | 37 | this.bind(ServerAccess.class) 38 | .to(FabricServerAccess.class); 39 | 40 | this.bind(Path.class) 41 | .annotatedWith(DataDirectory.class) 42 | .toInstance(FabricLoader.getInstance().getGameDir().resolve("squaremap")); 43 | 44 | this.bind(AbstractPlayerManager.class) 45 | .to(FabricPlayerManager.class); 46 | 47 | this.bind(ModContainer.class) 48 | .toInstance(FabricLoader.getInstance().getModContainer("squaremap").orElseThrow()); 49 | } 50 | 51 | @Provides 52 | public ComponentFlattener componentFlattener(final FabricServerAccess serverAccess) { 53 | return MinecraftServerAudiences.of(serverAccess.requireServer()).flattener(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/mixin/BlockItemMixin.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.mixin; 2 | 3 | import net.minecraft.server.level.ServerLevel; 4 | import net.minecraft.world.InteractionResult; 5 | import net.minecraft.world.item.BlockItem; 6 | import net.minecraft.world.item.context.BlockPlaceContext; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 11 | import xyz.jpenilla.squaremap.fabric.event.MapUpdateEvents; 12 | 13 | @Mixin(BlockItem.class) 14 | abstract class BlockItemMixin { 15 | @Inject( 16 | method = "place(Lnet/minecraft/world/item/context/BlockPlaceContext;)Lnet/minecraft/world/InteractionResult;", 17 | at = @At( 18 | value = "INVOKE", 19 | target = "Lnet/minecraft/world/level/Level;gameEvent(Lnet/minecraft/core/Holder;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/gameevent/GameEvent$Context;)V" 20 | ) 21 | ) 22 | void injectPlace(BlockPlaceContext blockPlaceContext, CallbackInfoReturnable cir) { 23 | if (blockPlaceContext.getLevel() instanceof ServerLevel level) { 24 | MapUpdateEvents.BLOCK_CHANGED.invoker().updatePosition(level, blockPlaceContext.getClickedPos()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/mixin/ChunkMapAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.mixin; 2 | 3 | import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; 4 | import java.util.Optional; 5 | import java.util.concurrent.CompletableFuture; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.server.level.ChunkHolder; 8 | import net.minecraft.server.level.ChunkMap; 9 | import net.minecraft.world.level.ChunkPos; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.gen.Accessor; 12 | import org.spongepowered.asm.mixin.gen.Invoker; 13 | 14 | @Mixin(ChunkMap.class) 15 | public interface ChunkMapAccess extends xyz.jpenilla.squaremap.common.util.ChunkMapAccess { 16 | @Invoker("getVisibleChunkIfPresent") 17 | @Override 18 | ChunkHolder squaremap$getVisibleChunkIfPresent(long pos); 19 | 20 | @Invoker("readChunk") 21 | @Override 22 | CompletableFuture> squaremap$readChunk(ChunkPos pos); 23 | 24 | @Accessor("pendingUnloads") 25 | @Override 26 | Long2ObjectLinkedOpenHashMap squaremap$pendingUnloads(); 27 | } 28 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/mixin/CommandSourceStackAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.mixin; 2 | 3 | import net.minecraft.commands.CommandSource; 4 | import net.minecraft.commands.CommandSourceStack; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(CommandSourceStack.class) 9 | public interface CommandSourceStackAccess { 10 | @Accessor("source") 11 | CommandSource source(); 12 | } 13 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/mixin/LiquidBlockAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.mixin; 2 | 3 | import net.minecraft.world.level.block.LiquidBlock; 4 | import net.minecraft.world.level.material.FlowingFluid; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(LiquidBlock.class) 9 | public interface LiquidBlockAccess { 10 | @Accessor("fluid") 11 | FlowingFluid fluid(); 12 | } 13 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/mixin/PlayerListMixin.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.mixin; 2 | 3 | import net.minecraft.server.level.ServerLevel; 4 | import net.minecraft.server.level.ServerPlayer; 5 | import net.minecraft.server.players.PlayerList; 6 | import net.minecraft.world.entity.Entity; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 11 | import xyz.jpenilla.squaremap.fabric.event.ServerPlayerEvents; 12 | 13 | @Mixin(PlayerList.class) 14 | abstract class PlayerListMixin { 15 | private final ThreadLocal preRespawnLevel = new ThreadLocal<>(); 16 | 17 | @Inject( 18 | method = "Lnet/minecraft/server/players/PlayerList;respawn(Lnet/minecraft/server/level/ServerPlayer;ZLnet/minecraft/world/entity/Entity$RemovalReason;)Lnet/minecraft/server/level/ServerPlayer;", 19 | at = @At("HEAD") 20 | ) 21 | void injectRespawnHead(ServerPlayer serverPlayer, boolean bl, Entity.RemovalReason removalReason, CallbackInfoReturnable cir) { 22 | this.preRespawnLevel.set((ServerLevel) serverPlayer.level()); 23 | } 24 | 25 | @Inject( 26 | method = "Lnet/minecraft/server/players/PlayerList;respawn(Lnet/minecraft/server/level/ServerPlayer;ZLnet/minecraft/world/entity/Entity$RemovalReason;)Lnet/minecraft/server/level/ServerPlayer;", 27 | at = @At("RETURN") 28 | ) 29 | void injectRespawnReturn(ServerPlayer serverPlayer, boolean bl, Entity.RemovalReason removalReason, CallbackInfoReturnable cir) { 30 | final ServerLevel oldLevel = this.preRespawnLevel.get(); 31 | this.preRespawnLevel.remove(); 32 | final ServerPlayer player = cir.getReturnValue(); 33 | if (player.level() == oldLevel) { 34 | return; 35 | } 36 | ServerPlayerEvents.WORLD_CHANGED.invoker().worldChanged(player); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/mixin/ServerPlayerMixin.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.mixin; 2 | 3 | import net.minecraft.server.level.ServerPlayer; 4 | import net.minecraft.world.entity.Entity; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 9 | import xyz.jpenilla.squaremap.fabric.event.ServerPlayerEvents; 10 | 11 | @Mixin(ServerPlayer.class) 12 | abstract class ServerPlayerMixin { 13 | @Inject( 14 | method = "teleport(Lnet/minecraft/world/level/portal/TeleportTransition;)Lnet/minecraft/server/level/ServerPlayer;", 15 | at = @At( 16 | value = "INVOKE", 17 | target = "Lnet/minecraft/server/players/PlayerList;sendLevelInfo(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/server/level/ServerLevel;)V", 18 | shift = At.Shift.AFTER 19 | ) 20 | ) 21 | void injectChangeDimension(final CallbackInfoReturnable cir) { 22 | ServerPlayerEvents.WORLD_CHANGED.invoker().worldChanged((ServerPlayer) (Object) this); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/mixin/SleepBlockingMinecraftServerMixin.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import net.minecraft.server.MinecraftServer; 6 | import net.minecraft.server.players.PlayerList; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import xyz.jpenilla.squaremap.common.util.SleepBlockingMinecraftServer; 11 | 12 | @Mixin(MinecraftServer.class) 13 | abstract class SleepBlockingMinecraftServerMixin implements SleepBlockingMinecraftServer { 14 | @Unique 15 | private volatile boolean allowSleep = true; 16 | 17 | @WrapOperation( 18 | method = "tickServer", 19 | at = @At( 20 | value = "INVOKE", 21 | target = "Lnet/minecraft/server/players/PlayerList;getPlayerCount()I" 22 | ) 23 | ) 24 | private int allowSleep(final PlayerList instance, final Operation original) { 25 | return this.allowSleep ? original.call(instance) : 1; 26 | } 27 | 28 | @Override 29 | public void squaremap$blockSleep() { 30 | this.allowSleep = false; 31 | } 32 | 33 | @Override 34 | public void squaremap$allowSleep() { 35 | this.allowSleep = true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/mixin/SpriteContentsMixin.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.mixin; 2 | 3 | import com.mojang.blaze3d.platform.NativeImage; 4 | import net.minecraft.client.renderer.texture.SpriteContents; 5 | import org.spongepowered.asm.mixin.Final; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Shadow; 8 | import xyz.jpenilla.squaremap.fabric.FabricFluidColorExporter; 9 | 10 | @Mixin(SpriteContents.class) 11 | abstract class SpriteContentsMixin implements FabricFluidColorExporter.SpriteContentsExtension { 12 | @Shadow @Final private NativeImage originalImage; 13 | 14 | @Override 15 | public int getPixel(final int x, final int y) { 16 | // always gets from frame 0 of animated texture 17 | return this.originalImage.getPixel(x, y); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fabric/src/main/java/xyz/jpenilla/squaremap/fabric/network/FabricNetworking.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.fabric.network; 2 | 3 | import com.google.inject.Inject; 4 | import net.fabricmc.fabric.api.networking.v1.PacketSender; 5 | import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; 6 | import net.minecraft.network.FriendlyByteBuf; 7 | import net.minecraft.server.MinecraftServer; 8 | import net.minecraft.server.level.ServerPlayer; 9 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 10 | import org.checkerframework.checker.nullness.qual.NonNull; 11 | import org.checkerframework.framework.qual.DefaultQualifier; 12 | import xyz.jpenilla.squaremap.common.network.NetworkingHandler; 13 | import xyz.jpenilla.squaremap.common.util.Util; 14 | import xyz.jpenilla.squaremap.fabric.event.ServerPlayerEvents; 15 | 16 | @DefaultQualifier(NonNull.class) 17 | public final class FabricNetworking { 18 | private final NetworkingHandler networking; 19 | 20 | @Inject 21 | private FabricNetworking(final NetworkingHandler networking) { 22 | this.networking = networking; 23 | } 24 | 25 | public void register() { 26 | // ServerPlayNetworking.registerGlobalReceiver(NetworkingHandler.CHANNEL, this::handleInconming); // TODO 1.20.5 27 | ServerPlayConnectionEvents.DISCONNECT.register(this::handleDisconnect); 28 | ServerPlayerEvents.WORLD_CHANGED.register(this.networking::worldChanged); 29 | } 30 | 31 | private void handleInconming( 32 | final MinecraftServer server, 33 | final ServerPlayer player, 34 | final ServerGamePacketListenerImpl handler, 35 | final FriendlyByteBuf buf, 36 | final PacketSender responseSender 37 | ) { 38 | this.networking.handleIncoming(player, Util.raw(buf), map -> true); 39 | } 40 | 41 | private void handleDisconnect( 42 | final ServerGamePacketListenerImpl handler, 43 | final MinecraftServer server 44 | ) { 45 | this.networking.onDisconnect(handler.player.getUUID()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /fabric/src/main/resources/squaremap-fabric.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v1 named 2 | 3 | # below are manually copied from squaremap-common-at.cfg, be sure to keep up to date with changes 4 | accessible method net/minecraft/world/level/chunk/PalettedContainer get (I)Ljava/lang/Object; 5 | -------------------------------------------------------------------------------- /fabric/src/main/resources/squaremap-fabric.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8.4", 4 | "package": "xyz.jpenilla.squaremap.fabric.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "BlockItemMixin", 8 | "ChunkMapAccess", 9 | "CommandSourceStackAccess", 10 | "SleepBlockingMinecraftServerMixin", 11 | "PlayerListMixin", 12 | "ServerPlayerMixin" 13 | ], 14 | "client": [ 15 | "LiquidBlockAccess", 16 | "SpriteContentsMixin" 17 | ], 18 | "injectors": { 19 | "defaultRequire": 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=xyz.jpenilla 2 | version=1.3.7-SNAPSHOT 3 | description=Minimalistic and lightweight world map viewer for Minecraft servers 4 | 5 | githubUrl=https://github.com/jpenilla/squaremap/ 6 | 7 | org.gradle.parallel=true 8 | org.gradle.caching=true 9 | org.gradle.jvmargs=-Xmx3G 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /neoforge/src/main/java/xyz/jpenilla/squaremap/forge/ForgeFluidColorExporter.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.forge; 2 | 3 | import com.google.inject.Inject; 4 | import net.minecraft.client.renderer.texture.TextureAtlas; 5 | import net.minecraft.client.renderer.texture.TextureAtlasSprite; 6 | import net.minecraft.client.resources.model.Material; 7 | import net.minecraft.world.level.block.Block; 8 | import net.minecraft.world.level.block.LiquidBlock; 9 | import net.minecraft.world.level.material.Fluid; 10 | import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; 11 | import net.neoforged.neoforge.fluids.FluidType; 12 | import org.checkerframework.checker.nullness.qual.NonNull; 13 | import org.checkerframework.checker.nullness.qual.Nullable; 14 | import org.checkerframework.framework.qual.DefaultQualifier; 15 | import xyz.jpenilla.squaremap.common.data.DirectoryProvider; 16 | import xyz.jpenilla.squaremap.common.util.AbstractFluidColorExporter; 17 | import xyz.jpenilla.squaremap.common.util.ColorBlender; 18 | import xyz.jpenilla.squaremap.common.util.Colors; 19 | 20 | @DefaultQualifier(NonNull.class) 21 | public final class ForgeFluidColorExporter extends AbstractFluidColorExporter { 22 | @Inject 23 | private ForgeFluidColorExporter(final DirectoryProvider directoryProvider) { 24 | super(directoryProvider); 25 | } 26 | 27 | @Override 28 | protected @Nullable Fluid fluid(final Block block) { 29 | if (block instanceof LiquidBlock liquidBlock) { 30 | return liquidBlock.fluid; 31 | } 32 | return null; 33 | } 34 | 35 | @Override 36 | protected String color(final Fluid fluid) { 37 | return Colors.toHexString(Colors.argbToRgba(color(fluid.getFluidType()))); 38 | } 39 | 40 | public static int color(final FluidType fluidType) { 41 | final IClientFluidTypeExtensions ext = IClientFluidTypeExtensions.of(fluidType); 42 | final TextureAtlasSprite sprite = new Material(TextureAtlas.LOCATION_BLOCKS, ext.getStillTexture()).sprite(); 43 | final ColorBlender blender = new ColorBlender(); 44 | for (int i = 0; i < sprite.contents().width(); i++) { 45 | for (int h = 0; h < sprite.contents().height(); h++) { 46 | final int rgba = sprite.getPixelRGBA(0, i, h); 47 | blender.addColor(rgba); 48 | } 49 | } 50 | return color(blender.result(), ext.getTintColor()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /neoforge/src/main/java/xyz/jpenilla/squaremap/forge/ForgeSquaremapJarAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.forge; 2 | 3 | import com.google.inject.Inject; 4 | import java.io.IOException; 5 | import java.nio.file.Path; 6 | import net.neoforged.fml.ModContainer; 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | import org.checkerframework.framework.qual.DefaultQualifier; 9 | import xyz.jpenilla.squaremap.common.util.CheckedConsumer; 10 | import xyz.jpenilla.squaremap.common.util.FileUtil; 11 | import xyz.jpenilla.squaremap.common.util.SquaremapJarAccess; 12 | 13 | @DefaultQualifier(NonNull.class) 14 | final class ForgeSquaremapJarAccess implements SquaremapJarAccess { 15 | private final ModContainer modContainer; 16 | 17 | @Inject 18 | private ForgeSquaremapJarAccess(final ModContainer modContainer) { 19 | this.modContainer = modContainer; 20 | } 21 | 22 | @Override 23 | public void useJar(final CheckedConsumer consumer) throws IOException { 24 | FileUtil.openJar(this.modContainer.getModInfo().getOwningFile().getFile().getFilePath(), fs -> consumer.accept(fs.getPath("/"))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /neoforge/src/main/java/xyz/jpenilla/squaremap/forge/data/ForgeMapWorld.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.forge.data; 2 | 3 | import com.google.inject.assistedinject.Assisted; 4 | import com.google.inject.assistedinject.AssistedInject; 5 | import net.minecraft.server.level.ServerLevel; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | import xyz.jpenilla.squaremap.common.config.ConfigManager; 9 | import xyz.jpenilla.squaremap.common.data.DirectoryProvider; 10 | import xyz.jpenilla.squaremap.common.data.MapWorldInternal; 11 | import xyz.jpenilla.squaremap.common.task.TaskFactory; 12 | import xyz.jpenilla.squaremap.common.task.UpdateMarkers; 13 | import xyz.jpenilla.squaremap.common.task.render.RenderFactory; 14 | 15 | @DefaultQualifier(NonNull.class) 16 | public final class ForgeMapWorld extends MapWorldInternal { 17 | private final UpdateMarkers updateMarkers; 18 | 19 | @AssistedInject 20 | private ForgeMapWorld( 21 | @Assisted final ServerLevel level, 22 | final RenderFactory renderFactory, 23 | final DirectoryProvider directoryProvider, 24 | final ConfigManager configManager, 25 | final TaskFactory taskFactory 26 | ) { 27 | super(level, renderFactory, directoryProvider, configManager); 28 | 29 | this.updateMarkers = taskFactory.createUpdateMarkers(this); 30 | } 31 | 32 | public void tickEachSecond(final long tick) { 33 | if (tick % (this.config().MARKER_API_UPDATE_INTERVAL_SECONDS * 20L) == 0) { 34 | this.updateMarkers.run(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /neoforge/src/main/java/xyz/jpenilla/squaremap/forge/mixin/ChunkMapAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.forge.mixin; 2 | 3 | import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; 4 | import java.util.Optional; 5 | import java.util.concurrent.CompletableFuture; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.server.level.ChunkHolder; 8 | import net.minecraft.server.level.ChunkMap; 9 | import net.minecraft.world.level.ChunkPos; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.gen.Accessor; 12 | import org.spongepowered.asm.mixin.gen.Invoker; 13 | 14 | @Mixin(ChunkMap.class) 15 | public interface ChunkMapAccess extends xyz.jpenilla.squaremap.common.util.ChunkMapAccess { 16 | @Invoker("getVisibleChunkIfPresent") 17 | @Override 18 | ChunkHolder squaremap$getVisibleChunkIfPresent(long pos); 19 | 20 | @Invoker("readChunk") 21 | @Override 22 | CompletableFuture> squaremap$readChunk(ChunkPos pos); 23 | 24 | @Accessor("pendingUnloads") 25 | @Override 26 | Long2ObjectLinkedOpenHashMap squaremap$pendingUnloads(); 27 | } 28 | -------------------------------------------------------------------------------- /neoforge/src/main/java/xyz/jpenilla/squaremap/forge/mixin/SleepBlockingMinecraftServerMixin.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.forge.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import net.minecraft.server.MinecraftServer; 6 | import net.minecraft.server.players.PlayerList; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import xyz.jpenilla.squaremap.common.util.SleepBlockingMinecraftServer; 11 | 12 | @Mixin(MinecraftServer.class) 13 | abstract class SleepBlockingMinecraftServerMixin implements SleepBlockingMinecraftServer { 14 | @Unique 15 | private volatile boolean allowSleep = true; 16 | 17 | @WrapOperation( 18 | method = "tickServer", 19 | at = @At( 20 | value = "INVOKE", 21 | target = "Lnet/minecraft/server/players/PlayerList;getPlayerCount()I" 22 | ) 23 | ) 24 | private int allowSleep(final PlayerList instance, final Operation original) { 25 | return this.allowSleep ? original.call(instance) : 1; 26 | } 27 | 28 | @Override 29 | public void squaremap$blockSleep() { 30 | this.allowSleep = false; 31 | } 32 | 33 | @Override 34 | public void squaremap$allowSleep() { 35 | this.allowSleep = true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /neoforge/src/main/java/xyz/jpenilla/squaremap/forge/network/ForgeNetworking.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.forge.network; 2 | 3 | import com.google.inject.Inject; 4 | import net.minecraft.server.level.ServerPlayer; 5 | import net.neoforged.neoforge.common.NeoForge; 6 | import net.neoforged.neoforge.event.entity.player.PlayerEvent; 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | import org.checkerframework.framework.qual.DefaultQualifier; 9 | import xyz.jpenilla.squaremap.common.network.NetworkingHandler; 10 | 11 | @DefaultQualifier(NonNull.class) 12 | public final class ForgeNetworking { 13 | private final NetworkingHandler networking; 14 | 15 | @Inject 16 | private ForgeNetworking(final NetworkingHandler networking) { 17 | this.networking = networking; 18 | } 19 | 20 | public void register() { 21 | /* TODO 1.20.5 22 | NeoForge.EVENT_BUS.addListener((RegisterPayloadHandlerEvent event) -> { 23 | final IPayloadRegistrar registrar = event.registrar("squaremap"); 24 | registrar.play( 25 | NetworkingHandler.CHANNEL, 26 | fbb -> new NetworkingHandler.SquaremapClientPayload(Util.raw(fbb)), 27 | builder -> { 28 | builder.server((payload, context) -> { 29 | context.player().ifPresent(player -> { 30 | if (!(player instanceof ServerPlayer serverPlayer)) { 31 | return; 32 | } 33 | this.networking.handleIncoming(serverPlayer, payload.bytes(), map -> true); 34 | }); 35 | }); 36 | } 37 | ).optional(); 38 | }); 39 | */ 40 | NeoForge.EVENT_BUS.addListener((PlayerEvent.PlayerLoggedOutEvent event) -> this.networking.onDisconnect(event.getEntity().getUUID())); 41 | NeoForge.EVENT_BUS.addListener((PlayerEvent.PlayerChangedDimensionEvent event) -> { 42 | if (!(event.getEntity() instanceof ServerPlayer player)) { 43 | return; 44 | } 45 | this.networking.worldChanged(player); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/accesstransformer.cfg: -------------------------------------------------------------------------------- 1 | # below are manually copied from squaremap-common-at.cfg, be sure to keep up to date with changes 2 | public net.minecraft.world.level.chunk.PalettedContainer get(I)Ljava/lang/Object; 3 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "squaremap resources", 4 | "pack_format": 18 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/squaremap-forge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8.4", 4 | "package": "xyz.jpenilla.squaremap.forge.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "ChunkMapAccess", 8 | "SleepBlockingMinecraftServerMixin" 9 | ], 10 | "injectors": { 11 | "defaultRequire": 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /paper/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import io.papermc.paperweight.userdev.ReobfArtifactConfiguration 2 | import xyz.jpenilla.resourcefactory.bukkit.BukkitPluginYaml 3 | 4 | plugins { 5 | id("squaremap.platform") 6 | id("io.papermc.paperweight.userdev") 7 | alias(libs.plugins.run.paper) 8 | alias(libs.plugins.hangar.publish) 9 | alias(libs.plugins.resource.factory.bukkit) 10 | } 11 | 12 | val minecraftVersion = libs.versions.minecraft 13 | 14 | dependencies { 15 | paperweight.paperDevBundle(minecraftVersion.map { "$it-R0.1-SNAPSHOT" }) 16 | 17 | implementation(projects.squaremapCommon) 18 | implementation(projects.squaremapPaper.folia) 19 | 20 | implementation(libs.cloudPaper) 21 | implementation(libs.bStatsBukkit) 22 | } 23 | 24 | tasks { 25 | jar { 26 | manifest { 27 | attributes("squaremap-target-minecraft-version" to minecraftVersion.get()) 28 | } 29 | } 30 | shadowJar { 31 | archiveFileName = productionJarName(minecraftVersion) 32 | listOf( 33 | "org.incendo.cloud", 34 | "io.leangen.geantyref", 35 | "org.bstats", 36 | "jakarta.inject", 37 | "com.google.inject", 38 | "org.aopalliance", 39 | ).forEach(::reloc) 40 | } 41 | runServer { 42 | runProps(layout, providers).forEach { (key, value) -> 43 | systemProperty(key, value) 44 | } 45 | } 46 | } 47 | 48 | squaremapPlatform.productionJar = tasks.shadowJar.flatMap { it.archiveFile } 49 | 50 | runPaper.folia.registerTask() 51 | 52 | paperweight { 53 | injectPaperRepository = false 54 | reobfArtifactConfiguration = ReobfArtifactConfiguration.MOJANG_PRODUCTION 55 | } 56 | 57 | bukkitPluginYaml { 58 | name = "squaremap" 59 | main = "xyz.jpenilla.squaremap.paper.SquaremapPaperBootstrap" 60 | load = BukkitPluginYaml.PluginLoadOrder.STARTUP 61 | authors = listOf("jmp") 62 | website = githubUrl 63 | apiVersion = minecraftVersion 64 | foliaSupported = true 65 | } 66 | 67 | hangarPublish.publications.register("plugin") { 68 | version = project.version as String 69 | id = "squaremap" 70 | channel = "Release" 71 | changelog = releaseNotes 72 | apiKey = providers.environmentVariable("HANGAR_UPLOAD_KEY") 73 | platforms.paper { 74 | jar = squaremapPlatform.productionJar 75 | platformVersions.add(minecraftVersion) 76 | } 77 | } 78 | 79 | publishMods.modrinth { 80 | minecraftVersions.add(minecraftVersion) 81 | modLoaders.addAll("paper", "folia") 82 | } 83 | -------------------------------------------------------------------------------- /paper/folia/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("squaremap.base-conventions") 3 | } 4 | 5 | dependencies { 6 | compileOnly(projects.squaremapCommon) 7 | compileOnly(libs.foliaApi) 8 | } 9 | -------------------------------------------------------------------------------- /paper/folia/src/main/java/xyz/jpenilla/squaremap/paper/folia/FoliaInitListener.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.paper.folia; 2 | 3 | import io.papermc.paper.threadedregions.RegionizedServerInitEvent; 4 | import org.bukkit.event.EventHandler; 5 | import org.bukkit.event.Listener; 6 | import org.bukkit.plugin.java.JavaPlugin; 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | import org.checkerframework.framework.qual.DefaultQualifier; 9 | 10 | @DefaultQualifier(NonNull.class) 11 | public final class FoliaInitListener implements Listener { 12 | private final JavaPlugin plugin; 13 | private final Runnable action; 14 | 15 | public FoliaInitListener(final JavaPlugin plugin, final Runnable action) { 16 | this.plugin = plugin; 17 | this.action = action; 18 | } 19 | 20 | @EventHandler 21 | public void handle(final RegionizedServerInitEvent event) { 22 | this.plugin.getServer().getAsyncScheduler().runNow(this.plugin, $ -> this.action.run()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /paper/src/main/java/xyz/jpenilla/squaremap/paper/PaperPlayerManager.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.paper; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Singleton; 5 | import net.kyori.adventure.text.Component; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import org.bukkit.NamespacedKey; 8 | import org.bukkit.persistence.PersistentDataContainer; 9 | import org.bukkit.persistence.PersistentDataType; 10 | import org.bukkit.plugin.java.JavaPlugin; 11 | import org.checkerframework.checker.nullness.qual.NonNull; 12 | import org.checkerframework.framework.qual.DefaultQualifier; 13 | import xyz.jpenilla.squaremap.common.AbstractPlayerManager; 14 | import xyz.jpenilla.squaremap.common.ServerAccess; 15 | 16 | @DefaultQualifier(NonNull.class) 17 | @Singleton 18 | public final class PaperPlayerManager extends AbstractPlayerManager { 19 | public final NamespacedKey hiddenKey; 20 | 21 | @Inject 22 | private PaperPlayerManager( 23 | final JavaPlugin plugin, 24 | final ServerAccess serverAccess 25 | ) { 26 | super(serverAccess); 27 | this.hiddenKey = new NamespacedKey(plugin, "hidden"); 28 | } 29 | 30 | @Override 31 | protected boolean persistentHidden(final ServerPlayer player) { 32 | return pdc(player).getOrDefault(this.hiddenKey, PersistentDataType.BYTE, (byte) 0) != (byte) 0; 33 | } 34 | 35 | @Override 36 | protected void persistentHidden(final ServerPlayer player, final boolean value) { 37 | pdc(player).set(this.hiddenKey, PersistentDataType.BYTE, (byte) (value ? 1 : 0)); 38 | } 39 | 40 | @Override 41 | public boolean otherwiseHidden(final ServerPlayer player) { 42 | return player.getBukkitEntity().hasMetadata("NPC"); 43 | } 44 | 45 | @Override 46 | public Component displayName(final ServerPlayer player) { 47 | return player.getBukkitEntity().displayName(); 48 | } 49 | 50 | private static PersistentDataContainer pdc(final ServerPlayer player) { 51 | return player.getBukkitEntity().getPersistentDataContainer(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /paper/src/main/java/xyz/jpenilla/squaremap/paper/PaperWorldManager.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.paper; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Singleton; 5 | import java.util.Optional; 6 | import net.minecraft.server.level.ServerLevel; 7 | import org.bukkit.World; 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | import xyz.jpenilla.squaremap.common.ServerAccess; 11 | import xyz.jpenilla.squaremap.common.WorldManagerImpl; 12 | import xyz.jpenilla.squaremap.common.config.ConfigManager; 13 | import xyz.jpenilla.squaremap.common.data.DirectoryProvider; 14 | import xyz.jpenilla.squaremap.common.data.MapWorldInternal; 15 | import xyz.jpenilla.squaremap.paper.util.CraftBukkitHelper; 16 | import xyz.jpenilla.squaremap.paper.util.WorldNameToKeyMigration; 17 | 18 | @DefaultQualifier(NonNull.class) 19 | @Singleton 20 | public final class PaperWorldManager extends WorldManagerImpl { 21 | private final DirectoryProvider directoryProvider; 22 | 23 | @Inject 24 | private PaperWorldManager( 25 | final MapWorldInternal.Factory factory, 26 | final ServerAccess serverAccess, 27 | final DirectoryProvider directoryProvider, 28 | final ConfigManager configManager 29 | ) { 30 | super(factory, serverAccess, configManager); 31 | this.directoryProvider = directoryProvider; 32 | } 33 | 34 | @Override 35 | public void initWorld(final ServerLevel level) { 36 | WorldNameToKeyMigration.tryMoveDirectories(this.directoryProvider, level); 37 | super.initWorld(level); 38 | } 39 | 40 | public Optional getWorldIfEnabled(final World world) { 41 | return this.getWorldIfEnabled(CraftBukkitHelper.serverLevel(world)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /paper/src/main/java/xyz/jpenilla/squaremap/paper/listener/WorldLoadListener.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.paper.listener; 2 | 3 | import com.google.inject.Inject; 4 | import org.bukkit.event.EventHandler; 5 | import org.bukkit.event.EventPriority; 6 | import org.bukkit.event.Listener; 7 | import org.bukkit.event.world.WorldLoadEvent; 8 | import org.bukkit.event.world.WorldUnloadEvent; 9 | import org.checkerframework.checker.nullness.qual.NonNull; 10 | import org.checkerframework.framework.qual.DefaultQualifier; 11 | import xyz.jpenilla.squaremap.paper.PaperWorldManager; 12 | import xyz.jpenilla.squaremap.paper.util.CraftBukkitHelper; 13 | 14 | @DefaultQualifier(NonNull.class) 15 | public final class WorldLoadListener implements Listener { 16 | private final PaperWorldManager worldManager; 17 | 18 | @Inject 19 | private WorldLoadListener(final PaperWorldManager worldManager) { 20 | this.worldManager = worldManager; 21 | } 22 | 23 | // Use low priority to load world before other plugins load listeners 24 | @EventHandler(priority = EventPriority.LOW) 25 | public void handleWorldLoad(final WorldLoadEvent event) { 26 | this.worldManager.initWorld(CraftBukkitHelper.serverLevel(event.getWorld())); 27 | } 28 | 29 | // Use high priority to unload world after other plugins unload listeners 30 | @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) 31 | public void handleWorldUnload(final WorldUnloadEvent event) { 32 | this.worldManager.worldUnloaded(CraftBukkitHelper.serverLevel(event.getWorld())); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /paper/src/main/java/xyz/jpenilla/squaremap/paper/util/CraftBukkitHelper.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.paper.util; 2 | 3 | import net.minecraft.server.level.ServerLevel; 4 | import net.minecraft.server.level.ServerPlayer; 5 | import org.bukkit.World; 6 | import org.bukkit.craftbukkit.CraftWorld; 7 | import org.bukkit.craftbukkit.entity.CraftPlayer; 8 | import org.bukkit.entity.Player; 9 | import org.checkerframework.checker.nullness.qual.NonNull; 10 | 11 | public final class CraftBukkitHelper { 12 | private CraftBukkitHelper() { 13 | } 14 | 15 | public static @NonNull ServerLevel serverLevel(final @NonNull World world) { 16 | return ((CraftWorld) world).getHandle(); 17 | } 18 | 19 | public static @NonNull ServerPlayer serverPlayer(final @NonNull Player player) { 20 | return ((CraftPlayer) player).getHandle(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /paper/src/main/java/xyz/jpenilla/squaremap/paper/util/Folia.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.paper.util; 2 | 3 | import org.checkerframework.checker.nullness.qual.Nullable; 4 | import xyz.jpenilla.squaremap.common.util.ReflectionUtil; 5 | 6 | public final class Folia { 7 | public static final boolean FOLIA; 8 | 9 | static { 10 | final @Nullable Class regionizedServerCls = ReflectionUtil.findClass("io.papermc.paper.threadedregions.RegionizedServer"); 11 | FOLIA = regionizedServerCls != null; 12 | } 13 | 14 | private Folia() { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /paper/src/main/java/xyz/jpenilla/squaremap/paper/util/PaperEntityScheduler.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.paper.util; 2 | 3 | import com.google.inject.Inject; 4 | import io.papermc.paper.command.brigadier.CommandSourceStack; 5 | import net.minecraft.world.entity.Entity; 6 | import org.bukkit.Server; 7 | import org.bukkit.command.BlockCommandSender; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.plugin.java.JavaPlugin; 10 | import org.checkerframework.checker.nullness.qual.NonNull; 11 | import org.checkerframework.framework.qual.DefaultQualifier; 12 | import xyz.jpenilla.squaremap.common.command.Commander; 13 | import xyz.jpenilla.squaremap.common.util.EntityScheduler; 14 | import xyz.jpenilla.squaremap.paper.command.PaperCommander; 15 | 16 | @DefaultQualifier(NonNull.class) 17 | public final class PaperEntityScheduler implements EntityScheduler { 18 | private final Server server; 19 | private final JavaPlugin plugin; 20 | 21 | @Inject 22 | private PaperEntityScheduler(final Server server, final JavaPlugin plugin) { 23 | this.server = server; 24 | this.plugin = plugin; 25 | } 26 | 27 | @Override 28 | public void scheduleFor(final Entity entity, final Runnable task) { 29 | if (Folia.FOLIA) { 30 | entity.getBukkitEntity().getScheduler().execute(this.plugin, task, null, 0L); 31 | } else { 32 | task.run(); 33 | } 34 | } 35 | 36 | @SuppressWarnings("UnstableApiUsage") 37 | @Override 38 | public void scheduleFor(final Commander commander, final Runnable task) { 39 | if (!Folia.FOLIA) { 40 | task.run(); 41 | return; 42 | } 43 | final CommandSourceStack source = ((PaperCommander) commander).stack(); 44 | final CommandSender sender = source.getExecutor() != null ? source.getExecutor() : source.getSender(); 45 | if (sender instanceof org.bukkit.entity.Entity entity) { 46 | entity.getScheduler().execute(this.plugin, task, null, 0L); 47 | } else if (sender instanceof BlockCommandSender block) { 48 | this.server.getRegionScheduler().execute(this.plugin, block.getBlock().getLocation(), task); 49 | } else { 50 | this.server.getGlobalRegionScheduler().execute(this.plugin, task); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /paper/src/main/java/xyz/jpenilla/squaremap/paper/util/PaperRegionFileDirectoryResolver.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.paper.util; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Singleton; 5 | import java.nio.file.Path; 6 | import net.minecraft.server.level.ServerLevel; 7 | import net.minecraft.world.level.storage.LevelStorageSource; 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | import xyz.jpenilla.squaremap.common.util.RegionFileDirectoryResolver; 11 | 12 | @DefaultQualifier(NonNull.class) 13 | @Singleton 14 | public final class PaperRegionFileDirectoryResolver implements RegionFileDirectoryResolver { 15 | @Inject 16 | private PaperRegionFileDirectoryResolver() { 17 | } 18 | 19 | @Override 20 | public Path resolveRegionFileDirectory(final ServerLevel level) { 21 | return LevelStorageSource.getStorageFolder( 22 | level.getWorld().getWorldFolder().toPath(), 23 | level.getTypeKey() 24 | ).resolve("region"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /paper/src/main/java/xyz/jpenilla/squaremap/paper/util/WorldNameToKeyMigration.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.paper.util; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import net.minecraft.server.level.ServerLevel; 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | import org.checkerframework.framework.qual.DefaultQualifier; 9 | import xyz.jpenilla.squaremap.common.Logging; 10 | import xyz.jpenilla.squaremap.common.config.AbstractConfig; 11 | import xyz.jpenilla.squaremap.common.data.DirectoryProvider; 12 | import xyz.jpenilla.squaremap.common.util.Util; 13 | 14 | @DefaultQualifier(NonNull.class) 15 | public final class WorldNameToKeyMigration { 16 | private WorldNameToKeyMigration() { 17 | } 18 | 19 | @SuppressWarnings("unused") // called using Reflection in AbstractWorldConfig constructor 20 | public static void migrate(final AbstractConfig config, final ServerLevel level) { 21 | final String oldName = level.getWorld().getName(); 22 | config.migrateLevelSection(level, oldName); 23 | } 24 | 25 | public static void tryMoveDirectories(final DirectoryProvider directoryProvider, final ServerLevel level) { 26 | try { 27 | moveDirectories(directoryProvider, level); 28 | } catch (final IOException ex) { 29 | Logging.logger().error("Failed to migrate directories for '{}'", level.dimension().location()); 30 | } 31 | } 32 | 33 | private static void moveDirectories(final DirectoryProvider directoryProvider, final ServerLevel level) throws IOException { 34 | final String oldName = level.getWorld().getName(); 35 | final String webName = Util.levelWebName(level); 36 | final Path tilesFrom = directoryProvider.tilesDirectory().resolve(oldName); 37 | if (Files.exists(tilesFrom)) { 38 | final Path tilesDest = directoryProvider.tilesDirectory().resolve(webName); 39 | Files.move(tilesFrom, tilesDest); 40 | } 41 | 42 | final Path data = directoryProvider.dataDirectory().resolve("data"); 43 | final Path dataFrom = data.resolve(oldName); 44 | if (Files.exists(dataFrom)) { 45 | final Path dataDest = data.resolve(webName); 46 | Files.move(dataFrom, dataDest); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /paper/src/main/java/xyz/jpenilla/squaremap/paper/util/chunksnapshot/PaperChunkSnapshotProviderFactory.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.paper.util.chunksnapshot; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Singleton; 5 | import net.minecraft.server.level.ServerLevel; 6 | import org.bukkit.Server; 7 | import org.bukkit.plugin.java.JavaPlugin; 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.framework.qual.DefaultQualifier; 10 | import xyz.jpenilla.squaremap.common.util.chunksnapshot.ChunkSnapshotProvider; 11 | import xyz.jpenilla.squaremap.common.util.chunksnapshot.ChunkSnapshotProviderFactory; 12 | 13 | @DefaultQualifier(NonNull.class) 14 | @Singleton 15 | public final class PaperChunkSnapshotProviderFactory implements ChunkSnapshotProviderFactory { 16 | private final Server server; 17 | private final JavaPlugin plugin; 18 | 19 | @Inject 20 | private PaperChunkSnapshotProviderFactory(final Server server, final JavaPlugin plugin) { 21 | this.server = server; 22 | this.plugin = plugin; 23 | } 24 | 25 | @Override 26 | public ChunkSnapshotProvider createChunkSnapshotProvider(final ServerLevel level) { 27 | return new PaperChunkSnapshotProvider(level, this.server, this.plugin); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "ignoreDeps": [ 7 | "com.mojang:minecraft", 8 | "dev.architectury:architectury-loom", 9 | "net.fabricmc:quiet-fabric-loom" 10 | ], 11 | "labels": [ 12 | "dependencies" 13 | ], 14 | "packageRules": [ 15 | { 16 | "description": "Correct version handling for dependencies with format major.minor.patch+mcver", 17 | "matchPackageNames": [ 18 | "net.fabricmc.fabric-api:fabric-api", 19 | "net.fabricmc.fabric-api:fabric-api-deprecated" 20 | ], 21 | "versioning": "regex:^(?\\d+)(\\.(?\\d+))?(\\.(?\\d+))?(?:\\+(?.*))?$" 22 | }, 23 | { 24 | "description": "Correct version handling for NeoForge", 25 | "matchPackageNames": [ 26 | "net.neoforged:neoforge" 27 | ], 28 | "versioning": "regex:^(?(\\d+\\.){2})(?\\d+)(-beta)?$" 29 | }, 30 | { 31 | "matchManagers": [ 32 | "github-actions", 33 | "gradle-wrapper" 34 | ], 35 | "groupName": "gradle and github actions" 36 | }, 37 | { 38 | "matchDepTypes": [ 39 | "plugin" 40 | ], 41 | "groupName": "gradle and github actions" 42 | }, 43 | { 44 | "matchFileNames": [ 45 | "build-logic/*", 46 | "buildSrc/*" 47 | ], 48 | "groupName": "gradle and github actions" 49 | } 50 | ], 51 | "semanticCommitType": "build", 52 | "commitMessagePrefix": "chore(deps): " 53 | } 54 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | mavenCentral() 7 | maven("https://maven.fabricmc.net/") 8 | maven("https://maven.neoforged.net/releases/") 9 | maven("https://maven.architectury.dev/") 10 | maven("https://repo.jpenilla.xyz/snapshots/") 11 | } 12 | includeBuild("build-logic") 13 | } 14 | 15 | plugins { 16 | id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" 17 | } 18 | 19 | rootProject.name = "squaremap" 20 | 21 | setupSubproject("api") 22 | setupSubproject("common") 23 | setupSubproject("paper") 24 | include(":squaremap-paper:folia") 25 | setupSubproject("fabric") 26 | setupSubproject("neoforge") 27 | setupSubproject("sponge") 28 | 29 | fun setupSubproject(moduleName: String) { 30 | val name = "squaremap-$moduleName" 31 | include(name) 32 | val proj = project(":$name") 33 | proj.projectDir = file(moduleName) 34 | } 35 | -------------------------------------------------------------------------------- /sponge/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.spongepowered.gradle.plugin.config.PluginLoaders 2 | import org.spongepowered.plugin.metadata.model.PluginDependency 3 | 4 | plugins { 5 | id("squaremap.platform.mdg") 6 | alias(libs.plugins.sponge.gradle.plugin) 7 | alias(libs.plugins.sponge.gradle.ore) 8 | } 9 | 10 | val minecraftVersion = libs.versions.minecraft 11 | 12 | neoForge { 13 | enable { 14 | neoFormVersion = libs.versions.neoform.get() 15 | } 16 | } 17 | 18 | dependencies { 19 | shade(projects.squaremapCommon) { 20 | exclude("io.leangen.geantyref") 21 | } 22 | shade(libs.cloudSponge) { 23 | exclude("io.leangen.geantyref") 24 | } 25 | compileOnly("javax.inject:javax.inject:1") 26 | 27 | compileOnly(libs.mixin) 28 | } 29 | 30 | // https://github.com/SpongePowered/SpongeGradle/issues/70 31 | configurations.spongeRuntime { 32 | resolutionStrategy { 33 | eachDependency { 34 | if (target.name == "spongevanilla") { 35 | useVersion("1.21.5-15.+") 36 | } 37 | } 38 | } 39 | } 40 | 41 | sponge { 42 | apiVersion("15.0.0-SNAPSHOT") 43 | plugin("squaremap") { 44 | loader { 45 | name(PluginLoaders.JAVA_PLAIN) 46 | version("1.0") 47 | } 48 | license("MIT") 49 | entrypoint("xyz.jpenilla.squaremap.sponge.SquaremapSpongeBootstrap") 50 | dependency("spongeapi") { 51 | loadOrder(PluginDependency.LoadOrder.AFTER) 52 | optional(false) 53 | } 54 | } 55 | } 56 | 57 | tasks { 58 | productionJar { 59 | archiveFileName = productionJarName(minecraftVersion) 60 | } 61 | shadowJar { 62 | listOf( 63 | "org.incendo.cloud", 64 | ).forEach(::reloc) 65 | manifest { 66 | attributes( 67 | "Access-Widener" to "squaremap-sponge.accesswidener", 68 | "MixinConfigs" to "squaremap-sponge.mixins.json", 69 | ) 70 | } 71 | } 72 | runServer { 73 | runProps(layout, providers).forEach { (key, value) -> 74 | systemProperty(key, value) 75 | } 76 | } 77 | } 78 | 79 | oreDeployment { 80 | defaultPublication { 81 | versionBody.set(releaseNotes) 82 | publishArtifacts.setFrom(squaremapPlatform.productionJar) 83 | } 84 | } 85 | 86 | publishMods.modrinth { 87 | minecraftVersions.add(minecraftVersion) 88 | modLoaders.add("sponge") 89 | } 90 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/SpongePlayerManager.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Singleton; 5 | import net.kyori.adventure.text.Component; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | import org.checkerframework.framework.qual.DefaultQualifier; 9 | import org.spongepowered.api.ResourceKey; 10 | import org.spongepowered.api.data.Key; 11 | import org.spongepowered.api.data.value.Value; 12 | import xyz.jpenilla.squaremap.common.AbstractPlayerManager; 13 | import xyz.jpenilla.squaremap.common.ServerAccess; 14 | 15 | @DefaultQualifier(NonNull.class) 16 | @Singleton 17 | public final class SpongePlayerManager extends AbstractPlayerManager { 18 | public static final Key> HIDDEN_KEY = Key.from(ResourceKey.of("squaremap", "hidden"), Boolean.class); 19 | 20 | @Inject 21 | private SpongePlayerManager(final ServerAccess serverAccess) { 22 | super(serverAccess); 23 | } 24 | 25 | @Override 26 | protected boolean persistentHidden(final ServerPlayer player) { 27 | return ((org.spongepowered.api.entity.living.player.server.ServerPlayer) player).get(HIDDEN_KEY).orElse(false); 28 | } 29 | 30 | @Override 31 | protected void persistentHidden(final ServerPlayer player, final boolean value) { 32 | ((org.spongepowered.api.entity.living.player.server.ServerPlayer) player).offer(HIDDEN_KEY, value); 33 | } 34 | 35 | @Override 36 | public Component displayName(final ServerPlayer player) { 37 | return ((org.spongepowered.api.entity.living.player.server.ServerPlayer) player).displayName().get(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/SpongeServerAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Singleton; 5 | import java.util.Collection; 6 | import java.util.List; 7 | import java.util.UUID; 8 | import net.minecraft.server.level.ServerLevel; 9 | import net.minecraft.server.level.ServerPlayer; 10 | import org.checkerframework.checker.nullness.qual.NonNull; 11 | import org.checkerframework.checker.nullness.qual.Nullable; 12 | import org.checkerframework.framework.qual.DefaultQualifier; 13 | import org.spongepowered.api.Game; 14 | import org.spongepowered.api.ResourceKey; 15 | import xyz.jpenilla.squaremap.api.WorldIdentifier; 16 | import xyz.jpenilla.squaremap.common.util.SleepBlockingMinecraftServer; 17 | import xyz.jpenilla.squaremap.common.ServerAccess; 18 | 19 | @DefaultQualifier(NonNull.class) 20 | @Singleton 21 | public final class SpongeServerAccess implements ServerAccess { 22 | private final Game game; 23 | 24 | @Inject 25 | private SpongeServerAccess(final Game game) { 26 | this.game = game; 27 | } 28 | 29 | @Override 30 | public Collection levels() { 31 | if (!this.game.isServerAvailable()) { 32 | return List.of(); 33 | } 34 | return this.game.server().worldManager().worlds().stream() 35 | .map(level -> (ServerLevel) level) 36 | .toList(); 37 | } 38 | 39 | @Override 40 | public @Nullable ServerLevel level(final WorldIdentifier identifier) { 41 | return (ServerLevel) this.game.server().worldManager() 42 | .world(ResourceKey.of(identifier.namespace(), identifier.value())) 43 | .orElse(null); 44 | } 45 | 46 | @Override 47 | public @Nullable ServerPlayer player(UUID uuid) { 48 | return (ServerPlayer) this.game.server().player(uuid).orElse(null); 49 | } 50 | 51 | @Override 52 | public int maxPlayers() { 53 | return this.game.server().maxPlayers(); 54 | } 55 | 56 | @Override 57 | public void blockSleep() { 58 | ((SleepBlockingMinecraftServer) this.game.server()).squaremap$blockSleep(); 59 | } 60 | 61 | @Override 62 | public void allowSleep() { 63 | ((SleepBlockingMinecraftServer) this.game.server()).squaremap$allowSleep(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/command/SpongeCommander.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge.command; 2 | 3 | import net.kyori.adventure.audience.Audience; 4 | import net.kyori.adventure.audience.ForwardingAudience; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | import org.spongepowered.api.command.CommandCause; 9 | import xyz.jpenilla.squaremap.common.command.Commander; 10 | import xyz.jpenilla.squaremap.common.command.PlayerCommander; 11 | 12 | @DefaultQualifier(NonNull.class) 13 | public class SpongeCommander implements Commander, ForwardingAudience.Single { 14 | private final CommandCause cause; 15 | 16 | private SpongeCommander(final CommandCause cause) { 17 | this.cause = cause; 18 | } 19 | 20 | @Override 21 | public Audience audience() { 22 | return this.cause.audience(); 23 | } 24 | 25 | @Override 26 | public boolean hasPermission(final String permission) { 27 | return this.cause.hasPermission(permission); 28 | } 29 | 30 | public CommandCause cause() { 31 | return this.cause; 32 | } 33 | 34 | @Override 35 | public Object commanderId() { 36 | return this.cause.root(); 37 | } 38 | 39 | public static SpongeCommander from(final CommandCause cause) { 40 | if (cause.root() instanceof ServerPlayer) { 41 | return new Player(cause); 42 | } 43 | return new SpongeCommander(cause); 44 | } 45 | 46 | public static final class Player extends SpongeCommander implements PlayerCommander { 47 | private Player(final CommandCause stack) { 48 | super(stack); 49 | } 50 | 51 | @Override 52 | public ServerPlayer player() { 53 | return (ServerPlayer) this.cause().root(); 54 | } 55 | 56 | @Override 57 | public Object commanderId() { 58 | return this.player().getGameProfile().getId(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/config/SpongeAdvanced.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge.config; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import xyz.jpenilla.squaremap.common.config.Advanced; 5 | 6 | @SuppressWarnings("unused") 7 | public final class SpongeAdvanced { 8 | private SpongeAdvanced() { 9 | } 10 | 11 | public static boolean CHUNK_GENERATION = true; 12 | public static boolean CHUNK_LOAD = false; 13 | 14 | public static boolean BLOCK_PLACE = true; 15 | public static boolean BLOCK_BREAK = true; 16 | public static boolean BLOCK_MODIFY = true; 17 | public static boolean BLOCK_GROWTH = true; 18 | public static boolean BLOCK_DECAY = true; 19 | 20 | public static boolean LIQUID_SPREAD = true; 21 | public static boolean LIQUID_DECAY = true; 22 | 23 | private static boolean listenerEnabled(final @NonNull String key, final boolean def) { 24 | return Advanced.config().getBoolean("settings.map-update-triggers." + key, def); 25 | } 26 | 27 | private static void listenerToggles() { 28 | CHUNK_GENERATION = listenerEnabled("chunk-generation", CHUNK_GENERATION); 29 | CHUNK_LOAD = listenerEnabled("chunk-load", CHUNK_LOAD); 30 | 31 | BLOCK_PLACE = listenerEnabled("block-place", BLOCK_PLACE); 32 | BLOCK_BREAK = listenerEnabled("block-break", BLOCK_BREAK); 33 | BLOCK_MODIFY = listenerEnabled("block-modify", BLOCK_MODIFY); 34 | BLOCK_GROWTH = listenerEnabled("block-growth", BLOCK_GROWTH); 35 | BLOCK_DECAY = listenerEnabled("block-decay", BLOCK_DECAY); 36 | 37 | LIQUID_SPREAD = listenerEnabled("liquid-spread", LIQUID_SPREAD); 38 | LIQUID_DECAY = listenerEnabled("liquid-decay", LIQUID_DECAY); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/data/SpongeMapWorld.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge.data; 2 | 3 | import com.google.inject.assistedinject.Assisted; 4 | import com.google.inject.assistedinject.AssistedInject; 5 | import java.time.Duration; 6 | import net.minecraft.server.level.ServerLevel; 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | import org.checkerframework.framework.qual.DefaultQualifier; 9 | import org.spongepowered.api.Game; 10 | import org.spongepowered.api.scheduler.ScheduledTask; 11 | import org.spongepowered.api.scheduler.Task; 12 | import org.spongepowered.plugin.PluginContainer; 13 | import xyz.jpenilla.squaremap.common.config.ConfigManager; 14 | import xyz.jpenilla.squaremap.common.data.DirectoryProvider; 15 | import xyz.jpenilla.squaremap.common.data.MapWorldInternal; 16 | import xyz.jpenilla.squaremap.common.task.TaskFactory; 17 | import xyz.jpenilla.squaremap.common.task.render.RenderFactory; 18 | 19 | @DefaultQualifier(NonNull.class) 20 | public final class SpongeMapWorld extends MapWorldInternal { 21 | private final ScheduledTask updateMarkers; 22 | 23 | @AssistedInject 24 | private SpongeMapWorld( 25 | @Assisted final ServerLevel level, 26 | final RenderFactory renderFactory, 27 | final DirectoryProvider directoryProvider, 28 | final Game game, 29 | final PluginContainer pluginContainer, 30 | final ConfigManager configManager, 31 | final TaskFactory taskFactory 32 | ) { 33 | super(level, renderFactory, directoryProvider, configManager); 34 | 35 | this.updateMarkers = game.server().scheduler().submit( 36 | Task.builder() 37 | .plugin(pluginContainer) 38 | .delay(Duration.ofSeconds(5)) 39 | .interval(Duration.ofSeconds(this.config().MARKER_API_UPDATE_INTERVAL_SECONDS)) 40 | .execute(taskFactory.createUpdateMarkers(this)) 41 | .build() 42 | ); 43 | } 44 | 45 | @Override 46 | public void shutdown() { 47 | this.updateMarkers.cancel(); 48 | super.shutdown(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/listener/WorldLoadListener.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge.listener; 2 | 3 | import com.google.inject.Inject; 4 | import net.minecraft.server.level.ServerLevel; 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | import org.checkerframework.framework.qual.DefaultQualifier; 7 | import org.spongepowered.api.event.Listener; 8 | import org.spongepowered.api.event.Order; 9 | import org.spongepowered.api.event.world.LoadWorldEvent; 10 | import org.spongepowered.api.event.world.UnloadWorldEvent; 11 | import xyz.jpenilla.squaremap.common.WorldManagerImpl; 12 | 13 | @DefaultQualifier(NonNull.class) 14 | public final class WorldLoadListener { 15 | private final WorldManagerImpl worldManager; 16 | 17 | @Inject 18 | private WorldLoadListener(final WorldManagerImpl worldManager) { 19 | this.worldManager = worldManager; 20 | } 21 | 22 | @Listener(order = Order.EARLY) 23 | public void worldLoad(final LoadWorldEvent event) { 24 | this.worldManager.initWorld((ServerLevel) event.world()); 25 | } 26 | 27 | @Listener(order = Order.LATE) 28 | public void worldUnload(final UnloadWorldEvent event) { 29 | this.worldManager.worldUnloaded((ServerLevel) event.world()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/mixin/ChunkMapAccess.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge.mixin; 2 | 3 | import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; 4 | import java.util.Optional; 5 | import java.util.concurrent.CompletableFuture; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.server.level.ChunkHolder; 8 | import net.minecraft.server.level.ChunkMap; 9 | import net.minecraft.world.level.ChunkPos; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.gen.Accessor; 12 | import org.spongepowered.asm.mixin.gen.Invoker; 13 | 14 | @Mixin(ChunkMap.class) 15 | public interface ChunkMapAccess extends xyz.jpenilla.squaremap.common.util.ChunkMapAccess { 16 | @Invoker("getVisibleChunkIfPresent") 17 | @Override 18 | ChunkHolder squaremap$getVisibleChunkIfPresent(long pos); 19 | 20 | @Invoker("readChunk") 21 | @Override 22 | CompletableFuture> squaremap$readChunk(ChunkPos pos); 23 | 24 | @Accessor("pendingUnloads") 25 | @Override 26 | Long2ObjectLinkedOpenHashMap squaremap$pendingUnloads(); 27 | } 28 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/mixin/MainMixin.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge.mixin; 2 | 3 | import net.minecraft.server.Main; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 8 | import xyz.jpenilla.squaremap.sponge.SquaremapSpongeBootstrap; 9 | 10 | @Mixin(Main.class) 11 | abstract class MainMixin { 12 | @Inject( 13 | method = "main", 14 | at = @At( 15 | value = "INVOKE", 16 | target = "Lnet/minecraft/server/packs/repository/ServerPacksSource;createPackRepository(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;)Lnet/minecraft/server/packs/repository/PackRepository;" 17 | ) 18 | ) 19 | private static void startSquaremap(final String[] $$0, final CallbackInfo ci) { 20 | // currently we want to init before command registration, but when it is safe to classload mc. 21 | // I couldn't find the right event for that, and didn't feel like refactoring things for Sponge, 22 | // so a Mixin will work for now 23 | SquaremapSpongeBootstrap.instance.init(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/mixin/SleepBlockingMinecraftServerMixin.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge.mixin; 2 | 3 | import net.minecraft.server.MinecraftServer; 4 | import net.minecraft.server.players.PlayerList; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Redirect; 9 | import xyz.jpenilla.squaremap.common.util.SleepBlockingMinecraftServer; 10 | 11 | @Mixin(MinecraftServer.class) 12 | abstract class SleepBlockingMinecraftServerMixin implements SleepBlockingMinecraftServer { 13 | @Unique 14 | private volatile boolean allowSleep = true; 15 | 16 | // Use redirect instead of WrapOperation as Sponge doesn't have MixinExtras 17 | @Redirect( 18 | method = "tickServer", 19 | at = @At( 20 | value = "INVOKE", 21 | target = "Lnet/minecraft/server/players/PlayerList;getPlayerCount()I" 22 | ) 23 | ) 24 | private int allowSleep(final PlayerList instance) { 25 | return this.allowSleep ? instance.getPlayerCount() : 1; 26 | } 27 | 28 | @Override 29 | public void squaremap$blockSleep() { 30 | this.allowSleep = false; 31 | } 32 | 33 | @Override 34 | public void squaremap$allowSleep() { 35 | this.allowSleep = true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/network/SpongeNetworking.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge.network; 2 | 3 | import com.google.inject.Inject; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.checkerframework.framework.qual.DefaultQualifier; 8 | import org.spongepowered.api.ResourceKey; 9 | import org.spongepowered.api.event.Listener; 10 | import org.spongepowered.api.event.entity.ChangeEntityWorldEvent; 11 | import org.spongepowered.api.event.network.ServerSideConnectionEvent; 12 | import org.spongepowered.api.network.EngineConnectionState; 13 | import org.spongepowered.api.network.channel.ChannelBuf; 14 | import org.spongepowered.api.network.channel.ChannelManager; 15 | import org.spongepowered.api.network.channel.raw.RawDataChannel; 16 | import org.spongepowered.api.profile.GameProfile; 17 | import xyz.jpenilla.squaremap.common.network.NetworkingHandler; 18 | import xyz.jpenilla.squaremap.common.util.Util; 19 | 20 | @DefaultQualifier(NonNull.class) 21 | public final class SpongeNetworking { 22 | private final NetworkingHandler networking; 23 | 24 | @Inject 25 | private SpongeNetworking( 26 | final NetworkingHandler networking, 27 | final ChannelManager channelManager 28 | ) { 29 | this.networking = networking; 30 | channelManager.ofType( 31 | (ResourceKey) (Object) NetworkingHandler.CHANNEL, 32 | RawDataChannel.class 33 | ).play().addHandler(EngineConnectionState.Game.class, this::handleIncoming); 34 | } 35 | 36 | private void handleIncoming(final ChannelBuf data, final EngineConnectionState.Game state) { 37 | this.networking.handleIncoming((ServerPlayer) state.player(), Util.raw((FriendlyByteBuf) data), map -> true); 38 | } 39 | 40 | @Listener 41 | public void changeWorld(final ChangeEntityWorldEvent.Post event) { 42 | if (event.entity() instanceof ServerPlayer player) { 43 | this.networking.worldChanged(player); 44 | } 45 | } 46 | 47 | @Listener 48 | public void disconnect(final ServerSideConnectionEvent.Disconnect event) { 49 | event.profile().map(GameProfile::uuid).ifPresent(this.networking::onDisconnect); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sponge/src/main/java/xyz/jpenilla/squaremap/sponge/util/SpongeVectors.java: -------------------------------------------------------------------------------- 1 | package xyz.jpenilla.squaremap.sponge.util; 2 | 3 | import org.spongepowered.math.vector.Vector3i; 4 | import xyz.jpenilla.squaremap.common.data.ChunkCoordinate; 5 | import xyz.jpenilla.squaremap.common.util.Numbers; 6 | 7 | public final class SpongeVectors { 8 | private SpongeVectors() { 9 | } 10 | 11 | public static ChunkCoordinate fromChunkPos(final Vector3i chunkPos) { 12 | return new ChunkCoordinate(chunkPos.x(), chunkPos.z()); 13 | } 14 | 15 | public static ChunkCoordinate fromBlockPos(final Vector3i blockPos) { 16 | return new ChunkCoordinate( 17 | Numbers.blockToChunk(blockPos.x()), 18 | Numbers.blockToChunk(blockPos.z()) 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sponge/src/main/resources/squaremap-sponge.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v1 named 2 | 3 | # below are manually copied from squaremap-common-at.cfg, be sure to keep up to date with changes 4 | accessible method net/minecraft/world/level/chunk/PalettedContainer get (I)Ljava/lang/Object; 5 | -------------------------------------------------------------------------------- /sponge/src/main/resources/squaremap-sponge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8.4", 4 | "package": "xyz.jpenilla.squaremap.sponge.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "ChunkMapAccess", 8 | "MainMixin", 9 | "SleepBlockingMinecraftServerMixin" 10 | ], 11 | "injectors": { 12 | "defaultRequire": 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies (bun install) 2 | node_modules 3 | 4 | # output 5 | out 6 | dist 7 | *.tgz 8 | 9 | # code coverage 10 | coverage 11 | *.lcov 12 | 13 | # logs 14 | logs 15 | _.log 16 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 17 | 18 | # dotenv environment variable files 19 | .env 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | .env.local 24 | 25 | # caches 26 | .eslintcache 27 | .cache 28 | *.tsbuildinfo 29 | 30 | # IntelliJ based IDEs 31 | .idea 32 | 33 | # Finder (MacOS) folder config 34 | .DS_Store 35 | -------------------------------------------------------------------------------- /web/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | 5 | # Package Managers 6 | package-lock.json 7 | pnpm-lock.yaml 8 | yarn.lock 9 | bun.lock 10 | -------------------------------------------------------------------------------- /web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "useTabs": false, 4 | "tabWidth": 4, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /web/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "eslint/config"; 2 | import js from "@eslint/js"; 3 | import globals from "globals"; 4 | 5 | export default defineConfig([ 6 | { 7 | files: ["**/*.js"], 8 | plugins: { 9 | js, 10 | }, 11 | extends: ["js/recommended"], 12 | rules: { 13 | "no-unused-vars": "warn", 14 | "no-undef": "warn", 15 | }, 16 | languageOptions: { 17 | globals: { 18 | ...globals.browser, 19 | }, 20 | }, 21 | }, 22 | ]); 23 | -------------------------------------------------------------------------------- /web/global.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/global.d.ts -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "scripts": { 4 | "dev": "vite dev --host", 5 | "build": "vite build --emptyOutDir", 6 | "preview": "vite preview", 7 | "format": "prettier --write .", 8 | "lint": "prettier --check . && eslint ." 9 | }, 10 | "devDependencies": { 11 | "@eslint/js": "^9.25.1", 12 | "@types/leaflet": "^1.9.17", 13 | "eslint": "^9.25.1", 14 | "globals": "^16.0.0", 15 | "leaflet": "^1.9.4", 16 | "prettier": "^3.5.3", 17 | "vite": "^6.3.2" 18 | }, 19 | "private": true, 20 | "type": "module" 21 | } 22 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/images/armor/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/0.png -------------------------------------------------------------------------------- /web/public/images/armor/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/1.png -------------------------------------------------------------------------------- /web/public/images/armor/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/10.png -------------------------------------------------------------------------------- /web/public/images/armor/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/11.png -------------------------------------------------------------------------------- /web/public/images/armor/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/12.png -------------------------------------------------------------------------------- /web/public/images/armor/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/13.png -------------------------------------------------------------------------------- /web/public/images/armor/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/14.png -------------------------------------------------------------------------------- /web/public/images/armor/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/15.png -------------------------------------------------------------------------------- /web/public/images/armor/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/16.png -------------------------------------------------------------------------------- /web/public/images/armor/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/17.png -------------------------------------------------------------------------------- /web/public/images/armor/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/18.png -------------------------------------------------------------------------------- /web/public/images/armor/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/19.png -------------------------------------------------------------------------------- /web/public/images/armor/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/2.png -------------------------------------------------------------------------------- /web/public/images/armor/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/20.png -------------------------------------------------------------------------------- /web/public/images/armor/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/3.png -------------------------------------------------------------------------------- /web/public/images/armor/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/4.png -------------------------------------------------------------------------------- /web/public/images/armor/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/5.png -------------------------------------------------------------------------------- /web/public/images/armor/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/6.png -------------------------------------------------------------------------------- /web/public/images/armor/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/7.png -------------------------------------------------------------------------------- /web/public/images/armor/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/8.png -------------------------------------------------------------------------------- /web/public/images/armor/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/armor/9.png -------------------------------------------------------------------------------- /web/public/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/clear.png -------------------------------------------------------------------------------- /web/public/images/end_sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/end_sky.png -------------------------------------------------------------------------------- /web/public/images/foliage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/foliage.png -------------------------------------------------------------------------------- /web/public/images/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/grass.png -------------------------------------------------------------------------------- /web/public/images/health/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/0.png -------------------------------------------------------------------------------- /web/public/images/health/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/1.png -------------------------------------------------------------------------------- /web/public/images/health/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/10.png -------------------------------------------------------------------------------- /web/public/images/health/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/11.png -------------------------------------------------------------------------------- /web/public/images/health/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/12.png -------------------------------------------------------------------------------- /web/public/images/health/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/13.png -------------------------------------------------------------------------------- /web/public/images/health/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/14.png -------------------------------------------------------------------------------- /web/public/images/health/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/15.png -------------------------------------------------------------------------------- /web/public/images/health/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/16.png -------------------------------------------------------------------------------- /web/public/images/health/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/17.png -------------------------------------------------------------------------------- /web/public/images/health/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/18.png -------------------------------------------------------------------------------- /web/public/images/health/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/19.png -------------------------------------------------------------------------------- /web/public/images/health/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/2.png -------------------------------------------------------------------------------- /web/public/images/health/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/20.png -------------------------------------------------------------------------------- /web/public/images/health/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/3.png -------------------------------------------------------------------------------- /web/public/images/health/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/4.png -------------------------------------------------------------------------------- /web/public/images/health/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/5.png -------------------------------------------------------------------------------- /web/public/images/health/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/6.png -------------------------------------------------------------------------------- /web/public/images/health/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/7.png -------------------------------------------------------------------------------- /web/public/images/health/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/8.png -------------------------------------------------------------------------------- /web/public/images/health/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/health/9.png -------------------------------------------------------------------------------- /web/public/images/icon/blue-cube-smol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/icon/blue-cube-smol.png -------------------------------------------------------------------------------- /web/public/images/icon/green-cube-smol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/icon/green-cube-smol.png -------------------------------------------------------------------------------- /web/public/images/icon/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/icon/player.png -------------------------------------------------------------------------------- /web/public/images/icon/purple-cube-smol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/icon/purple-cube-smol.png -------------------------------------------------------------------------------- /web/public/images/icon/red-cube-smol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/icon/red-cube-smol.png -------------------------------------------------------------------------------- /web/public/images/icon/spawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/icon/spawn.png -------------------------------------------------------------------------------- /web/public/images/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/link.png -------------------------------------------------------------------------------- /web/public/images/nether_sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/nether_sky.png -------------------------------------------------------------------------------- /web/public/images/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/og.png -------------------------------------------------------------------------------- /web/public/images/overworld_sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/overworld_sky.png -------------------------------------------------------------------------------- /web/public/images/pinned.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/pinned.png -------------------------------------------------------------------------------- /web/public/images/unpinned.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpenilla/squaremap/b12844114310e0fbc5f6d57428faaed2e7d49734/web/public/images/unpinned.png -------------------------------------------------------------------------------- /web/src/js/Sidebar.js: -------------------------------------------------------------------------------- 1 | import { Pin } from "./util/Pin.js"; 2 | import { Fieldset } from "./util/Fieldset.js"; 3 | import { S } from "./Squaremap.js"; 4 | 5 | class Sidebar { 6 | /** @type {HTMLDivElement} */ 7 | sidebar; 8 | /** @type {boolean} */ 9 | showSidebar; 10 | /** @type {Pin} */ 11 | pin; 12 | /** @type {Fieldset} */ 13 | worlds; 14 | /** @type {Fieldset} */ 15 | players; 16 | 17 | /** 18 | * @param {Settings_UI_Sidebar} json 19 | * @param {boolean} show 20 | */ 21 | constructor(json, show) { 22 | this.sidebar = S.createElement("div", "sidebar", this); 23 | this.showSidebar = show; 24 | if (!show) { 25 | this.sidebar.style.display = "none"; 26 | } 27 | this.sidebar.addEventListener("click", (e) => { 28 | S.playerList.followPlayerMarker(null); 29 | e.stopPropagation(); 30 | }); 31 | document.body.appendChild(this.sidebar); 32 | 33 | this.pin = new Pin(json.pinned === "pinned"); 34 | this.show(this.pin.pinned); 35 | if (json.pinned !== "hide") { 36 | this.sidebar.appendChild(this.pin.element); 37 | } 38 | 39 | this.worlds = new Fieldset("worlds", json.world_list_label); 40 | this.sidebar.appendChild(this.worlds.element); 41 | 42 | this.players = new Fieldset("players", json.player_list_label.replace(/{cur}/g, 0).replace(/{max}/g, 0)); 43 | this.sidebar.appendChild(this.players.element); 44 | 45 | this.sidebar.onmouseleave = () => { 46 | if (!this.pin.pinned) { 47 | this.show(false); 48 | } 49 | }; 50 | this.sidebar.onmouseenter = () => { 51 | if (!this.pin.pinned) { 52 | this.show(true); 53 | } 54 | }; 55 | 56 | document.addEventListener("click", (e) => { 57 | if (!this.sidebar.contains(e.target) && !this.pin.pinned && this.sidebar.className === "show") { 58 | this.show(false); 59 | } 60 | }); 61 | } 62 | show(show) { 63 | this.sidebar.className = show ? "show" : ""; 64 | } 65 | remove() { 66 | this.sidebar.remove(); 67 | } 68 | } 69 | 70 | export { Sidebar }; 71 | -------------------------------------------------------------------------------- /web/src/js/SquaremapTileLayer.js: -------------------------------------------------------------------------------- 1 | import L from "leaflet"; 2 | 3 | export const SquaremapTileLayer = L.TileLayer.extend({ 4 | // @method createTile(coords: Object, done?: Function): HTMLElement 5 | // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 6 | // to return an `` HTML element with the appropriate image URL given `coords`. The `done` 7 | // callback is called when the tile has been loaded. 8 | createTile: function (coords, done) { 9 | var tile = document.createElement("img"); 10 | 11 | L.DomEvent.on(tile, "load", () => { 12 | //Once image has loaded revoke the object URL as we don't need it anymore 13 | URL.revokeObjectURL(tile.src); 14 | this._tileOnLoad(done, tile); 15 | }); 16 | L.DomEvent.on(tile, "error", L.Util.bind(this._tileOnError, this, done, tile)); 17 | 18 | if (this.options.crossOrigin || this.options.crossOrigin === "") { 19 | tile.crossOrigin = this.options.crossOrigin === true ? "" : this.options.crossOrigin; 20 | } 21 | 22 | tile.alt = ""; 23 | tile.setAttribute("role", "presentation"); 24 | 25 | //Retrieve image via a fetch instead of just setting the src 26 | //This works around the fact that browsers usually don't make a request for an image that was previously loaded, 27 | //without resorting to changing the URL (which would break caching). 28 | fetch(this.getTileUrl(coords)) 29 | .then((res) => { 30 | //Call leaflet's error handler if request fails for some reason 31 | if (!res.ok) { 32 | this._tileOnError(done, tile, null); 33 | return; 34 | } 35 | 36 | //Get image data and convert into object URL so it can be used as a src 37 | //Leaflet's onload listener will take it from here 38 | res.blob().then((blob) => (tile.src = URL.createObjectURL(blob))); 39 | }) 40 | .catch(() => this._tileOnError(done, tile, null)); 41 | 42 | return tile; 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /web/src/js/UICoordinates.js: -------------------------------------------------------------------------------- 1 | import { S } from "./Squaremap.js"; 2 | import L from "leaflet"; 3 | 4 | class UICoordinates { 5 | /** 6 | * @param {Settings_UI_Coordinates} json 7 | * @param {boolean} show 8 | */ 9 | constructor(json, show) { 10 | const Coords = L.Control.extend({ 11 | _container: null, 12 | options: { 13 | position: "bottomleft", 14 | }, 15 | onAdd: function () { 16 | const coords = L.DomUtil.create("div", "leaflet-control-layers coordinates"); 17 | this._coords = coords; 18 | if (!show) { 19 | this._coords.style.display = "none"; 20 | } 21 | return coords; 22 | }, 23 | update: function (html, point) { 24 | this.x = point == null ? "---" : Math.floor(point.x); 25 | this.z = point == null ? "---" : Math.floor(point.y); 26 | if (html != null) { 27 | this._coords.innerHTML = html.replace(/{x}/g, this.x).replace(/{z}/g, this.z); 28 | } 29 | }, 30 | }); 31 | this.showCoordinates = show; 32 | this.html = json.html == null ? "undefined" : json.html; 33 | this.coords = new Coords(); 34 | S.map.addControl(this.coords).addEventListener("mousemove", (event) => { 35 | if (S.worldList.curWorld != null) { 36 | this.coords.update(this.html, S.toPoint(event.latlng)); 37 | } 38 | }); 39 | if (!json.enabled) { 40 | this.coords._coords.style.display = "none"; 41 | } 42 | this.coords.update(this.html); 43 | } 44 | } 45 | 46 | export { UICoordinates }; 47 | -------------------------------------------------------------------------------- /web/src/js/UILink.js: -------------------------------------------------------------------------------- 1 | import { S } from "./Squaremap.js"; 2 | import L from "leaflet"; 3 | 4 | class UILink { 5 | /** 6 | * @param {Settings_UI_Link} json 7 | * @param {boolean} show 8 | */ 9 | constructor(json, show) { 10 | const Link = L.Control.extend({ 11 | _container: null, 12 | options: { 13 | position: "bottomleft", 14 | }, 15 | onAdd: function () { 16 | const link = L.DomUtil.create("div", "leaflet-control-layers link"); 17 | this._link = link; 18 | this._link.innerHTML = ``; 19 | this._link.onclick = async () => { 20 | const url = S.worldList.curWorld == null ? "" : S.getUrlFromView(); 21 | window.history.replaceState(null, "", url); 22 | await navigator.clipboard.writeText(window.location.href); 23 | }; 24 | if (!show) { 25 | this._link.style.display = "none"; 26 | } 27 | return link; 28 | }, 29 | }); 30 | this.showLinkButton = show; 31 | this.link = new Link(); 32 | if (!json.enabled) { 33 | this.link._link.style.display = "none"; 34 | } 35 | S.map.addControl(this.link); 36 | } 37 | } 38 | 39 | export { UILink }; 40 | -------------------------------------------------------------------------------- /web/src/js/types.ts: -------------------------------------------------------------------------------- 1 | export interface Settings { 2 | static: boolean; 3 | worlds: Settings_World[]; 4 | ui: Settings_UI; 5 | } 6 | 7 | export interface Settings_World { 8 | name: string; 9 | display_name: string; 10 | icon: string; 11 | type: string; 12 | order: number; 13 | } 14 | 15 | export interface Settings_UI_Coordinates { 16 | enabled: boolean; 17 | html: string; 18 | } 19 | 20 | export interface Settings_UI_Link { 21 | enabled: boolean; 22 | } 23 | 24 | export interface Settings_UI_Sidebar { 25 | pinned: string; 26 | player_list_label: string; 27 | world_list_label: string; 28 | } 29 | 30 | export interface Settings_UI { 31 | title: string; 32 | coordinates: Settings_UI_Coordinates; 33 | link: Settings_UI_Link; 34 | sidebar: Settings_UI_Sidebar; 35 | } 36 | 37 | export interface WorldSettings_Spawn { 38 | x: number; 39 | z: number; 40 | } 41 | 42 | export interface WorldSettings_PlayerTracker_Nameplates { 43 | enabled: boolean; 44 | show_heads: boolean; 45 | heads_url: string; 46 | show_armor: boolean; 47 | show_health: boolean; 48 | } 49 | 50 | export interface WorldSettings_PlayerTracker { 51 | enabled: boolean; 52 | update_interval: number; 53 | label: string; 54 | show_controls: boolean; 55 | default_hidden: boolean; 56 | priority: number; 57 | z_index: number; 58 | nameplates: WorldSettings_PlayerTracker_Nameplates; 59 | } 60 | 61 | export interface WorldSettings_Zoom { 62 | max: number; 63 | def: number; 64 | extra: number; 65 | } 66 | 67 | export interface WorldSettings { 68 | spawn: WorldSettings_Spawn; 69 | player_tracker: WorldSettings_PlayerTracker; 70 | zoom: WorldSettings_Zoom; 71 | marker_update_interval: number; 72 | tiles_update_interval: number; 73 | } 74 | 75 | export interface PlayerData { 76 | name: string; 77 | display_name: string; 78 | uuid: string; 79 | world: string; 80 | x: number; 81 | y: number; 82 | z: number; 83 | yaw: number; 84 | armor: number; 85 | health: number; 86 | } 87 | 88 | export interface PlayersData { 89 | players: PlayerData[]; 90 | max: number; 91 | } 92 | -------------------------------------------------------------------------------- /web/src/js/util/Fieldset.js: -------------------------------------------------------------------------------- 1 | import { S } from "../Squaremap.js"; 2 | 3 | class Fieldset { 4 | /** @type {HTMLFieldSetElement} */ 5 | element; 6 | /** @type {HTMLLegendElement} */ 7 | legend; 8 | 9 | constructor(id, title) { 10 | this.element = S.createElement("fieldset", id); 11 | this.legend = S.createTextElement("legend", title); 12 | this.element.appendChild(this.legend); 13 | } 14 | } 15 | 16 | export { Fieldset }; 17 | -------------------------------------------------------------------------------- /web/src/js/util/Pin.js: -------------------------------------------------------------------------------- 1 | import { S } from "../Squaremap.js"; 2 | 3 | class Pin { 4 | /** @type {boolean} */ 5 | pinned; 6 | /** @type {HTMLImageElement} */ 7 | element; 8 | 9 | /** 10 | * @param {boolean} def 11 | */ 12 | constructor(def) { 13 | this.pinned = def; 14 | 15 | this.element = S.createElement("img", "pin", this); 16 | 17 | this.element.onclick = () => this.toggle(); 18 | 19 | this.pin(this.pinned); 20 | } 21 | toggle() { 22 | this.pin(!this.pinned); 23 | } 24 | pin(pin) { 25 | this.pinned = pin; 26 | this.element.className = pin ? "pinned" : "unpinned"; 27 | this.element.src = `images/${this.element.className}.png`; 28 | } 29 | } 30 | 31 | export { Pin }; 32 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Environment setup & latest features 4 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "allowJs": true, 9 | 10 | // Bundler mode 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "verbatimModuleSyntax": true, 14 | "noEmit": true, 15 | 16 | // Best practices 17 | "strict": true, 18 | "skipLibCheck": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "noUncheckedIndexedAccess": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /web/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | 3 | export default defineConfig({ 4 | base: "./", 5 | build: { 6 | target: "esnext", 7 | outDir: "../common/build/web", 8 | sourcemap: true, 9 | }, 10 | }); 11 | --------------------------------------------------------------------------------