Registrarse

[pokeemerald] ¡Añadir nuevas emociones!

Disturbo

Decomper
¡Hola hola otra vez!

Otro tuto más para las decomps, recién salidito de cocina. Esta vez, como podéis ver en el título, ¡es para añadir nuevas emociones (de 16x16)! Habrá que hacer cambios a bastantes archivos, pero serán cortitos y de copia y pega básicamente. Los nombres de los métodos creados son orientativos, vosotros podéis darles los que queráis. Se darán referencias sobre donde colocar el código en caso de que sea necesario. Empecemos.

Definir la nueva emoción
Tenemos que crear una referencia a esta emoción, para lo que vamos a include/constants/field_effects.h y encontramos lo siguiente:
C:
#ifndef GUARD_FIELD_EFFECT_CONSTANTS_H
#define GUARD_FIELD_EFFECT_CONSTANTS_H

#define FLDEFF_EXCLAMATION_MARK_ICON     0 
#define FLDEFF_USE_CUT_ON_GRASS          1
#define FLDEFF_USE_CUT_ON_TREE           2
#define FLDEFF_SHADOW                    3
#define FLDEFF_TALL_GRASS                4
#define FLDEFF_RIPPLE                    5
#define FLDEFF_FIELD_MOVE_SHOW_MON       6
#define FLDEFF_ASH                       7
#define FLDEFF_SURF_BLOB                 8
#define FLDEFF_USE_SURF                  9
#define FLDEFF_DUST                      10
// ...
// Muchos "defines" más
// ...
#define FLDEFF_USE_TOMB_PUZZLE_EFFECT    60
#define FLDEFF_PCTURN_ON                 61
#define FLDEFF_HALL_OF_FAME_RECORD       62
#define FLDEFF_USE_TELEPORT              63
#define FLDEFF_RAYQUAZA                  64
#define FLDEFF_65                        65
#define FLDEFF_MOVE_DEOXYS_ROCK          66

#endif
Simplemente debemos añadir un nuevo define al antes del "#endif", tal que
C:
#define FLDEFF_65                        65
#define FLDEFF_MOVE_DEOXYS_ROCK          66

#define FLDEFF_TEST_ICON                 67
Haremos esto para cada emoción que queramos añadir ¡Es importante que su número sea uno más que el último!



Métodos para la carga de gráficos
Ahora vamos a hacer todo lo necesario para que el juego pueda mostrar los gráficos de la nueva emoción. Primero definimos la localización del gráfico (de la paleta no ya que usa la del jugador). Vamos a src/trainer_see.c y en el siguiente bloque de código:
C:
static const u8 sEmotion_ExclamationMarkGfx[] = INCBIN_U8("graphics/misc/emotion_exclamation.4bpp");
static const u8 sEmotion_QuestionMarkGfx[] = INCBIN_U8("graphics/misc/emotion_question.4bpp");
static const u8 sEmotion_HeartGfx[] = INCBIN_U8("graphics/misc/emotion_heart.4bpp");
Añadiremos la referencia a nuestra imagen, situada en "graphics/misc/" y llamada "emotion_test.png", tal que así:
C:
static const u8 sEmotion_HeartGfx[] = INCBIN_U8("graphics/misc/emotion_heart.4bpp");

static const u8 sEmotion_TestGfx[] = INCBIN_U8("graphics/misc/emotion_test.4bpp");

Después le damos una estructura para que el juego reconozca su tamaño y gráfico. En el mismo archivo, buscamos:
C:
static const struct SpriteFrameImage sSpriteImageTable_HeartIcon[] =
{
    {
        .data = sEmotion_HeartGfx,
        .size = 0x80
    }
};
Y justo debajo añadimos nuestro método:
C:
static const struct SpriteFrameImage sSpriteImageTable_TestIcon[] =
{
    {
        .data = sEmotion_TestGfx,
        .size = 0x80
    }
};

Y por último le damos otra estructura con el resto de la información que necesita para cargar el gráfico. Buscamos:
C:
static const struct SpriteTemplate sSpriteTemplate_HeartIcon =
{
    .tileTag = 0xffff,
    .paletteTag = 0x1004,
    .oam = &sOamData_Icons,
    .anims = sSpriteAnimTable_Icons,
    .images = sSpriteImageTable_HeartIcon,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_TrainerIcons
};
Y debajo de esto añadimos el método para nuestra emoción:
C:
static const struct SpriteTemplate sSpriteTemplate_TestIcon =
{
    .tileTag = 0xffff,
    .paletteTag = 0x1004,
    .oam = &sOamData_Icons,
    .anims = sSpriteAnimTable_Icons,
    .images = sSpriteImageTable_TestIcon,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_TrainerIcons
};

Por último, creamos un método que más adelante nos permita cargar el gráfico a la memoria. Bucamos:
C:
u8 FldEff_HeartIcon(void)
{
    u8 spriteId = CreateSpriteAtEnd(&sSpriteTemplate_HeartIcon, 0, 0, 0x52);

    if (spriteId != MAX_SPRITES)
    {
        struct Sprite *sprite = &gSprites[spriteId];

        SetIconSpriteData(sprite, FLDEFF_HEART_ICON, 0);
        sprite->oam.paletteNum = 2;
    }

    return 0;
}
Y añadimos justo después:
C:
u8 FldEff_TestIcon(void)
{
    u8 spriteId = CreateSpriteAtEnd(&sSpriteTemplate_TestIcon, 0, 0, 0x52);

    if (spriteId != MAX_SPRITES)
    {
        struct Sprite *sprite = &gSprites[spriteId];
	// Daos cuenta de que teneis que reemplazar FLDEFF_TEST_ICON con el define que habéis creado antes
        SetIconSpriteData(sprite, FLDEFF_TEST_ICON, 0);
        sprite->oam.paletteNum = 0;
    }

    return 0;
}

También tenemos que añadir este último método al "include", para lo que vamos a include/trainer_see.h y buscamos:
C:
u8 FldEff_ExclamationMarkIcon(void);
u8 FldEff_QuestionMarkIcon(void);
u8 FldEff_HeartIcon(void);
Y debajo añadimos el FldEff_ que acabamos de crear:
C:
u8 FldEff_ExclamationMarkIcon(void);
u8 FldEff_QuestionMarkIcon(void);
u8 FldEff_HeartIcon(void);

u8 FldEff_TestIcon(void);
Repetid estos pasos para cada emoción que queráis añadir.



Empezar la integración en los comanos de scripting
Ahora crearemos un método que nos será útil más adelante. Para ello haremos dos cambios en data/field_effect_scripts.s. Para el primero, buscamos:
C:
gFieldEffectScriptPointers:: @ 82DB9D4
	.4byte gFieldEffectScript_ExclamationMarkIcon1
	.4byte gFieldEffectScript_UseCutOnTallGrass
	.4byte gFieldEffectScript_UseCutOnTree
	.4byte gFieldEffectScript_Shadow
	@ Muchos ".4bytes" por aquí
	.4byte gFieldEffectScript_Rayquaza
	.4byte gFieldEffectScript_Unknown65
	.4byte gFieldEffectScript_MoveDeoxysRock
Y añadimos al final:
C:
	.4byte gFieldEffectScript_Rayquaza
	.4byte gFieldEffectScript_Unknown65
	.4byte gFieldEffectScript_MoveDeoxysRock

	.4byte gFieldEffectScript_TestIcon
Y ahora al final del archivo añadimos:
C:
gFieldEffectScript_TestIcon:: @ 82DBC56
	field_eff_loadfadedpal_callnative gFieldEffectObjectPaletteInfo0, FldEff_TestIcon
	field_eff_end
De nuevo, repetimos estos pasos si queremos añadir más emociones.



Crear un nuevo tipo de movimiento
Para el juego, las emociones son equivalentes a dar un paso o saltar un borde, es decir, están consideradas como movimiento, y por ello también vamos a definirlas como tal. Vamos a include/constants/event_object_movement.h y encontramos esto:
C:
#ifndef GUARD_CONSTANTS_EVENT_OBJECT_MOVEMENT_H
#define GUARD_CONSTANTS_EVENT_OBJECT_MOVEMENT_H

#define MOVEMENT_TYPE_NONE                             0x0
#define MOVEMENT_TYPE_LOOK_AROUND                      0x1
#define MOVEMENT_TYPE_WANDER_AROUND                    0x2
#define MOVEMENT_TYPE_WANDER_UP_AND_DOWN               0x3
#define MOVEMENT_TYPE_WANDER_DOWN_AND_UP               0x4
#define MOVEMENT_TYPE_WANDER_LEFT_AND_RIGHT            0x5
#define MOVEMENT_TYPE_WANDER_RIGHT_AND_LEFT            0x6
#define MOVEMENT_TYPE_FACE_UP                          0x7
#define MOVEMENT_TYPE_FACE_DOWN                        0x8
#define MOVEMENT_TYPE_FACE_LEFT                        0x9
#define MOVEMENT_TYPE_FACE_RIGHT                       0xA
#define MOVEMENT_TYPE_PLAYER                           0xB
#define MOVEMENT_TYPE_BERRY_TREE_GROWTH                0xC
#define MOVEMENT_TYPE_FACE_DOWN_AND_UP                 0xD
#define MOVEMENT_TYPE_FACE_LEFT_AND_RIGHT              0xE
#define MOVEMENT_TYPE_FACE_UP_AND_LEFT                 0xF

//Muchos más "defines" en este espacio

#define MOVEMENT_ACTION_FREE_AND_UNLOCK_ANIM            0x95
#define MOVEMENT_ACTION_WALK_LEFT_AFFINE                0x96
#define MOVEMENT_ACTION_WALK_RIGHT_AFFINE               0x97
#define MOVEMENT_ACTION_LEVITATE                        0x98
#define MOVEMENT_ACTION_STOP_LEVITATE                   0x99
#define MOVEMENT_ACTION_DESTROY_EXTRA_TASK_IF_AT_TOP    0x9A
#define MOVEMENT_ACTION_FIGURE_8                        0x9B
#define MOVEMENT_ACTION_FLY_UP                          0x9C
#define MOVEMENT_ACTION_FLY_DOWN                        0x9D

#define MOVEMENT_ACTION_STEP_END 0xFE

#endif // GUARD_CONSTANTS_EVENT_OBJECT_MOVEMENT_H
Y antes del "#define MOVEMENT_ACTION_STEP_END 0xFE", añadimos lo siguiente:
C:
#define MOVEMENT_ACTION_FIGURE_8                        0x9B
#define MOVEMENT_ACTION_FLY_UP                          0x9C
#define MOVEMENT_ACTION_FLY_DOWN                        0x9D

//Por supuesto le damos el nombre que queramos
#define MOVEMENT_ACTION_EMOTE_TEST                      0x9E

#define MOVEMENT_ACTION_STEP_END 0xFE
Igual que con el "define" de la primera parte del tutorial, su valor debe ser uno mayor que el último, pero esta vez contando en hexadecimal.



Darle funcionalidad al nuevo movimiento
Ya hemos definido nuestra emoción como un movimiento, pero ahora necesitamos que haga algo, para lo que vamos a src/event_object_movement.c y buscamos lo siguiente:
C:
bool8 MovementAction_EmoteHeart_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
    ObjectEventGetLocalIdAndMap(objectEvent, &gFieldEffectArguments[0], &gFieldEffectArguments[1], &gFieldEffectArguments[2]);
    FieldEffectStart(FLDEFF_HEART_ICON);
    sprite->data[2] = 1;
    return TRUE;
}
Y añadimos después de esto:
C:
bool8 MovementAction_EmoteTest_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
    ObjectEventGetLocalIdAndMap(objectEvent, &gFieldEffectArguments[0], &gFieldEffectArguments[1], &gFieldEffectArguments[2]);
    // De nuevo, reemplazad FLDEFF_TEST_ICON con el define que habéis creado al principio
    FieldEffectStart(FLDEFF_TEST_ICON);
    sprite->data[2] = 1;
    return TRUE;
}
Y os escucho decir "Oye pues ya, ¿no?". Pobres ilusos, ¡queda lo mas mejor! ^^

Ahora tenemos que añadir este nuevo método al "include", pero esta vez de manera especial. En total, ¡3 veces! Vamos a src/data/object_events/movement_action_func_tables.h para empezar la fiesta.
Encontramos este bloque de código:
C:
u8 MovementAction_FaceDown_Step0(struct ObjectEvent *, struct Sprite *);
u8 MovementAction_FaceDown_Step0(struct ObjectEvent *, struct Sprite *);
u8 MovementAction_PauseSpriteAnim(struct ObjectEvent *, struct Sprite *);
u8 MovementAction_FaceUp_Step0(struct ObjectEvent *, struct Sprite *);
u8 MovementAction_FaceLeft_Step0(struct ObjectEvent *, struct Sprite *);
u8 MovementAction_FaceRight_Step0(struct ObjectEvent *, struct Sprite *);

// Una montonera de "u8"

u8 MovementAction_FlyUp_Step0(struct ObjectEvent *, struct Sprite *);
u8 MovementAction_FlyUp_Step1(struct ObjectEvent *, struct Sprite *);
u8 MovementAction_Fly_Finish(struct ObjectEvent *, struct Sprite *);
u8 MovementAction_FlyDown_Step0(struct ObjectEvent *, struct Sprite *);
u8 MovementAction_FlyDown_Step1(struct ObjectEvent *, struct Sprite *);
Y al final añadimos nuestra nueva emoción:
C:
u8 MovementAction_FlyDown_Step0(struct ObjectEvent *, struct Sprite *);
u8 MovementAction_FlyDown_Step1(struct ObjectEvent *, struct Sprite *);

u8 MovementAction_EmoteTest_Step0(struct ObjectEvent *, struct Sprite *);
Encontramos otro bloquecito de nada de código:
C:
u8 (*const gMovementActionFuncs_FaceDown[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_FaceUp[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_FaceLeft[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_FaceRight[])(struct ObjectEvent *, struct Sprite *);

//Otro montón de "u8"

u8 (*const gMovementActionFuncs_Figure8[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_FlyUp[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_FlyDown[])(struct ObjectEvent *, struct Sprite *);
Y ¡oh, sorpresa! añadimos nuestra nueva emoción al final:
C:
u8 (*const gMovementActionFuncs_FlyUp[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_FlyDown[])(struct ObjectEvent *, struct Sprite *);

u8 (*const gMovementActionFuncs_EmoteTest[])(struct ObjectEvent *, struct Sprite *);
Y también añadimos al final del archivo lo siguiente:
C:
u8 (*const gMovementActionFuncs_EmoteTest[])(struct ObjectEvent *, struct Sprite *) = {
    MovementAction_EmoteTest_Step0,
    MovementAction_Finish,
};

Finalmente, encontramos otro tocho más:
C:
u8 (*const *const gMovementActionFuncs[])(struct ObjectEvent *, struct Sprite *) = {
    [MOVEMENT_ACTION_FACE_DOWN] = gMovementActionFuncs_FaceDown,
    [MOVEMENT_ACTION_FACE_UP] = gMovementActionFuncs_FaceUp,
    [MOVEMENT_ACTION_FACE_LEFT] = gMovementActionFuncs_FaceLeft,
    [MOVEMENT_ACTION_FACE_RIGHT] = gMovementActionFuncs_FaceRight,
    
    //Unas cuantas asignaciones

    [MOVEMENT_ACTION_FIGURE_8] = gMovementActionFuncs_Figure8,
    [MOVEMENT_ACTION_FLY_UP] = gMovementActionFuncs_FlyUp,
    [MOVEMENT_ACTION_FLY_DOWN] = gMovementActionFuncs_FlyDown,
};
Para sopresa de nadie, añadimos al final nuestra emoción:
C:
    [MOVEMENT_ACTION_FLY_UP] = gMovementActionFuncs_FlyUp,
    [MOVEMENT_ACTION_FLY_DOWN] = gMovementActionFuncs_FlyDown,
    
    [MOVEMENT_ACTION_EMOTE_TEST] = gMovementActionFuncs_EmoteTest,
Enhorabuena, habéis terminado los includes. Ahora continuemos.



Continuando la implementación de scripting
Esta vez vamos a crear un comando (el cuál no será usado por el jugador) que sea un puente con el método de carga de gráficos. Esta parte es sencilla, solo tenemos que ir a asm/macros/movement.inc
C:
	.macro create_movement_action name
	enum _\name
	.macro \name
	.byte _\name
	.endm
	.endm

	enum_start
	create_movement_action face_down
	create_movement_action face_up
	create_movement_action face_left
	create_movement_action face_right
	create_movement_action walk_slow_down
	create_movement_action walk_slow_up
	create_movement_action walk_slow_left
	create_movement_action walk_slow_right
	
	@ Un montón de "create_movement_action"

	create_movement_action figure_8
	create_movement_action fly_up
	create_movement_action fly_down

	enum_start 0xfe
	create_movement_action step_end
Y añadimos antes de "enum_start 0xfe":
C:
	create_movement_action figure_8
	create_movement_action fly_up
	create_movement_action fly_down

	create_movement_action emote_test

	enum_start 0xfe
	create_movement_action step_end


Incorporar el nuevo tipo de movimiento al comando "applymovement"
Ahora haremos un nuevo "comando" que hará de argumento en el comando "applymovement" y que llamará al "emote_" que acabamos de crear. Vamos a y añadimos en cualquier parte del archivo:
C:
Common_Movement_Test:
	//Reemplazamos esto con el "emote" que creamos antes
	emote_test
	step_end


¡Y con eso hemos terminado! Solo queda resaltar la importancia de que el número del "FLDEFF_" coincida con la posición del "gFieldEffectScript", algo que, si habéis seguido bien el tutorial, no debería ser un problema.
Para utilizar la emoción que acabamos de añadir en un script: applymovement <id del npc> <nombre del movimiento>
En mi caso, "<nombre del movimiento>" es "Common_Movement_Test".

Espero que hayais econtrado útil el tutorial, y si os lo habéis leído hasta aquí y no utilizais ya decomp, ¡a que esperáis! Pasaos por el canal #decompilación en Discord.

~Disturbo
 
Última edición:

Kaktus

Miembro insignia
Miembro insignia
¡¡Woooow!!

Esto es super útil, ¿y no tiene ni un comentario? hace unas semanas estuve pensando acerca de como se podría hacer esto y me encuentro con que tú ya lo has aportado, grande Disturbo <3
 

Xiros

¡Pokémon Omega con actualización del 30/8!
Miembro de honor
Excelente!

En realidad, ya implementé mis propias emociones sin haber sabido de la existencia de este tutorial, pero me hubiese ahorrado un ratito de investigación si lo hubiese visto antes :)
 
Arriba