diff --git a/README.md b/README.md
deleted file mode 100644
index af5626b..0000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Hello, world!
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..89eec2c
--- /dev/null
+++ b/index.html
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+ Architectury Template Generator
+
+
+
+
+ Architectury Template Generator
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..50b5dcd
--- /dev/null
+++ b/script.js
@@ -0,0 +1,182 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+import init, { create_state, generate, is_valid_mod_id, list_all_minecraft_versions, supports_neoforge, to_mod_id, validate_mod_id } from "./templateer.js";
+await init();
+
+const state = create_state();
+
+// Set up Minecraft version dropdown with contents
+const mcSelect = document.getElementById("minecraft-version-select");
+mcSelect.onchange = refreshAvailablePlatforms;
+
+for (const version of list_all_minecraft_versions().reverse()) {
+ const option = document.createElement("option");
+ option.textContent = version;
+ mcSelect.appendChild(option);
+}
+
+// Hide multiplatform settings when deselected
+const projectTypeToggles = document.getElementById("project-type-toggles").getElementsByTagName("input");
+const multiplatformInput = document.getElementById("multiplatform-input");
+const multiplatformSettings = document.getElementById("multiplatform-settings");
+
+for (const input of projectTypeToggles) {
+ input.onchange = refreshDisplayedProjectType;
+};
+
+// Add listeners to Fabric and Quilt checkboxes for controlling the Fabric-like checkbox,
+// and refresh the Fabric-like status according to the default state.
+document.getElementById("fabric-loader-input").onchange = refreshFabricLikeCheckbox;
+document.getElementById("quilt-loader-input").onchange = refreshFabricLikeCheckbox;
+refreshFabricLikeCheckbox();
+
+// Add generated mod id placeholder when not specified manually
+const modNameInput = document.getElementById("mod-name-input");
+const modIdInput = document.getElementById("mod-id-input");
+
+modNameInput.oninput = () => {
+ refreshModIdPlaceholder();
+ validateModId();
+};
+
+function refreshModIdPlaceholder() {
+ modIdInput.placeholder = to_mod_id(modNameInput.value) ?? "";
+}
+
+// Validate mod ids
+const modIdLabel = document.getElementById("mod-id-label");
+modIdInput.oninput = validateModId;
+
+function validateModId() {
+ const validation = validate_mod_id(getModId());
+
+ if (validation[0]) {
+ modIdLabel.removeAttribute("error");
+ } else {
+ modIdLabel.setAttribute("error", validation[1]);
+ }
+}
+
+function isModIdValid() {
+ return is_valid_mod_id(getModId());
+}
+
+function getModId() {
+ let value = modIdInput.value;
+ if (value === "") {
+ value = modIdInput.placeholder;
+ }
+ return value;
+}
+
+function getProjectType() {
+ for (const input of projectTypeToggles) {
+ if (input.checked) {
+ return input.getAttribute("projecttype");
+ }
+ }
+}
+
+function getMappingSet() {
+ for (const input of document.getElementsByTagName("input")) {
+ if (input.name !== "mappings") continue;
+ if (input.checked) {
+ return input.getAttribute("mappingset");
+ }
+ }
+}
+
+function updateState() {
+ state.mod_name = modNameInput.value;
+ state.mod_id = getModId();
+ state.package_name = document.getElementById("package-input").value;
+ state.game_version = mcSelect.value;
+ state.project_type = getProjectType();
+ state.mapping_set = getMappingSet();
+ state.subprojects.fabric = document.getElementById("fabric-loader-input").checked;
+ state.subprojects.forge = document.getElementById("forge-loader-input").checked;
+ state.subprojects.neoforge = document.getElementById("neoforge-loader-input").checked && isNeoForgeAvailable();
+ state.subprojects.quilt = document.getElementById("quilt-loader-input").checked;
+ state.subprojects.fabric_likes = document.getElementById("fabric-like-input").checked && isFabricLikeAvailable();
+ state.dependencies.architectury_api = document.getElementById("architectury-api-input").checked;
+}
+
+function showError(error) {
+ let container = document.getElementById("error-message-container");
+ container.textContent = error;
+ container.classList.remove("hidden");
+}
+
+function clearError() {
+ let container = document.getElementById("error-message-container");
+ container.textContent = "";
+ container.classList.add("hidden");
+}
+
+function refreshDisplayedProjectType() {
+ if (multiplatformInput.checked) {
+ multiplatformSettings.classList.remove("hidden");
+ } else {
+ multiplatformSettings.classList.add("hidden");
+ }
+}
+
+function isFabricLikeAvailable() {
+ const fabricInput = document.getElementById("fabric-loader-input");
+ const quiltInput = document.getElementById("quilt-loader-input");
+ return fabricInput.checked && quiltInput.checked;
+}
+
+function isNeoForgeAvailable() {
+ const version = mcSelect.value;
+ return supports_neoforge(version);
+}
+
+function refreshAvailablePlatforms() {
+ const hasNeoForge = isNeoForgeAvailable();
+ const neoForgeProjectInput = document.getElementById("neoforge-project-input");
+ const neoForgeLoaderInput = document.getElementById("neoforge-loader-input");
+ neoForgeProjectInput.disabled = !hasNeoForge;
+ neoForgeLoaderInput.disabled = !hasNeoForge;
+
+ // Change project type if Neo is not available.
+ if (!hasNeoForge && neoForgeProjectInput.checked) {
+ multiplatformInput.checked = true;
+ neoForgeProjectInput.checked = false;
+ refreshDisplayedProjectType();
+ }
+}
+
+// Enables/disables the Fabric-like checkbox based on whether it can be selected for the current state.
+function refreshFabricLikeCheckbox() {
+ const hasFabricLike = isFabricLikeAvailable();
+ const fabricLikeInput = document.getElementById("fabric-like-input");
+ fabricLikeInput.disabled = !hasFabricLike;
+}
+
+document.getElementById("generate-button").onclick = async () => {
+ updateState();
+
+ if (state.mod_name === "") {
+ showError("Mod name is empty");
+ return;
+ } else if (!isModIdValid()) {
+ showError("Mod ID is not valid");
+ return;
+ } else if (state.package_name === "") {
+ showError("Package name is empty");
+ return;
+ }
+
+ clearError();
+ await generate(state);
+};
+
+// Apply initial state
+modNameInput.value = state.mod_name;
+modIdInput.value = state.mod_id;
+refreshModIdPlaceholder();
+document.getElementById("package-input").value = state.package_name;
+document.getElementById("architectury-api-input").checked = state.dependencies.architectury_api;
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..3196add
--- /dev/null
+++ b/style.css
@@ -0,0 +1,190 @@
+/*
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
+*/
+
+:root {
+ font-family: sans-serif;
+ cursor: default;
+ user-select: none;
+ background-color: var(--canvas-color);
+ color: var(--text-color);
+
+ --error-color: #BB0000;
+ --border-color: #888888;
+ --canvas-color: #F0F0F0;
+ --card-color: white;
+ --text-color: black;
+ --button-accent-color: blue;
+ --active-button-text-color: white;
+ --disable-button-color: #CCCCCC;
+ --text-field-background: white;
+}
+
+fieldset {
+ border-radius: 1ex;
+ margin: 1ex;
+}
+
+fieldset, fieldset > legend {
+ background-color: var(--card-color);
+ border: solid 1px var(--border-color);
+}
+
+fieldset > legend {
+ padding: 0.7ex;
+ border-radius: 0.5ex;
+}
+
+fieldset h2 {
+ margin-bottom: 0.5ex;
+}
+
+fieldset > h2:first-of-type, h3:first-of-type {
+ margin-top: 0;
+}
+
+input[type = "text"] {
+ display: block;
+ width: 24em;
+ padding: 0.8ex;
+
+ background-color: var(--text-field-background);
+ color: var(--text-color);
+ border: solid 1px var(--border-color);
+}
+
+select {
+ padding: 1ex;
+}
+
+.label-heading {
+ font-weight: bold;
+}
+
+.property-description {
+ display: block;
+ margin-top: 1ex;
+ margin-bottom: 1ex;
+}
+
+.multicol, .horizontal-flow {
+ display: flex;
+ flex-direction: row;
+}
+
+.vertical-flow {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+}
+
+.multicol > * {
+ margin: 1ex;
+}
+
+h1 {
+ text-align: center;
+}
+
+.horizontal-flow {
+ align-items: start;
+ justify-content: center;
+}
+
+.toggle-button {
+ display: inline-block;
+ margin: 0.5ex;
+ padding: 0.7ex;
+ border: solid 1px var(--button-accent-color);
+ border-radius: 0.5ex;
+ transition: background-color 0.1s;
+}
+
+.toggle-button:hover {
+ background-color: color-mix(in srgb, var(--button-accent-color), transparent 75%);
+}
+
+.toggle-button:has(input:checked) {
+ background-color: var(--button-accent-color);
+ color: var(--active-button-text-color);
+}
+
+.toggle-button:has(input:disabled) {
+ background-color: var(--disable-button-color);
+ border-color: var(--border-color);
+}
+
+.toggle-button input {
+ appearance: none;
+ margin: 0;
+}
+
+.hidden {
+ display: none;
+}
+
+label[error]::after {
+ display: block;
+ content: "❌ " attr(error);
+ color: var(--error-color);
+}
+
+#error-message-container::before {
+ content: "❌ ";
+ color: var(--error-color);
+}
+
+#error-message-container {
+ color: var(--error-color);
+}
+
+button {
+ width: 100%;
+ height: 3em;
+ font-size: 1em;
+
+ border: solid 1px var(--border-color);
+ border-radius: 0.5ex;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ /* Overridden properties */
+ --border-color: #464666;
+ --canvas-color: #1D1D2A;
+ --card-color: #2A2A3D;
+ --text-color: #DADADF;
+ --button-accent-color: #3259CC;
+ --active-button-text-color: white;
+ --disable-button-color: #35354A;
+ --text-field-background: #232333;
+
+ /* Properties unique to dark mode */
+ --button-hover-color: #2948a5;
+ --select-background: var(--text-field-background);
+ --select-hover: color-mix(in srgb, var(--text-field-background), var(--card-color));
+ }
+
+ button {
+ border-color: var(--button-accent-color);
+ background-color: var(--button-accent-color);
+ color: var(--active-button-text-color);
+ }
+
+ button:hover {
+ background-color: var(--button-hover-color);
+ }
+
+ select {
+ background-color: var(--select-background);
+ color: var(--text-color);
+ border: solid 1px var(--border-color);
+ border-radius: 0.5ex;
+ }
+
+ select:hover {
+ background-color: var(--select-hover);
+ }
+}
diff --git a/templateer.js b/templateer.js
new file mode 100644
index 0000000..b683428
--- /dev/null
+++ b/templateer.js
@@ -0,0 +1,911 @@
+let wasm;
+
+const heap = new Array(128).fill(undefined);
+
+heap.push(undefined, null, true, false);
+
+function getObject(idx) { return heap[idx]; }
+
+let heap_next = heap.length;
+
+function dropObject(idx) {
+ if (idx < 132) return;
+ heap[idx] = heap_next;
+ heap_next = idx;
+}
+
+function takeObject(idx) {
+ const ret = getObject(idx);
+ dropObject(idx);
+ return ret;
+}
+
+const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
+
+if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
+
+let cachedUint8Memory0 = null;
+
+function getUint8Memory0() {
+ if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
+ cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+ }
+ return cachedUint8Memory0;
+}
+
+function getStringFromWasm0(ptr, len) {
+ ptr = ptr >>> 0;
+ return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
+}
+
+function addHeapObject(obj) {
+ if (heap_next === heap.length) heap.push(heap.length + 1);
+ const idx = heap_next;
+ heap_next = heap[idx];
+
+ heap[idx] = obj;
+ return idx;
+}
+
+let WASM_VECTOR_LEN = 0;
+
+const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+ ? function (arg, view) {
+ return cachedTextEncoder.encodeInto(arg, view);
+}
+ : function (arg, view) {
+ const buf = cachedTextEncoder.encode(arg);
+ view.set(buf);
+ return {
+ read: arg.length,
+ written: buf.length
+ };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+ if (realloc === undefined) {
+ const buf = cachedTextEncoder.encode(arg);
+ const ptr = malloc(buf.length, 1) >>> 0;
+ getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
+ WASM_VECTOR_LEN = buf.length;
+ return ptr;
+ }
+
+ let len = arg.length;
+ let ptr = malloc(len, 1) >>> 0;
+
+ const mem = getUint8Memory0();
+
+ let offset = 0;
+
+ for (; offset < len; offset++) {
+ const code = arg.charCodeAt(offset);
+ if (code > 0x7F) break;
+ mem[ptr + offset] = code;
+ }
+
+ if (offset !== len) {
+ if (offset !== 0) {
+ arg = arg.slice(offset);
+ }
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
+ const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
+ const ret = encodeString(arg, view);
+
+ offset += ret.written;
+ }
+
+ WASM_VECTOR_LEN = offset;
+ return ptr;
+}
+
+function isLikeNone(x) {
+ return x === undefined || x === null;
+}
+
+let cachedInt32Memory0 = null;
+
+function getInt32Memory0() {
+ if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
+ cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
+ }
+ return cachedInt32Memory0;
+}
+
+let cachedFloat64Memory0 = null;
+
+function getFloat64Memory0() {
+ if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {
+ cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);
+ }
+ return cachedFloat64Memory0;
+}
+
+function debugString(val) {
+ // primitive types
+ const type = typeof val;
+ if (type == 'number' || type == 'boolean' || val == null) {
+ return `${val}`;
+ }
+ if (type == 'string') {
+ return `"${val}"`;
+ }
+ if (type == 'symbol') {
+ const description = val.description;
+ if (description == null) {
+ return 'Symbol';
+ } else {
+ return `Symbol(${description})`;
+ }
+ }
+ if (type == 'function') {
+ const name = val.name;
+ if (typeof name == 'string' && name.length > 0) {
+ return `Function(${name})`;
+ } else {
+ return 'Function';
+ }
+ }
+ // objects
+ if (Array.isArray(val)) {
+ const length = val.length;
+ let debug = '[';
+ if (length > 0) {
+ debug += debugString(val[0]);
+ }
+ for(let i = 1; i < length; i++) {
+ debug += ', ' + debugString(val[i]);
+ }
+ debug += ']';
+ return debug;
+ }
+ // Test for built-in
+ const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
+ let className;
+ if (builtInMatches.length > 1) {
+ className = builtInMatches[1];
+ } else {
+ // Failed to match the standard '[object ClassName]'
+ return toString.call(val);
+ }
+ if (className == 'Object') {
+ // we're a user defined class or Object
+ // JSON.stringify avoids problems with cycles, and is generally much
+ // easier than looping through ownProperties of `val`.
+ try {
+ return 'Object(' + JSON.stringify(val) + ')';
+ } catch (_) {
+ return 'Object';
+ }
+ }
+ // errors
+ if (val instanceof Error) {
+ return `${val.name}: ${val.message}\n${val.stack}`;
+ }
+ // TODO we could test for more things here, like `Set`s and `Map`s.
+ return className;
+}
+
+function makeMutClosure(arg0, arg1, dtor, f) {
+ const state = { a: arg0, b: arg1, cnt: 1, dtor };
+ const real = (...args) => {
+ // First up with a closure we increment the internal reference
+ // count. This ensures that the Rust closure environment won't
+ // be deallocated while we're invoking it.
+ state.cnt++;
+ const a = state.a;
+ state.a = 0;
+ try {
+ return f(a, state.b, ...args);
+ } finally {
+ if (--state.cnt === 0) {
+ wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
+
+ } else {
+ state.a = a;
+ }
+ }
+ };
+ real.original = state;
+
+ return real;
+}
+function __wbg_adapter_36(arg0, arg1) {
+ wasm.wasm_bindgen__convert__closures__invoke0_mut__hff116f4730a514d0(arg0, arg1);
+}
+
+function __wbg_adapter_39(arg0, arg1, arg2) {
+ wasm.wasm_bindgen__convert__closures__invoke1_mut__h1f9a9e3d2a14eab2(arg0, arg1, addHeapObject(arg2));
+}
+
+/**
+* @returns {any}
+*/
+export function create_state() {
+ try {
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+ wasm.create_state(retptr);
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
+ var r2 = getInt32Memory0()[retptr / 4 + 2];
+ if (r2) {
+ throw takeObject(r1);
+ }
+ return takeObject(r0);
+ } finally {
+ wasm.__wbindgen_add_to_stack_pointer(16);
+ }
+}
+
+/**
+* @returns {Array}
+*/
+export function list_all_minecraft_versions() {
+ const ret = wasm.list_all_minecraft_versions();
+ return takeObject(ret);
+}
+
+/**
+* @param {string} mod_name
+* @returns {string}
+*/
+export function to_mod_id(mod_name) {
+ let deferred2_0;
+ let deferred2_1;
+ try {
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+ const ptr0 = passStringToWasm0(mod_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len0 = WASM_VECTOR_LEN;
+ wasm.to_mod_id(retptr, ptr0, len0);
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
+ deferred2_0 = r0;
+ deferred2_1 = r1;
+ return getStringFromWasm0(r0, r1);
+ } finally {
+ wasm.__wbindgen_add_to_stack_pointer(16);
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
+ }
+}
+
+/**
+* @param {string} mod_id
+* @returns {boolean}
+*/
+export function is_valid_mod_id(mod_id) {
+ const ptr0 = passStringToWasm0(mod_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len0 = WASM_VECTOR_LEN;
+ const ret = wasm.is_valid_mod_id(ptr0, len0);
+ return ret !== 0;
+}
+
+/**
+* @param {string} mod_id
+* @returns {Array}
+*/
+export function validate_mod_id(mod_id) {
+ const ptr0 = passStringToWasm0(mod_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len0 = WASM_VECTOR_LEN;
+ const ret = wasm.validate_mod_id(ptr0, len0);
+ return takeObject(ret);
+}
+
+/**
+* @param {any} state
+* @returns {Promise}
+*/
+export function generate(state) {
+ const ret = wasm.generate(addHeapObject(state));
+ return takeObject(ret);
+}
+
+/**
+* @param {any} game_version
+* @returns {boolean}
+*/
+export function supports_neoforge(game_version) {
+ try {
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+ wasm.supports_neoforge(retptr, addHeapObject(game_version));
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
+ var r2 = getInt32Memory0()[retptr / 4 + 2];
+ if (r2) {
+ throw takeObject(r1);
+ }
+ return r0 !== 0;
+ } finally {
+ wasm.__wbindgen_add_to_stack_pointer(16);
+ }
+}
+
+function handleError(f, args) {
+ try {
+ return f.apply(this, args);
+ } catch (e) {
+ wasm.__wbindgen_exn_store(addHeapObject(e));
+ }
+}
+function __wbg_adapter_171(arg0, arg1, arg2, arg3) {
+ wasm.wasm_bindgen__convert__closures__invoke2_mut__h7187a80a21fcff54(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
+}
+
+async function __wbg_load(module, imports) {
+ if (typeof Response === 'function' && module instanceof Response) {
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
+ try {
+ return await WebAssembly.instantiateStreaming(module, imports);
+
+ } catch (e) {
+ if (module.headers.get('Content-Type') != 'application/wasm') {
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
+
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ const bytes = await module.arrayBuffer();
+ return await WebAssembly.instantiate(bytes, imports);
+
+ } else {
+ const instance = await WebAssembly.instantiate(module, imports);
+
+ if (instance instanceof WebAssembly.Instance) {
+ return { instance, module };
+
+ } else {
+ return instance;
+ }
+ }
+}
+
+function __wbg_get_imports() {
+ const imports = {};
+ imports.wbg = {};
+ imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
+ takeObject(arg0);
+ };
+ imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
+ const ret = getStringFromWasm0(arg0, arg1);
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
+ const ret = getObject(arg0);
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
+ const obj = getObject(arg1);
+ const ret = typeof(obj) === 'string' ? obj : undefined;
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ var len1 = WASM_VECTOR_LEN;
+ getInt32Memory0()[arg0 / 4 + 1] = len1;
+ getInt32Memory0()[arg0 / 4 + 0] = ptr1;
+ };
+ imports.wbg.__wbindgen_is_string = function(arg0) {
+ const ret = typeof(getObject(arg0)) === 'string';
+ return ret;
+ };
+ imports.wbg.__wbindgen_is_object = function(arg0) {
+ const val = getObject(arg0);
+ const ret = typeof(val) === 'object' && val !== null;
+ return ret;
+ };
+ imports.wbg.__wbindgen_is_undefined = function(arg0) {
+ const ret = getObject(arg0) === undefined;
+ return ret;
+ };
+ imports.wbg.__wbindgen_in = function(arg0, arg1) {
+ const ret = getObject(arg0) in getObject(arg1);
+ return ret;
+ };
+ imports.wbg.__wbindgen_boolean_get = function(arg0) {
+ const v = getObject(arg0);
+ const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
+ return ret;
+ };
+ imports.wbg.__wbindgen_cb_drop = function(arg0) {
+ const obj = takeObject(arg0).original;
+ if (obj.cnt-- == 1) {
+ obj.a = 0;
+ return true;
+ }
+ const ret = false;
+ return ret;
+ };
+ imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
+ const ret = new Error(getStringFromWasm0(arg0, arg1));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) {
+ const ret = getObject(arg0) == getObject(arg1);
+ return ret;
+ };
+ imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
+ const obj = getObject(arg1);
+ const ret = typeof(obj) === 'number' ? obj : undefined;
+ getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
+ getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
+ };
+ imports.wbg.__wbg_getwithrefkey_15c62c2b8546208d = function(arg0, arg1) {
+ const ret = getObject(arg0)[getObject(arg1)];
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_set_20cbc34131e76824 = function(arg0, arg1, arg2) {
+ getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
+ };
+ imports.wbg.__wbg_fetch_381efb5e862610fa = function(arg0) {
+ const ret = fetch(getObject(arg0));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_instanceof_Element_4622f5da1249a3eb = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof Element;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_setid_1984ee27e5075311 = function(arg0, arg1, arg2) {
+ getObject(arg0).id = getStringFromWasm0(arg1, arg2);
+ };
+ imports.wbg.__wbg_setinnerHTML_b089587252408b67 = function(arg0, arg1, arg2) {
+ getObject(arg0).innerHTML = getStringFromWasm0(arg1, arg2);
+ };
+ imports.wbg.__wbg_getElementsByTagName_5e330e72abb60016 = function(arg0, arg1, arg2) {
+ const ret = getObject(arg0).getElementsByTagName(getStringFromWasm0(arg1, arg2));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_remove_48288e91662163dc = function(arg0) {
+ getObject(arg0).remove();
+ };
+ imports.wbg.__wbg_instanceof_Document_0498564890e9742f = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof Document;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_URL_0fa2d65a766ab4b7 = function() { return handleError(function (arg0, arg1) {
+ const ret = getObject(arg1).URL;
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len1 = WASM_VECTOR_LEN;
+ getInt32Memory0()[arg0 / 4 + 1] = len1;
+ getInt32Memory0()[arg0 / 4 + 0] = ptr1;
+ }, arguments) };
+ imports.wbg.__wbg_body_674aec4c1c0910cd = function(arg0) {
+ const ret = getObject(arg0).body;
+ return isLikeNone(ret) ? 0 : addHeapObject(ret);
+ };
+ imports.wbg.__wbg_createElement_4891554b28d3388b = function() { return handleError(function (arg0, arg1, arg2) {
+ const ret = getObject(arg0).createElement(getStringFromWasm0(arg1, arg2));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_getElementsByTagName_f5ac50391c7f853c = function(arg0, arg1, arg2) {
+ const ret = getObject(arg0).getElementsByTagName(getStringFromWasm0(arg1, arg2));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_instanceof_Window_9029196b662bc42a = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof Window;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_document_f7ace2b956f30a4f = function(arg0) {
+ const ret = getObject(arg0).document;
+ return isLikeNone(ret) ? 0 : addHeapObject(ret);
+ };
+ imports.wbg.__wbg_alert_8c9ebcc791f5eaa8 = function() { return handleError(function (arg0, arg1, arg2) {
+ getObject(arg0).alert(getStringFromWasm0(arg1, arg2));
+ }, arguments) };
+ imports.wbg.__wbg_instanceof_HtmlElement_6f4725d4677c7968 = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof HTMLElement;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_setinnerText_1849424c2fdc16ec = function(arg0, arg1, arg2) {
+ getObject(arg0).innerText = getStringFromWasm0(arg1, arg2);
+ };
+ imports.wbg.__wbg_setonclick_4e9c9187dbc33082 = function(arg0, arg1) {
+ getObject(arg0).onclick = getObject(arg1);
+ };
+ imports.wbg.__wbg_fetch_8eaf01857a5bb21f = function(arg0, arg1) {
+ const ret = getObject(arg0).fetch(getObject(arg1));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_instanceof_HtmlInputElement_31b50e0cf542c524 = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof HTMLInputElement;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_setaccept_c88dd3ef66a1bc96 = function(arg0, arg1, arg2) {
+ getObject(arg0).accept = getStringFromWasm0(arg1, arg2);
+ };
+ imports.wbg.__wbg_settype_ed9a0cf484870612 = function(arg0, arg1, arg2) {
+ getObject(arg0).type = getStringFromWasm0(arg1, arg2);
+ };
+ imports.wbg.__wbg_instanceof_HtmlAnchorElement_a293f072b6174b83 = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof HTMLAnchorElement;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_setdownload_0d874703cef6b180 = function(arg0, arg1, arg2) {
+ getObject(arg0).download = getStringFromWasm0(arg1, arg2);
+ };
+ imports.wbg.__wbg_sethref_a3fde9630423d8ed = function(arg0, arg1, arg2) {
+ getObject(arg0).href = getStringFromWasm0(arg1, arg2);
+ };
+ imports.wbg.__wbg_instanceof_HtmlButtonElement_6bd3bcb5370764a5 = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof HTMLButtonElement;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_length_b37ae9be90ea7cf5 = function(arg0) {
+ const ret = getObject(arg0).length;
+ return ret;
+ };
+ imports.wbg.__wbg_item_3364fbfadbf2cf08 = function(arg0, arg1) {
+ const ret = getObject(arg0).item(arg1 >>> 0);
+ return isLikeNone(ret) ? 0 : addHeapObject(ret);
+ };
+ imports.wbg.__wbg_textContent_c5d9e21ee03c63d4 = function(arg0, arg1) {
+ const ret = getObject(arg1).textContent;
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ var len1 = WASM_VECTOR_LEN;
+ getInt32Memory0()[arg0 / 4 + 1] = len1;
+ getInt32Memory0()[arg0 / 4 + 0] = ptr1;
+ };
+ imports.wbg.__wbg_appendChild_51339d4cde00ee22 = function() { return handleError(function (arg0, arg1) {
+ const ret = getObject(arg0).appendChild(getObject(arg1));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_signal_4bd18fb489af2d4c = function(arg0) {
+ const ret = getObject(arg0).signal;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_new_55c9955722952374 = function() { return handleError(function () {
+ const ret = new AbortController();
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_abort_654b796176d117aa = function(arg0) {
+ getObject(arg0).abort();
+ };
+ imports.wbg.__wbg_new_1eead62f64ca15ce = function() { return handleError(function () {
+ const ret = new Headers();
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_append_fda9e3432e3e88da = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
+ getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
+ }, arguments) };
+ imports.wbg.__wbg_newwithstrandinit_cad5cd6038c7ff5d = function() { return handleError(function (arg0, arg1, arg2) {
+ const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_createObjectURL_d82f2880bada6a1d = function() { return handleError(function (arg0, arg1) {
+ const ret = URL.createObjectURL(getObject(arg1));
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len1 = WASM_VECTOR_LEN;
+ getInt32Memory0()[arg0 / 4 + 1] = len1;
+ getInt32Memory0()[arg0 / 4 + 0] = ptr1;
+ }, arguments) };
+ imports.wbg.__wbg_newwithu8arraysequenceandoptions_854056d2c35b489c = function() { return handleError(function (arg0, arg1) {
+ const ret = new Blob(getObject(arg0), getObject(arg1));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_new_f648bc7adcace0bc = function() { return handleError(function () {
+ const ret = new DOMParser();
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_parseFromString_3d7be3de8b4c264e = function() { return handleError(function (arg0, arg1, arg2, arg3) {
+ const ret = getObject(arg0).parseFromString(getStringFromWasm0(arg1, arg2), takeObject(arg3));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_instanceof_Response_fc4327dbfcdf5ced = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof Response;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_url_8503de97f69da463 = function(arg0, arg1) {
+ const ret = getObject(arg1).url;
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len1 = WASM_VECTOR_LEN;
+ getInt32Memory0()[arg0 / 4 + 1] = len1;
+ getInt32Memory0()[arg0 / 4 + 0] = ptr1;
+ };
+ imports.wbg.__wbg_status_ac85a3142a84caa2 = function(arg0) {
+ const ret = getObject(arg0).status;
+ return ret;
+ };
+ imports.wbg.__wbg_headers_b70de86b8e989bc0 = function(arg0) {
+ const ret = getObject(arg0).headers;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_arrayBuffer_288fb3538806e85c = function() { return handleError(function (arg0) {
+ const ret = getObject(arg0).arrayBuffer();
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_text_a667ac1770538491 = function() { return handleError(function (arg0) {
+ const ret = getObject(arg0).text();
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_get_44be0491f933a435 = function(arg0, arg1) {
+ const ret = getObject(arg0)[arg1 >>> 0];
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_length_fff51ee6522a1a18 = function(arg0) {
+ const ret = getObject(arg0).length;
+ return ret;
+ };
+ imports.wbg.__wbg_new_898a68150f225f2e = function() {
+ const ret = new Array();
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_is_function = function(arg0) {
+ const ret = typeof(getObject(arg0)) === 'function';
+ return ret;
+ };
+ imports.wbg.__wbg_newnoargs_581967eacc0e2604 = function(arg0, arg1) {
+ const ret = new Function(getStringFromWasm0(arg0, arg1));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_next_526fc47e980da008 = function(arg0) {
+ const ret = getObject(arg0).next;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_next_ddb3312ca1c4e32a = function() { return handleError(function (arg0) {
+ const ret = getObject(arg0).next();
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_done_5c1f01fb660d73b5 = function(arg0) {
+ const ret = getObject(arg0).done;
+ return ret;
+ };
+ imports.wbg.__wbg_value_1695675138684bd5 = function(arg0) {
+ const ret = getObject(arg0).value;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_iterator_97f0c81209c6c35a = function() {
+ const ret = Symbol.iterator;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_get_97b561fb56f034b5 = function() { return handleError(function (arg0, arg1) {
+ const ret = Reflect.get(getObject(arg0), getObject(arg1));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_call_cb65541d95d71282 = function() { return handleError(function (arg0, arg1) {
+ const ret = getObject(arg0).call(getObject(arg1));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_new_b51585de1b234aff = function() {
+ const ret = new Object();
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_self_1ff1d729e9aae938 = function() { return handleError(function () {
+ const ret = self.self;
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_window_5f4faef6c12b79ec = function() { return handleError(function () {
+ const ret = window.window;
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_globalThis_1d39714405582d3c = function() { return handleError(function () {
+ const ret = globalThis.globalThis;
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_global_651f05c6a0944d1c = function() { return handleError(function () {
+ const ret = global.global;
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_push_ca1c26067ef907ac = function(arg0, arg1) {
+ const ret = getObject(arg0).push(getObject(arg1));
+ return ret;
+ };
+ imports.wbg.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof ArrayBuffer;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_call_01734de55d61e11d = function() { return handleError(function (arg0, arg1, arg2) {
+ const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_entries_e51f29c7bba0c054 = function(arg0) {
+ const ret = Object.entries(getObject(arg0));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_new_43f1b47c28813cbd = function(arg0, arg1) {
+ try {
+ var state0 = {a: arg0, b: arg1};
+ var cb0 = (arg0, arg1) => {
+ const a = state0.a;
+ state0.a = 0;
+ try {
+ return __wbg_adapter_171(a, state0.b, arg0, arg1);
+ } finally {
+ state0.a = a;
+ }
+ };
+ const ret = new Promise(cb0);
+ return addHeapObject(ret);
+ } finally {
+ state0.a = state0.b = 0;
+ }
+ };
+ imports.wbg.__wbg_resolve_53698b95aaf7fcf8 = function(arg0) {
+ const ret = Promise.resolve(getObject(arg0));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_then_f7e06ee3c11698eb = function(arg0, arg1) {
+ const ret = getObject(arg0).then(getObject(arg1));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_then_b2267541e2a73865 = function(arg0, arg1, arg2) {
+ const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_buffer_085ec1f694018c4f = function(arg0) {
+ const ret = getObject(arg0).buffer;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_newwithbyteoffsetandlength_6da8e527659b86aa = function(arg0, arg1, arg2) {
+ const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_new_8125e318e6245eed = function(arg0) {
+ const ret = new Uint8Array(getObject(arg0));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_set_5cf90238115182c3 = function(arg0, arg1, arg2) {
+ getObject(arg0).set(getObject(arg1), arg2 >>> 0);
+ };
+ imports.wbg.__wbg_length_72e2208bbc0efc61 = function(arg0) {
+ const ret = getObject(arg0).length;
+ return ret;
+ };
+ imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof Uint8Array;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_buffer_f5b7059c439f330d = function(arg0) {
+ const ret = getObject(arg0).buffer;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_has_c5fcd020291e56b8 = function() { return handleError(function (arg0, arg1) {
+ const ret = Reflect.has(getObject(arg0), getObject(arg1));
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_set_092e06b0f9d71865 = function() { return handleError(function (arg0, arg1, arg2) {
+ const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_stringify_e25465938f3f611f = function() { return handleError(function (arg0) {
+ const ret = JSON.stringify(getObject(arg0));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
+ const ret = debugString(getObject(arg1));
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len1 = WASM_VECTOR_LEN;
+ getInt32Memory0()[arg0 / 4 + 1] = len1;
+ getInt32Memory0()[arg0 / 4 + 0] = ptr1;
+ };
+ imports.wbg.__wbindgen_throw = function(arg0, arg1) {
+ throw new Error(getStringFromWasm0(arg0, arg1));
+ };
+ imports.wbg.__wbindgen_memory = function() {
+ const ret = wasm.memory;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_closure_wrapper575 = function(arg0, arg1, arg2) {
+ const ret = makeMutClosure(arg0, arg1, 278, __wbg_adapter_36);
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_closure_wrapper954 = function(arg0, arg1, arg2) {
+ const ret = makeMutClosure(arg0, arg1, 469, __wbg_adapter_39);
+ return addHeapObject(ret);
+ };
+
+ return imports;
+}
+
+function __wbg_init_memory(imports, maybe_memory) {
+
+}
+
+function __wbg_finalize_init(instance, module) {
+ wasm = instance.exports;
+ __wbg_init.__wbindgen_wasm_module = module;
+ cachedFloat64Memory0 = null;
+ cachedInt32Memory0 = null;
+ cachedUint8Memory0 = null;
+
+
+ return wasm;
+}
+
+function initSync(module) {
+ if (wasm !== undefined) return wasm;
+
+ const imports = __wbg_get_imports();
+
+ __wbg_init_memory(imports);
+
+ if (!(module instanceof WebAssembly.Module)) {
+ module = new WebAssembly.Module(module);
+ }
+
+ const instance = new WebAssembly.Instance(module, imports);
+
+ return __wbg_finalize_init(instance, module);
+}
+
+async function __wbg_init(input) {
+ if (wasm !== undefined) return wasm;
+
+ if (typeof input === 'undefined') {
+ input = new URL('templateer_bg.wasm', import.meta.url);
+ }
+ const imports = __wbg_get_imports();
+
+ if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
+ input = fetch(input);
+ }
+
+ __wbg_init_memory(imports);
+
+ const { instance, module } = await __wbg_load(await input, imports);
+
+ return __wbg_finalize_init(instance, module);
+}
+
+export { initSync }
+export default __wbg_init;
diff --git a/templateer_bg.wasm b/templateer_bg.wasm
new file mode 100644
index 0000000..73521ee
Binary files /dev/null and b/templateer_bg.wasm differ
diff --git a/templates/fabric/build.gradle b/templates/fabric/build.gradle
new file mode 100644
index 0000000..1a3be97
--- /dev/null
+++ b/templates/fabric/build.gradle
@@ -0,0 +1,61 @@
+plugins {
+ id 'com.github.johnrengelman.shadow'
+}
+
+architectury {
+ platformSetupLoomIde()
+ fabric()
+}
+
+configurations {
+ common {
+ canBeResolved = true
+ canBeConsumed = false
+ }
+ compileClasspath.extendsFrom common
+ runtimeClasspath.extendsFrom common
+ developmentFabric.extendsFrom common
+
+ // Files in this configuration will be bundled into your mod using the Shadow plugin.
+ // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files.
+ shadowBundle {
+ canBeResolved = true
+ canBeConsumed = false
+ }
+}
+
+dependencies {
+ modImplementation "net.fabricmc:fabric-loader:$rootProject.fabric_loader_version"
+
+ // Fabric API. This is technically optional, but you probably want it anyway.
+ modImplementation "net.fabricmc.fabric-api:fabric-api:$rootProject.fabric_api_version"
+//% if architectury_api
+
+ // Architectury API. This is optional, and you can comment it out if you don't need it.
+ modImplementation "%ARCHITECTURY_GROUP%:architectury-fabric:$rootProject.architectury_api_version"
+//% end
+
+ common(project(path: ':common', configuration: 'namedElements')) { transitive false }
+ shadowBundle project(path: ':common', configuration: 'transformProductionFabric')
+//% if fabric_like
+ common(project(path: ':fabric-like', configuration: 'namedElements')) { transitive false }
+ shadowBundle project(path: ':fabric-like', configuration: 'transformProductionFabric')
+//% end
+}
+
+processResources {
+ inputs.property 'version', project.version
+
+ filesMatching('fabric.mod.json') {
+ expand version: project.version
+ }
+}
+
+shadowJar {
+ configurations = [project.configurations.shadowBundle]
+ archiveClassifier = 'dev-shadow'
+}
+
+remapJar {
+ input.set shadowJar.archiveFile
+}
diff --git a/templates/fabric/src/main/java/PACKAGE_DIR/fabric/ExampleModFabric.java b/templates/fabric/src/main/java/PACKAGE_DIR/fabric/ExampleModFabric.java
new file mode 100644
index 0000000..8ab712d
--- /dev/null
+++ b/templates/fabric/src/main/java/PACKAGE_DIR/fabric/ExampleModFabric.java
@@ -0,0 +1,26 @@
+package %PACKAGE_NAME%.fabric;
+
+import net.fabricmc.api.ModInitializer;
+
+//% if fabric_like
+import %PACKAGE_NAME%.fabriclike.ExampleModFabricLike;
+//% else
+import %PACKAGE_NAME%.ExampleMod;
+//% end
+
+public final class ExampleModFabric implements ModInitializer {
+ @Override
+ public void onInitialize() {
+ // This code runs as soon as Minecraft is in a mod-load-ready state.
+ // However, some things (like resources) may still be uninitialized.
+ // Proceed with mild caution.
+
+//% if fabric_like
+ // Run the Fabric-like setup.
+ ExampleModFabricLike.init();
+//% else
+ // Run our common setup.
+ ExampleMod.init();
+//% end
+ }
+}
diff --git a/templates/fabric/src/main/java/PACKAGE_DIR/fabric/client/ExampleModFabricClient.java b/templates/fabric/src/main/java/PACKAGE_DIR/fabric/client/ExampleModFabricClient.java
new file mode 100644
index 0000000..2874e27
--- /dev/null
+++ b/templates/fabric/src/main/java/PACKAGE_DIR/fabric/client/ExampleModFabricClient.java
@@ -0,0 +1,10 @@
+package %PACKAGE_NAME%.fabric.client;
+
+import net.fabricmc.api.ClientModInitializer;
+
+public final class ExampleModFabricClient implements ClientModInitializer {
+ @Override
+ public void onInitializeClient() {
+ // This entrypoint is suitable for setting up client-specific logic, such as rendering.
+ }
+}
diff --git a/templates/fabric/src/main/resources/fabric.mod.json b/templates/fabric/src/main/resources/fabric.mod.json
new file mode 100644
index 0000000..57290f9
--- /dev/null
+++ b/templates/fabric/src/main/resources/fabric.mod.json
@@ -0,0 +1,40 @@
+{
+ "schemaVersion": 1,
+ "id": "%MOD_ID%",
+ "version": "${version}",
+ "name": "%MOD_NAME%",
+ "description": "This is an example description! Tell everyone what your mod is about!",
+ "authors": [
+ "Me!"
+ ],
+ "contact": {
+ "homepage": "https://fabricmc.net/",
+ "sources": "https://github.com/FabricMC/fabric-example-mod"
+ },
+ "license": "CC0-1.0",
+ "icon": "assets/%MOD_ID%/icon.png",
+ "environment": "*",
+ "entrypoints": {
+ "main": [
+ "%PACKAGE_NAME%.fabric.ExampleModFabric"
+ ],
+ "client": [
+ "%PACKAGE_NAME%.fabric.client.ExampleModFabricClient"
+ ]
+ },
+ "mixins": [
+ "%MOD_ID%.mixins.json"
+ ],
+ "depends": {
+ "fabricloader": ">=%FABRIC_LOADER_VERSION%",
+ "minecraft": "~%MINECRAFT_VERSION%",
+ "java": ">=%JAVA_MAJOR_VERSION%",
+//% if architectury_api
+ "architectury": ">=%ARCHITECTURY_API_VERSION%",
+//% end
+ "fabric-api": "*"
+ },
+ "suggests": {
+ "another-mod": "*"
+ }
+}
diff --git a/templates/fabric_like/build.gradle b/templates/fabric_like/build.gradle
new file mode 100644
index 0000000..563ad18
--- /dev/null
+++ b/templates/fabric_like/build.gradle
@@ -0,0 +1,15 @@
+architectury {
+ common rootProject.enabled_platforms.split(',')
+}
+
+dependencies {
+ modImplementation "net.fabricmc:fabric-loader:$rootProject.fabric_loader_version"
+ modImplementation "net.fabricmc.fabric-api:fabric-api:$rootProject.fabric_api_version"
+//% if architectury_api
+
+ // Architectury API. This is optional, and you can comment it out if you don't need it.
+ modImplementation "%ARCHITECTURY_GROUP%:architectury-fabric:$rootProject.architectury_api_version"
+//% end
+
+ compileOnly(project(path: ':common', configuration: 'namedElements')) { transitive false }
+}
diff --git a/templates/fabric_like/src/main/java/PACKAGE_DIR/fabriclike/ExampleModFabricLike.java b/templates/fabric_like/src/main/java/PACKAGE_DIR/fabriclike/ExampleModFabricLike.java
new file mode 100644
index 0000000..6fa63ae
--- /dev/null
+++ b/templates/fabric_like/src/main/java/PACKAGE_DIR/fabriclike/ExampleModFabricLike.java
@@ -0,0 +1,10 @@
+package %PACKAGE_NAME%.fabriclike;
+
+import %PACKAGE_NAME%.ExampleMod;
+
+public final class ExampleModFabricLike {
+ public static void init() {
+ // Run our common setup.
+ ExampleMod.init();
+ }
+}
diff --git a/templates/forge/build.gradle b/templates/forge/build.gradle
new file mode 100644
index 0000000..fcc12d6
--- /dev/null
+++ b/templates/forge/build.gradle
@@ -0,0 +1,60 @@
+plugins {
+ id 'com.github.johnrengelman.shadow'
+}
+
+loom {
+ forge {
+ mixinConfig "%MOD_ID%.mixins.json"
+ }
+}
+
+architectury {
+ platformSetupLoomIde()
+ forge()
+}
+
+configurations {
+ common {
+ canBeResolved = true
+ canBeConsumed = false
+ }
+ compileClasspath.extendsFrom common
+ runtimeClasspath.extendsFrom common
+ developmentForge.extendsFrom common
+
+ // Files in this configuration will be bundled into your mod using the Shadow plugin.
+ // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files.
+ shadowBundle {
+ canBeResolved = true
+ canBeConsumed = false
+ }
+}
+
+dependencies {
+ forge "net.minecraftforge:forge:$rootProject.forge_version"
+//% if architectury_api
+
+ // Architectury API. This is optional, and you can comment it out if you don't need it.
+ modImplementation "%ARCHITECTURY_GROUP%:architectury-forge:$rootProject.architectury_api_version"
+//% end
+
+ common(project(path: ':common', configuration: 'namedElements')) { transitive false }
+ shadowBundle project(path: ':common', configuration: 'transformProductionForge')
+}
+
+processResources {
+ inputs.property 'version', project.version
+
+ filesMatching('META-INF/mods.toml') {
+ expand version: project.version
+ }
+}
+
+shadowJar {
+ configurations = [project.configurations.shadowBundle]
+ archiveClassifier = 'dev-shadow'
+}
+
+remapJar {
+ input.set shadowJar.archiveFile
+}
diff --git a/templates/forge/gradle.properties b/templates/forge/gradle.properties
new file mode 100644
index 0000000..a58ba14
--- /dev/null
+++ b/templates/forge/gradle.properties
@@ -0,0 +1 @@
+loom.platform = forge
diff --git a/templates/forge/src/main/java/PACKAGE_DIR/forge/ExampleModForge.java b/templates/forge/src/main/java/PACKAGE_DIR/forge/ExampleModForge.java
new file mode 100644
index 0000000..bf54c45
--- /dev/null
+++ b/templates/forge/src/main/java/PACKAGE_DIR/forge/ExampleModForge.java
@@ -0,0 +1,24 @@
+package %PACKAGE_NAME%.forge;
+
+//% if architectury_api
+import %ARCHITECTURY_PACKAGE%.platform.forge.EventBuses;
+//% end
+import net.minecraftforge.fml.common.Mod;
+//% if architectury_api
+import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
+//% end
+
+import %PACKAGE_NAME%.ExampleMod;
+
+@Mod(ExampleMod.MOD_ID)
+public final class ExampleModForge {
+ public ExampleModForge() {
+//% if architectury_api
+ // Submit our event bus to let Architectury API register our content on the right time.
+ EventBuses.registerModEventBus(ExampleMod.MOD_ID, FMLJavaModLoadingContext.get().getModEventBus());
+
+//% end
+ // Run our common setup.
+ ExampleMod.init();
+ }
+}
diff --git a/templates/forge/src/main/resources/META-INF/mods.toml b/templates/forge/src/main/resources/META-INF/mods.toml
new file mode 100644
index 0000000..d6873e4
--- /dev/null
+++ b/templates/forge/src/main/resources/META-INF/mods.toml
@@ -0,0 +1,37 @@
+modLoader = "javafml"
+loaderVersion = "[%FORGE_LOADER_MAJOR%,)"
+#issueTrackerURL = ""
+license = "Insert License Here"
+
+[[mods]]
+modId = "%MOD_ID%"
+version = "${version}"
+displayName = "%MOD_NAME%"
+authors = "Me!"
+description = '''
+This is an example description! Tell everyone what your mod is about!
+'''
+#logoFile = ""
+
+[[dependencies.%MOD_ID%]]
+modId = "forge"
+mandatory = true
+versionRange = "[%FORGE_LOADER_MAJOR%,)"
+ordering = "NONE"
+side = "BOTH"
+
+[[dependencies.%MOD_ID%]]
+modId = "minecraft"
+mandatory = true
+versionRange = "[%MINECRAFT_VERSION%,)"
+ordering = "NONE"
+side = "BOTH"
+#% if architectury_api
+
+[[dependencies.%MOD_ID%]]
+modId = "architectury"
+mandatory = true
+versionRange = "[%ARCHITECTURY_API_VERSION%,)"
+ordering = "AFTER"
+side = "BOTH"
+#% end
diff --git a/templates/forge/src/main/resources/pack.mcmeta b/templates/forge/src/main/resources/pack.mcmeta
new file mode 100644
index 0000000..47b4f92
--- /dev/null
+++ b/templates/forge/src/main/resources/pack.mcmeta
@@ -0,0 +1,9 @@
+{
+ "pack": {
+ "description": "%MOD_NAME%",
+//% if FORGE_DATA_PACK_FORMAT
+ "%FORGE_DATA_PACK_FORMAT_KEY%": %FORGE_DATA_PACK_FORMAT%,
+//% end
+ "pack_format": %FORGE_PACK_FORMAT%
+ }
+}
diff --git a/templates/forge_only/build.gradle b/templates/forge_only/build.gradle
new file mode 100644
index 0000000..4a6c45a
--- /dev/null
+++ b/templates/forge_only/build.gradle
@@ -0,0 +1,75 @@
+plugins {
+ id 'dev.architectury.loom' version '%LOOM_VERSION%'
+ id 'maven-publish'
+}
+
+group = project.maven_group
+version = project.mod_version
+
+base {
+ archivesName = project.archives_name
+}
+
+loom {
+ forge {
+ mixinConfig '%MOD_ID%.mixins.json'
+ }
+}
+
+repositories {
+ // Add repositories to retrieve artifacts from in here.
+ // You should only use this when depending on other mods because
+ // Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
+ // See https://docs.gradle.org/current/userguide/declaring_repositories.html
+ // for more information about repositories.
+}
+
+dependencies {
+ minecraft "net.minecraft:minecraft:$project.minecraft_version"
+//% if yarn
+ mappings "net.fabricmc:yarn:$project.yarn_mappings:v2"
+//% end
+//% if mojang_mappings
+ mappings loom.officialMojangMappings()
+//% end
+ forge "net.minecraftforge:forge:$project.forge_version"
+}
+
+processResources {
+ inputs.property 'version', project.version
+
+ filesMatching('META-INF/mods.toml') {
+ expand version: project.version
+ }
+}
+
+java {
+ // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
+ // if it is present.
+ // If you remove this line, sources will not be generated.
+ withSourcesJar()
+
+ sourceCompatibility = JavaVersion.VERSION_%GRADLE_JAVA_VERSION%
+ targetCompatibility = JavaVersion.VERSION_%GRADLE_JAVA_VERSION%
+}
+
+tasks.withType(JavaCompile).configureEach {
+ it.options.release = %JAVA_MAJOR_VERSION%
+}
+
+// Configure Maven publishing.
+publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ from components.java
+ }
+ }
+
+ // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
+ repositories {
+ // Add repositories to publish to here.
+ // Notice: This block does NOT have the same function as the block in the top level.
+ // The repositories here will be used for publishing your artifact, not for
+ // retrieving dependencies.
+ }
+}
diff --git a/templates/forge_only/gradle.properties b/templates/forge_only/gradle.properties
new file mode 100644
index 0000000..9d88fb0
--- /dev/null
+++ b/templates/forge_only/gradle.properties
@@ -0,0 +1,17 @@
+# Done to increase the memory available to Gradle.
+org.gradle.jvmargs=-Xmx1G
+loom.platform = forge
+
+# Mod properties
+mod_version = 1.0.0
+maven_group = %PACKAGE_NAME%
+archives_name = %MOD_ID%
+
+# Minecraft properties
+minecraft_version = %MINECRAFT_VERSION%
+#% if yarn
+yarn_mappings = %YARN_MAPPINGS%
+#% end
+
+# Dependencies
+forge_version = %FORGE_VERSION%
diff --git a/templates/forge_only/settings.gradle b/templates/forge_only/settings.gradle
new file mode 100644
index 0000000..3ecc8b1
--- /dev/null
+++ b/templates/forge_only/settings.gradle
@@ -0,0 +1,10 @@
+pluginManagement {
+ repositories {
+ maven { url "https://maven.fabricmc.net/" }
+ maven { url "https://maven.architectury.dev/" }
+ maven { url "https://files.minecraftforge.net/maven/" }
+ gradlePluginPortal()
+ }
+}
+
+rootProject.name = '%MOD_ID%'
diff --git a/templates/forge_only/src/main/java/PACKAGE_DIR/ExampleMod.java b/templates/forge_only/src/main/java/PACKAGE_DIR/ExampleMod.java
new file mode 100644
index 0000000..0aedb0b
--- /dev/null
+++ b/templates/forge_only/src/main/java/PACKAGE_DIR/ExampleMod.java
@@ -0,0 +1,14 @@
+package %PACKAGE_NAME%;
+
+import net.minecraftforge.fml.common.Mod;
+
+@Mod(ExampleMod.MOD_ID)
+public final class ExampleMod {
+ public static final String MOD_ID = "%MOD_ID%";
+
+ public ExampleMod() {
+ // This code runs as soon as Minecraft is in a mod-load-ready state.
+ // However, some things (like registries and resources) may still be uninitialized.
+ // Proceed with mild caution.
+ }
+}
diff --git a/templates/forge_only/src/main/resources/META-INF/mods.toml b/templates/forge_only/src/main/resources/META-INF/mods.toml
new file mode 100644
index 0000000..1102e51
--- /dev/null
+++ b/templates/forge_only/src/main/resources/META-INF/mods.toml
@@ -0,0 +1,28 @@
+modLoader = "javafml"
+loaderVersion = "[%FORGE_LOADER_MAJOR%,)"
+#issueTrackerURL = ""
+license = "Insert License Here"
+
+[[mods]]
+modId = "%MOD_ID%"
+version = "${version}"
+displayName = "%MOD_NAME%"
+authors = "Me!"
+description = '''
+This is an example description! Tell everyone what your mod is about!
+'''
+#logoFile = ""
+
+[[dependencies.%MOD_ID%]]
+modId = "forge"
+mandatory = true
+versionRange = "[%FORGE_LOADER_MAJOR%,)"
+ordering = "NONE"
+side = "BOTH"
+
+[[dependencies.%MOD_ID%]]
+modId = "minecraft"
+mandatory = true
+versionRange = "[%MINECRAFT_VERSION%,)"
+ordering = "NONE"
+side = "BOTH"
diff --git a/templates/forge_only/src/main/resources/MOD_ID.mixins.json b/templates/forge_only/src/main/resources/MOD_ID.mixins.json
new file mode 100644
index 0000000..d9df560
--- /dev/null
+++ b/templates/forge_only/src/main/resources/MOD_ID.mixins.json
@@ -0,0 +1,13 @@
+{
+ "required": true,
+ "package": "%PACKAGE_NAME%.mixin",
+ "compatibilityLevel": "%MIXIN_COMPAT_LEVEL%",
+ "minVersion": "0.8",
+ "client": [
+ ],
+ "mixins": [
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/templates/forge_only/src/main/resources/pack.mcmeta b/templates/forge_only/src/main/resources/pack.mcmeta
new file mode 100644
index 0000000..47b4f92
--- /dev/null
+++ b/templates/forge_only/src/main/resources/pack.mcmeta
@@ -0,0 +1,9 @@
+{
+ "pack": {
+ "description": "%MOD_NAME%",
+//% if FORGE_DATA_PACK_FORMAT
+ "%FORGE_DATA_PACK_FORMAT_KEY%": %FORGE_DATA_PACK_FORMAT%,
+//% end
+ "pack_format": %FORGE_PACK_FORMAT%
+ }
+}
diff --git a/templates/multiplatform/build.gradle b/templates/multiplatform/build.gradle
new file mode 100644
index 0000000..a4385d7
--- /dev/null
+++ b/templates/multiplatform/build.gradle
@@ -0,0 +1,75 @@
+plugins {
+ id 'dev.architectury.loom' version '%LOOM_VERSION%' apply false
+ id 'architectury-plugin' version '%PLUGIN_VERSION%'
+ id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
+}
+
+architectury {
+ minecraft = project.minecraft_version
+}
+
+allprojects {
+ group = rootProject.maven_group
+ version = rootProject.mod_version
+}
+
+subprojects {
+ apply plugin: 'dev.architectury.loom'
+ apply plugin: 'architectury-plugin'
+ apply plugin: 'maven-publish'
+
+ base {
+ // Set up a suffixed format for the mod jar names, e.g. `example-fabric`.
+ archivesName = "$rootProject.archives_name-$project.name"
+ }
+
+ repositories {
+ // Add repositories to retrieve artifacts from in here.
+ // You should only use this when depending on other mods because
+ // Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
+ // See https://docs.gradle.org/current/userguide/declaring_repositories.html
+ // for more information about repositories.
+ }
+
+ dependencies {
+ minecraft "net.minecraft:minecraft:$rootProject.minecraft_version"
+//% if yarn
+ mappings "net.fabricmc:yarn:$rootProject.yarn_mappings:v2"
+//% end
+//% if mojang_mappings
+ mappings loom.officialMojangMappings()
+//% end
+ }
+
+ java {
+ // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
+ // if it is present.
+ // If you remove this line, sources will not be generated.
+ withSourcesJar()
+
+ sourceCompatibility = JavaVersion.VERSION_%GRADLE_JAVA_VERSION%
+ targetCompatibility = JavaVersion.VERSION_%GRADLE_JAVA_VERSION%
+ }
+
+ tasks.withType(JavaCompile).configureEach {
+ it.options.release = %JAVA_MAJOR_VERSION%
+ }
+
+ // Configure Maven publishing.
+ publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ artifactId = base.archivesName.get()
+ from components.java
+ }
+ }
+
+ // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
+ repositories {
+ // Add repositories to publish to here.
+ // Notice: This block does NOT have the same function as the block in the top level.
+ // The repositories here will be used for publishing your artifact, not for
+ // retrieving dependencies.
+ }
+ }
+}
diff --git a/templates/multiplatform/common/build.gradle b/templates/multiplatform/common/build.gradle
new file mode 100644
index 0000000..e2364e8
--- /dev/null
+++ b/templates/multiplatform/common/build.gradle
@@ -0,0 +1,15 @@
+architectury {
+ common rootProject.enabled_platforms.split(',')
+}
+
+dependencies {
+ // We depend on Fabric Loader here to use the Fabric @Environment annotations,
+ // which get remapped to the correct annotations on each platform.
+ // Do NOT use other classes from Fabric Loader.
+ modImplementation "net.fabricmc:fabric-loader:$rootProject.fabric_loader_version"
+//% if architectury_api
+
+ // Architectury API. This is optional, and you can comment it out if you don't need it.
+ modImplementation "%ARCHITECTURY_GROUP%:architectury:$rootProject.architectury_api_version"
+//% end
+}
diff --git a/templates/multiplatform/common/src/main/java/PACKAGE_DIR/ExampleMod.java b/templates/multiplatform/common/src/main/java/PACKAGE_DIR/ExampleMod.java
new file mode 100644
index 0000000..424d26e
--- /dev/null
+++ b/templates/multiplatform/common/src/main/java/PACKAGE_DIR/ExampleMod.java
@@ -0,0 +1,9 @@
+package %PACKAGE_NAME%;
+
+public final class ExampleMod {
+ public static final String MOD_ID = "%MOD_ID%";
+
+ public static void init() {
+ // Write common init code here.
+ }
+}
diff --git a/templates/multiplatform/common/src/main/resources/MOD_ID.mixins.json b/templates/multiplatform/common/src/main/resources/MOD_ID.mixins.json
new file mode 100644
index 0000000..d9df560
--- /dev/null
+++ b/templates/multiplatform/common/src/main/resources/MOD_ID.mixins.json
@@ -0,0 +1,13 @@
+{
+ "required": true,
+ "package": "%PACKAGE_NAME%.mixin",
+ "compatibilityLevel": "%MIXIN_COMPAT_LEVEL%",
+ "minVersion": "0.8",
+ "client": [
+ ],
+ "mixins": [
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/templates/multiplatform/gradle.properties b/templates/multiplatform/gradle.properties
new file mode 100644
index 0000000..5617431
--- /dev/null
+++ b/templates/multiplatform/gradle.properties
@@ -0,0 +1,34 @@
+# Done to increase the memory available to Gradle.
+org.gradle.jvmargs=-Xmx2G
+org.gradle.parallel=true
+
+# Mod properties
+mod_version = 1.0.0
+maven_group = %PACKAGE_NAME%
+archives_name = %MOD_ID%
+enabled_platforms = %ARCHITECTURY_PLATFORMS%
+
+# Minecraft properties
+minecraft_version = %MINECRAFT_VERSION%
+#% if yarn
+yarn_mappings = %YARN_MAPPINGS%
+#% end
+
+# Dependencies
+#% if architectury_api
+architectury_api_version = %ARCHITECTURY_API_VERSION%
+#% end
+fabric_loader_version = %FABRIC_LOADER_VERSION%
+#% if fabric
+fabric_api_version = %FABRIC_API_VERSION%
+#% end
+#% if forge
+forge_version = %FORGE_VERSION%
+#% end
+#% if neoforge
+neoforge_version = %NEOFORGE_VERSION%
+#% end
+#% if quilt
+quilt_loader_version = %QUILT_LOADER_VERSION%
+quilted_fabric_api_version = %QUILTED_FABRIC_API_VERSION%
+#% end
diff --git a/templates/multiplatform/settings.gradle b/templates/multiplatform/settings.gradle
new file mode 100644
index 0000000..0e7d390
--- /dev/null
+++ b/templates/multiplatform/settings.gradle
@@ -0,0 +1,27 @@
+pluginManagement {
+ repositories {
+ maven { url "https://maven.fabricmc.net/" }
+ maven { url "https://maven.architectury.dev/" }
+ maven { url "https://files.minecraftforge.net/maven/" }
+ gradlePluginPortal()
+ }
+}
+
+rootProject.name = '%MOD_ID%'
+
+include 'common'
+//% if fabric
+include 'fabric'
+//% end
+//% if fabric_like
+include 'fabric-like'
+//% end
+//% if forge
+include 'forge'
+//% end
+//% if neoforge
+include 'neoforge'
+//% end
+//% if quilt
+include 'quilt'
+//% end
diff --git a/templates/neoforge/build.gradle b/templates/neoforge/build.gradle
new file mode 100644
index 0000000..95c5c0c
--- /dev/null
+++ b/templates/neoforge/build.gradle
@@ -0,0 +1,61 @@
+plugins {
+ id 'com.github.johnrengelman.shadow'
+}
+
+architectury {
+ platformSetupLoomIde()
+ neoForge()
+}
+
+configurations {
+ common {
+ canBeResolved = true
+ canBeConsumed = false
+ }
+ compileClasspath.extendsFrom common
+ runtimeClasspath.extendsFrom common
+ developmentNeoForge.extendsFrom common
+
+ // Files in this configuration will be bundled into your mod using the Shadow plugin.
+ // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files.
+ shadowBundle {
+ canBeResolved = true
+ canBeConsumed = false
+ }
+}
+
+repositories {
+ maven {
+ name = 'NeoForged'
+ url = 'https://maven.neoforged.net/releases'
+ }
+}
+
+dependencies {
+ neoForge "net.neoforged:neoforge:$rootProject.neoforge_version"
+//% if architectury_api
+
+ // Architectury API. This is optional, and you can comment it out if you don't need it.
+ modImplementation "%ARCHITECTURY_GROUP%:architectury-neoforge:$rootProject.architectury_api_version"
+//% end
+
+ common(project(path: ':common', configuration: 'namedElements')) { transitive false }
+ shadowBundle project(path: ':common', configuration: 'transformProductionNeoForge')
+}
+
+processResources {
+ inputs.property 'version', project.version
+
+ filesMatching('META-INF/mods.toml') {
+ expand version: project.version
+ }
+}
+
+shadowJar {
+ configurations = [project.configurations.shadowBundle]
+ archiveClassifier = 'dev-shadow'
+}
+
+remapJar {
+ input.set shadowJar.archiveFile
+}
diff --git a/templates/neoforge/gradle.properties b/templates/neoforge/gradle.properties
new file mode 100644
index 0000000..2e6ed76
--- /dev/null
+++ b/templates/neoforge/gradle.properties
@@ -0,0 +1 @@
+loom.platform = neoforge
diff --git a/templates/neoforge/src/main/java/PACKAGE_DIR/neoforge/ExampleModNeoForge.java b/templates/neoforge/src/main/java/PACKAGE_DIR/neoforge/ExampleModNeoForge.java
new file mode 100644
index 0000000..892afa5
--- /dev/null
+++ b/templates/neoforge/src/main/java/PACKAGE_DIR/neoforge/ExampleModNeoForge.java
@@ -0,0 +1,13 @@
+package %PACKAGE_NAME%.neoforge;
+
+import net.neoforged.fml.common.Mod;
+
+import %PACKAGE_NAME%.ExampleMod;
+
+@Mod(ExampleMod.MOD_ID)
+public final class ExampleModNeoForge {
+ public ExampleModNeoForge() {
+ // Run our common setup.
+ ExampleMod.init();
+ }
+}
diff --git a/templates/neoforge/src/main/resources/META-INF/mods.toml b/templates/neoforge/src/main/resources/META-INF/mods.toml
new file mode 100644
index 0000000..2fe9524
--- /dev/null
+++ b/templates/neoforge/src/main/resources/META-INF/mods.toml
@@ -0,0 +1,40 @@
+modLoader = "javafml"
+loaderVersion = "[%NEOFORGE_LOADER_MAJOR%,)"
+#issueTrackerURL = ""
+license = "Insert License Here"
+
+[[mods]]
+modId = "%MOD_ID%"
+version = "${version}"
+displayName = "%MOD_NAME%"
+authors = "Me!"
+description = '''
+This is an example description! Tell everyone what your mod is about!
+'''
+#logoFile = ""
+
+[[dependencies.%MOD_ID%]]
+modId = "neoforge"
+type = "required"
+versionRange = "[%NEOFORGE_MAJOR%,)"
+ordering = "NONE"
+side = "BOTH"
+
+[[dependencies.%MOD_ID%]]
+modId = "minecraft"
+type = "required"
+versionRange = "[%MINECRAFT_VERSION%,)"
+ordering = "NONE"
+side = "BOTH"
+#% if architectury_api
+
+[[dependencies.%MOD_ID%]]
+modId = "architectury"
+type = "required"
+versionRange = "[%ARCHITECTURY_API_VERSION%,)"
+ordering = "AFTER"
+side = "BOTH"
+#% end
+
+[[mixins]]
+config = "%MOD_ID%.mixins.json"
diff --git a/templates/neoforge_only/build.gradle b/templates/neoforge_only/build.gradle
new file mode 100644
index 0000000..4c1ec39
--- /dev/null
+++ b/templates/neoforge_only/build.gradle
@@ -0,0 +1,75 @@
+plugins {
+ id 'dev.architectury.loom' version '%LOOM_VERSION%'
+ id 'maven-publish'
+}
+
+group = project.maven_group
+version = project.mod_version
+
+base {
+ archivesName = project.archives_name
+}
+
+repositories {
+ // Add NeoForged repository.
+ maven {
+ name = 'NeoForged'
+ url = 'https://maven.neoforged.net/releases'
+ }
+
+ // Add repositories to retrieve artifacts from in here.
+ // You should only use this when depending on other mods because
+ // Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
+ // See https://docs.gradle.org/current/userguide/declaring_repositories.html
+ // for more information about repositories.
+}
+
+dependencies {
+ minecraft "net.minecraft:minecraft:$project.minecraft_version"
+//% if yarn
+ mappings "net.fabricmc:yarn:$project.yarn_mappings:v2"
+//% end
+//% if mojang_mappings
+ mappings loom.officialMojangMappings()
+//% end
+ neoForge "net.neoforged:neoforge:$project.neoforge_version"
+}
+
+processResources {
+ inputs.property 'version', project.version
+
+ filesMatching('META-INF/mods.toml') {
+ expand version: project.version
+ }
+}
+
+java {
+ // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
+ // if it is present.
+ // If you remove this line, sources will not be generated.
+ withSourcesJar()
+
+ sourceCompatibility = JavaVersion.VERSION_%GRADLE_JAVA_VERSION%
+ targetCompatibility = JavaVersion.VERSION_%GRADLE_JAVA_VERSION%
+}
+
+tasks.withType(JavaCompile).configureEach {
+ it.options.release = %JAVA_MAJOR_VERSION%
+}
+
+// Configure Maven publishing.
+publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ from components.java
+ }
+ }
+
+ // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
+ repositories {
+ // Add repositories to publish to here.
+ // Notice: This block does NOT have the same function as the block in the top level.
+ // The repositories here will be used for publishing your artifact, not for
+ // retrieving dependencies.
+ }
+}
diff --git a/templates/neoforge_only/gradle.properties b/templates/neoforge_only/gradle.properties
new file mode 100644
index 0000000..7e49bc1
--- /dev/null
+++ b/templates/neoforge_only/gradle.properties
@@ -0,0 +1,17 @@
+# Done to increase the memory available to Gradle.
+org.gradle.jvmargs=-Xmx1G
+loom.platform = neoforge
+
+# Mod properties
+mod_version = 1.0.0
+maven_group = %PACKAGE_NAME%
+archives_name = %MOD_ID%
+
+# Minecraft properties
+minecraft_version = %MINECRAFT_VERSION%
+#% if yarn
+yarn_mappings = %YARN_MAPPINGS%
+#% end
+
+# Dependencies
+neoforge_version = %NEOFORGE_VERSION%
diff --git a/templates/neoforge_only/settings.gradle b/templates/neoforge_only/settings.gradle
new file mode 100644
index 0000000..3ecc8b1
--- /dev/null
+++ b/templates/neoforge_only/settings.gradle
@@ -0,0 +1,10 @@
+pluginManagement {
+ repositories {
+ maven { url "https://maven.fabricmc.net/" }
+ maven { url "https://maven.architectury.dev/" }
+ maven { url "https://files.minecraftforge.net/maven/" }
+ gradlePluginPortal()
+ }
+}
+
+rootProject.name = '%MOD_ID%'
diff --git a/templates/neoforge_only/src/main/java/PACKAGE_DIR/ExampleMod.java b/templates/neoforge_only/src/main/java/PACKAGE_DIR/ExampleMod.java
new file mode 100644
index 0000000..7bca4ad
--- /dev/null
+++ b/templates/neoforge_only/src/main/java/PACKAGE_DIR/ExampleMod.java
@@ -0,0 +1,14 @@
+package %PACKAGE_NAME%;
+
+import net.neoforged.fml.common.Mod;
+
+@Mod(ExampleMod.MOD_ID)
+public final class ExampleMod {
+ public static final String MOD_ID = "%MOD_ID%";
+
+ public ExampleMod() {
+ // This code runs as soon as Minecraft is in a mod-load-ready state.
+ // However, some things (like registries and resources) may still be uninitialized.
+ // Proceed with mild caution.
+ }
+}
diff --git a/templates/neoforge_only/src/main/resources/META-INF/mods.toml b/templates/neoforge_only/src/main/resources/META-INF/mods.toml
new file mode 100644
index 0000000..c3678a6
--- /dev/null
+++ b/templates/neoforge_only/src/main/resources/META-INF/mods.toml
@@ -0,0 +1,31 @@
+modLoader = "javafml"
+loaderVersion = "[%NEOFORGE_LOADER_MAJOR%,)"
+#issueTrackerURL = ""
+license = "Insert License Here"
+
+[[mods]]
+modId = "%MOD_ID%"
+version = "${version}"
+displayName = "%MOD_NAME%"
+authors = "Me!"
+description = '''
+This is an example description! Tell everyone what your mod is about!
+'''
+#logoFile = ""
+
+[[dependencies.%MOD_ID%]]
+modId = "neoforge"
+type = "required"
+versionRange = "[%NEOFORGE_MAJOR%,)"
+ordering = "NONE"
+side = "BOTH"
+
+[[dependencies.%MOD_ID%]]
+modId = "minecraft"
+type = "required"
+versionRange = "[%MINECRAFT_VERSION%,)"
+ordering = "NONE"
+side = "BOTH"
+
+[[mixins]]
+config = "%MOD_ID%.mixins.json"
diff --git a/templates/neoforge_only/src/main/resources/MOD_ID.mixins.json b/templates/neoforge_only/src/main/resources/MOD_ID.mixins.json
new file mode 100644
index 0000000..d9df560
--- /dev/null
+++ b/templates/neoforge_only/src/main/resources/MOD_ID.mixins.json
@@ -0,0 +1,13 @@
+{
+ "required": true,
+ "package": "%PACKAGE_NAME%.mixin",
+ "compatibilityLevel": "%MIXIN_COMPAT_LEVEL%",
+ "minVersion": "0.8",
+ "client": [
+ ],
+ "mixins": [
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/templates/quilt/build.gradle b/templates/quilt/build.gradle
new file mode 100644
index 0000000..58f99f4
--- /dev/null
+++ b/templates/quilt/build.gradle
@@ -0,0 +1,70 @@
+plugins {
+ id 'com.github.johnrengelman.shadow'
+}
+
+repositories {
+ maven { url 'https://maven.quiltmc.org/repository/release/' }
+}
+
+architectury {
+ platformSetupLoomIde()
+ loader('quilt')
+}
+
+configurations {
+ common {
+ canBeResolved = true
+ canBeConsumed = false
+ }
+ compileClasspath.extendsFrom common
+ runtimeClasspath.extendsFrom common
+ developmentQuilt.extendsFrom common
+
+ // Files in this configuration will be bundled into your mod using the Shadow plugin.
+ // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files.
+ shadowBundle {
+ canBeResolved = true
+ canBeConsumed = false
+ }
+}
+
+dependencies {
+ modImplementation "org.quiltmc:quilt-loader:$rootProject.quilt_loader_version"
+
+ // Quilt Standard Libraries and QSL.
+ modImplementation "org.quiltmc.quilted-fabric-api:quilted-fabric-api:$rootProject.quilted_fabric_api_version"
+//% if architectury_api
+
+ // Architectury API. This is optional, and you can comment it out if you don't need it.
+ modImplementation("%ARCHITECTURY_GROUP%:architectury-fabric:$rootProject.architectury_api_version") {
+ // We must not pull Fabric Loader and Fabric API from Architectury Fabric.
+ exclude group: 'net.fabricmc'
+ exclude group: 'net.fabricmc.fabric-api'
+ }
+//% end
+
+ common(project(path: ':common', configuration: 'namedElements')) { transitive false }
+ shadowBundle project(path: ':common', configuration: 'transformProductionQuilt')
+//% if fabric_like
+ common(project(path: ':fabric-like', configuration: 'namedElements')) { transitive false }
+ shadowBundle project(path: ':fabric-like', configuration: 'transformProductionQuilt')
+//% end
+}
+
+processResources {
+ inputs.property 'group', project.group
+ inputs.property 'version', project.version
+
+ filesMatching('quilt.mod.json') {
+ expand group: project.group, version: project.version
+ }
+}
+
+shadowJar {
+ configurations = [project.configurations.shadowBundle]
+ archiveClassifier = 'dev-shadow'
+}
+
+remapJar {
+ input.set shadowJar.archiveFile
+}
diff --git a/templates/quilt/gradle.properties b/templates/quilt/gradle.properties
new file mode 100644
index 0000000..56fe802
--- /dev/null
+++ b/templates/quilt/gradle.properties
@@ -0,0 +1 @@
+loom.platform = quilt
diff --git a/templates/quilt/src/main/java/PACKAGE_DIR/quilt/ExampleModQuilt.java b/templates/quilt/src/main/java/PACKAGE_DIR/quilt/ExampleModQuilt.java
new file mode 100644
index 0000000..c1a4d5f
--- /dev/null
+++ b/templates/quilt/src/main/java/PACKAGE_DIR/quilt/ExampleModQuilt.java
@@ -0,0 +1,23 @@
+package %PACKAGE_NAME%.quilt;
+
+import org.quiltmc.loader.api.ModContainer;
+import org.quiltmc.qsl.base.api.entrypoint.ModInitializer;
+
+//% if fabric_like
+import %PACKAGE_NAME%.fabriclike.ExampleModFabricLike;
+//% else
+import %PACKAGE_NAME%.ExampleMod;
+//% end
+
+public final class ExampleModQuilt implements ModInitializer {
+ @Override
+ public void onInitialize(ModContainer mod) {
+//% if fabric_like
+ // Run the Fabric-like setup.
+ ExampleModFabricLike.init();
+//% else
+ // Run our common setup.
+ ExampleMod.init();
+//% end
+ }
+}
diff --git a/templates/quilt/src/main/resources/quilt.mod.json b/templates/quilt/src/main/resources/quilt.mod.json
new file mode 100644
index 0000000..a99f520
--- /dev/null
+++ b/templates/quilt/src/main/resources/quilt.mod.json
@@ -0,0 +1,45 @@
+{
+ "schema_version": 1,
+ "quilt_loader": {
+ "group": "${group}",
+ "id": "%MOD_ID%",
+ "version": "${version}",
+ "metadata": {
+ "name": "%MOD_NAME%",
+ "description": "This is an example description! Tell everyone what your mod is about!",
+ "contributors": [
+ "Me!"
+ ],
+ "icon": "assets/%MOD_ID%/icon.png"
+ },
+ "intermediate_mappings": "net.fabricmc:intermediary",
+ "entrypoints": {
+ "init": [
+ "%PACKAGE_NAME%.quilt.ExampleModQuilt"
+ ]
+ },
+ "depends": [
+ {
+ "id": "quilt_loader",
+ "version": "*"
+ },
+ {
+ "id": "quilt_base",
+ "version": "*"
+ },
+ {
+ "id": "minecraft",
+ "version": ">=%MINECRAFT_VERSION%"
+//% if architectury_api
+ },
+ {
+ "id": "architectury",
+ "version": ">=%ARCHITECTURY_API_VERSION%"
+//% end
+ }
+ ]
+ },
+ "mixin": [
+ "%MOD_ID%.mixins.json"
+ ]
+}
diff --git a/templates/shared/gradle/wrapper/gradle-wrapper.jar b/templates/shared/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..d64cd49
Binary files /dev/null and b/templates/shared/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/templates/shared/gradle/wrapper/gradle-wrapper.properties b/templates/shared/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..b82aa23
--- /dev/null
+++ b/templates/shared/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/templates/shared/gradlew b/templates/shared/gradlew
new file mode 100755
index 0000000..1aa94a4
--- /dev/null
+++ b/templates/shared/gradlew
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/templates/shared/gradlew.bat b/templates/shared/gradlew.bat
new file mode 100644
index 0000000..6689b85
--- /dev/null
+++ b/templates/shared/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/version_index.json b/version_index.json
new file mode 100644
index 0000000..38278a2
--- /dev/null
+++ b/version_index.json
@@ -0,0 +1,62 @@
+{
+ "1.16.5": {
+ "architectury_api": "1.32.68",
+ "forge": "1.16.5-36.2.42",
+ "neoforge": null
+ },
+ "1.18.1": {
+ "architectury_api": "3.9.66",
+ "forge": "1.18.1-39.1.2",
+ "neoforge": null
+ },
+ "1.20.4": {
+ "architectury_api": "11.1.17",
+ "forge": "1.20.4-49.0.39",
+ "neoforge": "20.4.227"
+ },
+ "1.19.1": {
+ "architectury_api": "6.3.56",
+ "forge": "1.19.1-42.0.9",
+ "neoforge": null
+ },
+ "1.19.3": {
+ "architectury_api": "7.1.86",
+ "forge": "1.19.3-44.1.23",
+ "neoforge": null
+ },
+ "1.17.1": {
+ "architectury_api": "2.10.12",
+ "forge": "1.17.1-37.1.1",
+ "neoforge": null
+ },
+ "1.19.2": {
+ "architectury_api": "6.6.92",
+ "forge": "1.19.2-43.3.9",
+ "neoforge": null
+ },
+ "1.19.4": {
+ "architectury_api": "8.2.91",
+ "forge": "1.19.4-45.2.10",
+ "neoforge": null
+ },
+ "1.20.1": {
+ "architectury_api": "9.2.14",
+ "forge": "1.20.1-47.2.23",
+ "neoforge": null
+ },
+ "1.19": {
+ "architectury_api": "5.14.84",
+ "forge": "1.19-41.1.0",
+ "neoforge": null
+ },
+ "1.18.2": {
+ "architectury_api": "4.12.94",
+ "forge": "1.18.2-40.2.18",
+ "neoforge": null
+ },
+ "1.20.2": {
+ "architectury_api": "10.1.20",
+ "forge": "1.20.2-48.1.0",
+ "neoforge": null
+ }
+}
\ No newline at end of file