LE1 Power and Talent Documentation - ME3Tweaks/LegendaryExplorer GitHub Wiki
With the release of LE1 Talent and Power Overhaul, it seemed prudent to provide an in-depth reference document outlining precisely how it was accomplished. This is not a tutorial itself but is designed to complement existing Legendary Explorer (LEX) tutorials.
In LE2 and LE3, class powers can be customized to one’s desire, and
custom classes can be created from existing powers with relative ease.
DropTheSquid’s Bonus Bonus Powers makes this even easier by giving the
player the ability to do this within the game itself. Even new powers
can feasibly be created. In fact, Tajfun403 has successfully ported
combat rolls and biotic dashes from LE3 to LE2 utilizing LEX tools. A
quick glance at the contents of the CookedPCConsole
folder of LE2
and LE3 can provide a starting point to understand how this was
accomplished. Individual files for each character class can be seen,
each containing stats and scripts linked to powers usable by that class.
For example, the file SFXCharacterClass_Adept
will contain all power
scripts in addition to an object that contains references to all
abilities usable by that class, listed in the order they appear on the
squad screen. In contrast, LE1's CookedPCConsole
lacks player class
files, instead featuring a single SFXGameContent_Powers
file
containing only power scripts, with no visible stats or objects that
could feasibly be used to assign powers to a character.
This is because information detailing classes and powers is stored
differently in LE1. During the development of the original Mass Effect,
Bioware used Unreal Engine 3 before it was fully developed. As a result,
they fell back on familiar design habits from their in-house engines,
such as storing large amounts of data in two-dimensional arrays (2DAs).
These 2DAs resemble spreadsheets and are viewable in Legendary Explorer.
Existing tutorials on the LEX wiki cover 2DA modification, but the
actual structure and interconnectivity of these objects can seem quite
complex. To demystify the system, the functionality and connectivity
should be mapped starting with the location of all relevant 2DAs. These
are stored in Engine.pcc
within LE1’s CookedPCConsole
folder.
The important 2DA objects are:
-
Characters_Character
-
Classes_BaseClass
-
Classes_ClassTalents
-
Classes_ClassSpecializations
-
Talent_TalentEffectLevels
-
GameProperty_GameProperties
-
GameProperty_GameEffects
-
Talent_BonusTalents
-
Talent_TalentGUI
-
Powers_Powers
-
Template_Template
Figure 1
The Characters_Character
2DA contains labels corresponding to
squadmates and player classes, with a BaseClassID
column used to
assign classes to characters (e.g., Vanguard is 5, Adept is 2). (Figure
1). The SuperSoldier
class is a test class that contains all talents
for development purposes.
Figure 2
The Classes_BaseClass
2DA defines information visible in-game in the
Squad and Squad Select screens (Figure 2).
The IsHuman
label could be better thought of as IsPlayer
and can
be seen marked for all player classes.
The DisplayName
field contains a string reference (stringref) that
specifies the class name shown during character creation and in the
upper left corner of the Squad Screen, below the character's name.
The DisplayDescription
field provides a description of the class at
the bottom of the screen during class selection in character creation.
The next three columns indicate the class strengths, with the numerical values corresponding to the strength bars displayed at the bottom of the screen when selecting the squad before departing the Normandy. The last three columns are vestigial and not visible to players during gameplay.
Figure 3
Figure 3 shows a small excerpt of the Classes_ClassTalent
2DA that
corresponds to the Soldier class. The BaseClassID
column can be seen
to match the BaseClassID
in the Characters_Character
2DA. The
following column, TalentID
will be explained further in subsequent
sections.
The MaxRank
column indicates the maximum number of ranks available
for each talent, with most entries set to 12, corresponding to the
talents upgradeable on the Squad screen.
The LevelOffset
column specifies the level the character must be
before that talent become available. All rows with positive
LevelOffset
s also include a prerequisite. These prerequisites can be
seen in the PrereqTalent0
column. The PrereqRank0
column
specifies how many ranks of that prerequisite are needed before the
locked talent is available. Rows with negative LevelOffset
s have no
prerequisite talent, with -1 representing a standard amount of ranks
available at Level 1 (with the amount confined by the PrereqRank0
column of the corresponding locked talent. So, talent 0 will not have 4
ranks fully available until level 2, when talent 15 can be unlocked),
and -12 indicating immediate availability of all ranks at Level 1.
The LevelsPerRank
column denotes the level progression per rank,
which is one level per rank for all talents.
StartingRank
shows the number of ranks filled in when a character of
this class is spawned.
The VisualOrder
column determines the display order on the talent
screen, with lower numbers appearing at the top and higher numbers at
the bottom. To demonstrate this, compare the non-zero numbers with the
squad screen for the Soldier class in-game. The TalentID
values
correspond to the talents visible in the game. Talents without a
VisualOrder
will be explained later.
Figure 4
Classes_ClassSpecialization
also contains the BaseClassID
column
as seen in other 2DAs (Figure 4). The first two rows have a
BaseClassID
of 0 and thus correspond to the Soldier class.
The TalentID
column from Classes_ClassTalents
can be
cross-referenced with the ReplaceTalentID
in
Classes_ClassSpecialization
.
The BonusID
adjacent to that column indicates the talent
specialization that will be used to replace the corresponding passive
talent on the Squad screen after the completion of the side mission on
Luna.
Similar to the Classes_BaseClass
2DA, the DisplayName
and
DisplayDescription
columns contain stringrefs for the names and
descriptions of the Specialization Talents, such as Shock Trooper
or
Commando
, which are displayed on the Squad screen for those talents.
A:
B:
Figure 5
Talent_TalentEffectLevels
introduces new headers, including
GameProperty
and GameEffect
, along with their corresponding
Label
(Figure 5). There are 12 columns representing each rank of a
talent. These values can be understood better by comparing them to the
rank descriptions on the squad screen.
For example, the first talent, 0 (Pistols), is displayed in Figure 5a.
At level 3, Marksman is unlocked, as indicated by the
GE_LWMarksman_Enable
row being set to 1. This signifies that the
power is ready for use and can be added to the power wheel, though an
additional condition for GUI visibility is needed and will be explained
later. The GE_LWMarksman_MaxDriftRed
and
GE_LWMarksman_MinDriftRed
values are both 0.4 at rank 3. According
to the description, which states “Boosts accuracy by 60%”, this value is
logical, as a 40% reduction in drift corresponds to a 60% increase in
accuracy. Other values are self-explanatory. “Kickback” is labeled as
“Accuracy Cost” in the player-visible description, representing the
degree to which weapon accuracy is reduced after using the specific
power associated with this talent.
Returning to the idea of talents without a VisualOrder
seen in the
Class_ClassTalents
2DA, one such talent is displayed in Figure 5b.
The Talent_Label
of this talent indicates it is a Setup
talent,
which described its intended function. This talent activates various
game effects that enhance the Soldier class's abilities, such as
automatically applying the capability to wear light and medium armor.
The PowerGive
GameEffect
s in this talent add specific powers to
the power wheel, including Immunity, Shield Boost, Overkill
(Suppression), Carnage, Adrenaline Burst, Marksman, and Assassination
(Critical), once these powers are enabled in their respective
progressions.
Additionally, this talent grants access to the First Aid skill, which is available to all player classes. It can be used by squadmates when health is low but is not enabled by default, thus giving the player complete control over medi-gel reserves. Weapon proficiencies can also be seen within this talent. In LE1, all classes are proficient with every weapon. In OT1, proficiency improved with the corresponding talent, explaining why the sniper rifle scope would sway like Shepard had just slammed a six pack of Smirnoff and is trying to remember which way is the floor and which is the ceiling until approximately eight ranks in the talent had been unlocked.
A:
B:
Figure 6
Figure 6b displays an excerpt of the GameProperty_GameEffects
2DA
that contains the game effects for the Throw
talent, identified as
talent 49 in the Talent_TalentEffectsLevel
2DA. In
Talent_TalentEffectsLevel
, the GameEffect
column contains values
that correspond to the row labels of GameProperty_GameEffects
.
Similarly, the GameEffect_Label
column of
Talent_TalentEffectsLevel
corresponds to the Label
column of
GameProperty_GameEffects
.
The Class
column specifies the type of game effect, with certain
game effect classes labeled with variable types like float
or
bool
. The class names that include the word parameter
vary from
talent to talent and are used to store information unique to that power.
For example, Throw imparts force and damage on a target, so these custom
parameters are included. Thus, the power can pull that information from
the effects levels 2DA.
The csid
column is consistently marked as 1, and while it is
required, it does not affect anything in-game.
Subsequent columns are paired as property
and value
. A game
effect can be viewed as an object with four variables:
m_attributename
, m_powername
, m_aspect
, and m_modifier
.
m_attributename
denotes the specific attribute controlled by the
game effect. For instance, the second row has a value of
m_CastingTime
for its m_attributename
, which means this
attribute controls the base casting time for a power. The
m_powername
property indicates which row in the Powers_Powers
2DA should be referenced for that specific power, such as TK_Throw
,
which will be explained further later.
The m_aspect
property specifies whether the values in the LevelX
columns of the Talent_TalentEffectsLevel
2DA are base values or
modifiers. The m_modifier
column remains empty for base values but
is present for modifiers, identifying the modifier type as a sum
,
float
, or layer
.
A sum
modifier adds its value to the base value, a float
acts as
a percentage increase, and a layer
reduces a base value by a
percentage. For example, if a power deals m_Damage
of 20 points with
a float modifier of 0.5, the actual damage becomes 30 points, which is
displayed in-game as “Increases damage of X power by 50%”.
Conversely, a layer modifier is a float value that is directly
multiplied by the base value, resulting in an overall reduction, e.g., a
m_CooldownTime
of 45 can have a layer modifier of 0.9, which would
reduce the base value by 10% to 40.5 points.
Modifier game effects are named similarly to their base counterparts,
but with \_S
, \_F
, or \_L
suffixes. These typically are not
contained within the talent that unlocks the power but are instead used
within passive talents such as “Nemesis”.
Resistance modifiers may seem counterintuitive, often appearing as a
layer
modifier that implies a reduction in resistance. Instead, this
reduces the damage values applied to the character. For instance, taking
ranks in Combat Armor will cause incoming damage to be multiplied by the
damage resistance game effect, thus reducing the actual damage taken.
Game Properties can be conceptualized as a container of Game Effects.
For example, the Throw ability has a Game Property of 17, or
GP_TKThrowCoreGFX
(Figure 6a), which contains Game Effects 53-64 as
seen in the Talent_TalentEffectLevels
2DA. Three types of game
properties are defined in the third column of
GameProperty_GameProperties
(Figure 6a). For talents that include
powers, the relevant types are GPT_Static
and GPT_Instant
, where
GPT
stands for “Game Property Timer”.
In this context, static
refers to a property that is continuously
applied without a time limit and is not a one-time occurrence. This can
be observed in the Property and Value sets on the right-hand side of the
table, where the property m_bTicked
is set to 0, indicating
persistence without a time limit.
Statistics for powers are stored in static game properties, as seen in
the Talent_TalentEffectsLevel
2DA, where all stats for Throw are
stored in GP_TKThrowCoreGFX
, a static game property. Each talent
containing a power includes a static Core
game property.
Modifiers are typically placed in a separate static game property that
specifies the modifier's function. For example, a game effect that
increases the m_Force
of Throw would be stored in
GP_TKThrow_ForceInc
.
Every power has an associated PowerGive
Game Property, which is a
GPT_Instant
Game Property. This type means it is executed once,
typically when a character is spawned, adding the power to the power
wheel. In the case of NPCs, this addition makes the ability available
for AI utilization.
Figure 7
Powers_Powers
controls the visual and functional elements that are
executed when a power is activated. The power TK_Throw
is shown in
Figure 7 as an example (one line of the table is displayed in five parts
for ease of illustration). The Label
column with the power name
corresponds to the m_powername
value in the
GameProperty_GameEffects
2DA.
The DisplayName
column contains the stringref of the power name,
which is generally unused in-game. Following this, the Description
column shows what appears when hovering over a power in the power wheel.
The power name is displayed in bold, with the rest of the description
appearing beneath it, formatted by the game based upon line breaks
within the stringref.
The Type
column defines the type of power, which can be Target
,
Party
, MELEE
, or Cylinder
. Target
powers are typically
hitscan and activate where the player is pointing. Party
powers
generally apply buffs or debuffs to the caster or the caster’s squad.
MELEE
powers operate within melee range. Cylinder
powers are
projectiles.
The Effect
column specifies the effect type of the power, such as
ATTACK
, Disable
, DEFENSE
, Heal
, BUFF
, Suicide
,
or Death
. ATTACK
applies the power to the targeted area,
Disable
applies a debuff, DEFENSE
restricts incoming damage, and
Heal
restores health. BUFF
enhances the caster’s stats or alters
weapon firing modes. Suicide
causes the caster to die upon casting,
while Death
triggers the power when the caster dies.
The Icon
column displays what appears in the power wheel when the
power is available, with Throw’s icon being 25 (Figure 7).
ImpactText
displays text over the target’s health bar when hit,
typically left blank but can be used to add words like “In Stasis” or
“Hacked” for specific powers. The RequiredWeapon
column ensures the
icon appears only if the corresponding weapon is equipped, for instance,
cycling between Marksman, Overkill, Carnage, and Assassination based on
the weapon.
The Anim
column specifies an instance path to an animation object
which is triggered whenever the power is activated. The optional
CoverAnim
column plays a different animation when the player is in
cover, useful for powers cast without breaking cover. It should be noted
that animations can be freely ported between games. The animation’s rate
is directly influenced by the CastingTime
parameter seen in Figure
6b. Longer casting times result in the animation playing out more slowly
than faster casting times.
The VFXAppearance
column determines the visual effect applied when
the power is cast. NoiseRelease
and NoiseImpact
indicate whether
a sound effect plays when the power is cast or impacts the target (it is
unclear whether this value actually affects anything in-game. Typically
sounds are controlled directly by VFX). ImpactShape
is primarily
used for melee powers, defining the effect area in front of the caster.
The next four columns are vestigial and unused in the vanilla game.
CasterCanMove
indicates whether the character’s movement is disabled
during casting. AimType
can be either CameraTrace
or
SelectedTarget
, determining whether the power hits the selected
target or where the camera is pointing. A CameraTrace
power can be
fired over the shoulder of a SelectedTarget
without hitting them.
RequiredResource
specifies if a resource is depleted upon using the
power, such as medi-gel or omni-gel for First Aid or Mako repairs,
respectively. HUDType
determines whether the player or squadmate can
see and cast the power from the power wheel.
The script column refers to the specific Unrealscript class controlling
the power’s action upon casting. Lastly, the parameters columns match
various listed parameters in the GameProperty_GameEffects
2DA and
can be directly accessed from an Unrealscript function.
Figure 8
Talent_BonusTalents
is utilized when a bonus talent is selected at
the character creation screen and serves as the counterpart to the
Classes_ClassSpecialization
2DA. The replacement passive talent is
routed through this 2DA using one of the first 11 rows to substitute the
original passive class talent (Figure 8).
The rest of the rows correspond to a specific bonus talent. Each bonus
talent is associated with two rows. The first row is a talent that
provides the PowerGive
effect, enabling the player to use the power.
The second row represents the talent itself to be added to the list. The
VisualOrder
is set to 85, indicating that the talent should be
inserted into the class talent list between talents with VisualOrder
values of 80 and 90.
Figure 9
The Talent_TalentGUI
2DA contains the interface for the Squad
screen, with each row corresponding to a specific talent listed by row
numbers. The DisplayName
labels each talent row on the Squad screen,
while the DisplayDescription
appears in the box below when the
player hovers over the talent name. The UnlockName
and
UnlockBlurb
columns provide notifications such as Unlock X
and
X unlocked
when a talent with a prerequisite is unlocked. Icons
follow the same rules as those in the Powers_Powers
2DA. The
NameX
and UnlockBlurbX
columns are used for the pop-up window
which appears when a power is unlocked. The DescriptionX
boxes
contain stringrefs that display the text in the box below when hovering
over that talent rank.
Figure 10
The last 2DA is Template_Template
located in the package export
BIOG_2DA_LevelUp_X
(Figure 10). This governs which auto level up
tables are assigned to which class. Examining Figure 10, the
BaseClassID
is once again present; however, the most important
element of this 2DA is the row label. Which row each class is located
within governs which Template
is used when calculating talent ranks
during auto level up. An excerpt of the Soldier Auto Level Up table,
Template_10
, is displayed in Figure 11.
Figure 11
This may be the least intuitive 2DA of the system. There are only two
columns present, TalentID
and Rank
. There are 60 levels possible
in the game, but only 48 rows in the table. Additionally, each row
contains only one talent, and choosing auto level up on the Squad screen
assigns talent ranks to multiple talents.
The logic that governs assignment of talent ranks per level is native but can be gleaned by comparing each table to what occurs whenever an auto level up is performed in-game.
For Classic Levels 1-5, each level unlocks three talent points to be assigned to ranks. Soldiers begin with a rank assigned in Assault Rifles (7) and Combat Armor (30). Pressing the auto level up button assigns one rank to Assault Rifles (7), one to Combat Armor (30), and one to Assault Training (35). This repeats for levels 2 and 3. At level 4, suddenly, one rank is assigned to Assault Rifles (7), and two ranks are assigned to the passive Soldier Talent. This reveals the logic undergirding the table.
When auto level up is pressed, the game locates the first row of the 2DA
and ascertains if the number of ranks listed in the Rank
column have
been assigned to the talent listed in the TalentID
column. If the
number is less than what is listed in that cell, it assigns one rank to
the talent if one or more ranks have previously been taken in the
talent, and two ranks if no ranks have yet been assigned to the talent.
It then moves on to the next row and repeats this process.
Using this knowledge, an auto level up table for a custom class can be created. The easiest method is to mirror an extant table with the list of updated talents for the custom class.
Figure 12
With the network of 2DAs mapped, it is now possible to move forward into the Unrealscript components that govern power function. Reference materials are available for the construction and syntax of UnrealScript classes, functions, and variables, as well as for understanding how LEX interprets the bytecode it generates. Although there are minor differences between LEXscript and Unrealscript, the term “Unrealscript” will be used going forward to represent LEXscript. Further details on the differences of the two languages are available in the Legendary Explorer documentation on GitHub.
To begin, the BioPower
and BioPowerScript
classes will need to
be explored. It should be noted that within the BioPower
class, all
functions except one are marked as “native” and lack an executable
script body. This indicates that these functions are implemented in C++
and compiled directly into the executable file. Reverse-engineering
these functions is a complex process, involving assembly code that would
need to be deobfuscated to approximate the original source code. With an
understanding of that code, modification is possible with ASI mods;
however, this requires both a well-rounded knowledge of C++ and of the
offsets of the native functions within the executable. Both are likely
beyond the knowledge of the average modder, so these will have to remain
an obstacle to work around.
Figure 12 is an image of the instance variables where information from
the 2DA tables is stored within the BioPower
class. There is one
non-native function (not shown) that allows access to the parameters
listed within the Powers_Powers
2DA. For modification or creation of
powers, no adjustments to this class should be necessary. In fact, any
modification to a native class is strongly discouraged. Any disruption
of the visible compilation chain will create a disconnect with the
compiled C++ code that backs the class, which could result in
instability and game crashes.
Figure 13
The ubiquity of the BioComplexFloatStructAttribute
enum
is
immediately noticeable. The enum
's structure is shown in Figure 13.
Although the enum
may appear complex, only two variables are
significant: m_Current
and m_Base
. m_Base
represents the
value specified by the rows in the Talent_TalentEffectLevels
2DA for
the talent. m_Current
denotes the value after modifiers have been
applied and is the value used in-game.
Figure 14
In the BioPowerScript
class, three functions are labeled as
“native”. The first two are primarily handled by the game, while the
third is a utility function that returns a ground location when provided
with a vector position in the level (Figure 14). The AdjustCooldown
function appears to be vestigial and does not return any useful
information. The remaining functions describe the power's action.
OnImpact
is the parent function executed whenever a power affects a
target. The game provides parameters to this function, including
oCaster
(the pawn that initiated the power), fCasterStability
(the source of this value is unclear, and it is typically 1.0 and
unused), oImpacted
(the pawn hit by the ability), and
nPreviouslyImpacted
(which increments the achievement for power
usage).
StartPhase
and EndPhase
are executed during casting, with
StartPhase
running before casting begins and EndPhase running when
it concludes. These functions include a variable for an enum
detailing the exact phase of casting (e.g., BIO_POWER_NOT_STARTED
,
BIO_POWER_CASTING
, BIO_POWER_RELEASE
, BIO_POWER_USING
, and
BIO_POWER_FINISHED
), along with oCaster
and fDuration
(the
intended duration of the power, as specified in the previously detailed
2DAs).
The CanStartPower
function can interrupt casting if a certain
parameter is not met; however, for most powers, this function is
inactive since powers can usually be cast if they are not on cooldown.
InitializePowerScript
is the very first function executed for every
power, detailing anything required before casting begins. By default,
this includes assigning the name of the power being used to the instance
variable m_nmPower
.
Figure 15
The BioPowerScript
class extends into the BioPowerScriptDesign
class within the SFXGameContent_Powers
file, where all functions of
this class can be observed (Figure 15). These functions serve as
utilities managing the specifics of power effects. This class acts as
the parent to all scripts executing various powers.
Figure 16
The class responsible for running the Throw power, BioThrowScript
,
is identified in the Power_Powers
2DA. Examining the StartPhase
of that power reveals how all components integrate (Figure 16). The
parameters listed in Power_Powers
are visible here once again, with
values that can be traced back through all relevant 2DAs. The
m_ThrowForce
parameter is present in both Power_Powers
and
GameProperties_GameEffects
. Locating m_ThrowForce
as a parameter
in GameProperties_GameEffects
identifies the game effect referenced
in Talent_TalentEffectsLevels
, with the rank of the power in
Talent_TalentEffectsLevels
determining the value of that parameter.
Figure 17
When examining the OnImpact
function of the same power, the sequence
of events occurring when the power hits an enemy is revealed (Figure
17). The initial part of this function verifies the target being hit.
The function is designed to exit if the target is either dead or allied
with the caster, preventing unintended interactions with allies.
Subsequently, the function determines the type of damage inflicted,
which is set by default properties associated with the class and
modified by elements from the talent 2DAs. The function compares the
“physics level” of the impacted target with that of the power. This
attribute, assigned to various actors, approximates their weight. For
instance, casting Singularity with a single rank on a krogan results in
no effect but upgrading Singularity to level 12 causes the krogan to be
lifted due to the physics level of Singularity surpassing that of the
krogan. In Throw, the physics level determines whether the target is
affected by m_bIgnorePhysicsThreshold
, which dictates whether the
target is bodily thrown or simply withstands the attack's force.
The subsequent part of the function, present in every vanilla power available to the player, increments the achievement counter for power usage. The function then performs calculations to determine the trajectory and damage to the target. It calculates the difference between the caster and the target’s location, normalizing this into a vector that is a mathematical representation of a directional arrow. Modifications to the damage are computed based on the target's biotic resistance and momentum from the force multiplied by the direction. Finally, a function is called to apply these calculated effects.
Figure 18
The EffectTakeDamage
function from the list of effects in
BioPowerScriptDesign
provides insight into the mechanics when an
actor takes damage (Figure 18). Initially, the AI Controller of the
caster is retrieved, to be fed into a function later. Damage is then
calculated based on the proximity of the impacted actor to the point of
impact, with actors further away receiving less damage than those hit
directly. After calculating this distance, the game creates a game
property to apply to the impacted actor, incorporating all gathered
information.
Figure 19
However, the function that applies this property is native (Figure 19). Thus, a mod developer has to rely on the game to perform as expected; a bit of a troublesome prospect. This illustrates a recurring issue in LE1, where important processes are inaccessible to the average modder behind native barriers. It is advised to verify if vanilla objects use native functions as anticipated, as unexpected behavior may lead to game instability and difficulties in tracing crashes.
One example of this unexpected behavior is the native function
SetBaseFloatValue
in the BioAttributes
class which deallocates
memory dedicated to an array prior to garbage collection, which runs
counterintuitive to its name. When garbage collection is run, the game
attempts to deallocate memory that has already been cleared. This
results in a heap corruption and a game crash. However, since this
behavior is native, no debugging information can easily be obtained
without some knowledge of native function offsets and the ability to
apply this knowledge to a minidump file.
Figure 20
The fundamental architecture of LE1’s internal power functions has been
established, but clearly, it is possible to port powers across games, so
how was this accomplished? To begin, LE2’s SFXGame
should be
explored, starting with its version of BioPowerScript
(Figure 20).
Familiar functions are clearly present, but now they’re buried under an
avalanche of functions that LE1 does not have. Notably, greater
complexity in power functionality is allowed via the presence of a
Tick
function. In Unreal Engine 3, the Tick
function is
typically a function reserved for Actors, enabling real-time control by
executing a function every frame (for instance, 60 times per second at
60 FPS). BioWare extended this functionality to powers in LE2, allowing
for complex interactions such as power projectiles and dynamic responses
to in-game events, like Tactical Cloak disengaging when a weapon is
fired. In contrast, LE1 powers are relatively simple, either executing
an immediate action or adhering to a fixed time limit, unaffected by
player or enemy actions. The absence of a Tick
function in LE1
imposes limitations on the capabilities of its powers, making porting of
more intricate powers from later games appear impossible. However, this
was achieved through a clever workaround.
Non-actor objects lack Tick
functions, but a simulation of one can
be created by leveraging an Actor
's Tick
via Timer
s.
Timer
s are used to execute a function after a specified duration,
measured in seconds, and can be associated with an Actor
. By
configuring the timer to trigger frequently (essentially every tick) and
attaching it to the caster of the power, the power script can simulate a
ticking function. This allows for real-time control over the power,
effectively mimicking the Tick
function within LE1.
Through this method, the essential components are in place: stat storage
(2DAs), scripts (extending from BioPowerScriptDesign
), and precise
control mechanisms (Timer
s). These elements enable the creation of
more complex powers within LE1. However, power creation also involves
animations, sounds, and visual effects, which will be addressed next.
Figure 21
In the Powers_Powers
2DA, the column labeled VFXAppearance
contains an instanced path to an object (Figure 9). For vanilla powers,
these objects are located in the file BIOC_Materials.pcc
. Upon
opening this file and locating the specified object, seven basic
components of a power’s VFX appearance are identified, with six visible
in Figure 21. The components are as follows:
-
PlayerCrust
: The visual effect applied to the caster upon power activation, such as the biotic aura seen during the casting of Throw. -
FramebufferEffect
: The effect applied to the screen when the power is cast, such as the slight screen warp that occurs when biotic powers are activated. -
TargetCrust
: The visual effect applied to the target when struck by the power. -
WorldImpactVisualEffect
: The visual effect that occurs when the power hits a solid object. -
nmProjectileAttachPoint
: The specific point on the caster’s body from which the power originates (e.g., the right hand, indicated byRightWrist
). -
vfxReleaseEffect
: The effect that occurs when the associatedBioPowerScript
reaches theEndPhase
. In Figure 9, this is another type of framebuffer. -
vfxProjectileEffect
: This component ofVFXAppearance
can be found within theThrow_Proj_VFX_Appearance
export in the same package folder as the export in Figure 21 (Figure 22). This component represents the appearance of the projectile. While vanilla player powers do not include projectiles, biotic enemies utilize them, allowing the player the potential to dodge out of the way of the incoming attack.
Figure 22
It is important to note that as this exploration of VFX continues, the effects in-game will become less straightforward. Working with VFX often involves significant trial and error due to interactions with the game’s native code and the power’s stats. Consequently, not all elements listed in LEX may correspond directly to in-game effects in an intuitive manner. Therefore, VFX editing typically requires frequent backups, along with a methodical approach of hypothesis testing and verification.
The elements within the VFX template will be systematically analyzed,
starting with BioCurveDrivenParameters
. These parameters serve as
modifiers that can be applied to the Effects Material. Two variables are
typically associated with this information: the enum
and the name
variable. The enum
functions as a label, while the name variable
directly influences the in-game visuals.
Figure 23
Each BioCurveDrivenParameter
includes specific elements. The
parameter name determines which aspect of the effects material is
affected, varying depending on the material. Additionally, a lookup
table (LUT) of values is present. This LUT represents a baked curve of
floats, each float adjusts intensity of the specific effect of the
material defined via the sParameterName
. It should be noted that
adjusting these LUTs and time scales may not be intuitive to effects
seen in-game. It is recommended to set high values and then observe the
effects to determine how to best adjust each parameter (Figure 23).
The Lifetime
of the effects material within the VFX template also
interacts with these parameters. The combination of these elements
determines the duration for which the effect remains visible in-game.
This duration is further influenced by the stats of the power associated
with the VFX template. The precise interaction of these elements is
complex and could require a substantial amount of trial and error.
Regarding the SoundCue
element, existing tutorials on importing
sound into the game can be used to link aa sound directly to the
specific visual effect.
Figure 24
Figure 25
Within a Prefab
, a relatively simple object is observed, containing
an archetype array (Figure 24). Delving further into the archetype
reveals several key elements (Figure 25). These include a vector and
rotator that determine the location of the archetype relative to the
pawn it is attached to, a tag identifying it as an emitter, a Name
corresponding to the bone on the pawn to which the archetype is
attached, and a float labeled CreationTime
.
Modifications to the relative location settings are generally
unnecessary unless precise adjustments are required, and
CreationTime
can also be disregarded. The Tag, which is consistent
across all archetypes, is not particularly useful for developers and is
therefore best left unchanged. The primary focus should be on the bone
attachment, which may be modified if necessary, and the
ParticleSystemComponent
.
Figure 26
Within the ParticleSystemComponent
, various properties are present,
though not all require attention (Figure 26). Properties such as
OldPosition
, ReplacementPrimitive
, LightingChannels
, and
TickGroup
can be safely ignored. InstanceParameters
typically do
not need modification but can be adjusted to set attributes like the
color of the effect. However, not all changes may produce noticeable
effects in-game. The focus should be on the template, where the actual
ParticleSystem
is stored.
Figure 27
In the template, several properties may be present, but the emitters list is the primary concern, as it comprises the system (Figure 27). Other properties can generally be left untouched, as they remain consistent across most systems.
When examining an emitter, a list of LODLevels
and an
EmitterName
are observed (Figure 28). Both parameters are important:
EmitterName
assists in identifying an emitter for developers, while
LODLevels
control the detail displayed at various distances on
screen. To ensure modifications are effective, all LODLevels
should
be modified in a block when any changes are made. Further analysis of
the LODLevel
reveals a complex structure, which will be explored in
detail (Figure 29).
Figure 28
Figure 29
The RequiredModule
is the most critical module within the list, as
it contains the material used by the emitter (Figure 30). When porting
particle systems between games, it is essential to address every
RequiredModule
because materials cloned from another game will not
function correctly by default and will need to be replaced with a
suitable donor material.
The primary focus should be on the material itself, which must be
replaced when integrating new systems. The original material’s specific
properties should be examined, particularly the boolean
properties
(Figure 31). Properties such as bUsedWithParticleSprites
,
bUsedWithBeamTrails
, and bUsedWithParticleSubUV
are crucial. To
find a compatible material in-game, the Asset Database can be used to
filter available materials based on these required Boolean
properties. Once a suitable donor material is identified, the material
in the RequiredModule
can be replaced, followed by verification of
the associated textures listed in the binary interpreter tab, which may
need to be adjusted (Figure 32).
Figure 30
Figure 31
Figure 32
It is recommended to locate a material that matches both the boolean
properties and the number of materials in the binary as closely as
possible to the original material. Although an exact match may not
always be feasible, the goal is to find a material that closely
resembles the original. Achieving this requires a process of trial and
error. The material's behavior within the particle system should be
tested by repeatedly booting the game and triggering the particle
system. Observing these behaviors will inform whether the selected
material is suitable. Once this process is complete, attention can be
redirected to the other modules.
Figure 33
As an example, ParticleModuleSize
is displayed in Figure 33. Similar
to other modules, it contains a LookupTable
(LUT) with minimum and
maximum values. This LUT is responsible for setting the size of the
particles and adjusting them throughout the lifetime of the particle
system. LEX also includes a Particle Module
tab (Figure 34), this
can be helpful when editing Color
and ColorOverLife
modules as
it provides a visual into how vector values translate into an RGB color.
When modifying these parameters, it is essential to adjust the LUT and
the minimum and maximum values simultaneously, as the minimum and
maximum settings may alter the final particle size from the intended
values. Understanding how these changes will impact the particle system
is challenging without observing the effects in-game. Therefore, it is
advised to use a process of backing up, making educated guesses, and
testing to achieve the desired outcome.
Figure 34
Figure 35
Another crucial module for defining the overall shape of certain effects
is the TypeDataModule
. This module can be observed in the particle
system associated with the Singularity impact effect in vanilla LE1
(Figure 35). Within the TypeDataModule
, two key properties are
highlighted: the mesh and the bOverrideMaterial
boolean
(Figure
36).
Figure 36
The mesh is always a StaticMesh
, which the game utilizes to shape
the particle system’s effect. The bOverrideMaterial
property enables
toggling between the material specified by the RequiredModule
and
the material associated with the StaticMesh
in the binary tab. It is
highly recommended to keep this property enabled to maintain precise
control over the material via the RequiredModule
. This setting also
allows the same mesh to be used across multiple particle systems.
The last component involves the integration of new icons into the UI.
The basegame of LE1 includes 26 icons, among these are icons for each
weapon and power. Examining the Icon
column of the Powers_Powers
2DA in Figure 9 and the Icon#
columns of the Talent_TalentGUI
2DA in Figure 11 reveals which specific icons are associated with each
ability. For instance, icon 25 is associated with the Throw ability.
These integers are associated with frames of a sprite within a scaleform
object. Scaleform objects are embedded Shockwave Flash (SWF) files
within PC-Console (PCC) package files (scaleform is a middleware that
allows an interface between flash objects and game engines).
Specifically, the objects most associated with talents and powers are
GUI_SF_CharacterRecord.CharacterRecord
,
GUI_SF_CharacterRecord.PC_CharacterRecord
, GUI_SF_HUD.ME_HUD
,
GUI_SF_HUD.PC_ME_HUD
, and gui_sf_utility.WindowPop
.
GUI_SF_CharacterRecord.CharacterRecord
and
GUI_SF_CharacterRecord.PC_CharacterRecord
control the squad screen.
The ability icons stored in these SWFs are used in the level block UI,
popping up over a block whenever a power is about to be unlocked. Which
SWF is utilized depends on the type of UI displayed for the player. The
former is used when the player is using a controller whereas the latter
is used when the player is using a mouse and keyboard.
GUI_SF_HUD.ME_HUD
and GUI_SF_HUD.PC_ME_HUD
control the power
wheel. The ability icons stored in these SWFs are used whenever the
power wheel is pulled up on the player’s screen. As with the Character
Record SWFs, which object is used is dependent on the input used by the
player during gameplay.
Lastly, gui_sf_utility.WindowPop
contains the icons used whenever a
pop up appears on screen (e.g. when a power is upgraded on the Squad
screen, the pop up that appears that provides more information).
Scaleform editing is a rather fiddly type of modding that is prone to unexpected behavior. The oft-repeated mantra of “backup, guess, and check” is even more important for this component, as any change to a scaleform object may unexpectedly break unrelated elements and require restoration to correct.
With this stated, it is advised to not use the basegame SWFs that are
located in the Startup
files. Modder DropTheSquid’s HUD Enhancements
mod both provides a more functionally flexible HUD for end users and
does not suffer from the propensity of UI elements to behave in
unexpected manners whenever an element is edited (Figure 37).
Figure 37
As seen in LEX scaleform tutorials, JPEXS, an open-source flash
decompiler can be used to open exported SWF files from LEX. Once a SWF
is opened, the icon library for each UI can be located. This will take
the form of a sprite with frames that cycle through each icon. It should
be noted that there may be multiple copies of this sprite. Each of these
copies will be used in different elements of the HUD. Thus, any
modification to one copy should be duplicated on each copy. For the HUD
Enhancements SWF GUI_SF_HUD_Enhanced.ME_HUD
, the icon sprites that
are used in game are 210, 479, 912, 1220, 1335, and 1557.
Shapes and sprites can be copied from SWFs exported from other Mass Effect games. Alternatively, JPEXS supports the importation of Scalable Vector Graphic (SVG) files.
The frames of each sprite can be pulled either from shapes, which is done for every icon in vanilla LE1, or other sprites, which is done for some icons in later games. Careful importation and adjustments of sprites from later games can be done to expand the list of possible icons in each Icon Sprite. However, given that adjustment of SWFs is a very volatile process, any change to any sprite or shape should be tested in-game to ensure no existing UI elements have been adversely affected in form, function, or both.
Figure 38
There are a few flags which should always be set whenever an icon is
added to the frame list in a sprite. placeFlagHasMatrix
allows fine
control of the exact dimensions and location of the icon within the
frame. placeFlagHasCharacter
determines the shape or sprite for the
icon. placeFlagHasMove
overrides the previous icon shape and
replaces it with the current icon.
Figure 39
A sprite can contain multiple layers of shapes as seen in Figure 39. Since SVGs are, by nature, very simple objects, layering them within JPEXS allows the creation of more complex icons with fine details. LE2 and LE3 use this method frequently. Thus, the migration of a sprite between SWFs of different games involves the importation of multiple objects. To minimize the impact on the recipient SWF, each shape component of the sprite should be imported one at a time. Then, the sprite itself can be recreated using the parameters seen in the donor SWF.
Occasionally, drastic impact to UI elements is entirely unavoidable, as extending the frames of the icon sprite will shift the file offsets of all subsequent elements, creating a snowball effect which could lead to the side effects seen in Figure 40.
Figure 40
Power and Talent Overhaul solves this problem by keeping the basegame
SWF file offsets of GUI_SF_CharacterRecord.PC_CharacterRecord
in
place and cloning the sprite labeled TalentHilightBlock
to the end
of the sprite list with the name TalentHilightBlock2
. The
ActionScript that references this sprite was then rerouted by editing
line 2396 of the associated pcode to instead reference
TalentHilightBlock2
. This succeeded in adding onto the talent icon
list while retaining the formatting of all elements in the Squad screen.
Counterintuitively, this affects the icon list for both the mouse and
keyboard Squad screen and the controller Squad screen, eliminating the
need to extend this workaround to
GUI_SF_CharacterRecord.CharacterRecord
.
The last element, gui_sf_utility.WindowPop
is by far the simplest,
as the lack of colored elements prevents the possible formatting impact
of file offset editing seen in other SWFs. The sprite with the icon list
in this is 341.
It is essential to check how the size and position of each icon translates to its appearance in game. Depending on where the sprite is used, the icons could appear larger, smaller, or in a different position. Similar to VFX editing, scaleform modification always requires extensive testing to ensure what is seen in JPEXS translates appropriately to the in-game UI.
LE1’s underlying talent and power architecture may be far more complex than later games in the series; however, the modification of powers and indeed, the importation of abilities from later games is entirely feasible. Functionalities that LE1 obstinately does not have can be emulated through innovative workarounds and careful manipulation of 2DA files. Visual effects can be ported and adapted by modifying VFX templates, particle systems, and materials, requiring extensive trial and error due to the complexity of their interactions with game code. The incorporation of new icons into the user interface can be accomplished by editing scaleform objects, though this process can often lead to unintended side effects that necessitate iterative testing and adjustments. A backup-and-test methodology is essential in maintaining the stability and functionality of the game throughout all parts of the process. Hopefully, the information and strategies outlined provide a comprehensive framework for anyone considering modding powers within the game.