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, and IsSingleplayer() is equivalent to not IsDST(). Calling IsHost() (also aliased as IsServer() and IsMasterSimulation()) 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 singleplayer IsHost() also always returns true. Calling IsDedicated() (also aliased as IsDedicatedHost() and IsDedicatedServer()) 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 instance IfDST(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 the fn 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 of Class() 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 variable TheLocalPlayer (note that they may be nil if we are running in a dedicated server). The DST variable ThePlayer is also available (even in singleplayer), but I don't recommend its use since it's less clear about what it represents. The old GetPlayer() was removed (actually, it was replaced by a function raising an error), even though DST still has that function (being equivalent to accessing ThePlayer); this was done to prevent buggy code written in single player style.

  • GetWorld() and the variable TheWorld are freely interchangeable. In DST, GetWorld() prints a warning in the log saying it's been deprecated (and that the variable TheWorld 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 a GetWorld() call by wickchery.

  • The interface available in singleplayer via GetClock() (or GetWorld().components.clock), such as GetClock():MakeNextDay()) is present via GetPseudoClock() (for methods, members variables are not present and should not be accessed). In singleplayer, GetPseudoClock() is the same as GetClock(). The name was changed from GetClock to avoid singleplayer minded mistakes, much like with GetPlayer, and attempting to call GetClock() raises an error. Calling methods from GetPseudoClock() that change world state (such as GetPseudoClock():MakeNextDay()) on a client will raise an error; and even methods like GetPseudoClock():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() (or GetWorld().components.seasonmanager) is present via GetPseudoSeasonManager(). What was said about GetPseudoClock() also holds here. The method DoMediumLightning() from the season manager was the only method removed, since it was too singleplayer centric; the DoLightningStrike 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 the inst:ListenForEvent() calls for these particular events and replacing them with the corresponding inst:WatchWorldState() calls (and doing the reverse analogue for inst:RemoveEventCallback). Thus, at the mod level, usage remains exactly as in singleplayer, including the contents of the data 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. Call Game.Effects.ShakeCamera(inst, source_inst, shakeType, duration, speed, maxShake, maxDist) instead.

  • Use TryPause(true) to attempt to pause the game and TryPause(false) to unpause it. TryPause(true) will pause the game in singleplayer and do nothing in multiplayer. Attempting to call SetPause will raise an error.

  • The function GetRecipe(name), which was removed in DST, was reimplemented. Use it by just calling GetRecipe (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 before SetupNetwork(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 call MakeSnowCoveredPristine(inst) directly.

  • An utility function replica(inst) was added. I DST, it returns inst.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 via TheMod: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 the AddComponentAction 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 table COMPONENT_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").

⚠️ **GitHub.com Fallback** ⚠️