GFF Items and Economy - OpenKotOR/PyKotor GitHub Wiki

GFF Types: Items and Economy

Items, merchants, journals, and factions form the game’s economy and progression systems. UTI defines every equippable or consumable object [UTI], UTM describes store inventories and pricing, JRL tracks quest progress visible to the player, and FAC controls inter-faction hostility and friendship.

Contents


UTI (Item)

Part of the GFF File Format Documentation.

UTI files define item templates for all objects in creature inventories, containers, and stores. Items range from weapons and armor to quest items, upgrades, and consumables. UTI files are loaded with the same resource resolution order as other resources (override, MOD/SAV, KEY/BIF).

Official Bioware Documentation: For the authoritative Bioware Aurora Engine Item format specification, see Bioware Aurora Item Format.

For mod developers:

Related formats:

PyKotor models UTI templates with the dedicated UTI data class and read_uti / write_uti helpers on top of the shared binary GFFBinaryReader.load pipeline, while Holocron Toolset exposes the same item fields through its uti.py editor. Other toolchains keep UTI in the generic GFF family as well, including reone's gff.cpp and gffreader.cpp, KotOR.js's GFFObject.ts, Kotor.NET's GFF.cs, and xoreos's Aurora GFF stack.

Core Identity fields

Field Type Description
TemplateResRef ResRef Template identifier for this item
Tag CExoString Unique tag for script references
LocalizedName CExoLocString Item name (localized)
Description CExoLocString Generic description
DescIdentified CExoLocString Description when identified
Comment CExoString Developer comment/notes

Base Item Configuration

Field Type Description
BaseItem int32 Index into baseitems.2da (defines item type)
Cost UInt32 Base value in credits
AddCost UInt32 Additional cost from properties
Plot byte Plot-critical item (cannot be sold/destroyed)
Charges byte Number of uses remaining
StackSize word Current stack quantity
ModelVariation byte model variation index [uti.py L82]
BodyVariation byte Body variation for armor [uti.py L81]
TextureVar byte texture variation for armor [uti.py L83]

BaseItem types (from baseitems.2da); row index into the 2DA defines item type.

Item Properties

Field Type Description
PropertiesList List Item properties and enchantments
Upgradable byte Can accept upgrades (KotOR1 only)
UpgradeLevel byte Current upgrade tier (KotOR2 only)

PropertiesList Struct fields:

  • PropertyName (word): Index into itempropdef.2da
  • Subtype (word): Property subtype/category
  • CostTable (byte): Cost table index
  • CostValue (word): Cost value
  • Param1 (byte): First parameter
  • Param1Value (byte): First parameter value
  • ChanceAppear (byte): Percentage chance to appear on randomly generated loot; default 100 [uti.py L169]

Property types are defined entirely in itempropdef.2da; the PropertyName field indexes into this table which maps each row to its subtype table, cost table, and parameter tables. PyKotor serializes each UTIProperty without enumerating property-type names in code — all valid property names derive from the 2DA at runtime [uti.py L165–L176].

Weapon-Specific fields

Field Type Description
WeaponColor (KotOR2) byte Blade color for lightsabers
WeaponWhoosh (KotOR2) byte Whoosh sound type

Lightsaber colors (KotOR2 WeaponColor):

  • 0: Blue, 1: Yellow, 2: Green, 3: Red
  • 4: Violet, 5: Orange, 6: Cyan, 7: Silver
  • 8: White, 9: Viridian, 10: Bronze

Armor-Specific fields

Field Type Description
BodyVariation byte Body model variation [uti.py L81]
TextureVar byte texture variation [uti.py L83]
ModelVariation byte model type [uti.py L82]
ArmorRulesType (KotOR2) byte Armor class category

Armor model Variations:

  • Body + texture Variation: Creates visual diversity
  • Armor adapts to wearer's body type and gender
  • appearance.2da defines valid combinations

Quest & Special Items

Field Type Description
Plot byte Cannot be sold or destroyed
Stolen byte Marked as stolen
Cursed byte Cannot be unequipped
Identified byte Player has identified the item

Plot Item Behavior:

  • Immune to destruction/selling
  • Often required for quest completion
  • Can have special script interactions

Upgrade System (KotOR1)

Field Type Description
Upgradable byte Item accepts upgrade items

Upgrade Mechanism:

  • Weapon/armor can have upgrade slots
  • Player applies upgrade items to base item
  • Properties from upgrade merge into base
  • Referenced in upgradetypes.2da

Upgrade System (KotOR2 Enhanced)

Field Type Description
UpgradeLevel byte Current upgrade tier (0-2)
WeaponColor byte Lightsaber blade color
WeaponWhoosh byte Swing sound type
ArmorRulesType byte Armor restriction category

KotOR2 Upgrade Slots:

Weapons and armor may have upgrade slots defined in baseitems.2da.

Visual & Audio

Field Type Description
ModelVariation byte Base model index [uti.py L82]
BodyVariation byte Body model for armor [uti.py L81]
TextureVar byte texture variant [uti.py L83]

model Resolution:

Item model names are derived from the ModelResRef column in baseitems.2da, combined with the ModelVariation index. textures follow the same naming convention.

Palette & Editor

Field Type Description
PaletteID byte Toolset palette category
Comment CExoString Designer notes/documentation

Toolset Integration:

  • PaletteID organizes items in editor
  • Does not affect gameplay
  • Used for content creation workflow

Implementation Notes

PyKotor parses UTI via construct_uti [uti.py L128], which deserializes each GFF field into the UTI data object.

Armor identification uses the ARMOR_BASE_ITEMS frozenset ({35,36,37,38,39,40,41,42,43,53,58,63,64,65,69,71,85,89,98,100,102,103}) defined in [uti.py L19] to distinguish armor from weapons when computing visual variants. PaletteID and Comment are toolset-only fields not used at game runtime.

See also


UTM (Merchant)

Part of the GFF File Format Documentation.

UTM files are GFF resources with root content type UTM (GFFContent.UTM) that define merchant templates: localized name, pricing (mark up / mark down), buy/sell flags, optional OnOpenStore script hook, and an ItemList of stock lines. Module store instances in the GIT reference a UTM template via the area’s store list (PyKotor maps GIT stores to ResourceType.UTM; instance wrapper GITStore L967–L1000). UTMs resolve like other resources: override -> MOD/SAV -> KEY/BIF.

Official Bioware Documentation: See Bioware Aurora Store Format for Aurora-era store semantics; KotOR field names below match PyKotor’s construct_utm / dismantle_utm and the class docstring’s LoadStore notes.

For mod developers:

Global merchant metadata also lives in merchants.2da (data table, not the per-template GFF).

Root struct fields

Field GFF type Role
ResRef ResRef Template resref (file stem).
LocName CExoLocString Localized merchant name.
Tag CExoString Tag for scripts / identification.
MarkUp int32 Markup when selling to the player (integer; divide by 100 for percentage multiplier [ModuleStore.ts L56]).
MarkDown int32 Markdown when buying from the player (integer; divide by 100 for percentage multiplier [ModuleStore.ts L52]).
OnOpenStore ResRef Script executed when the store UI opens.
Comment CExoString Authoring comment.
BuySellFlag byte Bit 0 = can buy from player; bit 1 = can sell to player [construct_utm L127–L128].
ItemList List Stock entries (see below).
ID byte Legacy field; default 5 in PyKotor [utm.py L83]; emitted only when use_deprecated=True in dismantle_utm L172.

ItemList element struct

Each list element is a struct with (at minimum) the fields PyKotor reads and writes:

Field GFF type Role
InventoryRes ResRef Item template (UTI) resref.
Infinite byte Infinite stock when non-zero.
Dropable byte Droppable flag (PyKotor writes the field only when true — dismantle_utm L213–L214).
Repos_PosX uint16 Repository grid X (writer uses slot index — dismantle_utm L164).
Repos_PosY uint16 Repository grid Y (writer uses 0dismantle_utm L165). Note: reone reads this field as Repos_Posy (lowercase 'y'); PyKotor writes Repos_PosY (capital 'Y').

PyKotor documents and round-trips merchant templates through UTM, construct_utm, dismantle_utm, read_utm, and write_utm on top of the shared GFFBinaryReader.load path, and the same merchant-root-as-GFF approach appears in reone's gffreader.cpp and gff.cpp, KotOR.js's GFFObject.ts, Kotor.NET's UTM.cs plus GFF.cs, and xoreos's Aurora GFF loader stack.

See also


JRL (Journal)

Part of the GFF File Format Documentation.

JRL files define the structure of the player's quest journal. They organize quests into categories and track progress through individual journal entries. JRL files are loaded with the same resource resolution order as other resources (override, MOD/SAV, KEY/BIF).

Official Bioware Documentation: For the authoritative Bioware Aurora Engine Journal format specification, see Bioware Aurora Journal Format.

For mod developers:

  • Journal updates are typically driven by DLG Quest/QuestEntry and scripts (AddJournalQuestEntry).

See also:

Related formats:

  • DLG (Quest, QuestEntry)
  • NCS (journal API)
  • 2DA (e.g. journal.2da for XP)

PyKotor represents journals through JRL, JRLQuest, JRLEntry, construct_jrl, read_jrl, and write_jrl, tags them as GFFContent.JRL, and decodes them through the same shared GFFBinaryReader.load path that Holocron Toolset builds on in its dialogue and module editors. Other implementations also keep JRL inside the generic GFF family, including reone's gff.cpp and gffreader.cpp, KotOR.js's GFFObject.ts, Kotor.NET's GFF.cs, and xoreos's Aurora reader stack.

Quest structure

JRL files contain a list of Categories (Quests), each containing a list of EntryList (States).

Field Type Description
Categories List List of quests

Quest Category (JRLQuest)

Field Type Description
Tag CExoString Unique quest identifier
Name CExoLocString Quest title
Comment CExoString Developer comment
Priority uint32 Sorting priority (0=Highest, 4=Lowest) [JRLQuestPriority L93–98]; default LOWEST when constructing a new JRLQuest object [jrl.py L64]
PlotIndex int32 Legacy plot index
PlanetID int32 Planet association (unused)
EntryList List List of quest states

Priority Levels:

  • 0 (Highest): Main quest line
  • 1 (High): Important side quests
  • 2 (Medium): Standard side quests
  • 3 (Low): Minor tasks
  • 4 (Lowest): Completed/Container

Quest Entry (JRLEntry)

Field Type Description
ID uint32 State identifier (referenced by scripts/dialogue)
Text CExoLocString Journal text displayed for this state
End uint16 1 if this state completes the quest [jrl.py L153]
XP_Percentage float (single) XP reward multiplier for reaching this state [jrl.py L155]

Quest Updates:

  • Scripts use AddJournalQuestEntry("Tag", ID) to update quests.
  • Dialogues use Quest and QuestEntry fields.
  • Only the highest ID reached is typically displayed (unless AllowOverrideHigher is set in global.jrl logic).
  • End=1 moves the quest to the "Completed" tab.

Implementation Notes

  • global.jrl: The master journal files for the entire game.
  • Module JRLs: Not typically used; most quests are global.
  • XP Rewards: XP_Percentage scales the journal.2da XP value for the quest.

See also


FAC (Faction) File Format

FAC files are GFF-based format files that store faction definitions and reputation relationships between factions in KotOR modules. The file is typically named repute.fac in modules. FAC files are loaded with the same resource resolution order as other resources (override, MOD/SAV, KEY/BIF).

Official BioWare Documentation: For the authoritative BioWare Aurora Engine Faction Format specification, see Bioware Aurora Faction Format.

Source: This documentation is based on the official BioWare Aurora Engine Faction Format PDF, contained in xoreos-docs: specs/bioware/Faction_Format.pdf.


Overview

A Faction is a control system for determining how game objects interact with each other in terms of friendly, neutral, and hostile reactions. Faction information is stored in the repute.fac file in a module or savegame. This file uses BioWare's Generic File Format (GFF), and the GFF FileType string in the header of repute.fac is "FAC ".

Related Files:

PyKotor describes module faction state with FACFaction, FACReputation, FAC, construct_fac, read_fac, and write_fac, labels the root as GFFContent.FAC, and parses it through the shared GFFBinaryReader.load; Holocron Toolset exposes the same repute.fac data where its module and generic GFF workflows surface faction editing. Other engines again keep FAC as ordinary GFF, including reone's gff.cpp and gffreader.cpp, KotOR.js's GFFObject.ts, Kotor.NET's GFF.cs, and xoreos's Aurora stack.


Top Level Struct

The top-level GFF struct contains two lists:

Label Type Description
FactionList List List of Faction Structs (StructID = list index). Defines what Factions exist in the module.
RepList List List of Reputation Structs (StructID = list index). Defines how each Faction stands with every other Faction.

Faction Struct

Each Faction Struct in the FactionList defines a single faction. The StructID corresponds to the faction's index in the list, which is used as the faction ID in reputation relationships.

Label Type Description
FactionName CExoString Name of the Faction.
FactionGlobal WORD Global Effect flag. 1 if all members of this faction immediately change their standings with respect to another faction if just one member of this faction changes it standings. 0 if other members of a faction do not change their standings in response to a change in a single member.
FactionParentID DWORD Index into the Top Level Struct's FactionList specifying the Faction from which this Faction was derived. The first four standard factions (PC, Hostile, Commoner, and Merchant) have no parents, and use 0xFFFFFFFF as their FactionParentID. No other Factions can use this value.

Standard Factions

KotOR modules typically contain the following standard factions (in order):

  1. PC (Player) - Index 0, Parent: 0xFFFFFFFF
  2. Hostile - Index 1, Parent: 0xFFFFFFFF
  3. Commoner - Index 2, Parent: 0xFFFFFFFF
  4. Merchant - Index 3, Parent: 0xFFFFFFFF
  5. Defender - Index 4, Parent: 0xFFFFFFFF (KotOR 2 only)

Reputation Struct

Each Reputation Struct in the RepList describes how one faction feels about another faction. Feelings need not be mutual. For example, Exterminators might be hostile to Rats, but Rats may be neutral to Exterminators, so that a Rat would only attack a Hunter or run away from a Hunter if a Hunter attacked the Rat first.

Label Type Description
FactionID1 DWORD Index into the Top-Level Struct's FactionList. "Faction1"
FactionID2 DWORD Index into the Top-Level Struct's FactionList. "Faction2"
FactionRep DWORD How Faction2 perceives Faction1. 0–10 = Faction2 is hostile to Faction1, 11–89 = Faction2 is neutral to Faction1, 90–100 = Faction2 is friendly to Faction1 [FactionManager.ts L131–L141]; default 50 when field absent [fac.py L134]

Reputation Values

Range Relationship Description
0–10 Hostile Faction2 will attack Faction1 on sight [IsHostile L132].
11–89 Neutral Faction2 is neutral to Faction1. No automatic aggression [IsNeutral L136].
90–100 Friendly Faction2 is friendly to Faction1. Will not attack and may assist [IsFriendly L140].

RepList Completeness

For the RepList to be exhaustively complete, it requires N*N elements, where N = the number of elements in the FactionList. However, the way that the PC Faction (FactionID2 == 0) feels about any other faction is actually meaningless, because PCs are player-controlled and not subject to faction-based AI reactions. Therefore, any Reputation Struct where FactionID2 == 0 (i.e., PC) is not strictly necessary, and can therefore be omitted from the RepList.

Thus, for the RepList to be sufficiently complete, it requires N*N - N elements, where N = the number of elements in the FactionList, assuming that one of those Factions is the PC Faction.

In practice, however, the RepList may contain anywhere from (NN - N) to (NN - 1) elements, due to a small idiosyncrasy in how the toolset generates and saves the list. When a new faction is created, up to two new entries may appear for the PC Faction.

From all the above, it follows that a module that contains no user-defined factions will have exactly NN - N Faction Structs, where N = 5. Modules containing user-defined factions will have more. The maximum number of Faction Structs in the RepList is NN - 1, because the Player Faction itself can never be a parent faction.


Related 2DA Files

repute.2da

The repute.2da file defines default faction standings. Each row corresponds to a faction ID, and columns represent how that faction feels about other factions.

Rows (by faction ID):

  • Row 0: Player
  • Row 1: Hostile
  • Row 2: Commoner
  • Row 3: Merchant
  • Row 4: Defender (KotOR 2 only)

Columns:

  • LABEL - String: Programmer label; name of faction being considered by the faction named in each of the other columns. Row number is the faction ID.
  • HOSTILE - Integer: How the Hostile faction feels about the other factions
  • COMMONER - Integer: How the Commoner faction feels about the other factions
  • MERCHANT - Integer: How the Merchant faction feels about the other factions
  • DEFENDER - Integer: How the Defender faction feels about the other factions

Note: Do not add new rows to repute.2da. They will be ignored.

repadjust.2da

The repadjust.2da file describes how faction reputation standings change in response to different faction-affecting actions, how the presence of witnesses affects the changes, and by how much the changes occur.

Rows (action types - hardcoded, do not change order):

  • Attack
  • Theft
  • Kill
  • Help

Columns:

  • LABEL - String: Programmer label; name of an action.
  • PERSONALREP - Integer: Personal reputation adjustment of how the target feels about the perpetrator of the action named in the LABEL.
  • FACTIONREP - Integer: Base faction reputation adjustment in how the target's Faction feels about the perpetrator. This reputation adjustment is modified further by the effect of witnesses, as controlled by the columns described below. Note that a witness only affects faction standing if the witness belongs to a Global faction.
  • WITFRIA - Integer: Friendly witness target faction reputation adjustment.
  • WITFRIB - Integer: Friendly witness personal reputation adjustment.
  • WITFRIC - Integer: Friendly witness faction reputation adjustment.
  • WITNEUA - Integer: Neutral witness target faction reputation adjustment.
  • WITNEUB - Integer: Neutral witness personal reputation adjustment.
  • WITNEUC - Integer: Neutral witness faction reputation adjustment.
  • WITENEA - Integer: Enemy witness target faction reputation adjustment.
  • WITENEB - Integer: Enemy witness personal reputation adjustment.
  • WITENEC - Integer: Enemy witness faction reputation adjustment.

Note: Do not change the order of rows in repadjust.2da. Adding new rows will have no effect.


Usage Examples

Reading a FAC File

from pykotor.resource.generics.fac import read_fac

# Read from file
fac = read_fac("module/repute.fac")

# Access factions
for i, faction in enumerate(fac.factions):
    print(f"Faction {i}: {faction.name}")
    print(f"  Global Effect: {faction.global_effect}")
    print(f"  Parent ID: {faction.parent_id}")

# Access reputations
for rep in fac.reputations:
    print(f"Faction {rep.faction_id2} perceives Faction {rep.faction_id1} as: {rep.reputation}")

Creating a FAC File

from pykotor.resource.generics.fac import FAC, FACFaction, FACReputation, write_fac

fac = FAC()

# Add standard factions
pc = FACFaction()
pc.name = "PC"
pc.global_effect = False
pc.parent_id = 0xFFFFFFFF
fac.factions.append(pc)

hostile = FACFaction()
hostile.name = "Hostile"
hostile.global_effect = True
hostile.parent_id = 0xFFFFFFFF
fac.factions.append(hostile)

# Add reputation relationship
rep = FACReputation()
rep.faction_id1 = 1  # Hostile
rep.faction_id2 = 0  # PC
rep.reputation = 5   # Hostile (0-10 range)
fac.reputations.append(rep)

# Write to file
write_fac(fac, "output/repute.fac")

Implementation Notes

  • Faction IDs correspond to list indices in the FactionList
  • The PC faction (index 0) typically has no reputation entries where FactionID2 == 0, as PC reactions are player-controlled
  • Standard factions use 0xFFFFFFFF as their parent ID
  • Reputation values outside the 0-100 range may cause undefined behavior
  • Global factions propagate reputation changes across all members when one member's reputation changes

See also


See also

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