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 |
--------------------------------------------------------------------------------