FE8 EA Eventing Guide

Introduction

Due to the number of pieces there are to events, wrapping your head around how they work can be daunting. This guide is written assuming you have minimal knowledge of events and basic knowledge of other game systems to hopefully help eventing newcomers get a grasp on how everything works. This guide also serves as a doc for event opcodes and their function. For more in-depth documentation, see Stan’s event doc.

Going into this guide, I assume you have Event Assembler and are familiar enough with it to press the assemble button. I recommend you swap out your Core.exe for ColorzCore, as it’s generally an improvement across the board. This guide also uses terminology in line with Stan’s EA stdlib, language raws, and extensions, so I highly recommend that you update your stdlib/raws to this version. Presumably, the next release of Event Assembler will come with both of these (eventually™); at that time, this section will be removed.

Installing ColorzCore

Installing ColorzCore is very simple. Just download ColorzCore.exe from the above link and place it in your Event Assembler directory. You can either remove Core.exe and rename ColorzCore.exe to Core.exe or change your invocation of EA, through a make hack script or otherwise, to use ColorzCore instead of Core.

Updating EA Standard Library & Language Raws

Once again, updating these is simple. Download the github repository linked above, delete the folders and files it contains from your Event Assembler directory, and drag in your newly downloaded versions. If you run into issues with previously working things no longer functioning, you can put #include "EA Standard Library/Backward Compatibility.txt" right after you #include EAstdlib.event.

37 Likes

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:

  • LOAD1 loads a unit group using the specified restriction.

  • LOAD2 loads a unit group using restriction 2. It will ignore restriction type given.

  • LOAD3 loads a unit group using the specified restriction, but the units loaded are replaced with units in the player’s party in unit list order.

  • LOAD4 loads 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:

  • 0 will cause units in the unit data being loaded that are dead to not be loaded.

  • 1 will have no special restrictions.

  • 2 will 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:

  • 0x10 is a house event. Its offset is the label for the house event.

  • 0x11 is a seize event. Its offset is the label for the event to run. If there is no offset given, it runs EndingScene.

  • 0x12 is a door event. It does not take an offset.

  • 0x14 is a chest event. It does not take an offset.

  • 0x16 is an armory. Its offset is a shop list.

  • 0x17 is a vendor. Its offset is a shop list.

  • 0x18 is a secret shop. Its offset is a shop list.

  • 0x20 is 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.

12 Likes

CHAPTER 2: Opcodes and the Event Engine

Event Engine

Now that we have basic functioning events, we can move on to more complex ones. To do more with our events, first we need to learn a bit. What’s actually going on here?

When the chapter starts, the BeginningScene event plays automatically. The piece responsible for this is the event engine. The event engine reads your event scene, takes the instructions you give it within that scene, and performs actions based on your instructions. Each of these instructions is known as an opcode. Of what we’ve covered so far, only 3 are opcodes: LOAD commands, ENUN, and ENDA. The rest of the event codes we’ve covered are just fancy ways of setting up data for other game systems to read. There are a number of event engine systems, including flags and memory slots that we have covered already, but there are others we haven’t covered, as well as event opcodes for manipulating them.

Evbits

Evbits are a set of event engine flags that have specific meanings. Many codes modify and/or behave differently based on their state.

  • Evbit 0 is the “ignore queued scenes” evbit.

  • Evbit 1 is the “scene is active” evbit.

  • Evbit 2 is the “scene-skipping” evbit.

  • Evbit 3 is the “text-skipping” evbit.

  • Evbit 4 is the “prevent scene-skipping” evbit.

  • Evbit 5 is the “prevent text-skipping” evbit.

  • Evbit 6 is the “prevent text-fast-forwarding” evbit.

  • Evbit 7 is the “no end fade” evbit.

  • Evbit 8 is the “faded in” evbit.

  • Evbit 9 is the “camera follows moving units” evbit.

  • Evbit 10 is the “moving to another chapter” evbit.

  • Evbit 11 is the “changing game mode” evbit.

  • Evbit 12 is the “gfx locked” evbit.

Most of these you will never need to set individually, as they are set/unset by other codes automatically.

CHECK_EVBIT evbitID returns the value of the specified evbit to sC.

EVBIT_T evbitID will set the specified evbit to true.

EVBIT_F evbitID will set the specified evbit to false.

There exists the macro NoFade, shorthand for EVBIT_T 7, which if placed just before an ENDA will cancel the screen fading in and back out at the end of your event. You likely want to use this 99.9% of the time. If the evbitID is negative, the evbit to set will be read from s2.

EVBIT_MODIFY setting will configure evbits en masse related to skipping. The settings are as follows:

  • 0 allows scene skipping, dialogue skipping and dialogue fast-forwarding.

  • 1 disallows scene skipping, dialogue skipping and dialogue fast-forwarding.

  • 2 allows scene skipping and dialogue skipping, but disallows dialogue fast-forwarding.

  • 3 disallows scene skipping, but allows dialogue skipping and dialogue fast-forwarding.

  • 4 disallows scene skipping and dialogue skipping, but allows dialogue fast-forwarding.

Any other setting will freeze the game. If the setting is non-zero and the scene is currently being skipped, evbit 2 is set to false and evbit 8 is set to true. You cannot read the setting from a memory slot.

Flags

As we covered earlier, flags run from 0-40 for temporary flags and 101-300 for permanent flags, a number of which from each group having reserved functions.

ENUT flagID will set the specified flag.

ENUF flagID will clear the specified flag.

If flagID is negative, the flag to set or clear will be read from s2.

CHECK_EVENTID flagID returns the true/false state of the specified flag to sC.

Memory Slots

As covered prior, memory slots range from s0-sD with some having specific reserved functions.

SVAL slotID value will set the given slot to the given value. Setting s0 has no effect.

You can perform a number of mathematical operations with the values in slots.

SADD destSlotID operand1slot operand2slot will put operand1 + operand2 in destSlotID

SSUB destSlotID operand1slot operand2slot will put operand1 - operand2 in destSlotID

SMUL destSlotID operand1slot operand2slotwill put operand1 * operand2 in destSlotID

SDIV destSlotID operand1slot operand2slot will put operand1 / operand2 in destSlotID

SMOD destSlotID operand1slot operand2slot will put operand1 % operand2 in destSlotID

SAND destSlotID operand1slot operand2slot will put operand1 & operand2 in destSlotID

SORR destSlotID operand1slot operand2slot will put operand1 | operand2 in destSlotID

SXOR destSlotID operand1slot operand2slot will put operand1 ^ operand2 in destSlotID

SLSL destSlotID operand1slot operand2slot will put operand1 << operand2 in destSlotID

SLSR destSlotID operand1slot operand2slot will put operand1 >> operand2 in destSlotID

Setting destination to s0 has no effect. Note that for all of these operations, all numbers are signed, divisions round towards 0, and right shift is an arithmetic shift.

Vanilla uses SADD destSlotID sourceSlotID s0 to move the value of one slot into another.

Counters

Counters store a value from 0-15 inclusive. There are 8 of them in total, from counter 0 to counter 7. They reset between chapters, but do not reset through suspend, much like temporary flags.

COUNTER_CHECK counterID returns the value of the specified counter to sC.

COUNTER_SET counterID value sets the value of the specified counter to the given value. The value cannot be read from a memory slot.

COUNTER_INC counterID increases the value of the specified counter by 1.

COUNTER_DEC counterID decreases the value of the specified counter by 1.

COUNTER_INC and COUNTER_DEC cannot overflow or underflow the counter; if the value in the counter is 0 and you use COUNTER_DEC, the value will remain as zero. Similarly, if the value is 15 and you use COUNTER_INC the value will still be 15.

Event Queue

The event queue is a list of values following a FIFO (First In First Out) model (hence why it’s called a queue). It is generally used to pass a list of values to codes or subscene. As you may recall, sD always holds the current size of the queue. There is room for up to 30 values in the queue.

SENQUEUE slotID will enqueue the value stored in the given slot.

SENQUEUE is the same as the above, but defaults to s1.

SDEQUEUE slotID will dequeue the value at the front of the queue to the given slot.

These systems are all used by different event opcodes, so having an understanding of how they work is important. There is one more set of useful utility codes to know before moving forward:

STAL time will pause the scene for the given amount of time.

STAL2 time will pause the game for the given amount of time. If the game speed option is set to fast or the A button is held, the time will decrement 4 times faster.

STAL3 time will pause the game for the given amount of time. If the game speed option is set to fast or the A button is held, the time will decrement 4 times faster. The pause will end early if the text-skipping evbit is set or the B button is pressed at any point.

No pause will occur if events are currently being skipped. There is no way of getting time from a memory slot. If time is 0, the game will freeze.

Now that we understand our basic systems, we can move on to some more practical features:

Fades

One of the most common things you’ll do in events is fading in and fading out to black or white. There’s a set of four codes for this:

FADU speed fades out of black.

FADI speed fades into black.

FAWU fades out of white.

FAWI fades into white.

These will set and clear respectively the faded in evbit. Note the speed value is not fade duration, and as such a larger value will be a shorter fade. No fade happens if the scene is being skipped. The event engine will pause until the fade is completed.

Text

There are three pieces to displaying text: setting the text mode, displaying the text, and cleaning up afterwards. First, we set the text mode:

TEXTSTART will set the text mode to 0, which is standard, no-background dialogue.

REMOVEPORTRAITS will set the text mode to 1, which is standard dialogue with a background.

CGTEXTSTART will set the text mode to 2, which is CG text. Using [SetName] at the start of your text displayed in this mode will display the text immediately following in a smaller box in the top corner of the text box. This can be used with CG images or standard conversation BG images.

TUTORIALTEXTBOXSTART will set the text type to 3, which is the scroll box used in vanilla for tutorials. The location on the screen that this box will be drawn at is determined by coordinates in sB. If the coordinates are -1, the text box will be centered automatically. The macro CenterTutorialTextBox sets sB to center the box.

SOLOTEXTBOXSTART will set the text mode to 4, which displays a text box similar to CG text but without the option for a name field. The location on the screen that this box will be drawn at is determined by coordinates in sB. If the coordinates are -1, the text box will be centered automatically. The macro CenterTutorialTextBox sets sB to center the box.

No matter what text mode we use, the next step is using TEXTSHOW. This is where we tell the game what text to display by giving it a text ID.

TEXTSHOW textID displays the given text in the currently set text mode after resetting the text-skipping evbit. In practice, this means that pressing B mid-text will take you to the next TEXTSHOW.

TEXTSHOW2 textID displays the given text in the currently set text mode only if the text-skipping evbit is not set. In practice, this means that pressing B mid-text before a TEXTSHOW2 will skip over its text.

Finally, we end our text. This is done with the aptly named TEXTEND.

TEXTEND waits for text to end then continues events following from it.

After TEXTEND, we still need to clean up a few text-related things. REMA will do this for us:

REMA ends any running text interpreters, clears text from the screen, removes portraits, and clears the text-skipping evbit.

So, the most basic text event would be laid out as:


TEXTSTART
TEXTSHOW textID

TEXTEND
REMA

But this is a bit cumbersome to type out every time, no? Well, that’s where macros come in! The macro Text(textID) is the same as the above event, but in a single line. This is much easier to follow than typing all it out every single time.

Text with Backgrounds

There are a few ways to do backgrounds, but by far the easiest is setting the background separately from the text.

BACG backgroundID displays the given background for the active text mode. If text mode is not 1 or 2, the game will hang.

The default behavior of BACG is to display a background in text mode 1, which is useful as it means we can do some shenanigans to display text in non-background mode on backgrounds. Due to what is presumably a bug, CG text mode can only display a single CG image. But if we set the background beforehand, we can display whatever type of text we want overtop of it.

To set backgrounds, the vanilla game calls a macro event to fade in the background, which is expressed for our purposes through the macro SetBackground(backgroundID). Following this, we can set up text of any type to display overtop of the background.

When we’re done with our background, we call a vanilla macro event to get rid of it, CALL $9EE2C4. For convenience, we define this as EndConvo. Text with backgrounds then looks something like this:


SetBackground(backgroundID)

Text(textID)
EndConvo

Mid-Text Events

You may be aware of the text control code [LoadOverworldFaces], which can be used to activate events mid-text without ending it. This can be accomplished using TEXTCONT:

TEXTCONT returns to text from events.

Now, this alone isn’t enough to get our text to pause for events. Recall that TEXTEND returns to events from text. Well, [LoadOverworldFaces] counts as ending text to TEXTEND. Therefore, you lay out your mid-text events like so:


TEXTSTART
TEXTSHOW textID
TEXTEND
//mid-text events go here

TEXTCONT
TEXTEND
REMA

You can have more than one event break in your text as well. As long as you have a TEXTEND; TEXTCONT for each one, you can have as many events run as you like.

Clearing

Sometimes, you need to remove everything off of the screen. There’s an event code for this:

CLEAN clears the top 2 graphics layers, text, and portraits, reloads the font, default map sprite palettes, and UI graphics, and unblocks game graphics logic.

CLEAN is more or less a nuke button for anything displayed over the map on the screen. If you recall, we used the macro EndConvo to accomplish this for our backgrounds earlier. The event this is calling contains, among other things, CLEAN.

Other times, you don’t want to clear absolutely everything from the screen, but just text boxes. There’s also an event code for this:

TEXTCLOSE will clear any open text box.

More Backgrounds

There are also some other background-related things you can do:

BACKGROUND_CHANGE backgroundID newTextMode speed will transition from the currently displayed background to the given background as if it were being displayed in the given text mode. This does not change the current text mode. You cannot transition from a non-background text mode to another non-background text mode or the game will freeze. You can transition from a background text mode to a non-background text mode, in which case the current background is just faded out. Valid background text modes are 1 for BGs and 2 for CGs.

BACKGROUND_TOCOLOR backgroundID color speed displays the given background in the active text mode, and fades it in from the given color at the given speed.

BACKGROUND_FROMCOLOR color speed fades the currently displayed background out to the given color at the given speed.

Note these cannot be used properly without being in a background text mode, something that we’ve previously avoided doing.

Displaying Portraits

You can always display portraits through your text, but what if you want to do it through events instead? There’s a set of event codes for this purpose:

FACE_SHOW faceSlot portraitID

Slot IDs are as follows:

  • 0: FarLeft

  • 1: MidLeft

  • 2: Left

  • 3: Right

  • 4: MidRight

  • 5: FarRight

  • 6: FarFarLeft

  • 7: FarFarRight

If portraitID is -1 the ID is read from s2. If portraitID is -2 the portrait in the code’s position will be cleared, along with any open text box. If portraitID is -3 all portraits are cleared, regardless of which code is used. Displaying text immediately after using this may have some side effects, so to be safe you should follow each of these with a STAL for a few frames as the portrait is loaded in.

Much like in text, you can also move portraits drawn via events to different locations on the screen:

FACE_MOVE fromSlot toSlot moves the portrait in the given from slot to the given to slot.

Subscenes

Subscenes are events run from within other events.

CALL eventOffset will pause the current event, go to the event at the given offset, and play that event. When the scene finishes, the current event is unpaused and continues from after the CALL.

CALL doesn’t always have to return to the parent scene, however. Remember earlier we mentioned that ENDA ends your events? Well, the behavior of this changes slightly when you’re in a subscene. If a subscene ends with ENDA, it will return to the parent scene. However, if it instead ends with ENDB, it will end all scenes. If you have subscenes within subscenes, using ENDB will end every event there instead of having to back out to the highest layer to end.

ENDA ends the active scene. If the active scene is a subscene, it will return to its parent scene.

ENDB ends the active scene and all parent scenes.

Giving Items and Money

GIVE_ITEM charId will give an item based on the item ID in s3 to the specified unit, and displays an item get popup.

GIVE_MONEY charId will give money of the amount in s3 to the specified unit’s faction, and displays a gold get popup.

TAKE_MONEY will silently take money of the amount in s3 from the player faction. This will now allow money to underflow.

If char ID is 0, the unit used is the first player unit. If char ID is -1, the unit used is the active unit. If char ID is -2, the unit used is the one at the coordinates in sB. If char ID is -3, the unit used is the one whose char ID matches the one in s2.

Music and Sound Effects

MUSC songID will transition the BGM to the given song.

MUSCFAST songID will stop the current BGM and transition to the given song slightly faster than MUSC.

MUSCMID songID will stop the current BGM and transition to the given song slightly slower than MUSC.

MUSCSLOW songIDwill stop the current BGM and transition to the given song slower than MUSCMID.

MUSS songID will transition the BGM to the given song. The previously playing BGM is remembered. You can only remember one song at a time.

MURE speed will transition the BGM back to the song previously stored using MUSS. You can only remember one song at a time.

MUSI will lower the BGM volume.

MUNO will restore the BGM volume to its state pre-MUSI.

SOUN songID will play the given song. Does not play if skipping or if sound effects are turned off in the options menu.

Song ID 0x7FFF is silent. If song ID is negative, the ID will be read from s2.

Manually Moving Units

Sometimes, you need to move an already loaded unit to a different location than it was loaded to. These event codes allow you to do so:

MOVE speed charID [x, y] will move the specified unit to the specified coordinates at the specified speed.

MOVEONTO speed charID targetCharID will move the specified unit to where the specified target unit is standing.

MOVE_1STEP speed charID direction will move the specified unit 1 step in the specified direction.

MOVE_DEFINED charID will move the specified unit according to movement instructions in the event queue. Movement instructions are the same format as REDAs.

If speed is negative, movement is performed instantly. The higher the speed value, the slower the unit moves. If you are currently skipping, movement is performed instantly regardless of speed.

If char ID is 0, the unit used is the first player unit. If there are too many units currently moving, movement will be held until a moving unit reaches its destination. If char ID is -1, the unit used is the active unit. If char ID is -2, the unit used is the one at the coordinates in sB. If char ID is -3, the unit used is the one whose char ID matches the one in s2. None of these apply to MOVEONTO’s target char ID.

Directions for MOVE_1STEP are:

  • 0 for left.

  • 1 for right.

  • 2 for down.

  • 3 for up.

These values are defined as MoveLeft, MoveRight, MoveDown, and MoveUp respectively.

The event engine will not wait for movement to complete before continuing. As such, you require an ENUN after your MOVEs to avoid things breaking.

Camera Movement

You can manually move the camera using events.

CAMERA [X, Y] will move the camera to the given coordinates in such a way that they are on screen and the camera sits more than 2 tiles away from the edge of the map, if possible.

CAMERA charID will move the camera to the given character in such a way that they are on screen and the camera sits more than 2 tiles away from the edge of the map, if possible.

CAMERA2 [X, Y] will move the camera to the given coordinates such that they become the center of the screen.

CAMERA2 charID will move the camera to the given character such that they become the center of the screen.

Due to a bug, CAMERA2 can move the camera past the edge of the map, so be careful using it. For coordinate variants, if both coordinates are negative the target position will be read from sB. For convenience, you can use CAMERA_SB and CAMERA2_SB. Camera movement is instantaneous if you are currently skipping. If not, the event engine will wait for camera movement to complete before continuing.

If char ID is 0, the unit used is the first player unit. If char ID is -1, the unit used is the active unit. If char ID is -2, the unit used is the one at the coordinates in sB. If char ID is -3, the unit used is the one whose char ID matches the one in s2.

Change AI

You can change a unit’s AI after loading them in.

CHAI charID will change all units of the specified ID’s AI1 and AI2 values based on the value in s1.

CHAI [X, Y] will change the AI1 and AI2 values of the unit at the given coordinates based on the value in s1.

The data in s1 is formatted as 0x0000YYXX, where YY is AI2 and XX is AI1. If AI1 is changed to 0x13, no change will occur. If A12 is changed to 0x15, no change will occur. There is no way to change AI3 and AI4 with CHAI in vanilla, but Tequila has written a fix that allows for this.

Triggering Tile Changes

You can trigger tile changes for the current map manually via events, in addition to the few automatic ways they are triggered.

TILECHANGE tilechangeID will activate the tile change with the given ID if it has not already been activated.

TILEREVERT tilechangeID will revert the tile change with the given ID if it has been activated.

If tilechange ID is -1, a position is read from sB and the first tile change in the current map’s list that contains those coordinates will be activated. If tilechange ID is -2, the position of the active unit is used and the first tile change in the current map’s list that contains those coordinates will be activated. If tilechange ID is -3, multiple tile changes will be activated or reverted. The tilechange IDs to use are stored in the event queue. If you are currently skipping, tilechanges happen instantaneously. Otherwise, the event engine will wait for them to fade in/out.

Change Allegiance

You can change any unit’s allegiance using events.

CUSA charID will change the given unit’s allegiance to blue.

CUSN charID will change the given unit’s allegiance to green.

CUSE charID will change the given unit’s allegiance to red.

Display Cursor

You can display the cursor on the screen during events to highlight a given tile or character.

CURSOR charID will display the cursor on the given character.

CURSOR [X, Y] will display the cursor at the given coordinates.

CURSOR_FLASHING charID will display a flashing cursor on the given character.

CURSOR_FLASHING [X, Y] will display a flashing cursor at the given coordinates.

REMOVE_CURSORS will remove all cursors on the map.

If the given coordinates are negative, the position in sB will be used instead. CURSOR_SB and CURSOR_FLASHING_SB exist as shortcuts for this. Note that the event engine does not pause for cursors, so when using them your event will likely look something like:

CURSOR Seth
STAL 20
REMOVE_CURSORS

Prep Screen

Starting the prep screen is very simple:

PREP will call the preparations screen.

Calling the prep screen automatically clears flag 0x84, which suppresses blinking map sprite icons.

EA stdlib gives us the macro GotoPrepScreen, but be wary! This macro is set up to function like an FE7 event code, and as such contains an ENDA. You can have events occur after the prep screen within the same event, but if you use this macro, it will automatically end your event after the prep screen.

Changing Chapters

When your chapter is over, you can use these to move on to the next one. Chapters do not have to go in linear order, as you can define the ID of the chapter to move to every time.

MNCH chapterID will move to the world map and run the world map unlock events tied to the given chapter ID.

MNC2 chapterID will move to the specified chapter without going through the world map.

MNC3 chapterID will move to the specified chapter without going through the world map or the save screen.

GOTO_EPILOGUE will move to the Epilogue and credits.

GOTO_TITLE will return to the title screen.

If chapter ID is negative, the chapter ID will be read from s2.

Be careful when using MNC2 and MNC3, as if the chapter you are moving to is not set as the next “story” location, normally done through world map unlock events, the game will consider the chapter a skirmish when you arrive.

Changing Maps

For cutscene maps or fancy chapter schenanigans, you can change the current map mid-chapter. Note that switching maps will update the chapter number, and events for the chapter the map you loaded will be run instead of the one you came from as a result.

LOMA chapterID will load the map associated with the given chapter and sets the camera given the position in sB. If chapter ID is negative, chapter ID will be read from s2.

So far, we’ve covered most of the event opcodes most people will ever use. But, there’s still two pretty major pieces of events we haven’t covered yet…

11 Likes

CHAPTER 3: Conditionals

Labels and Goto

Labels are used to define locations in your events that can then be jumped to later. Although Event Assembler has its own version of labels, what we’re interested in here is the version used in the events themselves.

LABEL labelID will mark the location of a label. Label ID is a short and cannot be read from a memory slot.

LABEL does nothing by itself but mark a location. To take advantage of it, we need to use another code to jump to the label’s position. The most basic of these is GOTO:

GOTO labelID will jump to the first instance of a label with the given ID since the start of the currently running event. Label ID is a short and cannot be read from a memory slot.

Under normal circumstances, events are always executed in linear order. They start at the offset you give, and they end once they reach ENDA or ENDB. However, using LABEL and GOTO you can break this linear order. Since GOTO moves the event’s “cursor” to the specified LABEL, instead of going in linear order events will jump from GOTO to LABEL. These two alone don’t do a whole lot other than create infinitely looping events, from GOTO back to LABEL, or unconditionally skipping over sections of events. However, it’s vital to understand the function of these two opcodes before moving forward.

Conditional Branches

GOTO is fairly useless on its own, but there are variants of it that only jump, or branch, to a label if certain conditions are met:

BEQ labelID slot1 slot2 will branch to the specified label if the values in slot1 and slot2 are equal.

BNE labelID slot1 slot2 will branch to the specified label if the values in slot1 and slot2 are not equal.

BGE labelID slot1 slot2 will branch to the specified label if the value in slot 1 is greater than or equal to the value in slot 2.

BGT labelID slot1 slot2 will branch to the specified label if the value in slot 1 is greater than the value in slot 2.

BLE labelID slot1 slot2 will branch to the specified label if the value in slot 1 is less than or equal to the value in slot 2.

BLT labelID slot1 slot2 will branch to the specified label if the value in slot 1 is less than the value in slot 2.

Numbers in slots are considered as signed for the purpose of these opcodes. There is no way to read label ID or slot IDs from memory slots.

There’s a handful of these conditional branches, but the two you’ll likely find yourself using the most often are BEQ and BNE. But, what is there in memory slots to compare that we don’t put there manually? Well, that’s where the next piece fits in:

Checks

Checks are event opcodes that check some value about the current state of the game and return it to sC. Recall that s0 always contains 0 and used in conjunction with BEQ and BNE conditional branches you can make very easy true/false evaluations.

The following event opcodes return either true (1) or false (0):

CHECK_HARD will return true if you are in hard mode, false if you are not.

CHECK_TUTORIAL will return true if you are in easy mode, false if you are not.

CHECK_EVENTID flagID will return true or false based on the state of the specified flag.

CHECK_POSTGAME will return true if you are currently in postgame mode, false if you are not.

CHECK_EXISTS charID will return true if the unit exists, false if not.

CHECK_ALIVE charID will return true if the unit exists and is alive, false if not.

CHECK_DEPLOYED charID will return true if the unit is deployed, false if not.

CHECK_ACTIVEID charID will return true if the unit is the current active unit, false if not.

CHECK_INAREA charID [XTopLeft, YTopLeft] [Width, Height] will return true if the given unit is within the rectangle created by the given coordinates, false if not.

If char ID is 0, the unit used is the first player unit. If char ID is -1, the unit used is the active unit. If char ID is -2, the unit used is the one at the coordinates in sB. If char ID is -3, the unit used is the one whose char ID matches the one in s2.

Note that CHECK_ALIVE will only function on player units, as non-player units are completely wiped from memory upon death.CHECK_EXISTS will return true for dead player units but false for dead non-player units, while CHECK_ALIVE will return false for dead player units and false for dead non-player units.

You can also perform checks that return non-true/false (boolean) results. These in conjunction with SVAL for a desired result gives you further control over conditionals:

RANDOMNUMBER max will return a random number from 0 to max, inclusive. If max is 0, the result will always be 0. Max value cannot be read from a memory slot.

CHECK_CHAPTER_NUMBER will return the current chapter ID.

CHECK_TURNS will return the current turn count. Turn count increments just before the start of each player phase.

CHECK_ENEMIES will return the number of red units on the map.

CHECK_OTHERS will return the number of green units on the map.

CHECK_SKIRMISH will return 0 for story chapters, 1 for tower/ruins, and 2 for skirmishes.

CHECK_MONEY will return the party’s current gold balance.

CHECK_EVENTID will return the flag associated with the currently running event. If there is not one, returns 0.

CHECK_AT [X, Y] will return the ID of the character at the given coordinates. If there is no character at those coordinates, will return 0. There is no way to read coordinates from a memory slot.

CHECK_ACTIVE will return the ID of the active unit. If there is no active unit, will return 0.

CHECK_STATUS charID will return the status byte for the given unit. The upper nybble is duration and the lower nybble is status ID. If the unit does not exist, the game will hang. This opcode may be bugged.

CHECK_ALLEGIANCE charID will return 2 if the given unit is an enemy, 0 if a player unit, and 1 otherwise. If the unit does not exist, the game will hang.

CHECK_COORDS charID will return the coordinates of the given unit. If the unit does not exist, the game will hang.

CHECK_CLASS charID will return the class of the given unit. If the unit does not exist, the game will hang.

CHECK_LUCK charID will return the class of the given unit. If the unit does not exist, the game will hang.

If char ID is 0, the unit used is the first player unit. If char ID is -1, the unit used is the active unit. If char ID is -2, the unit used is the one at the coordinates in sB. If char ID is -3, the unit used is the one whose char ID matches the one in s2.

Using all of these pieces, you can create basic conditional events like so:


CHECK_ACTIVE //whichever check you want to use

SVAL s1 Eirika //the value to compare the check result to; unnecessary if the value is 0

BNE 1 s1 sC //if the check result != Eirika, go to label 1

Text(EirikaSpecificText) //this will only play when Eirika is the active unit

LABEL 1 //skips to here if the active unit is not Eirika

This framework can be copy-pasted for most all common needs for conditionals, swapping out the label ID, check, and s1 value as necessary.

CHAPTER 4: Scripted Battles

You can force units to engage in battles with fixed outcomes. To do this, you store values to the event queue in order of the events you want to occur during the battle. EA stdlib gives us macros to handle this part for you:

StartBattle will queue a marker for the start of a scripted battle.

NormalDamage(combatantNumber,damage) will queue a normal hit from the specified combatant dealing the specified amount of damage to the other combatant.

CriticalHit(combatantNumber,damage) will queue a critical hit from the specified combatant dealing the specified amount of damage to the other combatant.

Silencer(combatantNumber,damage) will queue a silencer/lethality attack from the specified combatant dealing the specified amount of damage to the other combatant.

SureShot(combatantNumber,damage) will queue a sure shot attack from the specified combatant dealing the specified amount of damage to the other combatant.

Poison(combatantNumber,damage) will queue a poisoning attack from the specified combatant dealing the specified amount of damage to the other combatant.

DevilReversal (combatantNumber,damage) will queue a devil reversal attack from the specified combatant dealing the specified amount of damage to the same combatant.

Pierce(combatantNumber,damage) will queue a pierce attack from the specified combatant dealing the specified amount of damage to the other combatant.

EndAttack will mark the end of the battle script.

You use these macros in order you wish for events to occur, starting with StartBattle and ending with EndAttack. Note that the damage numbers here do not have to match actual damage numbers for each unit. However, the actual damage numbers will appear during the battle.

You then need to trigger the battle with an event opcode.

FIGHT attackerID defenderID weaponID isBallista will trigger a scripted battle with animations on where the specified attacker attacks the specified defender using the specified weapon. If is ballista is true, the scene will be set accordingly.

Scripted battles give no experience. Damage taken during battle is maintained after battle, with the exception of devil reversal damage and killing blows. Which combatant is which number combatant in the battle script macros is reliant on the order you set the IDs here. Attacker is combatant 0 and defender is combatant 1. If weapon ID is not in the attacker’s inventory, you will get a weapon broke! popup at the end of the battle. If damage dealt in a scripted battle is greater than a unit’s remaining HP, it does not overflow. If a unit is attacking at a range they cannot normally with their current weapon, funkiness will happen and no damage will be visibly dealt (though it will be dealt after battle). If a unit is killed in an event battle, after the battle they will be alive and have the remaining HP they did before the killing blow. To properly kill a unit, use the previously mentioned event opcodes to do so after the battle.

You can also trigger the battle with no animations:

FIGHT_MAP attackerID defenderID weaponID isBallista will trigger a scripted battle with animations off where the specified attacker attacks the specified defender using the specified weapon. If is ballista is true, the scene will be set accordingly.

Restrictions without animations are the same as with animations, but if a unit lacks an animation for a weapon at the given range the game will not hang.

10 Likes

CHAPTER 5: To Go Even Further Beyond

At this point, you should have the tools to do most things with events you need, but there’s still more you can do. This chapter covers everything not yet covered.

Calling Assembly Functions

You can call any arbitrary asm function from your events using ASMC:

ASMC functionOffset will call the asm function at the given offset.

The ASMC code will always stop the event engine for the current frame. This means every time you use ASMC, no matter what you call, you gain a frame of lag. There is no way to read the function offset from a memory slot. ASMC does nothing if the function offset is 0.

If you’re writing an ASMC, some important information to note:

  • Your function gets passed a pointer to the currently running event engine proc in r0.

  • Return value of your function is ignored.

Ignore Key Presses

You can disable reading of inputs to specific buttons through events.

IGNORE_KEYS keyMask will set a key press ignore mask based on the given key mask for all purposes besides soft resetting.

Key mask is 10 bits in size, each one corresponding to a specific key:


bit 0 | A button

bit 1 | B button

bit 2 | select button

bit 3 | start button

bit 4 | right D-pad button

bit 5 | left D-pad button

bit 6 | up D-pad button

bit 7 | down D-pad button

bit 8 | R button

bit 9 | L button

With EA, we can put the key mask in binary to make it easier to visualize. To do this, we just end our binary number with b. For example, to disable the start button, you would put 0000001000b as your key mask. To enable it, just run it again with the disabled key’s bit as 0.

Arbitrary Color Fade

In addition to the set black and white fades, you can also create fades of any given color:

STARTFADE sets up the fade buffer from the current palette.

FADECOLORS target speed r g b will fade the palettes specified by target at the given speed to the given color.

ENDFADE returns palettes to normal after FADECOLORS.

To use FADECOLORS, these three opcodes need to come in the order STARTFADE; FADECOLORS; ENDFADE. Anything that does not touch fading or palettes can come between STARTFADE and ENDFADE as well without issues in all cases. If you do not change R, G, and B, you cannot have multiple FADECOLORS between one set of STARTFADE and ENDFADE. This in effect makes the minimum value for R, G, and B to be 1. The only exception is if all 3 values are 0, in which case it will fade to pure black.

Target is two bytes, palette index to start at and number of palettes to change from that index. Because of endianness, this becomes 0x’numPal’’startPal’. Palette indexes run from 0x00 to 0x20, the first half for background tiles and the second half for sprites.

For example, map tileset palettes begin at palette 0x06 and there are 10 (0x0A) of them, so to fade just the map to a given color you would use target 0x1006. Map sprite palettes start at palette 0x1B and there are 5 of them (3 for blue/green/red units, 1 for greyed out units, 1 for light rune for some reason), so to change just map sprites you would use target 0x051B. Map sprite palettes are ordered light rune/player/enemy/npc/grey, so to change just one of these palettes you would add however many palettes after light rune your desired palette rests to the palette index and set the number of palettes to 1.

Weather and Fog of War

Although initial values for weather and fog vision range are set in chapter data, you can alter them at any time via events.

WEATHER weatherID will change the current weather to the specified weather.

FOGVISION viewRange will change the fog vision range to the specified value.

A vision range of 0 means no fog. A negative vision range will use the vision range set in chapter data.

Weather IDs:

0: No weather

1: Snow

2: Blizzard

4: Rain

5: Fire

6: Sandstorm

7: Cloudy

Showing/Hiding the Map

When backgrounds are called, this is one of the functions performed. You can call it manually with these event opcodes:

HIDEMAP will disable map sprite display, weather effects, and camera updates.

SHOWMAP will enable map sprite display, weather effects, and camera updates after HIDEMAP.

Unit Load Configuration

You can configure a few settings prior to loading a unit group, primarily used in vanilla for Valni/Lagdou enemies.

LOADCONFIG_COUNT unitCount will set the number of units to load from the next unit group. If unit count is 0, it will load the entire unit block.

LOADCONFIG_RANDOM_POS_PROPORTION chance will set the % chance for units marked to load at a random position to actually be loaded.

LOADCONFIG_DISABLE_DEFINED_MOVEMENT will disable REDAs for the next unit block loaded.

Overriding Map Sprite Palettes

You can overwrite the map sprite palettes for different factions using events.

UNIT_COLORS blueID redID greenID will change the map sprites palettes for the blue, red, and green factions respectively to the given palettes.

IDs used here are:
0 - default palette

1 - blue palette

2 - red palette

3 - green palette

4 - flashback palette

Map sprite palettes are not reset at the end of a scene, so make sure to do that yourself, as they will not persist into gameplay but be set back to normal the next time palettes get reloaded.

Show/Hide Attack Range

You can show and hide the attack range for a given unit using the following opcodes:

SHOW_ATTACK_RANGE charID displays the move and attack/staff range for the given unit. This also sets the active unit to the one specified.

HIDE_ATTACK_RANGE will hide the range shown with SHOW_ATTACK_RANGE and restore the active unit to the unit beforehand.

Load Single Units

You can load individual units with default information without needing to write out unit blocks. This is largely not useful outside of cutscenes due to the state it loads units in.

SPAWN_ALLY charID [X, Y] will spawn the given character as a blue unit.

SPAWN_NPC charID [X, Y] will spawn the given character as a green unit.

SPAWN_ENEMY charID [X, Y] will spawn the given character as a red unit.

SPAWN_CUTSCENE_ALLY charID [X, Y] will spawn the given character as a blue unit in the same way LOAD2 would, specifying them as a cutscene unit.

The loaded unit’s class will be the default class for the character (the one used in the support viewer). Their stats and level will match character bases. They are loaded with no items, no AI, and with the drop items flag set. If char ID is -3, the character to load is read from s2. If X and Y are negative, the position to load at is read from coordinates in sB.

Modifying Units

Various aspects of a unit’s current state can be edited with this set of event opcodes:

HIDEUNIT charID will hide the given unit in the player party.

SHOWUNIT charID will unhide the given unit in the player party.

SET_HP charID will set the given unit’s current HP to the value in s1. If HP is set to 0, it also marks the unit as dead.

SET_HASMOVED charID will gray out the given unit.

SET_HASMOVED_AI charID will mark the given unit as having already moved by the AI during the current phase.

SET_DEPLOYED charID will deploy or undeploy the given unit based on the value in s1. If 0, the unit will be undeployed. If 1, the unit will be deployed. If -1, the unit will be deployed only if they were not deployed in the previous chapter.

KILL charID will start the map sprite death fade animation for the given unit. Usually precedes AWAIT_KILL.

AWAIT_KILL charID will wait for any death fade animation to finish, then permanently remove the given unit. Usually follows KILL.

REMOVEUNIT charID will permanently remove the given unit.

If char ID is 0, the unit used is the first player unit. If char ID is -1, the unit used is the active unit. If char ID is -2, the unit used is the one at the coordinates in sB. If char ID is -3, the unit used is the one whose char ID matches the one in s2. If no unit with the specified ID exists the game will hang, exceptions being KILL, AWAIT_KILL, and REMOVEUNIT.

In addition, you can affect units en masse with these:

REMOVEALL_BLUE will hide every blue unit, then clear every cutscene unit.

REMOVEALL_GREEN will remove every green unit.

REMOVEALL_RED will remove every red unit.

Changing Unit Class Without Promotion

You can change the class of a unit in a few ways:

RECLASS charID classID will change the given character into the given class.

RECLASS_FROMCHAR charID1 charID2 will change the first character’s class into the default class of the second character.

SWITCH_CLASSES charID1 charID2 will change the first character’s class into the default class of the second character and change the second character to the previous class of the first character.

If char ID is 0, the unit used is the first player unit. If char ID is -1, the unit used is the active unit. If char ID is -2, the unit used is the one at the coordinates in sB. If char ID is -3, the unit used is the one whose char ID matches the one in s2. These only apply to character 1, character 2 is always checked by given character ID.

Set Active Unit

SET_ACTIVE charID will set the given character as the active unit. If the given character does not exist, the game will hang.

If char ID is 0, the unit used is the first player unit. If char ID is -1, the unit used is the active unit. If char ID is -2, the unit used is the one at the coordinates in sB. If char ID is -3, the unit used is the one whose char ID matches the one in s2.

Popups

You can make your own popup windows of the same kind that appear for obtaining items, but containing whatever text you want:

POPUP textID songID will display a popup with the given text while playing the given sound effect.

You can also use the box used in the vanilla game for location names in the corner of the screen:

BROWNBOX textID [X,Y] will display a location name box containing the given text at the given coordinates.

Moving the Player’s Cursor

You can check and set the position of the player’s cursor.

CHECK_CURSOR will return the cursor’s position in sC.

SET_CURSOR [X, Y] will set the cursor’s position to the given coordinates.

If the given coordinates are negative, the position to move the cursor to will be read from sB. SET_CURSOR_SB exists to do this for you. Note that this is the player’s cursor, as opposed to previously where we covered displaying arbitrary cursors.

Disable Menu Commands

You can disable specific commands on the unit and map menus.

DISABLEOPTIONS optionBits will disable unit and map menu options in adherence with the specified bits.

DISABLEWEAPONS optionBits will gray out weapons selectable for attacking in adherence with the specified bits.

Bit list for DISABLEOPTIONS:
0 - Attack (Unit Menu)

1 - Staff (Unit Menu)

2 - Wait (Unit Menu)

3 - Rescue (Unit Menu)

4 - Drop (Unit Menu)

5 - Visit (Unit Menu)

6 - Talk (Unit Menu)

7 - Item (Unit Menu)

8 - Discard (Item Menu)

9 - Trade (Unit Menu)

10 - Supply (Unit Menu)

11 - Support (Unit Menu)

12 - Armory (Unit Menu)

13 - Options (Map Menu)

14 - End (Map Menu)

Bit list for DISABLEWEAPONS:

0 - Item 1 (Weapon Select Menu)

1 - Item 2 (Weapon Select Menu)

2 - Item 3 (Weapon Select Menu)

3 - Item 4 (Weapon Select Menu)

4 - Item 5 (Weapon Select Menu)

Promote Units

PROMOTE charID classID itemID will take a given character and promote them to the given class and display the item used as the given item.

If char ID is 0, the unit used is the first player unit. If char ID is -1, the unit used is the active unit. If char ID is -2, the unit used is the one at the coordinates in sB. If char ID is -3, the unit used is the one whose char ID matches the one in s2.

PROMOTE must be used with a BG present. This BG must be cleared before returning to the map. After the unit promotes, the event engine will fade in automatically. If the class you promote to does not have animations for the weapon type you have equipped in the class you are promoting from, the promotion will happen improperly with map animations and cause a number of visual issues.

Warp In/Out Animations

WARPIN [X, Y] will draw the warp in effect at the given coordinates and play the appropriate sound effect.

WARPOUT [X, Y] will draw the warp out effect at the given coordinates and play the appropriate sound effect.

ENDWARP will remove the warp effect and play the appropriate sound effect.

Anything that does not load or alter a unit cannot be run between WARPIN/WARPOUT and ENDWARP. The idea here is placing a unit at the coordinates of the warp effect at the same time the effect is played. Generally, you’ll do something like:

WARPIN [X, Y]
LOAD1 0 UnitGroup
ENDWARP

WARPOUT [X, Y]
REMOVEUNIT charID

ENDWARP

Earthquake

EARTHQUAKE shakeDirection playSound will cause an earthquake effect, shaking in the set direction and playing sound if set to true.

EARTHQUAKE_END will stop the earthquake effect.

Valid shake directions are 0 for horizontal and 1 for vertical. Play sound is a boolean (true/false). The event engine does not pause for the earthquake effect, so you will need a STAL in between to have it be visible for any length of time.

Cutscene Animations

There are a number of animations you can call for use in cutscenes.

SUMMONUNIT charID will display the effect used when summoning a phantom on the given unit and play the appropriate sound.

BREAKSTONE charID will display the effect used in vanilla when a sacred stone breaks on the given unit and play the appropriate sound.

GLOWINGCROSS charID will display a glowing X underneath the given character and play the appropriate sound.

GLOWINGCROSS_END will remove the glowing cross.

If char ID is 0, the unit used is the first player unit. If char ID is -1, the unit used is the active unit. If char ID is -2, the unit used is the one at the coordinates in sB. If char ID is -3, the unit used is the one whose char ID matches the one in s2.

The event engine will pause for the duration of the execution of all of these effects. Only GLOWINGCROSS leaves any lingering effect after it is run, that being the purpose of GLOWINGCROSS_END to remove.

16 Likes

ok snek now it’s your turn to do your guide

7 Likes

Sme just watches me me in #hacking-help, thinking I must be a real crusty old boomer for reading Zim’s and Arch’s guides.

4 Likes

yeah yeah I’ll get to it eventually

5 Likes

this is such a great guide thank you sme

2 Likes

:tada: Thank you very much this can’t be understated

POIN $0 $0 $0 $0 $0 $0

but mmmyyyyy skirmishhhhh daaaaaaaaata :(

(We do actually know what Dunno data is for, but we’ll cover that later when we cover other similar things)

I feel like they should be named as a matter of style in this case.

TEXTSTART; TEXTSHOW textID; TEXTEND; AdditionalEvents; TEXTCONT; TEXTEND

Interesting! This greatly surprises me. I would have expected this to not require the redundant TEXTENDs.

And, of course;

If there are too many units currently moving, movement will be held until a moving unit reaches its destination.

(You have this text repeated in numerous places where it doesn’t make sense to include it.)

:tada: Thank you very much this can’t be understated

4 Likes

Some notes on enemy flags and randomization (I know tower/ruins stuff doesn’t get used a ton in most hacks, but it might be useful to somebody):

There is a table at $8D2060 which controls which classes map to which “classID”, you could certainly adapt it for non-monster classes if you wanted to take advantage of the random inventory/rare item drops this template allows you to configure. Circles provided some .nmms for these tables here. Just remember to give any units marked with MonsterTemplate an empty inventory (NoItems is EA’s shorthand for that)

Flag 4 is used, but it is almost exclusively combined with flag 1 (MonsterTemplate) in vanilla. (I didn’t find any data on flag 8 while I was researching the LOAD command internals, so that one probably still is unused.)
Flag 4 allows the unit’s starting position to be randomized slightly (respecting the terrain and class movement restrictions). It works in combination with the following instruction:

This event code is almost never called directly, there is a wrapper/helper at $9EE84C which does this for you and all tower/ruins/skirmish maps just call the wrapper. (EA actually does not know how to disassemble the _2B eventcodes properly and won’t output the parameter value, anyway, so disassembling vanilla doesn’t help here)
Here’s how vanilla does it:

SVAL 0xD 0x0 //clear queue size
SVAL 0x1 0x32 //50% randomization chance
SAVETOQUEUE
SVAL 0x1 0x19 //25% randomization chance
SAVETOQUEUE
SVAL 0x1 0xF //15% randomization chance
SAVETOQUEUE
SVAL 0x1 0x5 //5% randomization chance
SAVETOQUEUE
SVAL 0x1 0x5 //5% randomization chance
SAVETOQUEUE
CALL $9EE84C //randomizes positions of enemies based on these queued parameters + the result of a RANDOMNUMBER call
LOAD1 0 UnitsOffset 

Also, LOAD4 is also only used within a helper and never called directly (as it usually depends on the skirmish data in WorldMapStruct), but it defaults to loading the first skirmish enemy group for the chapter if called manually, for whatever that’s worth.

7 Likes