たくさんの自由帳
Androidのお話
たくさんの自由帳
投稿日 : | 0 日前
文字数(だいたい) : 3328
どうもこんばんわ。
一応言っておくと定住先でよく使われる1.12.2ではなく1.21.2です。
(最近はどこに定住しているのか知らない)
自作 MOD のMinecraft 1.21.2への移行メモです。
移行メモ振り返ってみたけど、1.20.6から全部今年なの・・・
今回も地味に大変だった。前回のエンチャント作り直しよりはマシかな?
レシピ周り(ServerWorld#getRecipeManagerとか)触ってなければ多分今回のアップデートは怖くない。はず。
Fabricの方々がまとめてくれているので、どこが変わっているかはそれを見ればいいです。Fabric for Minecraft 1.21.2
Minecraft 1.21.2, the “Bundles of Bravery” drop, is expected to release soon. Mojang has recently announced that they will release these “drops” throughout the year. Like with other updates, this drop contains some significant changes affecting mod makers.
https://fabricmc.net/2024/10/14/1212.html
はバグ修正バージョンらしいので、1.21.2からの対応は無いと思います。
オブジェクト{ "item": "アイテムID" }じゃなくて、アイテムのIDを渡すようになりました。
{
"type": "minecraft:crafting_shaped",
"pattern": [
"DDD",
"DDD",
"DDD"
],
"key": {
- "D": {
- "item": "minecraft:crafting_table"
- }
+ "D": "minecraft:crafting_table"
},
"result": {
"count": 1,
"id": "clickmanaita:clickmanaita_wood"
}
}1.21.1時代のコードを貼り付けるとこんなエラーが出る。
NullPointerException: Item id not setこれは、Minecraftにアイテムを追加する前までに、アイテムの設定(食べ物かどうか、スタック数)へアイテムIDを設定しておく必要があるということです。
いままでは、Minecraftに登録する際に、アイテムIDとアイテムのクラスのインスタンスを渡せば登録できたのですが、もう一箇所、アイテム設定の時点でも渡す必要があります。
Fabric + Kotlinの場合は多分こんな感じで、Item.Settings#registryKeyを呼べば良いはず。
// アイテムID
private val ID_CLICKMANAITA_WOOD = Identifier.of("clickmanaita", "clickmanaita_wood")
// レジストリキー
private val KEY_CLICKMANAITA_WOOD = RegistryKey.of(RegistryKeys.ITEM, ID_CLICKMANAITA_WOOD)
/** 木製。2個増える */
val CLICKMANAITA_WOOD = ClickManaitaBaseItem(settings = Item.Settings().registryKey(KEY_CLICKMANAITA_WOOD), dropSize = 2)
/** アイテムを登録する。この関数を MOD のエントリーポイントで呼び出す */
fun register() {
// アイテム追加
Registry.register(Registries.ITEM, KEY_CLICKMANAITA_WOOD, CLICKMANAITA_WOOD)
}Forgeの場合は多分こんな感じで、Item.Properties#setIdを呼べば良いはず。
// 各 アイテム ID
private static final ResourceLocation ID_CLICKMANAITA_WOOD = ResourceLocation.fromNamespaceAndPath(ClickManaita.MOD_ID, "clickmanaita_wood");
// 各 アイテム リソースキー
private static final ResourceKey<Item> KEY_CLICKMANAITA_WOOD = ResourceKey.create(Registries.ITEM, ID_CLICKMANAITA_WOOD);
public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, ClickManaita.MOD_ID);
/**
* 木製のクリックまな板
* 2倍化
*/
public static final RegistryObject<ClickManaitaBaseItem> CLICKMANAITA_WOOD = ITEMS.register(KEY_CLICKMANAITA_WOOD.location().getPath(), () -> createItem(KEY_CLICKMANAITA_WOOD, 2, MaterialColor.MATERIAL_WOOD_COLOR));
/**
* アイテムを登録する。このメソッドを MOD のエントリーポイントから呼び出す。
*/
public static void register(IEventBus eventBus) {
// 登録
ITEMS.register(eventBus);
}
/**
* ClickManaitaBaseItem を作成する
*
* @param itemId アイテムのID
* @param dropSize ドロップ数
* @param tooltipColor ツールチップの色
* @return ClickManaitaBaseItem
*/
private static ClickManaitaBaseItem createItem(ResourceKey<Item> itemId, int dropSize, String tooltipColor) {
ClickManaitaBaseItem item = new ClickManaitaBaseItem((new Item.Properties().setId(itemId)), dropSize);
item.setToolTipColor(tooltipColor);
return item;
}型推論に頼っている場合は明示的に型を渡さないといけなくなりそうです。
// SUCCESS にすると腕を振るう
var clickResult:ActionResult = ActionResult.PASS
if (/* 何かあれば */) {
clickResult = ActionResult.SUCCESS
}BlockEntityType.Builderは削除された?FabricBlockEntityTypeBuilderになったそうです。
- val RESET_TABLE_BLOCK_ENTITY: BlockEntityType<ResetTableEntity> = BlockEntityType.Builder.create(
- { pos, state -> ResetTableEntity(pos, state) },
- ResetTableBlocks.RESET_TABLE_BLOCK
- ).build(null)
+ val RESET_TABLE_BLOCK_ENTITY: BlockEntityType<ResetTableEntity> = FabricBlockEntityTypeBuilder.create(
+ { pos, state -> ResetTableEntity(pos, state) },
+ ResetTableBlocks.RESET_TABLE_BLOCK
+ ).build(null)大幅に変わってそう、これのせいでResetTableはかなりの書き直しが必要だった。
一番でかいのが、サーバー側のみレシピが参照できる点。クライアント側のコード(例えばGUIのテクスチャを描画するクラス)ではレシピにアクセスできなくなりました。
Worldクラスにレシピに関してのクラスがあったのですが、Worldの中でもServerWorldにしかレシピのクラスがゲットできなくなってしまったので、まずはif (world instanceof ServerWorld)する必要があります。
サーバー側で処理されるクラスの場合には(Entityとかはサーバー側なのでServerWorldになると思う)、instanceofでServerWorldかどうか見れば特に問題はないはず。
一方GUIのテクスチャを描画する、クライアント側にしか無い場合はレシピにアクセスできなくなりました。
自作MODではレシピが存在するかをGUI側で確認して、無い場合は無いというテキストをGUIに描画するためクライアント側でもレシピを使っていた。
困った。
仕方ないので、サーバー側でレシピがあるか確認して、戻せない場合にはクライアント側へFabricのNetworking APIで通知するようにしました。
シリアライズする必要があって面倒。
今回はレシピが戻せない理由をクライアント側へ送ろうと思います。
戻せない理由はenum(初見で読めない)で定義済みで、enumのシリアライズにはordinal()で順番を使おうと思います、、、
まずはネットワークで使う一意のIDを作ります。
/** ネットワークの ID 一覧 */
object ResetTableNetworkIds {
/** リセットテーブルで戻せない理由をサーバーからクライアントに送るためのネットワークの ID */
val RESET_TABLE_ERROR_NETWORK_ID = Identifier.of("clickmanaita", "reset_table_error_network")
}つぎにサーバー側←→クライアント側でやり取りするためのクラスを作ります。Kotlinならdata class、Javaならrecordで作って、CustomPayloadインターフェース(?)を実装します。
また、わかりやすいので、ここにシリアライズ、デシリアライズ用の処理もstaticで用意しておくことにします。PacketByteBufsっていう書き込むクラスが渡されて、シリアライズの際はそのクラスに順番に書き込んでいく。writeInt()とか、あとMinecraftで使いがちのブロック位置を書き込むwriteBlockPosなんてもあります。
デシリアライズの時は書き込んだPacketByteBufsが渡されます。書き込んだ順番と同じ順番でreadしていけば良いです。
data class ResetTableErrorPayload(
val blockPos: BlockPos,
val verifyResult: ResetTableTool.VerifyResult
) : CustomPayload {
override fun getId(): CustomPayload.Id<out CustomPayload> = ID
companion object {
val ID = CustomPayload.Id<ResetTableErrorPayload>(ResetTableNetworkIds.RESET_TABLE_ERROR_NETWORK_ID)
val CODEC = PacketCodec.of<RegistryByteBuf, ResetTableErrorPayload>(
/* encoder = */ { data, buf ->
buf.writeBlockPos(data.blockPos)
buf.writeInt(data.verifyResult.ordinal)
},
/* decoder = */ { buf ->
ResetTableErrorPayload(
blockPos = buf.readBlockPos(),
verifyResult = ResetTableTool.VerifyResult.entries[buf.readInt()]
)
}
)
}
}次はサーバー側とクライアント側でネットワークを登録します。ClientPlayNetworking.registerGlobalReceiverで、サーバー側から送られてきたデータを受け取ることができます。
// サーバー側
// コンストラクタなど、MOD のエントリーポイントで呼び出す。
// ネットワークの追加(クライアント・サーバー間でやり取りする)
PayloadTypeRegistry.playS2C().register(ResetTableErrorPayload.ID, ResetTableErrorPayload.CODEC)
// クライアント側
// コンストラクタなど、MOD のエントリーポイントで呼び出す。
// クライアント側は追加でネットワーク登録が必要
ClientPlayNetworking.registerGlobalReceiver(ResetTableErrorPayload.ID) { payload, context ->
context.client().execute {
// ネットワーク経由でイベントが来た
// 今表示されている画面がリセットテーブルの GUI の場合はエラーを出す
// currentScreenHandler は表示されている GUI のやつ
val resetTableScreenHandler = (context.player().currentScreenHandler as? ResetTableScreenHandler)
if (resetTableScreenHandler != null) {
resetTableScreenHandler.recipeVerifyResult = payload.verifyResult
}
}
}あとはサーバー側でクライアント側に送る処理を書けば良いはず。
サーバー側である必要があります。多分
// サーバー側であること
if (player !is ServerPlayerEntity) return
ServerPlayNetworking.send(player, ResetTableErrorPayload(blockPos, verifyResult))差分をおいておきます。
以上です。88888888。ノシ