Jump to content

How to change a local function of a a prefab that is conjoined with other prefabs?


Recommended Posts

Many prefabs in the game have their own files where most of their relevant code is written in. Some prefabs, however, such as an amulet, are all declared or whatever in one file, so in my case, the lazy forager shares a file with code with all other amulets. This means that it's all technically one prefab with different types of instances, or something along the lines, if I'm understanding this correctly. So, while for Flower and Evil Flower, this type of code structure in a file will work and change things accordingly, it crashes the game when I'm trying to work with the lazy forager:

local function orangeChanged()
    local inst = commonfn("orangeamulet")

    if not TheWorld.ismastersim then
        return inst
    end

    -- inst.components.inspectable.nameoverride = "unimplemented"
    -- inst:AddComponent("useableitem")
    -- inst.components.useableitem:SetOnUseFn(unimplementeditem)
    inst.components.equippable:SetOnEquip(onequip_orange)
    inst.components.equippable:SetOnUnequip(onunequip_orange)

    inst:AddComponent("finiteuses")
    inst.components.finiteuses:SetOnFinished(inst.Remove)
    inst.components.finiteuses:SetMaxUses(TUNING.ORANGEAMULET_USES)
    inst.components.finiteuses:SetUses(TUNING.ORANGEAMULET_USES)

    inst:AddComponent("fueled")
    inst.components.fueled.fueltype = FUELTYPE.NIGHTMARE

    MakeHauntableLaunch(inst)

    return inst
end

local function orangeInitChanged(prefab)
    prefab.components.commonfn.orange = orangeChanged
end

AddPrefab("amulet", orangeInitChanged)

And this is the error it gives me in the logs:

[string "../mods/Changed values/scripts/fuelableChanged.l..."]:31: attempt to call global 'AddPrefab' (a nil value)

So, could I have some ideas on what is going on and how exactly I could make it not crash? Please, explain how you've structured it too and why what is declared is needed, because coding in terms of modding seems to be even harder to comprehend than just reading code written for a game itself.

Oh, and in terms of what I am trying to do, it's to make the lazy forager refuelable... if Klei isn't gonna do this, someone's gotta!

Link to comment
Share on other sites

12 hours ago, EuedeAdodooedoe said:

This means that it's all technically one prefab with different types of instances, or something along the lines, if I'm understanding this correctly.

Nope.

It's still a separate prefab. In amulet.lua file there's a separate function for every amulet and a "commonfn" function, which is used by every prefab.

Prefab("amulet", red, assets),

See, here's where the Prefab is created: "amulet" is the name of the prefab, "red" is the function we're going to use, and "assets" is, well, the assets table that this prefab's going to use.

Here's the "red" function:

local function red()
    local inst = commonfn("redamulet", "resurrector")

    if not TheWorld.ismastersim then
        return inst
    end

    -- red amulet now falls off on death, so you HAVE to haunt it
    -- This is more straightforward for prototype purposes, but has side effect of allowing amulet steals
    -- inst.components.inventoryitem.keepondeath = true

    inst.components.equippable:SetOnEquip(onequip_red)
    inst.components.equippable:SetOnUnequip(onunequip_red)

    inst:AddComponent("finiteuses")
    inst.components.finiteuses:SetOnFinished(inst.Remove)
    inst.components.finiteuses:SetMaxUses(TUNING.REDAMULET_USES)
    inst.components.finiteuses:SetUses(TUNING.REDAMULET_USES)

    inst:AddComponent("hauntable")
    inst.components.hauntable:SetHauntValue(TUNING.HAUNT_INSTANT_REZ)

    return inst
end

You can see that at first we call "commonfn" function, which contains base things that are used by every amulet. It's made a local variable and we add stuff to it.

Why is it done like that? I guess it's so that it's a bit more efficient, and a bit more... fancy.

13 hours ago, EuedeAdodooedoe said:

And this is the error it gives me in the logs:


[string "../mods/Changed values/scripts/fuelableChanged.l..."]:31: attempt to call global 'AddPrefab' (a nil value)

Because there's no "AddPrefab" function. I don't wanna be rude, but please look at other mods and how they do it before failing and asking to forums. Some things are so commonly used that it's really simple to find.

If you want to change an existing prefab, then use AddPrefabPostInit function in your modmain:

function PostInitFunction(inst)
    if GLOBAL.TheWorld.ismastersim then -- this function should only run on the server and not on client
		-- some code
    end
end 

AddPrefabPostInit("prefabname", PostInitFunction)

You can find more functions usable by mods in Steam/SteamApps/common/Don't Starve Together/data/scripts/modutil.lua.

Link to comment
Share on other sites

"Because there's no "AddPrefab" function"

...

What? There's no "AddPrefab" function where? I don't know what you're talking about.

I am not sure how well looking through other mods will help me, but I'll try to keep that in mind next time. I still haven't understood anything here though, like why do I need "AddPrefabPostInit" instead of "AddPrefab" or whatever? The latter worked for changing functions in flower files, so idk what the big difference in logic here is.

And just to be clear, are you implying I need to call out the "commonfn" function in my code before I can use anything used by it or the file it's in or whatever?

I don't get why so often, obscure explenations are made by people when it comes to coding. It's not just here, but other forums that I've asked for some coding help as well. "Because there is no "AddPrefab" function" <= how am I supposed to interpret it? Where is "there"? Why does this matter? Because to my understanding, the only clear and constructive way to help someone in terms of coding is telling what they've done wrong, what needs to be done, how to do it (the code itself, an example) and WHY it needs to be done like that.

Edited by EuedeAdodooedoe
Link to comment
Share on other sites

3 hours ago, EuedeAdodooedoe said:

"Because there's no "AddPrefab" function"

...

What? There's no "AddPrefab" function where? I don't know what you're talking about.

(...)

I don't get why so often, obscure explenations are made by people when it comes to coding. It's not just here, but other forums that I've asked for some coding help as well. "Because there is no "AddPrefab" function" <= how am I supposed to interpret it? Where is "there"? Why does this matter? Because to my understanding, the only clear and constructive way to help someone in terms of coding is telling what they've done wrong, what needs to be done, how to do it (the code itself, an example) and WHY it needs to be done like that.

Uhh, okay. Well, the thing is, how am I supposed to know how advanced you're at coding/modding? Maybe you had some experience with things similar to it, maybe you've just opened one of Klei's *.luas for the first time.

"There's no AddPrefab function"

Yes, and I've pointed you where you should look for more functions like AddPrefabPostInit:

9 hours ago, PanAzej said:

You can find more functions usable by mods in Steam/SteamApps/common/Don't Starve Together/data/scripts/modutil.lua.

In that file there's all useful functions you can call from your modmain.lua file in your mod directory.

I gave you the code - the only thing you need is to paste it into your modmain and customize it - put things you want to be changed in there.

 

There's no "AddPrefab" function there, that's why the game crashes - because there's no function like that, the game doesn't know what to do and shows you the error screen.

3 hours ago, EuedeAdodooedoe said:

(...)why do I need "AddPrefabPostInit" instead of "AddPrefab" or whatever? The latter worked for changing functions in flower files, so idk what the big difference in logic here is.

Huh, okay, apparently I was wrong - there IS an AddPrefab function in dlcsupport.lua... But it's a local function.

 

3 hours ago, EuedeAdodooedoe said:

And just to be clear, are you implying I need to call out the "commonfn" function in my code before I can use anything used by it or the file it's in or whatever?

Nope, I just explained to you how the game "thinks" and builds the prefab. Amulet's prefabs are made out of a few functions, placed in a single file. You can see at the bottom what are actual prefabs, returned by the file:

return Prefab("amulet", red, assets),
    Prefab("blueamulet", blue, assets),
    Prefab("purpleamulet", purple, assets),
    Prefab("orangeamulet", orange, assets, {"sand_puff"}),
    Prefab("greenamulet", green, assets),
    Prefab("yellowamulet", yellow, assets, { "yellowamuletlight" }),
    Prefab("yellowamuletlight", yellowlightfn)

You can put whatever changes in the function I gave you, no need to worry about what's in that file.

 

Also, you seem to think that you can edit local stuff, like local variables or local functions. You can't do that. They're "local", so they can be used only in that file.

You can access some of them via component variables, well, if they're stored in there, that is.

If you want to make the Lazy Forager possible to be fueled, then I recommend looking into other amulets (yellow one can be refueled, for example).

I bet you'd want to use Orange Gems to refuel it, right? That'd be the hard way of doing it. You can see existing fuel types in Don't Starve Together/data/scripts/constants.lua:

FUELTYPE =
{
    BURNABLE = "BURNABLE",
    USAGE = "USAGE",
    MAGIC = "MAGIC",
    CAVE = "CAVE",
    NIGHTMARE = "NIGHTMARE",
    ONEMANBAND = "ONEMANBAND",
    PIGTORCH = "PIGTORCH",
    CHEMICAL = "CHEMICAL",
    WORMLIGHT = "WORMLIGHT",
}

 

You've chosen probably the hardest one to change, though. You need to remove the finiteuses component and change how the amulet takes fuel while it's being equipped.

I'm sorry, but if you need finished code, maybe somebody else will come and help you.

I also bet that somebody did the change you want to do already. Didn't Rezecib's Rebalance change that? Maybe some other mod?

Edited by PanAzej
Link to comment
Share on other sites

@PanAzej hmm, looking at Rezecib's rebalance mod's code, it seems like he got the refuelable thing going through the use of... applying the code every frame to every lazy forager existent in the game, AFTER the initialisation. So, that's what PostInit means? Post initialisation? If so, that explains why the code cannot be applied like I've done it, due to the whole local variable thing, unless I make an edited carbon copy of the amulet.lua file.

Hmm... perhaps I'll try both methods.

And the fuel type I want to add is nightmare fuel, not orange gems... dunno why you would want to refuel it via orange gems, that would simply be a waste.

On 23/03/2017 at 2:27 PM, PanAzej said:

You've chosen probably the hardest one to change, though. You need to remove the finiteuses component and change how the amulet takes fuel while it's being equipped.

I really don't understand why this would be so much trouble, I mean, it's just changing an existing value to a higher one for an item's durability, it's simple math! Is "finiteuses" tag something that prevents an item from it being possible to add a way to refuel it? If so, why, it seems like a very dumb way of making things harder if you were a dev at Klei and thought "hmm, actually, this item could do with a way to refuel it". I mean, look at the amount of crap Rezecib needed to go through JUST to make it possible to add 25 durab when using up nightmare fuel for the lazy deserter:

Spoiler

local orangeamulet_rate = 1/25

local UpvalueHacker = require("tools/upvaluehacker")
AddPrefabPostInit("world", function(TheWorld)
	UpvalueHacker.SetUpvalue(GLOBAL.Prefabs.panflute.fn, HearPanFlute, "HearPanFlute")
	
	local ACTIONS = GLOBAL.ACTIONS
	local COMPONENT_ACTIONS = UpvalueHacker.GetUpvalue(TheWorld.IsActionValid, "COMPONENT_ACTIONS")
	COMPONENT_ACTIONS.POINT.blinkstaff = function(inst, doer, pos, actions, right)
		if right and TheWorld.Map:IsAboveGroundAtPoint(pos:Get()) and not inst:HasTag("fueldepleted") then
			table.insert(actions, ACTIONS.BLINK)
		end
	end
	
	if not GLOBAL.TheNet:GetIsServer() then return end
	
	local TheSim = GLOBAL.TheSim
	local SpawnPrefab = GLOBAL.SpawnPrefab
	local function pickup(inst, owner)
		if inst.components.fueled:IsEmpty() then return end
		if owner == nil or owner.components.inventory == nil then
			return
		end
		local x, y, z = owner.Transform:GetWorldPosition()
		local ents = TheSim:FindEntities(x, y, z, TUNING.ORANGEAMULET_RANGE, { "_inventoryitem" }, { "INLIMBO", "NOCLICK", "catchable", "fire" })
		for i, v in ipairs(ents) do
			if v.components.inventoryitem ~= nil and
				v.components.inventoryitem.canbepickedup and
				v.components.inventoryitem.cangoincontainer and
				not v.components.inventoryitem:IsHeld() and
				owner.components.inventory:CanAcceptCount(v, 1) > 0 then
local fx = SpawnPrefab("small_puff")
				fx.Transform:SetPosition(v.Transform:GetWorldPosition())
				fx.Transform:SetScale(.5, .5, .5)

				inst.components.fueled:DoDelta(-TUNING.LARGE_FUEL*orangeamulet_rate)

				if v.components.stackable ~= nil then
					v = v.components.stackable:Get()
				end

				if v.components.trap ~= nil and v.components.trap:IsSprung() then
					v.components.trap:Harvest(owner)
				else
					owner.components.inventory:GiveItem(v)
				end
				return
			end
		end
	end
	UpvalueHacker.SetUpvalue(GLOBAL.Prefabs.orangeamulet.fn, pickup, "onequip_orange", "pickup")
end)

local FUELTYPE = GLOBAL.FUELTYPE
FUELTYPE.BAT = "BAT"
FUELTYPE.ORANGEGEM = "ORANGEGEM"

if not GLOBAL.TheNet:GetIsServer() then return end

local function convert_finiteuses_to_fueled(inst, fueltype, rate, inflation)
	inflation = inflation or 1
	inst:AddComponent("fueled")
	inst.components.fueled.fueltype = fueltype
	inst.components.fueled:InitializeFuelLevel(inst.components.finiteuses.total*inflation*TUNING.LARGE_FUEL*rate)
	inst.components.fueled.accepting = true
	
	local impossible_uses = inst.components.finiteuses.total + 1
	inst.components.finiteuses:SetUses(impossible_uses)
	inst:DoTaskInTime(0, function(inst)
		local uses = inst.components.finiteuses:GetUses()
		if uses ~= impossible_uses then
			inst.components.fueled:InitializeFuelLevel(uses*inflation*TUNING.LARGE_FUEL*rate)
		end
		inst:RemoveComponent("finiteuses")
		inst.components.fueled:DoDelta(0)
	end)
end

 

Rezecib himself commented: "I can't believe I have to resort to this for this kind of change, but... 'tis the mod life, I guess".

It almost feels like modding is a huge waste of time; you do more work than the developers do themselves for the same amount of changes, your work is highly undermined (unless perhaps it's visual client type mods), because they are mods and for games development, this kind of crap might not even be useful, since in the industry you'll be changing code directly.

No wonder so many modders have simply quit! Klei needs to sort some crap out for modding, I don't want to do useless thought magic just to get one simple thing working. And what's more, this code runs through frames, imagine how big of a hell it would be with players having a bunch of those amulets lying in one place with the mod enabled! It'll be like beefalo due to the whole domestication mechanics... which mostly are there for no reason because so many people don't use that **** due to it being too much to give than what you actually gain.

Edited by EuedeAdodooedoe
Link to comment
Share on other sites

@PanAzej I now get why it's so difficult to do this... Even if I do the bad method (making copies of existing lua files and editing them for the mod), fueled.lua and finiteuses.lua are two ways that fuel is consumed in the game. The former happens over time, depending on conditions (e.g. it is equipped) and the latter loses uses and no item in the game that has durability in uses is fuelable...

Except bone armour!

I checked its code and it revealed to me the way to do this extremely easy, provided you make a carbon copy of amulet.lua file in your mod and edit it. What I did was:

Change this code under "pickup" function that takes away 1 point of durability per item pickup from:

inst.components.finiteuses:Use(1)

To:

inst.components.fueled:DoDelta(-1)

 

Then changed this code in "orange", which initiates some things for the orange amulet, from:

inst:AddComponent("finiteuses")
inst.components.finiteuses:SetOnFinished(inst.Remove)
inst.components.finiteuses:SetMaxUses(TUNING.ORANGEAMULET_USES)
inst.components.finiteuses:SetUses(TUNING.ORANGEAMULET_USES)

To:

inst:AddComponent("fueled")
inst.components.fueled.fueltype = FUELTYPE.NIGHTMARE
inst.components.fueled:InitializeFuelLevel(TUNING.ORANGEAMULET_USES)
inst.components.fueled.accepting = true

 

What I can't figure out though is where the amount of fuel it gives back is declared exactly. Having 675 uses (in my mod) gave it back about 23%, which equates to about 150 uses. Similar or the same amount was for when the uses were reset back to 225, so a higher percentage was repaired when changing the amount of uses it had. Are you able to understand? I mean, this is all the code I changed in amulet.lua. Let me know if you need the fully changed file, although this is pretty much it, everything else was left as is in the original game's file.

Kudos for your help so far, btw :)

Edited by EuedeAdodooedoe
Added in "inst:AddComponent("fueled")" for the edited script, since I forgot to place that in here...
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...