Skeetendo

’Cause all games were better on the GBC

You are not logged in.

#1 2011-05-20 19:28:32

Sawakita
Administrator
Registered: 2010-10-16
Post 118/365

R/B Scripting Guidelines

=========================
R/B SCRIPTING GUIDELINES
=========================

Knowledge in ASM programming and code assembling/inserting are being taken for granted.

This is an attempt to explain how to create scripts from scratch in Red/Blue, modifying an existing script in a specific manner is not covered here (although this doc should help when trying to figure out the various calls' meaning).

=============================
---------
INDEX:
---------
Part 1 - General map-script structure
Part 2 - Triggers and flags
Part 3 - Get/check item, get/check pokemon
=============================



===========
      PART 1
===========
-----------------------------------------
GENERAL MAP-SCRIPT STRUCTURE
-----------------------------------------

A map script (or level script, if you want) is generally organized in a formatted structure:
- main function;
- table of subroutines' pointers;
- first subroutine
- ...
- last subroutine



Main Function:
Here you redirect to the various sub-routines, and optionally can check/set certain flags that involve other map-scripts. I'm using PalletTown script as example (because I can't find a way to explain it clearly):

;ROM6:4E5B
    ld a,($d74b)    ;1
    bit 4,a         ;2
    jr z,.next0\@   ;3
    ld hl,$d747     ;4
    set 6,(hl)      ;5

.next0\@
;ROM6:4E67
    call $3c3c      ;6
    ld hl,$4e73     ;7
    ld a,($d5f1)    ;8
    jp $3d97        ;9

As you can see, at line 1-5 there's a "flag checking/setting".
But the very standard code is at line 6-9.
- "$3c3c": is called in every map-script I've seen (although, since I've not seen every script, I can't tell if it's always used or not);
- "$4e73": address of the various subroutines' pointers' table;
- "$d5f1": address in RAM where subroutine ID is stored;
- "$3d97": this routine redirects to the routine at hl + a * 2


Pointers' Table:
Right after the 'main function' you can find the table of the various sub-routines' pointers:

;ROM6:4E73 Pointers Table
dw 4e81    ;sub-routine 0
dw 4eb2    ;sub-routine 1
dw 4ed2    ;sub-routine 2
dw 4f12    ;sub-routine 3
dw 4f4b    ;sub-routine 4
dw 4f56    ;sub-routine 5
dw 4f87    ;sub-routine 6

Sub-routines:
Finally we have the actual script, splitted in various sub-parts (this organization exists because a return to upper function is required between a sub-script and another, or to ease the repetition of certain chunks of code and not other ones):

;PART 0
ROM6:4E81 FA 47 D7         ld a,(d747)
ROM6:4E84 CB 47            bit 0,a
;...
ROM6:4EAC 3E 01            ld a,01
ROM6:4EAE EA F1 D5         ld (d5f1),a
ROM6:4EB1 C9               ret

At the end of each script-chunk, the value for the next script-chunk to be run is loaded in a certain RAM location ($d5f1 is for Pallet Town). Notice that the first sub-part's ID is 0, second one's is 1, and so on.



In the next update we'll start exploring some simple scripting possibilities, like triggers, flag-conditioned texts and maybe something else. Let me know if there's something that needs clarification or correction.

Last edited by Sawakita (2011-06-17 19:51:45)

Offline

#2 2011-05-20 20:44:43

Tauwasser
Member
Registered: 2010-10-16
Post 139/452

Re: R/B Scripting Guidelines

Hi there.

What you're describing is the basic code that got replaced in Gold/Silver with script headers and various scripting commands.

The sub routines are actually different map states/ trigger states just like in GSC's script header. All bit checks are probably hardcoded checks for certain numbered bit flags that just got replaced via a macro expansion while compiling the game.

For instance, maps will initially start at 0x00 in their respective trigger tables. For Pallet town this is exactly so:

ld a, [$D747]
bit 0, a
ret nz        Exit if OAK event already finished
ld a, [$D361]
cp a, $01
ret nz        Exit if Player is not at Y = 01
xor a, a
ld [$FF00 + $B4], a
ld a, $04
ld [$D528], a
ld a, $FF
call $23B1    Music fadeout?
ld a, $02
ld c, a
ld a, $DB
call $23A1    Change to silence?
ld a, $FC
ld [$CD6B], a
ld hl, $D74B
set 7, [hl]    Set some flag
ld a, $01
ld [$D5F1], a    Next trigger ID
ret

The reason it needs to split it up like this is probably because both script header parts are combined and there are not real trigger events but only queries in code. So there have to be small pieces of code that can execute relatively often without taking too much time, since else the game will lag much more.

Also, events can be split up that way, too, of course:

00 -- 03 are the actual trigger scripts and event scripts for the OAK event.
04 waits until the script finishes and the hero warps to another map
05 Town Map can be obtained flags are set.
06 No Operation (just a ret)

cYa,

Tauwasser

Offline

#3 2011-05-20 23:24:46

Sawakita
Administrator
Registered: 2010-10-16
Post 119/365

Re: R/B Scripting Guidelines

I called them sub-routines because I didn't know how to define them otherwise; they're actually various self-standing scripts. And, yes, I decided to start explaining very very general facts about map scripts.

I fixed the code you quoted a little bit:

ld a, [$D747]
bit 0, a
ret nz                ;Exit if OAK event already finished
ld a, [$D361]
cp a, $01
ret nz                ;Exit if Player is not at Y = 01
xor a, a
ld [$FF00 + $B4], a   ;Clear "KeyPress" memory location (because now some forced actions will be performed)
ld a, $04             
ld [$D528], a         ;Player turns South when screen is refreshed
ld a, $FF             ;(marker for stop music)
call $23B1            ;Stop current music
ld a, $02             ;bank for music track that will be loaded
ld c, a
ld a, $DB             ;ID of music track
call $23A1            ;load music track
ld a, $FC
ld [$CD6B], a         ;disable joypads (player is prevented from moving or doing anything)
ld hl, $D74B
set 7, [hl]           ;Set some flag (I can't remember which one too)
ld a, $01
ld [$D5F1], a         ;Next trigger ID
ret
Tauwasser wrote:

The reason it needs to split it up like this is probably because both script header parts are combined and there are not real trigger events but only queries in code. So there have to be small pieces of code that can execute relatively often without taking too much time, since else the game will lag much more.

Agreed 100%. It's a sort of: |Main loop|update screen|get keypress event|play some music|...|run a part of current map's script|...|Main loop|...

Tauwasser wrote:

Also, events can be split up that way, too, of course:

00 -- 03 are the actual trigger scripts and event scripts for the OAK event.
04 waits until the script finishes and the hero warps to another map
05 Town Map can be obtained flags are set.
06 No Operation (just a ret)

Thanks for this last summary, I really fail when it comes to summarize something in short sentences :P

Offline

#4 2011-06-15 14:43:54

Sawakita
Administrator
Registered: 2010-10-16
Post 130/365

Re: R/B Scripting Guidelines

===========
      PART 2
===========
-----------------------------------------
TRIGGERS: PLAYER's POSITION
-----------------------------------------

Event determined by player's coordinates is pretty easy to implement; here are the RAM locations for player's coord.s:
$D361 = Y position
$D362 = X position

If you want that just a spot triggers the event, you put a control code like this: (xx and yy are 8bit value for the coordinates of the spot)

;    
    ld a,[$d361]
    cp a,yy
    ret nz
    ld a,[$d362]
    cp a,xx
    ret nz
;[your script here]

If you want your script to occur when player is in a certain row/column of the map, just put a control code for either Y-pos or X-pos. With a little more code you could make the script occur when player is in a certain (rectangular) area:

;    
    ld a,[$d361]
    cp a,y1
    ret c
    cp a,(y2 + 1)
    ret nc
    ld a,[$d362]
    cp a,x1
    ret c
    cp a,(x2 + 1)
    ret nc
;[your script here]

(x1,y1) is top-left corner of the area
(x2,y2) is bottom-right corner of the area



-----------------------------------------
TRIGGERS: FLAGS
-----------------------------------------

A flag is just a boolean (true/false) value. You can us it to tell if player already received an item from a certain person, or to tell if you already have beaten a trainer (just a couple of examples, of course). To store a flag you just need a bit. So you have to find a byte with a free bit to use (for instance in Red/Blue flags are located around RAM location $D747-onwards)
Using flags as triggers is simple too:

ld a,[hhll]
    bit x,a
    ret nz
;[your script here]

hhll = byte where flag is stored
x = bit-number (0-7)



-----------------------------------------
TEXT-SCRIPTS: FLAG-CONDITIONED TEXT
-----------------------------------------

If you want to run an ASM routine in a text-script (find out more HERE) just put a byte=$08 at the beginning. You will often want to have different texts displayed in dialogues, according to certain flags. Here's a way to do it:

db $08
    ld hl,TextPtr1
    ld a,[jjkk]              ;jjkk = byte where flag is stored
    bit x,a                  ;x = bit number (0-7)
    jr z,.next\@
    ld hl,TextPtr2
.next\@
    call $3C49               ;print text
    jp $24D7                 ;end an ASM routine that was run in a text-script
TextPtr1:
    db $17                   ;means that a 3-byte pointer (2-byte ptr + bank) follows
    dw PointerToText1        ;2-byte pointer to text
    db Bank(PointerToText1)  ;bank of text
    db $50                   ;end
TextPtr2:
    db $17                   ;means that a 3-byte pointer (2-byte ptr + bank) follows
    dw PointerToText2        ;2-byte pointer to text
    db Bank(PointerToText2)  ;bank of text
    db $50                   ;end

So if bit x of byte jjkk is on (=1) Text1 is displayed; instead, if bit is off (=0) Text2 is displayed.

Last edited by Sawakita (2011-06-15 14:49:30)

Offline

#5 2011-06-17 19:50:26

Sawakita
Administrator
Registered: 2010-10-16
Post 131/365

Re: R/B Scripting Guidelines

===========
      PART 3
===========
-----------------------------------------
GET/CHECK ITEM
-----------------------------------------

Getting (or, to be correct, receiving) item requires 3 steps:
1) load item_ID (in b) and item_quantity (in c)
2) call "get-item" routine
3) check carry

ld bc,xxyy
    call $3E2E

xx = item ID
yy = item quantity

Remember to check carry flag after calling $3E2E: carry flag is:
- reset, if your bag is full (in which case you obviously won't get any item at all),
- set, if "getting item" was successful.



Checking if an item is in player's bag requires just 3 steps:
1) select Item ID
2) call "check-item" routine
3) read result (zero flag)

ld b,xx
    call $3493

xx = item ID
when program returns from routine $3493 zero flag is:
- set if item is in player's bag,
- reset if there's no such item in your bag.




-----------------------------------------
GET/CHECK POKEMON
-----------------------------------------

To create a get-pokemon script, load pokemon ID (internal order) and level respectively in b and c; then, call the "get-pokemon" routine:

ld bc,xxyy
    call $3E48

xx = pokemon ID
yy = pokemon level



I haven't (yet) met an already existing "check-pokemon" routine, but writing a brand new one is really easy, since it's very little time/space taking.
Just load in b the pokemon ID that you want to find in your party, and call the following routine:

check_mon_id:
    push hl
    call .subroutine\@
    pop hl
    ret
.subroutine\@
    ld hl,$d163    ;party data beginning in RAM
    ld a,[hli]     ;get number of pokemon in your party
.doesnt_exist\@
    and a          ;this resets carry flag
    ret z
.check_next\@
    ld a,[hli]
    cp a,b
    jr z,.exists\@
    cp a,$FF
    jr z,.doesnt_exist\@
    jr .check_next\@
.exists\@
    scf            ;this sets carry flag
    ret

So this routine returns:
- carry, if that pokemon actually is in your party;
- non carry, if that pokemon is not in your party;

This routine, as you can see, doesn't take too much space (24 bytes, if I counted correctly) so you could put it in a free area in bank_0, and then you'll be able to use it any time you want, without needing to switch banks.

Last edited by Sawakita (2011-08-27 10:56:58)

Offline

Board footer

Powered by FluxBB