与ダメージ値の取得方法

Modding・サーバPlugin制作・ツール制作など、開発関連の質問があればこちらにお願い致します。
フォーラムルール
質問関連フォーラムで質問する時は、必ず次のトピックを一読/厳守お願い致します。
viewtopic.php?f=5&t=999
  • (PostNo.23985)

与ダメージ値の取得方法

投稿記事by kuroratt » 2012年2月12日(日) 01:04

初めまして。早速ですが質問です。

ModLoaderを用いるMODを作成しており、次の機能を付け加えたいと考えています。
・プレイヤーが敵に与えたダメージを管理する
・対象とする敵は既存の敵やMODで追加される敵を含めたすべての敵
・ダメージはプレイヤーが攻撃して与えたダメージのみ(炎ダメージなどは除く)

既存のコードを書き換えずにModLoaderの機能のみで実装したいのですが、
ModLoaderやBaseModのクラスを見てもそのようなものは無さそうで、
Entity,EntityLiving,EntityCreature,EntityMob周りを見ても、
そもそもダメージの記述がどれなのか分からず・・・。
というわけで質問させていただきました。
どなたか良い方法を教えてくださらないでしょうか。

宜しくお願いいたします。
kuroratt
ID:97c71c81
 

  • (PostNo.24406)

Re: 与ダメージ値の取得方法

投稿記事by kuroratt » 2012年2月15日(水) 01:06

一応暫定的な解決策を見つけました。
MinecraftのフィールドthePlayerを、
EntityPlayerSPを継承する独自のクラスのインスタンスに置き換える方法です。
しかし、そこでまた問題が発生したので、
不躾ながら改めて質問させていただきたく思います。

フォーラム内を調べた限りでは見当たらなかったので(既出なら申し訳ありません)、
ダメージの計算に関して調べたことを報告させていただきます。
ただし、マインクラフトのバージョンは1.1で、MCP・ModLoaderを用いています。

先ず、ダメージ処理の分析に関して。
マインクラフト上のプレイヤー・Mob・アイテム・その他の実体を伴うものは、
すべてEntity(=実体)の継承クラスのインスタンスとして取り扱われます。
このうち、生きている実体はEntityLivingを継承し、
それらのHPはすべてこのEntityLivingのフィールドhealthによって管理されます。
HPの全快復やスポーン時の処理、その他の即死処理などを除けば、
ダメージ処理のためにhealthの値が変更されるのは、
実質的にはprotectedなメソッドであるdamageEntity(DamageSource damagesource, int i)によってのみです。
よって、ダメージを与える処理は最終的にはdamageEntityを必ず呼び出します。

次に、このdamageEntity()をプレイヤーの攻撃時に呼び出すメソッドを探すと、
EntityPlayerSPのattackTargetEntityWithCurrentItem(Entity entity)が見つかります。
敵に与える基本ダメージの計算もattackTargetEntityWithCurrentItemで行われており
(*基本ダメージ:鎧その他によるダメージ減衰などの考慮されないダメージ値)、
これを記録することができれば目的を達成することができると考えました。

しかし、前述したとおり既存ソースコードを書き換えたくないため、
マインクラフトが操作プレイヤーを管理している変数を探して、
それを自作のEntityPlayerSPサブクラスに差し替えることで、
attackTargetEntityWithCurrentItemに処理を追加する方法を考えました。
(というか、ソースコードを書き換えないならそれ以外には方法が無さそう)
継承クラスEntityPlayerDRの具体的なコードは以下のようになります。

========== EntityPlayerDRのコード ここから ==========
//自作したダメージ値管理用のEntityPlayerSPのサブクラス
public class EntityPlayerDR extends EntityPlayerSP {
  
  private List<PlayerAttackListener> palList;
  
  public EntityPlayerDR(EntityPlayerSP p) {
    super(p.mc, p.worldObj, p.mc.session, p.worldObj.worldProvider.worldType);
    
    //フィールド変数のコピー ここから
    this.inventory = p.inventory;
    this.inventorySlots = p.inventorySlots;
    /* ... 省略 ... */
    this.ignoreFrustumCheck = p.ignoreFrustumCheck;
    this.isAirBorne = p.isAirBorne;
    //フィールド変数のコピー ここまで

    palList = new ArrayList<PlayerAttackListener>();
  }
  
  public void addPlayerAttackListener(PlayerAttackListener pal) {
    palList.add(pal);
  }
  
  @Override
  public void attackTargetEntityWithCurrentItem(Entity entity)
  {
    int damage, k;
    super.attackTargetEntityWithCurrentItem(entity);
    
    // "EntityPlayer.attackTargetEntity"の基本ダメージ計算部分のコピー ここから
    damage = inventory.getDamageVsEntity(entity);
    if (isPotionActive(Potion.damageBoost))
    {
      damage += 3 << getActivePotionEffect(Potion.damageBoost).getAmplifier();
    }
    if (isPotionActive(Potion.weakness))
    {
      damage -= 2 << getActivePotionEffect(Potion.weakness).getAmplifier();
    }
    k = 0;
    if (entity instanceof EntityLiving)
    {
      k = EnchantmentHelper.getEnchantmentModifierLiving(inventory, (EntityLiving)entity);
    }
    if (damage > 0 || k > 0)
    {
      boolean flag = fallDistance > 0.0F && !onGround && !isOnLadder() && !isInWater() && !isPotionActive(Potion.blindness) && ridingEntity == null && (entity instanceof EntityLiving);
      if (flag)
      {
        damage += rand.nextInt(damage / 2 + 2);
      }
      damage += k;
    }
    // "EntityPlayer.attackTargetEntity"の基本ダメージ計算部分のコピー ここまで

    for (Iterator<PlayerAttackListener> it = palList.iterator(); it.hasNext();) {
      it.next().onUpdate(this, damage);
    }
  }
  
}
========== EntityPlayerDRのコード ここまで ==========

ダメージ値を使って何かしたいクラスにはインターフェースPlayerAttackListenerを実装させて、
onUpdateメソッドによってダメージ値取得・管理を行うようにしています。
本筋ではありませんが、インターフェースのコードは以下のようなものとなっています。

========== PlayerAttackListenerのコード ここから ==========
public interface PlayerAttackListener {
 void onUpdate(EntityPlayer player, int damage);
}
========== PlayerAttackListenerのコード ここまで ==========


さて、そこで「マインクラフトが操作プレイヤーを管理している変数」として、
MinecraftのフィールドthePlayerを見つけだし、これを置き換えることにしました。
具体的には、BaseModで次のような処理を行いました。

========== mod_Sampleのコード ここから ==========
public class mod_Sample extends BaseMod {

  public static PlayerAttackListener itemDR;
  public static final int itemID = 25000;
  
  public String getVersion() {return "1.1.0";}
  
  public void load() {
    // ダメージ管理用の(PlayerAttackListenerを実装した)アイテムを生成
    itemDR = new ItemDR(itemID);
    
    // "OnTickInGame"を有効にする
    ModLoader.SetInGameHook(this, true, true);
  }
  
  @Override
  public boolean OnTickInGame (float f, Minecraft mc) {
    super.OnTickInGame(f, mc);
    EntityPlayerDR ep;
    EntityPlayerSP sp;
    /* "thePlayer"が自作クラスではない場合は入れ替える */
    if (!(mc.thePlayer instanceof EntityPlayerDR)) {
      //spにもともとのプレイヤーを、epに自作クラスのプレイヤーを入れ、thePlayerを差し替える
      sp = mc.thePlayer;
      ep = new EntityPlayerDR(mc.thePlayer);
      mc.thePlayer = ep;
      //スポーン時用の処理?
      mc.thePlayer.preparePlayerToSpawn();
      mc.playerController.flipPlayer(mc.thePlayer);
      mc.renderViewEntity = mc.thePlayer;
      //ダメージ管理用のアイテムを登録
      ((EntityPlayerDR)mc.thePlayer).addPlayerAttackListener(itemDR);
    }
    return true;
  }
}
========== mod_Sampleのコード ここまで ==========

ModLoaderがワールド生成時にBaseModから呼び出してくれるメソッドが無かったので、
とりあえず常に呼び出されるメソッドとしてOnTickInGameに
プレイヤー差し替え処理を書きました。

そして、これで動くかと思いきや、
もともとのプレイヤーと自作したプレイヤーの2体が生成されてしまいました。
色々とプレイヤー差し替え時の処理を工夫してみたのですが、
敵がスポーンしなくなったり表示がおかしくなったり、どうにも上手くいきません。
上手くthePlayerを差し替える方法についてご教示いただけないでしょうか。

宜しくお願いいたします。
kuroratt
ID:97c71c81
 


Return to 質問:開発・制作関連

x