ゲーム好きな豚のしっぽ

筆者の下手の横好きなゲーム日記、情報メモです。主にプレイするのはtrophymanager、WoT、WoWS、洋モノSLGなどの予定。

DataWatcher

注意事項

このリファレンスは
Datawatcher - Minecraft Forge
を筆者が記述した時点での拙い知識で翻訳、解釈したもののメモです。
誤訳や理解不足の可能性を多分に含みます。ご覧になる際はご注意ください。
3/26追記
1.9からDataWatcherに代わってEntityDataManagerが使用されています。機能的には同じですが、使用できるオブジェクトの総数が増えたり、タイプセーフの考え方を取り入れて改良されているようです。使用されるメソッドも1.9からは変更となります。
1.8.9でmod作り始めているというのに公開してから変更されていることに気づいた…

DataWatcherとは何か

DataWatcherはスレッドセーフな変数の変化を追跡するオブジェクトです。
DataWatcherの利点はデータの更新が起こった際の同期を一つのパケットで行うことができるということです。また、データを必要のあるクライアントにのみ送ることができるので、それを必要としない1万マイル離れた誰かがあなたの体力の変更を受信することはありません。欠点は、最大で32個の限定された型[Byte,Short,Integer,Float,String,ItemStack,ShunkCoorinates]でのみ情報を保存するということです。DateWatcherのChunkCoodinateはエンティティの座標ですので、あなたはそれを必要としない公算が高いです。おそらくあなたが最も利用する典型的な型はint,float,Stringです。

先ほど述べたように、DataWatcherは32個のオブジェクトのみ監視できます。以下が、LvingEntityでの値です。
#は元の記事には印があるもののFoegeのソースでは確認できなかったもの

Entity class 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
Entity x x                                                            
 ┗EntityLivingBase x x         x x x x                                            
 ┣EntityPlayer x x         x x x x             x x x                          
 ┗EntityLiving x x         x x x x # #                                        
   ┣EntitySlime x x         x x x x x x         x                              
   ┣EntityAmbientCreatuer x x         x x x x x x                                        
   ┃┗EntityBat x x         x x x x x x         x                              
   ┣EntityFlying x x         x x x x x x                                        
   ┃┗EntityGhast x x         x x x x x x         x                              
   ┗EntityCreature x x         x x x x x x                                        
     ┣EntityGolem x x         x x x x x x                                        
     ┃┗EntityIronGolem x x         x x x x x x         x                              
     ┣EntityMob x x         x x x x x x                                        
     ┃┣EntityBlaze x x         x x x x x x         x                              
     ┃┣EntityCreeper x x         x x x x x x         x x x                          
     ┃┣EntityEndermen x x         x x x x x x         x x x                          
     ┃┣EntitySkeleton x x         x x x x x x   x                                    
     ┃┣EntitySpider x x         x x x x x x         x                              
     ┗EntityAgeable x x         x x x x x x x                                      
       ┣EntityVillager x x         x x x x x x x       x                              
       ┗EntityAnimal x x         x x x x x x x                                      
         ┣EntitySheep x x         x x x x x x x       x                              
         ┣EntityPig x x         x x x x x x x       x                              
         ┣EntityHorse x x         x x x x x x x       x     x x x x                  
         ┗EntityTameable x x         x x x x x x x       x x                            
           ┣EntityOcelot x x         x x x x x x x       x x x                          
           ┗EntityWolf x x         x x x x x x x       x x x x x                      

では、DataWatcherに新しい値を追加します。それにはaddObject(IL)メソッドを使用します。新しい値はリストに加えられて初めて、DataWatcherが監視を始めます。このとき、DataWatcherには、あなたが保存したい値がどの型であるのかを明示するようにしてください。1と1.0は同じ値として評価されますが、その型は同じではありません。

DataWatcher dw = entity.getDataWatcher();
//dw.addObject(id, object);
dw.addObject(20, 1);//add a new int with id 20, when initializing you can put anything even if it's irrelevant
//(新しくintをid20で追加。初期化の際にたとえそれが無関係な値でも追加することができます。)
dw.addObject(21, 2.5f);//new float at id 21
dw.addObject(22, "hello datawatcher");//string@22
dw.addObject(20, 2);//this will not update the value, this will crash because there's already something at 20, there is another method to update which we will see later
//(この値はアップデートされません。既に20に値があるため、クラッシュします。後ほど、アップデートのためのメソッドを記述します。)
どこで追加するのか

あなたが自作のmobを使用する際には、スポーンしたmobのインスタンスはentityInitメソッドを呼び出して初期化できます。DataWatcherにあなたの監視したいオブジェクトを追加する前に、まずはsuper.entityInit()をコールしましょう。なぜなら親クラスがすでにいくつかのIDを使用しているからです。
もしもあなたがバニラの既存のmobにステータス監視を追加したいときには、EntityConstructing型のイベントを処理できるハンドラを準備する必要があります。EntityConstructingイベントはエンティティと呼ばれるもろもろの参照が現れるたびに呼び出されます。ハンドラの中でinstanceofを使ってエンティティが監視したい型なのかをチェックして、getDatawatcherメソッドでDataWatcherを取得し、単純にaddObjectで監視したい値を追加します。

@ForgeSubscribe
public void handleConstruction(EntityConstructing event){
    if(event.entity instanceof EntityPlayer){
        DataWatcher dw = event.entity.getDataWatcher();
        dw.addObject(20, 0.0f);//karma level
        dw.addObject(21, 0.0f);//amount of mana
        dw.addObject(22, 0);//amount of kill
    }
}

判りやすいシンプルな例ですな
DataWatcherの値はどちらのサイドでも知っているようにしましょう。なぜなら、サーバーが新しい値があるよ、と言っても、クライアントがそれを期待していないのなら、それは完全に無視されてしまうからです。ですから、このハンドラはどちらのサイドでも登録されているようにします。

どのように相互作用するか

次に、DataWatcherの読み書きについてのポイントです。もしも2つのスレッドが同時に書き込みを行おうとした場合、片方はもう片方の書き込みが終わるまで待機することに注意しましょう。

DataWatcherからの読み込み:

DataWatcher dw = entity.getDataWatcher();
//dw.getWatchableObject*type*(int id);
int amountOfKill = dw.getWatchableObjectInt(22);
String title = dw.getWatchableObjectString(23);
ItemStack itemInLeftHand = dw.getWatchableObjectItemStack(24);
//now float was not translated in forge 789, maybe its different in other version
//the name should be "getWatchableObjectFloat(int id)" but its:
//(Forge789現在、floatは翻訳されていません。おそらくほかのバージョンではこのメソッド名は”getWatchableObjectFloat(int id)”となるはずです)
float karmaLevel = dw.func_111145_d(20);

この例では、RenderPlayerEventで負のカルマを持つプレイヤーは暗いオーラを、正のカルマを持つプレイヤーは明るいオーラをまとうようにすることができるでしょう。

DateWatcherに書き込むときには、まず次のことを理解しましょう。DataWatcherはサーバーからクライアントへアップデートを通知する唯一のものであるということ、クライアントがDateWatcherに書き込むことはよほど特殊な理由がない限りは意味がないということです。(例えばreiminimapではクライアント側でのみウェイポイントを描画します。そしてこれはサーバー側では存在しませんし、アップデートする必要はないでしょう。)

DataWatcherへの書き込み:

DataWatcher dw = player.getDataWatcher();
dw.updateObject(20, karmaMap.get(player.username));//writing (updating) is very similar to addObjectMethod. in this case karmaMap would be a hashmap<String, float> that hold the values of everyone's karma
//(addObjectメソッドとよく似た方法で書き込み(アップデート)を行います。この場合、karumaMapはHashMap<String,float>で皆のカルマの値を保持しています)
//訳注:karumaMapからプレイヤー名で値を引いてwatcherの値を上書きしている。先の読み込みの例のFloatの値の話から続いている
どこでDataWatcherを使用してはいけない

理想としては、特定のクライアントに向けて使用されているすべての変数はほかのプレイヤーに伝搬するべきではなく、DataWatcherに入れられるべきではありません。パケットとクライアントプロキシを使えば同じことを達成できるため、あなたはスペースを無駄遣いしていることになります。たとえば、あるプレイヤーが持っているお金の量は他のプレイヤーが参照できてはいけないでしょうから、DataWatcherに登録されるべきではありません。仮にあなたがほかのプレイヤーのお金の量を取得できるようにしたいのであれば、それは頻繁に行われる操作ではありませんから、リクエストを送るパケットを作ることを考えるべきです。
クライアント(プレイヤー)が意識しなくても更新されるべきデータはDataWathcer、インタラクト(右クリックやキー操作)でデータが必要になるときにはパケットでリクエストを送る、と考えれば良いでしょうか