rezecib

[Guide] Getting started with modding DST (and some general tips for DS as well)

Recommended Posts

PeterA    1,671

The sun rises in the east, sets in the west, and @rezecib hands out solid programming advice. Two thumbs up in agreement from me about not polluting the global namespace.

  • Like 2

Share this post


Link to post
Share on other sites
Snowhusky5    80
local OldGetPosition = inst.GetPositioninst.GetPosition = function(thing)    local pos = OldGetPosition(thing)    pos.x = math.floor(pos.x+0.5)    pos.y = math.floor(pos.y+0.5)    pos.z = math.floor(pos.z+0.5)    return posend

If we try to do something similar to this (replacing a function in the game with a modded one that calls the original and then does other stuff), where exactly would we put this in the mod files? modmain does not work since 'inst' is not defined when you copy the old function. I'm pretty sure that this would work in a prefab, but would it only work for that specific prefab or everywhere that this function is used?

Edited by Snowhusky5

Share this post


Link to post
Share on other sites
rezecib    3,078

@Snowhusky5, Look at Always On Status (the DST version) for a bunch of examples. You use the AddPrefabPostInit, AddComponentPostInit, AddClassPostConstruct functions in the modmain to pass a function that takes inst, self, self as arguments (one for each, respectively), and then you can do it within those.

Share this post


Link to post
Share on other sites
Snowhusky5    80

@Snowhusky5, Look at Always On Status (the DST version) for a bunch of examples. You use the AddPrefabPostInit, AddComponentPostInit, AddClassPostConstruct functions in the modmain to pass a function that takes inst, self, self as arguments (one for each, respectively), and then you can do it within those.

Thanks, AddComponentPostInit is exactly what I needed.

Share this post


Link to post
Share on other sites
Muche    295
1 hour ago, HomShaBom said:

How come every single code block in this post has everything written out on a single line?

Because the guide was written a while ago and the forum upgrade, which was done in January, stripped all newline characters from code blocks.

Share this post


Link to post
Share on other sites
rezecib    3,078

I would like to fix the formatting of the post, but unfortunately there's still no source editor in the current forum, which makes editing it very time-consuming.

  • Like 1

Share this post


Link to post
Share on other sites
Layarion    1
On 12/22/2014 at 7:28 PM, rezecib said:

 

Fix the single lined madness by taking pictures of the code instead. i CANNOT read this BS and take your guide seriously.

Edited by Layarion

Share this post


Link to post
Share on other sites
IvanX    33

Nice guide, it's missing a few very important points thou..

 

For example, you've mentioned that DST and DS have different initialization order, on top of that first world init has different initialization order than not first one. So looking at a couple of cases, we'll have the following for most common events

DS, first launch:

1. AddPlayerPostInit
2. AddSimPostInit
3. AddClassPostInit
4. AddGamePostInit

DS, sequential launch:

1. AddPlayerPostInit
2. AddGamePostInit
3. AddSimPostInit
4. AddClassPostInit

DST has the same order in both first and any sequential launches, but I still would rather not trust the fixed ordering:

1. AddSimPostInit
2. AddGamePostInit
3. AddPlayerPostInit
4. AddClassPostInit

What does all of this mean? Usually the correct order of the mod program code would be to

1. get all of the required data
2. launch our initialize using all of that data

But the problem is, how do you determine when to launch that initialize? Simple, just add a global boolean variable for each of these post inits, aka

local _GameAdded = false
local _SimAdded = false
local _ClassAdded = false
local _PlayerAdded = false

Inside of every function that you use to gather data, set up one of those variables to true. Then fire OnAfterLoad() on the last line of the function.

! Note that since AddClassPostInit is fired last, for example you manipulate player.HUD.controls class, you would not be able to call player.HUD.controls. inside of the OnAfterLoad, thus you must pass it as an argument to OnAfterLoad(self)

Inside of the OnAfterLoad function, just check that all of those functions finished running aka Added, then launch your code.

Final code looks like this, and it does the same in both DS and DST.

local functon OnAfterLoad(ctrls)
	if not _SimAdded or not _GameAdded or not _PlayerAdded or not _ControlsAdded then
		return false
	end
	local controls = GetPlayer().HUD.controls or ctrls
	-- do stuff with all the gathered data
	...
end

local function AddSimPostInit()
 	-- do stuff with sim
	...	
	_SimAdded = true
	OnAfterLoad()
end

local function AddGamePostInit()
 	-- do stuff with Game
	...	
	_GameAdded = true
	OnAfterLoad()
end

local function AddPlayerPostInit()
 	-- do stuff with Player
	...	
	_PlayerAdded = true
	OnAfterLoad()
end

local function AddControlsPostInit(self)
 	-- do stuff with controls
	...	
	_ControlsAdded = true
	OnAfterLoad(self)
end
	

 

Edited by IvanX

Share this post


Link to post
Share on other sites
IvanX    33

Another Point is AddPlayerPostInit

That does not work in DST anymore, because if you write a local mode, you definitely only want your very player, but the function fires for all of the players. Unfortunately for you ThePlayer is NOT YET DEFINED within AddPlayerPostInit, because that is how constructors work, until you finish running one, the class is not yet out in the globals.

Thus

local function AddPlayerPostInit(player)
	if GLOBALS.ThePlayer == player then
		...

WILL NOT WORK.

The solution is rather complicated, I've ninjaed it from this thread, thanks to @Kam297

AddPrefabPostInit("world", function(wrld)    wrld:ListenForEvent("playeractivated", function(wlrd, player)        if player == GLOBAL.ThePlayer then            player:AddComponent("myclientsidecomponentwhichdoesntequireanyserversidestuff")        end    end)end)

So to make it both DS and DST compatible you use The AddPrefabPostInit+ListenForEvent in DST and simply use AddPlayerPostInit in DS
 

if IsDST() then
  AddPrefabPostInit("world", ...)
else
  AddPlayerPostInit(...)
end

 

 

Share this post


Link to post
Share on other sites
Muche    295

@IvanX, I've tried simple (DST)

AddGamePostInit(function()
	print("[Test|GamePostInit]")
end)
AddSimPostInit(function()
	print("[Test|SimPostInit]")
end)

, started hosting a world (got SimPostInit, then GamePostInit); disconnected, started it again and the order didn't change (SimPostInit, then GamePostInit). Are there more conditions to what you describe as sequential launch?

  • Like 1

Share this post


Link to post
Share on other sites
IvanX    33

Note thou, that this will, YET AGAIN, change the priority for launching, but the OnAfterLoad hack should work here as well.

AddPrefabPostInit + ListenForEvent will fire last of all

Share this post


Link to post
Share on other sites
IvanX    33
10 minutes ago, Muche said:

@IvanX, I've tried simple (DST)


AddGamePostInit(function()
	print("[Test|GamePostInit]")
end)
AddSimPostInit(function()
	print("[Test|SimPostInit]")
end)

, started hosting a world (got SimPostInit, then GamePostInit); disconnected, started it again and the order didn't change (SimPostInit, then GamePostInit). Are there more conditions to what you describe as sequential launch?

Hmm, guess I should test it again, could be that it's running in the fixed order in DST now, did not do sufficient testing, as it is completely different order in DS and for a joint mod I still need to determine which one is fired last. Thanks :)

P.S. I would not trust them running in a fixed order anyways. The way I look at it, it's very simillar to expecting pairs to run in the given order. In some cases it may be true, in some, there's no really fixed order set.

Edited by IvanX

Share this post


Link to post
Share on other sites
IvanX    33

One more very important thing:

AddGamePostInit, AddPlayerPostInit, AddSimPostInit, AddClassPostInit, AddPrefabPostInit

For game host, all of those methods fire BEFORE OnLoad methods of components. While OnLoad method of component will NOT fire if the component is added for the first time.

So the only way to execute some code after making sure the component has been loaded is by using the "playeractivated hack"

AddPrefabPostInit("world", function(wrld)
	wrld:ListenForEvent("playeractivated", function(wlrd, player)
		if player == GLOBAL.ThePlayer then
			-- launch this code very very late, after everything's been initialized
		end
	end)
end)

Not too convenient tbh.

Share this post


Link to post
Share on other sites
IvanX    33

Here's an extremely useful set of polyfills, that is a must have on any DS-DST client mod.

Just put it at the top of modmain.lua:

local function IsDST()
	return GLOBAL.TheSim:GetGameID() == "DST"
end

local function IsClientSim()
	return IsDST() and GLOBAL.TheNet:GetIsClient()
end

local function GetPlayer()
	if IsDST() then
		return GLOBAL.ThePlayer
	else
		return GLOBAL.GetPlayer()
	end
end

local function GetWorld()
	if IsDST() then
		return GLOBAL.TheWorld
	else
		return GLOBAL.GetWorld()
	end
end

local function AddPlayerPostInit(fn)
	if IsDST() then
		env.AddPrefabPostInit("world", function(wrld)
			wrld:ListenForEvent("playeractivated", function(wlrd, player)
				if player == GLOBAL.ThePlayer then
					fn(player)
				end
			end)
		end)
	else
		env.AddPlayerPostInit(function(player)
			fn(player)
		end)
	end
end

IsDST - true for DST, false for DS

IsClientSim - true for DST client, false for DST mastersim, falst for DS

GetPlayer,GetWorld and AddPlayerPostInit - are polyfills for simillar functions in DS. AddPlayerPostInit fires only once for your very player.

Share this post


Link to post
Share on other sites
IvanX    33

And here's a code snippet on how to save component data in a DS-DST client mode:

local function IsDST()
    return TheSim:GetGameID() == "DST"
end

local function IsClientSim()
    return IsDST() and TheNet:GetIsClient()
end

local MyComponent = Class(function(self)
  ...
  if IsClientSim() then
    self._filepath = "session/"..(TheNet:GetSessionIdentifier() or "INVALID_SESSION").."/"..(TheNet:GetUserID() or "INVALID_USERID").."_/MyComponent_data"
      self:OnLoad()
  end
end)

function KnownFoods:OnSave()
  local data = {
    -- assign data here
    ...
  }
  if IsClientSim() then
    local str = json.encode(data)
    TheSim:SetPersistentString(self._filepath, str, true)
  else
    return data
  end
end

function KnownFoods:OnLoad(data)
  if IsClientSim() then
    -- ClientSim load uses PersistentString data
    TheSim:GetPersistentString(self._filepath, function(success, strdata)
      if success then
        data = json.decode(strdata)
      end
    end)
  else
    -- MasterSim uses OnLoad data
  end

  if data then
    -- manipulate data here
    ...
    print('MyComponent ~~~ component loaded with ... data')        
  else
    print('MyComponent ~~~ component loaded with no data')
  end
end

How does it work? For DS and DST MasterSim we use the common OnSave and OnLoad functions, as for the ClientSim: first of all we need to force launch OnLoad at the end of the component constructor, since OnLoad does not fire for client only mods. As for the data, instead of sending it to the server, we pack it up to json in OnSave and save it into client's documents folder, then we do the opposite for OnLoad.

Hope somebody's going to find this useful.

Edited by IvanX

Share this post


Link to post
Share on other sites
Joachim    339
On 7/2/2016 at 10:31 PM, Layarion said:

Fix the single lined madness by taking pictures of the code instead. i CANNOT read this BS and take your guide seriously.

I don't see what's wrong with it, though? Taking pictures of code is even more inconvenient.

  • Like 1

Share this post


Link to post
Share on other sites
Layarion    1
On 7/28/2016 at 5:18 AM, Joachim said:

I don't see what's wrong with it, though? Taking pictures of code is even more inconvenient.

it is very difficult to follow

Share this post


Link to post
Share on other sites
Tykvesh    44

Does new ListenForEvent overwrites the old one if their trigger event is the same?

Share this post


Link to post
Share on other sites
PanAzej    1,848
Posted (edited)
44 minutes ago, Tykvesh said:

Does new ListenForEvent overwrites the old one if their trigger event is the same?

Nope.

If you wanted to replace an eventlistener by making a new one, well, it's not really possible. You gotta make some workarounds. Eventlisteners are the most annoying thing you have to work around, tbh.

Edited by PanAzej
  • Like 2

Share this post


Link to post
Share on other sites
Tykvesh    44
Posted (edited)
On 08.03.2017 at 2:27 AM, PanAzej said:

If you wanted to replace an eventlistener by making a new one, well, it's not really possible. You gotta make some workarounds. Eventlisteners are the most annoying thing you have to work around, tbh.

Thanks for reply. Also, is it possible to get list of all possible events? I'm currently interested of event where spider got a leader.

EDIT: Ouch. All this time the answer was in front of me. This is PushEvent - what i was looking for.

Edited by Tykvesh

Share this post


Link to post
Share on other sites
Tykvesh    44

Is there any guides about creating animbanks/builds? I have painted background for my container widget, but I have no idea how to export it into ui_blahblah_0x0.zip file.

Share this post


Link to post
Share on other sites
Lumina    1,295

It's probably too late to answer, but just in case. You have two ways to create a animbanks/build. Either you are using another file as a template, and in this case you need a tool to change the build name (build renamer), and a tool to export your .png as a .tex file (but in this case, the .tex should be the same size than your template .tex size, or it will not works). Or using spriter (in don't starve mod tool), and create an entire new ui_blahblah.zip, but you'll need to export it to compile it in the right files. Either methods have advantage and inconvenients.

 

 

Here you could find link to some of the tools.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now