Registrarse

[pokeemerald] Cambiar la t-box dependiendo del tipo de MSGBOX

Disturbo

Decomper
¡Hola hola!

Como dice el título, haciendo los cambios que veréis a continuación podréis asignar un gráfico diferente a cada tipo de MSGBOX, e incluso diferentes gráficos dentro de cada MSGBOX (inception). Para esta demostración añadiremos la separación a aquellos del tipo MSGBOX_SIGN, pero el mismo procedimiento puede ser usado para crear otras. Para que sepáis donde coloco las cosas en cada archivo, en cada bloque de código colocaré algunas de las lineas anteriores. Empecemos.

Lo primero es añadir la nueva t-box. Para ello, vamos a intentar mantener el formato que utiliza pokeemerald.
Las declaraciones de variables relacionadas con las t-boxes se realizan en "graphics/text_window/". En nuestro, y a forma de prueba, cargaremos el gráfico "sign_message_box.png"
Ahora debemos añadir las referencias a la imagen y la paleta (la cual debe ser de máximo 16 colores). En include/graphics.h:
C:
extern const u32 gMessageBox_Gfx[];
extern const u16 gMessageBox_Pal[];

//Debemos insertar estas dos líneas
extern const u32 gSignMessageBox_Gfx[];
extern const u16 gSignMessageBox_Pal[];
Y en src/graphics.c:
C:
const u16 gMessageBox_Pal[] = INCBIN_U16("graphics/text_window/message_box.gbapal");
const u8 gMessageBox_Gfx[] = INCBIN_U8("graphics/text_window/message_box.4bpp");

//Debemos insertar estas dos líneas
const u16 gSignMessageBox_Pal[] = INCBIN_U16("graphics/text_window/sign_message_box.gbapal");
const u8 gSignMessageBox_Gfx[] = INCBIN_U8("graphics/text_window/sign_message_box.4bpp");
También debemos indicar que queremos cargar esta paleta como parte de las que se muestran en mensajes, asique vamos a src/text_window.c:
C:
//Simplemente copiamos y pegamos lo que asignamos a "gSignMessageBox_Pal" dentro del método
static const u16 sUnknown_0851017C[][16] =
{
    INCBIN_U16("graphics/text_window/message_box.gbapal"),
    INCBIN_U16("graphics/text_window/sign_message_box.gbapal"), //Tal que así
    INCBIN_U16("graphics/text_window/text_pal1.gbapal"),
    INCBIN_U16("graphics/text_window/text_pal2.gbapal"),
    INCBIN_U16("graphics/text_window/text_pal3.gbapal"),
    INCBIN_U16("graphics/text_window/text_pal4.gbapal")
};
Para cada nueva t-box debemos repetir este proceso.
Lo primero que necesitamos hacer es ir a sym_ewram.txt y añadir la siguiente línea al final:
Código:
	.include "src/text_window.o"
Necesitamos tener una variable que almacene el tipo de t-box que estamos usando y otra para el subtipo. De nuevo, las añadiremos en src/text_window.c:
C:
//Bajo todos los includes
static EWRAM_DATA u8 sMsgBoxType = MSGBOX_DEFAULT;
static EWRAM_DATA u8 sMsgBoxSubType = MSGBOX_SUB_DEFAULT;

//Aquí también añadiremos la siguiente línea, que nos será útil más adelante
void LoadMsgBox(const u32 **graphic, const u16 **palette);
Ahora tenemos que añadir los métodos que nos permitirán por un lado asignar valores a ellas y cargar la t-box corresponiente. Para ello, al final de src/text_window.c:
C:
//MBSP
//Set the current type and subtype of t-box
void SetMsgBox(u8 type, u8 subtype)
{
    sMsgBoxType = type;
    sMsgBoxSubType = subtype;
}

//Load t-box graphics depending on type and subtype
//Este método es un ejemplo. De lo que pongáis aquí depende que aspecto tendrá cada tipo y subtipo de t-box
void LoadMsgBox(const u32 **graphic, const u16 **palette)
{
    switch(sMsgBoxType)
    {
    case 3: //Los carteles tienen asociado el 3. Esto será explicado más adelante en el tutorial.
        if(sMsgBoxSubType == 1)
        {
            *graphic = gSignMessageBox_Gfx;
            *palette = gSignMessageBox_Pal;
        }
        else
        {
            *graphic = gMessageBox_Gfx;
            *palette = gMessageBox_Pal;
        }
        break;
    default:
        *graphic = gMessageBox_Gfx;
        *palette = gMessageBox_Pal;
        break;
    }
    //Esto reestablece el tipo y subtipo cargados a los default.
    SetMsgBox(4, 0);
}
Y para poder acceder a ellos desde otros archivos, vamos a include/text_window.h y al final añadimos:
C:
//MBSP
void SetMsgBox(u8 type, u8 subtype);
void LoadMsgBox(const u32 **graphic, const u16 **palette);
Ya hemos implementado la diferenciación de t-boxes, pero ahora tenemos que hacer que se tenga en cuenta al cargar los gráficos. Para ello vamos a src/text_window.c:
C:
//Tenemos que reemplazar
void LoadMessageBoxGfx(u8 windowId, u16 destOffset, u8 palOffset)
{
    LoadBgTiles(GetWindowAttribute(windowId, WINDOW_BG), gMessageBox_Gfx, 0x1C0, destOffset);
    LoadPalette(gMessageBox_Pal, palOffset, 0x20);
}

//con lo siguiente
void LoadMessageBoxGfx(u8 windowId, u16 destOffset, u8 palOffset)
{
    //MBSP
    //Call LoadMsgBox to loas proper graphics and palette
    const u32 *currentMessageBox_Gfx;
    const u16 *currentMessageBox_Pal;
    LoadMsgBox(&currentMessageBox_Gfx, &currentMessageBox_Pal);
   
    LoadBgTiles(GetWindowAttribute(windowId, WINDOW_BG), currentMessageBox_Gfx, 0x1C0, destOffset);
    LoadPalette(currentMessageBox_Pal, palOffset, 0x20);
}

¡Ahora ya tenemos todo necesario para modificar los comandos de scripting!
En lugar de hardcodear todo, vamos a optar por modificar los métodos habituales de carga de t-boxes. Para ello, primero vamos a ams/macros/event.inc.
Aquí vamos a hacer dos cambios, que podéis ver en el siguiente bloque de código:
C:
//Buscamos "msgbox" y reemplazamos esto
.macro msgbox text:req, type=MSGBOX_DEFAULT
    loadword 0, \text
    callstd \type
    .endm

//Con esto
MSGBOX_SUB_DEFAULT = 0

.macro msgbox text:req, type=MSGBOX_DEFAULT, subtype=MSGBOX_SUB_DEFAULT
    loadword 0, \text
    loadbyte 1, \type
    loadbyte 2, \subtype
    callstd \type
    .endm



//Más atrás, buscamos "signmsg" y reemplazamos esto
.macro signmsg

//Con esto
.macro setmessagebox type:req, subtype:req
Con estos cambios añadimos definimos los nuevos argumentos que necesitan los comandos "msgbox" y "setmessagebox" para funcionar.
Tranquilos, no hace falta editar todos los "msgbox" del juego ya que el último valor es opcional, por lo que su estructura pasaría a ser una de estas tres (¡las tres son válidas!):
Código:
msgbox <texto>
msgbox <texto>, <tipo de text box>
msgbox <texto>, <tipo de text box>, <subtipo de text box>
Nos dirigimos a data/script_cmd_table.inc y hacemos este cambio:
C:
    //Cambiamos este bloque:
    .4byte ScrCmd_bufferboxname
    .4byte ScrCmd_nop1
    .4byte ScrCmd_nop1
    .4byte ScrCmd_nop1
    .4byte ScrCmd_nop1
    .4byte ScrCmd_nop1
    .4byte ScrCmd_nop1
    .4byte ScrCmd_setmonobedient


    //Por el siguiente:
    .4byte ScrCmd_bufferboxname
    .4byte ScrCmd_nop1
    .4byte ScrCmd_nop1
    .4byte ScrCmd_nop1
    .4byte ScrCmd_setmessagebox
    .4byte ScrCmd_nop1
    .4byte ScrCmd_nop1
    .4byte ScrCmd_setmonobedient
El juego ya reconoce el comando, ¡pero de poco nos sirve si no hace nada!
Para hacer que haga algo, nos dirigimos a src/scrcmd.c y añadimos esto en cualquier parte del archivo:
C:
bool8 ScrCmd_setmessagebox(struct ScriptContext * ctx)
{
    u8 type = ctx->data[1];
    u8 subtype = ctx->data[2];
    
    SetMsgBox(type, subtype);
    return FALSE;
}
Ahora queremos que el juego sepa, a partir del comando que ponemos en el script, el tipo y el subtipo del cartel. Para ello vamos a data/scripts/std_msgbox.inc.
En este archivo tenemos que reemplazar lo siguiente:
C:
//Para cada aparición de 
    message 0x0
//Debemos reemplazarla con lo siguiente
    setmessagebox 0x1 0x2
    message 0x0
Esta parte es la que hace que cada vez que nosotros llamemos a msgbox en un script se establezca el tipo y el subtipo de cartel

Aprovecho para decir que hay text boxes, como la de los entrenadores cuando te retan, que no se cargan usando el comando msgbox. Para que esto funcione en esos casos, hay que buscar el método llamado (habitualmente ShowFieldMessage) y añadir ANTES del método un SetMsgBox(type, sybtype) con los tipos deseados. Cabe también destacar que se debe hacer "#include script.h" en caso de que el archivo donde esté el ShowFieldMessage no lo incluya ya.

¡Ya está todo implementado! Solo quedan dos cosas por hacer: definir los tipos y los subtipos de MSGBOX y hacer que se carguen las t-boxes que queramos para cada uno. Sin más dilación, continuemos.
Como hemos visto antes en el spoiler "Implementar el método que marcará el tipo de t-box a usar", usamos números para identificar los tipos de t-box. Resulta muy incómodo trabajar con valores numéricos que para el ojo del público no tienen ningún sentido, por lo que vamos a emplear "defines" y otros métodos similares.
Hay dos lugares en los que debemos definir los valores a un número, empecemos con el más sencillo, scr/text_window.c.

Por un lado necesitamos definir los tipos de t-boxes, que ya vienen dados por el juego. Debemos poner bajo los "#include":
C:
#define MSGBOX_NPC              2
#define MSGBOX_SIGN             3
#define MSGBOX_DEFAULT          4
#define MSGBOX_YESNO            5
#define MSGBOX_AUTOCLOSE        6
#define MSGBOX_GETPOINTS        9
Pero como también queremos trabajar con nombres y no con números, añadimos también los subtipos:
C:
#define MSGBOX_SUB_DEFAULT      0
#define MSGBOX_SUB_COOLSIGN     1
Añadimos un define por cada subtipo que queramos.

Desafortunadamente esos no son todos los sitios donde debemos añadir cosas. Ahora volvemos a ams/macros/event.inc.
Aquí tenemos que añadir los subtipos que acabamos de definir. Para ello, buscamos "msgbox" y encontramos el siguiente código y colocamos las definiciones:
C:
@ Message box types
	MSGBOX_NPC = 2
	MSGBOX_SIGN = 3
	MSGBOX_DEFAULT = 4
	MSGBOX_YESNO = 5
	MSGBOX_AUTOCLOSE = 6
	MSGBOX_GETPOINTS = 9
	
	YES = 1
	NO  = 0
        
	//Esto es lo que añadimos
	MSGBOX_SUB_DEFAULT = 0
	MSGBOX_SUB_COOLSIGN = 1

	.macro msgbox text:req, type=MSGBOX_DEFAULT, subtype=MSGBOX_SUB_DEFAULT
Igual que antes, añadimos uno por cada línea que queramos. Estos son muy importantes pues son los que el juego utiliza para entender el comando "msgbox"
Venga, que ya es casi lo último. Solo queda personalizar el método "LoadMsgBox" en src/script.c para acomodar tus tipos y subtipos. Recordemos la del ejemplo:
C:
void LoadMsgBox(const u32 **graphic, const u16 **palette)
{
    switch(sMsgBoxType)
    {
    case 3:
        if(sMsgBoxSubType == 1)
        {
            *graphic = gSignMessageBox_Gfx;
            *palette = gSignMessageBox_Pal;
        }
        else
        {
            *graphic = gMessageBox_Gfx;
            *palette = gMessageBox_Pal;
        }
    default:
        *graphic = gMessageBox_Gfx;
        *palette = gMessageBox_Pal;
        break;
    }
    SetMsgBox(4, 0);
}
Podemos distinguir tres partes:
  • El switch: Cargar el caso correspondiente a sMsgBoxType.
  • Los ifs de cada caso: También podrían ser un switch, y cargan el gráfico y paleta correspondiente.
  • El default: Si no hay ningún casó con el valor de sMsgBoxType, esto se "activa"

Pero bueno, ahora que hemos definido las cosas por nombre, vamos a ponerlo bonito:
C:
void LoadMsgBox(const u32 **graphic, const u16 **palette)
{
    switch(sMsgBoxType)
    {
    case MSGBOX_SIGN:
        if(sMsgBoxSubType == MSGBOX_SUB_COOLSIGN)
        {
            *graphic = gSignMessageBox_Gfx;
            *palette = gSignMessageBox_Pal;
        }
        else
        {
            *graphic = gMessageBox_Gfx;
            *palette = gMessageBox_Pal;
        }
        break;
    default:
        *graphic = gMessageBox_Gfx;
        *palette = gMessageBox_Pal;
        break;
    }
    SetMsgBox(MSGBOX_DEFAULT, MSGBOX_SUB_DEFAULT);
}
¿Ahora mejor? Bien. Si quisiesemos dar una textbox diferente a otro tipo de MSGBOX, bastaría con añadir un caso para esta. Igual sucede con los subtipos, como se puede ver este ejemplo.
C:
//Suponiendo que hemos cargado los gráficos y paletas gNPCMessageBox y gWoodSignMessageBox
//Suponiendo también que creamos el define para el subtipo MSGBOX_SUB_WOODSIGN en los dos lugares requeridos


void LoadMsgBox(const u32 **graphic, const u16 **palette)
{
    switch(sMsgBoxType)
    {
    case MSGBOX_SIGN:
        if(sMsgBoxSubType == MSGBOX_SUB_COOLSIGN)
        {
            *graphic = gSignMessageBox_Gfx;
            *palette = gSignMessageBox_Pal;
        }
        else if(sMsgBoxSubType == MSGBOX_SUB_WOODSIGN)
        {
            *graphic = gWoodSignMessageBox_Gfx;
            *palette = gWoodSignMessageBox_Pal;
        }
        else
        {
            *graphic = gMessageBox_Gfx;
            *palette = gMessageBox_Pal;
        }
        break;
    case MSGBOX_NPC:
        *graphic = gNPCMessageBox_Gfx;
        *palette = gNPCMessageBox_Pal;
        break;
    default:
        *graphic = gMessageBox_Gfx;
        *palette = gMessageBox_Pal;
        break;
    }
    SetMsgBox(MSGBOX_DEFAULT, MSGBOX_SUB_DEFAULT);
}
Aquí creamos una nueva separación para el tipo de MSGBOX NPC sin subtipos, y un nuevo subtipo para MSGBOX_SIGN.
Esta sección será la más breve, puesto que cambian muy poquitas cosas. Tenemos dos opciones:
  • msgbox <texto>, <tipo>: Si el tipo de MSGBOX no tiene subtipos, utiliza este
  • msgbox <texto>, <tipo>, <subtipo>: Si el tipo de MSGBOX tiene subtipos, utiliza este, indicando el subtipo al dinal

Whew, ¡eso es todo! Quién podría imaginar que un tutorial sobre separar text boxes se podría alargar tanto. Bueno, espero que os sea útil, estos días subiré una repo a Github con los cambios para que no os tiréis la vida copiando esto.

~Disturbo
 
Última edición:

Jaizu

Usuario mítico
El tutorial es largo y tedioso pero la verdad es que es muy útil para varios tipos de carteles, tengo alguna idea en mente.
Si quisiésemos solamente portar los carteles de FR deberíamos haber hecho algo más sencillo pero la verdad esto está bastante mejor, aunque suerte liándote con tanto código.
Por cierto, en donde indicamos nosotros nuestros tipos de carteles te saltaste un } al final, y supongo que en los dos siguientes que pusiste de ejemplo también.
 
Arriba