Просмотр исходного кода

pokemon: Refactor, UI update, feature update

This patch moves the UI system to using a scene manager as well
as a few builtin modules as opposed to a new view for every
step. This decreases the binary size as well as presumably
reduces runtime memory footprint.

It also folds back in the features added in #17 and manages
to fix an experience calculation bug. Note that, the features
from #17 were not fully tested and there may be some incorrect
differences at this time.

This commit adds new features on top of that, the ability to
specify a custom nickname for the traded pokemon, its type(s)
as well as its OT name and ID#.

Types were modified a bit from the original implememtation in #17.
In Gen I, a pokemon with a single type just repeats that type
twice in its data structure. Rather than using 0xFF as a "No Type"
indicator, the main data table repeats types for single typed
pokemon.

There is a bit of a shortcoming with setting types. There is no
way to easily reset to default types. Due to the way the item_list
works, it directly modifies the actual trade data block. The only
way to reset at the moment is to select a different pokemon, and
then re-select the desired pokemon.

The solution to the Nidoran issue was reworked a bit. I had
issues compiling with the unicode escape characters, while
others had issues compiling with the actual unicode characters.
It would have been possible to use wchar_t for the pokemon_table
names, however that would have quadruled the size of each string.
Regardless, the Flipper can't print the male/female symbols at
this time. It might be possible to hack them back in with some
custom icon and View manipulation. The fix was to just use extended
ASCII character values. Since the Flipper doesn't support those
either, it still just renders as a "space". It keeps the strings
as char arrays. Compiles in any environment. And works.

Some of this implementation is still quite hacky and could use
an overhaul before new feature additions. e.g. Adding Gen II
support might not easily work with the current state of the
codebase.

Fixes #7
Fixes #8
Kris Bahnsen 2 лет назад
Родитель
Сommit
20f949f93f

Разница между файлами не показана из-за своего большого размера
+ 2089 - 197
pokemon_app.c


+ 56 - 2
pokemon_app.h

@@ -3,22 +3,47 @@
 
 #pragma once
 
+#include <gui/scene_manager.h>
 #include <gui/view.h>
 #include <gui/view_dispatcher.h>
 #include <gui/icon.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/variable_item_list.h>
 
 #include "pokemon_data.h"
 
 #define TAG "Pokemon"
 
+/* #defines for the data table entries */
+#define GROWTH_FAST 4
+#define GROWTH_MEDIUM_FAST 0
+#define GROWTH_MEDIUM_SLOW 3
+#define GROWTH_SLOW 5
+
 struct pokemon_data_table {
     const char* name;
     const Icon* icon;
-    const uint8_t species;
+    const uint8_t index;
+    const uint8_t base_hp;
+    const uint8_t base_atk;
+    const uint8_t base_def;
+    const uint8_t base_spd;
+    const uint8_t base_special;
+    const uint8_t type[2];
+    const uint8_t move[4];
+    const uint8_t growth;
 };
 
 typedef struct pokemon_data_table PokemonTable;
 
+struct named_list {
+    const char* name;
+    const uint8_t index;
+};
+
+typedef struct named_list NamedList;
+
 typedef enum {
     GAMEBOY_INITIAL,
     GAMEBOY_READY,
@@ -36,15 +61,29 @@ struct pokemon_fap {
     View* select_view;
     View* trade_view;
 
+    /* Scene manager */
+    SceneManager* scene_manager;
+
+    /* gui modules used in the application lifetime */
+    Submenu* submenu;
+    TextInput* text_input;
+    VariableItemList* variable_item_list;
+
     /* Table of pokemon data for Gen I */
     const PokemonTable* pokemon_table;
 
+    /* List of moves, alphabetically ordered */
+    const NamedList* move_list;
+
+    /* List of types, alphabetically ordered */
+    const NamedList* type_list;
+
     /* Struct for holding trade data */
     /* NOTE: There may be some runtime memory savings by adding more intelligence
      * to views/trade and slimming down this struct to only contain the single
      * pokemon data rather than the full 6 member party data.
      */
-    TradeBlock* trade_party;
+    TradeBlock* trade_block;
 
     /* The currently selected pokemon */
     int curr_pokemon;
@@ -61,14 +100,29 @@ struct pokemon_fap {
      * moveset, etc. Likely will want to be another sub struct similar to
      * the actual pokemon data structure.
      */
+    int curr_stats;
 };
 
 typedef struct pokemon_fap PokemonFap;
 
 typedef enum {
+    AppViewMainMenu,
+    AppViewOpts, // Generic view ID meant for module re-use
     AppViewSelectPokemon,
     AppViewTrade,
     AppViewExitConfirm,
 } AppView;
 
+int pokemon_named_list_get_num_elements(const NamedList* list);
+
+int pokemon_named_list_get_list_pos_from_index(const NamedList* list, uint8_t index);
+
+const char* pokemon_named_list_get_name_from_index(const NamedList* list, uint8_t index);
+
+void pokemon_trade_block_set_default_name(char* dest, PokemonFap* pokemon_fap, size_t n);
+
+void pokemon_trade_block_recalculate(PokemonFap* pokemon_fap);
+
+void pokemon_trade_block_recalculate_stats_from_level(PokemonFap* pokemon_fap);
+
 #endif /* POKEMON_APP_H */

+ 314 - 0
pokemon_char_encode.c

@@ -0,0 +1,314 @@
+#include <stdint.h>
+#include <stddef.h>
+
+#include "pokemon_char_encode.h"
+
+/* XXX Current text_input module only offers alnum and space input */
+char pokemon_char_to_encoded(int byte) {
+    switch(byte) {
+    case 'A':
+        return A_;
+    case 'B':
+        return B_;
+    case 'C':
+        return C_;
+    case 'D':
+        return D_;
+    case 'E':
+        return E_;
+    case 'F':
+        return F_;
+    case 'G':
+        return G_;
+    case 'H':
+        return H_;
+    case 'I':
+        return I_;
+    case 'J':
+        return J_;
+    case 'K':
+        return K_;
+    case 'L':
+        return L_;
+    case 'M':
+        return M_;
+    case 'N':
+        return N_;
+    case 'O':
+        return O_;
+    case 'P':
+        return P_;
+    case 'Q':
+        return Q_;
+    case 'R':
+        return R_;
+    case 'S':
+        return S_;
+    case 'T':
+        return T_;
+    case 'U':
+        return U_;
+    case 'V':
+        return V_;
+    case 'W':
+        return W_;
+    case 'X':
+        return X_;
+    case 'Y':
+        return Y_;
+    case 'Z':
+        return Z_;
+    case 'a':
+        return a_;
+    case 'b':
+        return b_;
+    case 'c':
+        return c_;
+    case 'd':
+        return d_;
+    case 'e':
+        return e_;
+    case 'f':
+        return f_;
+    case 'g':
+        return g_;
+    case 'h':
+        return h_;
+    case 'i':
+        return i_;
+    case 'j':
+        return j_;
+    case 'k':
+        return k_;
+    case 'l':
+        return l_;
+    case 'm':
+        return m_;
+    case 'n':
+        return n_;
+    case 'o':
+        return o_;
+    case 'p':
+        return p_;
+    case 'q':
+        return q_;
+    case 'r':
+        return r_;
+    case 's':
+        return s_;
+    case 't':
+        return t_;
+    case 'u':
+        return u_;
+    case 'v':
+        return v_;
+    case 'w':
+        return w_;
+    case 'x':
+        return x_;
+    case 'y':
+        return y_;
+    case 'z':
+        return z_;
+    case '0':
+        return _0_;
+    case '1':
+        return _1_;
+    case '2':
+        return _2_;
+    case '3':
+        return _3_;
+    case '4':
+        return _4_;
+    case '5':
+        return _5_;
+    case '6':
+        return _6_;
+    case '7':
+        return _7_;
+    case '8':
+        return _8_;
+    case '9':
+        return _9_;
+
+    /* This was previously implemented with unicode escape codes, however, that
+     * seemed to cause compilation issues. Which is strange because others reported
+     * compilation issues with the actual unicode characters. I'm not sure a good
+     * universal way to resolve this.
+     *
+     * Additionally, the ♂/♀ symbols don't render properly on the flipper. Would
+     * need to create a custom image/icon somehow, otherwise its nonobvious that
+     * the traded pokemon would have this symbol in their name.
+     */
+
+    case '\201':
+        return MALE_;
+    case '\200':
+        return FEMALE_;
+    default:
+        return TERM_;
+    }
+}
+
+int pokemon_encoded_to_char(char byte) {
+    switch(byte) {
+    case A_:
+        return 'A';
+    case B_:
+        return 'B';
+    case C_:
+        return 'C';
+    case D_:
+        return 'D';
+    case E_:
+        return 'E';
+    case F_:
+        return 'F';
+    case G_:
+        return 'G';
+    case H_:
+        return 'H';
+    case I_:
+        return 'I';
+    case J_:
+        return 'J';
+    case K_:
+        return 'K';
+    case L_:
+        return 'L';
+    case M_:
+        return 'M';
+    case N_:
+        return 'N';
+    case O_:
+        return 'O';
+    case P_:
+        return 'P';
+    case Q_:
+        return 'Q';
+    case R_:
+        return 'R';
+    case S_:
+        return 'S';
+    case T_:
+        return 'T';
+    case U_:
+        return 'U';
+    case V_:
+        return 'V';
+    case W_:
+        return 'W';
+    case X_:
+        return 'X';
+    case Y_:
+        return 'Y';
+    case Z_:
+        return 'Z';
+    case a_:
+        return 'a';
+    case b_:
+        return 'b';
+    case c_:
+        return 'c';
+    case d_:
+        return 'd';
+    case e_:
+        return 'e';
+    case f_:
+        return 'f';
+    case g_:
+        return 'g';
+    case h_:
+        return 'h';
+    case i_:
+        return 'i';
+    case j_:
+        return 'j';
+    case k_:
+        return 'k';
+    case l_:
+        return 'l';
+    case m_:
+        return 'm';
+    case n_:
+        return 'n';
+    case o_:
+        return 'o';
+    case p_:
+        return 'p';
+    case q_:
+        return 'q';
+    case r_:
+        return 'r';
+    case s_:
+        return 's';
+    case t_:
+        return 't';
+    case u_:
+        return 'u';
+    case v_:
+        return 'v';
+    case w_:
+        return 'w';
+    case x_:
+        return 'x';
+    case y_:
+        return 'y';
+    case z_:
+        return 'z';
+    case _0_:
+        return '0';
+    case _1_:
+        return '1';
+    case _2_:
+        return '2';
+    case _3_:
+        return '3';
+    case _4_:
+        return '4';
+    case _5_:
+        return '5';
+    case _6_:
+        return '6';
+    case _7_:
+        return '7';
+    case _8_:
+        return '8';
+    case _9_:
+        return '9';
+
+    /* This was previously implemented with unicode escape codes, however, that
+     * seemed to cause compilation issues. Which is strange because others reported
+     * compilation issues with the actual unicode characters. I'm not sure a good
+     * universal way to resolve this.
+     *
+     * Additionally, the ♂/♀ symbols don't render properly on the flipper. Would
+     * need to create a custom image/icon somehow, otherwise its nonobvious that
+     * the traded pokemon would have this symbol in their name.
+     */
+    case MALE_:
+        return '\201';
+    case FEMALE_:
+        return '\200';
+    default:
+        return '\0';
+    }
+}
+
+/* encode n bytes, any currently noninputtable characters are set with TERM_ */
+void pokemon_str_to_encoded_array(uint8_t* dest, char* src, size_t n) {
+    for(; n > 0; n--) {
+        *dest = pokemon_char_to_encoded(*src);
+        dest++;
+        src++;
+    }
+}
+
+/* decode n bytes, any currently noninputtable characters are set with '\0' */
+void pokemon_encoded_array_to_str(char* dest, uint8_t* src, size_t n) {
+    for(; n > 0; n--) {
+        *dest = pokemon_encoded_to_char(*src);
+        dest++;
+        src++;
+    }
+}

+ 110 - 0
pokemon_char_encode.h

@@ -0,0 +1,110 @@
+#ifndef POKEMON_CHAR_ENCODE_H
+#define POKEMON_CHAR_ENCODE_H
+/* NOTE: These map to the Gen 1 character set! */
+/* NOTE: These map to English */
+/* TODO: It may make more sense to put this in a const array as a LUT,
+ * e.g. t['F'], t['l'], t['i'], t['p'], t['e'], t['r'], t['\0']
+ * As this could be an easier translation for each letter to build a string
+ * to set names and things on the fly in the flipper. Need to explore that.
+ * once I get to that point.
+ */
+#define TERM_ 0x50
+#define SPACE_ 0x7f
+#define A_ 0x80
+#define B_ 0x81
+#define C_ 0x82
+#define D_ 0x83
+#define E_ 0x84
+#define F_ 0x85
+#define G_ 0x86
+#define H_ 0x87
+#define I_ 0x88
+#define J_ 0x89
+#define K_ 0x8a
+#define L_ 0x8b
+#define M_ 0x8c
+#define N_ 0x8d
+#define O_ 0x8e
+#define P_ 0x8f
+#define Q_ 0x90
+#define R_ 0x91
+#define S_ 0x92
+#define T_ 0x93
+#define U_ 0x94
+#define V_ 0x95
+#define W_ 0x96
+#define X_ 0x97
+#define Y_ 0x98
+#define Z_ 0x99
+#define O_PAREN_ 0x9a
+#define C_PAREN_ 0x9b
+#define COLON_ 0x9c
+#define SEMI_ 0x9d
+#define O_BRACKET_ 0x9e
+#define C_BRACKET_ 0x9f
+#define a_ 0xa0
+#define b_ 0xa1
+#define c_ 0xa2
+#define d_ 0xa3
+#define e_ 0xa4
+#define f_ 0xa5
+#define g_ 0xa6
+#define h_ 0xa7
+#define i_ 0xa8
+#define j_ 0xa9
+#define k_ 0xaa
+#define l_ 0xab
+#define m_ 0xac
+#define n_ 0xad
+#define o_ 0xae
+#define p_ 0xaf
+#define q_ 0xb0
+#define r_ 0xb1
+#define s_ 0xb2
+#define t_ 0xb3
+#define u_ 0xb4
+#define v_ 0xb5
+#define w_ 0xb6
+#define x_ 0xb7
+#define y_ 0xb8
+#define z_ 0xb9
+#define e_ACCENT_ 0xba
+#define d_TICK_ 0xbb
+#define l_TICK_ 0xbc
+#define s_TICK_ 0xbd
+#define t_TICK_ 0xbe
+#define v_TICK_ 0xbf
+#define S_QUOTE_ 0xe0
+#define PK_ 0xe1
+#define MN_ 0xe2
+#define DASH_ 0xe3
+#define r_TICK_ 0xe4
+#define m_TICK_ 0xe5
+#define QUESTION_ 0xe6
+#define EXCLAIM_ 0xe7
+#define PERIOD_ 0xe8
+#define R_ARR_ 0xec
+#define D_ARR_ 0xee
+#define MALE_ 0xef
+#define FEMALE_ 0xf5
+#define _0_ 0xf6
+#define _1_ 0xf7
+#define _2_ 0xf8
+#define _3_ 0xf9
+#define _4_ 0xfa
+#define _5_ 0xfb
+#define _6_ 0xfc
+#define _7_ 0xfd
+#define _8_ 0xfe
+#define _9_ 0xff
+
+#include <stdint.h>
+#include <stddef.h>
+
+char pokemon_char_to_encoded(int byte);
+int pokemon_encoded_to_char(char byte);
+
+void pokemon_str_to_encoded_array(uint8_t* dest, char* src, size_t n);
+void pokemon_encoded_array_to_str(char* dest, uint8_t* src, size_t n);
+
+#endif // POKEMON_CHAR_ENCODE_H

+ 35 - 111
pokemon_data.h

@@ -3,117 +3,30 @@
 
 #pragma once
 
-/* NOTE: These map to the Gen 1 character set! */
-/* TODO: It may make more sense to put this in a const array as a LUT,
- * e.g. t['F'], t['l'], t['i'], t['p'], t['e'], t['r'], t['\0']
- * As this could be an easier translation for each letter to build a string
- * to set names and things on the fly in the flipper. Need to explore that.
- * once I get to that point.
+/* The struct is laid out exactly as the data trasfer that gets sent for trade
+ * information. It has to be packed in order to not have padding in the Flipper.
+ * Packing is always potentially filled with pitfalls, however this has worked
+ * in testing without issue and this code isn't meant to be portable.
  */
-#define TERM_ 0x50
-#define SPACE_ 0x7f
-#define A_ 0x80
-#define B_ 0x81
-#define C_ 0x82
-#define D_ 0x83
-#define E_ 0x84
-#define F_ 0x85
-#define G_ 0x86
-#define H_ 0x87
-#define I_ 0x88
-#define J_ 0x89
-#define K_ 0x8a
-#define L_ 0x8b
-#define M_ 0x8c
-#define N_ 0x8d
-#define O_ 0x8e
-#define P_ 0x8f
-#define Q_ 0x90
-#define R_ 0x91
-#define S_ 0x92
-#define T_ 0x93
-#define U_ 0x94
-#define V_ 0x95
-#define W_ 0x96
-#define X_ 0x97
-#define Y_ 0x98
-#define Z_ 0x99
-#define O_PAREN_ 0x9a
-#define C_PAREN_ 0x9b
-#define COLON_ 0x9c
-#define SEMI_ 0x9d
-#define O_BRACKET_ 0x9e
-#define C_BRACKET_ 0x9f
-#define a_ 0xa0
-#define b_ 0xa1
-#define c_ 0xa2
-#define d_ 0xa3
-#define e_ 0xa4
-#define f_ 0xa5
-#define g_ 0xa6
-#define h_ 0xa7
-#define i_ 0xa8
-#define j_ 0xa9
-#define k_ 0xaa
-#define l_ 0xab
-#define m_ 0xac
-#define n_ 0xad
-#define o_ 0xae
-#define p_ 0xaf
-#define q_ 0xb0
-#define r_ 0xb1
-#define s_ 0xb2
-#define t_ 0xb3
-#define u_ 0xb4
-#define v_ 0xb5
-#define w_ 0xb6
-#define x_ 0xb7
-#define y_ 0xb8
-#define z_ 0xb9
-#define e_ACCENT_ 0xba
-#define d_TICK_ 0xbb
-#define l_TICK_ 0xbc
-#define s_TICK_ 0xbd
-#define t_TICK_ 0xbe
-#define v_TICK_ 0xbf
-#define S_QUOTE_ 0xe0
-#define PK_ 0xe1
-#define MN_ 0xe2
-#define DASH_ 0xe3
-#define r_TICK_ 0xe4
-#define m_TICK_ 0xe5
-#define QUESTION_ 0xe6
-#define EXCLAIM_ 0xe7
-#define PERIOD_ 0xe8
-#define R_ARR_ 0xec
-#define D_ARR_ 0xee
-#define MALE_ 0xef
-#define FEMALE_ 0xf5
-#define _0_ 0xf6
-#define _1_ 0xf7
-#define _2_ 0xf8
-#define _3_ 0xf9
-#define _4_ 0xfa
-#define _5_ 0xfb
-#define _6_ 0xfc
-#define _7_ 0xfd
-#define _8_ 0xfe
-#define _9_ 0xff
 
 /* NOTE: These are all opposite endianness on the flipper than they are in the
- * GB/Z80. e.g. a uint16_t value of 0x2c01 translates to 0x012c. Does flipper
- * API have calls to swap endianness?
+ * GB/Z80. e.g. a uint16_t value of 0x2c01 translates to 0x012c.
+ * Need to use __builtin_bswap16(val) to switch between Flipper and Pokemon.
  */
 struct __attribute__((__packed__)) pokemon_structure {
-    uint8_t species;
-    uint16_t hp;
+    uint8_t index;
+    uint16_t hp; // Calculated from level
+    /* Level is normally calculated from exp, however, level is more human
+     * readable/digestable compared to exp. Therefore, we set legel and then
+     * from that calculate, (Max)HP, ATK, DEF, SPD, SPC.
+     */
     uint8_t level;
-    uint8_t status_condition;
-    uint8_t type[2];
-    uint8_t catch_held;
+    uint8_t status_condition; // Do you really want to trade a Poisoned pokemon?
+    uint8_t type[2]; // Pokemon with a single type just repeat the type twice
+    uint8_t catch_held; // Unsure if this has any effect in Gen 1
     uint8_t move[4];
-    uint16_t orig_trainer;
-    uint8_t exp[3];
+    uint16_t ot_id;
+    uint8_t exp[3]; // Calculated from level
     uint16_t hp_ev;
     uint16_t atk_ev;
     uint16_t def_ev;
@@ -121,23 +34,34 @@ struct __attribute__((__packed__)) pokemon_structure {
     uint16_t special_ev;
     uint16_t iv;
     uint8_t move_pp[4];
-    uint8_t level_again;
-    uint16_t max_hp;
-    uint16_t atk;
-    uint16_t def;
-    uint16_t spd;
-    uint16_t special;
+    uint8_t level_again; // Copy of level
+    uint16_t max_hp; // Calculated from level
+    uint16_t atk; // Calculated from level
+    uint16_t def; // Calculated from level
+    uint16_t spd; // Calculated from level
+    uint16_t special; // Calculated from level
 };
 
 struct __attribute__((__packed__)) name {
+    /* Reused a few times, but in Gen I, all name strings are 11 bytes in memory.
+     * At most, 10 symbols and a TERM_ byte.
+     * Note that some strings must be shorter than 11.
+     */
     unsigned char str[11];
 };
 
 struct __attribute__((__packed__)) trade_data_block {
     unsigned char trainer_name[11];
     uint8_t party_cnt;
-    uint8_t party_members[7]; // Unsure if last byte always needs to be 0xff for terminator
+    /* Only the first pokemon is ever used even though there are 7 bytes here.
+     * If the remaining 6 bytes are _not_ 0xff, then the trade window renders
+     * garbage for the Flipper's party.
+     */
+    uint8_t party_members[7];
+    /* Only the first pokemon is set up, even though there are 6 total party members */
     struct pokemon_structure party[6];
+    /* Only the first pokemon has an OT name and nickname even though there are 6 members */
+    /* OT name should not exceed 7 chars! */
     struct name ot_name[6];
     struct name nickname[6];
 };

+ 59 - 0
scenes/pokemon_level.c

@@ -0,0 +1,59 @@
+#include <furi.h>
+#include <gui/modules/text_input.h>
+#include <gui/view_dispatcher.h>
+#include <stdlib.h>
+
+#include "../pokemon_app.h"
+#include "pokemon_menu.h"
+
+static char level_buf[4];
+
+static bool select_level_input_validator(const char* text, FuriString* error, void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    int level_val;
+    bool rc = true;
+
+    level_val = atoi(text);
+    if(level_val < 2 || level_val > 100) {
+        furi_string_printf(error, "Level must\nbe a number\nbetween\n2-100!\n");
+        rc = false;
+    } else {
+        pokemon_fap->trade_block->party[0].level = level_val;
+        /* XXX: Need to recalculate other stats with level updated! */
+    }
+
+    return rc;
+}
+
+static void select_level_input_callback(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    pokemon_trade_block_recalculate_stats_from_level(pokemon_fap);
+    scene_manager_previous_scene(pokemon_fap->scene_manager);
+}
+
+void select_level_scene_on_exit(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewMainMenu);
+    view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}
+
+void select_level_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    text_input_reset(pokemon_fap->text_input);
+    text_input_set_validator(pokemon_fap->text_input, select_level_input_validator, pokemon_fap);
+    text_input_set_result_callback(
+        pokemon_fap->text_input,
+        select_level_input_callback,
+        pokemon_fap,
+        level_buf,
+        sizeof(level_buf),
+        true);
+    text_input_set_header_text(pokemon_fap->text_input, "Enter level (numbers only):");
+
+    view_dispatcher_add_view(
+        pokemon_fap->view_dispatcher, AppViewOpts, text_input_get_view(pokemon_fap->text_input));
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}

+ 9 - 0
scenes/pokemon_level.h

@@ -0,0 +1,9 @@
+#ifndef POKEMON_LEVEL_H
+#define POKEMON_LEVEL_H
+
+#pragma once
+
+void select_level_scene_on_enter(void* context);
+void select_level_scene_on_exit(void* context);
+
+#endif // POKEMON_LEVEL_H

+ 164 - 0
scenes/pokemon_menu.c

@@ -0,0 +1,164 @@
+#include "../pokemon_app.h"
+#include "../pokemon_char_encode.h"
+
+#include "pokemon_menu.h"
+#include "pokemon_select.h"
+#include "pokemon_nickname.h"
+#include "pokemon_level.h"
+#include "pokemon_move.h"
+#include "pokemon_type.h"
+#include "pokemon_stats.h"
+#include "pokemon_ot_id.h"
+#include "pokemon_ot_name.h"
+#include "pokemon_trade.h"
+
+static void scene_change_from_main_cb(void* context, uint32_t index) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    /* Set scene state to the current index so we can have that element highlighted when
+     * we return.
+     */
+    scene_manager_set_scene_state(pokemon_fap->scene_manager, MainMenuScene, index);
+    scene_manager_next_scene(pokemon_fap->scene_manager, index);
+}
+
+bool main_menu_back_event_callback(void* context) {
+    furi_assert(context);
+    PokemonFap* pokemon_fap = context;
+    return scene_manager_handle_back_event(pokemon_fap->scene_manager);
+}
+
+void main_menu_scene_on_enter(void* context) {
+    char buf[32];
+    char name_buf[11]; // All name buffers are 11 bytes at most, including term
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    /* Clear the scene state of the Move scene since that is used to set the
+     * highlighted meny item.
+     */
+    scene_manager_set_scene_state(pokemon_fap->scene_manager, SelectMoveScene, 0);
+
+    submenu_reset(pokemon_fap->submenu);
+
+    snprintf(
+        buf,
+        sizeof(buf),
+        "Pokemon:   %s",
+        pokemon_fap->pokemon_table[pokemon_fap->curr_pokemon].name);
+    submenu_add_item(
+        pokemon_fap->submenu, buf, SelectPokemonScene, scene_change_from_main_cb, pokemon_fap);
+    pokemon_encoded_array_to_str(
+        name_buf, (uint8_t*)pokemon_fap->trade_block->nickname, sizeof(name_buf));
+    snprintf(buf, sizeof(buf), "Nickname:  %s", name_buf);
+    submenu_add_item(
+        pokemon_fap->submenu, buf, SelectNicknameScene, scene_change_from_main_cb, pokemon_fap);
+    snprintf(buf, sizeof(buf), "Level:           %d", pokemon_fap->trade_block->party[0].level);
+    submenu_add_item(
+        pokemon_fap->submenu,
+        buf,
+        SelectLevelScene,
+        scene_change_from_main_cb,
+        pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu,
+        "Select Moves",
+        SelectMoveScene,
+        scene_change_from_main_cb,
+        pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu,
+        "Select Types",
+        SelectTypeScene,
+        scene_change_from_main_cb,
+        pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu,
+        stats_text[pokemon_fap->curr_stats],
+        SelectStatsScene,
+        scene_change_from_main_cb,
+        pokemon_fap);
+    snprintf(
+        buf,
+        sizeof(buf),
+        "OT ID#:          %05d",
+        __builtin_bswap16(pokemon_fap->trade_block->party[0].ot_id));
+    submenu_add_item(
+        pokemon_fap->submenu, buf, SelectOTIDScene, scene_change_from_main_cb, pokemon_fap);
+    pokemon_encoded_array_to_str(
+        name_buf, (uint8_t*)pokemon_fap->trade_block->ot_name, sizeof(name_buf));
+    snprintf(buf, sizeof(buf), "OT Name:      %s", name_buf);
+    submenu_add_item(
+        pokemon_fap->submenu, buf, SelectOTNameScene, scene_change_from_main_cb, pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu, "Trade PKMN", TradeScene, scene_change_from_main_cb, pokemon_fap);
+
+    submenu_set_selected_item(
+        pokemon_fap->submenu,
+        scene_manager_get_scene_state(pokemon_fap->scene_manager, MainMenuScene));
+
+    view_dispatcher_set_navigation_event_callback(
+        pokemon_fap->view_dispatcher, main_menu_back_event_callback);
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewMainMenu);
+}
+
+bool null_scene_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void null_scene_on_exit(void* context) {
+    UNUSED(context);
+}
+
+void (*const pokemon_scene_on_enter_handlers[])(void*) = {
+    main_menu_scene_on_enter,
+    select_pokemon_scene_on_enter,
+    select_nickname_scene_on_enter,
+    select_level_scene_on_enter,
+    select_move_scene_on_enter,
+    select_move_index_scene_on_enter,
+    select_move_set_scene_on_enter,
+    select_type_scene_on_enter,
+    select_stats_scene_on_enter,
+    select_ot_id_scene_on_enter,
+    select_ot_name_scene_on_enter,
+    trade_scene_on_enter,
+};
+
+void (*const pokemon_scene_on_exit_handlers[])(void*) = {
+    null_scene_on_exit,
+    select_pokemon_scene_on_exit,
+    select_nickname_scene_on_exit,
+    select_level_scene_on_exit,
+    null_scene_on_exit,
+    null_scene_on_exit,
+    null_scene_on_exit,
+    select_type_scene_on_exit,
+    null_scene_on_exit,
+    select_ot_id_scene_on_exit,
+    select_ot_name_scene_on_exit,
+    null_scene_on_exit,
+};
+
+bool (*const pokemon_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
+    null_scene_on_event,
+    null_scene_on_event,
+    null_scene_on_event,
+    null_scene_on_event,
+    null_scene_on_event,
+    null_scene_on_event,
+    null_scene_on_event,
+    null_scene_on_event,
+    null_scene_on_event,
+    null_scene_on_event,
+    null_scene_on_event,
+    null_scene_on_event,
+};
+
+const SceneManagerHandlers pokemon_scene_manager_handlers = {
+    .on_enter_handlers = pokemon_scene_on_enter_handlers,
+    .on_exit_handlers = pokemon_scene_on_exit_handlers,
+    .on_event_handlers = pokemon_scene_on_event_handlers,
+    .scene_num = SceneCount,
+};

+ 26 - 0
scenes/pokemon_menu.h

@@ -0,0 +1,26 @@
+#ifndef POKEMON_MENU_H
+#define POKEMON_MENU_H
+
+#pragma once
+
+#include <gui/scene_manager.h>
+
+typedef enum {
+    MainMenuScene,
+    SelectPokemonScene,
+    SelectNicknameScene,
+    SelectLevelScene,
+    SelectMoveScene,
+    SelectMoveIndexScene,
+    SelectMoveSetScene,
+    SelectTypeScene,
+    SelectStatsScene,
+    SelectOTIDScene,
+    SelectOTNameScene,
+    TradeScene,
+    SceneCount,
+} AppScene;
+
+extern const SceneManagerHandlers pokemon_scene_manager_handlers;
+
+#endif // POKEMON_MENU_H

+ 148 - 0
scenes/pokemon_move.c

@@ -0,0 +1,148 @@
+#include <gui/modules/submenu.h>
+#include <gui/scene_manager.h>
+#include <stdio.h>
+
+#include "../pokemon_app.h"
+#include "pokemon_menu.h"
+
+static void select_move_selected_callback(void* context, uint32_t index) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    uint32_t move = scene_manager_get_scene_state(pokemon_fap->scene_manager, SelectMoveScene);
+
+    if(index == UINT32_MAX) {
+        pokemon_fap->trade_block->party[0].move[move] =
+            pokemon_fap->pokemon_table[pokemon_fap->curr_pokemon].move[move];
+    } else {
+        pokemon_fap->trade_block->party[0].move[move] = (uint8_t)index;
+    }
+
+    /* Move back to move menu */
+    scene_manager_search_and_switch_to_previous_scene(pokemon_fap->scene_manager, SelectMoveScene);
+}
+
+static void select_move_index_callback(void* context, uint32_t index) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    /* Move to next scene */
+    scene_manager_set_scene_state(pokemon_fap->scene_manager, SelectMoveIndexScene, index);
+    scene_manager_next_scene(pokemon_fap->scene_manager, SelectMoveSetScene);
+}
+
+static void select_move_number_callback(void* context, uint32_t index) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    /* Move to move index scene, save which move number we're selecting,
+     * This doubles as the move slot we're going to write to later.
+     */
+    scene_manager_set_scene_state(pokemon_fap->scene_manager, SelectMoveScene, index);
+    scene_manager_next_scene(pokemon_fap->scene_manager, SelectMoveIndexScene);
+}
+
+void select_move_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    uint8_t* pkmn_move = pokemon_fap->trade_block->party[0].move;
+    char buf[64];
+
+    submenu_reset(pokemon_fap->submenu);
+
+    snprintf(
+        buf,
+        sizeof(buf),
+        "Move 1:         %s",
+        pokemon_named_list_get_name_from_index(pokemon_fap->move_list, pkmn_move[0]));
+    submenu_add_item(pokemon_fap->submenu, buf, 0, select_move_number_callback, pokemon_fap);
+    snprintf(
+        buf,
+        sizeof(buf),
+        "Move 2:        %s",
+        pokemon_named_list_get_name_from_index(pokemon_fap->move_list, pkmn_move[1]));
+    submenu_add_item(pokemon_fap->submenu, buf, 1, select_move_number_callback, pokemon_fap);
+    snprintf(
+        buf,
+        sizeof(buf),
+        "Move 3:        %s",
+        pokemon_named_list_get_name_from_index(pokemon_fap->move_list, pkmn_move[2]));
+    submenu_add_item(pokemon_fap->submenu, buf, 2, select_move_number_callback, pokemon_fap);
+    snprintf(
+        buf,
+        sizeof(buf),
+        "Move 4:        %s",
+        pokemon_named_list_get_name_from_index(pokemon_fap->move_list, pkmn_move[3]));
+    submenu_add_item(pokemon_fap->submenu, buf, 3, select_move_number_callback, pokemon_fap);
+
+    /* TODO: Add a "Default all moves" item? */
+
+    submenu_set_selected_item(
+        pokemon_fap->submenu,
+        scene_manager_get_scene_state(pokemon_fap->scene_manager, SelectMoveScene));
+
+    /* Clear cursor position on MoveIndex */
+    scene_manager_set_scene_state(pokemon_fap->scene_manager, SelectMoveIndexScene, 0);
+}
+
+void select_move_index_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    int i;
+    char letter[2] = {'\0'};
+    char buf[32];
+    int curr_pokemon = pokemon_fap->curr_pokemon;
+    uint32_t move_num = scene_manager_get_scene_state(pokemon_fap->scene_manager, SelectMoveScene);
+    uint8_t default_move = pokemon_fap->pokemon_table[curr_pokemon].move[move_num];
+    const NamedList* move_list = pokemon_fap->move_list;
+
+    submenu_reset(pokemon_fap->submenu);
+    /* The move list should always start with No Move, put that at the start
+     * for quick access.
+     */
+    submenu_add_item(
+        pokemon_fap->submenu,
+        move_list[0].name,
+        move_list[0].index,
+        select_move_selected_callback,
+        pokemon_fap);
+
+    /* Option to set move back to default */
+    snprintf(
+        buf,
+        sizeof(buf),
+        "Default [%s]",
+        pokemon_named_list_get_name_from_index(pokemon_fap->move_list, default_move));
+    submenu_add_item(
+        pokemon_fap->submenu, buf, UINT32_MAX, select_move_selected_callback, pokemon_fap);
+
+    /* Now, walk through the list and make a submenu item for each move's starting letter */
+    for(i = 1;; i++) {
+        if(move_list[i].name == NULL) break;
+        if(toupper(move_list[i].name[0]) != toupper(letter[0])) {
+            letter[0] = toupper(move_list[i].name[0]);
+            submenu_add_item(
+                pokemon_fap->submenu, letter, letter[0], select_move_index_callback, pokemon_fap);
+        }
+    }
+
+    submenu_set_selected_item(
+        pokemon_fap->submenu,
+        scene_manager_get_scene_state(pokemon_fap->scene_manager, SelectMoveIndexScene));
+}
+
+void select_move_set_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    int i;
+    char letter =
+        (char)scene_manager_get_scene_state(pokemon_fap->scene_manager, SelectMoveIndexScene);
+
+    /* Populate submenu with all moves that start with `letter` */
+    /* NOTE! Start with index of 1 in the move list since 0 should always be no move! */
+    submenu_reset(pokemon_fap->submenu);
+    for(i = 1;; i++) {
+        if(pokemon_fap->move_list[i].name == NULL) break;
+        if(toupper(pokemon_fap->move_list[i].name[0]) == toupper(letter)) {
+            submenu_add_item(
+                pokemon_fap->submenu,
+                pokemon_fap->move_list[i].name,
+                pokemon_fap->move_list[i].index,
+                select_move_selected_callback,
+                pokemon_fap);
+        }
+    }
+}

+ 12 - 0
scenes/pokemon_move.h

@@ -0,0 +1,12 @@
+#ifndef POKEMON_MOVE_H
+#define POKEMON_MOVE_H
+
+#pragma once
+
+void select_move_scene_on_enter(void* context);
+
+void select_move_index_scene_on_enter(void* context);
+
+void select_move_set_scene_on_enter(void* context);
+
+#endif // POKEMON_MOVE_H

+ 78 - 0
scenes/pokemon_nickname.c

@@ -0,0 +1,78 @@
+#include <ctype.h>
+#include <furi.h>
+#include <gui/modules/text_input.h>
+#include <gui/view_dispatcher.h>
+#include <stdlib.h>
+
+#include "../pokemon_app.h"
+#include "../pokemon_char_encode.h"
+#include "pokemon_menu.h"
+
+static char name_buf[11];
+
+/* NOTE:
+ * It would be nice if we could cleanly default to the pokemon's name as their
+ * nickname. The issue is that if you enter a blank line to text input, it does
+ * call this function, but returning true does nothing. However, I've found that
+ * if you check for the first char of the buffer being \0, you can then set the
+ * buffer and then return true. This has the effect of staying in the text_input
+ * screen, but, prepopulating the text entry with the buffer AND staying on the
+ * save button.
+ */
+static bool select_nickname_input_validator(const char* text, FuriString* error, void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    bool rc = true;
+
+    if(text[0] == '\0') {
+        /* Get the pokemon's name and populate our buffer with it */
+        /* XXX: Nidoran M/F are still a problem with this. */
+        pokemon_trade_block_set_default_name(name_buf, pokemon_fap, sizeof(name_buf));
+        return true;
+    }
+
+    if(rc == false) {
+        furi_string_printf(error, "Some error?");
+    } else {
+        /* Clear existing nickname in trade block*/
+        memset(pokemon_fap->trade_block->nickname, TERM_, sizeof(struct name));
+
+        /* Encode string to nickname */
+        pokemon_str_to_encoded_array(
+            (uint8_t*)pokemon_fap->trade_block->nickname, (char*)text, strlen(text));
+    }
+
+    return rc;
+}
+
+static void select_nickname_input_callback(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    scene_manager_previous_scene(pokemon_fap->scene_manager);
+}
+
+void select_nickname_scene_on_exit(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewMainMenu);
+    view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}
+
+void select_nickname_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    text_input_reset(pokemon_fap->text_input);
+    text_input_set_validator(
+        pokemon_fap->text_input, select_nickname_input_validator, pokemon_fap);
+    text_input_set_result_callback(
+        pokemon_fap->text_input,
+        select_nickname_input_callback,
+        pokemon_fap,
+        name_buf,
+        sizeof(name_buf),
+        true);
+    text_input_set_header_text(pokemon_fap->text_input, "Nickname (none for default)");
+
+    view_dispatcher_add_view(
+        pokemon_fap->view_dispatcher, AppViewOpts, text_input_get_view(pokemon_fap->text_input));
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}

+ 9 - 0
scenes/pokemon_nickname.h

@@ -0,0 +1,9 @@
+#ifndef POKEMON_NICKNAME_H
+#define POKEMON_NICKNAME_H
+
+#pragma once
+
+void select_nickname_scene_on_enter(void* context);
+void select_nickname_scene_on_exit(void* context);
+
+#endif // POKEMON_NICKNAME_H

+ 72 - 0
scenes/pokemon_ot_id.c

@@ -0,0 +1,72 @@
+#include <ctype.h>
+#include <furi.h>
+#include <gui/modules/text_input.h>
+#include <gui/view_dispatcher.h>
+#include <stdlib.h>
+
+#include "../pokemon_app.h"
+#include "pokemon_menu.h"
+
+static char ot_id_buf[6];
+
+static bool select_ot_id_input_validator(const char* text, FuriString* error, void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    int ot_id;
+    uint16_t ot_id_16;
+    bool rc = true;
+    unsigned int i;
+
+    /* Need to check each byte to ensure is not alpha. atoi returns 0 which is
+     * technically a valid ID, so we need to separately check for alpha chars.
+     */
+    for(i = 0; i < sizeof(ot_id_buf); i++) {
+        if(!isdigit((unsigned int)text[i])) {
+            if(text[i] == '\0') break;
+            rc = false;
+            break;
+        }
+    }
+
+    ot_id = atoi(text);
+    if(ot_id < 0 || ot_id > 65535 || rc == false) {
+        furi_string_printf(error, "OT ID must\nbe between\n0-65535!");
+        rc = false;
+    } else {
+        ot_id_16 = __builtin_bswap16((uint16_t)ot_id);
+        pokemon_fap->trade_block->party[0].ot_id = ot_id_16;
+    }
+
+    return rc;
+}
+
+static void select_ot_id_input_callback(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    scene_manager_previous_scene(pokemon_fap->scene_manager);
+}
+
+void select_ot_id_scene_on_exit(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewMainMenu);
+    view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}
+
+void select_ot_id_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    text_input_reset(pokemon_fap->text_input);
+    text_input_set_validator(pokemon_fap->text_input, select_ot_id_input_validator, pokemon_fap);
+    text_input_set_result_callback(
+        pokemon_fap->text_input,
+        select_ot_id_input_callback,
+        pokemon_fap,
+        ot_id_buf,
+        sizeof(ot_id_buf),
+        true);
+    text_input_set_header_text(pokemon_fap->text_input, "Enter OT ID (numbers only):");
+
+    view_dispatcher_add_view(
+        pokemon_fap->view_dispatcher, AppViewOpts, text_input_get_view(pokemon_fap->text_input));
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}

+ 9 - 0
scenes/pokemon_ot_id.h

@@ -0,0 +1,9 @@
+#ifndef POKEMON_OT_ID_H
+#define POKEMON_OT_ID_H
+
+#pragma once
+
+void select_ot_id_scene_on_enter(void* context);
+void select_ot_id_scene_on_exit(void* context);
+
+#endif // POKEMON_OT_ID_H

+ 73 - 0
scenes/pokemon_ot_name.c

@@ -0,0 +1,73 @@
+#include <ctype.h>
+#include <furi.h>
+#include <gui/modules/text_input.h>
+#include <gui/view_dispatcher.h>
+#include <stdlib.h>
+
+#include "../pokemon_app.h"
+#include "../pokemon_char_encode.h"
+#include "pokemon_menu.h"
+
+static char ot_name_buf[8];
+
+static bool select_ot_name_input_validator(const char* text, FuriString* error, void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    bool rc = true;
+    unsigned int i;
+
+    // XXX If no pokemon name, use default. How TF to have text input handle that?
+    // OT name is 7 chars max on gen 1, so only take that and then fill the rest of the 11 bytes with term
+
+    for(i = 0; i < sizeof(ot_name_buf); i++) {
+        if(!isalnum((unsigned int)text[i])) {
+            if(text[i] == '\0') break;
+            rc = false;
+            break;
+        }
+    }
+
+    if(rc == false) {
+        furi_string_printf(error, "Some error?");
+    } else {
+        /* Clear existing OT Name in trade block*/
+        memset(pokemon_fap->trade_block->ot_name, TERM_, sizeof(struct name));
+
+        /* Encode string to OT Name */
+        pokemon_str_to_encoded_array(
+            (uint8_t*)pokemon_fap->trade_block->ot_name, (char*)text, strlen(text));
+    }
+
+    return rc;
+}
+
+static void select_ot_name_input_callback(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    scene_manager_previous_scene(pokemon_fap->scene_manager);
+}
+
+void select_ot_name_scene_on_exit(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewMainMenu);
+    view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}
+
+void select_ot_name_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    text_input_reset(pokemon_fap->text_input);
+    text_input_set_validator(pokemon_fap->text_input, select_ot_name_input_validator, pokemon_fap);
+    text_input_set_result_callback(
+        pokemon_fap->text_input,
+        select_ot_name_input_callback,
+        pokemon_fap,
+        ot_name_buf,
+        sizeof(ot_name_buf),
+        true);
+    text_input_set_header_text(pokemon_fap->text_input, "Enter OT Name");
+
+    view_dispatcher_add_view(
+        pokemon_fap->view_dispatcher, AppViewOpts, text_input_get_view(pokemon_fap->text_input));
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}

+ 9 - 0
scenes/pokemon_ot_name.h

@@ -0,0 +1,9 @@
+#ifndef POKEMON_OT_NAME_H
+#define POKEMON_OT_NAME_H
+
+#pragma once
+
+void select_ot_name_scene_on_enter(void* context);
+void select_ot_name_scene_on_exit(void* context);
+
+#endif // POKEMON_OT_NAME_H

+ 22 - 0
scenes/pokemon_select.c

@@ -0,0 +1,22 @@
+#include "../pokemon_app.h"
+
+void select_pokemon_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    // switch to select pokemon scene
+    // Note for the future, this might make sense to setup and teardown each view
+    // at runtime rather than at the start of the whole application
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewSelectPokemon);
+}
+
+void select_pokemon_scene_on_exit(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    /* If a new pokemon was selected, then recalculate all of the trade_block
+     * values for the first pokemon in the party.
+     */
+    /* XXX: Find a way to see if exit was caused by an OK or a Back input? */
+    if(pokemon_fap->pokemon_table[pokemon_fap->curr_pokemon].index !=
+       pokemon_fap->trade_block->party[0].index) {
+        pokemon_trade_block_recalculate(pokemon_fap);
+    }
+}

+ 9 - 0
scenes/pokemon_select.h

@@ -0,0 +1,9 @@
+#ifndef POKEMON_SELECT_H
+#define POKEMON_SELECT_H
+
+#pragma once
+
+void select_pokemon_scene_on_enter(void* context);
+void select_pokemon_scene_on_exit(void* context);
+
+#endif // POKEMON_SELECT_H

+ 41 - 0
scenes/pokemon_stats.c

@@ -0,0 +1,41 @@
+#include <gui/modules/submenu.h>
+
+#include "../pokemon_app.h"
+#include "pokemon_menu.h"
+
+const char* stats_text[6] = {
+    "Random IV, Zero EV",
+    "Random IV, Max EV / Level",
+    "Random IV, Max EV",
+    "Max IV, Zero EV",
+    "Max IV, Max EV / Level",
+    "Max IV, Max EV",
+};
+
+static void select_stats_selected_callback(void* context, uint32_t index) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    pokemon_fap->curr_stats = index;
+
+    pokemon_trade_block_recalculate_stats_from_level(pokemon_fap);
+
+    scene_manager_previous_scene(pokemon_fap->scene_manager);
+}
+
+void select_stats_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    submenu_reset(pokemon_fap->submenu);
+    submenu_add_item(
+        pokemon_fap->submenu, stats_text[0], 0, select_stats_selected_callback, pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu, stats_text[1], 1, select_stats_selected_callback, pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu, stats_text[2], 2, select_stats_selected_callback, pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu, stats_text[3], 3, select_stats_selected_callback, pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu, stats_text[4], 4, select_stats_selected_callback, pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu, stats_text[5], 5, select_stats_selected_callback, pokemon_fap);
+}

+ 9 - 0
scenes/pokemon_stats.h

@@ -0,0 +1,9 @@
+#ifndef POKEMON_STATS_H
+#define POKEMON_STATS_H
+
+#pragma once
+
+extern const char* stats_text[6];
+void select_stats_scene_on_enter(void* context);
+
+#endif // POKEMON_STATS_H

+ 9 - 0
scenes/pokemon_trade.c

@@ -0,0 +1,9 @@
+#include "../pokemon_app.h"
+
+void trade_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    // switch to select pokemon scene
+    // Note for the future, this might make sense to setup and teardown each view
+    // at runtime rather than at the start?
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewTrade);
+}

+ 8 - 0
scenes/pokemon_trade.h

@@ -0,0 +1,8 @@
+#ifndef POKEMON_TRADE_H
+#define POKEMON_TRADE_H
+
+#pragma once
+
+void trade_scene_on_enter(void* context);
+
+#endif // POKEMON_TRADE_H

+ 69 - 0
scenes/pokemon_type.c

@@ -0,0 +1,69 @@
+#include <gui/modules/variable_item_list.h>
+
+#include "../pokemon_app.h"
+#include "pokemon_menu.h"
+
+/* TODO: In the future I would like to be able to set the types and then
+ * require a "save" button to save them. This would require tracking of
+ * the two different VariableItems in a way that I don't know how to do
+ * yet with this interface.
+ * For now, selecting a type immediately updates the trade_block struct,
+ * requiring the user to press Back to go back. I would like to implement
+ * an OK press or something to save both. But thats a problem for another
+ * day.
+ */
+static void select_type_1_callback(VariableItem* item) {
+    PokemonFap* pokemon_fap = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, pokemon_fap->type_list[index].name);
+    pokemon_fap->trade_block->party[0].type[0] = pokemon_fap->type_list[index].index;
+}
+
+static void select_type_2_callback(VariableItem* item) {
+    PokemonFap* pokemon_fap = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, pokemon_fap->type_list[index].name);
+    pokemon_fap->trade_block->party[0].type[1] = pokemon_fap->type_list[index].index;
+}
+
+void select_type_scene_on_exit(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewMainMenu);
+    view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}
+
+void select_type_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+    VariableItem* type1;
+    VariableItem* type2;
+    int curr_pokemon_type1 = pokemon_fap->trade_block->party[0].type[0];
+    int curr_pokemon_type2 = pokemon_fap->trade_block->party[0].type[1];
+    int num_types = pokemon_named_list_get_num_elements(pokemon_fap->type_list);
+    const NamedList* type_list = pokemon_fap->type_list;
+
+    variable_item_list_reset(pokemon_fap->variable_item_list);
+
+    type1 = variable_item_list_add(
+        pokemon_fap->variable_item_list, "Type 1:", num_types, select_type_1_callback, pokemon_fap);
+    type2 = variable_item_list_add(
+        pokemon_fap->variable_item_list, "Type 2:", num_types, select_type_2_callback, pokemon_fap);
+
+    variable_item_set_current_value_index(
+        type1, pokemon_named_list_get_list_pos_from_index(type_list, curr_pokemon_type1));
+    variable_item_set_current_value_text(
+        type1, pokemon_named_list_get_name_from_index(type_list, curr_pokemon_type1));
+
+    variable_item_set_current_value_index(
+        type2, pokemon_named_list_get_list_pos_from_index(type_list, curr_pokemon_type2));
+    variable_item_set_current_value_text(
+        type2, pokemon_named_list_get_name_from_index(type_list, curr_pokemon_type2));
+
+    view_dispatcher_add_view(
+        pokemon_fap->view_dispatcher,
+        AppViewOpts,
+        variable_item_list_get_view(pokemon_fap->variable_item_list));
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}

+ 9 - 0
scenes/pokemon_type.h

@@ -0,0 +1,9 @@
+#ifndef POKEMON_TYPE_H
+#define POKEMON_TYPE_H
+
+#pragma once
+
+void select_type_scene_on_enter(void* context);
+void select_type_scene_on_exit(void* context);
+
+#endif // POKEMON_TYPE_H

+ 20 - 40
views/select_pokemon.c

@@ -1,11 +1,14 @@
 #include <gui/elements.h>
 #include <pokemon_icons.h>
 
+#include "../scenes/pokemon_menu.h"
 #include "../pokemon_app.h"
 
+int selected_pokemon;
+
 static void select_pokemon_render_callback(Canvas* canvas, void* model) {
     PokemonFap* pokemon_fap = *(PokemonFap**)model;
-    const uint8_t current_index = pokemon_fap->curr_pokemon;
+    const uint8_t current_index = selected_pokemon;
     char pokedex_num[5];
 
     snprintf(pokedex_num, sizeof(pokedex_num), "#%03d", current_index + 1);
@@ -25,7 +28,6 @@ static void select_pokemon_render_callback(Canvas* canvas, void* model) {
 
 static bool select_pokemon_input_callback(InputEvent* event, void* context) {
     PokemonFap* pokemon_fap = (PokemonFap*)context;
-    int pokemon_num = pokemon_fap->curr_pokemon;
     bool consumed = false;
 
     furi_assert(context);
@@ -36,22 +38,17 @@ static bool select_pokemon_input_callback(InputEvent* event, void* context) {
     switch(event->key) {
     /* Advance to next view with the selected pokemon */
     case InputKeyOk:
-        view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewTrade);
-        consumed = true;
-        break;
-
-    /* Return to the previous view */
-    case InputKeyBack:
-        view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, VIEW_NONE);
+        pokemon_fap->curr_pokemon = selected_pokemon;
+        scene_manager_previous_scene(pokemon_fap->scene_manager);
         consumed = true;
         break;
 
     /* Move back one through the pokedex listing */
     case InputKeyLeft:
-        if(pokemon_num == 0)
-            pokemon_num = 150;
+        if(selected_pokemon == 0)
+            selected_pokemon = 150;
         else
-            pokemon_num--;
+            selected_pokemon--;
         consumed = true;
         break;
 
@@ -59,19 +56,19 @@ static bool select_pokemon_input_callback(InputEvent* event, void* context) {
          * underflow.
          */
     case InputKeyDown:
-        if(pokemon_num >= 10)
-            pokemon_num -= 10;
+        if(selected_pokemon >= 10)
+            selected_pokemon -= 10;
         else
-            pokemon_num = 150;
+            selected_pokemon = 150;
         consumed = true;
         break;
 
     /* Move forward one through the pokedex listing */
     case InputKeyRight:
-        if(pokemon_num == 150)
-            pokemon_num = 0;
+        if(selected_pokemon == 150)
+            selected_pokemon = 0;
         else
-            pokemon_num++;
+            selected_pokemon++;
         consumed = true;
         break;
 
@@ -79,10 +76,10 @@ static bool select_pokemon_input_callback(InputEvent* event, void* context) {
          * overflow.
          */
     case InputKeyUp:
-        if(pokemon_num <= 140)
-            pokemon_num += 10;
+        if(selected_pokemon <= 140)
+            selected_pokemon += 10;
         else
-            pokemon_num = 0;
+            selected_pokemon = 0;
         consumed = true;
         break;
 
@@ -91,27 +88,12 @@ static bool select_pokemon_input_callback(InputEvent* event, void* context) {
         break;
     }
 
-    pokemon_fap->curr_pokemon = pokemon_num;
-
     return consumed;
 }
 
 void select_pokemon_enter_callback(void* context) {
-    furi_assert(context);
-    UNUSED(context);
-}
-
-bool select_pokemon_custom_callback(uint32_t event, void* context) {
     PokemonFap* pokemon_fap = (PokemonFap*)context;
-    UNUSED(event);
-    furi_assert(context);
-    view_dispatcher_send_custom_event(pokemon_fap->view_dispatcher, 0);
-    return true;
-}
-
-void select_pokemon_exit_callback(void* context) {
-    furi_assert(context);
-    UNUSED(context);
+    selected_pokemon = pokemon_fap->curr_pokemon;
 }
 
 View* select_pokemon_alloc(PokemonFap* pokemon_fap) {
@@ -127,13 +109,11 @@ View* select_pokemon_alloc(PokemonFap* pokemon_fap) {
     view_set_draw_callback(view, select_pokemon_render_callback);
     view_set_input_callback(view, select_pokemon_input_callback);
     view_set_enter_callback(view, select_pokemon_enter_callback);
-    view_set_custom_callback(view, select_pokemon_custom_callback);
-
-    view_set_exit_callback(view, select_pokemon_exit_callback);
     return view;
 }
 
 void select_pokemon_free(PokemonFap* pokemon_fap) {
     furi_assert(pokemon_fap);
+    view_free_model(pokemon_fap->select_view);
     view_free(pokemon_fap->select_view);
 }

+ 5 - 30
views/trade.c

@@ -155,20 +155,6 @@ static void trade_draw_callback(Canvas* canvas, void* model) {
     }
 }
 
-static bool trade_input_callback(InputEvent* event, void* context) {
-    bool consumed = false;
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
-
-    furi_assert(context);
-
-    if(event->type == InputTypePress && event->key == InputKeyBack) {
-        view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewSelectPokemon);
-        consumed = true;
-    }
-
-    return consumed;
-}
-
 uint32_t micros() {
     return DWT->CYCCNT / 64;
 }
@@ -230,7 +216,7 @@ byte getMenuResponse(byte in) {
 
 byte getTradeCentreResponse(byte in, void* context) {
     PokemonFap* pokemon_fap = (PokemonFap*)context;
-    uint8_t* trade_party_flat = (uint8_t*)pokemon_fap->trade_party;
+    uint8_t* trade_block_flat = (uint8_t*)pokemon_fap->trade_block;
     byte send = in;
 
     furi_assert(context);
@@ -274,7 +260,7 @@ byte getTradeCentreResponse(byte in, void* context) {
         if((in & 0xF0) != 0xF0) {
             counter = 0;
             INPUT_BLOCK[counter] = in;
-            send = trade_party_flat[counter];
+            send = trade_block_flat[counter];
             counter++;
             trade_centre_state = SENDING_DATA;
         }
@@ -282,7 +268,7 @@ byte getTradeCentreResponse(byte in, void* context) {
 
     case SENDING_DATA:
         INPUT_BLOCK[counter] = in;
-        send = trade_party_flat[counter];
+        send = trade_block_flat[counter];
         counter++;
         if(counter == 405) //TODO: replace with sizeof struct rather than static number
             trade_centre_state = SENDING_PATCH_DATA;
@@ -404,9 +390,6 @@ void trade_enter_callback(void* context) {
     pokemon_fap->connected = false;
     pokemon_fap->gameboy_status = GAMEBOY_INITIAL;
 
-    pokemon_fap->trade_party->party_members[0] =
-        pokemon_fap->pokemon_table[pokemon_fap->curr_pokemon].species;
-
     // B3 (Pin6) / SO (2)
     furi_hal_gpio_write(&GAME_BOY_SO, false);
     furi_hal_gpio_init(&GAME_BOY_SO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
@@ -428,14 +411,6 @@ void trade_enter_callback(void* context) {
     // furi_hal_gpio_init(&GAME_BOY_CLK, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
 }
 
-bool trade_custom_callback(uint32_t event, void* context) {
-    UNUSED(event);
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
-    furi_assert(context);
-    view_dispatcher_send_custom_event(pokemon_fap->view_dispatcher, 0);
-    return true;
-}
-
 void disconnect_pin(const GpioPin* pin) {
     furi_hal_gpio_init(pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
     furi_hal_gpio_write(pin, true);
@@ -461,9 +436,7 @@ View* trade_alloc(PokemonFap* pokemon_fap) {
         view, PokemonFap** model_fap, { *model_fap = pokemon_fap; }, false);
 
     view_set_draw_callback(view, trade_draw_callback);
-    view_set_input_callback(view, trade_input_callback);
     view_set_enter_callback(view, trade_enter_callback);
-    view_set_custom_callback(view, trade_custom_callback);
     view_set_exit_callback(view, trade_exit_callback);
 
     return view;
@@ -476,5 +449,7 @@ void trade_free(PokemonFap* pokemon_fap) {
     furi_hal_gpio_remove_int_callback(&GAME_BOY_CLK);
 
     disconnect_pin(&GAME_BOY_CLK);
+
+    view_free_model(pokemon_fap->trade_view);
     view_free(pokemon_fap->trade_view);
 }

Некоторые файлы не были показаны из-за большого количества измененных файлов