Files
kayjaydee 5fc3bda1c5 feat(examples): four runnable plugin examples
- player-load: load-on-join (PlayerReadyEvent → IO → modify).
- async-moderation: IAsyncEvent → coroutine bridge for chat moderation.
- periodic-leaderboard: pluginScope loop with parallel reads.
- bounty-board: kitchen-sink demo exercising every v0.1 primitive.
2026-04-28 16:30:39 +02:00

72 lines
2.9 KiB
Markdown

# bounty-board
The kitchen-sink example. A PvP bounty system that exercises every public
Async v0.1 primitive in one realistic plugin.
Players post bounties on each other (paying gold to put a price on a head).
On payout, the killer collects. A periodic public broadcast lists the top 5
active bounties; an optional Discord webhook fires on placements and
payouts; in-memory state writes to disk on shutdown (load on boot left to your plugin).
## What it shows
| Primitive | Where |
|---|---|
| `installAsync()` | `start()` |
| `Async.shutdown()` | `shutdown()` |
| `ComponentRegistry.register<T>(...)` | `start()`, twice |
| `playerScope(player)` | `registerJoinHook` |
| `pluginScope(this)` | chat handlers, broadcast loop, shutdown save |
| `worldScope(world)` | `startWorldDecayForKnownWorlds` |
| `WorldScopes.cancel(uuid)` | `onWorldUnload` |
| `Player.handle()` / `PlayerRef.toEntityHandle()` | every `modify` / `read` |
| `withContext(AsyncDispatchers.HytaleIO)` | webhook + persistence |
| `delay(...)` (HytaleScheduled) | broadcast and decay loops |
| `read<T, R>` strict | `handlePayout` |
| `readOrNull<T, R>` | join, broadcast, wallet query |
| `modify<T>` Unit | bounty append, decay, payout |
| `modify<T, R>` returning Boolean | atomic gold deduction |
| `withTimeout(...)` | guards the world-death race in the broadcast loop |
| `ComponentNotFoundException` handling | `handlePayout` |
| `WorldClosedException` handling | `notifyDiscord` |
| `pluginScope.future { }` | every chat command |
| Parallel `read` via `async`/`awaitAll` | `broadcastTop5` |
## Commands
| Chat | Effect |
|---|---|
| `!bounty?` | Show your wallet. |
| `!bounty <player> <amount>` | Pay gold to put a bounty on someone. |
| `!payout <player>` | Admin demo: collect bounties from a target (stand-in for a kill hook — Hytale's v0.1 SDK doesn't expose `PlayerDeathEvent`). |
The `!` prefix matters: Hytale's `CommandManager` swallows every `/`-prefixed
message before `PlayerChatEvent` fires, so this example uses `!` to flow
through chat normally.
## Build
```bash
./gradlew shadowJar
# → build/libs/bounty-board.jar
```
## Run
1. Drop the JAR in `mods/`.
2. Wire the SDK stubs in `BountyPlugin.kt`:
- `onlinePlayerRefs()`, `onlinePlayerRefsIn(world)`, `knownWorlds()`,
`broadcastToAllWorlds(text)` — your dev server's accessors.
- `WalletBinding.componentType()`, `BountyStateBinding.componentType()`
register `Wallet` and `BountyState` on `EntityStore.REGISTRY` and
return the result.
3. Optionally set `BOUNTY_WEBHOOK_URL` in the server's env for Discord
notifications.
4. In game: `!bounty <other-player> 100` places, `!payout <other-player>`
collects, the top-5 broadcast appears every 60s.
The persistence layer (`BountyRepo`) is intentionally a one-line JSON
serializer for the demo. Real plugins should use `kotlinx.serialization` or
a database — the load-bearing pattern is
`withContext(HytaleIO) { Files.writeString(...) }`, not the JSON shape.