C#の勉強を一旦放棄してtModLoaderでMod製作をしている私は(悪い意味での)怠惰
なにそれ?
tModLoaderとはTerrariaというゲームのMod製作ツール
もともと非公式だったが1.4リリースに合わせて公式も協力して
現在Steamで入手可能
ただしバージョンは1.3.5.3(2021/6/10現在)
がちがちのリファレンスじゃなくて
ちょこっとしたメソッドやフィールドとかの
役に立ちそうなのを紹介
日本語のtModLoaderリファレンスが無さすぎるので共有
随時 役に立ったものとかを追記していく感じ
(そもそも私はあまりQiitaに現れないので難しいかもしれない)
日本人tModder増えてほしいので頑張ります
(最終追記日: 2021/06/10)
tModLoader ちょこっとリファレンス
まず大前提としてExample Mod 入れろ。 (入れてじゃなくて入れろ)
Mod Browserに検索をかけると出てくる。
GitHubでもいいぞ。
だいたいの常套手段とか、どう書けばいいのかは書かれている。
ここではExample Modでも補えないものとか
Example Modに書かれていたり、tModLoader wikiに書かれていても、
便利だと思ったし、まぁ日本語じゃないので。
みたいなものを書いていく予定です
ちなみに知ってると思うが「バニラ」とはModを入れていない本家Terrariaのコンテンツを指す。
Terrariaじゃなくても「バニラ」という単語はModじゃない本家ゲームのコンテンツを指す。
英語でVanilla
あとtModLoaderのデコンパイルしたコードあると便利。
ソースコードを公開するのは禁止だが、個人でILSpyなどによるデコンパイルは許可されている。
バニラアイテムの挙動変更など、場合によってはそのコードを見たり、マネしないとうまくいかないことも稀にある。
そもそもIL編集やるなら必須。
Mod
Mod.Load()
IL編集は大体ここに書いて。
Mod.PostSetupContent()
これは全てのModがロードし終わった後に呼び出されるため、
NPCLoader.NPCCountなど○○Loader.○○CountでModで追加された物を含めたアイテム数を取得できる。
もちろん取得できるのはアイテム数に限らない。
ModItem
ModItem.SetStaticDefaults()
bool ItemID.Sets.ItemNoGravity[item.type]
trueにするとアイテムを重力に逆らわなくできる。
Soul of lightとかソウル系のアイテムやStardust Fragmentとかのフラグメント系アイテムみたいな感じ。
ModItem.SetDefaults()
item.value = Item.sellPrice([PlatinumCoin = 0], [GoldCoin = 0], [SilverCoin = 0], [CopperCoin = 0]);
アイテムの価格は買う時と売る時によって値段が違う。(たしか5倍だっけかな)
分かりづらいのでItem.sellPrice()を使えば売る時の値段で価格指定ができる。
何よりわかりやすい。
ModItem.ModifyTooltips(List<TooltipLine> tooltips)
Tooltip.SetDeafults("")で色付きのツールチップ([c:ffff00:]って書くやつ)がなんか違和感だったり、
(ほかのツールチップは少し色が変化する(明暗)のだが、[c:ff0000:]を使うとそのままの色になってしまう)
任意の位置にツールチップを入れたいけど、なぜか最後に表示されてしまう
と言うときに使うと便利。
むしろTooltip.SetDefaults("")なんか使わないでこれ使え
不自然でない色付きのツールチップを追加する方法
public override void ModifyTooltips(List<TooltipLine> tooltips)
{
TooltipLine line = new TooltipLine(mod, "(ツールチップ名)", "(ツールチップの文章)")
{
overrideColor = new Color(255, 255, 0)
};
tooltips.Add(line);
}
アイテム名の下に任意のツールチップを表示させる方法
public override void ModifyTooltips(List<TooltipLine> tooltips)
{
int index = tooltips.FindIndex(tooltipLine => tooltipLine.Name.Equals("ItemName"));
if (index == -1)
{
return;
}
tooltips.Insert(index++, new TooltipLine(mod, "(ツールチップ名)", "(文章)"));
}
ModItem.PostDrawInInventory(SpriteBatch spriteBatch, Vector2 position, Rectangle frame, Color drawColor, Color itemColor, Vector2 origin, float scale)
回復アイテムを飲んだ時のバッテン印、あれを表示する方法がここにある。
大したものではないので、ここで共有するとしよう。
public override void PostDrawInInventory(SpriteBatch spriteBatch, Vector2 position, Rectangle frame, Color drawColor, Color itemColor, Vector2 origin, float scale)
{
Vector2 position3 = position + frame.Size() * scale / 2;
position3 -= Main.cdTexture.Size() * Main.inventoryScale / 2f;
spriteBatch.Draw(Main.cdTexture, position3, null, Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
}
自作発言とか普通に好きではないが、実際バニラのコードを元に、tModLoader用に書ているだけなので
使いますみたいな許可とか、クレジットに書く義務はない。(あったら嬉しいけどそこまでのものではない。)
クレジットに書かなくていい代わりに、なぜこのようなコードなのか理解して使ってほしい。
趣味でプログラミングをやっている者だが、それは多分みんなにとって大切だと思うものなはずだから。
さぁ、ILSpy使ってTerraria.ModLoader.ModItem.PostDrawInInventoryからAnalyzeの旅だ!
ModNPC
bool ModNPC.PreNPCLoot()
ここでfalseを呼び出すとModNPC.NPCLoot()が呼ばれなくなる。
ボスはデフォルトでLesser Healing Potionを落とすため、それが嫌ならfalseを返すべし。
ただし、*** has been defeated!も表示されない上、ハートやスターも自分で落とす処理を書かなければならなくなる。
ModProjectile
projectile.aiStyle = 1;
これに関してだが、これを使うと数千行のコードを参照することになる。
実はtModLoader wikiに5行程度で済むように書くことをお勧めしている(?)
projectile.aiStyle = -1;にして
AI()に下のリンクにあるコードを書くといい。
https://github.com/tModLoader/tModLoader/wiki/Basic-Projectile#aistyle-1
他のaiStyleに関しても同等のことが言えたりする。
NPCのaiStyleも。
ModPlayer
bool ModPlayer.PreItemCheck()
バニラアイテムの挙動を変えたい時、場合によってはこれが必要になる。
ただし、とても面倒なうえ、そこまでやる必要があるか考えること。
なるべくだったら違う手段を探した方がよい。 IL編集とか
Player.ItemCheck()をtModLoader用に書き換え、不要な部分(if (item.type == x)など)を削る必要がある。
GlobalNPC
GlobalNPC.EditSpawnPool(IDictionary<int, float> pool, NPCSpawnInfo spawnInfo)
Example Modにこの説明がなかったので
使い方はModNPC.SpawnChance(NPCSpawnInfo spawnInfo)とほぼ同じ。
if文やSpawnConditonを使いつつ、
pool[NPCID.~ または ModContent.NPCType<T>()] = SpawnCondition.~ * 0.2f;
みたいな感じで使う。
pool[npc.type] = float
ModWorld
ModWorld.ModifyWorldGenTasks(List<GenPass> tasks, ref float totalWeight)
もう書くネタが多すぎますね。
常套手段
まず常套手段の書き方を置いておく
public override void ModifyWorldGenTasks(List<GenPass> tasks, ref float totalWeight)
{
int index = tasks.FindIndex(genPass => genPass.Name.Equals("shinies"); // 地中に鉱石を埋めるならshiniesでよいが、場合によっては違うのを入れたりする。
if (index != -1)
{
tasks.insert(index++, new PassLegacy("(なんの処理か簡単に説明する)", delegate (GenerationProgress progress
{
progress.Messarge = "(実際に表示されるプログレス名)";
//(処理)
}));
}
}
処理に関わるメソッドはWorldGenの項目やMainの項目を参照。
カスタムワールド生成
tasksに関してだが、List<GenPass>の意味がなんとなく分かるなら、もうカスタムでワールド生成コードを書くことができることに気付けるだろう。
私は最近、スーパーフラットのようなワールド生成がかけるか試したところ、成功したので共有する。
実際、バニラのコードを見れば分かるのだが、
Reset, Terrain, Spawn Point, Final Cleanupは最低限必要と見えるだろう
私は怠惰なのでReset, Terrain, Tile Cleanup, Final Cleanupを残しておいた。
スポーンポイントの設定はそこまで複雑ではない。
さて、ぱっと見上手くいったように見えるのだが、もしかしたら何かが足りない可能性は十二分にある。
これから書くコードは推奨しないうえ、自己責任でお願いします。
あと極力使わないでくれ。似たようなプロセスを得て、正しく実行できるプログラムならいいけど。
正直使うならクレジットに書いてほしい気持ちがある。
さらに言うと、ここに載せるのはエラー処理などは書いていない。
そのままコピペすればいずれ破滅するコードだ。
1 まず、必要最低限(だと思われる)のものをあらかじめ変数で引っこ抜いておく。
public override void ModifyWorldGenTasks(List<GenPass> tasks, ref float totalWeight)
{
int index = tasks.FindIndex(genpass => genpass.Name.Equals("Reset"));
GenPass reset = tasks[index];
index = tasks.FindIndex(genpass => genpass.Name.Equals("Terrain"));
GenPass terrain = tasks[index];
index = tasks.FindIndex(genpass => genpass.Name.Equals("Tile Cleanup"));
GenPass tileCleanup = tasks[index];
index = tasks.FindIndex(genpass => genpass.Name.Equals("Final Cleanup"));
GenPass final = tasks[index];
//続きはここから
}
2 元のワールド生成を削除
tasks.Clear();
3 ResetとTerrainを追加
tasks.Add(reset);
tasks.Add(terrain);
4 スーパーフラット
tasks.Add(new PassLegacy("SuperFlat", delegate (GenerationProgress progress)
{
progress.Messarge = "Super Flat";
for (int i = 0; i < Main.maxTilesX; i++)
{
for (int j = (int)Main.worldSurface - 100; j < Main.maxTilesY; j++)
{
WorldGen.PlaceTile(i, j, TileID.Dirt); //層ごとにタイル変えたいって? 自分で考えて... ここに書くのめんどくさいのよ...
}
}
for (int i = 0; i < Main.maxTilesX; i++)
{
int j = (int)Main.worldSurface - 100;
WorldGen.SpreadGrass(i, j); // 申し訳なさそうな程度の草生やすプロセス
}
}));
5 スポーン地点
tasks.Add(new PassLegacy("SetSpwanPointAlt", delegate (GenerationProgress progress)
{
progress.Messarge = "Set Spawn Point";
Main.spawnTileX = Main.maxTilesX / 2;
Main.spawnTileY = (int)Main.worldSurface - 100 - 3; //多分-3じゃなくて-2程度がいいかも
}));
6 Cleanup系統追加
tasks.Add(tileCleanup);
tasks.Add(final);
↓実際に生成した様子 (ここに載せたコードとは異なるが、やったことはほぼ一緒)
https://youtu.be/GeytP4mLqqE
ちなみにプロセスバーが進んでいない原因は不明。
もしかしたらtotalWeight使うのだろうけど分からないので
誰か教えてくれ()
GlobalTile
WorldGen
WorldGen.Convert(int i, int j, int conversionType)
i, jはブロック座標
conversionTypeは 1で邪悪化 2で神聖化 4で真紅化
Main.item, Main.projectile, Main.npcなど
Mainは多すぎるので項目を分ける。
ここではMain.itemなどについて説明。
Main.itemや、Main.npc、Main.projectileはSetDefaultsしたものを入れている配列ではない。
Main.itemは地面に落ちているアイテムの配列。
Main.projectileはフレーム内に存在する発射体の配列
Main.npcはフレーム内に存在するNPCの配列である。
あくまでゲーム内の情報を表すので
バニラアイテムの挙動を変えるなどは、Global系を継承しよう。
Vanilla Item Info
Recipe
ピッケル = 木材x4 + 何らかの素材x12
1.4では8個にコストが下がった
Value
インゴット(Bar) と 鉱石(Ore)の価格は クラフト前と後では変わらない
鉱石1個当たりの価格 * インゴットにするに必要な数 = その鉱石のインゴット(1個)の価格
Rarity
↧