エンジニア的ゲーム理論

FFXIVやPCが好きなITスペシャリストの屁理屈とか

「論理と、幻想と」絶アルテマ論理攻略チーム参加報告書

次のパッチ5.1の絶コンテンツに向けてレイドプレイヤー界隈がぼちぼち賑わいはじめたような気がします。そういえば私も絶をやっていて、とても楽しく、また中々に得がたい経験を出来たので書き記しておきます。

話は半年ほど前の話になります。誘っていただいたコミュニティ内で、かねてから「いつか私も絶やりたいと思っているんだよね」という話をしていて、しかし数カ月後には漆黒のヴィランズのリリースがある。やるなら今しかない。やろうか。そんな流れで話が始まりました。

論理か、幻想か

絶アルテマ破壊作戦 論理攻略チーム「UltimateRINGS」

f:id:hackermind:20191010113036p:plain

補助輪の開発実績にとても大きな敬意を払っているデベロッパーであり、かつオフラインでも顔を合わせたことのある友人でもあるanoyetta氏のお誘いを二つ返事で承諾し、私はこのチームのメンバーとして活動を開始しました。

www.anoyetta.com

私の想い

エンジニア的な想い

このパーティはとても特殊でした。なんせ、ツール作ってる人が主催してツール活用してみたいと思っている人が「もっと面白い活用方法を見つけたい!」と思って参加しているわけですから。

既に私にとってACTっていうのは「ゲームクライアントから何かに繋げるインターフェースのひとつ」でしかなかった。けれど、まだ世の中でツールを使っている人の多くが、目の前にある数字を見たりシナジースキルの読み上げ程度で留まっていたじゃないですか。だから「我々が本気で使いこなしたら……」そういうパフォーマンス的な側面もあった。決してToxicにならず、こういうこともできるんだよ、っていうアプローチです。

単純にプレイヤーとしての自己顕示欲

絶バハムートはプライベートの都合が合わなくて挑戦を途中で諦めた。絶アルテマも休止していたし、後からクリア済の人に一緒にやろう、と声かけてもらったはいいけれど、そこでもリアル都合やパーティの姿勢に合わなくて早々に離脱した。

このまま「やらない」という選択をし続けるのは簡単だけど、けれど自分がそれほどド下手だとも思っていないので、やればできる子なんですよ!っていう実績は作りたかった。

納得してやると決めたからには、徹底的にやる。

研究開始

私達は徹底的にツールを活用した。あったら便利だと思った機能はその場で作ったり書き変えたりした。

既知の情報をかき集めて、共通のタイムラインと、そのタイムライン上でロードされる外部設定ファイルによって、あらゆるアクティビティについて個々人で簡便に書き換えが可能なタイムライン定義ファイルを作った。

挑戦前後には予習範囲と課題を確認して打ち合わせを行い、毎日PDCAサイクルを回した。挑戦中に何か気づいたことがあれば、各自のタイムラインの設定ファイルを書き換えて「ここで私はこのコールを組み込む」というのを1トライ毎に繰り返した。配信もしていたけれど、テキストエディタがドドーンと映るゲーム配信というのは私は初めて見た。自分たちでやってたんだけど。

戦術の選定については、クリア経験のある人や最終フェーズ付近まで到達したことのある方を中心にレコメンドしてもらい、それらのロジックを組み立てて理解することに努め、時系列に沿ったタイムライン形式に展開した。一つ一つのギミックの解法をかい摘めば、より効率的なやり方はいくらでもあったかもしれない。けれど自分たちの課題に対して有効かどうか、結局これが全ての基準だったような気がする。

異変

挑戦から半月が経った頃、私は引っ越しやら何やらで急激に忙しくなってきた。さて、どうするか。

私の思いはもはや当初とは異なっていた。ツールを活用して楽しくアルテマウェポンをハッキングする、これはいつの間にか1つの自己実現の目標になっていた。私が近い将来に世の中に問いかけたいITリテラシーの在り方、その哲学に鮮やかな色味を持たせること、ここに通じてしまった。都合のいい幻想のひとつです。

同時に、親族が倒れたり、うまく形容できないけれど悲しい出来事も同時に重なった。珍しく精神的にボロボロだった。時間がないわけではない、しかしMPが足りなくなって感情のコントロールが少し難しくなってしまい、攻略への影響を危惧していた瞬間もあった。感情というのもまた、私たちを惑わす幻想の一種だと思っています。

話は変わります。

私たちのタイムラインをロードした状態で、/rings logo とゲーム内でテキストコマンド風チャットを打つと、チームのアイキャッチ画像が画面いっぱいにオーバーレイ表示されます。そういうふうに作ってくれました。

練習の開始時には必ずanoyetta氏がこのオーバーレイ画像を出し、チームのスローガンを読み上げてくれた。この儀式が、私の葛藤をすっとどこかに追いやって、脳を攻略モードにスイッチするためにとても重要な役割を果たしてくれました。何があった日でも、私はこの人たちと共にやり遂げたい、という思いに迷いは生じなかった。

論理と、幻想と

思っていたより時間はかかったものの、とうとうアルテマウェポンを撃破しました。引っ越しのためにPCを撤去する前日でした。声には出さなかったけれど涙が出そうでした。この期間に失ってしまった何かがあった気がするけれど、代わりに大きな何かを得た気がした。何かは分からないけど何か。

f:id:hackermind:20191010113711p:plain

論理という武器をもって幻想(ゲーム)に立ち向かう。当初はそんなコンセプトだったような気がします。

しかし実際はどうも違った。ツールを使いこなせたから勝てた、とは微塵も思っていない。練習を進めるのに必要だったのは理解と慣れ、ログの解析をしている過程で多少のアドバンテージは得たかもしれないけれど微々たるものです。そして最後に私を突き動かしていたのは結局、冷静さと諦めない心とクリアへの執念みたいなエモい幻想の類だった気がします。時に幻想に惑わされ、時には幻想に恋い焦がれた。そもそものコンセプトに惑わされていたことすらあったかもしれない。論理とは、そうした幻想に立ち向かう自身の在り方や、その骨子を補強するものだったと気づくわけです。

「論理か、幻想か。」それはOR条件である必要はなく、共存、すなわちANDでよかったんだな、というのが私の所感でした。なので「論理と、幻想と。」という件名にしました。

論理が幻想に勝利したのではない、論理と幻想が融和したんだ、とカッコつけて言いたかったけどタイミング逃した。

おまけの本編:成果物

成果物であるタイムラインは公開しています。

www.anoyetta.com

一部、自分だけのカスタムを加えた部分を共有します。

三連ジェイルを紐解くアルゴリズム

タイタンの3連ジェイル付与時に、自分以外に誰が対象になったかを判定し、それらによって自分がジェイルを配置すべき相対位置を音声によってコールする機能を1人でコネコネ作ってました。自分がジェイルを置く位置が「先頭」なのか「真ん中」なのか「後ろ」なのか。パーティ編成が変わっても同じように使えるようにしています。

アイコンによる並び順の表示はこんな感じでやりました。

blog.sheeva.me

しかしこいつで実現できるのは表示順に従って順番に並び替えたアイコンを表示するだけです。他のトリガとの複合的な条件判断をして相対的な位置関係をアナウンスをしているわけではないです。だから「自分がジェイル対象になった」というアナウンスしかできません。なのでタイムライン側でそのロジックを書きました。適材適所ってやつです。

慣れないうちは画面見るよりも耳で聞いた方が動き出しが早いような気がして、では作るかと意気込んでいたんですが、これもまた作ってる間に慣れてしまってアイコンぱっと見て動けるようになってました。けれど「あった方が良い」または「あっても良い」というものなら作りたくなってしまいます。そういう性分なんです。出来上がったら少しだけ楽でした。

JSONファイル側の追加定義

コール内容、並び順の配列 JailOrder を外部jsonファイル内で定義しています。ジェイルの判定用のトリガーのコンパイル時に、タイムラインのxml内に埋め込んだrazor構文によってこの配列が呼び出されます。

{
    "NoticeForJail" : "true",
    "NoticeAllJails" : "true", // 自分以外に付与された全てのジェイルを表示するか否か。既定値true
    "NoticeForJailsCompact" : "false", // 対象者アイコンのみ表示にするか否か。既定値false
    "JailPositionHead" : "ジェイル、先頭。",
    "JailPositionCenter" : "ジェイル、真ん中。",
    "JailPositionTail" : "ジェイル、後ろ。",
    "JailNotMe" : "自分は対象外。",
    "JailOrder" : [ "PLD", "WAR", "DRG", "NIN", "BRD", "SMN", "WHM", "SCH" ] 
}
タイムラインXMLファイル側の追加定義

razorエンジンでjsonファイル内に定義された並び順配列 JailOrder の先頭要素からループさせて、人数分のトリガーを書き出す処理をしています。

タイムラインが受け取るデータダンプのうち Model.Player.Job によって自分のジョブを判定し、自ジョブより前に並ぶべき人がジェイル対象になったときにはスペスペたいむのタイムライン内部変数 MyPosition を加算する処理をトリガー内に書き加えます。

この変数 MyPosition自分より前に立つ人の数 を示す値で、つまり自分の相対的な立ち位置を示すポインタになります。 最終判定時に MyPosition0 なら先頭、1 なら真ん中、2 なら最後尾、と定義してロジックを組みます。初期値は0で、自分より前に立つ人がジェイル対象となった判定が走るたびに1ずつ加算されるようにします。

  • 各プレイヤーにマッチするジェイル判定トリガー(sync-count="1"no="1~8")の判定
    • 自分より前の人がジェイル対象の場合、MyPosition += 1 (1加算)する
      • 自分または自分より後ろの人にマッチするトリガーにはこの MyPosition += 1 の処理を含めない
  • 3つ目のジェイルのトリガーにのみマッチするトリガー(sync-count="3"no="10~13")の判定
    • 最終判定。ジェイル対象者3名が確定し、パーティメンバ個別のトリガ処理が全て終わっている状態
    • 自分にジェイルがついている場合
      • MyPosition0 なら自分より前に立つべき人はいない。先頭へ、とコール
      • MyPosition1 なら自分より前に立つべき人が1人いる。真ん中へ、とコール
      • MyPosition2 なら自分より前に立つべき人が2人いる。最後尾へ、とコール
    • 自分がジェイル対象外だった場合は自分がジェイル対象外である旨のコール
    <!-- 優先度ジェイル トリガ begin -->
    @if (Convert.ToBoolean(@json.NoticeForJail)) {
    <t sync="タイタンは「大激震」の構え" >
      <expressions>
        <set name="MyPosition" count="0" />
        <set name="IsJailMe" value="false" />
      </expressions>
    </t>

    <t text="自分のジェイル対象判定" sync=":グラナイト・ジェイル:........:[mex]:" sync-count="1" >
      <expressions>
        <set name="IsJailMe" value="true" />
      </expressions>
    </t>

   {
    int priority = 0;
    bool isMyFront = true;
    foreach (string job in json.JailOrder) {
    @:<t no="@priority" sync=":グラナイト・ジェイル:........:[@job]:" sync-count="1" >
      if (job == Model.Player.Job) { isMyFront = !isMyFront; }
      if (isMyFront) {
      <expressions>
        <set name="MyPosition" count="+1" />
      </expressions>
      };
      if (Convert.ToBoolean(@json.NoticeAllJails)) {
        if (Convert.ToBoolean(@json.NoticeForJailsCompact)) {
      @:<v-notice order="@priority" duration="5" duration-visible="false" job-icon="true" />
        } else {
      @:<v-notice order="@priority" text="[@job]" duration="5" duration-visible="false" job-icon="true" />
        }
      }
    @:</t>
    priority++;
    }
  }

    <!-- 最終判定 -->
    <t no="10" text="ジェイル先頭" sync=":グラナイト・ジェイル:........:" sync-count="3" notice="@json.JailPositionHead" >
      <expressions>
        <pre name="IsJailMe" value="true" />
        <pre name="MyPosition" count="0" />
      </expressions>
      <v-notice text="ジェイル ➜ 先頭へ!" duration="10" duration-visible="false" />
    </t>

    <t no="11" text="ジェイル真ん中" sync=":グラナイト・ジェイル:........:" sync-count="3" notice="@json.JailPositionCenter" >
      <expressions>
        <pre name="IsJailMe" value="true" />
        <pre name="MyPosition" count="1" />
      </expressions>
      <v-notice text="ジェイル ➜ 真ん中!" duration="10" duration-visible="false" />
    </t>

    <t no="12" text="ジェイル最後尾" sync=":グラナイト・ジェイル:........:" sync-count="3" notice="@json.JailPositionTail" >
      <expressions>
        <pre name="IsJailMe" value="true" />
        <pre name="MyPosition" count="2" />
      </expressions>
      <v-notice text="ジェイル ➜ 後ろ!" duration="10" duration-visible="false" />
    </t>

    <!-- 自分にジェイルが来なかった -->
    <t no="13" text="ジェイル対象外" sync=":グラナイト・ジェイル:........:" sync-count="3" notice="@json.JailNotMe" >
      <expressions>
        <pre name="IsJailMe" value="false" />
      </expressions>
      <v-notice text="ジェイル対象外" duration="10" duration-visible="false" />
    </t>
    }
    <!-- 優先度ジェイル トリガ end -->

razor構文は一見複雑に見えますがシンプルなプログラムです。コンテンツ突入時にこの構文を読み解いてコンパイルされると、先のトリガは以下のようなXMLとしてタイムライン上に埋め込まれます。当然、並び順を定義した配列を書き換えるとこのトリガ内のジョブのソートオーダーも変わるので、パーティ編成や並び順を変えても混乱することは少ないでしょう。

    <t no="0" sync=":グラナイト・ジェイル:........:[PLD]:" sync-count="1" >
      <v-notice order="0" text="[PLD]" duration="5" duration-visible="false" job-icon="true" />
    </t>
    <t no="1" sync=":グラナイト・ジェイル:........:[WAR]:" sync-count="1" >
      <v-notice order="1" text="[WAR]" duration="5" duration-visible="false" job-icon="true" />
    </t>
    <t no="2" sync=":グラナイト・ジェイル:........:[DRG]:" sync-count="1" >
      <v-notice order="2" text="[DRG]" duration="5" duration-visible="false" job-icon="true" />
    </t>
    <t no="3" sync=":グラナイト・ジェイル:........:[NIN]:" sync-count="1" >
      <v-notice order="3" text="[NIN]" duration="5" duration-visible="false" job-icon="true" />
    </t>
    <t no="4" sync=":グラナイト・ジェイル:........:[BRD]:" sync-count="1" >
      <v-notice order="4" text="[BRD]" duration="5" duration-visible="false" job-icon="true" />
    </t>
    <t no="5" sync=":グラナイト・ジェイル:........:[SMN]:" sync-count="1" >
      <v-notice order="5" text="[SMN]" duration="5" duration-visible="false" job-icon="true" />
    </t>
    <t no="6" sync=":グラナイト・ジェイル:........:[WHM]:" sync-count="1" >
      <v-notice order="6" text="[WHM]" duration="5" duration-visible="false" job-icon="true" />
    </t>
    <t no="7" sync=":グラナイト・ジェイル:........:[SCH]:" sync-count="1" >
      <v-notice order="7" text="[SCH]" duration="5" duration-visible="false" job-icon="true" />
    </t>

配信用アイキャッチ画像の表示コマンド

  <!-- UltimateRINGS アイキャッチ -->
  <t name="RINGSLogo" sync="/rings logo" >
    <i-notice image="UltimateRINGS_LOGO.png" duration="10" scale="1.0" left="1080.0" top="342.0" />
  </t>

これもタイムラインの機能を使っています。トリガ対象文字列に自分用のコマンドを定義してしまえば良い。この場合は /e /rings logo と入力すると i-notice に指定した画像が表示されます。ただLSやFCでこの文字列を入力して攻略の邪魔することもできてしまう。echo区分のネットワークログを示すprefixか何かをマッチング条件に追加しておくのが無難かもしれない。

表示座標は私のプレイ環境(3440x1440)で画面中央にアイキャッチ画像(1280x756)を配置する例です。この部分は手で計算してますが、razor構文で画像サイズと画面の中央に表示するためのロジックを書いて lefttop にあたる変数を書き出す、という運用もできなくもなさそう。