Jump to content

Making an item only holdable by a certain character


Recommended Posts

Hello, I'm working on a custom character and one of their perks is that they have a unique weapon, I'm trying to make it so that if the weapon is picked up by another player it is dropped or otherwise unusable, similarly to Lucy, I tried to poke into Lucy's code to see if I could find out what makes that work but was unable to glean what part is responsible for it.

I assumed this was the code for it but it crashes the game after the character is selected in a world

	inst._container = nil

    inst._oncontainerownerchanged = function(container)
        topocket(inst, container)
    end

    inst._oncontainerremoved = function()
        unstore(inst)
    end

    inst:ListenForEvent("onputininventory", topocket)
    inst:ListenForEvent("ondropped", toground)

Thank you for reading

Link to comment
Share on other sites

I was putzing around with the code trying to see what, if anything would stick and I think I kerfuffled everything, I also ended up calling the lucy_classified prefab even though I'm not sure if it has anything to do with what I'm trying to do, plus when I tried to remove the added code the game crashes on character select

All lines with comments are lifted directly from lucy.lua

local assets = {
	Asset("ANIM", "anim/robewand.zip"),
	Asset("ANIM", "anim/swap_robewand.zip"),   
	
	Asset("ATLAS", "images/inventoryimages/robewand.xml"),    
	Asset("IMAGE", "images/inventoryimages/robewand.tex"),
}
		
prefabs = 
{
	"lucy_classified" -- 
}

local prefabs ={

}

-- This section up until the next break is taken directly from lucy.lua -----------------------------------
local function AttachClassified(inst, classified)
    inst.lucy_classified = classified
    inst.ondetachclassified = function() inst:DetachClassified() end
    inst:ListenForEvent("onremove", inst.ondetachclassified, classified)
end

local function DetachClassified(inst)
    inst.lucy_classified = nil
    inst.ondetachclassified = nil
end

local function OnRemoveEntity(inst)
    if inst.lucy_classified ~= nil then
        if TheWorld.ismastersim then
            inst.lucy_classified:Remove()
            inst.lucy_classified = nil
        else
            inst.lucy_classified._parent = nil
            inst:RemoveEventCallback("onremove", inst.ondetachclassified, inst.lucy_classified)
            inst:DetachClassified()
        end
    end
end

local function storeincontainer(inst, container)
    if container ~= nil and container.components.container ~= nil then
        inst:ListenForEvent("onputininventory", inst._oncontainerownerchanged, container)
        inst:ListenForEvent("ondropped", inst._oncontainerownerchanged, container)
        inst:ListenForEvent("onremove", inst._oncontainerremoved, container)
        inst._container = container
    end
end

local function unstore(inst)
    if inst._container ~= nil then
        inst:RemoveEventCallback("onputininventory", inst._oncontainerownerchanged, inst._container)
        inst:RemoveEventCallback("ondropped", inst._oncontainerownerchanged, inst._container)
        inst:RemoveEventCallback("onremove", inst._oncontainerremoved, inst._container)
        inst._container = nil
    end
end

local function topocket(inst, owner)
    if inst._container ~= owner then
        unstore(inst)
        storeincontainer(inst, owner)
    end
    inst.lucy_classified:SetTarget(owner.components.inventoryitem ~= nil and owner.components.inventoryitem.owner or owner)
end

local function toground(inst)
    unstore(inst)
    inst.lucy_classified:SetTarget(nil)
end
-----------------------------------

local function OnEquip(inst, owner)
	owner.AnimState:OverrideSymbol("swap_object", "swap_robewand", "swap_robewand")
	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 function fn()
	local inst = CreateEntity()
	inst.entity:AddTransform()
	inst.entity:AddAnimState()
	inst.entity:AddNetwork()
	MakeInventoryPhysics(inst)
	
	inst.AnimState:SetBank("robewand")
	inst.AnimState:SetBuild("robewand")
	inst.AnimState:PlayAnimation("idle")
	
	if not TheWorld.ismastersim then
		return inst
	end

	inst:AddComponent("inventoryitem")
	inst.components.inventoryitem.imagename = "robewand"
	inst.components.inventoryitem.atlasname = "images/inventoryimages/robewand.xml"
	inst:AddComponent("equippable")
	inst:AddComponent("inspectable")
	inst.components.equippable:SetOnEquip(OnEquip)
	inst.components.equippable:SetOnUnequip(OnUnequip)
	
	-- This section up until the next break is taken directly from lucy.lua ------------------
	inst:AddComponent("sentientaxe")
	
	inst.lucy_classified = SpawnPrefab("lucy_classified")
    inst.lucy_classified.entity:SetParent(inst.entity)
    inst.lucy_classified._parent = inst
    inst.lucy_classified:SetTarget(nil)
	
	inst._container = nil

    inst._oncontainerownerchanged = function(container)
        topocket(inst, container)
    end

    inst._oncontainerremoved = function()
        unstore(inst)
    end

    inst:ListenForEvent("onputininventory", topocket)
    inst:ListenForEvent("ondropped", toground)
	
	
	return inst
	----------------------------------
end

return Prefab("common/inventory/robewand", fn, assets)

And heres the crash error from the game

[00:00:50]: [string "scripts/components/inventory.lua"]:637: attempt to index local 'inst' (a nil value)
LUA ERROR stack traceback:
    scripts/components/inventory.lua:637 in (method) GiveItem (Lua) <636-769>
    scripts/prefabs/player_common.lua:1941 in (method) OnNewSpawn (Lua) <1937-1949>
    scripts/networking.lua:250 in () ? (Lua) <238-256>
    =[C]:-1 in (method) SendSpawnRequestToServer (C) <-1--1>
    scripts/mainfunctions.lua:1289 in (local) cb (Lua) <1287-1290>
    scripts/frontend.lua:538 in (method) DoFadingUpdate (Lua) <502-542>
    scripts/frontend.lua:590 in (method) Update (Lua) <550-707>
    scripts/update.lua:92 in () ? (Lua) <33-129>

 

Link to comment
Share on other sites

lucy_classified prefab is used for communication between the host and client purpose only and is irrelevant for what you want to achieve.

If you look into the other component linked to sentientaxe, which is possessedaxe.lua you will see a lot of functions that should help you see what to do.

Functions like

Spoiler

 


local function IsValidOwner(inst, owner)
    return owner:HasTag("woodcutter")
end

 

or

Spoiler

function PossessedAxe:LinkToPlayer(player)
    self:StopWaitingForPlayer()

    if self.player == player then
        return
    elseif self.player ~= nil then
        self.inst:RemoveEventCallback("onremove", self.onplayerremoved, self.player)
        self.inst:RemoveEventCallback("possessedaxe", self.onplayerpossessedaxe, self.player)
    end

    self.player = player
    if player == nil then
        self.userid = nil
        self.inst:PushEvent("axepossessedbyplayer", nil)
        return
    end
    self.userid = player.userid

    player:PushEvent("possessedaxe", self.inst)
    self.inst:ListenForEvent("onremove", self.onplayerremoved, player)
    self.inst:ListenForEvent("possessedaxe", self.onplayerpossessedaxe, player)
    self.inst:PushEvent("axepossessedbyplayer", player)
end

 

without forgetting

Spoiler

local function OnCheckOwner(inst, self)
    self.checkownertask = nil
    local owner = inst.components.inventoryitem:GetGrandOwner()
    if owner == nil or owner.components.inventory == nil then
        return
    elseif not IsValidOwner(inst, owner) then
        self:Drop()
        inst:PushEvent("axerejectedowner", owner)
    else
        local other = OwnerAlreadyHasPossessedAxe(inst, owner)
        if other ~= nil then
            self:Drop()
            other:PushEvent("axerejectedotheraxe", inst)
        elseif owner:HasTag("player") then
            self:LinkToPlayer(owner)
        end
    end
end

 

Hope these functions will show you the way.

 

EDIT: a small tip to make it easier to find your way around the DST code.

Download grepWin (http://stefanstools.sourceforge.net/grepWin.html) and install it. Once you have this tool, you can look into some codes that you think is relevant for the task you try to achieve. Identify chunks and names of function or events which seems to be linked to your goal. Then open winGrep inside the folder

Steam\steamapps\common\Don't Starve Together\data\scripts

and copy/paste what you want to look for in the search box. It will show you all the files which contains this piece of code. From there you can keep on investigating.

Edited by ZupaleX
Link to comment
Share on other sites

Tried running a very simplified version that I was hoping would run without doing all the checks for duplicates and the like but now I'm getting this error.

[00:00:53]: [string "scripts/entityscript.lua"]:509: bad argument #1 to 'lower' (string expected, got nil)
LUA ERROR stack traceback:
    =[C]:-1 in (field) lower (C) <-1--1>
    scripts/entityscript.lua:509 in (field) AddComponent (Lua) <508-530>
    ../mods/robe/scripts/prefabs/robewand.lua:44 in (field) fn (Lua) <22-47>
    scripts/mainfunctions.lua:175 in () ? (Lua) <164-206>
    =[C]:-1 in (method) SpawnPrefab (C) <-1--1>
    scripts/mainfunctions.lua:220 in (global) SpawnPrefab (Lua) <217-222>
    scripts/mainfunctions.lua:226 in (global) SpawnSaveRecord (Lua) <224-266>
    scripts/components/inventory.lua:174 in (method) OnLoad (Lua) <159-191>
    scripts/entityscript.lua:1525 in (method) SetPersistData (Lua) <1516-1533>
    scripts/mainfunctions.lua:1323 in () ? (Lua) <1319-1331>
    =[C]:-1 in (method) SendResumeRequestToServer (C) <-1--1>
    scripts/prefabs/world_network.lua:30 in (field) fn (Lua) <19-34>
    scripts/scheduler.lua:177 in (method) OnTick (Lua) <155-207>
    scripts/scheduler.lua:371 in (global) RunScheduler (Lua) <369-377>
    scripts/update.lua:170 in () ? (Lua) <149-228>

This is the code for the component, aside from this the only other changes to the previous working build were that i add a call for the component in the item's prefab, and the "painter" tag in the character prefab

local function IsValidOwner(inst, owner)
    return owner:HasTag("painter")
end

local function OnCheckOwner(inst, self)
    self.checkownertask = nil
    local owner = inst.components.inventoryitem:GetGrandOwner()
    if owner == nil or owner.components.inventory == nil then
        return
    elseif not IsValidOwner(inst, owner) then
        self:Drop()
        end
    end
end

Would it be possible to get the locking working with a code this stripped down?

Link to comment
Share on other sites

Character prefab

local MakePlayerCharacter = require "prefabs/player_common"


local assets = {

        Asset( "ANIM", "anim/player_basic.zip" ),
        Asset( "ANIM", "anim/player_idles_shiver.zip" ),
        Asset( "ANIM", "anim/player_actions.zip" ),
        Asset( "ANIM", "anim/player_actions_axe.zip" ),
        Asset( "ANIM", "anim/player_actions_pickaxe.zip" ),
        Asset( "ANIM", "anim/player_actions_shovel.zip" ),
        Asset( "ANIM", "anim/player_actions_blowdart.zip" ),
        Asset( "ANIM", "anim/player_actions_eat.zip" ),
        Asset( "ANIM", "anim/player_actions_item.zip" ),
        Asset( "ANIM", "anim/player_actions_uniqueitem.zip" ),
        Asset( "ANIM", "anim/player_actions_bugnet.zip" ),
        Asset( "ANIM", "anim/player_actions_fishing.zip" ),
        Asset( "ANIM", "anim/player_actions_boomerang.zip" ),
        Asset( "ANIM", "anim/player_bush_hat.zip" ),
        Asset( "ANIM", "anim/player_attacks.zip" ),
        Asset( "ANIM", "anim/player_idles.zip" ),
        Asset( "ANIM", "anim/player_rebirth.zip" ),
        Asset( "ANIM", "anim/player_jump.zip" ),
        Asset( "ANIM", "anim/player_amulet_resurrect.zip" ),
        Asset( "ANIM", "anim/player_teleport.zip" ),
        Asset( "ANIM", "anim/wilson_fx.zip" ),
        Asset( "ANIM", "anim/player_one_man_band.zip" ),
        Asset( "ANIM", "anim/shadow_hands.zip" ),
        Asset( "SOUND", "sound/sfx.fsb" ),
        Asset( "SOUND", "sound/wilson.fsb" ),
        Asset( "ANIM", "anim/beard.zip" ),

        Asset( "ANIM", "anim/robecharacter.zip" ),
        Asset( "ANIM", "anim/ghost_robecharacter_build.zip" ),
}
local prefabs = {}

-- Custom starting items
local start_inv = {
"robewand"
}

-- When the character is revived from human
local function onbecamehuman(inst)
	-- Set speed when loading or reviving from ghost (optional)
	inst.components.locomotor.walkspeed = 4
	inst.components.locomotor.runspeed = 6
end

-- When loading or spawning the character
local function onload(inst)
    inst:ListenForEvent("ms_respawnedfromghost", onbecamehuman)

    if not inst:HasTag("playerghost") then
        onbecamehuman(inst)
    end
end


-- This initializes for both the server and client. Tags can be added here.
local common_postinit = function(inst) 
	-- Minimap icon
	inst.MiniMapEntity:SetIcon( "robecharacter.tex" )
	inst:AddTag("painter")
end

-- This initializes for the server only. Components are added here.
local master_postinit = function(inst)
	-- choose which sounds this character will play
	inst.soundsname = "wendy"
	
	-- Uncomment if "wathgrithr"(Wigfrid) or "webber" voice is used
    --inst.talker_path_override = "dontstarve_DLC001/characters/"
	
	-- Stats	
	inst.components.health:SetMaxHealth(125)
	inst.components.hunger:SetMax(150)
	inst.components.sanity:SetMax(200)
	
	-- Damage multiplier (optional)
    inst.components.combat.damagemultiplier = .8
	
	-- Hunger rate (optional)
	inst.components.hunger.hungerrate = 1.1 * TUNING.WILSON_HUNGER_RATE
	
	inst.OnLoad = onload
    inst.OnNewSpawn = onload
end

return MakePlayerCharacter("robecharacter", prefabs, assets, common_postinit, master_postinit, start_inv)

and item prefab

local assets = {
	Asset("ANIM", "anim/robewand.zip"),
	Asset("ANIM", "anim/swap_robewand.zip"),   
	
	Asset("ATLAS", "images/inventoryimages/robewand.xml"),    
	Asset("IMAGE", "images/inventoryimages/robewand.tex"),
}
		
prefabs = {}

local function OnEquip(inst, owner)
	owner.AnimState:OverrideSymbol("swap_object", "swap_robewand", "swap_robewand")
	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 function fn()
	local inst = CreateEntity()
	inst.entity:AddTransform()
	inst.entity:AddAnimState()
	inst.entity:AddNetwork()
	MakeInventoryPhysics(inst)
	
	inst.AnimState:SetBank("robewand")
	inst.AnimState:SetBuild("robewand")
	inst.AnimState:PlayAnimation("idle")
	
	if not TheWorld.ismastersim then
		return inst
	end

	inst:AddComponent("inventoryitem")
	inst.components.inventoryitem.imagename = "robewand"
	inst.components.inventoryitem.atlasname = "images/inventoryimages/robewand.xml"
	inst:AddComponent("equippable")
	inst:AddComponent("inspectable")
	inst.components.equippable:SetOnEquip(OnEquip)
	inst.components.equippable:SetOnUnequip(OnUnequip)
	inst.AddComponent("chosenweapon")
	
	return inst
end

return Prefab("common/inventory/robewand", fn, assets)

 

Link to comment
Share on other sites

Yes but somehow you need to add that component, right?I mean, you need to register it, so it is a known component. If you have just this piece of code, you did not create a component.

Edited by ZupaleX
Link to comment
Share on other sites

Alright, kinda let this sit a while but guess I'll ask now, what do I need to do to make the component a component? I added a little bit to it but it still doesn't have any effect

local function IsValidOwner(inst, owner)
    return owner:HasTag("painter")
end

local function OnCheckOwner(inst, self)
    self.checkownertask = nil
    local owner = inst.components.inventoryitem:GetGrandOwner()
    if owner == nil or owner.components.inventory == nil then
        return
    elseif not IsValidOwner(inst, owner) then
        self:Drop()
        end
    end
end

local ChosenWeapon = Class(function(self, inst)
    self.inst = inst

    self.checkownertask = nil
end)

return ChosenWeapon

 

Link to comment
Share on other sites

the codes placed in a lua file named chosenweapon in the component folder

I don't really understand what you mean by where is it used? I tried figuring it out on my own by following the trail of stuff that calls it as far back as I can, but I'm still not getting any results and even if I am on the right trail I don't know how I would find what's causing issues

The current code for the chosenweapon.lua component

Spoiler

local function IsValidOwner(inst, owner)
    return owner:HasTag("painter")
end

local function OnCheckOwner(inst, self)
    self.checkownertask = nil
    local owner = inst.components.inventoryitem:GetGrandOwner()
    if owner == nil or owner.components.inventory == nil then
        return
    elseif not IsValidOwner(inst, owner) then
        self:Drop()
        end
    end
end

local function OnChangeOwner(inst, owner)
    local self = inst.components.possessedaxe
    if self.currentowner == owner then
        return
    elseif self.currentowner ~= nil and self.oncontainerpickedup ~= nil then
        inst:RemoveEventCallback("onputininventory", self.oncontainerpickedup, self.currentowner)
        self.oncontainerpickedup = nil
    end

    if self.checkownertask ~= nil then
        self.checkownertask:Cancel()
        self.checkownertask = nil
    end

    self.currentowner = owner

    if owner == nil then
        return
    elseif owner.components.inventoryitem ~= nil then
        self.oncontainerpickedup = function()
            if self.checkownertask ~= nil then
                self.checkownertask:Cancel()
            end
            self.checkownertask = inst:DoTaskInTime(0, OnCheckOwner, self)
        end
        inst:ListenForEvent("onputininventory", self.oncontainerpickedup, owner)
    end
    self.checkownertask = inst:DoTaskInTime(0, OnCheckOwner, self)
end

local ChosenWeapon = Class(function(self, inst)
    self.inst = inst

    self.currentowner = nil
    self.oncontainerpickedup = nil
	self.checkownertask = nil
	
    inst:ListenForEvent("onputininventory", OnChangeOwner)
    inst:ListenForEvent("ondropped", OnChangeOwner)
end)

function ChosenWeapon:Drop()
    local owner = self.inst.components.inventoryitem:GetGrandOwner()
    if owner ~= nil and owner.components.inventory ~= nil then
        owner.components.inventory:DropItem(self.inst, true, true)
    end
end

return ChosenWeapon

 

the current code for the robewand.lua prefab

Spoiler

local assets = {
    Asset("ANIM", "anim/robewand.zip"),
    Asset("ANIM", "anim/swap_robewand.zip"),   
    
    Asset("ATLAS", "images/inventoryimages/robewand.xml"),    
    Asset("IMAGE", "images/inventoryimages/robewand.tex"),
}
        
prefabs = {}

local function storeincontainer(inst, container)
    if container ~= nil and container.components.container ~= nil then
        inst:ListenForEvent("onputininventory", inst._oncontainerownerchanged, container)
        inst:ListenForEvent("ondropped", inst._oncontainerownerchanged, container)
        inst._container = container
    end
end

local function unstore(inst)
    if inst._container ~= nil then
        inst:RemoveEventCallback("onputininventory", inst._oncontainerownerchanged, inst._container)
        inst:RemoveEventCallback("ondropped", inst._oncontainerownerchanged, inst._container)
        inst._container = nil
    end
end

local function topocket(inst, owner)
    if inst._container ~= owner then
        unstore(inst)
        storeincontainer(inst, owner)
    end
end

local function toground(inst)
    unstore(inst)
end

local function OnEquip(inst, owner)
    owner.AnimState:OverrideSymbol("swap_object", "swap_robewand", "swap_robewand")
    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 function fn()
    local inst = CreateEntity()
    inst.entity:AddTransform()
    inst.entity:AddAnimState()
    inst.entity:AddNetwork()
    MakeInventoryPhysics(inst)
    
    inst.AnimState:SetBank("robewand")
    inst.AnimState:SetBuild("robewand")
    inst.AnimState:PlayAnimation("idle")
    
    if not TheWorld.ismastersim then
        return inst
    end
    
    inst:ListenForEvent("onputininventory", topocket)
    inst:ListenForEvent("ondropped", toground)

    inst:AddComponent("inventoryitem")
    inst.components.inventoryitem.imagename = "robewand"
    inst.components.inventoryitem.atlasname = "images/inventoryimages/robewand.xml"
    inst:AddComponent("equippable")
    inst:AddComponent("inspectable")
    inst.components.equippable:SetOnEquip(OnEquip)
    inst.components.equippable:SetOnUnequip(OnUnequip)
    inst.AddComponent("chosenweapon")
    
    return inst
end

return Prefab("common/inventory/robewand", fn, assets)

 

Link to comment
Share on other sites

Well there are a few things which are not right. I am even surprised you say it's doing nothing, I would expect it to crash.

In your OnChangeOwner, you define a local variable

self = inst.components.possessedaxe

which will be nil in your case as your item does not have that component. So then it should crash at the next line when you try to check if self has a sub-member called currentowner, as self is nil.

This would mean that this code is not even called.

Then in your prefab script you have all this unstore, topocket... functions which are creating event callback with an associated function inst._oncontainerownerchanged that I did not see defined anywhere.

If I were you,m I would put a bunch of print() in the functions you think should be called when you drop, put in inventory, etc... and see if these are displayed. You could like that track at which point your chain is broken.

Link to comment
Share on other sites

When I said it did nothing I did mean it crashed, sorry for the lack of clarity, I haven't been reposting the crash log since as far as I'm aware it's identical to the one at: 

 

Thank you for pointing out that I left the possessedaxe component in there, I must have missed that, same with the oncontainerownerchanged bit, I added the following bits to the local function in the prefab

    inst._container = nil
	
	inst._oncontainerownerchanged = function(container)
        topocket(inst, container)
    end

and I changed

self = inst.components.possessedaxe

to

self = inst.components.chosenweapon

unfortunately it still crashes, how would I be able to use the Print()'s like you said, and where would I have to go to check if they're doing their job?

Link to comment
Share on other sites

Actually comparing the crash logs I am noticing they are a little different now

here's the current crash log

[00:01:14]: [string "scripts/entityscript.lua"]:509: bad argument #1 to 'lower' (string expected, got nil)
LUA ERROR stack traceback:
    =[C]:-1 in (field) lower (C) <-1--1>
    scripts/entityscript.lua:509 in (field) AddComponent (Lua) <508-530>
    ../mods/robe/scripts/prefabs/robewand.lua:80 in (field) fn (Lua) <49-83>
    scripts/mainfunctions.lua:175 in () ? (Lua) <164-206>
    =[C]:-1 in (method) SpawnPrefab (C) <-1--1>
    scripts/mainfunctions.lua:220 in (global) SpawnPrefab (Lua) <217-222>
    scripts/prefabs/player_common.lua:1941 in (method) OnNewSpawn (Lua) <1937-1949>
    scripts/networking.lua:250 in () ? (Lua) <238-256>
    =[C]:-1 in (method) SendSpawnRequestToServer (C) <-1--1>
    scripts/mainfunctions.lua:1289 in (local) cb (Lua) <1287-1290>
    scripts/frontend.lua:541 in (method) DoFadingUpdate (Lua) <505-545>
    scripts/frontend.lua:593 in (method) Update (Lua) <553-710>
    scripts/update.lua:92 in () ? (Lua) <33-129>

 

Link to comment
Share on other sites

Aaaaa thank you so much! After I changed it to inst:AddComponent all I needed to do was delete an extra end and now it loads up properly!

Still need to check if the item is unique to the character but I'm very glad it at least loads

Edit: Yup it works! Now I just have to work on everything else in the mod, hopefully I won't need to return to the forums for everything

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