Loading Level Resources With Lua
This is what has been found so far to add and/or modify level objects (during a level) and other level resources using a Lua script, along with brief descriptions of what these objects/resources do.
See Loading Level Objects When Level Starts for info on adding/modifying level objects "the normal way" right as the level begins, which is generally more stable.
(This page probably should be split into two pages about "Loading Level Resources" and "Modifying Level Objects During a Level" etc)
Loading Resource Files
One way to change a level is to change what resources are loaded for the level; for example, loading a different "TriggerInstances" resource to load a different set of Triggers. One way to do this 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 execute this code, 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 files embedded in Journey.exe, but all other resources are loaded from the game folders.
Keep in mind that some types of level objects reference each other in a way that will cause the game to crash if something is wrong/missing, so some resources can only be safely replaced if you properly replace certain others as well.
If you try to load a resource that doesn't exist, some resource types will allow it and just use nothing for 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 Level Objects
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 level objects 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 level-object-loading functions, this appears to always be the order that level objects get loaded into the Names table, in all levels. You should probably use the same order when adding/modifying different types of level objects 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 level objects or other things you want ready before the level actually "starts"; though for level object mods that you want ever since the level starts, it's probably better to just insert them directly into the initial object table that gets loaded
- 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 during a level, but it can be done at the start of a level. 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.
You can also change the values of individual Vars in a Trigger if they are basic data types (like a boolean, number, string, etc - not a Names["Something"] reference) by simply running: Names["ObjectName"][ "VarName" ]( Names["ObjectName"], NewVarValue )
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 apparently 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. Based on other research, the triggers might be permanent if you run the same TriggerInstances = { whatever } code again before you run ResolveTriggerNames, but this hasn't been tested.
If that doesn't work, 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 that instantly activates and then is removed from memory by Lua garbage collection after it's done doing its stuff ( 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 are point locations, which objects can be aimed at/away from, moved towards/away from, or attached to by certain Triggers.
Markers can 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() )
Doing the above works to modify Markers that already exist, but might clutter up memory by just replacing Names table entries with new Markers, and the old ones aren't garbage collected... not yet figured out.
Markers can also be modified by running this code:
MarkerInitialize( Names["The Marker's ObjectName"], { an individual Marker constructor table })
Or individual variables for Markers can be modified by running Names["The Marker's ObjectName"]:function with functions:
SetPosition{X, Y, Z}= set position at XYZ coordinatesSetQuat{a, b, c, d}= set orientation/angle to that quaternion. Is used for certain Triggers/etc that do stuff like "set some other object's angle to the quaternion of this Marker".SetScale{X, Y, Z}= sets "scale" in XYZ axis. Markers are points so their actual size is always "infinitely small", in terms of anything that Triggers/etc check about whether the Marker is touching something/inside some area, etc; the scale is just used for stuff like Triggers that say "set some object's scale to the scale of this Marker".enabled(true or false)= whether the Marker is enabled (assumedly if it's disabled, then any Triggers/etc that "do something" relating to the Marker will not do that thing; not tested)
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: most collision data comes from physical Hulls put in the same place as the mesh, and for cloth objects the collision/interaction mechanics are set by certain types of Triggers.
You can add/change Decoration Meshes loaded at start of level by editing DecorationMeshInstances.lua.bin in the folder for that level under Data/Scripts. See Paleologos's guide on Journey fan forum modding board.
Decoration Meshes that exist can probably be modified by running:
DecorationInitialize( game:resources(), Names["The DM's ObjectName", {a table defining the DM, see below}] )
They can definitely have individual variables modified by running Names["The DM's ObjectName"]:function with the same kind of code found inside the DecorationInitialize function (found in DecorationMeshes.lua);
for example Names["ObjectName"]:SetMatrix( Transformation formatted like below ) will change the rotation/scale/position of the mesh.
Here is basically how the Decoration Mesh constructor table is formatted (this is the Sphinx in Level_Graveyard):
{
ObjectName = "ecd9205156899a8ad85b0dc3eb91313c"
, GardenerName = "|Decoration549|Decoration549_G"
, Mesh = "P_GraveyardGateRaySphinx"
, FadeMin = 200
, FadeMax = 220
, ShaderLODMin = 15
, ShaderLODMax = 30
, Brightness = 1
, Saturation = 1
, Hue = 0
, Transformation = { {4.44089e-016, 0, 1, 0}, {-0, 1, 0, 0}, {-1, -0, 4.44089e-016, 0}, {253.055, 6.07769, 491.756, 1} }
, Shader = "MeshCham"
, RenderMode = "Solid"
, ShaderParams =
{ { ParamType = "float3"
, ParamName = "uvMlt"
, ParamVal = { 1, 1, 1 }
}
, { ParamType = "texture"
, ParamName = "texColor"
, ParamVal = "Sphinx"
}
, { ParamType = "texture"
, ParamName = "texDetail"
, ParamVal = "Warm"
}
, { ParamType = "texture"
, ParamName = "texRamp"
, ParamVal = "DesertShadowRamp"
}
, { ParamType = "texture"
, ParamName = "texAo"
, ParamVal = "White"
}
, { ParamType = "texture"
, ParamName = "texEdgeMask"
, ParamVal = "White"
}
, { ParamType = "float"
, ParamName = "inShadow"
, ParamVal = 0
}
, { ParamType = "float3"
, ParamName = "aoStepBiasInt"
, ParamVal = { 0, 0, 0 }
}
, { ParamType = "float"
, ParamName = "chamLevel"
, ParamVal = 11
}
, { ParamType = "float3"
, ParamName = "chamDir"
, ParamVal = { 0, 1, 0 }
}
, { ParamType = "texture"
, ParamName = "texCham"
, ParamVal = "SphinxSymbol"
}
, { ParamType = "texture"
, ParamName = "texChamMask"
, ParamVal = "BlurredNoise"
}
, { ParamType = "float3"
, ParamName = "chamColor"
, ParamVal = { 15, 15, 15 }
}
, { ParamType = "float3"
, ParamName = "chamEdgeColor"
, ParamVal = { 3.5, 2.21585, 1.3055 }
}
, { ParamType = "float2"
, ParamName = "uvOffset"
, ParamVal = { 0, 0 }
}
}
}
Stuff known so far about adding new objects during the level:
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 a DecorationMeshInstances table to Decorations and load them into the Names table, but there is no DecorationMeshInstances table ever created(?), plus 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.
Rails
Rails are invisible lines drawn between a set of point locations, which may be "closed", as in the last point connects back to the first point. They seem to mostly be used as a path for an object/etc to move along, but are also used for other kinds of things that are based on a "2D" line in space - like some wind walls are an infinite flat plane stretching up and/or down from a Rail.
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 objects/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 3D regions of space, which are used as collision geometry and/or a way for Triggers to determine "if this thing is touching/inside this region, do this event", and also can have some built-in properties like being wind walls, etc.
You can change the Hulls loaded at start of level by editing HullInstances.lua.bin in the folder for that level under Data/Scripts.
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, or editing the code for QueueResourcesForLoad() to change the sound bank resource in the resource table it receives at the start of the level.
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 level objects 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 )
--Mod
Hack_CreatureBarn = creatureBarn
--/Mod
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 )