diff --git a/asm/macros/event.inc b/asm/macros/event.inc index 49e0109451..d2329a6922 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -2654,3 +2654,11 @@ callnative ScriptChangeFollowerNPCBattlePartner .2byte \battlePartner .endm + + @ Manually buffer a string as the speaker's name for namebox. + @ The next shown message/msgbox will include a namebox, if the provided string is not NULL. + @ An SP_NAME_* constant can also be used, it'll take the name from gSpeakerNamesTable instead. + .macro setspeaker name:req + callnative SetSpeaker + .4byte \name + .endm diff --git a/charmap.txt b/charmap.txt index 83f2cf602b..dd2123f8f9 100644 --- a/charmap.txt +++ b/charmap.txt @@ -459,6 +459,13 @@ JPN = FC 15 ENG = FC 16 PAUSE_MUSIC = FC 17 RESUME_MUSIC = FC 18 +SPEAKER = FC 19 + +@ Speaker names, the order must be matching with include/constants/speaker_names.h +NAME_NONE = 00 +NAME_MOM = 01 +NAME_PLAYER = 02 +NAME_COUNT = 03 @ fonts diff --git a/data/event_scripts.s b/data/event_scripts.s index 9cedbb04d7..2fd8a0a151 100644 --- a/data/event_scripts.s +++ b/data/event_scripts.s @@ -62,6 +62,7 @@ #include "constants/union_room.h" #include "constants/vars.h" #include "constants/weather.h" +#include "constants/speaker_names.h" .include "asm/macros.inc" .include "asm/macros/event.inc" .include "constants/constants.inc" diff --git a/docs/tutorials/how_to_namebox.md b/docs/tutorials/how_to_namebox.md new file mode 100644 index 0000000000..db059af61d --- /dev/null +++ b/docs/tutorials/how_to_namebox.md @@ -0,0 +1,100 @@ +# How to Use Namebox + +_New implementation made by mudskipper13, originally made by Tustin2121._ + +## Overview + +![Npc Trainers](/docs/tutorials/img/namebox/npc_trainers.gif) +![Pokenav](/docs/tutorials/img/namebox/pokenav.gif) +![Messagebox](/docs/tutorials/img/namebox/msgbox.gif) + +This is a broad and self-contained implementation of Tustin2121's namebox feature branch [here](https://github.com/tustin2121/pokeemerald/tree/feature/namebox), which includes the following: +- Cleaner implementation of namebox onto both the field message box _and_ the field PokéNav textbox. +- New configs: + - `OW_NAME_BOX_USE_DYNAMIC_WIDTH` lets the namebox use dynamic window width depending on the speaker's string length. + - When disabled and/or the speaker name is too long, `OW_NAME_BOX_DEFAULT_WIDTH` will be used as the maximum width. + - `OW_NAME_BOX_NPC_TRAINER` lets any approaching NPC trainers shows a namebox in their dialogue automagically. + - `OW_NAME_BOX_DEFAULT_WIDTH` and `OW_NAME_BOX_DEFAULT_HEIGHT` sets the default width and height. + - `OW_NAME_BOX_FOREGROUND_COLOR` and `OW_NAME_BOX_SHADOW_COLOR` sets the default text colors, the background color is handled by the engine. + - `OW_FLAG_SUPPRESS_NAME_BOX` lets you enable/disable the namebox globally, assign a flag from [`include/constants/flags.h`](/include/constants/flags.h) onto this config to be able to use it. +- Added a Speaker Name table, frequently-used names can be stored into `gSpeakerNamesTable` in [`src/data/speaker_names.h`](/src/data/speaker_names.h) and they can accessed by using a `SP_NAME_*` constant defined in [`include/constants/speaker_names.h`](/include/constants/speaker_names.h). +- Added a new scripting macro `setspeaker ([textPointer]/[SP_NAME_*])`. + - Besides a text pointer, it is possible to use the Speaker Name table to set the textPointer with the `gSpeakerNamesTable` array instead. + - Feed it either `NULL` or `SP_NAME_NONE` will remove the namebox instead. + - `release`, `releaseall`, and `closemessage` will automatically remove the namebox, together with the messagebox. +- Added a new text control code/inline text `{SPEAKER NAME_*}`. + - Unlike the `setspeaker` macro, you can only use the `SP_NAME_*` constants for this. It is partly due to the text engine's limitation itself. + - You'll need to add the constants into `charmap.txt` to be able to use them for the same reason as above. + - Feed it `SP_NAME_NONE` to remove the namebox manually. + - Similarly, `release`, `releaseall`, and `closemessage` will automatically remove the namebox, together with the message box. + +## Usage + +### `setspeaker` +#### Using a text pointer +First, define your speaker's string. +``` +Speaker_Jeremy: + .string "Jeremy$" +``` + +And then in your script, add the `setspeaker` with the speaker's name earlier. +``` +... + setspeaker Speaker_Jeremy +... +``` + +If you are using poryscript, you can also include the string right there with the `setspeaker` aka inline. +``` +... + setspeaker("Jeremy") +... +``` +#### Using a `SP_NAME_*` constant +Add the `setspeaker` with your constant. +``` + setspeaker SP_NAME_JEREMY +``` +For instruction on how to add a new Speaker Name, continue [here](#adding-a-new-speaker-name). + +### `SPEAKER` inline +The usage is identical to using `setspeaker` with `SP_NAME_*` constant, but instead it's within your _text_ script and uses the constant you added to `charmap.txt`. +``` + "{SPEAKER NAME_JEREMY}Yo wassup!" +``` +For instruction on how to add a new Speaker Name, continue [here](#adding-a-new-speaker-name). + +### Adding a new Speaker Name +1. Add a new constant to [`include/constants/speaker_names.h`](/include/constants/speaker_names.h) just after `SP_NAME_NONE` _and_ before `SP_NAME_COUNT`. +```diff + enum SpeakerNames { + SP_NAME_NONE = 0, + SP_NAME_MOM, + SP_NAME_PLAYER, ++ SP_NAME_JEREMY, + SP_NAME_COUNT + }; + +``` + +2. Add an entry to `gSpeakerNamesTable` in [`src/data/speaker_names.h`](/src/data/speaker_names.h) with your newly added constant as the array index. +```diff + const u8 *const gSpeakerNamesTable[SP_NAME_COUNT] = + { + [SP_NAME_MOM] = COMPOUND_STRING("MOM"), + [SP_NAME_PLAYER] = COMPOUND_STRING("{PLAYER}"), ++ [SP_NAME_JEREMY] = COMPOUND_STRING("JEREMY"), + }; +``` + +3. In order for this constant to be usable for `{SPEAKER}` inline, you'll need to add your constant onto [`charmap.txt`](/charmap.txt). **Do note that the order here MUST match with the one in [`include/constants/speaker_names.h`](/include/constants/speaker_names.h)!** +```diff + @ Speaker names, the order must be matching with include/constants/speaker_names.h + NAME_NONE = 00 + NAME_MOM = 01 + NAME_PLAYER = 02 +-NAME_COUNT = 03 ++NAME_JEREMY = 03 ++NAME_COUNT = 04 +``` diff --git a/docs/tutorials/img/namebox/msgbox.gif b/docs/tutorials/img/namebox/msgbox.gif new file mode 100644 index 0000000000..a5d9455725 Binary files /dev/null and b/docs/tutorials/img/namebox/msgbox.gif differ diff --git a/docs/tutorials/img/namebox/npc_trainers.gif b/docs/tutorials/img/namebox/npc_trainers.gif new file mode 100644 index 0000000000..6e9e1b13fb Binary files /dev/null and b/docs/tutorials/img/namebox/npc_trainers.gif differ diff --git a/docs/tutorials/img/namebox/pokenav.gif b/docs/tutorials/img/namebox/pokenav.gif new file mode 100644 index 0000000000..6de3f1370d Binary files /dev/null and b/docs/tutorials/img/namebox/pokenav.gif differ diff --git a/graphics/pokenav/name_box.png b/graphics/pokenav/name_box.png new file mode 100644 index 0000000000..7c2ed7036b Binary files /dev/null and b/graphics/pokenav/name_box.png differ diff --git a/graphics/text_window/name_box.png b/graphics/text_window/name_box.png new file mode 100644 index 0000000000..735f39247e Binary files /dev/null and b/graphics/text_window/name_box.png differ diff --git a/include/config/overworld.h b/include/config/overworld.h index 480c5dbcea..15e7e6d3d8 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -110,6 +110,7 @@ #define OW_FLAG_NO_TRAINER_SEE 0 // If this flag is set, trainers will not battle the player unless they're talked to. #define OW_FLAG_NO_COLLISION 0 // If this flag is set, the player will be able to walk over tiles with collision. Mainly intended for debugging purposes. #define OW_FLAG_POKE_RIDER 0 // If this flag is set, the player will be able to use fly from the Pokenav Region Map and the Town Map key item by pressing 'R' on a city/location they are able to fly to. +#define OW_FLAG_SUPPRESS_NAME_BOX 0 // If this flag is set, any namebox (whether its from a macro or a code) will not show up until this flag is unset. #define BATTLE_PYRAMID_RANDOM_ENCOUNTERS FALSE // If set to TRUE, battle pyramid Pokemon will be generated randomly based on the round's challenge instead of hardcoded in src/data/battle_frontier/battle_pyramid_level_50_wild_mons.h (or open_level_wild_mons.h) @@ -142,4 +143,15 @@ // Berry Blender #define BERRY_BLENDER_THROW_ALL_BERRIES_AT_ONCE TRUE // This is a small little addition, that basically speeds up the animation where all players' berries are thrown into the blender. Self-explanatory I hope! +// Namebox Speaker configs +#define OW_NAME_BOX_USE_DYNAMIC_WIDTH TRUE // When TRUE, the namebox window can use different width depending on the length of the speaker's name. +#define OW_NAME_BOX_NPC_TRAINER FALSE // When TRUE, any approaching NPC trainers will have a namebox shown automagically. The name will be taken from their trainer data. +#define OW_NAME_BOX_DEFAULT_WIDTH 8 // Maximum width of what OW_NAME_BOX_USE_DYNAMIC_WIDTH can set. Also the default width when the config above is set to FALSE (or the dynamic width exceeds this value). +#define OW_NAME_BOX_DEFAULT_HEIGHT 2 // Maximum height of the namebox window. + +// Text colors of Namebox. The numbers corresponds to the palette index. +// The BG color is not provided as it always needs to be 0. +#define OW_NAME_BOX_FOREGROUND_COLOR 1 +#define OW_NAME_BOX_SHADOW_COLOR 2 + #endif // GUARD_CONFIG_OVERWORLD_H diff --git a/include/constants/characters.h b/include/constants/characters.h index 6ac3c5224c..0af7b110f0 100644 --- a/include/constants/characters.h +++ b/include/constants/characters.h @@ -232,6 +232,7 @@ #define EXT_CTRL_CODE_ENG 0x16 #define EXT_CTRL_CODE_PAUSE_MUSIC 0x17 #define EXT_CTRL_CODE_RESUME_MUSIC 0x18 +#define EXT_CTRL_CODE_SPEAKER 0x19 #define TEXT_COLOR_TRANSPARENT 0x0 #define TEXT_COLOR_WHITE 0x1 diff --git a/include/constants/speaker_names.h b/include/constants/speaker_names.h new file mode 100644 index 0000000000..1f4f399e75 --- /dev/null +++ b/include/constants/speaker_names.h @@ -0,0 +1,11 @@ +#ifndef GUARD_CONSTANTS_SPEAKER_NAMES_H +#define GUARD_CONSTANTS_SPEAKER_NAMES_H + +enum SpeakerNames { + SP_NAME_NONE = 0, + SP_NAME_MOM, + SP_NAME_PLAYER, + SP_NAME_COUNT +}; + +#endif // GUARD_CONSTANTS_SPEAKER_NAMES_H diff --git a/include/field_name_box.h b/include/field_name_box.h new file mode 100644 index 0000000000..7732fa3418 --- /dev/null +++ b/include/field_name_box.h @@ -0,0 +1,17 @@ +#ifndef GUARD_FIELD_NAME_BOX_H +#define GUARD_FIELD_NAME_BOX_H + +extern EWRAM_DATA const u8 *gSpeakerName; +extern const u8 *const gSpeakerNamesTable[]; + +void TrySpawnNamebox(void); +u32 GetNameboxWindowId(void); +void DestroyNamebox(void); +void FillNamebox(void); +void DrawNamebox(u32 windowId, bool32 copyToVram); +void ClearNamebox(u32 windowId, bool32 copyToVram); +void SetSpeakerName(const u8 *name); +u32 GetNameboxWidth(void); +void TrySpawnAndShowNamebox(const u8 *speaker); + +#endif // GUARD_FIELD_NAME_BOX_H diff --git a/include/match_call.h b/include/match_call.h index ed2cf506c5..468a977a13 100644 --- a/include/match_call.h +++ b/include/match_call.h @@ -19,5 +19,6 @@ void BufferPokedexRatingForMatchCall(u8 *destStr); bool32 SelectMatchCallMessage(int trainerId, u8 *str); void LoadMatchCallWindowGfx(u32 windowId, u32 destOffset, u32 paletteId); void DrawMatchCallTextBoxBorder(u32 windowId, u32 tileOffset, u32 paletteId); +void RedrawMatchCallTextBoxBorder(void); #endif //GUARD_MATCH_CALL_H diff --git a/include/menu.h b/include/menu.h index ca7f8d008c..fec38f40d3 100644 --- a/include/menu.h +++ b/include/menu.h @@ -135,5 +135,6 @@ u8 AddSecondaryPopUpWindow(void); u8 GetSecondaryPopUpWindowId(void); void RemoveSecondaryPopUpWindow(void); void HBlankCB_DoublePopupWindow(void); +void RedrawDialogueFrame(void); #endif // GUARD_MENU_H diff --git a/src/battle_setup.c b/src/battle_setup.c index 01e95d45a1..39499fe179 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -42,6 +42,7 @@ #include "data.h" #include "vs_seeker.h" #include "item.h" +#include "field_name_box.h" #include "constants/battle_frontier.h" #include "constants/battle_setup.h" #include "constants/event_objects.h" @@ -1488,9 +1489,19 @@ static const u8 *ReturnEmptyStringIfNull(const u8 *string) static const u8 *GetIntroSpeechOfApproachingTrainer(void) { if (gApproachingTrainerId == 0) + { + if (OW_NAME_BOX_NPC_TRAINER) + gSpeakerName = GetTrainerNameFromId(TRAINER_BATTLE_PARAM.opponentA); + return ReturnEmptyStringIfNull(TRAINER_BATTLE_PARAM.introTextA); + } else + { + if (OW_NAME_BOX_NPC_TRAINER) + gSpeakerName = GetTrainerNameFromId(TRAINER_BATTLE_PARAM.opponentB); + return ReturnEmptyStringIfNull(TRAINER_BATTLE_PARAM.introTextB); + } } const u8 *GetTrainerALoseText(void) diff --git a/src/data/speaker_names.h b/src/data/speaker_names.h new file mode 100644 index 0000000000..7f0ca56ca7 --- /dev/null +++ b/src/data/speaker_names.h @@ -0,0 +1,5 @@ +const u8 *const gSpeakerNamesTable[SP_NAME_COUNT] = +{ + [SP_NAME_MOM] = COMPOUND_STRING("MOM"), + [SP_NAME_PLAYER] = COMPOUND_STRING("{PLAYER}"), +}; diff --git a/src/field_message_box.c b/src/field_message_box.c index bf9ce1aa93..7a8d4bed10 100755 --- a/src/field_message_box.c +++ b/src/field_message_box.c @@ -7,6 +7,7 @@ #include "field_message_box.h" #include "text_window.h" #include "script.h" +#include "field_name_box.h" static EWRAM_DATA u8 sFieldMessageBoxMode = 0; EWRAM_DATA u8 gWalkAwayFromSignpostTimer = 0; @@ -39,9 +40,12 @@ static void Task_DrawFieldMessage(u8 taskId) task->tState++; break; case 1: - DrawDialogueFrame(0, TRUE); - task->tState++; - break; + u32 nameboxWinId = GetNameboxWindowId(); + DrawDialogueFrame(0, TRUE); + if (nameboxWinId != WINDOW_NONE) + DrawNamebox(nameboxWinId, TRUE); + task->tState++; + break; case 2: if (RunTextPrintersAndIsPrinter0Active() != TRUE) { @@ -123,6 +127,7 @@ bool8 ShowFieldMessageFromBuffer(void) static void ExpandStringAndStartDrawFieldMessage(const u8 *str, bool32 allowSkippingDelayWithButtonPress) { + TrySpawnNamebox(); StringExpandPlaceholders(gStringVar4, str); AddTextPrinterForMessage(allowSkippingDelayWithButtonPress); CreateTask_DrawFieldMessage(); @@ -138,6 +143,7 @@ void HideFieldMessageBox(void) { DestroyTask_DrawFieldMessage(); ClearDialogWindowAndFrame(0, TRUE); + DestroyNamebox(); sFieldMessageBoxMode = FIELD_MESSAGE_BOX_HIDDEN; } diff --git a/src/field_name_box.c b/src/field_name_box.c new file mode 100644 index 0000000000..709dc4546f --- /dev/null +++ b/src/field_name_box.c @@ -0,0 +1,194 @@ +#include "global.h" +#include "main.h" +#include "menu.h" +#include "bg.h" +#include "window.h" +#include "text.h" +#include "string_util.h" +#include "international_string_util.h" +#include "script_menu.h" +#include "field_message_box.h" +#include "graphics.h" +#include "script.h" +#include "field_name_box.h" +#include "event_data.h" +#include "match_call.h" +#include "malloc.h" +#include "constants/speaker_names.h" +#include "data/speaker_names.h" + +#define NAME_BOX_BASE_TILE_TOTAL (6) +#define NAME_BOX_BASE_TILE_NUM (0x194 - (OW_NAME_BOX_DEFAULT_WIDTH * OW_NAME_BOX_DEFAULT_HEIGHT) - NAME_BOX_BASE_TILE_TOTAL) + +static EWRAM_INIT u8 sNameboxWindowId = WINDOW_NONE; +EWRAM_DATA const u8 *gSpeakerName = NULL; + +static const u32 sNameBoxDefaultGfx[] = INCBIN_U32("graphics/text_window/name_box.4bpp"); +static const u32 sNameBoxPokenavGfx[] = INCBIN_U32("graphics/pokenav/name_box.4bpp"); + +static void WindowFunc_DrawNamebox(u8, u8, u8, u8, u8, u8); +static void WindowFunc_ClearNamebox(u8, u8, u8, u8, u8, u8); + +void TrySpawnNamebox(void) +{ + u8 *strbuf = AllocZeroed(32 * sizeof(u8)); + if ((OW_FLAG_SUPPRESS_NAME_BOX != 0 && FlagGet(OW_FLAG_SUPPRESS_NAME_BOX)) || gSpeakerName == NULL || !strbuf) + { + // Re-check again in case anything but !strbuf is TRUE. + if (strbuf) + Free(strbuf); + + DestroyNamebox(); + return; + } + + StringExpandPlaceholders(strbuf, gSpeakerName); + + u32 fontId = FONT_SMALL; + u32 winWidth = OW_NAME_BOX_DEFAULT_WIDTH; + + if (OW_NAME_BOX_USE_DYNAMIC_WIDTH) + { + winWidth = ConvertPixelWidthToTileWidth(GetStringWidth(fontId, strbuf, -1)); + if (winWidth > OW_NAME_BOX_DEFAULT_WIDTH) + winWidth = OW_NAME_BOX_DEFAULT_WIDTH; + } + + if (sNameboxWindowId != WINDOW_NONE) + { + DestroyNamebox(); + RedrawDialogueFrame(); + } + + bool32 matchCall = IsMatchCallTaskActive(); + + struct WindowTemplate template = + { + .bg = 0, + .tilemapLeft = 2, + .tilemapTop = 13, + .width = winWidth, + .height = OW_NAME_BOX_DEFAULT_HEIGHT, + .paletteNum = matchCall ? 14 : DLG_WINDOW_PALETTE_NUM, + .baseBlock = 0x194 - (OW_NAME_BOX_DEFAULT_WIDTH * OW_NAME_BOX_DEFAULT_HEIGHT), + }; + + sNameboxWindowId = AddWindow(&template); + FillNamebox(); + + u8 colors[3] = {TEXT_COLOR_TRANSPARENT, OW_NAME_BOX_FOREGROUND_COLOR, OW_NAME_BOX_SHADOW_COLOR}; + u8 bakColors[3]; + int strX = GetStringCenterAlignXOffset(fontId, strbuf, (winWidth * 8)); + if (matchCall) + { + colors[1] = 1; + colors[2] = 0; + } + + SaveTextColors(&bakColors[0], &bakColors[1], &bakColors[2]); + AddTextPrinterParameterized3(sNameboxWindowId, fontId, strX, 0, colors, 0, strbuf); + RestoreTextColors(&bakColors[0], &bakColors[1], &bakColors[2]); + PutWindowTilemap(sNameboxWindowId); + Free(strbuf); +} + +u32 GetNameboxWindowId(void) +{ + return sNameboxWindowId; +} + +void DestroyNamebox(void) +{ + if (sNameboxWindowId == WINDOW_NONE) + return; + + ClearNamebox(sNameboxWindowId, TRUE); + ClearWindowTilemap(sNameboxWindowId); + RemoveWindow(sNameboxWindowId); + sNameboxWindowId = WINDOW_NONE; + gSpeakerName = NULL; +} + +u32 GetNameboxWidth(void) +{ + return gWindows[sNameboxWindowId].window.width; +} + +static const u32 *GetNameboxGraphics(void) +{ + if (IsMatchCallTaskActive()) + return sNameBoxPokenavGfx; + else + return sNameBoxDefaultGfx; +} + +void FillNamebox(void) +{ + u32 winSize = GetNameboxWidth(); + const u32 *gfx = GetNameboxGraphics(); + + for (u32 i = 0; i < winSize; i++) + { + #define TILE(x) (8 * x) + CopyToWindowPixelBuffer(sNameboxWindowId, &gfx[TILE(1)], TILE_SIZE_4BPP, i); + CopyToWindowPixelBuffer(sNameboxWindowId, &gfx[TILE(4)], TILE_SIZE_4BPP, i + winSize); + #undef TILE + } +} + +void DrawNamebox(u32 windowId, bool32 copyToVram) +{ + LoadBgTiles(GetWindowAttribute(sNameboxWindowId, WINDOW_BG), GetNameboxGraphics(), 0x0C0, NAME_BOX_BASE_TILE_NUM); + CallWindowFunction(windowId, WindowFunc_DrawNamebox); + PutWindowTilemap(windowId); + if (copyToVram == TRUE) + CopyWindowToVram(windowId, COPYWIN_FULL); +} + +void ClearNamebox(u32 windowId, bool32 copyToVram) +{ + CallWindowFunction(windowId, WindowFunc_ClearNamebox); + ClearWindowTilemap(windowId); + if (copyToVram == TRUE) + CopyWindowToVram(windowId, COPYWIN_FULL); +} + +static void WindowFunc_DrawNamebox(u8 bg, u8 L, u8 T, u8 w, u8 h, u8 p) +{ + // left-most + FillBgTilemapBufferRect(bg, NAME_BOX_BASE_TILE_NUM, L - 1, T, 1, 1, p); + FillBgTilemapBufferRect(bg, NAME_BOX_BASE_TILE_NUM + 3, L - 1, T + 1, 1, 1, p); + + // right-most + FillBgTilemapBufferRect(bg, NAME_BOX_BASE_TILE_NUM + 2, L + w, T, 1, 1, p); + FillBgTilemapBufferRect(bg, NAME_BOX_BASE_TILE_NUM + 5, L + w, T + 1, 1, 1, p); +} + +static void WindowFunc_ClearNamebox(u8 bg, u8 L, u8 T, u8 w, u8 h, u8 p) +{ + FillBgTilemapBufferRect(bg, 0, L - 1, T, w + 2, h, 0); // palette doesn't matter +} + +void SetSpeaker(struct ScriptContext *ctx) +{ + u32 arg = ScriptReadWord(ctx); + const u8 *speaker = NULL; + + if (arg < SP_NAME_COUNT) + speaker = gSpeakerNamesTable[arg]; + else if (arg >= ROM_START && arg < ROM_END) + speaker = (const u8 *)arg; + + gSpeakerName = speaker; +} + +// useful for other context e.g. match call +void TrySpawnAndShowNamebox(const u8 *speaker) +{ + gSpeakerName = speaker; + TrySpawnNamebox(); + if (sNameboxWindowId != WINDOW_NONE) + DrawNamebox(sNameboxWindowId, TRUE); + else // either NULL or SP_NAME_NONE + RedrawDialogueFrame(); +} diff --git a/src/match_call.c b/src/match_call.c index 221714de0e..e828dac999 100644 --- a/src/match_call.c +++ b/src/match_call.c @@ -27,6 +27,7 @@ #include "task.h" #include "wild_encounter.h" #include "window.h" +#include "field_name_box.h" #include "constants/abilities.h" #include "constants/battle_frontier.h" #include "constants/event_objects.h" @@ -1321,9 +1322,11 @@ static bool32 MatchCall_PrintIntro(u8 taskId) { FillWindowPixelBuffer(tWindowId, PIXEL_FILL(8)); - // Ready the message + // Ready the message (and the speaker's name if possible) if (!sMatchCallState.triggeredFromScript) SelectMatchCallMessage(sMatchCallState.trainerId, gStringVar4); + + TrySpawnAndShowNamebox(gSpeakerName); InitMatchCallTextPrinter(tWindowId, gStringVar4); return TRUE; } @@ -1348,9 +1351,9 @@ static bool32 MatchCall_PrintMessage(u8 taskId) static bool32 MatchCall_SlideWindowOut(u8 taskId) { s16 *data = gTasks[taskId].data; - if (ChangeBgY(0, 0x600, BG_COORD_SUB) <= -0x2000) + if (ChangeBgY(0, 0x600, BG_COORD_SUB) <= -0x4000) { - FillBgTilemapBufferRect_Palette0(0, 0, 0, 14, 30, 6); + FillBgTilemapBufferRect_Palette0(0, 0, 0, 12, 30, 8); DestroyTask(tIconTaskId); RemoveWindow(tWindowId); CopyBgTilemapBufferToVram(0); @@ -1404,6 +1407,31 @@ static void DrawMatchCallTextBoxBorder_Internal(u32 windowId, u32 tileOffset, u3 FillBgTilemapBufferRect_Palette0(bg, ((paletteId << 12) & 0xF000) | (tileNum + 7), x + width, y + height, 1, 1); } +static u8 GetMatchCallWindowId(void) +{ + if (!IsMatchCallTaskActive()) + return WINDOW_NONE; + + u32 taskId = FindTaskIdByFunc(ExecuteMatchCall); + return gTasks[taskId].tWindowId; +} + +// redraw only the top-half +void RedrawMatchCallTextBoxBorder(void) +{ + u32 windowId = GetMatchCallWindowId(); + u32 bg = GetWindowAttribute(windowId, WINDOW_BG); + u32 x = GetWindowAttribute(windowId, WINDOW_TILEMAP_LEFT); + u32 y = GetWindowAttribute(windowId, WINDOW_TILEMAP_TOP); + u32 width = GetWindowAttribute(windowId, WINDOW_WIDTH); + u32 tileNum = TILE_MC_WINDOW + GetBgAttribute(bg, BG_ATTR_BASETILE); + u32 paletteId = 14; + + FillBgTilemapBufferRect_Palette0(bg, ((paletteId << 12) & 0xF000) | (tileNum + 0), x - 1, y - 1, 1, 1); + FillBgTilemapBufferRect_Palette0(bg, ((paletteId << 12) & 0xF000) | (tileNum + 1), x, y - 1, width, 1); + FillBgTilemapBufferRect_Palette0(bg, ((paletteId << 12) & 0xF000) | (tileNum + 2), x + width, y - 1, 1, 1); +} + static void InitMatchCallTextPrinter(int windowId, const u8 *str) { struct TextPrinterTemplate printerTemplate; diff --git a/src/menu.c b/src/menu.c index 48bbc01dc9..b771635de4 100644 --- a/src/menu.c +++ b/src/menu.c @@ -22,6 +22,7 @@ #include "task.h" #include "text_window.h" #include "window.h" +#include "match_call.h" #include "config/overworld.h" #include "constants/songs.h" @@ -341,6 +342,53 @@ void DrawDialogueFrame(u8 windowId, bool8 copyToVram) CopyWindowToVram(windowId, COPYWIN_FULL); } +static void WindowFunc_RedrawDialogueFrame(u8 bg, u8 tilemapLeft, u8 tilemapTop, u8 width, u8 height, u8 paletteNum) +{ + FillBgTilemapBufferRect(bg, + DLG_WINDOW_BASE_TILE_NUM + 1, + tilemapLeft - 2, + tilemapTop - 1, + 1, + 1, + DLG_WINDOW_PALETTE_NUM); + FillBgTilemapBufferRect(bg, + DLG_WINDOW_BASE_TILE_NUM + 3, + tilemapLeft - 1, + tilemapTop - 1, + 1, + 1, + DLG_WINDOW_PALETTE_NUM); + FillBgTilemapBufferRect(bg, + DLG_WINDOW_BASE_TILE_NUM + 4, + tilemapLeft, + tilemapTop - 1, + width - 1, + 1, + DLG_WINDOW_PALETTE_NUM); + FillBgTilemapBufferRect(bg, + DLG_WINDOW_BASE_TILE_NUM + 5, + tilemapLeft + width - 1, + tilemapTop - 1, + 1, + 1, + DLG_WINDOW_PALETTE_NUM); + FillBgTilemapBufferRect(bg, + DLG_WINDOW_BASE_TILE_NUM + 6, + tilemapLeft + width, + tilemapTop - 1, + 1, + 1, + DLG_WINDOW_PALETTE_NUM); +} + +void RedrawDialogueFrame(void) +{ + if (IsMatchCallTaskActive()) + RedrawMatchCallTextBoxBorder(); + else + CallWindowFunction(0, WindowFunc_RedrawDialogueFrame); +} + void DrawStdWindowFrame(u8 windowId, bool8 copyToVram) { CallWindowFunction(windowId, WindowFunc_DrawStandardFrame); diff --git a/src/string_util.c b/src/string_util.c index fd4a0861ee..fe8b6fa592 100644 --- a/src/string_util.c +++ b/src/string_util.c @@ -706,6 +706,7 @@ u8 GetExtCtrlCodeLength(u8 code) [EXT_CTRL_CODE_ENG] = 1, [EXT_CTRL_CODE_PAUSE_MUSIC] = 1, [EXT_CTRL_CODE_RESUME_MUSIC] = 1, + [EXT_CTRL_CODE_SPEAKER] = 1, }; u8 length = 0; diff --git a/src/text.c b/src/text.c index 8166b35302..be19272fe5 100644 --- a/src/text.c +++ b/src/text.c @@ -12,6 +12,8 @@ #include "menu.h" #include "dynamic_placeholder_text_util.h" #include "fonts.h" +#include "field_name_box.h" +#include "constants/speaker_names.h" static u16 RenderText(struct TextPrinter *); static u32 RenderFont(struct TextPrinter *); @@ -1228,6 +1230,13 @@ static u16 RenderText(struct TextPrinter *textPrinter) case EXT_CTRL_CODE_ENG: textPrinter->japanese = FALSE; return RENDER_REPEAT; + case EXT_CTRL_CODE_SPEAKER: + { + enum SpeakerNames name = *textPrinter->printerTemplate.currentChar++; + TrySpawnAndShowNamebox(gSpeakerNamesTable[name]); + + return RENDER_REPEAT; + } } break; case CHAR_PROMPT_CLEAR: @@ -1418,6 +1427,7 @@ static u32 UNUSED GetStringWidthFixedWidthFont(const u8 *str, u8 fontId, u8 lett case EXT_CTRL_CODE_SKIP: case EXT_CTRL_CODE_CLEAR_TO: case EXT_CTRL_CODE_MIN_LETTER_SPACING: + case EXT_CTRL_CODE_SPEAKER: ++strPos; break; case EXT_CTRL_CODE_RESET_FONT: @@ -1566,6 +1576,7 @@ s32 GetStringWidth(u8 fontId, const u8 *str, s16 letterSpacing) case EXT_CTRL_CODE_ESCAPE: case EXT_CTRL_CODE_SHIFT_RIGHT: case EXT_CTRL_CODE_SHIFT_DOWN: + case EXT_CTRL_CODE_SPEAKER: ++str; break; case EXT_CTRL_CODE_FONT: @@ -1735,6 +1746,7 @@ u8 RenderTextHandleBold(u8 *pixels, u8 fontId, u8 *str) case EXT_CTRL_CODE_SKIP: case EXT_CTRL_CODE_CLEAR_TO: case EXT_CTRL_CODE_MIN_LETTER_SPACING: + case EXT_CTRL_CODE_SPEAKER: ++strPos; break; case EXT_CTRL_CODE_RESET_FONT: