API利用ガイド
API利用ガイド(アドオン開発者向け)
MoripaFishingは、アドオン開発者がプラグインの機能を拡張できるAPIを提供しています。 このガイドでは、APIの導入方法から基本的な使い方までを解説します。
依存関係の追加
リポジトリの設定
MoripaFishing APIはMaven Centralで公開されています。 認証不要で利用できます。
Gradle (Kotlin DSL)
repositories {
mavenCentral()
}
dependencies {
compileOnly("party.morino:moripafishing-api:<version>")
}Gradle (Groovy)
repositories {
mavenCentral()
}
dependencies {
compileOnly 'party.morino:moripafishing-api:<version>'
}Maven
<dependencies>
<dependency>
<groupId>party.morino</groupId>
<artifactId>moripafishing-api</artifactId>
<version>VERSION</version>
<scope>provided</scope>
</dependency>
</dependencies>Integration 向けの SPI (WorldStateApplier / WorldLifecycle / WeatherSource) も同じ moripafishing-api に含まれます
(party.morino.moripafishing.api.core.world.spi パッケージ)。Integration の作り方は Integration とは を参照してください。
プラグイン定義ファイルの設定
MoripaFishing 本体は Paper プラグイン (paper-plugin.yml) として配布しているため、アドオンも paper-plugin.yml で
Core への必須依存を宣言します。こうすると Core が先に有効化されるため、onEnable からそのまま API を取得できます。
dependencies:
server:
MoripaFishing:
load: BEFORE
required: trueAPIの取得
APIの取得口はMoripaFishingAPIProviderです。コア(MoripaFishing)は有効化時にAPIをBukkitのServicesManagerへ登録し、無効化時に解除します。MoripaFishingAPIProviderはその登録を引くだけの状態を持たないファサードです。
プラグイン名でのキャスト(getPlugin("MoripaFishing"))には依存しないでください。
Kotlin
import party.morino.moripafishing.api.MoripaFishingAPIProvider
// コア未初期化時は IllegalStateException
val api = MoripaFishingAPIProvider.get()
// null 許容で取得したい場合
val apiOrNull = MoripaFishingAPIProvider.getOrNull()Java
import party.morino.moripafishing.api.MoripaFishingAPI;
import party.morino.moripafishing.api.MoripaFishingAPIProvider;
// @JvmStatic のため、Java からも静的メソッドとして呼び出せます
MoripaFishingAPI api = MoripaFishingAPIProvider.get();ServicesManager 経由の取得
MoripaFishingAPIProviderと同じく、BukkitのServicesManagerから直接取得することもできます。
val api = Bukkit.getServicesManager().load(MoripaFishingAPI::class.java)
?: error("MoripaFishing is not loaded")利用可能なマネージャー
APIインスタンスから以下のマネージャーを取得できます。
| マネージャー | メソッド | 説明 |
|---|---|---|
ConfigManager | getConfigManager() | プラグイン設定(config.json)の取得 |
FishManager | getFishManager() | 魚の登録・取得 |
RarityManager | getRarityManager() | レアリティの登録・取得 |
FishDrawer | getFishDrawer() | 魚の抽選 |
WorldManager | getWorldManager() | 釣り場ワールドの管理 |
AnglerManager | getAnglerManager() | つりびと(プレイヤー)の取得 |
主なマネージャーのメソッドは次のとおりです。
FishManager:getAll()/get(id)/getByRarity(rarity)/register(fish)/unregister(id)RarityManager:getAll()/get(id)/register(rarity)/unregister(id)FishDrawer:draw(worldId)/roll(fishData)WorldManager:getDefaultWorldId()/getWorlds()/getWorld(id)/getWorldByKey(key)/getGeneratorIds()/createWorld(id, generatorId)/deleteWorld(id)/updateWorldConfig(config)/reload()AnglerManager:getAngler(anglerId)/getOnlineAnglers()
FishManager / RarityManager の register は検証に失敗すると IllegalArgumentException を投げます。register で追加した定義は /mf reload で破棄されるため、必要なら MoripaFishingReloadedEvent で登録し直してください。
WorldManager の取得系メソッドは任意のスレッドから呼べます。変更系 (createWorld / deleteWorld / updateWorldConfig / reload) はメインスレッドから呼んでください。
独自の天候ソースは ServicesManager に登録します。詳細は WeatherSource の登録 を参照してください。
使用例
魚の一覧を取得する
val fishManager = api.getFishManager()
val allFish = fishManager.getAll()
allFish.forEach { fish ->
// displayName は Map<Locale, String> なので、ロケールを指定して取り出す
val name = fish.displayName[Locale.JAPAN] ?: fish.id.value
println("${fish.id.value}: $name")
}レアリティ別に魚を取得する
val fishManager = api.getFishManager()
val rarityId = RarityId("legendary")
val legendaryFish = fishManager.getByRarity(rarityId)つりびとの情報を取得する
AnglerId はプレイヤーの Minecraft UUID そのもので、JSON では UUID 文字列になります。
getAngler は存在確認をせずに常に Angler を返します。オフラインのつりびとでは getWorld() と getLocation() が null になります。
val anglerManager = api.getAnglerManager()
val angler = anglerManager.getAngler(AnglerId(player.uniqueId))
println("Name: ${angler.getName()}")
println("World: ${angler.getWorld()?.getId()?.value}")魚を抽選する
val fish = api.getFishDrawer().draw(FishingWorldId("default")) ?: return
println("${fish.data.id.value} (${fish.rarity.id.value}): ${fish.size}cm, worth ${fish.worth}")ワールドの一覧を取得する
getWorlds() と getWorld(id) は、設定ファイルが存在する 定義済み の釣りワールドを返します。
対応する Bukkit ワールドがロードされているかどうかは Bukkit.getWorld(world.getWorldKey()) で確認します。
val worldManager = api.getWorldManager()
worldManager.getWorlds().forEach { world ->
val loaded = Bukkit.getWorld(world.getWorldKey()) != null
println("World: ${world.getId().value} / ${world.getWeather()} / loaded=$loaded")
}Bukkit ワールドから釣りワールドを引く
Bukkit ワールドが釣りワールドかどうかは、キーで getWorldByKey を引いて判定します。
val fishingWorld = api.getWorldManager().getWorldByKey(player.world.key)
if (fishingWorld != null) {
player.sendMessage("ここは釣りワールド ${fishingWorld.getId().value} です (天候: ${fishingWorld.getWeather()})")
}ワールドの設定を変更する
FishingWorld は読み取り専用です。設定を変えるには、getConfig() の値をもとに新しい設定を作り、
メインスレッドから updateWorldConfig に渡します。保存とロード済みワールドへの適用が即座に行われます。
val worldManager = api.getWorldManager()
val world = worldManager.getWorld(FishingWorldId("default")) ?: return
val config = world.getConfig()
worldManager.updateWorldConfig(
config.copy(
spawn = SpawnPoint(0.0, 70.0, 0.0, 90f, 0f),
border = BorderConfig(centerX = 0.0, centerZ = 0.0, size = 200.0),
),
)ワールドを作成する
ワールド作成の実体は WorldLifecycle Integration が提供します。
未導入の環境では createWorld が false を返し、getGeneratorIds() は空を返します。
サーバーの起動完了 (ServerLoadEvent) 前に呼んだ createWorld も false を返すため、onEnable からではなく起動後に呼んでください。
val worldManager = api.getWorldManager()
val generatorIds = worldManager.getGeneratorIds()
// メインスレッドから呼ぶ
val created = worldManager.createWorld(FishingWorldId("my_world"), "terra")WeatherSource の登録
アドオンは WeatherSource を実装して独自の天候ソースを提供できます。
Bukkit の ServicesManager に登録し、ワールドの気候で {"type": "external", "source": "<キー>"} を指定すると、
そのワールドの天候がアドオンに委ねられます。
import net.kyori.adventure.key.Key
import org.bukkit.plugin.ServicePriority
import party.morino.moripafishing.api.core.world.spi.WeatherSource
import party.morino.moripafishing.api.model.world.FishingWorldId
import party.morino.moripafishing.api.model.world.WeatherType
class MyWeatherSource : WeatherSource {
override val key: Key = Key.key("myplugin", "realistic")
// true: この天候をワールドへ適用させる (Weather Integration 導入時)。false: 魚の条件にのみ使う
override val appliesToWorld: Boolean = true
// 任意のスレッドから呼ばれる。Bukkit API を直接呼ばず、自前の状態だけを読むこと
override fun getWeather(
worldId: FishingWorldId,
worldKey: Key,
): WeatherType = WeatherType.SUNNY
}
override fun onEnable() {
server.servicesManager.register(WeatherSource::class.java, MyWeatherSource(), this, ServicePriority.Normal)
}- 登録はプラグインの無効化時に Bukkit が自動で解除します。
- 同じ
keyの登録が複数ある場合は、優先度の高い登録が使われます。 - Core は天候を決めるたびに
ServicesManagerを引き直すため、後から登録したソースも次の判定から使われます。 - 一致するソースが登録されていない間は、内蔵の天候カレンダー (
randomの既定値) で天候を決め、警告を一度だけ出します。
イベント
MoripaFishingは、釣果・ワールド・天候・設定に関するBukkitイベントを発火します。
いずれもmoripafishing-apiに含まれ、通常のBukkitリスナーで購読できます。
| イベント | パッケージ | Cancellable | 発火タイミング |
|---|---|---|---|
AnglerFishCaughtEvent | api.event.fishing | ✔ | 釣り上げの確定直前。キャンセルするとバニラの釣り上げごと取り消される。setCaughtFishで釣果を差し替え可能 |
AnglerFishCaughtResultEvent | api.event.fishing | ✘ | 釣り上げ処理の完了後。通知・記録などの副作用はこちらを購読する |
AnglerJoinTeleportEvent | api.event.angler | ✔ | 参加時テレポートの実行直前(WorldLifecycle Integration が発火)。setDestinationで移動先を変更可能 |
FishingWorldLoadEvent | api.event.world | ✘ | 定義済みの釣りワールドの Bukkit ワールドがロードされ、状態の適用を終えた後(Bukkit の WorldLoadEvent に追従。サーバー起動時点でロード済みのワールドと、/mf reload で新たに定義されたロード済みのワールドにも発火) |
FishingWorldUnloadEvent | api.event.world | ✘ | 定義済みの釣りワールドの Bukkit ワールドのアンロード時(Bukkit の WorldUnloadEvent に追従。プラグイン無効化時には発火しない) |
FishingWorldCreateEvent | api.event.world | ✘ | createWorld の成功後(作成したワールドの FishingWorldLoadEvent の後) |
FishingWorldDeleteEvent | api.event.world | ✔ | deleteWorld による削除の直前。キャンセルで削除中止 |
FishingWorldDeletedEvent | api.event.world | ✘ | 削除の完了後(ロード済みだった場合は FishingWorldUnloadEvent の後) |
FishingWorldWeatherChangeEvent | api.event.world | ✘ | 魚の抽選条件に使う天候 (getWeather()) の変化時。常にメインスレッドで発火し、リフレッシュ間隔ごとに検知する。初回の観測では発火しない |
MoripaFishingReloadedEvent | api.event | ✘ | /mf reloadで設定・ワールド・レアリティ・魚・翻訳の再読み込みがすべて完了した後。設定由来の値をキャッシュしている場合はここで再取得する |
パッケージはいずれもparty.morino.moripafishing.配下です(例: party.morino.moripafishing.api.event.fishing.AnglerFishCaughtEvent)。
AnglerFishCaughtEvent
つりびとが魚を釣ったときに発生するイベントです。キャンセルや釣果の差し替えができます。
差し替えた釣果はアイテム化と後続のAnglerFishCaughtResultEventに反映されます。
import party.morino.moripafishing.api.event.fishing.AnglerFishCaughtEvent
class MyListener : Listener {
@EventHandler(ignoreCancelled = true)
fun onFishCaught(event: AnglerFishCaughtEvent) {
val angler = event.getAngler()
val caughtFish = event.getCaughtFish()
// 釣れた魚の情報を取得
val fishId = caughtFish.fish
val size = caughtFish.size
val rarity = caughtFish.rarity
val worth = caughtFish.worth
// つりびとの情報
val playerName = angler.getName()
// イベントのキャンセル(釣り上げ自体を取り消す)
// event.isCancelled = true
// 釣果の差し替え
// event.setCaughtFish(caughtFish.copy(worth = worth * 2))
}
}AnglerFishCaughtResultEvent
釣り上げ処理が完了した後に発生するモニター用途のイベントです。
AnglerFishCaughtEventがキャンセルされず、釣果のアイテム化まで完了した場合にのみ発火します。
アナウンスやログ記録などの副作用はこちらを購読してください。
import party.morino.moripafishing.api.event.fishing.AnglerFishCaughtResultEvent
class MyListener : Listener {
@EventHandler
fun onFishCaughtResult(event: AnglerFishCaughtResultEvent) {
val caughtFish = event.getCaughtFish()
// 確定した釣果に対する通知・記録など
}
}CaughtFish のフィールドは CaughtFish を参照してください。
ワールド関連のイベント
釣りワールドのロード・アンロード・作成・削除・天候変化に対応するイベントです。
いずれも getWorldId() で対象の FishingWorldId を取得できます。FishingWorldDeleteEvent だけが
Cancellable を実装しており、削除を中止できます。
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import party.morino.moripafishing.api.model.world.FishingWorldId
import party.morino.moripafishing.api.event.world.FishingWorldDeleteEvent
class ProtectWorldListener : Listener {
private val protected = setOf(FishingWorldId("protected_world"))
@EventHandler(ignoreCancelled = true)
fun onWorldDelete(event: FishingWorldDeleteEvent) {
if (event.getWorldId() in protected) {
event.isCancelled = true
}
}
}FishingWorldWeatherChangeEvent は getOldWeather() と getNewWeather() で変化前後の
WeatherType を取得できます。常にメインスレッドで発火するため、ハンドラから Bukkit API をそのまま呼べます。
AnglerJoinTeleportEvent
つりびとが参加して釣り場ワールドへテレポートする直前に発火します。Cancellable であるため
テレポート自体を中止できるほか、setDestination(LocationData) で行き先を差し替えることもできます。
import party.morino.moripafishing.api.event.angler.AnglerJoinTeleportEvent
@EventHandler
fun onJoinTeleport(event: AnglerJoinTeleportEvent) {
val angler = event.getAngler()
// 行き先を変更する
event.setDestination(event.getDestination().copy(x = 100.0, z = 100.0))
// テレポート自体を中止する
// event.isCancelled = true
}MoripaFishingReloadedEvent
/mf reload で設定・ワールド・レアリティ・魚・翻訳の再読み込みがすべて完了した後に、メインスレッドで発火します。
設定由来の値をキャッシュしているアドオンや、API の register で魚・レアリティを追加しているアドオンは、このイベントで再取得・再登録してください。
Dokka APIリファレンス
詳細なAPIリファレンスはDokkaを参照してください。
注意事項
注意
現在MoripaFishingは開発中のため、APIに破壊的な変更が行われる可能性があります。 バージョンアップ時は、リリースノートを確認してください。