If you have any additions for this article or the reference sheets feel free to ping Schinken#8712 on the ?ServerName? Discord Server.
Tools:
- FXMLR
- Yabber
- Notepad++
General
SFX are the special visual effects that use models and textures to "simulate" glowing, fire, flames and magic. They are located in the "sfx" folder of the game:
1. There is the "FRPG_SfxBnd_CommonEffects.ffxbnd(.dcx)" that contains SFX that can appear everywhere which also includes magic.
2. There are e.g. "FRPG_SfxBnd_m10.ffxbnd(.dcx)" which are the map SFX.
3. There are e.g. "FRPG_SfxBnd_c3471.ffxbnd(.dcx)" which are the SFX for special enemies.
After you unpacked one with Yabber there are some folders:
- "tex" which holds the textures as .tpf files, can be (un)packed with Yabber
- "model" which holds the models as .flver files, can only be converted/exported/imported
- "OutputData" which holds the .ffx files
The .ffx files determine how the SFX looks. To edit them, you need to convert them with "FXMLR" to .xml. When you have finished editing, the .xml must be converted back to .ffx and you must repack the folder with Yabber (as well as adding the entries in the Yabber .xml if you created additional .ffx files). You can download it from the tools page.
References
These sheets contain all the calls of the FFX file which are explained in the example further down.
IDs
The IDs are systematic:
0-1000: using items SFX
2000 - 2099: predefined SFX Effects
2100 - 2199: predefined SFX Effects
3000 - 3999: environment e.g. water splashes
4000 - 4999:
5000 - 5999: arrow SFX
6000 - 6999: bolt SFX
10000 - 19999: enemy SFX
20000 - 20099: casting SFX
20100 - 20199: bullet SFX
20200 - 20299: impact SFX
20300 - 20399: magic orbs SFX
20700 - 20799: Dragon Torso SFX
21818: corrosion cloud
22000 - 22999: item SFX
23000 - 23999: special attacks of weapons SFX
24000 - 24099:
80000 - 80999: fog gates
90000 - 90099: bonfires
95000 - 99999: dropped items
1200000 - 1200099:
100000000 - 100000099:
100001000 - 100001099:
…
Spell SFX example
To give an overview you can convert a spell .ffx file e.g. Fireball: f0020085.ffx -> f0020085.xml and then open it with Notepad++. In Notepad++, collapse all sections so it looks like this:
Overview
<?xml version="1.0" encoding="utf-8"?> <fxr1> <fxr1_data> <dcx_compression>None</dcx_compression> <big_endian>false</big_endian> <wide>false</wide> <unk1>1</unk1> <unk2>0</unk2> <root struct_type="EFFECT_CONSTRUCTOR" EffectID="20085" _varint_Unknown8="8"> <inline_fx_parameter_container name="Container1" unk1="0" unk2="0" unk3="1" /> <inline_fx_parameter_container name="Container2" unk1="0" unk2="0" unk3="1"> <fx_state_ref refid="FXState_0x680" /> <fx_state_ref refid="FXState_0x698" /> </root> <all_states> <fx_state refid="FXState_0x680"> <fx_action action_type="FXActionType14"> <fx_container name="action_parameter_container" unk1="0" unk2="0" unk3="1"> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0">" <fx_parameter struct_type="EFFECT_CALL" EffectID="2101" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0"> <fx_modifier struct_type="FXModifier0_TrimParamList" LastEnabledParamListIndex="1" /> </fx_container> </fx_action> <fx_transition_ref refid="FXTransition_0x6B0" /> </fx_state> <fx_state refid="FXState_0x698"> <fx_action action_type="FXActionType5"> <fx_container name="action_parameter_container" unk1="0" unk2="0" unk3="1" /> </fx_action> </fx_state> </all_states> <all_transitions> </fxr1_data> <fxr1_template> </fxr1>
There you can see a root structure with an "EFFECT_CONSTRUCTOR" and an "EffectID". You need to set the ID so the game knows which ID it should pick to display the SFX as it looks up the params. The .ffx file name should always be the same ID as the Effect ID.
The states and transitions are typically 2 states, where the first state is the SFX and then the transition transfers it to the second state and the SFX ends. The root structure calls the states and the first state executes the "Effect_Calls".
The template is exactly what it says. It is there so the file can be converted back and you can look up the structure of other calls.
So the important part is everything within "<fx_state refid="FXState_0x680">".
Effect calls
Within it there are predefined "Effect calls" e.g. "2121" which contain other child predefined Effect calls e.g. "2023" and "2101". Within those are usually the "Action calls".
Expand the Effect call "2101" and collapse the sections within so it looks like this:
<fx_parameter struct_type="EFFECT_CALL" EffectID="2101" _varint_Unknown2="0"> <fx_parameter_container name="ParamContainer" unk1="0" unk2="1" unk3="1"> <fx_parameter struct_type="TimeParameter" Seconds="-0.0166666675" /> <fx_parameter struct_type="IntParameterType1" Value="1" /> <fx_parameter struct_type="ACTION_CALL" ActionID="0" _varint_Unknown2="0"> <fx_parameter struct_type="TimeParameter" Seconds="0" /> <fx_parameter struct_type="ACTION_CALL" ActionID="0" _varint_Unknown2="0"> <fx_parameter struct_type="ACTION_CALL" ActionID="35" _varint_Unknown2="0"> <fx_parameter struct_type="ACTION_CALL" ActionID="1" _varint_Unknown2="0"> <fx_parameter struct_type="IntParameterType1" Value="1" /> <fx_parameter struct_type="ACTION_CALL" ActionID="14" _varint_Unknown2="0"> <fx_modifier struct_type="FXModifier6_SetLifetimeB" Lifetime="-0.0166666675" _s32_Unknown1="1" /> </fx_parameter_container> </fx_parameter>
Ignore the empty Action calls "0".
The action calls "35" and "1" influence the whole SFX meaning all child calls thus everything within is e.g. rotated instead of giving every single child some values to rotate it.
ActionID "14" calls another Effect as a child. It belongs to "special Action calls" which only occur in these cases.
Effect calls have different presets of available values and they are always around the Action calls.
e.g. for "2101" in this example:
at the top:
TimeParameter
IntParameterType1
TimeParameter
TimeParameter
<- Action Calls ->
at the bottom:
FXModifier0_TrimParamList
These values influence e.g. how much the main action call is delayed, how often it can occur at once, at which rate etc.
An Effect call is mostly one main action call but being present multiple times, spawning again and again in short intervalls. For instance a fireball spawns many "flames" over and over to look like the fire is moving. It obviously wouldn't look good if it only spawned once and would look like a picture of a static flame…
Now collapse the Action call "14" section so it looks like this:
<fx_parameter struct_type="ACTION_CALL" ActionID="14" _varint_Unknown2="0"> <fx_parameter_container name="ParamContainer" unk1="0" unk2="0" unk3="1"> <fx_parameter struct_type="EFFECT_CALL" EffectID="2101" _varint_Unknown2="0" tag="red flame around the middle"> <fx_parameter struct_type="EFFECT_CALL" EffectID="2101" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="2023" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="2023" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0"> <fx_parameter_container name="ParamContainer" /> </fx_parameter> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0"> <fx_parameter struct_type="EFFECT_CALL" EffectID="0" _varint_Unknown2="0"> <fx_modifier struct_type="FXModifier0_TrimParamList" LastEnabledParamListIndex="4" /> </fx_parameter_container> </fx_parameter>
As you can see I added a tag after one Effect call:
tag="red flame around the middle"
I advise to do this too when you figured out which part of the SFX this Effect call is.
This makes editing WAY easier.
Important to note is that if you replace empty Effect calls (EffectID="0") or simply add them then you also need to edit the fx modifier "FXModifier0_TrimParamList". This value starts counting at 0 (meaning one entry used starting at the top) and can only be the amount of Effect calls minus 1 at maximum. If you enter a higher number the game will crash.
Each single "Effect_Call" holds mostly one and sometimes multiple main action calls.
Multiple ones are called for e.g. multiple layers to hide something behind them or one main call is the actual effect and then there is another one that is supposed to appear as a trace of it in-game.
Action calls
Within an Effect call there are often one or more main action calls and some secondary action calls always below a main one.
The main action call determines the general usage e.g. 40 for light circles, 59/71 typically for detailed textures and 108 for models etc.
The secondary action calls modify the main call e.g. length, shifting, angle, range, extension etc.
Here is a typical structure (collapsed in Notepad++):
<fx_parameter struct_type="ACTION_CALL" ActionID="71" _varint_Unknown2="0"> <fx_parameter struct_type="ACTION_CALL" ActionID="28" _varint_Unknown2="0"> <fx_parameter struct_type="ACTION_CALL" ActionID="105" _varint_Unknown2="0"> <fx_parameter struct_type="ACTION_CALL" ActionID="35" _varint_Unknown2="0"> <fx_parameter struct_type="ACTION_CALL" ActionID="106" _varint_Unknown2="0"> <fx_parameter struct_type="IntParameterType1" Value="1" /> <fx_parameter struct_type="ACTION_CALL" ActionID="79" _varint_Unknown2="0">
We can see that "71" is the first one so it is the main action call that determines the general type of this SFX part. Below is the secondary call "28" for offsetting the position where it can spawn.
Below this one is the next secondary call "105" that e.g. can drag the SFX into a direction on the X axis or influence how close it will drag to the emitter. The emitter is basically the invisible spawn point that every SFX has and is determined by the DmyPolyId of the tae event. The position of the emitter itself can also be changed with the secondary call "1".
Note that some secondary action calls can only be replaced, e.g. 28 with 32. They can't both exist below the main call. Also there is a certain order you need to follow for secondary action calls. However, it is also possible to leave some out. Some only got 71,28,105 and that's it, some have more. Looking at multiple different FFX files for spells will give a clear picture.
In this example you can also see a special action call "79" which always contains the same main and secondary calls again, sometimes slightly modified, sometimes exactly the same. The difference between the special action calls is unknown.
Here is an example of a main action call:
<fx_parameter struct_type="ACTION_CALL" ActionID="71" _varint_Unknown2="0"> <fx_parameter_container name="ParamContainer" unk1="0" unk2="0" unk3="1"> <fx_action_data struct_type="ActionParamStructType71"> <resources /> <f32 name="Duration" value="0.5" /> <f32 name="UnkFloat2" value="1" /> <f32 name="UnkFloat3" value="0" /> <s32 name="UnkInt4" value="0" /> <f32 name="RenderDepth" value="0" /> <s32 name="TextureID" value="20016" /> <s32 name="UnkInt7" value="0" /> <s32 name="UnkInt8" value="1" /> <s32 name="BlendMode" value="0" /> <s32 name="UnkInt10" value="0" /> <fx_value_node name="Scale1X" struct_type="RangedFloat" Value="0.0250000004" Min="0.25" Max="1.5" /> <fx_value_node name="Scale1Y" struct_type="EmptyValueNode" /> <fx_value_node name="Scale2X" struct_type="FloatSequence"> <float_tick time="0" value="1" /> <float_tick time="0.5" value="0.25" /> </fx_value_node> <fx_value_node name="Scale2Y" struct_type="FloatSequence"> <float_tick time="0" value="1" /> <float_tick time="0.5" value="0.25" /> </fx_value_node> <fx_value_node name="RotX" struct_type="EmptyValueNode" /> <fx_value_node name="RotY" struct_type="EmptyValueNode" /> <fx_value_node name="RotZ" struct_type="ConstFloat" Value="6.28000021" /> <fx_value_node name="RotSpeedX" struct_type="EmptyValueNode" /> <fx_value_node name="RotSpeedY" struct_type="EmptyValueNode" /> <fx_value_node name="RotSpeedZ" struct_type="RangedFloat" Value="0.790000021" Min="-1" Max="2" /> <s32 name="AnimFrameSliceCountPerDimension" value="8" /> <s32 name="AnimFrameTotalCount" value="32" /> <fx_value_node name="UnkField14_1" struct_type="RangedFloat" Value="1" Min="0" Max="32" /> <fx_value_node name="AnimFrameSelection" struct_type="RepeatingFloatSequence"> <float_tick time="0" value="0" /> <float_tick time="0.5" value="32" /> </fx_value_node> <fx_value_node name="Color1R" struct_type="ConstFloat" Value="1" /> <fx_value_node name="Color1G" struct_type="ConstFloat" Value="1" /> <fx_value_node name="Color1B" struct_type="ConstFloat" Value="1" /> <fx_value_node name="Color1A" struct_type="ConstFloat" Value="1" /> <fx_value_node name="Color2R" struct_type="FloatSequence"> <float_tick time="0" value="1" /> <float_tick time="0.400000006" value="1" /> <float_tick time="0.5" value="1" /> </fx_value_node> <fx_value_node name="Color2G" struct_type="FloatSequence"> <float_tick time="0" value="0.56078434" /> <float_tick time="0.400000006" value="0.56078434" /> <float_tick time="0.5" value="0.56078434" /> </fx_value_node> <fx_value_node name="Color2B" struct_type="FloatSequence"> <float_tick time="0" value="0.250980407" /> <float_tick time="0.400000006" value="0.250980407" /> <float_tick time="0.5" value="0.250980407" /> </fx_value_node> <fx_value_node name="Color2A" struct_type="FloatSequence"> <float_tick time="0" value="1" /> <float_tick time="0.400000006" value="1" /> <float_tick time="0.5" value="0" /> </fx_value_node> <s32 name="DS1R_UnkA1" value="4" /> <s32 name="DS1R_UnkA2" value="0" /> <s32 name="DS1R_UnkA3" value="0" /> <s32 name="DS1R_UnkA4" value="0" /> <s32 name="UnkInt15" value="0" /> <s32 name="UnkInt16" value="-1" /> <s32 name="UnkInt17" value="-1" /> <s32 name="UnkInt18" value="-1" /> <f32 name="UnkFloat19" value="0" /> <s32 name="UnkInt20" value="0" /> <f32 name="UnkFloat21" value="-1" /> <s32 name="UnkInt22" value="1" /> <fx_value_node name="DS1R_ColorModR" struct_type="ConstFloat" Value="5" /> <fx_value_node name="DS1R_ColorModG" struct_type="ConstFloat" Value="2.5" /> <fx_value_node name="DS1R_ColorModB" struct_type="ConstFloat" Value="1.25" /> <fx_value_node name="DS1R_ColorModA" struct_type="ConstFloat" Value="1" /> <fx_value_node name="DS1R_ColorModMult" struct_type="ConstFloat" Value="1" /> <f32 name="DS1R_UnkFloat6" value="0" /> <s32 name="DS1R_UnkInt7" value="0" /> <s32 name="DS1R_UnkInt8" value="0" /> <s32 name="DS1R_UnkInt9" value="0" /> <s32 name="DS1R_UnkInt10" value="0" /> <s32 name="DS1R_UnkInt11" value="0" /> </fx_action_data> </fx_parameter_container> </fx_parameter>
It might look confusing at first but that's simply because some values are not uncovered yet.
Each value has a name so you can recognize what it does. The sheets already cover many ones that are not named yet.
Important to note is that the "fx_value_node" lines can be different types:
"ConstFloat" = one value:
<fx_value_node name="RotZ" struct_type="ConstFloat" Value="6.28000021" />
"RangedFloat" = one value but the min and max values are are random range differences each time this main action call is called:
<fx_value_node name="Scale1X" struct_type="RangedFloat" Value="0.0250000004" Min="0.25" Max="1.5" />
"FloatSequence" = multiple values, each line has a time value and one value for the actual modifier:
<fx_value_node name="Scale2X" struct_type="FloatSequence"> <float_tick time="0" value="1" /> <float_tick time="0.5" value="0.25" /> </fx_value_node>
Meaning at time 0 it will have scaling multiplicator of 1, at time 0.5 it will have 0.25.
"RangedFloatEx" = uses a value, min+max and the resources tag
Resources (below the "ActionParamStructType" line):
<resource_entry unk="1" struct_type="FloatSequence"> <float_tick time="0" value="0.0719999969" /> <float_tick time="1" value="0.180000007" /> </resource_entry> <resource_entry unk="1" struct_type="FloatSequence"> <float_tick time="0" value="0.0719999969" /> <float_tick time="1" value="0.180000007" /> </resource_entry> <resource_entry unk="1" struct_type="FloatSequence"> <float_tick time="0" value="0.0719999969" /> <float_tick time="1" value="0.180000007" /> </resource_entry>
Actual line:
<fx_value_node name="UnkField4_1" struct_type="RangedFloatEx" Value="1" Min="1" Max="1" ResourceIndex="0" />
This covers most of it. Playing around with some existing SFX is advised to get into it. You can also copy whole effect calls from one FFX file to another, but be aware of the TrimParamList value and of the general syntax. Completely writing one from scratch has no benefit. Simply deleting everything within the parent effect call and writing your own effect calls etc. is the maximum editing you should do.