目次
強化機能を作る
元ネタの武器には強化システムがあります。簡単に述べると、強化パーツを枠(8個)に付けて性能を伸ばして使用します。
また、強化パーツの育成、経験値による武器の育成、それぞれ育成要素があります。
マイクラの場合はプレイヤーに入るEXPを流用するのが合っていそうですが、それならEXPを消費して強化するエンチャントシステムで代用できそうです。今回の方針として、独自のアイテムや装置をあまり増やすより、バニラにあるものを利用する方針にも合っています。(可能であればバニラ要素の拡張のほうが、他の拡張系MODと合わせてシナジーを期待しやすいので。)
というわけで、火炎放射器アイテムにエンチャントを付けると撃たれる弾が強化状態になるようにします。
理想的には本家同様、"ビルドをよく考えて構築することで見違えるように強くなる"なのですが、マイクラのエンチャントは本を使わない限りランダム付与なので、そこも後々考えたいところです。
ひとまずエンチャントの効果を考えます。独断と偏見によりアレンジを加えているので、本家の厳密な再現などは考えず。
- 射撃ダメージ増強
- 本家ではダメージ増加+ダメージ発生間隔を短くするのだが、マイクラのダメージの間隔(というかダメージ発生後の無敵時間)がうまく微調整できなかったため、ダメージ増加にとどめる。無敵時間はエンチャント無関係に0.0Fに。
- パンチ
- ノックバック強化ですが、射程延長に効果を変更。弾速を上げれば60tickでより遠くまで届くので、弾速を上げる効果として作る。
- フレイム
- 本家は元々火炎属性の武器に火炎効果を上乗せするとダメージが上がります。ということで、ダメージ増加+着火効果でも付与しようかなと。
- 無限
- 本家には対応できそうな物がないのでマイクラ準拠のマガジン無限化でしょうか。ただ、本家は敵を倒せば弾薬はぽこぽこドロップするので、有限であることにあまり拘らなくてもいい気がします。
- 耐久力
- マガジンの上限を伸ばす。
これに加えて、新規にエンチャントも追加してみます。
- 毒エンチャント
- 通常は毒ポーション付与の追加効果。火炎放射器に付けると火炎+毒の複合属性となり、どちらでもない腐食属性としてアーマー貫通攻撃+弱体化ポーション付与の追加効果に変わる。
- 榴弾エンチャント
- モブに当たった場合に爆発の追加効果の発生。火炎放射器の場合は、本家の効果やバランスを考えてキル時にのみ爆発。
毒と火炎の複合属性は本来ガス属性なのですが、イマイチ再現するありがたみがないので腐食に変更。一応、当初は電気や氷のエンチャントも追加する構想はあったのですが、複合属性含めて状態異常効果の再現に苦慮したのでポシャりました。
エンチャント効果を追加する
毒と榴弾はバニラエンチャントではなく、独自のエンチャントとして追加します。
Enchantment継承クラスを作る
- EnchantPoisonous.class
Poisonousは弓用の毒エンチャント。見ての通りエンチャントのクラスには効果は全く入っていない。効果は、別途ヒット時イベントを利用して作る。
package defeatedcrow.flamethrower.enchant; import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.EnumEnchantmentType; // 毒のエンチャント public class EnchantPoisonous extends Enchantment { public EnchantPoisonous(int id, int weight) { // superに送るパラメータは、エンチャントのID、エンチャント台での出現頻度、エンチャント対象のタイプ。 super(id, weight, EnumEnchantmentType.bow); } // Enchantabilityは素材やレベルによって決まる出現エンチャントの補正値。このメソッドは下限を設定する。 @Override public int getMinEnchantability(int par1) { return 10; } // こちらはエンチャント補正値の上限。 @Override public int getMaxEnchantability(int par1) { return super.getMinEnchantability(par1) + 50; } // エンチャントの最高レベル。 @Override public int getMaxLevel() { return 2; } }
今回は他のエンチャントも同様で、エンチャント自体は効果は持たせていない。エンチャントクラスが持っているのは他に、同じアイテムに同時に付けられないエンチャントの制限や、特定ダメージソースからのダメージカット量などの設定は可能。(詳しくはEnchantment.classを参照されたし。)
- EnchantVenomous.class
ベノムスも毒エンチャントだが、付けられる対象は弓ではなく近接武器。
package defeatedcrow.flamethrower.enchant; import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.EnumEnchantmentType; public class EnchantVenomous extends Enchantment { public EnchantVenomous(int id, int weight) { super(id, weight, EnumEnchantmentType.weapon); } @Override public int getMinEnchantability(int par1) { return 15; } @Override public int getMaxEnchantability(int par1) { return super.getMinEnchantability(par1) + 50; } @Override public int getMaxLevel() { return 2; } }
- EnchantThunderbolt.class
榴弾エンチャント。英名のサンダーボルトは元ネタに由来している。
package defeatedcrow.flamethrower.enchant; import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.EnumEnchantmentType; public class EnchantThunderbolt extends Enchantment { public EnchantThunderbolt(int id, int weight) { super(id, weight, EnumEnchantmentType.bow); } @Override public int getMinEnchantability(int par1) { return 20; } @Override public int getMaxEnchantability(int par1) { return super.getMinEnchantability(par1) + 50; } @Override public int getMaxLevel() { return 4; } }
エンチャントの登録
これもメインクラスで登録が必要。
注意点として、1.7.10時点ではこれは数値IDが必要であり、他MODと使用IDが被らないようにする。IDチェッカーやNEIのdamp機能を利用してある程度は空いていそうなIDに目星を付けておくが、完璧ではないのでコンフィグでIDを変更可能にしておく。
- FlameCore.class
まずエンチャントのインスタンスと初期IDの宣言。無機能アイテム追加時に追加したItemインスタンスの下あたりに追加しておく。
public static Item flamethrower; public static Enchantment poisonous; public static Enchantment venomous; public static Enchantment explosive; public static int poisonID = 160; public static int venomID = 161; public static int explosiveID = 162; @EventHandler public void preInit(FMLPreInitializationEvent event) { ...
次にpreInitメソッドのコンフィグ部分にID変更機能を追加。
注意点として、コンフィグ項目はまだ作られていなかった初回のみ項目を生成し、二回目以降はコンフィグファイルにある値を読み取る。よって、リリース後のバージョンアップで初期値を変えても、既にMODを入れてコンフィグファイルを生成した後では古い初期値が入ったままになったり、新バージョンで削除済みの不要項目が残ったままになってしまう。
@EventHandler public void preInit(FMLPreInitializationEvent event) { // conf Configuration cfg = new Configuration(event.getSuggestedConfigurationFile()); try { cfg.load(); // Property(コンフィグの項目)を追加。カテゴリ名、コンフィグ項目名、初期値。 Property e1ID = cfg.get("enchantment", "Poisonous ID", poisonID); Property e2ID = cfg.get("enchantment", "Venomous ID", venomID); Property e3ID = cfg.get("enchantment", "Explosive ID", explosiveID); // 項目に入っている値を取得してIDに入れる。 poisonID = e1ID.getInt(); venomID = e2ID.getInt(); explosiveID = e3ID.getInt(); } catch (Exception e) { e.printStackTrace(); } finally { cfg.save(); } // 以降、Item登録処理など }
そして、initメソッドでエンチャントの登録処理。
@EventHandler public void init(FMLInitializationEvent event) throws IOException { // entity int idFlame = 1; EntityRegistry.registerModEntity(EntityFlame.class, "dcsflame.entity_flame", idFlame, this, 128, 5, true); // render proxy.registerRenderers(); // ここが追加部分。enchantmentの登録 poisonous = new EnchantPoisonous(poisonID, 2).setName("dcs.poisonous"); venomous = new EnchantVenomous(venomID, 5).setName("dcs.venomous"); explosive = new EnchantThunderbolt(explosiveID, 2).setName("dcs.explosive"); }
エンチャントによる強化機能をアイテムに付ける
このMODの場合、強化されているかどうかで効果が変わるのは弾Entityなので、弾Entity生成時にエンチャントのチェックと、強化効果を付ける。
- ItemIgnis.class
@Override public ItemStack onItemRightClick(ItemStack par1ItemStack, World par2World, EntityPlayer par3EntityPlayer) { // Enchantmentのチェック部分 boolean inf = EnchantmentHelper.getEnchantmentLevel(Enchantment.infinity.effectId, par1ItemStack) > 0; int power = EnchantmentHelper.getEnchantmentLevel(Enchantment.power.effectId, par1ItemStack); int fire = EnchantmentHelper.getEnchantmentLevel(Enchantment.flame.effectId, par1ItemStack); int punch = EnchantmentHelper.getEnchantmentLevel(Enchantment.punch.effectId, par1ItemStack); int poison = EnchantmentHelper.getEnchantmentLevel(FlameCore.poisonous.effectId, par1ItemStack); int unb = EnchantmentHelper.getEnchantmentLevel(Enchantment.unbreaking.effectId, par1ItemStack); boolean creative = par3EntityPlayer.capabilities.isCreativeMode; boolean hasCharge = false; if (par1ItemStack.getItem() == this) { int c2 = this.discharge(par1ItemStack, 1, false); if (c2 > 0) { hasCharge = true; this.discharge(par1ItemStack, 1, true); } } if (creative || hasCharge) { // エンチャントのレベルによって初期パラメータを変えている float fireDam = 2.0F * fire; float powerDam = 0.4F * power; float poisonDam = 0.5F * poison; float dam = 2.0F + fireDam + powerDam + poisonDam; float ram = par2World.rand.nextFloat(); int cooltime = 0; // 一旦凍結 // if (cooltime < 0) // cooltime = 0; float speed = 1.0F + (punch * 1.0F); for (int i = 0; i < 4; i++) { float f = speed * (i * 0.35F) + ram; EntityFlame bullet = new EntityFlame(par2World, par3EntityPlayer, f, 0.0F, dam, 4.0F, cooltime); bullet.setFire(100); // EntityFlameのsetterを使って属性効果のフラグをtrueにしている if (poison > 0) { bullet.setCorrosion(true); } if (fire > 0) { bullet.setAdvFire(true); } if (!par2World.isRemote) { par2World.spawnEntityInWorld(bullet); } } par2World.playSoundAtEntity(par3EntityPlayer, "dcsflame:flame", 0.6F, 1.6F); } else { // 耐久エンチャントがついていたときにマガジン最大値が増えるので、回復量もそれに合わせて増える if (par3EntityPlayer.inventory.hasItem(Items.gunpowder) || inf) { NBTTagCompound nbt = par1ItemStack.getTagCompound(); if (nbt != null) { nbt.setInteger("magazine", 100 + unb * 50); par1ItemStack.setTagCompound(nbt); } else { NBTTagCompound nbt2 = new NBTTagCompound(); nbt2.setInteger("magazine", 100 + unb * 50); par1ItemStack.setTagCompound(nbt2); } if (!inf) par3EntityPlayer.inventory.consumeInventoryItem(Items.gunpowder); par2World.playSoundAtEntity(par3EntityPlayer, "random.door_close", 1.0F, 1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + 1.3F); } } return par1ItemStack; } // マガジン表示も最大値上昇効果を反映させる @Override @SideOnly(Side.CLIENT) public void addInformation(ItemStack par1ItemStack, EntityPlayer par2EntityPlayer, List par3List, boolean par4) { super.addInformation(par1ItemStack, par2EntityPlayer, par3List, par4); NBTTagCompound nbt = par1ItemStack.getTagCompound(); int unb = EnchantmentHelper.getEnchantmentLevel(Enchantment.unbreaking.effectId, par1ItemStack); int charge = 0; int max = 100 + unb * 50; if (nbt != null && nbt.hasKey("magazine")) { charge = nbt.getInteger("magazine"); } String s = new String("magazine : " + charge + "/" + max); par3List.add(s); } @Override public double getDurabilityForDisplay(ItemStack stack) { NBTTagCompound nbt = stack.getTagCompound(); int unb = EnchantmentHelper.getEnchantmentLevel(Enchantment.unbreaking.effectId, stack); int charge = 0; int max = 100 + unb * 50; if (nbt != null && nbt.hasKey("magazine")) { charge = nbt.getInteger("magazine"); charge = MathHelper.clamp_int(charge, 0, max); } int i = max - charge; return (double) i / (double) max; }
ここまでで火炎放射器放射器にエンチャント効果を反映させ、エンチャントによる強化システムを作った。 ItemStackにエンチャントが付いているかどうかは、
EnchantmentHelper.getEnchantmentLevel(Enchantment.unbreaking.effectId, stack);
のように、エンチャントIDとItemStackから確かめられる。エンチャントがついていなければ0、付いていればエンチャントのレベルが返ってくる。
エンチャントの効果を作る
火炎放射器にはエンチャントの効果を反映したが、今の状態では、追加した3種のエンチャントは火炎放射器以外では付与しても何も起こらない無駄なエンチャントになってしまう。
他の武器のソースを直接弄ることは出来ないが、ForgeのEvent機能を使って特定条件で効果が起こるようにすれば、自分が追加していないアイテムにも効果を適用することが出来る。
- HurtEvent.class
イベントを作るには、Forgeのイベントを引数にしたpublic voidメソッドを作り、@SubscribeEventアノテーションを付ける。(クラス名やメソッド名は任意でOK。)必要なパラメータは引数のeventから取得する。得られるパラメータやイベントの起こるタイミング、呼び出し元の処理をキャンセル可能か、など色々違うので、目的に合ったイベントを選んで使う。
package defeatedcrow.flamethrower; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; import net.minecraft.item.ItemBow; import net.minecraft.item.ItemStack; import net.minecraft.potion.Potion; import net.minecraft.potion.PotionEffect; import net.minecraft.util.DamageSource; import net.minecraft.util.MathHelper; import net.minecraftforge.event.entity.living.LivingHurtEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import defeatedcrow.flamethrower.item.ItemIgnis; public class HurtEvents { @SubscribeEvent public void onHurtEvent(LivingHurtEvent event) { EntityLivingBase target = event.entityLiving; DamageSource source = event.source; float damage = event.ammount; ItemStack hold = null; // DamageSourceの種類を判定。Entity由来のダメージソースで、かつダメージソース側EntityがEntityLivingBaseかをチェック if (target != null && source.getEntity() instanceof EntityLivingBase) { EntityLivingBase attacker = (EntityLivingBase) source.getEntity(); if (attacker != null) { // EntityLivingBaseのgetHeldItem()を使って手に持っている物を取得している。 hold = attacker.getHeldItem(); } } if (hold != null) { // holdItemのエンチャントチェック int poi = EnchantmentHelper.getEnchantmentLevel(FlameCore.poisonous.effectId, hold); int ven = EnchantmentHelper.getEnchantmentLevel(FlameCore.venomous.effectId, hold); int exp = EnchantmentHelper.getEnchantmentLevel(FlameCore.explosive.effectId, hold); // 毒エンチャントならターゲットに毒ポーション if (poi > 0) { target.addPotionEffect(new PotionEffect(Potion.poison.getId(), 100, poi)); } if (ven > 0) { target.addPotionEffect(new PotionEffect(Potion.poison.getId(), 100, ven)); } // 榴弾エンチャントの場合、火炎放射器以外のItem/ダメージがとどめに十分である、どちらかで爆発効果 if (exp > 0) { if (!(hold.getItem() instanceof ItemIgnis) || damage >= target.getHealth()) { int i = MathHelper.ceiling_float_int(4.0F / exp); float size = 1.0F + exp * 0.5F; if (target.worldObj.rand.nextInt(i) == 0) { target.worldObj.createExplosion(null, target.posX, target.posY, target.posZ, size, false); } } } } } }
今回使うのはLivingHurtEventで、生きているエンティティ(EntityLivingBase)がダメージを受ける直前に呼ばれる。ここでは行っていないがキャンセルも可能で、キャンセルした場合はダメージ処理がキャンセル、つまりダメージを与えずに無効化される。
ターゲットのEntityLivingBaseと与えるダメージの量、ダメージソースが取得できるので、これらを使ってエンチャントの効果を作る。
if (target != null && source.getEntity() instanceof EntityLivingBase) { ...
DamageSourceはダメージの種類で、何に由来するダメージかの情報を持っている。(ちなみにMODによる追加も可能。)
今回はダメージを与えたEntityを利用したいので.getEntity()を使っている。getSourceOfDamage()というメソッドもあるが、こちらは飛び道具での攻撃の場合に、放たれた飛び道具の方のEntityが入っている。
余談だが、public IChatComponent func_151519_b(EntityLivingBase p_151519_1_)のメソッドには死亡メッセージが定義されている。自作ダメージソースの死亡メッセージもオリジナルに出来るので、自作したい人はご参考までに。
また、今回の場合はダメージ源がEntityLibingBaseかを判定して使っている。これによりアイテムを持てるEntityならプレイヤーでなくとも発動するようにしているため、榴弾エンチャント付きの弓をスケルトンが手にしたりすると大惨事になる。(ということを狙っている。)
if (target != null && source.getEntity() instanceof EntityPlayer) { ...
このように変えると、プレイヤーが持ったアイテムにしか反応しなくなる。ただし、EntityPlayerの処理を偽装するMODの効果では誤作動するかもしれない。
- FlameCore.class
イベントも登録しないと使用されない。登録はinitメソッドに以下を書き足す。
MinecraftForge.EVENT_BUS.register(new HurtEvents());