Jump to content

Recommended Posts

I know you need AddPrefabPostInit("prefabname", functionImadename) at end of file to make edit for prefab, but not sure about rest. Have tried copy loot table and edit it, but not sure how to go from that to adding new loot table to the prefab. And what if want edit existing function of the prefab as well? Could I have examples pls?

Looks very overcomplicated and restrictive for what I am doing, can I just not declare local loot table, then have it apply post init with a function? I actually trying claw palm tree and it look like its loot structured completely differently and thought this would change loot but not working

image.thumb.png.f10f1197d247419d014b9851f6f5cb21.png

I put this in new file under scripts/prefabs of my mod and import file in modmain.

Edited by Charsis

EDIT: OOPS! I accidentally went to talking about deciduous trees instead. They work exactly the same, though, so the code is still solid, and all my points about the problems of working with these tree scripts are still valid for e.g. claw trees. I've left the whole deciduous tree info and code here, and just made a new similar script for the claw trees at the bottom.

Nope. GetBuild() is a local function in deciduoustree.lua and 'prefabs' and 'builds' are local variables in it. You cannot access or change them in any way. These trees do not want to be messed with. Absolutely everything about them is local. I don't think I've seen a less moddable prefab in my entire "career" in modding. And they change their loot table every time they grow leaves, and they push no events whatsoever that you can listen to.

The only thing you can do, is mess with their components, one of which is their lootdropper. I would do something as idiotic as running a periodic task every 10 seconds on them, and manually set a new loot table on them.

If you look at their code, they use the lootdropper like so:

inst.components.lootdropper:SetLoot(GetBuild(inst).short_loot)

As you've discovered, GetBuild(inst) gets one of the builds found in the builds, and they then access short_loot on it. This line of code would translate to this for the 'normal' build:

inst.components.lootdropper:SetLoot( {"log", "log"} )

All this does, is tell it to drop exactly two logs when it is killed. It's just a list of prefab names to drop.

Here's the problem with this...all deciduous trees ARE THE SAME PREFAB *facepalm*. Luckily, there is ONE thing you can access which identifies which kind it is: inst.build

inst.build will either give you:

  • nil, which defaults to "normal"
  • "normal"
  • "barren"
  • "red"
  • "orange"
  • "yellow"
  • "poison"


So, what we have to do is actually pretty simple, and we can steal a whole lot of the original code for this. I always like a challenge, and these trees being so darn stubborn peaked my interest. If I'm not mistaken, this should work. Put it all in your modmain.lua. You can edit all the loot directly in the table at the top.

-- We steal their idea of listing things by build, but this is our own list,
-- which has nothing to do with theirs, since we cannot access it.
-- Change these loot tables to whatever you like.

local builds = 
{
	normal = { --Green
		normal_loot = {"log", "log"},
		short_loot = {"log"},
		tall_loot = {"log", "log", "log", "acorn"},
	},
	barren = {
		normal_loot = {"log", "log"},
		short_loot = {"log"},
		tall_loot = {"log", "log", "log"},
	},
	red = {
		normal_loot = {"log", "log"},
		short_loot = {"log"},
		tall_loot = {"log", "log", "log", "acorn"},
	},
	orange = {
		normal_loot = {"log", "log"},
		short_loot = {"log"},
		tall_loot = {"log", "log", "log", "acorn"},
	},
	yellow = {
		normal_loot = {"log", "log"},
		short_loot = {"log"},
		tall_loot = {"log", "log", "log", "acorn"},
	},
	poison = {
		normal_loot = {"livinglog", "acorn", "acorn"},
		short_loot = {"livinglog", "acorn"},
		tall_loot = {"livinglog", "acorn", "acorn", "acorn"},
	},
}

-- We also steal their way of accessing things by build.
local function GetBuild(inst)
	local build = builds[inst.build]
	if build == nil then
		return builds["normal"]
	end
	return build
end

-- Some drops are done directly in the code, and you cannot touch those.
-- It is things like extra living logs, extra acorns and nightmare fuel.
-- Sadly, we can only change the base loot.

AddPrefabPostInit("deciduoustree", function(inst)
	inst:DoPeriodicTask(10, function(inst)
		if inst:HasTag("stump") then
			-- If the tree is a stump, then the lootdropper's DropLoot() function
			-- is not called, so you cannot change the loot from stumps.
			-- It is determined directly in code, so we skip those.
			return
		end
		
		if inst:HasTag("burnt") then
			-- What to drop if it is burnt?
			-- It already automatically drops charcoal, and you can't change that.
			-- It's in code separate from the rest of the drop table code.
			-- But it does call DropLoot(), so it will still drop the loot you've set.
			-- If you just want it to drop the normal loot when burnt, delete this
			-- whole if-statement.
			return
		end
		
		-- Now for the normal loot. This is where we use our custom loot tables.
		-- Just like the original code, we use the growable component to determine
		-- which loot to assign. We also use inst.build, just like the original code.
		
		if inst.components.growable then
			if inst.components.growable.stage == 1 then
				inst.components.lootdropper:SetLoot(GetBuild(inst).short_loot)
			elseif inst.components.growable.stage == 2 then
				inst.components.lootdropper:SetLoot(GetBuild(inst).normal_loot)
			else
				inst.components.lootdropper:SetLoot(GetBuild(inst).tall_loot)
			end
		end
	end
end


The way I figured all this out, was just looking through the original code. If they hadn't exposed inst-build, this would be mostly impossible. We would have been left with only one option; to give all deciduous trees the exact same loot, only changing it by how much they've grown. Obviously, this would have been bad, since the poison trees have such special loot, and some types don't drop acorns. It took me about an hour to compile enough knowledge about the workings of the trees, to write all the information in the comments, and know how to properly handle stumps and burnt trees. Programming is very complicated and a lot of work. Very nearly always :)

 

EDIT: Here's the claw tree code instead xD
Put it all in your modmain.lua.

-- We steal their idea of listing things by build, but this is our own list,
-- which has nothing to do with theirs, since we cannot access it.
-- Change these loot tables to whatever you like.

local builds = 
{
	normal = {
		file="claw_tree_build",
		prefab_name="clawpalmtree",
		normal_loot = {"cork", "cork"},
		short_loot = {"cork"},
		tall_loot = {"cork", "cork", "cork"},
	},
}

-- We also steal their way of accessing things by build.
local function GetBuild(inst)
	local build = builds[inst.build]
	if build == nil then
		return builds["normal"]
	end
	return build
end

-- Some drops are done directly in the code, and you cannot touch those.
-- It is things like extra cork. Sadly, we can only change the base loot.

AddPrefabPostInit("clawpalmtree", function(inst)
	inst:DoPeriodicTask(10, function(inst)
		if inst:HasTag("stump") then
			-- If the tree is a stump, then the lootdropper's DropLoot() function
			-- is not called, so you cannot change the loot (cork) from stumps.
			-- It is determined directly in code, so we skip those.
			return
		end
		
		if inst:HasTag("burnt") then
			-- What to drop if it is burnt?
			-- It already automatically drops charcoal, and you can't change that.
			-- It's in code separate from the rest of the drop table code.
			-- But it does call DropLoot(), so it will still drop the loot you set.
			-- If you just want it to drop the normal loot when burnt, delete this
			-- whole if-statement.
			return
		end
		
		-- Now for the normal loot. This is where we use our custom loot tables.
		-- Just like the original code, we use the growable component to determine
		-- which loot to assign. We also use inst.build, just like the original code.
		
		if inst.components.growable then
			if inst.components.growable.stage == 1 then
				inst.components.lootdropper:SetLoot(GetBuild(inst).short_loot)
			elseif inst.components.growable.stage == 2 then
				inst.components.lootdropper:SetLoot(GetBuild(inst).normal_loot)
			else
				inst.components.lootdropper:SetLoot(GetBuild(inst).tall_loot)
			end
		end
	end
end

 

Edited by Ultroman

No no. This code just puts a script on each of the trees (there aren't THAT many) which runs every 10 seconds. You can change it to 60 seconds if you want. The code itself is very quick and benign. It's FAR better than overwriting game files. It CAN detect whether the tree is a stump or burnt, and you can add loot to it if it is burnt. Only the loot for the stump is locked.

It's up to you, of course, but overwriting game files is really bad practice. It the penultimate bad practice. If you're planning on ever releasing it, you really shouldn't. If it's just for yourself, go for it :)

Edited by Ultroman
  • Developer

You don't have to do it on a timer. Growable component can call a function when a new stage is reached (Growable:SetOnGrowthFn(fn)), you could respond to that.

Alternatively, you would mod LootDropper:GenerateLoot with a 

if self.customlootfn then
	return self.customlootftn()
end

and have the prefabs you want to respond differently in different situations hook up this function?

Bizziboi in from the sideline with a big play! :D You obviously read the code with a better overview than me.

But he still won't be able to affect the extra loot from burnt trees, or change the loot from digging up stumps, right?

2 hours ago, bizziboi said:

You don't have to do it on a timer. Growable component can call a function when a new stage is reached (Growable:SetOnGrowthFn(fn)), you could respond to that.

Alternatively, you would mod LootDropper:GenerateLoot with a 


if self.customlootfn then
	return self.customlootftn()
end

and have the prefabs you want to respond differently in different situations hook up this function?

What do you mean with Growable component? How can I use this? And also same with LootDropper method, really confused about structure and syntax.

  • Developer

@Charsis Sorry, too pressed for time to dive deep, but if @Ultroman gets what I mean maybe he's willing to help you along.

@Ultromanthe burnt trees is harder because of the local functions, but you could override the entire surrounding function with a similar function that just adds in the callback functionality, or, if you really want to - you could for example override burnable:SetOnBurntFn to stub in your own function, you can also access and replace local function through digging into stuff with debug.getupvalue/setupvalue, but it gets hairy - the local functions are not mod-friendly.

You could make use of Rezecib's upvalue API to access the local function more easily, but I have no experience with that. As bizziboi says, it gets hairy, so using an API made by a very respectable modder could be a way to do that, if it's that important to you. I don't have time to sit down and learn about it right now, though.

If you use SetOnGrowth then the trees won't have their loot changed after loading a game, until they grow again, since (if I'm reading it correctly) ongrowth is not called during load, unless it is its time to grow. Other than that, the solution is fine, and you could just manually call our function on all the trees in the postinit.

I like the idea of overriding GenerateLoot much better, however. That way the function is only called when needed.

All of these solutions would still keep our current limitations of forcing some coal to be dropped regardless when you chop down a burnt tree (in addition to whatever loot you set), and that we cannot affect the stump loot. You could probably do something like bizziboi said with SeOnBurnt, but CBA to look into that now.

You could do the GenerateLoot extension like this:

-- We steal their idea of listing things by build, but this is our own list,
-- which has nothing to do with theirs, since we cannot access it.
-- I added burnt_loot as well for streamlinedness :)
-- Change these loot tables to whatever you like.

local builds = 
{
	normal = {
		file="claw_tree_build",
		prefab_name="clawpalmtree",
		normal_loot = {"cork", "cork"},
		short_loot = {"cork"},
		tall_loot = {"cork", "cork", "cork"},
		burnt_loot = {"cork", "cork", "cork"},
	},
}

-- We also steal their way of accessing things by build.
local function GetBuild(inst)
	local build = builds[inst.build]
	if build == nil then
		return builds["normal"]
	end
	return build
end

local getLoot = function(lootdropper_component, tree_inst)
	if tree_inst:HasTag("burnt") then
		-- What to drop if it is burnt?
		-- It already automatically drops charcoal, and you can't change that.
		-- It's in code separate from the rest of the drop table code.
		-- But it does call DropLoot(), so it will still drop the loot you set.
		-- If you just want it to drop the normal loot when burnt, delete this
		-- whole if-statement.
		return lootdropper_component:SetLoot(GetBuild(tree_inst).burnt_loot)
	end
	
	-- Now for the normal loot. This is where we use our custom loot tables.
	-- Just like the original code, we use the growable component to determine
	-- which loot to assign. We also use tree_inst.build, just like the original code.
	
	if tree_inst.components.growable then
		if tree_inst.components.growable.stage == 1 then
			return lootdropper_component:SetLoot(GetBuild(tree_inst).short_loot)
		elseif tree_inst.components.growable.stage == 2 then
			return lootdropper_component:SetLoot(GetBuild(tree_inst).normal_loot)
		else
			return lootdropper_component:SetLoot(GetBuild(tree_inst).tall_loot)
		end
	end
	
	return {}
end

-- Some drops are done directly in the code, and you cannot touch those.
-- It is things like extra cork. Sadly, we can only change the base loot.

AddPrefabPostInit("clawpalmtree", function(inst)
	if inst.components.lootdropper then
		inst.components.lootdropper.customlootfn = getLoot
		
		local oldGenerateLoot = inst.components.lootdropper.GenerateLoot
		function inst.components.lootdropper:GenerateLoot()
			if self.customlootfn then
				return self.customlootfn(self, self.inst)
			end
			return oldGenerateLoot()
		end
	end
end

If you want to be able to follow what's going on or improve upon it or in general start modding, you should take some time to learn the LUA syntax and structure. My newcomer post is a great place to start, and I highly recommend that you take the LUA Crash Course and look through the "getting started with modding guide" thread. I can't sink much more time into this, as I'm busy with my master thesis, and this is more like procrastination for me, haha :) 

Edited by Ultroman

The following should work...

=-=-=- TREE DROPS (This only adds extra loot when chopped.... not if burned )
function TreeLootPostInit(inst)    
        local oldonfinish = inst.components.workable.onfinish
            inst.components.workable.onfinish = function(inst, chopper)            
                if chopper then
                    if inst.components.growable then
                        if inst.components.growable.stage > 2 then
                        inst.components.lootdropper:AddChanceLoot("log", 0.03) -- Adds 3% chance of extra log on growth stage greater than 2
                        elseif     inst.components.growable.stage > 1 then
                        inst.components.lootdropper:AddChanceLoot("log", 0.02) -- else adds 2% extra log chance on growth stage greater than 1
                        end
                    else
                        inst.components.lootdropper:AddChanceLoot("log", 0.01) -- else adds 1% chance of extra log
                    end            
                end    
            oldonfinish(inst, chopper)        
        end    
    return inst
end

local modtrees = {    -- list the prefab files of the trees you want to mod
    "evergreen_sparse",
    "marsh_tree"
    }
    for k,v in pairs(modtrees) do AddPrefabPostInit(v, TreeLootPostInit)
end

=-=-=-=-= MONSTER DROPS -- ADD DROP ITEM to CREATURES
    
    local function YourLogLoot(prefab)
        prefab.components.lootdropper:AddChanceLoot("log",0.5) -- 50% chance for one log
        prefab.components.lootdropper:AddChanceLoot("log",0.25) -- 25% chance for one log
-- The chances above are independent, so its possible to get none, one, or two
-- 37.5% none, 50% only one, 12.5% two
-- You can set a line item to 1.0 for 100% chance

    end

    local function AddLogPostInit(prefab)
        YourLogLoot(prefab)
    end

local modcreature = {    -- list the prefab files of the creatures you want to mod
    "rocky",
    "merm"
    }
    for k,v in pairs(modcreature) do AddPrefabPostInit(v, AddLogPostInit)
end
    

Edited by MidrealmDM

As far as removing loot and replacing it..
Something like this might work, I think - replacing one item with another.

=-=-=-=-=
local function NoCharcoalPostinit(inst)
            for i, v in ipairs(chanceloot) do
                if v.prefab == "charcoal" and math.random() < 0.15 then -- 15% chance to replace charcoal with log
                    v.prefab = "log"
                    break
                end
            end

end
 
AddPrefabPostInit("jungletree", NoCharcoalPostinit)  -- or whatever tree prefab you want

=-=-=-
Sorry, its late, so this may not be accurate
 

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
×
  • Create New...