
Attenzione: questo NON è un tutorial! E' una ricerca / teoria sullo sviluppo di una modifica sull'utilizzo delle TM / HM in Pokemon Red / Blue / Yellow.
Notare che, a livello puramente concettuale, molte delle riflessioni e proposte qui contenute sarebbero applicabili anche in Gen3, previo restante il fatto che ci si adopera di un'architettura ASM differente e di un hardware 16bit (il GBA) alquanto diverso dal Game Boy Color (il quale si adopera di una ASM z80 modificata). (Essendo un linguaggio macchina, è inevitabile che esso sia fortemente dipendente dalla macchina in questione).
Attenzione 2: Thread di ricerca ASM sperimentale, per Gen1 ed in parte Gen2. Pertanto incomprensibile alla maggior parte dei lettori.
Quanto è stato ottenuto fino ad ora:
Essendo una ricerca specialistica che include ASM (per giunta su Red / Blue / Yellow) non mi aspetto che tra qui a 10 anni ci siano molte persone che capiscano quanto stia scrivendo (a meno che non accada una qualche specie di miracolo).
Nota Bene: Le espansioni e le modifiche "sperimentali" che sono riuscito effettivamente a scritturare sono indicizzate, all'inizio del rispettivo paragrafo, da suddetta Icona:

I progetti sperimentali ed i sistemi rimasti a piano teorico sono indicizzati con l'icona:



Grazie all'ausilio dell'ASM e delle disassembly, oggi è diventato possibile e fattibile creare delle nuove espansioni di funzionalità in modo da rendere la gestione delle HM e delle mosse fuori dalla lotta completamente indipendente dai sistemi congegnati in R/B/Y.
Un primo esempio in tal senso venne raggiunto prima nel 2012 ad opera di Miksy91 ed in seguito nel 2020 circa dal team di sviluppo di Pokemon Coral:
- Rendere l'utilizzo delle Mosse fuori dalla lotta indipendente dall'aver conseguito medaglie -- ossia quello che avviene in Pokemon Dark Energy, dove per utilizzare le mosse MN basta aver preso possesso della HM corrispondente, e
- fare sì che certi Pokemon possano utilizzare mosse MN indipendentemente dalla conoscenza della mossa MN in questione (come avviene in parte in Pokemon Coral).
Fino ad oggi, queste modifiche hanno riguardato la ben più gestibile Seconda Generazione.
In questo documento, invece, proporrò un overhaul del sistema HM per i giochi di prima generazione ben più radicale di quello proposto fino ad oggi nei giochi Pokemon.
Non appena ne avrò le possibilità, vi fornirò estratti di codice di esempio ed elencherò le modifiche da aggiungere per poter realizzare questa "visione".
Ma procediamo con ordine-


In Gen.1 l'utilizzo delle HM è gestito principalmente da due routines: una serie di routines nel Bank04 che definiscono le mosse utilizzabili al di fuori della lotta -- StartMenu_Pokemon: ; 130a9 (4:70a9) .
Codice:
StartMenu_Pokemon: ; 130a9 (4:70a9)
; [...]
.choseOutOfBattleMove
push hl
ld a,[$cf92]
ld hl,W_PARTYMON1NAME
call GetPartyMonName
pop hl
ld a,[hl]
dec a
add a
ld b,0
ld c,a
ld hl,.outOfBattleMovePointers
add hl,bc
ld a,[hli]
ld h,[hl]
ld l,a
ld a,[W_OBTAINEDBADGES] ; badges obtained
jp [hl]
.outOfBattleMovePointers
dw .cut
dw .fly
dw .surf
dw .surf
dw .strength
dw .flash
dw .dig
dw .teleport
dw .softboiled
.fly
; [...] etc. etc.
Un altra importante serie di funzioni sono le routines di utilizzo delle HM e TM nel Bank3 (a loro volta interconnesse da una serie di routines ASM poste nel Bank0 che definiscono l'utilizzo delle HM, l'uso degli oggetti nello zaino, e così via).
Codice:
ItemUseTMHM: ; e479 (3:6479)
ld a,[W_ISINBATTLE]
and a
jp nz,ItemUseNotTime
ld a,[$cf91] ; ID temporaneo oggetto in uso
sub a,TM_01 ; ID - $c4
push af ; push
jr nc,.skipAdding
add a,55 ; if item is an HM, add 55
.skipAdding
inc a
ld [$d11e],a
ld a,$44
call Predef ; get move ID from TM/HM ID
ld a,[$d11e]
ld [$d0e0],a
call GetMoveName
call CopyStringToCF4B ; copy name to $cf4b
pop af
ld hl,BootedUpTMText
jr nc,.printBootedUpMachineText
ld hl,BootedUpHMText
.printBootedUpMachineText
call PrintText
ld hl,TeachMachineMoveText
call PrintText
FuncCoord 14,7
ld hl,Coord
ld bc,$080f
ld a,$14
ld [$d125],a
call DisplayTextBoxID ; yes/no menu
ld a,[wCurrentMenuItem]
and a
jr z,.useMachine
ld a,2
ld [$cd6a],a ; item not used
ret
.useMachine
ld a,[$cf92]
push af
ld a,[$cf91]
push af
.chooseMon
ld hl,$cf4b
ld de,$d036
ld bc,14
call CopyData
ld a,$ff
ld [$cfcb],a
ld a,$03 ; teach TM/HM party menu
ld [$d07d],a
call DisplayPartyMenu
push af
ld hl,$d036
ld de,$cf4b
ld bc,14
call CopyData
pop af
jr nc,.checkIfAbleToLearnMove
; if the player canceled teaching the move
pop af
pop af
call GBPalWhiteOutWithDelay3
call CleanLCD_OAM
call GoPAL_SET_CF1C
jp LoadScreenTilesFromBuffer1 ; restore saved screen
.checkIfAbleToLearnMove
ld a,$43
call Predef ; check if the pokemon can learn the move
push bc
ld a,[$cf92]
ld hl,W_PARTYMON1NAME
call GetPartyMonName
pop bc
ld a,c
and a ; can the pokemon learn the move?
jr nz,.checkIfAlreadyLearnedMove
; if the pokemon can't learn the move
ld a,$a5
call PlaySoundWaitForCurrent ; play sound
ld hl,MonCannotLearnMachineMoveText
call PrintText
jr .chooseMon
.checkIfAlreadyLearnedMove
ld hl, Func_2fe18
ld b, BANK(Func_2fe18)
call Bankswitch ; check if the pokemon already knows the move
jr c,.chooseMon
ld a,$1b
call Predef ; teach move
pop af
ld [$cf91],a
pop af
ld [$cf92],a
ld a,b
and a
ret z
ld a,[$cf91]
call IsItemHM
ret c
jp RemoveUsedItem
BootedUpTMText: ; e54f (3:654f)
TX_FAR _BootedUpTMText
db "@"
BootedUpHMText: ; e554 (3:6554)
TX_FAR _BootedUpHMText
db "@"
TeachMachineMoveText: ; e559 (3:6559)
TX_FAR _TeachMachineMoveText
db "@"
MonCannotLearnMachineMoveText: ; e55e (3:655e)
TX_FAR _MonCannotLearnMachineMoveText
db "@"
Quando una TM o MN viene utilizzata, ecco cosa succede:
- Il numero dell'oggetto TM o HM viene "convertito" ed adattato in un numero che va dal 01 al 55. 01-50: Mosse TM, 51-55: Mosse MN.
Notare che in Gen1 esistono 5 oggetti di Debug (le TM51, TM52 etc fino alla TM55) che, sebbene insegnino mosse MN, si comportano effettivamente come Mosse TM in quanto oggetti (ovvero: si possono buttare via, e si consumano dopo l'uso);
- Ciascun Pokemon contiene uno Header di 0x1C bytes RAM, dei quali i 7 bytes dal al byte 0x15 al byte 0x1B contengono un "bit-mask" di zeri ed uno (in totale: 56 bits) che specificano quali tra le possibili 56 (su 55) TM/HM possono essere apprese dal pokemon, in ordine dalla TM01 alla MN05.
Bit = 0 : Non può apprendere la mossa. Bit = 1 : Il Pokemon può apprendere la mossa TM / HM;
Codice:; tests if mon [$cf91] can learn move [$d0e0]
TestMonMoveCompatibility: ; 1373e (4:773e)
ld a, [$cf91]
ld [$d0b5], a
call GetMonHeader
ld hl, W_MONHLEARNSET
push hl
ld a, [$d0e0]
ld b, a
ld c, $0
ld hl, TechnicalMachines
.findTMloop
ld a, [hli]
cp b
jr z, .TMfoundLoop
inc c
jr .findTMloop
.TMfoundLoop
pop hl
ld b, $2 ; read corresponding bit from TM compatibility array
ld a, $10
jp Predef ; indirect jump to HandleBitArray (f666 (3:7666)) - Il numero "convertito" della TM / HM in uso si ricollega ad una lista di mosse contenuta in un altro Bank che contiene l'identità / costante ID sotto forma di numero esadecimale corrispondente alla lista interna di mosse;
Codice:; converts TM/HM number in $d11e into move number
; HMs start at 51
TMToMove: ; 13763 (4:7763)
ld a, [$d11e]
dec a
ld hl, TechnicalMachines
ld b, $0
ld c, a
add hl, bc
ld a, [hl]
ld [$d11e], a
ret
TechnicalMachines: ; 13773 (4:7773)
db MEGA_PUNCH
db RAZOR_WIND
db SWORDS_DANCE
db WHIRLWIND
db MEGA_KICK
db TOXIC
db HORN_DRILL
db BODY_SLAM
db TAKE_DOWN
db DOUBLE_EDGE
db BUBBLEBEAM
db WATER_GUN
db ICE_BEAM
db BLIZZARD
db HYPER_BEAM
db PAY_DAY
db SUBMISSION
db COUNTER
db SEISMIC_TOSS
db RAGE
db MEGA_DRAIN
db SOLARBEAM
db DRAGON_RAGE
db THUNDERBOLT
db THUNDER
db EARTHQUAKE
db FISSURE
db DIG
db PSYCHIC_M
db TELEPORT
db MIMIC
db DOUBLE_TEAM
db REFLECT
db BIDE
db METRONOME
db SELFDESTRUCT
db EGG_BOMB
db FIRE_BLAST
db SWIFT
db SKULL_BASH
db SOFTBOILED
db DREAM_EATER
db SKY_ATTACK
db REST
db THUNDER_WAVE
db PSYWAVE
db EXPLOSION
db ROCK_SLIDE
db TRI_ATTACK
db SUBSTITUTE
db CUT
db FLY
db SURF
db STRENGTH
db FLASH - la mossa viene identificata e "prelevata" e posta in un'unità di memoria WRAM temporanea (vedasi primo estratto ASM: ItemUseTMHM);
- Si procede all'ASM di insegnamento delle mosse ad un Pokemon, utilizzando come mossa da apprendere la mossa salvata temporaneamente nella WRAM, previ i checks per verificare che il Pokemon non abbia già appreso la mossa, etc. (in fondo alla funzione ItemUseTMHM).
Questo comporta ovvi limiti:
- Un massimo di 56 tra mosse TM e HM -- che significa, tradotto in soldoni, che si può al massimo aggiungere una HM al massimo, oppure una TM sebbene con difficoltà;
- Un numero limitato di TM e HM ben specifico;
- Un numero limitato di mosse che il pokemon può apprendere tramite HM;
- Un sistema di utilizzo di HM alquanto deleterio per gli standard dei giocatori moderni;
- L'uso di HM è limitato all'acquisizione di medaglie, il che limita il design di gioco;
- Peggio ancora, e la limitazione di base del mondo dei Pokemon, è di avere la piaga degli HM slave e di mosse sprecate;
In questo topic vedrò di analizzare possibili risposte ed ampliamenti a tali limiti tramite asm.


Questa modifica farebbe sì che i 7 bytes dei move pool TM e HM per ciascun pokemon diventino superflui: i bitmask verrebbero utilizzati ed inclusi direttamente nell'utilizzo delle TM e HM nelle routines contenuti nel bank03 (nello specifico, nelle routines dell'uso degli oggetti).
Assumendo che il numero di Pokemon rimanga limitato entro i 255 esemplari (M'Block/MissingNO 0x00 escluso), il bitmask in questione indicherà invece quali specie di Pokemon da 0x01 a 0xFF (secondo la lista di ID Interni in uso in Pokemon R/B/Y, che NON segue l'ordine del Pokedex) possono apprendere la TM / HM in uso, mediante una semplice aggiunta ASM tramite compare:
- Ciascuna TM / HM nella tabella dell'utilizzo degli oggetti utilizza un utilizzo diverso, specifico per ciascuna TM / HM ;
- L'utilizzo di una TM / HM fa sì che venga caricata una lista / array di bytes, ciascuno contenente l'ID interno del Pokemon in questione. Non conviene affatto indicizzare le specie di Pokemon disponibili secondo il byte: l'unica soluzione fattibile in termini di spazio è l'utilizzo di array di bitmask.
Ciascuna TM / HM ha assegnata nell'utilizzo un array di bitmask di 0x20 bytes, cui ciascun bit indicizza una specie di Pokemon dal 0x01 al 0xFF secondo l'ID interno: 0x20 bytes per 55 tra TM e HM sono 0x6E0 bytes, quindi bisogna tenere in conto lo spazio.
- L' ID del pokemon selezionato su cui si vuole utilizzare la TM / MN (contenuto nel byte WRAM 0xcf92 (wWhichMon), temporaneamente) viene posto nel registro a e poi comparato alla lista di bytes caricati nell'array (tramite funzione di comparazione degli array, nel Bank0) che è stato in precedenza stabilito/creato dalla funzione ddi utilizzo della TM / HM. In caso di bitmask, la funzione di comparazione da eseguirsi utilizza una funzione diversa (quando posso vi rimando alla disassembly, in questo momento non ce l'ho sotto mano);
- Se la comparazione ha esito positivo (flag nz attivata), si procede come di consueto alla funzionalità di apprendimento della mossa, altrimenti (jr z, oppure jp z) si passa al caso di utilizzo fallimentare di TM / HM;
Le parti da sostituire riguardano dunque:
- la comparazione con i bitmask delle mosse TM / HM che possono essere apprese dal pokemon selezionato contenuti nelle statistiche del Pokemon selezionato (per l'appunto) -- da rimuovere e sostituire con la comparazione dei bytes e/o bitmask caricati dall'uso della TM / HM stessa;
- i bytes contenuti nella tabella di pointers dell'utilizzo delle TM e/o HM. In merito a ciò, si necessita di un'ulteriore modifica per fare sì che l'utilizzo di tali oggetti TM e HM non rimandino ad un utilizzo comune (la funzione " UseTMHM " come definita nella Disassembly) sebbene alla lista dei pointer di utilizzo degli oggetti -- ossia, rimuovendo quel compare nella funzione UseItems (cp $C4 (TM01) // jp nc UseTMHM ). Inoltre, si necessita di un'ulteriore seconda modifica per correggere un bug della GameFreak che fa sì che gli oggetti con ID interno oltre 0x7F ri-utilizzinpo gli utilizzi/funzioni a partire dalla MasterBall (ID = 0x01). Per ulteriori informazioni, consultare il mio Item Hacking Tutorial.

- Risparmio di 7 bytes nelle statistiche dei Pokemon, ri-utilizzabili per utilizzi ASM completamente nuovi -- es: strumenti tenuti, difesa speciale, secondo strumento tenuto, ID abilità, ID Natura, ID Pokemon Mega-Evoluzione, etc. etc. etc. in base a quanta ASM nuova e funzionalità ASM si è disposti ad implementare. Liberare quei 7 bytes aprirebbe ad orizzonti e possibilità nuove;
- Move-Pool TM e HM customizzabili per ciascun Pokemon in base alla singola TM e HM e non più in base a quei 7 bytes assegnati a ciascun Pokemon -- ergo, più di 50 TM e più di 6 MN possibili;

- Aggiungere un sacco di modifiche ASM e di correzioni di bug della GF, oltre che riscrivere l'utilizzo delle TM / HM -- facilmente superabile col disassembly, sebbene richieda tempo;
- Consuma spazio: minimo 0x6E0 bytes in caso di bitmask per 255 Pokemon e 55 TM / HM -- se il numero di specie è inferiore a 255, o se è limitato, o se si trova il modo di trasferire l'utilizzo delle TM / HM e questa funzione in un altro bank, è un problema meno gravoso;
Ad oggi la routine ASM per questa funzione non è ancora pubblicata; lo sarà quando avrò modo di testarla.


Una modifica avanzata e proposta recentemente (2021, Pokemon Coral --
Spoiler (Clicca per visualizzare)
L'utilizzo delle mosse avverrebbe dunque unicamente tramite menù squadra Pokemon, che includerebbe le mosse HM automaticamente in base ai famosi bitmask move-pools TM e HM in odtazione ai pokemon.
Nel caso specifico di R / B / Y, ciò richiederebbe solo 1 byte di spazio: il byte 0x1B di ciascuna statistica base di Pokemon -- il byte andrebbe confrontato come bitmask e, se uno degli 8 bit risultasse == 1, la mossa MN corrispondente verrebbe immessa nel menù.
Una proposta audace, che però (come osservato dall'utenza), pone diversi problemi:
- Come gestire le mosse non MN utilizzabili fuori dalla lotta (es: Fossa) -- si può risolvere comparando tutti e 7 i bytes dei move-pool (ipotesi più in voga)
- Essendoci più di 4 mosse in uso fuori dalla lotta, come gestire tutte queste mosse in un menù contestuale -- soluzione di gen2: creare un menù contestuale apposito (soluzione rimasta irrisolta).
Soluzione mia avanzata da 80C per Gen1 (e, possibilmente, Gen2): creare un menù contestuale apposito con un'opzione dedicata ("Field Move") alla cui selezione si apre un sotto-menù contestuale di selezione tipo quello del Market che, anziché elencare oggetti, esso elenca mosse, caricando in WRAM la costante ID 0x02 (Menù di Mosse) nell apposita WRAM temporanea (wListType, se non erro la dicitura).
Anzichè creare un ID di oggetti si crea una lista i cui componenti selezionabili sono Mosse.L'array in questione (ossia, i 0x20 oggetti del sotto-menù in questione) verrebbe creato per l'appunto comparando i famosi 7 bytes dei move-pools TM e HM, ed estraendo la mossa TM / HM in questione se il bit della bitmask assegnata alle mosse TM HM apprendibili è ==1. La lista andrebbe terminata dal byte 0xFF.Spoiler (Clicca per visualizzare)

- Niente più corrispondenza mosse apprese / field moves utilizzabili fuori dalla lotta: dipenderebbe dalla specie di pokemon selezionato, e non dal moveset. Addio HM Slaves.
- Più di 4 mosse utilizzabili fuori dalla lotta. Addio HM slaves per sempre -- basterebbe avere un Mew o anche solo un Meowth per utilizzare tutti i field moves possibili immaginabili.

- L'ASM per il menù contestuale sarebbe difficile da inserire: Gen.1 ha un modo tedioso di utilizzare i sotto-menu nel menu pokemon (garantisco io: aggiungere gli strumenti tenuti in Grape ed il menu di assegnazione degli strumenti ai Pokemon come in gen2 è stato tedioso, specie per come le varie ASM di sotto-menu sono spezzettate in banks diversi). In Gen2 dovrebbe essere un pochino più semplice, data la migliore organizzazione del menù di squadra pokemon.
- L'ASM di creazione del menù field move è un tedio di programmazione ASM. Buona fortuna ad utilizzare tutti quei bitmask e a scervellarsi con le operazioni di rotazioni e spostamento di bit.


Questa è l'unica modifica semplice, addirittura da essere realizzabile tramite hex editor per i disperati senza disassembler.
Nel Bank04 di R / B / Y sono contenuti gli utilizzi delle field moves fuori dalla lotta.
Esiste un pointer contenente gli indirizzi in cui sono raggruppate le routines ASM che portano alle field moves (e, badate bene, è possibile aggiungerne delle altre, come fatto in hacks come Grape, Maize, TRE2, etc. etc. etc.); ciascuna routine delle field moves delle TM contiene un compare con il byte WRAM in cui vengono salvate le medaglie acquisite dal giocatore (1 byte = 8 bit = 8 medaglie. 0 = medaglia non ottenuta, 1 = medaglia ottenuta).
Codice:
StartMenu_Pokemon: ; 130a9 (4:70a9)
; [...]
.choseOutOfBattleMove
push hl
ld a,[$cf92]
ld hl,W_PARTYMON1NAME
call GetPartyMonName
pop hl
ld a,[hl]
dec a
add a
ld b,0
ld c,a
ld hl,.outOfBattleMovePointers
add hl,bc
ld a,[hli]
ld h,[hl]
ld l,a
ld a,[W_OBTAINEDBADGES] ; badges obtained
jp [hl]
.outOfBattleMovePointers
dw .cut
dw .fly
dw .surf
dw .surf
dw .strength
dw .flash
dw .dig
dw .teleport
dw .softboiled
Basta eliminare (o, alla Carlona, "azzerare" con dei bytes 0x00, se proprio siete disperati e non disponete di disassembly) quelle famose linee di bit-testing contenute in ciascuna delle ASM in uso per le field moves per eliminare la compatibilità medaglie / mosse.
Vediamo un esempio con la mossa Volo utilizzata fuori dalla lotta:
Codice:
.fly
- ; bit 2,a ; does the player have the Thunder Badge?
- ; jp z,.newBadgeRequired
call CheckIfInOutsideMap
jr z,.canFly
ld a,[$cf92]
ld hl,W_PARTYMON1NAME
call GetPartyMonName
ld hl,.cannotFlyHereText
call PrintText
jp .loop
.canFly
; [...]
Ciò tornerebbe utile in caso di hacks tipo Snakewood, Climax, ed altre per cui non c'è bisogno di avere palestre e/o medaglie.


(La modifica è in fondo al codice di esempio, secondo il formato di editing "GitHub" secondo il quale il " - " all'inizio indica le linee da commentare via con un " ; " ad inizio rigo, mentre " + " indica le linee di codice da aggiungere).
Codice:
ItemUseTMHM: ; e479 (3:6479)
; [...]
.checkIfAlreadyLearnedMove
ld hl, Func_2fe18
ld b, BANK(Func_2fe18)
call Bankswitch ; check if the pokemon already knows the move
jr c,.chooseMon
ld a,$1b
call Predef ; teach move
pop af
ld [$cf91],a
pop af
ld [$cf92],a
ld a,b
and a
ret z
; TM infinite: la distinzione TM / HM è dunque superflua qui, rimuovere
- ; ld a,[$cf91]
- ; call IsItemHM
- ; ret c
- ; jp RemoveUsedItem
+ ret ; return, non consumare la TM dopo l'uso.


Ciò risolverebbe il problema delle TM e HM come oggetti dello zaino che occupano spazio: se le TM sono infinite e riutilizzabili, la quantità non ha più senso, e dunque basterebbe avere 7 bytes (56 bits) per le 50 TM e 5 HM in uso nel gioco (naturalmente, apiù TM e HM servirebbero più bytes e bit da settare).
Problema: come fare sì che anzichè ricevere l'oggetto venga settato il bit della TM / HM in questione.
Risposta: Naturalmente facendo sì che in ciascuna istanza del gioco in cui si riceve una HM o TM si abbia un caso del tipo:
Codice:
ld hl,(wTmMoves01) ; nuova flag. 1 bit per mossa TM / HM
set 0,(hl) ; "0" è il primo bit della mia flag, può essere da 0 a 7, questo è solo un esempio
Resta il problema delle PokeBall che si raccolgono sul terreno nelle mappe...
Esso è potenzialmente risolvibile andando a modificare radicalmente la ASM sugli oggetti raccolti sul terreno, assegnando all'NPC della PokeBall un tipo di movimento diverso, la cui flag verrebbe comparata con un caso/biforcazione speciale nella nostra ASM: se compatibile, il numero dell'oggetto contenuto nella PokeBall/Evento-NPC (tra 0x00 e 0xFF) andrebbe "convertito" in uno dei bit della nostra bitmask delle nostre TM e HM possedute.
Con questo sistema, sarebbe possibile indicizzare un massimo di 255 tra TM e HM (in 0x20 bytes di memoria WRAM in totale).
80C: Vita & Opere ♫ 80C's Music ♫ - Enciclopedia Hacks Gen.1 e Hacks Gen.2 - Storia dell'Hacking GB\C
1°Posto Best Beta 2015 - pokehack.altervista.org (Clicca per visualizzare)