Registrarse

[pokeemerald] Implementación de un menú de debug

Tyren Sealess

A fullmetal heart.
Post original creado por Ketsuban en GitHub y posteado en PokeCommunity, todos los créditos van dirigidos al autor/a. Adaptado y traído a WaH para vuestro uso y disfrute por el Equipo de traducción. Os dejamos con ello.

[hr][/hr]



Implementación de un menú de debug
pokeemerald


Durante el desarrollo de un juego es común encontrarse en situaciones en las que, para testear algo, has de jugar una gran parte del juego, lo cual supone una pérdida de tiempo. Por eso es útil tener un menú de debug con una colección de herramientas que te permitan hacer este testeo, a la vez que ahorrar tiempo. Sin embargo, como pokeemerald reproduce el lanzamiento comercial de Pokémon Esmeralda, no hay ningún menú de debug. Vamos a arreglar eso.

Este tutorial está dirigido a aquellos con un buen conocimiento de C, así que no explicaré cosas como directivas preprocesador, include guards, qué es una struct y sus campos y demás. Si desconoces qué son estas cosas, ya sabes qué hacer. Las adiciones de código que se hagan en archivos ya existentes estarán marcadas con un "+" al comienzo de la línea.

[hr][/hr]


Paso 0: Añadir una flag de compilación

No tiene sentido darles un menú debug a los jugadores, así que es una buena idea ocultarlo con una flag de compilación como hizo Game Freak. Técnicamente esto es opcional, ya que simplemente puedes decirle a la gente que no mantenga apretado R mientras pulsa Start o algo similar, pero es muy fácil de hacer. Si decides, no hacerlo ten cuidado durante el resto del tutorial para ignorar las parejas #if DEBUG y #endif.

Comienza añadiendo esto al Makefile:

Código:
ifeq ($(NODEP),1)
$(C_BUILDDIR)/%.o: c_dep :=
else
$(C_BUILDDIR)/%.o: c_dep = $(shell $(SCANINC) -I include $(C_SUBDIR)/$*.c)
endif

ifeq ($(DINFO),1)
override CFLAGS += -g
endif

+ifeq ($(DDEBUG),1)
+override ASFLAGS += --defsym DEBUG=1
+override CPPFLAGS += -D DEBUG=1
+endif

$(C_BUILDDIR)/%.o : $(C_SUBDIR)/%.c $$(c_dep)
	@$(CPP) $(CPPFLAGS) $< -o $(C_BUILDDIR)/$*.i
	@$(PREPROC) $(C_BUILDDIR)/$*.i charmap.txt | $(CC1) $(CFLAGS) -o $(C_BUILDDIR)/$*.s
	@echo -e ".text\n\t.align\t2, 0\n" >> $(C_BUILDDIR)/$*.s
	$(AS) $(ASFLAGS) -o $@ $(C_BUILDDIR)/$*.s

De ahora en adelante, cuando estés construyendo la versión del hack para ti, querrás usar make DDEBUG=1 (¡cuidado con la doble D!) en lugar de make.

[hr][/hr]


Paso 1: Añadir una combinación de botones

En este paso trabajaremos en el archivo fuente "field_control_avatar.c" y su correspondiente archivo puntero "field_control_avatar.h". Busca en el archivo puntero la definición de FieldInput. Nos dirigimos al header, que para mí contiene esto:

Código:
struct FieldInput
{
    bool8 pressedAButton:1;
    bool8 checkStandardWildEncounter:1;
    bool8 pressedStartButton:1;
    bool8 pressedSelectButton:1;
    bool8 heldDirection:1;
    bool8 heldDirection2:1;
    bool8 tookStep:1;
    bool8 pressedBButton:1;
    bool8 input_field_1_0:1;
    bool8 input_field_1_1:1;
    bool8 input_field_1_2:1;
    bool8 input_field_1_3:1;
    bool8 input_field_1_4:1;
    bool8 input_field_1_5:1;
    bool8 input_field_1_6:1;
    bool8 input_field_1_7:1;
    u8 dpadDirection;
};
Los campos cuyos nombres empiezan con "input_field_1_" son flags de entrada al menú de debug; en las compilaciones de debug de Game Freak estas se activan cuando el usuario introduce cierta combinación de botones. Usaré "input_field_1_2" porque es la que usó Game Freak. Puede que tenga otro nombre en tu copia de pokeemerald, pero estará en la misma posición y estructura así que todo lo que tienes que hacer es recordar el nombre.

Vuelve al source y añade al final de la función FieldGetPlayerInput lo siguiente:

Código:
    if (heldKeys & DPAD_UP)
        input->dpadDirection = DIR_NORTH;
    else if (heldKeys & DPAD_DOWN)
        input->dpadDirection = DIR_SOUTH;
    else if (heldKeys & DPAD_LEFT)
        input->dpadDirection = DIR_WEST;
    else if (heldKeys & DPAD_RIGHT)
        input->dpadDirection = DIR_EAST;

+#if DEBUG
+    if ((heldKeys & R_BUTTON) && input->pressedStartButton)
+    {
+        input->input_field_1_2 = TRUE;
+        input->pressedStartButton = FALSE;
+    }
+#endif
}

Esto define la combinación de botones para acceder al menú de debug: mantén R y pulsa start. Puedes cambiarlo si quieres, pero el número de combinaciones sensatas está bastante limitado.

Ahora baja y añade al final de la función ProcessPlayerFieldInput lo siguiente:

Código:
    if (input->pressedSelectButton && UseRegisteredKeyItemOnField() == TRUE)
        return TRUE;

+#if DEBUG
+    if (input->input_field_1_2)
+    {
+        PlaySE(SE_WIN_OPEN);
+        Debug_ShowMainMenu();
+        return TRUE;
+    }
+#endif

    return FALSE;
}

El resultado de estos cambios es que si el usuario ha presionado la combinación de botones habrá un sonido y (próximamente) un menú. Si quieres puedes comentar la línea Debug_ShowMainMenu(); y testear el código tal y como está ahora. El sonido es el mismo que suena al abrir el menú start, pero no se abre ningún menú. Para acabar, añade al comienzo del archivo el siguiente include:

Código:
#include "global.h"
#include "battle_setup.h"
#include "bike.h"
#include "coord_event_weather.h"
#include "daycare.h"
+#include "debug.h"
#include "faraway_island.h"

Ahora vamos a crear los archivos "debug.h" y "debug.c" en las carpetas "include" y "src" respectivamente.

[hr][/hr]


Paso 2: Añadir el menú

Ya hemos terminado de editar el código de Game Freak, ahora toca añadir el nuestro. Crearemos un archivo de nombre "debug.h" en la carpeta "include" y le pondremos el siguiente código:

Código:
#ifndef GUARD_DEBUG_H
#define GUARD_DEBUG_H
#if DEBUG

void Debug_ShowMainMenu(void);

#endif
#endif // GUARD_DEBUG_H

Luego, crearemos un archivo de nombre "debug.h" en la carpeta "src" con el siguiente código:

Código:
#if DEBUG

#include "global.h"
#include "list_menu.h"
#include "main.h"
#include "menu.h"
#include "script.h"
#include "sound.h"
#include "strings.h"
#include "task.h"
#include "constants/songs.h"

#define DEBUG_MAIN_MENU_HEIGHT 7
#define DEBUG_MAIN_MENU_WIDTH 11

void Debug_ShowMainMenu(void);
static void Debug_DestroyMainMenu(u8);
static void DebugAction_Cancel(u8);
static void DebugTask_HandleMainMenuInput(u8);

enum {
    DEBUG_MENU_ITEM_CANCEL,
};

static const u8 gDebugText_Cancel[] = _("Cancel");

static const struct ListMenuItem sDebugMenuItems[] =
{
    [DEBUG_MENU_ITEM_CANCEL] = {gDebugText_Cancel, DEBUG_MENU_ITEM_CANCEL}
};

static void (*const sDebugMenuActions[])(u8) =
{
    [DEBUG_MENU_ITEM_CANCEL] = DebugAction_Cancel
};

static const struct WindowTemplate sDebugMenuWindowTemplate =
{
    .bg = 0,
    .tilemapLeft = 1,
    .tilemapTop = 1,
    .width = DEBUG_MAIN_MENU_WIDTH,
    .height = 2 * DEBUG_MAIN_MENU_HEIGHT,
    .paletteNum = 15,
    .baseBlock = 1,
};

static const struct ListMenuTemplate sDebugMenuListTemplate =
{
    .items = sDebugMenuItems,
    .moveCursorFunc = ListMenuDefaultCursorMoveFunc,
    .totalItems = ARRAY_COUNT(sDebugMenuItems),
    .maxShowed = DEBUG_MAIN_MENU_HEIGHT,
    .windowId = 0,
    .header_X = 0,
    .item_X = 8,
    .cursor_X = 0,
    .upText_Y = 1,
    .cursorPal = 2,
    .fillValue = 1,
    .cursorShadowPal = 3,
    .lettersSpacing = 1,
    .itemVerticalPadding = 0,
    .scrollMultiple = LIST_NO_MULTIPLE_SCROLL,
    .fontId = 1,
    .cursorKind = 0
};

void Debug_ShowMainMenu(void) {
    struct ListMenuTemplate menuTemplate;
    u8 windowId;
    u8 menuTaskId;
    u8 inputTaskId;

    // create window
    windowId = AddWindow(&sDebugMenuWindowTemplate);
    DrawStdWindowFrame(windowId, FALSE);

    // create list menu
    menuTemplate = sDebugMenuListTemplate;
    menuTemplate.windowId = windowId;
    menuTaskId = ListMenuInit(&menuTemplate, 0, 0);

    // draw everything
    CopyWindowToVram(windowId, 3);

    // create input handler task
    inputTaskId = CreateTask(DebugTask_HandleMainMenuInput, 3);
    gTasks[inputTaskId].data[0] = menuTaskId;
    gTasks[inputTaskId].data[1] = windowId;
}

static void Debug_DestroyMainMenu(u8 taskId)
{
    DestroyListMenuTask(gTasks[taskId].data[0], NULL, NULL);
    ClearStdWindowAndFrame(gTasks[taskId].data[1], TRUE);
    RemoveWindow(gTasks[taskId].data[1]);
    DestroyTask(taskId);
    EnableBothScriptContexts();
}

static void DebugTask_HandleMainMenuInput(u8 taskId)
{
    void (*func)(u8);
    u32 input = ListMenu_ProcessInput(gTasks[taskId].data[0]);

    if (gMain.newKeys & A_BUTTON)
    {
        PlaySE(SE_SELECT);
        if ((func = sDebugMenuActions[input]) != NULL)
            func(taskId);
    }
    else if (gMain.newKeys & B_BUTTON)
    {
        PlaySE(SE_SELECT);
        Debug_DestroyMainMenu(taskId);
    }
}

static void DebugAction_Cancel(u8 taskId)
{
    Debug_DestroyMainMenu(taskId);
}

#endif
Vamos a ver qué hace cada parte del código que acabamos de ver.

Código:
static const u8 gDebugText_Cancel[] = _("Cancel");

enum {
    DEBUG_MENU_ITEM_CANCEL,
};

static const struct ListMenuItem sDebugMenuItems[] =
{
    [DEBUG_MENU_ITEM_CANCEL] = {gDebugText_Cancel, DEBUG_MENU_ITEM_CANCEL},
};

static void (*const sDebugMenuActions[])(u8) =
{
    [DEBUG_MENU_ITEM_CANCEL] = DebugAction_Cancel,
};

En esta parte definimos la lista de herramientas. sDebugMenuItems agrupa el nombre y la ID en una enumeración. sDebugMenuActions es una lista de funciones que contiene la acción que tiene lugar cuando se usa un elemento de la lista anterior. Es importante que la enumeración y la lista tengan la misma longitud, o pasarán cosas extrañas. Si quieres añadir una nueva entrada a la lista, utiliza este código como guía:

Código:
static const u8 gDebugText_NewEntry[] = _("New Entry");
static const u8 gDebugText_Cancel[] = _("Cancel");

enum {
    DEBUG_MENU_ITEM_NEW_ENTRY,
    DEBUG_MENU_ITEM_CANCEL,
};

static const struct ListMenuItem sDebugMenuItems[] =
{
    [DEBUG_MENU_ITEM_NEW_ENTRY] = {gDebugText_NewEntry, DEBUG_MENU_ITEM_NEW_ENTRY},
    [DEBUG_MENU_ITEM_CANCEL] = {gDebugText_Cancel, DEBUG_MENU_ITEM_CANCEL},
};

static void (*const sDebugMenuActions[])(u8) =
{
    [DEBUG_MENU_ITEM_NEW_ENTRY] = DebugAction_NewEntry,
    [DEBUG_MENU_ITEM_CANCEL] = DebugAction_Cancel,
};

Asegúrate de crear la función static void DebugAction_NewEntry(u8 taskId). Si quieres que la nueva entrada no haga nada cuando sea usada, simplemente usa null.

Código:
static const struct WindowTemplate sDebugMenuWindowTemplate =
{
    .bg = 0,
    .tilemapLeft = 1,
    .tilemapTop = 1,
    .width = DEBUG_MAIN_MENU_WIDTH,
    .height = 2 * DEBUG_MAIN_MENU_HEIGHT,
    .paletteNum = 15,
    .baseBlock = 1,
};
Esta es la plantilla de una ventana. Una ventana es una caja enmarcada por el borde que elijas en las opciones del juego, y una plantilla define las propiedades de esta ventana. La mayoría de los campos no deben ser modificados, pero los que interesa conocer son "tilemapLeft" y "tilemapTop" (que son la posición relativa a la parte izquierda y la superior de la pantalla en tiles), "width" y "height", (que, como podrás imaginar, son la altura y el ancho de la ventana en tiles) y "baseBlock" (que es usado por el engine para colocar los tiles de cada ventana). Si solo tenemos una ventana abierta, como es nuestro caso, "baseBlock" es 1, pero si queremos abrir otra ventana al mismo tiempo, "baseBlock" debe ser igual al producto de los campos width y height para evitar errores.

Código:
static const struct ListMenuTemplate sDebugMenuListTemplate =
{
    .items = sDebugMenuItems,
    .moveCursorFunc = ListMenuDefaultCursorMoveFunc,
    .totalItems = ARRAY_COUNT(sDebugMenuItems),
    .maxShowed = DEBUG_MAIN_MENU_HEIGHT,
    .windowId = 0,
    .header_X = 0,
    .item_X = 8,
    .cursor_X = 0,
    .upText_Y = 1,
    .cursorPal = 2,
    .fillValue = 1,
    .cursorShadowPal = 3,
    .lettersSpacing = 1,
    .itemVerticalPadding = 0,
    .scrollMultiple = LIST_NO_MULTIPLE_SCROLL,
    .fontId = 1,
    .cursorKind = 0
};
Esta es una plantilla de la lista de elementos de un menú. La lista se compone por los elementos entre los que el jugador puede elegir pulsando A. Al igual que la plantilla de una ventana define las propiedades de esta, esta plantilla hace lo correspondiente. Los campos importantes son "totalItems" (la cantidad de elementos que tiene la lista), "maxShowed" (la cantidad de elementos que se muestran) y "scrollMultiple" (define la capacidad de saltar elementos de la lista, y tiene tres opciones: LIST_NO_MULTIPLE_SCROLL para que esta función esté desactivada, y LIST_MULTIPLE_SCROLL_DPAD y LIST_MULTIPLE_SCROLL_L_R para que esta función esté activa usando izquierda y derecha en el D-pad o L y R respectivamente).

Código:
void Debug_ShowMainMenu(void) {
    struct ListMenuTemplate menuTemplate;
    u8 windowId;
    u8 menuTaskId;
    u8 inputTaskId;

    // create window
    windowId = AddWindow(&sDebugMenuWindowTemplate);
    DrawStdWindowFrame(windowId, FALSE);

    // create list menu
    menuTemplate = sDebugMenuListTemplate;
    menuTemplate.windowId = windowId;
    menuTaskId = ListMenuInit(&menuTemplate, 0, 0);

    // draw everything
    CopyWindowToVram(windowId, 3);

    // create input handler task
    inputTaskId = CreateTask(DebugTask_HandleMainMenuInput, 3);
    gTasks[inputTaskId].data[0] = menuTaskId;
    gTasks[inputTaskId].data[1] = windowId;
}

Esta es la función que se ejecuta en "field_control_avatar.c". Primero crea tanto la ventana como la lista de elementos que ya hemos explicado, y después crea una tarea para manejar el input del jugador. Esta tarea es una función que se ejecuta cada frame, en este caso DebugTask_HandleMainMenuInput será la función ejecutada, que comprueba y redirige el input del jugador a la función correspondiente.

Ahora mismo, deberías poder compilar el juego y abrir y cerrar el menú. Esto tan sólo es una base, es ahora tu trabajo el darle utilidad a este menú de debug.​
 
Última edición por un moderador:

DGamers64

RomHacker Novato
Mi pregunta es, entonces para compilar por el subsistema de Linux sobre Windows en el que para compilar se pone el comando make -jN, se pone ese comando cuando haces una versión para el público y make DDEBUG=1 cuando es una versión exclusivamente para ti?
 
Última edición:

DGamers64

RomHacker Novato
¡Hola de nuevo! Tengo un problema al compilar:
Captura de pantalla (403).png
Probé a llamar al archivo desde debug.h al debug.c y viceversa y nada, probé muchas cosas pero nada, agradecería que probarais si a vosotros también os pasa, por cierto arriba donde dice.
Luego, crearemos un archivo de nombre "debug.h" en la carpeta "src" con el siguiente código:
Ahí dice debug.h, el cuál acababamos de crearlo antes y en GitHub dice que es debug.c, que es así.
 

Kaktus

Miembro insignia
Miembro insignia
¡Hola de nuevo! Tengo un problema al compilar:
Probé a llamar al archivo desde debug.h al debug.c y viceversa y nada, probé muchas cosas pero nada, agradecería que probarais si a vosotros también os pasa, por cierto arriba donde dice.

Ahí dice debug.h, el cuál acababamos de crearlo antes y en GitHub dice que es debug.c, que es así.
Esta pregunta es de hace tiempo, pero para resolver los problemas de referenced in section X of Y: defined in discardes section... lo que hay que hacer es declarar los nombres de los archivos que dan error en el archivo ld_script.txt, para que el juego realmente compile el nuevo archivo que has creado. En este caso tendrías que añadir entre los corchetes de

C:
.text :
    ALIGN(4)
    {
        src/crt0.o(.text);
        src/main.o(.text);
        gflib/malloc.o(.text);
        gflib/dma3_manager.o(.text);
       ...
   }
La línea src/debug.o(.text);, y en los corchetes de

C:
.rodata :
    ALIGN(4)
    {
        src/main.o(.rodata);
        gflib/bg.o(.rodata);
        gflib/window.o(.rodata);
        gflib/text.o(.rodata);
       ...
   }
La línea src/debug.o(.rodata);.

Guardas el archivo, vuelves a compilar, y problema resuelto.
 
Arriba