summaryrefslogtreecommitdiff
path: root/Add-a-new-Unown-form.md
blob: ba339af41529c0ada85a2647857e1ad008d769dd (plain)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
This tutorial is for how to add a new Unown form. As an example, we'll add the **!** and **?** forms introduced in Gen 3.


## Contents

1. [Define a Unown form constant](#1-define-a-unown-form-constant)
2. [Design its sprites and animation](#2-design-its-sprites-and-animation)
3. [Include and point to the sprite and animation data](#3-include-and-point-to-the-sprite-and-animation-data)
4. [Change how DVs determine forms to make them all available](#4-change-how-dvs-determine-forms-to-make-them-all-available)
5. [Make room for it in the Pokédex WRAM](#5-make-room-for-it-in-the-pokédex-wram)
6. [Create a Unown font character for the new form](#6-create-a-unown-font-character-for-the-new-form)
7. [Define its Unown Mode word](#7-define-its-unown-mode-word)
8. [Update Unown Mode to make room for the new form](#8-update-unown-mode-to-make-room-for-the-new-form)
9. [Allow the new form to be unlocked in the wild](#9-allow-the-new-form-to-be-unlocked-in-the-wild)
10. [Correct the Research Center computer](#10-correct-the-research-center-computer)
11. [Fix bank overflow errors](#11-fix-bank-overflow-errors)


## 1. Define a Unown form constant

Edit [constants/pokemon_constants.asm](../blob/master/constants/pokemon_constants.asm):

```diff
 ; Unown forms
 ; indexes for:
 ; - UnownWords (see data/pokemon/unown_words.asm)
 ; - UnownPicPointers (see data/pokemon/unown_pic_pointers.asm)
 ; - UnownAnimationPointers (see gfx/pokemon/unown_anim_pointers.asm)
 ; - UnownAnimationIdlePointers (see gfx/pokemon/unown_idle_pointers.asm)
 ; - UnownBitmasksPointers (see gfx/pokemon/unown_bitmask_pointers.asm)
 ; - UnownFramesPointers (see gfx/pokemon/unown_frame_pointers.asm)
 	const_def 1
 	const UNOWN_A ;  1
 	...
 	const UNOWN_Z ; 26
+	const UNOWN_EXCLAMATION
+	const UNOWN_QUESTION
 NUM_UNOWN EQU const_value + -1 ; 26
```


## 2. Design its sprites and animation

Create **gfx/pokemon/unown_exclamation/front.png**:

![gfx/pokemon/unown_exclamation/front.png](screenshots/gfx-pokemon-unown_exclamation-front.png)

And **gfx/pokemon/unown_exclamation/back.png**:

![gfx/pokemon/unown_exclamation/back.png](screenshots/gfx-pokemon-unown_exclamation-back.png)

Then create **gfx/pokemon/unown_question/front.png**:

![gfx/pokemon/unown_question/front.png](screenshots/gfx-pokemon-unown_question-front.png)

And **gfx/pokemon/unown_question/back.png**:

![gfx/pokemon/unown_question/back.png](screenshots/gfx-pokemon-unown_question-back.png)

front.png is a vertical strip of unique animation frames. Frames are all the same size, 40x40 pixels, since that's the size of all the other Unown forms. back.png is always 48x48. Both sprites have to use the same four colors: white, black, and the same two hues as every other Unown.

Now create **gfx/pokemon/unown_exclamation/anim.asm**:

```diff
+	frame 0, 09
+	frame 1, 09
+	frame 2, 09
+	frame 3, 05
+	frame 2, 05
+	frame 4, 05
+	frame 1, 05
+	frame 1, 05
+	endanim
```

And **gfx/pokemon/unown_exclamation/anim_idle.asm**:

```diff
+	frame 0, 18
+	setrepeat 2
+	frame 5, 05
+	frame 0, 05
+	dorepeat 2
+	endanim
```

Then create **gfx/pokemon/unown_question/anim.asm**:

```diff
+	frame 0, 09
+	setrepeat 3
+	frame 1, 05
+	frame 2, 05
+	frame 1, 05
+	frame 0, 05
+	dorepeat 2
+	endanim
```

And finally **gfx/pokemon/unown_question/anim_idle.asm**:

```diff
+	frame 0, 13
+	setrepeat 2
+	frame 3, 05
+	frame 0, 05
+	dorepeat 2
+	endanim
```

anim.asm defines the main sprite animation sequence; anim_idle.asm defines an extra one that gets played in some contexts (notably *not* when a Pokémon is encountered in battle). A full description of the animation script commands is at [docs/pic_animations.md](../blob/master/docs/pic_animations.md).

These animations were designed by SCMidna. But you're more likely to have a single static front sprite than a whole animated sequence of frames. In that case, you can save the one sprite as front.png (so it will be a single square frame, not a vertical strip of them), and just put `endanim` as the full contents of anim.asm and anim_idle.asm.

When you `make` the ROM, a number of sprite-related files will be automatically generated for you. For more information on that, see the tutorial for [how to add a new Pokémon](Add-a-new-Pokémon#10-design-its-sprites-and-animation).


## 3. Include and point to the sprite and animation data

Edit [data/pokemon/unown_pic_pointers.asm](../blob/master/data/pokemon/unown_pic_pointers.asm):

```diff
 UnownPicPointers::
 ; entries correspond to Unown letters, two apiece
 	dba_pic UnownAFrontpic
 	dba_pic UnownABackpic
 	...
 	dba_pic UnownZFrontpic
 	dba_pic UnownZBackpic
+	dba_pic UnownExclamationFrontpic
+	dba_pic UnownExclamationBackpic
+	dba_pic UnownQuestionFrontpic
+	dba_pic UnownQuestionBackpic
```

We have to use `dba_pic` here instead of a standard `dba`—declaring the bank and address of each label—because of [this design flaw](../blob/master/docs/design_flaws.md#pic-banks-are-offset-by-pics_fix). I strongly recommend removing the whole `FixPicBank` routine from [engine/gfx/load_pics.asm](../blob/master/engine/gfx/load_pics.asm), including all four calls to it in that file, and just using `dba` here; then you'll be able to `INCBIN` sprites in arbitrary banks.

Edit [gfx/pics.asm](../blob/master/gfx/pics.asm):

```diff
 SECTION "Pics 19", ROMX

-; Seems to be an accidental copy of the previous bank
-
-INCBIN "gfx/pokemon/spinarak/back.2bpp.lz"
-...
-INCBIN "gfx/pokemon/unown_r/back.2bpp.lz"
+UnownExclamationFrontpic: INCBIN "gfx/pokemon/unown_exclamation/front.animated.2bpp.lz"
+UnownExclamationBackpic:  INCBIN "gfx/pokemon/unown_exclamation/back.2bpp.lz"
+UnownQuestionFrontpic: INCBIN "gfx/pokemon/unown_question/front.animated.2bpp.lz"
+UnownQuestionBackpic:  INCBIN "gfx/pokemon/unown_question/back.2bpp.lz"
```

(If you *don't* fix the `dba_pic` design flaw, you'll have to put your sprites in the "Pics *N*" sections, which are compatible with `dba_pic`. "Pics 19" isn't used for anything useful—all its contents are unused duplicates of "Pics 18"—and it has a whole bank to itself, so it's the easiest place to start adding new sprites. (The other sections, includng "Pics 20" through "Pics 24", have limited remaining space since they already contain some sprites and/or share their banks with other sections.) But if you have a lot of new sprites to add, you risk overflowing the banks, and it's hard to fit sprites within fixed bank limits. By using just `dba`, you can create new sections with a few sprites each, that will automatically be placed wherever they can fit in the ROM.)

Anyway, edit [gfx/pokemon/unown_anim_pointers.asm](../blob/master/gfx/pokemon/unown_anim_pointers.asm):

```diff
 UnownAnimationPointers:
 	dw UnownAAnimation
 	...
 	dw UnownZAnimation
+	dw UnownExclamationAnimation
+	dw UnownQuestionAnimation
```

Edit [gfx/pokemon/unown_anims.asm](../blob/master/gfx/pokemon/unown_anims.asm):

```diff
 UnownAnimations: ; used only for BANK(UnownAnimations)

 UnownAAnimation: INCLUDE "gfx/pokemon/unown_a/anim.asm"
 ...
 UnownZAnimation: INCLUDE "gfx/pokemon/unown_z/anim.asm"
+UnownExclamationAnimation: INCLUDE "gfx/pokemon/unown_exclamation/anim.asm"
+UnownQuestionAnimation: INCLUDE "gfx/pokemon/unown_question/anim.asm"
```

Edit [gfx/pokemon/unown_idle_pointers.asm](../blob/master/gfx/pokemon/unown_idle_pointers.asm):

```diff
 UnownAnimationIdlePointers:
 	dw UnownAAnimationIdle
 	...
 	dw UnownZAnimationIdle
+	dw UnownExclamationAnimationIdle
+	dw UnownQuestionAnimationIdle
```

Edit [gfx/pokemon/unown_idles.asm](../blob/master/gfx/pokemon/unown_idles.asm):

```diff
 UnownAAnimationIdle: INCLUDE "gfx/pokemon/unown_a/anim_idle.asm"
 ...
 UnownZAnimationIdle: INCLUDE "gfx/pokemon/unown_z/anim_idle.asm"
+UnownExclamationAnimationIdle: INCLUDE "gfx/pokemon/unown_exclamation/anim_idle.asm"
+UnownQuestionAnimationIdle: INCLUDE "gfx/pokemon/unown_question/anim_idle.asm"
```

Edit [gfx/pokemon/unown_bitmask_pointers.asm](../blob/master/gfx/pokemon/unown_bitmask_pointers.asm):

```diff
 UnownBitmasksPointers:
 	dw UnownABitmasks
 	...
 	dw UnownZBitmasks
+	dw UnownExclamationBitmasks
+	dw UnownQuestionBitmasks
```

Edit [gfx/pokemon/unown_bitmasks.asm](../blob/master/gfx/pokemon/unown_bitmasks.asm):

```diff
 UnownABitmasks: INCLUDE "gfx/pokemon/unown_a/bitmask.asm"
 ...
 UnownZBitmasks: INCLUDE "gfx/pokemon/unown_z/bitmask.asm"
+UnownExclamationBitmasks: INCLUDE "gfx/pokemon/unown_exclamation/bitmask.asm"
+UnownQuestionBitmasks: INCLUDE "gfx/pokemon/unown_question/bitmask.asm"
```

Edit [gfx/pokemon/unown_frame_pointers.asm](../blob/master/gfx/pokemon/unown_frame_pointers.asm):

```diff
 UnownFramesPointers:
 	dw UnownAFrames
 	...
 	dw UnownZFrames
+	dw UnownExclamationFrames
+	dw UnownQuestionFrames
```

Finally, edit [gfx/pokemon/unown_frames.asm](../blob/master/gfx/pokemon/unown_frames.asm):

```diff
 UnownsFrames: ; used only for BANK(UnownsFrames)

 UnownAFrames: INCLUDE "gfx/pokemon/unown_a/frames.asm"
 ...
 UnownZFrames: INCLUDE "gfx/pokemon/unown_z/frames.asm"
+UnownExclamationFrames: INCLUDE "gfx/pokemon/unown_exclamation/frames.asm"
+UnownQuestionFrames: INCLUDE "gfx/pokemon/unown_question/frames.asm"
```


## 4. Change how DVs determine forms to make them all available

Edit [engine/gfx/load_pics.asm](../blob/master/engine/gfx/load_pics.asm):

```diff
 GetUnownLetter:
 ; Return Unown letter in wUnownLetter based on DVs at hl

 ; Take the middle 2 bits of each DV and place them in order:
 ;	atk  def  spd  spc
 ;	.ww..xx.  .yy..zz.

 	; atk
 	ld a, [hl]
 	and %01100000
 	sla a
 	ld b, a
 	; def
 	ld a, [hli]
 	and %00000110
 	swap a
 	srl a
 	or b
 	ld b, a

 	; spd
 	ld a, [hl]
 	and %01100000
 	swap a
 	sla a
 	or b
 	ld b, a
 	; spc
 	ld a, [hl]
 	and %00000110
 	srl a
 	or b

-; Divide by 10 to get 0-25
+; Divide by 9 to get 0-28
 	ldh [hDividend + 3], a
 	xor a
 	ldh [hDividend], a
 	ldh [hDividend + 1], a
 	ldh [hDividend + 2], a
-	ld a, $ff / NUM_UNOWN + 1
+	ld a, 9
 	ldh [hDivisor], a
 	ld b, 4
 	call Divide

-; Increment to get 1-26
+; Increment to get 1-29
 	ldh a, [hQuotient + 3]
 	inc a
+; The valid range is 1-28, so use UNOWN_E (5) instead of 29
+	cp NUM_UNOWN + 1
+	jr c, .valid
+	ld a, UNOWN_E
+.valid
 	ld [wUnownLetter], a
 	ret
```

The comments in `GetUnownLetter` already describe how it works; as you can see, we've changed it to allow 28 possible values instead of 29. Before, Unown A to Y each had a 10/256 chance of appearing, and Unown Z only had a 6/256 chance (assuming wild DVs are perfectly random). Now, Unown A to Z, as well as **!** and **?**, all have a 9/256 chance of appearing—except for Unown E, which has a 13/256 chance because the routine defaults to it for the invalid 29th Unown.

Of course, the choice to give Unown E a boost is arbitrary. I picked it because E is the most common English letter; you could pick a different one, or try distributing the invalid cases more fairly to give four different forms a 10/256 chance; or completely revamp how forms are determined. The important thing is that every form needs to be available, and invalid forms should be impossible.


## 5. Make room for it in the Pokédex WRAM

Edit [wram.asm](../blob/master/wram.asm):

```diff
-	ds 22
+	ds 20

 wPokedexCaught:: flag_array NUM_POKEMON ; de99
 wEndPokedexCaught::

 wPokedexSeen:: flag_array NUM_POKEMON ; deb9
 wEndPokedexSeen::

 wUnownDex:: ds NUM_UNOWN ; ded9
 wUnlockedUnowns:: db ; def3
 wFirstUnownSeen:: db
```

This WRAM bank is very close to being full. Since `wUnownDex` has grown by 2 bytes, we need to remove 2 bytes elsewhere. Luckily there are 22 unused bytes nearby.


## 6. Create a Unown font character for the new form

Edit [gfx/font/unown_font.png](../blob/master/gfx/font/unown_font.png):

![gfx/font/unown_font.png](screenshots/gfx-font-unown_font.png)

We've added the **!** and **?** characters after Z and before **◆**. This font gets used by the Unown Mode for the Pokédex.


## 7. Define its Unown Mode word

Edit [data/pokemon/unown_words.asm](../blob/master/data/pokemon/unown_words.asm):

```diff
 UnownWords:
 ; entries correspond to UNOWN_* form constants
 	dw UnownWordA
 	...
 	dw UnownWordZ
+	dw UnownWordExclamation
+	dw UnownWordQuestion

 UnownWordA: unownword "ANGRY"
 ...
 UnownWordZ: unownword "ZOOM"
+UnownWordExclamation: unownword "(((((" ; "!!!!!" since "Z" + 1 == "("
+UnownWordQuestion: unownword ")))))" ; "?????" since "Z" + 2 == ")"
```

The valid characters here correspond to the ones in [gfx/font/unown_font.png](../blob/master/gfx/font/unown_font.png): A to Z, plus the new **!** and **?**. You could also use `":"` (`"Z"` + 3) for **◆**, but that's meant to be the cursor, not for words.

"!!!!!" and "?????" are the words from the Unown Report in HG/SS.


## 8. Update Unown Mode to make room for the new form

This is the default Unown Mode Pokédex:

![Screenshot](screenshots/pokedex-unown-mode.png)

It has room to add one more Unown form in the bottom-right, but for two, we'll also have to widen it a little.

Edit [engine/pokedex/pokedex.asm](../blob/master/engine/pokedex/pokedex.asm):

```diff
 Pokedex_UnownModePlaceCursor:
 	ld a, [wDexCurUnownIndex]
-	ld c, $5a ; diamond cursor
+	ld c, $5c ; diamond cursor

...

 Pokedex_DrawUnownModeBG:
 	call Pokedex_FillBackgroundColor2
 	hlcoord 2, 1
-	lb bc, 10, 13
+	lb bc, 10, 14
 	call Pokedex_PlaceBorder
 	hlcoord 2, 14
-	lb bc, 1, 13
+	lb bc, 1, 14
 	call Pokedex_PlaceBorder
 	hlcoord 2, 15
 	ld [hl], $3d
-	hlcoord 16, 15
+	hlcoord 17, 15
 	ld [hl], $3e
 	hlcoord 6, 5
 	call Pokedex_PlaceFrontpicAtHL
 	ld de, 0
 	ld b, 0
-	ld c, 26
+	ld c, NUM_UNOWN
 .loop
 	ld hl, wUnownDex
 	add hl, de
 	ld a, [hl]
 	and a
 	jr z, .done
 	push af
 	ld hl, UnownModeLetterAndCursorCoords
 rept 4
 	add hl, de
 endr
 	ld a, [hli]
 	ld h, [hl]
 	ld l, a
 	pop af
 	add $40 - 1 ; Unown A
 	ld [hl], a
 	inc de
 	inc b
 	dec c
 	jr nz, .loop
 .done
 	ld a, b
 	ld [wDexUnownCount], a
 	ret

 UnownModeLetterAndCursorCoords:
 ; entries correspond to Unown forms
 ;           letter, cursor
 	dwcoord   4,11,   3,11 ; A
 	dwcoord   4,10,   3,10 ; B
 	dwcoord   4, 9,   3, 9 ; C
 	dwcoord   4, 8,   3, 8 ; D
 	dwcoord   4, 7,   3, 7 ; E
 	dwcoord   4, 6,   3, 6 ; F
 	dwcoord   4, 5,   3, 5 ; G
 	dwcoord   4, 4,   3, 4 ; H
 	dwcoord   4, 3,   3, 2 ; I
 	dwcoord   5, 3,   5, 2 ; J
 	dwcoord   6, 3,   6, 2 ; K
 	dwcoord   7, 3,   7, 2 ; L
 	dwcoord   8, 3,   8, 2 ; M
 	dwcoord   9, 3,   9, 2 ; N
 	dwcoord  10, 3,  10, 2 ; O
 	dwcoord  11, 3,  11, 2 ; P
 	dwcoord  12, 3,  12, 2 ; Q
 	dwcoord  13, 3,  13, 2 ; R
-	dwcoord  14, 3,  15, 2 ; S
-	dwcoord  14, 4,  15, 4 ; T
-	dwcoord  14, 5,  15, 5 ; U
-	dwcoord  14, 6,  15, 6 ; V
-	dwcoord  14, 7,  15, 7 ; W
-	dwcoord  14, 8,  15, 8 ; X
-	dwcoord  14, 9,  15, 9 ; Y
-	dwcoord  14,10,  15,10 ; Z
+	dwcoord  14, 3,  14, 2 ; S
+	dwcoord  15, 3,  16, 2 ; T
+	dwcoord  15, 4,  16, 4 ; U
+	dwcoord  15, 5,  16, 5 ; V
+	dwcoord  15, 6,  16, 6 ; W
+	dwcoord  15, 7,  16, 7 ; X
+	dwcoord  15, 8,  16, 8 ; Y
+	dwcoord  15, 9,  16, 9 ; Z
+	dwcoord  15,10,  16,10 ; !
+	dwcoord  15,11,  16,11 ; ?

...

 Pokedex_LoadUnownFont:
 	ld a, BANK(sScratch)
 	call OpenSRAM
 	ld hl, UnownFont
 	ld de, sScratch + $188
 	ld bc, 39 tiles
 	ld a, BANK(UnownFont)
 	call FarCopyBytes
 	ld hl, sScratch + $188
-	ld bc, 27 tiles
+	ld bc, (NUM_UNOWN + 1) tiles
 	call Pokedex_InvertTiles
 	ld de, sScratch + $188
 	ld hl, vTiles2 tile $40
-	lb bc, BANK(Pokedex_LoadUnownFont), 27
+	lb bc, BANK(Pokedex_LoadUnownFont), NUM_UNOWN + 1
 	call Request2bpp
 	call CloseSRAM
 	ret
```

First of all, notice that we had to add 2 to the value of the "diamond cursor" tile because we added two characters in front of it in [gfx/font/unown_font.png](../blob/master/gfx/font/unown_font.png).

As for `Pokedex_DrawUnownModeBG`, our edits do three things:

- one, widen the interface;
- two, define coordinates for the letter and cursor at new positions for **!** and **?** (and adjust the positions for S to Z to accomodate the wider interface);
- three, change some hard-coded constants to depend on `NUM_UNOWN` (this was already done in pokecrystal as of July 19, 2018).


## 9. Allow the new form to be unlocked in the wild

There are four chambers in the Ruins of Alph with puzzles of Kabuto, Omanyte, Aerodactyl, and Ho-Oh; solving each one unlocks a set of more Unown forms to be available in the wild. You could simply add the new forms to one of their four sets, but this step will show you how to add a fifth set.

Edit [constants/engine_flags.asm](../blob/master/constants/engine_flags.asm):

```diff
 ; wUnlockedUnowns
 	const ENGINE_UNLOCKED_UNOWNS_A_TO_K
 	const ENGINE_UNLOCKED_UNOWNS_L_TO_R
 	const ENGINE_UNLOCKED_UNOWNS_S_TO_W
 	const ENGINE_UNLOCKED_UNOWNS_X_TO_Z
-	const ENGINE_UNLOCKED_UNOWNS_UNUSED_4
+	const ENGINE_UNLOCKED_UNOWNS_EXCLAMATION_QUESTION
 	const ENGINE_UNLOCKED_UNOWNS_UNUSED_5 ; 30
 	const ENGINE_UNLOCKED_UNOWNS_UNUSED_6
 	const ENGINE_UNLOCKED_UNOWNS_UNUSED_7
```

Edit [data/engine_flags.asm](../blob/master/data/engine_flags.asm):

```diff
 	; unown sets (see data/wild/unlocked_unowns.asm)
 	engine_flag wUnlockedUnowns, 0 ; A-K
 	engine_flag wUnlockedUnowns, 1 ; L-R
 	engine_flag wUnlockedUnowns, 2 ; S-W
 	engine_flag wUnlockedUnowns, 3 ; X-Z
-	engine_flag wUnlockedUnowns, 4 ; unused
+	engine_flag wUnlockedUnowns, 4 ; !-?
 	engine_flag wUnlockedUnowns, 5 ; unused ; $30
 	engine_flag wUnlockedUnowns, 6 ; unused
 	engine_flag wUnlockedUnowns, 7 ; unused
```

Edit [data/wild/unlocked_unowns.asm](../blob/master/data/wild/unlocked_unowns.asm):

```diff
 UnlockedUnownLetterSets:
 ; entries correspond to wUnlockedUnowns bits
 	dw .Set_A_K ; ENGINE_UNLOCKED_UNOWNS_A_TO_K
 	dw .Set_L_R ; ENGINE_UNLOCKED_UNOWNS_L_TO_R
 	dw .Set_S_W ; ENGINE_UNLOCKED_UNOWNS_S_TO_W
 	dw .Set_X_Z ; ENGINE_UNLOCKED_UNOWNS_X_TO_Z
+	dw .Set_Exclamation_Question ; ENGINE_UNLOCKED_UNOWNS_EXCLAMATION_QUESTION
 .End

 .Set_A_K:
 	unown_set A, B, C, D, E, F, G, H, I, J, K
 .Set_L_R:
 	unown_set L, M, N, O, P, Q, R
 .Set_S_W:
 	unown_set S, T, U, V, W
 .Set_X_Z:
 	unown_set X, Y, Z
+.Set_Exclamation_Question:
+	unown_set EXCLAMATION, QUESTION
```

Finally, edit [maps/RuinsOfAlphInnerChamber.asm](../blob/master/maps/RuinsOfAlphInnerChamber.asm):

```diff
 RuinsOfAlphInnerChamberStatue:
+	checkflag ENGINE_UNLOCKED_UNOWNS_EXCLAMATION_QUESTION
+	iftrue .already_unlocked
+	readvar VAR_UNOWNCOUNT
+	ifless 26, .dont_unlock
+	opentext
+	writetext RuinsOfAlphInnerChamberStatueText
+	waitbutton
+	writetext RuinsOfAlphInnerChamberStatueUnlockText
+	waitbutton
+	closetext
+	pause 30
+	earthquake 30
+	showemote EMOTE_SHOCK, PLAYER, 20
+	pause 30
+	playsound SFX_STRENGTH
+	earthquake 50
+	setflag ENGINE_UNLOCKED_UNOWNS_EXCLAMATION_QUESTION
+	jumptext RuinsOfAlphStrangePresenceText
+
+.already_unlocked
+.dont_unlock
 	jumptext RuinsOfAlphInnerChamberStatueText

 RuinsOfAlphStrangePresenceText:
 	text "There is a strange"
 	line "presence here…"
 	done

 ...

 RuinsOfAlphInnerChamberStatueText:
 	text "It's a replica of"
 	line "an ancient #-"
 	cont "MON."
 	done
+
+RuinsOfAlphInnerChamberStatueUnlockText:
+	text "…The statue is"
+	line "shaking!"
+	done
```

The simple method would be to just edit [data/wild/unlocked_unowns.asm](../blob/master/data/wild/unlocked_unowns.asm) and add `EXCLAMATION` and `QUESTION` to one of the four existing sets. This demonstrates which other files need editing to add a fifth set.

We've also invented an event in the Ruins of Alph: if you talk to one of the statues after catching 26 different Unown, there will be an earthquake and the final set will be unlocked. (I copied the earthquake code from the wall-opening script in [maps/RuinsOfAlphKabutoChamber.asm](../blob/master/maps/RuinsOfAlphKabutoChamber.asm).)


## 10. Correct the Research Center computer

Edit [maps/RuinsOfAlphResearchCenter.asm](../blob/master/maps/RuinsOfAlphResearchCenter.asm):

```diff
 RuinsOfAlphResearchCenterComputerText_GotAllUnown:
 	text "Mystery #MON"
 	line "Name: UNOWN"

-	para "A total of 26"
+	para "A total of 28"
 	line "kinds found."
 	done
```


## 11. Fix bank overflow errors

We're done adding the Unown forms, but `make` won't compile the ROM:

```
error: Unable to place 'Pics 2' (ROMX section) at $40A8 in bank $49
```

But we didn't change anything in "Pics 2", so why is this happening?

As defined in [pokecrystal.link](../blob/master/pokecrystal.link), the "Unown Pic Pointers" and "Pics 2" sections are both in bank $49:

```
ROMX $49
	org $4000
	"Unown Pic Pointers"
	"Pics 2"
```

It turns out that adding the new `dba_pic`s to the `UnownPicPointers` table was a little too much data for that bank. To fix this, we'll need to move some data out of bank $49. "Unown Pic Pointers" has to be as large as it is, but "Pics 2" is an arbitrary set of sprites, so we can move one of those.

Edit [gfx/pics.asm](../blob/master/gfx/pics.asm) again:

```diff
 SECTION "Pics 2", ROMX
 
 BlastoiseFrontpic:   INCBIN "gfx/pokemon/blastoise/front.animated.2bpp.lz"
 RapidashFrontpic:    INCBIN "gfx/pokemon/rapidash/front.animated.2bpp.lz"
 MeganiumFrontpic:    INCBIN "gfx/pokemon/meganium/front.animated.2bpp.lz"
 NidoqueenFrontpic:   INCBIN "gfx/pokemon/nidoqueen/front.animated.2bpp.lz"
 HitmonleeFrontpic:   INCBIN "gfx/pokemon/hitmonlee/front.animated.2bpp.lz"
 ScizorFrontpic:      INCBIN "gfx/pokemon/scizor/front.animated.2bpp.lz"
 BeedrillFrontpic:    INCBIN "gfx/pokemon/beedrill/front.animated.2bpp.lz"
 ArcanineFrontpic:    INCBIN "gfx/pokemon/arcanine/front.animated.2bpp.lz"
 TyranitarFrontpic:   INCBIN "gfx/pokemon/tyranitar/front.animated.2bpp.lz"
 MoltresFrontpic:     INCBIN "gfx/pokemon/moltres/front.animated.2bpp.lz"
 ZapdosFrontpic:      INCBIN "gfx/pokemon/zapdos/front.animated.2bpp.lz"
 ArbokFrontpic:       INCBIN "gfx/pokemon/arbok/front.animated.2bpp.lz"
 MewtwoFrontpic:      INCBIN "gfx/pokemon/mewtwo/front.animated.2bpp.lz"
 FearowFrontpic:      INCBIN "gfx/pokemon/fearow/front.animated.2bpp.lz"
 CharizardFrontpic:   INCBIN "gfx/pokemon/charizard/front.animated.2bpp.lz"
-QuilavaFrontpic:     INCBIN "gfx/pokemon/quilava/front.animated.2bpp.lz"
 
 ...
 
 SECTION "Pics 19", ROMX
 
 UnownExclamationFrontpic: INCBIN "gfx/pokemon/unown_exclamation/front.animated.2bpp.lz"
 UnownExclamationBackpic:  INCBIN "gfx/pokemon/unown_exclamation/back.2bpp.lz"
 UnownQuestionFrontpic: INCBIN "gfx/pokemon/unown_question/front.animated.2bpp.lz"
 UnownQuestionBackpic:  INCBIN "gfx/pokemon/unown_question/back.2bpp.lz"
+QuilavaFrontpic:     INCBIN "gfx/pokemon/quilava/front.animated.2bpp.lz"
```

Now we're done!

![Screenshot](screenshots/unown-exclamation-question.png)

You can add even more than 28 Unown forms, but the Unown mode interface will need redesigning to accomodate them. For example, here's a version that fits 33 forms:

![Screenshot](screenshots/greek-unown.png)

All you have to do is:

- Edit `Pokedex_DrawUnownModeBG` in [engine/pokedex/pokedex.asm](../blob/master/engine/pokedex/pokedex.asm) to change where the UI elements are drawn, and
- Edit `_CGB_PokedexUnownMode` in [engine/gfx/cgb_layouts.asm](../blob/master/engine/gfx/cgb_layouts.asm) to align the Unown sprite's palette with its new position (if that changed).