Registrarse
  • ¡Comienzan las votaciones del MPC o Mejor Proyecto Cuatrimestral de enero a abril de 2026!
    ¡Vota por tu proyecto favorito y recompensa el trabajo duro de los participantes!

    El plazo concluye el 15 de mayo

[ASM] E | Pokémon errante no escapa durmiendo (o congelado)

aptitud

Usuario mítico
Idioma del tutorial : español

Base juego : emerald (US)

Créditos :
a) Archivo del "emerald decompilation project"
para comprobar como y donde están los códigos que hacen lo que nos interesan.
b) la herramienta thumb para compilar codigo asm


Introducción :

Los pokémones errantes/"roaming" (o pkmn salvajes que pueden escapar, en general)
les gustan a algunos, y los despiden a otros.

Un problema que se puede tener es la que :
El errante se puede escapar cuando esta durmiendo o congelado.
(Esto se ha casi-cambiado en un broma en el web.)

Pero, en realidad, antes de gen 3,
es decir ; cuando aparecen, en gen2, las funcionalidades de pokemones errantes,
y de pokemones salvajes que pueden tratar de escapar :
En gen 2 un pokémon salvaje no se escapa cuando esta durmiendo o congelado.

Entonces, la idea seria :

Como hacer que, en emerald, de nuevo,
un pokemon salvaje que puede tratar de escapar,
no lo hace cuando su estado esta durmiendo o congelado ?


(He buscado como hacerlo, por años...)
Y : "¡Finalmente!"

El tutorial de hoy explica un medio para hacer esta modificacion,
con añadidos de recursos sobre como funciona los sistemas implicados.​



Dos cosas que modificar

De verdad, cuando le ha descubierto a lo que debe cambiar,
es "bastante" simple hacer que un pokémon errante/salvaje no escapa durmiendo/congelado.

Hay dos cosas que se necesita cambiar para que funciona bien :
A) El fácil de comprobar/cambiar :
La "Inteligencia Artificial" del errantes para elegir de escapar o no
B) el código "HandleAction_Run" que decide lo que pasa,
en general, cuando un pokémon salvaje trata de escapar
(cuando lo ha elegido hacer al principio del turno).


A) Cambiar el código (formateado) de la IA_errante/AI_Roaming

En el juego, la IA de un pokemon, en combate, funciona con una lista de flags :
Simplificando : Cada flag decide 'si' o 'no', en orden, se necesita aplicar cosa.
(Estos flags son decidido, en general, por el tipo de batalla.)

Entonces, cuando empezá una batalla contra un errante,
el tipo de batalla es registrado como "Roaming" ;
y con eso, se activa el flag para que el juego hace el script de la IA_errante/AI_Roaming.

Aquí es un extracto de código decompilado, del "emerald decompilation project" :
Código:
gBattleAI_ScriptsTable:: @ 82DBEF8
    .4byte AI_CheckBadMove          @ AI_SCRIPT_CHECK_BAD_MOVE
    .4byte AI_TryToFaint            @ AI_SCRIPT_TRY_TO_FAINT
    .4byte AI_CheckViability        @ AI_SCRIPT_CHECK_VIABILITY
    .4byte AI_SetupFirstTurn        @ AI_SCRIPT_SETUP_FIRST_TURN
    .4byte AI_Risky                 @ AI_SCRIPT_RISKY
    .4byte AI_PreferStrongestMove   @ AI_SCRIPT_PREFER_STRONGEST_MOVE
    .4byte AI_PreferBatonPass       @ AI_SCRIPT_PREFER_BATON_PASS
    .4byte AI_DoubleBattle             @ AI_SCRIPT_DOUBLE_BATTLE
    .4byte AI_HPAware               @ AI_SCRIPT_HP_AWARE
    .4byte AI_Unknown               @ AI_SCRIPT_UNKNOWN
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Ret
    .4byte AI_Roaming               @ AI_SCRIPT_ROAMING
    .4byte AI_Safari                @ AI_SCRIPT_SAFARI
    .4byte AI_FirstBattle           @ AI_SCRIPT_FIRST_BATTLE

Entonces, se puede comprobar que el script para AI_Roaming esta a dirección 2DE309
(y el comprobá si el errante elige de huir/escapar, depende sobre estados y habilidades).

El decompilation le transcribe con :
Código:
AI_Roaming:
    if_status2 AI_USER, STATUS2_WRAPPED, AI_Roaming_End
    if_status2 AI_USER, STATUS2_ESCAPE_PREVENTION, AI_Roaming_End
    get_ability AI_TARGET
    if_equal ABILITY_SHADOW_TAG, AI_Roaming_End
    get_ability AI_USER
    if_equal ABILITY_LEVITATE, AI_Roaming_Flee
    get_ability AI_TARGET
    if_equal ABILITY_ARENA_TRAP, AI_Roaming_End

AI_Roaming_Flee: @ 82DE335
    flee

AI_Roaming_End: @ 82DE336
    end
que esta, en hexadecimal :
Código:
0B 01 00 E0 00 00 36 E3 2D 08
0B 01 00 00 00 04 36 E3 2D 08
2F 00 13 17 36 E3 2D 08
2F 01 13 1A 35 E3 2D 08
2F 00 13 47 36 E3 2D 08
45
5A
También, un otro extracto de código decompilado,
cuales son las instrucciones utilizadas en estos scripts de IA de combate :
Código:
static const BattleAICmdFunc sBattleAICmdTable[] =
{
    Cmd_if_random_less_than,                        // 0x0
    Cmd_if_random_greater_than,                     // 0x1
    Cmd_if_random_equal,                            // 0x2
    Cmd_if_random_not_equal,                        // 0x3
    Cmd_score,                                      // 0x4
    Cmd_if_hp_less_than,                            // 0x5
    Cmd_if_hp_more_than,                            // 0x6
    Cmd_if_hp_equal,                                // 0x7
    Cmd_if_hp_not_equal,                            // 0x8
    Cmd_if_status,                                  // 0x9
    Cmd_if_not_status,                              // 0xA
    Cmd_if_status2,                                 // 0xB
    Cmd_if_not_status2,                             // 0xC
    Cmd_if_status3,                                 // 0xD
    Cmd_if_not_status3,                             // 0xE
    Cmd_if_side_affecting,                          // 0xF
    Cmd_if_not_side_affecting,                      // 0x10
    Cmd_if_less_than,                               // 0x11
    Cmd_if_more_than,                               // 0x12
    Cmd_if_equal,                                   // 0x13
    Cmd_if_not_equal,                               // 0x14
    Cmd_if_less_than_ptr,                           // 0x15
    Cmd_if_more_than_ptr,                           // 0x16
    Cmd_if_equal_ptr,                               // 0x17
    Cmd_if_not_equal_ptr,                           // 0x18
    Cmd_if_move,                                    // 0x19
    Cmd_if_not_move,                                // 0x1A
    Cmd_if_in_bytes,                                // 0x1B
    Cmd_if_not_in_bytes,                            // 0x1C
    Cmd_if_in_hwords,                               // 0x1D
    Cmd_if_not_in_hwords,                           // 0x1E
    Cmd_if_user_has_attacking_move,                 // 0x1F
    Cmd_if_user_has_no_attacking_moves,             // 0x20
    Cmd_get_turn_count,                             // 0x21
    Cmd_get_type,                                   // 0x22
    Cmd_get_considered_move_power,                  // 0x23
    Cmd_get_how_powerful_move_is,                   // 0x24
    Cmd_get_last_used_battler_move,                 // 0x25
    Cmd_if_equal_,                                  // 0x26
    Cmd_if_not_equal_,                              // 0x27
    Cmd_if_user_goes,                               // 0x28
    Cmd_if_user_doesnt_go,                          // 0x29
    Cmd_nop_2A,                                     // 0x2A
    Cmd_nop_2B,                                     // 0x2B
    Cmd_count_usable_party_mons,                    // 0x2C
    Cmd_get_considered_move,                        // 0x2D
    Cmd_get_considered_move_effect,                 // 0x2E
    Cmd_get_ability,                                // 0x2F
    Cmd_get_highest_type_effectiveness,             // 0x30
    Cmd_if_type_effectiveness,                      // 0x31
    Cmd_nop_32,                                     // 0x32
    Cmd_nop_33,                                     // 0x33
    Cmd_if_status_in_party,                         // 0x34
    Cmd_if_status_not_in_party,                     // 0x35
    Cmd_get_weather,                                // 0x36
    Cmd_if_effect,                                  // 0x37
    Cmd_if_not_effect,                              // 0x38
    Cmd_if_stat_level_less_than,                    // 0x39
    Cmd_if_stat_level_more_than,                    // 0x3A
    Cmd_if_stat_level_equal,                        // 0x3B
    Cmd_if_stat_level_not_equal,                    // 0x3C
    Cmd_if_can_faint,                               // 0x3D
    Cmd_if_cant_faint,                              // 0x3E
    Cmd_if_has_move,                                // 0x3F
    Cmd_if_doesnt_have_move,                        // 0x40
    Cmd_if_has_move_with_effect,                    // 0x41
    Cmd_if_doesnt_have_move_with_effect,            // 0x42
    Cmd_if_any_move_disabled_or_encored,            // 0x43
    Cmd_if_curr_move_disabled_or_encored,           // 0x44
    Cmd_flee,                                       // 0x45
    Cmd_if_random_safari_flee,                      // 0x46
    Cmd_watch,                                      // 0x47
    Cmd_get_hold_effect,                            // 0x48
    Cmd_get_gender,                                 // 0x49
    Cmd_is_first_turn_for,                          // 0x4A
    Cmd_get_stockpile_count,                        // 0x4B
    Cmd_is_double_battle,                           // 0x4C
    Cmd_get_used_held_item,                         // 0x4D
    Cmd_get_move_type_from_result,                  // 0x4E
    Cmd_get_move_power_from_result,                 // 0x4F
    Cmd_get_move_effect_from_result,                // 0x50
    Cmd_get_protect_count,                          // 0x51
    Cmd_nop_52,                                     // 0x52
    Cmd_nop_53,                                     // 0x53
    Cmd_nop_54,                                     // 0x54
    Cmd_nop_55,                                     // 0x55
    Cmd_nop_56,                                     // 0x56
    Cmd_nop_57,                                     // 0x57
    Cmd_call,                                       // 0x58
    Cmd_goto,                                       // 0x59
    Cmd_end,                                        // 0x5A
    Cmd_if_level_cond,                              // 0x5B
    Cmd_if_target_taunted,                          // 0x5C
    Cmd_if_target_not_taunted,                      // 0x5D
    Cmd_if_target_is_ally,                          // 0x5E
    Cmd_is_of_type,                                 // 0x5F
    Cmd_check_ability,                              // 0x60
    Cmd_if_flash_fired,                             // 0x61
    Cmd_if_holds_item,                              // 0x62
};

Y lo que nos interesan en el script de IA_errante
seria las dos linea de instrucciones a su principio :
Código:
    if_status2 AI_USER, STATUS2_WRAPPED, AI_Roaming_End
    if_status2 AI_USER, STATUS2_ESCAPE_PREVENTION, AI_Roaming_End
los parámetros de 'if_status2' (0x0B) están, entonces, en orden :
> 01 o 00 (AI_USER o AI_TARGET) (1 byte)
> mask de estado (4 bytes)
> dirección donde iría el script si la comprobá es verdadera (i.e. no cero) (4 bytes)


Código:
// Non-volatile status conditions
// These persist remain outside of battle and after switching out
#define STATUS1_NONE             0
#define STATUS1_SLEEP            (1 << 0 | 1 << 1 | 1 << 2) // First 3 bits (Number of turns to sleep)
#define STATUS1_SLEEP_TURN(num)  ((num) << 0) // Just for readability (or if rearranging statuses)
#define STATUS1_POISON           (1 << 3)
#define STATUS1_BURN             (1 << 4)
#define STATUS1_FREEZE           (1 << 5)
#define STATUS1_PARALYSIS        (1 << 6)
#define STATUS1_TOXIC_POISON     (1 << 7)
#define STATUS1_TOXIC_COUNTER    (1 << 8 | 1 << 9 | 1 << 10 | 1 << 11)
#define STATUS1_TOXIC_TURN(num)  ((num) << 8)
#define STATUS1_PSN_ANY          (STATUS1_POISON | STATUS1_TOXIC_POISON)
#define STATUS1_ANY              (STATUS1_SLEEP | STATUS1_POISON | STATUS1_BURN | STATUS1_FREEZE | STATUS1_PARALYSIS | STATUS1_TOXIC_POISON)

// Volatile status ailments
// These are removed after exiting the battle or switching out
#define STATUS2_CONFUSION             (1 << 0 | 1 << 1 | 1 << 2)
#define STATUS2_CONFUSION_TURN(num)   ((num) << 0)
#define STATUS2_FLINCHED              (1 << 3)
#define STATUS2_UPROAR                (1 << 4 | 1 << 5 | 1 << 6)
#define STATUS2_UPROAR_TURN(num)      ((num) << 4)
#define STATUS2_UNUSED                (1 << 7)
#define STATUS2_BIDE                  (1 << 8 | 1 << 9)
#define STATUS2_BIDE_TURN(num)        (((num) << 8) & STATUS2_BIDE)
#define STATUS2_LOCK_CONFUSE          (1 << 10 | 1 << 11) // e.g. Thrash
#define STATUS2_LOCK_CONFUSE_TURN(num)((num) << 10)
#define STATUS2_MULTIPLETURNS         (1 << 12)
#define STATUS2_WRAPPED               (1 << 13 | 1 << 14 | 1 << 15)
#define STATUS2_WRAPPED_TURN(num)     ((num) << 13)
#define STATUS2_INFATUATION           (1 << 16 | 1 << 17 | 1 << 18 | 1 << 19)  // 4 bits, one for every battler
#define STATUS2_INFATUATED_WITH(battler) (gBitTable[battler] << 16)
#define STATUS2_FOCUS_ENERGY          (1 << 20)
#define STATUS2_TRANSFORMED           (1 << 21)
#define STATUS2_RECHARGE              (1 << 22)
#define STATUS2_RAGE                  (1 << 23)
#define STATUS2_SUBSTITUTE            (1 << 24)
#define STATUS2_DESTINY_BOND          (1 << 25)
#define STATUS2_ESCAPE_PREVENTION     (1 << 26)
#define STATUS2_NIGHTMARE             (1 << 27)
#define STATUS2_CURSED                (1 << 28)
#define STATUS2_FORESIGHT             (1 << 29)
#define STATUS2_DEFENSE_CURL          (1 << 30)
#define STATUS2_TORMENT               (1 << 31)


Entonces, aunque :
Los estados principal y secundario de un pokémon se comprobá con "mask" números.
(que se compraban con "bitwise_and" entre el estado y la referencia "mask".)

Pues, se puede combinar
Código:
    if_status2 AI_USER, STATUS2_WRAPPED, AI_Roaming_End
y
Código:
    if_status2 AI_USER, STATUS2_ESCAPE_PREVENTION, AI_Roaming_End
en un equivalente
Código:
    if_status2 AI_USER, STATUS2_WRAPPED_o_ESCAPE_PREVENTION, AI_Roaming_End
para añadir una instrucción "if_status" (0x09) :
Código:
     if_status AI_USER, STATUS_SLEEP_o_FROZEN, AI_Roaming_End
Y, haciendo calculaciones :
estar "durmiendo o congelado" es 0x27 (binario 100111)
y "WRAPPED o ESCAPE_PREVENTION" es 0x 04 00 E0 00 (binario 100 0000 0000 1110 0000 0000 0000)

Se nota que estos se entran, como secundario parámetros de if_status/if_status2,
en orden reverso de bytes y, entonces, nuestra modificación :
Código:
     if_status AI_USER, STATUS_SLEEP_o_FROZEN, AI_Roaming_End
    if_status2 AI_USER, STATUS2_WRAPPED_o_ESCAPE_PREVENTION, AI_Roaming_End
se escrito, en hex (a dirección 2DE309) :
Código:
09 01 27 00 00 00 36 E3 2D 08
0B 01 00 E0 00 04 36 E3 2D 08


B) modificar el código asm de "HandleAction_Run"

Con la modificación de la IA_errante, el pokemon errante
no va a elegir de escapar cuando es durmiendo (o congelado) al principio del turno.
Pero, no le impide escapar si el fue adormecido (o congelado) durante el turno.

Entonces, también se necesita comprobar y modificar donde se impide estos tipos de escapa.
(i.e. pokémon ha elegido de escapar pero es impedido
por la habilidad del enemigo y/o su estado que han cambiados desde la elección.)


...

Y el código que hace esto es el código a dirección 3EE48,
nominado "HandleAction_Run" en el emerald decompilation project.

Este código es un poco complicado, pero, de verdad,
solo os necesitan cambiar su parte de termino.

Entonces, vamos a cambiar el código desde 3ef2A, hasta 3efa7 (incluido).​

Para seguridad, aquí es el hexadecimal original para borrar el cambio, si necesiten :
Código:
04 49 03 20 48 71 03 49 04 48 08 60 04 49 0A 20 2C E0 32 43 02 02 14 42 02 02 02 AB 2D 08 83 40 02 02 09 49 22 78 58 20 50 43 50 31 40 18 00 68 07 49 08 40 00 28 14 D0 06 49 04 20 48 71 05 49 06 48 08 60 06 49 0A 20 10 E0 84 40 02 02 00 E0 00 04 32 43 02 02 14 42 02 02 02 AB 2D 08 83 40 02 02 04 48 00 78 28 70 04 49 06 20 08 70 70 BC 01 BC 00 47 00 00 6C 40 02 02 3A 43 02 02

Código:
mov    r0, #0x3 // instrucción (2 bytes) que no se debe copiar del hex compilado
//esta añadida para que los ".align    2, 0" corresponden
//(3ef28 es como 0x0 : un múltiple de 0x4, cuando 3ef2A no esta)

    ldr    r1, .L500
    mov    r0, #0x3

    strb    r0, [r1, #0x5]
.LM235:

    ldr    r1, .L501
    ldr    r0, .L502
    str    r0, [r1]
.LM236:
.L700:
    ldr    r1, .L503
    mov    r0, #0xa
.LM237:

    b    .L217
.L211:
.LM238:
-------------------------------------
    ldr    r1, .L224 // carga la dirección de la RAM dedicada por los pokémones en batalle
    ldrb    r2, [r4] // r2 es el indice del pokémon
    mov    r0, #0x58 // tamaño de ram utilizado por cada pkmn
    mul    r0, r0, r2 // bytes que añadir para estar al principio de la RAM por el pokémon
    add    r1, r1, #0x4c // offset para el principio del información de estado principal
    add    r0, r0, r1 // r0 devenía la dirección del estado principal del pokémon
    ldr    r0, [r0] // carga el estado actual en r0
    mov r1, #0x27 // define "durmiendo o congelado" en r1
    and    r0, r0, r1 // comprobá el estado contra "durmiendo o congelado" y poné el en r0
    cmp    r0, #0
    bne    .LM239    @cond_branch // si durmiendo o congelado, va a la parte que impide escapar
-------------------------------------
    ldr    r1, .L224
    mov    r0, #0x58
    mul    r0, r0, r2
    add    r1, r1, #0x50
    add    r0, r0, r1
    ldr    r0, [r0]
    ldr    r1, .L224+0x4
.L600:
    and    r0, r0, r1
    cmp    r0, #0
    beq    .L214    @cond_branch
.LM239:  // parte que impide escapar y define el mensaje que mostrar

    ldr    r1, .L224+0x8
    mov    r0, #0x4

    strb    r0, [r1, #0x5]
    ldr    r1, .L501
    ldr    r0, .L502
    str    r0, [r1]

    b    .L700
.L225:
    .align    2, 0
.L224:
    .word    0x02024084 :: gBattleMons
    .word    0x0400e000 :: mask de estado_secundario WRAPPED_o_ESCAPE_PREVENTION
.L500:
    .word    0x02024332 :: gBattleCommunication
.L501:
    .word    0x02024214 :: gBattlescriptCurrInstr
.L502:
    .word    0x082dab02 :: BattleScript_PrintFailedToRunString
.L503:
    .word    0x02024083 :: gCurrentActionFuncId


.L214: // código para que el pokémon escapa
.LM243:

    ldr    r0, .L226
    ldrb    r0, [r0]
    strb    r0, [r5]
.LM244:

    ldr    r1, .L226+0x4
    mov    r0, #0x6
.L217:
    strb    r0, [r1]
.L210:
.LM245:

    pop    {r4, r5, r6}
    pop    {r0}
    bx    r0
.L227:
    .align    2, 0
.L226:
    .word    0x0202406c :: gBattlersCount
    .word    0x0202633a :: gBattleOutcome
.LFE5:

Lo que hice es, quitar a ".word"s que aparecían en doble en el código original,
para añadir la parte entre "-" lineas y cambiar/comprimir un poco el resto del código,
para que hace la misma cosa que antes,
solo también con la verificación añadida del estado principal.


Finalmente, el hexadecimal compilado de la modificación
a copiar a dirección 3ef2A
(hasta 3efa7) es :
Código:
14 49 03 20 48 71 13 49 14 48 08 60 14 49 0A 20 2C E0 0D 49 22 78 58 20 50 43 4C 31 40 18 00 68 27 21 08 40 00 28 09 D1 08 49 58 20 50 43 50 31 40 18 00 68 06 49 08 40 00 28 12 D0 05 49 04 20 48 71 04 49 05 48 08 60 E0 E7 84 40 02 02 00 E0 00 04 32 43 02 02 14 42 02 02 02 AB 2D 08 83 40 02 02 04 48 00 78 28 70 04 49 06 20 08 70 70 BC 01 BC 00 47 00 00 6C 40 02 02 3A 63 02 02

Utiliza a este tutorial y modificaciones como le quiere.

Si necesite clarificación, no hesita preguntar.
 

Micael_Alighieri

Emperador Kaktiácero
Redactor/a
Miembro de honor
Has traído algo muy bueno a una modalidad de ROM Hacking que todavía goza de buena salud, y con explicaciones al detalle, estudiando las rutinas de decompilación, has hecho un magnífico trabajo de estudio.

Eso sí, si ya quisieras dejarlo con nota de sobresaliente en cuanto a la forma del texto, avísame, podemos compartir algunos consejos muy útiles.

¡Gran aporte!
 
Arriba