pokeemerald/src/trainer_hill.c

1091 lines
36 KiB
C
Raw Normal View History

2019-01-13 12:12:27 +01:00
#include "global.h"
#include "malloc.h"
2019-01-13 12:12:27 +01:00
#include "battle.h"
#include "battle_tower.h"
#include "battle_setup.h"
#include "ereader_helpers.h"
2019-01-13 12:12:27 +01:00
#include "event_data.h"
2019-01-13 20:50:08 +01:00
#include "event_scripts.h"
2019-01-13 12:12:27 +01:00
#include "fieldmap.h"
#include "field_message_box.h"
#include "international_string_util.h"
#include "item.h"
#include "main.h"
#include "menu.h"
#include "overworld.h"
#include "palette.h"
#include "pokemon.h"
#include "script.h"
#include "string_util.h"
2019-01-13 13:15:23 +01:00
#include "strings.h"
2019-01-13 12:12:27 +01:00
#include "text.h"
2019-01-13 13:15:23 +01:00
#include "trainer_hill.h"
2019-01-13 12:12:27 +01:00
#include "window.h"
#include "util.h"
2019-11-13 22:10:05 +01:00
#include "constants/battle_ai.h"
2019-11-21 20:03:35 +01:00
#include "constants/event_object_movement.h"
2019-01-13 13:15:23 +01:00
#include "constants/event_objects.h"
2019-01-13 20:50:08 +01:00
#include "constants/items.h"
2019-01-31 22:51:20 +01:00
#include "constants/layouts.h"
2019-01-19 12:57:18 +01:00
#include "constants/moves.h"
2019-01-13 20:50:08 +01:00
#include "constants/trainers.h"
#include "constants/trainer_hill.h"
2020-04-21 21:53:48 +02:00
#include "constants/trainer_types.h"
2019-01-13 20:50:08 +01:00
2019-01-13 12:12:27 +01:00
#define HILL_MAX_TIME 215999 // 60 * 60 * 60 - 1
2022-03-04 17:02:19 +01:00
struct FloorTrainers
{
u8 name[HILL_TRAINERS_PER_FLOOR][TRAINER_NAME_LENGTH + 1];
2022-03-04 17:02:19 +01:00
u8 facilityClass[HILL_TRAINERS_PER_FLOOR];
};
2022-03-04 17:02:19 +01:00
static EWRAM_DATA struct {
u8 floorId;
struct TrainerHillChallenge challenge;
struct TrainerHillFloor floors[NUM_TRAINER_HILL_FLOORS];
} *sHillData = NULL;
static EWRAM_DATA struct FloorTrainers *sFloorTrainers = NULL;
2019-03-01 07:49:11 +01:00
EWRAM_DATA u32 *gTrainerHillVBlankCounter = NULL;
2019-01-13 12:12:27 +01:00
// This file's functions.
static void TrainerHillStartChallenge(void);
2019-11-13 22:10:05 +01:00
static void GetOwnerState(void);
static void GiveChallengePrize(void);
static void CheckFinalTime(void);
static void TrainerHillResumeTimer(void);
static void TrainerHillSetPlayerLost(void);
static void TrainerHillGetChallengeStatus(void);
2019-11-13 22:10:05 +01:00
static void BufferChallengeTime(void);
static void GetAllFloorsUsed(void);
static void GetInEReaderMode(void);
2019-11-13 22:10:05 +01:00
static void IsTrainerHillChallengeActive(void);
static void ShowTrainerHillPostBattleText(void);
static void SetAllTrainerFlags(void);
static void GetGameSaved(void);
static void SetGameSaved(void);
static void ClearGameSaved(void);
static void GetChallengeWon(void);
2022-03-04 17:02:19 +01:00
static void TrainerHillSetMode(void);
2019-01-13 20:50:08 +01:00
static void SetUpDataStruct(void);
static void FreeDataStruct(void);
2021-03-21 04:47:08 +01:00
static void TrainerHillDummy(void);
2019-01-13 20:50:08 +01:00
static void SetTimerValue(u32 *dst, u32 val);
static u32 GetTimerValue(u32 *src);
2019-11-13 22:10:05 +01:00
static void SetTrainerHillMonLevel(struct Pokemon *mon, u8 level);
static u16 GetPrizeItemId(void);
2019-01-13 12:12:27 +01:00
// const data
2019-01-19 12:57:18 +01:00
#include "data/battle_frontier/trainer_hill.h"
2019-01-13 13:15:23 +01:00
2019-01-13 12:12:27 +01:00
struct
{
u8 trainerClass;
u8 musicId;
2019-11-13 22:10:05 +01:00
} static const sTrainerClassesAndMusic[] =
2019-01-13 20:50:08 +01:00
{
{TRAINER_CLASS_TEAM_AQUA, TRAINER_ENCOUNTER_MUSIC_AQUA},
{TRAINER_CLASS_AQUA_ADMIN, TRAINER_ENCOUNTER_MUSIC_AQUA},
{TRAINER_CLASS_AQUA_LEADER, TRAINER_ENCOUNTER_MUSIC_AQUA},
{TRAINER_CLASS_AROMA_LADY, TRAINER_ENCOUNTER_MUSIC_FEMALE},
{TRAINER_CLASS_BATTLE_GIRL, TRAINER_ENCOUNTER_MUSIC_INTENSE},
{TRAINER_CLASS_SWIMMER_F, TRAINER_ENCOUNTER_MUSIC_FEMALE},
{TRAINER_CLASS_POKEFAN, TRAINER_ENCOUNTER_MUSIC_TWINS},
{TRAINER_CLASS_DRAGON_TAMER, TRAINER_ENCOUNTER_MUSIC_INTENSE},
{TRAINER_CLASS_COOLTRAINER, TRAINER_ENCOUNTER_MUSIC_COOL},
{TRAINER_CLASS_GUITARIST, TRAINER_ENCOUNTER_MUSIC_INTENSE},
{TRAINER_CLASS_SAILOR, TRAINER_ENCOUNTER_MUSIC_MALE},
{TRAINER_CLASS_TWINS, TRAINER_ENCOUNTER_MUSIC_TWINS},
{TRAINER_CLASS_INTERVIEWER, TRAINER_ENCOUNTER_MUSIC_INTERVIEWER},
{TRAINER_CLASS_RUIN_MANIAC, TRAINER_ENCOUNTER_MUSIC_HIKER},
{TRAINER_CLASS_GENTLEMAN, TRAINER_ENCOUNTER_MUSIC_RICH},
{TRAINER_CLASS_SWIMMER_M, TRAINER_ENCOUNTER_MUSIC_FEMALE},
{TRAINER_CLASS_POKEMANIAC, TRAINER_ENCOUNTER_MUSIC_SUSPICIOUS},
{TRAINER_CLASS_BLACK_BELT, TRAINER_ENCOUNTER_MUSIC_INTENSE},
{TRAINER_CLASS_OLD_COUPLE, TRAINER_ENCOUNTER_MUSIC_INTENSE},
{TRAINER_CLASS_BUG_MANIAC, TRAINER_ENCOUNTER_MUSIC_SUSPICIOUS},
{TRAINER_CLASS_CAMPER, TRAINER_ENCOUNTER_MUSIC_MALE},
{TRAINER_CLASS_KINDLER, TRAINER_ENCOUNTER_MUSIC_HIKER},
{TRAINER_CLASS_TEAM_MAGMA, TRAINER_ENCOUNTER_MUSIC_MAGMA},
{TRAINER_CLASS_MAGMA_ADMIN, TRAINER_ENCOUNTER_MUSIC_MAGMA},
{TRAINER_CLASS_MAGMA_LEADER, TRAINER_ENCOUNTER_MUSIC_MAGMA},
{TRAINER_CLASS_LASS, TRAINER_ENCOUNTER_MUSIC_FEMALE},
{TRAINER_CLASS_BUG_CATCHER, TRAINER_ENCOUNTER_MUSIC_MALE},
{TRAINER_CLASS_NINJA_BOY, TRAINER_ENCOUNTER_MUSIC_SUSPICIOUS},
{TRAINER_CLASS_RICH_BOY, TRAINER_ENCOUNTER_MUSIC_RICH},
{TRAINER_CLASS_HEX_MANIAC, TRAINER_ENCOUNTER_MUSIC_SUSPICIOUS},
{TRAINER_CLASS_BEAUTY, TRAINER_ENCOUNTER_MUSIC_FEMALE},
{TRAINER_CLASS_LADY, TRAINER_ENCOUNTER_MUSIC_FEMALE},
{TRAINER_CLASS_PARASOL_LADY, TRAINER_ENCOUNTER_MUSIC_FEMALE},
{TRAINER_CLASS_PICNICKER, TRAINER_ENCOUNTER_MUSIC_GIRL},
{TRAINER_CLASS_PKMN_BREEDER, TRAINER_ENCOUNTER_MUSIC_FEMALE},
{TRAINER_CLASS_COLLECTOR, TRAINER_ENCOUNTER_MUSIC_SUSPICIOUS},
{TRAINER_CLASS_PKMN_RANGER, TRAINER_ENCOUNTER_MUSIC_COOL},
{TRAINER_CLASS_RIVAL, TRAINER_ENCOUNTER_MUSIC_MALE},
2019-01-13 20:50:08 +01:00
{TRAINER_CLASS_YOUNG_COUPLE, TRAINER_ENCOUNTER_MUSIC_GIRL},
{TRAINER_CLASS_PSYCHIC, TRAINER_ENCOUNTER_MUSIC_INTENSE},
{TRAINER_CLASS_SR_AND_JR, TRAINER_ENCOUNTER_MUSIC_TWINS},
{TRAINER_CLASS_ELITE_FOUR, TRAINER_ENCOUNTER_MUSIC_FEMALE},
{TRAINER_CLASS_YOUNGSTER, TRAINER_ENCOUNTER_MUSIC_MALE},
{TRAINER_CLASS_EXPERT, TRAINER_ENCOUNTER_MUSIC_INTENSE},
{TRAINER_CLASS_TRIATHLETE, TRAINER_ENCOUNTER_MUSIC_MALE},
{TRAINER_CLASS_BIRD_KEEPER, TRAINER_ENCOUNTER_MUSIC_COOL},
{TRAINER_CLASS_FISHERMAN, TRAINER_ENCOUNTER_MUSIC_HIKER},
{TRAINER_CLASS_CHAMPION, TRAINER_ENCOUNTER_MUSIC_MALE},
{TRAINER_CLASS_TUBER_M, TRAINER_ENCOUNTER_MUSIC_MALE},
{TRAINER_CLASS_TUBER_F, TRAINER_ENCOUNTER_MUSIC_GIRL},
{TRAINER_CLASS_SIS_AND_BRO, TRAINER_ENCOUNTER_MUSIC_SWIMMER},
{TRAINER_CLASS_HIKER, TRAINER_ENCOUNTER_MUSIC_HIKER},
{TRAINER_CLASS_LEADER, TRAINER_ENCOUNTER_MUSIC_FEMALE},
{TRAINER_CLASS_SCHOOL_KID, TRAINER_ENCOUNTER_MUSIC_MALE},
};
2019-11-13 22:10:05 +01:00
static const u16 sPrizeListRareCandy1[] = {ITEM_RARE_CANDY, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListLuxuryBall1[] = {ITEM_LUXURY_BALL, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListMaxRevive1[] = {ITEM_MAX_REVIVE, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListMaxEther1[] = {ITEM_MAX_ETHER, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListElixir1[] = {ITEM_ELIXIR, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListRoar[] = {ITEM_TM05_ROAR, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListSludgeBomb[] = {ITEM_TM36_SLUDGE_BOMB, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListToxic[] = {ITEM_TM06_TOXIC, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListSunnyDay[] = {ITEM_TM11_SUNNY_DAY, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListEarthQuake[] = {ITEM_TM26_EARTHQUAKE, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListRareCandy2[] = {ITEM_RARE_CANDY, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListLuxuryBall2[] = {ITEM_LUXURY_BALL, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListMaxRevive2[] = {ITEM_MAX_REVIVE, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListMaxEther2[] = {ITEM_MAX_ETHER, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListElixir2[] = {ITEM_ELIXIR, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListBrickBreak[] = {ITEM_TM31_BRICK_BREAK, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListTorment[] = {ITEM_TM41_TORMENT, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 sPrizeListSkillSwap[] = {ITEM_TM48_SKILL_SWAP, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
2020-05-19 22:13:25 +02:00
static const u16 sPrizeListGigaDrain[] = {ITEM_TM19_GIGA_DRAIN, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
2019-11-13 22:10:05 +01:00
static const u16 sPrizeListAttract[] = {ITEM_TM45_ATTRACT, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL};
static const u16 *const sPrizeLists1[NUM_TRAINER_HILL_PRIZE_LISTS] =
{
sPrizeListRareCandy1,
sPrizeListLuxuryBall1,
sPrizeListMaxRevive1,
sPrizeListMaxEther1,
sPrizeListElixir1,
sPrizeListRoar,
sPrizeListSludgeBomb,
sPrizeListToxic,
sPrizeListSunnyDay,
sPrizeListEarthQuake
2019-01-13 20:50:08 +01:00
};
2019-01-13 12:12:27 +01:00
2019-11-13 22:10:05 +01:00
static const u16 *const sPrizeLists2[NUM_TRAINER_HILL_PRIZE_LISTS] =
{
sPrizeListRareCandy2,
sPrizeListLuxuryBall2,
sPrizeListMaxRevive2,
sPrizeListMaxEther2,
sPrizeListElixir2,
sPrizeListBrickBreak,
sPrizeListTorment,
sPrizeListSkillSwap,
2020-05-19 22:13:25 +02:00
sPrizeListGigaDrain,
2019-11-13 22:10:05 +01:00
sPrizeListAttract
2019-01-13 20:50:08 +01:00
};
2019-11-13 22:10:05 +01:00
static const u16 *const *const sPrizeListSets[] =
2019-01-13 20:50:08 +01:00
{
2019-11-13 22:10:05 +01:00
sPrizeLists1,
sPrizeLists2
2019-01-13 20:50:08 +01:00
};
2019-01-13 12:12:27 +01:00
2022-01-14 18:29:30 +01:00
static const u16 sEReader_Pal[] = INCBIN_U16("graphics/trainer_hill/ereader.gbapal");
2021-04-10 04:39:34 +02:00
static const u8 sRecordWinColors[] = {TEXT_COLOR_TRANSPARENT, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_LIGHT_GRAY};
2019-01-13 13:15:23 +01:00
2022-03-04 17:02:19 +01:00
static const struct TrainerHillChallenge *const sChallengeData[NUM_TRAINER_HILL_MODES] =
2019-01-13 13:15:23 +01:00
{
2022-03-04 17:02:19 +01:00
[HILL_MODE_NORMAL] = &sChallenge_Normal,
[HILL_MODE_VARIETY] = &sChallenge_Variety,
[HILL_MODE_UNIQUE] = &sChallenge_Unique,
[HILL_MODE_EXPERT] = &sChallenge_Expert,
2019-01-13 13:15:23 +01:00
};
// Unused.
static const u8 *const sFloorStrings[] =
{
gText_TrainerHill1F,
gText_TrainerHill2F,
gText_TrainerHill3F,
gText_TrainerHill4F,
};
static void (* const sHillFunctions[])(void) =
{
2019-11-23 22:08:50 +01:00
[TRAINER_HILL_FUNC_START] = TrainerHillStartChallenge,
[TRAINER_HILL_FUNC_GET_OWNER_STATE] = GetOwnerState,
[TRAINER_HILL_FUNC_GIVE_PRIZE] = GiveChallengePrize,
[TRAINER_HILL_FUNC_CHECK_FINAL_TIME] = CheckFinalTime,
[TRAINER_HILL_FUNC_RESUME_TIMER] = TrainerHillResumeTimer,
[TRAINER_HILL_FUNC_SET_LOST] = TrainerHillSetPlayerLost,
[TRAINER_HILL_FUNC_GET_CHALLENGE_STATUS] = TrainerHillGetChallengeStatus,
[TRAINER_HILL_FUNC_GET_CHALLENGE_TIME] = BufferChallengeTime,
[TRAINER_HILL_FUNC_GET_ALL_FLOORS_USED] = GetAllFloorsUsed,
[TRAINER_HILL_FUNC_GET_IN_EREADER_MODE] = GetInEReaderMode,
2019-11-23 22:08:50 +01:00
[TRAINER_HILL_FUNC_IN_CHALLENGE] = IsTrainerHillChallengeActive,
[TRAINER_HILL_FUNC_POST_BATTLE_TEXT] = ShowTrainerHillPostBattleText,
2019-11-13 22:10:05 +01:00
[TRAINER_HILL_FUNC_SET_ALL_TRAINER_FLAGS] = SetAllTrainerFlags,
2019-11-23 22:08:50 +01:00
[TRAINER_HILL_FUNC_GET_GAME_SAVED] = GetGameSaved,
[TRAINER_HILL_FUNC_SET_GAME_SAVED] = SetGameSaved,
[TRAINER_HILL_FUNC_CLEAR_GAME_SAVED] = ClearGameSaved,
[TRAINER_HILL_FUNC_GET_WON] = GetChallengeWon,
2022-03-04 17:02:19 +01:00
[TRAINER_HILL_FUNC_SET_MODE] = TrainerHillSetMode,
2019-01-13 13:15:23 +01:00
};
2022-03-04 17:02:19 +01:00
static const u8 *const sModeStrings[NUM_TRAINER_HILL_MODES] =
2019-01-13 13:15:23 +01:00
{
2022-03-04 17:02:19 +01:00
[HILL_MODE_NORMAL] = gText_NormalTagMatch,
[HILL_MODE_VARIETY] = gText_VarietyTagMatch,
[HILL_MODE_UNIQUE] = gText_UniqueTagMatch,
[HILL_MODE_EXPERT] = gText_ExpertTagMatch,
2019-01-13 13:15:23 +01:00
};
static const struct ObjectEventTemplate sTrainerObjectEventTemplate =
2019-01-13 13:15:23 +01:00
{
.graphicsId = OBJ_EVENT_GFX_RIVAL_BRENDAN_NORMAL,
2019-01-13 13:15:23 +01:00
.elevation = 3,
.movementType = MOVEMENT_TYPE_LOOK_AROUND,
.movementRangeX = 1,
.movementRangeY = 1,
2020-04-21 21:53:48 +02:00
.trainerType = TRAINER_TYPE_NORMAL,
2019-01-13 13:15:23 +01:00
};
static const u32 sNextFloorMapNum[NUM_TRAINER_HILL_FLOORS] =
2019-11-13 22:10:05 +01:00
{
2022-03-04 17:02:19 +01:00
[TRAINER_HILL_1F - 1] = MAP_NUM(TRAINER_HILL_2F),
[TRAINER_HILL_2F - 1] = MAP_NUM(TRAINER_HILL_3F),
[TRAINER_HILL_3F - 1] = MAP_NUM(TRAINER_HILL_4F),
[TRAINER_HILL_4F - 1] = MAP_NUM(TRAINER_HILL_ROOF)
2019-11-13 22:10:05 +01:00
};
2022-03-04 17:02:19 +01:00
static const u8 sTrainerPartySlots[HILL_TRAINERS_PER_FLOOR][PARTY_SIZE / 2] =
2019-11-13 22:10:05 +01:00
{
{0, 1, 2},
2019-11-13 22:10:05 +01:00
{3, 4, 5}
};
2019-01-13 13:15:23 +01:00
void CallTrainerHillFunction(void)
2019-01-13 12:12:27 +01:00
{
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
sHillFunctions[gSpecialVar_0x8004]();
FreeDataStruct();
2019-01-13 12:12:27 +01:00
}
2019-01-13 20:50:08 +01:00
void ResetTrainerHillResults(void)
2019-01-13 12:12:27 +01:00
{
s32 i;
2019-11-13 22:31:01 +01:00
gSaveBlock2Ptr->frontier.savedGame = 0;
2019-11-16 19:45:21 +01:00
gSaveBlock2Ptr->frontier.unk_EF9 = 0;
gSaveBlock1Ptr->trainerHill.bestTime = 0;
2022-03-04 17:02:19 +01:00
for (i = 0; i < NUM_TRAINER_HILL_MODES; i++)
2019-01-13 13:15:23 +01:00
SetTimerValue(&gSaveBlock1Ptr->trainerHillTimes[i], HILL_MAX_TIME);
2019-01-13 12:12:27 +01:00
}
2019-01-13 20:50:08 +01:00
static u8 GetFloorId(void)
2019-01-13 12:12:27 +01:00
{
2019-01-31 22:51:20 +01:00
return gMapHeader.mapLayoutId - LAYOUT_TRAINER_HILL_1F;
2019-01-13 12:12:27 +01:00
}
u8 GetTrainerHillOpponentClass(u16 trainerId)
{
u8 id = trainerId - 1;
2022-03-04 17:02:19 +01:00
return gFacilityClassToTrainerClass[sFloorTrainers->facilityClass[id]];
2019-01-13 12:12:27 +01:00
}
void GetTrainerHillTrainerName(u8 *dst, u16 trainerId)
{
s32 i;
u8 id = trainerId - 1;
for (i = 0; i < TRAINER_NAME_LENGTH + 1; i++)
2022-03-04 17:02:19 +01:00
dst[i] = sFloorTrainers->name[id][i];
2019-01-13 12:12:27 +01:00
}
u8 GetTrainerHillTrainerFrontSpriteId(u16 trainerId)
{
u8 id, facilityClass;
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
2019-01-13 12:12:27 +01:00
id = trainerId - 1;
facilityClass = sHillData->floors[sHillData->floorId].trainers[id].facilityClass;
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
return gFacilityClassToPicIndex[facilityClass];
}
2019-01-13 20:50:08 +01:00
void InitTrainerHillBattleStruct(void)
2019-01-13 12:12:27 +01:00
{
s32 i, j;
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
2022-03-04 17:02:19 +01:00
sFloorTrainers = AllocZeroed(sizeof(*sFloorTrainers));
2019-01-13 12:12:27 +01:00
2022-03-04 17:02:19 +01:00
for (i = 0; i < HILL_TRAINERS_PER_FLOOR; i++)
2019-01-13 12:12:27 +01:00
{
for (j = 0; j < TRAINER_NAME_LENGTH + 1; j++)
2022-03-04 17:02:19 +01:00
sFloorTrainers->name[i][j] = sHillData->floors[sHillData->floorId].trainers[i].name[j];
sFloorTrainers->facilityClass[i] = sHillData->floors[sHillData->floorId].trainers[i].facilityClass;
2019-01-13 12:12:27 +01:00
}
2019-03-01 07:49:11 +01:00
SetTrainerHillVBlankCounter(&gSaveBlock1Ptr->trainerHill.timer);
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
}
2019-01-13 20:50:08 +01:00
void FreeTrainerHillBattleStruct(void)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
TRY_FREE_AND_SET_NULL(sFloorTrainers);
2019-01-13 12:12:27 +01:00
}
2019-01-13 20:50:08 +01:00
static void SetUpDataStruct(void)
2019-01-13 12:12:27 +01:00
{
2019-01-13 13:15:23 +01:00
if (sHillData == NULL)
2019-01-13 12:12:27 +01:00
{
sHillData = AllocZeroed(sizeof(*sHillData));
2019-01-31 22:51:20 +01:00
sHillData->floorId = gMapHeader.mapLayoutId - LAYOUT_TRAINER_HILL_1F;
2022-03-04 17:02:19 +01:00
// This copy depends on the floor data for each challenge being directly after the
// challenge header data, and for the field 'floors' in sHillData to come directly
// after the field 'challenge'.
// e.g. for HILL_MODE_NORMAL, it will copy sChallenge_Normal to sHillData->challenge and
// it will copy sFloors_Normal to sHillData->floors
CpuCopy32(sChallengeData[gSaveBlock1Ptr->trainerHill.mode], &sHillData->challenge, sizeof(sHillData->challenge) + sizeof(sHillData->floors));
2021-03-21 04:47:08 +01:00
TrainerHillDummy();
2019-01-13 12:12:27 +01:00
}
}
2019-01-13 20:50:08 +01:00
static void FreeDataStruct(void)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
TRY_FREE_AND_SET_NULL(sHillData);
2019-01-13 12:12:27 +01:00
}
void CopyTrainerHillTrainerText(u8 which, u16 trainerId)
{
2019-01-13 13:15:23 +01:00
u8 id, floorId;
2019-01-13 12:12:27 +01:00
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
floorId = GetFloorId();
2019-01-13 12:12:27 +01:00
id = trainerId - 1;
switch (which)
{
2019-11-13 22:10:05 +01:00
case TRAINER_HILL_TEXT_INTRO:
FrontierSpeechToString(sHillData->floors[floorId].trainers[id].speechBefore);
2019-01-13 12:12:27 +01:00
break;
2019-11-13 22:10:05 +01:00
case TRAINER_HILL_TEXT_PLAYER_LOST:
FrontierSpeechToString(sHillData->floors[floorId].trainers[id].speechWin);
2019-01-13 12:12:27 +01:00
break;
2019-11-13 22:10:05 +01:00
case TRAINER_HILL_TEXT_PLAYER_WON:
FrontierSpeechToString(sHillData->floors[floorId].trainers[id].speechLose);
2019-01-13 12:12:27 +01:00
break;
2019-11-13 22:10:05 +01:00
case TRAINER_HILL_TEXT_AFTER:
FrontierSpeechToString(sHillData->floors[floorId].trainers[id].speechAfter);
2019-01-13 12:12:27 +01:00
break;
}
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
}
static void TrainerHillStartChallenge(void)
2019-01-13 12:12:27 +01:00
{
2021-03-21 04:47:08 +01:00
TrainerHillDummy();
2019-04-04 17:55:18 +02:00
if (!ReadTrainerHillAndValidate())
2019-01-13 12:12:27 +01:00
gSaveBlock1Ptr->trainerHill.field_3D6E_0f = 1;
else
gSaveBlock1Ptr->trainerHill.field_3D6E_0f = 0;
2019-11-16 19:45:21 +01:00
gSaveBlock1Ptr->trainerHill.unk_3D6C = 0;
2019-03-01 07:49:11 +01:00
SetTrainerHillVBlankCounter(&gSaveBlock1Ptr->trainerHill.timer);
gSaveBlock1Ptr->trainerHill.timer = 0;
2019-11-13 22:10:05 +01:00
gSaveBlock1Ptr->trainerHill.spokeToOwner = 0;
gSaveBlock1Ptr->trainerHill.checkedFinalTime = 0;
gSaveBlock1Ptr->trainerHill.maybeECardScanDuringChallenge = 0;
2019-11-13 22:10:05 +01:00
gSaveBlock2Ptr->frontier.trainerFlags = 0;
2019-01-13 12:12:27 +01:00
gBattleOutcome = 0;
2019-11-13 22:10:05 +01:00
gSaveBlock1Ptr->trainerHill.receivedPrize = 0;
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
static void GetOwnerState(void)
2019-01-13 12:12:27 +01:00
{
2019-03-01 07:49:11 +01:00
ClearTrainerHillVBlankCounter();
2019-01-13 12:12:27 +01:00
gSpecialVar_Result = 0;
2019-11-13 22:10:05 +01:00
if (gSaveBlock1Ptr->trainerHill.spokeToOwner)
2019-01-13 12:12:27 +01:00
gSpecialVar_Result++;
2019-11-13 22:10:05 +01:00
if (gSaveBlock1Ptr->trainerHill.receivedPrize && gSaveBlock1Ptr->trainerHill.checkedFinalTime)
2019-01-13 12:12:27 +01:00
gSpecialVar_Result++;
2019-11-13 22:10:05 +01:00
gSaveBlock1Ptr->trainerHill.spokeToOwner = TRUE;
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
static void GiveChallengePrize(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
u16 itemId = GetPrizeItemId();
2019-01-13 12:12:27 +01:00
2022-03-04 17:02:19 +01:00
if (sHillData->challenge.numFloors != NUM_TRAINER_HILL_FLOORS || gSaveBlock1Ptr->trainerHill.receivedPrize)
2019-01-13 12:12:27 +01:00
{
gSpecialVar_Result = 2;
}
else if (AddBagItem(itemId, 1) == TRUE)
{
CopyItemName(itemId, gStringVar2);
2019-11-13 22:10:05 +01:00
gSaveBlock1Ptr->trainerHill.receivedPrize = TRUE;
2019-11-16 19:45:21 +01:00
gSaveBlock2Ptr->frontier.unk_EF9 = 0;
2019-01-13 12:12:27 +01:00
gSpecialVar_Result = 0;
}
else
{
gSpecialVar_Result = 1;
}
}
2019-11-16 19:54:50 +01:00
// If bestTime > timer, the challenge was completed faster and its a new record
// Otherwise the owner says it was a slow time and to complete it faster next time
2019-11-13 22:10:05 +01:00
static void CheckFinalTime(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
if (gSaveBlock1Ptr->trainerHill.checkedFinalTime)
2019-01-13 12:12:27 +01:00
{
gSpecialVar_Result = 2;
}
else if (GetTimerValue(&gSaveBlock1Ptr->trainerHill.bestTime) > gSaveBlock1Ptr->trainerHill.timer)
2019-01-13 12:12:27 +01:00
{
SetTimerValue(&gSaveBlock1Ptr->trainerHill.bestTime, gSaveBlock1Ptr->trainerHill.timer);
2022-03-04 17:02:19 +01:00
gSaveBlock1Ptr->trainerHillTimes[gSaveBlock1Ptr->trainerHill.mode] = gSaveBlock1Ptr->trainerHill.bestTime;
2019-01-13 12:12:27 +01:00
gSpecialVar_Result = 0;
}
else
{
gSpecialVar_Result = 1;
}
2019-11-13 22:10:05 +01:00
gSaveBlock1Ptr->trainerHill.checkedFinalTime = TRUE;
2019-01-13 12:12:27 +01:00
}
static void TrainerHillResumeTimer(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
if (!gSaveBlock1Ptr->trainerHill.spokeToOwner)
2019-01-13 12:12:27 +01:00
{
if (gSaveBlock1Ptr->trainerHill.timer >= HILL_MAX_TIME)
gSaveBlock1Ptr->trainerHill.timer = HILL_MAX_TIME;
2019-01-13 12:12:27 +01:00
else
2019-03-01 07:49:11 +01:00
SetTrainerHillVBlankCounter(&gSaveBlock1Ptr->trainerHill.timer);
2019-01-13 12:12:27 +01:00
}
}
static void TrainerHillSetPlayerLost(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
gSaveBlock1Ptr->trainerHill.hasLost = TRUE;
2019-01-13 12:12:27 +01:00
}
static void TrainerHillGetChallengeStatus(void)
2019-01-13 12:12:27 +01:00
{
if (gSaveBlock1Ptr->trainerHill.hasLost)
2019-01-13 12:12:27 +01:00
{
// The player lost their last match.
2019-11-13 22:10:05 +01:00
gSaveBlock1Ptr->trainerHill.hasLost = FALSE;
gSpecialVar_Result = TRAINER_HILL_PLAYER_STATUS_LOST;
2019-01-13 12:12:27 +01:00
}
else if (gSaveBlock1Ptr->trainerHill.maybeECardScanDuringChallenge)
2019-01-13 12:12:27 +01:00
{
// Unreachable code. Something relating to eCards?
gSaveBlock1Ptr->trainerHill.maybeECardScanDuringChallenge = 0;
gSpecialVar_Result = TRAINER_HILL_PLAYER_STATUS_ECARD_SCANNED;
2019-01-13 12:12:27 +01:00
}
else
{
// Continue playing.
gSpecialVar_Result = TRAINER_HILL_PLAYER_STATUS_NORMAL;
2019-01-13 12:12:27 +01:00
}
}
2019-11-13 22:10:05 +01:00
static void BufferChallengeTime(void)
2019-01-13 12:12:27 +01:00
{
s32 total, minutes, secondsWhole, secondsFraction;
total = gSaveBlock1Ptr->trainerHill.timer;
2019-01-13 12:12:27 +01:00
if (total >= HILL_MAX_TIME)
total = HILL_MAX_TIME;
minutes = total / (60 * 60);
total %= (60 * 60);
secondsWhole = total / 60;
total %= 60;
secondsFraction = (total * 168) / 100;
ConvertIntToDecimalStringN(gStringVar1, minutes, STR_CONV_MODE_RIGHT_ALIGN, 2);
ConvertIntToDecimalStringN(gStringVar2, secondsWhole, STR_CONV_MODE_RIGHT_ALIGN, 2);
ConvertIntToDecimalStringN(gStringVar3, secondsFraction, STR_CONV_MODE_LEADING_ZEROS, 2);
}
2019-11-13 22:10:05 +01:00
// Returns TRUE if all 4 floors are used
// Returns FALSE otherwise, and buffers the number of floors used
2019-11-13 22:31:01 +01:00
// The only time fewer than all 4 floors are used is for the JP-exclusive E-Reader and Default modes
2019-11-13 22:10:05 +01:00
static void GetAllFloorsUsed(void)
2019-01-13 12:12:27 +01:00
{
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
2022-03-04 17:02:19 +01:00
if (sHillData->challenge.numFloors != NUM_TRAINER_HILL_FLOORS)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
ConvertIntToDecimalStringN(gStringVar1, sHillData->challenge.numFloors, STR_CONV_MODE_LEFT_ALIGN, 1);
2019-11-13 22:10:05 +01:00
gSpecialVar_Result = FALSE;
2019-01-13 12:12:27 +01:00
}
else
{
2019-11-13 22:10:05 +01:00
gSpecialVar_Result = TRUE;
2019-01-13 12:12:27 +01:00
}
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
// May have been dummied. Every time this is called a conditional for var result occurs afterwards
// Relation to E-Reader is an assumption, most dummied Trainer Hill code seems to be JP E-Reader mode related
static void GetInEReaderMode(void)
2019-01-13 12:12:27 +01:00
{
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
gSpecialVar_Result = FALSE;
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
}
2019-10-07 02:04:30 +02:00
bool8 InTrainerHillChallenge(void)
2019-01-13 12:12:27 +01:00
{
if (VarGet(VAR_TRAINER_HILL_IS_ACTIVE) == 0)
2019-01-13 12:12:27 +01:00
return FALSE;
2019-11-13 22:10:05 +01:00
else if (gSaveBlock1Ptr->trainerHill.spokeToOwner)
2019-01-13 12:12:27 +01:00
return FALSE;
else if (GetCurrentTrainerHillMapId() != 0)
return TRUE;
else
return FALSE;
}
2019-11-13 22:10:05 +01:00
static void IsTrainerHillChallengeActive(void)
2019-01-13 12:12:27 +01:00
{
2019-10-07 02:04:30 +02:00
if (!InTrainerHillChallenge())
2019-11-13 22:10:05 +01:00
gSpecialVar_Result = FALSE;
2019-01-13 12:12:27 +01:00
else
2019-11-13 22:10:05 +01:00
gSpecialVar_Result = TRUE;
2019-01-13 12:12:27 +01:00
}
2021-03-21 04:47:08 +01:00
static void TrainerHillDummy_Unused(void)
2019-01-13 12:12:27 +01:00
{
}
2021-03-21 04:47:08 +01:00
static void TrainerHillDummy(void)
2019-01-13 12:12:27 +01:00
{
}
void PrintOnTrainerHillRecordsWindow(void)
{
s32 i, x, y;
u32 total, minutes, secondsWhole, secondsFraction;
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
FillWindowPixelBuffer(0, PIXEL_FILL(0));
2021-10-30 22:47:37 +02:00
x = GetStringCenterAlignXOffset(FONT_NORMAL, gText_TimeBoard, 0xD0);
AddTextPrinterParameterized3(0, FONT_NORMAL, x, 2, sRecordWinColors, TEXT_SKIP_DRAW, gText_TimeBoard);
2019-01-13 12:12:27 +01:00
y = 18;
2022-03-04 17:02:19 +01:00
for (i = 0; i < NUM_TRAINER_HILL_MODES; i++)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
AddTextPrinterParameterized3(0, FONT_NORMAL, 0, y, sRecordWinColors, TEXT_SKIP_DRAW, sModeStrings[i]);
2019-01-13 12:12:27 +01:00
y += 15;
2019-01-13 13:15:23 +01:00
total = GetTimerValue(&gSaveBlock1Ptr->trainerHillTimes[i]);
2019-01-13 12:12:27 +01:00
minutes = total / (60 * 60);
total %= (60 * 60);
ConvertIntToDecimalStringN(gStringVar1, minutes, STR_CONV_MODE_RIGHT_ALIGN, 2);
secondsWhole = total / 60;
total %= 60;
ConvertIntToDecimalStringN(gStringVar2, secondsWhole, STR_CONV_MODE_RIGHT_ALIGN, 2);
secondsFraction = (total * 168) / 100;
ConvertIntToDecimalStringN(gStringVar3, secondsFraction, STR_CONV_MODE_LEADING_ZEROS, 2);
StringExpandPlaceholders(StringCopy(gStringVar4, gText_TimeCleared), gText_XMinYDotZSec);
2021-10-30 22:47:37 +02:00
x = GetStringRightAlignXOffset(FONT_NORMAL, gStringVar4, 0xD0);
AddTextPrinterParameterized3(0, FONT_NORMAL, x, y, sRecordWinColors, TEXT_SKIP_DRAW, gStringVar4);
2019-01-13 12:12:27 +01:00
y += 17;
}
PutWindowTilemap(0);
2021-11-03 20:29:18 +01:00
CopyWindowToVram(0, COPYWIN_FULL);
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
}
2019-01-13 13:15:23 +01:00
// Leftover from Fire Red / Leaf Green as in these games,
// the timer had to be xored by the encryption key in Sav2.
2019-01-13 20:50:08 +01:00
static u32 GetTimerValue(u32 *src)
2019-01-13 12:12:27 +01:00
{
return *src;
}
2019-01-13 20:50:08 +01:00
static void SetTimerValue(u32 *dst, u32 val)
2019-01-13 12:12:27 +01:00
{
*dst = val;
}
void LoadTrainerHillObjectEventTemplates(void)
2019-01-13 12:12:27 +01:00
{
2019-01-13 13:15:23 +01:00
u8 i, floorId;
struct ObjectEventTemplate *eventTemplates = gSaveBlock1Ptr->objectEventTemplates;
2019-01-13 12:12:27 +01:00
if (!LoadTrainerHillFloorObjectEventScripts())
2019-01-13 12:12:27 +01:00
return;
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
2022-03-04 17:02:19 +01:00
for (i = 0; i < HILL_TRAINERS_PER_FLOOR; i++)
2019-02-07 18:37:28 +01:00
gSaveBlock2Ptr->frontier.trainerIds[i] = 0xFFFF;
CpuFill32(0, gSaveBlock1Ptr->objectEventTemplates, sizeof(gSaveBlock1Ptr->objectEventTemplates));
2019-01-13 12:12:27 +01:00
2019-01-13 13:15:23 +01:00
floorId = GetFloorId();
2022-03-04 17:02:19 +01:00
for (i = 0; i < HILL_TRAINERS_PER_FLOOR; i++)
2019-01-13 12:12:27 +01:00
{
u8 bits;
eventTemplates[i] = sTrainerObjectEventTemplate;
2019-01-13 12:12:27 +01:00
eventTemplates[i].localId = i + 1;
eventTemplates[i].graphicsId = FacilityClassToGraphicsId(sHillData->floors[floorId].trainers[i].facilityClass);
2022-03-04 17:02:19 +01:00
eventTemplates[i].x = sHillData->floors[floorId].map.trainerCoords[i] & 0xF;
eventTemplates[i].y = ((sHillData->floors[floorId].map.trainerCoords[i] >> 4) & 0xF) + 5;
2019-01-13 12:12:27 +01:00
bits = i << 2;
2022-03-04 17:02:19 +01:00
eventTemplates[i].movementType = ((sHillData->floors[floorId].map.trainerDirections >> bits) & 0xF) + MOVEMENT_TYPE_FACE_UP;
eventTemplates[i].trainerRange_berryTreeId = (sHillData->floors[floorId].map.trainerRanges >> bits) & 0xF;
2019-11-13 22:10:05 +01:00
eventTemplates[i].script = TrainerHill_EventScript_TrainerBattle;
2019-02-07 18:37:28 +01:00
gSaveBlock2Ptr->frontier.trainerIds[i] = i + 1;
2019-01-13 12:12:27 +01:00
}
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
}
bool32 LoadTrainerHillFloorObjectEventScripts(void)
2019-01-13 12:12:27 +01:00
{
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
2019-11-13 22:10:05 +01:00
// Something may have been dummied here
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
return TRUE;
}
2022-03-04 17:02:19 +01:00
static u16 GetMetatileForFloor(u8 floorId, u32 x, u32 y, u32 floorWidth) // floorWidth is always 16
2019-01-13 12:12:27 +01:00
{
bool8 impassable;
u16 metatile;
u16 elevation;
2019-01-13 12:12:27 +01:00
2022-03-04 17:02:19 +01:00
impassable = (sHillData->floors[floorId].map.collisionData[y] >> (15 - x) & 1);
metatile = sHillData->floors[floorId].map.metatileData[floorWidth * y + x] + NUM_METATILES_IN_PRIMARY;
2022-01-19 16:15:32 +01:00
elevation = 3 << MAPGRID_ELEVATION_SHIFT;
2019-01-13 12:12:27 +01:00
2022-01-19 16:15:32 +01:00
return ((impassable << MAPGRID_COLLISION_SHIFT) & MAPGRID_COLLISION_MASK) | elevation | (metatile & MAPGRID_METATILE_ID_MASK);
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
void GenerateTrainerHillFloorLayout(u16 *mapArg)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
s32 y, x;
2019-01-13 12:12:27 +01:00
u16 *src, *dst;
2019-01-13 13:15:23 +01:00
u8 mapId = GetCurrentTrainerHillMapId();
2019-01-13 12:12:27 +01:00
2019-11-13 22:10:05 +01:00
if (mapId == TRAINER_HILL_ENTRANCE)
2019-01-13 12:12:27 +01:00
{
InitMapFromSavedGame();
return;
}
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
2019-11-13 22:10:05 +01:00
if (mapId == TRAINER_HILL_ROOF)
2019-01-13 12:12:27 +01:00
{
InitMapFromSavedGame();
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
return;
}
2019-01-13 13:15:23 +01:00
mapId = GetFloorId();
2019-01-13 12:12:27 +01:00
src = gMapHeader.mapLayout->map;
gBackupMapLayout.map = mapArg;
2022-03-04 17:02:19 +01:00
// Dimensions include border area loaded beyond map
gBackupMapLayout.width = HILL_FLOOR_WIDTH + 15;
gBackupMapLayout.height = HILL_FLOOR_HEIGHT + 14;
2019-01-13 12:12:27 +01:00
dst = mapArg + 224;
// First 5 rows of the map (Entrance / Exit) are always the same
2022-03-04 17:02:19 +01:00
for (y = 0; y < HILL_FLOOR_HEIGHT_MARGIN; y++)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
for (x = 0; x < HILL_FLOOR_WIDTH; x++)
dst[x] = src[x];
2019-01-13 12:12:27 +01:00
dst += 31;
src += 16;
}
// Load the 16x16 floor-specific layout
2022-03-04 17:02:19 +01:00
for (y = 0; y < HILL_FLOOR_HEIGHT_MAIN; y++)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
for (x = 0; x < HILL_FLOOR_WIDTH; x++)
dst[x] = GetMetatileForFloor(mapId, x, y, HILL_FLOOR_WIDTH);
2019-01-13 12:12:27 +01:00
dst += 31;
}
RunOnLoadMapScript();
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
}
bool32 InTrainerHill(void)
{
bool32 ret;
2019-01-31 22:51:20 +01:00
if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_1F
2019-11-13 22:10:05 +01:00
|| gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_2F
|| gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_3F
|| gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_4F)
2019-01-13 12:12:27 +01:00
ret = TRUE;
else
ret = FALSE;
return ret;
}
u8 GetCurrentTrainerHillMapId(void)
{
2019-11-13 22:10:05 +01:00
u8 mapId;
2019-01-13 12:12:27 +01:00
2019-01-31 22:51:20 +01:00
if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_1F)
2019-11-13 22:10:05 +01:00
mapId = TRAINER_HILL_1F;
2019-01-31 22:51:20 +01:00
else if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_2F)
2019-11-13 22:10:05 +01:00
mapId = TRAINER_HILL_2F;
2019-01-31 22:51:20 +01:00
else if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_3F)
2019-11-13 22:10:05 +01:00
mapId = TRAINER_HILL_3F;
2019-01-31 22:51:20 +01:00
else if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_4F)
2019-11-13 22:10:05 +01:00
mapId = TRAINER_HILL_4F;
2019-01-31 22:51:20 +01:00
else if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_ROOF)
2019-11-13 22:10:05 +01:00
mapId = TRAINER_HILL_ROOF;
2019-01-31 22:51:20 +01:00
else if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_ENTRANCE)
2019-11-13 22:10:05 +01:00
mapId = TRAINER_HILL_ENTRANCE;
2019-01-13 12:12:27 +01:00
else
2019-11-13 22:10:05 +01:00
mapId = 0;
2019-01-13 12:12:27 +01:00
2019-11-13 22:10:05 +01:00
return mapId;
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
// Unused
static bool32 OnTrainerHillRoof(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
bool32 onRoof;
2019-01-13 12:12:27 +01:00
2019-01-31 22:51:20 +01:00
if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_ROOF)
2019-11-13 22:10:05 +01:00
onRoof = TRUE;
2019-01-13 12:12:27 +01:00
else
2019-11-13 22:10:05 +01:00
onRoof = FALSE;
2019-01-13 12:12:27 +01:00
2019-11-13 22:10:05 +01:00
return onRoof;
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
const struct WarpEvent* SetWarpDestinationTrainerHill4F(void)
2019-01-13 12:12:27 +01:00
{
const struct MapHeader *header = Overworld_GetMapHeaderByGroupAndId(MAP_GROUP(TRAINER_HILL_4F), MAP_NUM(TRAINER_HILL_4F));
return &header->events->warps[1];
}
2019-11-13 22:10:05 +01:00
// For warping from the roof in challenges where the 4F is not the final challenge floor
2019-11-13 22:31:01 +01:00
// This would only occur in the JP-exclusive Default and E-Reader challenges
2019-11-13 22:10:05 +01:00
const struct WarpEvent* SetWarpDestinationTrainerHillFinalFloor(u8 warpEventId)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
u8 numFloors;
2019-01-13 12:12:27 +01:00
const struct MapHeader *header;
if (warpEventId == 1)
return &gMapHeader.events->warps[1];
2019-11-13 22:10:05 +01:00
numFloors = GetNumFloorsInTrainerHillChallenge();
if (numFloors == 0 || numFloors > NUM_TRAINER_HILL_FLOORS)
numFloors = NUM_TRAINER_HILL_FLOORS;
2019-01-13 12:12:27 +01:00
2019-11-13 22:10:05 +01:00
header = Overworld_GetMapHeaderByGroupAndId(MAP_GROUP(TRAINER_HILL_4F), sNextFloorMapNum[numFloors - 1]);
2019-01-13 12:12:27 +01:00
return &header->events->warps[0];
}
2019-01-13 20:50:08 +01:00
u16 LocalIdToHillTrainerId(u8 localId)
2019-01-13 12:12:27 +01:00
{
2019-02-07 18:37:28 +01:00
return gSaveBlock2Ptr->frontier.trainerIds[localId - 1];
2019-01-13 12:12:27 +01:00
}
bool8 GetHillTrainerFlag(u8 objectEventId)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
u32 trainerIndexStart = GetFloorId() * HILL_TRAINERS_PER_FLOOR;
u8 bitId = gObjectEvents[objectEventId].localId - 1 + trainerIndexStart;
2019-01-13 12:12:27 +01:00
2019-11-13 22:10:05 +01:00
return gSaveBlock2Ptr->frontier.trainerFlags & gBitTable[bitId];
2019-01-13 12:12:27 +01:00
}
2019-01-13 20:50:08 +01:00
void SetHillTrainerFlag(void)
2019-01-13 12:12:27 +01:00
{
u8 i;
2022-03-04 17:02:19 +01:00
u8 trainerIndexStart = GetFloorId() * HILL_TRAINERS_PER_FLOOR;
2019-01-13 12:12:27 +01:00
2022-03-04 17:02:19 +01:00
for (i = 0; i < HILL_TRAINERS_PER_FLOOR; i++)
2019-01-13 12:12:27 +01:00
{
2019-02-07 18:37:28 +01:00
if (gSaveBlock2Ptr->frontier.trainerIds[i] == gTrainerBattleOpponent_A)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
gSaveBlock2Ptr->frontier.trainerFlags |= gBitTable[trainerIndexStart + i];
2019-01-13 12:12:27 +01:00
break;
}
}
if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)
{
2022-03-04 17:02:19 +01:00
for (i = 0; i < HILL_TRAINERS_PER_FLOOR; i++)
2019-01-13 12:12:27 +01:00
{
2019-02-07 18:37:28 +01:00
if (gSaveBlock2Ptr->frontier.trainerIds[i] == gTrainerBattleOpponent_B)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
gSaveBlock2Ptr->frontier.trainerFlags |= gBitTable[trainerIndexStart + i];
2019-01-13 12:12:27 +01:00
break;
}
}
}
}
const u8 *GetTrainerHillTrainerScript(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
return TrainerHill_EventScript_TrainerBattle;
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
static void ShowTrainerHillPostBattleText(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
CopyTrainerHillTrainerText(TRAINER_HILL_TEXT_AFTER, gSpecialVar_LastTalked);
2020-06-04 00:25:16 +02:00
ShowFieldMessageFromBuffer();
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
static void CreateNPCTrainerHillParty(u16 trainerId, u8 firstMonId)
2019-01-13 12:12:27 +01:00
{
u8 trId, level;
2019-11-13 22:10:05 +01:00
s32 i, floorId, partySlot;
2019-01-13 12:12:27 +01:00
2022-03-04 17:02:19 +01:00
if (trainerId == 0 || trainerId > HILL_TRAINERS_PER_FLOOR)
2019-01-13 12:12:27 +01:00
return;
trId = trainerId - 1;
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
2019-01-13 12:12:27 +01:00
level = GetHighestLevelInPlayerParty();
2019-01-13 13:15:23 +01:00
floorId = GetFloorId();
2022-03-04 17:02:19 +01:00
for (i = firstMonId, partySlot = 0; i < firstMonId + PARTY_SIZE / 2; i++, partySlot++)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
u8 id = sTrainerPartySlots[trId][partySlot];
2019-01-13 12:12:27 +01:00
struct Pokemon *mon = &gEnemyParty[i];
CreateBattleTowerMon(mon, &sHillData->floors[floorId].trainers[trId].mons[id]);
2019-11-13 22:10:05 +01:00
SetTrainerHillMonLevel(mon, level);
2019-01-13 12:12:27 +01:00
}
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
}
2019-01-13 20:50:08 +01:00
void FillHillTrainerParty(void)
2019-01-13 12:12:27 +01:00
{
ZeroEnemyPartyMons();
2019-11-13 22:10:05 +01:00
CreateNPCTrainerHillParty(gTrainerBattleOpponent_A, 0);
2019-01-13 12:12:27 +01:00
}
2019-01-13 20:50:08 +01:00
void FillHillTrainersParties(void)
2019-01-13 12:12:27 +01:00
{
ZeroEnemyPartyMons();
2019-11-13 22:10:05 +01:00
CreateNPCTrainerHillParty(gTrainerBattleOpponent_A, 0);
2022-03-04 17:02:19 +01:00
CreateNPCTrainerHillParty(gTrainerBattleOpponent_B, PARTY_SIZE / 2);
2019-01-13 12:12:27 +01:00
}
// This function is unused, but my best guess is
// it was supposed to return AI scripts for trainer
// hill trainers.
2019-11-13 22:10:05 +01:00
u32 GetTrainerHillAIFlags(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
return (AI_SCRIPT_CHECK_BAD_MOVE | AI_SCRIPT_TRY_TO_FAINT | AI_SCRIPT_CHECK_VIABILITY);
2019-01-13 12:12:27 +01:00
}
2019-01-13 20:50:08 +01:00
u8 GetTrainerEncounterMusicIdInTrainerHill(u16 trainerId)
2019-01-13 12:12:27 +01:00
{
s32 i;
u8 trId, facilityClass;
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
2019-01-13 12:12:27 +01:00
trId = trainerId - 1;
facilityClass = sHillData->floors[sHillData->floorId].trainers[trId].facilityClass;
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
2019-11-13 22:10:05 +01:00
for (i = 0; i < ARRAY_COUNT(sTrainerClassesAndMusic); i++)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
if (sTrainerClassesAndMusic[i].trainerClass == gFacilityClassToTrainerClass[facilityClass])
return sTrainerClassesAndMusic[i].musicId;
2019-01-13 12:12:27 +01:00
}
return 0;
}
2019-11-13 22:10:05 +01:00
static void SetTrainerHillMonLevel(struct Pokemon *mon, u8 level)
2019-01-13 12:12:27 +01:00
{
u16 species = GetMonData(mon, MON_DATA_SPECIES, NULL);
u32 exp = gExperienceTables[gBaseStats[species].growthRate][level];
SetMonData(mon, MON_DATA_EXP, &exp);
SetMonData(mon, MON_DATA_LEVEL, &level);
CalculateMonStats(mon);
}
2019-11-13 22:10:05 +01:00
u8 GetNumFloorsInTrainerHillChallenge(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
u8 floors;
2019-01-13 12:12:27 +01:00
2019-01-13 13:15:23 +01:00
SetUpDataStruct();
2022-03-04 17:02:19 +01:00
floors = sHillData->challenge.numFloors;
2019-01-13 13:15:23 +01:00
FreeDataStruct();
2019-01-13 12:12:27 +01:00
2019-11-13 22:10:05 +01:00
return floors;
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
static void SetAllTrainerFlags(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
gSaveBlock2Ptr->frontier.trainerFlags = 0xFF;
2019-01-13 12:12:27 +01:00
}
// Palette never loaded, OnTrainerHillEReaderChallengeFloor always FALSE
void TryLoadTrainerHillEReaderPalette(void)
2019-01-13 12:12:27 +01:00
{
if (OnTrainerHillEReaderChallengeFloor() == TRUE)
LoadPalette(sEReader_Pal, 0x70, 0x20);
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
static void GetGameSaved(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
gSpecialVar_Result = gSaveBlock2Ptr->frontier.savedGame;
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
static void SetGameSaved(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
gSaveBlock2Ptr->frontier.savedGame = TRUE;
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
static void ClearGameSaved(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
gSaveBlock2Ptr->frontier.savedGame = FALSE;
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
// Always FALSE
bool32 OnTrainerHillEReaderChallengeFloor(void)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
if (!InTrainerHillChallenge() || GetCurrentTrainerHillMapId() == TRAINER_HILL_ENTRANCE)
2019-01-13 12:12:27 +01:00
return FALSE;
GetInEReaderMode();
if (gSpecialVar_Result == FALSE)
2019-01-13 12:12:27 +01:00
return FALSE;
else
return TRUE;
}
2019-11-13 22:10:05 +01:00
static void GetChallengeWon(void)
2019-01-13 12:12:27 +01:00
{
if (gSaveBlock1Ptr->trainerHill.hasLost)
2019-11-13 22:10:05 +01:00
gSpecialVar_Result = FALSE;
2019-01-13 12:12:27 +01:00
else
2019-11-13 22:10:05 +01:00
gSpecialVar_Result = TRUE;
2019-01-13 12:12:27 +01:00
}
2022-03-04 17:02:19 +01:00
static void TrainerHillSetMode(void)
2019-01-13 12:12:27 +01:00
{
2022-03-04 17:02:19 +01:00
gSaveBlock1Ptr->trainerHill.mode = gSpecialVar_0x8005;
gSaveBlock1Ptr->trainerHill.bestTime = gSaveBlock1Ptr->trainerHillTimes[gSpecialVar_0x8005];
2019-01-13 12:12:27 +01:00
}
2022-04-01 07:21:00 +02:00
// Determines which prize list to use from the set of prize lists.
static u8 GetPrizeListId(bool8 allowTMs)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
u8 prizeListId, i, modBy;
2019-01-13 12:12:27 +01:00
2022-04-01 07:21:00 +02:00
// The initial selection depends on the trainer numbers for the completed challenge.
// These don't change with the available challenge modes, so Normal/Unique will always
// have a prizeListId of 8, and Variety/Expert will have a prizeListId of 24.
2019-11-13 22:10:05 +01:00
prizeListId = 0;
for (i = 0; i < NUM_TRAINER_HILL_FLOORS; i++)
2019-01-13 12:12:27 +01:00
{
2019-11-13 22:10:05 +01:00
prizeListId ^= sHillData->floors[i].trainerNum1 & 0x1F;
prizeListId ^= sHillData->floors[i].trainerNum2 & 0x1F;
2019-01-13 12:12:27 +01:00
}
2022-04-01 07:21:00 +02:00
// In practice, the conditional below is always true.
// The 2nd half of the lists in both sets of lists all have a TM as the "grand prize", while the 1st half do not,
// so taking the mod of the (total / 2) ensures that a prize list without a TM will be used.
if (allowTMs)
2019-11-13 22:10:05 +01:00
modBy = NUM_TRAINER_HILL_PRIZE_LISTS;
2019-01-13 12:12:27 +01:00
else
2019-11-13 22:10:05 +01:00
modBy = NUM_TRAINER_HILL_PRIZE_LISTS / 2;
2019-01-13 12:12:27 +01:00
2019-11-13 22:10:05 +01:00
prizeListId %= modBy;
return prizeListId;
2019-01-13 12:12:27 +01:00
}
2019-11-13 22:10:05 +01:00
static u16 GetPrizeItemId(void)
2019-01-13 12:12:27 +01:00
{
u8 i;
2019-11-13 22:10:05 +01:00
const u16 *prizeList;
2022-04-01 07:21:00 +02:00
s32 trainerNumSum = 0, prizeListSetId, minutes, id;
2019-01-13 12:12:27 +01:00
2022-04-01 07:21:00 +02:00
// First determine which set of prize lists to use. The sets of lists only differ in
// what TMs they can offer as the "grand prize" for a time under 12 minutes.
// Which set of lists gets used is based on the sum of all the trainer numbers for that
// challenge. These don't change with the available challenge modes, so Normal will always
// have a prizeListSetId of 0, and Unique/Variety/Expert will have a prizeListSetId of 1.
2019-11-13 22:10:05 +01:00
for (i = 0; i < NUM_TRAINER_HILL_FLOORS; i++)
2019-01-13 12:12:27 +01:00
{
2022-04-01 07:21:00 +02:00
trainerNumSum += sHillData->floors[i].trainerNum1;
trainerNumSum += sHillData->floors[i].trainerNum2;
2019-01-13 12:12:27 +01:00
}
2022-04-01 07:21:00 +02:00
prizeListSetId = trainerNumSum / 256;
prizeListSetId %= (int)ARRAY_COUNT(sPrizeListSets);
2019-01-13 12:12:27 +01:00
2022-04-01 07:21:00 +02:00
// Now get which prize list to use from the set. See GetPrizeListId for details.
// The below conditional will always be true, because a Trainer Hill challenge can't be entered
// until the player has entered the Hall of Fame (FLAG_SYS_GAME_CLEAR is set) and because all
// of the available challenge modes have the full 8 trainers (NUM_TRAINER_HILL_TRAINERS).
2022-03-04 17:02:19 +01:00
if (FlagGet(FLAG_SYS_GAME_CLEAR) && sHillData->challenge.numTrainers == NUM_TRAINER_HILL_TRAINERS)
2019-11-13 22:10:05 +01:00
i = GetPrizeListId(TRUE);
2019-01-13 12:12:27 +01:00
else
2019-11-13 22:10:05 +01:00
i = GetPrizeListId(FALSE);
2019-01-13 12:12:27 +01:00
2022-04-01 07:21:00 +02:00
// 1 is added to Expert mode's prize list selection because otherwise it has the same prizes as Variety
2022-03-04 17:02:19 +01:00
if (gSaveBlock1Ptr->trainerHill.mode == HILL_MODE_EXPERT)
2019-11-13 22:10:05 +01:00
i = (i + 1) % NUM_TRAINER_HILL_PRIZE_LISTS;
2019-01-13 12:12:27 +01:00
2022-04-01 07:21:00 +02:00
// After the above (non-random) calculations, the following are the possible prize list selections:
// sPrizeListSets[0][8] (Normal)
// sPrizeListSets[1][4] (Variety)
// sPrizeListSets[1][8] (Unique)
// sPrizeListSets[1][5] (Expert)
2019-11-13 22:10:05 +01:00
prizeList = sPrizeListSets[prizeListSetId][i];
2022-04-01 07:21:00 +02:00
// Which prize is given from the list depends on the time scored.
// The prize for any time after 12 minutes is the same in every list.
// The prizes for a time under 12 minutes are:
// - ITEM_TM11_SUNNY_DAY (Normal)
// - ITEM_ELIXIR (Variety)
// - ITEM_TM19_GIGA_DRAIN (Unique)
// - ITEM_TM31_BRICK_BREAK (Expert)
// As an additional note, if players were allowed to enter a Trainer Hill challenge before
// entering the Hall of Fame, there would be 1 additional prize possibility (ITEM_MAX_ETHER)
// as Normal / Unique modes would use sPrizeListSets[0][3] / sPrizeListSets[1][3] respectively.
minutes = (signed)(gSaveBlock1Ptr->trainerHill.timer) / (60 * 60);
2019-01-13 12:12:27 +01:00
if (minutes < 12)
2022-04-01 07:21:00 +02:00
id = 0; // Depends on list
2019-01-13 12:12:27 +01:00
else if (minutes < 13)
2022-04-01 07:21:00 +02:00
id = 1; // ITEM_ETHER
2019-01-13 12:12:27 +01:00
else if (minutes < 14)
2022-04-01 07:21:00 +02:00
id = 2; // ITEM_MAX_POTION
2019-01-13 12:12:27 +01:00
else if (minutes < 16)
2022-04-01 07:21:00 +02:00
id = 3; // ITEM_REVIVE
2019-01-13 12:12:27 +01:00
else if (minutes < 18)
2022-04-01 07:21:00 +02:00
id = 4; // ITEM_FLUFFY_TAIL
2019-01-13 12:12:27 +01:00
else
2022-04-01 07:21:00 +02:00
id = 5; // ITEM_GREAT_BALL
2019-01-13 12:12:27 +01:00
2019-11-13 22:10:05 +01:00
return prizeList[id];
2019-01-13 12:12:27 +01:00
}