From 4894ab3e62a7bec211cd68970024ba75c37b1c94 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 8 Aug 2026 09:47:02 -0700 Subject: [PATCH 1/5] The respawn loaner is your boat, not a hull in your pocket Playtest: died, respawned at the port, was handed the bamboo raft - and every trader refused: "no ship at this quay". The raft was created UNOWNED whenever the player still had an active boat far away, on the reasoning that boarding it is what abandons the old one. But trade transacts against the ACTIVE boat record, so an unowned hull in the pack is not a hold at all; it only became theirs after placing it and getting in. grantRespawnBoat now always uses BoatService.createFor, so the loaner is active and owned the moment it is handed over and the wreck's hull is demoted to an OLD BOAT - still charted, cargo aboard, still theirs to row back out and reclaim. New locale boat.respawn-given-replacing says so. Taking ownership is destructive to whatever they had, so a keepInventory death - where the real boat never left the pack - must not trigger it, or the raft demotes a boat they are holding, cargo and all. stillHasTheirBoat skips the grant there. testBoatWithinReachMeansNoLoaner had been passing for the wrong reason: CommonTestSetup's shared mock Location answers 0.0 to every distanceSquared and world.getName() is unstubbed, so "an ocean away" and "at my feet" were the same test. Stub a world name and a real location. Version 0.1.1 - and deploy.sh no longer hardcodes it, which is what broke the copy. It takes whatever package built and clears stale TradeWinds jars from the addons folder first, since two versions load as two addons. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr --- pom.xml | 2 +- scripts/deploy.sh | 21 +++++++++- .../listeners/SpawnRespawnListener.java | 38 +++++++++++++------ src/main/resources/locales/en-US.yml | 3 +- .../listeners/SpawnRespawnListenerTest.java | 25 +++++++++++- 5 files changed, 73 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index f9f8321..d7de67f 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ -LOCAL - 0.1.0 + 0.1.1 diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 10007ae..4cc3af5 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -14,7 +14,6 @@ set -euo pipefail ADDONS="/Users/ben/Minecraft/26.2/plugins/BentoBox/addons" -JAR="target/TradeWinds-0.1.0-SNAPSHOT-LOCAL.jar" # Match the server jar loosely: it is named paper-26.2-87.jar, not # paper-26.2.jar, so an exact pattern silently never matched and the guard was @@ -34,5 +33,25 @@ if [ -f "$LOG" ] && [ -n "$(find "$LOG" -mmin -1 2>/dev/null)" ]; then fi mvn -q clean package "$@" + +# The jar carries the version, so never hardcode it here: a version bump used +# to break the copy (0.1.1, 2026-08-08). Take whatever package just built. +shopt -s nullglob +built=(target/TradeWinds-*-LOCAL.jar) +if [ ${#built[@]} -ne 1 ]; then + echo "Expected exactly one target/TradeWinds-*-LOCAL.jar, found ${#built[@]}." + exit 1 +fi +JAR="${built[0]}" + +# An older version left behind loads ALONGSIDE the new one - two TradeWinds +# addons over one world. Clear the deck first. +for old in "$ADDONS"/TradeWinds-*.jar; do + if [ "$(basename "$old")" != "$(basename "$JAR")" ]; then + echo "Removing stale $(basename "$old")" + rm -f "$old" + fi +done + cp "$JAR" "$ADDONS/" echo "Deployed $(basename "$JAR") to $ADDONS" diff --git a/src/main/java/world/bentobox/tradewinds/listeners/SpawnRespawnListener.java b/src/main/java/world/bentobox/tradewinds/listeners/SpawnRespawnListener.java index 276b0f0..8caf2c9 100644 --- a/src/main/java/world/bentobox/tradewinds/listeners/SpawnRespawnListener.java +++ b/src/main/java/world/bentobox/tradewinds/listeners/SpawnRespawnListener.java @@ -20,8 +20,10 @@ * Death also drops the boat where they fell, so a respawned sailor would be * marooned ashore with no way back to their own wreck. The port lends them a * boat ({@code boats.respawn-boat}, default a bamboo raft; NONE disables) - - * but only if they have none, so keepInventory deaths and bed respawns are - * never double-boated. + * but only if they have none within reach, so keepInventory deaths and bed + * respawns are never double-boated. The loaner becomes their ACTIVE boat on + * the spot, demoting the wreck's hull to an OLD BOAT: a hull nobody owns is + * not a hold, and its owner cannot trade. * * @author tastybento */ @@ -117,18 +119,32 @@ private void grantRespawnBoat(org.bukkit.entity.Player player) { return; } Material boat = respawnBoat(); - if (boat == null || boatWithinReach(player)) { + if (boat == null || stillHasTheirBoat(player) || boatWithinReach(player)) { return; } - // They have no boat, or theirs is far away: lend a hull. If they had - // one, boarding this raft is what abandons it (with the standard - // confirmation) - the loaner itself takes nothing from them. - var hold = addon.getHoldService().active(player.getUniqueId()).isEmpty() - ? addon.getBoatService().createFor(player, boat) - : addon.getHoldManager().create(boat, null); + // The loaner is theirs the MOMENT it is handed over. An unowned hull + // is not a hold: the harbourmaster's raft sat in the pack while every + // trader turned its owner away for having no ship at the quay, and it + // only became a boat once it was placed and boarded (playtest + // 2026-08-08). Whatever they were sailing becomes their OLD BOAT - + // still charted, still carrying its cargo, still theirs to row back + // out and reclaim. + boolean replacing = addon.getHoldService().active(player.getUniqueId()).isPresent(); + var hold = addon.getBoatService().createFor(player, boat); addon.getBoatService().giveBoatItem(player, hold); - User.getInstance(player).sendMessage("tradewinds.boat.respawn-given", "[material]", - world.bentobox.tradewinds.economy.PriceEngine.prettify(boat.name())); + User.getInstance(player).sendMessage( + replacing ? "tradewinds.boat.respawn-given-replacing" : "tradewinds.boat.respawn-given", + "[material]", world.bentobox.tradewinds.economy.PriceEngine.prettify(boat.name())); + } + + /** + * Whether their own boat is already in their pack - a keepInventory death, + * where the hull never left them. The loaner would demote a boat they are + * literally holding, cargo and all, so there is nothing to lend. + */ + private boolean stillHasTheirBoat(org.bukkit.entity.Player player) { + var hold = addon.getHoldService().active(player.getUniqueId()); + return hold.isPresent() && addon.getBoatService().isCarrying(player, hold.get()); } /** diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 5081e42..e805b43 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -369,7 +369,8 @@ tradewinds: merged-empty: "You shift everything across and leave the empty [material] adrift." merged-partial: "You shift across what fits - the rest stays in the [material], adrift at your feet." adrift-raft: "Adrift with no boat - the harbourmaster's [material] is in your pack." - respawn-given: "The port lends you a [material] - your own boat is adrift where you fell." + respawn-given: "The port lends you a [material] - she is yours, and your hold sails with her." + respawn-given-replacing: "The port lends you a [material] - she is yours now. Your old boat lies adrift where you fell, cargo aboard; your chart still marks her." claimed-plain: "The [material] is yours now - your hold awaits its first cargo." upgraded-plain: "You take the [material] - [slots] cargo slots. Your old boat is broken up; your cargo is aboard." claimed: "You haul in the [material] - it is yours now, cargo and all." diff --git a/src/test/java/world/bentobox/tradewinds/listeners/SpawnRespawnListenerTest.java b/src/test/java/world/bentobox/tradewinds/listeners/SpawnRespawnListenerTest.java index 43835f5..f2d57d8 100644 --- a/src/test/java/world/bentobox/tradewinds/listeners/SpawnRespawnListenerTest.java +++ b/src/test/java/world/bentobox/tradewinds/listeners/SpawnRespawnListenerTest.java @@ -50,6 +50,11 @@ public void setUp() throws Exception { when(im.getSpawnPoint(world)).thenReturn(new Location(world, 72.5, 73, 72.5)); when(mockPlayer.getWorld()).thenReturn(world); when(mockPlayer.isOnline()).thenReturn(true); + // A REAL post-respawn location on the plaza: the shared mock Location + // answers 0 to every distanceSquared, so "my boat is an ocean away" + // and "my boat is at my feet" were the same test + when(world.getName()).thenReturn("tradewinds_world"); + when(mockPlayer.getLocation()).thenReturn(new Location(world, 72, 73, 72)); // The loaner is granted a tick after the respawn: run it inline when(sch.runTask(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any(Runnable.class))).thenAnswer(inv -> { @@ -134,12 +139,28 @@ void testDistantBoatStillGetsALoaner() { boat.setX(9000); boat.setZ(9000); listener.onRespawn(new PlayerRespawnEvent(mockPlayer, deathBed, false, false)); - // A loaner hull exists for them, and it did NOT displace the boat - // they still own an ocean away + // The loaner is their ACTIVE boat at once - an unowned hull is not a + // hold, and its carrier is turned away at every quay (playtest + // 2026-08-08). The distant hull becomes their OLD BOAT. + verify(boats).createFor(mockPlayer, org.bukkit.Material.BAMBOO_RAFT); verify(boats).giveBoatItem(org.mockito.ArgumentMatchers.eq(mockPlayer), org.mockito.ArgumentMatchers.any()); + } + + @Test + void testCarriedBoatMeansNoLoaner() { + // keepInventory: the hull never left their pack, so lending would + // demote a boat they are holding - cargo and all + var boat = holds.giveBoat(uuid, org.bukkit.Material.OAK_BOAT); + boat.setWorld(world.getName()); + boat.setX(9000); + boat.setZ(9000); + when(boats.isCarrying(mockPlayer, boat)).thenReturn(true); + listener.onRespawn(new PlayerRespawnEvent(mockPlayer, deathBed, false, false)); verify(boats, never()).createFor( org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any()); + verify(boats, never()).giveBoatItem(org.mockito.ArgumentMatchers.any(), + org.mockito.ArgumentMatchers.any()); } @Test From aca9e863eef0dbe645ff3a1b3fb0f086422ee07d Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 8 Aug 2026 10:12:25 -0700 Subject: [PATCH 2/5] Publish to Modrinth when a GitHub Release is published The house workflow from GushBlock/Border, adapted to this repo. Two differences worth knowing: GIT_BRANCH=origin/main, not origin/master. The pom's release profile activates on the branch we actually release from; point it at master and every build stays a SNAPSHOT and the upload cannot find its file. Tests RUN here. GushBlock skips them because the patched MockBukkit it needs is published nowhere; ours ships inside the repo under libs/, so a bare checkout tests green and the artifact we publish is the artifact we tested. A guard step sits between the two: the jar is named from the pom's build.version and the upload from the release TAG, so a v-prefixed tag or a forgotten version bump used to surface as a bare "file not found". It now says which jar was expected, lists what was built, and names the cause. game-versions lists only 26.2 - the one version TradeWinds is tested on, and what the release notes promise. The compile floor is far lower (clean against paper-api 1.21.10; only the dialog API, Minecraft 1.21.6, breaks below that), so the list can widen as older versions are actually played. MODRINTH_TOKEN and MODRINTH_PROJECT_ID are already set as repo secrets. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr --- .github/workflows/modrinth-publish.yml | 121 +++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 .github/workflows/modrinth-publish.yml diff --git a/.github/workflows/modrinth-publish.yml b/.github/workflows/modrinth-publish.yml new file mode 100644 index 0000000..11bfbe7 --- /dev/null +++ b/.github/workflows/modrinth-publish.yml @@ -0,0 +1,121 @@ +name: Publish to Modrinth + +# Triggers when you publish a GitHub Release +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to publish (e.g. 0.1.1)' + required: true + type: string + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + # 1. Check out the repository at the release tag + - name: Checkout repository + uses: actions/checkout@v4 + + # 2. JDK 25, not the 17 SonarCloud's wizard suggests: the Paper 26.2 and + # BentoBox dependencies ship Java 25 bytecode (class version 69), + # which older compilers cannot read. The pom still compiles + # TradeWinds' own sources at release 21. + - name: Set up JDK 25 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 25 + + # 3. Cache Maven dependencies to speed up builds + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + # 4. Build the jar - WITH tests, unlike GushBlock's copy of this + # workflow, which skips them because the patched MockBukkit it needs + # is not published anywhere. TradeWinds ships that jar inside the + # repo (libs/), so a bare checkout tests green and the artifact we + # publish is the artifact we tested. + # + # GIT_BRANCH=origin/main activates the pom's 'main' profile, which + # strips -SNAPSHOT and the build number so the jar is named + # TradeWinds-.jar. This repo releases from main, NOT master - + # point it at the wrong branch and every build stays a SNAPSHOT and + # the upload below cannot find its file. + - name: Build with Maven + run: mvn -B clean package + env: + GIT_BRANCH: origin/main + + # 5. Fail legibly if the jar is not where the upload expects it. The jar + # is named from the POM's build.version, the upload from the release + # TAG, so a tag that does not match the pom (v0.1.1, or a forgotten + # version bump) means the upload step just says "file not found". + - name: Verify the jar matches the release tag + env: + TAG: ${{ github.event.release.tag_name || inputs.tag }} + run: | + jar="target/TradeWinds-${TAG}.jar" + if [ ! -f "$jar" ]; then + echo "::error::Expected $jar but the build produced:" + ls -1 target/*.jar || true + echo "::error::The release tag must match in pom.xml (no 'v' prefix)." + exit 1 + fi + echo "Publishing $jar" + + # 6. Upload the jar to Modrinth + # + # Repository secrets (Settings -> Secrets and variables -> Actions): + # + # MODRINTH_TOKEN - personal access token from + # https://modrinth.com/settings/pats + # (scope: "Create versions") + # MODRINTH_PROJECT_ID - the Modrinth project ID for TradeWinds, + # on the project page under the three-dot + # menu ("Copy ID") + # + - name: Publish to Modrinth + uses: cloudnode-pro/modrinth-publish@0be4916ad5f081d936eb5615aa35ce3f0949979c # v2 + with: + token: ${{ secrets.MODRINTH_TOKEN }} + project: ${{ secrets.MODRINTH_PROJECT_ID }} + + # The release tag, or the manually supplied tag on workflow_dispatch + version: ${{ github.event.release.tag_name || inputs.tag }} + + # The GitHub release body becomes the changelog (empty when run + # manually) + changelog: ${{ github.event.release.body }} + + # Release channel - auto-detected from the version string: + # *-alpha -> alpha, *-beta -> beta, anything else -> release + # Override here if needed: release | beta | alpha + # channel: release + + # TradeWinds is a Paper addon + loaders: |- + paper + purpur + + # Only 26.2 is TESTED, and 26.2+ is what the release notes promise. + # The compile floor is much lower - the sources build clean against + # paper-api 1.21.10, and the only thing that breaks below that is + # the dialog API (Minecraft 1.21.6) - so this list can be widened + # as older versions are actually played. Widening it untested just + # buys bug reports. + game-versions: |- + 26.2 + + # Maven produces TradeWinds-.jar in target/ (see the pom's + # finalName), and the release profile above makes the tag + files: target/TradeWinds-${{ github.event.release.tag_name || inputs.tag }}.jar From 60ff7050a8d8c440c28bbc71dfcf6364f259ad03 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 8 Aug 2026 10:15:38 -0700 Subject: [PATCH 3/5] Publish for the whole 26.x family, not just 26.2 Ben: widen the Modrinth game versions. 26.2, 26.1.2, 26.1.1, 26.1 - the same list the other 26.2-era addons publish, so the version strings are known-good at Modrinth's end. The sources compile clean against paper-api 26.1.2 as well as 26.2 (checked, not assumed), so nothing of ours needs 26.2. It stops at 26.1 rather than following our own compile floor down to Minecraft 1.21.6, because that floor was never the binding one: BentoBox 3.18.1, which addon.yml requires, ships Java 25 bytecode (class major 69) like paper-api 26.x, while every 1.21.x paper-api is Java 21 (major 65). A 1.21.x server would have to run a Java 25 JVM before BentoBox could load at all, and BentoBox is built against the 26.x API besides. Our compiler only ever proved TradeWinds touches no 26.x-only Bukkit API. Only 26.2 is playtested; the rest is a compile-level claim, and the comment says so. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr --- .github/workflows/modrinth-publish.yml | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/modrinth-publish.yml b/.github/workflows/modrinth-publish.yml index 11bfbe7..461f5de 100644 --- a/.github/workflows/modrinth-publish.yml +++ b/.github/workflows/modrinth-publish.yml @@ -107,14 +107,25 @@ jobs: paper purpur - # Only 26.2 is TESTED, and 26.2+ is what the release notes promise. - # The compile floor is much lower - the sources build clean against - # paper-api 1.21.10, and the only thing that breaks below that is - # the dialog API (Minecraft 1.21.6) - so this list can be widened - # as older versions are actually played. Widening it untested just - # buys bug reports. + # The 26.x family, matching the other 26.2-era addons. TradeWinds' + # own sources compile clean against paper-api 26.1.2 as well as + # 26.2 (and, for what it is worth, against 1.21.10 - only the + # dialog API, Minecraft 1.21.6, breaks below that). + # + # The floor is NOT ours to set, though: BentoBox 3.18.1 - which + # addon.yml requires - ships Java 25 bytecode (class major 69), the + # same as paper-api 26.x, while every 1.21.x paper-api is Java 21 + # (major 65). A 1.21.x server would have to be started on a Java 25 + # JVM before BentoBox could even load, and BentoBox is built against + # the 26.x API besides. So 26.1 is the honest bottom, whatever our + # own compiler says. + # + # Only 26.2 is actually PLAYTESTED. game-versions: |- 26.2 + 26.1.2 + 26.1.1 + 26.1 # Maven produces TradeWinds-.jar in target/ (see the pom's # finalName), and the release profile above makes the tag From bd305f2cf22e2d99a99fbb8caa89191b5385641d Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 8 Aug 2026 10:28:50 -0700 Subject: [PATCH 4/5] Publish releases to CurseForge (project 1644572) Ben uploaded TradeWinds to CurseForge. This is the house publish.yml - the shared BentoBoxWorld/.github reusable workflow, same as BentoBox core, Border and AcidIsland - pinned to master's current HEAD (1f91a0ed, the same SHA those two use; GushBlock's pin is older). It publishes the jar ATTACHED to the GitHub release rather than rebuilding, so a dependency-repo outage cannot block a release that already has a working jar. The 0.1.0 release carries TradeWinds-0.1.0.jar, so that path is proven for this repo. Hangar is left blank (= skipped): HANGAR_API_KEY exists org-wide, but there is no hangar.papermc.io/BentoBoxWorld/TradeWinds project yet. One line to enable when there is. game_versions stays the 26.x family, matching modrinth-publish.yml rather than the longer 1.21.x list the other addons carry - BentoBox 3.18.1 is Java 25 bytecode and cannot load on a 1.21.x server's Java 21 runtime. CURSEFORGE_TOKEN is an ORGANISATION secret, already available here, so nothing more needs adding. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr --- .github/workflows/publish.yml | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..e2c57b9 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,42 @@ +# TradeWinds — .github/workflows/publish.yml +# Publishes the jar attached to a GitHub release to CurseForge via the shared +# BentoBoxWorld/.github reusable workflow. Downloads the release asset instead +# of rebuilding from source, so a dependency-repo outage cannot block a release +# that already has a built jar. The reusable workflow is pinned to a commit SHA +# (Sonar githubactions:S7637). workflow_dispatch lets you (re)publish a version. +# +# Modrinth is published by modrinth-publish.yml, which is a separate action and +# rebuilds from source - both fire on the same `release: published` event. +# +# Needs a CURSEFORGE_TOKEN repo secret. The reusable workflow skips a platform +# whose secret is missing, so without it this job runs and quietly does nothing. + +name: Publish release to CurseForge + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: "Version to publish (e.g. 0.1.1)" + required: true + type: string + +jobs: + publish: + uses: bentoboxworld/.github/.github/workflows/publish-platforms.yml@1f91a0edf72e8c86d671b3b8fdd3121ac6fb88e1 # master + with: + use_release_asset: "true" # publish the jar attached to the release; do not rebuild + java_version: "25" # only a fallback if use_release_asset is turned off - Paper 26.x and BentoBox ship Java 25 bytecode + hangar_slug: "" # no Hangar project yet; blank = skip Hangar + curseforge_id: "1644572" + # The 26.x family only, matching modrinth-publish.yml - NOT the longer + # 1.21.x list the other addons publish. BentoBox 3.18.1, which addon.yml + # requires, ships Java 25 bytecode like paper-api 26.x, while every + # 1.21.x paper-api is Java 21: a 1.21.x server cannot load BentoBox at + # all without a Java 25 JVM. Only 26.2 is actually playtested. + game_versions: "26.2,26.1.2,26.1.1,26.1" + version: ${{ inputs.version }} # empty on release events -> falls back to the release tag + secrets: + CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} From 70c1d67851e7b4cfc8baf65edd4b28fdf933dcfb Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 8 Aug 2026 10:38:02 -0700 Subject: [PATCH 5/5] Publish to Hangar as well hangar.papermc.io/BentoboxWorld/TradeWinds now exists, so the slug goes in and HANGAR_API_KEY (an organisation secret, already available here) is passed through. Both platforms publish from the same release event. hangar_owner stays at the reusable workflow's default, "BentoBoxWorld". Hangar's API reports the namespace as "BentoboxWorld" - lower-case b - for TradeWinds AND for Border, AcidIsland and bentobox, all of which publish with the default spelling, so the lookup is case-insensitive. The comment says as much, because the casing looks like a typo and is not. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr --- .github/workflows/publish.yml | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e2c57b9..aa31e9b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,17 +1,19 @@ # TradeWinds — .github/workflows/publish.yml -# Publishes the jar attached to a GitHub release to CurseForge via the shared -# BentoBoxWorld/.github reusable workflow. Downloads the release asset instead -# of rebuilding from source, so a dependency-repo outage cannot block a release -# that already has a built jar. The reusable workflow is pinned to a commit SHA -# (Sonar githubactions:S7637). workflow_dispatch lets you (re)publish a version. +# Publishes the jar attached to a GitHub release to CurseForge and Hangar via +# the shared BentoBoxWorld/.github reusable workflow. Downloads the release +# asset instead of rebuilding from source, so a dependency-repo outage cannot +# block a release that already has a built jar. The reusable workflow is pinned +# to a commit SHA (Sonar githubactions:S7637). workflow_dispatch lets you +# (re)publish a version. # # Modrinth is published by modrinth-publish.yml, which is a separate action and # rebuilds from source - both fire on the same `release: published` event. # -# Needs a CURSEFORGE_TOKEN repo secret. The reusable workflow skips a platform -# whose secret is missing, so without it this job runs and quietly does nothing. +# CURSEFORGE_TOKEN and HANGAR_API_KEY are ORGANISATION secrets, already +# available here. The reusable workflow skips a platform whose secret is +# missing, so a lost secret is a quiet no-op rather than a failed release. -name: Publish release to CurseForge +name: Publish release to CurseForge and Hangar on: release: @@ -29,7 +31,12 @@ jobs: with: use_release_asset: "true" # publish the jar attached to the release; do not rebuild java_version: "25" # only a fallback if use_release_asset is turned off - Paper 26.x and BentoBox ship Java 25 bytecode - hangar_slug: "" # no Hangar project yet; blank = skip Hangar + # hangar.papermc.io/BentoboxWorld/TradeWinds. hangar_owner is left at its + # default of "BentoBoxWorld": Hangar stores the namespace as + # "BentoboxWorld" (lower-case b) and every sibling project - Border, + # AcidIsland, bentobox - reports that spelling while publishing with the + # default, so the lookup is case-insensitive. Do not "fix" the casing. + hangar_slug: "TradeWinds" # blank = skip Hangar curseforge_id: "1644572" # The 26.x family only, matching modrinth-publish.yml - NOT the longer # 1.21.x list the other addons publish. BentoBox 3.18.1, which addon.yml @@ -39,4 +46,5 @@ jobs: game_versions: "26.2,26.1.2,26.1.1,26.1" version: ${{ inputs.version }} # empty on release events -> falls back to the release tag secrets: + HANGAR_API_KEY: ${{ secrets.HANGAR_API_KEY }} CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}