Skip to content
Open
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
10 changes: 6 additions & 4 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ jobs:
python-version: '3.12'

- name: Install MkDocs Material
# Pin exact versions and force binary wheels: Sonar S8544/S8541
# (unpinned dependencies + setup-script execution risk).
run: |
python -m pip install --upgrade pip
pip install \
"mkdocs-material>=9.5" \
"mkdocs-static-i18n>=1.2" \
"mkdocs-git-revision-date-localized-plugin>=1.2"
pip install --only-binary :all: \
"mkdocs-material==9.5.45" \
"mkdocs-static-i18n==1.2.3" \
"mkdocs-git-revision-date-localized-plugin==1.2.9"

- name: Configure Pages
uses: actions/configure-pages@v5
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/publish-hangar.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Publish to Hangar

on:
push:
tags: ['v*']
# Publish only on a real GitHub release. Bare tag pushes do not fire this.
release:
types: [published]
workflow_dispatch:

permissions:
Expand Down
28 changes: 25 additions & 3 deletions .github/workflows/publish-modrinth.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Publish to Modrinth

on:
push:
tags: ['v*']
# Publish only on a real GitHub release. Bare tag pushes do not fire this.
release:
types: [published]
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -73,8 +74,29 @@ jobs:
echo "jar_publish=$jar_publish"
} >> "$GITHUB_OUTPUT"

- name: Publish to Modrinth
- name: Check Modrinth for an existing version
if: env.MODRINTH_TOKEN != '' && steps.meta.outputs.jar_publish == 'true'
id: dedupe
env:
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
existing=$(curl -fsSL \
-H "User-Agent: xcutiboo/MythicRod CI" \
"https://api.modrinth.com/v2/project/mythicrod/version/${VERSION}" \
-o /dev/null -w "%{http_code}" || true)
if [[ "$existing" == "200" ]]; then
echo "Modrinth already has version $VERSION; skipping upload."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Publish to Modrinth
if: |
env.MODRINTH_TOKEN != ''
&& steps.meta.outputs.jar_publish == 'true'
&& steps.dedupe.outputs.skip == 'false'
uses: Kir-Antipov/mc-publish@v3.3
with:
modrinth-id: mythicrod
Expand Down
129 changes: 129 additions & 0 deletions .github/workflows/publish-polymart.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: Publish to Polymart

on:
# Publish only on a real GitHub release. Bare tag pushes do not fire this.
release:
types: [published]
workflow_dispatch:

permissions:
contents: read

jobs:
publish:
runs-on: ubuntu-latest
env:
POLYMART_API_TOKEN: ${{ secrets.POLYMART_API_TOKEN }}
POLYMART_RESOURCE_ID: ${{ vars.POLYMART_RESOURCE_ID }}

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@ed408507eac070d1f99cc633dbcf757c94c7933a # v4

- name: Set up JDK 25
uses: actions/setup-java@v5
with:
java-version: '25'
distribution: 'temurin'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4

- name: Make Gradle wrapper executable
run: chmod +x ./gradlew

- name: Skip when not configured
if: env.POLYMART_API_TOKEN == '' || env.POLYMART_RESOURCE_ID == ''
run: |
echo "POLYMART_API_TOKEN secret or POLYMART_RESOURCE_ID variable is not set; skipping Polymart publish."
exit 0

- name: Build shadow jar
if: env.POLYMART_API_TOKEN != '' && env.POLYMART_RESOURCE_ID != ''
run: ./gradlew :mythicrod-paper:shadowJar --stacktrace --no-daemon

- name: Resolve release metadata
if: env.POLYMART_API_TOKEN != '' && env.POLYMART_RESOURCE_ID != ''
id: meta
env:
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
version="${REF_NAME#v}"
if [[ -z "$version" || "$version" == "$REF_NAME" ]]; then
version=$(./gradlew -q :mythicrod-paper:properties | awk -F': *' '/^version:/{print $2; exit}')
fi
jar=$(ls mythicrod-paper/build/libs/MythicRod-Paper-*.jar | grep -v -- '-dev' | head -1)
beta=0
snapshot=0
case "$version" in
*-rc*|*-beta*|*-alpha*) beta=1 ;;
*-snapshot*|*-SNAPSHOT*) snapshot=1 ;;
esac
{
echo "version=$version"
echo "jar=$jar"
echo "beta=$beta"
echo "snapshot=$snapshot"
} >> "$GITHUB_OUTPUT"

- name: Check Polymart for an existing version
if: env.POLYMART_API_TOKEN != '' && env.POLYMART_RESOURCE_ID != ''
id: dedupe
env:
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
existing=$(curl -fsSL "https://api.voxel.shop/v1/getResourceUpdates?resource_id=${POLYMART_RESOURCE_ID}" \
| jq -r --arg v "$VERSION" '.response.updates[]? | select(.version == $v) | .id' | head -1)
if [[ -n "$existing" ]]; then
echo "Polymart already has update id $existing for version $VERSION; skipping upload."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Upload version to Polymart
if: |
env.POLYMART_API_TOKEN != ''
&& env.POLYMART_RESOURCE_ID != ''
&& steps.dedupe.outputs.skip == 'false'
# Polymart's voxel.shop rebrand is in open beta and the
# postUpdate API has been returning NO_KEY for valid keys.
# Treat the upload as soft so a transient Polymart auth
# bug cannot block the rest of the release lane (Hangar,
# Modrinth, GitHub Release, JitPack).
continue-on-error: true
env:
VERSION: ${{ steps.meta.outputs.version }}
JAR: ${{ steps.meta.outputs.jar }}
BETA: ${{ steps.meta.outputs.beta }}
SNAPSHOT: ${{ steps.meta.outputs.snapshot }}
run: |
set -euo pipefail
message=$(awk '/^## \['"$VERSION"'\]/{flag=1; next} /^## \[/{flag=0} flag' CHANGELOG.md \
| sed '/./,$!d' | head -200)
if [[ -z "$message" ]]; then
message="See https://github.com/xcutiboo/MythicRod/releases/tag/v${VERSION}"
fi
response=$(curl -fsS -X POST "https://api.voxel.shop/v1/postUpdate" \
-F "api_key=${POLYMART_API_TOKEN}" \
-F "resource_id=${POLYMART_RESOURCE_ID}" \
-F "version=${VERSION}" \
-F "title=MythicRod ${VERSION}" \
-F "message=${message}" \
-F "beta=${BETA}" \
-F "snapshot=${SNAPSHOT}" \
-F "file=@${JAR}")
echo "$response"
status=$(echo "$response" | jq -r '.response.success // false')
if [[ "$status" != "true" ]]; then
echo "Polymart rejected the upload."
echo "$response" | jq -r '.response.errors[]?' || true
exit 1
fi
31 changes: 31 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Release Please

on:
push:
branches: [master]

permissions:
contents: write
pull-requests: write

jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
with:
release-type: simple
package-name: MythicRod
include-v-in-tag: true
changelog-types: |
[
{"type":"feat","section":"Features","hidden":false},
{"type":"fix","section":"Bug fixes","hidden":false},
{"type":"perf","section":"Performance","hidden":false},
{"type":"deps","section":"Dependencies","hidden":false},
{"type":"docs","section":"Documentation","hidden":false},
{"type":"refactor","section":"Refactoring","hidden":false},
{"type":"chore","section":"Chores","hidden":true},
{"type":"test","section":"Tests","hidden":true},
{"type":"ci","section":"CI","hidden":true}
]
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "2026.1.0"
}
52 changes: 45 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,52 @@

[![Release](https://img.shields.io/github/v/release/xcutiboo/MythicRod?style=flat-square)](https://github.com/xcutiboo/MythicRod/releases) [![Paper 26.1.2](https://img.shields.io/badge/Paper-26.1.2-dca13a?style=flat-square)](https://papermc.io) [![Folia](https://img.shields.io/badge/Folia-region--ready-835516?style=flat-square)](https://papermc.io/software/folia) [![Discord](https://img.shields.io/badge/Discord-chat-5865F2?style=flat-square)](https://discord.gg/MDwtUgxX9U)

Weighted loot tables with biome filters, permission gates, an in-game
Weighted fishing drops with biome rules, permission gates, an in-game
drop editor, per-player statistics, and a small public API for Paper
and Folia.

</div>

<!-- HERO: 1280x640 lake render with a player landing a legendary catch. -->
<!-- ![Hero](assets/screenshots/hero.png) -->

## Features

- Weighted drop tables with biome and permission filters
- In-game GUI editor for drop entries
- In-game GUI editor for drop entries (live, no reload)
- Per-player and global statistics plus a leaderboard
- Three rod tiers (basic / advanced / legendary) gated by permission
- Four rod tiers (basic / advanced / legendary / mythic), each behind
its own permission
- Tier-coded catch celebrations (per-player particles + sound, helix
animation on top tiers, throttled to once per 4 seconds)
- Folia region-aware schedulers
- Public Java API for other plugins
- Crowdin localization (English and Japanese shipped)
- Public Java API for downstream plugins: `createRod(tier)`,
`previewEligibleDrops(uuid, biome)`, external drop providers,
stats lookup + leaderboard, five Paper events (bite, roll, catch,
stats, reload)
- PlaceholderAPI tokens for scoreboards and chat (`%mythicrod_total%`,
`%mythicrod_legendary%`, `%mythicrod_rod_tier%`, ...)
- Crowdin localisation (English and Japanese ship in the jar)
- Optional Nexo integration for custom items

## Preview

<!-- Screenshots and short clips. PNG for stills, WebM or APNG for motion. -->
<!-- Suggested sizes: 1280×720 for full screens, 720×480 for tight crops. -->
<!-- Drop each file into assets/screenshots/ and uncomment the matching line. -->

<!-- Drop editor GUI (live edit, no reload): -->
<!-- ![Drop editor](assets/screenshots/editor.png) -->

<!-- Catch in action (rare drop landing, particles + sound): -->
<!-- ![Catch demo](assets/screenshots/catch.gif) -->

<!-- /mythicrod stats and the leaderboard view: -->
<!-- ![Stats screen](assets/screenshots/stats.png) -->

<!-- /mythicrod drops preview <biome> personalised by viewer perms: -->
<!-- ![Drops preview command](assets/screenshots/preview.png) -->

## Quick install

Drop the jar in `plugins/`, start the server, then `/mythicrod reload`
Expand All @@ -34,6 +63,15 @@ Everything else lives on the docs site:
- Commands and permissions
- Configuration reference
- Drop table format
- Developer API
- Localization workflow
- Developer API (events, drop providers, rod factory, stats)
- PlaceholderAPI token reference
- Localisation workflow
- Release runbook

## Community

- [Discord](https://discord.gg/MDwtUgxX9U) for support and feature
discussion.
- [Crowdin](https://crowdin.com/project/mythicrod) for translations.
- Issues and pull requests on
[GitHub](https://github.com/xcutiboo/MythicRod).
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ subprojects {
maven("https://repo.papermc.io/repository/maven-public/") {
name = "papermc"
}
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") {
name = "placeholderapi"
}
}

java {
Expand Down
23 changes: 22 additions & 1 deletion docs/developer-api/events.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
# Paper events

MythicRod publishes four Paper events. All live under
MythicRod publishes five Paper events. All live under
`io.xcutiboo.mythicrod.paper.events`.

| Event | Cancellable | Thread |
| --- | --- | --- |
| `MythicRodBiteEvent` | yes | player-owner |
| `MythicRodRewardRollEvent` | no | player-owner |
| `MythicRodFishCatchEvent` | yes | player-owner |
| `MythicRodStatsUpdateEvent` | no | stats writer |
| `MythicRodReloadEvent` | no | reload-caller |

## `MythicRodBiteEvent`

Fires when a fish bites a MythicRod rod (`PlayerFishEvent.State.BITE`).
Only fires for rods MythicRod recognises - vanilla rods never trigger
it. Use this for skill-check minigames, custom bite cues, or to
short-circuit the catch when the player is missing a buff item.

```java
@EventHandler(ignoreCancelled = true)
public void onBite(MythicRodBiteEvent event) {
Player player = event.getPlayer();
Location at = event.getHook().getLocation();
player.spawnParticle(Particle.BUBBLE_POP, at, 8, 0.3, 0.1, 0.3, 0);
skillCheck.start(player, () -> event.setCancelled(true));
}
```

Cancelling the event cancels the underlying `PlayerFishEvent`, so the
catch never resolves.

## `MythicRodRewardRollEvent`

Fires once MythicRod has identified eligible drops but **before** it has
Expand Down
Loading
Loading