In Esploratori del Cielo, i Pokémon non hanno una statistica di velocità come nella serie principale; al suo posto, esiste una statistica semi-nascosta chiamata "velocità di movimento" che ha cinque possibili stadi, da un minimo di -1 (velocità dimezzata) a un massimo di +3 (velocità quadrupla). Lo stadio base è ovviamente +0 (velocità normale). La velocità di movimento determina il numero di azioni che un Pokémon può effettuare all'interno di un turno. Da qui in poi chiamerò i Pokémon semplicemente "mostri", dato che questo sembra essere il termine usato dagli sviluppatori, che è stato anche adottato dalla community di ROM hacking.
L'ordine in cui i mostri agiscono è fisso, e non dipende in alcun modo dalle statistiche; anzi, in teoria le azioni in un turno avvengono tutte contemporaneamente anche se vengono mostrate in sequenza, un po' come succede in un round di combattimento di D&D. In effetti, c'è un caso in cui questo succede a schermo, cioè se tutti i mostri sono a +0 velocità e l'unica azione che compiono è un passo. Quando avanza un turno, il gioco scorre una lista di mostri presenti sul piano, ed esegue le loro azioni nell'ordine in cui si trovano all'interno dell'array monster_slot_ptrs (per i non informatici, una lista ordinata) che fa parte della tabella delle entità del piano:
Tabella delle entità (dungeon_mode.h)
// Dungeon entity table header
struct entity_table_hdr {
// 0x0: A list of all monster pointers, whether they're used or not
struct entity* monster_slot_ptrs[20];
// 0x50: Null-terminated array of pointers to actually active monsters
struct entity* active_monster_ptrs[20];
struct entity* item_ptrs[64]; // 0xA0
struct entity* trap_ptrs[64]; // 0x1A0
struct entity* hidden_stairs_ptr; // 0x2A0
};Come vediamo ci sono un massimo di 20 slot disponibili all'interno di un piano, non per forza tutti sempre attivi. Il primo elemento di questo array è sempre il leader, e dopo di lui ci possono essere da 0 a 3 membri del party, nell'ordine in cui sono stati aggiunti al gruppo a Borgo Tesoro. Infine, nei 15 slot seguenti, vengono inseriti senza particolare ordine i mostri nemici che spawnano sul piano (l'ultimo slot, come dice il commento, è sempre vuoto in quanto l'array è null-terminated). Quindi, l'ordine di azione è sempre:
leader -> party -> nemici
Vediamo ora come la velocità di movimento influisce sul numero di azioni, ma per farlo dovremo prima introdurre un concetto un po' strano, quello di "turno frazionario" (fractional turn). Quando un mostro agisce con velocità aumentata, ci dobbiamo immaginare che tale azione non occupi un turno intero, bensì una frazione di turno, o turno frazionario. Tale turno frazionario sarà tanto più piccolo quanto più alto il moltiplicatore di velocità. Nel codice, il turno frazionario non è altro che il valore di un contatore che è sempre tenuto in memoria durante un piano, ed è definito così:
Turno frazionario (dungeon.h)
// 0x780: Controls when a monster at a certain speed stage is able to act.
// Increased by 1-4 each turn, depending on the team leader's speed level:
// At normal speed, this will tick up by 4 each turn (can act when x % 4 == 3)
// At +1 speed, ticks up by 2 each turn (can act when x % 2 == 1)
// At +2 speed, ticks up by 1 or 2 each turn (can act when x % 4 != 0)
// At +3 speed, ticks up by 1 each turn (an act every tick)
// The counter is increased modulo 24, goes from 0x00 to 0x17 then starts over
uint16_t fractional_turn;Se non ci avete capito molto, non preoccupatevi: il commento non è molto chiaro, e non è nemmeno completo, anche perché non è una spiegazione che si riassume facilmente in poche righe (manca infatti il caso -1). Andiamo con ordine: innanzitutto, per chi non lo sapesse l'operatore % indica l'operazione di modulo, vale a dire il resto della divisione intera, quella che abbiamo imparato alle elementari. Ad esempio, se 5 / 3 = 1, resto 2 allora 5 % 3 = 2 (si può scrivere anche 5 mod 3). A cosa ci serve? Come abbiamo visto, il contatore viene incrementato modulo 24, cioè va da 0 a 23 e poi riparte. Ad esempio, se il contatore valesse 23 e ci dovessi sommare 4, farei 23 + 4 mod 24 = 3 mod 24 dato che 23 + 4 = 27 e 27 / 24 = 1 resto 3 cioè 27 mod 24 = 3 = 3 mod 24.
Il valore di fractional_turn parte sempre da 0 all'inizio di un piano, e comincia subito ad incrementare. Ad ogni tick del contatore, il gioco scorre tutta la lista dei mostri nell'ordine che abbiamo visto, e controlla se ce n'è qualcuno che può agire all'interno di quel turno frazionario; quando ne trova uno, quel mostro agisce e si passa al successivo. Se il leader può agire, il gioco si bloccherà in attesa dell'input del giocatore, altrimenti passerà al party, poi ai nemici, poi di nuovo al leader e così via.
Per capire se un mostro può agire, il codice paragona il suo stadio di velocità con il valore di fractional_turn. Un mostro può agire nei seguenti casi:
- A -1, se
fractional_turn mod 8 = 7 - A +0, se
fractional_turn mod 4 = 3 - A +1, se
fractional_turn mod 2 = 1 - A +2, se
fractional_turn mod 4è diverso da 0 - A +3, sempre (ogni volta che
fractional_turnaumenta).
Quindi, facciamo un esempio. Assumiamo che fractional_turn valga 3, che tutti i mostri siano a +0 e che il leader faccia un passo. L'azione viene eseguita e tocca al party. Dato che 3 mod 4 = 3, il party agisce, e lo stesso vale per i nemici. Finito il giro, il contatore aumenta di 1. Dato che 4 mod 4 = 0, nessuno agisce. Lo stesso vale per 5 e 6. Quando il contatore vale 7, 7 mod 4 = 3, perciò il leader può agire di nuovo, e così da capo.
Questo esempio vale per il caso banale in cui tutti i mostri sono a +0, ma se ci sono mostri con velocità modificata basta applicare le regole che abbiamo visto prima. In sostanza, i mostri a -1 agiscono ogni 8 turni frazionari, quelli a +0 ogni 4, quelli a +1 agiscono a turni alterni, quelli a +2 saltano un turno ogni 3 e quelli a +3 agiscono sempre.
Ora vediamo come il gioco gestisce le modifiche alla velocità di movimento. Gli stadi di velocità vengono salvati tramite due variabili differenti, una che contiene lo stadio vero e proprio, e un'altra che contiene i turni rimanenti per ogni buff e debuff ricevuto dal mostro:
Speed stage (dungeon_mode.h)
// 0x67: 1 means normal. 0 means half speed. 2, 3, and 4 mean 2x, 3x, and 4x speed.
int speed_stage;
// Each counter ticks down to 0 turn by turn. The current speed_stage is calculated as:
// min(max({# nonzero speed_up_counters} - {# nonzero speed_down_counters}, 0), 4)
uint8_t speed_up_counters[5]; // 0x6B
uint8_t speed_down_counters[5]; // 0x70In pratica, per ogni speed_up_counter maggiore di 0, lo stadio prende un +1, e per ogni speed_down_counter maggiore di 0, lo stadio prende un -1. Il risultato non può mai essere minore di 0 (-1) o maggiore di 4 (+3). Alla fine di ogni turno (frazionario) in cui il mostro agisce, tutti i contatori si decrementano di 1. Questo spiega perché se si usa subito Agilità fino al +3 la velocità quadrupla durerà per più turni rispetto al caso in cui si alternino boost ed attacchi: Agilità setta un contatore a 10, quindi se la usiamo 3 volte di fila, avremo un contatore a 9, uno a 8 e uno a 7, e potremo agire per 7 volte a velocità quadrupla. In pratica, Faremo 4 attacchi di fila, i nemici ne faranno uno solo, e poi noi altri 3. Se invece lasciamo che i primi contatori scadano, ad esempio quello del +1, c'è il rischio di arrivare a fare un +2 che dopo un solo turno si ritrasforma in +1, con l'effetto di refreshare il +1 ma senza di fatto sfruttare il +2.
Quando un mostro subisce un effetto che ne modifica la velocità, tale modifica avviene prima che il turno frazionario avanzi; quindi, se dopo la modifica il mostro potrebbe agire ancora al turno frazionario successivo, lo farà. Questo spiega perché quando incontriamo un nemico che usa Agilità, esso otterrà sempre un'altra azione immediatamente dopo essersi aumentato la velocità. Anche se dal nostro punto di vista le due azioni sono consecutive, in realtà avvengono a due turni frazionari di distanza (nel caso in cui il nemico sia a +1).
Ciò che abbiamo detto fin qui si applica a tutti i mostri, ma c'è una regola speciale che vale solo per il giocatore. Infatti, quando la velocità del leader viene modificata, fractional_turn viene resettato a 0, a prescindere dal valore che aveva. Ne risulta che la velocità di movimento del leader funziona in modo leggermente diverso.
Ad esempio potremmo chiederci, perché Agilità-Centripugno è una true combo per il leader ma non per un alleato o un nemico? Adesso abbiamo tutti gli strumenti per rispondere. Dobbiamo tenere a mente che:
- L'ordine di azione è leader -> alleati -> nemici
- Il leader a +1 agisce se
fractional_turn mod 2 = 1 - Al prossimo incremento, il leader sarà già a +1
- Usare Agilità resetta
fractional_turna 0
Assumiamo che tutti i mostri siano a +0: usare agilità imposta fractional_turn a 0. A questo punto tocca agli altri mostri, che non potranno agire, perché 0 mod 4 = 0. Quindi il contatore viene incrementato, e vale adesso 1. Dato che 1 mod 2 = 1, il leader può agire di nuovo, e gli altri stanno fermi. Il contatore incrementa di nuovo, e vale ora 2, ma nessuno può agire. Si incrementa quindi un'altra volta, e ora vale 3. Dato che 3 mod 2 = 1, il leader agisce per la terza volta di fila indisturbato, rendendo possibile la combo.
Per visualizzare meglio l'ordine dei turni frazionari, potete guardare questa tabella, in cui fractional_turn aumenta da sinistra verso destra e ogni simbolo colorato di una riga corrisponde a una azione di quel tier di velocità, mentra una ✖️ indica un turno frazionario in cui non si può agire.
| 0-3 | 4-7 | 8-11 | 12-15 | 16-19 | 20-23 | ||
|---|---|---|---|---|---|---|---|
| -1 | ✖️✖️✖️✖️ | ✖️✖️✖️🟥 | ✖️✖️✖️✖️ | ✖️✖️✖️🟥 | ✖️✖️✖️✖️ | ✖️✖️✖️🟥 | x % 8 == 7 |
| +0 | ✖️✖️✖️🟧 | ✖️✖️✖️🟧 | ✖️✖️✖️🟧 | ✖️✖️✖️🟧 | ✖️✖️✖️🟧 | ✖️✖️✖️🟧 | x % 4 == 3 |
| +1 | ✖️🟨✖️🟨 | ✖️🟨✖️🟨 | ✖️🟨✖️🟨 | ✖️🟨✖️🟨 | ✖️🟨✖️🟨 | ✖️🟨✖️🟨 | x % 2 == 1 |
| +2 | ✖️🟩🟩🟩 | ✖️🟩🟩🟩 | ✖️🟩🟩🟩 | ✖️🟩🟩🟩 | ✖️🟩🟩🟩 | ✖️🟩🟩🟩 | x % 4 != 0 |
| +3 | 🟦🟦🟦🟦 | 🟦🟦🟦🟦 | 🟦🟦🟦🟦 | 🟦🟦🟦🟦 | 🟦🟦🟦🟦 | 🟦🟦🟦🟦 | (ogni turno) |
Tutte le informazioni in questo documento sono basate, oltre che sui test che ho fatto personalmente, sulla repository pmdsky-debug (a cui ho in piccola parte contribuito), che riassume le informazioni sul codice di Esploratori del Cielo raccolte dalla community nel corso degli anni tramite reverse engineering.