【マイコン】ポケモンSVでタマゴの受け取りを自動化するコード!

ポケモンSVのピクニックでタマゴを自動で受け取っていくコードを作成しました。

孵化は行わないため孵化をしたい場合は以下の記事のコードをご利用ください。

関連記事

ポケモンSVの孵化自動化コードは以前にも作ったのですが、改良版ができたため公開します。 [sitecard subtitle=関連記事 url=https://pokemonit.com/sv-hatch-eggs-in-the-bo[…]

この記事をご覧になる前に

マイコンをまだ導入していない方は、以下の記事を参考に導入してみてください。このブログでは、Arduino Leonardoというマイコンを使っているため、異なる種類のものではうまく動かない可能性があります。

関連記事

マイコンと呼ばれるものをご存知でしょうか? Switchに接続するだけで様々な作業を "自動" で行ってくれるというものです。 導入すれば作業が楽になるだけでなく、寝てる間に色々稼ぐこともできちゃう便利なアイテムなわけですね! […]

マイコンを使って
ポケモン剣盾
自動化しよう!
【導入編】

今回のコードは「NintendoSwitchControlLibrary」を使用しています。まだダウンロードされていない方は、解凍したフォルダを『Arduino』フォルダの中にある『libraries』フォルダの中にコピーするか、ライブラリマネージャよりインストールしてください。

コード利用における注意点!

今回のコードは完全な動作を約束するものではありません。

ポケモンSV特有の処理落ちや入力漏れにはなるべく対応できるよう尽力していますが、それでも止まってしまう可能性は否定できません。

また、今回のコードに限らずなのですが、エラー落ちは想定できておりません。ポケモンSVは特にエラーが発生しやすいという話を聞いているので、そういった観点でもある程度見張れる状態での使用をおすすめいたします。

エラー落ちした場合、ホーム画面でどんな操作をするかわかりません。e-shopで勝手にソフトを購入したり、ポケモンのセーブデータの削除、Switchの初期化などを行ってしまう可能性も0ではないです。放置は自己責任でお願いいたします。

ソースコード

実際に書いたコードはこちらになります。

2022年12月29日追記:コードの安定感を少し向上させました。具体的には「タマゴパワー」が稀にLv1になってしまう問題の解消、及び、バスケット方向への振り向きに稀に失敗することがあったため2回に分けて入力するように変更しています。
2023年3月28日追記:Ver1.2.0用に更新しました。Ver1.2.0以降若干不安定になっていたのですが、今回の対応により安定感を向上させています。

具体的には「マップを開いた際にカーソルが動かせず空を飛ぶに失敗することによるループ破綻」、「カーソル移動の入力漏れにより別のサンドイッチを作成しだして、完成までもっていけないことによるループ破綻」の2つを事象を改善し、その他の安定感も全体的に向上させています。

また、サンドイッチを作る時間を短縮することにも成功しています。

2023年9月14日追記:Ver2.0.1用に更新しました。変更点は最初にマイコンを認識させるためのコマンドがLボタン押し込みになっていましたが、アップデートによりこちらのボタンに口笛が割り当てられるようになったためBボタンに変更しました。それ以外の中身は変更を加えておりません。小さな修正のため、旧コードは残さずそのまま上書きして公開しています。

2023年9月20日追記:Ver2.0.1用に再度更新しました。また9月14日分の更新分に関してはVer1.3.2用として残しています。Ver2.0.1で空を飛ぶに時間がかかるようになってしまっていたため、それを考慮して空を飛ぶの時間を長めに設けるようにしました。(少々過剰な気もするのですが、10秒程度いじったところで大きく効率に影響するものでもないので許容範囲かと思います)また、それとは別に極稀にテーブルへの移動に失敗することがある問題が見受けられたため、移動の間隔を少し伸ばしました。

Ver.1.1.0用はこちら
/**
 * タマゴをひたすら受け取るスケッチ
 *
 * ※このスケッチはタマゴの受け取りのみでタマゴ孵化は行いません。
 * ボックスがタマゴで埋め尽くされても泣かないように!
 *
 * 初期条件は以下の通り
 * 1.「プラトタウン」に空を飛ぶを行い、その着地点にいること
 * 2.マップを開いた際に北が上にくるようになっていること
 * 3.ピクニック、及び、サンドウィッチのチュートリアルを終えていること
 * 4.「17 スーパーピーナッツバターサンド」のレシピを開放済であること
 * 5.「17 スーパーピーナッツバターサンド」の素材(バナナスライス・ピーナッツバター・バター・シルバーピック(金額気にしないなら他のピックでもOK))をループ回数分用意してあること
 * 6.手持ちは親となるポケモン2匹のみになるようにしてあること
 * 7.ボックスに十分な空きを確保してあること(相性にもよりますが、1ループあたり50個前後のタマゴを受け取ります)
 * 8.オフライン状態であること
 * 9.無線のコントローラーが接続されていないこと
 * 10.「設定」から「話の速さ」を「はやい」に、「わざ覚えスキップ」を「する」にしておくこと
 */

////////////////// libraries ////////////////////////////////

// ライブラリを読み込むためのコード
#include <NintendoSwitchControlLibrary.h>

////////////////// options ////////////////////////////////

// 何回ループさせるか?
const int LOOP_COUNT = 2;

// 「17 スーパーピーナッツバターサンド」にカーソルを合わせるのに、下キーと右キーの入力回数を指定する
const int SANDWICH_DOWN_COUNT = 8;
const int SANDWICH_RIGHT_COUNT = 0;

////////////////// constant parameters ////////////////////////////////

// 具材の個数
const int INGREDIENT_COUNT = 3;

// タマゴパワーの有効時間(分)
const int EGG_POWER_MINUTES = 30;

// ボタン押下時の標準的な待機時間
const int BASE_PUSH_BUTTON_TIME_MS = 1000;

// カーソル操作時の標準的な待機時間
const int BASE_CURSOR_OPERATION_TIME_MS = 200;

// スティックによる移動の待機時間
const int STICK_OPERATION_TILT_TIME_MS = 100;
const int STICK_OPERATION_DELAY_TIME_MS = 500;

// メニューの開閉にかかる待機時間
const int MENU_OPEN_OR_CLOSE_TIME_MS = 1500;

////////////////// variable parameters ////////////////////////////////

// ループをカウントするための変数
int count = 0;

////////////////// methods ////////////////////////////////

/**
 * Switchをスリープさせる
 *
 * @return void
 */
void sleepGame() {
    holdButton(Button::HOME, 1500);
    pushButton(Button::A, BASE_PUSH_BUTTON_TIME_MS);
}

/**
 * 「プラトタウン」から「コサジの小道灯台」に空を飛ぶで移動する(同じ場所に空を飛ぼうとするとAボタンが反応しないことがあるため、「プラトタウン」からのスタートとした)
 *
 * @return void
 */
void goFromLosPlatosToLighthouse() {
    pushButton(Button::Y, 3000);
    tiltLeftStick(204, Stick::MAX, 380);
    delay(1000);
    pushButton(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    pushButton(Button::A, 2000);
    pushButton(Button::A, 10000);
}

/**
 * 「コサジの小道灯台」から「プラトタウン」に空を飛ぶで移動する
 *
 * @return void
 */
void goFromLighthouseToLosPlatos() {
    pushButton(Button::Y, 3000);
    tiltLeftStick(52, Stick::MIN, 380);
    delay(1000);
    pushButton(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    pushButton(Button::A, 2000);
    pushButton(Button::A, 10000);
}

/**
 * ピクニックを開始する
 *
 * @param bool is_init
 * @return void
 */
void startPicnic(bool is_init = false) {
    pushButton(Button::L, 2000);
    pushButton(Button::X, MENU_OPEN_OR_CLOSE_TIME_MS);

    // 初回のみカーソルを合わせる
    if (is_init) {
        holdHat(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
        holdHat(Hat::UP, 2000);
        pushHat(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
        pushHat(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
    }

    pushButton(Button::A, 10000);
}

/**
 * ピクニックを終了する
 *
 * @return void
 */
void stopPicnic() {
    pushButton(Button::Y, 1500);
    pushButton(Button::A, 4000);
    pushButton(Button::B, 500, 6);
}

/**
 * スティックで1マスずつ移動する
 *
 * @param uint8_t x
 * @param uint8_t y
 * @return void
 */
void moveWithStick(uint8_t x, uint8_t y) {
    tiltLeftStick(x, y, 200);
    delay(500);
}

/**
 * 初期位置(テーブル)に向かう
 *
 * @return void
 */
void moveToTablePosition() {
    // コライドン(ミライドン)をボールに戻す
    pushButton(Button::PLUS, 2000);
    // しゃがんで移動することで位置を安定させる
    pushButton(Button::B, BASE_PUSH_BUTTON_TIME_MS);
    // 右3
    moveWithStick(Stick::MAX, Stick::NEUTRAL);
    moveWithStick(Stick::MAX, Stick::NEUTRAL);
    moveWithStick(Stick::MAX, Stick::NEUTRAL);
    // 上5
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    // 左1
    moveWithStick(Stick::MIN, Stick::NEUTRAL);
    // 左下2
    moveWithStick(Stick::MIN, Stick::MAX);
    moveWithStick(Stick::MIN, Stick::MAX);
}

/**
 * サンドウィッチを作る
 *
 * @return void
 */
void makeSandwich() {
    // テーブルの方向を向く
    tiltLeftStick(Stick::NEUTRAL, Stick::MAX, STICK_OPERATION_TILT_TIME_MS);
    delay(STICK_OPERATION_DELAY_TIME_MS);

    // 料理をする
    pushButton(Button::A, 1500);
    pushButton(Button::A, 5000);

    // 「17 スーパーピーナッツバターサンド」を選択
    pushHat(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS, SANDWICH_DOWN_COUNT);
    if (SANDWICH_RIGHT_COUNT == 1) {
        pushHat(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
    }

    pushButton(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    pushButton(Button::A, 9000);

    // 具材を乗せる
    for (int i = 0; i < INGREDIENT_COUNT; i++) {
        tiltLeftStick(Stick::NEUTRAL, Stick::MIN, i == 0 ? 640 : 540);
        SwitchControlLibrary().moveLeftStick(Stick::NEUTRAL, Stick::MAX);
        SwitchControlLibrary().sendReport();
        SwitchControlLibrary().pressButton(Button::A);
        SwitchControlLibrary().sendReport();
        delay(540);
        SwitchControlLibrary().releaseButton(Button::A);
        SwitchControlLibrary().sendReport();
        SwitchControlLibrary().moveLeftStick(Stick::NEUTRAL, Stick::NEUTRAL);
        SwitchControlLibrary().sendReport();
        delay(500);
    }
    delay(500);

    // 上のパンはいらない
    tiltLeftStick(Stick::MAX, Stick::MIN, 2000);

    // あとはA連打で食事を終える
    pushButton(Button::A, 300, 40);
    delay(20000);
    pushButton(Button::A, 2500);
}

/**
 * バスケットの確認(処理時間がちょうど1分になるよう調整)
 *
 * @return void
 */
void checkBasket() {
    pushButton(Button::A, 1000, 2);  // 2300ms
    pushButton(Button::B, 500, 96);  // 57700ms
}

////////////////// execute ////////////////////////////////

void setup() {
    // Switchがマイコンを認識するまでは信号を受け付けないため、適当な処理をさせておく
    pushButton(Button::LCLICK, 500, 5);
}

void loop() {
    // 指定のループ回数を終えたあと、スリープする
    if (count == LOOP_COUNT) {
        sleepGame();
        exit(0);
    }

    // 「コサジの小道灯台」に空を飛ぶ
    goFromLosPlatosToLighthouse();

    // ピクニック開始
    startPicnic(count == 0);
    moveToTablePosition();

    // サンドウィッチ作成
    makeSandwich();

    // 最初の確認まではバスケットの方向を向いて待機(1分)
    tiltLeftStick(Stick::MIN, Stick::NEUTRAL, 100);
    delay(29600);
    tiltLeftStick(Stick::MIN, Stick::NEUTRAL, 100);
    delay(60000 - 2600 - 30000);

    // タマゴパワーの有効時間が続く限り、1分毎に受け取りを繰り返す(iを1から始めているのは初回分は待機済のため)
    for (int i = 1; i < EGG_POWER_MINUTES; i++) {
        checkBasket();
    }

    // ピクニック終了
    stopPicnic();

    // 「プラトタウン」に戻る
    goFromLighthouseToLosPlatos();

    count++;
}
Ver.1.3.2用はこちら
/**
 * タマゴをひたすら受け取るスケッチ
 *
 * ※このスケッチはタマゴの受け取りのみでタマゴ孵化は行いません。
 * ボックスがタマゴで埋め尽くされても泣かないように!
 *
 * 初期条件は以下の通り
 * 1.「プラトタウン」に空を飛ぶを行い、その着地点にいること
 * 2.マップを開いた際に北が上にくるようになっていること
 * 3.ピクニック、及び、サンドウィッチのチュートリアルを終えていること
 * 4.「17 スーパーピーナッツバターサンド」のレシピを開放済であること
 * 5.「17 スーパーピーナッツバターサンド」の素材(バナナスライス・ピーナッツバター・バター・シルバーピック(金額気にしないなら他のピックでもOK))をループ回数分用意してあること
 * 6.手持ちは親となるポケモン2匹のみになるようにしてあること
 * 7.ボックスに十分な空きを確保してあること(相性にもよりますが、1ループあたり50個前後のタマゴを受け取ります)
 * 8.オフライン状態であること
 * 9.本体に装着された状態のジョイコン以外にコントローラーが接続されていないこと
 * 10.「設定」から「話の速さ」を「はやい」に、「わざ覚えスキップ」を「する」にしておくこと
 */

////////////////// libraries ////////////////////////////////

// ライブラリを読み込むためのコード
#include <NintendoSwitchControlLibrary.h>

////////////////// options ////////////////////////////////

// 何回ループさせるか?
const int LOOP_COUNT = 2;

// 「17 スーパーピーナッツバターサンド」にカーソルを合わせるのに、下キーと右キーの入力回数を指定する
const int SANDWICH_DOWN_COUNT = 8;
const int SANDWICH_RIGHT_COUNT = 0;

////////////////// constant parameters ////////////////////////////////

// 具材の個数
const int INGREDIENT_COUNT = 3;

// タマゴパワーの有効時間(分)
const int EGG_POWER_MINUTES = 30;

// ボタン押下時の標準的な待機時間
const int BASE_PUSH_BUTTON_TIME_MS = 1200;

// カーソル操作時の標準的な待機時間
const int BASE_CURSOR_OPERATION_TIME_MS = 150;

// スティックによる移動の待機時間
const int STICK_OPERATION_TILT_TIME_MS = 100;
const int STICK_OPERATION_DELAY_TIME_MS = 500;

// メニューの開閉にかかる待機時間
const int MENU_OPEN_OR_CLOSE_TIME_MS = 1500;

// Custom版のpushHatにおける押下時間
const int CUSTOM_INPUT_TIME_MS = 120;

// Custom版のholdButtonにおける押下後の追加の待機時間
const int HOLD_BUTTON_ADD_INTERVAL_TIME_MS = 100;

////////////////// variable parameters ////////////////////////////////

// ループをカウントするための変数
int count = 0;

////////////////// methods ////////////////////////////////

/**
 * pushHatのカスタム版(ボックス操作時にコマンド漏れが起きることを抑制するために用意)
 *
 * @param uint8_t       hat        押す十字キーのボタン
 * @param unsigned long delay_time ボタンを押した後の待ち時間(1秒 = 1000)
 * @param unsigned int  loop       ボタンを押す回数(省略可、デフォルトは1)
 * @return void
 */
void pushHatCustom(uint8_t hat, unsigned long delay_time, unsigned int loop = 1) {
    for (unsigned int i = 0; i < loop; i++) {
        SwitchControlLibrary().pressHatButton(hat);
        SwitchControlLibrary().sendReport();
        delay(CUSTOM_INPUT_TIME_MS);
        SwitchControlLibrary().releaseHatButton();
        SwitchControlLibrary().sendReport();
        delay(delay_time);
    }
    delay(CUSTOM_INPUT_TIME_MS);
}

/**
 * holdButtonのカスタム版(hold後の待機時間を一律で伸ばす目的で用意)
 */
void holdButtonCustom(uint16_t button, unsigned long hold_time) {
    holdButton(button, hold_time);
    delay(HOLD_BUTTON_ADD_INTERVAL_TIME_MS);
}

/**
 * Switchをスリープさせる
 *
 * @return void
 */
void sleepGame() {
    holdButton(Button::HOME, 1500);
    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
}

/**
 * 「プラトタウン」から「コサジの小道灯台」に空を飛ぶで移動する(同じ場所に空を飛ぼうとするとAボタンが反応しないことがあるため、「プラトタウン」からのスタートとした)
 *
 * @return void
 */
void goFromLosPlatosToLighthouse() {
    holdButtonCustom(Button::Y, 1000);
    delay(4000);
    tiltLeftStick(204, Stick::MAX, 380);
    delay(1000);
    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    holdButtonCustom(Button::A, 2000);
    holdButtonCustom(Button::A, 2000);
    delay(8000);
}

/**
 * 「コサジの小道灯台」から「プラトタウン」に空を飛ぶで移動する
 *
 * @return void
 */
void goFromLighthouseToLosPlatos() {
    holdButtonCustom(Button::Y, 1000);
    delay(4000);
    tiltLeftStick(52, Stick::MIN, 380);
    delay(1000);
    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    holdButtonCustom(Button::A, 2000);
    holdButtonCustom(Button::A, 2000);
    delay(8000);
}

/**
 * ピクニックを開始する
 *
 * @param bool is_init
 * @return void
 */
void startPicnic(bool is_init = false) {
    holdButtonCustom(Button::L, 2000);
    holdButtonCustom(Button::X, MENU_OPEN_OR_CLOSE_TIME_MS);

    // 初回のみカーソルを合わせる
    if (is_init) {
        holdHat(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
        holdHat(Hat::UP, 2000);
        pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
        pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
    }

    holdButtonCustom(Button::A, 2000);
    delay(8000);
}

/**
 * ピクニックを終了する
 *
 * @return void
 */
void stopPicnic() {
    holdButtonCustom(Button::Y, 1500);
    holdButtonCustom(Button::A, 2000);
    delay(2000);
    pushButton(Button::B, 500, 6);
}

/**
 * スティックで1マスずつ移動する
 *
 * @param uint8_t x
 * @param uint8_t y
 * @return void
 */
void moveWithStick(uint8_t x, uint8_t y) {
    tiltLeftStick(x, y, 200);
    delay(500);
}

/**
 * 初期位置(テーブル)に向かう
 *
 * @return void
 */
void moveToTablePosition() {
    // コライドン(ミライドン)をボールに戻す
    holdButtonCustom(Button::PLUS, 2000);
    // しゃがんで移動することで位置を安定させる
    holdButtonCustom(Button::B, BASE_PUSH_BUTTON_TIME_MS);
    // 右3
    moveWithStick(Stick::MAX, Stick::NEUTRAL);
    moveWithStick(Stick::MAX, Stick::NEUTRAL);
    moveWithStick(Stick::MAX, Stick::NEUTRAL);
    // 上5
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    // 左1
    moveWithStick(Stick::MIN, Stick::NEUTRAL);
    // 左下2
    moveWithStick(Stick::MIN, Stick::MAX);
    moveWithStick(Stick::MIN, Stick::MAX);
}

/**
 * サンドウィッチを作る
 *
 * @return void
 */
void makeSandwich() {
    // テーブルの方向を向く
    tiltLeftStick(Stick::NEUTRAL, Stick::MAX, STICK_OPERATION_TILT_TIME_MS);
    delay(STICK_OPERATION_DELAY_TIME_MS);

    // 料理をする
    holdButtonCustom(Button::A, 1500);
    holdButtonCustom(Button::A, 2000);
    delay(3000);

    // 「17 スーパーピーナッツバターサンド」を選択
    pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS, SANDWICH_DOWN_COUNT);
    if (SANDWICH_RIGHT_COUNT == 1) {
        pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
    }

    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    holdButtonCustom(Button::A, 2000);
    delay(7000);

    // 具材を乗せる
    for (int i = 0; i < INGREDIENT_COUNT; i++) {
        tiltLeftStick(Stick::NEUTRAL, Stick::MIN, i == 0 ? 640 : 540);
        SwitchControlLibrary().moveLeftStick(Stick::NEUTRAL, Stick::MAX);
        SwitchControlLibrary().sendReport();
        SwitchControlLibrary().pressButton(Button::A);
        SwitchControlLibrary().sendReport();
        delay(540);
        SwitchControlLibrary().releaseButton(Button::A);
        SwitchControlLibrary().sendReport();
        SwitchControlLibrary().moveLeftStick(Stick::NEUTRAL, Stick::NEUTRAL);
        SwitchControlLibrary().sendReport();
        delay(500);
    }
    delay(500);

    // 上のパンはいらない
    tiltLeftStick(Stick::MAX, 55, 1200);

    // あとはA連打で食事を終える
    pushButton(Button::A, 300, 40);
    delay(20000);
    holdButtonCustom(Button::A, 2000);
    delay(500);
}

/**
 * バスケットの確認(処理時間がちょうど1分になるよう調整)
 *
 * @return void
 */
void checkBasket() {
    pushButton(Button::A, 1000, 2);  // 2300ms
    pushButton(Button::B, 500, 96);  // 57700ms
}

////////////////// execute ////////////////////////////////

void setup() {
    // Switchがマイコンを認識するまでは信号を受け付けないため、適当な処理をさせておく
    pushButton(Button::B, 500, 8);
}

void loop() {
    // 指定のループ回数を終えたあと、スリープする
    if (count == LOOP_COUNT) {
        sleepGame();
        exit(0);
    }

    // 「コサジの小道灯台」に空を飛ぶ
    goFromLosPlatosToLighthouse();

    // ピクニック開始
    startPicnic(count == 0);
    moveToTablePosition();

    // サンドウィッチ作成
    makeSandwich();

    // 最初の確認まではバスケットの方向を向いて待機(1分)
    tiltLeftStick(Stick::MIN, Stick::NEUTRAL, 100);
    delay(29600);
    tiltLeftStick(Stick::MIN, Stick::NEUTRAL, 100);
    delay(60000 - 2600 - 30000);

    // タマゴパワーの有効時間が続く限り、1分毎に受け取りを繰り返す(iを1から始めているのは初回分は待機済のため)
    for (int i = 1; i < EGG_POWER_MINUTES; i++) {
        checkBasket();
    }

    // ピクニック終了
    stopPicnic();

    // 「プラトタウン」に戻る
    goFromLighthouseToLosPlatos();

    count++;
}
/**
 * タマゴをひたすら受け取るスケッチ
 *
 * ※このスケッチはタマゴの受け取りのみでタマゴ孵化は行いません。
 * ボックスがタマゴで埋め尽くされても泣かないように!
 *
 * 初期条件は以下の通り
 * 1.「プラトタウン」に空を飛ぶを行い、その着地点にいること
 * 2.マップを開いた際に北が上にくるようになっていること
 * 3.ピクニック、及び、サンドウィッチのチュートリアルを終えていること
 * 4.「17 スーパーピーナッツバターサンド」のレシピを開放済であること
 * 5.「17 スーパーピーナッツバターサンド」の素材(バナナスライス・ピーナッツバター・バター・シルバーピック(金額気にしないなら他のピックでもOK))をループ回数分用意してあること
 * 6.手持ちは親となるポケモン2匹のみになるようにしてあること
 * 7.ボックスに十分な空きを確保してあること(相性にもよりますが、1ループあたり50個前後のタマゴを受け取ります)
 * 8.オフライン状態であること
 * 9.本体に装着された状態のジョイコン以外にコントローラーが接続されていないこと
 * 10.「設定」から「話の速さ」を「はやい」に、「わざ覚えスキップ」を「する」にしておくこと
 */

////////////////// libraries ////////////////////////////////

// ライブラリを読み込むためのコード
#include <NintendoSwitchControlLibrary.h>

////////////////// options ////////////////////////////////

// 何回ループさせるか?
const int LOOP_COUNT = 2;

// 「17 スーパーピーナッツバターサンド」にカーソルを合わせるのに、下キーと右キーの入力回数を指定する
const int SANDWICH_DOWN_COUNT = 8;
const int SANDWICH_RIGHT_COUNT = 0;

////////////////// constant parameters ////////////////////////////////

// 具材の個数
const int INGREDIENT_COUNT = 3;

// タマゴパワーの有効時間(分)
const int EGG_POWER_MINUTES = 30;

// ボタン押下時の標準的な待機時間
const int BASE_PUSH_BUTTON_TIME_MS = 1200;

// カーソル操作時の標準的な待機時間
const int BASE_CURSOR_OPERATION_TIME_MS = 150;

// スティックによる移動の待機時間
const int STICK_OPERATION_TILT_TIME_MS = 100;
const int STICK_OPERATION_DELAY_TIME_MS = 500;

// メニューの開閉にかかる待機時間
const int MENU_OPEN_OR_CLOSE_TIME_MS = 1500;

// Custom版のpushHatにおける押下時間
const int CUSTOM_INPUT_TIME_MS = 120;

// Custom版のholdButtonにおける押下後の追加の待機時間
const int HOLD_BUTTON_ADD_INTERVAL_TIME_MS = 100;

////////////////// variable parameters ////////////////////////////////

// ループをカウントするための変数
int count = 0;

////////////////// methods ////////////////////////////////

/**
 * pushHatのカスタム版(ボックス操作時にコマンド漏れが起きることを抑制するために用意)
 *
 * @param uint8_t       hat        押す十字キーのボタン
 * @param unsigned long delay_time ボタンを押した後の待ち時間(1秒 = 1000)
 * @param unsigned int  loop       ボタンを押す回数(省略可、デフォルトは1)
 * @return void
 */
void pushHatCustom(uint8_t hat, unsigned long delay_time, unsigned int loop = 1) {
    for (unsigned int i = 0; i < loop; i++) {
        SwitchControlLibrary().pressHatButton(hat);
        SwitchControlLibrary().sendReport();
        delay(CUSTOM_INPUT_TIME_MS);
        SwitchControlLibrary().releaseHatButton();
        SwitchControlLibrary().sendReport();
        delay(delay_time);
    }
    delay(CUSTOM_INPUT_TIME_MS);
}

/**
 * holdButtonのカスタム版(hold後の待機時間を一律で伸ばす目的で用意)
 */
void holdButtonCustom(uint16_t button, unsigned long hold_time) {
    holdButton(button, hold_time);
    delay(HOLD_BUTTON_ADD_INTERVAL_TIME_MS);
}

/**
 * Switchをスリープさせる
 *
 * @return void
 */
void sleepGame() {
    holdButton(Button::HOME, 1500);
    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
}

/**
 * 「プラトタウン」から「コサジの小道灯台」に空を飛ぶで移動する(同じ場所に空を飛ぼうとするとAボタンが反応しないことがあるため、「プラトタウン」からのスタートとした)
 *
 * @return void
 */
void goFromLosPlatosToLighthouse() {
    holdButtonCustom(Button::Y, 1000);
    delay(4000);
    tiltLeftStick(204, Stick::MAX, 380);
    delay(1000);
    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    holdButtonCustom(Button::A, 2000);
    holdButtonCustom(Button::A, 2000);
    delay(30000);
}

/**
 * 「コサジの小道灯台」から「プラトタウン」に空を飛ぶで移動する
 *
 * @return void
 */
void goFromLighthouseToLosPlatos() {
    holdButtonCustom(Button::Y, 1000);
    delay(4000);
    tiltLeftStick(52, Stick::MIN, 380);
    delay(1000);
    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    holdButtonCustom(Button::A, 2000);
    holdButtonCustom(Button::A, 2000);
    delay(30000);
}

/**
 * ピクニックを開始する
 *
 * @param bool is_init
 * @return void
 */
void startPicnic(bool is_init = false) {
    holdButtonCustom(Button::L, 2000);
    holdButtonCustom(Button::X, MENU_OPEN_OR_CLOSE_TIME_MS);

    // 初回のみカーソルを合わせる
    if (is_init) {
        holdHat(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
        holdHat(Hat::UP, 2000);
        pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
        pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
    }

    holdButtonCustom(Button::A, 2000);
    delay(8000);
}

/**
 * ピクニックを終了する
 *
 * @return void
 */
void stopPicnic() {
    holdButtonCustom(Button::Y, 1500);
    holdButtonCustom(Button::A, 2000);
    delay(2000);
    pushButton(Button::B, 500, 6);
}

/**
 * スティックで1マスずつ移動する
 *
 * @param uint8_t x
 * @param uint8_t y
 * @return void
 */
void moveWithStick(uint8_t x, uint8_t y) {
    tiltLeftStick(x, y, 200);
    delay(600);
}

/**
 * 初期位置(テーブル)に向かう
 *
 * @return void
 */
void moveToTablePosition() {
    // コライドン(ミライドン)をボールに戻す
    holdButtonCustom(Button::PLUS, 2000);
    // しゃがんで移動することで位置を安定させる
    holdButtonCustom(Button::B, BASE_PUSH_BUTTON_TIME_MS);
    // 右3
    moveWithStick(Stick::MAX, Stick::NEUTRAL);
    moveWithStick(Stick::MAX, Stick::NEUTRAL);
    moveWithStick(Stick::MAX, Stick::NEUTRAL);
    // 上5
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    moveWithStick(Stick::NEUTRAL, Stick::MIN);
    // 左1
    moveWithStick(Stick::MIN, Stick::NEUTRAL);
    // 左下2
    moveWithStick(Stick::MIN, Stick::MAX);
    moveWithStick(Stick::MIN, Stick::MAX);
}

/**
 * サンドウィッチを作る
 *
 * @return void
 */
void makeSandwich() {
    // テーブルの方向を向く
    tiltLeftStick(Stick::NEUTRAL, Stick::MAX, STICK_OPERATION_TILT_TIME_MS);
    delay(STICK_OPERATION_DELAY_TIME_MS);

    // 料理をする
    holdButtonCustom(Button::A, 1500);
    holdButtonCustom(Button::A, 2000);
    delay(3000);

    // 「17 スーパーピーナッツバターサンド」を選択
    pushHatCustom(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS, SANDWICH_DOWN_COUNT);
    if (SANDWICH_RIGHT_COUNT == 1) {
        pushHatCustom(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
    }

    holdButtonCustom(Button::A, BASE_PUSH_BUTTON_TIME_MS);
    holdButtonCustom(Button::A, 2000);
    delay(7000);

    // 具材を乗せる
    for (int i = 0; i < INGREDIENT_COUNT; i++) {
        tiltLeftStick(Stick::NEUTRAL, Stick::MIN, i == 0 ? 640 : 540);
        SwitchControlLibrary().moveLeftStick(Stick::NEUTRAL, Stick::MAX);
        SwitchControlLibrary().sendReport();
        SwitchControlLibrary().pressButton(Button::A);
        SwitchControlLibrary().sendReport();
        delay(540);
        SwitchControlLibrary().releaseButton(Button::A);
        SwitchControlLibrary().sendReport();
        SwitchControlLibrary().moveLeftStick(Stick::NEUTRAL, Stick::NEUTRAL);
        SwitchControlLibrary().sendReport();
        delay(500);
    }
    delay(500);

    // 上のパンはいらない
    tiltLeftStick(Stick::MAX, 55, 1200);

    // あとはA連打で食事を終える
    pushButton(Button::A, 300, 40);
    delay(20000);
    holdButtonCustom(Button::A, 2000);
    delay(500);
}

/**
 * バスケットの確認(処理時間がちょうど1分になるよう調整)
 *
 * @return void
 */
void checkBasket() {
    pushButton(Button::A, 1000, 2);  // 2300ms
    pushButton(Button::B, 500, 96);  // 57700ms
}

////////////////// execute ////////////////////////////////

void setup() {
    // Switchがマイコンを認識するまでは信号を受け付けないため、適当な処理をさせておく
    pushButton(Button::B, 500, 8);
}

void loop() {
    // 指定のループ回数を終えたあと、スリープする
    if (count == LOOP_COUNT) {
        sleepGame();
        exit(0);
    }

    // 「コサジの小道灯台」に空を飛ぶ
    goFromLosPlatosToLighthouse();

    // ピクニック開始
    startPicnic(count == 0);
    moveToTablePosition();

    // サンドウィッチ作成
    makeSandwich();

    // 最初の確認まではバスケットの方向を向いて待機(1分)
    tiltLeftStick(Stick::MIN, Stick::NEUTRAL, 100);
    delay(29600);
    tiltLeftStick(Stick::MIN, Stick::NEUTRAL, 100);
    delay(60000 - 2600 - 30000);

    // タマゴパワーの有効時間が続く限り、1分毎に受け取りを繰り返す(iを1から始めているのは初回分は待機済のため)
    for (int i = 1; i < EGG_POWER_MINUTES; i++) {
        checkBasket();
    }

    // ピクニック終了
    stopPicnic();

    // 「プラトタウン」に戻る
    goFromLighthouseToLosPlatos();

    count++;
}

設定の確認

マイコンを繋げる前に、以下の状態になっていることを確認してください。

  1. 「プラトタウン」に空を飛ぶを行い、その着地点にいること
  2. マップを開いた際に北が上にくるようになっていること
  3. ピクニック、及び、サンドウィッチのチュートリアルを終えていること
  4. 「17 スーパーピーナッツバターサンド」のレシピを開放済であること
  5. 「17 スーパーピーナッツバターサンド」の素材(バナナスライス・ピーナッツバター・バター・シルバーピック(金額気にしないなら他のピックでもOK))をループ回数分用意してあること
  6. 手持ちは親となるポケモン2匹のみになるようにしてあること
  7. ボックスに十分な空きを確保してあること(相性にもよりますが、1ループあたり50個前後のタマゴを受け取ります)
  8. オフライン状態であること
  9. 本体に装着された状態のジョイコン以外にコントローラーが接続されていないこと
  10. 「設定」から「話の速さ」を「はやい」に、「わざ覚えスキップ」を「する」にしておくこと

重要なものをいくつか解説します!

開始位置・手持ちのポケモンについて

開始位置は以下になります。厳密にこの位置でなくてもポケセンの裏とかでも空を飛ぶには成功するはずなので、割と大雑把で大丈夫です!

開始位置

また、手持ちは親となるポケモン2匹のみの状態にしておいてください。

オプションについて

コードの27行目、31〜32行目がオプションになります。

// 何回ループさせるか?
const int LOOP_COUNT = 2;

27行目の「2」の部分がループ回数になります。デフォルトの2ループだと、相性にもよりますがだいたい100個くらいタマゴできると思います!

ほしいタマゴの数に合わせて調整してもらえればと思います。

// 「17 スーパーピーナッツバターサンド」にカーソルを合わせるのに、下キーと右キーの入力回数を指定する
const int SANDWICH_DOWN_COUNT = 8;
const int SANDWICH_RIGHT_COUNT = 0;

31〜32行目は「17 スーパーピーナッツバターサンド」のカーソルを合わせるために必要なキーの入力回数を指定します。レシピの開放状態により入力回数が異なると思いますので、オプションとしております。

SANDWICH_DOWN_COUNTには下キーの入力回数を入れます。

SANDWICH_RIGHT_COUNTには右キーの入力回数を入れます。(ここは1か0で入力してください)

数値の編集は必ず半角で行っていただくようお願いいたします。

動作について

以下のように動作していれば正常です。

  1. 「プラトタウン」から「コサジの小道灯台」に空を飛ぶ
  2. ピクニックを開始
  3. 移動
  4. サンドウィッチ作成
  5. 移動
  6. 待機
  7. バスケット確認
  8. スクワットしながら待機
  9. 30分間、⑦と⑧を繰り返し
  10. ピクニック終了
  11. 「コサジの小道灯台」から「プラトタウン」に空を飛ぶ

コード作成のノウハウについて

今回のコード作成のノウハウを共有します。

タマゴの受け取り

SVではタマゴ受け取り時にBボタン連打でもタマゴ受け取りが可能です。そのため、バスケット確認の初回のAボタン以外はBボタンを連打することでテキスト飛ばしつつタマゴも受け取れるのが便利でした。

選定するサンドウィッチやタマゴ受け取りのボタン連打法のノウハウを共有してくださった方のおかげで早く仕上げることができました!! 非常に助かりました。

移動法について

しゃがんだ状態での移動はカメラの向きが勝手に変わったりしなかったり、また、マス目移動のような正確な移動が可能になるのが利点です!

この移動法を使うことで安定した移動ができるようになりました。

うまく使えば、ストーリーを自動化することも夢じゃないかもしれませんね!

正直なところ、ピクニック場所の選定に関しては孵化コードの処理を使い回したかった都合だけで「コサジの小道灯台」としています。この選定にはあまり意味はないためもっと適した場所があると思っています。

タマゴの受け取りと孵化を同時に行うコードについて

作成は未定ですが構想はあります。

  • 孵化はオリーブころがし場で行う
  • オリーブころがし場に入るまでは、基本的にしゃがんだ状態で移動する
  • セルクルタウンとオリーブころがし場の中間で30分ピクニックをする(野生ポケモンが出現しない場所があります)
  • 1回のループで2BOXずつ孵化を行う

雨の時の処理落ちが怖かったり、柵を飛び越える際にライド状態になる必要がある点、タマゴが極端に少なかったり極端に多かったりした際の挙動、空を飛ぶで安定して戻れるかなど、いくつか懸念点はありますが、プログラムとしては上記で実現できるかなーと思っております。

ちなみに、野生ポケモンが出現しないピクニックできる場所というのは、ここから前に進んだところ(セルクルタウンとオリーブころがし場の間のスペース)になります。

野生ポケモンが出現しないルート

最後に

今回のコードはなるべく安定するよう仕上げたつもりですが、もしループを抜けてしまう事象など見つけましたら@lefmarnaまで共有いただければと思います!

スポンサーリンク