Difference between revisions of "Loading Level Objects When Level Starts"

From Journey Modding Wiki
Jump to navigation Jump to search
(basic explanation, and mostly-done WIP example script)
 
m
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:
So far, it seems the most stable way to add level objects that will interact with absolutely everything properly - especially by adding non-temporary Triggers - is to include them in the "(Object Type)Instances" tables that get loaded at the very start of the level. This is also one of the best times to edit any level object that you want to be changed before the level actually begins to run, and might be the only time you can change Triggers from one Type to another.
So far, it seems the most stable way to add level objects that will interact with absolutely everything properly - especially by adding non-temporary Triggers - is to include them in the "(Object Type)Instances" tables that get loaded at the very start of the level.  
 
This is also one of the best times to edit any level object that you want to be changed before the level actually begins to run, and might be the only time you can change Triggers from one Type to another.
 


To do this, you can add code to the Lua functions which construct objects from their ...Instances tables, so that whenever the game calls those functions at the start of a level, new object constructors are inserted and/or existing objects are changed before the objects start getting constructed.
To do this, you can add code to the Lua functions which construct objects from their ...Instances tables, so that whenever the game calls those functions at the start of a level, new object constructors are inserted and/or existing objects are changed before the objects start getting constructed.
Another option is to add code to the embedded (Object Type)Instances.lua files themselves, and make them call a function which adds/edits level objects in the table or simply loads a completely new table from a non-embedded file. Some level object types seem to be built with currently-inaccessible C functions instead of Lua functions, and this might be the only way to change those tables.
Another option is to add code to the embedded (Object Type)Instances.lua files themselves, and make them call a function which adds/edits level objects in the table or simply loads a completely new table from a non-embedded file. Some level object types seem to be built with currently-inaccessible C functions instead of Lua functions, and this might be the only way to change those tables.
'''Important note-''' To avoid causing new glitches in your game, and ''especially'' to avoid causing them in the games of non-modding companions, new objects which weren't in the original game apparently need to:
*be inserted at the end of their (Object)Instances tables
*have ObjectNames that ''do not'' start with any numbers or the letters A through F (either uppercase or lowercase).
So for example instead of naming something "ABC" or "123", then name it at least "gABC" or "g123".
<div class="toccolours mw-collapsible mw-collapsed">
<div style="font-weight:bold;line-height:1.6;">Explanation</div>
<div class="mw-collapsible-content">
Further testing is needed, but it appears that some things in the game code look for objects based on the order they were created from the table and/or their place in the alphabetical order of ObjectNames, instead of just looking for the ObjectName in the Names table. This seems especially to happen when the game is sending updates about networked objects to your companion.
So, if your objects are ordered differently than they should be because you put something new into the middle of the ordered list, then stuff won't work right, for example because the game will try to activate "object # 50" but the correct object is now "object # 55", or because it's "object # 55" in your game but is "object # 50" in your companion's game.
Alphabetical order seems important, but if so then fortunately all the game's original ObjectNames are in hex, so they only contain numbers and the letters "a" through "f" - meaning that any new ObjectName starting with a letter after "f" will come in alphabetical order after all the original objects. (Technically anything after "ff" also works, like "fgName")
There is a chance that actually ''any'' new objects will disrupt some order and cause issues in online play if companion doesn't have the exact same objects, but tests done so far had no visible problems as long as the new objects were at the end of the table and had "safe" object names.
</div></div>


=Loading objects with Lua functions=
=Loading objects with Lua functions=
Line 424: Line 444:


Run the main script, and the script which modifies the object constructor functions (or modify the embedded Lua files containing the object constructor functions so that they look like they do here, and run the main script before the game starts loading Level_Graveyard for the first time).
Run the main script, and the script which modifies the object constructor functions (or modify the embedded Lua files containing the object constructor functions so that they look like they do here, and run the main script before the game starts loading Level_Graveyard for the first time).
*Note: if your other Lua scripts run those functions after the level has started running, make sure you pass it a non-false value for "lua", for example: <code>CreateTriggers(game:eventBarn(),true)</code>  
*Note: if you run those object constructor functions in any other Lua code after the level has started, make sure you pass them a non-false value at the end for "lua", for example: <code>CreateTriggers(game:eventBarn(),true)</code>  


Then create/assign tables of level object constructors inside the Mod.LevelObjects... tables, and the object changes will occur whenever a level loads.


Create/define tables of level objects inside the Mod.LevelObjects... tables that are created by the main script.


*"Add" object constructors will create new, extra objects.
'''Table usage'''
**'''Please note''' that new objects made with Add tables should probably always have ObjectNames that don't start with numbers or letters before "g", so for example instead of naming something "Apple" then name it at least "gApple" or something:
**since number-alphabetical order references seem to be used for determining how some object-related stuff works (not sure yet though), and all the original ObjectNames are in hex so they start with numbers 0-9 or letters a-f, naming a new object like that seems to mess up the object order, which will likely cause level glitches during multiplayer and possibly also in solo play.


*"Edit" object constructors will replace the original constructor that has the same ObjectName, this can be used to change objects from the original game as well as new objects made with the "Add" tables.


*"Null" ObjectName keys will safely "delete" the object with the same ObjectName, by changing it into an object that exists but does nothing.
The "main" tables are for the different functions of the script:
*Mod.LevelObjects.'''Add''' tables will create additional new objects.
*Mod.LevelObjects.'''Edit''' tables will replace existing objects with different objects, using the same ObjectName; this can be used to change objects from the original game, as well as new objects made with the "Add" tables.
*Mod.LevelObjects.'''Null''' tables will safely "remove" objects with the designated ObjectNames, by changing them into predefined "null" objects that do nothing but technically are still there.
**This change to "nothing" takes priority over any changes done in Edit tables.
**This change to "nothing" takes priority over any changes done in Edit tables.
**The script on this page only works with triggers/timelines/clumps so far, haven't bothered figuring out "null" constructors for other object types yet.
**The script on this page only can do Null for triggers/timelines/clumps so far, haven't bothered figuring out "null" constructors for other object types yet.
 
 
Each of these function-based tables includes object-type-based tables, with the type name used by their Instances table: for example Mod.LevelObjects.Add.Trigger is for adding Triggers to TriggerInstances.
 
 
'''Specifying levels to change'''
 
 
The tables actually filled with object constructors are level-based, and are named like:
 
<code> Mod.LevelObjects.(Function).(Object Type).(Level)</code>


For tables that should only load in a specific level, use ["(the level UID).0"] as the table key, for example adding triggers to the default Chapter Select/Level_Graveyard would be:  
For tables that should only load in a specific level, use ["(the level UID).0"] as the table key, for example adding triggers to the default Chapter Select/Level_Graveyard would be:  
Line 444: Line 474:
<code>Mod.LevelObjects.Add.Trigger["0.0"]</code>
<code>Mod.LevelObjects.Add.Trigger["0.0"]</code>


For tables that should load in all levels, use All/["All"] as the table key, for example:  
For tables that should load in all levels, use ["All"]/All as the table key, for example:  


<code>Mod.LevelObjects.Add.Trigger.All
<code>Mod.LevelObjects.Add.Trigger["All"]


Mod.LevelObjects.Add.Trigger["All"]</code>
Mod.LevelObjects.Add.Trigger.All</code>




You can simply build things in the main tables, running code like:
You can create these tables directly, running code like:


<code>Mod.LevelObjects.X.Y["Z"] = { Object1, Object2, Object3 }</code>
<code>Mod.LevelObjects.X.Y["Z"] = { Object1, Object2, Object3 }</code>


But it might be more convenient to store the tables elsewhere and just swap them around, like:
But it might be more convenient to "store" the tables elsewhere and assign them around, like:
  <nowiki>
  <nowiki>
Table1 = { Object1, Object2, Object3 }
Table1 = { Object1, Object2, Object3 }
Table2 = { Object4, Object5, Object6 }
Table2 = { Object4, Object5, Object6 }
Mod.LevelObjects.X.Y["Z"] = Table1
Mod.LevelObjects.X.Y["Z"] = Table1
-- Then later you swap it:
-- Then later you decide to swap it and do:
Mod.LevelObjects.X.Y["Z"] = Table2
Mod.LevelObjects.X.Y["Z"] = Table2
</nowiki>
</nowiki>


To make a level stop using any table for a function, run code like:
 
To make a level stop changing something, run code like:


<code>Mod.LevelObjects.X.Y["Z"] = nil</code>
<code>Mod.LevelObjects.X.Y["Z"] = nil</code>


You must format the tables a little differently from what is used in (Object Type)Instances.lua's, since this function uses strings of tables rather than actual tables (the only way I found so far to avoid errors with persistent tables).


For example, (ObjectType)Instances tables are formatted like this:
'''Formatting object constructors'''
 
 
You must format these tables a little differently from what is used in (Object Type)Instances.lua files, since this script uses strings for object constructors rather than actual tables (the only way I found so far to avoid errors with persistent tables, explained more in code comments).
 
For example, (ObjectType)Instances.lua files are formatted like this:
  <nowiki>SomethingInstances =  
  <nowiki>SomethingInstances =  
{ ObjectType = "SomethingInstances"
{ ObjectType = "SomethingInstances"
Line 482: Line 517:
}</nowiki>
}</nowiki>


'But' the Mod.LevelObjects tables must be formatted like 'this':
'''But''' the ModLevelObjects tables must be formatted like '''this''':
  <nowiki>ExampleTable =  
  <nowiki>ExampleTable =  
{ ExampleName1 = [[ ObjectName = "ExampleName1"
{ ExampleName1 = [[ ObjectName = "ExampleName1"
Line 496: Line 531:




Objects in Null tables can either be formatted the same as Add/Edit, or simply as:  
Null objects can either be formatted the same as Add/Edit, or simply as:  


<code>ExampleName = true</code>
<code>ExampleName = true</code>
Line 504: Line 539:




Note that this does not yet work for Hulls or Decoration Meshes. Also, in order to work for Particle Emitters and Sound Emitters, the embedded (thing)EmitterInstances.lua's need to be edited so that they appear like:
'''Object types that need more work'''
  <nowiki>
 
(thing)EmitterInstances =
 
{  
This script does not yet work for Hulls or Decoration Meshes, see code comments.
keep this table the way it is  
 
}
Also, in order to make it work for Particle Emitters and Sound Emitters, the embedded (thing)EmitterInstances.lua files need to be edited so that they read like:
LoadModLevelObjects("(thing)Emitter")</nowiki>
  <nowiki>(thing)EmitterInstances =
{  
keep the original table the way it is  
}
LoadModLevelObjects("(thing)Emitter")</nowiki>
</div></div>
</div></div>



Latest revision as of 16:24, 23 September 2021

So far, it seems the most stable way to add level objects that will interact with absolutely everything properly - especially by adding non-temporary Triggers - is to include them in the "(Object Type)Instances" tables that get loaded at the very start of the level.

This is also one of the best times to edit any level object that you want to be changed before the level actually begins to run, and might be the only time you can change Triggers from one Type to another.


To do this, you can add code to the Lua functions which construct objects from their ...Instances tables, so that whenever the game calls those functions at the start of a level, new object constructors are inserted and/or existing objects are changed before the objects start getting constructed. Another option is to add code to the embedded (Object Type)Instances.lua files themselves, and make them call a function which adds/edits level objects in the table or simply loads a completely new table from a non-embedded file. Some level object types seem to be built with currently-inaccessible C functions instead of Lua functions, and this might be the only way to change those tables.


Important note- To avoid causing new glitches in your game, and especially to avoid causing them in the games of non-modding companions, new objects which weren't in the original game apparently need to:

  • be inserted at the end of their (Object)Instances tables
  • have ObjectNames that do not start with any numbers or the letters A through F (either uppercase or lowercase).

So for example instead of naming something "ABC" or "123", then name it at least "gABC" or "g123".

Explanation

Further testing is needed, but it appears that some things in the game code look for objects based on the order they were created from the table and/or their place in the alphabetical order of ObjectNames, instead of just looking for the ObjectName in the Names table. This seems especially to happen when the game is sending updates about networked objects to your companion.

So, if your objects are ordered differently than they should be because you put something new into the middle of the ordered list, then stuff won't work right, for example because the game will try to activate "object # 50" but the correct object is now "object # 55", or because it's "object # 55" in your game but is "object # 50" in your companion's game.

Alphabetical order seems important, but if so then fortunately all the game's original ObjectNames are in hex, so they only contain numbers and the letters "a" through "f" - meaning that any new ObjectName starting with a letter after "f" will come in alphabetical order after all the original objects. (Technically anything after "ff" also works, like "fgName")

There is a chance that actually any new objects will disrupt some order and cause issues in online play if companion doesn't have the exact same objects, but tests done so far had no visible problems as long as the new objects were at the end of the table and had "safe" object names.

Loading objects with Lua functions

Many level objects are constructed out of (ObjectType)Instances tables by Lua functions which the game calls right after it loads the table; so to add/edit objects, you just need to put extra code into those functions to change the table before anything gets constructed, but make sure that only happens when the game's C code is calling the function to initialize a level.

Example script: LoadModLevelObjects

Here is some code that can be injected or embedded to modify the original Lua functions for most level object types, providing a rough framework for adding/editing level objects when the level starts:

Main script
--Create necessary tables
Mod =
{ LevelObjects =
 { Add =
  { CameraKeyFrame = {}
  , Clump = {}
  , DecorationMesh = {}
  , DynamicNode = {}
  , EnvNode = {}
  , Hull = {}
  , Jet = {}
  , Marker = {}
  , ParticleEmitter = {}
  , Rail = {}
  , Sign = {}
  , SoundEmitter = {}
  , Timeline = {}
  , Trigger = {}
  }
 , Edit =
  { CameraKeyFrame = {}
  , Clump = {}
  ,DecorationMesh = {}
  , DynamicNode = {}
  , EnvNode = {}
  ,Hull = {}
  , Jet = {}
  , Marker = {}
  , ParticleEmitter = {}
  , Rail = {}
  , Sign = {}
  , SoundEmitter = {}
  , Timeline = {}
  , Trigger = {}
  }
 , Null = 
  { CameraKeyFrame = {}
  , Clump = { NULL = [[Objects = {} ]]}
  ,DecorationMesh = {}
  , DynamicNode = {}
  , EnvNode = {}
  , Hull = {}
  , Jet = {}
  , Marker = {}
  , ParticleEmitter = {}
  , Rail = {}
  , Sign = {}
  , SoundEmitter = {}
  , Timeline = { NULL = [[Triggers = {}, Length = 0, LoopStart = 0, LoopEnd = 0, PauseTime = 0, Looping = false, NonGracefulLooping = false, Name = ""]]}
  , Trigger = { NULL = [[Type = "NOP", Vars = {}]] }
  }
 }
}

--converts object constructor strings to tables, inserts them at end of Instances table
local function AddObjects(objType,lvl)
	local tbl = Mod.LevelObjects.Add[objType][lvl]
	if tbl then
		for k,v in pairs(tbl) do
			local obj = load("table.insert("..objType.."Instances, {"..v.."})")
			obj()
		end
	end
end

--checks if an object constructor in Instances table has same name as one in Edit table, if so it converts constructor string to table and uses that to overwrite original constructor
local function EditObjects(objType,lvl)
	local tbl = Mod.LevelObjects.Edit[objType][lvl]
	if tbl then
		for i,v in ipairs(_G[objType.."Instances"]) do
			local n = v.ObjectName
			if tbl[n] then
				local obj = load(objType.."Instances["..tostring(i).."] = {"..tbl[n].."}")
				obj()
			end
		end
	end
end

-- checks if constructor in Instances table has ObjectName matching an Edit table key that does not equal false/nil, if so overwrites it with a premade "null" constructor
-- only bothered to make this work for triggers/clumps/timelines so far
local function NullObjects(objType,lvl)
	local tbl = Mod.LevelObjects.Null[objType]
		if tbl[lvl] then
			for i,v in ipairs(_G[objType.."Instances"]) do
				local n = v.ObjectName
				if tbl[lvl][n] then
					local obj = load(objType.."Instances["..tostring(i).."] = {ObjectName = [["..n.."]], "..tbl["NULL"].."}")
					obj()
				end
			end
		end
end

-- if lua = false (which should only happen when C code calls function, if your lua script calls it make sure it's = true) 
-- then it does the add/edit/null functions with the "all levels" table and then the current level's table
function LoadModLevelObjects(objType,lua)
	if not lua then
		local uid = tostring(game:GetCurrentLevelNumber())
		AddObjects(objType,"All")
		AddObjects(objType,uid)
		EditObjects(objType,"All")
		EditObjects(objType,uid)
		NullObjects(objType,"All")
		NullObjects(objType,uid)
	end
end

-- These functions are set up so that objects are stored as strings and then converted to tables when needed:
-- if simply stored as tables, then any variable defined as equal to Names["Something"] or a formula or other
-- non-static value will always be equal to whatever it was at the time the table was created, which can
-- very likely be a nil value or something else wrong, so it breaks or crashes the game.
-- There's probably better ways to do this then storing strings, but this works for now.


Script to modify/overwrite the original level-object-constructor functions:
-- The only modification done to these functions was adding the "lua" function variable (which is automatically false when function is called by C code),
-- and putting "LoadModLevelObjects((the object type as string),lua)" at the start of the function.

-- CAMERA KEY FRAMES, from CameraInit.lua

function LoadCameraKeyFrames( cameraSys, lua )
	LoadModLevelObjects("CameraKeyFrame",lua)
	
	for i, desc in ipairs( CameraKeyFrameInstances ) do
		local camKey = cameraSys:AllocateCameraKey()
		CameraKeyFrameInitialize( cameraSys, camKey, desc )

		-- Now triggers can refer to gardener-placed camera keyframes
		Names[ desc.ObjectName ] = camKey 
	end

    CameraKeyFrameInstances = {} 
end

-- CLUMPS, from ClumpLoader.lua

function LoadClumps( clumpBarn, lua )
	LoadModLevelObjects("Clump",lua)
	
	--print("LoadClumps \n")
	--print( #ClumpInstances )

	for i,ClumpDesc in ipairs( ClumpInstances ) do
		local clumpPtr = clumpBarn:CreateClump(  )
		--clumpBarn:AllocateClumpStorage( clumpPtr, #ClumpDesc.Objects )

        --Fill in the name table so triggers can refer to clumps
		Names[ ClumpDesc.ObjectName ] = clumpPtr

	end
end

--build clumps out of the resolved name table
function SetClumpMembers( clumpBarn, lua )
	LoadModLevelObjects("Clump",lua)

	for i,ClumpDesc in ipairs( ClumpInstances ) do
	
		-- Traverse the clump table and compute how many objects will be in the clump
		local clumpSize = 0
		for j,ClumpMember in ipairs( ClumpDesc.Objects ) do
			if type( Names[ ClumpMember ] ) == "table" then
				clumpSize = clumpSize + #Names[ ClumpMember ]
			else
				clumpSize = clumpSize + 1
			end
		end
		
		clumpBarn:AllocateClumpStorage( Names[ ClumpDesc.ObjectName ], clumpSize )
	
		local idx = 0
	    for j,ClumpMember in ipairs( ClumpDesc.Objects ) do
			-- @Added by Nick on 3.17.08 to support multiple objects at one Names table entry
			if type( Names[ ClumpMember ] ) == "table" then
				for k,SubMember in ipairs( Names[ ClumpMember] ) do
					local classPtr = Class.cast( SubMember )
					Names[ ClumpDesc.ObjectName ]:SetAt( idx, classPtr )
					idx = idx + 1
				end
			else
				local classPtr = Class.cast( Names[ ClumpMember ] )
				Names[ ClumpDesc.ObjectName ]:SetAt( idx, classPtr )
				idx = idx + 1
			end	   
		end

    end

    --Free the memory taken by the ClumpInstances table
    ClumpInstances = {}
end

-- DECORATION MESHES, from DecorationMeshes.lua(?)
-- needs more work to fit in LoadModLevelObjects function, since it's not loaded the same way with a "DecorationMeshInstances" etc
-- is loaded from .bin's that are already outside the .exe and editable anyways

-- DYNAMIC NODES/CREATURES, from Creatures.lua

function CreateCreatures( creatureBarn, lua )
	LoadModLevelObjects("DynamicNode",lua)
	
	for i, v in ipairs( DynamicNodeInstances ) do
		if v.Type == "Creature" then
			Names[ v.ObjectName ] = creatureBarn:CreateCreature()
		end
 	end
end

function ResolveCreatureNames( creatureBarn, lua )
	LoadModLevelObjects("DynamicNode",lua)

	for i, v in ipairs( DynamicNodeInstances ) do
		if v.Type == "Creature" then
			CreatureInitialize( Names[ v.ObjectName ], v )
			Names[ v.ObjectName ]:gardenerName( v.GardenerName )
		end
 	end
end

-- ENVIRONMENT NODES, from Environment.lua

function LoadEnvNodes( env, lua )
	LoadModLevelObjects("EnvNode",lua)

	for i, desc in ipairs( EnvNodeInstances ) do
		local envNode
		if desc.IsZNode then
			envNode = env:AddZNode( desc.ZPos, desc.Radius )
		else
			local ignoreLightDir = true
			if desc.Env_IgnoreLightDir ~= nil then
				ignoreLightDir = desc.Env_IgnoreLightDir
			end
			envNode = env:AddVolumeNode( desc.MinBoundingBox.Position, desc.MinBoundingBox.Extent, desc.MaxBoundingBox.Extent, desc.FadeDuration, ignoreLightDir )
		end

		if envNode == nil then
			print( "*** Error loading env node "..desc.ObjectName )
		else
			EnvNodeInitialize( envNode, desc )

			--load the envNode into the name table to be accessible to the trigger system
			Names[ desc.ObjectName ] = envNode
		end
	end
	--For some reason, this causes a crash.. not fixing right now
	--EnvNodeInstances = {}
end

-- HULLS/REGIONS, from HullBarn.lua(?)
-- needs more work to fit in LoadModLevelObjects function, since it's not loaded the same way with a "HullInstances" etc
-- is loaded from .bin's that are already outside the .exe and editable anyways

-- JETS, from Jets.lua

function LoadJets( JetBarn, lua )
	LoadModLevelObjects("Jet",lua)

	for i, v in ipairs( JetInstances ) do
		local jetPtr = JetBarn:CreateJet()

		JetInitialize( jetPtr, v )
		
        --for Events/Triggers
		Names[ v.ObjectName ] = jetPtr 

	end
	
	JetInstances = {}
	
end

-- MARKERS, from Markers.lua

function LoadMarkers( MarkerBarn, lua )
	LoadModLevelObjects("Marker",lua)

	for i, v in ipairs( MarkerInstances ) do
		local markerPtr = MarkerBarn:CreateMarker()
		MarkerInitialize( markerPtr, v )
		Names[ v.ObjectName ] = markerPtr
	end
	
	MarkerInstances = { }
end

-- PARTICLE EMITTERS, from EnvFX.lua and/or ParticleBarn.lua(?)
-- the obvious function in EnvFX.lua is not actually used, not sure how to rig something together from other Lua-accessible stuff yet
-- for now, need to put LoadModLevelObjects("ParticleEmitter") at the very end of embedded ParticleEmitterInstances.lua's

-- RAILS, from RailBarn.lua

function LoadRails( railBarn, lua )
	LoadModLevelObjects("Rail",lua)
	
	for i, desc in ipairs( RailInstances ) do
		local rail = railBarn:CreateRail()

		railBarn:AllocateRailPointStorage( rail, #desc.RailDataPoints )
		railBarn:AllocateRailArcParam( rail )

		RailInitialize( rail, desc )

        --Fill in the name table so triggers and clumps can refer to rails
		Names[ desc.ObjectName ] = rail
	end
end

-- SIGNS/SCROLLING TEXT, from SignBarn.lua
-- this only lets you change position/appearance of Signs, not the actual text
-- text comes from SignData.lua which goes into "Credits" table, not "SignInstances"
-- need to rework LoadModLevelObjects functions to accomodate that; low priority so skipped it for now

function InitializeSignBarn( signBarn, signData, signInstances, lua )
	LoadModLevelObjects("Sign",lua)
	
	local credSec, textEnt

--  load the text entries from SignData.lua

	for i,sec in ipairs( Credits ) do
		credSec = signBarn:AddCreditSection( sec.SectionName, #sec.Entries )
		for j,ent in ipairs( sec.Entries ) do
			textEnt = credSec:GetEntry( j-1 )
			signBarn:SetTextEntry( textEnt, ent.Text )  
		end
	end

-- SignInstances.lua
-- iterate through the sign instances and tie the signs to the credit entries

    --local color = { 255, 128, 64, 255 }

	for i,signInst in ipairs( SignInstances ) do
		if signInst.secName ~= "" then
			textEnt = signBarn:FindTextEntry( signInst.secName, signInst.entNum )
		else
			textEnt = signBarn:GetTextEntry( signInst.secNum, signInst.entNum )		
		end 
		
		local sign = signBarn:AddSignTextEntry( textEnt )
		SignInitialize( sign, signInst )
		
		Names[ signInst.ObjectName ] = sign
	end
	
	SignInstances = {}
	Credits = {}
end

-- SOUND EMITTERS, from SoundBarn.lua(?)
-- the obvious function in SoundBarn.lua is not actually used, not sure how to rig something together from other Lua-accessible stuff yet
-- for now, need to put LoadModLevelObjects("SoundEmitter") at the very end of embedded SoundEmitterInstances.lua's

-- TIMELINES, from TimelineBarn.lua

function LoadTimelines( timelineBarn, lua )
	LoadModLevelObjects("Timeline",lua)

	for i, info in ipairs( TimelineInstances ) do
		local timeline = timelineBarn:CreateTimeline()
		TimelineInitialize( timeline, info )
		Names[ info.ObjectName ] = timeline
	end
end

--TRIGGERS/EVENTS, from EventSetter.lua

function CreateTriggers( eventBarn, lua )
	LoadModLevelObjects("Trigger",lua)

	-- This double loop is necessary so Triggers can refer to other Triggers by name
	for i,TrigDesc in ipairs( TriggerInstances ) do
		local event = eventBarn:MetaAddEvent( TrigDesc.Type )
        -- grab the actual function out of the global variable table
		local eventImplPtr = _G[ TrigDesc.Type ].cast( event )

		Names[ TrigDesc.ObjectName ] = eventImplPtr
	end
end

--reload the TriggerInstances.Lua file before this gets called
function ResolveTriggerNames( eventBarn, clumpBarn, lua )
	LoadModLevelObjects("Trigger",lua)

	TriggerShortcuts = {}
	for i,TrigDesc in ipairs( TriggerInstances ) do
		TriggerInitialize( Names[ TrigDesc.ObjectName ], TrigDesc )
	end

	--To forcibly consume the Triggers
	TriggerInstances = {}
end

How to use

Run the main script, and the script which modifies the object constructor functions (or modify the embedded Lua files containing the object constructor functions so that they look like they do here, and run the main script before the game starts loading Level_Graveyard for the first time).

  • Note: if you run those object constructor functions in any other Lua code after the level has started, make sure you pass them a non-false value at the end for "lua", for example: CreateTriggers(game:eventBarn(),true)

Then create/assign tables of level object constructors inside the Mod.LevelObjects... tables, and the object changes will occur whenever a level loads.


Table usage


The "main" tables are for the different functions of the script:

  • Mod.LevelObjects.Add tables will create additional new objects.
  • Mod.LevelObjects.Edit tables will replace existing objects with different objects, using the same ObjectName; this can be used to change objects from the original game, as well as new objects made with the "Add" tables.
  • Mod.LevelObjects.Null tables will safely "remove" objects with the designated ObjectNames, by changing them into predefined "null" objects that do nothing but technically are still there.
    • This change to "nothing" takes priority over any changes done in Edit tables.
    • The script on this page only can do Null for triggers/timelines/clumps so far, haven't bothered figuring out "null" constructors for other object types yet.


Each of these function-based tables includes object-type-based tables, with the type name used by their Instances table: for example Mod.LevelObjects.Add.Trigger is for adding Triggers to TriggerInstances.


Specifying levels to change


The tables actually filled with object constructors are level-based, and are named like:

Mod.LevelObjects.(Function).(Object Type).(Level)

For tables that should only load in a specific level, use ["(the level UID).0"] as the table key, for example adding triggers to the default Chapter Select/Level_Graveyard would be:

Mod.LevelObjects.Add.Trigger["0.0"]

For tables that should load in all levels, use ["All"]/All as the table key, for example:

Mod.LevelObjects.Add.Trigger["All"]

Mod.LevelObjects.Add.Trigger.All


You can create these tables directly, running code like:

Mod.LevelObjects.X.Y["Z"] = { Object1, Object2, Object3 }

But it might be more convenient to "store" the tables elsewhere and assign them around, like:

Table1 = { Object1, Object2, Object3 }
Table2 = { Object4, Object5, Object6 }
Mod.LevelObjects.X.Y["Z"] = Table1
-- Then later you decide to swap it and do:
Mod.LevelObjects.X.Y["Z"] = Table2


To make a level stop changing something, run code like:

Mod.LevelObjects.X.Y["Z"] = nil


Formatting object constructors


You must format these tables a little differently from what is used in (Object Type)Instances.lua files, since this script uses strings for object constructors rather than actual tables (the only way I found so far to avoid errors with persistent tables, explained more in code comments).

For example, (ObjectType)Instances.lua files are formatted like this:

SomethingInstances = 
{ ObjectType = "SomethingInstances"
, { ObjectName = "ExampleName1"
  , Variable1 = "whatever"
  , Variable2 = "etc."
  }
, { ObjectName = "ExampleName2"
  , all the other stuff
  }
}

But the ModLevelObjects tables must be formatted like this:

ExampleTable = 
{ ExampleName1 = [[ ObjectName = "ExampleName1"
  , Variable1 = "whatever"
  , Variable2 = "etc."
 ]]
, ExampleName2 = [[ ObjectName = "ExampleName2"
  , all the other stuff
 ]]
}

( I know doubling the ObjectName in the key is redundant, but using the ObjectName as the key makes the code more efficient (I think) because there's less for/do loops needed; and keeping the ObjectName in the constructor too will make it easier to convert already-made lists back into normal tables once it's figured out how to make the function just use normal tables without breaking. )


Null objects can either be formatted the same as Add/Edit, or simply as:

ExampleName = true

Also, if any table key starts with a number (which many of the original ObjectNames do) it seems the key needs to be wrapped in [" "], like: ["123ExampleName"] = all the stuff


Object types that need more work


This script does not yet work for Hulls or Decoration Meshes, see code comments.

Also, in order to make it work for Particle Emitters and Sound Emitters, the embedded (thing)EmitterInstances.lua files need to be edited so that they read like:

(thing)EmitterInstances =
 { 
 	keep the original table the way it is 
 }
 LoadModLevelObjects("(thing)Emitter")
Examples of usage

Run the LoadModLevelObjects scripts.

Run this code to create a table containing triggers that will make text appear on your screen when you start a level:

TextAtStart = 
{ TriggerTheText = [[ ObjectName = "TriggerTheText"
 , Type = "EventOnLevelStart"
 , Vars = {	triggers = Names["ShowText"] }
 ]]
, ShowText = [[ ObjectName = "ShowText"
 , Type = "DisplayText"
 , Vars = { duration = 7
		  , text = "Hello world!"
		  }
 ]]
}

Run this code to assign that table, so that the text appears at the start of every level:

Mod.LevelObjects.Add.Trigger.All = TextAtStart

Run this code to create a table containing triggers that will make different text appear, and it instead happens the first time in a level that you reach max scarf length:

TextOnMaxScarf =
{ TriggerTheText = [[ ObjectName = "TriggerTheText"
 , Type = "EventOnCapeLength"
 , Vars = { capeLength = 30	
		  , effects = Names["ShowText"]
		  , fireOnce = true
		  }
 ]]
, ShowText = [[ ObjectName = "ShowText"
 , Type = "DisplayText"
 , Vars = { duration = 7
		  , text = "Hello scarf!"
		  }
 ]]
}

Run this code to assign that table, so that different text appears and it happens when you get max scarf, but only in Paradise:

Mod.LevelObjects.Edit.Trigger["7.0"] = TextOnMaxScarf

Run this code to make a table simply for "removing" the trigger object which shows the text:

NoText = { ShowText = true }

Run this code to assign that table, to "remove" that text trigger in all levels - although the activation trigger still exists, so for example if it instead was made to activate a clump that contained that text trigger, everything else in the clump besides the text trigger would still work normally:

Mod.LevelObjects.Null.Trigger.All = NoText

Loading objects by editing (Object)Instances.lua

Rather than putting code into existing Lua functions, you can put a small amount of code into the embedded (Object)Instances.lua files to change the table immediately when it's loaded, though you would have to do this for each different level and each object type you wanted to manipulate this way.

This is inconvenient but right now seems to be the only way to edit Particle Emitters or Sound Emitters, since the function that converts their (Object)Instances table into level objects seems to be done through C code instead of Lua functions... some Lua functions exist which seem built for this task but the game never calls them, and no way has been found yet to access the necessary Barn class objects to make the functions work.


See the "[PC] Lua Script Editing / Injection" thread in "How to Mod" on Journey fan forum ( https://thatgamecompanyfan.boards.net/board/9/modding ) for details of how to actually change the embedded Lua files.


The files should be edited so that at least these comments at the start of the file are deleted:

-- THIS FILE IS AUTOMATICALLY GENERATED.

-- DO NOT MODIFY THIS FILE!

That gives you just enough free space to fit in some useful code, especially if you use the maximum possible compression.

To get even more space, you could delete:

  • unnecessary spaces/indents/newlines etc
  • GardenerName variables, which seem to either be totally unused or do nothing that affects gameplay
  • at least some variables (like Vars for Triggers) that say "variable = false" or "variable = 0", which will not affect how the object gets constructed, unless those variables have default values other than false/zero (can check that in MetaSystem)


Once you have enough space, you can add some code at the end to call something which modifies the (Object)Instances table right after it gets made, for example if using the LoadModLevelObjects script above you would change the code to something like:

ObjectTypeInstances =
{
	(this whole table stays the same)
}
LoadModLevelObjects("ObjectType")

Or you could set a condition to just load a completely different table instead, for example:

if UseDifferentTable == true then ObjectTypeInstances = DifferentTable
-- OR: if UseDifferentTable = true then dofile("a modified ObjectTypeInstances.lua file outside the .exe")
-- although that by itself would be insecure and most likely a bad idea for mods you plan to distribute publicly
else ObjectTypeInstances =
{
	(this whole table stays the same)
}
end