Project ZomboidのModを作った話と重複アイテムの並べ替え処理
公開:
昨年後半あたりから「Project Zomboid」というゲームをやっていました。ゲーム自体は2014年に購入して数百時間はプレイしていたのですが、ひさびさに再開したという感じです。
それはいいのですが、アイテム収集癖のあるわたしはアイテムの管理に苦労していました。
このゲームのアイテムはカテゴリーや名前でソートできるのですが、同じ名前のアイテムはスタックされて、その中身はソートできません。
耐久値などのパラメーターが存在するアイテムの場合、同じ名前でも状態が異なるのです。
拾ったアイテムなどは最初から状態がバラバラなこともあって、雑に突っ込んでいるとアイテムを取り出すときに状態を確認して選び出すという行動を取らなくてはなりません。
それが面倒くさくて、重複アイテムを出し入れして並べ替えるというModを作ることにしました。
アイテムを収納している箱やバッグから、一度プレイヤーキャラクターにアイテムを移し替える必要がある関係上、キャラクターが持っているアイテムは並べ替えられないなどの制限が発生してしまいましたが、どうにかModは完成し、2月27日に公開できました。
それから何度かアップデートして、3月12日版でひとまずやりたかったことは終わり、現在に至ります。
Steam Workshop::Reorder Duplicates by Condition
ありがたいことに2000人以上にも使っていただいており、作ってよかったと思っています。 初めてLuaを書きましたが、とりあえずエラーなく動いているので大丈夫だと思います……というか、基本的に たとえば「包丁」という武器カテゴリーアイテムが複数ある場合は次のような表示になります。 これをクリックするとスタックが展開されます。 その際に、耐久値などのバーが表示されますが、パラメーターによってはマウスオーバーでツールチップを表示させなければなりません。 このゲームの重複アイテムスタックは任意のアイテムを取り出せますが、スタックに突っ込むときは最後にしか入れられません。 Mod作成の場合、収納の中身を並べ替えてデータを書き換えるという処理も可能ですが、若干チート気味なので個人的には避けたく、バニラで可能な動作のみで完結させたかったのです。 この最後にしか追加できないという状況で、最少の手順をどうやって考えればいいのかというのが課題でした。 ちなみにアイテムスタックの中身を並べ替える場合、キャラクターに次の動作を行わせます。 これを耐久値などの、アイテムカテゴリーごとの重要パラメーターを見ながら行います。 最少手順を考えなければ、先に書いたように最低または最高の状態以外のアイテムを全部並べ替えれば完了です。 たとえば次のようなアイテムスタックがあるとします。 このとき、耐久値が低い順に並べ替えるなら、耐久値が 結局どうしたとかというと、並べ替えるアイテムを選択するためのデータを作成して、それで必要分を選び出すという処理をしました。 まず、元の並びのアイテムスタックのデータを取り出し、次のように変換します。 ここでは耐久値ですが、他にも色々なパラメーターを利用しています。それらの値はメソッドで取得するので、この時点で取得して適当なキーに値を入れておきます。 ここまで終わったら、昇順または降順に応じて、上記のテーブルを耐久値で並べ替えます。これは 問題の並べ替えるべきアイテムのフィルタリングは次のようにしています。 引数 あとはこのフィルタリングしたデータにそって取り出して戻すという処理をキューに追加するだけです。 もしかするともっといい方法があるのかもしれませんが、わたしが思いついた方法は以上です。 ModのソースはGitHubにあるので、興味のある方は見てみてください開発中の話
if
とfor
くらいしか使っていません。
どちらかというとゲームのAPIのほうが苦戦しました。
APIの非公式Javadocはあるのですが、説明とかがあるわけではないので、それっぽいものを探して試すを繰り返しました。並べ替え処理の話
▶ 包丁 (5)
▶ 包丁 (5)
包丁
包丁
包丁
包丁
包丁
例えば上記の包丁の場合、3番めのアイテムを取り出すのは問題ありませんが、戻したときは5番目になります。
チートっぽいと感じるのは、アイテムの取り出しや収納にはゲーム内時間が経過しますが、一気に書き換えるとノータイムで並べ替えが完了するからです。
一番最初に出したバージョンでは、たとえば耐久値が「低 → 高」の順で並べ替える際に、最低値以外はすべて並べ替えるという処理をしていました。目的は達成できますが、とても無駄が多かったです。
これはバニラの状態で手作業でやるものですが、これをModで自動化したいというわけです。
アイテム数が10未満だと手作業でも大変ではないですが、そもそもアイテムの種類が多いのと、わたしのような収集癖があるプレイヤーは50個以上のスタックがあってもおかしくありません。
また、マルチプレイの場合は複数人分のアイテムスタックも存在するでしょう。
(Modの説明にはマルチプレイでテストしていないと書いていますが、サーバー機能は使っていないので普通に動くはずです)
しかし実際に使ってみると「このキャラクター手際が悪いな」と感じます。
展開したアイテムに付いている角括弧内の数値は耐久値だと考えてください。▶ 包丁 (5)
包丁 [1]
包丁 [3]
包丁 [10]
包丁 [10]
包丁 [5]
10
のアイテム2つを取り出して戻せばいいだけです。
ところが何も考えていない場合、耐久値が1
以外のアイテムを3
5
10
10
の順で取り出して戻すことになります。
手際が悪いです。
(アイテムスタックのデータを取り出すのもちょっとややこしいのですがスキップします)。{
{
condition = 耐久値,
index = 元の並びのインデックス,
item = {アイテムのデータテーブル}
},
...
}
次は値ごとにアイテムをまとめます。{
[耐久値] = {
value = 耐久値,
last = 該当耐久値が一致する最後のアイテムのインデックス,
items = {各アイテムのデータテーブル}
},
...
}
sort
を使うだけなので難しくありません。local function createTransferItems(data, itemsCount)
local transferItems = {}
local previousLastIndex = 0
for index, baseDataItems in ipairs(data) do
for _, itemData in pairs(baseDataItems.items) do
if itemData.index < previousLastIndex + 1 then
if index > 1 then
table.insert(transferItems, itemData.item)
end
end
end
if #transferItems > 1 then
previousLastIndex = itemsCount
elseif previousLastIndex < baseDataItems.last then
previousLastIndex = baseDataItems.last
end
end
return transferItems
end
data
には、前述の値ごとにまとめたアイテムのテーブルを渡します。itemsCount
はそのままアイテム数です。
やっていることですが、たとえば低い順に並べ替える場合、現在の耐久値より低い耐久値のアイテムテーブルの最後のインデックスより前にある現在の耐久値のアイテムは並べ替えるべき、という感じです。