Loading Level Resources With Lua
This is what has been found so far to add and/or modify level resource objects using a Lua script, along with brief descriptions of what these objects do.
Loading Resource Files
To make different sets of resources load at the start of a level, one way is to run code that alters the resTbl table used within the QueueResourcesForLoad() function. This function is initially defined in plain text in Journey.exe . (Research on this and the other functions in that ".lua" will probably reveal how to load completely customized resources from outside the .exe)
For example, if you inject this script, then whenever you define swapLevel as an internal level name ("Graveyard","Barrens",etc) the next level's resources will all be replaced with the ones from Level_swapLevel.
swapLevel = "" function QueueResourcesForLoad( resManager, resTbl, alloc ) DebugPrint( "Loading: Queuing resources for load." ) --this if/then block is the modded code, the rest is original if swapLevel ~= "" then for i,v in ipairs (resTbl) do if v.class == "Script" then v.source = "Level_"..swapLevel.."/"..v.name..".lua" elseif v.class == "Binary" then v.source = "Scripts/Level_"..swapLevel.."/"..v.name..".lua" elseif v.name == "LvlMus" then v.source = swapLevel.."-MUS.bnk" elseif v.name == "LvlSfx" then v.source = swapLevel.."-SFX.bnk" elseif v.class == "Texture" then v.source = "Level_"..swapLevel.."/DuneColorShadow.dds" elseif v.class == "TerrainData" then v.outDir = "Level_"..swapLevel.."/" v.srcGround = "Level_"..swapLevel.."/LandColor.dds" v.srcHeight = "Level_"..swapLevel.."/Heightmap.dds" v.srcMask = "Level_"..swapLevel.."/MaskData.dds" end end swapLevel = "" end for i,resCons in ipairs( resTbl ) do DebugPrint( "Loading: Queuing "..resCons.name..", a "..resCons.class.."." ) -- Allocate the resource object. local metatbl = _G[ resCons.class ] local res = metatbl.new( alloc ) -- Initialize some values in the resource object based on the -- construction table. resCons.class = nil for varName,varValue in pairs( resCons ) do local varAccessor = res.__vars[ varName ] if varAccessor then varAccessor( res, varValue ) end end -- Tell the C++ end this resource is ready to be loaded. resManager:AddToLoadList( res ) end end
"Script" resources are .lua's embedded in Journey.exe, but all other resources are loaded from the game folders.
Keep in mind that some resource types reference each other in a way that will cause the game to crash if something is wrong/missing, so some resource sets can only be safely replaced if you properly replace certain others as well.
If you try to load a set of resources that doesn't exist, some resource types will allow it and just load none of that resource, but other types will make the game crash.
Here is a sample of the contents of resTbl as it gets passed to QueueResourcesForLoad:
{
{
class = "ScreamBank",
name = "LvlSfx",
source = "Graveyard-SFX.bank"
}, {
class = "ScreamBank",
name = "LvlMus",
source = "Graveyard-MUS.bank"
}, {
class = "TerrainData",
name = "TerrainData",
outDir = "Level_Graveyard/",
srcGround = "Level_Graveyard/LandColor.dds",
srcHeight = "Level_Graveyard/Heightmap.dds",
srcMask = "Level_Graveyard/MaskData.dds"
}, {
class = "Binary",
name = "HullInstances",
source = "Scripts/Level_Graveyard/HullInstances.lua",
type = "Hull"
}, {
class = "Binary",
name = "DecorationMeshInstances",
source = "Scripts/Level_Graveyard/DecorationMeshInstances.lua",
type = "Decoration"
}, {
class = "Script",
name = "EnvNodeInstances",
source = "Level_Graveyard/EnvNodeInstances.lua"
}, {
class = "Script",
name = "MarkerInstances",
source = "Level_Graveyard/MarkerInstances.lua"
}, {
class = "Script",
name = "ParticleEmitterInstances",
source = "Level_Graveyard/ParticleEmitterInstances.lua"
}, {
class = "Script",
name = "SoundEmitterInstances",
source = "Level_Graveyard/SoundEmitterInstances.lua"
}, {
class = "Script",
name = "SignInstances",
source = "Level_Graveyard/SignInstances.lua"
}, {
class = "Script",
name = "SignData",
source = "Level_Graveyard/SignData.lua"
}, {
class = "Script",
name = "JetInstances",
source = "Level_Graveyard/JetInstances.lua"
}, {
class = "Script",
name = "DynamicNodeInstances",
source = "Level_Graveyard/DynamicNodeInstances.lua"
}, {
class = "Script",
name = "CameraKeyFrameInstances",
source = "Level_Graveyard/CameraKeyFrameInstances.lua"
}, {
class = "Script",
name = "StartLocation",
source = "Level_Graveyard/StartLocation.lua"
}, {
class = "Script",
name = "TimelineInstances",
source = "Level_Graveyard/TimelineInstances.lua"
}, {
class = "Script",
name = "TriggerInstances",
source = "Level_Graveyard/TriggerInstances.lua"
}, {
class = "Script",
name = "ClumpInstances",
source = "Level_Graveyard/ClumpInstances.lua"
}, {
class = "Script",
name = "RailInstances",
source = "Level_Graveyard/RailInstances.lua"
}, {
class = "Texture",
gammaRemap = true,
name = "DuneColorShadow",
source = "Level_Graveyard/DuneColorShadow.dds"
}
}
Adding/Modifying Individual Resources
Important things to remember
These methods do not actually edit how the game reads an embedded Level_Somewhere/SomethingInstances.lua , so these changes only last until the level is unloaded. When a level unloads, it seems the Names table is completely cleared, including any new objects you created that weren't in the original SomethingInstances.lua.
If you reference an ObjectName that exists in the Names table, but the associated object no longer exists in memory or is corrupted, your game will most likely crash. It also may crash if you reference an object that isn't in the Names table.
The SomethingInstances/etc tables that you construct to load resources into the Names table are usually (but not always) cleared automatically after the functions run, to free up memory.
From tests done by inserting logging code into the resource-loading functions, this appears to always be the order that resources get loaded into the Names table, in all levels. You should probably use the same order when adding/modifying different types of resources at the same time, it is not yet known if doing them in a different order can break anything.
- Camera Key Frames
- Hulls
- Environment Nodes
- Markers
- Jets
- Decoration Meshes
- Signs
- Timelines
- Rails
- Clumps
- Creatures
- Triggers
- PrintNameTableSize() from Helpers.lua is called - seems like a safe/good function to edit so it instead adds/modifies all the resources you want ready before the level actually "starts", mostly untested
- Environment Nodes (apparently gets loaded twice)
- Load order not yet known for Particle Emitters, Sound Emitters, Terrain Data, the SFX/Music Banks, or the DuneColorShadow texture.
Triggers
Triggers are various types of events, including events that activate other events. They control basically everything that ever "happens" in a level, apart from basic player actions. The functions in this category are mainly from EventSetter.lua .
Modifying Triggers
To modify existing Triggers, find them in TriggerInstances.lua, define them in the TriggerInstances table with the same format used in that lua, then run the ResolveTriggerNames function. Code "template":
TriggerInstances = { ObjectType = "TriggerInstances", {ObjectName = "the original trigger's ObjectName", GardenerName = "the original GardenerName", Type = "the original Type", Shortcut = "", Vars = { customize the vars however you want } }, { all that same stuff for another trigger }, {and another, etc} }
ResolveTriggerNames( game:eventBarn(), game:clumpBarn() )
Code example - using this in CS will change the music cue when you collect the first symbol at the broken statue, so you hear the start-game cutscene music instead:
TriggerInstances = { ObjectType = "TriggerInstances", {ObjectName = "ed489eebe96121c9de7ccce1d25d6ced", GardenerName = "|PlaySFXTrigger270|PlaySFXTrigger270_G", Type = "PlaySFXTrigger", Shortcut = "", Vars = { bankName = "LvlMus", soundName = "M_0m1", vol = 1} } }
ResolveTriggerNames( game:eventBarn(), game:clumpBarn() )
With this method by itself, if you change the trigger type to a different one than the original, it seems to (sometimes?) not work due to it expecting different data types; it's not yet known how to completely change the trigger type. You might be able to at least change trigger types if the Vars would be the same data types/names - for example if two trigger types both use { var1 = float, var2 = string, var3 = a Clump of Hulls, var4 = boolean } you could possibly switch from one type to the other and change the Var values and it would work; not tested yet.
Creating new Triggers
You can temporarily create new triggers basically the same way, by adding the CreateTriggers function:
TriggerInstances = { ObjectType = "TriggerInstances", { ObjectName = "NewTrigger", GardenerName = "NewTrigger_G(this name doesn't matter?)", Type = "whatever type you want", Shortcut = "", Vars = { whatever vars that type of trigger uses } }, {same for more new triggers} }
CreateTriggers( game:eventBarn() )
ResolveTriggerNames( game:eventBarn(), game:clumpBarn() )
For example:
TriggerInstances = { ObjectType = "TriggerInstances", { ObjectName = "TextTrigger", GardenerName = "TextTrigger_G", Type = "DisplayText", Shortcut = "", Vars = {text = "Check this out, I can do a backflip", x = 0, y = 0, duration = 3, fadeTime = 0.5 } }, { ObjectName = "AnimTrigger", GardenerName = "AnimTrigger_G", Type = "PlayDudeAnim", Shortcut = "", Vars = { animName = "HitBodyMAir", clearAnimQueue = true, useLocal = true} } }
CreateTriggers( game:eventBarn() )
ResolveTriggerNames( game:eventBarn(), game:clumpBarn() )
-- these activations aren't necessary to load the triggers, just done on the same frame to demonstrate that they work; see below for why it's done on same frame
ActivateTriggerByName( "TextTrigger" )
ActivateTriggerByName( "AnimTrigger" )
With this method alone, new Trigger ObjectNames are added to the Names table for the rest of the level, but the actual data defining what the Trigger should do is not added to the section of memory where normal Trigger data is stored. Wherever the data is, the Trigger does exist at least until the end of the frame (maybe a few frames) and it can be activated for that short time, but then the data is overwritten and your game will crash if it tries to activate the nonexistent Trigger.
It is not yet known how to make the actual Trigger persist for the entire level. Until we figure that out, one solution to create new "persistent" Triggers might be to have this code running every frame - though you'd have to make it not run while the normal level load/unload stuff is happening. Haven't tested that yet.
Once you have created a new Trigger, you only need to use ResolveTriggerNames() to re-create it after its data is deleted. However, there does not appear to be any harm in using CreateTriggers() for it again, it just won't do anything - the Names table will stay the same.
Spawning instant one-time Triggers
If you just want to make a new Trigger-like event that instantly happens and then disappears ( instead of being a "normal" Trigger that relies on other Triggers to make it happen and can be reused) you can use the SpawnEvent function from PWeb3.lua. It seems that SpawnEvent is not a global function, so you have to re-define it to use it globally.
SpawnEvent { TriggerType = { var1 = x, var2 = y, var3 = z... basically the same kind of Vars table that would be used with that Trigger type }
Clumps
Clumps are groups of multiple objects, used so that a single Trigger can reference and/or manipulate everything in the Clump at once. All Objects in a clump might need to be the same type of object, like they must be all Triggers, or all Hulls, or all Meshes, etc - not tested yet. Also, a Clump can contain other Clumps.
The functions in this category are from ClumpLoader.lua.
Creating new Clumps
To create new Clumps, define the ClumpInstances table with the same format used in ClumpInstances.lua (but concatenated, not with line breaks). Then run the LoadClumps function, then the SetClumpMembers function. Code "template":
ClumpInstances = { ObjectType = "ClumpInstances", { ObjectName = "NewClumpName", GardenerName = "NewClumpName_G(this name doesn't matter?)", Objects = { "some object's ObjectName", "another object's ObjectName", "and another, etc" } }, { same thing for another new Clump }, {another, etc} }
LoadClumps( game:clumpBarn() )
SetClumpMembers( game:clumpBarn() )
Code example - using this in CS will create a new Clump that duplicates the Clump of Triggers which activates when you stand up after the new-game cutscene:
ClumpInstances = { ObjectType = "ClumpInstances", { ObjectName = "NewGameStandUpEvents", GardenerName = "NewGameStandUpEvents_G", Objects = { "70c1306a4a41c624a9ff123f344ef6ad", "d951daedc9f58a681efd39a8875f32c6", "1a737db99c2f3a7733e5c6426211d0e8", "daa0dd930abe88d5d241e02752c636d4", "f26b85b929a3e48edf43a5ec1f076c91", "65eef6f7bcbfcfa07b32c34d07e53132", "796c354ac61bd62ac987778b9fa96c4c", "5abf9a5b00e2e29c36f7871028e9c1e5", "e4fada7df843f162ebb2ee56a0e9f40f" } } }
LoadClumps( game:clumpBarn() )
SetClumpMembers( game:clumpBarn() )
Demonstration of the new Clump:
SpawnEvent {EventAfterDelay = {delay = 0.01, effects = Names["NewGameStandUpEvents"]}}
Demonstration of the Clump it's duplicating:
SpawnEvent {EventAfterDelay = {delay = 0.01, effects = Names["e7fa07adac45a2fb9a369edcf5964ec7"]}}
Modifying Clumps
To modify existing Clumps, get them from ClumpInstances.lua and do basically the same code, but without the LoadClumps function:
ClumpInstances = { ObjectType = "ClumpInstances", { ObjectName = "original Clump's ObjectName", GardenerName = "original Clump's GardenerName", Objects = { the ObjectNames of whatever objects you want to be in that Clump } }, { same thing for another Clump }, {another, etc} }
SetClumpMembers( game:clumpBarn() )
For example, using this in CS will modify the existing Clump of stand-up-after-new-game Triggers (same as the new Clump example), so that you get knocked over by wind instead of standing up (replacing first Trigger in Objects list) and you hear the music cue from the broken statue symbol cutscene (adding that Trigger at end of list):
ClumpInstances = { ObjectType = "ClumpInstances", { ObjectName = "e7fa07adac45a2fb9a369edcf5964ec7", GardenerName = "|Clump849|Clump849_GARDENER", Objects = { "27b916d7b9ebb26d91432e16257cba5d", "d951daedc9f58a681efd39a8875f32c6", "1a737db99c2f3a7733e5c6426211d0e8", "daa0dd930abe88d5d241e02752c636d4", "f26b85b929a3e48edf43a5ec1f076c91", "65eef6f7bcbfcfa07b32c34d07e53132", "796c354ac61bd62ac987778b9fa96c4c", "5abf9a5b00e2e29c36f7871028e9c1e5", "e4fada7df843f162ebb2ee56a0e9f40f", "ed489eebe96121c9de7ccce1d25d6ced" } } }
SetClumpMembers( game:clumpBarn() )
Demonstration:
SpawnEvent {EventAfterDelay = {delay = 0.01, effects = Names["e7fa07adac45a2fb9a369edcf5964ec7"]}}
Timelines
Timelines are timed sequences of Trigger activations, used to organize cutscenes and other events where a bunch of Triggers get activated in a row.
It has not yet been tested, but Timelines can probably be added and/or modified in a similar way to Triggers and Clumps, based on TimelineInstances.lua's and the functions in TimelineBarn.lua:
TimelineInstances = { construct based on the format in TimelineInstances.lua }
LoadTimelines( game:timelineBarn() )
Creatures / Dynamic Nodes
Creatures are dynamically moving and interacting objects, other than the player avatars and the tiny flying cloths (Strands). Although code comments say that Creatures are just a Type of Dynamic Node, it appears that all Dynamic Nodes have their Type set as "Creatures" anyway. Creatures include Fish/Carpets, Guardians/War Machines, Cloth Guardians/Whales/Dragons, Jellyfish, Flow creatures, and Meteors/Comets/Shooting Stars.
Creatures can be added and/or modified in a similar way to Triggers and Clumps, using the functions from Creatures.lua:
DynamicNodeInstances = { construct based on the format in DynamicNodeInstances.lua }
CreateCreatures( creatureBarn* ) --only used when adding new creatures(?)
ResolveCreatureNames( creatureBarn* )
* There is no "game:creatureBarn" or other predefined global variable, but you can still access the "hidden" Barn and use these functions - see end of article.
Particle Emitters
Particle Emitters emit particles (sparkles, puffs of sand/dust, etc.)
It has not yet been tested, but Particle Emitters can probably be added and/or modified in a similar way to Triggers and Clumps, based on ParticleEmitterInstances.lua's and the functions in EnvFX.lua:
ParticleEmitterInstances = { construct based on the format in ParticleEmitterInstances.lua }
LoadEnvFXParticles( game:envFXBarn(), game:particleBarn() )
Based on tests where logging code was put in LoadEnvFXParticles() and ParticleEmitterInitialize() and nothing was logged, it seems this script doesn't actually run at level start, so Particle Emitters might be added to Names table by another Lua script or by inaccessible C code. But maybe this function could still add/modify Particle Emitters anyway.
Sound Emitters
Sound Emitters emit sounds. They can be stationary or attached to a moving object somehow. It seems that most looping music is played by Sound Emitters that are set to be audible at the same volume anywhere in the level.
It has not yet been tested, but Sound Emitters can probably be added and/or modified in a similar way to Triggers and Clumps, based on SoundEmitterInstances.lua's and the functions in SoundBarn.lua:
SoundEmitterInstances = { construct based on the format in SoundEmitterInstances.lua }
LoadSoundEmitters( game:soundBarn() )
Based on tests where logging code was put in LoadSoundEmitters() and SoundEmitterInitialize() and nothing was logged, it seems this script doesn't actually run at level start, so Sound Emitters might be added to Names table by another Lua script or by inaccessible C code. But maybe this function could still add/modify Sound Emitters anyway.
Environment Nodes
Environment/Env Nodes set the environmental effects within certain regions, such as lighting, sky color, shadows, fog, bloom, etc. "IsZNode = true" nodes affect the region all along a certain space on the z-axis (usually toward/away from mountain), "= false" nodes affect a certain cube-shaped volume.
It has not yet been tested, but Env Nodes can probably be added and/or modified in a similar way to Triggers and Clumps, based on EnvNodeInstances.lua's and the functions in Environment.lua:
EnvNodeInstances = { construct based on the format in EnvNodeInstances.lua }
LoadEnvNodes( game:environment() )
There is also an UpdateEnvNamesTableEntry() function in Environment.lua that might be for modifying existing Env Nodes somehow.
LoadEnvNodes does not clear the EnvNodeInstances table automatically upon completion, and the code comments say this causes a crash, so might be a bad idea to do it yourself.
Markers
Markers seem to be locations, or regions of space around locations, which objects can be aimed or moved towards/away from by certain Triggers. Also, Markers can maybe be moved around or attached to moving objects by Triggers.
It has not yet been tested, but Markers can probably be added and/or modified in a similar way to Triggers and Clumps, based on MarkerInstances.lua's and the functions in Markers.lua:
MarkerInstances = { construct based on the format in MarkerInstances.lua }
LoadMarkers( game:markerBarn() )
Rails
Rails seem to be paths that objects can move along.
It has not yet been tested, but Rails can probably be added and/or modified in a similar way to Triggers and Clumps, based on RailInstances.lua's and the functions in RailBarn.lua:
RailInstances = { construct based on the format in RailInstances.lua }
LoadRails( railBarn* )
* There is no "game:railBarn" or other predefined global variable, but you can probably still access the "hidden" Barn and use this function - see end of article.
Camera Key Frames
Camera Key Frames seem to be the data for basically any location/angle/etc that the camera can get moved to, whenever the camera moves by itself - not just following behind the player, or being moved around by the player.
It has not yet been tested, but Camera Key Frames can probably be added and/or modified in a similar way to Triggers and Clumps, based on CameraKeyFrameInstances.lua's and the functions in CameraInit.lua:
CameraKeyFrameInstances = { construct based on the format in CameraKeyFrameInstances.lua }
LoadCameraKeyFrames( game:cameraSystem() )
Jets
Jets seem to be the physics effect that pushes your avatar down when you are inside certain sandfalls, and maybe other effects. Could probably be used to push the avatar in other directions as well.
It has not yet been tested, but Jets can probably be added and/or modified in a similar way to Triggers and Clumps, based on JetInstances.lua's and the functions in Jets.lua:
JetInstances = { construct based on the format in JetInstances.lua }
LoadJets( jetBarn* )
* There is no "game:jetBarn" or other predefined global variable, but you can probably still access the "hidden" Barn and use this function - see end of article.
Signs
Signs are scrolling text, used only in the credits. Could maybe be used for other scrolling text as well, although there seem to already be scrolling text Triggers that might be easier for that.
It has not yet been tested, but Signs can probably be added and/or modified in a similar way to Triggers and Clumps, based on SignData.lua's, SignInstances.lua's and the functions in SignBarn.lua:
Credits = { construct based on the format in SignData.lua }
SignInstances = { construct based on the format in SignInstances.lua }
InitializeSignBarn( signBarn*, signData**, signInstances** )
* There is no "game:jetBarn" or other predefined global variable, but you can probably still access the "hidden" Barn and use this function - see end of article.
** signData and signInstances don't seem to actually be referenced inside InitializeSignBarn, so they might not be required, but if required then maybe signData is the Credits table and signInstances is the SignInstances table. )
TBD / Other
These are the remaining types of level resources. They can *maybe* be manipulated in real time with Lua code, but it's not yet known if/how it can be done.
Hulls
Hulls are regions of space that never move(?), which are used as physical collision geometry and/or a way for Triggers to determine "if this thing is touching/inside this region, do this event".
You can change the Hulls loaded at start of level by editing HullInstances.lua in the folder for that level under Data/Scripts.
Decoration Meshes
Decoration Meshes are any visible object other than terrain, fog, particle effects, Strands(?), and the basic "naked" player avatar. By themselves, they are not physical objects, their collision data comes from physical Hulls put in the same place.
You can change the Decoration Meshes loaded at start of level by editing DecorationMeshInstances.lua.bin in the folder for that level under Data/Scripts.
Known so far:
The game loads Decoration Meshes into the Names table by calling AddDecoToNames() which is in DecorationMeshes.lua ; a Decoration class object gets passed to the function as "deco". It is not yet known if/how we could create a Decoration in Lua.
DecorationMeshes.lua includes a DecorationMeshes function (mostly commented out) and DecorationInitialize function, which seem like they could convert data in the DecorationMeshInstances table to Decorations and load them into the Names table, but the DecorationMeshes function needs to be passed a DecorationBarn as "decBarn" and there is nothing like a game:decBarn()/decorationBarn()/etc. It is not yet known if/how we could call a usable DecorationBarn in Lua.
Sound Banks
SFX Banks contain sounds, and references/mixing data for longer sounds streamed from the audio files in Data/Sounds/Streams.
Music Banks just contain references/mixing data for music audio files streamed in from Data/Sounds/Streams.
These are LevelSfxSomewhere-SFX.bnk and LevelMusSomewhere-MUS.bnk in the Data/Sounds folder; not much is known yet about how to edit these at all, other than renaming files to swap one level for another.
TerrainData
Terrain Data determines the terrain's Height Map and Land Color (the color of sand/snow added to the sides and/or top of some Decoration Meshes, and that collects on the player avatars). It also determines Mask Data which seems to be unused.
You can change what is loaded at start of level by editing the TerrainData.bin inside TerrainData.bin.gz in the Data/Terrain/Level_Somewhere folder.
DuneColorShadow
DuneColorShadow is a texture that determines the color of the terrain in a level, and which areas are in shadow.
You can change what is loaded at the start of a level by editing/replacing the DuneColorShadow.dds texture inside the DuneColorShadow.dds.D3D11x64 file in the Data/Textures/Level_Somewhere/Bin folder.
Using Functions With "Hidden" Barns/Managers
Many of the functions for creating resources need a related "somethingBarn"/etc - many of them already have this as "game:somethingBarn()", but some don't appear to have any predefined global variable in Lua. For at least some of these, you can still access it and make your own global variable for it, and then use that variable whenever a function calls for a "somethingBarn". (This has only been tested with Creatures)
You do this by modifying/overwriting the function used with the Barn, to save the variable that it receives when the game calls it normally (whenever a level loads). For example, you could overwrite CreateCreatures with this:
function CreateCreatures( creatureBarn )
Hack_CreatureBarn = creatureBarn -- this is the part that's been modded in
for i, v in ipairs( DynamicNodeInstances ) do
if v.Type == "Creature" then
Names[ v.ObjectName ] = creatureBarn:CreateCreature()
end
end
end
And then after the game has loaded a level at least once, you would be able to successfully call the function yourself anytime with:
CreateCreatures( Hack_CreatureBarn )