Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
23 changes: 22 additions & 1 deletion .github/workflows/publish-modrinth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,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
128 changes: 128 additions & 0 deletions .github/workflows/publish-polymart.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Publish to Polymart

on:
push:
tags: ['v*']
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.2.0"
}
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
Project history starts here. Earlier prototype builds are gone, so this file
is just the timeline going forward.

## [2026.2.0](https://github.com/xcutiboo/MythicRod/compare/v2026.1.0...v2026.2.0) (2026-05-24)


### Features

* add mythic rod tier above legendary ([8d787fd](https://github.com/xcutiboo/MythicRod/commit/8d787fd80664bf5224dccafb94bd53baee7c3a91))
* **api:** createRod(tier) helper ([54c80ee](https://github.com/xcutiboo/MythicRod/commit/54c80eeaba91c933a0b2c8cb56b3e0fcda71e9d9))
* **api:** preview eligible drops by player + biome ([612e935](https://github.com/xcutiboo/MythicRod/commit/612e935557e60a1478594f8de6f616d45e61c643))

## 2026.1.0

Fresh start. The plugin was rebuilt from scratch as a multi-module project
Expand Down
39 changes: 34 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,44 @@

[![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
- Folia region-aware schedulers
- Public Java API for other plugins
- Crowdin localization (English and Japanese shipped)
- Public Java API for downstream plugins
- 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 @@ -35,5 +56,13 @@ Everything else lives on the docs site:
- Configuration reference
- Drop table format
- Developer API
- Localization workflow
- 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).
34 changes: 34 additions & 0 deletions docs/developer-api/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,38 @@ public Optional<String> rodTier(ItemStack rod) {
}
```

## Minigame: "what could I catch here?" preview

`previewEligibleDrops(UUID, biomeKey)` returns the drop table after
biome and permission filters. Use it in tutorial overlays, fishing
spot UIs, or scoreboard tickers without running an actual catch.

```java
import io.xcutiboo.mythicrod.api.MythicRodAPI;
import io.xcutiboo.mythicrod.api.platform.PlatformDrop;
import io.xcutiboo.mythicrod.paper.api.MythicRodServices;
import org.bukkit.entity.Player;

public void showPreview(Player player) {
String biomeKey = player.getWorld()
.getBiome(player.getLocation())
.getKey()
.toString();

MythicRodServices.find().ifPresent(api -> {
var eligible = api.previewEligibleDrops(player.getUniqueId(), biomeKey);
int total = eligible.stream().mapToInt(PlatformDrop::getWeight).sum();
eligible.forEach(drop -> {
double share = total == 0 ? 0.0 : (drop.getWeight() * 100.0 / total);
player.sendMessage("§7- " + drop.getIdentifier()
+ " §8(" + String.format("%.1f", share) + "%§8)");
});
});
}
```

Pass `null` as the biome to ignore biome filters. The list is an
immutable snapshot; reloads do not retroactively change a returned
list.

[← Developer API](../developer-api.md)
28 changes: 21 additions & 7 deletions docs/developer-api/rods.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,31 @@ PersistentDataContainer keys:
| Key | Type | Meaning |
| --- | --- | --- |
| `mythicrod:custom_rod` | byte (1) | This item is a MythicRod rod |
| `mythicrod:rod_tier` | string | One of `basic`, `advanced`, `legendary` |
| `mythicrod:rod_tier` | string | One of `basic`, `advanced`, `legendary`, `mythic` |

The display name or lore is never used as identity. A survival player
cannot rename a vanilla rod into a MythicRod rod.

## Creating a rod from your plugin

If you want to hand out a MythicRod-compatible rod from your own code,
use the API's item factory instead of building an `ItemStack` by hand.
That route keeps your rod compatible with future internal changes:
The cleanest path is `MythicRodAPI.createRod(tier)`. It returns the
fully-tagged rod with display name, lore, glow, and unbreakable flag
matching MythicRod's built-in presets.

```java
MythicRodAPI api = MythicRodServices.require();
PlatformItem rod = api.createRod("advanced").orElseThrow();
player.getInventory().addItem(((PaperPlatformItem) rod).getItemStack());
```

Valid tiers (case-insensitive): `basic`, `advanced`, `legendary`,
`mythic`. An unknown tier returns a `Result.failure` rather than
throwing.

### Manual rod creation (legacy path)

If you need to override the preset, build the rod through the item
factory and write the PDC keys yourself:

```java
MythicRodAPI api = MythicRodServices.require();
Expand All @@ -29,9 +44,8 @@ meta.getPersistentDataContainer().set(rodTier, PersistentDataType.STRING, "advan
rod.setItemMeta(meta);
```

This is rare. Most integrations should hand off to MythicRod's own
`/mythicrod give` flow or use an `ExternalDropProvider` for tier-aware
rewards.
Reach for the manual path only when the built-in preset does not fit;
otherwise prefer `createRod(tier)`.

## Detecting a MythicRod rod

Expand Down
Loading