Otaara Posted December 1, 2016 Share Posted December 1, 2016 Would it be possible to make a mod that would let admins turn giant spawning on and off after a world has been created? Either a mod with a setting you could change if no one's in the world, or maybe some kind of actual switch in the world that players could build and then trigger. The first would be ideal, because it could have individual settings, Bearger on/off, etc., but one switch to turn them all on or off would work, too. I tend to play with only a couple other people, and it's not uncommon for us to end up soloing, which we're all fine with until a giant smashes a bunch of stuff when there's only one person on. It's basically like having griefers built into the game, not exciting, just annoying during the first couple seasons when we're extra vulnerable. If there's some incredibly obvious reason why this is a big NO, apologies. I'm reasonably tech-savvy, but I've opened exactly zero DST files to see how anything works. Link to comment Share on other sites More sharing options...
blubuild Posted December 1, 2016 Share Posted December 1, 2016 Haven't looked, but it'd say it's very possible, and not particularly hard if you grasp Lua. In general, the game is highly moddable. Even where it isn't, you can often still accomplish stuff using tricks such as the debug library, but it shouldn't be necessary for what you want. Link to comment Share on other sites More sharing options...
Otaara Posted December 2, 2016 Author Share Posted December 2, 2016 Thanks, that's good motivation to dip my toes in. Link to comment Share on other sites More sharing options...
Otaara Posted December 2, 2016 Author Share Posted December 2, 2016 So. . . if I set configuration options for each seasonal boss like so -- { name = "BeargerOn", label = "Bearger", options = { {description = "Yes", data = 1}, {description = "No", data = 0}, }, default = 1, }, and then give these instructions -- if GLOBAL.TheNet and GLOBAL.TheNet:GetIsServer() then if not BeargerOn then beargerspawner:SetFirstBeargerChance(0) beargerspawner:SetSecondBeargerChance(0) if not DeerclopsOn then deerclopsspawner:OverrideAttacksPerSeason("DEERCLOPS", 0) deerclopsspawner:OverrideAttackDuringOffSeason("DEERCLOPS", false) if not MooseGooseOn then moosespawner:OverrideAttackDensity(0) if not DragonflyOn then DRAGONFLY_RESPAWN_TIME = TUNING.TOTAL_DAY_TIME * 9999, DRAGONFLY_SPAWN_TIME = TUNING.TOTAL_DAY_TIME * 9999 end should that do it? If I have it right, if an admin selects "No" for a given giant, the mod would overwrite whatever frequency they select in the server world generation menu and prevent spawning; if they select "Yes," it won't do anything, i.e., whatever they set up in world generation would go ahead and happen. After starting a world, if they shut the server down and change any setting from "Yes" to "No," that would prevent any giants of that type from spawning. If they shut down and change a setting from "No" to "Yes," giants would spawn according to their world generation settings. I think? Can it be that easy? (All the giants' spawn code is written differently. Dunno why; I just looked up what was in the tuning overwrite file and used that.) Link to comment Share on other sites More sharing options...
blubuild Posted December 2, 2016 Share Posted December 2, 2016 (edited) No, that will never work, because you didn't sufficiently read about Lua and check out (read, also) how existing mods are written and how they work. However, even though you have some fundamental syntax and usage/understanding errors, you're actually not far off at all! 6 hours ago, Otaara said: I think? Can it be that easy? Yes. It's easy for me to take your code and make it functional, there's no need for much additions to it. Hopefully making such a modification will work as desired in on ongoing world. I'll just point out the problems in the posted code. First, your modinfo.lua (configuration options) code looks fine provided you only posted a part of it. Although many don't utilize this, it's more convenient to use true and false as the options' data. The values 0 and 1 are equivalent to true and false in some languages, but in Lua both 0 and 1 are true values; so if you write if 0 then ... end, then the code in the block will run (replace 0 with false or nil, then it won't). All values except false and nil are true values, in Lua. Suggested modinfo.lua contents: Spoiler --my template name = "My mod name" version = "My mod version" --e.g. "1.0" description = " Mod version: "..version.."\n".. [[My mod description. This mod does X and Y. Thanks to Klei and to my mother!]] author = "myname" icon_atlas = "mod.xml" icon = "modicon.tex" forumthread = "" --leaving empty string causes game button to link to Steam Workshop page, if appropriate api_version = 10 -- Specify compatibility with the game! dont_starve_compatible = false reign_of_giants_compatible = false shipwrecked_compatible = false dst_compatible = true client_only_mode = false all_clients_require_mod = false configuration_options = { { label = "Bearger spawning", -- label in mod config GUI name = "beargerdisabled", -- name used by code (in other Lua files) to refer to this option, can be whatever you want. hover = "Control whether Bearger spawning is enabled or not.", -- hover text, optional options = { {description = "Off", data = true, hover = "aaa"}, -- hover optional, again {description = "On", data = false, hover = "bbb"}, }, default = false, -- by default the option will be the "On" option. }, { -- next config option label = "Deerclops spawning", name = "deerclopsdisabled", -- [...] }, -- [...] } In your modmain.lua code, we've got a bunch of programming issues. First, no if statement except the first one has a closure to its block - Lua ifs are in the form of if Condition then Statement end. It's kind of common to forget, indeed, in my experience, especially since we rely on indentation (tabbing/spaces) to intuitively read code with our eyes - but the computer/compiler doesn't care, one missing character can cause code to fail. Indentation/whitespace is mostly meaningless in Lua (yes, you could write everything on one line if you really wanted to), unlike in, say, Python. Lua ifs can also be in the form of if Condition1 then Statement1 elseif Condition2 then Statement2 else Statement3 end , et cetera. Second, the code keeps referring to variables that don't exist. if not BeargerOn then The symbol BeargerOn doesn't refer to anything that already exists in the mod environment (e.g., no local or global var). So it will be interpreted as referring to a global var that hasn't been assigned yet, and will return a nil value. This won't cause an error, but will cause the code in the if block to always run. To access a mod configuration option, we use the built-in GetModConfigData("my label") function. It returns the data value of the currently selected option (of the config option "my label" refers to) of the currently running mod. You can store this value in a var with whatever name you want, or just use it directly, if you only need to use it once. beargerspawner:SetFirstBeargerChance(0) Nothing called beargerspawner exists in the mod environment, either. So this will be interpreted as nil:SetFirstBeargerChance(0), which will cause an error. beargerspawner is set up in the game as a component of the world (Klei's forest.lua initializes it with inst:AddComponent("beargerspawner") ) . So you must access it in such a way, using a reference to the world, which can be found outside the mod environment in a global var named TheWorld (from within the mod environment, you must access it with "GLOBAL.TheWorld"). DRAGONFLY_RESPAWN_TIME = TUNING.TOTAL_DAY_TIME * 9999, DRAGONFLY_SPAWN_TIME = TUNING.TOTAL_DAY_TIME * 9999 The comma is not appropriate here, because this isn't in a table definition (like it was in tuning_override.lua where you took it from). Ignoring that, DRAGONFLY_RESPAWN_TIME will again refer to a var that doesn't exist, so it will end up creating a new var with that name in the mod environment, and assign the value to it. In the game, DRAGONFLY_RESPAWN_TIME is a field in the global TUNING table. It can be accessed from a mod environment using just "TUNING", because Klei decided to give us a shortcut for that. "GLOBAL.TUNING" is equivalent and may also be used. Here's a list of what vars are directly accessible from the mod environment... Spoiler --DS/T Lua version is a slightly modified version of Lua 5.1.4 --[[ directly available in mod environment: lua pairs ipairs print math table type string tostring game Class TUNING CHARACTERLIST Prefab Asset Ingredient cookerrecipes --the mod's recipes mod functions GetModConfigData AddLevelPreInit AddLevelPreInitAny, AddTaskSetPreInit AddTaskSetPreInitAny AddTaskPreInit AddRoomPreInit AddLocation AddLevel AddTaskSet AddTask AddRoom AddStartLocation AddGamePostInit AddSimPostInit AddGlobalClassPostConstruct AddClassPostConstruct -- modmain only (not in worldgen): AddPrefabPostInitAny AddPrefabPostInit AddPlayerPostInit AddBrainPostInit AddIngredientValues AddCookerRecipe AddModCharacter RemoveDefaultCharacter AddRecipe AddRecipeTab AddAction AddComponentAction AddMinimapAtlas AddStategraphActionHandler AddStategraphState AddStategraphEvent AddStategraphPostInit AddComponentPostInit LoadPOFile RemapSoundEvent AddReplicableComponent AddModRPCHandler GetModRPCHandler SendModRPCToServer GetModRPC SetModHUDFocus AddUserCommand AddVoteCommand ExcludeClothingSymbolForModCharacter worldgen GROUND LOCKS KEYS LEVELTYPE utility GLOBAL modname --name of the mod's folder (i.e. often "workshop-XXXXXXXXX"). for name from modinfo.lua, use GLOBAL.KnownModIndex:GetModFancyName(modname). use GLOBAL.ModInfoname(modname) to get a string with both names (informative for debug purposes). modinfo --contains the environment of the mod's modinfo.lua: all vars in it are stored in this table, including any custom ones. MODROOT env modimport moderror modassert extra CurrentRelease ReleaseID postinitdata postinitfns deprecated AddGameMode Recipe MOD_RPC added global functions by klei (GLOBAL.xx needed to access the standalone ones): util.lua string.split(str,sep) - splits str to parts by specified separator, returns array (ordered table i.e. with sequential numeric keys begining at 1) of the resulting strings table.contains(t, element) - returns true if element exists as a value within t table.containskey(t, k) - returns true if k exists as a key within t. usually simply using "t[k]" instead will suffice (accessing the value of a missing key will give back 'nil' and will not raise an error). table.reverse(t) - returns a new table with the elements of array t in reversed order. table.invert(t) - returns a new table with the key-value pairs of t swapped table.reverselookup(t,val) - looks for val as a value in t and returns its corresponding key (or nil) RemoveByValue(t, val) - removes (as with table.remove()) all occurrences of val in array t. GetTableSize(t) - counts and returns the number of key-value pairs in table t. can be useful for tables without sequential numeric keys, where the value of #t is undefined. math.inf - constant, equal to 123/0. identical to lua math.huge... GetRandomItem(choices) GetRandomItemWithIndex(choices) PickSome(num, choices) --handles arrays. modifies given 'choices' table. PickSomeWithDups(num, choices) JoinArrays(...) --concatenates multiple arrays. returns a new sequential array with the contents of all values within the arrays given as arguments (including any duplicate values). ExceptionArrays(tSource, tException) --returns a new array with the difference between the two provided arrays ArrayUnion(...) --merge multiple arrays into a new table that is returned, while only allowing each value once ArrayIntersection(...) --return only values found in all given arrays MergeMaps(...) --merge two map-style tables, overwriting duplicate keys with the latter map's value MergeMapsDeep(...) --merge two map-style tables, overwriting duplicate keys with the latter map's value. subtables are recursed into. MergeKeyValueList(...) SubtractMapKeys(base, subtract) ExtendedArray(orig, addition, mult) --returns a copy table of orig with 'addition' appended 'mult' (or 1) times to the end. GetRandomKey(t) --returns a random key picked from any key-value table t GetRandomMinMax(min, max) --returns rand number between min,max inclusive. why not just use math.random(min,max)?? distsq(v1, v2, v3, v4) --returns the squared distance between points described by numbers or vectors resolvefilepath( filepath, force_path_search ) --looks for a file in the mod and data directories. errors if not found, returns path found otherwise. softresolvefilepath(filepath, force_path_search) --similar to above. returns nil if file not found. kleifileexists(filepath) --hardcoded function. checks if filepath exists. path navigations starts from "/Don't Starve Together/data", so a filepath beginning with "../" corresponds to the game's root folder. from there you can access a file in any folder. --f.ex. GLOBAL.kleifileexists("../bin/fmodex.dll") and GLOBAL.kleifileexists("../bin/dontstarve_steam.exe") will both return true. --same for GLOBAL.kleifileexists(MODROOT.."modmain.lua") --if filepath is not found then nothing is returned, otherwise filepath is returned. --NOTE: filename is CASE SENSITIVE. isnan(x) --true if x ~= x e.g. isnan(0/0) isinf(x) --true if x is inf or -inf isbadnumber(x) --true if isinf(x) or isnan(x) weighted_random_choice(t) --t is a key-value table of choice-weight (weight between 0.0 and 1.0). returns a weigthed random key of t. PrintTable(t) --returns a multilne string detailing the contents of t, including any sub-tables within it (infinite recursion) shuffleArray(t) --randomly shuffles array t (modifies it) and returns t. shuffledKeys(t) --returns a new array with the keys of table t shuffled randomly. sortedKeys(t) --returns a new array with the keys of table t sorted from smallest to largest. deepcopy(t) --returns a full (arbitrarily deep) copy of table t including copies of all its subtables, if any. LinkedList = Class() --functions: Append(), Remove(), Head(), Tail(), Clear(), Count(), Iterator() table.setfield(Table,Name,Value) table.getfield(Table,Name) table.findfield(Table,Name) table.findpath(Table,Names,indx) checkbit(x, bit) string.random(Length, CharSet) --Length: number. CharSet: string, optional; e.g. %l%d for lower case letters and digits function HexToRGB(hex) --Returns the 0 - 255 color of a hex code function RGBToPercentColor(r, g, b) --Returns the 0.0 - 1.0 color from r, g, b parameters function HexToPercentColor(hex) --Returns the 0.0 - 1.0 color from a hex parameter\ global() -- "declare" a new global that can be used in the main environment (GLOBAL). required due to strict.lua. ]] Fixed modmain.lua: Spoiler if GLOBAL.TheNet and GLOBAL.TheNet:GetIsServer() then -- code inside runs only on servers (or client-hosted servers) if GetModConfigData("beargerdisabled") then local my_bearger_spawner = GLOBAL.TheWorld.components.beargerspawner my_bearger_spawner:SetFirstBeargerChance(0) my_bearger_spawner:SetSecondBeargerChance(0) end if GetModConfigData("deerclopsdisabled" then local deerclopsspawner = GLOBAL.TheWorld.components.deerclopsspawner deerclopsspawner:OverrideAttacksPerSeason("DEERCLOPS", 0) deerclopsspawner:OverrideAttackDuringOffSeason("DEERCLOPS", false) end -- [...] if GetModConfigData("dragonflydisabled") then TUNING.DRAGONFLY_RESPAWN_TIME = TUNING.TOTAL_DAY_TIME * 9999 TUNING.DRAGONFLY_SPAWN_TIME = TUNING.TOTAL_DAY_TIME * 9999 end end Well, to tell the truth, it still won't work, and will cause an error if anything but the Dragonfly is disabled - because modmain.lua runs before the game world is initialized, meaning that GLOBAL.TheWorld is nil at that time (although TUNING is already valid). We can get around that by putting our code in a separate function, and setting that function up to be called after the world starts up, so that the timing is right, using either the AddGamePostInit or the AddSimPostInit function (the difference between the two is negligible, as far as I know). Final modmain.lua: Spoiler if GLOBAL.TheNet and GLOBAL.TheNet:GetIsServer() then -- no need to do anything at all on non-servers, so all the code can be inside this local function modify_giants() if GetModConfigData("beargerdisabled") then local my_bearger_spawner = GLOBAL.TheWorld.components.beargerspawner my_bearger_spawner:SetFirstBeargerChance(0) my_bearger_spawner:SetSecondBeargerChance(0) end if GetModConfigData("deerclopsdisabled" then local deerclopsspawner = GLOBAL.TheWorld.components.deerclopsspawner deerclopsspawner:OverrideAttacksPerSeason("DEERCLOPS", 0) deerclopsspawner:OverrideAttackDuringOffSeason("DEERCLOPS", false) end -- [...] if GetModConfigData("dragonflydisabled") then -- this block can also be outside the function TUNING.DRAGONFLY_RESPAWN_TIME = TUNING.TOTAL_DAY_TIME * 9999 TUNING.DRAGONFLY_SPAWN_TIME = TUNING.TOTAL_DAY_TIME * 9999 end end AddSimPostInit(modify_giants) -- the modify_giants function will be called after the game simulation starts end Edited December 2, 2016 by blubuild clarification Link to comment Share on other sites More sharing options...
Serpens Posted December 2, 2016 Share Posted December 2, 2016 (edited) @blubuild are you sure it is even possible to change the settings after the game was already launched? I don't know a way how this could be possible. I ask this, because you are writing that it should be very easy... I don't think so. (of course you can change TUNING values, but is this enough?) okay, thinking about this, it really should be possible to change every single value. But not the setting itself. So to enable/disable one single setting you might end up changing 10 or more values... still a bit complicated for a beginner to find out how to change what values... Edited December 2, 2016 by Serpens Link to comment Share on other sites More sharing options...
blubuild Posted December 2, 2016 Share Posted December 2, 2016 There's no problem with changing settings on the fly. The code will do that (well, it will do the same thing that selecting the setting pre-world-generation will do, that's where he got the code from). It can depend on how things work, and the timing of execution (since we modify them Post Sim Init, and some things could run before that, that use the settings). But you can, for example, change the TUNING value of something (e.g. the amount of damage a spear does), and all code that runs subsequently after that (including the damage function when a spear is used) will be "updated" accordingly to use the new value, provided it refers directly to TUNING.xxx . Etc. I didn't check if his approach worked, but if not, it will still be fairly simple to block the giant generation by modifying those spawning components (e.g. just remove the component from the world, or override the main or spawning function to do nothing, or something to that extent), or add a PrefabPostInit("deerclops",deleteme) to immediately delete it as soon as it's spawned, etc. So still easy for a beginner. But if merely changing the settings can work, then it's a cleaner solution, I guess. Link to comment Share on other sites More sharing options...
Otaara Posted December 3, 2016 Author Share Posted December 3, 2016 Thanks for the discussion & very extensive input. (Why on earth would a language make 0 a true value? Rhetorical question -- I'm sure there's some reason, but that is certainly unusual. Noted for the future.) I'll see if I can indeed make the mod not crash the game, and hopefully not spawn giants at will. @Serpens -- why were you concerned that changing a particular setting would have wider effects as you describe? I get @blubuild's answer, since that was pretty much what I was thinking when I came up with the idea, but I'm curious about what you were thinking just because there may be useful things in there that I don't know. I am going to ding you for unwarranted gender assumptions, though -- 10 hours ago, blubuild said: that's where he got the code from 10 hours ago, blubuild said: his approach Come on, I use a female Homestuck character as an av and I have a username that sounds girly, at least to English speakers -- I don't even look gender-neutral online, I look very probably female. There are girls who code, and there have been, now, for decades. There are even full-on middle-aged women who poke around inexpertly off and on. Link to comment Share on other sites More sharing options...
Serpens Posted December 3, 2016 Share Posted December 3, 2016 (edited) 7 hours ago, Otaara said: @Serpens -- why were you concerned that changing a particular setting would have wider effects as you describe? I get @blubuild's answer, since that was pretty much what I was thinking when I came up with the idea, but I'm curious about what you were thinking just because there may be useful things in there that I don't know. I think everything is fine All I wanted to say is that there is no way to change setting directly, like just making sth like GLOBAL.Setting.Beager = Medium + all other values are adjusted accordingly. You have to find out and change those values yourself, like you already do. Edited December 3, 2016 by Serpens Link to comment Share on other sites More sharing options...
blubuild Posted December 3, 2016 Share Posted December 3, 2016 Sorry - I don't think that there aren't women who program, of course. I don't think it's particularly bad to assume or take language shortcuts, and personally I care only about the person and not so much about his/her nickname or gender, so I don't pay those too much attention (not that I'd have managed to confirm your gender from your nickname and avatar anyway, the former appearing as much random and gender-neutral to me as, say, my nick or Serpen's). Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now