#include "global.h" #include "malloc.h" #include "battle.h" #include "battle_tower.h" #include "battle_setup.h" #include "ereader_helpers.h" #include "event_data.h" #include "event_scripts.h" #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" #include "strings.h" #include "text.h" #include "trainer_hill.h" #include "window.h" #include "util.h" #include "constants/battle_ai.h" #include "constants/event_object_movement.h" #include "constants/event_objects.h" #include "constants/items.h" #include "constants/layouts.h" #include "constants/moves.h" #include "constants/trainers.h" #include "constants/easy_chat.h" #include "constants/trainer_hill.h" #include "constants/trainer_types.h" #define HILL_TAG_NORMAL 0 #define HILL_TAG_VARIETY 1 #define HILL_TAG_UNIQUE 2 #define HILL_TAG_EXPERT 3 #define HILL_MAX_TIME 215999 // 60 * 60 * 60 - 1 // EWRAM struct TrHillStruct2 { u8 floorId; struct TrHillTag tag; struct TrHillFloor floors[NUM_TRAINER_HILL_FLOORS]; }; static EWRAM_DATA struct TrHillStruct2 *sHillData = NULL; static EWRAM_DATA struct TrHillRoomTrainers *sRoomTrainers = NULL; EWRAM_DATA u32 *gTrainerHillVBlankCounter = NULL; // This file's functions. static void TrainerHillStartChallenge(void); static void GetOwnerState(void); static void GiveChallengePrize(void); static void CheckFinalTime(void); static void TrainerHillResumeTimer(void); static void TrainerHillSetPlayerLost(void); static void TrainerHillGetChallengeStatus(void); static void BufferChallengeTime(void); static void GetAllFloorsUsed(void); static void GetInEReaderMode(void); 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); static void TrainerHillSetTag(void); static void SetUpDataStruct(void); static void FreeDataStruct(void); static void TrainerHillDummy(void); static void SetTimerValue(u32 *dst, u32 val); static u32 GetTimerValue(u32 *src); static void SetTrainerHillMonLevel(struct Pokemon *mon, u8 level); static u16 GetPrizeItemId(void); // const data #include "data/battle_frontier/trainer_hill.h" struct { u8 trainerClass; u8 musicId; } static const sTrainerClassesAndMusic[] = { {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}, {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}, }; 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}; static const u16 sPrizeListGigaDrain[] = {ITEM_TM19_GIGA_DRAIN, ITEM_ETHER, ITEM_MAX_POTION, ITEM_REVIVE, ITEM_FLUFFY_TAIL, ITEM_GREAT_BALL}; 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 }; static const u16 *const sPrizeLists2[NUM_TRAINER_HILL_PRIZE_LISTS] = { sPrizeListRareCandy2, sPrizeListLuxuryBall2, sPrizeListMaxRevive2, sPrizeListMaxEther2, sPrizeListElixir2, sPrizeListBrickBreak, sPrizeListTorment, sPrizeListSkillSwap, sPrizeListGigaDrain, sPrizeListAttract }; static const u16 *const *const sPrizeListSets[] = { sPrizeLists1, sPrizeLists2 }; static const u16 sEReader_Pal[] = INCBIN_U16("graphics/misc/trainer_hill_ereader.gbapal"); static const u8 sRecordWinColors[] = {TEXT_COLOR_TRANSPARENT, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_LIGHT_GRAY}; static const struct TrHillTag *const sDataPerTag[] = { &sDataTagNormal, &sDataTagVariety, &sDataTagUnique, &sDataTagExpert, }; // Unused. static const u8 *const sFloorStrings[] = { gText_TrainerHill1F, gText_TrainerHill2F, gText_TrainerHill3F, gText_TrainerHill4F, }; static void (* const sHillFunctions[])(void) = { [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, [TRAINER_HILL_FUNC_IN_CHALLENGE] = IsTrainerHillChallengeActive, [TRAINER_HILL_FUNC_POST_BATTLE_TEXT] = ShowTrainerHillPostBattleText, [TRAINER_HILL_FUNC_SET_ALL_TRAINER_FLAGS] = SetAllTrainerFlags, [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, [TRAINER_HILL_FUNC_SET_TAG] = TrainerHillSetTag, }; static const u8 *const sTagMatchStrings[] = { gText_NormalTagMatch, gText_VarietyTagMatch, gText_UniqueTagMatch, gText_ExpertTagMatch, }; static const struct ObjectEventTemplate sTrainerObjectEventTemplate = { .graphicsId = OBJ_EVENT_GFX_RIVAL_BRENDAN_NORMAL, .elevation = 3, .movementType = MOVEMENT_TYPE_LOOK_AROUND, .movementRangeX = 1, .movementRangeY = 1, .trainerType = TRAINER_TYPE_NORMAL, }; static const u32 sNextFloorMapNum[NUM_TRAINER_HILL_FLOORS] = { MAP_NUM(TRAINER_HILL_2F), MAP_NUM(TRAINER_HILL_3F), MAP_NUM(TRAINER_HILL_4F), MAP_NUM(TRAINER_HILL_ROOF) }; static const u8 sTrainerPartySlots[][PARTY_SIZE / 2] = { {0, 1, 2}, {3, 4, 5} }; // code void CallTrainerHillFunction(void) { SetUpDataStruct(); sHillFunctions[gSpecialVar_0x8004](); FreeDataStruct(); } void ResetTrainerHillResults(void) { s32 i; gSaveBlock2Ptr->frontier.savedGame = 0; gSaveBlock2Ptr->frontier.unk_EF9 = 0; gSaveBlock1Ptr->trainerHill.bestTime = 0; for (i = 0; i < 4; i++) SetTimerValue(&gSaveBlock1Ptr->trainerHillTimes[i], HILL_MAX_TIME); } static u8 GetFloorId(void) { return gMapHeader.mapLayoutId - LAYOUT_TRAINER_HILL_1F; } u8 GetTrainerHillOpponentClass(u16 trainerId) { u8 id = trainerId - 1; return gFacilityClassToTrainerClass[sRoomTrainers->facilityClass[id]]; } void GetTrainerHillTrainerName(u8 *dst, u16 trainerId) { s32 i; u8 id = trainerId - 1; for (i = 0; i < HILL_TRAINER_NAME_LENGTH; i++) dst[i] = sRoomTrainers->name[id][i]; } u8 GetTrainerHillTrainerFrontSpriteId(u16 trainerId) { u8 id, facilityClass; SetUpDataStruct(); id = trainerId - 1; facilityClass = sHillData->floors[sHillData->floorId].trainers[id].facilityClass; FreeDataStruct(); return gFacilityClassToPicIndex[facilityClass]; } void InitTrainerHillBattleStruct(void) { s32 i, j; SetUpDataStruct(); sRoomTrainers = AllocZeroed(sizeof(*sRoomTrainers)); for (i = 0; i < 2; i++) { for (j = 0; j < HILL_TRAINER_NAME_LENGTH; j++) { sRoomTrainers->name[i][j] = sHillData->floors[sHillData->floorId].trainers[i].name[j]; } sRoomTrainers->facilityClass[i] = sHillData->floors[sHillData->floorId].trainers[i].facilityClass; } SetTrainerHillVBlankCounter(&gSaveBlock1Ptr->trainerHill.timer); FreeDataStruct(); } void FreeTrainerHillBattleStruct(void) { if (sRoomTrainers != NULL) FREE_AND_SET_NULL(sRoomTrainers); } static void SetUpDataStruct(void) { if (sHillData == NULL) { sHillData = AllocZeroed(sizeof(*sHillData)); sHillData->floorId = gMapHeader.mapLayoutId - LAYOUT_TRAINER_HILL_1F; CpuCopy32(sDataPerTag[gSaveBlock1Ptr->trainerHill.tag], &sHillData->tag, sizeof(sHillData->tag) + 4 * sizeof(struct TrHillFloor)); TrainerHillDummy(); } } static void FreeDataStruct(void) { if (sHillData != NULL) FREE_AND_SET_NULL(sHillData); } void CopyTrainerHillTrainerText(u8 which, u16 trainerId) { u8 id, floorId; SetUpDataStruct(); floorId = GetFloorId(); id = trainerId - 1; switch (which) { case TRAINER_HILL_TEXT_INTRO: FrontierSpeechToString(sHillData->floors[floorId].trainers[id].speechBefore); break; case TRAINER_HILL_TEXT_PLAYER_LOST: FrontierSpeechToString(sHillData->floors[floorId].trainers[id].speechWin); break; case TRAINER_HILL_TEXT_PLAYER_WON: FrontierSpeechToString(sHillData->floors[floorId].trainers[id].speechLose); break; case TRAINER_HILL_TEXT_AFTER: FrontierSpeechToString(sHillData->floors[floorId].trainers[id].speechAfter); break; } FreeDataStruct(); } static void TrainerHillStartChallenge(void) { TrainerHillDummy(); if (!ReadTrainerHillAndValidate()) gSaveBlock1Ptr->trainerHill.field_3D6E_0f = 1; else gSaveBlock1Ptr->trainerHill.field_3D6E_0f = 0; gSaveBlock1Ptr->trainerHill.unk_3D6C = 0; SetTrainerHillVBlankCounter(&gSaveBlock1Ptr->trainerHill.timer); gSaveBlock1Ptr->trainerHill.timer = 0; gSaveBlock1Ptr->trainerHill.spokeToOwner = 0; gSaveBlock1Ptr->trainerHill.checkedFinalTime = 0; gSaveBlock1Ptr->trainerHill.maybeECardScanDuringChallenge = 0; gSaveBlock2Ptr->frontier.trainerFlags = 0; gBattleOutcome = 0; gSaveBlock1Ptr->trainerHill.receivedPrize = 0; } static void GetOwnerState(void) { ClearTrainerHillVBlankCounter(); gSpecialVar_Result = 0; if (gSaveBlock1Ptr->trainerHill.spokeToOwner) gSpecialVar_Result++; if (gSaveBlock1Ptr->trainerHill.receivedPrize && gSaveBlock1Ptr->trainerHill.checkedFinalTime) gSpecialVar_Result++; gSaveBlock1Ptr->trainerHill.spokeToOwner = TRUE; } static void GiveChallengePrize(void) { u16 itemId = GetPrizeItemId(); if (sHillData->tag.numFloors != NUM_TRAINER_HILL_FLOORS || gSaveBlock1Ptr->trainerHill.receivedPrize) { gSpecialVar_Result = 2; } else if (AddBagItem(itemId, 1) == TRUE) { CopyItemName(itemId, gStringVar2); gSaveBlock1Ptr->trainerHill.receivedPrize = TRUE; gSaveBlock2Ptr->frontier.unk_EF9 = 0; gSpecialVar_Result = 0; } else { gSpecialVar_Result = 1; } } // 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 static void CheckFinalTime(void) { if (gSaveBlock1Ptr->trainerHill.checkedFinalTime) { gSpecialVar_Result = 2; } else if (GetTimerValue(&gSaveBlock1Ptr->trainerHill.bestTime) > gSaveBlock1Ptr->trainerHill.timer) { SetTimerValue(&gSaveBlock1Ptr->trainerHill.bestTime, gSaveBlock1Ptr->trainerHill.timer); gSaveBlock1Ptr->trainerHillTimes[gSaveBlock1Ptr->trainerHill.tag] = gSaveBlock1Ptr->trainerHill.bestTime; gSpecialVar_Result = 0; } else { gSpecialVar_Result = 1; } gSaveBlock1Ptr->trainerHill.checkedFinalTime = TRUE; } static void TrainerHillResumeTimer(void) { if (!gSaveBlock1Ptr->trainerHill.spokeToOwner) { if (gSaveBlock1Ptr->trainerHill.timer >= HILL_MAX_TIME) gSaveBlock1Ptr->trainerHill.timer = HILL_MAX_TIME; else SetTrainerHillVBlankCounter(&gSaveBlock1Ptr->trainerHill.timer); } } static void TrainerHillSetPlayerLost(void) { gSaveBlock1Ptr->trainerHill.hasLost = TRUE; } static void TrainerHillGetChallengeStatus(void) { if (gSaveBlock1Ptr->trainerHill.hasLost) { // The player lost their last match. gSaveBlock1Ptr->trainerHill.hasLost = FALSE; gSpecialVar_Result = TRAINER_HILL_PLAYER_STATUS_LOST; } else if (gSaveBlock1Ptr->trainerHill.maybeECardScanDuringChallenge) { // Unreachable code. Something relating to eCards? gSaveBlock1Ptr->trainerHill.maybeECardScanDuringChallenge = 0; gSpecialVar_Result = TRAINER_HILL_PLAYER_STATUS_ECARD_SCANNED; } else { // Continue playing. gSpecialVar_Result = TRAINER_HILL_PLAYER_STATUS_NORMAL; } } static void BufferChallengeTime(void) { s32 total, minutes, secondsWhole, secondsFraction; total = gSaveBlock1Ptr->trainerHill.timer; 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); } // Returns TRUE if all 4 floors are used // Returns FALSE otherwise, and buffers the number of floors used // The only time fewer than all 4 floors are used is for the JP-exclusive E-Reader and Default modes static void GetAllFloorsUsed(void) { SetUpDataStruct(); if (sHillData->tag.numFloors != NUM_TRAINER_HILL_FLOORS) { ConvertIntToDecimalStringN(gStringVar1, sHillData->tag.numFloors, STR_CONV_MODE_LEFT_ALIGN, 1); gSpecialVar_Result = FALSE; } else { gSpecialVar_Result = TRUE; } FreeDataStruct(); } // 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) { SetUpDataStruct(); gSpecialVar_Result = FALSE; FreeDataStruct(); } bool8 InTrainerHillChallenge(void) { if (VarGet(VAR_TRAINER_HILL_IS_ACTIVE) == 0) return FALSE; else if (gSaveBlock1Ptr->trainerHill.spokeToOwner) return FALSE; else if (GetCurrentTrainerHillMapId() != 0) return TRUE; else return FALSE; } static void IsTrainerHillChallengeActive(void) { if (!InTrainerHillChallenge()) gSpecialVar_Result = FALSE; else gSpecialVar_Result = TRUE; } static void TrainerHillDummy_Unused(void) { } static void TrainerHillDummy(void) { } void PrintOnTrainerHillRecordsWindow(void) { s32 i, x, y; u32 total, minutes, secondsWhole, secondsFraction; SetUpDataStruct(); FillWindowPixelBuffer(0, PIXEL_FILL(0)); x = GetStringCenterAlignXOffset(FONT_NORMAL, gText_TimeBoard, 0xD0); AddTextPrinterParameterized3(0, FONT_NORMAL, x, 2, sRecordWinColors, TEXT_SKIP_DRAW, gText_TimeBoard); y = 18; for (i = 0; i < 4; i++) { AddTextPrinterParameterized3(0, FONT_NORMAL, 0, y, sRecordWinColors, TEXT_SKIP_DRAW, sTagMatchStrings[i]); y += 15; total = GetTimerValue(&gSaveBlock1Ptr->trainerHillTimes[i]); 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); x = GetStringRightAlignXOffset(FONT_NORMAL, gStringVar4, 0xD0); AddTextPrinterParameterized3(0, FONT_NORMAL, x, y, sRecordWinColors, TEXT_SKIP_DRAW, gStringVar4); y += 17; } PutWindowTilemap(0); CopyWindowToVram(0, COPYWIN_FULL); FreeDataStruct(); } // Leftover from Fire Red / Leaf Green as in these games, // the timer had to be xored by the encryption key in Sav2. static u32 GetTimerValue(u32 *src) { return *src; } static void SetTimerValue(u32 *dst, u32 val) { *dst = val; } void LoadTrainerHillObjectEventTemplates(void) { u8 i, floorId; struct ObjectEventTemplate *eventTemplates = gSaveBlock1Ptr->objectEventTemplates; if (!LoadTrainerHillFloorObjectEventScripts()) return; SetUpDataStruct(); for (i = 0; i < 2; i++) gSaveBlock2Ptr->frontier.trainerIds[i] = 0xFFFF; CpuFill32(0, gSaveBlock1Ptr->objectEventTemplates, sizeof(gSaveBlock1Ptr->objectEventTemplates)); floorId = GetFloorId(); for (i = 0; i < 2; i++) { u8 bits; eventTemplates[i] = sTrainerObjectEventTemplate; eventTemplates[i].localId = i + 1; eventTemplates[i].graphicsId = FacilityClassToGraphicsId(sHillData->floors[floorId].trainers[i].facilityClass); eventTemplates[i].x = sHillData->floors[floorId].display.coords[i] & 0xF; eventTemplates[i].y = ((sHillData->floors[floorId].display.coords[i] >> 4) & 0xF) + 5; bits = i << 2; eventTemplates[i].movementType = ((sHillData->floors[floorId].display.direction >> bits) & 0xF) + MOVEMENT_TYPE_FACE_UP; eventTemplates[i].trainerRange_berryTreeId = (sHillData->floors[floorId].display.range >> bits) & 0xF; eventTemplates[i].script = TrainerHill_EventScript_TrainerBattle; gSaveBlock2Ptr->frontier.trainerIds[i] = i + 1; } FreeDataStruct(); } bool32 LoadTrainerHillFloorObjectEventScripts(void) { SetUpDataStruct(); // Something may have been dummied here FreeDataStruct(); return TRUE; } static u16 GetMetatileForFloor(u8 floorId, u32 x, u32 y, u32 stride) // stride is always 16 { bool8 impassable; u16 metatile; u16 elevation; impassable = (sHillData->floors[floorId].display.collisionData[y] >> (15 - x) & 1); metatile = sHillData->floors[floorId].display.metatileData[stride * y + x] + NUM_METATILES_IN_PRIMARY; elevation = 3 << METATILE_ELEVATION_SHIFT; return ((impassable << METATILE_COLLISION_SHIFT) & METATILE_COLLISION_MASK) | elevation | (metatile & METATILE_ID_MASK); } void GenerateTrainerHillFloorLayout(u16 *mapArg) { s32 i, j; u16 *src, *dst; u8 mapId = GetCurrentTrainerHillMapId(); if (mapId == TRAINER_HILL_ENTRANCE) { InitMapFromSavedGame(); return; } SetUpDataStruct(); if (mapId == TRAINER_HILL_ROOF) { InitMapFromSavedGame(); FreeDataStruct(); return; } mapId = GetFloorId(); src = gMapHeader.mapLayout->map; gBackupMapLayout.map = mapArg; gBackupMapLayout.width = 31; gBackupMapLayout.height = 35; dst = mapArg + 224; // First 5 rows of the map (Entrance / Exit) are always the same for (i = 0; i < 5; i++) { for (j = 0; j < 16; j++) dst[j] = src[j]; dst += 31; src += 16; } // Load the 16x16 floor-specific layout for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) dst[j] = GetMetatileForFloor(mapId, j, i, 16); dst += 31; } RunOnLoadMapScript(); FreeDataStruct(); } bool32 InTrainerHill(void) { bool32 ret; if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_1F || gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_2F || gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_3F || gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_4F) ret = TRUE; else ret = FALSE; return ret; } u8 GetCurrentTrainerHillMapId(void) { u8 mapId; if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_1F) mapId = TRAINER_HILL_1F; else if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_2F) mapId = TRAINER_HILL_2F; else if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_3F) mapId = TRAINER_HILL_3F; else if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_4F) mapId = TRAINER_HILL_4F; else if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_ROOF) mapId = TRAINER_HILL_ROOF; else if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_ENTRANCE) mapId = TRAINER_HILL_ENTRANCE; else mapId = 0; return mapId; } // Unused static bool32 OnTrainerHillRoof(void) { bool32 onRoof; if (gMapHeader.mapLayoutId == LAYOUT_TRAINER_HILL_ROOF) onRoof = TRUE; else onRoof = FALSE; return onRoof; } const struct WarpEvent* SetWarpDestinationTrainerHill4F(void) { const struct MapHeader *header = Overworld_GetMapHeaderByGroupAndId(MAP_GROUP(TRAINER_HILL_4F), MAP_NUM(TRAINER_HILL_4F)); return &header->events->warps[1]; } // For warping from the roof in challenges where the 4F is not the final challenge floor // This would only occur in the JP-exclusive Default and E-Reader challenges const struct WarpEvent* SetWarpDestinationTrainerHillFinalFloor(u8 warpEventId) { u8 numFloors; const struct MapHeader *header; if (warpEventId == 1) return &gMapHeader.events->warps[1]; numFloors = GetNumFloorsInTrainerHillChallenge(); if (numFloors == 0 || numFloors > NUM_TRAINER_HILL_FLOORS) numFloors = NUM_TRAINER_HILL_FLOORS; header = Overworld_GetMapHeaderByGroupAndId(MAP_GROUP(TRAINER_HILL_4F), sNextFloorMapNum[numFloors - 1]); return &header->events->warps[0]; } u16 LocalIdToHillTrainerId(u8 localId) { return gSaveBlock2Ptr->frontier.trainerIds[localId - 1]; } bool8 GetHillTrainerFlag(u8 objectEventId) { u32 floorId = GetFloorId() * 2; u8 bitId = gObjectEvents[objectEventId].localId - 1 + floorId; return gSaveBlock2Ptr->frontier.trainerFlags & gBitTable[bitId]; } void SetHillTrainerFlag(void) { u8 i; u8 floorId = GetFloorId() * 2; for (i = 0; i < 2; i++) { if (gSaveBlock2Ptr->frontier.trainerIds[i] == gTrainerBattleOpponent_A) { gSaveBlock2Ptr->frontier.trainerFlags |= gBitTable[floorId + i]; break; } } if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) { for (i = 0; i < 2; i++) { if (gSaveBlock2Ptr->frontier.trainerIds[i] == gTrainerBattleOpponent_B) { gSaveBlock2Ptr->frontier.trainerFlags |= gBitTable[floorId + i]; break; } } } } const u8 *GetTrainerHillTrainerScript(void) { return TrainerHill_EventScript_TrainerBattle; } static void ShowTrainerHillPostBattleText(void) { CopyTrainerHillTrainerText(TRAINER_HILL_TEXT_AFTER, gSpecialVar_LastTalked); ShowFieldMessageFromBuffer(); } static void CreateNPCTrainerHillParty(u16 trainerId, u8 firstMonId) { u8 trId, level; s32 i, floorId, partySlot; if (trainerId == 0 || trainerId > 2) return; trId = trainerId - 1; SetUpDataStruct(); level = GetHighestLevelInPlayerParty(); floorId = GetFloorId(); for (i = firstMonId, partySlot = 0; i < firstMonId + 3; i++, partySlot++) { u8 id = sTrainerPartySlots[trId][partySlot]; struct Pokemon *mon = &gEnemyParty[i]; CreateBattleTowerMon(mon, &sHillData->floors[floorId].trainers[trId].mons[id]); SetTrainerHillMonLevel(mon, level); } FreeDataStruct(); } void FillHillTrainerParty(void) { ZeroEnemyPartyMons(); CreateNPCTrainerHillParty(gTrainerBattleOpponent_A, 0); } void FillHillTrainersParties(void) { ZeroEnemyPartyMons(); CreateNPCTrainerHillParty(gTrainerBattleOpponent_A, 0); CreateNPCTrainerHillParty(gTrainerBattleOpponent_B, 3); } // This function is unused, but my best guess is // it was supposed to return AI scripts for trainer // hill trainers. u32 GetTrainerHillAIFlags(void) { return (AI_SCRIPT_CHECK_BAD_MOVE | AI_SCRIPT_TRY_TO_FAINT | AI_SCRIPT_CHECK_VIABILITY); } u8 GetTrainerEncounterMusicIdInTrainerHill(u16 trainerId) { s32 i; u8 trId, facilityClass; SetUpDataStruct(); trId = trainerId - 1; facilityClass = sHillData->floors[sHillData->floorId].trainers[trId].facilityClass; FreeDataStruct(); for (i = 0; i < ARRAY_COUNT(sTrainerClassesAndMusic); i++) { if (sTrainerClassesAndMusic[i].trainerClass == gFacilityClassToTrainerClass[facilityClass]) return sTrainerClassesAndMusic[i].musicId; } return 0; } static void SetTrainerHillMonLevel(struct Pokemon *mon, u8 level) { 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); } u8 GetNumFloorsInTrainerHillChallenge(void) { u8 floors; SetUpDataStruct(); floors = sHillData->tag.numFloors; FreeDataStruct(); return floors; } static void SetAllTrainerFlags(void) { gSaveBlock2Ptr->frontier.trainerFlags = 0xFF; } // Palette never loaded, OnTrainerHillEReaderChallengeFloor always FALSE void TryLoadTrainerHillEReaderPalette(void) { if (OnTrainerHillEReaderChallengeFloor() == TRUE) LoadPalette(sEReader_Pal, 0x70, 0x20); } static void GetGameSaved(void) { gSpecialVar_Result = gSaveBlock2Ptr->frontier.savedGame; } static void SetGameSaved(void) { gSaveBlock2Ptr->frontier.savedGame = TRUE; } static void ClearGameSaved(void) { gSaveBlock2Ptr->frontier.savedGame = FALSE; } // Always FALSE bool32 OnTrainerHillEReaderChallengeFloor(void) { if (!InTrainerHillChallenge() || GetCurrentTrainerHillMapId() == TRAINER_HILL_ENTRANCE) return FALSE; GetInEReaderMode(); if (gSpecialVar_Result == FALSE) return FALSE; else return TRUE; } static void GetChallengeWon(void) { if (gSaveBlock1Ptr->trainerHill.hasLost) gSpecialVar_Result = FALSE; else gSpecialVar_Result = TRUE; } static void TrainerHillSetTag(void) { gSaveBlock1Ptr->trainerHill.tag = gSpecialVar_0x8005; gSaveBlock1Ptr->trainerHill.bestTime = gSaveBlock1Ptr->trainerHillTimes[gSpecialVar_0x8005]; } static u8 GetPrizeListId(bool8 maxTrainers) { u8 prizeListId, i, modBy; prizeListId = 0; for (i = 0; i < NUM_TRAINER_HILL_FLOORS; i++) { prizeListId ^= sHillData->floors[i].trainerNum1 & 0x1F; prizeListId ^= sHillData->floors[i].trainerNum2 & 0x1F; } // Not possible to win TMs with fewer than 8 trainers if (maxTrainers) modBy = NUM_TRAINER_HILL_PRIZE_LISTS; else modBy = NUM_TRAINER_HILL_PRIZE_LISTS / 2; prizeListId %= modBy; return prizeListId; } static u16 GetPrizeItemId(void) { u8 i; const u16 *prizeList; s32 var = 0, prizeListSetId, minutes, id; for (i = 0; i < NUM_TRAINER_HILL_FLOORS; i++) { var += sHillData->floors[i].trainerNum1; var += sHillData->floors[i].trainerNum2; } prizeListSetId = var / 256; prizeListSetId %= 2; if (FlagGet(FLAG_SYS_GAME_CLEAR) && sHillData->tag.numTrainers == NUM_TRAINER_HILL_TRAINERS) i = GetPrizeListId(TRUE); else i = GetPrizeListId(FALSE); if (gSaveBlock1Ptr->trainerHill.tag == HILL_TAG_EXPERT) i = (i + 1) % NUM_TRAINER_HILL_PRIZE_LISTS; prizeList = sPrizeListSets[prizeListSetId][i]; minutes = (signed)(gSaveBlock1Ptr->trainerHill.timer) / (60 * 60); if (minutes < 12) id = 0; else if (minutes < 13) id = 1; else if (minutes < 14) id = 2; else if (minutes < 16) id = 3; else if (minutes < 18) id = 4; else id = 5; return prizeList[id]; }