### Install Project Dependencies with Bun Source: https://docs.canvasmc.io/guides/developers/contributing/docs Installs all necessary project dependencies using the Bun package manager. This is a required step before starting development. ```bash bun install ``` -------------------------------- ### Start Development Server with Bun Source: https://docs.canvasmc.io/guides/developers/contributing/docs Starts the local development server using Bun. This command allows you to preview your changes in real-time at http://localhost:3000. ```bash bun dev ``` -------------------------------- ### EULA Configuration for CanvasMC Server Source: https://docs.canvasmc.io/guides/getting-started/installation Configuration snippet showing the required EULA agreement modification in the eula.txt file. Change the eula parameter from false to true to accept Minecraft's End User License Agreement and allow the server to start. This file is automatically generated on first server launch. ```properties # By changing the setting below to TRUE, you agree to our EULA (https://aka.ms/MinecraftEULA). # Thu Jan 1 00:00:00 UTC 1970 eula=false eula=true ``` -------------------------------- ### Clone CanvasMC Repository Source: https://docs.canvasmc.io/guides/developers/contributing/canvas This command clones the CanvasMC repository from GitHub and navigates into the project directory. Ensure Git is installed on your system. ```bash git clone https://github.com/CraftCanvasMC/Canvas.git cd Canvas ``` -------------------------------- ### Example YAML Configuration Source: https://docs.canvasmc.io/guides/developers/contributing/canvas A sample YAML configuration file demonstrating a field 'max-tick-rate' with a negative value, which is intended to fail validation. ```yaml # config.yml max-tick-rate: -1 ``` -------------------------------- ### Example YAML Output with Experimental Annotation Source: https://docs.canvasmc.io/guides/developers/contributing/canvas Illustrates the resulting YAML output for a field annotated with `@Experimental`. A comment block is prepended to the field's entry. ```yaml ## === EXPERIMENTAL FEATURE === example: false ``` -------------------------------- ### CanvasMC Server Startup Script - Windows and Linux Source: https://docs.canvasmc.io/guides/getting-started/installation Platform-specific startup scripts that launch the CanvasMC server with optimized JVM memory allocation (2GB minimum, 4GB maximum) and vector module support. Use the Windows batch version (.bat) or Linux bash version (.sh) depending on your operating system. Customize the Xms and Xmx values based on available server RAM. ```batch java -Xms2G -Xmx4G --add-modules=jdk.incubator.vector -jar canvas.jar --nogui pause >nul 2>&1 ``` ```bash #!/bin/bash java -Xms2G -Xmx4G --add-modules=jdk.incubator.vector -jar canvas.jar --nogui pause > /dev/null 2>&1 ``` -------------------------------- ### Create Experimental Annotation and Handler in Java Source: https://docs.canvasmc.io/guides/developers/contributing/canvas Example demonstrating how to define a custom annotation `@Experimental` and its corresponding handler `ExperimentalProcessor`. The handler adds a comment to the YAML output for fields marked with `@Experimental`. ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Experimental {} ``` ```java @RegisteredHandler("experimental") public class ExperimentalProcessor implements AnnotationContextProvider { @Override public void apply(final @NotNull StringWriter yamlWriter, final String indent, final String fullKey, final Field field, final @NotNull Experimental annotation) { yamlWriter.append(indent).append("## ").append("=== EXPERIMENTAL FEATURE ===").append("\n"); } public Class experimental() { return Experimental.class; } } ``` -------------------------------- ### GET /builds Source: https://docs.canvasmc.io/guides/developers/rest-api Fetches a list of builds based on Minecraft version and experimental status. Builds are sorted by build number. ```APIDOC ## GET /builds ### Description Fetches all builds for a specific Minecraft version. If `experimental` is true, experimental builds are included. Otherwise, only stable builds are returned. ### Method GET ### Endpoint `/builds` ### Query Parameters - **minecraftVersion** (string) - Optional - The target Minecraft version. If blank, all builds are fetched. - **experimental** (boolean) - Optional - Whether to include experimental builds. Defaults to false. ### Response #### Success Response (200) - **builds** (array) - A list of build objects. - **buildNumber** (integer) - The build number. - **minecraftVersion** (string) - The Minecraft version this build is for. - **isExperimental** (boolean) - Indicates if the build is experimental. - **commitHash** (string) - The Git commit hash associated with this build. - **timestamp** (string) - The ISO 8601 timestamp of when the build was created. #### Response Example ```json [ { "buildNumber": 123, "minecraftVersion": "1.19.4", "isExperimental": false, "commitHash": "a1b2c3d4e5f6", "timestamp": "2023-01-01T12:00:00Z" } ] ``` ``` -------------------------------- ### GET /builds/latest Source: https://docs.canvasmc.io/guides/developers/rest-api Retrieves the latest available build, optionally including experimental builds. ```APIDOC ## GET /builds/latest ### Description Returns the latest build across all Minecraft versions. If `experimental` is true, experimental builds are considered. ### Method GET ### Endpoint `/builds/latest` ### Query Parameters - **experimental** (boolean) - Optional - Whether to allow experimental builds to be returned. Defaults to false. ### Response #### Success Response (200) - **buildNumber** (integer) - The build number. - **minecraftVersion** (string) - The Minecraft version this build is for. - **isExperimental** (boolean) - Indicates if the build is experimental. - **commitHash** (string) - The Git commit hash associated with this build. - **timestamp** (string) - The ISO 8601 timestamp of when the build was created. #### Response Example ```json { "buildNumber": 456, "minecraftVersion": "1.20", "isExperimental": true, "commitHash": "f6e5d4c3b2a1", "timestamp": "2023-02-01T13:00:00Z" } ``` ``` -------------------------------- ### Apply Canvas Patches Source: https://docs.canvasmc.io/guides/developers/contributing/canvas This command applies all necessary patches to the Canvas development environment using the Gradle wrapper. This step is crucial for building the environment and is recommended for plugin development with Canvas. ```bash ./gradlew applyAllPatches ``` -------------------------------- ### Avoid Imports: Use Fully Qualified Names in Java Source: https://docs.canvasmc.io/guides/developers/contributing/canvas This Java code demonstrates the preferred method of referencing classes by using their fully qualified names instead of traditional import statements. This practice aims to improve maintainability by preventing issues that arise from upstream updates to Minecraft, Paper, or Folia. The example shows how to reference `net.minecraft.server.level.ServerLevel` directly. ```java import net.minecraft.server.MinecraftServer; // Don't add imports here, use the fully qualified class name like bellow. public class SomeRandomVanillaClassExample { public final net.minecraft.server.level.ServerLevel world; // Canvas - add world } ``` -------------------------------- ### Add a Multi-Line Comment Annotation (Java) Source: https://docs.canvasmc.io/guides/developers/contributing/canvas Demonstrates adding a configuration option with a multi-line comment using the `@Comment` annotation. This allows for more detailed descriptions of configuration options. ```Java @Comment(value = { "This is super interesting stuff! I really don't know what I'm going to say since", "it's 2am at the time of writing this section of the docs! This is such a cool config!!" }) public boolean enableSuperCoolOptimization = false; ``` -------------------------------- ### Create a New Git Branch for Documentation Changes Source: https://docs.canvasmc.io/guides/developers/contributing/docs Creates a new Git branch from the 'main' branch, specifically for documentation feature development. This follows Git best practices for organizing contributions. ```git git checkout -b docs/your-feature-name ``` -------------------------------- ### Create a Boolean Runtime Modifier (Java) Source: https://docs.canvasmc.io/guides/developers/contributing/canvas Shows how to create a runtime modifier that sets all boolean fields matching a given pattern to 'true' in memory. This example demonstrates overriding disk-saved values for in-memory usage. ```Java new RuntimeModifier<>(boolean.class, (original) -> true) ``` -------------------------------- ### Define Inner Class for Associated Configurations (Java) Source: https://docs.canvasmc.io/guides/developers/contributing/canvas Demonstrates how to define an inner class to group associated configuration options. This is useful when a configuration has multiple related settings. ```Java public ExampleMultiConfig exampleMultiConfig = new ExampleMultiConfig(); public static class ExampleMultiConfig { public boolean enabled = false; public int count = 43; public float chance = 0.212; } ``` -------------------------------- ### GET /jd - Redirect to Canvas Javadocs Source: https://docs.canvasmc.io/guides/developers/rest-api Performs an HTTP redirect to the Javadocs page for a specified Canvas build version. Accepts optional query parameters for Minecraft version and experimental flag; defaults to the latest stable build if no parameters provided. Returns a redirect URL to the Maven Javadoc repository rather than JSON data. ```text https://maven.canvasmc.io/javadoc/snapshots/io/canvasmc/canvas/canvas-api/1.21.8-R0.1-SNAPSHOT ``` -------------------------------- ### Add a Single-Line Comment Annotation (Java) Source: https://docs.canvasmc.io/guides/developers/contributing/canvas Illustrates how to add a new configuration option with a single-line comment using the `@Comment` annotation. This provides a description for the configuration option. ```Java @Comment("This enables a super cool optimization that boosts performance 10000000%!") public boolean enableSuperCoolOptimization = false; ``` -------------------------------- ### Mark Multi-line Code Changes Source: https://docs.canvasmc.io/guides/developers/contributing/canvas This Java code snippet demonstrates how to mark multi-line code modifications within Canvas source files. It uses specific comments to delineate the start and end of changes, including a description of the patch. ```java public void baseTick() { // Canvas start - don't tick if we are spinning if (this.isSpinning) { this.becomeDizzy(); return; } // Canvas end - don't tick if we are spinning ... } ``` -------------------------------- ### Commit Documentation Changes with a Clear Message Source: https://docs.canvasmc.io/guides/developers/contributing/docs Commits staged changes to the Git repository with a descriptive message. The 'docs:' prefix helps categorize the commit as a documentation-related change. ```git git commit -m "docs: add contributing workflow details" ``` -------------------------------- ### Execute Patch Rebuilding Command Source: https://docs.canvasmc.io/guides/developers/contributing/canvas This command initiates the process of rebuilding patches for the Canvas project. It's typically run after making code modifications to ensure that changes are properly formatted into patches suitable for contribution. The script automatically detects modifications and applies the necessary Gradle tasks. For certain `build.gradle.kts` file changes, the `--force` flag might be required. ```shell ./rebuildPatches ``` -------------------------------- ### GET /jd Source: https://docs.canvasmc.io/guides/developers/rest-api Redirects to the Javadocs page for the latest Canvas build. Supports optional filtering by Minecraft version and experimental status. This endpoint performs an HTTP redirect rather than returning JSON data. ```APIDOC ## GET /jd ### Description Redirects to the Javadocs page for the Canvas API. Optionally filters by Minecraft version and experimental build status. Performs an HTTP redirect rather than returning JSON. ### Method GET ### Endpoint https://canvasmc.io/api/v2/jd ### Query Parameters - **version** (string) - Optional - The Minecraft version to use. Defaults to the version of the latest stable build if not provided - **experimental** (boolean) - Optional - Include experimental builds (true or false). Defaults to false ### Response #### HTTP Redirect (302/301) Redirects to the Javadocs page URL based on parameters. ### Response Example With `version=1.21.8` and `experimental=false`: ``` https://maven.canvasmc.io/javadoc/snapshots/io/canvasmc/canvas/canvas-api/1.21.8-R0.1-SNAPSHOT ``` ### Notes - If version is not provided, the endpoint uses the Minecraft version from the latest build (respecting the experimental flag) - This endpoint does not return JSON; it performs an HTTP redirect to the Javadocs page - Use this endpoint to quickly navigate to Javadocs without manually looking up build numbers - With no parameters specified, defaults to the latest stable API version ``` -------------------------------- ### GET /builds Source: https://docs.canvasmc.io/guides/developers/rest-api Returns a list of all recent Canvas builds. Supports filtering by Minecraft version and experimental status. The deprecated 'commit' property has been replaced with a 'commits' array containing full commit history. ```APIDOC ## GET /builds ### Description Returns a list of all recent Canvas builds with optional filtering by Minecraft version and experimental status. ### Method GET ### Endpoint https://canvasmc.io/api/v2/builds ### Query Parameters - **minecraft_version** (string) - Optional - Filter builds by Minecraft version - **experimental** (boolean) - Optional - Include experimental builds ### Response #### Success Response (200) - **buildNumber** (integer) - The build number identifier - **url** (string) - Jenkins build URL - **downloadUrl** (string) - Direct download URL for the build JAR - **minecraftVersion** (string) - Target Minecraft version (e.g., "1.21.4") - **timestamp** (integer) - Build timestamp in milliseconds - **isExperimental** (boolean) - Whether this is an experimental build - **commits** (array) - Array of commit objects with message and hash - **message** (string) - Commit message - **hash** (string) - Commit hash ### Response Example ```json [ { "buildNumber": 1337, "url": "https://jenkins.canvasmc.io/job/Canvas/1337/", "downloadUrl": "https://jenkins.canvasmc.io/job/Canvas/1337/artifact/canvas-server/build/libs/canvas-build.1337.jar", "minecraftVersion": "1.21.4", "timestamp": 1739339329161, "isExperimental": true, "commits": [ { "message": "Hello world!", "hash": "3768ac53eb2671853145bd077ade0579e13741ed" }, { "message": "Why are you reading this?", "hash": "895b307dcc7c6fbb040dc7bd26d9a754e03cf8c7" } ] } ] ``` ### Notes - The deprecated `commit` property has been replaced with the `commits` array - Returns an array of build objects, may contain multiple builds ``` -------------------------------- ### Rebuild Patches Script: Bash Script for Patch Management Source: https://docs.canvasmc.io/guides/developers/contributing/canvas This bash script automates the process of detecting and rebuilding file patches within the Canvas repository. It checks for changes in specified directories and modified Gradle build files, then executes corresponding `./gradlew` tasks. The script supports a `--force` flag to run all tasks regardless of detected changes and a `--gradle` flag to specifically run `rebuildFoliaSingleFilePatches`. It includes error handling for non-existent directories and provides verbose output during execution. ```bash #!/bin/bash set -e force_run=false gradle_run=false for arg in "$@"; do case "$arg" in --force) force_run=true echo "Force mode enabled. All Gradle tasks will run." ;; --gradle) gradle_run=true echo "--gradle flag detected. Will run rebuildFoliaSingleFilePatches." ;; esac done echo "Processing file patches..." declare -A gradle_tasks process_changes() { local dir="$1" local project="$2" if [ ! -d "$dir" ]; then echo "Error: The directory '$dir' does not exist or is not valid." exit 1 fi cd "$dir" if $force_run || ! git diff --quiet || ! git diff --cached --quiet; then echo "Changes detected in $dir (or force mode enabled). Running Gradle fixup and rebuild tasks." gradle_tasks["fixup${project}FilePatches"]="true" gradle_tasks["rebuild${project}FilePatches"]="true" else echo "No changes detected in $dir" fi cd - > /dev/null } run_gradle_task() { local task="$1" if [ "${gradle_tasks[$task]}" = "true" ]; then echo "Running Gradle task: $task" ./gradlew "$task" -Dpaperweight.debug=true || echo "Gradle task '$task' failed, continuing..." echo "Gradle task '$task' completed (or failed but continuing)." else echo "Skipping Gradle task '$task' as no changes were detected." fi } process_changes "./paper-server/" "PaperServer" process_changes "./paper-api/" "PaperApi" process_changes "./folia-server/" "FoliaServer" process_changes "./folia-api/" "FoliaApi" process_changes "./canvas-server/src/minecraft/java" "Minecraft" gradle_rebuild_task=false if $force_run || ! git diff --quiet "./canvas-server/build.gradle.kts" || ! git diff --cached --quiet "./canvas-server/build.gradle.kts"; then echo "Changes detected in ./canvas-server/build.gradle.kts" gradle_rebuild_task=true fi if $force_run || ! git diff --quiet "./canvas-api/build.gradle.kts" || ! git diff --cached --quiet "./canvas-api/build.gradle.kts"; then echo "Changes detected in ./canvas-api/build.gradle.kts" gradle_rebuild_task=true fi if $gradle_rebuild_task || $gradle_run; then gradle_tasks["rebuildFoliaSingleFilePatches"]="true" fi echo "Running fixup tasks..." run_gradle_task "fixupPaperApiFilePatches" run_gradle_task "fixupPaperServerFilePatches" run_gradle_task "fixupFoliaApiFilePatches" run_gradle_task "fixupFoliaServerFilePatches" run_gradle_task "fixupMinecraftFilePatches" echo "Running rebuild tasks..." run_gradle_task "rebuildPaperApiFilePatches" run_gradle_task "rebuildPaperServerFilePatches" run_gradle_task "rebuildFoliaApiFilePatches" run_gradle_task "rebuildFoliaServerFilePatches" run_gradle_task "rebuildMinecraftFilePatches" run_gradle_task "rebuildFoliaSingleFilePatches" ``` -------------------------------- ### Validation Flow Logic Source: https://docs.canvasmc.io/guides/developers/contributing/canvas This code snippet illustrates the runtime validation process. When deserializing a configuration file, it checks if a field has the relevant annotation and invokes the corresponding validator, wrapping any 'ValidationException' in a 'RuntimeException'. ```java if (field.isAnnotationPresent(annotation)) { try { validator.validate(key, field, field.getAnnotation(annotation), value); } catch (ValidationException exception) { throw new RuntimeException("Field " + key + " did not pass validation of " + annotation.getSimpleName() + " for reason of '" + exception.getMessage() + "'"); } } ``` -------------------------------- ### Define Annotation for Runtime Field Access Source: https://docs.canvasmc.io/guides/developers/contributing/canvas Defines the retention policy and target type for custom annotations. Ensures annotations are available at runtime and can only be applied to fields. ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) ``` -------------------------------- ### GET /builds - List Recent Canvas Builds Source: https://docs.canvasmc.io/guides/developers/rest-api Fetches a list of all recent Canvas builds with optional filtering by Minecraft version and experimental status. Returns an array of build objects containing build number, URLs, version info, timestamps, and commit details. The deprecated 'commit' property has been replaced with a 'commits' array. ```json [ { "buildNumber": 1337, "url": "https://jenkins.canvasmc.io/job/Canvas/1337/", "downloadUrl": "https://jenkins.canvasmc.io/job/Canvas/1337/artifact/canvas-server/build/libs/canvas-build.1337.jar", "minecraftVersion": "1.21.4", "timestamp": 1739339329161, "isExperimental": true, "commits": [ { "message": "Hello world!", "hash": "3768ac53eb2671853145bd077ade0579e13741ed" }, { "message": "Why are you reading this?", "hash": "895b307dcc7c6fbb040dc7bd26d9a754e03cf8c7" } ] } ] ``` -------------------------------- ### GET /builds/latest Source: https://docs.canvasmc.io/guides/developers/rest-api Returns the latest Canvas build. Supports filtering for experimental builds. The response includes full commit history in the 'commits' array, with the deprecated 'commit' property no longer recommended for use. ```APIDOC ## GET /builds/latest ### Description Returns the latest Canvas build, optionally filtered to include or exclude experimental builds. ### Method GET ### Endpoint https://canvasmc.io/api/v2/builds/latest ### Query Parameters - **experimental** (boolean) - Optional - Include experimental builds (defaults to false) ### Response #### Success Response (200) - **buildNumber** (integer) - The build number identifier - **url** (string) - Jenkins build URL - **downloadUrl** (string) - Direct download URL for the build JAR - **minecraftVersion** (string) - Target Minecraft version (e.g., "1.21.4") - **timestamp** (integer) - Build timestamp in milliseconds - **isExperimental** (boolean) - Whether this is an experimental build - **commits** (array) - Array of commit objects with message and hash - **message** (string) - Commit message - **hash** (string) - Commit hash ### Response Example ```json { "buildNumber": 1337, "url": "https://jenkins.canvasmc.io/job/Canvas/1337/", "downloadUrl": "https://jenkins.canvasmc.io/job/Canvas/1337/artifact/canvas-server/build/libs/canvas-build.1337.jar", "minecraftVersion": "1.21.4", "timestamp": 1739339329161, "isExperimental": true, "commits": [ { "message": "Hello world!", "hash": "3768ac53eb2671853145bd077ade0579e13741ed" }, { "message": "Why are you reading this?", "hash": "895b307dcc7c6fbb040dc7bd26d9a754e03cf8c7" } ] } ``` ### Notes - The deprecated `commit` property has been replaced with the `commits` array - Returns a single build object rather than an array ``` -------------------------------- ### Accessing ServerRegionizer API in Java Source: https://docs.canvasmc.io/info/helpful/compat This Java code snippet demonstrates how to access the ServerRegionizer interface, which provides methods to interact with the ThreadedRegionizer for world regions. It includes synchronized and unsynchronized methods for getting regions by chunk coordinates and iterating over all regions. Plugins can use this to manage or query region data. ```java /** * Interface for accessing and interacting with region data managed by the ThreadedRegionizer. * *

This interface allows plugins or subsystems to query or iterate over {@link ThreadedWorldRegion}s

* *

Regions are guaranteed to be spatially unique, but may change size or contents over time * due to merging, splitting, or chunk addition/removal.

*/ public interface ServerRegionizer { /** * Gets the region that owns the given chunk coordinate, using synchronized locking to ensure thread safety. * This method may be more expensive but guarantees the returned region is current and consistent. * * @param chunkX The X coordinate of the chunk. * @param chunkZ The Z coordinate of the chunk. * @return The {@link ThreadedWorldRegion} responsible for the chunk, or {@code null} if none exists. */ ThreadedWorldRegion getRegionAtSynchronized(int chunkX, int chunkZ); /** * Gets the region that owns the given chunk coordinate without acquiring any locks. * This method is faster but may return a stale or inconsistent result if regions are actively mutating. * * @param chunkX The X coordinate of the chunk. * @param chunkZ The Z coordinate of the chunk. * @return The {@link ThreadedWorldRegion} responsible for the chunk, or {@code null} if none exists. */ ThreadedWorldRegion getRegionAtUnsynchronized(int chunkX, int chunkZ); /** * Iterates over all regions currently managed by the regionizer in a thread-safe manner. * The provided consumer is called once for each region. * * @param regionConsumer A consumer to apply to each region, guaranteed to run with internal locking held. */ void computeForAllRegionsSynchronized(Consumer regionConsumer); /** * Iterates over all regions currently managed by the regionizer without acquiring any locks. * This method is faster but may observe inconsistent or stale data if regions are mutating concurrently. * * @param regionConsumer A consumer to apply to each region. */ void computeForAllRegionsUnsynchronized(Consumer regionConsumer); /** * Gets an immutable snapshot list of all regions currently managed by the regionizer. * This may include inactive or empty regions and is not guaranteed to be updated in real-time. * * @return A list of all {@link ThreadedWorldRegion}s. */ List getAllRegions(); } ``` -------------------------------- ### GET /builds/latest - Retrieve Latest Canvas Build Source: https://docs.canvasmc.io/guides/developers/rest-api Returns the single most recent Canvas build object, optionally filtered to include experimental builds. Response structure mirrors the /builds endpoint with build metadata, download URL, and commit information. The 'commit' field is deprecated in favor of the 'commits' array. ```json { "buildNumber": 1337, "url": "https://jenkins.canvasmc.io/job/Canvas/1337/", "downloadUrl": "https://jenkins.canvasmc.io/job/Canvas/1337/artifact/canvas-server/build/libs/canvas-build.1337.jar", "minecraftVersion": "1.21.4", "timestamp": 1739339329161, "isExperimental": true, "commits": [ { "message": "Hello world!", "hash": "3768ac53eb2671853145bd077ade0579e13741ed" }, { "message": "Why are you reading this?", "hash": "895b307dcc7c6fbb040dc7bd26d9a754e03cf8c7" } ] } ``` -------------------------------- ### Example Java Field with Annotation Source: https://docs.canvasmc.io/guides/developers/contributing/canvas A Java field 'maxTickRate' annotated with '@NonNegativeNumericValue'. When this field is populated from the YAML configuration, the custom validator will be triggered. ```java @NonNegativeNumericValue public int maxTickRate = 20; ``` -------------------------------- ### Expected Validation Failure Message Source: https://docs.canvasmc.io/guides/developers/contributing/canvas This shows the expected 'RuntimeException' message when the 'max-tick-rate' from the example YAML is validated against the '@NonNegativeNumericValue' annotation in the Java code, indicating a validation error. ```text RuntimeException: Field maxTickRate did not pass validation of NonNegativeNumericValue for reason of 'Value must be a non-negative value' ``` -------------------------------- ### Apply a Boolean Runtime Modifier with IDE Check (Java) Source: https://docs.canvasmc.io/guides/developers/contributing/canvas An example of applying a runtime modifier within the `buildSerializer` method. This specific modifier sets boolean fields to true if the application is running in an IDE or if the original value was true. ```Java .runtimeModifier("*.enable", new RuntimeModifier<>(boolean.class, (original) -> net.minecraft.SharedConstants.IS_RUNNING_IN_IDE || original)) ``` -------------------------------- ### Java API Client for Canvas V2 Source: https://docs.canvasmc.io/guides/developers/rest-api This Java class acts as a client for the Canvas V2 REST API. It allows fetching build information, including all builds for a given Minecraft version (optionally including experimental builds), the latest build for a specific version, and the absolute latest build across all versions. It utilizes Java's HttpClient for making requests and includes methods for parsing JSON responses. Dependencies include standard Java libraries like HttpClient, URI, HttpRequest, and List, as well as custom classes like Build. ```java /** * Client for interacting with the Canvas V2 REST API. * *

URL: https://canvasmc.io/api/v2

* *

This client provides methods to fetch builds, retrieve the latest stable or experimental versions, * and inspect commits associated with each build.

* *

All returned collections are non-null but may be empty unless otherwise noted.

*/ public final class ApiClient { /** * The base URL for API access */ private static final String BASE_URL = "https://canvasmc.io/api/v2"; /** * The HTTP client */ private final HttpClient CLIENT = HttpClient.newHttpClient(); /** * Fetches all builds for a specific Minecraft version. * *

If {@code experimental} is true, experimental builds are included. * Otherwise, only stable builds are returned.

* * @param minecraftVersion the target Minecraft version (non-null, but may be blank to fetch all builds) * @param experimental whether to include experimental builds in the result * @return a non-null list of matching builds (may be empty) * @throws IOException if the API request fails * @throws InterruptedException if the HTTP request is interrupted */ public @NonNull List getAllBuilds(String minecraftVersion, boolean experimental) throws IOException, InterruptedException { StringBuilder url = new StringBuilder(BASE_URL + "/builds"); boolean hasQuery = false; if (minecraftVersion != null && !minecraftVersion.isBlank()) { url.append("?minecraft_version=").append(minecraftVersion); hasQuery = true; } if (experimental) { url.append(hasQuery ? "&" : "?").append("experimental=true"); } String json = sendRequest(url.toString()); List builds = parseBuildsArray(json); builds.sort(Comparator.comparingInt(Build::buildNumber)); return builds; } /** * Returns the latest build for the specified Minecraft version. * *

If {@code includeExperimental} is true, experimental builds are considered. * Otherwise, only stable builds are used when determining the latest version.

* * @param minecraftVersion the target Minecraft version * @param includeExperimental whether to include experimental builds in the search * @return the latest build matching the filters, or {@code null} if none exist * @throws IOException if the API request fails * @throws InterruptedException if the HTTP request is interrupted */ public @Nullable Build getLatestBuildForVersion(String minecraftVersion, boolean includeExperimental) throws IOException, InterruptedException { List builds = getAllBuilds(minecraftVersion, includeExperimental); if (builds.isEmpty()) { return null; } return builds.stream() .max(Comparator.comparingInt(Build::buildNumber)) .orElse(null); } /** * Returns the latest stable build for the specified Minecraft version. * *

This is equivalent to calling * {@link #getLatestBuildForVersion(String, boolean)} with {@code includeExperimental = false}.

* * @param minecraftVersion the target Minecraft version * @return the latest stable build, or {@code null} if none exist * @throws IOException if the API request fails * @throws InterruptedException if the HTTP request is interrupted */ public @Nullable Build getLatestBuildForVersion(String minecraftVersion) throws IOException, InterruptedException { return getLatestBuildForVersion(minecraftVersion, false); } /** * Returns the latest build across all Minecraft versions. * * @param experimental whether to allow experimental builds to be returned * @return the latest available build * @throws IOException if the API request fails * @throws InterruptedException if the HTTP request is interrupted */ public @NonNull Build getLatestBuild(boolean experimental) throws IOException, InterruptedException { String url = BASE_URL + "/builds/latest" + (experimental ? "?experimental=true" : ""); String json = sendRequest(url); return parseSingleBuild(json); } private String sendRequest(String url) throws IOException, InterruptedException { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Accept", "application/json") .GET() .build(); ``` -------------------------------- ### Define @NonNegativeNumericValue Annotation Source: https://docs.canvasmc.io/guides/developers/contributing/canvas This code defines the custom annotation '@NonNegativeNumericValue'. It specifies that the annotation can be applied to fields and will be available at runtime for validation purposes. ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface NonNegativeNumericValue {} ``` -------------------------------- ### Fetch and Parse Canvas API Build Data - Java Source: https://docs.canvasmc.io/guides/developers/rest-api Fetches build data from the Canvas API, handles HTTP responses, and parses the JSON response into a list of Build objects. It relies on helper methods for JSON parsing and object creation. ```java HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new IOException("Failed to fetch from Canvas API: " + response.statusCode()); } return response.body(); } private @NonNull List parseBuildsArray(@NonNull String json) { List builds = new ArrayList<>(); int start = json.indexOf("["); int end = json.lastIndexOf("]"); if (start < 0 || end < 0) return builds; String arrayContent = json.substring(start + 1, end); String[] objects = splitObjects(arrayContent); for (String obj : objects) { obj = obj.strip(); if (!obj.isEmpty()) { builds.add(parseSingleBuild(obj)); } } return builds; } private @NonNull Build parseSingleBuild(String json) { int buildNumber = extractInt(json, "buildNumber"); String url = extractString(json, "url"); String downloadUrl = extractString(json, "downloadUrl"); String mcVersion = extractString(json, "minecraftVersion"); long timestamp = extractLong(json, "timestamp"); boolean experimental = extractBoolean(json, "isExperimental"); List commits = parseCommits(json); return new Build(buildNumber, url, downloadUrl, mcVersion, timestamp, experimental, commits.toArray(new Commit[0])); } private @NonNull List parseCommits(@NonNull String json) { List commits = new LinkedList<>(); String key = "\"commits\":"; int start = json.indexOf(key); if (start < 0) return commits; start = json.indexOf("[", start); int end = json.indexOf("]", start); if (start < 0 || end < 0) return commits; String arrayContent = json.substring(start + 1, end); String[] objects = splitObjects(arrayContent); for (String obj : objects) { String message = extractString(obj, "message"); String hash = extractString(obj, "hash"); if (message != null && hash != null) { commits.add(new Commit(message, hash)); } } return commits; } private @NonNull String @NonNull [] splitObjects(@NonNull String json) { List objects = new LinkedList<>(); int braceCount = 0; int lastSplit = 0; for (int i = 0; i < json.length(); i++) { char c = json.charAt(i); if (c == '{') braceCount++; else if (c == '}') braceCount--; if (braceCount == 0 && c == '}') { objects.add(json.substring(lastSplit, i + 1)); lastSplit = i + 2; // skip comma + space } } return objects.toArray(new String[0]); } private @Nullable String extractString(@NonNull String json, String key) { String k = "\"" + key + \":"; int idx = json.indexOf(k); if (idx < 0) return null; idx = json.indexOf('"', idx + k.length()); if (idx < 0) return null; int end = json.indexOf('"', idx + 1); if (end < 0) return null; return json.substring(idx + 1, end); } private int extractInt(String json, String key) { String value = extractNumber(json, key); return value == null ? 0 : Integer.parseInt(value); } private long extractLong(String json, String key) { String value = extractNumber(json, key); return value == null ? 0L : Long.parseLong(value); } private boolean extractBoolean(@NonNull String json, String key) { String k = "\"" + key + \":"; int idx = json.indexOf(k); if (idx < 0) return false; int start = idx + k.length(); int end = json.indexOf(',', start); if (end < 0) end = json.indexOf('}', start); if (end < 0) return false; return Boolean.parseBoolean(json.substring(start, end).trim()); } private @Nullable String extractNumber(@NonNull String json, String key) { String k = "\"" + key + \":"; int idx = json.indexOf(k); if (idx < 0) return null; int start = idx + k.length(); int end = json.indexOf(',', start); if (end < 0) end = json.indexOf('}', start); if (end < 0) return null; return json.substring(start, end).trim(); } /** * Represents a Jenkins build * * @param buildNumber the build number * @param url the URL for this associated build * @param downloadUrl the download URL * @param minecraftVersion the Minecraft version for this build * @param timestamp the timestamp of the associated build * @param isExperimental if the build is marked as experimental */ ``` -------------------------------- ### Implement NonNegativeProcessor Validator Source: https://docs.canvasmc.io/guides/developers/contributing/canvas This Java class implements the 'AnnotationValidationProvider' interface to create a validator for the '@NonNegativeNumericValue' annotation. It checks if a field's value is a non-negative number, throwing a 'ValidationException' otherwise. ```java @RegisteredHandler("nonNegative") public class NonNegativeProcessor implements AnnotationValidationProvider { @Override public boolean validate(final String fullKey, final Field field, final NonNegativeNumericValue annotation, final Object value) throws ValidationException { if (value instanceof Number number) { if (number.floatValue() < 0) { throw new ValidationException("Value must be a non-negative value"); } return true; } throw new ValidationException("NonNegativeNumericValue validation applied to a non-numeric object."); } public Class nonNegative() { return NonNegativeNumericValue.class; } } ```