summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRangi <remy.oukaour+rangi42@gmail.com>2018-07-18 12:50:20 -0400
committerRangi <remy.oukaour+rangi42@gmail.com>2018-07-18 12:50:20 -0400
commit205977761ab5a604eb4d9b5a149388fb71ffbdf2 (patch)
tree69ac4469f51b553bc006ffb90670fc1b921c6f64
parent9768ee22296951f4b05151d6d96accb199b89ef1 (diff)
Add a new Unown form
-rw-r--r--Add-a-new-Unown-form.md662
-rw-r--r--Add-a-new-trainer-class.md2
-rw-r--r--Tutorials.md2
-rw-r--r--screenshots/gfx-font-unown_font.pngbin0 -> 312 bytes
-rw-r--r--screenshots/gfx-pokemon-unown_exclamation-back.pngbin0 -> 209 bytes
-rw-r--r--screenshots/gfx-pokemon-unown_exclamation-front.pngbin0 -> 1570 bytes
-rw-r--r--screenshots/gfx-pokemon-unown_question-back.pngbin0 -> 234 bytes
-rw-r--r--screenshots/gfx-pokemon-unown_question-front.pngbin0 -> 1285 bytes
-rw-r--r--screenshots/pokedex-unown-mode.pngbin0 -> 675 bytes
-rw-r--r--screenshots/unown-exclamation-question.pngbin0 -> 6020 bytes
10 files changed, 664 insertions, 2 deletions
diff --git a/Add-a-new-Unown-form.md b/Add-a-new-Unown-form.md
new file mode 100644
index 0000000..49594d0
--- /dev/null
+++ b/Add-a-new-Unown-form.md
@@ -0,0 +1,662 @@
+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
+ ld [hDividend + 3], a
+ xor a
+ ld [hDividend], a
+ ld [hDividend + 1], a
+ ld [hDividend + 2], a
+- ld a, $ff / NUM_UNOWN + 1
++ ld a, 9
+ ld [hDivisor], a
+ ld b, 4
+ call Divide
+
+-; Increment to get 1-26
++; Increment to get 1-29
+ ld a, [hQuotient + 2]
+ 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
+```
+
+Since `wUnownDex` has grown by 2 bytes, we need to remove 2 bytes elsewhere, since their WRAM bank is very close to being full. 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, [wDexCurrentUnownIndex]
+- 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 ; ?
+```
+
+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 two things: one, widen the interface; and two, define coordinates for the letter and cursor at new positions for **!** and **?**. (We also had to adjust the positions for S to Z to accomodate the wider interface.)
+
+
+## 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
++ checkcode 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)
diff --git a/Add-a-new-trainer-class.md b/Add-a-new-trainer-class.md
index da1eabd..4ffefb1 100644
--- a/Add-a-new-trainer-class.md
+++ b/Add-a-new-trainer-class.md
@@ -361,7 +361,7 @@ ROMX $4a
"Pics 3"
```
-It turns out that adding `dba_pic ParasolLadyPic` to the `TrainerPicPointers` table was a little too much data for that bank. To fix this, we'll need to move some data our of bank $4A. "Trainer Pic Pointers" has to be as large as it is, but "Pics 3" is an arbitrary set of sprites, so we can move one of those.
+It turns out that adding `dba_pic ParasolLadyPic` to the `TrainerPicPointers` table was a little too much data for that bank. To fix this, we'll need to move some data out of bank $4A. "Trainer Pic Pointers" has to be as large as it is, but "Pics 3" is an arbitrary set of sprites, so we can move one of those.
Edit [gfx/pics.asm](../blob/master/gfx/pics.asm) again:
diff --git a/Tutorials.md b/Tutorials.md
index f82c077..e0c28e6 100644
--- a/Tutorials.md
+++ b/Tutorials.md
@@ -26,6 +26,7 @@ Tutorials may use diff syntax to show edits:
- [Pack pocket](Add-a-new-Pack-pocket)
- [Radio channel](Add-a-new-radio-channel)
- [Wild Pokémon slot](Add-a-new-wild-Pokémon-slot)
+- [Unown form](Add-a-new-wild-Unown-form)
- [Fishing rod](Add-a-new-fishing-rod)
**How to edit the…**
@@ -68,7 +69,6 @@ Tutorials may use diff syntax to show edits:
- Rock Smash (demonstrating how HMS themselves work, since Rock Smash already exists)
- Dive (exists in [Orange](https://github.com/PiaCarrot/pokeorange))
- Rock Climb (exists in [Orange](https://github.com/PiaCarrot/pokeorange))
-- Two more Unown forms
- Evolution methods (location, held item, move, [etc](https://gitgud.io/pfero/axyllagame/commit/81914d43eb89195734caee724c0a40d4686a0bab))
- More daily and weekly events
- Third trainer card page for Kanto badges
diff --git a/screenshots/gfx-font-unown_font.png b/screenshots/gfx-font-unown_font.png
new file mode 100644
index 0000000..b649ea0
--- /dev/null
+++ b/screenshots/gfx-font-unown_font.png
Binary files differ
diff --git a/screenshots/gfx-pokemon-unown_exclamation-back.png b/screenshots/gfx-pokemon-unown_exclamation-back.png
new file mode 100644
index 0000000..8054a03
--- /dev/null
+++ b/screenshots/gfx-pokemon-unown_exclamation-back.png
Binary files differ
diff --git a/screenshots/gfx-pokemon-unown_exclamation-front.png b/screenshots/gfx-pokemon-unown_exclamation-front.png
new file mode 100644
index 0000000..0358dc6
--- /dev/null
+++ b/screenshots/gfx-pokemon-unown_exclamation-front.png
Binary files differ
diff --git a/screenshots/gfx-pokemon-unown_question-back.png b/screenshots/gfx-pokemon-unown_question-back.png
new file mode 100644
index 0000000..97f2347
--- /dev/null
+++ b/screenshots/gfx-pokemon-unown_question-back.png
Binary files differ
diff --git a/screenshots/gfx-pokemon-unown_question-front.png b/screenshots/gfx-pokemon-unown_question-front.png
new file mode 100644
index 0000000..e368ab7
--- /dev/null
+++ b/screenshots/gfx-pokemon-unown_question-front.png
Binary files differ
diff --git a/screenshots/pokedex-unown-mode.png b/screenshots/pokedex-unown-mode.png
new file mode 100644
index 0000000..0a1f888
--- /dev/null
+++ b/screenshots/pokedex-unown-mode.png
Binary files differ
diff --git a/screenshots/unown-exclamation-question.png b/screenshots/unown-exclamation-question.png
new file mode 100644
index 0000000..2ae0033
--- /dev/null
+++ b/screenshots/unown-exclamation-question.png
Binary files differ