Script Editing - Kizari/Flagrum GitHub Wiki

このガイドの日本語版をご覧になりたい方は、こちらをクリックしてください。
 

Table of Contents

Requirements

You will need:

  • Final Fantasy XV Windows Edition
  • Flagrum
  • A text editor—preferably one that can handle XML syntax (Notepad++ is a good example, but any will do)
     

Introduction and Terminology

FFXV scripts are very similar to the likes of Blueprints in Unreal Engine. They are a series of nodes that are connected together to form a sequence of actions for the game to perform.
 

Node Graph

A visual representation of a FFXV Node Graph—this is not a real tool!

 

These node graphs are stored in a text-based format known as XML (eXtensible Markup Language), which is a common standard of data representation. If you are not already familiar with XML, it may benefit you to learn a bit more about how it works before diving deeper into this guide.

Unfortunately, FFXV does not have a visual node editor at this time, so the only way to alter these node graphs is by rewriting the XML.

The table below is optional for getting a better understanding of how the game refers to script files.
 

Term Full Name Purpose
ebex Ebony Entity XML This is the name of a FFXV XML file before it is converted to XMB2 for the game. You will see this extension in URIs in Flagrum's Asset Explorer, but the underlying file type is exml.
exml Ebony XML This is the name given to FFXV XML files after they are converted to XMB/XMB2. This is the format that scripts you find in the game files will already be in.
xmb XML Binary This is a binary representation of XML that the game uses for performance reasons. XMB is only used in older demo versions of the game.
xmb2 XML Binary Version 2 This is version 2 of the binary representation of XML. XMB2 is used in all release versions of the game and is what is contained in exml files before conversion to xml.
prefab Prefabricated This file is generally used in environments and is basically just a group of constructs that share a similar theme. These are effectively just another exml file.

 

Finding and Converting Script Files

Flagrum's Asset Explorer is the best tool for this job. Any file that has the exml extension is a script. While these scripts are in XMB2 form, Flagrum will automatically convert them to XML when left-clicking to preview so that they are human-readable. To edit a script, you can Right-Click > Export as XML to save the file to disk.
 

Export as XML
 


WARNING: Do NOT try to [Ctrl + F] to find keywords in Flagrum's XML preview as it will
only search the text that is currently visible on screen! Export the file before searching it this way.


 

Anatomy of an EXML File

Every EXML file contains a <package> element which is just a container for the contents of the file. These are often referred to by the engine as an Entity Package. You will seldom ever edit this.

Directly inside the package is an <objects> element, which is just declares that a list of <object> elements will be inside of it.

<package name="Name_of_Package">
  <objects>
    ...
  </objects>
</package>

 

Inside the <objects> element is a list of <object> elements. An <object> represents one construct on the node graph. This is usually either a container, or a node. You can think of a node as one box on the visual representation at the start.

<object objectIndex="75" ownerPath="nodes_" checked="True" type="SQEX.Ebony.Framework.Sequence.Variable.SequenceConstInt" path="entities_.Ctrl.nodes_.[0].nodes_.[38].nodes_.[3]" name="[3]" ownerIndex="71" owner="entities_.Ctrl.nodes_.[0].nodes_.[38]">
  <out_>
    <connections_>
      <reference objectIndex="72" reference="True" relativePath="refInPorts_.Mode" object="entities_.Ctrl.nodes_.[0].nodes_.[38].nodes_.[0]" />
    </connections_>
  </out_>
  <value_ type="int">2</value_>
</object>

 

The above example is a simple node. Let's break it down.

Attribute Purpose
objectIndex This is a unique number for identifying the object. No two objects in the file can have the same index.
ownerPath This is the path to the container which this object is inside of.
checked This determines whether the object is active or not.
type The most important attribute, this is the type of object that this is and determines its behaviour.
path This is the path to the object. It is essentially just a combination of all paths of the containers it is within separated by a . plus its own name.
name This is the name of the object. This should also be unique.
ownerIndex The index of the container object that this object is inside of.
owner The path of the container object that this object is inside of.

 
It is important to keep each of these attributes in mind when adding your own nodes, as they will need to have the correct values to function.
 

Connections

Nodes in the node graph are connected together to create the overall sequence of events. There are two types of connections:

Trigger Connections — These control the order in which nodes are executed
Value Connections — These pass values from one node to another

In general, nodes must be inside the same container to be connected successfully.

Take the following two nodes for example. Some of the data has been omitted to make it easier to read.

<object objectIndex="2" path="entities_.nodes_.[5]" name="[5]">
  <out_>
    <connections_>
      <reference objectIndex="3" reference="True" relativePath="_in" object="entities_.nodes_.[6]" />
    </connections_>
  </out_>
</object>

We'll refer to this as "object 2."

<object objectIndex="3" path="entities_.nodes_.[6]" name="[6]">
  <in_>
    <connections_>
      <reference objectIndex="2" reference="True" relativePath="_out" object="entities_.nodes_.[5]" />
    </connections_>
  </in_>
</object>

We'll refer to this as "object 3."

You can see that object 2 has an element called <out_>. We know this is a port on the node because it has a <connections_> element inside it. We can infer from the name that it is an output port. Likewise, object 3 has an input port.

We can see by the path of each object that they are inside the same container, as the path is identical besides for the name at the very end. Therefore, we know that these can be connected successfully.

Nodes must be connected at both ends to form a successful connection.

Inside of <connections_> on object 2, the <reference> element means that it's a reference to another object.
 

The structure is simple.

Attribute Purpose
objectIndex The index of the object this reference is pointing to
reference Not entirely sure why this was needed as it will always be true, but this must be here
relativePath The path from the reference object to the port this connection is connecting to
object The path of the object this reference is connecting to

 
With this in mind, it is clear that this reference is a connection to object 3's <in_> port.

Likewise, there is a reference back to object 2's <out_> port on object 3, as this must be connected at both ends.

The above example can be visually represented as follows.
 

Connections
 

Containers

In EXML, a container is an object that can hold other objects.
 

Containers

A container may hold many nodes or only a few—not all nodes have to be in a container

 
The game's behaviour is mostly controlled by sequence scripts. As an example of a container, all sequence scripts have a sequence container that holds all the nodes that are part of the sequence of events.

<object objectIndex="1" ownerPath="entities_" checked="True" type="SQEX.Ebony.Framework.Sequence.SequenceContainer" path="entities_.Layout" name="Layout" ownerIndex="0" owner="">
  <updateAtPause_ type="bool">False</updateAtPause_>
  <nodes_>
    <reference objectIndex="2" reference="True" object="entities_.Layout.nodes_.[70]" />
    <reference objectIndex="3" reference="True" object="entities_.Layout.nodes_.[71]" />
    <reference objectIndex="4" reference="True" object="entities_.Layout.nodes_.[72]" />
    <reference objectIndex="5" reference="True" object="entities_.Layout.nodes_.[73]" />
    <reference objectIndex="6" reference="True" object="entities_.Layout.nodes_.[74]" />
    <reference objectIndex="7" reference="True" object="entities_.Layout.nodes_.[75]" />
    <reference objectIndex="8" reference="True" object="entities_.Layout.nodes_.[76]" />
    <reference objectIndex="9" reference="True" object="entities_.Layout.nodes_.[82]" />
    <reference objectIndex="10" reference="True" object="entities_.Layout.nodes_.[83]" />
    <reference objectIndex="11" reference="True" object="entities_.Layout.nodes_.[84]" />
    <reference objectIndex="12" reference="True" object="entities_.Layout.nodes_.[77]" />
    <reference objectIndex="13" reference="True" object="entities_.Layout.nodes_.[81]" />
    <reference objectIndex="14" reference="True" object="entities_.Layout.nodes_.[80]" />
    <reference objectIndex="15" reference="True" object="entities_.Layout.nodes_.[79]" />
    <reference objectIndex="16" reference="True" object="entities_.Layout.nodes_.[78]" />
  </nodes_>
  <lastCenterX_ type="float">457.4387</lastCenterX_>
  <lastCenterY_ type="float">52.94099</lastCenterY_>
  <bIsPrefabTopSequence_ type="bool">True</bIsPrefabTopSequence_>
</object>

 
The important thing to note with container objects is that they contain a <nodes_> element. This is what determines which nodes are inside of the container. The <reference> elements within the <nodes_> collection is identical to the <reference> elements from the connections section, so you may refer back there for the structure.
 

Deleting Existing Behaviour

Sometimes there are parts of the game that we don't like and may wish to remove. An example of a mod I created was one that removed the Photo Contest from Galdin Quay, as it was never removed after the event ended.

To remove a node, you must remove the node itself, as well as any references to it. Be aware that any behaviour that is connected to the <out_> connector of the removed node will cease to function, so if this is not the intention, be sure to create a new connection to the previous node in the sequence.

To remove the node itself, simply delete the entire object starting at <object...> and ending at </object>.

To remove a reference to the node, delete the entire <reference ... /> tag.
 

Altering Existing Behaviour

There are countless nodes in the game, and very few have been explored, so it's not possible to cover this in great detail here. This is the part where you will need to experiment with different values to find out what does what. It would be great if you could also share any significant findings with us in the EXINERIS Discord so that we can improve this documentation further.

To alter existing behaviour, you want to change values inside the nodes other than the connections that may have an observable effect on the game.

Take the following example:

<object objectIndex="347" ownerPath="nodes_" checked="True" type="Black.Sequence.Actor.SequenceActionActorSetStatusInt" path="entities_.Ctrl.nodes_.[0].nodes_.[117]" name="[117]" ownerIndex="18" owner="entities_.Ctrl.nodes_.[0]">
 <Isolated_ type="bool">False</Isolated_>
 <in_>
   <connections_>
     <reference objectIndex="18" reference="True" relativePath="triInPorts_.CloseMenu" object="entities_.Ctrl.nodes_.[0]"/>
   </connections_>
 </in_>
 <target_ value="10" type="enum">TARGET_PLAYER_NOCTIS</target_>
 <kind_ value="789" type="enum">STATUS_KNIGHTS_OF_EOS</kind_>
 <value_ type="int">1</value_>
</object>

  A group in the EXINERIS Discord discovered how to activate the flying armiger mode by looking through the Leviathan fight.

By changing the <kind_> value of the SequenceActionActorSetStatusInt to 789 (STATUS_KNIGHTS_OF_EOS), and setting the <target_> value to 10 (TARGET_PLAYER_NOCTIS), this mode could be enabled anywhere in the game by connecting it to a node somewhere else.

The original node also had an <out_> element, which was removed as no additional behaviour was required to be executed after this, and an <inValue_> element, which was removed as we just wanted it to use the <value_> element instead to avoid needing an extra unnecessary node. The <value_> itself was set to 1 for on, but can also be set to 0 for off to disable the mode if it is already active.
 

Adding New Behaviour

This is of course the most exciting prospect of script editing. The ability to add your own behaviour to the game.

Following on from the example above, while it is great to be able to alter the flying armiger mode in the Leviathan fight, it's much more useful to be able to enable it elsewhere in the game world.

As a simple activation method for the flying armiger mode, it was proposed to have it activate when interacting with a chocobo rental machine. I opened up menuswfentry_chocoborental.exml and started scrolling until I saw this <CloseMenu> element on object 4.
 

<CloseMenu dynamic="True" type="SQEX.Ebony.Framework.Node.GraphTriggerInputPin">
  <pinName_ type="string">CloseMenu</pinName_>
  <connections_>
    <reference objectIndex="56" reference="True" relativePath="in_" object="entities_.Ctrl.nodes_.[0].nodes_.[74]" />
  </connections_>
  <isBrowsable_ type="bool">True</isBrowsable_>
  <delayType_ value="1" type="enum">DT_TIME</delayType_>
  <delayTime_ type="float">0</delayTime_>
  <delayMaxTime_ type="float">-1</delayMaxTime_>
  <pinType_ value="0" type="enum">PT_ARBITRARY</pinType_>
</CloseMenu>

 

I decided that we could plug the node in here so it could be activated when the menu for the rental is closed.

Seeing that there was a connection to object 56 here, I went down to that object and pasted the modified SequenceActionActorSetStatusInt node from the Altering Existing Behaviour section of this guide right below it.

We know that no two objects in an exml file can have the same index, so I scrolled to the very bottom of the file where I noticed the last object was 60. I updated the index of this newly pasted node to 61 so it would not conflict.

Looking at object 56, the ownerIndex was 4, so we know that object 56 is inside container object 4. I went up to this container to add a reference to this new node as covered in the Containers section.
 

<nodes_>
  <reference objectIndex="5" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[13]" />
  <reference objectIndex="6" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[14]" />
  <reference objectIndex="7" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[15]" />
  <reference objectIndex="8" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[16]" />
  <reference objectIndex="14" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[31]" />
  <reference objectIndex="15" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[49]" />
  <reference objectIndex="16" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[55]" />
  <reference objectIndex="31" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[58]" />
  <reference objectIndex="32" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[60]" />
  <reference objectIndex="33" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[61]" />
  <reference objectIndex="34" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[62]" />
  <reference objectIndex="35" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[64]" />
  <reference objectIndex="42" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[65]" />
  <reference objectIndex="43" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[66]" />
  <reference objectIndex="44" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[67]" />
  <reference objectIndex="45" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[68]" />
  <reference objectIndex="46" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[69]" />
  <reference objectIndex="47" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[70]" />
  <reference objectIndex="48" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[71]" />
  <reference objectIndex="49" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[72]" />
  <reference objectIndex="55" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[73]" />
  <reference objectIndex="56" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[74]" />
  <reference objectIndex="57" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[75]" />
  <reference objectIndex="61" reference="True" object="entities_.Ctrl.nodes_.[0].nodes_.[76]" />
</nodes_>

 

I simply copied the reference to 57, and updated it accordingly. I used 61 as that's the index I just gave the new node, and I used [76] as the name, as it was the next available number in the sequence.

Back down to the new node, I copied the ownerPath, path, ownerIndex, and owner from object 56. This is because I want it to be in the same container as 56. I then updated name to [76] to match the new reference, and changed the end of the path to [76] as well.

Lastly, this node needed to be connected to the <CloseMenu> behaviour so it will be triggered when the menu is closed. As covered in the Connections section, a connection is needed at both ends to achieve this.
 

<object objectIndex="61" ownerPath="nodes_" checked="True" type="Black.Sequence.Actor.SequenceActionActorSetStatusInt" path="entities_.Ctrl.nodes_.[0].nodes_.[76]" name="[76]" ownerIndex="4" owner="entities_.Ctrl.nodes_.[0]">
  <Isolated_ type="bool">False</Isolated_>
  <in_>
    <connections_>
      <reference objectIndex="4" reference="True" relativePath="triInPorts_.CloseMenu" object="entities_.Ctrl.nodes_.[0]" />
    </connections_>
  </in_>
  <target_ value="10" type="enum">TARGET_PLAYER_NOCTIS</target_>
  <kind_ value="789" type="enum">STATUS_KNIGHTS_OF_EOS</kind_>
  <value_ type="int">1</value_>
</object>

 

Above is the final XML for the flying armiger node. You can see the reference connects it back to the <CloseMenu> port of the <triInPorts_> element on object 4. Likewise, a reference to the <in_> port on the new node (object 61) was added to the <CloseMenu> element on object 4 to complete the connection.
 

<CloseMenu dynamic="True" type="SQEX.Ebony.Framework.Node.GraphTriggerInputPin">
  <pinName_ type="string">CloseMenu</pinName_>
  <connections_>
    <reference objectIndex="56" reference="True" relativePath="in_" object="entities_.Ctrl.nodes_.[0].nodes_.[74]" />
    <reference objectIndex="4" reference="True" relativePath="in_" object="entities_.Ctrl.nodes_.[0].nodes_.[76]" />
  </connections_>
  <isBrowsable_ type="bool">True</isBrowsable_>
  <delayType_ value="1" type="enum">DT_TIME</delayType_>
  <delayTime_ type="float">0</delayTime_>
  <delayMaxTime_ type="float">-1</delayMaxTime_>
  <pinType_ value="0" type="enum">PT_ARBITRARY</pinType_>
</CloseMenu>

 

Putting the Script In Game

With the script finished, all that was left was to put it in-game. Thankfully, Flagrum makes this a breeze. I simply created a new mod in the Mod Manager, replaced the menuswfentry_chocoborental.exml file with my modified XML file, and hit save. Mod finished and ready to go as soon as the game is next launched.

For more details on using the Mod Manager either for managing mods or making your own, check out this guide.

⚠️ **GitHub.com Fallback** ⚠️