Archived

This topic is now archived and is closed to further replies.

Please be aware that the content of this thread may be outdated and no longer applicable.

SethR

Mod API Updates - API Version 6 - May 23, 2014

Recommended Posts

SethR    1,229

Hey everyone. As I'd been promising during development on Reign of Giants, you're getting some more support for mods and changes to the modding API. The new patch with these changes is going live as I write this. The changes are basically comprised of some stuff that became necessary with Reign of Giants and stuff that I pulled from the Big List of Mod-Related Suggestions thread. @Cheerio and I are hoping to add a few more things in the coming weeks, but I don't want to make any promises as to what that might include yet. I'll keep you posted, though. Without further ado, the details of today's changes:

 

 

MOD_API_VERSION number is now 6. It has been updated due to the new mod compatibility feature, which is quite important (details below). We don’t anticipate increasing the mod API version number again until Don’t Starve Together.

 

Mod Compatibility is a new feature we’ve added that is very important. While it is not a required piece of the modinfo.lua file for the mod to function, it’s a powerful tool that we hope and expect people will update their mods to make use of (and use in their mods going forward). If a modinfo does not contain the compatibility variables, a warning will be printed in the log, but the mod will still be loaded. This means that your mod will continue to function as you expect it to until you have a chance to update it.

 

Updating your mod to use the new mod compatibility:

1. Open the modinfo.lua file for your mod.

2. Add the dont_starve_compatible and reign_of_giants_compatible boolean variables to modinfo. Here’s an example for a mod that is compatible with Reign of Giants but not the base game (code excerpted from modinfo.lua):

dont_starve_compatible = falsereign_of_giants_compatible = true

Based on the compatibility settings in modinfo as described above, a mod can be compatible (variable set to true), not compatible (variable set to false) or of unknown compatibility (variable not present). A mod’s compatibility is shown on the mod’s tile in the list of mods on the left side of the mods screen using the following icons:

cWMhFw0.png

 

Mods that are set to be compatible with a mode (Don’t Starve base game or Reign of Giants) will be loaded when starting or loading a game in that mode. Mods that are set to be NOT compatible with a mode will get silently unloaded when starting or loading a game in that mode, though the mod will stay enabled (i.e. you won’t have to go back to the mods screen and enable it to use it again: if you start or load a different game that is a mode the mod is compatible with, the mod will get loaded). Mods that do not have their compatibility set for the mode that is being played (i.e. unknown compatibility) will get loaded as normal and be usable, but there will be a warning in the log letting you know that it might behave badly.

 

Mod Configuration Options is a new feature that allows modders to have a set of options that are specific to their mod. The settings of these options get saved in a subdirectory of the Klei/DoNotStarve/save folder called mod_config_data on a per-mod basis, meaning that the settings persist across sessions. This new feature does two main things. First, it exposes customization or configuration of a mod to the player in a much easier way. Options can be changed in a screen that is accessed by clicking on the Configure Mod button that will appear on the mods screen when a mod with configuration options is selected. Second, it allows you to make your mods more flexible or to package multiple versions of your mod (differentiated by the configuration options provided) as one mod.

 

Adding mod configuration options to your mod (and then using them):

1. Open the modinfo.lua file for your mod.

2. Add a variable called configuration_options to your modinfo. It needs to be a table containing your customization/configuration options. It’s important that you use the same keys (name, options, description, data, default) in your table as in the below example. You can have as many configuration options as you want and each option can have as many possible settings as you want:

configuration_options ={	{		name = "mode",        label = "Mod Mode",		options = 		{			{description = "Basic", data = "basic_mode"}, 			{description = "Advanced", data = "advanced_mode"}		}, 		default = "basic_mode",	},	{		name = "inv_size",         label = "Inventory Size",		options = 		{			{description = "Small", data = 10}, 			{description = "Medium", data = 15}, 			{description = "Large", data = 20}		},		default = 15	},	{		name = "Fish", 		options = 		{			{description = "One Fish", data = "one"}, 			{description = "Two Fish", data = "two"}, 			{description = "Red Fish", data = "red"},			{description = "Blue Fish", data = "blue"}		},		default = "two"	},}

3. Once you’ve got your configuration data table all set up, you can access the settings by calling GetModConfigData(optionname). The optionname parameter must match the string set as the name of your option in the configuration_options table (i.e. the "name" key, such as "Fish" above). The function will return the saved value of the option if there is one, otherwise it will return the default value you specified in the configuration table.

 

A bit more about how it all works:

  • If your mod has a (valid) configuration options table, then there will be a button, Configure Mod, on the mods screen that appear when the mod is selected in the list of mods on the left side of the mods screen. Clicking that button will open a new screen that is dynamically built based on the configuration_options table: each option is a spinner UI element and the list of spinners will grow and shrink based on the size of your configuration data table (if there are a lot, navigation arrows for flipping pages of spinners will get added). Each spinner will be dynamically built based on the options subtable of each table in the configuration_options table.

  • The “name” key in the configuration_options table is the string that will be used to fetch the value for this configuration option. If not label is provided, the name will appear as the label for the spinner on the configuration screen.

  • The "label" key in the configurations_options table is the optional string that will be used as the label when making the spinner on the options screen. If it is not provided, the label for the spinner will be the string in "name".

  • The “options” key in the configuration_options is a table that determines the values of the spinner.

    • The “description” key in the options table is the string that will appear on the spinner representing the data for that option selection.

    • The “data” key in the options table is the actual data that will get set and saved for that option. This is the value retrieved by the GetModConfigData function.

  • The “default” key in the configuration_options is the default value for that option. Its value should match one of the values mapped to a “data” key from the options table.

 

New Post Init Functions have been added. There is now a AddPrefabPostInitAny function (line 198, modutil.lua) that allows you to add a post init function that will get run on all prefabs. While this is powerful, please be sure to check that you're dealing with the appropriate type of prefab before doing anything intensive, or else you might hit some performance issues when prefabs get spawned. The AddPlayerPostInit function (line 205, modutil.lua) is an example of how to do this. It also provides an easy shortcut to add a post init function specifically for player prefabs. It looks like this:

env.AddPrefabPostInitAny( function(inst)	if inst and inst:HasTag("player") then fn(inst) endend)

Moddable String Construction has been added (only used in Reign of Giants). The file in question here is dlcsupport_strings.lua. The new functions will make it easier (and more correct!) to localize the construction of phrases with adjectives that occurs when an entity is smoldering, withered or wet. By default, these adjectives are added before the name of the entity. Unless set otherwise, the table that tracks this is just a series of true or false (prefix and suffix style construction, respectively) values that are mapped to names and adjectives. There are three functions here that are worth covering:

  • MakeAllSuffixes(fn) is a function that will make all phrases constructed with the adjective coming AFTER the entity name. The fn parameter is optional: if you just want to make all phrases use a suffix style of string construction, then you do not need to enter a parameter. The parameter is there in case you want to make a more involved function for deciding how to construct the phrase.

  • MakeAllPrefixes(fn) is a function that will make all phrases constructed with the adjective coming BEFORE the entity name. The fn parameter is optional: if you just want to make all phrases use a prefix style of string construction, then you do not need to enter a parameter. The parameter is there in case you want to make a more involved function for deciding how to construct the phrase.

  • SetUsesPrefix(item, usePrefix) is a function that allows you to set special cases for individual items. The item parameter should be the name or the adjective that you’re concerned with. The usePrefix parameter should either be a bool (true or false as above) or a function (more involved decision as above).

 

Reign of Giants characters’ speech is no longer required within their speech files within the prefab constructor: they are now required in strings.lua and you should be able to treat their speech the same as base game characters speech for modding purposes.

 

Character mod portraits in the morgue screen are now supported. If the mod for a given character isn’t loaded, then the portrait will default to a question mark.

 

Stewer component: arbitrary size! The stewer component now supports changing the number of ingredients allowed, as well as the number of ingredients needed to do stewing.

 

Growable component: ongrowth callback! The growable component now has an ongrowthfn callback function that gets called when the DoGrowth function is called in the growable component.

 
 
There were also a handful of mods-related bug fixes that went out in today's patch, but I wanted to focus here on the new features and big changes.
 
As always, feel free to ask questions and provide feedback if you have it. Thanks for your patience with the mod support I'd been promising! Keep making awesome stuff :-)

Share this post


Link to post
Share on other sites
simplex    2,597

@SethR

Awesome improvements to the mod API! :grin:

I have a few remarks on the mod options, though, but keep in mind I haven't received the update yet, so I'm basing myself only on what you posted here, and not on reading the effective code changes.

First of all, I don't like the idea of the options specification being in modinfo.lua. I understand why you made this decision: it should be available even if the mod is not enabled. But the fact that modinfo.lua runs in an empty environment severely limits the inclusion of programming logic to automatically build parts of the options specification (more notably, we can't use pairs and ipairs). So I'd rather have the options specification be put in a dedicated file (such as modoptions.lua), which is loaded when it exists. This file can't run in the mod or global environments, of course, since allowing arbitrary mod code to run even when the mod is not enabled is something of a security hazard, but I'd like to see at least the following in its environment:

asserterroripairsnextpairspcallselecttonumbertostringtypeunpackmathstringtable
(if the entries listed above which are missing from the mod environment were added to it as well, that'd be a huge boon, and not just because then we could simply modimport the options file to check the options list)

A function analogous to modimport, loading a given file (relative to the mod folder, i.e. to MODROOT) in the options environment would also be quite handy, so we can split the options into several files.

Secondly, will the users have an option to reset all options of a particular mod to their default values? I think that's important.

Thirdly, could we also specify numeric options with a min-max range, presented to the player in a NumericSpinner widget?

Share this post


Link to post
Share on other sites
Rincevvind    60

what if i just want to save random string from one session to another?

is it good to use Profile for it?

like saving data:

GLOBAL.Profile:SetValue(modname_string,serialized_data_string)GLOBAL.Profile:Save()
well i have question about setting feature

this GetModConfigData(optionname) will work in any part of modmain.lua?

for example is it possible to use code like

 

if GetModConfigData("usenew") then    PrefabFiles = {"somenewprefab"}else    PrefabFiles = {"someoldprefab"}end
if yes its really good feature, especially for unstable/testing code. Users which want to test something new able to set new option to on, others which just want to play - dont care.

Share this post


Link to post
Share on other sites
simplex    2,597

tbh i dont get idea of settings, what if i just want to save random string from one session to another?

This is for end user facing options. The point is to provide a uniform interface for the user to configure the mod.

is it good to use Profile for it?

If you can ensure you're using a unique key, then it's fine. You can also use a custom save file (we do that in U&A to store the cached modinfo.lua grabbed from the GitHub server used to determine if the local installation is up to date).

Share this post


Link to post
Share on other sites
SethR    1,229

Awesome improvements to the mod API! :grin:

 

Thanks! I'm excited to see what the modding community can do with these new tools.

 

First of all, I don't like the idea of the options specification being in modinfo.lua. I understand why you made this decision: it should be available even if the mod is not enabled. But the fact that modinfo.lua runs in an empty environment severely limits the inclusion of programming logic to automatically build parts of the options specification (more notably, we can't use pairs and ipairs). So I'd rather have the options specification be put in a dedicated file (such as modoptions.lua), which is loaded when it exists. This file can't run in the mod or global environments, of course, since allowing arbitrary mod code to run even when the mod is not enabled is something of a security hazard, but I'd like to see at least the following in its environment: [list omitted]

 

So, the short version of why it works the way it does is that it was significantly faster to make this part of an existing file and load flow. The longer answer is a series of things:

  • I like that all of the mod's data is contained in one file (modinfo.lua). The meaning and application of the file has changed over time and it's arguably not very well named at this point, but it makes a lot of intuitive sense to me that there are two top-level files that define a mod: a data file (i.e. static definitions of data that is integral to the mod) and a main file (i.e. the initial code execution).
  • The configuration data is intended to be static. The point of the feature is to expose settings to users and THEN do stuff based on those settings, rather than to do stuff as part of the settings. In my head, the flow is something like this: "set up a bunch of data that defines various states for the mod > user changes those settings through the configuration screen > mod initializes and sets itself up/does logic based on the state of the data as defined by the user".
  • This is sort of expanding on the point immediately above this, but basically my thinking about the application of this configuration feature is providing a UI (and permanence across sessions) for users to customize a mod. It's as if you defined a bunch of constants at the top of modmain.lua that the user could change. They make those changes before the mod gets loaded and then the mod gets loaded in a certain state or with a certain set of data. This is just the fancy (better UX) and permanent (saved between sessions and enable/disable cycles) version of that.
  • Making the format and contents of the table extremely restricted makes it much easier on everyone when it comes to knowing when something has gone wrong and what that something is. As you're aware, the table gets loaded outside of the mod getting fully loaded, so I wanted it to be as basic as possible. It also means that when I'm building the configuration options screen, I have less special-casing and handling of different data formats to worry about.
  • Expanding on the "this was the fastest way" thing: certainly I could have made it more robust and fully featured in terms of what could be contained in the table, but for the reasons above, I chose not to. It's Occam's Razor: the simplest and fastest option is often the best one in game development. This way I have more time to work on other mod support features like mod-specific save data, etc. Obviously we have to pick our battles, but generally with mod support, I value breadth over depth.

So yeah, that's basically where my thinking is at. Did you have a particular application in mind that requires being able to do logic inside of the configuration_options table? I haven't been able to come up with any meaningful examples myself where the current implementation is actually limiting, but that doesn't mean it doesn't exist. :-)

 

Secondly, will the users have an option to reset all options of a particular mod to their default values? I think that's important.

 

Right now, no. It's an excellent point: I completely agree that it's important for users to be able to reset the values. An oversight on my part: I'll add a button to the screen to restore defaults ASAP. In the meantime, the way to do this is to delete the appropriate file in the mod_config_data folder: if there's no file, there's no saved data to load and the defaults will get restored.

 

Thirdly, could we also specify numeric options with a min-max range, presented to the player in a NumericSpinner widget?

 

It honestly didn't occur to me while I was building the feature. It's obvious that it can save lines of code, but I'm not sure the savings are as meaningful as they might seem. Let's take an example where you've got a numeric spinner (or even a "fake" one, as the current implementation requires) that has enough values in it that the savings would be meaningful. In that case, I'd argue that you have a poor user experience with that spinner and you should think about whether you need to expose that much granularity, or if there's a better way to do so (i.e. a "tens digit" spinner and a "ones digit" spinner). There's also pretty significant savings (in terms of complexity) on the backend of the system by keeping the format of all the tables uniform. So, yeah, I absolutely sympathize with why you're asking for this, but I don't think it's a good use of my limited time (Don't Starve Together isn't going to make itself!) :-)

 

 

Thanks for the thoughtful feedback! I'm happy to discuss further, of course. Also, the build should be live on Steam now if you want to check out the actual code.

Share this post


Link to post
Share on other sites
SethR    1,229

what if i just want to save random string from one session to another?

is it good to use Profile for it? @simplex is correct here. One thing I should add is that the saved settings in the new configuration options settings are agnostic to the save slot. They are global settings for the mod. If the string that you want to save is more about the functionality of the mod, this feature is a good place to do that. Before this feature, I could see a case where you'd want to store similar data in the Profile. If the string you want to save is more about the instance of the game in a given save slot, you'll want to use the save file for that save slot.

like saving data:

GLOBAL.Profile:SetValue(modname_string,serialized_data_string)GLOBAL.Profile:Save()
well i have question about setting feature

this GetModConfigData(optionname) will work in any part of modmain.lua? I believe it will.  I haven't tested the example you give below, but I expect that it would work (and that's a cool application!). If you try something like this and find that it doesn't work, please let me know!

for example is it possible to use code like

 

if GetModConfigData("usenew") then    PrefabFiles = {"somenewprefab"}else    PrefabFiles = {"someoldprefab"}end
if yes its really good feature, especially for unstable/testing code. Users which want to test something new able to set new option to on, others which just want to play - dont care.

 

 

Response in red above.

Share this post


Link to post
Share on other sites
Rincevvind    60

Response in red above.

thx for reply, about data size limits for storing in Profile, are they exists? like 4096/65535/X bytes?

Share this post


Link to post
Share on other sites
simplex    2,597
  • The configuration data is intended to be static. The point of the feature is to expose settings to users and THEN do stuff based on those settings, rather than to do stuff as part of the settings. In my head, the flow is something like this: "set up a bunch of data that defines various states for the mod > user changes those settings through the configuration screen > mod initializes and sets itself up/does logic based on the state of the data as defined by the user".
[...]

So yeah, that's basically where my thinking is at. Did you have a particular application in mind that requires being able to do logic inside of the configuration_options table? I haven't been able to come up with any meaningful examples myself where the current implementation is actually limiting, but that doesn't mean it doesn't exist. :-)

There can be no use case that absolutely needs programming logic. This is a mathematical fact: since the loading of modinfo (or wherever the configuration data is stored) has no side effects, it corresponds to a function in the mathematical sense (to a function in the lambda calculus, to be precise) and, since it has access to no runtime information, it takes no arguments (it is defined over a singleton type, to be precise), and the only functions satisfying this property are the constant functions; in other words, every programming logic put in the configuration data will have a single execution path, and so it may be replaced by its (unique) output without loss of generality.

I'm asking for this to avoid boilerplate code, as well as code duplication. If there are many related options, it would be preferable to iterate over their descriptions and insert the option in the body of the loop, to make any change to this common option structure propagate to all of them without having to copy it over in each instance.

Furthermore, with some programming logic things like numeric spinners can be easily emulated by generating the list of possibilities. If the range is large (and the step size relatively small) keeping a static list can quickly become unwieldly.

Again, allowing programming logic in the options specification is in no way essential. But I believe it has benefits enough to justify its inclusion.

  • This is sort of expanding on the point immediately above this, but basically my thinking about the application of this configuration feature is providing a UI (and permanence across sessions) for users to customize a mod. It's as if you defined a bunch of constants at the top of modmain.lua that the user could change. They make those changes before the mod gets loaded and then the mod gets loaded in a certain state or with a certain set of data. This is just the fancy (better UX) and permanent (saved between sessions and enable/disable cycles) version of that.

I understand why such a thing, as well as its separation from mod logic, is important. In fact, I was the one to originally request a feature like this, and a long time ago suggested something very similar to what you ended up implementing.

Share this post


Link to post
Share on other sites
SethR    1,229

@simplex that's fair enough. I'll need to think about it further (but not tonight: for now it's time to go home), but I think the ways that I'd encourage you to minimize code duplication and boilerplate code that would arise from the situation you describe are as follows:

 

For batch creation or updating of options in the configuration_options table, write a script that is external to Don't Starve: do a bit of file io and table manipulation to create the table as you desire, and then stick the resulting table into modinfo (either by doing the io directly on that file or by copy/pasting the results). This actually extends to the numeric spinner example: you can easily generate a large table using an external script that will result in a numeric style spinner. In case I wasn't clear in my last post, I do see why you might want to do larger scale manipulation of the table like you described, but I don't see any benefits to that happening at the game's runtime.

 

For batch reading of the table, you do in fact have direct access to the table through the MODCONFIG environment variable, so you can iterate over the table and do whatever logic you need to.

 

Thanks again for the thoughtful consideration of the problem at hand! For now, though, I go home. Have a good weekend!

Share this post


Link to post
Share on other sites
Rincevvind    60

yeah, its works

in modinfo.lua

....configuration_options ={    {        name = "mode",        options =        {            {description = "Basic", data = "basic"},            {description = "Advanced", data = "advanced"}        },        default = "basic",    },}
then in modmain.lua

if GetModConfigData("mode")=="advanced" then    PrefabFiles = {"hhnew"}else    PrefabFiles = {"hhold"}end
prefab returns same prefab, but have different message to print, and its works properly.

really love this feature now, its make alot of things much easier to implement.

only one thing - its probably a hell to use "mods" section menu without "manual workshop mod" by simplex, maybe you inregrate his code in game or make new button in main menu just for mods configuration?

Share this post


Link to post
Share on other sites
simplex    2,597

@SethR

A preprocessor would work, but embedding the logic right into the options specification file (which I still think should not be modinfo, both because modinfo until now was about mod metadata, not plain data, and more selfishly because in U&A we transmit modinfo.lua over the Internet whenever the player presses the U&A banner, so I'd rather keep it as short as possible) would be more maintainable and less error prone (and much, much easier, since using a preprocessor requires writing or at least using a Lua serializer, and possible also a parser depending on how flexible the insertion into modinfo.lua is meant to be).

But anyway, this is not such a big deal, since static data does accomplish everything dynamic data could. If you eventually have the time, it'd be nice if some of these details could be revisited (especially the possibility of putting the options specifications in a file other than modinfo.lua), but there are certainly more important matters for you to address than this. I appreciate the configuration system, this is something I've been wanting in the mod API for a long time.

EDIT:

But anyway,

my limited time (Don't Starve Together isn't going to make itself!) :-)

isn't one of the big (the biggest, in my opinion) selling points of DST the fact that supposedly a dedicated, newly hired team was taking care of it, so that development wouldn't be reallocated from the actual game?

Share this post


Link to post
Share on other sites
Rincevvind    60

one more suggestion for mod options

can we have something like

configuration_options ={    {        name = "mode",        description = "Game mode:",        options =        {            {description = "Basic", data = "basic"},            {description = "Advanced", data = "advanced"}        },        default = "basic",    },}
to split option name from his description

also there will really good to see something with list of values, for example i want to define mod key here, lets say its Alt-Z

so if i want to make it configurable via this menu i have to list all 26 English letters :/

same with numeric lists, which simplex probably mentioned.

something like

range.description = "stack size"range.min = 20range.max = 100range.step = 10range.default = 30char.description = "key"char.min = "A"char.max = "Z"char.default = "T"
oh wait, am able to use standard LUA operators here? like for .. do, if .. then?

Share this post


Link to post
Share on other sites
Heavenfall    183

Upon enabling attached mod game crashes with error upon starting a new game from scratch:

Reset() returningC:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/dlcsupport_worldgen.lua(24,1) DLC enabled : 	true	DoLuaFile Error: ...eamapps/common/dont_starve/data/scripts/modindex.lua:54: variable 'BRANCH' is not declaredLUA ERROR stack traceback:        =[C] in function 'error'        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/strict.lua(23,1)        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/modindex.lua(54,1) in function 'GetModConfigurationName'        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/modindex.lua(46,1) in function 'GetModConfigurationPath'        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/modindex.lua(350,1) in function 'GetModConfigurationOptions'        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/mods.lua(93,1) in function 'CreateEnvironment'        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/mods.lua(139,1) in function 'LoadMods'        scripts/worldgen_main.lua(73,1) in main chunk...eamapps/common/dont_starve/data/scripts/modindex.lua:54: variable 'BRANCH' is not declaredLUA ERROR stack traceback:        =[C] in function 'error'        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/strict.lua(23,1)        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/modindex.lua(54,1) in function 'GetModConfigurationName'        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/modindex.lua(46,1) in function 'GetModConfigurationPath'        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/modindex.lua(350,1) in function 'GetModConfigurationOptions'        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/mods.lua(93,1) in function 'CreateEnvironment'        C:/Program Files (x86)/Steam/steamapps/common/dont_starve/data/scripts/mods.lua(139,1) in function 'LoadMods'        scripts/worldgen_main.lua(73,1) in main chunkError loading worldgen_main.luaWorldSim::SimThread::Main() ERRORWorldSim::SimThread::Main() completescripts/gamelogic.lua(1030,1) Worldgen had an error, displaying...	scripts/frontend.lua(723,1) SCRIPT ERROR! Showing error screen	

Is configuration a must-have variable? Would appreciate help.

temp.zip

Share this post


Link to post
Share on other sites
Rincevvind    60

still about options in modinfo.lua

since strings not available, this one is works fine hehe

local alpha = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"}local KEY_A = 97local keyslist = {}for i = 1,#alpha do keyslist[i] = {description = alpha[i],data = i + KEY_A - 1} endconfiguration_options ={    {        name = "walllabel",        options =        {            {description = "Enable label", data = "on"},            {description = "Disable label", data = "off"}        },        default = "on",    },    {        name = "key",        options = keyslist,        default = 98,    },}

Share this post


Link to post
Share on other sites
squeek    222

@SethR, nice work. Really happy to see movement on the modding API front. You didn't even mention quite a few significant fixes that were implemented from The Big List Of Mod-Related Code Suggestions!

For anyone curious, I updated The Big List Of Mod-Related Code Suggestions with complete information about what all was implemented/fixed in this patch (see the "Suggestions that were fixed/addressed/implemented" section).

A few comments about the patch:

  • You forgot to make the change from GlobalPrefabPostInit to the new name of PrefabPostInitAny in mainfunctions.lua (line 136), making PrefabPostInitAny callbacks never get called.
  • Something I didn't consider when I wrote the fix for mod cooker products is the ability for modders to use a single anim file for multiple recipe products (as is done in cook_pot_food.zip). Not really sure about a good way to implement support for that (registered recipes would need to be able to optionally specify which anim file to use), but it might be worth considering.
  • Why not do self.console_edit:SetAllowClipboardPaste(true) in consolescreen.lua?
Keep it up! Hope to see more and more things from the suggestions thread get checked off. :-)

Share this post


Link to post
Share on other sites
Rincevvind    60

found bug with configuration_options

if one of options will be set to default value, all others will reset to defaults too :/

thats my ugly code :

local alpha = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"}local numb = {"0","1","2","3","4","5","6","7","8","9"}local KEY_A = 97local keyslist = {{description = "Off", data = 0}}for i = 1,#alpha do keyslist[i+1] = {description = alpha[i],data = i + KEY_A - 1} endlocal function GetKeyCode(char)	if char == "Off" then return 0 end --lets save few nanoseconds from circle above	for i = 2,#alpha do 		if alpha[i]==char then return i + KEY_A - 2 end	end	return 0endlocal function tostr(num)	local s = ""	local c = ""	if num >= 10 then s = tostr(num/10) end	num = num % 10	for i = 10,1,-1 do		if i > num then c = numb[i] end	end	return s .. cendlocal function WallMult(min,max,step)	local a = {}	local j = 0	for i = min, max, step do		j = j + 1		a[j] = {description = "x"..tostr(i),data = i}	end	return aendconfiguration_options ={    {        name = "Wall label",        options =        {            {description = "Enable", data = "on"},            {description = "Disable", data = "off"}        },        default = "on",    },    {        name = "Activate label on ALT+",        options = keyslist,        default = GetKeyCode("Z"),    },    {        name = "Hay wall health",        options = WallMult(2,5,1),	default = 5    },    {        name = "Wood wall health",        options = WallMult(2,7,1),	default = 7    },    {        name = "Stone wall health",        options = WallMult(2,10,1),	default = 10    },    {        name = "Nightmare wall health",        options = WallMult(2,13,1),	default = 13    },    {        name = "Ruins wall health",        options = WallMult(2,15,1),	default = 15    },}

Share this post


Link to post
Share on other sites
Rincevvind    60

and its not works well at all

1) enter to options

2) change all setting from defaults

3) apply

4) apply or cancel in mod section. doesn't matter

5) enter to mod configuration again - options is OK

6) change 1 value

7) do 3) and 4)

8) enter to mod configuration - all reset to defaults

Share this post


Link to post
Share on other sites
ShdwTR    0

I'm getting console spam. Lags game.

 

2z6bcjn.jpg

 

My Script

local function Clock_PostInit(inst)    local original_OnUpdate = inst.OnUpdate    inst.OnUpdate = function(self, dt)        original_OnUpdate(self, dt)		if GetModConfigData("Infinite Health") == "ON" then			----------------------------------------------------------			local Health = GetPlayer().components.health			----------------------------------------------------------			if Health and Health:GetPercent() < 1 then				Health:DoDelta(Health.maxhealth, true)			end		end		if GetModConfigData("Infinite Hunger") == "ON" then					----------------------------------------------------------			local Hunger = GetPlayer().components.hunger			----------------------------------------------------------			if Hunger and Hunger:GetPercent() < 1 then				Hunger:DoDelta(Hunger.max, true)			end		end		if GetModConfigData("Infinite Sanity") == "ON" then			----------------------------------------------------------			local Sanity = GetPlayer().components.sanity			----------------------------------------------------------			if Sanity and Sanity:GetPercent() < 1 then				Sanity:DoDelta(Sanity.max, true)			end		end	endendAddComponentPostInit("clock", Clock_PostInit)

Share this post


Link to post
Share on other sites
simplex    2,597

Upon enabling attached mod game crashes with error upon starting a new game from scratch:

Is configuration a must-have variable? Would appreciate help.

Don worry, it'ś just the game that's broken. If you check the main patch notes thread, you'll see the remark that the last patch makes the game crash at worldgen when ANY mod is enabled. And yet the patch wasn't rolled back, as if it were nearly OK to leave the game in this state.

I'm seriously considering unreleasing U&A, since it's unplayable anyway.

Share this post


Link to post
Share on other sites
Rincevvind    60

Well i make fix for options issue, at least its works for me

patched modconfigurationscreen.lua

require "util"require "strings"local Screen = require "widgets/screen"local Button = require "widgets/button"local AnimButton = require "widgets/animbutton"local ImageButton = require "widgets/imagebutton"local Menu = require "widgets/menu"local Grid = require "widgets/grid"local Text = require "widgets/text"local Image = require "widgets/image"local UIAnim = require "widgets/uianim"local Spinner = require "widgets/spinner"local NumericSpinner = require "widgets/numericspinner"local Widget = require "widgets/widget"local PopupDialogScreen = require "screens/popupdialog"local text_font = UIFONTlocal enableDisableOptions = { { text = STRINGS.UI.OPTIONS.DISABLED, data = false }, { text = STRINGS.UI.OPTIONS.ENABLED, data = true } }local spinnerFont = { font = BUTTONFONT, size = 30 }local COLS = 2local ROWS_PER_COL = 7local options = {}local ModConfigurationScreen = Class(Screen, function(self, modname)	Screen._ctor(self, "ModConfigurationScreen")	self.modname = modname	self.config = KnownModIndex:GetModConfigurationOptions(modname)	self.left_spinners = {}	self.right_spinners = {}	options = {}		if self.config and type(self.config) == "table" then		for i,v in ipairs(self.config) do			-- Only show the option if it matches our format exactly			if v.name and v.options and (v.saved or v.default) then --				table.insert(options, {name = v.name, options = v.options, default = v.saved or v.default})				table.insert(options, {name = v.name, options = v.options, default = v.default, value = v.saved})			end		end	end		self.bg = self:AddChild(Image("images/ui.xml", "bg_plain.tex"))    if IsDLCEnabled(REIGN_OF_GIANTS) then    	self.bg:SetTint(BGCOLOURS.PURPLE[1],BGCOLOURS.PURPLE[2],BGCOLOURS.PURPLE[3], 1)    else   		self.bg:SetTint(BGCOLOURS.RED[1],BGCOLOURS.RED[2],BGCOLOURS.RED[3], 1)    end        self.bg:SetVRegPoint(ANCHOR_MIDDLE)    self.bg:SetHRegPoint(ANCHOR_MIDDLE)    self.bg:SetVAnchor(ANCHOR_MIDDLE)    self.bg:SetHAnchor(ANCHOR_MIDDLE)    self.bg:SetScaleMode(SCALEMODE_FILLSCREEN)    	self.root = self:AddChild(Widget("ROOT"))    self.root:SetVAnchor(ANCHOR_MIDDLE)    self.root:SetHAnchor(ANCHOR_MIDDLE)    self.root:SetPosition(0,0,0)    self.root:SetScaleMode(SCALEMODE_PROPORTIONAL)    	    local shield = self.root:AddChild( Image( "images/globalpanels.xml", "panel.tex" ) )	shield:SetPosition( 0,0,0 )	shield:SetSize( 1000, 700 )			local titlestr = KnownModIndex:GetModFancyName(modname)	local maxtitlelength = 26	if titlestr:len() > maxtitlelength then		titlestr = titlestr:sub(1, maxtitlelength)	end	titlestr = titlestr.." "..STRINGS.UI.MODSSCREEN.CONFIGSCREENTITLESUFFIX	local title = self.root:AddChild( Text(TITLEFONT, 50, titlestr) )	title:SetPosition(0,210)	title:SetColour(1,1,1,1)	self.option_offset = 0    self.optionspanel = self.root:AddChild(Widget("optionspanel"))	    self.optionspanel:SetPosition(0,-20)	self.menu = self.root:AddChild(Menu(nil, 0, false))	self.applybutton = self.menu:AddItem(STRINGS.UI.MODSSCREEN.APPLY, function() self:Apply() end, Vector3(-95, -295, 0))	self.cancelbutton = self.menu:AddItem(STRINGS.UI.MODSSCREEN.CANCEL, function() self:Cancel() end,  Vector3(95, -295, 0))	self.applybutton:SetScale(.9)	self.cancelbutton:SetScale(.9)	self.applybutton:SetFocusChangeDir(MOVE_RIGHT, self.cancelbutton)	self.cancelbutton:SetFocusChangeDir(MOVE_LEFT, self.applybutton)	self.default_focus = self.applybutton	self.dirty = false	self.rightbutton = self.optionspanel:AddChild(ImageButton("images/ui.xml", "scroll_arrow.tex", "scroll_arrow_over.tex", "scroll_arrow_disabled.tex"))    self.rightbutton:SetPosition(440, 0, 0)    self.rightbutton:SetScale(.9)    self.rightbutton:SetOnClick( function() self:Scroll(ROWS_PER_COL) end)    if #options <= ROWS_PER_COL * COLS then -- Only show the arrow if we have a ton of options		self.rightbutton:Hide()	end		self.leftbutton = self.optionspanel:AddChild(ImageButton("images/ui.xml", "scroll_arrow.tex", "scroll_arrow_over.tex", "scroll_arrow_disabled.tex"))    self.leftbutton:SetPosition(-440, 0, 0)    self.leftbutton:SetScale(-.9,.9,.9)    self.leftbutton:SetOnClick( function() self:Scroll(-ROWS_PER_COL) end)	    self.leftbutton:Hide()    self.optionwidgets = {}    self:RefreshOptions()end)function ModConfigurationScreen:CollectSettings()	local settings = nil	for i,v in pairs(options) do		local curr_setting = options[i].value		if not settings then settings = {} end--		table.insert(settings, {name=v.name, options=v.options, default=v.default, saved=curr_setting})		table.insert(settings, {name=v.name, options=v.options, default=v.default, saved=v.value})	end	return settingsendfunction ModConfigurationScreen:Apply()	if self:IsDirty() then		local settings = self:CollectSettings()		KnownModIndex:SaveConfigurationOptions(function() TheFrontEnd:PopScreen() end, self.modname, settings)	else		TheFrontEnd:PopScreen()	endendfunction ModConfigurationScreen:ConfirmRevert()	TheFrontEnd:PushScreen(		PopupDialogScreen( STRINGS.UI.MODSSCREEN.BACKTITLE, STRINGS.UI.MODSSCREEN.BACKBODY,		  { 		  	{ 		  		text = STRINGS.UI.MODSSCREEN.YES, 		  		cb = function()					TheFrontEnd:PopScreen()					self:MakeDirty(false)					self:Cancel()				end			},			{ 				text = STRINGS.UI.MODSSCREEN.NO, 				cb = function()					TheFrontEnd:PopScreen()									end			}		  }		)	)		endfunction ModConfigurationScreen:Cancel()	if self:IsDirty() then		self:ConfirmRevert()	else		TheFrontEnd:PopScreen()	endendfunction ModConfigurationScreen:MakeDirty(dirty)	if dirty ~= nil then		self.dirty = dirty	else		self.dirty = true	endendfunction ModConfigurationScreen:IsDirty()	return self.dirtyendfunction ModConfigurationScreen:OnControl(control, down)    if ModConfigurationScreen._base.OnControl(self, control, down) then return true end        if not down then	    if control == CONTROL_CANCEL then			self:Cancel()	    elseif control == CONTROL_ACCEPT and TheInput:ControllerAttached() and not TheFrontEnd.tracking_mouse then	    	self:Apply() --apply changes and go back, or stay	    elseif control == CONTROL_PAGELEFT then    		if self.leftbutton.shown then    			TheFrontEnd:GetSound():PlaySound("dontstarve/HUD/click_move")    			self:Scroll(-ROWS_PER_COL)    		end    	elseif control == CONTROL_PAGERIGHT then    		if self.rightbutton.shown then    			TheFrontEnd:GetSound():PlaySound("dontstarve/HUD/click_move")    			self:Scroll(ROWS_PER_COL)    		end    	else    		return false    	end     	return true	endendfunction ModConfigurationScreen:Scroll(dir)	if (dir > 0 and (self.option_offset + ROWS_PER_COL*2) < #options) or		(dir < 0 and self.option_offset + dir >= 0) then			self.option_offset = self.option_offset + dir	end		if self.option_offset > 0 then		self.leftbutton:Show()	else		self.leftbutton:Hide()	end		if self.option_offset + ROWS_PER_COL*2 < #options then		self.rightbutton:Show()	else		self.rightbutton:Hide()	end		self:RefreshOptions()endfunction ModConfigurationScreen:RefreshOptions()	local focus = self:GetDeepestFocus()	local old_column = focus and focus.column	local old_idx = focus and focus.idx		for k,v in pairs(self.optionwidgets) do		v.root:Kill()	end	self.optionwidgets = {}	self.left_spinners = {}	self.right_spinners = {}	for k = 1, ROWS_PER_COL*2 do			local idx = self.option_offset+k				if options[idx] then						local spin_options = {} --{{text="default"..tostring(idx), data="default"},{text="2", data="2"}, }			for k,v in ipairs(options[idx].options) do				table.insert(spin_options, {text=v.description, data=v.data})			end						local opt = self.optionspanel:AddChild(Widget("option"))						local spin_height = 50			local w = 220			local spinner = opt:AddChild(Spinner( spin_options, w, spin_height))			spinner:SetTextColour(0,0,0,1)			local default_value = options[idx].value or options[idx].default						spinner.OnChanged =				function( _, data )					options[idx].value = data					self:MakeDirty()				end							spinner:SetSelected(default_value)			spinner:SetPosition(35,0,0 )			local spacing = 55			local label_width = 180						local label = spinner:AddChild( Text( BUTTONFONT, 30, options[idx].name or STRINGS.UI.MODSSCREEN.UNKNOWN_MOD_CONFIG_SETTING ) )			label:SetPosition( -label_width/2 - 105, 0, 0 )			label:SetRegionSize( label_width, 50 )			label:SetHAlign( ANCHOR_MIDDLE )			if k <= ROWS_PER_COL then				opt:SetPosition(-155, (ROWS_PER_COL-1)*spacing*.5 - (k-1)*spacing - 10, 0)				table.insert(self.left_spinners, spinner)				spinner.column = "left"				spinner.idx = #self.left_spinners			else				opt:SetPosition(265, (ROWS_PER_COL-1)*spacing*.5 - (k-1-ROWS_PER_COL)*spacing- 10, 0)				table.insert(self.right_spinners, spinner)				spinner.column = "right"				spinner.idx = #self.right_spinners			end						table.insert(self.optionwidgets, {root = opt})		end	end	--hook up all of the focus moves	self:HookupFocusMoves()	if old_column and old_idx then		local list = old_column == "right" and self.right_spinners or self.left_spinners		list[math.min(#list, old_idx)]:SetFocus()	end	endfunction ModConfigurationScreen:HookupFocusMoves()	local GetFirstEnabledSpinnerAbove = function(k, tbl)		for i=k-1,1,-1 do			if tbl[i] and tbl[i].enabled then				return tbl[i]			end		end		return nil	end	local GetFirstEnabledSpinnerBelow = function(k, tbl)		for i=k+1,#tbl do			if tbl[i] and tbl[i].enabled then				return tbl[i]			end		end		return nil	end	for k = 1, #self.left_spinners do		local abovespinner = GetFirstEnabledSpinnerAbove(k, self.left_spinners)		if abovespinner then			self.left_spinners[k]:SetFocusChangeDir(MOVE_UP, abovespinner)		end		local belowspinner = GetFirstEnabledSpinnerBelow(k, self.left_spinners)		if belowspinner	then			self.left_spinners[k]:SetFocusChangeDir(MOVE_DOWN, belowspinner)		else			self.left_spinners[k]:SetFocusChangeDir(MOVE_DOWN, self.applybutton)		end		if self.right_spinners[k] then			self.left_spinners[k]:SetFocusChangeDir(MOVE_RIGHT, self.right_spinners[k])		end	end	for k = 1, #self.right_spinners do		local abovespinner = GetFirstEnabledSpinnerAbove(k, self.right_spinners)		if abovespinner then			self.right_spinners[k]:SetFocusChangeDir(MOVE_UP, abovespinner)		end		local belowspinner = GetFirstEnabledSpinnerBelow(k, self.right_spinners)		if belowspinner	then			self.right_spinners[k]:SetFocusChangeDir(MOVE_DOWN,belowspinner)		else			self.right_spinners[k]:SetFocusChangeDir(MOVE_DOWN, self.cancelbutton)		end		if self.left_spinners[k] then			self.right_spinners[k]:SetFocusChangeDir(MOVE_LEFT, self.left_spinners[k])		end	end	self.applybutton:SetFocusChangeDir(MOVE_UP, self.left_spinners[#self.left_spinners])	self.cancelbutton:SetFocusChangeDir(MOVE_UP, self.right_spinners[#self.right_spinners])endfunction ModConfigurationScreen:GetHelpText()	local t = {}	local controller_id = TheInput:GetControllerID()	if self:IsDirty() then		table.insert(t, TheInput:GetLocalizedControl(controller_id, CONTROL_ACCEPT) .. " " .. STRINGS.UI.HELP.APPLY)		table.insert(t, TheInput:GetLocalizedControl(controller_id, CONTROL_CANCEL) .. " " .. STRINGS.UI.HELP.BACK)	else		table.insert(t, TheInput:GetLocalizedControl(controller_id, CONTROL_CANCEL) .. " " .. STRINGS.UI.HELP.BACK)	end	if self.leftbutton.shown then     	table.insert(t,  TheInput:GetLocalizedControl(controller_id, CONTROL_PAGELEFT) .. " " .. STRINGS.UI.HELP.SCROLLBACK)    end    if self.rightbutton.shown then    	table.insert(t,  TheInput:GetLocalizedControl(controller_id, CONTROL_PAGERIGHT) .. " " .. STRINGS.UI.HELP.SCROLLFWD)    end	return table.concat(t, "  ")endreturn ModConfigurationScreen

in short:

line 41

--				table.insert(options, {name = v.name, options = v.options, default = v.saved or v.default})				table.insert(options, {name = v.name, options = v.options, default = v.default, value = v.saved})
line 119

--		table.insert(settings, {name=v.name, options=v.options, default=v.default, saved=curr_setting})		table.insert(settings, {name=v.name, options=v.options, default=v.default, saved=v.value})

Share this post


Link to post
Share on other sites
Rincevvind    60

I'm getting console spam. Lags game.

 

2z6bcjn.jpg

 

My Script

local function Clock_PostInit(inst)    local original_OnUpdate = inst.OnUpdate    inst.OnUpdate = function(self, dt)        original_OnUpdate(self, dt)		if GetModConfigData("Infinite Health") == "ON" then			----------------------------------------------------------			local Health = GetPlayer().components.health			----------------------------------------------------------			if Health and Health:GetPercent() < 1 then				Health:DoDelta(Health.maxhealth, true)			end		end		if GetModConfigData("Infinite Hunger") == "ON" then					----------------------------------------------------------			local Hunger = GetPlayer().components.hunger			----------------------------------------------------------			if Hunger and Hunger:GetPercent() < 1 then				Hunger:DoDelta(Hunger.max, true)			end		end		if GetModConfigData("Infinite Sanity") == "ON" then			----------------------------------------------------------			local Sanity = GetPlayer().components.sanity			----------------------------------------------------------			if Sanity and Sanity:GetPercent() < 1 then				Sanity:DoDelta(Sanity.max, true)			end		end	endendAddComponentPostInit("clock", Clock_PostInit)

your issue is pretty easy to "fix"

you dont need to use API function for each time that object creating.

load them into variables and use in your function

at modmain.lua start:

local infSanity = GetModConfigData("Infinite Sanity").....if infSanity then ...

Share this post


Link to post
Share on other sites
Silentdarkness1    1,139

I'm seriously considering unreleasing U&A, since it's unplayable anyway.

Sadly, I have to agree with you there. But, apparently, the fix is supposed to be in early next week. So, i'd say wait until then, and if he STILL needs more time to fix it, then pull it.

Share this post


Link to post
Share on other sites