Registrarse

[pokeemerald] Comando para cambiar el avatar del jugador

Samu

Miembro insignia
Miembro insignia
Diría que el título es bastante explicativo, he hecho un par de comandos que permiten cambiar el npc que muestra el avatar del jugador. Creo que aún no había ninguno público y supongo que os será de utilidad a varios para hacer lo que os apetezca en vuestros scripts. En fin, vamos al grano.

Los comandos se llaman 'setdisguise' y 'cleardisguise'. El primero activa el sistema y nos permite indicar que npc queremos cargar, así como restringir alguna de sus acciones permitidas. Los parámetros opcionales pueden ser indicados o no. Su valor por defecto es 0 (desactivado).
Código:
setdisguise [id], {canrun}, {canridebike}, {cansurf}, {canfish}, {canirrigate}
id - es el id del npc que queréis cargar como avatar. (obligatorio)
canrun - Si vale 0 impide correr, si vale 1 permite correr. (opcional)
canridebike - Si vale 0 impide correr en bici, si vale 1 lo permite. (opcional)
cansurf - Si vale 0 impide hacer surf, si vale 1 lo permite. (opcional)
canfish - Si vale 0 impide pescar, si vale 1 lo permite. (opcional)
canirrigate - Si vale 0 impide regar, si vale 1 lo permite. (opcional)

El segundo comando desactiva el sistema
Código:
cleardisguise

Es importante aclarar, que para hacer el cambio de avatar efectivo, es necesario recargar el mapa de alguna manera; por ejemplo con un warp.
Los ids de cada uino de los npc podéis verlos desde el porymap o en el fichero 'include/constants/event_objects.h'.


INSTALACIÓN
Para la instalación tenéis dos opciones:
Antes de continuar, volver a insistir en que podéis mirar los cambios que hay en el código de forma bastante más sencilla desde este commit de GitHub.
Hay que hacer cambios en los ficheros listados a continuación.
En primer lugar vamos a ir a 'include/global.h' y vamos a añadir el siguiente struct, en mi caso lo voy a colocar encima de 'struct PyramidBag' (lo único realmente importante es que esté antes de 'struct SaveBlock2').
Código:
struct PlayerDisguise
{
    u8 enabled:1;
    u8 canRun:1;
    u8 canSurf:1;
    u8 canRideBike:1; 
    u8 canFish:1;
    u8 canIrrigate:1;
    u16 npcId;
};

A continuación, dentro de este mismo fichero buscamos 'struct SaveBlock2' y añadimos 'struct PlayerDisguise playerDisguise;' al final. Quedará algo así:
Código:
.
.
.
    /*0x21C*/ struct RankingHall1P hallRecords1P[HALL_FACILITIES_COUNT][2][3]; // From record mixing.
    /*0x57C*/ struct RankingHall2P hallRecords2P[2][3]; // From record mixing.
    /*0x624*/ u16 contestLinkResults[CONTEST_CATEGORIES_COUNT][CONTESTANT_COUNT];
    /*0x64C*/ struct BattleFrontier frontier;
    /*0x64F*/ struct PlayerDisguise playerDisguise;
}; // sizeof=0xF2C
El siguiente fichero que debemos modificar es 'asm/macros/event.inc'. añadiremos nuestros dos comandos (setdisguise y cleardisguise) justo después de '.macro bufferitemnameplural'.
Código:
    .macro bufferitemnameplural out:req, item:req, quantity:req
    .byte 0xe2
    .byte \out
    .2byte \item
    .2byte \quantity
    .endm    @El código original termina aquí

    @El código a copiar comienza aquí
    .macro setdisguise id:req, canrun=0, canridebike=0, cansurf=0, canfish=0, canirrigate=0
    .byte 0xe3
    .2byte \id
    .byte \canrun
    .byte \canridebike
    .byte \cansurf
    .byte \canfish
    .byte \canirrigate
    .endm

    .macro cleardisguise
    .byte 0xe4
    .endm
A continuación tenemos que añadir dos funciones a la tabla de 'data/script_cmd_table.inc', justo debajo de '.4byte ScrCmd_bufferitemnameplural'.
Código:
    .4byte SrcCmd_setdisguise               @ 0xe3
    .4byte SrcCmd_cleardisguise             @ 0xe4
Después de haber añadido las funciones a la tabla, nos toca declararlas dentro del fichero 'src/scrcmd.c'. Pegamos lo siguiente al final del fichero.
Código:
bool8 SrcCmd_setdisguise(struct ScriptContext *ctx)
{
    u16 id = ScriptReadHalfword(ctx);
    bool8 canRun = ScriptReadByte(ctx);
    bool8 canRideBike = ScriptReadByte(ctx);
    bool8 canSurf = ScriptReadByte(ctx);
    bool8 canFish = ScriptReadByte(ctx);
    bool8 canIrrigate = ScriptReadByte(ctx);

    gSaveBlock2Ptr->playerDisguise.enabled = TRUE;
    gSaveBlock2Ptr->playerDisguise.npcId = id;
    gSaveBlock2Ptr->playerDisguise.canRun = canRun;
    gSaveBlock2Ptr->playerDisguise.canRideBike = canRideBike;
    gSaveBlock2Ptr->playerDisguise.canSurf = canSurf;
    gSaveBlock2Ptr->playerDisguise.canFish = canFish;
    gSaveBlock2Ptr->playerDisguise.canIrrigate = canIrrigate;
    return FALSE;
}

bool8 SrcCmd_cleardisguise(struct ScriptContext* ctx)
{
    gSaveBlock2Ptr->playerDisguise.enabled = FALSE;
    return FALSE;
}
Este cambio es opcional, pero recomendado. En el fichero 'src/event_object_movement.c' dentro de la función 'static void SetPlayerAvatarObjectEventIdAndObjectId (u8 objectEventId, u8 spriteId)' cambiamos la siguiente línea:
Código:
gPlayerAvatar.gender = GetPlayerAvatarGenderByGraphicsId(gObjectEvents[objectEventId].graphicsId);
por esto:
Código:
gPlayerAvatar.gender = gSaveBlock2Ptr->playerGender;
Dentro del fichero 'field_player_avatar.c' tendremos que realizar varios cambios. En primer lugar buscamos la función 'void InitPlayerAvatar(s16 x, s16 y, u8 direction, u8 gender)' y susituimos la línea
Código:
playerObjEventTemplate.graphicsId = GetPlayerAvatarGraphicsIdByStateIdAndGender(PLAYER_AVATAR_STATE_NORMAL, gender);
por esto:
Código:
    if (gSaveBlock2Ptr->playerDisguise.enabled)
        playerObjEventTemplate.graphicsId = gSaveBlock2Ptr->playerDisguise.npcId;
    else
        playerObjEventTemplate.graphicsId = GetPlayerAvatarGraphicsIdByStateIdAndGender(PLAYER_AVATAR_STATE_NORMAL, gender);

A continuación buscamos la función 'u8 GetPlayerAvatarGraphicsIdByStateId(u8 state)' y sustituimos su contenido con lo siguiente:
Código:
    if (gSaveBlock2Ptr->playerDisguise.enabled)
        return gSaveBlock2Ptr->playerDisguise.npcId;
    else
        return GetPlayerAvatarGraphicsIdByStateIdAndGender(state, gPlayerAvatar.gender);

De forma similar, buscamos la función 'u8 GetPlayerAvatarGraphicsIdByStateIdAndGender(u8 state, u8 gender)' y sustituimos su contenido.
Código:
    if (gSaveBlock2Ptr->playerDisguise.enabled)
        return gSaveBlock2Ptr->playerDisguise.npcId;
    return sPlayerAvatarGfxIds[state][gender];

Ahora buscamos la función 'static void PlayerNotOnBikeMoving(u8 direction, u16 heldKeys)' y pegamos el siguiente código justo encima.
Código:
static bool8 CanDisguiseRun()
{
    if (gSaveBlock2Ptr->playerDisguise.enabled && !gSaveBlock2Ptr->playerDisguise.canRun)
        return FALSE;
    return TRUE;
}

Por último, dentro de esa misma función 'PlayerNotOnBikeMoving' buscamos la siguiente línea:
Código:
&& IsRunningDisallowed(gObjectEvents[gPlayerAvatar.objectEventId].currentMetatileBehavior) == 0)
Y la sustituimos por esto:
Código:
&& IsRunningDisallowed(gObjectEvents[gPlayerAvatar.objectEventId].currentMetatileBehavior) == 0 && CanDisguiseRun())
Dentro del fichero 'src/field_control_avatar.c' buscamos la función 'GetInteractedWaterScript' y justo encima añadimos lo siguiente:
Código:
static bool8 CanDisguiseSurf()
{
    if (gSaveBlock2Ptr->playerDisguise.enabled && !gSaveBlock2Ptr->playerDisguise.canSurf)
        return FALSE;
    return TRUE;
}

Después de esto cambiamos las siguientes líneas dentro de la función 'GetInteractedWaterScript'.
Código:
if (FlagGet(FLAG_BADGE05_GET) == TRUE && PartyHasMonWithSurf() == TRUE && IsPlayerFacingSurfableFishableWater() == TRUE)
Se cambia por
Código:
if (FlagGet(FLAG_BADGE05_GET) && PartyHasMonWithSurf() && IsPlayerFacingSurfableFishableWater() && CanDisguiseSurf())

Código:
if (FlagGet(FLAG_BADGE08_GET) == TRUE && IsPlayerSurfingNorth() == TRUE)
Se cambia por
Código:
if (FlagGet(FLAG_BADGE08_GET) && IsPlayerSurfingNorth() && CanDisguiseSurf())
Ya estamos a punto de terminar YAY..... solo nos quedan hacer los cambios necesarios del fichero 'src/item_use.c'.

Buscamos la función 'ItemUseOutOfBattle_Bike' y encima de la misma añadimos:
Código:
static bool8 CanDisguiseRideBike()
{
    if (gSaveBlock2Ptr->playerDisguise.enabled && !gSaveBlock2Ptr->playerDisguise.canRideBike)
        return FALSE;
    return TRUE;
}

Ahora, dentro de la propia función 'ItemUseOutOfBattle_Bike' buscamos la línea
Código:
if (Overworld_IsBikingAllowed() == TRUE && IsBikingDisallowedByPlayer() == 0)
Y la sustituimos por
Código:
if (Overworld_IsBikingAllowed() == TRUE && IsBikingDisallowedByPlayer() == 0 && CanDisguiseRideBike())

Buscamos la función 'void ItemUseOutOfBattle_Rod(u8 taskId)' y encima colocamos lo siguiente:
Código:
static bool8 CanDisguiseFish()
{
    if (gSaveBlock2Ptr->playerDisguise.enabled && !gSaveBlock2Ptr->playerDisguise.canFish)
        return FALSE;
    return TRUE;
}

Dentro de la propia función 'ItemUseOutOfBattle_Rod' sustituimos la línea
Código:
if (CanFish() == TRUE)
por
Código:
if (CanFish() && CanDisguiseFish())

Buscamos la función 'void ItemUseOutOfBattle_WailmerPail(u8 taskId)' y justo encima añadimos
Código:
static bool8 CanDisguiseIrrigate()
{
    if (gSaveBlock2Ptr->playerDisguise.enabled && !gSaveBlock2Ptr->playerDisguise.canIrrigate)
        return FALSE;
    return TRUE;
}

Por último, dentro de la propia función 'ItemUseOutOfBattle_WailmerPail' tendremos que sustituir la línea
Código:
if (TryToWaterSudowoodo() == TRUE)
por
Código:
if (TryToWaterSudowoodo() && CanDisguiseIrrigate())

y la siguiente línea
Código:
else if (TryToWaterBerryTree() == TRUE)
por
Código:
else if (TryToWaterBerryTree() && CanDisguiseIrrigate())
 

SenorX

Tipo de incógnito
Miembro de honor
Como comentaste, este comando se activa al recargar el mapa de alguna de las formas mencionadas. Imagínate que por ejemplo, estás en una cueva y consigues un "disfraz", usas el comando para ponértelo. Si una vez puesto, pasas por algún warp para ir más al interior de la cueva, al entrar en el nuevo mapa, ¿el disfraz se quitaría automáticamente, o es permanente hasta que lo anules con otro comando o condición?
 

Kaktus

Miembro insignia
Miembro insignia
Gracias por ahorrarme la media hora de investigación por mi cuenta, te quiero guarra.

Fuera coñas, buen aporte tío, se agradece que compartas tu sabiduría con toda la gente del foro, que ya sabes que es muuuy bien recibida. Sigue aportando así hdp, que necesitamos material para que los nuevos se animen ❤
 

Samu

Miembro insignia
Miembro insignia
Como comentaste, este comando se activa al recargar el mapa de alguna de las formas mencionadas. Imagínate que por ejemplo, estás en una cueva y consigues un "disfraz", usas el comando para ponértelo. Si una vez puesto, pasas por algún warp para ir más al interior de la cueva, al entrar en el nuevo mapa, ¿el disfraz se quitaría automáticamente, o es permanente hasta que lo anules con otro comando o condición?
El cambio de npc es permanente hasta que lo desactives con el comando cleardisguise o lo sobrescribas con otro npc. Da igual lo que hagas, que no lo se va a desactivar. Puedes apagar, moverte de mapa, entrar en combate, en los menús...
 

KevinXDE

Usuario mítico
Me he leído los cambios de la rama y me encanta como has escrito el código. A ver si se me pega algo. Genial aporte, segurísimo que lo usaré 😁
 
Arriba