Buenos días, he creado este tema con el objetivo de dejar anotaciones útiles para mí y para otras personas para el desarrollo en Pokecristal, y por extensión, en Pokecrystal. Más adelante, si el tema crece y organizo las anotaciones por bloques, crearé nuevos temas para organizar los contenidos. He de comentar que Github cuenta también con una wiki para estos contenidos, pero me parece igualmente útil traer este tipo de información a nuestra propia plataforma de foros.
Estas anotaciones son compatibles con Pokecristal, versión 1.0-LTS.
Ubicación de efectos de sonido
Archivo "/constants/sfx_constants.asm"
Listado de comandos de scripting
playsound
Descripción: Sirve para reproducir un sonido de la lista de efectos de sonido (véase "Ubicación de efectos de sonido")
Estructura: playsound [NOMBRE_DEL_SONIDO]
Ejemplo: playsound SFX_STRENGTH
Recreación de comandos de scripting en ASM
playsound
Estructura:
Ejemplo:
PlayMusic
Estructura:
Ejemplo:
Giveitem
Este comando es muy especial debido a la naturaleza de los objetos en los juegos principales de Pokémon a partir de la Segunda Generación, y por ese motivo, recrearlo no es una tarea fácil.
En este caso, cada bolsillo de la mochila incluye dos tipos de direcciones de memoria, uno que indica cuántos lotes de objetos tenemos en ese bolsillo, y otro que indica qué objeto hay en cada lote y su cantidad. A continuación, os indico cuáles son estas direcciones.
Bolsillo "Objetos"
[ID_HEXADECIMAL_DEL_OBJETO][CANTIDAD_EN_HEXADECIMAL][...][TERMINADOR]
Sabiendo esto, podemos manipular la memoria del juego para cargar los objetos que queramos, teniendo siempre cuidado de no sobrepasar el límite de objetos del bolsillo. Por ejemplo, en este ejemplo caso, voy a hacer que la mochila sólo tenga un único objeto en el Bolsillo "Objetos", que dicho objeto sea una Superpoción y que esté en un lote de 32 unidades (en otras palabras, meter 32 Súperpociones).
En este caso, el valor en hexadecimal de la Súperpoción sería 11, la cantidad sería 32 (valor hexadecimal 20), y el total de lotes de objetos que tendrá la mochila será sólo 1, así que usaría la siguiente rutina ASM:
Hay otro detalle importante, el bolsillo de las MTs y MOs funciona de forma distinta, puesto que al poder contenerlas todas, no necesita una dirección en la que guardar el número de objetos de la bolsa, tan sólo debemos indicarle la cantidad en la posición que le corresponda:
Bolsillo "MTs/MOs"
Si bien ya existen otras rutinas dentro del juego y podemos reutilizarlas o adaptarlas, poder conocer sobre estos punteros y cómo funcionan nos permite obtener un control absoluto de toda la mochila.
PlayMonCry
Estructura:
Ejemplo:
Curiosidades muy útiles en ASM
Cómo cambiar el día, la hora y los minutos del Pokégear
Estructura:
Ejemplo:
Cómo hardcodear nombres de protagonistas
Muy útil cuando alguien juega y queremos fijar los detalles de su personaje, podemos utilizar la siguiente rutina, por ejemplo:
Cómo agregar pausas
Con este par de líneas, podemos agregar pausas en la pantalla durante la ejecución de rutinas, algo muy útil y estético. En este caso, se indica el número de fotogramas que se debe esperar, y la unidad de referencia son 60 fotogramas, lo que equivale a 1 segundo.
En el siguiente ejemplo, implementamos una pausa de 2 segundos:
Cómo marcar Pokémon como vistos o capturados
La información con los Pokémon vistos y atrapados se localiza en las direcciones etiquetadas como wPokedexSeen y wPokedexCaught, respectivamente. Cada bit define si el Pokémon está marcado como visto o capturado, dichos bits se agrupan en grupos de 8, por lo que podremos cambiar los primeros 8 Pokémon de la Pokédex por defecto. Por ejemplo, si queremos marcar los 8 primeros, debemos asignar el valor binario 0x11111111, o en hexadecimal, 0xFF, podemos usar este bloque de código, en el que alojamos la dirección en el registro hl y cargamos estos valores:
En cambio, si queremos continuar asignando valores, debemos avanzar el registro hasta los siguientes 8 bits y repetir:
Para facilitar un poco la labor de asignación, también podemos usar un bucle. Por ejemplo, en el siguiente bloque de código, marcamos todos los Pokémon de la Pokédex como vistos:
Rutinas ASM a investigar más adelante
ResetGameTime
NamePlayer (engine/menus/intro_menu.asm)
Punteros ASM a investigar más adelante
Localizado en ram/wram.asm:
Listado de valores de datos de punteros en ASM
PokéGear
Lo controla el puntero wPhoneList, permite hasta un máximo de 9 números de teléfono por pantalla, pero internamente hay espacio para más. A continuación, os detallo los valores de cada contacto de teléfono:
Técnicamente podríamos extendernos más allá de los valores de esta tabla, pero por defecto encontraremos contactos basura (algunos incluso pueden tildar el juego o forzar un reinicio en modo GB).
Otras anotaciones en ASM
ram/wram.asm
Contiene múltiples punteros a direcciones de memoria, tales como los de la Pokédex, los del Pokégear, etc.
engine/menus/intro_menu.asm
Este archivo contiene rutinas que cargan la intro, desde la portada hasta el Nuevo Juego. Hay algunos bloques de código interesantes.
Por ejemplo, este bloque de código se ejecuta durante la pantalla de Nuevo Juego y es el responsable de la carga del sprite del Profesor Oak en memoria, y posteriormente, de mostrarlo por pantalla.
Este otro bloque deja un efecto de transición precioso entre una pantalla negra y una pantalla blanca durante dicha intro:
Este otro pinta a un Pokémon:
Y este os encantará, se utiliza para pintar en pantalla el sprite del protagonista al inicio. ¿Por qué se pintó de esta manera? Pues parece que el bloque de más arriba sólo pinta entrenadores o Pokémon, y el nuevo sprite del protagonista no está marcado como entrenador:
Cómo desarrollar scripts de gatillo
En Pokecristal, los scripts de gatillo se conocen como "escenas" (scene), su activación depende de un puntero en memoria y se pueden activar y desactivar a voluntad. Además, al pisar sobre ellos, no notaremos ningún tipo de lag, ni demora dentro del juego.
Pero eso sí, debemos declarar el puntero en el archivo "ram/wram.asm" o reciclar uno en desuso, después asociarle un nombre de escena en el archivo "data/maps/scenes.asm", y finalmente, declararlo dentro del archivo de scripts (.ASM ubicado en el directorio "maps"). En sí, existen varios tipos de escenas, pero el más sencillo de utilizar es este:
Más abajo, debemos indicar también el nombre clave de nuestra escena, y junto a él, las coordenadas dentro del mapa y el script de gatillo que activará, de esta forma:
Por otro lado, algunos mapas carecen de puntero por defecto, así que no os olvidéis de incluir el nombre del mapa y su puntero en el archivo "constants/map_constants.ASM".
Aparentemente, el proceso parece tedioso, pero debido a que el juego cuenta con muchas flags sin utilizar, podemos configurar todas las que necesitemos, y así, reutilizar el mismo registro para todas nuestras escenas. Recordad, además, que para declarar nuevas flags, debéis dirigiros al archivo "constants/event_flag.ASM" (he contado más de 200, así que tenéis hasta aburrir).
Estas anotaciones son compatibles con Pokecristal, versión 1.0-LTS.
Ubicación de efectos de sonido
Archivo "/constants/sfx_constants.asm"
Listado de comandos de scripting
playsound
Descripción: Sirve para reproducir un sonido de la lista de efectos de sonido (véase "Ubicación de efectos de sonido")
Estructura: playsound [NOMBRE_DEL_SONIDO]
Ejemplo: playsound SFX_STRENGTH
Recreación de comandos de scripting en ASM
playsound
Estructura:
Código:
push de
ld de, [NOMBRE_DEL_SONIDO]
call PlaySFX
pop de
Código:
push de
ld de, SFX_STRENGTH
call PlaySFX
pop de
Estructura:
Código:
ld de, [NOMBRE_MUSICA]
call PlayMusic
Código:
ld de, MUSIC_ROUTE_30
call PlayMusic
Este comando es muy especial debido a la naturaleza de los objetos en los juegos principales de Pokémon a partir de la Segunda Generación, y por ese motivo, recrearlo no es una tarea fácil.
En este caso, cada bolsillo de la mochila incluye dos tipos de direcciones de memoria, uno que indica cuántos lotes de objetos tenemos en ese bolsillo, y otro que indica qué objeto hay en cada lote y su cantidad. A continuación, os indico cuáles son estas direcciones.
Bolsillo "Objetos"
- wNumItems
- wItems
- wNumKeyItems
- wKeyItems
- wNumBalls
- wBalls
[ID_HEXADECIMAL_DEL_OBJETO][CANTIDAD_EN_HEXADECIMAL][...][TERMINADOR]
Sabiendo esto, podemos manipular la memoria del juego para cargar los objetos que queramos, teniendo siempre cuidado de no sobrepasar el límite de objetos del bolsillo. Por ejemplo, en este ejemplo caso, voy a hacer que la mochila sólo tenga un único objeto en el Bolsillo "Objetos", que dicho objeto sea una Superpoción y que esté en un lote de 32 unidades (en otras palabras, meter 32 Súperpociones).
En este caso, el valor en hexadecimal de la Súperpoción sería 11, la cantidad sería 32 (valor hexadecimal 20), y el total de lotes de objetos que tendrá la mochila será sólo 1, así que usaría la siguiente rutina ASM:
Código:
; Fijo el total de objetos del bolsillo
ld a, $1
ld [wNumItems], a
; Fijo el lote (objeto y cantidad)
ld a, $11
ld [wItems], a
ld a, $20
ld [wItems+1], a
Bolsillo "MTs/MOs"
- wTMsHMs
Código:
; Fijo el total de objetos del bolsillo
ld a, $5
ld [wTMsHMs], a
ld a, $6
ld [wTMsHMs+1], a
ld a, $ff
ld [wTMsHMs+2], a
PlayMonCry
Estructura:
Código:
ld a, [NOMBRE_DEL_POKÉMON]
call PlayMonCry
call WaitSFX
Código:
ld a, WOOPER
call PlayMonCry
call WaitSFX
Cómo cambiar el día, la hora y los minutos del Pokégear
Estructura:
Código:
; Ajustar hora y minutos del Pokégear
ld a, [HORA]
ld [wInitHourBuffer], a
ld a, [wInitHourBuffer]
ld [wStringBuffer2 + 1], a
ld a, [MINUTOS]
ld [wInitMinuteBuffer], a
ld a, [wInitMinuteBuffer]
ld [wStringBuffer2 + 2], a
call InitTimeOfDay
; Ajustar día del Pokégear
ld a, [DÍA]
ld [wTempDayOfWeek], a
ld [wStringBuffer2], a
call InitDayOfWeek
Código:
; Ajustar hora y minutos del Pokégear
ld a, 14 ; hora elegida = 2 PM
ld [wInitHourBuffer], a
ld a, [wInitHourBuffer]
ld [wStringBuffer2 + 1], a
ld a, 45 ; minutos elegidos = 45 minutos
ld [wInitMinuteBuffer], a
ld a, [wInitMinuteBuffer]
ld [wStringBuffer2 + 2], a
call InitTimeOfDay
; Ajustar día del Pokégear
ld a, MONDAY ; día elegido: Lunes
ld [wTempDayOfWeek], a
ld [wStringBuffer2], a
call InitDayOfWeek
Muy útil cuando alguien juega y queremos fijar los detalles de su personaje, podemos utilizar la siguiente rutina, por ejemplo:
Código:
; Sets a fixed name for the protagonist
SetFixedName:
ld a, '@'
ld bc, NAME_LENGTH
ld hl, wPlayerName
call ByteFill
ld hl, wPlayerName
ld de, .ProtagonistName
call CopyName2
farcall ApplyMonOrTrainerPals
ret
.ProtagonistName:
dname "PROTA", NAME_LENGTH
Con este par de líneas, podemos agregar pausas en la pantalla durante la ejecución de rutinas, algo muy útil y estético. En este caso, se indica el número de fotogramas que se debe esperar, y la unidad de referencia son 60 fotogramas, lo que equivale a 1 segundo.
En el siguiente ejemplo, implementamos una pausa de 2 segundos:
Código:
ld c, 120
call DelayFrames
La información con los Pokémon vistos y atrapados se localiza en las direcciones etiquetadas como wPokedexSeen y wPokedexCaught, respectivamente. Cada bit define si el Pokémon está marcado como visto o capturado, dichos bits se agrupan en grupos de 8, por lo que podremos cambiar los primeros 8 Pokémon de la Pokédex por defecto. Por ejemplo, si queremos marcar los 8 primeros, debemos asignar el valor binario 0x11111111, o en hexadecimal, 0xFF, podemos usar este bloque de código, en el que alojamos la dirección en el registro hl y cargamos estos valores:
Código:
ld hl, wPokedexSeen
ld a, $ff
ld [hl], a
Código:
...
inc hl
ld a, $ff
ld [hl], a
Código:
MarcarTodosLosPokemonComoVistos:
ld hl, wPokedexSeen
; Cubre a los primeros 248 Pokémon
; (0x1F * 0x8, since the seen Pokemon are distributed in 8-bit groups)
ld bc, $1F
ld a, $FF
.loop:
ld [hl], a
inc hl
dec c
ld a, a ; just for not losing a (optional)
jr nz, .loop
jr z, .addFinalMons
; Cubre al resto (Ho-Oh, Lugia y Celebi)
.addFinalMons
ld a, $7
ld [hl], a
ret
ResetGameTime
NamePlayer (engine/menus/intro_menu.asm)
Punteros ASM a investigar más adelante
Localizado en ram/wram.asm:
Código:
SECTION "Party", WRAMX
wPokemonData::
wPartyCount:: db
wPartySpecies:: ds PARTY_LENGTH
wPartyEnd:: db ; older code doesn't check wPartyCount
wPartyMons::
; wPartyMon1 - wPartyMon6
for n, 1, PARTY_LENGTH + 1
wPartyMon{d:n}:: party_struct wPartyMon{d:n}
endr
wPartyMonOTs::
; wPartyMon1OT - wPartyMon6OT
for n, 1, PARTY_LENGTH + 1
wPartyMon{d:n}OT:: ds NAME_LENGTH
endr
wPartyMonNicknames::
; wPartyMon1Nickname - wPartyMon6Nickname
for n, 1, PARTY_LENGTH + 1
wPartyMon{d:n}Nickname:: ds MON_NAME_LENGTH
endr
wPartyMonNicknamesEnd::
ds 22
PokéGear
Lo controla el puntero wPhoneList, permite hasta un máximo de 9 números de teléfono por pantalla, pero internamente hay espacio para más. A continuación, os detallo los valores de cada contacto de teléfono:
00 -> Vacío
01 -> MAMÁ
- Hay que hacer modificaciones extra dentro de otros datos del juego, puesto que la madre pregunta según nuestro progreso
02 -> TIENDA DE BICIS
03 -> BILL
04 -> PROF. ELM
05 -> AITOR: COLEGIAL
06 -> JULIA: POKÉFAN
07 -> KEN: MARINERO
08 -> NADA
09 -> NADA
0A -> NADA
0B -> GALO: ENTRE. GUAY
0C -> TINA: ENTRE. GUAY
0D -> JOSÉ: ORNITÓLOGO
0E -> TARA: ENTRE. GUAY
0F -> CHANO: JOVEN
10 -> TOMO: CAZABICHOS
11 -> JOSERRA: PESCADOR
12 -> LISI: DOMINGUERA
13 -> LUIS: MONTAÑERO
14 -> GORKA: CAMPISTA
15 -> GINA: DOMINGUERA
16 -> RAFI: MALABARISTA
17 -> ÁNGEL1: CAZABICHOS
18 -> ARTURO: COLEGIAL
19 -> NADA
1A -> SARA: CHICA
1B -> JAVIER: COLEGIAL
1C -> JONAY: POKÉFAN
1D -> ÓSCAR: PESCADOR
1E -> TONI: POKÉMANÍACO
1F -> VERA: DOMINGUERA
20 -> VIDAL: ORNITÓLOGO
21 -> DAVID: PESCADOR
22 -> LEUCO: KARATEKA
23 -> ÁNGEL2: MONTAÑERO
24 -> ELISA: DOMINGUERA
25 -> BUENA: DJ
01 -> MAMÁ
- Hay que hacer modificaciones extra dentro de otros datos del juego, puesto que la madre pregunta según nuestro progreso
02 -> TIENDA DE BICIS
03 -> BILL
04 -> PROF. ELM
05 -> AITOR: COLEGIAL
06 -> JULIA: POKÉFAN
07 -> KEN: MARINERO
08 -> NADA
09 -> NADA
0A -> NADA
0B -> GALO: ENTRE. GUAY
0C -> TINA: ENTRE. GUAY
0D -> JOSÉ: ORNITÓLOGO
0E -> TARA: ENTRE. GUAY
0F -> CHANO: JOVEN
10 -> TOMO: CAZABICHOS
11 -> JOSERRA: PESCADOR
12 -> LISI: DOMINGUERA
13 -> LUIS: MONTAÑERO
14 -> GORKA: CAMPISTA
15 -> GINA: DOMINGUERA
16 -> RAFI: MALABARISTA
17 -> ÁNGEL1: CAZABICHOS
18 -> ARTURO: COLEGIAL
19 -> NADA
1A -> SARA: CHICA
1B -> JAVIER: COLEGIAL
1C -> JONAY: POKÉFAN
1D -> ÓSCAR: PESCADOR
1E -> TONI: POKÉMANÍACO
1F -> VERA: DOMINGUERA
20 -> VIDAL: ORNITÓLOGO
21 -> DAVID: PESCADOR
22 -> LEUCO: KARATEKA
23 -> ÁNGEL2: MONTAÑERO
24 -> ELISA: DOMINGUERA
25 -> BUENA: DJ
Técnicamente podríamos extendernos más allá de los valores de esta tabla, pero por defecto encontraremos contactos basura (algunos incluso pueden tildar el juego o forzar un reinicio en modo GB).
Otras anotaciones en ASM
ram/wram.asm
Contiene múltiples punteros a direcciones de memoria, tales como los de la Pokédex, los del Pokégear, etc.
engine/menus/intro_menu.asm
Este archivo contiene rutinas que cargan la intro, desde la portada hasta el Nuevo Juego. Hay algunos bloques de código interesantes.
Por ejemplo, este bloque de código se ejecuta durante la pantalla de Nuevo Juego y es el responsable de la carga del sprite del Profesor Oak en memoria, y posteriormente, de mostrarlo por pantalla.
Código:
; Carga al Profesor Oak en memoria
ld [wCurPartySpecies], a
ld a, POKEMON_PROF
ld [wTrainerClass], a
; Prepara el sprite del Profesor Oak
call Intro_PrepTrainerPic
ld b, SCGB_TRAINER_OR_MON_FRONTPIC_PALS
; Muestra el sprite del Profesor Oak
call GetSGBLayout
call Intro_RotatePalettesLeftFrontpic
Este otro bloque deja un efecto de transición precioso entre una pantalla negra y una pantalla blanca durante dicha intro:
Código:
call RotateFourPalettesLeft
call ClearTilemap
call RotateFourPalettesRight
call RotateThreePalettesRight
; Querrás usar esta línea si no agregaste nada más abajo, o la pantalla se quedará blanca
call Intro_RotatePalettesLeftFrontpic
Código:
ld a, WOOPER
ld [wCurSpecies], a
ld [wCurPartySpecies], a
call GetBaseData
hlcoord 6, 4
call PrepMonFrontpic
xor a
ld [wTempMonDVs], a
ld [wTempMonDVs + 1], a
ld b, SCGB_TRAINER_OR_MON_FRONTPIC_PALS
call GetSGBLayout
call Intro_WipeInFrontpic
Código:
ld [wCurPartySpecies], a
farcall DrawIntroPlayerPic
ld b, SCGB_TRAINER_OR_MON_FRONTPIC_PALS
call GetSGBLayout
call Intro_RotatePalettesLeftFrontpic
En Pokecristal, los scripts de gatillo se conocen como "escenas" (scene), su activación depende de un puntero en memoria y se pueden activar y desactivar a voluntad. Además, al pisar sobre ellos, no notaremos ningún tipo de lag, ni demora dentro del juego.
Pero eso sí, debemos declarar el puntero en el archivo "ram/wram.asm" o reciclar uno en desuso, después asociarle un nombre de escena en el archivo "data/maps/scenes.asm", y finalmente, declararlo dentro del archivo de scripts (.ASM ubicado en el directorio "maps"). En sí, existen varios tipos de escenas, pero el más sencillo de utilizar es este:
Código:
scene_const SCENE_NOMBRE_DE_TU_ESCENA
Código:
def_coord_events
coord_event coordenada_X_en_decimal, coordenada_Y_en_decimal, SCENE_NOMBRE_DE_TU_ESCENA, Mapa_NombreDeTuScript
Aparentemente, el proceso parece tedioso, pero debido a que el juego cuenta con muchas flags sin utilizar, podemos configurar todas las que necesitemos, y así, reutilizar el mismo registro para todas nuestras escenas. Recordad, además, que para declarar nuevas flags, debéis dirigiros al archivo "constants/event_flag.ASM" (he contado más de 200, así que tenéis hasta aburrir).
Última edición: