1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
When you start a new game, the memory is zeroed out. But some things need to be initialized to nonzero values.
This is done with a callback script in [maps/PlayersHouse2F.asm](../blob/master/maps/PlayersHouse2F.asm), the map you first start in: if the `EVENT_INITIALIZED_EVENTS` event is not set yet, it does `jumpstd InitializeEventsScript`. That command actually calls the `InitializeEventsScript` script, which is defined in [engine/events/std_scripts.asm](../blob/master/engine/events/std_scripts.asm). It sets a lot of events, a couple of engine flags, initializes variable sprites, and finally sets the `EVENT_INITIALIZED_EVENTS` event so it won't repeat every time you enter PlayersHouse2F.
There are a few problems with this approach. One, it's tied to a particular map: if you change which map the player starts in, you have to remember to move the `InitializeEventsScript` callback. Two, it's hard to find: every time you need to add another initially-set event, you have to scroll halfway through an engine file to the right script. Three, it wastes ROM space: every single `setevent`, `setflag`, or `variablesprite` line spends one byte to identify the command.
This tutorial (using code originally by [ISSOtm](https://github.com/ISSOtm)) will solve all of those problems. It's recommended for if you want your project to be a base for other hacks, since they'll all need their own initializations.
## Contents
1. [Create data tables for what to initialize](#1-create-data-tables-for-what-to-initialize)
2. [Process the data tables to initialize everything](#2-process-the-data-tables-to-initialize-everything)
3. [Remove the scripts that initialized everything](#3-remove-the-scripts-that-initialized-everything)
## 1. Create data tables for what to initialize
Create **data/events/init_events.asm**:
```diff
+InitialEvents:
+ dw EVENT_EARLS_ACADEMY_EARL
+ ...
+ dw EVENT_INDIGO_PLATEAU_POKECENTER_RIVAL
+ dw EVENT_INITIALIZED_EVENTS
+ dw -1 ; end
+
+InitialEngineFlags:
+ dw ENGINE_ROCKET_SIGNAL_ON_CH20
+ dw ENGINE_ROCKETS_IN_MAHOGANY
+ dw -1 ; end
+
+InitialVariableSprites:
+initvarsprite: MACRO
+; variable sprite, appearance sprite
+ db \1 - SPRITE_VARS, \2
+ENDM
+ initvarsprite SPRITE_WEIRD_TREE, SPRITE_SUDOWOODO
+ initvarsprite SPRITE_OLIVINE_RIVAL, SPRITE_SILVER
+ initvarsprite SPRITE_AZALEA_ROCKET, SPRITE_ROCKET
+ initvarsprite SPRITE_FUCHSIA_GYM_1, SPRITE_JANINE
+ initvarsprite SPRITE_FUCHSIA_GYM_2, SPRITE_JANINE
+ initvarsprite SPRITE_FUCHSIA_GYM_3, SPRITE_JANINE
+ initvarsprite SPRITE_FUCHSIA_GYM_4, SPRITE_JANINE
+ initvarsprite SPRITE_COPYCAT, SPRITE_LASS
+ initvarsprite SPRITE_JANINE_IMPERSONATOR, SPRITE_LASS
+ db -1 ; end
```
This file defines three tables: `InitialEvents`, `InitialEngineFlags`, and `InitialVariableSprites`. They're all based on the commands from `InitializeEventsScript`:
- Every `setevent` command becomes a `dw` in `InitialEvents`
- Every `setflag` command becomes a `dw` in `InitialEngineFlags`
- Every `variablesprite` command becomes an `initvarsprite` in `InitialVariableSprites`
(The `initvarsprite` macro is so you don't have to subtract `SPRITE_VARS` in every single line; it's based on the definition of `variablesprite` in [macros/scripts/events.asm](../blob/master/macros/scripts/events.asm).)
There are 136 entries in these three tables, so that saves 136 bytes because they don't all start with script commands. We spend five bytes on the `-1`s to mark the end of each table (`dw -1` is two bytes), so if the code to process these tables takes fewer than 131 bytes, we'll have saved space overall.
## 2. Process the data tables to initialize everything
Create **engine/events/init_events.asm**:
```diff
+InitializeEvents:
+; initialize events
+ ld hl, InitialEvents
+.events_loop
+ ld a, [hli]
+ ld e, a
+ ld a, [hli]
+ ld d, a
+ and e
+ cp -1
+ jr z, .events_done
+ ld b, SET_FLAG
+ push hl
+ call EventFlagAction
+ pop hl
+ jr .events_loop
+.events_done
+
+; initialize engine flags
+ ld hl, InitialEngineFlags
+.flags_loop
+ ld a, [hli]
+ ld e, a
+ ld a, [hli]
+ ld d, a
+ and e
+ cp -1
+ jr z, .flags_done
+ ld b, SET_FLAG
+ push hl
+ farcall EngineFlagAction
+ pop hl
+ jr .flags_loop
+.flags_done
+
+; initialize variable sprites
+ ld hl, InitialVariableSprites
+.sprites_loop
+ ld a, [hli]
+ ld e, a
+ ld d, 0
+ cp -1
+ ret z
+ ld a, [hli]
+ push hl
+ ld hl, wVariableSprites
+ add hl, de
+ ld [hl], a
+ pop hl
+ jr .sprites_loop
+
+INCLUDE "data/events/init_events.asm"
```
This defines the `InitializeEvents` assembly function. It has three main loops, one for each table. They're all based on the definitions of the original script commands (`setevent`, `setflag`, or `variablesprite`) from [engine/overworld/scripting.asm](../blob/master/engine/overworld/scripting.asm).
Then edit [engine/menus/intro_menu.asm](../blob/master/engine/menus/intro_menu.asm):
```diff
InitializeWorld:
call ShrinkPlayer
farcall SpawnPlayer
farcall _InitializeStartDay
+ farcall InitializeEvents
ret
```
And edit [main.asm](../blob/master/main.asm):
```diff
SECTION "Phone Scripts 2", ROMX
INCLUDE "engine/events/std_scripts.asm"
+INCLUDE "engine/events/init_events.asm"
...
```
The code uses 73 bytes—67 to define `InitializeEvents` and 6 to `farcall` it—which means we've saved 58 bytes so far. But we're about to save more, since the old system of calling `InitializeEventsScript` needs to go.
## 3. Remove the scripts that initialized everything
Edit [engine/events/std_scripts.asm](../blob/master/engine/events/std_scripts.asm):
```diff
StdScripts::
...
- add_stdscript InitializeEventsScript
...
```
```diff
-InitializeEventsScript:
- setevent EVENT_EARLS_ACADEMY_EARL
- ...
- setevent EVENT_INITIALIZED_EVENTS
- endcallback
```
And edit [maps/PlayersHouse2F.asm](../blob/master/maps/PlayersHouse2F.asm):
```diff
.InitializeRoom:
special ToggleDecorationsVisibility
setevent EVENT_TEMPORARY_UNTIL_MAP_RELOAD_8
- checkevent EVENT_INITIALIZED_EVENTS
- iftrue .SkipInitialization
- jumpstd InitializeEventsScript
- endcallback
-
-.SkipInitialization:
endcallback
```
Now we have a system for initializing the game that's independent of the starting map, easy to find with the rest of the game data, and uses less space.
Let's see how much less. The [tools/free_space.py](../blob/master/tools/free_space.py) script exists to measure that, based on the .map file produced when you `make` the ROM. So run it before this tutorial:
```
$ tools/free_space.py pokecrystal.map
Free space: 455223/2097152 (21.71%)
```
And again after the tutorial:
```
$ tools/free_space.py pokecrystal.map
Free space: 455295/2097152 (21.71%)
```
That's 72 more ROM bytes than before. It's not a whole lot, but every bit helps.
You can eke out a few more by applying the tricks from the [assembly optimization tutorial](Optimizing-assembly-code) to `InitializeEvents`: it was written more for clarity than to save space. For example, all three `cp -1` can become `inc a` (the "[Compare `a` to 255](Optimizing-assembly-code#compare-a-to-255)" technique) to save three bytes *and* run a little faster. And you can optimize the entire `.sprites_loop` using the "[Add `a` to an address](Optimizing-assembly-code#add-a-to-an-address)" technique:
```diff
.sprites_loop
ld a, [hli]
- ld e, a
- ld d, 0
- cp -1
+ inc a
ret z
+ ; subtract 1 to balance the previous 'inc'
+ add LOW(wVariableSprites) - 1
+ ld e, a
+ adc HIGH(wVariableSprites)
+ sub e
+ ld d, a
ld a, [hli]
- push hl
- ld hl, wVariableSprites
- add hl, de
- ld [hl], a
- pop hl
+ ld [de], a
jr .sprites_loop
```
|