CHAPTER 1: Setting Up a Chapter’s Events
Basic Event Structure
At its most basic level, a chapter’s events are just a series of pointers to various types of events. We’ll go over what each of them are used for individually a bit later when they come up as relevant, but for now just know our event pointers should look something like:
POIN TurnBasedEvents
POIN CharacterBasedEvents
POIN LocationBasedEvents
POIN MiscBasedEvents
POIN Dunno Dunno Dunno
POIN Tutorial
POIN TrapData TrapDataHard
POIN PlayerUnits PlayerUnitsHard
POIN $0 $0 $0 $0 $0 $0
POIN BeginningScene EndingScene
On the pointer table, this is the data that’s pointed to for events. We can use this macro to set a pointer to our new event data on the pointer table:
EventPointerTable(7,MyEvents) //7 is the ID for prologue events in vanilla FE8
MyEvents:
POIN TurnBasedEvents
POIN CharacterBasedEvents
POIN LocationBasedEvents
POIN MiscBasedEvents
POIN Dunno Dunno Dunno
POIN Tutorial
POIN TrapData TrapDataHard
POIN PlayerUnits PlayerUnitsHard
POIN $0 $0 $0 $0 $0 $0
POIN BeginningScene EndingScene
Note that MyEvents can be anything you want it to be, as long as the name is not used elsewhere and it’s consistent between both places.
Now, we need to set labels for each of the pointers. To start, we’ll do a few that you really shouldn’t touch, starting with Dunno:
Dunno:
WORD $00
(We do actually know what Dunno data is for, but we’ll cover that later when we cover other similar things)
Tutorial data is something you likely don’t need (and messing with more often than not goes awry), so we’ll set that data like so:
Tutorial:
WORD $00
These two will likely be static for every event file you ever create.
Unit Groups
Getting into the parts of the events you’ll likely want to mess with, let’s start with PlayerUnits and PlayerUnitsHard. These are pointers to unit groups, which we fill in with the UNIT command. Its syntax is like so:
UNIT charID classID leaderID Level(level,allegiance,autolevel) [x,y] flags 0x0 numberOfREDAs pointerToREDAs [item1, item2, item3, item4] [ai1, ai2, ai3, ai4]
Let’s go through and look at what each of these means:
charID is the character ID of the unit you want to load.
classID is the class ID of the class you want to load.
leaderID is the character ID of the unit’s leader. This is used for some AI settings, but on player units means nothing.
Level is a macro that packs its 3 parameters into a single byte:
level is the starting level of the unit.
allegiance is the allegiance of the unit. This will be either Ally for a player unit, Enemy for an enemy unit, or NPC for a green unit.
autolevel is either true (1) or false (0), specifying if the unit should be levelled up to their starting level from their base stats or to just use their base stats regardless of level. In general, the only time you want autolevel to be true is for generic NPC and enemy units.
[x,y] are the coordinates of the unit’s starting position on the map. Note that coordinates start at [0,0] in the top left and go up from there.
flags is a combination of a few bitflags. You’ll likely only ever use the drop flag.
The MonsterTemplate flag will cause the unit to become a randomly generated monster unit, based on the class ID. You likely do not want to use this.
The DropItem flag will cause the unit to drop the last item in their inventory.
Flags 4 and 8 exist, but are unused.
numberOfREDAs and pointerToREDAs have to do with the unit moving after they are loaded in. We’ll touch more on REDA data later.
items are the unit’s starting inventory. Note that you do not need to fill out all 4 items; there needs to be something between those brackets, but it can be anywhere from 1 to 4 items.
ais are used by non-player units to control their behavior. This too we will go in-depth with later.
To construct a unit block, you use UNITs as so (macros and definitions used to simplify parts):
UNIT Eirika EirikaLord Eirika Level(1,Ally,False) [2,16] 0x0 0x0 0x0 0x1 [Rapier,Vulnerary] NoAI
UNIT Seth Paladin Eirika Level(1,Ally,False) [1,17] 0x0 0x0 0x0 0x1 [SilverLance,SteelSword] NoAI
UNIT
Notice the extra UNIT at the end there? That marks the end of the unit group, and tells the game to stop looking for more units after that point. Make sure to end your unit groups, or the game will try to read unrelated data as units (and likely crash).
PlayerUnitsHard is the unit placement for hard mode. If you don’t want a separate hard mode unit placement, just make both pointers PlayerUnits.
BeginningScene
BeginningScene is our entry point into proper events. To start, we’ll just craft the most basic of events that just loads our units we defined before. To do this, we use LOAD commands.
The syntax for LOAD commands is as follows:
LOAD RestrictionType UnitsOffset
There are four LOAD commands, conveniently named LOAD1, LOAD2, LOAD3, and LOAD4. Each of these has its own function:
-
LOAD1loads a unit group using the specified restriction. -
LOAD2loads a unit group using restriction 2. It will ignore restriction type given. -
LOAD3loads a unit group using the specified restriction, but the units loaded are replaced with units in the player’s party in unit list order. -
LOAD4loads a skirmish unit group using the specified restriction.
Now, what are restrictions? There are 3 of them from 0-2 that each have their own use:
-
0will cause units in the unit data being loaded that are dead to not be loaded. -
1will have no special restrictions. -
2will skip loading dead units unless they are one of Seth, L’Arachel, Myrrh, or Innes (you can change this list by editing a null terminated list of character IDs at FE8U:0x89ED674 (pointer to it at 0x8084864)). New units loaded with restriction 2 are also considered cutscene units, and are automatically removed when the running event ends.
You may have noticed that LOAD2 has the same effect as LOAD1 2. For this reason, LOAD2 is technically redundant and never has to be used; I recommend using LOAD1 0 for returning units and LOAD1 1 for new units. You’ll likely never need to use LOAD4.
UnitsOffset is just the label you put at your unit block. All 4 LOAD commands can also take a unit block offset from memory slot 2, but to do so you have to specify a negative offset with EA really doesn’t like. Thus, EA codes that set a negative offset automatically are necessary to use this feature; LOAD_S2 for LOAD1, LOAD_CUTSCENE_S2 for LOAD2, LOAD_DEPLOYED_S2 for LOAD3. If you don’t know anything about memory slots, that’s fine, we’ll cover them in-depth a bit later.
For our example event, we want to do:
BeginningScene:
LOAD1 1 PlayerUnits
ENUN
ENDA
This will simply load our player units then end. The BeginningScene is called automatically at the start of the chapter, so to run this event all we have to do is start the chapter.
You may have noticed the ENUN code in there, which we haven’t covered yet. ENUN will have the event pause until all units that are currently moving are done moving. This is useful as a large number of event codes will crash the game if there are units moving when you try to call them. In vanilla, every single LOAD command is followed by an ENUN, and you should do the same with yours.
We also haven’t touched on ENDA, which is a very important event code. ENDA denotes the end of your event, and should always be present. Otherwise, the game will continue on beyond your intended end point and start reading whatever data comes next as event data, which can get real bad real fast.
Now, just loading player units is pretty boring, isn’t it? Let’s make a new unit group with the label EnemyUnits, and give some enemies to meet with our player units.
BeginningScene:
LOAD1 1 PlayerUnits
ENUN
LOAD1 0 EnemyUnits
ENUN
ENDA
Now we have almost all the elements of a playable chapter. All we need now are…
Win Conditions
For now we’ll just cover 3 win conditions: Seize, Defeat Boss, and Rout.
Seize
To make a seize point, under LocationBasedEvents you put:
LOCA 3 [X,Y] 0x11
(We’ll cover LOCA later on when we touch on all of its uses)
But this is unintuitive to read, so we instead can use the stdlib macro:
Seize(seizeX,seizeY)
When your lord is at the coordinates you set, they’ll be able to Seize, which will run EndingScene.
Defeat Boss
To set a defeat boss win condition, under MiscBasedEvents put:
AFEV 3 eventOffset 2
(We’ll cover AFEV later on when we touch on all of its uses)
But this is unintuitive to read, so we instead can use the stdlib macro:
DefeatBoss(eventOffset)
When you’ve defeated a boss (their death quote needs to set flag 2) this will trigger the event you specify. Most of the time you’ll want eventOffset to be EndingScene.
Rout
To set a rout win condition, under MiscBasedEvents put:
AFEV 0 eventOffset 6
But this is unintuitive to read, so we instead can use the stdlib macro:
DefeatAll(eventOffset)
When you’ve defeated all of the enemies on the map, flag 6 is set automatically, which will trigger the event you specify. Most of the time you’ll want it to be EndingScene.
Using all that we’ve learned so far, we technically have a fully playable chapter. However, we’re still missing a number of the labels we referenced initially with our POINs so it won’t assemble. We’ll go into those soon, but first we need to learn about:
Flags & Memory Slots
Flags and memory slots are two very important pieces of events that get used a lot.
Flags
Flags are small pieces of data that can be either on or off. Events will use these mostly to mark events as complete after they’re run, so if the conditions are met again the event will not run a second time. There are two kinds of flags: temporary flags, which automatically reset at the end of each chapter, and permanent flags, which do not reset between chapters. Temporary flags run from 0-40 (0x0-0x28) and permanent flags run from 101-300 (0x65-0x12C). The temporary flags from 0-6 have reserved purposes, and you should not use as generic temporary flags or you may accidentally activate something you don’t mean to.
-
Flag 0 is always set to false and cannot be set to true.
-
Flag 1 is reserved for boss battle quotes.
-
Flag 2 is reserved for boss death quotes and activates defeat boss conditions.
-
Flag 3 is the chapter win flag and is normally set when seizing.
-
Flag 4 changes the map music to a second set that’s defined in chapter data when set.
-
Flag 6 is set when there are no enemies on the map and activates rout conditions.
You can use flags 7-40 for your chapter events and they’ll reset for you to use in other chapters.
See this thread for more information on how specific global flags are used.
Memory Slots
Memory slots are sections of memory that can be written to and read from via events. There are 14 memory slots from 0-13, some of which have special functions:
-
s0 is always 0. This is useful with conditionals, being a constant to check true/false against.
-
s1 is used for specific event codes. In general, if an event code always reads from a memory slot, it uses slot 1.
-
s2 is used for specific event codes. In general, if an event code has a setting where it will read from a memory slot, it uses slot 2.
-
s11/sB is used to hold coordinates. Event codes that read coordinates do so from this memory slot.
-
s12/sC is used for results. Event codes that check various things will return their result to this memory slot.
-
s13/sD is the current size of the queue. We’ll talk about this more when we cover the event queue later on.
Although they have specific uses, slots 1 and 2 are generally free to use unless you require using them for those specific uses and need to hold a value through that point. Note that memory slots do not hold their values after the end of a given event, so you shouldn’t expect them to.
Now that we know about these, we can move on to:
TurnBasedEvents
This is the section that controls, as the name suggests, turn-based events. To designate a turn-based event, you put under this label:
TURN flagID pointer [startTurn, endTurn] phase 0
flagID is the ID of the flag that will be activated when the turn event is activated, or will prevent the event from being run if set.
More cleanly, you can use the following macros:
TurnEventPlayer(eventID,pointer,turn)
TurnEventPlayer(eventID,pointer,startTurn,amountOfTurns)
TurnEventEnemy(eventID,pointer,turn)
TurnEventEnemy(eventID,pointer,startTurn,amountOfTurns)
TurnEventNPC(eventID,pointer,turn)
TurnEventNPC(eventID,pointer,startTurn,amountOfTurns)
Survive(pointer,endturn)
OpeningTurnEvent(pointer)
Make sure to put END_MAIN at the end of your list of turn-based events so the game knows where they stop.
CharacterBasedEvents
This is the section of your events where you define Talk conversations between units. The code structure is:
CHAR flagID eventPointer [char1,char2] 0
flagID is the ID of the flag that denotes if this event has been run, eventPointer is the label for the event to play when the characters talk, char1 is the one who initiates the conversation, char2 is the one who it is initiated with. Note this only goes one way, from char1 to char2. If you set a character ID to 0, this denotes that any unit will suffice for the talk conversation.
To make it go the other way as well, you need a second CHAR with the characters swapped.
Macros for these make the both ways talk events cleaner:
CharacterEvent(eventID,pointer,char1,char2)
CharacterEventBothWays(eventID,eventPtr,char1,char2)
Make sure to put END_MAIN at the end of your list of character-based events so the game knows where they stop.
LocationBasedEvents
The name of this section is a bit of a misnomer, as “map objects” would be more applicable. This is where you set the location of anything that would bring up a menu command when on or next to its tile, like shops or houses. Most all of the individual location events have their own codes and syntax, but they can be generally boiled down to:
LOCA flagID offset [xpos,ypos] objectType
flagID is the ID of the flag that denotes if this event has been run, offset is the label for the data that accompanies the map object (this isn’t always an event!), [xpos,ypos] is the coordinates of where the map object is located. There’s a number of object types, which are as follows:
-
0x10is a house event. Its offset is the label for the house event. -
0x11is a seize event. Its offset is the label for the event to run. If there is no offset given, it runs EndingScene. -
0x12is a door event. It does not take an offset. -
0x14is a chest event. It does not take an offset. -
0x16is an armory. Its offset is a shop list. -
0x17is a vendor. Its offset is a shop list. -
0x18is a secret shop. Its offset is a shop list. -
0x20is a village center. This is used when enemies target and destroy villages to know where to move towards to do so. It does not take an offset.
A few of these use shop lists. Shop lists are very simple to set up, just put a label for them then put SHLI item1 item2 item3 … itemN. You can technically have as many items following SHLI as you want within reason, though the game bugs out over 21 items in a shop list so you should be careful not to exceed that amount.
Now, that’s a whole lot of information to take in. Luckily, there’s some real easy macros for all of these:
Village(flagID,eventOffset,villX,villY)
House(flagID,eventOffset,villX,villY)
Armory(shopListOffset,shopX,shopY)
Vendor(shopListOffset,shopX,shopY)
SecretShop(shopListOffset,shopX,shopY)
Chest(item,chestX,chestY)
ChestMoney(amountOfMoney,chestX,chestY)
Door(doorX,doorY)
Seize(seizeX,seizeY)
The Village macro will also set a village center automatically.
Note how doors, shops, and chests do not take flag IDs as a parameter in their macro, even though they can take one in their event code. For shops this is so you can re-enter the shop after visiting it once, but for chests and doors this is due to how these events work. One of the requirements for chest and door objects to be usable is the terrain type they’re placed on must be the Door terrain for doors or the closed Chest terrain for chests. Because opening the door/chest activates a tile change that changes the terrain ID of the tile they are on, you can save needing a flag for every single chest and door, as they can number numerous in any given chapter, by just giving them flag 0 to make them always pass the flag check.
Speaking of, you may have noticed there is a macro for a chest containing money and we did not discuss a specific map object for this before. The way you set a chest to give money is simply to set the item ID to the amount of money you want to give. This number has to be higher than 255 or else the game will assume it’s an item and try to give you that instead.
Make sure to put END_MAIN at the end of your list of location-based events so the game knows where they stop.
MiscBasedEvents
Events in this section will always be run unless their associated event ID are set. These are very powerful and versatile, and can be used for many things. There are only two event codes that fall in this category:
AFEV flagID eventOffset activationFlag
AREA flagID eventOffset [corner1x, corner1y] [corner2x, corner2y]
AFEV runs immediately after the activationFlag flag is set. When it runs, it will set flag flagID and run event eventOffset. AREA runs when any unit steps in an area defined by [corner1x, corner1y] as the coordinate of the top left corner and corner2x, corner2y as the coordinate of the bottom right corner.
Since AFEV and AREA usage is generally pretty case-specific and the syntax is fairly simple, there’s not a ton of point in giving macros for them. However, the following macros do exist:
CauseGameOverIfLordDies
DefeatBoss(offset)
DefeatAll(offset)
We covered DefeatBoss and DefeatAll earlier, but CauseGameOverIfLordDies is new. You’ll likely want this on every chapter you make, as it is the part necessary to, as the name suggests, cause a game over if the lord dies. This runs after flag 101/0x65, which is designated as the game over global flag. If you set this flag in any form, it will result in this being run. It’s highly recommended that you always use this macro and setting that flag when you want to trigger a game over, as doing it any other way tends to lead to issues.
Make sure to put END_MAIN at the end of your list of misc-based events so the game knows where they stop.
Remember earlier when we talked about the Dunno label? Well, those are actually alternate MiscBasedEvents sections that activate at different times, rather than just after each unit takes an action; the problem is, each of them is fairly buggy and we really shouldn’t use them. Vanilla doesn’t use these anywhere, so they aren’t a necessity for doing anything specific and we can just ignore them.
There’s one last section we haven’t gone over yet, and that’s…
Trap Data
TrapData is a bit of a misnomer for what this section is actually used for, but one of the things it contains is traps. Since the contents of this section are widely varied, there’s no general explanation that really covers everything, but you can think of this sort of as changeable parts of a map.
Trap data contains:
-
Ballistae
-
Fire Traps
-
Poison Gas
-
Vertical Arrows
-
Gorgon Eggs
-
Instant Fire
-
Mines
-
Light Runes
-
Tile Changes
-
Snags
-
Breakable Walls
-
Torch Staves
The catch to this is a lot of these are created automatically. Thus, the following event codes are all you need to know about:
BLST [XX,YY] type
FIRE [x,y] startTurn repeatTimer
GAST [x,y] direction startTurn repeatTimer
ARROW [x,y] startTurn repeatTimer
FIRE2 [x,y]
MINE [x,y]
EGG [x,y] startTurn level
[x,y] is the location of the trap on the map. startTurn is the turn that the trap activates. repeatTimer is how many turns after it activates it should activate again. direction is the direction the trap should face. type is used by ballistae and is always either 0x34 for a standard ballista, 0x35 for an iron ballista, or 0x36 for a killer ballista. level is used by gorgon eggs and is the level the gorgon that hatches should be. Note that gorgon eggs require 2-3 other factors to work properly, if you wish to use them reference how they are set up in vanilla FE8.
For macros this time around, you’ve got:
NormalBallista(XX,YY)
IronBallista(XX,YY)
KillerBallista(XX,YY)
FireTrap(XX,YY,startTurn,repeatTimer)
GasTrap(XX,YY,direction,startTurn,repeatTimer)
PoisonTrap(XX,YY,direction,startTurn,repeatTimer)
ArrowTrap(XX,YY,startTurn,repeatTimer)
InstantFireTrap(XX,YY)
MineTrap(XX,YY)
GorgonEggTrap(XX,YY,startTurn,level)
Unlike the previous sections, this one ends with ENDTRAP.
You should now have everything you need to have a chapter that will assemble and run to initialize a chapter in-game.
REDAs
As alluded to before, REDAs are how you specify unit movement immediately upon loading. When a unit is loaded, if they have a non-zero value for numberOfREDAs they will be loaded at [x,y] and then immediately move to the positions defined in the REDA data pointed to by pointerToREDAs.
REDAs are laid out like so:
REDA [x,y] flags speed rescuing delay
Here’s what each of these mean:
[x,y] is the coordinates of the tile to move to.
flags is the same flags as on the UNIT; you don’t need to set these.
speed is how fast the unit should move. If 0, they move at normal speed. If 1, they move at a slower speed.
rescuing delays loading this unit until one of the specified character ID has finished moving. In vanilla, this is used to show a unit being rescued, as one unit moves, stops, and another loads on top of them and then moves off; think Seth dropping Eirika in the prologue opening.
delay is the number of frames to wait at this position before moving to the next one.
Most of the time, you’ll only care about the coordinates. In this case, an alternative REDA format exists that foregoes the rest of the fields:
REDA [x,y]
REDA data then is going to look something like this in use:
MyREDAs:
REDA [1,2]
REDA [2,3]
where MyREDAs is the value you put in the pointerToREDAs field.
Almost always you’ll only need a single REDA, and writing the whole thing out every time can be a bit tiresome. Snakey has made a file you can #include that contains premade single-entry REDAs for every coordinate [0,0] to [30,30], formatted as REDAXRY where X and Y are the respective coordinates.
If a unit has no REDAs set and another unit is occupying the starting position defined in UNIT, the unit will not be loaded when the group they belong to is. However, if they have a single REDA to the same location as their starting position, they will always spawn and automatically relocate to the nearest free tile. This can be utilized to prevent blocking reinforcement spawns. Note that this relocation behavior happens with every sort of unit movement.