ますたーです。こんにちは!
今回は、ポケモンBDSP(ダイパリメイク)の「お金稼ぎ」を自動化しました。
なお、Arduino Leonardo自動化の導入・機材構成については導入記事を参考にしてください。
導入記事:【Arduino自動化01】Arduino開発環境の導入
※本ブログに初めてお越しの方は「本ブログについて」もぜひ、ご覧ください。
概要
「バトルサーチャー」を使って、212番道路にいるジェントルマンのサダミツと再戦し続けて、賞金をいただく自動化です。約30分完全放置で244,480円*1稼げました(時給換算459,000円)。
なお、上記の金額は、殿堂入り後・ヒードラン捕獲前のROM*2で、先頭ポケモンには「おまもりこばん」*3を持たせた1ターン周回です。ヒードラン撃破後であれば、更に効率よく稼げると思われます。
もくじです。本ブログでは、ポケモンBDSPの自動化記事は初めてですね。
例のごとく「ソースコードだけ欲しい」という方はどうぞ→ソースコード
自動化の方法が知りたいという方はどうぞ→「自動化の準備」
動いている様子ももう少し詳しく見たいという方はどうぞ→「動作確認(自動化の様子)」
ポケモンBDSPのお金稼ぎには「バトルサーチャー」
ダイパリメイクのお金稼ぎの方法として、もっとも有力なのはトレーナーとの戦闘です。
ポケモン剣盾とは違い、今作では一部の例外を除いてお金稼ぎの方法がほとんど無いのです。一応、ポケモン剣盾のウッウロボ*4のように、「地下大洞窟」の道具交換で「台座」→「べにたま」→「わざマシン」の購入/交換/売却時の差額*5で稼ぐ方法もあるのですが、そもそも元手となるお金が必要という点や、しっかり差額を計算しながら購入しないと赤字になるリスクがあるなどの欠点があります。代わりに、今作では久しぶりに「バトルサーチャー」が登場しました。使うと、近くにいる再戦可能なNPCと確率*6で再戦ができるようになるアイテムです。
今作でも、バトルサーチャーを使ってくださいと言わんばかりにジェントルマンとマダム、おぼっちゃまとおじょうさま、のように高額の賞金をくれる2人が1組で同じところにいるため、効率良くおこづかいを稼ぐこともできます。
今作の所持金は100万円に届かない(999999円でカンスト)
ダイパリメイクの最大の特徴は、その呼び名が冠する通り、リメイクであること。これはゲームシステムなども同様です。2Dマップ、SDキャラなど端々に特徴が現れていますが、中でも、使い勝手という意味で決定的に違うのが、第4世代までと同様におこづかいが999,999円でカンストしてしまうこと。第5世代以降は9,999,999円と1桁違うため、剣盾ぶりの新作としてガッツリ(対戦・ポケモン収集などで)遊ぼうにも、この上限が意外とネックになります。
タウリンなどの努力値振り*7アイテムも、BP交換以外では、トバリシティで定価(9,800円)で買わねばならず、地下大洞窟での捕獲用ボールの購入も費用がかさみます。
特に今作は剣盾と同様に野生で出現するポケモンのレベルが非常に高い反面、「クイックボール」や「ダークボール」などのもっぱら捕獲用のボールを大量に手に入れる方法が無いことから、捕獲するためにもお金を稼がなければならないストレス(1個1000円)が生まれます。
上述の通り、今作では剣盾と比しても、おこづかいの上限がシビアで「買いだめ」が難しいです。特に、単価の高いドーピングアイテムはその影響が顕著で、1匹分の努力値510を賄うだけで499,980円、すなわち、おこづかい上限額の半分を要します。
従って、効率良くお金を稼ぎつつ、アイテムを集めつつ、捕獲・育成しつつ、という「やりくり」が必要ですから、今回のような「片手間に放置できる自動化」は大いに価値があると考えます(突然の持論)。
自動化の準備
さて、おまたせしました。ここからは自動化に向けての準備です。
なお、筆者の検証環境はあくまでも「殿堂入り後」「ヒードラン捕獲前」のタイミングとなりますのでその点、ご了承くださいませ。
バトルサーチャーを「下」、じてんしゃを「左」に「とうろく」する
まずは、今回の肝となる「バトルサーチャー」「じてんしゃ」についてです。これらは「たいせつなもの」ポケットにあるアイテムで、212番道路までシナリオが進んでいればすでに所持しているはずです。
今回の自動化では、バトルサーチャーを「下」、じてんしゃを「左」にそれぞれ割り当てておいてください。やり方としては、たいせつなものポケットを開き、「とうろく」したいアイテムを選択することで、+ボタンを押した際のショートカットを4つまで登録することができます。
ちなみに余談ですが、アイテムを1つだけ「登録」した場合は、「+」ボタンを押した際にダイアログが出ずに即座に使用できます。ポケトレなど即座に使いたいアイテムがある場合は重宝するので、覚えておくと良いと思います。
手持ちの先頭ポケモンで1撃でペラップを倒せるようにします。具体的には、1番目の技を今回の周回で使いたい技に並び替えておきます。
この際に注意すべきは、確実に1ターンで周回させるため、命中が100または必中技を使うようにしましょう。命中率95*8や90ではダメですし、命中率に下降補正がかかる特性「はりきり」などは注意しましょう。ペラップ自身はそれほど耐久も高く無いので十分なレベル差があれば、旅パでもタイプ一致技で確1にできます。
なお、もらえる賞金に直結するので、もし可能なら「おまもりこばん」をもたせることも忘れずにしておきましょう。もちろん、1ターンで周回できることが前提なので、火力補正アイテムや命中率の補正アイテムをもたせることの方が重要です。
212番道路でいろいろ準備
戦闘用のポケモン・バトルサーチャーの登録が終わったら、まずは、今回お世話になるジェントルマンのサダミツの元まで向かいます。
最寄りはヨスガシティです。ひたすら南へ木々の合間を進み、ジェントルマンとマダムが立っているところまで向かいます。2本の木がある角に着けばOKです。なお、自転車は4速ではなく3速(=早い方)にしておきます。他作品でいう「マッハ自転車」の方です。
着いたら、自転車から必ず降りてください。また、左上の角に主人公が立つように調整してください。
その後、「設定」画面から「はなしのはやさ」「せんとうアニメ」の項目をそれぞれ「はやい」「みない」に設定しておきます。ここまで終われば、ゲーム側の準備は完了です。
ソースコードの「PP」部分、「戦闘時間(A連打時間)」などを修正する
ソースコードは後述の通りですが、皆様の環境に応じてあらかじめ2箇所ほど修正をする必要があります。とは言っても、基本的にはPP部分だけ。ご自身で準備したポケモンのPP上限数を「PP」に入れるだけです。
なお、筆者の体感によればバトルサーチャーでの再戦に応じてくれる確率はおおよそ50%のため、実際にはPP数×1.3~1.5倍程度の数字をいれておくと、無駄なく周回してくれる可能性が高くなります(自己責任でお願いします)。筆者は1.4倍で周回しています。
volatile int PP = (20*1.4);
また、バトルサーチャーとじてんしゃについて、「+」ボタンのとうろく先の向きが違う場合は、それぞれ「BATTLE_SEARCHER_KEY」「BICYCLE_KEY」に"Right", "Left", "Up", "Down"いずれかを必要に応じて更新してください。
なお、戦闘中は常にA連打をするプログラムになっていますので、何秒間Aを連打し続けるかは、お使いの環境に合わせて「BATTLE_ELAPSED_TIME」を修正してください。
#define BATTLE_SEARCHER_KEY "Down"
#define BICYCLE_KEY "Left"
#define BATTLE_ELAPSED_TIME (45)
以上で、プログラム側の準備も完了です。お疲れさまでした。あとは、このプログラムをArduino Leonardoに書き込み、Switchと接続するだけです。
動作確認(自動化の様子)
本章では、ArduinoをSwitchに差し込んだ際の画面の流れを紹介します。ソースコードが早く欲しい!という方はどうぞ→ソースコード
Arduinoを差し込むと、まずRボタンが何回か押される挙動をします。すなわち、ポケッチが開いたり閉じたりしますが、慌てずそのまま放置してください(正常な動作です)。これは、Nintendo Switchの仕様で、新たに挿したコントローラーを認識するためにキー入力が必要だからです。
プログラムではRキーとLキーをカチャカチャと7回ほど入力するように作っています。
その後は、プログラム内に書き込んだ「PP」の数だけ、バトルサーチャーによるジェントルマンのサダミツとの再戦を自動で繰り返してくれます。
おまたせしました。ソースコードです。必要に応じて「★修正ここから★」から「★修正ここまで★」まで修正をしてお使いください。
2022/1/10追記:無限ループに対応しました。
※上級者向け。「ENDLESS_LOOP_using_CommunityRoom」の「false」を「true」に変えると、PPが0になった時に自動で回復してくれるようになります(コミュニティルームに入るとPPが回復する仕様を利用)。
#include <SwitchControlLibrary.h>
#define HOLDTIME (95)
#define RENDA_CYCLE (150)
volatile int PP = (16);
#define ENDLESS_LOOP_using_CommunityRoom (false)
#define BATTLE_SEARCHER_KEY "Down"
#define BICYCLE_KEY "Left"
#define BATTLE_ELAPSED_TIME (45)
#define ROTATE_CYCLE (165)
#define CHARGE_ELAPSED_TIME (7.2)
#define BEGIN_TRAINER_CARD (false)
volatile int curPP = PP;
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 die(void);
void setup() {
if(BEGIN_TRAINER_CARD){
for(int i=0;i<7;i++)PushRL(300);
delay(1200);
PushKey("B", HOLDTIME, 1000);
PushKey("B", HOLDTIME, 1300);
}else{
for(int i=0;i<7;i++)PushRL(300);
delay(1200);
}
PushKey("R", 1300, 1000);
PushKey("R", HOLDTIME, 1000);
}
void loop() {
int i=0;
unsigned long int current_time=0;
unsigned long int start_time=0;
unsigned long int temp_time=0;
int isholding = 0;
float holdA = 0;
float temp_deg = 0;
PushKey("+", HOLDTIME, 1000);
if(strcmp(BATTLE_SEARCHER_KEY,"+")!=0){
PushKey(BATTLE_SEARCHER_KEY, HOLDTIME, 1200);
}
delay(300);
PushKey("A", HOLDTIME, 400);
PushKey("A", HOLDTIME, 400);
PushKey("Right",1150, 400);
PushKey("Down", HOLDTIME, 400);
for( start_time=millis(), current_time=start_time, isholding=0 ; current_time - start_time < (unsigned long)BATTLE_ELAPSED_TIME*1000UL ; current_time=millis() ){
temp_time = (current_time - start_time) % (RENDA_CYCLE+1);
holdA = (float)temp_time / (float)RENDA_CYCLE * 100.0;
if( holdA <= 35.0 ){
if(!isholding){
SwitchControlLibrary().PressButtonA();
isholding = 1;
}
}else{
if(isholding){
SwitchControlLibrary().ReleaseButtonA();
isholding = 0;
}
}
}
if(isholding) SwitchControlLibrary().ReleaseButtonA();
curPP--;
PushKey("B",HOLDTIME, 400);
PushKey("B",HOLDTIME, 400);
PushKey("B",HOLDTIME, 400);
PushKey("Left",1800, 400);
PushKey("Up", 400, 400);
if(curPP <= 0){
if(ENDLESS_LOOP_using_CommunityRoom){
PushKey("Y",HOLDTIME, 600);
PushKey("A",HOLDTIME, 600);
PushKey("A",HOLDTIME, 600);
PushKey("A",HOLDTIME, 600);
PushKey("A",HOLDTIME, 600);
PushKey("A",HOLDTIME, 600);
PushKey("A",HOLDTIME, 600);
PushKey("A",HOLDTIME, 600);
PushKey("Down",HOLDTIME, 600);
PushKey("B",HOLDTIME, 600);
PushKey("B",HOLDTIME, 600);
PushKey("B",HOLDTIME, 800);
curPP = PP;
}else{
die();
}
}
PushKey("+", HOLDTIME, 1000);
PushKey(BICYCLE_KEY, HOLDTIME, 400);
for( start_time=millis(), current_time=start_time ; current_time - start_time < (unsigned long)CHARGE_ELAPSED_TIME*1000UL ; current_time=millis() ){
temp_time = (current_time - start_time) % (ROTATE_CYCLE+1);
temp_deg = (float)temp_time / (float)ROTATE_CYCLE * 360.0;
TiltLeftStick( (int)temp_deg, 1.0, 0, 0);
}
TiltLeftStick( 330, 1.0, 1000, 65);
TiltLeftStick( 0, 0.0, 0, 0);
PushKey("+", HOLDTIME, 1000);
PushKey(BICYCLE_KEY, HOLDTIME, 1200);
PushKey("Left", 500, 250);
PushKey("Up", 500, 250);
}
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){
if(strlen(keyname)==1){
switch(keyname[0]){
case 'A': case 'a':
SwitchControlLibrary().PressButtonA(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonA(); delay(delaytime);
break;
case 'B': case 'b':
SwitchControlLibrary().PressButtonB(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonB(); delay(delaytime);
break;
case 'X': case 'x':
SwitchControlLibrary().PressButtonX(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonX(); delay(delaytime);
break;
case 'Y': case 'y':
SwitchControlLibrary().PressButtonY(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonY(); delay(delaytime);
break;
case 'L': case 'l':
SwitchControlLibrary().PressButtonL(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonL(); delay(delaytime);
break;
case 'R': case 'r':
SwitchControlLibrary().PressButtonR(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonR(); delay(delaytime);
break;
case 'H': case 'h':
SwitchControlLibrary().PressButtonHome(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonHome(); delay(delaytime);
break;
case '+': case 'p': case 'P':
SwitchControlLibrary().PressButtonPlus(); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().ReleaseButtonPlus(); delay(delaytime);
break;
case '-': case 'm': case 'M':
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':
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':
SwitchControlLibrary().MoveHat(2); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
break;
case 'l': case 'L':
SwitchControlLibrary().MoveHat(6); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
break;
case 'u': case 'U':
SwitchControlLibrary().MoveHat(0); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
break;
case 'd': case 'D':
SwitchControlLibrary().MoveHat(4); delay(holdtime);
if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
break;
case 'H': case 'h':
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){
delay(holdtime);
SwitchControlLibrary().MoveLeftStick(128,128);
}
if(delaytime>0) delay(delaytime);
return;
}
void die(void){
if(BEGIN_TRAINER_CARD){
PushKey("X", HOLDTIME, 550);
PushKey("A", HOLDTIME, 350);
}
for(;;)delay(1000);
}
あとがき
今回は、ポケモンBDSPの「お金稼ぎ」の自動化を紹介しました。
所持金の上限まで4世代に準拠するとは…恐るべし「リメイク」ですね(ピカブイですら100万を超えて所持できたのに…)。ちょっとだけ時流を遡っている感じが否めないですね。なお、本ブログで今後、BDSPの自動化を実装・紹介していくかは、殿堂入り後の諸々をクリアしてから、ゆっくりと検討していきます。
*
いやー!!2021年11月19日(金)、ついにポケモンBDSPが販売されましたね!(いまさら)。
皆様、ダイパリメイクは遊んでらっしゃいますでしょうか?私はもちろん、ブリリアントダイヤモンド・シャイニングパール共に、「ダブルパック」で買いました。発売日の11月19日には有給を取り、しっかりと遊びました!遊びごたえもバッチリで、シナリオクリアだけで3日間も使ってしまいました。
まだの人、見送っている人は、ぜひ買いましょう!笑→Amazonで買う!
昨今の噂の通り、グラフィックやらUIやら上記のシステム面やら、かなり難があるところもありますが、今作はジムリーダーも四天王も強く、やりごたえのある「古き良きポケモン」が文字通りリメイクとして帰ってきています。本記事では紹介しませんが、当時の「なぞのばしょ」に迫るような大規模な「バグ」も多く、そういう意味でも当時を懐かしむことができます。
バグのみならず、もちろんゲーム面も改良されていて、「ヒコザルの『グロウパンチ』修得」「ひでんわざの廃止」など、旅パの編成自由度が増したこともあり、ビーダル*9を連れて行かずに旅パを作れるなど、6匹をフルで使って楽しく遊ぶことができました。その分、四天王・チャンピオンは本当に強かったですが…。
リメイクの目玉である「地下大洞窟」も試みとしては非常に面白く、オンラインプレイもできるので、思わず何十分も潜っていたくなるような仕掛けとして仕上がっています。
剣盾からポケモンを始めた方にとっては、おそらくライバルが主人公の弱点属性のポケモンを使うことにも(逆に)新鮮さを覚えるかもしれません。
そういう意味でも、DP時代のポケモンを知っている方には(ゲーム性はともかく)、満足できる内容になっていると思いますし、オススメです。今は発売まもなくですから、攻略Webサイトも増えてきていますし、私もこうして記事執筆できるまではシナリオも進められたわけですから、今がチャンスですね。
もう一度言います。買わずに見送っている人は、ぜひ買いましょう!!→Amazonで買う!
むしろ、「ポケモンLegendsアルセウス」の発売まであと3ヶ月しか無いですから、今を逃すと遊ばずに終わってしまうかもしれませんよ!(もったいない!)
*
最後はBDSPの宣伝みたいになりましたが、いちポケモンファンとしては楽しく遊べたので良かったです。もちろん、ポケモン剣盾もBDSPも遊んでいけたらと思いますので、これからも本ブログをよろしくお願いします!
もし、BDSP・剣盾で自動化のリクエストや、応援メッセージなどがあれば、コメントをいただければと思います(マイペース更新なのでリクエストについては仕上げられるか分からないですが…)。
ではでは~c⌒っ.ω.)っ
導入記事:【Arduino自動化01】Arduino開発環境の導入
次回記事:【BDSP自動化02】ふれあい広場できのみ集め【ダイパリメイク】
BDSP自動化記事:ダイパリメイクArduino自動化 カテゴリーの記事一覧
ポケモン「剣盾」の自動化:ポケモン剣盾Arduino自動化 カテゴリーの記事一覧
YouTubeチャンネル:ますたーの忘備録 - YouTube
---