General DST Compatibility - nsimplex/wicker GitHub Wiki
This document covers general topics regarding wicker usage for writing mods cross-compatible with both singleplayer Don't Starve and with Don't Starve Together. Topics which require more extensive documentation have their own wiki page.
-
If needed, to check if we are running DST the function
IsDST()
can be called.IsMultiplayer()
is also present as an alias, andIsSingleplayer()
is equivalent tonot IsDST()
. CallingIsHost()
(also aliased asIsServer()
andIsMasterSimulation()
) returns whether we are running in the host; on worldgen and outside of a game (e.g. on the main menu)IsHost()
always returns true; in singleplayerIsHost()
also always returns true. CallingIsDedicated()
(also aliased asIsDedicatedHost()
andIsDedicatedServer()
) returns whether we are running in a dedicated server; if we are not the host or in singleplayer,IsDedicated()
always returns false; at worldgen,IsDedicated()
always returns true (even in singleplayer). All of these functions have a corresponding "If" function, named by replacing the "Is" prefix with "If"; these functions take two arguments, returning the first if their condition is true, and the second if it is false, so for instanceIfDST(1, 2)
returns 1 if on DST and 2 otherwise. -
In DST, the callbacks registered via AddSimPostInit no longer receive the local player as their argument (they receive no argument); in fact, when sim postinits run no player entity has been spawned. For consistency, wicker made this also be the case in singleplayer. A method
TheMod:AddLocalPlayerPostActivation(fn)
was added. In singleplayer, this is the same as AddSimPostInit (except thefn
will receive the player as its argument); in multiplayer, the fn runs (receiving the player as its argument) when the local player activates (which happens a frame after it spawns). -
For convenience,
HostClass()
may be used instead ofClass()
when defining a new class. Attempting to create an object of such a class in a client raises an error. Use this when defining a class whose objects should only exist in the host (such as most components, if not all). -
Access the local player by calling
GetLocalPlayer()
or by accessing the variableTheLocalPlayer
(note that they may be nil if we are running in a dedicated server). The DST variableThePlayer
is also available (even in singleplayer), but I don't recommend its use since it's less clear about what it represents. The oldGetPlayer()
was removed (actually, it was replaced by a function raising an error), even though DST still has that function (being equivalent to accessingThePlayer
); this was done to prevent buggy code written in single player style. -
GetWorld()
and the variableTheWorld
are freely interchangeable. In DST,GetWorld()
prints a warning in the log saying it's been deprecated (and that the variableTheWorld
should be accessed instead), but wicker removes this warning. In single player,TheWorld
is a "fake" variable, because while it is accessed as a variable (i.e.TheWorld.components.clock
) it is translated into aGetWorld()
call by wickchery. -
The interface available in singleplayer via
GetClock()
(orGetWorld().components.clock
), such asGetClock():MakeNextDay()
) is present viaGetPseudoClock()
(for methods, members variables are not present and should not be accessed). In singleplayer,GetPseudoClock()
is the same asGetClock()
. The name was changed fromGetClock
to avoid singleplayer minded mistakes, much like withGetPlayer
, and attempting to callGetClock()
raises an error. Calling methods fromGetPseudoClock()
that change world state (such asGetPseudoClock():MakeNextDay()
) on a client will raise an error; and even methods likeGetPseudoClock():IsDay()
, while not raising an error, would only make sense in the client if that data is used somehow to change UI elements or some other local aspect of the game. -
The interface available in singleplayer via
GetSeasonManager()
(orGetWorld().components.seasonmanager
) is present viaGetPseudoSeasonManager()
. What was said aboutGetPseudoClock()
also holds here. The methodDoMediumLightning()
from the season manager was the only method removed, since it was too singleplayer centric; theDoLightningStrike
method (which was preserved) is likely what any mod will want to use. -
The events pushed in singleplayer by the Clock and SeasonManager components ("dusktime", "daycomplete", "rainstart", "seasonChange", etc.) no longer exist in multiplayer, having been replaced by world state watching (e.g.
inst:WatchWorldState("isdaytime", callback_fn)
), but for cross-compatibility the original events were wrapped using wicker, so from the mod perspective they still exist as they do in singleplayer. What wicker does is, if in multiplayer, intercepting theinst:ListenForEvent()
calls for these particular events and replacing them with the correspondinginst:WatchWorldState()
calls (and doing the reverse analogue forinst:RemoveEventCallback
). Thus, at the mod level, usage remains exactly as in singleplayer, including the contents of thedata
table passed as the second parameter to some event callbacks. -
Do not call
inst.components.playercontroller:ShakeCamera(source_inst, shakeType, duration, speed, maxShake, maxDist)()
, since it was removed in DST. CallGame.Effects.ShakeCamera(inst, source_inst, shakeType, duration, speed, maxShake, maxDist)
instead. -
Use
TryPause(true)
to attempt to pause the game andTryPause(false)
to unpause it.TryPause(true)
will pause the game in singleplayer and do nothing in multiplayer. Attempting to callSetPause
will raise an error. -
The function
GetRecipe(name)
, which was removed in DST, was reimplemented. Use it by just callingGetRecipe
(it's in the wicker environent, so it doesn't need to be imported), since_G.GetRecipe()
will not work in multiplayer. -
Call
MakeSnowCovered(inst)
as you would in singleplayer, and make that call beforeSetupNetwork(inst)
(see the notes of prefab setup for more information on that). This is translated into an immediate_G.MakeSnowCoveredPristine(inst)
call, followed by_G.MakeSnowCovered(inst)
after network setup if we are on the host. Do not callMakeSnowCoveredPristine(inst)
directly. -
An utility function
replica(inst)
was added. I DST, it returnsinst.replica
, and in DS it returns a "virtual table". This "virtual table" has a somewhat involved implementation, but what really matters is that it mimics the multiplayer behaviour, providing access to the mod component replicas (in accordance to what's set up viaTheMod:AddReplicaComponent()
calls), and providing access to regular components when a replica does not exist. -
In order to add component actions (the equivalent to functions like CollectSceneActions in singleplayer),
TheMod:AddComponentAction
can be used (even in singleplayer), in the same way theAddComponentAction
function defined in DST's modutil.lua is used (in DST, it will just be a proxy to that function). Additionally,TheMod:AddComponentsActions
can be used, which takes as its only parameter a table in the same format as the tableCOMPONENT_ACTIONS
found in DST's componentactions.lua file. The major change is that now these functions take the entity as their first parameter, instead of taking the component itself (i.e., the "self" parameter in functions such as CollectSceneActions). This change is important not just because the way these functions is written must be adapted, but because often you will want to access a component's replica instead of the component itself (otherwise the action won't be available on the client side). -
In principle, mod recipe sortkeys can no longer be set in DST, as they are used as unique identifiers for networking (so setting them could break the whole game). However, under wicker support for custom sortkeys is reenabled through wickchery (and some hackery). Just make sure to use the 'Recipe' class already present in the wicker environment instead of that in the global environment (or, alternatively, use "plugins.recipeadder").