Skip to content

Commit 33b7b19

Browse files
committed
inital SwSh Daily Highlight RNG
1 parent 9086b0f commit 33b7b19

File tree

6 files changed

+522
-10
lines changed

6 files changed

+522
-10
lines changed

SerialPrograms/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2222,6 +2222,8 @@ file(GLOB MAIN_SOURCES
22222222
Source/PokemonSwSh/Programs/RNG/PokemonSwSh_BasicRNG.h
22232223
Source/PokemonSwSh/Programs/RNG/PokemonSwSh_CramomaticRNG.cpp
22242224
Source/PokemonSwSh/Programs/RNG/PokemonSwSh_CramomaticRNG.h
2225+
Source/PokemonSwSh/Programs/RNG/PokemonSwSh_DailyHighlightRNG.cpp
2226+
Source/PokemonSwSh/Programs/RNG/PokemonSwSh_DailyHighlightRNG.h
22252227
Source/PokemonSwSh/Programs/RNG/PokemonSwSh_SeedFinder.cpp
22262228
Source/PokemonSwSh/Programs/RNG/PokemonSwSh_SeedFinder.h
22272229
Source/PokemonSwSh/Programs/ShinyHuntAutonomous/PokemonSwSh_ShinyHuntAutonomous-BerryTree.cpp
@@ -2260,6 +2262,8 @@ file(GLOB MAIN_SOURCES
22602262
Source/PokemonSwSh/Programs/ShinyHuntUnattended/PokemonSwSh_ShinyHuntUnattended-SwordsOfJustice.h
22612263
Source/PokemonSwSh/Programs/TestPrograms/PokemonSwSh_ShinyEncounterTester.cpp
22622264
Source/PokemonSwSh/Programs/TestPrograms/PokemonSwSh_ShinyEncounterTester.h
2265+
Source/PokemonSwSh/Resources/PokemonSwSh_DailyHighlightDatabase.cpp
2266+
Source/PokemonSwSh/Resources/PokemonSwSh_DailyHighlightDatabase.h
22632267
Source/PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.cpp
22642268
Source/PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.h
22652269
Source/PokemonSwSh/Resources/PokemonSwSh_NameDatabase.cpp

SerialPrograms/Source/PokemonSwSh/PokemonSwSh_Panels.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,9 @@
4747
#include "Programs/NonShinyHunting/PokemonSwSh_StatsReset-Moltres.h"
4848
#include "Programs/NonShinyHunting/PokemonSwSh_StatsReset-Regi.h"
4949

50-
#include "Programs/EggPrograms/PokemonSwSh_EggAutonomous.h"
51-
#include "Programs/EggPrograms/PokemonSwSh_EggFetcher2.h"
52-
#include "Programs/EggPrograms/PokemonSwSh_EggFetcherMultiple.h"
53-
#include "Programs/EggPrograms/PokemonSwSh_EggHatcher.h"
54-
#include "Programs/EggPrograms/PokemonSwSh_EggCombined2.h"
55-
#include "Programs/EggPrograms/PokemonSwSh_EggSuperCombined2.h"
56-
#include "Programs/EggPrograms/PokemonSwSh_GodEggDuplication.h"
57-
#include "Programs/EggPrograms/PokemonSwSh_GodEggItemDupe.h"
50+
#include "Programs/RNG/PokemonSwSh_CramomaticRNG.h"
51+
#include "Programs/RNG/PokemonSwSh_DailyHighlightRNG.h"
52+
#include "Programs/RNG/PokemonSwSh_SeedFinder.h"
5853

5954
#include "Programs/ShinyHuntUnattended/PokemonSwSh_MultiGameFossil.h"
6055
#include "Programs/ShinyHuntUnattended/PokemonSwSh_ShinyHuntUnattended-Regi.h"
@@ -74,8 +69,14 @@
7469
#include "Programs/ShinyHuntAutonomous/PokemonSwSh_ShinyHuntAutonomous-Fishing.h"
7570
#include "Programs/OverworldBot/PokemonSwSh_ShinyHuntAutonomous-Overworld.h"
7671

77-
#include "Programs/RNG/PokemonSwSh_CramomaticRNG.h"
78-
#include "Programs/RNG/PokemonSwSh_SeedFinder.h"
72+
#include "Programs/EggPrograms/PokemonSwSh_EggAutonomous.h"
73+
#include "Programs/EggPrograms/PokemonSwSh_EggFetcher2.h"
74+
#include "Programs/EggPrograms/PokemonSwSh_EggFetcherMultiple.h"
75+
#include "Programs/EggPrograms/PokemonSwSh_EggHatcher.h"
76+
#include "Programs/EggPrograms/PokemonSwSh_EggCombined2.h"
77+
#include "Programs/EggPrograms/PokemonSwSh_EggSuperCombined2.h"
78+
#include "Programs/EggPrograms/PokemonSwSh_GodEggDuplication.h"
79+
#include "Programs/EggPrograms/PokemonSwSh_GodEggItemDupe.h"
7980

8081
#include "Programs/PokemonSwSh_SynchronizedSpinning.h"
8182
#include "Programs/PokemonSwSh_RaidItemFarmerOKHO.h"
@@ -179,6 +180,7 @@ std::vector<PanelEntry> PanelListFactory::make_panels() const{
179180
ret.emplace_back("---- RNG ----");
180181
ret.emplace_back(make_single_switch_program<SeedFinder_Descriptor, SeedFinder>());
181182
ret.emplace_back(make_single_switch_program<CramomaticRNG_Descriptor, CramomaticRNG>());
183+
ret.emplace_back(make_single_switch_program<DailyHighlightRNG_Descriptor, DailyHighlightRNG>());
182184

183185
ret.emplace_back("---- Multi-Switch Programs ----");
184186
ret.emplace_back(make_multi_switch_program<SynchronizedSpinning_Descriptor, SynchronizedSpinning>());
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
/* RNG Manipulation of the Highlight Watt Trader in the Snowslide Slope area in the Crown Tundra
2+
*
3+
* From: https://github.com/PokemonAutomation/Arduino-Source
4+
*
5+
* Based on Anubis' findings: https://docs.google.com/spreadsheets/u/0/d/1pNYtCJKRh_efX9LvzjCiA-0n2lGSFnVmSWwmPzgSOMw/htmlview
6+
*
7+
*/
8+
9+
#include <algorithm>
10+
#include <set>
11+
#include "CommonFramework/Exceptions/ProgramFinishedException.h"
12+
#include "CommonFramework/Exceptions/OperationFailedException.h"
13+
#include "CommonFramework/Notifications/ProgramNotifications.h"
14+
#include "CommonFramework/VideoPipeline/VideoFeed.h"
15+
#include "CommonFramework/Tools/StatsTracking.h"
16+
#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h"
17+
#include "Pokemon/Pokemon_Strings.h"
18+
#include "PokemonSwSh/PokemonSwSh_Settings.h"
19+
#include "PokemonSwSh/Commands/PokemonSwSh_Commands_DateSpam.h"
20+
#include "PokemonSwSh/Inference/PokemonSwSh_SelectionArrowFinder.h"
21+
#include "PokemonSwSh/Programs/PokemonSwSh_MenuNavigation.h"
22+
#include "PokemonSwSh/Programs/RNG/PokemonSwSh_BasicRNG.h"
23+
#include "PokemonSwSh/Programs/RNG/PokemonSwSh_DailyHighlightRNG.h"
24+
25+
26+
namespace PokemonAutomation{
27+
namespace NintendoSwitch{
28+
namespace PokemonSwSh{
29+
using namespace Pokemon;
30+
31+
32+
const DailyHighlightDatabase& DAILY_HIGHLIGHT_DATABASE() {
33+
static DailyHighlightDatabase database("PokemonSwSh/DailyHighlights.json");
34+
return database;
35+
}
36+
37+
38+
DailyHighlightRNG_Descriptor::DailyHighlightRNG_Descriptor()
39+
: SingleSwitchProgramDescriptor(
40+
"PokemonSwSh:DailyHighlightRNG",
41+
STRING_POKEMON + " SwSh", "Daily Highlight RNG",
42+
"ComputerControl/blob/master/Wiki/Programs/PokemonSwSh/DailyHighlightRNG.md",
43+
"Perform RNG manipulation to get rare items from the daily highlight trader.",
44+
FeedbackType::REQUIRED,
45+
AllowCommandsWhenRunning::DISABLE_COMMANDS,
46+
PABotBaseLevel::PABOTBASE_12KB
47+
)
48+
{}
49+
50+
struct DailyHighlightRNG_Descriptor::Stats : public StatsTracker{
51+
public:
52+
Stats()
53+
: iterations(m_stats["Iterations"])
54+
, reads(m_stats["Seed Reads"])
55+
, errors(m_stats["Errors"])
56+
, highlights(m_stats["Highlights"])
57+
{
58+
m_display_order.emplace_back("Iterations");
59+
m_display_order.emplace_back("Seed Reads");
60+
m_display_order.emplace_back("Highlights");
61+
m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO);
62+
}
63+
64+
public:
65+
std::atomic<uint64_t>& iterations;
66+
std::atomic<uint64_t>& reads;
67+
std::atomic<uint64_t>& errors;
68+
std::atomic<uint64_t>& highlights;
69+
};
70+
std::unique_ptr<StatsTracker> DailyHighlightRNG_Descriptor::make_stats() const{
71+
return std::unique_ptr<StatsTracker>(new Stats());
72+
}
73+
74+
DailyHighlightRNG::DailyHighlightRNG()
75+
: NUM_HIGHLIGHTS(
76+
"<b>Number of highlights:</b><br>How many daily highlights should be bought. A value of 0 will run until you run out of Watts.",
77+
LockMode::UNLOCK_WHILE_RUNNING, 0)
78+
, CONTINUE(
79+
"<b>Continue from last time:</b><br>If the initial two daily highlights are already manipulated and should be bought.",
80+
LockMode::LOCK_WHILE_RUNNING, false)
81+
, HIGHLIGHT_SELECTION(
82+
"<b>Desired Highlights:</b>",
83+
"Highlight",
84+
DAILY_HIGHLIGHT_DATABASE().database(),
85+
"bottle-cap-1")
86+
, NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600))
87+
, NOTIFICATIONS({
88+
&NOTIFICATION_STATUS_UPDATE,
89+
&NOTIFICATION_PROGRAM_FINISH,
90+
&NOTIFICATION_ERROR_RECOVERABLE,
91+
&NOTIFICATION_ERROR_FATAL,
92+
})
93+
, m_advanced_options(
94+
"<font size=4><b>Advanced Options:</b> You should not need to touch anything below here.</font>"
95+
)
96+
, MAX_UNKNOWN_ADVANCES(
97+
"<b>Max Unknown advances:</b><br>How many advances to check when updating the rng state.",
98+
LockMode::LOCK_WHILE_RUNNING,
99+
100000
100+
)
101+
, ADVANCE_PRESS_DURATION(
102+
"<b>Advance Press Duration:</b><br>Hold the button down for this long to advance once.",
103+
LockMode::LOCK_WHILE_RUNNING,
104+
10
105+
)
106+
, ADVANCE_RELEASE_DURATION(
107+
"<b>Advance Release Duration:</b><br>After releasing the button, wait this long before pressing it again.",
108+
LockMode::LOCK_WHILE_RUNNING,
109+
10
110+
)
111+
, SAVE_SCREENSHOTS(
112+
"<b>Save Debug Screenshots:</b>",
113+
LockMode::LOCK_WHILE_RUNNING,
114+
false
115+
)
116+
, LOG_VALUES(
117+
"<b>Log Animation Values:</br>",
118+
LockMode::LOCK_WHILE_RUNNING,
119+
false
120+
)
121+
{
122+
PA_ADD_OPTION(START_LOCATION);
123+
124+
PA_ADD_OPTION(NUM_HIGHLIGHTS);
125+
PA_ADD_OPTION(CONTINUE);
126+
PA_ADD_OPTION(HIGHLIGHT_SELECTION);
127+
128+
PA_ADD_OPTION(NOTIFICATIONS);
129+
130+
PA_ADD_STATIC(m_advanced_options);
131+
PA_ADD_OPTION(MAX_UNKNOWN_ADVANCES);
132+
PA_ADD_OPTION(ADVANCE_PRESS_DURATION);
133+
PA_ADD_OPTION(ADVANCE_RELEASE_DURATION);
134+
PA_ADD_OPTION(SAVE_SCREENSHOTS);
135+
PA_ADD_OPTION(LOG_VALUES);
136+
}
137+
138+
void DailyHighlightRNG::move_to_trader(SingleSwitchProgramEnvironment& env, BotBaseContext& context) {
139+
pbf_move_left_joystick(context, 207, 1, 160, 10); // Magic numbers to barely reach the trader
140+
pbf_press_button(context, BUTTON_A, 20, 20);
141+
// TODO: check if NPC was reached -> DialogArrow
142+
}
143+
144+
void DailyHighlightRNG::navigate_to_party(SingleSwitchProgramEnvironment& env, BotBaseContext& context){
145+
pbf_mash_button(context, BUTTON_B, 2 * TICKS_PER_SECOND); // exit dialog
146+
pbf_press_button(context, BUTTON_X, 10, GameSettings::instance().OVERWORLD_TO_MENU_DELAY);
147+
navigate_to_menu_app(env, env.console, context, 1, NOTIFICATION_ERROR_RECOVERABLE); // TODO: try-catch-block + error recovery
148+
pbf_press_button(context, BUTTON_A, 10, 3*TICKS_PER_SECOND);
149+
context.wait_for_all_requests();
150+
}
151+
152+
uint8_t DailyHighlightRNG::calibrate_num_npc_from_party(SingleSwitchProgramEnvironment& env, BotBaseContext& context, Pokemon::Xoroshiro128Plus& rng){
153+
// TODO: implement
154+
return 2; // Usually either 1 or 2 -> higher numbers suggest bad npc state
155+
}
156+
157+
size_t DailyHighlightRNG::calculate_target(SingleSwitchProgramEnvironment& env, Xoroshiro128PlusState state, uint8_t num_npcs, std::vector<std::string> wanted_highlights){
158+
Xoroshiro128Plus rng(state);
159+
size_t advances = 0;
160+
bool found_advance_amount = false;
161+
162+
std::vector<std::pair<uint16_t, uint16_t>> ranges;
163+
for (std::string slug : wanted_highlights) {
164+
ranges.push_back(DAILY_HIGHLIGHT_DATABASE().get_range_for_slug(slug));
165+
}
166+
167+
while (!found_advance_amount) {
168+
// calculate the result for the current temp_rng state
169+
Xoroshiro128Plus temp_rng(rng.get_state());
170+
171+
for (size_t i = 0; i < num_npcs; i++) {
172+
temp_rng.nextInt(91);
173+
}
174+
temp_rng.next();
175+
temp_rng.nextInt(60);
176+
177+
uint64_t highlight_roll = temp_rng.nextInt(1000);
178+
179+
for (auto& range : ranges) {
180+
if (range.first < highlight_roll && range.second > highlight_roll) {
181+
found_advance_amount = true;
182+
// TODO: check if affordable, remove from vector if not. If vector empty: throw Exception
183+
}
184+
}
185+
186+
if (!found_advance_amount) {
187+
rng.next();
188+
advances++;
189+
}
190+
}
191+
192+
return advances;
193+
}
194+
195+
void DailyHighlightRNG::leave_to_overworld_and_interact(SingleSwitchProgramEnvironment& env, BotBaseContext& context, bool buy_highlight) {
196+
// Close menu
197+
pbf_press_button(context, BUTTON_B, 2 * TICKS_PER_SECOND, 5);
198+
pbf_press_button(context, BUTTON_B, 10, 70);
199+
200+
// Quickly interact
201+
pbf_press_button(context, BUTTON_A, 30, 30);
202+
pbf_wait(context, 2*TICKS_PER_SECOND);
203+
204+
// TODO: check if interaction worked -> see move_to_trader()
205+
206+
// Buy highlight
207+
if (buy_highlight) {
208+
pbf_press_button(context, BUTTON_ZL, 10, 40);
209+
pbf_press_dpad(context, DPAD_DOWN, 10, 10);
210+
pbf_mash_button(context, BUTTON_ZL, 400);
211+
}
212+
213+
// Leave dialog
214+
pbf_mash_button(context, BUTTON_B, 6 * TICKS_PER_SECOND);
215+
}
216+
217+
void DailyHighlightRNG::recover_from_wrong_state(SingleSwitchProgramEnvironment& env, BotBaseContext& context) {
218+
// Mash the B button to exit potential menus or dialog boxes
219+
pbf_mash_button(context, BUTTON_B, 30 * TICKS_PER_SECOND);
220+
221+
// Open map
222+
pbf_press_button(context, BUTTON_X, 20, GameSettings::instance().OVERWORLD_TO_MENU_DELAY);
223+
navigate_to_menu_app(env, env.console, context, 5, NOTIFICATION_ERROR_RECOVERABLE);
224+
225+
// Fly to Snowslide Slope
226+
pbf_move_left_joystick(context, 200, 210, 20, 20);
227+
pbf_mash_button(context, BUTTON_A, 1 * TICKS_PER_SECOND);
228+
}
229+
230+
231+
void DailyHighlightRNG::advance_date(SingleSwitchProgramEnvironment& env, BotBaseContext& context, uint8_t& year) {
232+
pbf_press_button(context, BUTTON_HOME, 10, GameSettings::instance().GAME_TO_HOME_DELAY_FAST);
233+
home_roll_date_enter_game_autorollback(env.console, context, year);
234+
//resume_game_from_home(env.console, context, false);
235+
}
236+
237+
238+
void DailyHighlightRNG::program(SingleSwitchProgramEnvironment& env, BotBaseContext& context){
239+
DailyHighlightRNG_Descriptor::Stats& stats = env.current_stats<DailyHighlightRNG_Descriptor::Stats>();
240+
env.update_stats();
241+
242+
std::vector<std::string> wanted_highlights = HIGHLIGHT_SELECTION.all_slugs();
243+
if (wanted_highlights.empty()){
244+
throw UserSetupError(env.console, "At least one highlight item needs to be selected!");
245+
}
246+
247+
if (START_LOCATION.start_in_grip_menu()) {
248+
grip_menu_connect_go_home(context);
249+
}
250+
else {
251+
pbf_press_button(context, BUTTON_B, 5, 5);
252+
}
253+
254+
Xoroshiro128Plus rng(0, 0);
255+
bool is_state_valid = false;
256+
size_t iteration = 0;
257+
size_t bought_highlights = 0; // = CONTINUE ? iteration : max(0, iteration - 2);
258+
uint8_t year = MAX_YEAR;
259+
uint16_t state_errors = 0;
260+
261+
// TODO: save from time to time
262+
move_to_trader(env, context);
263+
264+
while (bought_highlights < NUM_HIGHLIGHTS || NUM_HIGHLIGHTS <= 0){
265+
iteration++;
266+
stats.iterations++;
267+
env.update_stats();
268+
send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE);
269+
env.console.log("Daily Highlight RNG iteration: " + std::to_string(iteration));
270+
271+
advance_date(env, context, year);
272+
navigate_to_party(env, context);
273+
context.wait_for_all_requests();
274+
275+
// Find RNG state
276+
if (!is_state_valid){
277+
rng = Xoroshiro128Plus(find_rng_state(env.console, context, SAVE_SCREENSHOTS, LOG_VALUES));
278+
// rng = Xoroshiro128Plus(100, 10000);
279+
is_state_valid = true;
280+
stats.reads++;
281+
}else{
282+
rng = Xoroshiro128Plus(refind_rng_state(env.console, context, rng.get_state(), 0, MAX_UNKNOWN_ADVANCES, SAVE_SCREENSHOTS, LOG_VALUES));
283+
stats.reads++;
284+
}
285+
286+
Xoroshiro128PlusState rng_state = rng.get_state();
287+
if (rng_state.s0 == 0 && rng_state.s1 == 0){
288+
stats.errors++;
289+
env.update_stats();
290+
291+
state_errors++;
292+
if (state_errors >= 3){
293+
OperationFailedException::fire(
294+
env.console, ErrorReport::SEND_ERROR_REPORT,
295+
"Detected invalid RNG state three times in a row."
296+
);
297+
}
298+
VideoSnapshot screen = env.console.video().snapshot();
299+
send_program_recoverable_error_notification(env, NOTIFICATION_ERROR_RECOVERABLE, "Detected invalid RNG state.", screen);
300+
recover_from_wrong_state(env, context);
301+
is_state_valid = false;
302+
continue;
303+
}
304+
305+
// Calibrate number of NPCs in the area and check whether Trader is in slow state
306+
uint8_t num_npcs = calibrate_num_npc_from_party(env, context, rng);
307+
308+
// Do advances
309+
size_t target_advances = calculate_target(env, rng.get_state(), num_npcs, HIGHLIGHT_SELECTION.all_slugs());
310+
env.console.log("Needed advances: " + std::to_string(target_advances));
311+
do_rng_advances(env.console, context, rng, target_advances, ADVANCE_PRESS_DURATION, ADVANCE_RELEASE_DURATION);
312+
313+
// Talk to NPC and buy highlight
314+
bool buy_highlight = (iteration >= 3) || CONTINUE;
315+
leave_to_overworld_and_interact(env, context, buy_highlight);
316+
317+
if (buy_highlight){
318+
bought_highlights++;
319+
stats.highlights++;
320+
}
321+
322+
uint32_t watts = 1000000; // TODO: read screen
323+
uint32_t lowest_cost = 500; // TODO: calculate
324+
// Out of Watts.
325+
if (watts < lowest_cost){
326+
throw_and_log<ProgramFinishedException>(env.console, "Cannot buy more daily highlights with the remaining Watts.", env.console);
327+
}
328+
329+
env.update_stats();
330+
state_errors = 0;
331+
}
332+
send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH);
333+
}
334+
335+
336+
}
337+
}
338+
}

0 commit comments

Comments
 (0)