【Arduino自動化02】雪中渓谷のワットショップの目玉商品を全自動回収【カンムリ雪原】

ますたーです。こんにちは。

2つ目の記事は、剣盾DLC第2段カンムリ雪原で新たに増えたワットショップ、雪中渓谷の「目玉商品」交換に主眼をおいたArduino Leonardo自動化の記事です。

ソースコードの紹介と、使い方を載せていきます。

なお、Arduino Leonardo自動化の導入・機材構成については前記事を参考にしてください。

前記事:【Arduino自動化01】Arduino開発環境の導入 

※本ブログに初めてお越しの方は「本ブログについて」もぜひ、ご覧ください。

 

概要

本記事では、日付バグの併用による目玉商品の無限回収を実装します。
2020/12/5追記:NintendoSwitch Ver.11.0アップデート対応済です。

f:id:tangential_star:20201108025830j:plain
f:id:tangential_star:20201108025408g:plain
雪中渓谷のワットショップで「目玉商品」を自動購入し、日付更新

今回のもくじです。

 

作ったきっかけ

色々なWebサイトを見て調べた限りでは、DLC対応のArduino自動化プログラムで、この「目玉商品の自動回収」は見当たらなかったので作りました。ぼんぐりの回収とレアアイテムの回収が同時にできてハッピーです。

ちなみに、私はまだ見たことありませんが、ここのワットショップでは「ドリームボール」もラインナップにあるそうです。ほしい…

f:id:tangential_star:20201108040932j:plain
f:id:tangential_star:20201108041013p:plain
無限回収していると、まれに「ぼんぐり10個セット」や「サンのみ」、
「キョダイパウダー」など魅力的なラインナップに巡り会える。

 

使い方

ソースコードは本ページ最下部(コチラ)にございます。「使い方はいいから早くソースコードを寄越せ!」という方は本項を読み飛ばしてください。

一応、日付更新・ワット回収部分・話しかけ部分に工夫しており、任意の日付から始めてもうまく稼働すると思います(つまり予め1/1に戻したり31日まである月に変更したりしなくてOK)。

事前準備

正しく動作するための条件は下記の通りです。

  • ポケモン剣盾側の設定
    • しっかりと充分なワットをあらかじめ貯めておく
    • 一度はワットショップのおじさんに話しかけておく
      (初回の専用ルート云々のくだりを回収しておく)
    • ワットショップおじさんの隣にある巣穴に手動で柱を立てておく
    • 自転車からは降りておく
    • Xでメニューを開いた際のカーソルを「タウンマップ」にあわせておく
  • NintendoSwitch側の設定
    • Switch設定「時刻と時刻」の「インターネットで時間をあわせる」をOFF
    • インターネットには接続しない

ポケモン剣盾での操作

基本的に難しい操作はないのですが、雪中渓谷に「空飛ぶタクシー」で移動し、ワットショップの隣(奥)にある巣穴に「ねがいのかたまり」を投げ入れてください。

あとは、「自転車に乗っていないこと」「Xボタンでメニューを開いたときに『タウンマップ』がカーソルされていること」さえクリアしていれば多分動きます。

f:id:tangential_star:20201108103542j:plain
f:id:tangential_star:20201108025830j:plain
Xボタンでメニューを開いた際に、「タウンマップ」にカーソルがあっていればOK
f:id:tangential_star:20201108103408j:plain
f:id:tangential_star:20201108103538j:plain
自転車から降りて、ワットショップの隣の巣穴に「ねがいのかたまり」を投げ入れる
f:id:tangential_star:20201108103927p:plain
f:id:tangential_star:20201108103824p:plain
ワットを回収し、「みんなで挑戦」にカーソルをあてたら準備OK

レイドバトルの「みんなで 挑戦!」を押す前の状態で、スケッチを書き込んだArduino LeonardoをSwitchに差し込むと、自動化が始まります。

挿して3~5秒ほど、何度かHomeボタンが押されるような挙動をするかもしれませんが、これは仕様です(SwitchがArduinoをコントローラーとして認識するまでにかかる時間を調整しています)。

挿してから数秒後、5秒ほどなんの操作もしていない時間がありますが、このタイミングでHome画面(Switchでゲームを選ぶ画面)になっていればOKです。もし、なっていなければ手動で1度Homeボタンを押しておいてください。

書いたプログラム

処理フロー

setup()関数内の PushHome()連打は、挿してから認識されるまでの時間合わせです。

処理フローとしては、下記になります。

  1. 「みんなで挑戦」を押して、日付を進める
  2. 雪中渓谷に空を飛ぶ
  3. ワットショップに向かい、目玉商品を購入
  4. 雪中渓谷に空を飛ぶ
  5. 巣穴に向かい、ワット回収。
  6. 1に戻る(ループ)

空を飛ぶのは、天候や出現ポケモンによる負荷の違い?によって移動する向きが微妙にずれたり、オフセット誤差が溜まったりするのでこれらを是正するために入れています。効率ではなく安定性重視です。筆者環境では、このソースコードで3日間放置できました。

ワット回収ならびにワットショップ購入部分に工夫してあり、日付更新は、任意の月・日付から始められます。「1月1日から始めないと動かない」「31日ある月じゃないと動かない」みたいな煩雑な前準備は不要なはずです。

ソースコード

2020/12/5追記:NintendoSwitch Ver.11.0アップデート対応済(変更点はこちら

/* ★カンムリの雪原 目玉商品自動回収★
柱のWattを回収し、「みんなで挑戦」にカーソルをあわせてから、
ArduinoをSwitchに接続してください。
Homeボタンが何回か押されるような挙動をした後、5秒ほど待機状態に。
5秒間の待機状態がHome画面(Switchのメニュー画面)ならOK。
※違うなら急いで手動でHomeボタンを1回押すこと。
*/

#include
<SwitchControlLibrary.h> #define HOLDTIME (95) #define INTERVAL (105) void PushHome(int delay_time_ms); void PushA(int delay_time_ms); void PushB(int delay_time_ms); void PushX(int delay_time_ms); void PushL(int delay_time_ms); void move_for(char* Direction); // ex: "right" "left" "up" "down" void move_for(char* Direction, int delay_time_ms); void move_for(char* Direction, int hold_time_ms, int delay_time_ms); void setup() { PushHome(1000); PushHome(1000); PushHome(1000); PushHome(1000); PushHome(1000); PushHome(5000); // 5秒待つ PushHome(1000); delay(100); } void loop() { // 開始 みんなで挑戦の直前から。 PushA(4000); // 通信待機中・・・ // Homeボタンを押して設定の画面へ移動 PushHome(1000); // Home画面で「設定」を選ぶ move_for("down"); move_for("right"); move_for("right"); move_for("right"); move_for("right"); move_for("right");
PushA(1500); // 設定画面で「日付と時刻」を開く move_for("down", 1500, (int)INTERVAL/4); // 「↓」長押し move_for("right"); for(int i=0; i<4; i++){ move_for("down", (int)HOLDTIME/2, (int)INTERVAL/2); } PushA(500); // 時間設定(現在の日付と時刻を選ぶ) move_for("down"); move_for("down"); PushA(500); // 時刻設定(日付の部分のみ回していく // 時間の変更 move_for("right"); move_for("right"); move_for("up"); PushA(INTERVAL); PushA(INTERVAL); PushA(INTERVAL); PushA(INTERVAL); PushHome(2000); PushA(1000); // ↑ここまでで設定画面おわり。 // 募集終了処理 PushB(500); // 「募集終了」を選択 PushA(1000); // 「はい」 PushA(4500); // 「はい」 // ★フィールド画面に戻ってきて空を飛んでおっさんに話しかける PushX(700); // Xでメニューを開く PushA(2700); // Aでタウンマップを開く // PushX(150); SwitchControlLibrary().MoveLeftStick((128+70), (128+100)); // 右下(下強め)にカーソル合わせ delay(170); SwitchControlLibrary().MoveLeftStick((128), (128)); // 真ん中に戻す delay(100); // 空を飛ぶ(A) PushA(650); PushA(1700);// 天候によって画面切り替わりの時間がかわるっぽい。1700だと足りない場合あり?要検証 // おっさんに向かって走る SwitchControlLibrary().MoveLeftStick((128+100), (128-115)); // 右上(ちょい上強め)を向く delay(230); SwitchControlLibrary().MoveLeftStick((128), (128)); // 真ん中に戻す delay(100); PushL(400); SwitchControlLibrary().MoveLeftStick((128), (128-128)); // 上にあるく delay(1800); SwitchControlLibrary().MoveLeftStick((128), (128)); // 真ん中に戻す PushA(650); // いらっしゃいませ PushA(650); // 何をご覧になりますか? move_for("d"); PushA(650); // 今日の目玉商品は… or 目玉商品は売り切れです PushA(650); // ●●! 早いもの勝ち お値段 たったの ●●W  or 他に見たいものはありますか? PushA(650); // 買う?  or 普通の商品 PushA(2600); // はい(購入処理入るので遅い)→お目が高い!お買い上げありがとうございます PushB(2600); // ●●は ●● を 手に入れた!(SEなる) ★ここ以降はBにして共通処理 PushB(750); PushB(550); PushB(550); PushB(550); PushB(550); PushB(550); // 余計なB(1日にループした際の共通処理&2個セットのときなど2行に分かれたときの共通処理&きのみ購入時の1行ズレ吸収) // ★フィールド画面に戻ってきて空を飛んでDennに歩く PushX(700); // Xでメニューを開く PushA(2700); // Aでタウンマップを開く // PushX(150); SwitchControlLibrary().MoveLeftStick((128+70), (128+100)); // 右下(下強め)にカーソル合わせ delay(170); SwitchControlLibrary().MoveLeftStick((128), (128)); // 真ん中に戻す delay(100); // 空を飛ぶ(A) PushA(650); PushA(1700); // Dennに向かって走る SwitchControlLibrary().MoveLeftStick((128+28), (128-115)); // 右上(上強め)を向く delay(230); SwitchControlLibrary().MoveLeftStick((128), (128)); // 真ん中に戻す delay(100); PushL(400); SwitchControlLibrary().MoveLeftStick((128), (128-128)); // 上にあるく delay(4300); SwitchControlLibrary().MoveLeftStick((128), (128)); // 真ん中に戻す // ★Denに話しかける(Watt回収) // There's energy pouring out from the den! [Enter] PushA(500); // 1回目は「A」 // You gained 2,000W! [Quit] PushB(2500); // 2回目は「B」(日付が1日に戻った時との共通処理のため遅めに) // [Enter] PushA(1500); // 3回目は「A」(募集画面に入る) } void PushHome(int delay_time_ms){ SwitchControlLibrary().PressButtonHome(); delay(210); SwitchControlLibrary().ReleaseButtonHome(); delay(delay_time_ms); return; } void PushA(int delay_time_ms){ SwitchControlLibrary().PressButtonA(); delay(HOLDTIME); SwitchControlLibrary().ReleaseButtonA(); delay(delay_time_ms); return; } void PushB(int delay_time_ms){ SwitchControlLibrary().PressButtonB(); delay(HOLDTIME); SwitchControlLibrary().ReleaseButtonB(); delay(delay_time_ms); return; } void PushX(int delay_time_ms){ SwitchControlLibrary().PressButtonX(); delay(HOLDTIME); SwitchControlLibrary().ReleaseButtonX(); delay(delay_time_ms); return; } void PushL(int delay_time_ms){ SwitchControlLibrary().PressButtonL(); delay(HOLDTIME); SwitchControlLibrary().ReleaseButtonL(); delay(delay_time_ms); return; } void move_for(char* Direction){ // ex: "right" "left" "up" "down" switch(Direction[0]){ case 'r': case 'R': SwitchControlLibrary().MoveHat(2); // right delay(HOLDTIME); SwitchControlLibrary().MoveHat(8); // center delay(INTERVAL); break; case 'l': case 'L': SwitchControlLibrary().MoveHat(6); // left delay(HOLDTIME); SwitchControlLibrary().MoveHat(8); // center delay(INTERVAL); break; case 'u': case 'U': SwitchControlLibrary().MoveHat(0); // up delay(HOLDTIME); SwitchControlLibrary().MoveHat(8); // center delay(INTERVAL); break; case 'd': case 'D': SwitchControlLibrary().MoveHat(4); // down delay(HOLDTIME); SwitchControlLibrary().MoveHat(8); // center delay(INTERVAL); break; default: break; } } void move_for(char* Direction, int delay_time){ // ex: "right" "left" "up" "down" switch(Direction[0]){ case 'r': case 'R': SwitchControlLibrary().MoveHat(2); // right delay(HOLDTIME); SwitchControlLibrary().MoveHat(8); // center delay(delay_time); break; case 'l': case 'L': SwitchControlLibrary().MoveHat(6); // left delay(HOLDTIME); SwitchControlLibrary().MoveHat(8); // center delay(delay_time); break; case 'u': case 'U': SwitchControlLibrary().MoveHat(0); // up delay(HOLDTIME); SwitchControlLibrary().MoveHat(8); // center delay(delay_time); break; case 'd': case 'D': SwitchControlLibrary().MoveHat(4); // down delay(HOLDTIME); SwitchControlLibrary().MoveHat(8); // center delay(delay_time); break; default: break; } } void move_for(char* Direction, int hold_time_ms, int delay_time_ms){ // ex: "right" "left" "up" "down" switch(Direction[0]){ case 'r': case 'R': SwitchControlLibrary().MoveHat(2); // right delay(hold_time_ms); SwitchControlLibrary().MoveHat(8); // center delay(delay_time_ms); break; case 'l': case 'L': SwitchControlLibrary().MoveHat(6); // left delay(hold_time_ms); SwitchControlLibrary().MoveHat(8); // center delay(delay_time_ms); break; case 'u': case 'U': SwitchControlLibrary().MoveHat(0); // up delay(hold_time_ms); SwitchControlLibrary().MoveHat(8); // center delay(delay_time_ms); break; case 'd': case 'D': SwitchControlLibrary().MoveHat(4); // down delay(hold_time_ms); SwitchControlLibrary().MoveHat(8); // center delay(delay_time_ms); break; default: break; } }

 

f:id:tangential_star:20201205131649g:plain

2020/12/5対応;動作例(NintendoSwitchVer11アプデ対応済)

 

あとがき

剣盾の自動化は、今年の2/2にArduino Leonardoを買って以来、色々と試してきましたが、こんなふうにDLCが出てから更に魅力的になったと思います。

入手方法や個数が実質的に限られていたアイテム・ポケモンがちゃんと後からでも手に入れられるようになることは、改造やセーブデータ改竄など非合法な方法を撲滅する上でも必要だと思います。一方で、きのみ集めやレイド周回、ウッウロボガチャなど、通常プレイでは苦行と言わざるを得ないパートが残っているのもまた事実で、やりこみ要素としてのバランスは難しいのでしょう。

ここは、ゲームに対するプレイスタイルに依るところが大きいので、それぞれがポリシーを持って向き合っていただければと思います。

なお、本記事を始めとするArduinoを使った自動化は、コントローラー操作の代替手段の一つです。いわば、連射コントローラーや、マクロ対応コントローラーと同じカテゴリのものです。すなわち、ポケモン剣盾の仕様の範囲内で完結でき、前述の非合法的な手段(改造やセーブデータ改竄など)を使わない、プレイスタイルの一つとして位置づけられると考えます。

本記事を読んでいただいた皆様にはそれぞれに考え方があるかと存じますが、それぞれのプレイスタイルに応じた使い方を演出いただければ幸いです。 

 

ではではc⌒っ.ω.)っ

 

前の記事:

【Arduino自動化01】Arduino開発環境の導入

次の記事:

【Arduino自動化03】ワット稼ぎ・自動回収