Jump to content

Help needed with custom weapon mechanics


Recommended Posts

So about a week ago I started working on my first mod. Things have been going pretty well, and I've learned how to do a lot of different things by either reading tutorials or looking through the game's code to see how something is achieved. Right now I've got most of the artwork done, implemented basic stats and made some custom starting weapons. 

This is the part where I need some help on. One of my character's starting weapons is a sword, and I wanted it to function in a special way, unlike regular weapons. The basic idea is  that the sword starts off at 0% 'durability' and is only usable once it reaches 100%. Once equipped, that durability begins to rapidly decrease, similar to a torch. Once there is no more durability, the sword gets placed back into your inventory. The way the weapon gets charged by doing damage. (or getting kills, whichever is easiest)

I was able to get the sword's durability to decrease while equipped, and for it to go back into your inventory once it hits 0, but I've been stuck on making the recharge mechanics.

I tried adding this into my weapon's prefab.

local function onattack(inst, owner, target)
    if inst.components.fueled:GetPercent() < 100 then
		inst.components.fueled:DoDelta(5)
    end
end

It worked fine, as it was adding more fuel to the weapon on hit, but the problem was that fuel was only added if you were actually using the weapon. I want it to work so that the fuel would increase regardless of what weapon you're using, since the sword can't be equipped when it's below 100% anyway.

Tried looking around before posting this, and couldn't find any similar topic, so here I am.

Any help would be appreciated.

Link to comment
Share on other sites

18 minutes ago, ZupaleX said:

I would put a ListenForEvent on the sword, with the player carrying it as a source, and catching the event emitted when the player deals damages.

Any idea what the exact event is called?

Link to comment
Share on other sites

Several could work. Maybe the "onattackother" pushed during the Combat:DoAttack function?

Spoiler

function Combat:DoAttack(target_override, weapon, projectile, stimuli, instancemult)
    local targ = target_override or self.target
    local weapon = weapon or self:GetWeapon()

    if not self:CanHitTarget(targ, weapon) then
        self.inst:PushEvent("onmissother", { target = targ, weapon = weapon })
        if self.areahitrange ~= nil and not self.areahitdisabled then
            self:DoAreaAttack(projectile or self.inst, self.areahitrange, weapon, nil, stimuli, { "INLIMBO" })
        end
        return
    end

    self.inst:PushEvent("onattackother", { target = targ, weapon = weapon, projectile = projectile, stimuli = stimuli })

    if weapon ~= nil and projectile == nil then
        if weapon.components.projectile ~= nil then
            local projectile = self.inst.components.inventory:DropItem(weapon, false)
            if projectile ~= nil then
                projectile.components.projectile:Throw(self.inst, targ)
            end
            return

        elseif weapon.components.complexprojectile ~= nil then
            local projectile = self.inst.components.inventory:DropItem(weapon, false)
            if projectile ~= nil then
                projectile.components.complexprojectile:Launch(targ:GetPosition(), self.inst)
            end
            return

        elseif weapon.components.weapon:CanRangedAttack() then
            weapon.components.weapon:LaunchProjectile(self.inst, targ)
            return
        end
    end

    local reflected_dmg = 0
    local reflect_list = {}
    if targ.components.combat ~= nil then
        local mult =
            (stimuli == "electric" or
            (weapon ~= nil and weapon.components.weapon ~= nil and weapon.components.weapon.stimuli == "electric"))
            and not (targ:HasTag("electricdamageimmune") or
                    (targ.components.inventory ~= nil and targ.components.inventory:IsInsulated()))
            and TUNING.ELECTRIC_DAMAGE_MULT + TUNING.ELECTRIC_WET_DAMAGE_MULT * (targ.components.moisture ~= nil and targ.components.moisture:GetMoisturePercent() or (targ:GetIsWet() and 1 or 0))
            or 1
        local dmg = self:CalcDamage(targ, weapon, mult) * (instancemult or 1)
        --Calculate reflect first, before GetAttacked destroys armor etc.
        if projectile == nil then
            reflected_dmg = self:CalcReflectedDamage(targ, dmg, weapon, stimuli, reflect_list)
        end
        targ.components.combat:GetAttacked(self.inst, dmg, weapon, stimuli)
    elseif projectile == nil then
        reflected_dmg = self:CalcReflectedDamage(targ, 0, weapon, stimuli, reflect_list)
    end

    if weapon ~= nil then
        weapon.components.weapon:OnAttack(self.inst, targ, projectile)
    end

    if self.areahitrange ~= nil and not self.areahitdisabled then
        self:DoAreaAttack(targ, self.areahitrange, weapon, nil, stimuli, { "INLIMBO" })
    end

    self.lastdoattacktime = GetTime()

    --Apply reflected damage to self after our attack damage is completed
    if reflected_dmg > 0 and self.inst.components.health ~= nil and not self.inst.components.health:IsDead() then
        self:GetAttacked(targ, reflected_dmg)
        for i, v in ipairs(reflect_list) do
            if v.inst:IsValid() then
                v.inst:PushEvent("onreflectdamage", v)
            end
        end
    end
end

 

 

Link to comment
Share on other sites

18 hours ago, ZupaleX said:

Several could work. Maybe the "onattackother" pushed during the Combat:DoAttack function?

  Reveal hidden contents


function Combat:DoAttack(target_override, weapon, projectile, stimuli, instancemult)
    local targ = target_override or self.target
    local weapon = weapon or self:GetWeapon()

    if not self:CanHitTarget(targ, weapon) then
        self.inst:PushEvent("onmissother", { target = targ, weapon = weapon })
        if self.areahitrange ~= nil and not self.areahitdisabled then
            self:DoAreaAttack(projectile or self.inst, self.areahitrange, weapon, nil, stimuli, { "INLIMBO" })
        end
        return
    end

    self.inst:PushEvent("onattackother", { target = targ, weapon = weapon, projectile = projectile, stimuli = stimuli })

    if weapon ~= nil and projectile == nil then
        if weapon.components.projectile ~= nil then
            local projectile = self.inst.components.inventory:DropItem(weapon, false)
            if projectile ~= nil then
                projectile.components.projectile:Throw(self.inst, targ)
            end
            return

        elseif weapon.components.complexprojectile ~= nil then
            local projectile = self.inst.components.inventory:DropItem(weapon, false)
            if projectile ~= nil then
                projectile.components.complexprojectile:Launch(targ:GetPosition(), self.inst)
            end
            return

        elseif weapon.components.weapon:CanRangedAttack() then
            weapon.components.weapon:LaunchProjectile(self.inst, targ)
            return
        end
    end

    local reflected_dmg = 0
    local reflect_list = {}
    if targ.components.combat ~= nil then
        local mult =
            (stimuli == "electric" or
            (weapon ~= nil and weapon.components.weapon ~= nil and weapon.components.weapon.stimuli == "electric"))
            and not (targ:HasTag("electricdamageimmune") or
                    (targ.components.inventory ~= nil and targ.components.inventory:IsInsulated()))
            and TUNING.ELECTRIC_DAMAGE_MULT + TUNING.ELECTRIC_WET_DAMAGE_MULT * (targ.components.moisture ~= nil and targ.components.moisture:GetMoisturePercent() or (targ:GetIsWet() and 1 or 0))
            or 1
        local dmg = self:CalcDamage(targ, weapon, mult) * (instancemult or 1)
        --Calculate reflect first, before GetAttacked destroys armor etc.
        if projectile == nil then
            reflected_dmg = self:CalcReflectedDamage(targ, dmg, weapon, stimuli, reflect_list)
        end
        targ.components.combat:GetAttacked(self.inst, dmg, weapon, stimuli)
    elseif projectile == nil then
        reflected_dmg = self:CalcReflectedDamage(targ, 0, weapon, stimuli, reflect_list)
    end

    if weapon ~= nil then
        weapon.components.weapon:OnAttack(self.inst, targ, projectile)
    end

    if self.areahitrange ~= nil and not self.areahitdisabled then
        self:DoAreaAttack(targ, self.areahitrange, weapon, nil, stimuli, { "INLIMBO" })
    end

    self.lastdoattacktime = GetTime()

    --Apply reflected damage to self after our attack damage is completed
    if reflected_dmg > 0 and self.inst.components.health ~= nil and not self.inst.components.health:IsDead() then
        self:GetAttacked(targ, reflected_dmg)
        for i, v in ipairs(reflect_list) do
            if v.inst:IsValid() then
                v.inst:PushEvent("onreflectdamage", v)
            end
        end
    end
end

 

 

I made some changes and it seems like nothing is happening whenever damage is dealt. I'm not sure if I'm putting things in the right place.

Here's my weapon.lua:

Spoiler

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

local function addUltCharge(inst, owner, target)
	
	if inst.components.fueled:GetPercent() < 100 then
		inst.components.fueled:DoDelta(5)
    end

end

local function OnEquip(inst, owner)
	owner.AnimState:OverrideSymbol("swap_object", "swap_dragonblade", "swap_dragonblade")
	owner.AnimState:Show("ARM_carry")
	owner.AnimState:Hide("ARM_normal")
	
	inst.components.fueled:StartConsuming()
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("dragonblade")
	inst.AnimState:SetBuild("dragonblade")
	inst.AnimState:PlayAnimation("idle")
	
	inst:AddComponent("inventoryitem")
	inst.components.inventoryitem.imagename = "dragonblade"
	inst.components.inventoryitem.atlasname = "images/inventoryimages/dragonblade.xml"
	inst:AddComponent("equippable")
	inst.components.equippable:SetOnEquip(OnEquip)
	inst.components.equippable:SetOnUnequip(OnUnequip)
	
	inst:AddTag("sharp")
	inst:AddComponent("weapon")
	inst.components.weapon:SetDamage(55)
	inst.components.weapon:SetRange(1, 1)

	inst:AddComponent("fueled")
	inst.components.fueled:InitializeFuelLevel(TUNING.TORCH_FUEL / 2.5)
	
	inst:ListenForEvent("onattackother",  addUltCharge)
	
	return inst
end

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

 

 

Link to comment
Share on other sites

The event onattackother is pushed by the player attacking.

ListenForEvent takes 4 arguments.

- The first one is a hidden one and is implicitely passed with the : and is the entity itself.

- The second one is the event you want to listen to

- The third one is the function which will be called when that event is caught

- The optionnal 4th argument is the source pushing the event. If none is provided, it will implicetely be the entity itself.

In your case you do not give the 4th argument, so it assumes it is inst (your weapon). But your weapon never pushes that event, it is the player who pushes it.

Edited by ZupaleX
Link to comment
Share on other sites

2 hours ago, ZupaleX said:

The event onattackother is pushed by the player attacking.

ListenForEvent takes 4 arguments.

- The first one is a hidden one and is implicitely passed with the : and is the entity itself.

- The second one is the event you want to listen to

- The third one is the function which will be called when that event is caught

- The optionnal 4th argument is the source pushing the event. If none is provided, it will implicetely be the entity itself.

In your case you do not give the 4th argument, so it assumes it is inst (your weapon). But your weapon never pushes that event, it is the player who pushes it.

What would I need to type so that it knows that the player is the source of the event?

inst:ListenForEvent("onattackother",  addUltCharge, player)

I tried adding player as an argument, but it's telling me that it's not a defined variable.

Link to comment
Share on other sites

1 hour ago, ZupaleX said:

your item is an inventoryitem, so it has an owner :)

Well, my game doesn't crash anymore, but it seems like the function still isn't being executed. I declared an owner and added it as an argument, still no dice.

Spoiler

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

local function addUltCharge(inst, owner, target)
	
	if inst.components.fueled:GetPercent() < 100 then
		inst.components.fueled:DoDelta(5)
    end

end

local function OnEquip(inst, owner)
	owner.AnimState:OverrideSymbol("swap_object", "swap_dragonblade", "swap_dragonblade")
	owner.AnimState:Show("ARM_carry")
	owner.AnimState:Hide("ARM_normal")
	
	inst.components.fueled:StartConsuming()
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("dragonblade")
	inst.AnimState:SetBuild("dragonblade")
	inst.AnimState:PlayAnimation("idle")
	
	inst:AddComponent("inventoryitem")
	local owner = inst.components.inventoryitem:GetGrandOwner()
	inst.components.inventoryitem.imagename = "dragonblade"
	inst.components.inventoryitem.atlasname = "images/inventoryimages/dragonblade.xml"
	
	inst:AddComponent("equippable")
	inst.components.equippable:SetOnEquip(OnEquip)
	inst.components.equippable:SetOnUnequip(OnUnequip)
	
	inst:AddTag("sharp")
	inst:AddComponent("weapon")
	inst.components.weapon:SetDamage(55)
	inst.components.weapon:SetRange(1, 1)
	
	inst:AddComponent("fueled")
	inst.components.fueled:InitializeFuelLevel(TUNING.TORCH_FUEL / 2.5)
	
	inst:ListenForEvent("onattackother",  addUltCharge, owner)
	return inst
end

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

 

By the way, I appreciate all the replies. You've been really helpful. :D

Edited by cilbi
Link to comment
Share on other sites

So the issue is that when the entity is created, it doesn't have an owner yet.

But another event is pushed by the entity itself when put inside a container (the inventory is a container).

So you could in the constructor:

Listen for that event pushed by inst when it is put in the inventory.

Execute a function which would initiate the ListenForEvent with the onattackother, because now GrandOwner would actually return the player.

 

EDIT: by the way, I hope you don't mind me giving you information one by one instead of just spitting a premade function to do what you want to achieve. I believe that you will learn much more like that.

Edited by ZupaleX
Link to comment
Share on other sites

2 hours ago, ZupaleX said:

So the issue is that when the entity is created, it doesn't have an owner yet.

But another event is pushed by the entity itself when put inside a container (the inventory is a container).

So you could in the constructor:

Listen for that event pushed by inst when it is put in the inventory.

Execute a function which would initiate the ListenForEvent with the onattackother, because now GrandOwner would actually return the player.

 

EDIT: by the way, I hope you don't mind me giving you information one by one instead of just spitting a premade function to do what you want to achieve. I believe that you will learn much more like that.

Modified some code and got it working to some extent. I created a new function which would set the owner, and slightly changed the actual function that I wanted to execute.

Spoiler

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

local function addUltCharge(inst, owner, target)
	
	inst.components.talker:Say("Test2.")
	
	if inst.components.fueled and inst.components.fueled:GetPercent() < 100 then
		inst.components.fueled:DoDelta(5)
    end

end

local function ownerSet (inst)

	inst.components.talker:Say("Test.")
	local owner = inst.components.inventoryitem:GetGrandOwner()
	inst:ListenForEvent("onattackother",  addUltCharge, owner)

end

local function OnEquip(inst, owner)
	owner.AnimState:OverrideSymbol("swap_object", "swap_dragonblade", "swap_dragonblade")
	owner.AnimState:Show("ARM_carry")
	owner.AnimState:Hide("ARM_normal")
	
	inst.components.fueled:StartConsuming()
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("dragonblade")
	inst.AnimState:SetBuild("dragonblade")
	inst.AnimState:PlayAnimation("idle")
	
	inst:AddComponent("talker")
	
	inst:AddComponent("inventoryitem")
	inst.components.inventoryitem.imagename = "dragonblade"
	inst.components.inventoryitem.atlasname = "images/inventoryimages/dragonblade.xml"
	
	inst:AddComponent("equippable")
	inst.components.equippable:SetOnEquip(OnEquip)
	inst.components.equippable:SetOnUnequip(OnUnequip)
	
	inst:AddTag("sharp")
	inst:AddComponent("weapon")
	inst.components.weapon:SetDamage(55)
	inst.components.weapon:SetRange(1, 1)
	
	inst:AddComponent("fueled")
	inst.components.fueled:InitializeFuelLevel(TUNING.TORCH_FUEL / 2.5)
	
	inst:ListenForEvent("onputininventory", ownerSet)
	return inst
end

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

 

Both functions fire. My character says "test" whenever the weapon is equipped, and says "test2" whenever I attack something, regardless of the weapon. However, despite my character saying "test2", fuel never gets added. If I remove the 'if inst.components.fueled' from my if statement, my game will crash and throw out an error saying that I attempted to index the fueled component, which is what confuses me because I added that component to my weapon already.

I don't mind what you're doing, I just hate asking so many questions and not being able to solve it myself. :/

 

Link to comment
Share on other sites

Are you doing this mod for DST or DS?

If it is for DST you are missing in your entity initializer a very important part. The one which says when the client and server side entity stops to be identical.

DST has a network layer (obviously) and all the important things run on the server only. The client just has the part which is required for the entity to be displayed and all the local stuffs to work.

So on both the client and server, the entity need to exists

local inst = CreateEntity()

The entity need to have on both sides informations about its position in the world

inst.entity:AddTransform()

The entity needs to be displayed on the screen on the client side as well as on the host side

inst.entity:AddAnimState()

The network utility and functionality needs to be present on both the host and the client

inst.entity:AddNetwork()

The physics is as well initiated on both sides

MakeInventoryPhysics(inst)

Then you give informations about the appearance of the object by seting build, banks and animation to your AnimState and that needs to be done obviously on the client and host so everybody can see the entity.

inst.AnimState:SetBank("dragonblade")
inst.AnimState:SetBuild("dragonblade")
inst.AnimState:PlayAnimation("idle")

Finally you add tags to your entity. Tags are network variables which are shared by the server and the client version of the entity. It allows to do quick checks when you have a dialog between the server and the client without having to perform complex operations

inst:AddTag("sharp")

After that almost everything is exclusive to the host! All the components will be present for the host only and for most of them the client don't even need them. Your fueled component for instance. The client does not need to have that component on its side of the entity. Imagine if the fueled component was present on both client and host. You add fuel to your item. The client tries to set a new value for the fuel locally, then send the information to the server so it updates it on it's side? But your host was busy consuming fuel from your item and trying to update the info for the client. Both tries to access the component at the same time => you are in big trouble.

This is solved by having these components only on the host. In the end the host performs all the "calculations" and handles everything and redistributes only the useful information to the clients. Much less data traffic and no risk of multiple access to the same thing by different processes.

There are though a few exceptions to this, like the talker component. The talker is present on both the client and the host because this component is not modified. It is just here to display text so no risks here. And then you just need to send signals to tell to the client to display something instead of sending a complete string of character over the network, which is expensive.

In conclusion in your case you will now have that part as well common for the host and the client

inst:AddComponent("talker")

And then you tell the client that he's done and what happens after that is none of its business

inst.entity:SetPristine()

if not TheWorld.ismastersim then
	return inst
end

And finally you will add the part of the code which will be only executed on the server side (because if you are client you finished the execution of the initializer function at the previous line with the "return inst")

	inst:AddComponent("inventoryitem")
	inst.components.inventoryitem.imagename = "dragonblade"
	inst.components.inventoryitem.atlasname = "images/inventoryimages/dragonblade.xml"
	
	inst:AddComponent("equippable")
	inst.components.equippable:SetOnEquip(OnEquip)
	inst.components.equippable:SetOnUnequip(OnUnequip)

	inst:AddComponent("weapon")
	inst.components.weapon:SetDamage(55)
	inst.components.weapon:SetRange(1, 1)
	
	inst:AddComponent("fueled")
	inst.components.fueled:InitializeFuelLevel(TUNING.TORCH_FUEL / 2.5)
	
	inst:ListenForEvent("onputininventory", ownerSet)
	return inst

Eventually your item initializer function should look like that

Spoiler

local function fn()
	local inst = CreateEntity()
	inst.entity:AddTransform()
	inst.entity:AddAnimState()
	inst.entity:AddNetwork()
	MakeInventoryPhysics(inst)

	inst:AddTag("sharp")
	
	inst.AnimState:SetBank("dragonblade")
	inst.AnimState:SetBuild("dragonblade")
	inst.AnimState:PlayAnimation("idle")
	
	inst:AddComponent("talker")

	inst.entity:SetPristine()

	if not TheWorld.ismastersim then
		return inst
	end
	
	inst:AddComponent("inventoryitem")
	inst.components.inventoryitem.imagename = "dragonblade"
	inst.components.inventoryitem.atlasname = "images/inventoryimages/dragonblade.xml"
	
	inst:AddComponent("equippable")
	inst.components.equippable:SetOnEquip(OnEquip)
	inst.components.equippable:SetOnUnequip(OnUnequip)
	
	inst:AddComponent("weapon")
	inst.components.weapon:SetDamage(55)
	inst.components.weapon:SetRange(1, 1)
	
	inst:AddComponent("fueled")
	inst.components.fueled:InitializeFuelLevel(TUNING.TORCH_FUEL / 2.5)
	
	inst:ListenForEvent("onputininventory", ownerSet)
	return inst
end

 

I hope this is clear and helpful.

Cheers

Link to comment
Share on other sites

13 hours ago, ZupaleX said:

Are you doing this mod for DST or DS?

If it is for DST you are missing in your entity initializer a very important part. The one which says when the client and server side entity stops to be identical.

DST has a network layer (obviously) and all the important things run on the server only. The client just has the part which is required for the entity to be displayed and all the local stuffs to work.

So on both the client and server, the entity need to exists


local inst = CreateEntity()

The entity need to have on both sides informations about its position in the world


inst.entity:AddTransform()

The entity needs to be displayed on the screen on the client side as well as on the host side


inst.entity:AddAnimState()

The network utility and functionality needs to be present on both the host and the client


inst.entity:AddNetwork()

The physics is as well initiated on both sides


MakeInventoryPhysics(inst)

Then you give informations about the appearance of the object by seting build, banks and animation to your AnimState and that needs to be done obviously on the client and host so everybody can see the entity.


inst.AnimState:SetBank("dragonblade")
inst.AnimState:SetBuild("dragonblade")
inst.AnimState:PlayAnimation("idle")

Finally you add tags to your entity. Tags are network variables which are shared by the server and the client version of the entity. It allows to do quick checks when you have a dialog between the server and the client without having to perform complex operations


inst:AddTag("sharp")

After that almost everything is exclusive to the host! All the components will be present for the host only and for most of them the client don't even need them. Your fueled component for instance. The client does not need to have that component on its side of the entity. Imagine if the fueled component was present on both client and host. You add fuel to your item. The client tries to set a new value for the fuel locally, then send the information to the server so it updates it on it's side? But your host was busy consuming fuel from your item and trying to update the info for the client. Both tries to access the component at the same time => you are in big trouble.

This is solved by having these components only on the host. In the end the host performs all the "calculations" and handles everything and redistributes only the useful information to the clients. Much less data traffic and no risk of multiple access to the same thing by different processes.

There are though a few exceptions to this, like the talker component. The talker is present on both the client and the host because this component is not modified. It is just here to display text so no risks here. And then you just need to send signals to tell to the client to display something instead of sending a complete string of character over the network, which is expensive.

In conclusion in your case you will now have that part as well common for the host and the client


inst:AddComponent("talker")

And then you tell the client that he's done and what happens after that is none of its business


inst.entity:SetPristine()

if not TheWorld.ismastersim then
	return inst
end

And finally you will add the part of the code which will be only executed on the server side (because if you are client you finished the execution of the initializer function at the previous line with the "return inst")


	inst:AddComponent("inventoryitem")
	inst.components.inventoryitem.imagename = "dragonblade"
	inst.components.inventoryitem.atlasname = "images/inventoryimages/dragonblade.xml"
	
	inst:AddComponent("equippable")
	inst.components.equippable:SetOnEquip(OnEquip)
	inst.components.equippable:SetOnUnequip(OnUnequip)

	inst:AddComponent("weapon")
	inst.components.weapon:SetDamage(55)
	inst.components.weapon:SetRange(1, 1)
	
	inst:AddComponent("fueled")
	inst.components.fueled:InitializeFuelLevel(TUNING.TORCH_FUEL / 2.5)
	
	inst:ListenForEvent("onputininventory", ownerSet)
	return inst

Eventually your item initializer function should look like that

  Reveal hidden contents


local function fn()
	local inst = CreateEntity()
	inst.entity:AddTransform()
	inst.entity:AddAnimState()
	inst.entity:AddNetwork()
	MakeInventoryPhysics(inst)

	inst:AddTag("sharp")
	
	inst.AnimState:SetBank("dragonblade")
	inst.AnimState:SetBuild("dragonblade")
	inst.AnimState:PlayAnimation("idle")
	
	inst:AddComponent("talker")

	inst.entity:SetPristine()

	if not TheWorld.ismastersim then
		return inst
	end
	
	inst:AddComponent("inventoryitem")
	inst.components.inventoryitem.imagename = "dragonblade"
	inst.components.inventoryitem.atlasname = "images/inventoryimages/dragonblade.xml"
	
	inst:AddComponent("equippable")
	inst.components.equippable:SetOnEquip(OnEquip)
	inst.components.equippable:SetOnUnequip(OnUnequip)
	
	inst:AddComponent("weapon")
	inst.components.weapon:SetDamage(55)
	inst.components.weapon:SetRange(1, 1)
	
	inst:AddComponent("fueled")
	inst.components.fueled:InitializeFuelLevel(TUNING.TORCH_FUEL / 2.5)
	
	inst:ListenForEvent("onputininventory", ownerSet)
	return inst
end

 

I hope this is clear and helpful.

Cheers

I appreciate you taking the time to explain everything to me, and I feel that I actually have a better understanding of how some bits of code function and how they're supposed to be structured, but unfortunately I still can't get this to work. :[

This is a mod that I've been working on for DST. After reading your reply I've added those few pieces of code you mentioned which would allow for things to be kept exclusive to the host, but I'm still getting an error telling me that I'm attempting to access a nil value coming from the fueled component, so I'm guessing that I'm still missing something. Maybe it has something to do with my other two functions I made? 

Thanks for bearing with me, I know that helping me debug this might not be the most enjoyable use of your time, but hopefully it's something small.

Link to comment
Share on other sites

There is no glaring mistake in your functions if that can make you feel better. The issue is how the ListenForEvent works.

So if we go back to my explanation of ListenForEvent, you specify which event you want to listen to, then give a function to execute (the callback) and then the source of the event.

When an entity pushes an event, it is done through a line of code which would look like that

some_entity:PushEvent("some_event", table_with_info_about_the_event)

Let's take the onattackother for instance in the Combat component lua script

self.inst:PushEvent("onattackother", { target = targ, weapon = weapon, projectile = projectile, stimuli = stimuli })

you see that the information passed about the event are:

the target

the weapon

the projectile

the stimuli type

So the structure of a callback function should be like that

local function SomeCallbackFn(inst, data)
	-- inst is the source of the event
	-- data is the table you passed in the PushEvent function

	-- So if we assume this callback is for "onattackother" event you could retrieve
	local target = data.target
	local weapon = data.weapon
	[... etc ...]
end

So your AddUltCharge function is malformed, it should be

local function AddUltCharge(inst, data)
	[... do something ...]
end

Now the trick here is that "inst" is not what you think it is! Because as I said, in the callback, inst represent the source of the event which is the player.

So now you will tell me "that's really nice but then all of these bollocks won't solve my problem and everything I'm doing for 2 days is useless".

Well you could use a wrkaround like the following

local function AddUltCharge(item)
	return function(inst, data)
		-- reminder, inst is the source of the event so here it's the player
		inst.components.talker:Say("Test2.")
	
		if item.components.fueled and item.components.fueled:GetPercent() < 100 then
			item.components.fueled:DoDelta(5)
		end
	end
end

And when you setup the ListenForEvent

inst:ListenForEvent("onattackother",  AddUltCharge(inst), owner)

Does that make sense?

This might not work as is as I did not test it, and wrote it in 2 minutes in the web browser so there might be typo, etc... But this should give you the idea.

Btw you could have noticed that inst was actually the player currently as it was the player who was talking "test2" when the function got executed and not the item (I would presume) :)

PS: And I don't mind helping. Usually it's stuff which can also help other people eventually. If it was boring me I would not be here at the first place :)

Edited by ZupaleX
Link to comment
Share on other sites

22 minutes ago, ZupaleX said:

Usually it's stuff which can also help other people eventually.

Yes, a lot of things here could help someone else a day or another. I bookmark the topic about things i could use later, and when i have a problem with a crash or something, i try to google it to find topics with similars issues.

Link to comment
Share on other sites

23 hours ago, ZupaleX said:

There is no glaring mistake in your functions if that can make you feel better. The issue is how the ListenForEvent works.

So if we go back to my explanation of ListenForEvent, you specify which event you want to listen to, then give a function to execute (the callback) and then the source of the event.

When an entity pushes an event, it is done through a line of code which would look like that


some_entity:PushEvent("some_event", table_with_info_about_the_event)

Let's take the onattackother for instance in the Combat component lua script


self.inst:PushEvent("onattackother", { target = targ, weapon = weapon, projectile = projectile, stimuli = stimuli })

you see that the information passed about the event are:

the target

the weapon

the projectile

the stimuli type

So the structure of a callback function should be like that


local function SomeCallbackFn(inst, data)
	-- inst is the source of the event
	-- data is the table you passed in the PushEvent function

	-- So if we assume this callback is for "onattackother" event you could retrieve
	local target = data.target
	local weapon = data.weapon
	[... etc ...]
end

So your AddUltCharge function is malformed, it should be


local function AddUltCharge(inst, data)
	[... do something ...]
end

Now the trick here is that "inst" is not what you think it is! Because as I said, in the callback, inst represent the source of the event which is the player.

So now you will tell me "that's really nice but then all of these bollocks won't solve my problem and everything I'm doing for 2 days is useless".

Well you could use a wrkaround like the following


local function AddUltCharge(item)
	return function(inst, data)
		-- reminder, inst is the source of the event so here it's the player
		inst.components.talker:Say("Test2.")
	
		if item.components.fueled and item.components.fueled:GetPercent() < 100 then
			item.components.fueled:DoDelta(5)
		end
	end
end

And when you setup the ListenForEvent


inst:ListenForEvent("onattackother",  AddUltCharge(inst), owner)

Does that make sense?

This might not work as is as I did not test it, and wrote it in 2 minutes in the web browser so there might be typo, etc... But this should give you the idea.

Btw you could have noticed that inst was actually the player currently as it was the player who was talking "test2" when the function got executed and not the item (I would presume) :)

PS: And I don't mind helping. Usually it's stuff which can also help other people eventually. If it was boring me I would not be here at the first place :)

Played around with it for a bit, no luck. If anything though, you pointing out that I was missing that important piece of code in my entity initializer actually fixed a another problem that I was running into, which would crash my game whenever I would join or create a server with caves in it. So thank you for that!

I think I'll try messing around a bit with this later.

Link to comment
Share on other sites

1 hour ago, ZupaleX said:

Could you post the error log you get or if you do not have any more crash but it just does nothing, could you post the complete mod?

Thanks

Sure, I'm not crashing anymore so I'll just link my mod. The one I'm linking is a separate version from my official mod that I keep for testing.

Genji.zip

Link to comment
Share on other sites

You did not pay close enough attention to what I was saying :p

Maybe it's my fault though and I was not clear enough.

Here is a copy paste of your dragonblade.lua

function addUltCharge(item)
	return function(inst, data)
	
		if inst.components.fueled and inst.components.fueled:GetPercent() < 100 then
			inst.components.fueled:DoDelta(5)
    
		else
			inst.components.talker:Say("Test failed")
	
		end
	
	end

end

local function ownerSet (inst)

	inst.components.talker:Say("Test.")
	local owner = inst.components.inventoryitem:GetGrandOwner()
	inst:ListenForEvent("onattackother",  addUltCharge(inst), owner)

end

As I was explaining, the "inst" in a callback function is the source pushing the event. The source in your case is "owner" which is the player.

So inside you addUltCharge which is your callback function, inst is the player. The player has no component fueled. This is why you add a layer to this function by adding an argument "item" to pass your dragonblade

You need to operate on the fueled component of "item" and not "inst". If you do not understand why, let me know and we can discuss about it.

In any case, it means you did not blindly copy/pasted what I posted and you actually tried to redo it by yourself which is good :)

Link to comment
Share on other sites

1 hour ago, ZupaleX said:

You did not pay close enough attention to what I was saying :p

Maybe it's my fault though and I was not clear enough.

Here is a copy paste of your dragonblade.lua


function addUltCharge(item)
	return function(inst, data)
	
		if inst.components.fueled and inst.components.fueled:GetPercent() < 100 then
			inst.components.fueled:DoDelta(5)
    
		else
			inst.components.talker:Say("Test failed")
	
		end
	
	end

end

local function ownerSet (inst)

	inst.components.talker:Say("Test.")
	local owner = inst.components.inventoryitem:GetGrandOwner()
	inst:ListenForEvent("onattackother",  addUltCharge(inst), owner)

end

As I was explaining, the "inst" in a callback function is the source pushing the event. The source in your case is "owner" which is the player.

So inside you addUltCharge which is your callback function, inst is the player. The player has no component fueled. This is why you add a layer to this function by adding an argument "item" to pass your dragonblade

You need to operate on the fueled component of "item" and not "inst". If you do not understand why, let me know and we can discuss about it.

In any case, it means you did not blindly copy/pasted what I posted and you actually tried to redo it by yourself which is good :)

OOOOH. I SEE.

The first time I read your response it sort of  went over my head a bit, but after re-reading, it makes more sense to me now.

if inst.components.fueled and inst.components.fueled:GetPercent() < 100 then
			inst.components.fueled:DoDelta(.15)
    
		else
			inst.components.talker:Say("Test failed")
	
		end

I took 'inst' and changed it to 'item', since I passed my weapon to my addUltCharge function as a parameter. (which was re-named to item) So it was like you said, it wasn't doing anything because the 'inst' in this case was the player which didn't have a fueled component, so changing it to 'item' solved it.

Thank you! Now I can finally start moving forward with this mod and work on implementing the less-complex mechanics of the weapon and get it working how I want it to, without much trouble, hopefully!

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