【factorio】ブループリント解析してみた
これは2024team411アドベントカレンダー7日目の記事です。
昨日は猫好きさんの「Unityで一方通行の壁を(ゴリ押しで)作る」でした。一方通行の壁を作るのは結構めんどくさそうと思ってましたが意外と簡単に実装できるのですね。自分もUnity触りたいと思っているので参考になりました。
今回の記事はfactorioというゲームのブループリントを解析してみたお話です。
はじめに
皆さんはfactorioというゲームを知っていますか?簡単に言うと未知の惑星に不時着した工場長がその惑星で工場を作りロケットを発射することを目指しすゲームです。まぁ工場作る系のゲームです。これがまた時間泥棒で気づいたら5時間とかざらに経っちゃいます。情報学部系の大学生ならハマると思います。
こんなのとか
こんなの作れます
大規模な工場作成には設計図が付き物です。そうそれがブループリントです!(正確には建築計画のブループリントですが今回は省略してブループリントと言います)世の中の有志の方がブループリントを後悔してくれています。こことかここに多くの外人ニキが投稿してくれています。
ブループリントは文字列だけで表されていて、直接読むことはほぼ不可能です。
ブループリントの例
今回はそんなブループリントを解析してなんならゲーム内で発行しなくても自分で書けるようにしちゃおうのコーナーです。
まずは解析
まずはブループリントの中身を読めるようにしたいと思います。factorioのwikiにはこう書かれています。
The text string itself is a base64 encoded, compressed JSON string which contains all the information of the blueprint. It is therefore possible to decode/decompress the text string, change attributes of the blueprint in the JSON text and finally re-encode/compress it back to the known text string format. This basically allows blueprint editing outside of the game itself.
つまり、ブループリントはBase64形式でJSON形式の文字列がエンコードされているとのことです。そのためブループリントの文字列をBase64形式でデコードし、JSON形式の文字列に変換後、JSON形式で読みやすいよう変換すればブループリントを読めるようになります。意外と簡単ですね。ブループリントのJSON形式はこれで構成されています。以下はよく使うキーたちです。
JSONファイルのキーたち
ブループリントオブジェクト
- item
- アイテムの名前、文字列
- label
- ブループリントの名前、文字列
- entities
- ブループリントの内容、エンティティオブジェクトの配列
- icons
- ブループリントのアイコン、アイコンオブジェクトの配列
エンティティオブジェクト
- entity_number
- エンティティのインデックス、整数
- name
- 設置物の名前、文字列
- position
- 設置場所、左上を(0,0)として設置物の中心の位置を表す、浮動小数
これをPythonを用いて読めるようにします。以下のように処理することで読めるようになります。
zlibはデコードされた文字列をJSON形式の文字列に解凍してくれているっぽいです。(よく分かってない)
以下のコードでJSON形式で見れるようになります。
import json
import base64
import zlib
BP_str = "BPの文字列"
BP_64decoded = base64.b64decode(BP_str[1:])
BP_zlib = zlib.decompress(BP_64decoded)
BP_json = json.loads(BP_zlib)
print(blueprint_json)
ではテストしましょう。printの代わりにJSONファイルに保存します。
- print(blueprint_json)
+ new_json = open('bp.json','w')
+ json.dump(BP_json,new_json,indent=2)
+ new_json.close()
以下の画像のブループリントを読み取ってみようと思います。
これをさっきのコードでJSONファイルに書き込んでみると…
JSONファイル
{
"blueprint": {
"description": "hogehoge",
"snap-to-grid": {
"x": 3,
"y": 2
},
"absolute-snapping": true,
"position-relative-to-grid": {
"x": 2,
"y": 1
},
"icons": [
{
"signal": {
"name": "steel-chest"
},
"index": 1
}
],
"entities": [
{
"entity_number": 1,
"name": "small-electric-pole",
"position": {
"x": 0.5,
"y": 0.5
}
},
{
"entity_number": 2,
"name": "steel-chest",
"position": {
"x": 0.5,
"y": 1.5
}
},
{
"entity_number": 3,
"name": "steel-chest",
"position": {
"x": 2.5,
"y": 1.5
}
},
{
"entity_number": 4,
"name": "inserter",
"position": {
"x": 1.5,
"y": 1.5
},
"direction": 12
}
],
"item": "blueprint",
"label": "test_blueprint",
"version": 562949954928640
}
}
こうなりました。これを読んでみましょう。
説明文は"hogehoge"。
"snap-to-grid"(BPで使うグリッドの大きさ)はxが3でyは2。
アイコンは"steel-chest"でインデックスは1。
エンティティは"small-electric-pole"が(0.5,0.5)、"small-chest"が(0.5,1.5)と(2.5,1.5)、"inserter"(黄色いやつ)が(1.5,1.5)で"direction"が12(右向き)。
"item"が"blueprint"。
"label"が"test_blueprint"。
"version"…はマップのバージョンに依存するため確認しようがありません。(自分で書く際も同じ数字を使用します)
...全て合っています!!
("position-relative-to-grid"はBPの元になった本体の位置からの相対位置的なので無視します)
これでブループリントが解析できました!!!
次に編集
では自分でブループリントを書いてみようのコーナーです。解析ができてしまえばあとは簡単なお仕事です。
流れとしては解析の逆をたどれば大丈夫です。JSON形式の文字列をBase64でエンコードすればブループリントの完成です。
f = open('write.json','r')
write_json = json.load(f)
f.close()
json_str = json.dumps(write_json,separators=[',',':']).encode('utf-8')
BP_compressed = zlib.compress(json_str,level=9)
BP_64encoded = base64.b64encode(BP_compressed).decode()
BP_str = f"0{BP_64encoded}"
print(BP_str)
では、以下の画像の左下のやつを書いてみようと思います。
JSONファイルの要素を以下のようにします。
"description"を"fugafuga"
"snap-to-grid"をx=5,y=3
"icons"は組立機2(assembling-machine-2)
"entities"は黄色ベルト(transport-belt)を(0.5,0.5),(0.5,1.5),(0.5,2.5)、インサータ(inserter)を(1.5,1.5)、組立機2を(3.5,1.5)
"item"は"blueprint"
"label"は"test_write_blueprint"
"version"は562949954928640
これをJSONファイルに書き込みます。
JSONファイル
{
"blueprint": {
"description": "fugafuga",
"snap-to-grid": {
"x": 5,
"y": 3
},
"absolute-snapping": true,
"position-relative-to-grid": {
"x": 2,
"y": 0
},
"icons": [
{
"signal": {
"name": "assembling-machine-2"
},
"index": 1
}
],
"entities": [
{
"entity_number": 1,
"name": "transport-belt",
"position": {
"x": 0.5,
"y": 0.5
}
},
{
"entity_number": 2,
"name": "transport-belt",
"position": {
"x": 0.5,
"y": 1.5
}
},
{
"entity_number": 3,
"name": "transport-belt",
"position": {
"x": 0.5,
"y": 2.5
}
},
{
"entity_number": 4,
"name": "inserter",
"position": {
"x": 1.5,
"y": 1.5
},
"direction": 12
},
{
"entity_number": 5,
"name": "assembling-machine-2",
"position": {
"x": 3.5,
"y": 1.5
}
}
],
"item": "blueprint",
"label": "test_write_blueprint",
"version": 562949954928640
}
}
ではこれを読み込んでさっきのコードを実行すると以下のブループリントを得ました。
0eNqV0uFugyAQB/B3uc+wKGozfZWlWVAvjgQPA2ezpuHdh9qtyzRL+omAdz/+Ajdo7YyTN8TQ3CCQniQ7OXjTL/NPaCoBV2iKKEC3wdmZUS5Vk6EBGvYzCphcMGwcSY9Ws7ngH0KtRJYI0zkK0LylncxA2i4FpEeEBnQIOLY2sXLU3YchlAqWFuoxGXk8C0DitBFuwjq5vtM8tuhTgfiW2GsKk/MsW7QMj3z3ONnL9k9pjFHsIPUDGQroOa3tiPxOpDFF7I3HbvuaqwOxeDqaOo5WPg3lx1Al/j32HVf85tJFGMYxNT/ejoAL+rA2VCdVl3VdlbV6PZVZjF+2jsmQ
ではこれを実際にゲーム内で読み込んでみましょう!
ゲーム開きーの
ブループリントペーストしーの
設置しーの
トツギーノ
左のものと同じものが設置できました!!!
これにより自由にブループリントを書けるようになりました!やったね!
ちなみにそれなりの大きさのブループリントから人が書くのにはお勧めしない量になります。
あと、JSONファイルのオブジェクト内でのキーに順番はないので意外と自由度高かったりします。
ちょっとした愚痴?小話?
日本ではまだまだ知名度がないこのゲームですが、海外では意外と人気なのか、文献がたくさんあります。特に効率的なラインの話やラインを作るときに使うアイテム比を計算するサイトなどがあります。YouTubeでfactorioの実況動画は十数人ぐらいしか確認していません。日本人少なすぎですマジで。おかげで回路制御とか今回の話とか調べようにも英語の文献しかなくて困りまくっています。ちなみに今回の話に至っては海外の人でも見なかったのでおそらく世界初だったりしそうです。そもそもこんな変なことするに人間なんていないが
ブループリントを解析しようとしたきっかけとして、任意の画像を読み込んでfactorio内で地上絵として書いてみようのコーナーをしようとしたのが出発点でした。(3日目の記事で似たようなこと見たとか言わないでね) 今回の解析により、外部で書いたブループリントをゲーム内に実装することが可能になったので、後は任意の画像をゲーム内アイテムで再現する機構のみとなりました。ここから先はまた別の記事で話すとします。
おわりに
みなさんfactorioは神ゲーなのでプレイしましょう!最近大型DLCで惑星外での探索要素も追加されました!(僕は時間が溶けるのが怖くてまだ買っていませんが…) 公式がセールしないと公言しているらしいのでいつでも今がお買い得です!!!!!!全人類買いましょう。体験版もあるのでやってみてください。くそ面白いです。
明日はluuguasさんがなんか書くそうです。楽しみですね!!!!!
明日はluuguasさんの「Docker+Poetry+GitでPythonの開発環境を構築しよう」です。Dockerを用いた開発環境構築で社内の謎の監視システムも一緒に入ってしまった人の話を思い出しました。(全く関係ない話です)
Discussion