PSO2 8周年イベントスコアをGASで追いかけて見る

PSO2で開催中の「8周年WEB連動パネルイベント」で公開されている獲得スコアを、GoogleSpreadsheet+GoogleAppsScript(GAS)を使って自動集計してみました。


WEB連動パネルイベントとは

技術記事寄りなので、前提情報をちょっと補足。

夏の7~8月と、年始の1月に定期的に、あとは不定期コラボなどでも開催されるゲームイベント。

指定されたクエストにおける全ユーザーの報酬獲得数を集計し、そのスコアに応じて後日発生するボーナスイベントの内容が決まるというもの。

累計数は30分毎に集計され、WEBサイト上に公表されていますが、集計スコアは画像化されています。

GASによる自動集計手順

30分おきにWEBサイトを見に行ってスコアを目視、数値を記録、というのを1人でやると破綻してしまうので、GASを使って自動化します。GCPまでは使ってないので無料で。

サービス稼働初期のイベント時(2013年頃)には、掲示板で有志が逐一報告したりしていたようですけどね。

ゲーム内の自動操作化は利用規約(第9条第1項)で禁止されていますが、WEBスクレイピングについては禁止されていないので大丈夫…なはず。付随サービスに入るのかな…。

フローとしてはこんな感じ。

Googleドライブで画像をGoogleドキュメントに返還して読み込むと、画像の内容を文書化してくれるという機能があるので、これをOCRとして使います。

GoogleDocsファイルはOCRが終われば不要なので削除しますが、オリジナルの画像ファイルは残しておきます。うまくOCR出来なかったときのエビデンスにもなりますし。

GASソースコードがこちら。(フォルダIDは隠しています)

function check8thAnivScore() {
  const datetime = new Date();
  var file_blob = getCountImage_8thAniv(datetime);
  var ocr_text = scanOCR_8thAniv(datetime, file_blob);
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("8周年WEBスコア経過");
  var last_row = sheet.getLastRow();
  
  // 集計→WEB更新までの所要時間(5~8分ほど)を引いてから30分単位切り捨て
  var minute = datetime.getMinutes();
  var calc_datetime = new Date();
  calc_datetime.setMinutes(Math.floor( (calc_datetime.getMinutes()-8)/30)*30); 
  calc_datetime.setSeconds(0);
  
  // シート末尾に記載
  sheet.getRange(last_row+1, 1).setValue(datetime);
  sheet.getRange(last_row+1, 2).setValue(calc_datetime);
  sheet.getRange(last_row+1, 3).setValue(ocr_text);
  var dist_formula = "=C" +  (last_row+1) + "-C" + last_row
  sheet.getRange(last_row+1, 4).setValue(dist_formula);
  
}

// 7周年イベントのカウンタ画像を取得し、Driveに保存する
function getCountImage_8thAniv(datetime) {
  // 取得元URL
  var url = "http://pso2.jp/players/event/8th_anniversary/webevent/panel/count.png"
  // 保存先フォルダ
  var folder_id = "*********************************"
  var folder = DriveApp.getFolderById(folder_id)
  // 保存日時(=保存ファイル名)
  var file_name = Utilities.formatDate(datetime, "JST", "yyyyMMdd_HHmmss") + ".png"
  var response = UrlFetchApp.fetch(url);
  var file_blob = response.getBlob().setName(file_name);
  var file = folder.createFile(file_blob)
  
  return file_blob;
}

// OCRで数字画像を数値として読み取る
function scanOCR_8thAniv(datetime, file_blob) {
  var file_name = Utilities.formatDate(datetime, "JST", "yyyyMMdd_HHmmss")
  var resource = {
    title: file_name, // ファイル名
    mimeType: "image/png" // ファイルのMIMEタイプ
  };
  
  // OCRの設定
  var optional_args = {
    ocr: true,
    ocrLanguage: 'en'
  };
  // 保存先フォルダ
  var tmp_file = Drive.Files.insert(resource, file_blob, optional_args);
  var ocr_doc = DocumentApp.openById(tmp_file.id);
  var ocr_text = ocr_doc.getBody().getText();
  
  var folder_id = "*********************************"
  var folder = DriveApp.getFolderById(folder_id)

  // 余計な文字を除外
  ocr_text = ocr_text.replace(/^\n/, "");
  ocr_text = ocr_text.replace(/[.,]/g, "");
  folder.addFile(DriveApp.getFileById(tmp_file.id));

  // Docsファイルは削除
  var file_id = tmp_file.id
  Drive.Files.trash(file_id)
  
  return ocr_text;
}

なおGoogleDriveに画像を保存するため、GoogleAppsScriptの「リソース > Googleの拡張サービス」から、Drive APIを有効化する必要があります。

check8thAnivScore関数がメイン関数なので、これを実行すれば処理が回ります。

自動化するためトリガー設定で、30分おきの時間主導型に設定します。

ただ今回のケースの特殊なところが、30分毎に集計・公開されていると言っても、流石に向こうの処理にも時間が掛かるようで、17:30の集計内容がWEB上に表示反映されるのは17:40前後。

「○○おき」の時間主導型トリガーは、トリガー保存した時点から周期を取り始めるので、保存タイミングを調整する必要があります。

今回の場合は16分のマージンを取ってトリガー保存して、毎時16分・46分頃に取得するようにしました。

そうして集計しているGoogleスプレッドシートはこちらで公開しています。実際に動いている物なので、イベント終了まで集計し続けます。

スコア推移から見えるもの

時系列推移

この記事を書いている3週目時点でのグラフにするとこんな感じ。赤の棒グラフが30分間の増分、青の折れ線グラフが累計数の推移です。

増分0が3箇所あるのはメンテナンス中でゲームが動いていないため。

時間帯別の推移傾向

手修正を加えて別のBIツールを使って時間別の推移を見ると、19時頃から急激に伸び始めて、23時がピーク、25時を過ぎると低い水準になります。

緊急クエストによる増減具合

ただ、推移が滑らかにならず、土日19時や日曜21時などに凹みが見えるのは、「緊急クエスト」が予告発生している時間帯のため。

「緊急」が「予告」されるっておかしな話1)元々は突発的に発生する大型クエストとして売りにしていたのが、 サービス開始3年目くらいから事前にスケジュール発表されるようになりメインコンテンツ化ですが、目玉アイテムなどを求めてそちらに人が流れるのです。

緊急クエストはWEB連動イベントのスコア集計に入っていないので、緊急クエスト中はスコアが減っている、という格好。

予告緊急クエスト発生時間と照らし合わせると、こんな感じになります。予告回数が少ない(3週間中で2回以下)ものは除外。

スコアが平時(緊急クエストが無いとき、上表の「NULL」列)より伸びが鈍いところほど赤く記載しています。 緊急クエストに行っている人が多そうという指標。

直近で難易度が引き上げられた「異界に紡がれし知の化身」や「悲劇を歌う怨嗟の虚影(ペルソナ緊急)」が特に人気そう。

ペルソナ緊急に至ってはクリア後ブーストをスコア稼ぎに活用する人も多いようで、その時間帯にあたる「ペルソナ後30分」は平時以上にスコアが伸びている青文字が並んでいます。

Ship別ポイントランキングの効果

時間帯による波はありつつ、日を追う毎に緩やかに下がってきている…と思いきや、7/18に急に盛り上がりましたが、これは「Ship別ポイントランキング」集計時間に入ったため。

パネルによる累計集計と別に、個人別の集計もされているのですが、7/18午後から7/19午前の24時間は、個人スコアをShip別に集計した順位を競うというもの。

1週目土日の同時間帯は8万pt/時ほどまで減っていたのが、対抗戦期間中の2週目は45万pt/時を割り込まなかったので、寝る間を惜しんで稼いだ人が結構居たのでしょうね。

頑張る人は頑張るのだろうとは思っていましたが、深夜帯の稼働を5倍近く上げるというのは予想以上で驚きました。

ちなみに第1回の10位ボーダーを見て回ったところ2)これはゲーム内でしか参照できないので手動、Shipによって差はありますが、見た限りだと12000~16000ptくらいでした。

普通に回ると7分くらい毎に20ptほどですが、 最低でも毎時500ptペースということは、トリガーアイテム3)特別なクエストを受けることが出来る消費アイテム。こちらだと4分で60ptくらい稼げるを使い続けたりしたのでしょう。

8/1午後~8/2午前に第2回が開催予定ですが、毎月2日は更にブーストされる「PSO2の日」とも重なっているので、これ以上の伸びが見られるかもしれません。


ゲームも、個人プログラム・データ分析も、まずは自分のため、自分の手を伸ばせる範囲で楽しみながらやってます。

他にやってることだと、週次のスケジュール管理をGoogleフォーム+スプレッドシートで管理するとか。

PSO2以前にはゲーム攻略サイトを作ったりもしてましたが、手を広げるのに疲れてしまったので、今はこうやってニッチに細々と。広げ続けられる人は偉いと思う。

新型コロナウイルスの経過とかも、興味はあるけど、実際にどうこう出来る立場には居ないですからね。それは最前線の人に任せつつ。

でもこうして積み重ねながら、チャンスが降って来たときに掴める力は蓄えておきたいなと。

補足   [ + ]

1. 元々は突発的に発生する大型クエストとして売りにしていたのが、 サービス開始3年目くらいから事前にスケジュール発表されるようになりメインコンテンツ化
2. これはゲーム内でしか参照できないので手動
3. 特別なクエストを受けることが出来る消費アイテム。こちらだと4分で60ptくらい稼げる