Skip to content

Commit

Permalink
Merge pull request #116 from savaughn/115-dvs-do-not-randomize-on-lin…
Browse files Browse the repository at this point in the history
…k-trades

removed dv randomization on trade/evolutions
  • Loading branch information
savaughn authored Nov 29, 2023
2 parents 048ddf8 + 57d6503 commit e51f39c
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 178 deletions.
Binary file modified .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ PLATFORM ?= PLATFORM_DESKTOP

# Define project variables
PROJECT_NAME ?= pokeromtrader
PROJECT_VERSION := 0.9.1
PROJECT_VERSION := 0.10.0
# prerelease or release
PROJECT_VERSION_TYPE ?= prerelease
PROJECT_BUILD_PATH ?= .
Expand Down
5 changes: 0 additions & 5 deletions include/pksavhelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,6 @@ static const struct pkmn_evolution_pair_data pkmn_evolution_pairs_gen2[MAX_SPECI
[PORYGON] = {.species_name = "PORYGON", .evolution_name = "PORYGON2", .species_index = PORYGON, .evolution_index = PORYGON2, .evolution_item = UPGRADE},
[SEADRA] = {.species_name = "SEADRA", .evolution_name = "KINGDRA", .species_index = SEADRA, .evolution_index = KINGDRA, .evolution_item = DRAGON_SCALE}};

static bool disable_random_DVs_on_trade = false;
static bool item_required_evolutions = true;

int error_handler(enum pksav_error error, const char *message);
void swap_party_pkmn_at_indices(struct pksav_gen2_save *pkmn_save, uint8_t pkmn_index1, uint8_t pkmn_index2); // TODO: Update for cross-generation
pksavhelper_error swap_pkmn_at_index_between_saves(PokemonSave *player1_save, PokemonSave *player2_save, uint8_t pkmn_party_index1, uint8_t pkmn_party_index2);
Expand All @@ -92,8 +89,6 @@ void evolve_party_pokemon_at_index(PokemonSave *pkmn_save, uint8_t pkmn_party_in
void generate_random_number_step(void);
void update_pkmn_DVs(PokemonSave *pkmn_save, uint8_t pkmn_party_index);
void update_pkmn_stats(PokemonSave *pkmn_save, uint8_t pkmn_party_index);
bool get_is_random_DVs_disabled(void);
void set_is_random_DVs_disabled(bool is_disabled);
void generate_rand_num_step(SaveGenerationType save_generation_type);
enum eligible_trade_status check_trade_eligibility(struct trainer_info *trainer, uint8_t pkmn_party_index);

Expand Down
23 changes: 8 additions & 15 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,25 @@

## Overview

Pokerom Trader is an open-source project that simplifies the process of trading Pokémon between two save files using the [PKSav](https://github.com/savaughn/pksav) C library. This graphical user interface (GUI) provides an intuitive way for Pokémon enthusiasts to transfer Pokémon between different save files or Pokémon game versions. This is not another save file editor. This replicates the in-game trading experience resulting in legal pokémon.
There is no backup system implemented yet. Make backup saves before using this on your own personal files, because this is still very early stage and has not been thoroughly tested.
Pokerom Trader is an open-source project that simplifies the process of trading Pokémon between two save files using the [PKSav](https://github.com/savaughn/pksav) C library. This graphical user interface (GUI) provides an intuitive way for Pokémon enthusiasts to transfer Pokémon between different save files. This is not another save file editor. This replicates the in-game trading experience resulting in legal pokémon.
There is no backup system implemented yet. Make backup saves before using this on your own personal files.

### Features
- Trade - Allows a user to trade Pokémon between save files
- Trade with NPCs - Allows a user to trade Pokémon with NPCs such as gym leaders or Red (future feature)
- Evolve - A shortcut for evolving Pokémon that only evolve through trading
- This replicates a trade, the evolution, and a trade back to OT
- Bill's PC - Allows a user to view and manage their Pokémon boxes and party (in progress)

### Settings
![settings_menu](https://github.com/savaughn/pokerom-trader/assets/25937456/72e15dca-461a-40eb-a095-cc8d079976c4)

- Change save files folder with absolute path to folder
- Default is ~/Library/PokeromTrader/saves (MacOS)
- Default is ~/.pokeromtrader/ (Linux)
- Default is \<UserProfile\>\\Documents\\PokeromTrader\\saves (Windows)
- Disable Random DVs on trade (default off) when on will retain the dvs of the Pokémon being traded or evolved.
- The in-game experience always randomizes DVs on trade. This is a bypass of the official experience.
- Reset to defaults - Resets all settings to default values

### Deep Dive
- DV randomization
- Random function is 1:1 with the in-game Random call converted from assembly to C which generates an add byte for both generations.
- The add byte is anded with 0xF to get the lower 4 bits of the random byte (i.e. 0 to 15).
- The DV for HP is calculated by taking the least significant bit of each DV (attack, defense, speed, special) and concatenating them together into a single byte.
- Stats calculations
- Stats calculation on evolution
- Calculated using formula from [bulbapedia](https://bulbapedia.bulbagarden.net/wiki/Stats).
- Based off of the Pokémon's base stats, level, DVs (stored or generated), and EVs all of which are read from the save file sram.
- Based off of the Pokémon's base stats, level, DVs, and EVs all of which are read from the save file sram.

## What's working

Expand All @@ -47,6 +39,8 @@ There is no backup system implemented yet. Make backup saves before using this o
## What's not working
- See [issues tab](https://github.com/savaughn/pokerom-trader/issues) for current bugs
- No JP Region support
- No Gen 3+ support
- No support for romhacks that change the save file structure (almost all of them)

## Discord
https://discord.gg/JUzzegS3AP
Expand Down Expand Up @@ -79,7 +73,6 @@ This project is licensed under the [MIT License](LICENSE).

## Acknowledgments

- [PKSav](https://github.com/ncorgan/pksav): Thank you to the creators and maintainers of PKSav for providing the library that makes this project possible.
- [Raylib](www.github.com/raysan5/raylib): Thank you to the creators and maintainers of Raylib for providing the library that makes this project possible.
- [Bulbapedia](https://bulbapedia.bulbagarden.net/wiki/Main_Page): Thank you to the creators and maintainers of Bulbapedia for providing the information that makes this project possible.
- [TextStudio](https://www.textstudio.com/logo/pokemon-3d-text-318): Used their text generator for stylized text.
Expand Down
5 changes: 0 additions & 5 deletions src/filehelper.c
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,6 @@ void init_settings_from_config(struct save_file_data *save_file_data)
strcpy((char *)save_file_data->save_dir, "DIR_NOT_SET");
}

set_is_random_DVs_disabled(strcmp(config_data.disable_random_ivs_on_trade, "false"));

free(save_file_dir);
}

Expand Down Expand Up @@ -749,9 +747,6 @@ void init_settings_from_config(struct save_file_data *save_file_data)
strcpy((char *)save_file_data->save_dir, "DIR_NOT_SET");
}

// Read and save the disable random setting from config.ini
set_is_random_DVs_disabled(strcmp(read_key_from_config("DISABLE_RANDOM_IVS_ON_TRADE"), "false"));

// malloc'd from read_key_from_config
free(config_save_path);
}
Expand Down
48 changes: 1 addition & 47 deletions src/pksavhelper.c
Original file line number Diff line number Diff line change
Expand Up @@ -284,15 +284,9 @@ void randomize_dvs(uint8_t *dv_array, SaveGenerationType save_generation_type)
}
/*************************************************************************/

// Randomize the DVs of a pokemon on trade
// Randomize the DVs of a pokemon
void update_pkmn_DVs(PokemonSave *pkmn_save, uint8_t pkmn_party_index)
{
// From settings menu to prevent changing DVs on trade
if (disable_random_DVs_on_trade)
{
printf("DVs not randomized on trade\n");
return;
}
// randomize the dvs on trade except for HP
uint8_t traded_pkmn_rand_dvs[PKSAV_NUM_GB_IVS] = {0};
randomize_dvs(traded_pkmn_rand_dvs, pkmn_save->save_generation_type);
Expand All @@ -310,12 +304,6 @@ void update_pkmn_DVs(PokemonSave *pkmn_save, uint8_t pkmn_party_index)
// Calculate and update the pokemon's stats based on its level, base stats, IVs, and EVs
void update_pkmn_stats(PokemonSave *pkmn_save, uint8_t pkmn_party_index)
{
// From settings menu to prevent changing DVs on trade
if (disable_random_DVs_on_trade)
{
printf("DVs not randomized on trade\n");
return;
}
// Get the pokemon's DVs
uint8_t pkmn_dvs[PKSAV_NUM_GB_IVS];
pksav_get_gb_IVs(&pkmn_save->save.gen1_save.pokemon_storage.p_party->party[pkmn_party_index].pc_data.iv_data, pkmn_dvs, sizeof(pkmn_dvs));
Expand Down Expand Up @@ -624,14 +612,6 @@ pksavhelper_error swap_pkmn_at_index_between_saves_cross_gen(PokemonSave *player
player_gen1->save.gen1_save.pokemon_storage.p_party->otnames[pkmn_party_index1][strlen(tmp_otname_gen2)] = 0x50;
player_gen2->save.gen2_save.pokemon_storage.p_party->otnames[pkmn_party_index2][strlen(tmp_otname_gen1)] = 0x50;

// Generate random DVs and assign them to the traded pokemen
update_pkmn_DVs(player_gen1, pkmn_party_index1);
update_pkmn_DVs(player_gen2, pkmn_party_index2);

// Update each pokemon's stats based on its level, base stats, DVs, and EVs
update_pkmn_stats(player_gen1, pkmn_party_index1);
update_pkmn_stats(player_gen2, pkmn_party_index2);

player_gen1->save.gen1_save.pokemon_storage.p_party->party[pkmn_party_index1].pc_data.current_hp = player_gen1->save.gen1_save.pokemon_storage.p_party->party[pkmn_party_index1].party_data.max_hp;
player_gen2->save.gen2_save.pokemon_storage.p_party->party[pkmn_party_index2].party_data.current_hp = player_gen2->save.gen2_save.pokemon_storage.p_party->party[pkmn_party_index2].party_data.max_hp;

Expand Down Expand Up @@ -788,14 +768,6 @@ pksavhelper_error swap_pkmn_at_index_between_saves(PokemonSave *player1_save, Po
player2_save->save.gen2_save.pokemon_storage.p_party_mail->party_mail_backup[pkmn_party_index2] = tmp_party_mail;
}

// Generate random DVs and assign them to the traded pokemen
update_pkmn_DVs(player1_save, pkmn_party_index1);
update_pkmn_DVs(player2_save, pkmn_party_index2);

// Update each pokemon's stats based on its level, base stats, DVs, and EVs
update_pkmn_stats(player1_save, pkmn_party_index1);
update_pkmn_stats(player2_save, pkmn_party_index2);

return error_none;
}

Expand Down Expand Up @@ -840,9 +812,6 @@ void evolve_party_pokemon_at_index(PokemonSave *pkmn_save, uint8_t pkmn_party_in
pkmn_save->save.gen1_save.pokemon_storage.p_party->party[pkmn_party_index].pc_data.catch_rate = pkmn_base_stats_gen1[evolution_index].catch_rate;
// Update condition to none
pkmn_save->save.gen1_save.pokemon_storage.p_party->party[pkmn_party_index].pc_data.condition = PKSAV_GB_CONDITION_NONE;
// TODO: Update moves based on learn set and level
// engine/pokemon/evos_moves.asm
// pkmn_save->save.gen1_save.pokemon_storage.p_party->party[pkmn_party_index].pc_data.moves;
}
else
{
Expand Down Expand Up @@ -876,20 +845,5 @@ void evolve_party_pokemon_at_index(PokemonSave *pkmn_save, uint8_t pkmn_party_in

// Update condition to none
pkmn_save->save.gen2_save.pokemon_storage.p_party->party[pkmn_party_index].party_data.condition = PKSAV_CONDITION_NONE;
// TODO: Update moves based on learn set and level
// engine/pokemon/evos_moves.asm
// pkmn_save->save.gen2_save.pokemon_storage.p_party->party[pkmn_party_index].pc_data.moves;
}
}

// Settings getter for random DVs on trade
bool get_is_random_DVs_disabled(void)
{
return disable_random_DVs_on_trade;
}

// Settings setter for random DVs on trade
void set_is_random_DVs_disabled(bool is_disabled)
{
disable_random_DVs_on_trade = is_disabled;
}
87 changes: 69 additions & 18 deletions src/screens/ChangeDirScreen.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,33 @@
#include <sys/errno.h>
#endif

static struct save_file_data *_save_file_data = NULL;
static bool show_reset_modal = false;
static bool has_reset_config = false;

void on_reset_modal_cancel(void)
{
show_reset_modal = false;
}

void on_reset_modal_submit(void)
{
#ifdef _WIN32
create_default_config();
#else
create_default_config(true);
#endif
init_settings_from_config(_save_file_data);
show_reset_modal = false;
has_reset_config = true;
}

void draw_change_dir(struct save_file_data *save_file_data, GameScreen *current_screen, Texture2D *settings_texture)
{
_save_file_data = save_file_data;
static bool editing_text = false;
static char input_text[MAX_FILE_PATH_CHAR];
static int text_size = 0;
static int char_count = 0;
static bool has_shown_placeholder = false;
static enum file_op_results file_op_result = FILE_OP_FAILURE;
char input_text_backup[MAX_FILE_PATH_CHAR];
Expand All @@ -19,21 +41,25 @@ void draw_change_dir(struct save_file_data *save_file_data, GameScreen *current_
SCREEN_BUTTON_NONE = -1,
SCREEN_BUTTON_BACK,
SCREEN_BUTTON_SAVE,
SCREEN_BUTTON_CLEAR
SCREEN_BUTTON_CLEAR,
BUTTON_RESET
};
static int selected_index = SCREEN_BUTTON_NONE;
const Rectangle input_box_rec = (Rectangle){50, SCREEN_HEIGHT / 2 + 5, SCREEN_WIDTH - 100, 40};
const uint8_t input_text_size = 15;
const Color settings_text_color = BLACK;
const Color settings_text_color_selected = LIGHTGRAY;

text_size = strlen(input_text);
char_count = strlen(input_text);
strcpy(input_text_backup, (char *)save_file_data->save_dir);

// Placeholder Text
if (!editing_text && !has_shown_placeholder)
if (!editing_text && !has_shown_placeholder || has_reset_config)
{
strcpy(input_text, (char *)save_file_data->save_dir);
text_size = strlen(input_text);
char_count = strlen(input_text);
has_shown_placeholder = true;
has_reset_config = false;
}

if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
Expand All @@ -54,17 +80,17 @@ void draw_change_dir(struct save_file_data *save_file_data, GameScreen *current_
has_shown_placeholder = true;
int key = GetCharPressed();
int backspace = GetKeyPressed();
if (key >= 32 && key <= 125 && text_size < MAX_FILE_PATH_CHAR)
if (key >= 32 && key <= 125 && char_count < MAX_FILE_PATH_CHAR)
{
// Append character to input_text
input_text[text_size] = (char)key;
text_size++;
input_text[char_count] = (char)key;
char_count++;
}
else if ((key == KEY_BACKSPACE || backspace == KEY_BACKSPACE) && text_size > 0)
else if ((key == KEY_BACKSPACE || backspace == KEY_BACKSPACE) && char_count > 0)
{
// Remove last character
text_size--;
input_text[text_size] = '\0';
char_count--;
input_text[char_count] = '\0';
}

// Finish editing by pressing Enter
Expand Down Expand Up @@ -100,25 +126,39 @@ void draw_change_dir(struct save_file_data *save_file_data, GameScreen *current_
input_box_rec.x + 12 + MeasureText(input_text, input_text_size), input_box_rec.y + 30, BLACK);
}

// Draw reset default config button
const char *reset_config_text = "Reset to defaults";
Rectangle reset_config_rec = (Rectangle){input_box_rec.x, input_box_rec.y + 25 + input_box_rec.height - 5, MeasureText(reset_config_text, 20) + 10, 30};
DrawText(reset_config_text, reset_config_rec.x, reset_config_rec.y, 20, selected_index == BUTTON_RESET ? settings_text_color_selected : settings_text_color);

// Draw the save button
const Rectangle save_button_rec = (Rectangle){NEXT_BUTTON_X - 15, NEXT_BUTTON_Y - 30, BUTTON_WIDTH, BUTTON_HEIGHT};
if (selected_index == SCREEN_BUTTON_SAVE)
{
DrawText("Save!", save_button_rec.x, save_button_rec.y, 30, LIGHTGRAY);
DrawText("Save!", save_button_rec.x, save_button_rec.y, 20, LIGHTGRAY);
}
else
{
DrawText("Save!", save_button_rec.x, save_button_rec.y, 30, text_size ? BLACK : LIGHTGRAY);
DrawText("Save!", save_button_rec.x, save_button_rec.y, 20, char_count ? BLACK : LIGHTGRAY);
}

if (errno != 0)
error_handler(0, TextFormat("error writing config %d", errno));

const Rectangle back_button_rec = (Rectangle){BACK_BUTTON_X - 15, BACK_BUTTON_Y - 30, BUTTON_WIDTH, BUTTON_HEIGHT};
DrawText("< Back", back_button_rec.x, back_button_rec.y, 30, selected_index == SCREEN_BUTTON_BACK ? LIGHTGRAY : BLACK);
DrawText("< Back", back_button_rec.x, back_button_rec.y, 20, selected_index == SCREEN_BUTTON_BACK ? LIGHTGRAY : BLACK);

if (show_reset_modal)
{
const char *reset_modal_text = "Are you sure you want to reset all settings to default?";
const char *details_text = "";

draw_confirmation_modal(reset_modal_text, details_text, "Reset", on_reset_modal_submit, on_reset_modal_cancel, E_MODAL_INFO);
}

EndDrawing();

if (IsMouseButtonDown(MOUSE_BUTTON_LEFT))
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && !show_reset_modal)
{
if (CheckCollisionPointRec(GetMousePosition(), back_button_rec))
{
Expand All @@ -132,6 +172,10 @@ void draw_change_dir(struct save_file_data *save_file_data, GameScreen *current_
{
selected_index = SCREEN_BUTTON_CLEAR;
}
else if (CheckCollisionPointRec(GetMousePosition(), reset_config_rec))
{
selected_index = BUTTON_RESET;
}
else
{
selected_index = SCREEN_BUTTON_NONE;
Expand All @@ -157,7 +201,7 @@ void draw_change_dir(struct save_file_data *save_file_data, GameScreen *current_
selected_index = SCREEN_BUTTON_NONE;
break;
case SCREEN_BUTTON_SAVE:
if (CheckCollisionPointRec(GetMousePosition(), save_button_rec) && text_size > 0)
if (CheckCollisionPointRec(GetMousePosition(), save_button_rec) && char_count > 0)
{
file_op_result = write_key_to_config("SAVE_FILE_DIR", input_text);
if (file_op_result == FILE_OP_SUCCESS)
Expand All @@ -172,19 +216,26 @@ void draw_change_dir(struct save_file_data *save_file_data, GameScreen *current_
}
break;
case SCREEN_BUTTON_CLEAR:
if (CheckCollisionPointRec(GetMousePosition(), clear_button_rec) && text_size > 0)
if (CheckCollisionPointRec(GetMousePosition(), clear_button_rec) && char_count > 0)
{
// Clear the input text
memset(input_text, 0, sizeof(input_text));

text_size = 0;
char_count = 0;
editing_text = true;
has_shown_placeholder = true;
file_op_result = FILE_OP_FAILURE;
has_pressed_clear = true;
}
selected_index = SCREEN_BUTTON_NONE;
break;
case BUTTON_RESET:
if (CheckCollisionPointRec(GetMousePosition(), reset_config_rec))
{
show_reset_modal = true;
}
selected_index = SCREEN_BUTTON_NONE;
break;
default:
break;
}
Expand Down
8 changes: 2 additions & 6 deletions src/screens/EvolveScreen.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,16 +263,12 @@ void draw_evolve(PokemonSave *pkmn_save, char *save_path, struct trainer_info *t
case E_UI_EVOLVE:
if (CheckCollisionPointRec(GetMousePosition(), evolve_button_rec))
{
// Generates and assigns random dvs for simulated trade to new trainer
update_pkmn_DVs(pkmn_save, selected_index);
// Evolve pokemon with the simulated new trainer
evolve_party_pokemon_at_index(pkmn_save, selected_index);
// Update stats
update_pkmn_stats(pkmn_save, selected_index);
// Update pokedex
update_seen_owned_pkmn(pkmn_save, selected_index);
// Generates and assigns random dvs on simulated trade back to OT
update_pkmn_DVs(pkmn_save, selected_index);
// Update stats based on new dvs
update_pkmn_stats(pkmn_save, selected_index);
// Finalize pkmn data changes
show_saving_icon = true;
save_savefile_to_path(pkmn_save, save_path);
Expand Down
Loading

0 comments on commit e51f39c

Please sign in to comment.