Event script modding
Elden Ring event scripts work a lot like event scripts in previous games. They use the EMEVD/EVD/EventMakerEx format. The main difference comes from new commands and the world itself, which is large and interconnected and composed of hundreds of different maps.
Event scripts are extremely powerful tools for modifying gameplay. They interact with many different game systems, but usually only in specific ways that the game needs. They can give you items and orchestrate boss encounters, but they can't spawn in an enemy not already present in map data. This means that the most successful approach to event modding is not to start from scratch. Instead:
- Identify some modification to the game's functionality you want to make.
- Understand that functionality when it appears elsewhere in the game. Investigate scripts, maps, params, until you fully understand how the functionality is configured and what all of the important pieces are.
- Copy that functionality, piece by piece, and make small tweaks it until it resembles your desired modification.
The most important skill in modding is investigation. The game is too big for any one person to know all of it, and fromsoft provides no intentional documentation or modding support, so figuring mechanics out yourself is a critical part of the process. But it's not as daunting as it sounds! It's easy to identify patterns in game data and scripts using tools built by modders, since modders have already done some of it for you and will try to give you pointers when you ask.
This page isn't a full tutorial, but at least the minimum stuff you need to for how to get started modding event scripts. The first part applies to any way of editing event script, but later is some analysis of events decompiled with MattScript specifically.
Essential info
This section does not dive into details on the scripts themselves, but walks through the various tools and resources you'll need to browse and understand them. It's a bit of work to set things up upfront, but well worth it for making mods quickly and correctly!
Prerequisite tools
Event scripts interact with many different game systems, so to best understand them, you can use a variety of different specialized tools together in concert. This is just a quick listing. Check the tools page, and tutorials page for more specific guides. (Remember to copy oo2core_4_win64.dll if any tool needs it!!)
Required tools:
- Use Selective UXM (Github link, Nexus link) to select the game exe and unpack the game. This requires around 50GB of extra disk space.
- Use Mod Engine 2 (Github link) to use standalone directories to both modify game files and seamlessly switch between different mods. You can download this anywhere, but all of your mods will be stored here, so choose a suitable project directory for you.
- Use DSMapStudio (Github link) to view MSB files, which come from map\mapstudio in the game hierarchy, and also to view/edit game params. This tool is super important.
- Use WitchyBND (Github link) or the older Yabber (Github link) for unpacking specific formats like archives (ending in bnd.dcx) and game messages (ending in fmg).
- Use various Elden Ring reference pages, also listed below under "entry points"
Of course, also use an event script editor. These are available for Elden Ring:
- DarkScript3 by AinTunez and thefifthmatt (used for some examples below)
- Soulstruct by Grimrukh
These tools are useful in specific situations:
- Use DSLuaDecompiler to decompile and view AI scripts. Currently, Altimor+nex3's fork is the most Elden-Ring-compatible. Note that it is a command line application and must be built with Visual Studio; you may be able to ask around for decompiled scripts.
- Use ESDLang (Github link) to view NPC dialogue trees and scripted menus like graces and shops.
- Use Elden Ring Debug Tool (Github link) and/or Elden Ring Practice Tool (Github link) for easier in-game testing.
Tips for browsing maps and scripts
Before getting into event scripts themselves, here are some miscellaneous tricks and tips for using editors and viewers.
To search for an entity in currently loaded maps in DSMapStudio, go to "Search properties" and type "EntityID". Enter any entity id to find currently loaded entities with that id. Select the enemy in the search results, then click on the central map viewport, then press F to jump to the entity. This works for many properties including "Name", where you can enter a name like c2500_9000.
If there is some object in the way which is normally invisible in game, you can click on it, and press Ctrl+H to hide it. I would also recommend browsing the Map Object List using Type View, and switching between different Gizmos > Origin modes as appropriate.
If you've mass-dumped AI scripts or text fmgs or ESDs, Notepad++ is a great way to find things inside of them, because you can use Find in Files (Ctrl+Shift+F) within a directory.
DarkScript3 tips
Here are some key productivity features for in DarkScript3 in particular.
You can decompile all event scripts to JS files so you can search them easier. You can do this with File > Batch Dump and selecting all events. I would recommend making a whole separate copy of game events only just to browse them, so you always have a complete reliable dump of all vanilla events, but it depends on the kind of mod you're making. Select "Use MattScript" for easier-to-read scripts, *especially* if it's just a read-only copy of events. More details on that below. Either way, this will take a few minutes to complete. The program will hang, but don't exit out of it!
To search within the current script, use Ctrl+F. To search across all dumped JS files in the current directory, use Ctrl+Shift+F. Or select either respective option from the Edit menu. This is invaluable for finding all of the places where an entity or flag is referenced or where a command is used.
A list of all commands and conditions is accessible from Help > View EMEDF, and also this online mirror.
In event scripts, all events must be initialized to run in-game. You can Ctrl+Click on an event id to jump to its event definition, and Ctrl+Click again to go to its initializations. You can do the same with Ctrl+Enter while the text cursor is inside an event id. The status bar will contain info about the result of the search. One feature added in v3.3: when common_func.emevd.dcx is open in another tab, Ctrl+Click can jump from common event initializations into the common_func event definition.
Another minor convenience feature is Ctrl+1. When the text cursor is on an integer, DarkScript3 can show the bytes-equivalent floating point value. This is a stopgap until event initializations can be fully typed. Until then, you shouldn't replace them in the script itself, unfortunately. If it's decompiled as an int, it needs to remain an int for the time being.
"Is this thing on?"
Don't forget! If something isn't happening in the game when you expect it, make sure your changes are even recognized by the game in the first place. This is best accomplished by simple "is it on" checks which do something conspicuous. For instance, in DarkScript3:
DisplayBanner(TextBannerType.DutyFulfilled);
AwardItemLot(997200);
There are a few banners which the game can show, listed in the TextBannerType enum. Banners are very conspicuous, but they have the disadvantage of taking up most of the screen and having imprecise timing. You can usually get better results from awarding an item lot from ItemLotParam_map. Respawning resource pickups are a great choice here, like 997200 for Rowa Fruit or 997960 for Ruin Fragment. See item analysis to search by item name, but make sure no event flag is present in the item lot, or else the drop won't be repeatable.
So, if you're not sure if an event is executing at all, first make sure that it's actually initialized in a constructor. Add a debug instruction at the very top of the event, and make sure the effect occurs in-game upon quitting to the main menu then loading back in. Then move the existing debug instruction around, for instance to make sure a later condition triggers when you expect it to. Fix any bugs and repeat this process until everything triggers when you expect it to.
One last note: make sure to remove the debug instruction when you've fixed the issue! If you've ever played Dark Souls mods where nonsensical banners pop up, now you know why.
Commentated events (MattScript)
In lieu of more specific modmaking tutorials (planned), here are a few events in Elden Ring opened with DarkScript3 with MattScript enabled. You can find general MattScript documentation here, but basically, when enabled, MattScript simplifies event script control flow and makes them easier to understand and edit. Its major strength is that it allows dealing with complex conditions without dealing with condition groups, and eliminates the need to count skips ever. It's also easier to use as a base for future advanced features. I'd recommend it for experienced modders, but especially for newcomers. Even if you don't use it everywhere, you can still enable it for individual events at any time (use $Event instead of Event, it's just that simple!).
Below, I've included several different events and explanations for them. They deal with several different game systems, so if you find a term you're unfamiliar with, don't hesitate to check the glossary. Feedback on how to improve clarity, or additional concepts to explain, is greatly appreciated.
If we want to add other dialects (like regular DarkScript3 or soulstruct), this can be split off into a separate page.
Intro to events
Let's start with some shorter events in the game, before getting into longer ones which pull together a ton of game systems. An event script defines many events which run while the script is loaded. You can think of these like functions in a programming language, each identified by a unique number. An event must be initialized to have any effect in the game at all. All events run in parallel, executing one instruction after the next from top to bottom, sometimes skipping optional instructions or stopping altogether until some condition is met.
For how loading works, that's a bit complicated. See the map list for an introduction to maps in Elden Ring. When a map is loaded, its event script is also loaded. When you die, or save and quit, or warp to a grace, all the loaded event scripts are loaded in again from scratch. Everything gets completely reinitialized, and if it's not stored in the save file somewhere, it's like it never happened.
How this works in the event script itself is that there are special events called constructors which get automatically initialized when an event script is loaded. These are events with low ids like 0 and 50. As you can see, these constructor events are responsible for initializing every other event in the file, and a few others too.
For instance, take a look at m14_00_00_00.emevd.dcx, which is the event script for Raya Lucaria. In DarkScript3, you can see its first constructor is a very long event which starts with these lines:
$Event(0, Default, function() { RegisterBonfire(14000002, 14001952, 0, 0, 0, 5); RegisterBonfire(14000003, 14001953, 0, 0, 0, 5); InitializeCommonEvent(0, 9005810, 14000800, 14000000, 14000950, 14001950, 1084227584); InitializeCommonEvent(0, 9005810, 14000850, 14000001, 14000951, 14001951, 1084227584); InitializeEvent(0, 14002080, 0); ... });
Like all events, it has three main parts: an event id (0 in this case), a reset behavior, and a series of commands which execute from top to bottom. The reset behavior of "Default" means that the event does not reload when you rest at a grace, but "Restart" means that it does. There will be examples of both below.
Event 14002080: Virgin abduction (standard event flow)
You can Ctrl+Click on 14002080 in the above event (within DarkScript3) to see the event's definition. You will find this event, which is responsible for the Iron Virgin warp from Raya Lucaria to Volcano Manor:
$Event(14002080, Default, function() { EndIf(!PlayerIsInOwnWorld()); WaitFor(PlayerIsInOwnWorld() && InArea(10000, 14002080) && CharacterHasSpEffect(10000, 14307)); SetPlayerRespawnPoint(16002080); SaveRequest(); SetEventFlagID(16000540, ON); });
So as soon as Raya Lucaria loads, each command in this event starts getting executed one-by-one. First, the event might end immediately if the player is summoned or invading (if they're not in they're own world), since they're not eligible for the warp in those cases. Then, execution pauses until all of these conditions are met, based on the WaitFor line:
- The player is still in their own world
- The player is in the "basement" area in Raya Lucaria at the bottom of the giant elevator. The entity id 10000 refers to the player, and the EntityID of this region is 14002080.
- The player has the SpEffect numbered 14307. A SpEffect is like a status flag set on an individual entity, which is capable of many different kinds of temporary and permanent character modifications. In this case, it's set on the player during the "getting eaten" animation to indicate they died from the Iron Virgin's attack.
Once all of the above conditions are met, execution can continue past the WaitFor. The event script knows the player is about to die, so it quickly changes some game state. It manually sets the player's respawn position, which would normally be their last grace, to Volcano Manor (16002080 is a region EntityID there). It issues a SaveRequest to make absolute sure the respawn point is saved before everything gets unloaded, and sets an event flag which is checked by the event script in Volcano Manor to know it should do special things like trapping the player there.
Most events are like this. They have some condition for ending early, some initial setup sometimes, some WaitFor condition before they properly activate, and some result when the condition is met.
I've mentioned event flags a few times. Event flags are values in an on or off state, accessed and stored by a numerical identifier. The id is important, since only a few ranges are recognized as valid by the game. They're used to track global state while the game is running, and depending on the id, to automatically store that state in save files.
Event 10002460: Blind eagles (event restarts)
Here is another event in Stormveil Castle (m10_00_00_00) which has two waits in it. It includes a RestartEvent, which is the one exception to events only running from top-to-bottom. When a restart command is encountered, execution stops there and jumps back up to the top. This basically resets the event to like it was when it was first initialized.
$Event(10002460, Restart, function() { WaitFor(TimeOfDayInRange(19, 0, 0, 5, 59, 0)); SetSpEffect(10005460, 10930); WaitUntilTimeOfDayInRange(6, 0, 0, 18, 59, 0); ClearSpEffect(10005460, 10930); RestartEvent(); });
Without going into a ton of detail, SetSpEffect(10005460, 10930) makes all of the knife-talon eagles in Stormveil Castle worse at detecting the player, changing their sight range from 8 meters to 2.4 meters. ClearSpEffect undoes that effect.
The overall flow of this script is: wait until the in-game time of day is between 7pm and 5:59am, and make eagles more blind. If that's already the case when the map is loaded, this doesn't wait at all, and instead happens right away. Then, wait until a time between 6am and 6:59pm to restore their vision. Repeat ad infinitum.
Event 18002860: Soldier of Godrick boss fight start (if statements)
This event is in m18_00_00_00, Stranded Graveyard, which also includes the Cave of Knowledge. It follows a very common pattern with Elden Ring boss fights, which is that there's an event which disables the boss when it's defeated, and also initializes the fight when it isn't.
The key thing to note here is if (<condition>) { statements. Unlike WaitFor, these don't wait for a condition to become true, but instead immediately evaluate it.
$Event(18002860, Restart, function() { if (EventFlag(18000850)) { DisableCharacter(18000850); DisableCharacterCollision(18000850); ForceCharacterDeath(18000850, false); EndEvent(); } DisableCharacterAI(18000850); WaitFor(EventFlag(18002855) && InArea(10000, 18002850)); EnableCharacterAI(18000850); SetNetworkUpdateRate(18000850, true, CharacterUpdateFrequency.AlwaysUpdate); DisplayBossHealthBar(Enabled, 18000850, 0, 904311000); });
You can break this event down into 4 key parts. For now, I'll skip over what the commands do exactly, since it's discussed as part of the Godskin Duo explanation below.
- If the boss is defeated (event flag 18000850 is on), prevent the enemy from respawning, and then end the event. By default, all enemies respawn after resting at a bonfire, unless manually disabled in this way.
- If the boss is not defeated:
- Before the WaitFor, disable Soldier of Godrick's AI to make them unresponsive, so they don't detect the player before the fight actually begins. This happens as soon as the event script loads.
- Wait for the fight to trigger. In this case, it's waiting for the player to pass through the fog gate. This is explained in more detail below.
- After the WaitFor, reactivate the boss's AI and make "Soldier of Godrick" appear at the bottom of the screen.
One thing to observe here is that the entity id for Soldier of Godrick 18000850 uses the same number as the event flag 18000850. From the engine's perspective, however, they are completely independent. Setting the flag does not automatically affect the enemy, except through this event. You can distinguish one id from the other based on the context they appear in.
All of the above functions like DisableCharacter, which are usable in event scripts, are listed in the EMEDF HTML. Some of these may be simpler versions of other instructions, like {DisableCharacter} and {EnableCharacter} are both simpler versions of the longer {ChangeCharacterEnableState}.
Custom event: Suffer for fashion (condition variables)
(Feel free to skip this section if it's overly technical.)
Most examples of condition variables in the vanilla game are somewhat complex. Event 10003736 in Stormveil Castle is one of the simpler ones, which controls Gostoc stealing runes from you, but it still involves a ton of quest-specific event flags.
Refer to MattScript documentation for all of the exact details, but in short, condition variables allow for declaring a condition expression on its own, rather than just passing it straight into a WaitFor, if statement, or various other special commands (EndIf, RestartIf, GotoIf). This doesn't actually evaluate the condition until it's used in one of those other commands, however. Normally, you should avoid using this, but it is needed in these situations:
- If you want to WaitFor any of several conditions to become true (like using foo || bar to wait for either foo or bar) and check which one was true at the time the WaitFor passed.
- If you want to make a condition which itself depends in other conditions. This is mainly useful for event scripts with parameters, where parameterized conditions might be invalid in some cases.
We'll get to the second case when we talk about parameters, but a quick example of the first use case is as follows. This event kills the player if they've been wearing a certain piece of armor for more than 30 seconds straight. It does this by waiting for one of two things: either 30 seconds have passed, or the armor is unequipped. It uses variable.Passed after the WaitFor to see which was the case, and reset the timer in the second case.
$Event(xxxxxxxx, Restart, function() { WaitFor(PlayerHasArmorEquipped(ArmorType.Legs, 1930300, -1)); notWearingArmor = !PlayerHasArmorEquipped(ArmorType.Legs, 1930300, -1); WaitFor(notWearingArmor || ElapsedSeconds(30)); if (notWearingArmor.Passed) { RestartEvent(); } ForceCharacterDeath(10000, false); });
Again, declaring a condition variable doesn't evaluate the condition. Instead, it "registers" it for later evaluation, though this has some important restrictions. It's a quirk of EMEVD that requires some caution. In MattScript, it's distinct from JavaScript variables, which are also supported; use const to declare those.
Godskin Duo
This is a full dissection of how the Godskin Duo fight works from an events perspective. The first three events are very standard and applicable to most bosses, but later on it gets into specific resurrection mechanics. Godskin Duo is somewhat complicated, but not as complicated as something like Rennala (in either phase). Feel free to look at any other boss, if you want, and try the same process on them.
It's highly recommend you watch Zullie's video https://www.youtube.com/watch?v=7uPOfbmD7_0 "The Four Kings have a secret fifth member" since Godskin Duo is set up in a similar way. They both use generators to spawn the same enemy over and over, as many times as desired, with a hidden healthbar-only enemy. The main difference is that Godskin Duo's generators do not run on an automatic timer, but use the manual InvokeEnemyGenerator command.
Make sure you have DSMapStudio and DarkScript3 open while browsing these scripts. Open the m13_00_00_00 map in DSMapStudio to find entities mentioned below using "Search properties".
Because these events are on the longer side, I've added commentary inside of the events themselves, using JavaScript comment lines (with //). You can check out events without comments first if you want, but make sure to open up the version with comments to see a line-by-line explanation.
Event 13002860: Start Godskin Duo
Like with Soldier of Godrick, this event either initializes the fight or disables all of the boss enemies. Unlike Soldier of Godrick, Godskin Duo is more complicated because multiple enemies are involved, and there is different behavior depending on whether it's a first or subsequent encounter. This is the big if/else statement in the middle of the event, which switches between the two behaviors based on an event flag.
This fight actually involves three enemies, with these entity ids. Try locating them with DSMapStudio Property Search.
- 13000850: The hidden healthbar enemy, lurking beneath the floor. This is technically a Godskin Apostle, but the only thing that matters is that it has a lot of HP. See the Four Kings video!
- 13000851: The Godskin Apostle you fight
- 13000852: The Godskin Noble you fight
After this event finishes, the entire rest of the fight is just fighting the godskins as if they were normal enemies (aside from the regeneration mechanic, which is handled by other events). They choose attacks based on their AI script, which is specified in their NpcThinkParam.
Make sure to click on "Show event with comments" before continuing on!
Event 13002850: End Godskin Duo
The defeat event also follows a very regular structure for all Elden Ring bosses with fixed boss arenas. The most important thing this event does is turn the boss defeat flag 13000850 on, but it's responsible for many other rewards and effects as well.
Event 13002861: Music change
Not every boss has an event like this, but this approach is used in any boss where there is some condition for the soundtrack to "heat up".
Event 13002890: Godskin summoning after one dies
Now, it's time to discuss event parameters. These can be hard to wrap your head around. It allows for multiple copies of an event to run at the same time, but to behave in slightly different ways, or apply to different enemies.
Parameterized events are a natural choice for Godskin Duo, because there are two of them. In one case, you'd want Godskin Noble to see that the Apostle has died and summon them, and for the Apostle to respawn. In another case, you'd want the exact same thing, but with the enemies swapped. Making an event with parameters allows you to reuse the same logic in both cases.
The next event starts out like this:
$Event(13002890, Restart, function(X0_4, X4_4, X8_4, X12_4) { ... });
Note the extra X0_4, X4_4, and so on. Remember that all events must be initialized in order to be active. In this case, there is extra data passed in during initialization! If you Ctrl+Click on the event id here, you can hop to the initializations, and you'll see these two lines:
InitializeEvent(0, 13002890, 13002944, 13000851, 13000852, 15504); InitializeEvent(1, 13002890, 13002945, 13000852, 13000851, 15454);
0 or 1 is the event slot, and 13002890 is the event id, and all of the rest of the arguments are copied into X0_4 X4_4 etc. in order. What this means is that there are two copies of the event running simultaneously, which can run completely independently from each other. One of them tracks Apostle, the other tracks Noble. (Side note: the syntax for this will be improved in future versions of MattScript. In a few cases meanwhile, the arguments won't exactly match, since they're technically byte offsets.) Now, onto the events.
In this event, when one of the godskins is dead for 20 seconds, prompt the other one to resurrect them. If it helps, you can think of specific values (like X4_4 being 13000851 and X8_4 being 13000852) as you read through the script. Note that the meaning of X4_4 can change completely from one event to another; the name is only based on the index of the parameter and doesn't mean anything by itself.
Event 13002891: Godskin spawning in after being summoned
This event also has two initializations, and it works together with 13002890 to finish the summon+resurrect mechanic.
It respawns a godskin when it has been requested by the other alive godskin.
InitializeEvent(0, 13002891, 13002944, 13003851, 13000852, 15506, 13000851, 13002873); InitializeEvent(1, 13002891, 13002945, 13003852, 13000851, 15456, 13000852, 13002874);
If you want to quickly verify the SpEffect interaction yourself, you can use Elden Ring Script SpEffect Animations. To summarize: in the case of Godskin Apostle signaling for Godskin Noble to respawn, Apostle emits SpEffect 15456. This corresponds to this line:
SpEffect 15456 (c3560): anim 3038 frame 96-99
Then you can use DSLuaDecompiler (linked above) to decompile 356000_battle.lua from script/356000_battle.luabnd.dcx, and see that animation 3038 corresponds to Act18. In Goal.Activate, having SpEffect 15454 from the previous event leads to Act18's weight being set to 100%.
Event 13002892: Godskins spawning in after both die
From perusing the above scripts, you may have noticed a potential edge case. When one godskin is alive and the other is dead, they summon each other after 20 seconds. But what if they both die? Well, this final event includes handling for just that edge case. It's long, but the logic is straightforward.
When both godskins are dead, this event randomly picks one of them to respawn without an explicit summon. The randomly respawned godskin will then try to respawn their buddy using the above events.
I've left out event 13002859, which is how Bernahl enters the fight after he's summoned. It is not all that complicated, and you can see how event scripts control him throughout the process. There's no specific logic relevant to the Godskin Duo fight, though, so I've excluded it here.
Fog gates
Let's talk about common_func, which is an emevd file with no initializations. Instead, it contains a bunch of parameterized events which get initialized from other event scripts, and its main purpose is to contain heavily reused events which would otherwise have to be copied to a hundred different event scripts.
DS1 had no common_func and as a result it contained a lot of duplication. The same fog gate event was copied in a dozen different maps, although it could be reused for multiple bosses within each map. Bloodborne introduced a prototype version of common_func which allowed all Chalice dungeons to share the same set of events with map-specific parameters. DS3 properly introduced common_func which was usable in all maps. (DS2, if you're curious, uses ESD instead of emevd, also with a lot of duplication.)
Fog gates are a good example to start with, though the logic behind them is quite complicated. Almost all boss fog gates in all maps use the same common_func events, and most map-specific boss start events wait for the fog gate entry flag to get set in order for the fight to start. This is flag 13002855 in the case of Godskin Duo.
Event 9005800 has 3 initializations for Godskin Duo in m13_00_00_00, one for each fog gate you can enter. It has just over 100 initializations in the entire game.
This event allows a host player to traverse a boss fog gate, and sets a flag once they do. Summoned players use a separate event 9005801 to traverse the fog gate after the host does, meaning when flag X12_4 here is set. They also run this event, but it has no effect for them.
InitializeCommonEvent(0, 9005800, 13000850, 13001850, 13002850, 13002855, 13005850, 10000, 13000851, 13002851); InitializeCommonEvent(0, 9005800, 13000850, 13001852, 13002852, 13002855, 13005850, 10000, 13000851, 13002851); InitializeCommonEvent(0, 9005800, 13000850, 13001853, 13002853, 13002855, 13005850, 10000, 13000851, 13002851);
So the above event allows you pass through a solid wall using RotateCharacter, which forces an animation which ignores walls. It assumes the fog gate is blocking your way, but event 9005811 is the event that's responsible for actually making the fog gate appear and disappear. It has 5 initializations in m13_00_00_00, one for each fog gate, including those on the upper level of the Dragon Temple:
InitializeCommonEvent(0, 9005811, 13000850, 13001850, 5, 13000851); InitializeCommonEvent(0, 9005811, 13000850, 13001852, 5, 13000851); InitializeCommonEvent(0, 9005811, 13000850, 13001853, 5, 13000851); InitializeCommonEvent(0, 9005811, 13000850, 13001854, 5, 13000851); InitializeCommonEvent(0, 9005811, 13000850, 13001855, 5, 13000851);
This event controls when the fog gate shows up and disappears using the ChangeAssetEnableState and CreateObjectfollowingSFX commands. I won't explain the details of the event here, as it behaves in a bunch of different complicated ways depending on whether you're a host, a summon, or the invader. Still, the arguments are as follows:
// X0_4: The boss defeat flag. When this is on, the fog gate does not show up. // X4_4: Entity id for the fog gate asset // X8_4: SFX id which is responsible for an appropriate amount of the swirly golden SFX. // Without this, the fog gate would just be a see-through wall. // X12_4: An optional activation flag. If the fog gate has some delay in turning on, like // waiting for the first encounter to trigger, this is usually that encounter flag. // If the fog gate is up from the start, this is usually set to 0. $Event(9005811, Restart, function(X0_4, X4_4, X8_4, X12_4) { // Explore the contents yourself! });
Enemy activation
Here is one more common_func event which is not applied to bosses, but to regular enemies chilling in a level. Without any scripting, most enemies will activate when you enter vision or hearing ranges, which are broad spheres/cones defined in NpcThinkParam.
Some enemy encounters are more special, however, like enemies which hang from the ceiling and drop down when you step right below them, or wait around a corner to ambush you, or stand up from a slouch when you approach. In those cases, the enemy starts out in a looping animation which usually also greatly limits their vision and hearing so they can't detect you normally (speffects 5450 and 5080). Then, when some condition is met, like the player entering a region and/or radius, the enemy plays a wakeup animation. Alternatively, if the enemy is damaged they should also wake up, which usually leads to them playing their normal stagger animation and leaving their standby state that way.
Some version of this wakeup event is in every Soulsborne game, and it's a bit easier to understand if you're familiar with it from previous games. Still, this is a very commonly used event with dozens of variations, so it helps to be familiar with it!
Here is an example initialization in m14_00_00_00 (Raya Lucaria), which applies to the scholar in the middle of the Cuckoo Church:
InitializeCommonEvent(0, 90005200, 14000210, 30000, 20000, 14002451, 1065353216, 0, 0, 0, 0);
Here is another initialization in m30_04_00_00 (Murkwater Catacombs), which applies to an imp waiting to drop down from the ceiling when you approach:
InitializeCommonEvent(0, 90005200, 30040200, 30001, 20001, 30042200, 0, 1, 0, 0, 0);
In total, event 90005200 has nearly 600 total initializations across all maps in the game. It puts an enemy in standby and does a wakeup animation when the player enters a region. You can use ForceAnimationPlayback (or a Cheat Engine table with animation playback) to see these animations in-game.
How to investigate game behaviors
Remember that the key question for modding is "how does the game do X?", which you have to answer before doing X yourself. But the game has a lot of data, the challenge isn't just understanding it, it's finding even the smallest foothold for what you should be looking for. The best way to do this is to have entry points: things you can observe in the game itself, in particular locations or circumstances. You can then use the below entry points to find the appropriate numerical ids, then search for those numbers in the appropriate places.
Now that you've gotten a taste of event scripts above, here is a more detailed guide on how to start an investigation yourself:
In-game text
- Where it's located: In item.msgbnd.dcx and menu.msgbnd.dcx in msg/engus for English text, and other language/country names for other languages
- How to browse it: Use DSMapStudio's FMG editor (a version from 12 July 2022 or later) to browse all FMGs and search for text in the game. Alternatively, use Yabber to unpack the msgbnd files, and then use again it to unpack all of the FMG files in each one, and use Notepad++ or grep to search across all of them.
- How to use it: If you encounter text like "Enter evergaol" or "Traverse the mist" or a place name, NPC name, or item name, just search for it in FMGs. This will usually be easier if you know which FMG it is. This will give you a numerical id for the text, and you can search event scripts or params or ESDs for it, depending on the context. For instance, boss healthbar names are in NpcName.fmg and are always used in emevd directly.
- Example: You want to mod evergaol entry, so you look for "Enter evergaol" in the EventTextForMap FMG, and you get id 20300. Using Ctrl+Shift+F in DarkScript3 with "Match whole word", you see that it's a parameter to common_func event 90005881 in all of the maps with evergaols in them. This event includes a fade-to-black warp after you accept the prompt.
Item lots
- Where it's located: In ItemLotParam_enemy (random enemy drops) and ItemLotParam_map (everything else) in params (regulation.bin). Any time you see an item popup in the game, it is through an item lot being acquired. There are also shops, in ShopLineupParam.
- How to browse it: I would strongly recommend using Elden Ring Item Analysis, which is a summary of all item lots and shops in the game. You can also use a param editor to look at the params directly.
- How to use it: If there's an item you get somewhere in the game, look for the item lot to see where and how it's triggered. Item lots are referenced from a bunch of different places: from other params, like random enemy drops in NpcParam and material pickups like AssetEnvironmentGeometryParam, from MSB files for treasures you can pick up, and directly from emevd and ESD for scripted item popups. You can search for item lot ids yourself, but Item Analysis contains all of the references in one place as a starting point, so you can start with the appropriate MSB/emevd/ESD/param file. It also contains all event flags, which can be useful since event scripts often reference the flag as shorthand for "did you acquire this item?"
- Example: You want to see how Mohg's Great Rune is activated. From the item analysis page, you can see that there are two separate gifts for the inactive version (good 8152, acquired in Mohgwyn) and the active version (good 195, acquired in East Altus Divine Tower). Use an FMG viewer to verify that using the items' descriptions. Use Ctrl+Shift+F in DarkScript3 to search for item lot 34140710, and find an initialization of common_func 90005110. This event includes a "Great Rune Restored" banner.
- Example: You want to see where you get the Drawing-Room Key. Again search for it in item analysis: you'll note that there are two different item lots, but they both use the same flag (400072), so they're mutually exclusive. One place is in her chair as "Shiny Item" 16001708. Search for this in DarkScript3 and you'll find an initialization of common_func 90005750, which depends on flag 16009270 getting set, which depends on flag 16000800 getting set (Rykard's defeat flag). The other item lot is 100720, which mentions her ESD 300001600. Look at t300001600.py from ESDLang and you'll see that it is awarded after selecting "Join Volcano Manor" in a few different dialogue variants, and based on flags you can trace back to event scripts.
Map ids
- Where it's located: Maps are how most of the game world is split up, so finding a map id is critical to being able to search for something in a particular MSB (in map/mapstudio) or event script (in event).
- How to browse it: Use Elden Ring Map List to find commonly used maps, or use the lists provided in various editors. Other dumps like Item Analysis also contain map references to everything.
- How to use it: Open the files in a map/event editor!
- Example: You want to view/edit something in Roundtable Hold. Find that it's m11_10_00_00 in the map list, and open those files in DarkScript3/DSMapStudio.
Model names
- Where it's located: Character models are defined in the chr directory, and are referenced using the ModelName field in MSB parts like enemies, assets, etc.
- How to browse it: Use Elden Ring CHR ID reference to identify the model you're looking for.
- How to use it: In addition to or instead of the above reference, you can also use the Enemy Locations Dump to find specific maps to open, then open the relevant maps in DSMapStudio. To find out how the enemy is scripted, use its EntityID.
- Example: You want to find Radahn. Use above references to see that he's c4730, and that his full name is m60_52_38_00-c4730_9002, but he's located in map m60_13_09_02. The different ids are because his physical location is in the 52_38 small tile, but he can travel far away from that, so his data is in the 13_09 big tile instead. See the map reference for details.
Entity ids
- Where it's located: This is the most important thing to know about an enemy you want to script, or any other map entity like a region or generator. It's the EntityID field in MSB, and must be unique for any given entity. Alternatively, EntityGroupIDs are used in some contexts to refer to a group of entities at once.
- How to browse it: Once you find an enemy in a map, you can find its EntityID field. You can also start from the Entity ID List which contains most entity ids and entity group ids in the game.
- How to use it: The main use of entity ids is to search event scripts for it! Once you do that, you can find all scripted encounters involving that enemy. (Be aware that the same number can refer to either the entity id itself or to a flag/item lot, depending on context.) You can also jump to the entity in DSMapStudio to view it (see tips below). A handful of params also reference entity id.
- Example: You want to find Radahn's scripts. You know he's a c4730 in m60_13_09_02, so you use one of the above references or DSMapStudio to find that his entity id is 1052380800. You can then Ctrl+Shift+F to find his scripting in DarkScript3, and you can see that most of it is in the m60_52_38_00 emevd.
Dialogue
- Where it's located: Each NPC dialogue line is specified in TalkParam. The first line of dialogue of any sequence of lines is invoked from ESD files, which are used for NPC menus and dialogue trees.
- How to browse it: For dialogue specifically, I'd recommend dumping the entire game using ESDLang and searching for the first line of dialogue using multi-file search like Notepad++ or grep. If you need to find the first line, do a general FMG search in TalkMsg.fmg, then find the starting line of that section of ids. This is slightly tricky because the TalkParam id does not always match the FMG id. Finally, note that the same dialogue trees are used in multiple different ESD files. To find the right one, crossreference the talk ID with the Enemy Location dump and find it in the MSB.
- How to use it: Once you find the ESD file dialogue is used in, you can browse the script to see what triggers it and what else happens at the same time. Usually this is based on either reading flags or setting them, and you can then search event scripts to see how those flags in turn are triggered and used.
- Example: You want to know what makes you warp to Roundtable Hold, so you can search ESDLang output for the line "Forgive me. I've been…testing you." (or alternatively, the EventTalkForText entry "Go to the Roundtable Hold"), and you can see it's in t000003000.py. Grace Melina doesn't exist as a character in any map; she's spawned by the graces themselves (ESD 1000). You can search for the function name x48 and find it's called by x72, and find two conditions for activation: flags 10000851, 3062, 3063, 3064, or 3065. Search for these in event scripts to find that the first flag is for encountering Margit, and the other flags are for resting at an overworld grace outside of Limgrave.