• Content Count

  • Joined

  • Last visited

Community Reputation

2623 Excellent

About simplex

  • Rank
    Couldn't be Simpler


Recent Profile Visitors

14253 profile views
  1. I plan on uploading it to Steam once the DS and DST codebases are fully unified. The version posted here does also work under singleplayer Don't Starve, though it still has some glitches I have to iron out in that case.
  2. I'll double check. It was working fine until the last game update, but it's possible that upgrade broke it.
  3. @Ipsquiggle This is only fringe-related to this topic, but is there a way to obtain a shard name from a shard id at the Lua level? The engine level log prints show the engine has that info, but I haven't found any way to access it from Lua.
  4. @Ipsquiggle If I may ask, what's your current schedule? I mean, how is your time currently divided between the worldgen data and modding API changes discussed here, bug fixing DST and working on Through the Ages? I ask mostly to have a feel for what kind of request is reasonable or not.
  5. Try AddTaskSetPreInitAny(function(task_set_data) local task_names = task_set_data.tasks --// Do stuff with task_names, but note this must be done within this function. end)
  6. We use custom world and world network entities, so adding a preinit wouldn't even be necessary. There are certainly workarounds, but the major concern is user transparency: having a worldgen option which does nothing (because there's code overriding the override) feels off and glitchy. I'd say this is a low priority request, but a relatively simple one (due to not requiring affecting other game subsystems) to be worth it.
  7. @Ipsquiggle I just remembered a request: could you allow for some override options in levels being hidden from its customizationtab in server creation? For example, U&A requires rain to be set to "always"; it doesn't actually rain in U&A, this is just an element of how we represent the static mechanic and make it relate to vanilla prefabs. Changing rain to anything other than "always" will in fact break the mod, so having that be a spinner in customizationtab is really bad. The issue is, now that level overrides are in a key-value dictionary instead of an array of tuples, the simplest solution (adding support for an optional 'hideinfrontend = true' entry in the tuples themselves) is no longer available, so a custom scheme is necessary. Maybe an optional 'hiddenoverrides' array of override key names added to level data, which is checked by customizationtab to exclude certain override options from the frontend, but ignored by everything else in the game?
  8. It works online, but it's a security risk to use it that way. When online, you can already give admin access to users. It's only when offline that dedicated servers refuse any remote commands. So while you can use it online, I wouldn't recommend it.
  9. Using this mod in an online server would be a huge security hole. Obfuscation of the inner workings would do very little to protect from a clever attacker, as the mod wasn't designed with security in mind. Please just use this mod in offline servers. This is mainly meant for testing servers, but if you're going to use it in real games then restrict them to LAN.
  10. @Ipsquiggle One possible solution for the custom tile id stability concern I pointed out above is the following scheme: Each mod would have its own tile id range space, starting from 1 (or 0, but I mean this is Lua). The game would reserve a flat range of tile ids for all mods (say, all integers greater than or equal to 64 and strictly less than GROUND.UNDERGROUND, though it'd be nice if the range could be infinite). The game, after loading modworldgenmain.lua from each mod, would build a mod tile id translation table, let's call it 'mods_custom_tiles', mapping each modname (that is, the mod directory name) to its array of real tile ids; the indices in this array would be the mod-space tile ids for its custom tiles, and each associated value would be the corresponding real tile id. Whenever a world saves, this lookup table would be reversed, let's call the result world_tile_overrides, with world_tile_overrides mapping the real id of each custom tile to a pair {modname, modspace_tile_id}. This table would be constructed in the following way: function GetWorldTileOverrides(mods_custom_tiles) local world_tile_overrides = {} for modname, custom_tiles in pairs(mods_custom_tiles) do for mod_id, real_id in pairs(custom_tiles) do if world_tile_overrides[real_id] ~= nil then error("There is a logic error in AddTile, the same real id should never be handed out twice.") end world_tile_overrides[real_id] = {modname, mod_id} end end return world_tile_overrides end It is important that this table be re-generated whenever a world saves (i.e., whenever world savedata is generated and serialized), and not just once when it's baked in worldgen. Then world_tile_overrides would be saved in the world data of each level. Whenever world savedata is loaded, for each tile 't_id' in the map data map them through the following: --[[/* -- 't_id' stands for a tile id loaded from the map savedata. -- -- 'world_tile_overrides' comes from the map savedata. -- -- 'mods_custom_tiles' was built from the *current game instance*, right after -- mods finished loading. -- -- -- The purpose of this function is to perform a dual lookup on tile ids, -- translating them if needed under the premise that it is the '{modname, -- mod_ids}' pairs that should remain fixed. --*/]] function MigrateTileId(t_id, world_tile_overrides, mods_custom_tiles) --// We check if there is any custom tile override in the savedata. local t_override = world_tile_overrides[t_id] if t_override then --[[/* -- If there is, then we use the mods_custom_tiles from the *current* -- game state. --*/]] local modname, mod_id = t_override[1], t_override[2] --[[/* -- Here we get the lookup table for a specific mod, mapping its mod -- tile ids to the global, real ones. If this table or any of the -- needed subentries are nil, then we leave the tile id alone. This -- should prevent breaking savedata if a mod adding custom tiles is -- disabled. -- -- If I recall correctly, the game renders invalid tile ids as -- GROUND.GRASS. --*/]] local lookup = mods_custom_tiles[modname] if lookup ~= nil then local new_t_id = lookup[mod_id] if new_t_id ~= nil then t_id = new_t_id end end end return t_id end Unless I'm missing something, this should fully ensure id stability. EDIT: Modspace tile ids could also be modder-given strings ids, instead of consecutive numbers. This would make the code more robust, because it wouldn't break a mod that reorders its AddTile call chain or removes an element from it. The code above supports this without change.
  11. @Ipsquiggle If you do implement official AddTile support, the major concern (and the only thing that can't really be done from mods themselves) is ensuring numerical stability of tile ids. Suppose mods A and B were enabled at worldgen, with both adding custom tiles and A loading before B. Now suppose, an unspecified time later, A is updated and now further adds a new tile. If the new tile ids are just handled consecutively to each AddTile call performed by mods, this means B's tile ids have now been shifted by 1 to the right; but now loading a world from before A's update will have B's first custom tile mapped into A's new tile, breaking the save. This is why in my tile_adder.lua implementation I require custom tile ids to be set manually, with a not so reasonable request that they differ from any id used by other mods adding tiles.
  12. Sorry, but I couldn't resist: U&A has no problem with this precisely because it uses require instead of modimport (the modworldgen.lua is just a stub doing the require'ing) :P. @Kzisor Is this mod no longer working for you? The actual code is the file tile_adder.lua, which when modimported adds a AddTile function to the mod environment (without overriding any game files and such). The rest of the mod is just sample usage of it. This is still what U&A successfully uses in both DS and DST. It's possible I changed the version within wicker/U&A since that mod was posted, though, so if that implementation no longer works I should be able to extract the current one from wicker/U&A and reupload it.
  13. Lua's default debug.traceback() also doesn't use the stack level number passed to error (though if called on its own it does accept its own starting level parameter). This level number is only used for the error header (i.e., the first, non-indented line), which is generated by error itself before passing it as the first argument to debug.traceback (or whichever was configured as the error handler). The game does respect error's second argument in the same way "vanilla Lua 5.1" does. For example, running this program under the standard Lua 5.1 interpreter function f() error("test", 2) end function g() f() end g() produces the following as output: lua5.1: errortest.lua:6: test stack traceback: [C]: in function 'error' errortest.lua:2: in function 'f' errortest.lua:6: in function 'g' errortest.lua:9: in main chunk [C]: ? Well, as I mentioned this is just a "minor pet peeve" of mine. What I mean by this oxymoron is that, while this isn't super relevant, the shortcomings of modimport compared to the standard Lua utilities have always greatly irked me. But I agree making it clear what are the differences between modimport and require would be the ideal scenario. Too often already we see threads with modders asking how they can get mod configuration data from outside of modmain; understanding environments and how they may be manipulated is definitely an advanced knowledge of Lua, and one required (no pun intended) if one wishes to truly drop modimport.
  14. @Ipsquiggle Could you add a preinit to world savedata? Run over the savedata parameter of PopulateWorld (gamelogic.lua) before it gets passed into PopulateWorld? I used to do this by patching the PopulateWorld function itself (which used to be global), but now that it's local I haven't found a good way to do it (it's hard to get stable references to it as an upvalue).
  15. @Ipsquiggle On each topic, saving #2 for last because it's a minor pet peeve of mine. 1. Level data format. This is great. In order to get U&A/wicker compatible with both DS and DST, what I had been doing is adopt a custom level data representation format, a superset of both the DS and DST formats, which then got normalized into a temporary internal format (which already required a location to be set), which finally got turned into the respective game's level data format. And this temporary internal format was... exactly DST's version 2 format. So all this change does is save me some work in the DST case. 3. worldgenoverride.lua changes. These are nice, making worldgenoverride's format more consistent with level data representation. Not much else to be said. 4. EnableModError. I love the intention behind this, but I have some objections to its implementation. Firstly, moderror and modassert should behave like their stdlib counterparts: error and assert. Namely, moderror should take a second optional parameter, level, which is passed to the stdlib error function to indicate the stack level where the error occurred (the current implementation will always point to modutil.lua:79 as the source of the error). If this value is nil, then it should default to 1. Then, only if it is non-zero, one should be added to it (since the moderror function itself adds a stack level to the call stack). This is the code for what I suggest: function moderror(message, level) local modname = (global('env') and env.modname) or ModManager.currentlyloadingmod or "unknown mod" local message = string.format("MOD ERROR: %s: %s", ModInfoname(modname), tostring(message)) if KnownModIndex:IsModErrorEnabled() then level = level or 1 if level ~= 0 then level = level + 1 end return error(message, level) else print(message) -- I'm just making it explicit that we should return nothing in this case. -- A true 'nothing', such as below, makes select("#", moderror(stuff)) return zero. return end end In similar spirit, modassert's second parameter should default to some string if nil (not necessarily "assertion failed!", though that's a fine pick). But more importantly, modassert should return its first argument if an error was not raised. A very common use of assert is in 'var = assert(this_must_never_be_nil_or_false())'. This is the code for what I suggest: function modassert(test, message) if not test then return moderror(message or "assertion failed!", 2) else return test end end Secondly, there should be a modinfo flag that forces mod error enabling. Defensive programming is a perfectly valid (and personal favorite) style of programming, with strict errors making it much easier to detect and fix bugs from user reports, whereas stealth log prints lead to hard to find bugs and users confused about why something isn't working correctly. If you'd like to add a modsettings option which force disables mod errors, that's fine, but the mod creator should have the say on whether the default will be strict or permissive mode. 2. New Mod API methods, and some changes. This is not the first time we've had this discussion, but I don't think it's feasible to suggest modimport can replace require. Using require expresses a functional dependency tree, with files being loaded at most once and having a return value. On the other hand, modimport does no caching and discards the return values of the loaded chunk, forcing the use of stateful constructs (such as the setting of variables in the mod environment to track duplicate loading or "returning" values). I have, for years now, used custom require-like functionality which loads files like require but in a specified environment, which while not the mod environment itself, imports most of it, with specific care to import what's needed to play nice with the game's mod error handling system (that is, it includes 'env', 'modname', 'MODROOT', 'modinfo', etc.). If the new mod error handling system requires anything else from the mod environment, let me know and it'll be there, but I won't use modimport when it's a dofile that discards return values.