【Terraria】タイル破壊時のドロップアイテムの情報をリフレクションにてコードを生成する

#はじめに
この記事はテラリアというゲームでのMod開発に関する情報です。
サンプルコードや、公開されているコードを参考にMod開発をしている状況で、備忘録としてのまとめでもあり、記事投稿時点での知識で書いていますので、間違いもあるかもしれませんのでご注意ください。

#やりたいこと
指定したブロックや壁紙を破壊しないようにするModを作成したい。
そのためには、`Tile`オブジェクトからドロップする`Item`オブジェクトの情報が欲しい。

#背景
テラリアでは、ブロックなどを破壊した際にそのアイテムがドロップするが、ブロックなどを管理するオブジェクトは`Tile`オブジェクトであり、そこには破壊した際にドロップする`Item`オブジェクトの情報は紐付いていない。
では、Tileを破壊した際にどのようにしてドロップしているかと言うと、`Terraria.WorldGen`クラスに、`KillTile`メソッドがある。
Tile破壊時にはこれが呼ばれるようで、途中の判定でTileの破壊が失敗しなかった場合で、Itemがドロップする場合に、次のような感じで個別に設定されている。

```C#
	int num23 = 0;
	int num24 = 0;
	if (tile.type == 0 || tile.type == 2 || tile.type == 109)
	{
		num23 = 2;
	}
	else if (tile.type == 426)
	{
		num23 = 3621;
	}
```
<font color=red>※ソースコードはtModLoaderにてTerraria.exeを逆コンパイルして作成されたもの</font>

この中から、ブロック系のみを対象にして、どの`tile.type`が、どの`item.type`をドロップするかを抜き出したいが、いかんせんわかりづらい。
また、今後の事も考えて、tile->item を取得するようなメソッドを作っておきたい。

このため、この部分のコードを丸ごとコピーし、不要な処理を排除して、かつ、TileIDとItemIDで表記したコードにしたい。

なお、Tileのテクスチャは、次のようにおり、ゲーム上でどのテクスチャを表示するかは、`tile.frameX`で管理されている。
![Tile_0013.png](https://qiita-image-store.s3.amazonaws.com/0/156225/60d73a63-9b27-e422-bb3b-1aa811f84aab.png)

ブロック系のテクスチャは、表現が異なっていても、ドロップするアイテムは同一だが、上記のようなタイプはそれぞれドロップするアイテムが異なる。

今回生成するコードでは、単純に`tile.type`のみから判断できるものを対象とすることにしている。

#実際のコード

やっていることは、
1. `TileID`クラスと`ItemID`クラスをリフレクションにて、値と名のディクショナリーを作成
2. TileIDの数分、`Terraria.WorldGen.KillTile`メソッドのドロップ処理部分を別途抜き出し、不要な処理や、判断できない部分をコメントかしたメソッドを呼び出して、ItemIDを取得する（長いコードなのでここでは省略）
3. 取得できたItemID分の値とアイテム名のディクショナリーを作成
4. TileIDからItemIDを取得するメソッドのコードをファイルへ出力する

```C#
public static void MakeCodeOfTileTypeToDropItemType()
{
    //タイルタイプとTileID名のディクショナリーを作成する
    var dicTileID = new Dictionary<int, string>();
    var classTileID = typeof(TileID);
    classTileID.GetFields().ToList().ForEach(x => dicTileID.Add(int.Parse(x.GetValue(classTileID).ToString()), x.Name));

    //アイテムタイプとItemID名のディクショナリーを作成する
    var dicItemID = new Dictionary<int, string>();
    var classItemID = typeof(ItemID);
    classItemID.GetFields().ToList().ForEach(x =>
    {
        if (x.FieldType.Equals(typeof(short))) dicItemID.Add(int.Parse(x.GetValue(classItemID).ToString()), x.Name);
    });

    //タイルタイプからドロップするアイテムのリストを作成する
    List<Tuple<int, int>> listOK = new List<Tuple<int, int>>();
    List<int> listNG = new List<int>();
    for (int i = 0; i < dicTileID.Count; i++)
    {
        Tile tile = new Tile();
        tile.type = (ushort)i;
        int itemType = TileUtils.getKillDropItemType(tile);
        if (0 < itemType)
        {
            listOK.Add(new Tuple<int, int>(i, itemType));
        }
        else
        {
            listNG.Add(i);
        }
    }

    //ドロップするアイテムのアイテムタイプとアイテム名のディクショナリーを作成する
    var dicItemName = new Dictionary<int, string>();
    foreach (var x in listOK)
    {
        Item item = new Item();
        item.SetDefaults(x.Item2);
        if (!dicItemName.ContainsKey(x.Item2))
            dicItemName.Add(x.Item2, item.Name);
    }

    //TileIDとItemIDで置き換えたコードを生成する
    using (FileStream fs = new FileStream($@"{Main.SavePath}\TileUtils.cs", FileMode.Create))
    using (StreamWriter sw = new StreamWriter(fs))
    {
        sw.WriteLine("public static class TileUtils");
        sw.WriteLine("{");
        sw.WriteLine("    public static int getKillDropItemType(Tile tile)");
        sw.WriteLine("    {");
        sw.WriteLine("        int result = 0;");
        sw.WriteLine("        switch (tile.type)");
        sw.WriteLine("        {");
        foreach (var x in listOK)
        {
            sw.WriteLine($"            //{dicItemName[x.Item2]}");
            sw.WriteLine($"            case TileID.{dicTileID[x.Item1]}:");
            sw.WriteLine($"                result = ItemID.{dicItemID[x.Item2]};");
            sw.WriteLine("                break;");
        }
        foreach (var x in listNG)
        {
            sw.WriteLine($"            case TileID.{dicTileID[x]}:");
            sw.WriteLine("                break;");
        }
        sw.WriteLine("        }");
        sw.WriteLine("        return result;");
        sw.WriteLine("    }");
        sw.WriteLine("}");
    }
}
```

実際に出力されたものの一部

```C#
public static class TileUtils
{
    public static int getKillDropItemType(Tile tile)
    {
        int result = 0;
        switch (tile.type)
        {
            //土のブロック
            case TileID.Dirt:
                result = ItemID.DirtBlock;
                break;
            //石のブロック
            case TileID.Stone:
                result = ItemID.StoneBlock;
                break;
```

と言った感じで出力したので、後はここから必要な修正を施したりします。


以上