Jump to content

On/off switch for giants: possible?


Recommended Posts

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

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

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

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 by blubuild
clarification
Link to comment
Share on other sites

@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 by Serpens
Link to comment
Share on other sites

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

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

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 by Serpens
Link to comment
Share on other sites

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

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
  • Create New...