Jump to content

need help with crash


Recommended Posts

Hello, I need help trying to fix something cause I am too dumb to do it

 

code

Spoiler

local function Fail_Craft(inst, data)
	local item = data.item
	
	if math.random() <= inst.craft_fail then
		inst.components.talker:Say("Oops!")
		inst.components.inventory:DropItem(item)
		if not item.components.lootdropper then item:AddComponent("lootdropper") end
		item.components.lootdropper:DropLoot()
		item:Remove()
	end
end

master_postinit


inst:ListenForEvent("builditem", Fail_Craft)
inst.craft_fail = 1

 

I'm helping a friend and they want their character to have a chance to fail a crafting recipe causing it to break and getting half the resources back (basically like when hammering stuff), but when you craft a recipe which gives more than 1 product it gives out this crash and I don't know how to fix it :wilson_resigned:

crash

Spoiler

[00:00:58]: [string "scripts/components/inventoryitem_replica.lu..."]:152: attempt to index field 'classified' (a nil value)
LUA ERROR stack traceback:
    scripts/components/inventoryitem_replica.lua:152 in (method) SetPickupPos (Lua) <146-154>
    scripts/components/stackable.lua:6 in (field) ? (Lua) <3-8>
    scripts/class.lua:30 in () ? (Lua) <23-32>
    scripts/components/stackable.lua:55 in (method) SetStackSize (Lua) <53-57>
    scripts/components/builder.lua:468 in () ? (Lua) <388-509>

 

 

any help getting it to work would be great :wilson_flower:

Link to comment
Share on other sites

Take a look at the reporting line 468 in builder.lua, and read the comment above it.

--We still need to give the player the original product that was spawned, so do that.
self.inst.components.inventory:GiveItem(prod, nil, pt)

What happens is: you remove the item from the world when the event is pushed on line 438, but you have no way of stopping the game code in builder.lua's DoBuild function from continuing after that, so the code continues with an item which has effectively been removed from the world, which causes a crash down the line.

One way to fix this, would be to simply delay your code from running until next tick, where all the DoBuild code has finished, and the item has actually been given to the player. You can do this by wrapping the code in this:

self.inst:DoTaskInTime(0, function(inst)
	-- Your code
end)

This will result in some trouble, though, if the item is stackable (see lines 445-448 in builder.lua). Then the instance of the item (data.item) that you're working with, won't be the correct instance if there was already a stack of it in the inventory. Then you have to find out where the stack of it is, how many are crafted per "build", and then remove that number of items from the stack. You should be able to find some function or code in inventory.lua or builder.lua which does this for you, though, since the builder-component can figure it out when you craft things. I think it might be line 418 in builder.lua:

self:RemoveIngredients(materials, recname)

As you can see in the code surrounding this line, builds can be buffered on the replica, which I don't really know how works or influences what you're doing.

PERHAPS SOMEONE ELSE CAN TELL YOU ABOUT REPLICA PROBLEMS !??!;)

Link to comment
Share on other sites

data.item from the event builditem could be the reference of items that are already in inventory as Ultroman said.
I consider this as "Loss of information". That is, data.item is not what we are looking for. 
Further processing based on your code will require more information to be needed or processed.

So I decided to create new prefabs instead of getting the reference of data.item. And move it to the player then lootdrop it.
Also, This code works with recipes creating structures or walls.

Here's the code.

local FAIL_CHANCE = 1

local function Fail_Craft(inst)
	local _Dobuild = inst.components.builder.DoBuild
	function inst.components.builder.DoBuild(self, recname, ...)
		if math.random() <= FAIL_CHANCE then
			local recipe = GetValidRecipe(recname)
			
			local item = SpawnPrefab(recipe.product) or nil
			if item == nil then return end
			item.Transform:SetPosition(inst.Transform:GetWorldPosition())
			if not item.components.lootdropper then item:AddComponent("lootdropper") end
			if item.components.stackable ~= nil then
				item.components.stackable:SetStackSize(recipe.numtogive)
			end

			inst.components.talker:Say("Oops!")
			item.components.lootdropper:DropLoot()
			item:Remove()
			
			return true -- to prevent action fail speech.
		else
			return _Dobuild(self, recname, ...)
		end
	end
end

master_postinit

Fail_Craft(inst)

And don't mind of FAIL_CHANCE part because my code is copy-pasted that I just tested.

Link to comment
Share on other sites

@YakumoYukari Hello again, thanks for your help, but my friend seems to be having some glitches with this code and I don't know how to help them fix it so if you know how to fix it that would be great :D!

Basically, when you "fail" a recipe the items it cost to make the recipe aren't consumed, but the items the recipe should drop when it "breaks" still drop basically duping the items lol

Edited by Warbucks
Link to comment
Share on other sites

I didn't even assume such a typical case. My bad.
I added some code that consumes the recipe ingredient correctly. 

if self.buffered_builds[recname] ~= nil then -- is bufferable?
  self.buffered_builds[recname] = nil 
  self.inst.replica.builder:SetIsBuildBuffered(recname, false) -- turn the buffer off
else
  self:RemoveIngredients(self:GetIngredients(recname), recipe) -- or Remove its ingredients
end

it comes from DoBuild().

So the full code would be this.

local FAIL_CHANCE = 1

local function Fail_Craft(inst)
	local _Dobuild = inst.components.builder.DoBuild
	function inst.components.builder.DoBuild(self, recname, ...)
		if math.random() <= FAIL_CHANCE then
			local recipe = GetValidRecipe(recname)
			if self.buffered_builds[recname] ~= nil then
				self.buffered_builds[recname] = nil 
				self.inst.replica.builder:SetIsBuildBuffered(recname, false)
			else
				self:RemoveIngredients(self:GetIngredients(recname), recipe)
			end
			
			local item = SpawnPrefab(recipe.product)
			if item == nil then return end
			item.Transform:SetPosition(inst.Transform:GetWorldPosition())
			if not item.components.lootdropper then item:AddComponent("lootdropper") end
			if item.components.stackable ~= nil then
				item.components.stackable:SetStackSize(recipe.numtogive)
			end

			inst.components.talker:Say("Oops!")
			item.components.lootdropper:DropLoot()
			item:Remove()
			
			return true -- to prevent action fail speech.
		else
			return _Dobuild(self, recname, ...)
		end
	end
end

 

Edited by YakumoYukari
Link to comment
Share on other sites

@YakumoYukari Hey, sorry to be so annoying, but the code works fine! I was just wondering if you know a way to prevent crafting recipes which have placers from being able to break via this code since it makes somethings like crafting alchemy engines and base building really annoying, thanks!

Edited by Warbucks
Link to comment
Share on other sites

This code would work fine

local FAIL_CHANCE = 1

local function Fail_Craft(inst)
	local _Dobuild = inst.components.builder.DoBuild
	function inst.components.builder.DoBuild(self, recname, pt, rotation, skin)
		if math.random() <= FAIL_CHANCE then
			local recipe = GetValidRecipe(recname)
			if self.buffered_builds[recname] ~= nil then -- is bufferable?
				return _Dobuild(self, recname, pt, rotation, skin)
			end

			self:RemoveIngredients(self:GetIngredients(recname), recipe) -- or Remove its ingredients

			local item = SpawnPrefab(recipe.product, recipe.chooseskin or skin, nil, self.inst.userid) or nil
			if item == nil then return end
			item.Transform:SetPosition(inst.Transform:GetWorldPosition())
			if not item.components.lootdropper then item:AddComponent("lootdropper") end
			if item.components.stackable ~= nil then
				item.components.stackable:SetStackSize(recipe.numtogive)
			end

			self.inst:PushEvent("refreshcrafting")

			inst.components.talker:Say("Oops!")
			item.components.lootdropper:DropLoot()
			item:Remove()
			
			return true -- to prevent action fail speech.
		else
			return _Dobuild(self, recname, pt, rotation, skin)
		end
	end
end



 

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...