06-10-2022, 09:28 PM
(Questo messaggio è stato modificato l'ultima volta il: 06-10-2022, 10:46 PM da 80C.)
Ulteriori informazioni e prerequisiti:
La terminologia in uso suppone la fluenza nell'ASM e nello scripting avanzato oltre che la conoscenza dettagliata della Repository di Pokemon R/B/Y.
Come suggerito tempo fa da @Toni88, ho deciso di redarre una guida sullo Scripting Gen.1 con la quale è possibile far effettuare un'operazione all'apparenza semplice ma in realtà complessa: far muovere autonomamente personaggi e player tramite script. Molto meglio che stare a piagnucolare sulla negatività generale di oggi e sul non voler ammettere il mio stesso stress anziché scaricarlo su guerre nucleari e sulle povere altre persone che certamente non mi hanno torto un capello... ftw, sono umano.
Esattamente come per ogni meccanica interna in Gen1, essa ha bisogno di essere messa in moto tramite script interno tramite ASM.
Vedremo subito come procedere per casi:
Muovere il Player autonomamente
L'idea di fondo dietro queste meccaniche è quella di avere una routine ASM a sè stante che possa essere ri-chiamata più volte all'interno di diverse mappe collocate nello stesso Bank della ROM.
Per renderla utile, basterà caricare in un registro (vedremo presto) un pointer contenente i dati di movimento che verranno interpretati automaticamente dalla nostra routine.
Ciò torna utilissimo, in quanto, nei giochi originali questi script di movimento si utilizzano poco spesso, e quindi la routine intera viene ri-scritta OGNI volta in tutte le mappe in cui viene utilizzata - salvo eccezioni: l'ASM che muove gli allenatori verso il player in linea retta, o il movimento automatico quando il player esce dalla porta, etc.
La routine in questione va dunque scritta da zero, e va assegnato un nome per poterla indicizzare nella Repository.
La scelta sta a voi: o la ri-scrivete in tutti i bank in cui si sono le mappe in cui servono una sola volta per Bank o, meglio ancora, trovate una maniera di trasformare questa funzione in un Comando Predef aggiuntivo - o addirittura in una funzione messa in modo tramite Vettore RST, se avete spazio nel Bank00.
Per "tradizione" durante lo sviluppo di Grape, la routine di movimento automatico del Player la chiamai " ForceMovePlayerHS " ("HS" sta per "Hot Spring", la prima mappa in cui servì questa nuova routine).
Essa è stata ri-adattata a partire da quella originale in uso nel gioco proprio per essere multi-compatibile con diversi input di movimento anziché uno solo.
Input: un pointer caricato nei registri de, il pointer riferito ad una lista in formato binario che contiene dati utilizzabili per i movimenti.
Notare che quella stringa dall'originale è stata de-commentata dalla routine con la " ; ".
La routine va in loop e muove costantemente il giocatore fino a che la lista non è stata terminata dal byte 0xff.
Ecco il formato per la lista di movimenti del player, in formato "criptato" RLE:
I nuovi modelli di repository non hanno più bisogno di specificare manualmente il byte esadecimale per la direzione del passo: sono stati sostituiti negli ultimi anni da Macro dedicate apposta.
(Vedere il file della Macro nella disassembly per rendersene conto).
In ogni caso, per convenienza, ecco la tabella di equivalenza dei movimenti e direzioni (esadecimale).
ATTENZIONE: i movimenti degli NPC utilizzano costanti (e Macro nelle versioni recenti) differenti da quelle in uso per i movimenti automatizzati del Player -- Dio solo sa il perché GF ha fatto così (verosimilmente a causa di come sono gestite le sprite nella OAM e le flags singole degli NPC della mappa nella WRAM, e cioè in maniere diametralmente opposte e diverse tra di loro)
Vi capiteranno dunque esempi tipo questo nelle vecchie repository:
Questa è la teoria di base, ora vediamo come applicarla all'interno di uno script di mappa (in questo caso).
DA QUI IN POI SI SUPPONE CHE CONOSCIATE LO SCRIPTING GEN.1 E LA SUA TEORIA DI FONDO ABBASTANZA FLUENTEMENTE (QUINDI SCRIPT-MAPPA, CURSCRIPT, E VIA DICENDO).
Utilizziamo come semplice esempio lo Script-Mappa (cioè lo script in loop perpetuo che si aziona di continuo quando ci si trova in una mappa, non servono CurScript) del Porto di Hardat City, in cui il giocatore viene respinto se non ha il biglietto nave (perdonate i vecchi commenti e glosse in inglese maccheronico e da memo d'ufficio, questa ASM venne codificata/"compilata" con metodi altamente grossolani per i tempi odierni):
C'è anche un bell'esempio di impiego di Fumetto Emozioni.
In aggiunta, c'è anche il compare con la quale comparare la posizione del Player all'interno della mappa (ovvero la forma rudimentale ASM della "IF... THEN Sentence" in gergo, in questo caso la parte dell' "IF", per coloro che hanno presente C++).
Tuttavia, la parte che vi interessa in dettaglio è questa, la successiva:
ForceMovePlayerHS si suppone che vada già di per sè in loop e non dovrebbe creare problemi.
Tuttavia, in caso di mappe con CurScript multipli con attivazione in serie multipla (tipo Biancavilla) PIU' allenatori e NPC con evento contemporaneamente (scenario che non accade MAI nel gioco originale, perché non è complesso a tale livello), potrebbe eventualmente sorgere qualche intemperia minore (sembrebbe a causa dell'elevato numero di script che vanno a sovrascrivere le flag che gestiscono il blocco/sblocco del player).
Muovere un NPC qualsiasi della mappa autonomamente;
Per quest'operazione si usa la routine "base" posta nel Bank00 "MoveSprite" (non necessita di modifiche).
L'input di questa funzione è un pointer caricato nei registri de, pointer che corrisponde ad una lista di dati binari (esadecimali), formattate secondo un formato a sua volta diverso da quello in uso per i movimenti automatici player (tipiche cose derp da aspettarsi con Gen1).
In questo caso, bisogna specificare il "numero" e direzione di passi in un singolo byte, per poi terminare la lista con il byte 0xFF.
Voi però non dovrete preoccuparvi perché nelle Repository più recenti questo tipo di dati è stato indicizzato tramite Macro / Constant e non necessita più dell'imissione di dati binari/hex nella lista di movimenti automatici per i movimenti NPC.
Qui però incontriamo una difficoltà.
MoveSprite, per sua natura, fa sì che esso venga eseguito per conto suo mentre i CurScript o altri script in corso vengono utilizzati per conto proprio indipendentemente.
Questo vuol dire semplicemente che a causa di ciò ci saranno due routines in funzione contemporanea, generando potenziali errori. La causa di ciò: MoveSprite è una funzione posta nel Bank0,
Soluzione:
cambiare CurScript corrente dopo aver richiamato questa funzione e porre un semplicissimo "check" nel nuovo CurScript successivo in uso che attesti che l'NPC non è più in movimento e MoveSprite ha compiuto il suo dovere, in caso contrario loop fino a che ciò non ha avuto luogo.
Essendo già la routine contro-intuitiva nel suo utilizzo, nel gioco i programmatori hanno aggiunto una flag che indica che questo script di movimento NPC è in uso. Quando la funzione ha cessato il suo funzionamento, la flag viene resettata, e si possono evitare spiacevoli errori.
In genere, il gioco utilizza exploit del genere in casi di movimenti di NPC automatici -- e, in certi casi, il loop in questione fallisce, caso più infame: " Old Man Glitch ",.
Vediamo un esempio da un CurScript in uso:
Prima parte dello Script.
Caricati i movimenti dalla lista binaria.
Specificata quale NPC muovere, utilizzando come input il Numero interno dell'evento NPC (persona) della Mappa (vedasi: "Object Data" in questo tutorial per ulteriori informazioni).
Tale ID caricata in una flag HRAM appositamente dedicata allo scopo: ciò è indispensabile perché la HRAM è una delle pochissime unità di memoria in uso che si possono utilizzare durante le delicatissime fasi di vBlank e di manipolazione dell'OAM (Sprite Attribute Data). Pandocs per maggiori informazioni.
Funzione MoveSprite chiamata in azione.
Cambio CurScript.
Ora abbiamo la seconda parte in cui bisogna assolutamente assicurarsi che il nostro movimento agisca indisturbato senza che altri scipt in uso corrente vadano ad interferire.
La flag dedicata apposta per questo delicato check è il bit0 di 0xD730 (R/B, Yellow ha un indirizzo diverso), ma naturalmente nelle Repository nuove non dovrete preoccuparvi perché anche questo indirizzo è stato indicizzato come Macro / Constant.
Fin tanto che MoveSprite è in uso, lo script va in loop, e nessun problema.
In realtà un motivo ben preciso per questa scelta di avere MoveSprite "simultaneo" ad altri script c'è, ed è alquanto da complotto: il sistema caterpillar di Pokemon Yellow è stato concepito e reso possibile fin da Pokemon R/B, sebbene non sia in uso, ed è ereditato direttamente dall'idea originale di Capsule Monsters.
Correzione della direzione di sguardo degli NPC
Ora, a movimento concluso, vediamo però un'utilissima funzione, cioè la correzione della direzione in cui volge lo sguardo l'NPC a fine movimento:
In questo caso si tratta di una semplice sovrascrizione di flag in uso nella WRAM.
Ecco le costanti in uso riferite alla direzione dello sguardo nell'NPC (superflui a causa delle Macro / Constants nelle nuove Repo) -- ovviamente, a sua volta in un formato diverso da quello usato sia per i movimenti del player che diverso dai movimenti NPC...
Muovere NPC e Player simultaneamente (dilemma autentico di ingegneria informatica qui...)
Coming soon...
- How to Hack Gen1! - Guide Universali all'Hacking GB
- R\B Headers Advance - Guida Universale allo Scripting Gen1.
- 80C's Knowledge - ASM Lezione 1 / Introduzione all'ASM
La terminologia in uso suppone la fluenza nell'ASM e nello scripting avanzato oltre che la conoscenza dettagliata della Repository di Pokemon R/B/Y.
Come suggerito tempo fa da @Toni88, ho deciso di redarre una guida sullo Scripting Gen.1 con la quale è possibile far effettuare un'operazione all'apparenza semplice ma in realtà complessa: far muovere autonomamente personaggi e player tramite script. Molto meglio che stare a piagnucolare sulla negatività generale di oggi e sul non voler ammettere il mio stesso stress anziché scaricarlo su guerre nucleari e sulle povere altre persone che certamente non mi hanno torto un capello... ftw, sono umano.
Esattamente come per ogni meccanica interna in Gen1, essa ha bisogno di essere messa in moto tramite script interno tramite ASM.
Vedremo subito come procedere per casi:
- Muovere il Player autonomamente;
- Muovere un NPC qualsiasi della mappa autonomamente;
- Correzione della direzione di sguardo degli NPC;
- Muovere NPC e Player simultaneamente;

L'idea di fondo dietro queste meccaniche è quella di avere una routine ASM a sè stante che possa essere ri-chiamata più volte all'interno di diverse mappe collocate nello stesso Bank della ROM.
Per renderla utile, basterà caricare in un registro (vedremo presto) un pointer contenente i dati di movimento che verranno interpretati automaticamente dalla nostra routine.
Ciò torna utilissimo, in quanto, nei giochi originali questi script di movimento si utilizzano poco spesso, e quindi la routine intera viene ri-scritta OGNI volta in tutte le mappe in cui viene utilizzata - salvo eccezioni: l'ASM che muove gli allenatori verso il player in linea retta, o il movimento automatico quando il player esce dalla porta, etc.
La routine in questione va dunque scritta da zero, e va assegnato un nome per poterla indicizzare nella Repository.
La scelta sta a voi: o la ri-scrivete in tutti i bank in cui si sono le mappe in cui servono una sola volta per Bank o, meglio ancora, trovate una maniera di trasformare questa funzione in un Comando Predef aggiuntivo - o addirittura in una funzione messa in modo tramite Vettore RST, se avete spazio nel Bank00.
Per "tradizione" durante lo sviluppo di Grape, la routine di movimento automatico del Player la chiamai " ForceMovePlayerHS " ("HS" sta per "Hot Spring", la prima mappa in cui servì questa nuova routine).
Essa è stata ri-adattata a partire da quella originale in uso nel gioco proprio per essere multi-compatibile con diversi input di movimento anziché uno solo.
Input: un pointer caricato nei registri de, il pointer riferito ad una lista in formato binario che contiene dati utilizzabili per i movimenti.
Notare che quella stringa dall'originale è stata de-commentata dalla routine con la " ; ".
Codice:
ForceMovePlayerHS: ;
;INPUT: de = RLE List of movements that has been stored
ld a, $ff
ld [wJoypadForbiddenButtonsMask], a ; 0xcd6b
ld hl, $ccd3 ; wSimulatedJoypadStatesEnd
; ;ld de, RLEListXX ; list loaded previously
call DecodeRLEList ; 0x350c - RLE should be in de by now
dec a ; decreases value in a
ld [$cd38], a ; wSimulatedJoypadStatesIndex
call Func_3486 ; 0x3486 - sets flag of simulated NPC movement
;part 2
ld a, [$cd38] ; wSimulatedJoypadStatesIndex
and a ; cp a,00
ret nz ;
call Delay3 ; 0x3dd7
ld a,$00
ld [wJoypadForbiddenButtonsMask], a ; 0xcd6b
ret
Ecco il formato per la lista di movimenti del player, in formato "criptato" RLE:
Citazione:; 2 bytes per movimento/passo. Lista terminata dal byte FF.
; Si procede PER ORDINE INVERSO: l'ultima entrata di due bytes della lista corrisponde al primo passo, viceversa, la prima entrata all'ultimo passo.
MOVIMENTO (COSTANTE/MACRO), NUMERO DI PASSI (HEX)
I nuovi modelli di repository non hanno più bisogno di specificare manualmente il byte esadecimale per la direzione del passo: sono stati sostituiti negli ultimi anni da Macro dedicate apposta.
(Vedere il file della Macro nella disassembly per rendersene conto).
In ogni caso, per convenienza, ecco la tabella di equivalenza dei movimenti e direzioni (esadecimale).
ATTENZIONE: i movimenti degli NPC utilizzano costanti (e Macro nelle versioni recenti) differenti da quelle in uso per i movimenti automatizzati del Player -- Dio solo sa il perché GF ha fatto così (verosimilmente a causa di come sono gestite le sprite nella OAM e le flags singole degli NPC della mappa nella WRAM, e cioè in maniere diametralmente opposte e diverse tra di loro)
Codice:
$00 - RIGHT / DESTRA
$40 - LEFT / SINISTRA
$80 - DOWN / GIU'
$C0 - UP / SU
Vi capiteranno dunque esempi tipo questo nelle vecchie repository:
Codice:
RLEList02: ;
db $10, $01 ; right 1 step
db $80, $01 ; down 1 step
db $FF ; ender
Questa è la teoria di base, ora vediamo come applicarla all'interno di uno script di mappa (in questo caso).
DA QUI IN POI SI SUPPONE CHE CONOSCIATE LO SCRIPTING GEN.1 E LA SUA TEORIA DI FONDO ABBASTANZA FLUENTEMENTE (QUINDI SCRIPT-MAPPA, CURSCRIPT, E VIA DICENDO).
Utilizziamo come semplice esempio lo Script-Mappa (cioè lo script in loop perpetuo che si aziona di continuo quando ci si trova in una mappa, non servono CurScript) del Porto di Hardat City, in cui il giocatore viene respinto se non ha il biglietto nave (perdonate i vecchi commenti e glosse in inglese maccheronico e da memo d'ufficio, questa ASM venne codificata/"compilata" con metodi altamente grossolani per i tempi odierni):
Codice:
HardatPortScript: ;
call 3c3c ; autotextboxupdate
ld hl,PortCoords ; 7cfa
call ArePlayerCoordsInArray ; 0x34bf
ret nc ; ret if not
ld b,SS_TICKET ; 0x3f
call IsItemInBag ; 0x3493
ret nz
; emotion bubble
ld a,4
ld (cd4f),a
xor a
ld (cd50),a
ld a,4c
call Predef ; 0x3e6d
; Set Player's Facing Direction
; C1x9: facing direction (0: down, 4: up, 8: left, $c: right)
ld a, $08 ; player facing up
ld [$d528], a ; facing direction
; display some text
ld a,$04 ; NoTicketText ID
ld [$FF8C],a ; [$ff8c] = sprite ID or text ID
call DisplayTextID ; 2920 (0:2920) ;
In aggiunta, c'è anche il compare con la quale comparare la posizione del Player all'interno della mappa (ovvero la forma rudimentale ASM della "IF... THEN Sentence" in gergo, in questo caso la parte dell' "IF", per coloro che hanno presente C++).
Tuttavia, la parte che vi interessa in dettaglio è questa, la successiva:
Codice:
; do a movement cript
ld de,RLEList01 ; loads movements coords into de
call ForceMovePlayerHS ; 7ca6 - forces player moving
ret
Tuttavia, in caso di mappe con CurScript multipli con attivazione in serie multipla (tipo Biancavilla) PIU' allenatori e NPC con evento contemporaneamente (scenario che non accade MAI nel gioco originale, perché non è complesso a tale livello), potrebbe eventualmente sorgere qualche intemperia minore (sembrebbe a causa dell'elevato numero di script che vanno a sovrascrivere le flag che gestiscono il blocco/sblocco del player).
Spoiler (Clicca per visualizzare)

Per quest'operazione si usa la routine "base" posta nel Bank00 "MoveSprite" (non necessita di modifiche).
L'input di questa funzione è un pointer caricato nei registri de, pointer che corrisponde ad una lista di dati binari (esadecimali), formattate secondo un formato a sua volta diverso da quello in uso per i movimenti automatici player (tipiche cose derp da aspettarsi con Gen1).
In questo caso, bisogna specificare il "numero" e direzione di passi in un singolo byte, per poi terminare la lista con il byte 0xFF.
Voi però non dovrete preoccuparvi perché nelle Repository più recenti questo tipo di dati è stato indicizzato tramite Macro / Constant e non necessita più dell'imissione di dati binari/hex nella lista di movimenti automatici per i movimenti NPC.
Citazione:$c0 : RIGHT / DESTRA
(Gli altri non gli ho sottomano al momento, spiacente).
Qui però incontriamo una difficoltà.
MoveSprite, per sua natura, fa sì che esso venga eseguito per conto suo mentre i CurScript o altri script in corso vengono utilizzati per conto proprio indipendentemente.
Questo vuol dire semplicemente che a causa di ciò ci saranno due routines in funzione contemporanea, generando potenziali errori. La causa di ciò: MoveSprite è una funzione posta nel Bank0,
Spoiler (Clicca per visualizzare)
cambiare CurScript corrente dopo aver richiamato questa funzione e porre un semplicissimo "check" nel nuovo CurScript successivo in uso che attesti che l'NPC non è più in movimento e MoveSprite ha compiuto il suo dovere, in caso contrario loop fino a che ciò non ha avuto luogo.
Essendo già la routine contro-intuitiva nel suo utilizzo, nel gioco i programmatori hanno aggiunto una flag che indica che questo script di movimento NPC è in uso. Quando la funzione ha cessato il suo funzionamento, la flag viene resettata, e si possono evitare spiacevoli errori.
In genere, il gioco utilizza exploit del genere in casi di movimenti di NPC automatici -- e, in certi casi, il loop in questione fallisce, caso più infame: " Old Man Glitch ",
Spoiler (Clicca per visualizzare)
Vediamo un esempio da un CurScript in uso:
Codice:
; change the block of the last barrier, then
; move the old woman outside the counter
PCCScript6: ;
.MoveWoman
ld de, MovementData01 ; loads movement data for oldwoman
ld a, $07 ; ID ESADECIMALE DELL' NPC DELLA MAPPA + 1, IN QUESTO CASO 0x06 + 0x01
ld [$ff00+$8c],a ; H_SPRITEINDEX
call MoveSprite ; 0x363a
; trigger the next script
ld a,7
ld [PCCCURSCRIPT],a ; 0xdf27
ret
MovementData01: ;
db $c0,$c0,$c0,$FF ; right 3 steps
Caricati i movimenti dalla lista binaria.
Specificata quale NPC muovere, utilizzando come input il Numero interno dell'evento NPC (persona) della Mappa (vedasi: "Object Data" in questo tutorial per ulteriori informazioni).
Tale ID caricata in una flag HRAM appositamente dedicata allo scopo: ciò è indispensabile perché la HRAM è una delle pochissime unità di memoria in uso che si possono utilizzare durante le delicatissime fasi di vBlank e di manipolazione dell'OAM (Sprite Attribute Data). Pandocs per maggiori informazioni.
Funzione MoveSprite chiamata in azione.
Cambio CurScript.
Ora abbiamo la seconda parte in cui bisogna assolutamente assicurarsi che il nostro movimento agisca indisturbato senza che altri scipt in uso corrente vadano ad interferire.
La flag dedicata apposta per questo delicato check è il bit0 di 0xD730 (R/B, Yellow ha un indirizzo diverso), ma naturalmente nelle Repository nuove non dovrete preoccuparvi perché anche questo indirizzo è stato indicizzato come Macro / Constant.
Codice:
PCCScript7: ;
.CheckIfWomanIsMoving
ld hl,$d730
bit 0,(hl) ; bit 0 set: NPC sprite being moved by script
ret nz ; if so ret
In realtà un motivo ben preciso per questa scelta di avere MoveSprite "simultaneo" ad altri script c'è, ed è alquanto da complotto: il sistema caterpillar di Pokemon Yellow è stato concepito e reso possibile fin da Pokemon R/B, sebbene non sia in uso, ed è ereditato direttamente dall'idea originale di Capsule Monsters.
Spoiler (Clicca per visualizzare)

Ora, a movimento concluso, vediamo però un'utilissima funzione, cioè la correzione della direzione in cui volge lo sguardo l'NPC a fine movimento:
Codice:
.CorrectSpriteFacing
ld a,$04 ; sprite facing up
ld [$C109],a ; C1x9: facing direction (Player's sprite, ID 00)
ld [$C179],a ; (Old woman's sprite, in this case ID 06 + 01)... Ignorate questo commento, serviva solo a farmi capire quale flag corretta dovessi sovrascrivere dell'NPC a cui volevo correggere la direzione di sguardo senza sovrascrivere le flag di altri NPC.
; trigger the next script
; Ignorate quanto di seguito: è "solo" il seguito alla funzione CurScript successiva.
ld a,8
ld [PCCCURSCRIPT],a ;
ret
In questo caso si tratta di una semplice sovrascrizione di flag in uso nella WRAM.
Ecco le costanti in uso riferite alla direzione dello sguardo nell'NPC (superflui a causa delle Macro / Constants nelle nuove Repo) -- ovviamente, a sua volta in un formato diverso da quello usato sia per i movimenti del player che diverso dai movimenti NPC...

Citazione:C1x9: facing direction ($00: down, $04: up, $08: left, $0c: right)

Coming soon...
1°Posto Best Beta 2015 - PokemonHacking.it (Clicca per visualizzare)