Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 22 additions & 17 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
# Automatically build the project and run any configured tests for every push
# and submitted pull request. This can help catch issues that only occur on
# certain platforms or Java versions, and provides a first line of defence
# against bad commits.
# Builds every Minecraft version / loader combination declared in settings.gradle.kts.
# Stonecutter fans the build out across all targets; chiseledBuildAndCollect gathers the
# resulting jars into the root build/libs directory.

name: build
on: [pull_request, push]

jobs:
build:
strategy:
matrix:
# Use these Java versions
java: [
21, # Current Java LTS
]
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: validate gradle wrapper
uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
- name: setup jdk ${{ matrix.java }}

# 1.21.x targets compile against Java 21, 26.x targets against Java 25.
# Both toolchains have to be present for the whole matrix to build in one pass.
- name: setup jdks
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: ${{ matrix.java }}
distribution: 'microsoft'
java-version: |
17
21
25
distribution: 'temurin'

- name: setup gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0

- name: make gradle wrapper executable
run: chmod +x ./gradlew
- name: build
run: ./gradlew build

- name: build all versions
run: ./gradlew chiseledBuildAndCollect --stacktrace

- name: capture build artifacts
if: ${{ matrix.java == '21' }} # Only upload artifacts built from latest java on one OS
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Mod Jars
path: build/libs/
path: build/libs/
117 changes: 117 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Publishes every Minecraft version / loader combination to Modrinth and CurseForge, and attaches
# the jars to the GitHub release, when a version tag is pushed (e.g. 1.4.0, 1.4.0-beta.1).
#
# Required repository secrets:
# MODRINTH_TOKEN - Modrinth PAT with "Create versions" scope
# CURSEFORGE_TOKEN - CurseForge API token
#
# Required repository variables (Settings -> Secrets and variables -> Actions -> Variables):
# MODRINTH_ID - Modrinth project id (e.g. DwMSqx5B)
# CURSEFORGE_ID - CurseForge numeric project id (e.g. 980833)
# CURSEFORGE_SLUG - CurseForge project slug (e.g. openshock-shockcraft)
#
# Nothing is uploaded unless PUBLISH_RELEASE is true, so this workflow is the only thing that can
# publish; running publishMods anywhere else is a dry run.

name: release
on:
push:
# Version tags carry no prefix. This pattern uses only [] and *, which GitHub's tag filters
# definitely support, so a release can never silently fail to trigger.
tags:
- '[0-9]*'

permissions:
contents: write

jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: validate gradle wrapper
uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0

- name: setup jdks
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: |
17
21
25
distribution: 'temurin'

- name: setup gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0

# The tag is the source of truth for the published version: tag 1.4.0 publishes 1.4.0.
#
# It also decides the release type in one place so GitHub and the mod platforms cannot
# disagree. Stonecraft infers a type from the version string but only recognises alpha,
# beta and next - an "rc" tag would otherwise reach Modrinth and CurseForge as stable.
# Setting RELEASE_TYPE explicitly overrides that inference.
- name: derive version and release type from tag
id: version
run: |
case "$GITHUB_REF_NAME" in
*alpha*) release_type=alpha ;;
*beta*|*next*|*rc*) release_type=beta ;;
*) release_type=stable ;;
esac
if [ "$release_type" = stable ]; then prerelease=""; else prerelease="--prerelease"; fi
{
echo "version=$GITHUB_REF_NAME"
echo "release_type=$release_type"
echo "prerelease=$prerelease"
} >> "$GITHUB_OUTPUT"

# An unset variable expands to an empty string, which still counts as "present" to Gradle, so
# check before spending ten minutes building only to publish with a blank project id.
- name: check publishing credentials
env:
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
MODRINTH_ID: ${{ vars.MODRINTH_ID }}
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
CURSEFORGE_ID: ${{ vars.CURSEFORGE_ID }}
CURSEFORGE_SLUG: ${{ vars.CURSEFORGE_SLUG }}
run: |
missing=""
for name in MODRINTH_TOKEN MODRINTH_ID CURSEFORGE_TOKEN CURSEFORGE_ID CURSEFORGE_SLUG; do
[ -n "${!name}" ] || missing="$missing $name"
done
if [ -n "$missing" ]; then
echo "::error::Missing required secrets/variables:$missing"
exit 1
fi

- name: build all versions
env:
MOD_VERSION: ${{ steps.version.outputs.version }}
run: ./gradlew chiseledBuildAndCollect -Pmod.version="$MOD_VERSION" --stacktrace

- name: create github release
env:
GH_TOKEN: ${{ github.token }}
PRERELEASE: ${{ steps.version.outputs.prerelease }}
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--generate-notes \
$PRERELEASE \
build/libs/*.jar
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: publish to modrinth and curseforge
env:
PUBLISH_RELEASE: 'true'
RELEASE_TYPE: ${{ steps.version.outputs.release_type }}
# Point at the GitHub release rather than duplicating its notes on each platform.
CHANGELOG: "Full changelog: https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}"
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
MODRINTH_ID: ${{ vars.MODRINTH_ID }}
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
CURSEFORGE_ID: ${{ vars.CURSEFORGE_ID }}
CURSEFORGE_SLUG: ${{ vars.CURSEFORGE_SLUG }}
MOD_VERSION: ${{ steps.version.outputs.version }}
run: ./gradlew chiseledPublishMods -Pmod.version="$MOD_VERSION" --stacktrace
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ build/
out/
classes/

# stonecutter

versions/*/build/
versions/*/src/
versions/*/.gradle/
.stonecutter/

# eclipse

*.launch
Expand Down
128 changes: 128 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,131 @@
## Support

You can support the openshock dev team here: [Sponsor OpenShock](https://github.com/sponsors/OpenShock)

## Development

The mod is built from a single source tree for every supported Minecraft version and both mod
loaders, using [Stonecutter](https://github.com/stonecutter-versioning/stonecutter) for the version
matrix and [Stonecraft](https://github.com/meza/Stonecraft) (which wraps Architectury Loom) for the
loader wiring.

| | |
|---|---|
| Minecraft | 1.20.4, 1.21, 1.21.1, 1.21.4, 1.21.5, 1.21.11, 26.1, 26.2 |
| Loaders | Fabric, NeoForge |
| Targets | 16 (every version × every loader) |
| Java | 17 for 1.20.x, 21 for 1.21.x, 25 for 26.x (Gradle downloads any it is missing) |

### Building

```bash
./gradlew chiseledBuildAndCollect # build every target; jars land in build/libs/
./gradlew build # build only the currently active target
```

The active target is the one your IDE and `runClient` use. Switch it with:

```bash
./gradlew "Set active project to 1.21.1-neoforge"
```

Stonecutter rewrites the files under `src/` in place when you switch, commenting out the branches
that do not apply to the new target. That is expected — do not revert it.

### Running the game

There are two sets of IntelliJ run configurations:

- `Client <version>-<loader>` - generated by `./gradlew generateRunConfigs`. These invoke
`:<target>:runClient` through Gradle and work without a successful Gradle sync. **Re-run that
task after adding or removing a Minecraft version**, and it will rewrite the whole set.
- `Minecraft Client (:<version>-<loader>)` - generated by Architectury Loom during Gradle sync.
These launch the game directly (faster, easier to debug) but need the IDE to have created the
per-target modules first.

`.idea/` is gitignored, so both sets are local to your machine.

From the command line:

```bash
./gradlew :26.2-fabric:runClient
./gradlew :1.21.1-neoforge:runClient
```

You do not need to switch the active target to run a different one - every target is a real Gradle
subproject and can be launched directly. Switching only changes which one your IDE indexes and
edits.

Each target runs in its own directory, `run/<version>-<loader>/`, because Minecraft 1.21.1 and 26.2
cannot share a world or an options file and the two loaders cannot share a mods folder.

### Releasing

Pushing a version tag runs `.github/workflows/release.yml`, which builds all targets, creates a
GitHub release with the jars attached, and publishes every one of them to Modrinth and CurseForge.

```bash
git tag 1.4.0 && git push origin 1.4.0
```

The tag is the source of truth for the version (tag `1.4.0` publishes `1.4.0`), so `mod.version` in
`gradle.properties` does not need bumping first. The changelog on Modrinth and CurseForge is a link
back to the GitHub release, so the notes only ever live in one place.

The tag also decides the release type, consistently across all three destinations:

| Tag contains | Modrinth / CurseForge | GitHub release |
|---|---|---|
| `alpha` | Alpha | pre-release |
| `beta`, `next`, `rc` | Beta | pre-release |
| anything else | Stable | normal |

`rc` needs an explicit mapping because Stonecraft's own inference recognises only `alpha`, `beta`
and `next`; left to it, an `rc` tag would reach both platforms marked stable.

Configure these under **Settings -> Secrets and variables -> Actions**:

| Secret | |
|---|---|
| `MODRINTH_TOKEN` | Modrinth PAT with the "Create versions" scope |
| `CURSEFORGE_TOKEN` | CurseForge API token |

| Variable | Current value |
|---|---|
| `MODRINTH_ID` | `DwMSqx5B` |
| `CURSEFORGE_ID` | `980833` |
| `CURSEFORGE_SLUG` | `openshock-shockcraft` |

The workflow checks all five are set before building, so a missing one fails in seconds with a
clear message rather than part-way through publishing.

Nothing is ever uploaded unless `PUBLISH_RELEASE=true`, which only the release workflow sets, so
running `./gradlew chiseledPublishMods` locally is always a dry run. Use it to preview exactly what
would be sent:

```bash
./gradlew chiseledPublishMods
```

### Layout

- `settings.gradle.kts` — the list of targets. Adding a Minecraft version is one line here plus a
matching `versions/dependencies/<version>.properties` file.
- `versions/dependencies/<version>.properties` — every dependency version for that Minecraft
version, shared by its Fabric and NeoForge targets. This is the only file to touch when bumping
Fabric API, NeoForge, YACL, Mod Menu or Kotlin for Forge.
- `build.gradle.kts` — applied to every target.
- `src/main/kotlin/.../platform/` — the only loader- and version-specific code:
- `Entrypoint.kt` — the Fabric and NeoForge entrypoints.
- `Platform.kt` — the handful of loader APIs with no common equivalent.
- `McCompat.kt` — the few vanilla APIs that changed across the version range.

Everything outside `platform/` compiles unchanged on all sixteen targets. Sources use **Mojang
mappings**, which both loaders share.

The version-conditional code is deliberately confined to those files. `McCompat.kt` covers the
vanilla renames (`ResourceLocation`/`Identifier`, the `fromNamespaceAndPath` factory,
`displayClientMessage`/`sendOverlayMessage`, `Minecraft.screen` moving onto `Gui`), and the
NeoForge half of `Entrypoint.kt` covers the 1.21 API break (`TickEvent.ClientTickEvent` became
`ClientTickEvent.Post`, `ConfigScreenHandler.ConfigScreenFactory` became `IConfigScreenFactory`,
and `@Mod` gained its `dist` element).
Loading