Jump to content

[SOLVED] How to repair custom item that has Finite Uses?


Recommended Posts

Hello, really really need some help.


How do I go about coding a custom item with finite uses? Fueling and perishing works on decay, and health (i think) only works on structures.

Basically I have this weapon that I want to be able to be repaired with a moon rock; it shouldn't be destructible and on 0 it shouldn't be usable. Non-craftable as it would be great to just have it be "whetstoned" to good use again when it "dulls". Supposedly it also can chop trees quickly (much like Woody and Lucy chop speed), but would weather the blade quicker. I haven't coded in the speed chopping yet either, i was focusing on the durability thing first.

local prefabs = 
{
}



local function fn(colour)


local function makeobstacle(inst)
    inst.Physics:SetActive(true)
    inst._ispathfinding:set(true)
end


local function OnEquip(inst, owner) 
        owner.AnimState:OverrideSymbol("swap_object", "swap_moonRK", "swap_moonRK")
        owner.AnimState:Show("ARM_carry") 
        owner.AnimState:Hide("ARM_normal") 
    end
	

local function OnUnequip(inst, owner) 
        owner.AnimState:Hide("ARM_carry") 
        owner.AnimState:Show("ARM_normal") 
    end

    local inst = CreateEntity()
	inst.entity:AddNetwork()

    local trans = inst.entity:AddTransform()
    local anim = inst.entity:AddAnimState()
    MakeInventoryPhysics(inst)
    
    anim:SetBank("moonrunekatana")
    anim:SetBuild("moonrunekatana")
    anim:PlayAnimation("idle")
	

    inst.entity:SetPristine()

    if not TheWorld.ismastersim then
        return inst
    end
	
    inst:AddComponent("weapon")
    inst.components.weapon:SetDamage(42.5)
	inst.components.weapon.attackrange = 0 
    inst.components.weapon.hitrange = 0 
	
    inst:AddComponent("repairable")
         inst.components.repairable.repairmaterial = "moonrocknugget"
		 
inst:AddComponent("finiteuses")
    inst.components.finiteuses:SetMaxUses(150)
    inst.components.finiteuses:SetUses(150)
	inst.components.finiteuses:SetConsumption(ACTIONS.CHOP, 4)
	
	inst:AddComponent("tool")
    inst.components.tool:SetAction(ACTIONS.CHOP, 1)
	
	inst:AddTag("sharp")
    inst:AddTag("pointy")
	
    inst:AddComponent("inventoryitem")
    inst.components.inventoryitem.imagename = "moonrunekatana"
    inst.components.inventoryitem.atlasname = "images/inventoryimages/moonrunekatana.xml"
    
    inst:AddComponent("equippable")
    inst.components.equippable:SetOnEquip( OnEquip )
    inst.components.equippable:SetOnUnequip( OnUnequip )
	
	inst:AddComponent("inspectable")
	
	MakeHauntableLaunch(inst)

    return inst
end

return  Prefab("common/inventory/moonrunekatana", fn, assets, prefabs)

The weapon already works, components for its effects are in place, but I don't really know how to implement this. I've looked into other game files/mod files and in the game, there isn't really anything that acts like this, and the repair tools mod adds an actions.lua which was too much for me to digest without some help.

The way some others do it is by repairing the item with another prerequisite item, adding a component.lua that would trigger the repair?

Can anyone lead me in the right direction? Thanks.

 

Edited by SourNVitriol
Link to comment
Share on other sites

There seems no such a simple way. You need to either override functions of repairable or use fueled and trigger 'dodelta' in weapon's 'onattack'. You can disable weapon in 'depleted' of fueled.

Link to comment
Share on other sites

3 hours ago, ptr said:

There seems no such a simple way. You need to either override functions of repairable or use fueled and trigger 'dodelta' in weapon's 'onattack'. You can disable weapon in 'depleted' of fueled.

 

What are the inst.components for fueled and how does each act? I can't find a list for that anywhere in the forum's "components and what they do" thread, and I know it's a lot to ask of you, but it would help me in figuring out for myself how to use the component once I add it and then I could trigger the delta change since I -think- i have that covered. In this way, I can also learn for myself in the future.

Edited by SourNVitriol
Link to comment
Share on other sites

Yeah the repairable component is really only used for things that have a health, workable, or perishable component, so the easiest way (I've found) to deal with the finiteuses component is for you is to basically just write your own component and custom repair action, which luckily isn't too hard.

I actually have a mod that's all about repairing multiple item durabilities if you wanna check out how I set up the components/actions. The code could use a bit of refining (I could reduce the amount of components from like 7-8 to 2, I've just been sorta lazy about it lol) but I've commented the basic jist of what each block of code does.

I suggest taking a look at my "spooler.lua" component as it's one of the shorter components as well as it sounds like it's pretty much exactly what you need as far as a repair mechanic goes (you can ignore the "CollectUseActions" function as that is only needed for DS compatibility, DST uses AddComponentAction in the modmain instead). Then if you're not familiar, be sure to take a look at how I set up the custom action for it as well in the modmain. Lastly, near the bottom of the modmain you'll see how I set up the tag check for the repairable item(s) in question so that the game will know when the item should/shouldn't be repairable. Also don't forget to add your new component to the moonrock!

Hope this helps :) lemme know if you have any questions

Link to comment
Share on other sites

53 minutes ago, SourNVitriol said:

What are the inst.components for fueled? I can't find a list for that anywhere in the forum's "components and what they do" thread, and I know it's a lot to ask of you, but it would help me in figuring out for myself how to use the component once I add it and then I could trigger the delta change since I -think- i have that covered.

Well, there are no components for fueled since fueled itself is a component :p a component is a "mechanic" script that gets attached to an instance of a prefab (thing in the game world) so when you're calling inst.components, your calling the table of components that will be attached to each instance of the said prefab.

Think of an instance as a copy of whatever prefab it is, but it has it's own values for its list of variables separate from other copies (instances) of itself in the world. That way when you hurt a mob with health, it only damages that mob (instance) instead of every mob of the same type (prefab) in the world.

Little confusing of a concept to explain, but welcome to object oriented programming lol

Pro Modder Tip: In-game, you can target a specific instance using c_select() in the console (while hovering your mouse over it) and then you can reference it using c_sel(). This is a powerful debugging tool for particular cases where printing from the script may not necessarily work or information is needed at a separate time. Using c_select() and c_sel(), you can type things like this:
 

c_select();print(c_sel().components.finiteuses.current)

to check the exact number of uses left for the particular instance under your mouse (so long as it's using the finiteuses component). Just note that using the print() function on any worlds that have caves active or that are dedicated will put the results in your client_log.txt instead of the in-game log.

The same type of thing can be done for setting variables or performing functions attached to instances as well. You can type something like this:

c_select();c_sel().components.finiteuses:SetPercent(1)

to instantly repair the particular instance to 100% (again, as long as it uses the finiteuses component).

Edited by w00tyd00d
Link to comment
Share on other sites

2 hours ago, w00tyd00d said:

Well, there are no components for fueled since fueled itself is a component :p a component is a "mechanic" script that gets attached to an instance of a prefab (thing in the game world) so when you're calling inst.components, your calling the table of components that will be attached to each instance of the said prefab.

Think of an instance as a copy of whatever prefab it is, but it has it's own values for its list of variables separate from other copies (instances) of itself in the world. That way when you hurt a mob with health, it only damages that mob (instance) instead of every mob of the same type (prefab) in the world.

Little confusing of a concept to explain, but welcome to object oriented programming lol

Pro Modder Tip: In-game, you can target a specific instance using c_select() in the console (while hovering your mouse over it) and then you can reference it using c_sel(). This is a powerful debugging tool for particular cases where printing from the script may not necessarily work or information is needed at a separate time. Using c_select() and c_sel(), you can type things like this:
 


c_select();print(c_sel().components.finiteuses.current)

to check the exact number of uses left for the particular instance under your mouse (so long as it's using the finiteuses component). Just note that using the print() function on any worlds that have caves active or that are dedicated will put the results in your client_log.txt instead of the in-game log.

The same type of thing can be done for setting variables or performing functions attached to instances as well. You can type something like this:


c_select();c_sel().components.finiteuses:SetPercent(1)

to instantly repair the particular instance to 100% (again, as long as it uses the finiteuses component).

This helped a lot for troubleshooting, thanks! Although regarding your other post, i checked it out but ill have to study your spooler.lua a bit and weave thru the spider webs that is its code  (thanks for the great reference btw).

I think reading further my problem is I don't know what are the definite basics for the language itself (aka this is one of my first ever programming attempts), like

local HAMMERFIX = Action()
HAMMERFIX.id = "HAMMERFIX"
HAMMERFIX.str = "Repair"
HAMMERFIX.fn = function(act)

From what i can gather, this is telling the engine to look for HAMMERFIX defined as an action and so it can act as a function (act) now, but I don't know what .str means, and ill have to look it up first.

I'll post again if I encounter anything I don't further understand, and Ill make sure to post my own wip code so everyone can have a point of discussion that isn't too broad.

 

Edited by SourNVitriol
Link to comment
Share on other sites

28 minutes ago, SourNVitriol said:

This helped a lot for troubleshooting, thanks! Although regarding your other post, i checked it out but ill have to study your spooler.lua a bit and weave thru the spider webs that is its code  (thanks for the great reference btw). I'll post again if I encounter anything I don't further understand, and Ill make sure to post my own code so everyone can have a point of discussion that isn't too broad.

You're welcome my friend ;) and yeah, it does take a little bit of general knowledge of not only Lua, but Don't Starve's file structure to fully grasp what's going on but if you have any questions for how to set it up, let me know and I'll try to help the best I can. I have faith you can figure out most of it, if not all of it tho :) 

Link to comment
Share on other sites

I will say this, tho, that as long as your mod is going to be DST exclusive you can ignore all of the "IsDST" checks throughout my mod, just apply the function that would run if "IsDST" was true. That way you can also just ignore the whole "repairdata" script (that's pretty much what I use as my own tuning file for the mod, irrelevant to what you need for your mod), the main thing you want to is just the "Spool" function.

Here, I'll even cut it down to everything you should focus on and let you fill in the blanks ;) :

local <your component name> = Class(function(self,inst)
	self.inst = inst
end)

function <your component name>:<function name>(target, doer, invobj)	-- target == item youre trying to repair, doer == you, invobj == repair item
	local val = <your repair value>	
	local sum = math.min(1, target.components.finiteuses:GetPercent() + val)  -- assures value cant go above 100%
	target.components.finiteuses:SetPercent(sum)
	invobj.components.stackable:Get():Remove()  -- removes 1 repair item
	doer:PushEvent("repair")  -- optional, only makes a ding sound like when using the sewing kit
end

return <your component name>

Hopefully that should make a bit more sense as to what's going on in the component itself

Edited by w00tyd00d
  • Like 1
Link to comment
Share on other sites

If I'm understanding this right, then the component should be:
 

local Whetstone = Class(function(self,inst) --component name 
	self.inst = inst
end)

function Whetstone:Whetstonerepair(target, doer, moonrocknugget) -- is whetstone repair my .str?
	local val = doer:HasTag:("wappa") and 1/2 -- I'm assuming I can assign a tag to be checked by the component here
	local sum = math.min(1, target.components.finiteuses:GetPercent() + val)  -- 
	target.components.finiteuses:SetPercent(sum)
	invobj.components.stackable:Get():Remove() 
	
end

function Spooler:AddComponentActions(doer, target, actions, right)
	if right and target:HasTag("moonrockable") then  --moonrockable being a tag i will add to the item's prefabs later on
		table.insert(actions, 1, ACTIONS.WHETSTONING) --if I add the custom action to only my character, doesn't that mean if someone tries to repair the item the game will crash? 
	end
      
end

return Whetstone

Sorry for the commented questions, there's still a lot to learn, and i appreciate you taking the extra steps to do so.

Now I'm still confused from here, and I have read the modmain.lua and so far I understand that I have to do a TagCheck and a local WHETSTONEREPAIR = Action() if it was only a mod item, but in my case its for a character, does this mean I have to Addcomponent to my weapon's prefav, then call the inst.component.Whetstonerepair which will target my current finite uses to set it to the sum's percentage and also AddTag moonrockable... but how do I trigger the component only when it "receives" a moonrock?

From what I've learned so far, I think I have to add another component, that will have arguments that check whether or not this component can be enacted? Or is there a function I could already add?

 

I hope my questions aren't confusing.. and thanks again w00t, so great of you to help me so far.

 

Edited by SourNVitriol
Link to comment
Share on other sites

You actually call the "AddComponentAction" from the modmain as its a mod API function. The only reason the "CollectUseActions" is there is for DS compatibility, so you can just outright ignore that for now. ;) 

Here, I'll walk you through a little step by step guide as far as how I usually tackle adding in custom actions.

Spoiler

 

1. Write the Component

First thing's first is to (at least start to) write your custom component to add whatever mechanic you want. You don't need to necessarily finish it before doing anything else, but to have at least the function you want to call with the action named is an important part. The actual mechanics you can figure out and refine as you go, but you will need to think of some sort of name to reference the function later. Here's a very basic example/skeleton using a component from my Repair Mod:


local <Your Component Name> = Class(function(self,inst)
	self.inst = inst
end)

function <Your Component Name>:<Your Function>(target, doer, invobj)	-- target == item youre trying to repair, doer == you, invobj == repair item
	local val = <your repair value>	
	local sum = math.min(1, target.components.finiteuses:GetPercent() + val)  -- assures value cant go above 100%
	target.components.finiteuses:SetPercent(sum)
	invobj.components.stackable:Get():Remove()  -- removes 1 repair item
	doer:PushEvent("repair")  -- optional, only makes a ding sound like when using the sewing kit
end

return <Your Component Name>

*Note* You'll want to make sure this is in its own file, named after the component itself using lower cased letters (as that's what gets called using inst.components.<your component name>) and that its in the file path "<your mod name>\scripts\components". Everything else from here on out will go in your modmain.lua.

 

2. Add the Action itself

Next thing I usually do is set up the actual action of whatever thing I want to happen when the player does their thing. Here's a mini-code guide as far as how an action is set up (there's a shorthand way of doing this, but this way works for both games in case you ever want to port your mod in the future):


--required lib
local Action = GLOBAL.Action

local <YOUR ACTION ID> = Action()
<YOUR ACTION ID>.id = "<YOUR ACTION ID>"	-- how to identify the action later using ACTIONS.<YOUR ACTION ID>
<YOUR ACTION ID>.str = "<Your Action Text>"	-- what text shows up for on the players cursor
<YOUR ACTION ID>.fn = function(act)			-- what does this action actually do
	if act.target.components.finiteuses then	-- optional to do, but a good idea to double check the item youre working on has the component it needs
		act.invobject.components.<your component name>:<Your Function>(act.target, act.doer, act.invobject)
		return true		-- this is a callback to stop your player from saying "I can't do that" even if the action goes through fine
	end
end
AddAction(<YOUR ACTION ID>)

 

3. Add the Stategraph Handlers

So now the game will know what function to call when you perform your action, but it doesn't know what the player should do to animate the action itself. For that, we need to add an action handler to the player's stategraph (both the host's and the clients). For something like repairing an item, this is a pretty simple addition and is based around your preference to what animation the player should do when repairing. If you want them to do the funny infinity hands thing like when they craft something, you'll want "dolongaction" but if you just want them to act as if they're placing an object, you'll want "doshortaction". The code goes as follows:
 


--required lib
local ActionHandler = GLOBAL.ActionHandler

AddStategraphActionHandler("wilson", ActionHandler(ACTIONS.<YOUR ACTION ID>, "dolongaction"))
AddStategraphActionHandler("wilson_client", ActionHandler(ACTIONS.<YOUR ACTION ID>, "dolongaction"))

 

4. Add the Component Action

Now that the game will know what to do when the action occurs, we need to tell it when the action should be available to the player. We do this by assigning what kind of action it is, what component a prefab should have in order to commence the action, as well as whatever prerequisite tags we want to include to refine the selection even further. This is all done using the AddComponentAction:


--required lib
local ACTIONS = GLOBAL.ACTIONS

-- USEITEM is catagorized as any item that's used on another item/entity
AddComponentAction("USEITEM","<your component name>",function(inst, doer, target, actions, right)  -- right is only necessary here if you want to right click the action
	if right and target:HasTag("<your tag check>") then
		table.insert(actions, ACTIONS.<YOUR ACTION ID>)
	end
end)

What this essentially does is checks anything that has your custom component, if the right-click option is available and if its being dragged on top of something that has the tag you're checking for, it will put the action on your cursor and you can commence your action by right-clicking.

If you want to know more about other types of actions and what arguments they take, it's all listed in componentactions.lua.

 

5. Add the Custom Component and Tag Check(s) to Designated Items

Almost done! Now we need to add the tag checks to the items we would like to perform the action on. If you want the action to be available all the time, you can just simply add the tag you want to check for to the target prefab through AddPrefabPostInit and you can just skip right to adding your component to your designated action-inducing item.

However, for something like repairing, we ideally would like to take away the action to repair once something is fully repaired or else you'll just end up wasting materials. To make sure the tag is only there when we need it when it comes to repairing, the best thing I suggest is adding a ListenForEvent function to your prefab's postinit function that will dynamically add/remove the tag when the item drops below 100% durability. That will look something like this:


function TagCheck(inst)  -- whatever you want to name your postinit function
	local function percentCheck(inst, data)
		if (not inst:HasTag("<your tag check>")) and data.percent < 1 then
			inst:AddTag("<your tag check>")
		elseif inst:HasTag("<your tag check>") and data.percent >= 1 then
			inst:RemoveTag("<your tag check>")
		end
	end

	if GLOBAL.TheWorld.ismastersim then  -- since major data is stored on the host
		inst:ListenForEvent("percentusedchange", percentCheck)  --"percentusedchange" works for finiteuses, armor, and fueled
	end
end
AddPrefabPostInit("<your target item>", TagCheck)

Lastly, all you need to do is just add your custom component to the item you wish to be used as the action-inducing item, in this case to repair.


function ActionItemPostInit(inst)  -- whatever you want to name your postinit function
	if GLOBAL.TheWorld.ismastersim then  -- since major data is stored on the host
		inst:AddComponent("<your component>")
	end
end
AddPrefabPostInit("<your action item>", ActionItemPostInit)

 

And that should do it! You should now be able to perform your action on the designated target item using the designated action item. :) 

 

Whew lol lemme know if any of that doesn't make sense to you and I'll try to do the best I can to explain anything further ;P

Also the reason I went in to such broad detail is because this same process is pretty much done for any custom action you want to add to the game, so its a good process to learn if that's ever your desire to do in the future.

Edited by w00tyd00d
  • Like 1
Link to comment
Share on other sites

One quick thing I should mention about this system is that when the world first loads, I don't think it will automatically apply the tag to the repairable item until its durability changes in some way. To fix this, you can just run a quick wrapper function to the durability component's OnLoad function using AddComponentPostInit, like this:

function PercentUsedChangePostInit(comp)
	local old_OnLoad = comp.OnLoad
	comp.OnLoad = function(self, data)
		old_OnLoad(self, data)
		self.inst:PushEvent("percentusedchange", {percent = self:GetPercent()})
	end
end
AddComponentPostInit("finiteuses", PercentUsedChangePostInit)
AddComponentPostInit("armor", PercentUsedChangePostInit)
AddComponentPostInit("fueled", PercentUsedChangePostInit)

You would just need to add it to the component that applies to you

Edited by w00tyd00d
  • Like 1
Link to comment
Share on other sites

You helped me make it work, w00t!

amazing.png.6d2e9dfcde94bdc5f174ecaba59c1149.png

I also learned a lot of things along the way, and its super great. Althought to be honest you gave me such a concise tutorial that it was almost failproof and spoonfed; not that that's a bad thing, i learned a ton from it, it can almost be a tutorial on this specific thing, and its functions, that I'm planning on making another component for another effect!

 

10 hours ago, w00tyd00d said:

One quick thing I should mention about this system is that when the world first loads, I don't think it will automatically apply the tag to the repairable item until its durability changes in some way. To fix this, you can just run a quick wrapper function to the durability component's OnLoad function using AddComponentPostInit, like this:


function PercentUsedChangePostInit(comp)
	local old_OnLoad = comp.OnLoad
	comp.OnLoad = function(self, data)
		old_OnLoad(self, data)
		self.inst:PushEvent("percentusedchange", {percent = self:GetPercent()})
	end
end
AddComponentPostInit("finiteuses", PercentUsedChangePostInit)
AddComponentPostInit("armor", PercentUsedChangePostInit)
AddComponentPostInit("fueled", PercentUsedChangePostInit)

You would just need to add it to the component that applies to you

In this case, finiteuses. But I'm thinking of changing my weapon to fueled type instead, which is simply replacing certain parts of the code.

 

After all this i realized i need to learn some basic java strings and math methods as well if i want to "think in the language", along with studying available component actions and how other components are coded. But yeah, this was cool, big thanks again w00t, and to everyone else!

 

Edited by SourNVitriol
Link to comment
Share on other sites

On 10/13/2017 at 3:28 AM, SourNVitriol said:

In this case, finiteuses. But I'm thinking of changing my weapon to fueled type instead, which is simply replacing certain parts of the code.

Fueled is usually only used if you'd like to have it use durability over time, so if that is your intention for the item then all the power to ya :) luckily when it comes to repairing its durability, it uses all the same naming conventions as both finiteuses and armor.

The only exception to this rule is the thermal stone, which if I'm perfectly honest I'm not really sure why it uses the fueled component for its durability. Especially since in DST sewing is linked to the fueled mechanic, therefore you can actually sew the thermal stone to replenish durability lol (there is a no_sewing variable in the fueled component but it's only used by the spider hat and winter ornaments). Just seems like a silly choice to me and should probably be switched to finiteuses in the future. :p 

Not to mention this:

inst.components.fueled:SetPercent(inst.components.fueled:GetPercent() - 1 / TUNING.HEATROCK_NUMUSES)

doesn't look as clean as this:

inst.components.finiteuses:Use(1)

:?

  • Like 1
Link to comment
Share on other sites

Yeah, I wanted to use fueled as my component since I thought that would mean I didn't have to deal with coding it so the item just becomes unequippable upon reaching 0 uses.

But then.. i realized I could just force it to the inventory upon depleted uses (i wanted fueled since triggering ondepleted was easier than the finiteuses way) and add an argument wherein the weapon can only be equipped if current uses = 0, so... :D

Quote

The only exception to this rule is the thermal stone, which if I'm perfectly honest I'm not really sure why it uses the fueled component for its durability. Especially since in DST sewing is linked to the fueled mechanic, therefore you can actually sew the thermal stone to replenish durability lol

Huh, I didn't know that. Ill take at look at the heatrock and the fueled components it uses, try in game, have a nice chuckle, get some coffee then get back to coding my custom attack :D 

Edited by SourNVitriol
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...