Benvenuto Visitatore!  / Create an account







This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm whether you accept or reject these cookies being set.

A cookie will be stored in your browser regardless of choice to prevent you being asked this question again. You will be able to change your cookie settings at any time using the link in the footer.
Valutazione discussione:
  • 0 voto(i) - 0 media
  • 1
  • 2
  • 3
  • 4
  • 5
[Gb] Script Movimenti NPC e Player
#1
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;
  • Muovere un NPC qualsiasi della mappa autonomamente;
  • Correzione della direzione di sguardo degli NPC;
  • Muovere NPC e Player simultaneamente;


Pokéball 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 " ; ".
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
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:
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)        ;
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:
Codice:
; do a movement cript
    ld de,RLEList01            ; loads movements coords into de
    call ForceMovePlayerHS        ; 7ca6 - forces player moving
    ret
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).


Pokéball 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.
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,
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:
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
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.
Codice:
PCCScript7: ;
.CheckIfWomanIsMoving
    ld hl,$d730
    bit 0,(hl)        ; bit 0 set: NPC sprite being moved by script
    ret nz            ; if so ret
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.

Pokéball 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:

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... Angry
Citazione:C1x9: facing direction ($00: down, $04: up, $08: left, $0c: right)



Pokéball Muovere NPC e Player simultaneamente (dilemma autentico di ingegneria informatica qui...)

Coming soon...
 Mi Piace Ricevuti: 
Drk090, Flygon, Boh, Bonnox, IvanFGK like this post
Cita messaggio }
#2
Esiste una guida o comparto tecnico che possa far muovere in diagonale?
 Mi Piace Ricevuti: 
Cita messaggio }
#3
(06-10-2022, 11:48 PM)IvanFGK Ha scritto: Esiste una guida o comparto tecnico che possa far muovere in diagonale?

Ebbene, sì: il movimento diagonale è stato ottenuto per Pokemon Crystal, e può essere implementato esattamente come per le hacks di Terza generazione che utilizzano un sistema analogo.

In Pokémon R/B/Y dovrebbe dunque essere più che possibile, anche se il sistema di movimento e di calpestabile in Generazione1 è completamente diverso dal sistema in uso di Gen2 il quale invece prevede i bytes calpestabili a seconda del mini-blocco su cui poggia il giocatore.
Adesso ormai non ho davvero più tempo, anche se, dovesse avanzarmene, potrei lavorarci su.

Nel frattempo, quando avrò rimesso le mani sugli appunti giusti includerò in questa pagina la terza parte con i movimenti combinati simultanei per NPC e giocatore.
 Mi Piace Ricevuti: 
Bonnox likes this post
Cita messaggio }




Utenti che stanno guardando questa discussione: 1 Ospite(i)