_________________________________________

¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

GreedFall(PC)


Modding Tips

_________________________________________

¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯


April 30, 2020

Version 2.00


Written by: Dheu

Email: Dheuster@gmail.com

Use subject: GreedFall Modding Tips 2.00


If you see any mistakes, or have anything that you want to add,

you can email me or post to the forums located at nexusmods:

https://www.nexusmods.com/greedfall/mods/116?tab=posts



______________________________________Notes____________________________________

This Tutorial\FAQ is published at:


https://www.nexusmods.com/greedfall/mods/116


This Document looks best in a fixed-width font, such as Courier New.

Word wrap should be turned OFF if the option is available.


GreedFall is Copyright © 2019 by Focus Home Interactive


I am not affiliated with Focus Home Interactive or anyone who had anything to

do with the creation of this game. This Document may be posted on any site.

You may not charge for, or in any way profit from this Document.


-------------------------------------------------------------------------------

Table of Some-Contents:

-------------------------------------------------------------------------------


I. Introduction

I.1 Standard Tools

II. Extracting and editing Game Resources

II.1 PGZ Files

II.2 PSSG Files

II.3 pssgExtractor.py

II.4 PSSG Format Details

III. Basics

III.1 Load Order and Contention

III.2 Registering new Content

III.3 Inputs

III.4 Spells versus Commands

III.5 Everything is a Skill

IV. Quests

IV.1 SQG FILE Contents

IV.2 ConditionSets

V. Dialogues

V.1 SDG FILE Contents

VI. Audio

VI.1 Spoken Audio

VI.2 Music

VI.3 Sounds/FX

VI.4 Adding/Replacing Audio


...


X. Models and Textures

X.1 Textures

X.2 Models

X.3 Tools?


XI. Miscellaneous

XI.1 Cutscenes

XI.2 Levels and Maps

XII. Legalities

-------------------------------------------------------------------------------

Table of No-Contents (TODO)

-------------------------------------------------------------------------------

VII. Items

VII.1 General

VII.2 Armor

VII.3 Weapons

VII.4 Potions and Grenades

VII.5 Loot and Rewards


VIII. Merchants

VIII.1 Defining Inventories

VIII.1 Linking NPCs to inventories

IX. Spells

IX.1 ???

===============================================================================

I. > > > > Introduction

===============================================================================


This document is a set of observations I have made over the past few months

as I have modded Greedfall. I thought I would share with others to give

them a jump start. I am also hoping in doing so, others might share their

own discoveries with me so I can add them to this document.

This is a work in progress. So please forgive some of the empty sections.

I make certain assumptions about the person reading this. In short, I assume

the reader is a programmer of some ilk with enough general programming

experience that terms like "scope", "classes" and "inheritance" don't sound

alien. Python experience is also useful if you want to pull apart some

of the scripts I have made.

There are many different aspects to modding a game. A mod may introduce a new

capability, new dialog, new quests. For some, modding is about creating and

incorporating new models and special effects.

Unfortunately, there are not currently any readily-available programs or

utilities that allow one to import new models into GreedFall, though I

feel like it could be done for someone determined. I simply lack the

3 modeling knowledge to write import/export plugins myself.

This tutorial focuses on modding GreedFall programatically. Deforming and

Re-skinning models is outside the scope of this document. I will touch on

what I know, but it isn't much.


-------------------------------------------------------------------------------

I.1 > > > > Standard Tools

-------------------------------------------------------------------------------


7-Zip:


GreedFall resources are compressed into files using gzip compression.

You will need a program that can recognize and uncompress gzip to access,

add and/or edit those resources. 7-Zip is the defacto compression utility

of choice.


https://www.7-zip.org/


EGO PSSG Editor:


=== THE === modding tool for GreedFall. And if you make a mod that was

only possible thanks to that tool, I highly recommend sending the author,

Ryder25, a donation.

The most useful ability of the PSSG editor is the ability to export game

resource files (PSSG files) to XML. And then import XML back to PSSG. XML

is human readable and much easier to mod than binary data.

https://ryder25.itch.io/ego-pssg-editor

HxD:


Once you extract PSSG files to XML, you will find large sequences of HEX

characters within that represent the internal binary data. So how to you

convert those sequences to files that you can actually open up with say..

an audio editor?


For that, you need a Hex editor that supports pasting of hex characters.

I recommend HxD. It is a windows based Hex editor. It is the key to

taking the binary data presented in the XML versions of the PSSG files

and saving it off to independent files. (I step you through this process

later when I extract the OGG audio data from one of the files).

For those technical types, it is also useful for figuring out things like

the format of binary files. (It is how I figured out the PSSG format).


https://download.cnet.com/HxD-Hex-Editor/3000-2352_4-10891068.html

Notepad++


Greedfall was developed by a French gaming shop, so to open some files

you need a text editor with international character support.

https://notepad-plus-plus.org/downloads/


CYGWIN:


CYGWIN gives you a Linux style command shell on Windows complete with

most Linux core programs. It adds an icon to the desktop:

"Cygwin(64) Terminal"

Mostly I use it for the program "grep", which is used to search through

all the files in a directory for a text string.


$ cd /cygdrive/c/path/to/extracted/resources/

$ grep -Harl 'searchforthisstring' .


The above 2 commands will search all files in the resources directory and

subdirectories for the string "searchforthisstring" and list any file

names that hit. For those unfamiliar with Linux style command prompts,

you can get the documentation for most commands by typing 'man' followed

by the command. IE:

$ man grep


I used grep and HxD to discover most of the things I know about GreedFall

It is also handy for batch processing when you want to run a script on

all files in a directory (or sub directories). I document how to do that

later when I talk about some of the helper scripts I wrote.


https://cygwin.com/install.html

Python 3.x:


If you install CYGWIN, by default it installs python 2.7. With version

2.0 of this document, I upgraded the scripts to use the latest version

of python available with CYGWIN as BLENDER only works with Python 3.x

and I didn't want to have to switch python versions .

To install the latest 3.x Python with CYGWIN, you will need to run

the CYGWIN setup and search for "Python". Find the highest 3.x version

you can (python38 when I made this) and then change "skip" to the latest

version you can select.

Hit next a few times and eventually CYGWIN will download and install

python 3.x packages. However you are not done. The default python

(symbolic link) is still in the /usr/bin directory linked to

python2.7.exe. To correct this, you will need to open a CYGWIN terminal,

go to /usr and type "chmod 777 bin". Then in /usr/bin, remove python

"rm python" and create a new symbolic link "link python3.8.exe python"

Type : "python --version" to confirm you are now using the latest.

In my case it returned "Python 3.8.2"

To download the same version for windows, go to:


https://www.python.org/downloads/release/python-382/

(substitute the version for the last few digits)

I recommend keeping your CYGWIN and WINDOWS versions in sync.


===============================================================================

II > > > > Extracting and editing Game Resources

===============================================================================


If you look inside the GreedFall/packs directory, you will notice some large

files with the extension .spk. All the resources for the game are compressed

within these files. This serves 3 purposes:


1) Faster Loading:


Easier to ask the OS to retrieve 1 big file than 300 small files.


2) Smaller Footprint:


Hey, who wants the game to take up another 5 gigs?


3) User Overrides:


When a user extracts a resource from the .spk file and places it within

GreedFall/datalocal, the game engine will load the users version on

startup instead of the one in the SPK file. While this design allows user

mods, the downside is that it promotes contention between mods.



If you want to really get into modding the game, you will want to extract all

the .spk files. DO NOT extract them to the game directory as that will cause

the game to try to load everything as overrides, making it take minutes to

startup and most things to become unplayable. Rather, extract everything to a

datalocal directory somewhere else on your machine.

IE: I extracted them to the parent directory:

C:\Steam\steamapps\common\datalocal


MOST of the files extracted are XML type files with various extensions. These

can be opened with a regular text editor (notepad++ recommended) and you can

use GREP to search through them for keywords:


.gui: Associated with the graphical user interface, but still xml.

.sli: Associated with game configuration, but still xml

.sqg: Associated with quest and event definitions, but still xml

.sdg: Associated with dialogue trees, but still xml


A bit ironically, the ".txt" extension files under the text directory use

"UCS-2 LE BOM" character encoding, which will not cleanly open in a normal

text editor. You will need an editor such as Notepad++ to open those files.

The .txt files provide the language translations of the various strings (What

you see when people talk, descriptions of items, etc...). More importantly,

if you scan for a term using grep, grep probably wont find it as the

character encoding puts junk between the characters and screws up plain text

scans. If you really want, you can make a separate directory and open/save

off all the .txt files in the other directory using UTF-8. Notepad++ can

convert to other character sets. However... you will have to open and save

every file (all 1237 of them). You can do it pretty fast if you make a

macro in Notepad++ and open/convert 10 or so at a time. But still takes

awhile.


-------------------------------------------------------------------------------

II.1 > > > > PGZ Files

-------------------------------------------------------------------------------


PGZ means "PSSG GZIPPED" File. The contents of .pgz files normally do not

have extensions, but they are all .pssg files. You can uncompress these

with 7-zip. Conversely, if you uncompress a file, make alterations and wish

to re-compress it, you can do so by compressing the altered artifact with

7-zip, but you have to:


1. Remove the .pssg extension

2. Choose "gzip" as the archive format

3. Change the output extension to ".pgz"


As to what is INSIDE the .pgz files... The answer is a PSSG file.


-------------------------------------------------------------------------------

II.2 > > > > PSSG Files

-------------------------------------------------------------------------------


What is PSSG? PSSG is a container format for game resources. Basically it is

a custom Binary XML representation.

Imagine you have an OGG audio file. In the Silk Engine, it would save the

resource in a .pssg file. A binary header would indicate to the engine what

the contents of the file are, the mime-type and the offset of where the data

begins and other information like the id that is used by the game to reference

the object.


Bottom line... if you know the PSSG header format, you can figure out where

the ogg data is and extract it. Conversely, if you know that OGG audio files

started with a specific binary sequence (4F 67 67 53), you can open the file

in a hex-editor, search for that sequence and extract the audio without even

knowing the .pssg format.

** This only works when the PSSG file only wraps a single OGG file and that

File is the last element of the PSSG file. Fortunately, most audio in

the game does exactly that... 1 file wraps 1 ogg which is the last thing

in the file. So with a script, you can extract audio data without even

parsing the PSSG file. See included script pssgOggExtract.py


From what I can tell, PSSG was a format invented for the Phyre Engine: A Sony

PlayStation engine released in 2008 to promote creation of games for the PS3.

The engine and dev environment were made free because Sony wanted games for

their platforms. As far as I can tell, support for the engine ended in 2016

when the PS4 came out. However, it was free and cross-platform. So a number

of development shops took the engine and expanded it into their own

customized versions (updating it in-house to meet the demands of newer

Direct X standards). The Ego Engine and more recently the Silk Engine are

byproducts of the Phyre Engine. GreedFall uses the Silk Engine.


So why the history? These dev shops didn't customize everything. Only the

parts that they needed. So an app made for an ego engine game can also be

used to open most of the silk engine pssg files.


One such program is the PSSG Editor created by Ryder25 mentioned above in the

Standard Tools section.


An Extraction/Insertion Example

-------------------------------

Lets suppose I want to extract/update and change an audio file. For the sake

of this example, we will look at:


voice_dialog/cha_huf_aphra/dlg_aphra_friendship_101.pgz


1 Extract the file with 7zip.

2 Open the extracted file with PSSG Editor. It will show you a number of

nodes. The LAST node is the binary data.

4 In PSSG Editor, highlight all the binary data for the last node. Then go

to Hxd, create a new file and paste. Save the file as something with an

"ogg" extension.

5. Now if you double click on the file in explorer, you will hear the audio

play.


Lets pretend you edit the file and remove the last word. Now you want to

re-inject the updated file into the PSSG. PSSG Editor will not let you paste

binary data directly into the window. But you can paste data into an

exported XML version of the PSSG file. XML is recommended as it helps to

ensure integrity and works even when the binary data isn't the last item in

the PSSG payload.


In PSSG Editor, highlight the root node "PSSGDATABASE", then select:

Nodes -> Export.


Export to the same directory and filename, but with ".xml" added. Open the

XML file and find the binary data block at the end. There you will see a

bunch of hexadecimal values. Open up your edited .ogg file in HxD (Hex

Editor). Hit CTRL + A to select all and then CTRL + C to copy to clipboard.

HxD is smart enough to copy the data to the clipboard as text based hex data.

Now in the XML File, delete the existing hex values and then paste the hex

data into the xml. Don't worry about the column formatting. The XML doesn't

care. The original XML had line feeds every 24 hex chars for human

readability, but it isn't needed.


In the PSSG Editor, select New. Highlight the root node and select Import.

Browse to your updated XML file and hit Okay. Don't forget to update the

"binaryDataSize" in the definition of the last node, otherwise the game may

crash.

(To figure out the size of the binary data, open the ogg once more in HxD,

Hit CTRL+A and then (View -> Offset base -> Decimal). Now the length in

the bottom of HxD is the length of the binary data you have highlighted

in plan decimal form. That is the value you want to enter for the

binary length)).


Save the file in PSSG format. Then remove the .pssg extension and re-compress

the pssg file using 7-zip with the gzip archive format selected. Change the

extension from .gz to .pgz. Finally, place the file in the Greedfall/datalocal

directory (location doesn't matter). The game will now load that file when it

searches for the .pgz files as an override and use your edited audio line.


NOTES: Spoken audio also has time length data associated with the dialogue.

So technically, you would have to update that as well if the length

of the audio is significantly different. Otherwise the person would

get cut off or there would be an unusual pause at the end of the line.

See "Using existing audio lines to update/create new conversations"

below for more info.


-------------------------------------------------------------------------------

II.3 > > > > pssgExtractor.py

-------------------------------------------------------------------------------


The latest release of EGO PSSG Editor was 2017. That program can be used to

open most PSSG files and update many resources... But if you want to mess

with some of the new features added by Silk (like cutscenes), EGO PSSG

Editor wont help. Sometimes you will also run into snags where the

PSSG Editor will refuse to import your updated file.

The Ego PSSG Editor is schema based, so if you happen to open a PSSG file

that uses any unrecognized schema elements, it barfs. This is actually

surprisingly rare, but if you happen to run across one of these (such as

the cutscenes or levels), you need something else.


To help with this issue, I made my own extractor/combiner. See

pssgExtract.py (Python script). It is a command line script, so it isn't

as nice or user friendly as EGO PSSG Editor, however it does not rely on

schemas. It reads the file in and makes a best guess at the schema (saves

it off in case a user wants to tweak it) and then converts to XML. You can

then update/edit the XML file and convert the XML back to PSSG.

If you compare the XML output of this script with the XML output of the

PSSG Editor's export command, the two will often be very similar, in some

cases even exactly the same. However, there are minor differences, so

in general you shouldn't try opening pssgExtractor xml files with PSSG

Editor or vice versa.

Tweaking the Schema:

Version 2.0 of pssgExtractor.py now supports 3 big new features:

1) It is now cross-compatible with Python 2.x and 3.x.

2) pschema files now support composite types. A composite type is

and array that describes the structure of data that is normally

binary and renders as HEX in XML. For example, the TRANSLATE

tag of level files (.sla) normally render as 64 Bytes (Hex).

But now you can update the .pschema and change it to:

"_Type" : "Float[8]",

or

"_Type" : "Float[]",

Then the next time you convert the PSSG to XML, you will see floats

(which you can edit as floats and convert back to PSSG).

3) Deep Analysus : The "-d" flag is experimental, but basically

attempts to auto-generate these composite types for you by

analysing the binary data and deducing what it most likely is.

My deduction logic is pretty rudimentory, but it provides a

starting point. Note "-d" will do nothing if a .pschema file already

exists. So make sure you either remove the existing .pschema first or

run with -o as well to force overrite the current schema.

-------------------------------------------------------------------------------

II.4 > > > > PSSG Format Details

-------------------------------------------------------------------------------


PSSG is a form of Binary XML that consists of 2 parts, a header and a body:


A: Header: Describes the schema (elements, attributes and their indexes)


1: 4 char header: PSSG

2: 4 bytes (UIN32): Size of the entire file in Bytes (minus the

8 byte header):

3: 4 bytes (UINT32): MAX ATTRIBUTE ID (1 greater than largest ID in file)

4: 4 bytes (UINT32): NUMBER of UNIQ ELEMENTS

5: 4 bytes (UINT32): Index of Element Type (1 if it is your first)

6: 4 bytes (UINT32): Size of the first element tag (string length)

7: N chars : Name of the first tag (N = value of previous UINT32)

8: 4 bytes (UINT32): number of attributes in Element

9: 4 bytes (UINT32): ID of Attribute (global index, not index relative to

element)

10: 4 bytes (UINT32): size of the first attribute name (string length)

11: N chars : The name of the first attributes (N = value of previous

UINT32)

12: Repeat steps 9 - 11 until you have read value of #8 attributes

13: Repeat steps 5 - 11 until you have read value of #4 elements


*SPECIAL: Element "XXX" lists attributes that can appear under any element.

A common value is "id".

NOTES: The original PSSG spec made all elements uppercase and all

attributes camel-case. Silk added some elements and attributes and

those specific additions do not use the original standard, but

instead Make all new Elements and Attributes use First letter

uppercase.


Fortunately, most PSSG files use the original spec. So it is pretty

easy to scan the header with a hex editor and create your own index

map by counting capital and camel-case strings.

B: Body: Instead of Verbose XML, uses the indexes from the header to define

the content. Begins after processing the end of the schema.


1: 4 bytes (UIN32): GLOBAL ELEMENT INDEX

2: 4 bytes (UINT32): Total Size of element (attributes + content) in bytes

3: 4 bytes (UINT32): Size of Attribute Data in Bytes (starting after this

value)

a: If #3 > 0:

4a: 4 bytes (UINT32): AttributeId (global index)

5a: 4 bytes (UINT32): Size of attribute value in bytes:

6a: Attribute Value:

- if #5 indicates 4 Bytes, assume INT32 follows.

- If INT32 value is greater than 981668463 or less than

-981668463, recast to float.

- if #5 is more than 4 Bytes, read/cast first 4 Bytes to UINT32

- If value of UINT32 is exactly 4 less than the value of #5,

assume remaining bytes are ASCII friendly characters (string

value)

- else... assume N bytes following #5 are a byteArray.

(N = value of #5) This includes the 4 you read in to test for

string

... return to #4a until SIZE OF Attribute Data (#3) has been read.

7a: In the original PSSG spec, elements stored most of their content

in attributes. When large content was needed, you would make an

element that had no attributes specifically for that content. For

this reason, MOST pssg files wont have 7a (Elements with both

attributes AND content)

However Silk engine updated the spec with elements that have both

attributes and content. (Granted an element with both can not

parent other elements).

So when an element has attributes, how do you know if the content

(space between #2 and #3) is a value associated with the element

or merely space for other child elements? The answer: You don't.

It would require a schema. However... to define an element with

either an attribute or 4 bytes of content, you would need

at least 24 bytes. So when writing a generic scanner, if the

content size is greater than 0 and less than 24, we assume the

bytes are the elements value. If the size is greater than 23, we

assume it is space for child elements (but that might be wrong).

Additionally, Silk specifically expanded the spec to support

generic data. They added the elements:

<Int Name=""></Int> (4 Bytes)

<Float Name=""></Float> (4 Bytes)

<UnsignedInt Name=""></UnsignedInt> (4 Bytes)

<String Name="" Value=""/> ***

<Vector3 Name=""></Vector> (Contains 3 floats (12 Bytes))

<Color Name=""></Color> (Contains 3 floats (12 Bytes))

*** String was the only one that doesn't have content


Basically they added a bunch of named primitive Elements and all

but String have content. Mostly I have seen sequences of these in

the Cut scene definitions. So alternatively (or additionally), you

could specifically look for these element names.

b: if #3 == 0:

4b: Element Content Value:

N bytes of data are associated with element #1, N = value of

(#2 - 4) (subtract 4 for the bytes needed to read #3)

... return to #1 until all data is read.


NOTES:

** 2: PSSGDATABASE is normally the first element described, so the size of

the first element is normally the distance to the end of the file

following the UINT32.

** 3: In the example file, size of attributes was 88 byes.

** 4a: In the example file, first attributeId was "2"


**: In many cases, the game doesn't actually care if the file is converted

back to PSSG. If you use EGO Editor (which is schema driven), the game

will often work with the XML file, so long as the extension is removed

and it is still placed in a .pgz file.


===============================================================================

III > > > > Basics

===============================================================================


-------------------------------------------------------------------------------

III.1 > > > > Load Order and Contention

-------------------------------------------------------------------------------


I mentioned that the game loads resources found under GreedFall/datalocal

as overrides. There are a few things to know.


First... all filenames in greedfall are unique. That is why they are all

so huge. For this same reason, you don't have to recreate the path that you

found the file in to get the game to use it. You could place all your edited

files in the directory:


GreedFall/datalocal/


However, this isn't recommended. If you do that and the user installs 2 mods

that do the same thing, then somebody is going to get clobbered.


So the best way to store your modded resources is to place them in a

directory named after your mod.


GreedFall/datalocal/myMod/


Second... the game is first come first serve. That is... if the game has

already encountered “library_game_components.sli” in another sub-directory

before it gets to your sub-directory, your SLI file (and edits) will be

ignored.

The game searches subdirectories FIRST (Depth first traversal). And it seems

to sort the directories alpha-numerically before it dives. This is why you

will see some mods place their resources in “0ModName”. The “0” is the

authors way of indicate loading priority.


I don't recommend getting into naming wars. Then you end up with crazy names

like “0000000MyModIsMoreImportantThanYours”. Remember, if your mod breaks

10 other mods, that will get you more flack than your mod simply not

working. Contention is part of life. So I recommend simply sticking with

something that makes sense.

Different mod authors handle contention in different ways. The most common

systems I have seen:

1) Patches... : The mod author downloads other mods where users report

issues and release patches in their optional download sections. This

works if the mod author remains active. But tends to fall apart once

the mod author (inevitably) moves on to other projects.

2) Compatible/Incompatible Lists: Use the forums to maintain lists of known

compatible and incompatible mods. Author can maintain a summary at the

top with a sticky, but ultimately, users can maintain the list once the

author moves on.

3) Super-compatible approaches and code: In some cases, mods can be made

using ADDITIVE techniques that minimize the probability of conflict

with other mods. Unfortunately, GreedFall has no "Load Loose Files"

option. So there is no 100% contention-proof method of making a mod

for GreedFall. Though... you can minimize the probability and at the

very least, make it easy for the user to resolve on their own.

-------------------------------------------------------------------------------

III.2 > > > > Registering new Content

-------------------------------------------------------------------------------


My first mod added a bunch of “new” items to the game. (reused existing

models/textures, but defined items with different stats and color overlays).


Like other games, GreedFall will not simply load “loose” files simply because

they are present in the datalocal directory. Anything you ADD to the game has

to be registered by one of the SLI config files.


The easiest way to discover what needs to edited/updated is to simply

download another mod that already does something like what you are trying to

accomplish. Not to toot my own horn, but I know the mod I made, so I will use

it as an example.


My first mod (Naut-E Wares) is located here:


https://www.nexusmods.com/greedfall/mods/110?tab=files


You don't have to download or install the mod. However, if you go to the files

section of the mod, you will see a “Preview File Contents” link. This will

list all the files I created and edited to make the mod. I EDITED these game

resources:


- cha_autogen_npc_variantes.sli

- library_description_tags.sli

- library_equipment_default.sli

- library_game_components.sli

- library_item.sli

- lvl_02_south_city_background.sqg


Most of these files are “loader” type files that reach out and load other

game resources. In pretty much all cases, my edit bulls down to 1 line where

I add a new path to loader that points to a file specific to my mod with

new content. The files above point to:

- library_game_components.sli -> naute_wares_merchant_inventory.sli

- library_description_tags.sli -> naute_wares_descriptions.sli

- library_item.sli -> naute_wares_items.sli

- lvl_02_south_city_background.sqg -> naute_wares_interaction_registration.sqg


There will still be conflicts, but combining files with nothing but ADDITIVE

changes is trivial. It would be easy to create a mod manager that auto-merges

library files like this if all mods made 1 line additive changes to the config

files. Conversely, it isn't too hard to explain to users how to make two mods

work together when edits are made in this manner.


My next project may very well be a trivial merge program that scans datalocal

for files of the same name and attempts to trivial merge them into a

directory that the mod-manager manages. Most likely the name of the

“merged” files will be placed in a directory that starts with “##”. So I

humbly request other mods avoid the name “##” or mod directories that start

with more than 1 '#', so that such a mod manager can eventually be created.


I mentioned colors. It turns out, the game developers decided to make it

easy to put a color overlay on items with a simple config tweak to an

item definition. It is so they didn't have to create dozens of versions of

item textures to add variety to the game. Instead, each item has a primary

and secondary color. You can apply a color overlay to the primary color of

an item with a special tag in its definition:


IE: customShiftColor="0,-30,10"


or: customShiftColor="#1, #2, #3"

Where:

#1 : Hue offset: range -360 to 0.

#2 : Saturation: range -100 to 100. (-100 = gray scale)

#3 : Brightness: range -64 to 64. (-64 = black. 64 = white)


-------------------------------------------------------------------------------

III.3 > > > > Inputs

-------------------------------------------------------------------------------


Inputs have to do with key mappings. For example, you hit the letter "m" and

the local map appears. The games default key mappings are setup in a folder

under "datalocal/inputs".

For modders interested in making mods that change the mappings (Say for a

left-handed mouse user), that is where you would want to start. You can also

map Inputs to spell effects (which is how the light works), but that is more

complicated.


-------------------------------------------------------------------------------

III.4 > > > > Spells versus Commands

-------------------------------------------------------------------------------


Spells are immersive. You caste a spell and something happens in the game

world. Commands are the opposite. They "break immersion" by doing something

like bringing up an inventory screen, or a travel gui. Most commands are

accessed from interactions (dialogues or iterative objects)

This distinction is one of the reasons you haven't seen someone come out

with a mod that allows Fast Travel using a potion or spell. There is no

direct connection between casting a spell and issuing a command to the

game engine. Though there may be work arounds.


-------------------------------------------------------------------------------

III.5 > > > > Everything is a skill

-------------------------------------------------------------------------------

Lets get something out of the way. Almost all magic in greedfall is a

"skill". When you define skills, you define what they are named and what

category they show up in if the user hits tab to do something mid-combat.

"Use Potion", "Throw Grenade", "Cast Magic Missile"... all of these are

skills and in truth, every player starts the game with all of them.

However, to show up when you hit tab, the skills have conditions defined.

For example, to cast certain spells, the condition may require that you

have purchased something from the skill tree to show up in your spell

list.

Throwing a grenade is just like casting a spell. The difference is that it

has a inventory requirement AND a skill requirement to show up. Casting the

"Throw Grenade" spell also subtracts a potion from your inventory. And yes,

even shooting a gun is a spell that requires a specific item in one of the

hand slots and ammunition.


When a skill is activated, a series of "effects" occur. These can include

animations, particle effects, and changing certain stats on the player or

a target like their health or resistances. Effects can also change global

variables. Point here is as far as the game is concerned, casting a spell

or throwing a grenade or even shooting a gun are all a series of effects

connected to a skill.

I will likely be making some new spells in future mods, so I will fill

this section in with more details and an example when I do.

===============================================================================

IV. > > > > Quests

===============================================================================


Technically what I call "quests" are called "callgraphs" in Greedfall.

However, having come from Gamebryo dev environments (Skyrim/Fallout 4), I am

more comfortable thinking about these graphs as quests. So for the sake of

conversation, I will call them quests, but you can search/replace quests

with "graphs" if you want.


Quests are the glue that holds the game together. Some quests function the

way you think of when you play the game: They have a visual component in

the game that tracks progress with waypoint markers and stages, etc.. But the

game also has hidden quests that the player never sees. Quests that always

run and manage companions and relationships. Hidden quests might be given a

name like "RomanceManager".


In greedfall, when you enter a map, the map often kicks off a set of

map-specific quests. So for example, when you enter New Serene, the quest:


datalocal/quests/levelsdedicated/lvl_02_south_city_background.sqg


Kicks off. These "master" quest in turn kick off about a dozen other (hidden)

quests. The quests do things like add handlers to statically embedded trigger

zones in the map so that the game knows what to do when you enter the trigger

zone. ANY quest can kick off other quests. So if you want to avoid merge

conflicts with other mods, you can look at these large quest kickoff files and

possibly place your initialization code in a child quest or even grandchild

quest. There is also likely a master quest higher up the food chain that I

haven't discovered yet that causes the lvl_## quests to kick off.


Example:

You walk up to a merchant. When you approach, you enter a trigger zone that

surrounds the merchant. It kicks off a hidden quest that causes the merchant

to shout "Come one, come all, buy my wares!". It might also enable an

interaction icon to appear if you put the mouse cursor over the merchant.

If you keep walking and leave the trigger zone, that same hidden quest will

have handlers set up to remove the interaction icon and make the merchant

stop talking.


Later in the game, you have started a visible quest that wants you to

interrogate the merchant. The visible quest sets a global variable indicating

that the quest is running. When you approach that same merchant, the hidden

quest we mentioned earlier checks to see if that global variable has a

specific value and if so, still presents an interaction icon, but this

interaction is connected to a different dialogue than the normal one. When

you go to interact with the merchant, a cut-scene will now kick off that is

specific to the visible quest. When the cut-scene is over, the hidden quest

that monitors the area around the merchant may actually update that variable.

(or the dialogue might. Just depends).


Meanwhile, an event monitor is watching that variable. When the variable's

value changes, the event monitor notices and kicks off some code to react.

Based on the new variables value, it updates the non-hidden quest's state so

that when you go to your quest log, the fact that you talked to the merchant

is now checked off a list of todo items. At least that is one way to do it.

There are multiple ways to accomplish many tasks.


-------------------------------------------------------------------------------

IV.1 > > > > SQG FILE Contents

-------------------------------------------------------------------------------


Most quest definitions are found under the directory:

datalocal\quests

Quests are defined by .sqg files. You can open these with a plain-text editor

and see the declarative, XML structured data within.

At the top of the SQG file is a questGraph block, which identifies the

contents as a quest graph. This is sometimes followed by an arguments

block which indicate arguments that might be passed into the questGraph

when it is invoked. For example:

<questGraph id="travel_service"> <arguments> <arg name="LevelName" type="STRING" /> <arg name="IsHum" type="BOOL" /> </arguments>

** isHum means "isHumanMale", not "isHuman".


This is then followed by a circuitinfo block:

<circuitInfo stream="..."/>

CircuitInfo is a large block with a URL encoded summary of the contents of

the questBlock. As far as I can tell, the circuitblock likely supports the

silk-engine editor. IE: Provides a quick-to-read way for some fancy game

editor to visualize what the file does. However, changes to the circuitinfo

block do not seem to have any impact on the game. I generally leave it alone.

You don't need to waste time keeping it in sync with the changes you make to

the file.


After circuit info is the <Blocks> tag. This is where the meat of the sqg

file is.

SQG files use Objects to perform things like incrementing a variables,

performing an IF condition, starting a dialogue, etc... The objects are

represented in the SQG as XML blocks.

Here are the basic top level Objects:


Primary Building Blocks:

------------------------


1) startBlock :


<startBlock name="Start" userSpecificedLevelSpecific="false" levelSpecific=""> <connections> <connection pinType="OUTPUT" pinIndex="0" target="lvl_02_camp_manager" targetPinIndex="0" pinName="" targetPinName="Init Spawn Comp" /> </connections> </startBlock>


An entry point. If you "invoke" an sqg with no parameters, by default it

looks for the startBlock named "Start". However, you can have multiple

start blocks, each with different names allowing a single SQG file to have

different entry points.


The start block lists a number of connect tags. connections are a generic,

abstract concepts and what they do and how they behave depends on the

block parent type. In the case of startBlock, connections can be thought

of like newing an object. They point to blocks in the XML file (like

creating an instance of that xml object). In the case of start blocks,

all connections are created in parallel. Back to our merchant example,

you may want one object presenting an interaction icon, but at the same

time, you need another object watching to see if the player leaves the

trigger zone. These tasks can not be done one after the other. They both

need to be happening at the same time.


If you look at lvl_02_south_city_background.sqg, you will see a start

block with an enormous number of connections. All those quests are kicked

off in parallel.



2) pauseBlock :


<pauseBlock name="speakdown oldgovernor lvl02" userSpecificedLevelSpecific="true" levelSpecific="lvl_02_south_city"> <connections> <connection pinType="OUTPUT" pinIndex="0" target="MQ11 SecuriseVIP NOT_ACTIVE ?" targetPinIndex="0" pinName="" targetPinName="" /> </connections> <conditionSet isFalse="false" debugBreak="false" mode="ALL"> <conditions> <interactionTargeted isFalse="false" debugBreak="false" on="NAMED" onParam="Actor_NPC_OldGovernor_InHisHouse" interactionName="speak_down" /> </conditions> </conditionSet> </pauseBlock>


As the name implies, pause blocks wait for a conditionset to evaluate to

true and when it does they follow the connections block.

Pause Blocks are really key to quests. You are unlikely to find many SQG

files without them. Most pause blocks have 1 connection within the

connections block, but like a start block, they can also invoke multiple

connections in parallel when the condition is reached. In our merchant

example, you might have a pauseBlock that waits for the player to enter

the merchants trigger zone. When the condition fires, instead of a

single connection, it has 2 connections. One to show the interaction

icon and another that directs to yet another pause block that waits for

the player to leave the trigger zone. When they leave, the second pause

block may clean up the interaction and ultimately redirect back to the

original pause block, waiting for the player to re-enter the trigger zone

again.

This trigger-zone looped pausing drives much of the game.



3) testBlock :


<testBlock name="IsInDialogue_3" userSpecificedLevelSpecific="false" levelSpecific=""> <connections> <connection pinType="OUTPUT" pinIndex="0" target="TriggerStatic_Merchant_Trader_Talk_1" targetPinIndex="0" pinName="" targetPinName="" /> <connection pinType="OUTPUT" pinIndex="1" target="merchant_trader_01_ambiance" targetPinIndex="1" pinName="" targetPinName="Bye" /> <connection pinType="OUTPUT" pinIndex="1" target="TriggerStatic_Merchant_Trader_Talk_1" targetPinIndex="0" pinName="" targetPinName="" /> </connections> <conditionSet isFalse="false" debugBreak="false" mode="ALL"> <conditions> <IsInDialog isFalse="false" debugBreak="false" dialogFullInPlace="Both" dialogType="All" speakerID="cha_hum_merchant_trader_01" /> </conditions> </conditionSet> </testBlock>


Similar to pauseBlock, however, it doesn't wait for the condition to

evaluate to true. Rather it evaluates a condition then invokes the

connections block with the pinIndex set to the evaluated value of the

condition. Value "0" indicates that the condition was true and value "1"

indicates that the condition was false. So within testBlocks, you will

normally have 2 connections. 1 with a pinIndex of "0" and one with a

pinIndex of "1". If you only have 1 connection, then that thread of

execution bails if the value doesn't match your pinIndex.


4) testSwitchBlock:

<testSwitchBlock name="TestSwitch - V_COM_FightingStyle" userSpecificedLevelSpecific="false" levelSpecific=""> <connections> <connection pinType="OUTPUT" pinIndex="0" target="SHOW CHEST_1" targetPinIndex="0" pinName="Out" targetPinName="" /> <connection pinType="OUTPUT" pinIndex="1" target="SHOW CHEST" targetPinIndex="0" pinName="Out" targetPinName="" /> <connection pinType="OUTPUT" pinIndex="2" target="SHOW CHEST_2" targetPinIndex="0" pinName="Default" targetPinName="" /> </connections> <conditionSets> <conditionSet isFalse="false" debugBreak="false" mode="ALL"> <conditions> <IsVariableInt isFalse="false" debugBreak="false" name="V_COM_FightingStyle" value="2" minValue="INT_MIN" maxValue="INT_MAX" /> </conditions> </conditionSet> <conditionSet isFalse="false" debugBreak="false" mode="ALL"> <conditions> <IsVariableInt isFalse="false" debugBreak="false" name="V_COM_FightingStyle" value="3" minValue="INT_MIN" maxValue="INT_MAX" /> </conditions> </conditionSet> </conditionSets> </testSwitchBlock>


TestSwitchBlock is equivalent to a switch or case statement in other

languages. Instead of a series of many testBlocks, you can have a single

testSwitchBlock that can evaluate many conditions regarding a single

variable at once. The last connection acts as a default if all tests

fail.


5) dialogBlock:


<dialogBlock name="merchant_trader_01_talk" userSpecificedLevelSpecific="true" levelSpecific="lvl_02_south_city" dialogName="merchant_trader_01_talk" isLeftOnFight="false" setCameraControlByNone="false" maxDistance="-1.000000" transitionAtEnd="CUT" teleportPlayerAndWaitCharactersEnabled="true"> <connections> <connection pinType="OUTPUT" pinIndex="0" target="IsInDialogue_3" targetPinIndex="0" pinName="End" targetPinName="" /> <connection pinType="OUTPUT" pinIndex="0" target="TeleportPlayer" targetPinIndex="0" pinName="End" targetPinName="" /> <connection pinType="OUTPUT" pinIndex="1" target="Trade_2" targetPinIndex="0" pinName="LaunchBuy" targetPinName="" /> <connection pinType="OUTPUT" pinIndex="2" target="Trade_1" targetPinIndex="0" pinName="LaunchSell" targetPinName="" /> </connections> <transitionAtStart cutMode="DEFAULT" characterMode="DEFAULT" turningMode="DEFAULT" time="-1.000000" cutsceneName="" forbidTransition="false" /> </dialogBlock>


Dialogue blocks kick off a dialogue that is defined in an external file

(extension sdg). Dialogues have NPCs speak lines, sometimes present

choices, show cutscenes... etc. When a dialogue finishes it returns a

value which maps to a pinIndex and/or a targetPinName. This, in turn,

decides which of the connections to fire. As you can see above, you can

fire more than 1 connection in parallel for a given return type if you

wish.


6) timerPlayTimeBlock:


<timerPlayTimeBlock name="TimerPlayTime_1" userSpecificedLevelSpecific="false" levelSpecific="" delayInSeconds="5.000000" timerName=""> <connections> <connection pinType="OUTPUT" pinIndex="0" target="TriggerStatic_Merchant_Trader_Ambiance" targetPinIndex="0" pinName="" targetPinName="" /> </connections> </timerPlayTimeBlock>


Similar to pause, it waits and then follows the connection. But instead

of a condition, it waits a specific amount of time. Can be used in a

number of ways. For example, you could use one to have an event pause for

a while, but eventually proceed anyway if the player doesn't take action.

Or you could have an interaction that disables itself for a set period of

time before becoming available again (to prevent spam)


7) commandActionBlock:

<commandActionBlock name="Trade_2" userSpecificedLevelSpecific="false" levelSpecific=""> <connections> <connection pinType="OUTPUT" pinIndex="0" target="Timer_2" targetPinIndex="0" pinName="" targetPinName="" /> </connections> <actionSet> <actions> <LaunchTrade traderName="Actor_Merchant_Trader_01" tradeMode="BUY" nextCharName="" nextDialogName="" /> </actions> </actionSet> </commandActionBlock>

commandAction blocks are used to invoke commandActions. These perform

non-quest related functions such as bringing up the Buy\Trade menu,

Travel Menu or increasing core game variables that are not within the

configurable scope of quests. (skill points, attribute points or effecting

the player's inventory with rewards and loot).


Some commandAction examples gathered using `grep -Hr -a3 '<actions>' .`


<AddExtraAttributePoints charName="pla_hero" attribute="AGILITY" points="1" /> <AddItem itemId="item_potion_antidote" count="1" /> <Autosave autosaveType="EXCEPTIONAL" /> <BindSkill skillName="skilltree_item_trap_elem" bindTo="10" /> <ChangeCompanionMode companionId="hum_comp_kurt" mode="NPC" /> <ChangeCompanionMode companionId="hum_comp_kurt" mode="TrueCompanion" /> <ChangeLevel levelName="lvl_100_oph_ghetto" playerStart="StartPlayer_FromExchangeEast" /> <ForceCameraContext context="FORCED_2" /> <ForceDetection detectorIsSelf="false" detectorIsTarget="false" detectorIsNamed="Actor_MQ01_KurtTraining_01" detectorIsMain="false" detectedIsSelf="false" detectedIsTarget="false" detectedIsNamed="" detectedIsMain="true" detectionType="ALIVE_ENEMY" detectionValue="true" /> <giveLootTargeted on="NAMED" onParam="pla_hero" lootId="loot_reput_bridge_01" isVisible="true" /> <GiveReward lootTag="loot_all_equipement" isVisible="false" /> <GiveSkillTreePoint value="2" /> <incrementVariableInt name="V_ACHIEV_HARVESTMONSTERS" value="1" /> <launchActionTargeted on="MAIN" onParam="" actionName="" actionType="TO_NORMAL" /> <launchActionTargeted on="NAMED" onParam="InteractiveObject_KNCave_Door" actionName="cob_naturaldoor_open" actionType="NONE" /> <launchActionTargeted on="NAMED" onParam="Actor_COMP_Siora" actionName="" actionType="TO_NORMAL" /> <LaunchBook bookMenu="QUEST" bookSubMenu="" /> <LaunchBook bookMenu="MAP" bookSubMenu="" /> <LaunchBook bookMenu="INVENTORY" bookSubMenu="" /> <LaunchCommandAction actionId="template1A_warrior_activate" /> <LaunchCraft craftMode="ALCHEMY" nextCharName="" nextDialogName="" /> <LaunchCraft craftMode="FORGE" nextCharName="" nextDialogName="" /> <LaunchCredits /> <launchCrossSkillTargeted way="%var_way%" purpose="%var_skill_purpose%" on="TARGET" /> <LaunchCutscene name="cut_gp_sleep01" relativeTo="cut_chamber_sleep_wp"/> <LaunchDialog startBlockName="Door Locked" dialogName="pla_monologs" /> <launchEffectEventTargeted eventType="TRAP_HIT_ENNEMY" on="SELF" /> <LaunchInfoMessage id="quickinfo_not_enough_gold" /> <LaunchSequence2d sequenceId="fin_negative_continent_ile" /> <launchSkillTargeted purpose="INTERACT_HEAL" on="TARGET" /> <launchSkillTargeted purpose="INTERACT_REVIVE" on="TARGET" /> <LaunchSoundCue soundCue="music_fight_boss_gr2_01_end" /> <LaunchTrade traderName="Actor_Comp_TRADER_Test_01" /> <LaunchTrade traderName="Actor_Serene_Trader_01" tradeMode="SELL" nextCharName="" nextDialogName="" /> <playSoundTargeted on="MAIN" onParam="" soundbank="bk_cut_mq07_swampguardianaltar.pgz" soundname="cut_mq07_swampguardianritual_drum_2_2ch" /> <playSoundTargeted on="NAMED" onParam="InteractiveObject_MQ06_BrokenPot_B01" soundbank="bk_gao_fx_pottery_break.pgz" soundname="gao_fx_pottery_break_01" /> <RequestCraftGui /> <RequestGuiPanel panelID="TEAM_SELECTION"/> <RequestSafeGui /> <RequestTimeJumpGui /> <RequestTravelGui/> <setSquadRespawnImmediateTargeted immediate="true" on="NAMED" onParam="Squad_Spi" /> <SetVariableInt name="V_QM02_Spying_Detected" value="1"/> <setStateValueTargeted on="MAIN" onParam="" type="ALLOW_TARGETABLE" value="FORBID" /> <setStateValueTargeted on="NAMED" onParam="Actor_COMP_Vasco" type="ALLOW_TARGETABLE" value="FORBID" /> <setStateValueTargeted on="NAMED" onParam="Actor_MQ14_Ordeal_BearAlpha" type="STANCE" value="SLEEP" /> <setStateValueTargeted on="NAMED" onParam="Actor_SQGD01_WarehouseGuard_02" type="MAGIC_STASIED_STATE" value="ACTIVATED" /> <UnbindSkill skillName="skilltree_item_potion_life" /> <UnlockAllCamps/> <UnlockCodexEntry entry="CODEX_STORY_FIRSTCONTACT" /> <UnlockSkillTree skillName="skilltree_tec_arc_base" /> <RequestCharacterCreationGui /> (./quests/_test/test_crossobject.sqg/CREATION)


8) callGraphBlock


<callGraphBlock name="launch_blocking_tutorial" userSpecificedLevelSpecific="false" levelSpecific="" graphName="launch_blocking_tutorial"> <connections> <connection pinType="OUTPUT" pinIndex="0" target="SetVariable" targetPinIndex="0" pinName="End" targetPinName="" /> </connections> <arguments> <argument name="GuiTutorialName" value="quickinfo_tuto_travel_camp" /> <argument name="Intro_TimerDuration" value="3" /> <argument name="Out_TimerDuration" value="1" /> </arguments> </callGraphBlock>


callGraphBlocks are used to invoke other SQG files. You mostly see them

used by level loaders. In terms of hitting a start block named something

other than start, then the connection that points to the callGraph would

specify the entry point as a targetPin. IE: notice the "target" parameter

below when it redirects to the callgraph.


<testBlock name="IsInDialogue" userSpecificedLevelSpecific="false" levelSpecific=""> <connections> <connection pinType="OUTPUT" pinIndex="0" target="launch_blocking_tutorial" targetPinIndex="0" pinName="" targetPinName="alternativeStartBlockName" /> </connections> <conditionSet isFalse="false" debugBreak="false" mode="ALL"> <conditions> <IsInDialog isFalse="false" debugBreak="false" dialogFullInPlace="Both" dialogType="All" speakerID="cha_hum_merchant_trader_01" /> </conditions> </conditionSet> </testBlock>

9) setVariableBlock


<setVariableBlock name="SetVariable" userSpecificedLevelSpecific="false" levelSpecific="" variableName="V_Tutorial_Travel" value="1"> <connections> <connection pinType="OUTPUT" pinIndex="0" target="wait for loading" targetPinIndex="0" pinName="" targetPinName="" /> </connections> </setVariableBlock>

Used to set a global variable to a specific value. All Global values have

to be pre-declared. To view the variables, see the directory:

/datalocal/variables/

For example, to see the variables used by the main quest:

/datalocal/variables/variables.mq.sli

10) incrementVariableBlock

<incrementVariableBlock name="incVar" variableName="V_Tutorial_Step" value="1" userSpecificedLevelSpecific="false" levelSpecific=""> <connections> <connection pinType="OUTPUT" pinIndex="0" target="Goal" targetPinIndex="0" pinName="" targetPinName="" /> </connections> </incrementVariableBlock>

Used to increment a global variable by a certain amount. All Global values

have to be pre-declared. To view the variables, see the directory:

/datalocal/variables/

For example, to see the variables used by the main quest:

/datalocal/variables/variables.mq.sli

10) endBlock

<endBlock name="End" userSpecificedLevelSpecific="false" levelSpecific="" />


Used by many scripts to provide a graceful single exit point. You don't

HAVE to have one of these, but if you don't add one and then later realize

there is something you want to do on all the exit points, it may be hard

to track them down (especially in a big file). By explicitly mapping to

an END block, it makes it easier to figure out where all the scripts exit

points are.


11) killBlock


<killBlock name="Kill" userSpecificedLevelSpecific="false" levelSpecific=""> <connections> <connection pinType="OUTPUT" pinIndex="0" target="TriggerStatic_Merchant_Trader_Talk_1" targetPinIndex="0" pinName="" targetPinName="" /> <connection pinType="OUTPUT_KILL" pinIndex="0" target="int_ld_pla_speak" targetPinIndex="0" pinName="" targetPinName="" /> </connections> </killBlock>


The name of killBlock is deceiving. On Unix, there is a command called

"kill" It is used to send a signal to a running process. The signal might

be sleep or pause or interrupt or shutdown. The killBlock is like that

program. The type of signal sent is indicated by the pinType. The most

common two are OUTPUT and OUTPUT_KILL. The first is used to fork execution

to multiple threads, similar to the start block. The second is used to

kill an already running thread. So for example if you have a pause block

and a timer block. If the pause block fires first because a condition is

met, it might kill the timer block. Conversely, if the timer block fires

first because the timeout is reached, it might kill the pause block. So

killBlocks can be used for cleanup, but they also used just as much for

forking execution like start blocks.

Other less common Blocks:

-------------------------


<addToTeamBlock name="hum_comp_constantin" userSpecificedLevelSpecific="false" levelSpecific="" companionTag="hum_comp_constantin"> <autosaveBlock name="Autosave_1" userSpecificedLevelSpecific="false" levelSpecific="" autosaveType="EXCEPTIONAL"> <changeLevelBlock name="lvl_02_south_city" userSpecificedLevelSpecific="false" levelSpecific="" levelName="lvl_02_south_city" playerStart="StartPlayer_Driver" travelWithRover="false" /> <clearTeamBlock name="ClearTeam1" userSpecificedLevelSpecific="false" levelSpecific=""> <equipItemBlock name="equip hel_hum_pla_default" userSpecificedLevelSpecific="true" levelSpecific="lvl_01_continent_city" actorName="pla_hero" itemId="hel_hum_pla_default" loadoutIndex="-1" equip="true" /> <godModeBlock name="GodMode" userSpecificedLevelSpecific="true" levelSpecific="lvl_01_continent_city" actorName="Actor_MQ01_Boss" godMode="true"> <GUIKillQuickInfoBlock name="GUI_KillQuickInfo" userSpecificedLevelSpecific="false" levelSpecific="" killQuickInfoId="quickinfo_tuto_travel_vehicle"> <GUIOpenPanel name="Character Creation_1" userSpecificedLevelSpecific="false" levelSpecific="" panelId="CHARACTER_CREATION"> <killActorBlock name="KillActor" userSpecificedLevelSpecific="true" levelSpecific="lvl_01_continent_city" actorName="Actor_MQ01_Boss" instantKill="true"> <objectVisibilityBlock name="Show - Crate Block Arena Entrance" userSpecificedLevelSpecific="false" levelSpecific="lvl_01_continent_city" objectName="InteractiveObject_BossFight_Block_02" visible="true"> <questLogBlock name="FAIL - FindClothes" userSpecificedLevelSpecific="false" levelSpecific="" questName="MQ01_RaiseAnchor_S_FindClothes" state="FAIL" /> <quickInfoBlock name="QuickInfo_1" userSpecificedLevelSpecific="false" levelSpecific="" quickInfoId="quickinfo_tuto_travel_vehicle" onlyIfNothingDisplayed="false" /> <setAutoFollowedQuest name="SetAutoFollowedQuest_1" userSpecificedLevelSpecific="false" levelSpecific="" questName="MQ01_RaiseAnchor_I" forceFollow="true"> <setCompanionSlotCountBlock name="SetCompanionSlotCount" userSpecificedLevelSpecific="false" levelSpecific="" value="2"> <spawnActorBlock name="Spawn - Nautes_Guards_A" userSpecificedLevelSpecific="true" levelSpecific="lvl_02_south_city" spawnName="Squad_QG_Nautes_Guards_A" spawn="true" reselectSquadVariant="false"> <teleportCharacterBlock name="Teleport - Actor_MQ01_CaptainVasco" userSpecificedLevelSpecific="false" levelSpecific="" actor="Actor_MQ01_CaptainVasco" speakerId="" waypointName="Waypoint_MQ01_VascoBossFight" /> <teleportPlayerBlock name="TeleportPlayer" userSpecificedLevelSpecific="false" levelSpecific="" waypointName="DlgStgPos_Bridge_Trader_Talk" /> <timeJumpBlock name="TimeJump1" userSpecificedLevelSpecific="false" levelSpecific="" targetHour="12" targetDayStage="NONE" jumpedDays="0"> <timePauseBlock name="TimePause IN_1" userSpecificedLevelSpecific="false" levelSpecific="" paused="true">

What happens with Collisions?:

------------------------------

Suppose we have a start block or a kill block that forks to two separate

dialogue blocks and both attempt to start a conversation with the player.


What happens?

Good question. Someone should try this out and let me know. =)

In general, you should avoid collisions, so answering this question

shouldn't be a high priority. However, I SUSPECT what will happen

is one thread will "win" and reserve the player resource and the

other thread will "lose". Given the multi-core nature of the PS3

most likely, the engine uses real threads, so which thread wins

is probably a toss of the coin.

-------------------------------------------------------------------------------

IV.2 > > > > ConditionSets

-------------------------------------------------------------------------------


In the SQG File section, we often saw use of conditionSets. There are

all over the game, so it is worth having a dedicated section.

<conditionSet isFalse="false" debugBreak="false" mode="ALL">

isFalse: [ "true" | "false" ] Allows you to reverse the evaluation of the

result

debugBreak: ??? No idea...

mode: [ "ALL" | "AT_LEAST_ONE" ] ALL = conjunction or ... all conditions

must be true for the set to evaluate to true. AT_LEAST_ONE =

disjuction, which means if any of the conditions is true, the

set evaluates to true.

A conditionSet can also contain another conditionSet, which allows more

complicated conditions. For example:

<conditionSet isFalse="false" debugBreak="false" mode="ALL"> <conditions> <IsVariableInt isFalse="false" debugBreak="false" name="var" value="0" minValue="INT_MIN" maxValue="INT_MAX" /> <conditionSet isFalse="false" debugBreak="false" mode="AT_LEAST_ONE"> <conditions> <IsVariableInt isFalse="false" debugBreak="false" name="varA" value="5" minValue="INT_MIN" maxValue="INT_MAX" /> <IsVariableInt isFalse="false" debugBreak="false" name="varB" value="5" minValue="INT_MIN" maxValue="INT_MAX" /> <IsVariableInt isFalse="false" debugBreak="false" name="varC" value="5" minValue="INT_MIN" maxValue="INT_MAX" /> <IsVariableInt isFalse="false" debugBreak="false" name="varD" value="5" minValue="INT_MIN" maxValue="INT_MAX" /> </conditions> </conditionSet> </conditions> </conditionSet>

Which roughly translates to:

if (var == 0 || (varA == 5 || varB == 5 || varC == 5 || varD == 5))

As for the conditions themselves, there are a number of available checks.

Here are some samples gathered using:

`grep -Har -4 '<conditionSet ' .`

If you need more info about parameter values, just grep the extracted code

base for a specific tag. You will see lots of examples that will expose most

of the variable values like what "on" can point at:

<aliveTargeted value="true" on="SELF" /> <aliveTargeted isFalse="false" debugBreak="false" on="NAMED" onParam="Squad_MQ12B_Traitors_Library" value="false" /> <areTutorialsEnabled isFalse="false" debugBreak="false" /> <attackResult value="HURT_ANY" /> <attributeTargeted isFalse="false" debugBreak="false" on="SELF" onParam="" type="LIFE" value="50" delta="0.01f" comp="&gt;" percent="true" /> <attributeTargeted on="SELF" type="WEAPON_MAINHAND_DAMAGE_ELEM" value="0.25" comp="GREATER_OR_EQUAL" /> <cartridgeIsItemCountTargeted on="SELF" value="1" percent="true" cartridgeItem="item_trap_elem" slot="WEAPON_MAIN"/> <CompareValues isFalse="false" debugBreak="false" leftOperand="@int_CraftLevel" operator="=" rightOperand="3" /> <CompareValues isFalse="false" debugBreak="false" leftOperand="@LockPickingLevel" operator="=" rightOperand="2" /> <containsLootTargeted lootType="PICK" on="SELF" /> <DialogOrCutscenePlaying isFalse="true" debugBreak="false" type="CINEMATIC" /> <DialogOrCutscenePlaying isFalse="true" debugBreak="false" type="FREECAM" /> <DialogOrCutscenePlaying isFalse="true" debugBreak="false" type="AMBIANCE" /> <DialogTalentRandom isFalse="false" debugBreak="false" dialogLevel="2" talent="CHARISMA" /> <difficulty value="EASY" isFalse="true"/> <difficulty isFalse="false" debugBreak="false" value="EXTREM" /> <effectEventTargeted isFalse="false" debugBreak="false" on="MAIN" onParam="" eventTypeDynamic="TUTO_PLA_ATTS_IMPACT" eventType="PASSIVE" /> <effectEventTargeted isFalse="false" debugBreak="false" on="MAIN" onParam="" eventTypeDynamic="" eventType="ADRENALINE_ON_MAX" /> <equipmentTypeTargeted on="SELF" slot="WEAPON_MAIN" value="AXE"/> <equipmentTypeTargeted on="SELF" slot="WEAPON_MAIN" value="CLAYMORE" isFalse="true"/> <equipmentTypeTargeted value="BROADSWORD" on="SELF" slot="WEAPON_MAIN" /> <equipmentTypeTargeted value="FLAMBERGE" on="SELF" slot="WEAPON_MAIN" /> <equipmentTypeTargeted isFalse="false" debugBreak="false" on="MAIN" onParam="" value="WEAPONRING" slot="WEAPON_MAIN" /> <experienceLevelTargeted isFalse="false" debugBreak="false" on="MAIN" onParam="" value="20" comp="&gt;=" /> <experienceLevelTargeted isFalse="false" debugBreak="false" on="MAIN" onParam="" value="10" comp="&lt;=" /> <GUI_isInfoMessageDisplayed isFalse="true" debugBreak="false" infoMessageID="quickinfo_tuto_disguise" /> <GUI_isInfoMessageDisplayed isFalse="false" debugBreak="false" infoMessageID="@GuiTutorialName" /> <GUI_isPanelDisplayed isFalse="false" debugBreak="false" panelId="INGAME_TEAM_SELECTION" dynamicPanelId="" isDisplayed="true" /> <GUI_isPanelDisplayed isFalse="false" debugBreak="false" panelId="ACTIVE_PAUSE" dynamicPanelId="" isDisplayed="false" /> <GUI_isPanelDisplayed isFalse="false" debugBreak="false" panelId="INGAME_TRAVEL_WORLDMAP" dynamicPanelId="" isDisplayed="false" /> <GUI_isPanelDisplayed isFalse="false" debugBreak="false" panelId="CRAFT" dynamicPanelId="" isDisplayed="false" /> <GUI_isPanelDisplayed isFalse="false" debugBreak="false" panelId="TRADE" dynamicPanelId="" isDisplayed="false" /> <hasSkillPlayingTargeted on="SELF" skillBindInput="gun_shoot" isFalse="true" /> <hasSkillPlayingTargeted on="SELF" skillBindInput="block" /> <hasSkillPlayingTargeted on="SELF" skillBindInput="dodge_any" /> <hasSkillPlayingTargeted on="SELF" skillBindInput="att1"/> <hasSkillPlayingTargeted on="SELF" skillBindInput="att2"/> <hasSkillPlayingTargeted on="SELF" skillBindInput="block"/> <INPUT_Command isFalse="false" debugBreak="false" commandId="LOCK_TARGET" dynamicCommandId="" disableCommandCurrentFrame="false" /> <INPUT_Command isFalse="false" debugBreak="false" commandId="ACTIVE_PAUSE_BIND" dynamicCommandId="" disableCommandCurrentFrame="false" /> <INPUT_Command isFalse="false" debugBreak="false" commandId="MOVE_RUN" dynamicCommandId="" disableCommandCurrentFrame="true" /> <insideTriggerTargeted isFalse="false" debugBreak="false" on="MAIN" onParam="" triggerName="ZoneAggro_MQ06_Puzzle_Fail_Mobs" /> <interactionTargeted isFalse="false" debugBreak="false" on="NAMED" onParam="CrossObject_Palace_BigRoom_DoorA" interactionName="quest_event_open_doubledoor_a" /> <isCompanionInTeam isFalse="false" debugBreak="false" companionTag="huf_comp_id_mq24_alchemist" / <isCompanionInTeam isFalse="false" debugBreak="false" companionTag="huf_comp_aphra" /> <isCompanionInTeamTargeted isFalse="false" debugBreak="false" on="SELF" onParam="" /> <isCompanionInTeamTargeted isFalse="true" on="SELF" /> <isCompanionLocked isFalse="true" debugBreak="false" companionTag="hum_comp_petrus" /> <isCompanionRecruited companionTag="%VAR_COMPANION_TAG%" /> <isCurrentLevel isFalse="false" debugBreak="false" value="lvl_01_continent_city" /> <IsCurrentDayTime isFalse="false" debugBreak="false" minHour="21" maxHour="24" /> <IsCurrentDayStage isFalse="false" debugBreak="false" dayStage="NIGHT_ONLY" /> <IsCurrentDayStage isFalse="false" debugBreak="false" dayStage="DARK_NIGHT" /> <IsCurrentDayStage isFalse="false" debugBreak="false" dayStage="NIGHT_SIMPLE" /> <IsCurrentDayStage isFalse="false" debugBreak="false" dayStage="DAY_ONLY" /> <isFightingTargeted isFalse="false" on="SELF" /> <isFightingTargeted isFalse="false" debugBreak="false" on="MAIN" onParam="" squadMode="ALL" /> <isFightingTargeted isFalse="false" debugBreak="false" on="NAMED" onParam="Squad_HarborSecurity_01" squadMode="AT_LEAST_ONE" /> <IsInDialog isFalse="true" debugBreak="false" dialogFullInPlace="Both" dialogType="All" speakerID="cha_hum_bridge_mq24_soldier_ingeneer" /> <isInPlayerCloseRangeTargeted isFalse="false" debugBreak="false" on="SELF" onParam="" /> <isInPlayerCloseRangeTargeted isFalse="false" debugBreak="false" on="SELF" onParam="" /> <isInPlayerInteractionRangeTargeted isFalse="false" debugBreak="false" on="SELF" onParam="" /> <isMapZoneDiscovered zoneID="mainzone" mapID="lvl_02_south_city" value="true" /> <isSpawnedTargeted isFalse="false" debugBreak="false" on="NAMED" onParam="SpawnCharacter_WizardCastle_ExtremistMageLeader" /> <IsVariableInt isFalse="false" debugBreak="false" name="V_MQ08_EscortStatus" value="INT_MAX" minValue="1" maxValue="2" /> <IsVariableGoalValueReached isFalse="false" debugBreak="false" name="V_SQGE02_ObjectiveCounter" /> <itemCount itemId="item_potion_life" min="1" /> <itemEquippedTargeted isFalse="false" debugBreak="false" on="NAMED" onParam="Actor_COMP_Aphra" itemName="bod_hum_ccm_medium_01" slot="NONE" loadoutIndex="-1" questFlag="NONE" /> <MainCharacterSex isFalse="false" debugBreak="false" sex="MALE" /> <MainCharacterSex isFalse="false" debugBreak="false" sex="FEMALE" /> <Never isFalse="false" debugBreak="false" /> <Once isFalse="false" debugBreak="false" value="true" /> <- Note: Evaluates to true on first check only. <poiHasDummy isFalse="false" debugBreak="false" on="SELF" onParam="" dummyName="dum_poi_02" /> <Quest_State isFalse="false" debugBreak="false" quest="MQ08_ScientificExp_S_EscortScientists" state="ACTIVE" /> <raceTargeted isFalse="false" debugBreak="false" on="SELF" onParam="" value="liz" /> <randomChance isFalse="false" debugBreak="false" value="0.3" /> <ReputationStage isFalse="false" debugBreak="false" groupName="R_HUM_COMP_VASCO" stage="NONE" minStage="NICE" maxStage="NONE" /> <ReputationStage isFalse="false" debugBreak="false" groupName="R_FACTIONS_ALLIES_NATIVE" stage="NONE" minStage="NONE" maxStage="NICE" /> <ReputationStage isFalse="false" debugBreak="false" groupName="R_FACTIONS_ALLIES_BRIDGE" stage="NONE" minStage="NICE" maxStage="NONE" /> <ReputationStage isFalse="false" debugBreak="false" groupName="R_FACTIONS_ALLIES_WIZARD" stage="NONE" minStage="FRIENDLY" maxStage="NONE" /> <ReputationStage isFalse="false" debugBreak="false" groupName="R_FACTIONS_ALLIES_DENIER" stage="NONE" minStage="FRIENDLY" maxStage="NONE" /> <secondaryEffectResult value="IS_FROMBEHIND" /> <SkillTreeItemBound isFalse="false" debugBreak="false" idSkillTree="skilltree_item_potion_life" /> <SkillTreeItemBound isFalse="true" debugBreak="false" idSkillTree="skilltree_traps" /> <skillTreeItemUnlockedTargeted on="SELF" idSkillTree="skilltree_grenades_range" isFalse="true" /> <skillTreeItemUnlocked isFalse="false" debugBreak="false" idSkillTree="skilltree_traps" /> <stateValueTargeted type="INERTIA_SPEED" value="RUN" on="SELF" /> <stateValueTargeted type="INERTIA_SPEED" value="SPRINT" on="SELF" /> <stanceTargeted isFalse="false" debugBreak="false" on="NAMED" onParam="Chest_CQSI01_LockedChestDocB" value="OBJECT_LOCKED" /> (Is object locked) <stanceTargeted isFalse="false" debugBreak="false" on="NAMED" onParam="CrossObject_MQ08_JailDoor" value="OBJECT_OPEN" /> (Is object unlocked) <tagTargeted isFalse="false" debugBreak="false" on="SELF" onParam="" value="dee_deer01" /> <targets_poi isFalse="false" debugBreak="false" />


Most conditions just check things. but some conditions are special. They

actually DO something. The main 2 examples of this are:

<interactionTargeted isFalse="false" debugBreak="false" on="MAIN" onParam="" interactionName="int_ld_pla_speak" />

and

<Once isFalse="false" debugBreak="false" value="true" />

1) interactionTargeted

Causes an interaction to appear in front of the player (MAIN) or on an

object (NAMED). If it is an object, the onParam will contain the name

of the object. For example "Actor_Merchant_foo" or "some_chest".

The interactionName points to a pre-defined interaction name.

Interactions are defined in the file:


game_components/game_components_ld/game_components_ld_interactions.sli

For example, "int_ld_pla_speak" puts "Speak" on the screen. But there is

also the interaction "speak_down" that puts an actors name into the

interaction. In order for those interactions to work, they have to be

attached to NAMED objects (like actors). IE: "Speak to Siora".

"Interactions" are tricky because in addition, the quests script needs to

handle what to do if the player chooses the interaction. Sometimes

interactions will have their own conditions and actions associated with

them in their definitions.

2) Once

The once tags magically track if the block has ever been tested before.

First time you hit them, they evaluate to true and they evaluate to false

after that. It is not clear how the game does this. It also isn't clear

if once will evaluate to true a second time if the script is updated.


(IE: You install a mod that replaces the original .sqg file with an

updated version).


===============================================================================

V. > > > > Dialogues (SDG FILE Breakdown)

===============================================================================


Most dialogue definitions are found under the directory:

datalocal\dialog_graphs


You can open SDG Files with a plain-text editor and see the declarative, XML

structured data within.

At the top of a dialogue file, is the dialogueGraph tag identifying the

contents of the file as being a dialogueGraph.

A. dialogGraph

--------------

<dialogGraph id="aphra_meet_constantin" dialogType="Cinematic" stage="cut_stage_default_02" dialogLevelPosition="" playerSideMainSpeaker="&lt;--DEFAULT--&gt;" interlocutorSideMainSpeaker="cha_huf_aphra">




dialogType : [Ambiance | Cinematic | FreeCam ]

stage : [ "" | cut_stage_default_01 |

cut_stage_default_01_camv |

cut_stage_default_01_camv_02 |

cut_stage_default_01_examine |

cut_stage_default_01_governorpalace |

cut_stage_default_01_mainint_leanback |

cut_stage_default_01_mainint_leanelbow |

cut_stage_default_01_mainint_sitarmchair |

cut_stage_default_01_mainint_sitarmchair_constantinpalace |

cut_stage_default_01_mainint_sitarmchair_torsteinpalace |

cut_stage_default_01_mainint_sitstool |

cut_stage_default_01_mainint_sitstool_02 |

cut_stage_default_01_mainint_stand_shop |

cut_stage_default_01_without_comp |

cut_stage_default_02 |

cut_stage_default_02_int_01_side |

cut_stage_default_02_mainint_far |

cut_stage_default_02_mainint_far_02 |

cut_stage_default_03 |

cut_stage_default_04 |

cut_stg_01_sitarmchair_mainint_pla_comp02

]

dialogLevelPosition : ["" |

DlgStgPos_cqpe03_hybrid_woman_askshaman |

DlgStgPos_test_lvl_test_dialog

]

playerSideMainSpeaker : ["&lt;--DEFAULT--&gt;" |

pla_hero |

cha_huf_aphra |

cha_huf_rebelcamp_chief |

cha_huf_sqgd04_denier_lieutnant_c |

cha_hum_donaghvillage_younghumanboy |

cha_hum_mq01_kidnap_guard |

cha_hum_npc_native_clanchief_shaman |

cha_hum_sqau04_native_trader |

cha_hum_sqau05_warrior_spy_01 |

cha_hum_sqgd02_gdd_intermediary |

cha_hum_sqgd03_gdd_customs_officer |

cha_hum_sqgd04_denier_real_anton |

]

interlocutorSideMainSpeaker : ["&lt;--DEFAULT--&gt;" |

cha_huf_donaghvillage_younghybridgirl |

cha_hum_constantin |

cha_hum_kurt |

cha_huf_siora |

cha_hum_vasco |

cha_huf_siora_sister |

cha_hum_cqap02_rogue_01 |

cha_hum_cqku02_soldier_a |

cha_hum_cqku03_traitor_captain |

cha_hum_cqku03_traitor_lieut_a |

cha_hum_cqku03_traitor_lieut_mob_a |

cha_hum_donaghvillage_younghumanboy |

cha_hum_merchant_trader_01 |

cha_hum_mq01_kidnap_guard |

cha_hum_npc_native_travel_guide |

cha_hum_rebelcamp_chief_lieutnant |

cha_hum_sqau04_rebel_squad_leader |

cha_hum_sqau05_extremist_mage_01 |

cha_hum_sqau05_warrior_spy_01 |

cha_hum_sqgd03_gdd_customs_officer |

cha_hum_sqgd04_denier_officer_franz |

cha_hum_sqgd05_denier_captain_ulric

]


How do I figure this stuff out?

-------------------------------

I use cygwin commands to parse and print uniq portions of the data. IE:

$ grep -Hir 'dialogGrap' . | grep -e 'stage="[^"]*"' -o | sort -u | uniq

1) grep -Hir 'dialogGrap' . : Search current and subdirectories for

'dialogGrap' and output the name of the file and the line that the

string is found on.

2) redirect output to another grep that searches for a Regular Expression

within each line that matches an attributeName="..." ([^"] means "is

not a quote) and the * that follows it means the previous element will

appear zero or more times. Not the "-o" option, which means only

output the matching regex, not the whole line.

3) redirect output into a sort. This pushes all the empty lines to the

top of the output.

4) redirect to "uniq"... this scans a sorted list of lines and removes

2 adjacent lines if they are identical, leaving on the unique results.

The dialogueGraph is followed by a circuitInfo block:

B. circuitInfo

--------------


<circuitInfo stream="&lt;?xml version=&quot... "/>

As far as I can tell, the circuitInfo block exists for the silk-engine

tools so they can quickly open the file and deduce what it does without a

full parse. In other words, the contents don't matter and only serve the

game development tools (which we don't have). So you don't need to worry

about the contents of the circuitInfo or keeping it in sync with any

changes you make.

The third element is a Speakers block.

C. speakers

-----------

<speakers> <speaker speakerId="cha_hum_constantin" tag="" nameInGame="" defaultCamera="" talk="" listen="" idle="" mood="&lt;-- DEFAULT --&gt;" stagePos="" animPosture="STAND" animBodyPose="" forceIncludeCompanion="false" allowLipSync="" displayName="true" soundPreset="" /> <speaker speakerId="cha_huf_aphra" tag="" nameInGame="" defaultCamera="" talk="" listen="" idle="" mood="&lt;-- DEFAULT --&gt;" stagePos="way_interlocutor_01" animPosture="STAND" animBodyPose="" forceIncludeCompanion="false" allowLipSync="" displayName="true" soundPreset="" /> <speaker speakerId="pla_hero" tag="" nameInGame="" defaultCamera="" talk="" listen="" idle="" mood="&lt;-- DEFAULT --&gt;" stagePos="way_interlocutor_02" animPosture="STAND" animBodyPose="" forceIncludeCompanion="false" allowLipSync="" displayName="true" soundPreset="" /> <speaker speakerId="cha_huf_siora" tag="" nameInGame="" defaultCamera="" talk="" listen="" idle="" mood="&lt;-- DEFAULT --&gt;" stagePos="way_interlocutor_main" animPosture="STAND" animBodyPose="" forceIncludeCompanion="false" allowLipSync="" displayName="true" soundPreset="" /> <speaker speakerId="cha_hum_vasco" tag="" nameInGame="" defaultCamera="" talk="" listen="" idle="" mood="&lt;-- DEFAULT --&gt;" stagePos="" animPosture="STAND" animBodyPose="" forceIncludeCompanion="false" allowLipSync="" displayName="true" soundPreset="" /> <speaker speakerId="cha_hum_kurt" tag="" nameInGame="" defaultCamera="" talk="" listen="" idle="" mood="&lt;-- DEFAULT --&gt;" stagePos="" animPosture="STAND" animBodyPose="" forceIncludeCompanion="false" allowLipSync="" displayName="true" soundPreset="" /> <speaker speakerId="cha_hum_petrus" tag="" nameInGame="" defaultCamera="" talk="" listen="" idle="" mood="&lt;-- DEFAULT --&gt;" stagePos="" animPosture="STAND" animBodyPose="" forceIncludeCompanion="false" allowLipSync="" displayName="true" soundPreset="" /> </speakers>


This block identifies the POTENTIAL speakers that may partake in the

dialogue. However, not all speakers will necessarily be present.


tag = [ "" |

huf_comp_siora |

huf_npc_denier_sieglinde |

hum_comp_native_highking_shaman |

hum_mq22_camv

]

defaultCamera = [ "" |

american_std_l |

american_std_r

]

talk = [ "" ]

listen = [ "" ]

idle = [ "" ]

mood = [ "&lt;-- DEFAULT --&gt;" |

ANGRY_AGITATED |

ANGRY_RELAXED |

CLEVER_RELAXED |

HAPPY_RELAXED |

IRONIC_RELAXED |

IRRITATED_AGITATED |

IRRITATED_FIXED |

IRRITATED_RELAXED |

LOST_RELAXED |

NEUTRAL_FIXED |

NEUTRAL_RELAXED |

RESOLUTE_RELAXED |

SAD_RELAXED |

SCARED_AGITATED |

SCARED_RELAXED |

SILLY_AGITATED |

SMILING_RELAXED

]

animBodyPose = [ CROSSEDARMS,

HANDSBEHIND,

WOUNDED,

2HANDSHIP,

ELBOWSKNEES,

FIXED

]

animPosture = [ LEANBACK,

LEANELBOW,

SITARMCHAIR,

SITSTOOL,

STAND

]


Here are some of the supported combinations extracted from:

dialogues/autogen_dialogue_animations.sli


animPosture="LEANBACK" animBodyPose="CROSSEDARMS"

animPosture="LEANELBOW" animBodyPose="1HANDHIP"

animPosture="LEANELBOW" animBodyPose="2HANDSHIP"

animPosture="LEANELBOW" animBodyPose="AGITATED"

animPosture="LEANELBOW" animBodyPose="CROSSEDARMS"

animPosture="LEANELBOW" animBodyPose="FIXED"

animPosture="LEANELBOW" animBodyPose="FIXED"

animPosture="LEANELBOW" animBodyPose="FIXED"

animPosture="LEANELBOW" animBodyPose="HANDSBEHIND"

animPosture="LEANELBOW" animBodyPose="HOLDARM"

animPosture="SITARMCHAIR" animBodyPose="CROSSEDARMS"

animPosture="SITARMCHAIR" animBodyPose="ELBOWSKNEES"

animPosture="SITARMCHAIR" animBodyPose="FIXED"

animPosture="SITSTOOL" animBodyPose="CROSSEDARMS"

animPosture="SITSTOOL" animBodyPose="ELBOWSKNEES"

animPosture="STAND" animBodyPose="1HANDHIP"

animPosture="STAND" animBodyPose="2HANDSHIP"

animPosture="STAND" animBodyPose="AGITATED"

animPosture="STAND" animBodyPose="CROSSEDARMS"

animPosture="STAND" animBodyPose="FIXED"

animPosture="STAND" animBodyPose="FIXED"

animPosture="STAND" animBodyPose="FIXED"

animPosture="STAND" animBodyPose="HANDSBEHIND"

animPosture="STAND" animBodyPose="HOLDARM"

animPosture="STAND" animBodyPose="WOUNDED"


D. Blocks

The blocks parent element is where the meat of the dialogue definition is.

Primary tags:

1. start

<start name="Start"> <connections> <connection blockName="May I help you?" inputPinIndex="0" /> </connections> </start>


The start tag defines the entry point. Notice it doesn't have as many

attributes as the start QUEST element. Also notice that the

connection uses the attribute "blockName" to redirect instead of

the Quest versions "target".


There can be multiple start tags in an SDG file. The SQG file can

choose which one to use by using the start name in the targetPinName

of the connection that points to the DialogueBlock that kicks off the

dialogue. For example, to enter the SDG using the "AltStart" name:

(SQG File)


<killBlock name="example" userSpecificedLevelSpecific="false" levelSpecific=""> <connections> <connection pinType="OUTPUT" pinIndex="0" target="dBlock" targetPinName="AltStart" targetPinIndex="0" pinName="" /> </connections> </pauseBlock> <dialogBlock name="dBlock" userSpecificedLevelSpecific="false" levelSpecific="" dialogName="exampleDialogue" isLeftOnFight="false" setCameraControlByNone="false" maxDistance="-1.000000" transitionAtEnd="DEFAULT" teleportPlayerAndWaitCharactersEnabled="true"> <connections> <connection pinType="OUTPUT" pinIndex="1" target="acceptOffer" targetPinIndex="0" pinName="accept" targetPinName="" /> </connections> <transitionAtStart cutMode="DEFAULT" characterMode="DEFAULT" turningMode="DEFAULT" time="-1.000000" cutsceneName="" forbidTransition="false" /> </dialogBlock>

2. propositionBlock

<propositionBlock name="May I help you?"> <connections> <connection blockName="Choice" inputPinIndex="0" /> </connections> <propositions> <proposition locKey="#dlg_talk_lvl02_barman_1" textSample="May I help you?" annotate="" todo="" speaker="cha_hum_lvl02_barman" charTag="" allowLipSynch="&lt;-- DEFAULT --&gt;" speakerTargetPreset="&lt;--DEFAULT--&gt;" speakerTargetCustomName="" talkMood="&lt;-- DEFAULT --&gt;" talkAnim="" listenersGeneralMood="&lt;-- DEFAULT --&gt;" listenAnim="" endingTransition="Normal" soundPreset="" cutscene="" csRelativeTo="" camera="" cameraTarget="" isSkippable="true" /> </propositions> </propositionBlock>

Proposition Blocks are used to make NPCs speak. Each "proposition" has

a speaker tag that identifies the NPC who will be speaking the line.

Typically it alternates between some NPC and the Player:

speaker="pla_hero"

Remember that not every propositionBlock is associated with a

conversation. Sometimes you simply hear people talking as you walk

around. For example, the merchant that shouts "Come one, come all and

see my wares!".


Specific lines can override the global default settings set above in

the <speakers> tag. So if you want someone's mood to change because of

something that was said, you can specify that by providing a local

talkMood value for example.

In propositionBlocks, the lockKey references an audio file defined in

a sound bank associated with the the SDG file. This is ultimately how

the game knows what audio to play and what subtitles to use. More on

that below in the in section V.2

proposition elements can also kick off cutscenes after a spoken line if

one is provided by the cutscene attribute.

3. choiceBlock

<choiceBlock name="Choice"> <connections> <connection blockName="LongWindedTalkAboutPast" inputPinIndex="0" /> <connection blockName="StartAQuest" inputPinIndex="0" /> <connection blockName="HasEnoughGoldForAbeer" inputPinIndex="0" /> </connections> <choices> <choice locKey="#dlg_talk_lvl02_barman_9" textSample="His presence on the island" annotate="" todo="" speaker="pla_hero" charTag="" allowLipSynch="&lt;-- DEFAULT --&gt;" speakerTargetPreset="&lt;--DEFAULT--&gt;" speakerTargetCustomName="" talkMood="&lt;-- DEFAULT --&gt;" talkAnim="" listenersGeneralMood="&lt;-- DEFAULT --&gt;" listenAnim="" endingTransition="Normal" soundPreset="" cutscene="" csRelativeTo="" camera="" cameraTarget="" isSkippable="true"> <conditionSet isFalse="false" debugBreak="false" mode="ALL" /> </choice> <choice locKey="#dlg_talk_lvl02_barman_56" textSample="His business" annotate="" todo="" speaker="pla_hero" charTag="" allowLipSynch="&lt;-- DEFAULT --&gt;" speakerTargetPreset="&lt;--DEFAULT--&gt;" speakerTargetCustomName="" talkMood="&lt;-- DEFAULT --&gt;" talkAnim="" listenersGeneralMood="&lt;-- DEFAULT --&gt;" listenAnim="" endingTransition="Normal" soundPreset="" cutscene="" csRelativeTo="" camera="" cameraTarget="" isSkippable="true"> <conditionSet isFalse="false" debugBreak="false" mode="ALL" /> </choice> <choice locKey="#dlg_talk_lvl02_barman_8" textSample="Buy A Beer" annotate="" todo="" speaker="pla_hero" charTag="" allowLipSynch="&lt;-- DEFAULT --&gt;" speakerTargetPreset="&lt;--DEFAULT--&gt;" speakerTargetCustomName="" talkMood="&lt;-- DEFAULT --&gt;" talkAnim="" listenersGeneralMood="&lt;-- DEFAULT --&gt;" listenAnim="" endingTransition="Normal" soundPreset="" cutscene="" csRelativeTo="" camera="" cameraTarget="" isSkippable="true"> <conditionSet isFalse="false" debugBreak="false" mode="ALL" /> </choice> </choices> </choiceBlock>

Choice blocks present menus on the screen that the player can select.

Each Choice has a conditionset that determines if it appears or not.

The connections at the top correlate to the relative position of the

choices on the bottom.


ChoiceBlocks can connect to secondary ChoiceBlocks to simulate menus.

In a ChoiceBlock, the locKey is a reference to a String defined in a

language localized file:

(datalocal/text/<sdg_file_name>_eng.txt>)

The attributes of the choices are confusing. Greedfall supports

inheritance, and I believe the Choice elements inherit from generic

elements that have attributes like "speaker" and 'allowLipSynch", that

make no sense when you are simply displaying text on the screen. I'm

90% sure you can ignore every attribute aside from locKey and be

absolutely fine.

** NOTES: I have tried altering the text in the .txt files to no avail.

This is true for both dialogues and item descriptions.

I believe the strings were statically compiled into the game. Or

maybe there is a detail I am missing. Maybe with text files, you do

in fact have to place them in the datalocal/text directory. Don't

know. Maybe someone with more patience than me will figure this

out and tell me.

What I HAVE found is that one can inject text directly into the

locKey attribute and it will display. IE:

<choice locKey="Buy A Beer" textSample="" ..... > <conditionSet isFalse="false" debugBreak="false" mode="ALL" /> </choice>

Choice text also supports a number of special sequences for things

like icons in the text. Some examples:

@ICONE(img_misc_love_s)

@ICONE(img_book_talents_alchemy_s)

@ICONE(img_book_talents_craft_s)

@ICONE(img_map_marker_merchant_s)

@NAT<yellowtext>@END <- typically used to hi-light native words


4. test


<test name="HasEnoughGoldForAbeer"> <connections> <connection blockName="BuyBeer" inputPinIndex="0" /> <connection blockName="SorryNotEnough" inputPinIndex="0" /> </connections> <conditionSet isFalse="false" debugBreak="false" mode="ALL"> <conditions> <itemCount isFalse="false" debugBreak="false" itemId="item_gold" value="-1" min="5" max="-1" /> </conditions> </conditionSet> </test>

Similar to the SQG testBlock, but with just a name attribute. SQG

ConditionSets are fully supported.

5. commandAction

<commandAction name="SorryNotEnough"> <connections> <connection blockName="MoveAlong" inputPinIndex="0" /> </connections> <actionSet> <actions> <LaunchInfoMessage id="quickinfo_not_enough_gold" /> </actions> </actionSet> </commandAction>


Similar to the SQG commandActionBlock, but with just a name

attribute. All SQG actions are fully supported.

6. setVariable

<setVariable name="BuyBeer" varName="BuyBeer" value="1"> <connections> <connection blockName="RemoveGold" inputPinIndex="0" /> </connections> </setVariable>


Similar to the SQG setVariableBlock, but with just a name,

varName and value attribute.


7. removeItemBlock

<removeItemBlock name="RemoveGold" itemId="item_gold" count="5"> <connections> <connection blockName="Reward" inputPinIndex="0" /> </connections> </removeItemBlock>


remove an item from the players inventory.


** You can use rewardBlock or CommandAction -> AddItem to

give the player money/items/experience (XP is normally

connected to loot as part of a reward).

8. launchDialog


<launchDialog name="LongWindedTalkAboutPast" dialogName="long_winded_talk"> <connections> <connection blockName="Choice" inputPinIndex="0" /> </connections> </launchDialog>

Yes, a dialogue can redirect to another dialogue. If you need to

redirect to a specific start tag on another dialogue, consider the

commandAction LaunchDialog, as it supports an entry point parameter.


9. rewardBlock

<rewardBlock name="Reward" lootId="loot_barman_alcool" visible="true"> <connections> <connection blockName="LongWindedTalkAboutPast" inputPinIndex="0" /> </connections> </rewardBlock>


Loot definitions can be found under:

/datalocal/loots

Examples:

- loot_all_recipes

- loot_all_equipement

- loot_all_uniques

- loot_all_natives

- loot_25000_xp

- loot_default_ingredients

See: VI.5 Loot and Rewards

10 incrementVariable

<incrementVariable name="IncVar" varName="V_ROM_VASCO_SeductionPt" value="1"> <connections> <connection blockName="SetVariable3" inputPinIndex="0" /> </connections> </incrementVariable>


11 end

<end name="accept love" />

Similar to the SQG endBlock, but with just a name. Note that the Name

of the chosen end tag will often determine what the dialogBlock does

when it returns to the SQG file. IE: The connection with the

matching pinName will be followed when the thread returns to the

quest handler.


As you can see there are a lot of similarities with Quests in terms of

what dialogues can do. However there are some block types that ONLY exist

in dialogues.

12 testCompanion

<testCompanion name="TestCompanion"> <connections> <connection blockName="vascoSaySomething" inputPinIndex="0" /> <connection blockName="kurtSaySomething" inputPinIndex="0" /> <connection blockName="" inputPinIndex="0" /> <connection blockName="" inputPinIndex="0" /> <connection blockName="petrusSaySomething" inputPinIndex="0" /> <connection blockName="constantineSaySomething" inputPinIndex="0" /> <connection blockName="SioraAndAphraAreCompanions" inputPinIndex="0" /> </connections> <tagOrder> <companion characterTag="hum_comp_vasco" /> <companion characterTag="hum_comp_kurt" /> <companion characterTag="huf_comp_siora" /> <companion characterTag="huf_comp_aphra" /> <companion characterTag="hum_comp_petrus" /> <companion characterTag="hum_comp_constantin" /> <companion characterTag="__NONE__" /> </tagOrder> </testCompanion>


Test companion tests for the presence of tags in your current

companions using the order provided and when it has a hit, fires the

corresponding connection block unless the block is empty (""), in which

case it keeps going.

Because it is "first found speaks", order is important. It also

provides an easy way to detect if 2 companions are not with the player

thanks to the final "__NONE__" tag, which is only reached if no one

else speaks.


** Behavior of this element needs confirmation.

13 addReputationBlock

<addReputationBlock name="NAUTES_LIKEY" reputationName="R_FACTIONS_ALLIES_NAUTE" value="5"> <connections> <connection blockName="foundSailor" inputPinIndex="0" /> </connections> </addReputationBlock>


reputationName : [

R_FACTIONS_ALLIES_BRIDGE |

R_FACTIONS_ALLIES_DENIER |

R_FACTIONS_ALLIES_MERCHANT |

R_FACTIONS_ALLIES_NATIVE |

R_FACTIONS_ALLIES_NAUTE |

R_FACTIONS_ALLIES_WIZARD |

R_HUF_COMP_APHRA |

R_HUF_COMP_SIORA |

R_HUM_COMP_KURT |

R_HUM_COMP_PETRUS |

R_HUM_COMP_VASCO

]

Adjust the reputation with people or groups. Use negative values to

decrease reputation.

14 fadeInOut


<fadeInOut name="inAndOut" inLength="0.100000" stayLength="0.500000" outLength="0.500000" waitPreviousTextBlockEnd="false" waitFadeOutEnd="false"> <connections> <connection blockName="some_cutscene" inputPinIndex="0" /> </connections> </fadeInOut>

The fadeInOut element can be used to visually simulate the passing of

time or can be used to lead into cutscenes to make the transitions

smoother.

15 addToTeamBlock

<addToTeamBlock name="huf_comp_siora" companionTag="huf_comp_siora"> <connections> <connection blockName="TeamManagementGUI" inputPinIndex="0" /> </connections> </addToTeamBlock>

addToTeamBlock is normally used with setCompanionLockBlock and

teamManagementGUIBlock, to allow you to start a quest that requires a

specific companion. IE: Bring up the gui with the companion added (in

case they were not following you when you spoke to them), a lock next

to their name and force you to get rid of someone if necessary.


16 teamManagementGUIBlock

<teamManagementGUIBlock name="TeamManagementGUI" forceShow="true" confirm="true"> <connections> <connection blockName="SetCompanionLock" inputPinIndex="0" /> </connections> </teamManagementGUIBlock>

I know this brings up the team management gui, but I don't know much

else about it. IE: What happens if confirm is false?

17 setCompanionLockBlock

<setCompanionLockBlock name="SetCompanionLock" companionTag="huf_comp_siora" locked="true"> <connections> <connection blockName="some_siora_required_quest_canstart" inputPinIndex="0" /> </connections> </setCompanionLockBlock>

Can be used to set a companion as required... or unset them as required

by using locked="false".

18 questLog

<questLog name="PayServant-ACTIVE" questName="SQNA01_FamilyAffair_S_PayServant" state="ACTIVE"> <connections> <connection blockName="if enough gold" inputPinIndex="0" /> </connections> </questLog>

Use this element to set state of quests.

state : [ ACTIVE | FAIL | INACTIVE | NONE | SUCCESS ]

19 random

<random name="Random" forceCycleThroughOutputs="true"> <connections> <connection blockName="proposition1" inputPinIndex="0" /> <connection blockName="proposition2" inputPinIndex="0" /> </connections> <randomOutputs> <randomOutput weight="50" /> <randomOutput weight="50" /> </randomOutputs> </random>

An way to have entire dialogue branches random. Don't forget that

conditionSets that can be attached to many things also support the

randomChance element.

Adding a Menu

--------------


If a the blockName of a Choice block connection links to a launchDialog

block, this will cause an arrow to appear in the choice menu, indicating

a submenu.

Below the user would see:

[ Menu1 > ]

[ Menu2 > ]

[ Close ]

Example:

<choiceBlock name="Choice"> <connections> <connection blockName="menu1" inputPinIndex="0" /> <connection blockName="menu2" inputPinIndex="0" /> <connection blockName="close" inputPinIndex="0" /> </connections> <choices> <choice locKey="Menu1" textSample="" annotate="" todo="" speaker="pla_hero" charTag="" allowLipSynch="&lt;-- DEFAULT --&gt;" speakerTargetPreset="&lt;--DEFAULT--&gt;" speakerTargetCustomName="" talkMood="&lt;-- DEFAULT --&gt;" talkAnim="" listenersGeneralMood="&lt;-- DEFAULT --&gt;" listenAnim="" endingTransition="Normal" soundPreset="" cutscene="" csRelativeTo="" camera="" cameraTarget="" isSkippable="true"> <conditionSet isFalse="false" debugBreak="false" mode="ALL"/> </choice> <choice locKey="Menu2" textSample="" annotate="" todo="" speaker="pla_hero" charTag="" allowLipSynch="&lt;-- DEFAULT --&gt;" speakerTargetPreset="&lt;--DEFAULT--&gt;" speakerTargetCustomName="" talkMood="&lt;-- DEFAULT --&gt;" talkAnim="" listenersGeneralMood="&lt;-- DEFAULT --&gt;" listenAnim="" endingTransition="Normal" soundPreset="" cutscene="" csRelativeTo="" camera="" cameraTarget="" isSkippable="true"> <conditionSet isFalse="false" debugBreak="false" mode="ALL"/> </choice> <choice locKey="Close" textSample="" annotate="" todo="" speaker="pla_hero" charTag="" allowLipSynch="&lt;-- DEFAULT --&gt;" speakerTargetPreset="&lt;--DEFAULT--&gt;" speakerTargetCustomName="" talkMood="&lt;-- DEFAULT --&gt;" talkAnim="" listenersGeneralMood="&lt;-- DEFAULT --&gt;" listenAnim="" endingTransition="Normal" soundPreset="" cutscene="" csRelativeTo="" camera="" cameraTarget="" isSkippable="true"> <conditionSet isFalse="false" debugBreak="false" mode="ALL"/> </choice> </choices> </choiceBlock> <launchDialog name="menu1" dialogName="aphra_romance"> <connections> <connection blockName="BridgeStart1" inputPinIndex="0" /> </connections> </launchDialog> <bridgeStart name="BridgeStart1"> <connections> <connection blockName="BridgeEnd" inputPinIndex="0" /> </connections> </bridgeStart> <bridgeEnd name="BridgeEnd"> <connections> <connection blockName="Choice" inputPinIndex="0" /> </connections> </bridgeEnd> <end name="End" />

I'm not 100% certain what the bridgeStart/Ends do (They are used by most

talk based menus), but I suspect they are somehow responsible for menu

condensing. IE: When a sub-menu only has 1 choice that would display,

the parent menu just displays that choice instead of a menu. And when a

submenu has nothing to display, the menu goes away all together. There are

plenty of examples of menus in the game if you look. For example, check out:

dialog_graphs/talk_companions/kurt_talk.sdg

Other less common Blocks:

-------------------------

Most of these are rare because they normally aren't handled from dialogue,

but rather their parent quests.


<removeFromTeamBlock name="Remove huf_comp_siora FromTeam" companionTag="huf_comp_siora"> <setCompanionRecruitedBlock name="Set huf_comp_siora UNrecruited" companionTag="huf_comp_siora" recruited="false"> <setReputationStageBlock name="SetReputationStage" reputationName="R_HUM_COMP_KURT" stage="LOVING"> <timeJumpBlock name="TimeJump" targetHour="21.500000" targetDayStage="NONE" jumpedDays="0"> <timeJumpRelativeBlock name="TimeJumpRelative" jumpedHours="1.000000" jumpedDays="0">


Location Location Location:

---------------------------

If a dialogue only involves two speakers, then things are simple from a

camera perspective. The game has cameras set up to handle two speaker

interactions with the player without anything special.


However... If you want more than two speakers, it gets tricky. The camera

likes to rotate between the speakers. If one of the speakers isn't around.

the camera may suddenly point at a wall or at the floor.


This isn't an issue for most dialogues as most are limited to the player

and someone else. However... what if you need a companion to speak up who

may or may not be traveling with the player? This often happens in the

game when reporting to quest givers like constantine.


In this case (need someone to speak up who might not be around), you can

specify a waypoint marker to teleport a speaker to when the dialogue

begins. Most of these markers are statically embedded into the maps.

Which means conversations that involve interjections from companions who

are not traveling with you normally happen in throne rooms and around

campfires.


The attribute to specify a waypoint is called stagePos and there are a

number of common values:


stagePos="way_player"

stagePos="way_interlocutor_main"

stagePos="way_interlocutor_01"

stagePos="way_interlocutor_02"

stagePos="way_interlocutor_03"

stagePos="way_companion_01"

stagePos="way_companion_02"

stagePos="way_companion_03"

stagePos="way_companion_04"

stagePos="way_companion_05"

stagePos="way_companion_06"

stagePos="way_hide"


===============================================================================

VI. > > > > Audio

===============================================================================


-------------------------------------------------------------------------------

VI.1 > > > > Spoken Audio

-------------------------------------------------------------------------------


A. Where is it?

Spoken audio lines can be found under:

datalocal/voice_dialog/<CHARACTER>/*.pgz

The PGZ Files are gzipped PSSG files. (See above).

B. Can I extract everything to listen to it?

Yes, though it takes a few steps and even these directions only tackle

one NPC at a time.

1) Goto the directory of an NPC who's lines you wish to extract

ie: .../datalocal/voice_dialog/pla_hero

2) Right click the background and select New -> Folder. Name

it "extracted"

3) Select all the PGZ files (Click on first, then scroll to bottom

and SHIFT + CLICK last file).

4) Right click the selected files and select: 7-Zip -> Extract files...

5) Tell 7-zip to extract the files in your new "extracted" directory.

7-zip will create a sub-directory for each extarcted .pgz file.


6) Place a copy of pssgOggExtract.py in the /extracted directory

7) Startup CYGWIN. In the terminal window, cd to the extracted

directory. The command will look something like:

$ cd /cygdrive/c/Steam/steamapps/common/datalocal/voice_dialog

$ cd <CHARACTER>/extracted

8) Use CYGWIN to exectute pssgOggExtract on all files. IE:

$ for f in **/* ; do python pssgOggExtract.py $f; done

C. How does it connect to dialogues?

Connecting spoken audio lines to game dialogues requires 2 files.


1) Every SDG (dialogue) file has a BANK file and a BANK COFIG filename

that indicate what audio lines to load when the dialogue is invoked.

If you simply try to paste a line between 2 separate SDG files, it

won'r work because at a minimum, the BANK file must also be updated.

BANK files are located under:

.../datalocal/banks/

And BANK CONFIG files are located under:

.../datalocal/sounds/banks/voices/voice_dialog/eng/

Note that the BANK directory has bank files for both SDG files

and other resources like background music and ambient noises.

As far as I can tell, the game engine uses file name conventions

to search for associated files. So if the SDG file was named:

aphra_romance.sdg

Then the game would looke for the BANK CONFIG:

bk_voc_eng_aphra_romance.sli

under the directory

.../datalocal/sounds/banks/voices/voice_dialog/eng/


At the top of the BANK CONFIG, it indicates the name of the bank

file to use. It also summarizes the contents, however changes to the

summary do not seem to have any impact on the game.


Technically this means that BANK files could be named anything, but

in practice, they all follow a convention. I tend to just ignore

the sli files.


The actual BANK file associated with aphra_romance.sdg is:

banks/bk_voc_aphra_romance.pgz

If you use 7-zip to decompress this file, and then use EGO PSSG

EDITOR to open in, you will see the IDs of the audio data as

well as metadata such as the length of the audio file. Note the

bank simply helps the engine know when to load the audio data

into memory. It doesn't actually have any audio in it, just

pointers to a list of files to load.

Finally with a bank in place, the "proposition" elements of

the SDG files can now make use of those ids in the locRefs

attribute.

So in summary: A quest kicks off an SDG file. The game engine

loads the config, then the bank and finally all the audio lines

identified by the bank. Then when the SDG proposition elements

make use of the audiofile ids, the audio is played.

The NPC who speaks the audio is identified by the proposition

elements 'speaker' tag.


D. Editing/Updating a BANK


1) Find the bank name (.pgz)

2) Extract it using 7-zip

3) Convert it to XML using the PSSG Editor:

- highlight top-most node and select Node -> Export to XML


4) Edit the XML. If you are re-using/exposing lines from other

conversations, extract the other conversation to XML as well

and copy the corresponding pieces across. Note there are two

parts needed for each line you wish to re-use: A one line

AUDIODATAPROXY element in the top and a 4 line SOUNDBANKITEM

Element at the bottom. Order should match up. Don't forget to

update the TYPEINFO/typeCount info for the AUDIODATAPROXY and

SOUNDBANKITEM at the top or the game might crash when it loads

the bank.

5) Use PSSG Editor to create a "New" file, highlight the top most node and

import your XML. Update the spoken line counts if you didn't

already do so. And then Export to PSSG

6) Remove the .pssg extension, then convert the PSSG to .pgz using 7-zip

with gzip archive compression (rename extension .pgz)


7) Place the updated .pgz into the datalocal.

** NOTES:

- If creating a new dialogue, you will need to create a

bk_voc_NAME_eng.sli file and copy the file:

sound_library_voices_eng.sli

From the directory:

/datalocal/sounds/banks/voices/voice_dialog/eng/

Additional updates may also be necessary (still investigating)


- If using EGO PSSG EDITOR, you may find that converting the XML

back to PSSG causes the game to crash on save game load. I'm not

sure what causes it, but there are two work arounds.

First work around is to NOT compress the XML back to PSSG. IE:

remove the .xml extention and compress the file into a .psg.

Surprisingly, the game still loads it. At least for banks. EGO's

schema is accurate enough that the game will accept the non-binary

XML.


Second option is to use pssgExtractor to extract and compile the

file. When I run into walls with EGO, I can normally get around

them using the script. For purists who want everything to be true

binary PSSG, this is the way to go.

E. How are the Phonemes mapped?


The lip movement information (phonemes) are pulled from the audio lines

.pgz file. (phoneme information and timing are embedded into the audio

wrapper). If you use EGO PSSG EDITOR to convert the PSSG wrapper to

XML, you will see a section at the top that provides the phoneme mappings:

Example:

<LIBRARY type="LIPSYNC_SENTENCE"> <LIPSYNC_SENTENCE langId="EN" baseText="I'm the one" bestPass="-1" id="AnimLipSyncSentence"> <LIPSYNC_PASS startTime="0" endTime="3.529999" soundRangeRatio="0.9822545" recognitionRatio="0.7361887" info="FULLTEXT|STRICT" recoGrammar="FULLTEXT" recoMethod="STRICT"> <LIPSYNC_WORD startTime="0.0" endTime="0.270000" recognition="0.9233977"> <LIPSYNC_PHONEME id="ay"/> <LIPSYNC_PHONEME id="m"/> </LIPSYNC_WORD> <LIPSYNC_WORD startTime="0.2700000" endTime="0.400000" recognition="0.5397803"> <LIPSYNC_PHONEME id="dh"/> <LIPSYNC_PHONEME id="ax"/> </LIPSYNC_WORD> <LIPSYNC_WORD startTime="0.400000" endTime="0.6200000" recognition="0.9162213"> <LIPSYNC_PHONEME id="w"/> <LIPSYNC_PHONEME id="ah"/> <LIPSYNC_PHONEME id="n"/> </LIPSYNC_WORD> </LIPSYNC_PASS> </LIPSYNC_SENTENCE> </LIBRARY>

F. Can I get a list of everything someone says?


Yes, though it takes a few steps and even these directions only tackle

one NPC at a time.

1) Goto the directory of an NPC who's lines you wish to extract

ie: .../datalocal/voice_dialog/pla_hero

2) Right click the background and select New -> Folder. Name

it "extracted" (If it doesn't already exist)

3) Select all the PGZ files (Click on first, then scroll to bottom

and SHIFT + CLICK last file).

4) Right click the selected files and select: 7-Zip -> Extract files...

5) Tell 7-zip to extract the files in your new "extracted" directory.

7-zip will create a sub-directory for each extarcted .pgz file.


6) Place a copy of pssgExtractor.py in the /extracted directory

7) Startup CYGWIN. In the terminal window, cd to the extracted

directory. The command will look something like:

$ cd /cygdrive/c/Steam/steamapps/common/datalocal/voice_dialog

$ cd <CHARACTER>/extracted

8) Use CYGWIN to exectute pssgOggExtract on all files. IE:

$ for f in **/* ; do python pssgExtractor.py $f; done

9) Wait... (it takes 20 to 30 minutes for the pla_hero dir)

Note: If you already extracted the ogg data, you will see errors

on the .ogg files, but you can ignore them.

10) Use grep to find the line from each XML file that describes the text,

then pipe the output into sed to format and redirect to file:

$ grep -Hir -e 'baseText=' . | sed -n -e 's/^\(\.\/\)\([^\/]*\)\(.*langId="EN" \)\(baseText="[^"]*"\)\(.*\)/\4 : \2/p' > lines.txt

Explanation:

grep : recurse through all subdirectories and print filename + line

if the string "baseText" is found.

| : redirect output to the next program listed

sed : Use regex to break the line up into groups and print the

4th and 2nd group (so you know which file to find the line in)

> : redirect the output to the file lines.txt


The above command will generate lines.txt which you can open up

and see all the spoken lines and what files to find them in. It

doesn't convey tone or pacing, but it is a good start when trying

to create new conversations as it allows you to search for key

terms.


-------------------------------------------------------------------------------

VI.2 > > > > Music

-------------------------------------------------------------------------------

A. Where is it?


Music files are located under:

.../datalocal/music/*


The PGZ Files are gzipped PSSG files. (See above).

B. Can I extract everything to listen to it?

Yes, though it takes a few steps and even these directions only tackle

one NPC at a time.

1) Goto the directory where the music is stored

ie: .../datalocal/music

2) Right click the background and select New -> Folder. Name

it "extracted"

3) Select all the PGZ files (Click on first, then scroll to bottom

and SHIFT + CLICK last file).

4) Right click the selected files and select: 7-Zip -> Extract files...

5) Tell 7-zip to extract the files in your new "extracted" directory.

7-zip will create a sub-directory for each extarcted .pgz file.


6) Place a copy of pssgOggExtract.py in the /extracted directory

7) Startup CYGWIN. In the terminal window, cd to the extracted

directory. The command will look something like:

$ cd /cygdrive/c/Steam/steamapps/common/datalocal/music/extracted

8) Use CYGWIN to exectute pssgOggExtract on all files. IE:

$ for f in **/* ; do python pssgOggExtract.py $f; done

C. How is music connected to the game?


There are three files involved in connecting music to the game, or

put a different way, letting the game know when to load them into

memory.

1) SOUND LIBRARY:

.../datalocal/sounds/sound_library.sli


Identifies all the Sound and Music BANK CONFIGURATION files to

load.

2) BANK CONFIG:

various SLI Bank config files are located under:

.../datalocal/sounds/banks/music/

At the top of these text based files, the name of a GZIPPED

PSSG file is identified with most of the bank data.

3) BANK FILES:


various PGZ files are located under:

.../datalocal/banks/

This directory contains all bank files including the ones

for vocal and amient noises.

While the system above allows files to have any name they want,

the game as a whole requires all files to have a unique name.

Therefore, the developers used some basic conventions when

creating things to ensure they didn't accidently use a name

that was already in use.

If the music PGZ file is named:

music_cut_mq01_intro_part2.pgz

Then the BANK for the file will be named:

bk_music_cut_mq01_intro_part2.sli


and


bk_music_cut_mq01_intro_part2.pgz

If you are updating existing content, you can generally ignore

the BANK SLI files. You only need to include them if you are

introducing new content. In which case you also need to include

sound_library.sli to ensure the games loads your new bank.


D. Cues


If you read the vocal section above, then you learned that vocal files

are triggered by proposition elements in SDG files. Music is triggered

by Cues.


A master music configuration file is found at:


.../datalocal/sounds/music_library.sli

This defines a number of additional sli files to load when the player

enters specific levels:

<include>music_library_lvl_00_travel.sli</include>

<include>music_library_lvl_01_continent_city.sli</include>

<include>music_library_lvl_02_south_city.sli</include>

<include>music_library_lvl_03_dungeon_crypt.sli</include>

...


If you open up the music configuration file for a level you will

find the declaration of events and the music to play when those

events occur:

.../datalocal/sounds/music_library/music_library_lvl_02_south_city.sli

<level name="lvl_02_south_city.sle">
<levelStartCues> <cue> <conditionSet> <conditions> <insideTriggerTargeted on="MAIN" triggerName="TriggerStatic_music_palace_02"/> </conditions> </conditionSet> <delay randomDelayMin="3" randomDelayMax="3" stopCurrentMusic="true"/> <transition mode="FADE" fadeOutTime="4"/> <musicActions> <set track="2"> <randomize> <music bank="bk_music_explo_palace_02_01_loop.pgz" sound="music_explo_palace_02_01_loop" weight="1"/> </randomize> </set> <play track="2"/> </musicActions> </cue> <include>music_library_default_cues_level_start.sli</include> </levelStartCues>

Above, the game is configured to play the music_explo_palace_02_01_

loop from the bk_music_explo_palace_02_01_loop BANK when the player

enters the trigger TriggerStatic_music_palace_02

There are <genericCueSets> wich map the various tracks to play when

the player enters various trigger zones.

There are <cutsceneCueSets> which indicate what music to play when

specific cutscenes are playing.

There are <dialogCueSets> which indicate music to play when certain

dialogue IDs are encountered.

There are <squadCueSets> which indicate what fight music to play

based on the squad that an enemy belongs to.

Finally, music can also be triggered from SQG and SDG files using

the commandAction:


<LaunchSoundCue/>

Note that the LaunchSoundCue can't just play any music it wants

at any time. A Cue must be defined and configured for the loaded

area.

E. What are these Markers in the Music Wrappers?

The markers section is a sequence of UINT32 numbers. (every 4 HEX =

1 UINT32). Each consecutive value is larger than the previous. These

mark the sample offset at each half second. The number of markers

thus equals the length of the song in seconds times 2 and the distance.


The values could technically be different based on the audio

characteristics. However, all the MUSIC audio I have seen is

recorded at 44100 Hz. This means there are 44100 samples per

second. So a half second means there will be a 22050 sample

difference per marker.

The good news is, since all the audio is 44100 hz, the markers

sections all pretty much have the same values. The only real

difference is the number of markers and the last value which

equates to the total samples.

So if you need to create a markers section, just find a music

file that is bigger than yours. Then steal its markers and

shave off the values you don't need. (and update the last marker

to the total sample count). To get the total sample count,

I open the file in Audacity and change the view at the bottom

to samples. Then select all with CTRL + A.

As far as I can tell, the purpose of the markers is primarily

for Labels. If you open up a music file that ends with Loop

for example, you will see some labels after the markers. They

have a name like "LoopStart" and "LoopEnd" and a small value

which is the marker offset. This means you can have an audio

file that starts off with an intro but then goes into a loop.

If you were in audacity and wanted to create a Label for

a track name, you would just change the length view to sample

and divide the samples at a particular location by 22050 to

get the closest marker.


Possibly the best news is that markers are optional. You only

need them if you have looping audio or want to be able to

enter the audio at a specific point. (For example, there are

common label names for fight music to indicate the outro,

etc..) But you can always point a cue at track 1 of an audio

file and it will just play if there are no markers. (I have

had the game crash when using track 0, so I avoid track 0).

Finally... the markers are hints, and they are basically unused

unless you have a Label. Things to avoid:


- Don't let the last marker value be larger than the number of

samples in the audio.

- Don't make a Label that points to a marker index that doesn't

exist. Avoid those two things and you should be fine.

F. Subtitles:


Even though the pssg files that wrap vocal audio have the text line in

them, subtitles are controlled by separate files located under:


datalocal/text/dlg_.....txt (name of file matches the .sdg file).


I suspect that is because the audio probably wasn't recorded for every

supported language. So you can't assume the audio text will always be

there.

These text files are not ASCII based, but USC-2 LE BOM, which means you

need a compatible reader like Notepad++. It also means that grep wont find

text within.

Unfortunately, I have not yet been able to get the game to recognize

changes to these files. So for the time being, there is no way to

update the subtitles. If you re-use existing lines in whole (just add

existing IDs to banks and use them in dialogue), it isn't an issue as

the ID will still cause the subtitle to appear. But if you create new

lines or edit existing lines, there is no way to update the subtitle

text at the moment. Hopefully I will figure this out with some more

investigation.

-------------------------------------------------------------------------------

VI.3 > > > > Sounds/FX

-------------------------------------------------------------------------------

<< TODO >>


-------------------------------------------------------------------------------

VI.4 > > > > Adding/Replacing Audio

-------------------------------------------------------------------------------

A. Music


Before we start, know that the EGO PSSG Editor sometimes corrupts the

headers of files. I believe it occurs when it runs into unknown/new

attributes. In that case, it sometimes just silently ignores the value.

But often times those values are important. In the case of music files,

it can cause the audio to not play.

An easy test is to use it to convert a PSSG file to XML and then convert

it back with no changes. If the output is a different size than the

original, then EGO PSSG Editor is missing data.

I advise users to use pssgExtractor.py, at least with music files.

If you want to use EGO PSSG EDITOR, then make sure to do a test with

each file you use it on to make sure there are no missed schema elements


--------

Replace:

--------

We will start with a replacement example as that is much easier.

1) copy the following file from datalocal/ (where ever you have extracted

all the resources) to GreedFall/datalocal/ (game mods dir)

datalocal/music/music_cut_mq01_intro_part2.pgz

2) Extract the PGZ file with 7-zip to datalocal and then delete it.

3) Extract the PSSG file with pssgExtractor.py:

- Copy pssgExtractor.py to the GreedFall/datalocal using File Explorer

- Dbl-Click the Cygwin64 Terminal icon. Then in the terminal window

change directory to the games datalocal dir:

$ cd /cygdrive/c/Steam/steamapps/common/GreedFall/datalocal

- Convert the pssg file to xml using pssgExtractor.py

(IMPORTANT: Don't use EGO PSSG Editor for this)

$ python pssgExtractor.py music_cut_mq01_intro_part2

4) Use Notepad++ to edit music_cut_mq01_intro_part2.xml

- Remove the Markers.

If you haven't read it, check out the section above:

"What are these Markers in the Music Wrappers" above.

Even if you want Markers and Labels, you can come back and add them

later. For now, just remove them.

Change:

<LIBRARY type="AUDIODATA"> <OGGDATA binaryObjRef="#music_cut_mq01_intro_part2.ogg" markerCount="173" id="music_cut_mq01_intro_part2_audioData"/> <MARKERS> 00 00 00 00 00 00 56 22 00 .. .. .. ... ... </MARKERS> </OGGDATA> </LIBRARY>


To:

<LIBRARY type="AUDIODATA"> <OGGDATA binaryObjRef="#music_cut_mq01_intro_part2.ogg" markerCount="0" id="music_cut_mq01_intro_part2_audioData"/> </LIBRARY>


- Remove the Binary (Hex) data:

Go to the bottom and remove all the HEX between the <BINARYDATA>

tags. IE: Place cursor before first HEX digit, scroll to the bottom

and SHIFT+CLICK after the last HEX digit. Then hit Delete

The bottom should look like:

<BINARYDATA> </BINARYDATA> </BINARYOBJECT> </LIBRARY> </PSSGDATABASE> </PSSGFILE>

- Save the file.

5) Create your ogg file in the datalocal directory.


I'm not sure what compression limits/file sizes the engine can handle,

but it is probably best to use 44100 Hz sample rate as that is what all

the existing music uses. In my example, I used audacity to export the

file to OGG and in the options during the save process, I selected

quality level 6 and it worked fine.


6) Copy Ogg data into music_cut_mq01_intro_part2.xml:

Open the ogg file with HxD. Hit CTRL + A to select all

then select: File -> Export -> 'c'

Save the file to GreedFall/datalocal/music_cut_mq01_intro_part2.c

Close HxD and then open the new file in NotePad++. Within, you will see

something like:

unsigned char rawData[9999999] = {

0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, ...,

0x00, 0x00, 0x2E, 0x7B, 0x00, 0x00, 0x00, ...,

0x0A, 0x64, 0x01, 0x1E, 0x01, 0x76, 0x6F, ...,

...

};

Note the size of the rawData array above. (9999999). In

music_cut_mq01_intro_part2.xml, find the tag <BINARYOBJECT> and update the

binaryDataSize to match the size of the rawData array. In the case of

our example, it would look something like:

<BINARYOBJECT binaryDataSize="9999999" id="music_cut_mq01_intro_part2.ogg">

Finally, copy all the c-style hex to the clipboard and paste it into

music_cut_mq01_intro_part2.xml. (Hi-light all the data and hit CTRL+C to

copy to clipboard).

The BINARYDATA element of music_cut_mq01_intro_part2.xml should look

something like:


<BINARYDATA> 0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, ..., 0x00, 0x00, 0x2E, 0x7B, 0x00, 0x00, 0x00, ..., 0x0A, 0x64, 0x01, 0x1E, 0x01, 0x76, 0x6F, ..., ... </BINARYDATA>


** If you are using pssgExtractor, you can use the data as-is. If

you wish to use EGO PSSD Editor, you will need to use the

replace tools to remove all instances of '0x' and ',' in the

.c file before you copy/paste.

** The reason we do this and don't simply copy/paste directly from

HxD into NotePad++ is because notepad++ has a line size limit

(around 6 million characters). So if you blindly paste from HxD

to NotePad++, sometimes the data gets truncated off the end. To

be safe, always export to a format that includes line feeds.

7) Use pssgExtractor to convert the XML back to .pssg:

$ python pssgExtractor.py -toPSSG music_cut_mq01_intro_part2.xml

8) In File Explorer, remove the .pssg extension from the new file. Then

right click on "music_cut_mq01_intro_part2" and select:

7-Zip -> Add to Archive.

Make sure Archive Format is set to "gzip". Update the file extension from

".gz" to ".pgz". Then hit OK. The following file should appear in the dir:

music_cut_mq01_intro_part2.pgz

9) Clean up:

And that's it. The game will now use your audio when that music plays.

(See the next example for setting up a cue if you wish to test it).

You can now remove :

GreedFall/datalocal/music_cut_mq01_intro_part2

GreedFall/datalocal/music_cut_mq01_intro_part2.xml

GreedFall/datalocal/pssgExtractor.py

----

ADD:

----


Adding content is a bit more involved as you have to create some registration

files so that the game will load your content.

Here I walk through an example. Modify the details as neeeded. (replace

<YOURMOD> with something that makes sense)


1) copy the following files from datalocal/ (where ever you have extracted

all the resources) to GreedFall/datalocal/ (game mods dir)

datalocal/music/music_cut_mq01_intro_part2.pgz

datalocal/banks/bk_music_cut_mq01_intro_part2.pgz

datalocal/sounds/banks/music/bk_music_cut_mq01_intro_part2.sli

datalocal/sounds/music_library/music_library_lvl_02_south_city.sli

datalocal/sounds/sound_library.sli

2) Extract the 2 PGZ files with 7-zip to datalocal and then delete them

3) Rename the files to match your mod:

music_cut_mq01_intro_part2 => music_<YOURMOD>

bk_music_cut_mq01_intro_part2 => bk_music_<YOURMOD>

bk_music_cut_mq01_intro_part2.sli => bk_music_<YOURMOD>.sli

4) Update the SLI files:


- Scan bk_music_<YOURMOD>.sli, and anywhere it says

"music_cut_mq01_intro_part2" -> change it to "music_<YOURMOD>"

It should look something like:

<?xml version="1.0" encoding="utf-8"?> <bank name="bk_music_<YOURMOD>.pgz"> <sounds> <sound name = "music_<YOURMOD>" file = "music_<YOURMOD>.pgz" inheritFrom = "preset_music_cut" isLoop = "false" /> </sounds> </bank>

** Note: if it is a looping audiofile, set isLoop to "true"

- At the bottom of sound_library.sli add a link to include your

mods SLI Bank. IE, it should look something like:

... <include>sound_library_voices_eng.sli</include> <include>bk_music_<YOURMOD>.sli</include>

- Add a test que to music_library_lvl_02_south_city.sli.

Search for "<dialogCueSets>", and add the following:

<dialogCueSets> <dialogCueSet locKey="dlg_siora_friendship_23"> <sentenceStartCues> <cue><conditionSet/> <transition mode="FADE" fadeOutTime="3"/> <musicActions> <set track="1"> <randomize> <music bank="bk_music_<YOURMOD>.pgz" sound="music_<YOURMOD>" weight="1"/> </randomize> </set> <play track="1"/> </musicActions> </cue> </sentenceStartCues> </dialogCueSet>

The above que, will play your new music if you talk to Siora and ask

her about her Father.

5) Extract the PSSG files with pssgExtractor.py:

- Copy pssgExtractor.py to the GreedFall/datalocal using File Explorer

- Dbl-Click the Cygwin64 Terminal icon. Then in the terminal window

change directory to the games datalocal dir:

$ cd /cygdrive/c/Steam/steamapps/common/GreedFall/datalocal

- Convert the pssg files to xml using pssgExtractor.py

(IMPORTANT: Don't use EGO PSSG Editor for this)

$ python pssgExtractor.py music_<YOURMOD>

$ python pssgExtractor.py bk_music_<YOURMOD>


6) Use Notepad++ to edit bk_music_<YOURMOD>.xml.

- Replace "music_cut_mq01_intro_part2" with "music_<YOURMOD>"

(Hit CTRL+H to bring up the replace tool)


- Save the file

7) Use pssgExtractor to convert bk_music_<YOURMOD>.xml to PSSG:

$ python pssgExtractor.py -toPSSG bk_music_<YOURMOD>.xml


8) In File Explorer, remove the .pssg extension from bk_music_<YOURMOD>.pssg

Right click the file and select 7-Zip -> Add to Archive. Make sure

Archive Format is set to "gzip". Update the file extension from ".gz" to

".pgz". Then hit OK. The following file should appear in the dir:

bk_music_<YOURMOD>.pgz



9) Use Notepad++ to edit music_<YOURMOD>.xml

- Replace "music_cut_mq01_intro_part2" with "" music_<YOURMOD>"

Hit CTRL + H to bring up the replace tool if you want to

save time.

- Remove the Markers.

If you haven't read it, check out the section above:

"What are these Markers in the Music Wrappers" above.

Even if you want Markers and Labels, you can come back and add them

later. For now, just remove them.

Change:

<LIBRARY type="AUDIODATA"> <OGGDATA binaryObjRef="#music_<YOURMOD>.ogg" markerCount="173" id="music_<YOURMOD>_audioData"/> <MARKERS> 00 00 00 00 00 00 56 22 00 .. .. .. ... ... </MARKERS> </OGGDATA> </LIBRARY>

To:

<LIBRARY type="AUDIODATA"> <OGGDATA binaryObjRef="#music_<YOURMOD>.ogg" markerCount="0" id="music_<YOURMOD>_audioData"/> </LIBRARY>


- Remove the Binary (Hex) data:

Go to the bottom and remove all the HEX between the <BINARYDATA>

tags. IE: Place cursor before first HEX digit, scroll to the bottom

and SHIFT+CLICK after the last HEX digit. Then hit Delete

The bottom should look like:

<BINARYDATA> </BINARYDATA> </BINARYOBJECT> </LIBRARY> </PSSGDATABASE> </PSSGFILE>

- Save the file.

10) Create your ogg files in the datalocal directory.


I'm not sure what compression limits/file sizes the engine can handle,

but it is probably best to use 44100 Hz sample rate as that is what all

the existing audio uses. In my example, I used audacity to export the

file to OGG and in the options during the save process, I selected

quality level 6 and it worked fine.


11) Copy Ogg data into music_<YOURMOD>.xml:

Open the ogg file with HxD. Hit CTRL + A to select all

then select: File -> Export -> 'c'

Save the file to GreedFall/datalocal/music_<YOURMOD>.c

Close HxD and then open the new file in NotePad++. Within, you will see

something like:

unsigned char rawData[9999999] = {

0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, ...,

0x00, 0x00, 0x2E, 0x7B, 0x00, 0x00, 0x00, ...,

0x0A, 0x64, 0x01, 0x1E, 0x01, 0x76, 0x6F, ...,

...

};

Note the size of the rawData array above. (9999999). In

music_<YOURMOD>.xml, find the tag <BINARYOBJECT> and update the

binaryDataSize to match the size of the rawData array. In the case of

our example, it would look something like:

<BINARYOBJECT binaryDataSize="9999999" id="music_<YOURMOD>.ogg">

Finally, copy all the c-style hex to the clipboard and paste it into

music_<YOURMOD>.xml. (Hi-light all the data and hit CTRL+C to copy

to clipboard).

The BINARYDATA element of music_<YOURMOD>.xml should look something

like:


<BINARYDATA> 0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, ..., 0x00, 0x00, 0x2E, 0x7B, 0x00, 0x00, 0x00, ..., 0x0A, 0x64, 0x01, 0x1E, 0x01, 0x76, 0x6F, ..., ... </BINARYDATA>

** If you are using pssgExtractor, you can use the data as-is. If

you wish to use EGO PSSD Editor, you will need to use the

replace tools to remove all instances of '0x' and ',' in the

.c file before you copy/paste.

** The reason we do this and don't simply copy/paste directly from

HxD into NotePad++ is because notepad++ has a line size limit

(around 6 million characters). So if you blindly paste from HxD

to NotePad++, sometimes the data gets truncated off the end. To

be safe, always export to a format that includes line feeds.

12) Use pssgExtractor to convert the XML back to .pssg:

$ python pssgExtractor.py -toPSSG music_<YOURMOD>.xml

13) In File Explorer, remove the .pssg extension from music_<YOURMOD>.pssg

Right click the file "music_<YOURMOD>" and select:


7-Zip -> Add to Archive.

Make sure Archive Format is set to "gzip". Update the file extension

from ".gz" to ".pgz". Then hit OK. The following file should appear

in the dir:

music_<YOURMOD>.pgz


14) Test: Start up the game, head to New Serene and talk to Siora.

Ask her about her father and confirm your music starts to

play.

15) Clean up:

You can remove :

GreedFall/datalocal/bk_music_<YOURMOD>

GreedFall/datalocal/bk_music_<YOURMOD>.pschema

GreedFall/datalocal/bk_music_<YOURMOD>.xml

GreedFall/datalocal/music_<YOURMOD>

GreedFall/datalocal/music_<YOURMOD>.c

GreedFall/datalocal/music_<YOURMOD>.pschema

GreedFall/datalocal/music_<YOURMOD>.xml

GreedFall/datalocal/pssgExtractor.py

**GreedFall/datalocal/music_library_lvl_02_south_city.sli

** You will need to configure cues to determine when to play

your new music. music_library_lvl_02_south_city.sli was just

a testing example. Look at datalocal/sounds/music_library.sli

to follow the cue registration tree and decide when/how

to cue your music.

** If you are looking to simply replace the games existing

music, you would be better off clobbering the PGZ files

under datalocal/music with your own updated versions.

B. Spoken Lines


<< TODO >>


C. Sounds/FX


<< TODO >>

===============================================================================

VII. > > > > Items

===============================================================================


-------------------------------------------------------------------------------

VII.1 > > > > General

-------------------------------------------------------------------------------


< < < < < TODO / In PROGRESS> > > > >


-------------------------------------------------------------------------------

VII.2 > > > > Armor

-------------------------------------------------------------------------------


< < < < < TODO / In PROGRESS> > > > >


-------------------------------------------------------------------------------

VII.3 > > > > Weapons

-------------------------------------------------------------------------------


-------------------------------------------------------------------------------

VII.4 > > > > Potions and Grenades

-------------------------------------------------------------------------------


< < < < < TODO / In PROGRESS> > > > >


-------------------------------------------------------------------------------

VII.5 > > > > Loot and Rewards

-------------------------------------------------------------------------------


< < < < < TODO / In PROGRESS> > > > >


===============================================================================

VIII. > > > > Merchants

===============================================================================


-------------------------------------------------------------------------------

VIII.1 > > > > Defining Inventories

-------------------------------------------------------------------------------


< < < < < TODO / In PROGRESS> > > > >


-------------------------------------------------------------------------------

VIII.2 > > > > Linking NPCs to inventories

-------------------------------------------------------------------------------


< < < < < TODO / In PROGRESS> > > > >


===============================================================================

IX. > > > > Spells

===============================================================================


-------------------------------------------------------------------------------

IX.1 > > > > ???

-------------------------------------------------------------------------------


< < < < < TODO / In PROGRESS> > > > >


===============================================================================

X. > > > > Models and Textures

===============================================================================


-------------------------------------------------------------------------------

X.1 Textures

-------------------------------------------------------------------------------


Some pssg files wrap texture data. Here is an example PSSG wrapper:


<TEXTURE width="64" height="64" texelFormat="bc1" msaaType="0" mipmapCount="5" streaming="1" streamed="0" hasProxy="1" lowRes="0" nameHigh="tx_bat_decals_wall_01a_diff.stxh" nameUltra="tx_bat_decals_wall_01a_diff.stxu" sizeNormal="174760" sizeHigh="699048" sizeUltra="2796200" discardLocalAfterBind="1" id="tx_bat_decals_wall_01a_diff.dds"> <TEXTUREIMAGEBLOCK size="2728"> <TEXTUREIMAGEBLOCKDATA> hex hex hex hex hex....


Let me say upfront... disclaimer: I don't actually KNOW anything. The

following is an educated guess:


The hex appears to be a raw BC1 compressed data stream. The other files

mentioned (nameHigh and nameUltra) are likely also image data streams that

feed the same dds file.


Notice the "texelFormat" attribute. BC1 is a compression format used by

DirectX graphics cards. For an excellent article on the BC1-BC7 compression

formats, see :

http://www.reedbeta.com/blog/understanding-bcn-texture-compression-formats/


Normally, DirectX games use DirectDrawSurface (dds) files and the DDS file

internally has BCn compressed data. Here we don't actually have the dds file.

We only have the BC1 image data stream (that is fed to the graphics card). I

suspect this is because much of what the DDS header normally provides such

as width, height, compression format, etc... is already present in the PSSG

wrapper. So instead of repeating the information with a full DDS payload,

GreedFall devs (might have) decided to only store the BCn image data stream.


Bottom line: If you try to save the HEX data to a file and rename it .dds,

it won't work because it isn't DDS format. I suspect someone would need to

create DDS headers and inject the streams to create something that could be

edited using normal DDS editing tools. And a tool that could also go the

other way, as the updated texture would need to eventially be re-injected.


The link above includes a link to NVIDIA's texture tools and library,

however I looked at them. While they are valuable and include the

DirectDrawSurface.h and .cpp files, as is, they are not designed to do what

needs to be done. They are designed to compress common image formats like tga,

png into dds files and back. (They would have to be heavily modified to

accept a sequence of already compressed steams to feed into a new DDS file.


There is also https://github.com/hglm/detex It is C based and it was written

for linux. I installed CYGWIN's gcc, make, xinit, xlaunch, libgtk3-devel and

xorg-c11-fonts-*. Then compiled the program following its readme so that it

would run under cygwin. To run it, I had to start a multi-screen X Server

using the cygwin terminal command "xlaunch"


The advantage of detex is that you don't need to install or learn Visual

Studio to update the code. It is a (relatively) small program written in C

that you can compile using CYGWIN. So if I was going to tackle creating a

solution to this problem, I would probably start with detex.


Detex requires that files have a 64 Byte "ktx" header, followed by a 4 Byte

"Here is the size of the file". Problem is, a 64x64 BC1 image should be 2048

Bytes large. But the BC1 Stream size in most the PSSG files I saw was 680

bytes greater than that.


After a day of investigation, I gave up. I may revisit this in the future if

I find more time, but I don't currently have any ambitions to create/update

textures. So it is hard to find motivation to pursue this. But again, these

are tips so that someone else who cares might get started in the right

direction.


-------------------------------------------------------------------------------

X.2 Models

-------------------------------------------------------------------------------


If one uses EGO PSSG Editor on the model files and extracts them to XML, it

is pretty obvious the data within is meant for 3DS-MAX. I don't own 3DS-MAX,

so that is a problem.

I am aware that BLENDER supports python plugins. I also know years ago,

there was a python plugin that took the vertice data from MDL models

(Source Engine) and put them into a PROXY model that users could "nudge"

edit in Blender. (No new verticies could be introduced because they were

connected to the skeleton/animation system and no one knew how that worked.

So you could only do minor vertice nudging... think clothing seems. But

still... enough to make some new clothes with the help of BUMPMAP textures).


Then a separate python script would inject the edits into the original data.

I'm not sure if something like this might be possible with GreedFall. Or

if someone who knows 3DS-MAX might look at the XML output and go. "Oh...

I know where that goes!".

===============================================================================

XI. > > > > Miscellaneous

===============================================================================


An area for things that don't fit anywhere else right now....

-------------------------------------------------------------------------------

XI.1 > > > > Cutscenes

-------------------------------------------------------------------------------


Cutscenes use a custom shallow format that makes use of silks new PSSG tags.

If you use PSSGextract.py to XMLify one of the cutscenes, you will notice as

you follow the file, it is almost a scripting language built into the

attribute values. Most notable is the fact that some strings have a name and

value and some do not. I suspect Strings without a Value represent a marker

for a structure or instruction set.


When I edited cutscenes, all I did was swap out the names of the NPCs who

were playing certain parts. My edits were simple actor name changes. Someone

who is more interested in cutscenes could probably study some of these

cutscenes and learn the "language" of the cutscene. There are obviously

commands for things like hiding NPCs, removing or hiding items/weapons,

Changing cameras, invoking animation sequences, etc...


When adding a NEW cutscene to the game, you have to “register” it by editing

the library:


library_cutscene_loading.sli


This indicates what cutscenes to load into memory when the player enters

certain levels.


-------------------------------------------------------------------------------

XI.2 > > > > Levels and Maps

-------------------------------------------------------------------------------

The level files can be found under:


datalocal/level/


For size reasons, most of the files are either gzip compressed or PSSG files

even though they have extensions like:

.slr <- 7zip compressed pssg file that lists level resources to load/include

.sla <- pssg file that identifies objects/actors/triggerzone locations.

There are a number of .sla files. Not sure at this time what the numbering

means (if anything). What is clear is that the level content is spread

out, often times unevenly across the numbers. Sometimes I will see an

element defined in one file and then referenced from another, so it is

definitely confusing. Hopefully in time I will learn more.


If you extract these cells with pssgExtract and include the indent option,

it makes the file content a bit more clear. Some cells have Actor Library

blocks that define actors to spawn when the player enters trigger zones.


Example: lvl_00_travel-122.sla: (Travel Camp)

Each file is different. The one above has 2 sections. A section for "nodes".

which appears to define placement information. The id/name from nodes seems

to match up with the second section which defines Actors.

When I think of Actors, I think of people, but here Actor seems to include

anything that can be spawned. The Actors have a GAMEACTOR Type value which

dictates the remaining attributes that are found.

The companions/NPCs seem to use type "SpawnCompanion" while non-companions

use type "SpawnCharacter". And then there is a TriggerStaticSpawn that points

to the other SpawnCompanion nodes. Examples:

<LIBRARY type="NODE"> <LAYERROOT id="layerRoot_layer122"> ... <NODE id="SpawnCompanion$N5122BPKE_rootNode$N5122BPKE"> <USERDATA object="#SpawnCompanion_Travel_Camp_Merchant"/> <TRANSFORM> 3E FD 3F 6D BF 5E 7D C5 80 00 00 00 00 00 00 00 ... 00 00 00 00 80 00 00 00 3F 7F FF FE 00 00 00 00 ... </TRANSFORM> <BOUNDINGBOX> 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... </BOUNDINGBOX> </NODE> </LAYERROOT> </LIBRARY> <LIBRARY type="ACTOR"> <ACTOR RootNode="#SpawnCompanion$N5122BPKE_rootNode$N5122BPKE" id="SpawnCompanion_Travel_Camp_Merchant"> <GAMEACTOR Type="SpawnCompanion"> <UnsignedInt Name="SpawnCompanionClassVersion">4</UnsignedInt> <String Name="NPCId" Value="hum_npc_merchant_trader_travel"/> <String Name="startStance" Value="NORMAL"/> <UnsignedInt Name="registeredByTriggers">0</UnsignedInt> <UnsignedInt Name="SpawnClassVersion">10</UnsignedInt> <UnsignedInt Name="checkDistance">1</UnsignedInt> <UnsignedInt Name="useRadius">0</UnsignedInt> <UnsignedInt Name="alwaysSpawned">1</UnsignedInt> <UnsignedInt Name="spawnedCH1">1</UnsignedInt> <UnsignedInt Name="spawnedCH2">1</UnsignedInt> <UnsignedInt Name="spawnedCH3">1</UnsignedInt> <UnsignedInt Name="WaypointClassVersion">7</UnsignedInt> <Color Name="color">220.000000 220.000000 0.000000</Color> <UnsignedInt Name="GameActorClassVersion">3</UnsignedInt> </GAMEACTOR> </ACTOR> <ACTOR RootNode="#PolygonShape$LM45OKAD" id="TriggerStaticSpawn_Travel_Camp_NPC"> <GAMEACTOR Type="TriggerStaticSpawn"> <UnsignedInt Name="TriggerStaticSpawnClassVersion">4</UnsignedInt> <UnsignedInt Name="TriggerStaticClassVersion">4</UnsignedInt> <UnsignedInt Name="ConditionListClassVersion">4</UnsignedInt> <Int Name="LibConditions">0</Int> <UnsignedInt Name="validationType">0</UnsignedInt> <UnsignedInt Name="ActionListClassVersion">5</UnsignedInt> <Int Name="LibActions">0</Int> <UnsignedInt Name="ActionListClassVersion">5</UnsignedInt> <Int Name="LibActions">0</Int> <UnsignedInt Name="ActionListClassVersion">5</UnsignedInt> <Int Name="LibActions">0</Int> <UnsignedInt Name="StaticTriggerClassVersion">2</UnsignedInt> <UnsignedInt Name="GameActorClassVersion">3</UnsignedInt> <UnsignedInt Name="spawnType">1</UnsignedInt> <UnsignedInt Name="spawnActorsCount">7</UnsignedInt> <String Name="spawnActor0" Value="Squad_Travel_Camp_NPC"/> <String Name="spawnActor1" Value="SpawnCompanion_Siora"/> <String Name="spawnActor2" Value="SpawnCompanion_Aphra"/> <String Name="spawnActor3" Value="SpawnCompanion_Petrus"/> <String Name="spawnActor4" Value="SpawnCompanion_Vasco"/> <String Name="spawnActor5" Value="SpawnCompanion_Kurt"/> <String Name="spawnActor6" Value="SpawnCompanion_Travel_Camp_Merchant"/> </GAMEACTOR> </ACTOR>

So far I have been able to move NPCs and objects around. The Transform

data is a series of floats/uints and I don't quite yet know what they all

mean. I suspect from working on other games, the transform likely

positions the object relative to the parent using X,Y,Z and various

angling indicators (facing, lean forward, lean side).


The X and Y portion of the object position is at (Float) TRANSFORM[12] and

(Float) TRANSFORM[13] respectively. (zero index based, all values are

Big-Endian).

As of right now I haven't been able to add new objects, but time will tell.

===============================================================================

XII. > > > > Legalities

===============================================================================


GreedFall offers no provisional rights to mod developers. This has different

implications in different regions, however the bottom line is this:


YOU CAN'T MAKE ANY MONEY FROM YOUR MOD (ie : you can't sell it)


This should not come as a surprise since free work is generally convention

within the modding communities.


Also, DO NOT distribute GreedFall.exe with your mod. Doing so is blatantly

illegal and would be construed as distributing a pirated or "cracked" version

of the game.


===============================================================================

< < < < < Contributing Authors > > > > >

===============================================================================


Dheu