【Arduino自動化11】5番道路で全自動羽集め【DLC不要】

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

今回は、「5番道路」の橋の上でひたすら努力値振りに使える「ハネ」系アイテムの自動回収を行う記事です。

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

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

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

 

概要

本記事では、ランクマバグ状態を利用した、羽系アイテムの自動回収を実装します。
※便宜上「ランクマバグ」と表記しますが、当ブログではランクマッチを使用しません。通信切断・初手降参の推奨もしません。

NintendoSwitch Ver.11.0アップデート対応済です。
★2021/9/20追記:NintendoSwitch Ver.13.0アップデート対応済です。
★2022/10/23追記:NintendoSwitch Ver.15.0アップデート対応済です。

f:id:tangential_star:20210111155633p:plain
f:id:tangential_star:20210111121951g:plain
完全自動で5番道路の育て屋から羽を10枚回収する。1試行あたり約56秒だ


では、目次です。

ソースコードだけほしい方は読み飛ばしてください⇒読み飛ばす

 

羽集めで端数の努力値振りも簡単に

今回の記事で集める「たいりょくのハネ」「ちりょくのハネ」などのいわゆる「ハネ系アイテム」は、 ポケモンの基礎ポイント(努力値)を1増やすことができます。

ポケモンの育成をする上で努力値振りという作業をしますが、極振り(252振り)以外の端数で努力値を扱う場合には、10単位でしか努力値が増やせない各種ドーピングアイテム(タウリンブロムヘキシンなど)では対応できないため、1単位で努力値を増やせる「○○のハネ」は大変重宝します。

f:id:tangential_star:20210111131402p:plain
f:id:tangential_star:20210111131407p:plain
羽系アイテムなら両刀の配分や、素早さ抜き調整で例えばS28振りなど柔軟に対応できる

 

日付変更+マップ切り替えで無限に羽が集まる

「○○のハネ」はドロップアイテムとしてガラル地方の様々な場所に隠しアイテムとして落ちています。

これらの場所で拾える羽系アイテムは、1日経過で「同じ場所」に復活するため、これらの拾えるポイントを事前に覚えておけば、毎日これらのポイントを周回することでコツコツと集めることができます(例えば「5ばんどうろ」の橋の上や「エンジンシティはずれ」の橋の上、ヨロイじまの海上など)。

f:id:tangential_star:20210111122110p:plain
f:id:tangential_star:20210111155817p:plain
羽系アイテムは、橋の上や海の上など、様々な場所に隠しアイテムとして落ちている

条件についてより厳密には、日付の切り替え+マップ切り替え(そらとぶタクシー含む)の条件2つを満たすと復活するため、巣穴が近くにある場合には、いわゆる「日付バグ」との併用で無限に回収し続けることができます。

手動でやる場合は、ヨロイじまの「ワークアウトのうみ」の岩(一礼野原の駅を出て目の前に見える海上の岩)がおすすめです。岩場を1周するだけで羽が集められ、ポケモンの巣穴もあるため日付変更ペナルティの回避もできます。前述の通り羽の落ちている場所は常に一定なので、場所を覚えれば効率的に回収できます。

f:id:tangential_star:20210111151804g:plain

日付変更+そらとぶタクシーで同じ場所に羽系アイテムが復活する。無限に回収可能だ

 

ランクマバグで効率的な日付変更が可能に

前回の記事でも紹介しましたが、ランクマッチ1戦の直後や、YY通信のカジュアルバトルで機内モードに変更した際に、日付変更ペナルティが解除されるバグ(通称:ランクマバグ)があります。これを活用することで、短い時間で場所を選ばず効率的に日付変更ができるようになります。

なお、ランクマッチを使用しない方法での「ランクマバグ状態」移行は下記手順でできます。画像は過去記事の使いまわしです。あしからず。

  1. YY通信で「通信対戦」を行う(ローカル通信)
  2. もう一台のSwitch・剣盾を使ってその通信対戦に応じる
  3. 戦闘が始まったらHomeボタン長押し→「機内モード」をONに変更する
  4. エラーが発生するのでそれを閉じて、「にげる」選択
  5. フィールド画面に戻ってくる(ランクマバグ状態に移行完了)
f:id:tangential_star:20201122213022g:plain
f:id:tangential_star:20201122213037g:plain
YY通信でローカル対戦を募る。バトルが始まったらHomeボタン長押しで機内モード
f:id:tangential_star:20201122213148g:plain
f:id:tangential_star:20201122213205g:plain
エラーが出たら「にげる」で対戦終了。この状態で時刻変更すると一瞬画面が暗転する

 

羽集めの自動化は「5番道路」が最適

前述の通り、羽を集めるには、大きく「日付変更+マップ切り替え」と「羽を拾う動作」の2つを繰り返す必要があります。自動化する上で、この「日付変更+マップ切り替え」が結構な曲者です。

上記でおすすめした「ワークアウトのうみ」では、近くに「そらとぶタクシー」で移動できるポイントがあるため都合が良いのですが、海上に位置するため、道中、ポケモンに遭遇する可能性が高いです。具体的には、サメハダーに襲われたりタマンタが跳ねてきたり、他にもキャモメが空を飛んでいたりなどで不意な戦闘が発生するため、一定の動きでプログラミングしてしまうと、ループが途切れてしまいます。

そこで、羽集めの自動化には、「そらとぶタクシー」で近くにアクセスできて、ポケモンとの遭遇がほぼ発生しない「5番道路」を活用します。

「そらとぶタクシー」でこれらが選択できるよう、あらかじめ「5番道路」のキャンプ場と「預り屋さん」に足を運んでおきましょう。

f:id:tangential_star:20210111161229p:plain
f:id:tangential_star:20210111161238p:plain
「キャンプ場」「預り屋さん」を訪れておけば、そらとぶタクシーで移動できるようになる

ちなみに、5番道路を活用することで「DLCが不要」 という副次的なメリットもあります。剣盾ビギナーの方にはそういう意味でもおすすめと言えます。

 

自動化プログラムの使い方

今回のプログラム(ソースコードは後述の通り)は、上記の通り5番道路で羽集めを自動で行うものです。できるだけ効率化を図るため、下記条件で動くようにしました。

この状態でArduinoを挿すと、「日付変更⇒そらとぶタクシーで預り屋に移動⇒10枚の羽を拾う」をひたすらループしてくれます。

  1. ランクマバグ状態であること
  2. そらとぶタクシーで「5ばんどうろ」「あずかりやさん」が選択可能なこと
  3. 「話の速さ」が「はやい」になっていること
  4. Xボタンでメニューを開いた時に「タウンマップ」にカーソルが合うこと
  5. 5番道路で自転車に乗っていること
f:id:tangential_star:20210111162925p:plain
f:id:tangential_star:20210111162931p:plain
Xボタンでタウンマップがカーソルにあうこと・自転車に乗っていることを確認しよう

 

ソースコード

ランクマバグ状態・5番道路で自転車に乗ってArduinoを挿すだけの簡単仕様です。

2021/9/20追記:NintendoSwitch Ver.13.0アップデート対応済(詳細はこちら
2022/10/23追記:NintendoSwitch Ver.15.0アップデート対応済
※Switch Liteをご利用の方は「#define SWITCH_VER (15)」を「#define SWITCH_VER (12)」に書き換えてご利用ください。ただし、未検証です。

/* ★NintendoSwitchのファームウェアVer13に対応版(2021/9/20)★
 * ★NintendoSwitchのファームウェアVer15に対応版(2022/10/23)★ 
 *  
 *  羽集め@5番道路(育て屋)c⌒っ.ω.)っ 
 *  【ランクマバグ状態で使うこと!】
 *  Xボタンで「タウンマップ」にカーソルが合っていること、自転車に乗っていること
 *  
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/

#include <SwitchControlLibrary.h>

#define HOLDTIME (95) // 1回のキー入力の長押し時間

#define SWITCH_VER (15)
// 2021/9/20、SwitchのVer13アップデートに伴う追記
// Switchのバージョンを整数で入力(例:12.2.1⇒12, 13.0.0⇒13, 15.0.0→15)

void PushRL(int delay_time_ms);
int PushKey(char* keyname, int holdtime, int delaytime);
void TiltLeftStick(int direction_deg, double power, int holdtime, int delaytime);

void setup() {
  // コントローラーとして認識されるためにRLを7回ほどカチャカチャする
  for(int i=0;i<7;i++)PushRL(300);
  delay(1000);
}


void loop() {

  // ★ランクマバグ状態時に日付を回すやつ
  NextDayInCheatMode();

  // ★5番道路の預かり屋に空を飛ぶ。
  PushKey("X", HOLDTIME, 700);  // メニューを開き
  PushKey("A", HOLDTIME, 2700); // タウンマップを開く
  TiltLeftStick( 45, 1.0, 50, 170); // 5番道路から預かり屋にカーソル合わせ
  // 空を飛ぶ(A)
  PushKey("A", HOLDTIME, 650);  // 5番道路まで そらとぶタクシーで 移動しますか?
  PushKey("A", HOLDTIME, 3500); // はい


  // ★羽集め(育て屋前からスタート)
  // 1個め
  TiltLeftStick( 93, 1.0, 3000, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 2個め
  TiltLeftStick( 72, 1.0, 1950, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 3個め
  TiltLeftStick( 103, 1.0, 1460, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 4個め
  TiltLeftStick( 105, 1.0, 1250, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 5個め
  TiltLeftStick( 59, 1.0, 750, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);


  // 6個め
  TiltLeftStick( 100, 1.0, 2690, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);


  // 7個めnew
  TiltLeftStick( 63, 1.0, 1620, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 8個めnew
  TiltLeftStick( 120, 1.0, 1950, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 9個めnew
  TiltLeftStick( 69, 1.0, 1800, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  // 10個めnew
  TiltLeftStick( 125, 1.0, 900, 500);
  PushKey("A", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 100);
  PushKey("B", HOLDTIME, 300);

  delay(1000);

}

void PushRL(int delay_time_ms){
  SwitchControlLibrary().PressButtonR();
  SwitchControlLibrary().PressButtonL();
  delay(HOLDTIME);
  SwitchControlLibrary().ReleaseButtonR();
  SwitchControlLibrary().ReleaseButtonL();
  delay(delay_time_ms);
  return;
}
int PushKey(char* keyname, int holdtime, int delaytime){
  // ホームボタン・方向キーはRight, Left, Up, Down, Homeなど2文字以上で入力。
  // その他ボタン入力は1文字(A,B,X,Y,R,L,+,-)ZR・ZLにも対応
  // 同時押しは非対応
  
  if(strlen(keyname)==1){
    switch(keyname[0]){
      case 'A': case 'a': // A
        SwitchControlLibrary().PressButtonA(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonA(); delay(delaytime);
      break;
      case 'B': case 'b': // B
        SwitchControlLibrary().PressButtonB(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonB(); delay(delaytime);
      break;
      case 'X': case 'x': // X
        SwitchControlLibrary().PressButtonX(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonX(); delay(delaytime);
      break;
      case 'Y': case 'y': // Y
        SwitchControlLibrary().PressButtonY(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonY(); delay(delaytime);
      break;
      case 'L': case 'l': // L
        SwitchControlLibrary().PressButtonL(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonL(); delay(delaytime);
      break;
      case 'R': case 'r': // R
        SwitchControlLibrary().PressButtonR(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonR(); delay(delaytime);
      break;
      case 'H': case 'h': // Home
        SwitchControlLibrary().PressButtonHome(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonHome(); delay(delaytime);
      break;
      case '+': case 'p': case 'P': // Plus
        SwitchControlLibrary().PressButtonPlus(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonPlus(); delay(delaytime);
      break;
      case '-': case 'm': case 'M': // Minus
        SwitchControlLibrary().PressButtonMinus(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonMinus(); delay(delaytime);
      break;
      default:
      break;
    }
  }else if(strlen(keyname)>=2){
    switch(keyname[0]){
      case 'z': case 'Z': // ZR/ZL
        if(keyname[1]=='R'||keyname[1]=='r'){
          SwitchControlLibrary().PressButtonZR(); delay(holdtime);
          if(holdtime>0)SwitchControlLibrary().ReleaseButtonZR(); delay(delaytime);
        }
        if(keyname[1]=='L'||keyname[1]=='l'){
          SwitchControlLibrary().PressButtonZL(); delay(holdtime);
          if(holdtime>0)SwitchControlLibrary().ReleaseButtonZL(); delay(delaytime);
        }
      break;
      case 'r': case 'R': // right
        SwitchControlLibrary().MoveHat(2); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'l': case 'L': // left
        SwitchControlLibrary().MoveHat(6); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'u': case 'U': // up
        SwitchControlLibrary().MoveHat(0); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'd': case 'D': // down
        SwitchControlLibrary().MoveHat(4); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'H': case 'h': // Home
        SwitchControlLibrary().PressButtonHome(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonHome(); delay(delaytime);
      default:
      break;  
    }
  }else{
    return -1;
  }
  return strlen(keyname);
}
void TiltLeftStick(int direction_deg, double power, int holdtime, int delaytime){
  double rad = (double)direction_deg*PI/180.0; // 弧度法(ラジアン)変換
  int x, y;
  x = (double)128*sin(rad)*power;
  y = (double)-128*cos(rad)*power;
  x += 128; y += 128;
  if(x >= 255) x=255; if(x <= 0) x=0;
  if(y >= 255) y=255; if(y <= 0) y=0;

  SwitchControlLibrary().MoveLeftStick(x,y);
  if(holdtime> 0){ // holdtime=0のときは押しっぱなし。
    delay(holdtime);
    SwitchControlLibrary().MoveLeftStick(128,128); // 傾きを直す
  }
  if(delaytime>0) delay(delaytime);
  return;
}

void NextDayInCheatMode(){
  // ★日付変更
  // Homeボタンを押して設定の画面へ移動
  PushKey("Home", HOLDTIME, 1000);

  // Home画面で「設定」を選ぶ
  PushKey("down", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 1500);

  // 設定画面で「日付と時刻」を開く
  PushKey("down", 1500, 105); // 1.5秒「↓」長押し
  PushKey("right", HOLDTIME, 105); 


#if (SWITCH_VER >= 15)
  // Switchのver15アプデでBluetoothオーディオの設定が変わった?ので、微修正。
  // ※本体のバッテリー残量(%)にずれるようになったので。
  PushKey("down", 750, 55);
#elif (SWITCH_VER >= 13)
  // Switchのアプデで「ドックの更新」項目が追加されたので
  // 日付変更をする場合には「↓」キー長押しが必要に。
  PushKey("down", 780, 55); 
#else
  // ver12まで(13アプデ前)は、1ページに収まるのでそのままでOK
  for(int i=0; i<4; i++){
    PushKey("down", HOLDTIME, 55);
  }
#endif
  
  PushKey("A", HOLDTIME, 500);

  // 時間設定(現在の日付と時刻を選ぶ)
  PushKey("down", HOLDTIME, 105);
  PushKey("down", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 500);

  // 時刻設定(日付の部分のみ回していく
  // 時間の変更
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("up",    HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("Home", HOLDTIME, 2000);
  PushKey("A", HOLDTIME, 1000);
  return;
}

日付変更処理部分を関数にしたので、loop()の中が多少読みやすくなりました。

 

あとがき

今回の記事では、対応する努力値を「1」だけ増やすことができる、いわゆる「羽系アイテム」の自動回収について紹介しました。

前回の「【Arduino自動化10】きのみ・ぼんぐり自動回収」を使えば、対応する努力値を「10」下げられるので、これらを併用することで、努力値調整がかなり簡便に行えるようになりました。

さらに、技レコードやミント、各種カプセルなどは、それぞれ「ワット(【Arduino自動化03】ワット稼ぎ・自動回収)」、「バトルポイント(【Arduino自動化08】バトルタワー自動周回【BP稼ぎ】)」や「マックスこうせき(【Arduino自動化06】完全放置「マックスこうせき」集め)」で引き換えられるため、今までのものも含めて育成環境の整備がしっかりとできるようになってきたかな、と感じています。

 

アイテムなどの回収作業をArduinoで自動化でき、さらに副次的に、育成論の検討や対戦に時間を割けるようになるのは、まさに一石二鳥なのではないかと思います。皆様のプレイスタイルに応じてうまくご活用いただければ幸いです。

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

 

前記事:【Arduino自動化10】きのみ・ぼんぐり自動回収

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

次の記事:【Arduino自動化12】全自動タマゴ受け取り&孵化