Jump to content

Recommended Posts

[disregard this post-- brain error occured]

 

@ScallyCat, @Kzisor,

Here was the code provided(I have not changed this):


local function OnEquip(inst, owner, fname_override)    inst.components.equippable:_OnEquip(inst, owner, fname_override)      if owner:HasTag("monster") then        owner:RemoveTag("monster")    endend   local function onUnequip(inst, owner)    inst.components.equippable:_OnUnequip(inst, owner)      owner:AddTag("monster")           if inst.components.leader then        inst.components.leader:RemoveFollowersByTag("pig")    endend   local function TestHat(inst, owner)       inst.components.equippable._OnEquip = inst.compoennts.equippable.onequipfn    inst.components.equippable:SetOnEquip(OnEquip)    inst.components.equippable._OnUnequip = inst.components.equippable.onunequipfn    inst.components.equippable:SetOnUnequip(onUnequip)   end  

Note that when you call the OnEquip and OnUnequip methods, you are using the colon( : ) syntax.

inst.components.equippable:_OnEquip(inst, owner, fname_override)

Colon syntax is used with methods when you want to pass self as the first parameter. It's syntactic sugar(i.e. "it means the exact same thing") as this:
 

inst.components.equippable._OnEquip(inst.components.equippable, inst, owner, fname_override)

Note that that second form uses a period ( . ) instead of the colon.

Knowing this, you need to allow for self to be the first parameter in your replacement functions:
 

local function OnEquip(self, inst, owner, fname_override)    inst.components.equippable:_OnEquip(inst, owner, fname_override)    if owner:HasTag("monster") then        owner:RemoveTag("monster")    endendlocal function onUnequip(self inst, owner)    inst.components.equippable:_OnUnequip(inst, owner)    owner:AddTag("monster")    if inst.components.leader then        inst.components.leader:RemoveFollowersByTag("pig")    endend

What was happening before was that since the "self" parameter was not defined in your replacement function, all of the arguments were being shifted over 1 parameter similar to like this:
 

function somefunction(inst, owner, fnameoverride)    ..blah blah..endsomefunction(self, inst, owner, fname_override)

As you can see, self is getting passed to the inst parameter, inst is getting passed to the owner parameter, and owner is getting passed to the fname_override parameter.  The fname_override argument is getting dropped, because there are no more parameters to assign it to.

 

Hence the game receiving a table when it expected a string.

Edited by Corrosive

@Corrosive, I applaud your efforts of trying to debug this, you are actually incorrect in your solution to the problem. However, you did bring up a great point about the colon and period syntax and as such I've looked back over the code in the equippable component and have determined the required change to make it work.

 

local function OnEquip(inst, owner, fname_override)    inst.components.equippable._OnEquip(inst, owner, fname_override)       if owner:HasTag("monster") then        owner:RemoveTag("monster")    endend    local function onUnequip(inst, owner)    inst.components.equippable._OnUnequip(inst, owner)       owner:AddTag("monster")            if inst.components.leader then        inst.components.leader:RemoveFollowersByTag("pig")    endend    local function TestHat(inst, owner)        inst.components.equippable._OnEquip = inst.components.equippable.onequipfn    inst.components.equippable:SetOnEquip(OnEquip)    inst.components.equippable._OnUnequip = inst.components.equippable.onunequipfn    inst.components.equippable:SetOnUnequip(onUnequip)    end  

 

By simply changing it from a colon to a period will ensure that it gets pushed appropriately. The reason is because we are dealing with a local function inside the original code therefore our code need not pass the self parameter.

By simply changing it from a colon to a period will ensure that it gets pushed appropriately. The reason is because we are dealing with a local function inside the original code therefore our code need not pass the self parameter.

 

You're right-- although the reason is more that Klei treats methods with setters more like functions rather than methods.  Which makes more sense conceptually.  And also presumably since you can't have the same component on an entity more than once, inst will always be specific to the correct component.

function Equippable:Equip(owner, slot)    self.isequipped = true        if self.inst.components.burnable then        self.inst.components.burnable:StopSmoldering()    end        if self.onequipfn then        self.onequipfn(self.inst, owner)    end    self.inst:PushEvent("equipped", {owner=owner, slot=slot})end

If equippable.lua called the stored function using the colon syntax there, you'd need to deal with a self parameter on overrides.

Edited by Corrosive

@Corrosive, I think you are still incorrect with your explanation. Functions and methods are the exact same thing in programming terminology. In lua a function is simply another variable, whether it be stored as a global, in a table, as a local or not at all it's still just another variable.

 

The calling method plays no part in whether or not the function will handle the self parameter; it simply dictates if we want to auto forward the self parameter to the function. It's up to the original function itself to handle the self parameter in order to process the data properly.

 

Example:

This line of initialization code cannot be called by using a colon syntax because it originally is not handling the self parameter.

local function onequip(inst, owner, fname_override)

 

It would not matter whether the equippable.lua component was calling it using colon syntax or if our code was calling it using the colon syntax. The function itself is not handling the self parameter therefore in both instances you would receive the error. If the equippable.lua component was originally calling it via the colon syntax or if the function was a global function (Equippable:OnEquip) of the component my original code would have worked correctly.

 

Also to clarify, we should not actually need to handle the self parameter in any case not even in our override function. The self parameter should be handled automatically when you make a function which is accessible globally from a class or component, e.g., function Equippable:SetOnEquip(fn). This is the reason why you can access self outside of the component file when overriding these functions; because it's automatically handled by the compiler.

@Kzisor,
 
I'm aware that functions are first-class values in Lua.  However, the Lua reference manual explicitly defines methods in Lua as using the colon syntax:
 



The colon syntax is used for defining methods, that is, functions that have an implicit extra parameter self. Thus, the statement
function t.a.b.c:f (params) body end

is syntactic sugar for
t.a.b.c.f = function (self, params) body end

 

Anyhoo--

 

This is the reason why you can access self outside of the component file when overriding these functions; because it's automatically handled by the compiler.

 
Not... quite... the interpreter isn't giving you access to anything you don't already have access to.  It's just passing whatever table is the parent of the method.  Ex:

> tab_a = { val = "Table A" }> function tab_a:fn() print(self.val) end> tab_b = { val = "Table B" }> function tab_b:fn() print(self.val) end>> tab_a:fn()Table A> tab_b:fn()Table B> tab_a.fn = tab_b.fn>> tab_a:fn()Table A> tab_b:fn()Table B

Also to clarify, we should not actually need to handle the self parameter in any case not even in our override function.

 
If the stored onequipfn was called via colon syntax, e.g.:

self:onequipfn(self.inst, owner)

...it would send self as the first parameter to your stored function, in which case you would absolutely need to have a parameter for it.  It doesn't matter if you are using the self argument or not, it still gets passed. Since you are declaring the function and assigning it via a setter, you aren't able to use colon syntax.  You could alternatively bypass the setter and use colon syntax:
 

function Equippable:onequipfn(inst, owner)

 
or you could even create a dummy table and use it to allow you to add the self parameter via colon syntax:
 

t = {}function t:fn(inst, owner)Equippable:SetOnEquip(t.fn)

All this is moot, though, since that's not how Klei rolls.

If the stored onequipfn was called via colon syntax, e.g.:

self:onequipfn(self.inst, owner)

...it would send self as the first parameter to your stored function, in which case you would absolutely need to have a parameter for it.  It doesn't matter if you are using the self argument or not, it still gets passed. Since you are declaring the function and assigning it via a setter, you aren't able to use colon syntax.

 

Actually this is inaccurate information.

 

Example:

	inst._GiveItem = inst.GiveItem	function inst:GiveItem( inst, slot, screen_src_pos, skipsound )		if not self:CanTakeItemInSlot(inst, slot) then    		return false    	end		return self:_GiveItem(inst, slot, screen_src_pos, skipsound )	end 

 

I understand that you know Lua, but your explanation of it is inaccurate. Using a setter has nothing to do with whether or not you can use the colon syntax, it's completely up to how the original code was initialized. In the working example it's initialized already containing the colon syntax, therefore it's automatically handling the self parameter.

 

We could even override that function as:

inst._GiveItem = inst.GiveItem	function inst.GiveItem( self, inst, slot, screen_src_pos, skipsound )		if not self:CanTakeItemInSlot(inst, slot) then    		return false    	end		return self:_GiveItem( inst, slot, screen_src_pos, skipsound )	end  

 

This code would still function correctly. As would this code:

inst._GiveItem = inst.GiveItem	function inst.GiveItem( self, inst, slot, screen_src_pos, skipsound )		if not self:CanTakeItemInSlot(inst, slot) then    		return false    	end		return self._GiveItem( self, inst, slot, screen_src_pos, skipsound )	end  

 

All that using a setter accomplishes is it allows you to set a function to be called at a later date. The initialization of the setter calling method dictates how we propagate the actual setter function value.

 

All this is not rendered moot because of Klei, it's all very relevant because Klei uses both types of calling conventions in their code.

 

 

Either way, the problem should be fixed.

@Kzisor,

 

I think you misunderstood what I meant.

 

I did not mean that you can't achieve a working effect by using a setter.  I meant that you have nowhere to put a colon when using a setter.

 

 

We could even override that function as: ...

 

...I know, that was literally how I erroneously recommended to do it in the first place!

I did not mean that you can't achieve a working effect by using a setter.  I meant that you have nowhere to put a colon when using a setter.

 

 

As I said before this is incorrect information. In a general sense you can in fact use the colon syntax with a setter, however, in this particular instance you are correct we cannot. It's better to be precise with what you're talking about so that you do not misinform people of how Lua actually works.

 

If you do not believe that we cannot use a colon with setters, simply look at the examples I posted above, all three of them are working examples.

As I said before this is incorrect information. In a general sense you can in fact use the colon syntax with a setter, however, in this particular instance you are correct we cannot. It's better to be precise with what you're talking about so that you do not misinform people of how Lua actually works.

 

If you do not believe that we cannot use a colon with setters, simply look at the examples I posted above, all three of them are working examples.

  

@Kzisor,

 

I think you misunderstood what I meant.

 

I did not mean that you can't achieve a working effect by using a setter.  I meant that you have nowhere to put a colon when using a setter.

 

 

...I know, that was literally how I erroneously recommended to do it in the first place!

Wow you guys make my head spin! Seriously though, thanks so much to the both of you :D Everything seems to be working perfectly now! This was definitely a neat little learning experience :) I'll be sure to post my mod here on the forums upon it's completion. (Also nice Mir(?) picture)

I've been phrasing my arguments to apply to our single-case scenario.

 

If you do not believe that we cannot use a colon with setters, simply look at the examples I posted above, all three of them are working examples.

 

or you could even create a dummy table and use it to allow you to add the self parameter via colon syntax:

t = {}function t:fn(inst, owner)Equippable:SetOnEquip(t.fn)

 

 

somehow I managed to leave out the word end on line 2 there, but you get the point.

somehow I managed to leave out the word end on line 2 there, but you get the point.

 

That code would not work the way you vision it to work. The problem was not with the code we are setting in our TestHat function but, with the code we are using to call the original code in our OnEquip function which in this instance is inst.components.equippable._OnEquip.

Edited by Kzisor

For anyone who comes across this and wants to understand what's going on, here's a simplified (but long) summary.

 

 

One argument is about the places you can or can't use colons.

Say we have this module:

local t = {}function t:some_fn()    -- self is automatically available due to the colon    print(self)end

You can do this:

t.some_fn2 = t.some_fn

You can not do any of these:

t:some_fn2 = t:some_fn2t.some_fn2 = t:some_fn2t:some_fn2 = t.some_fn2 

 

 

Another point is about the fundamental use of colons.

Now we have this module (same from above):

local t = {}function t:some_fn()    -- self "should" refer to (an instance of) t    -- but in practice, anything can be passed as the first parameter    -- and it will be used as "self"    print(self)end

These will give you the same results:

t:some_fn() -- prints table t's idt.some_fn(t) -- prints table t's id

But this will be different:

local new_t = {}t.some_fn(new_t) -- prints table new_t's id

Note that using a colon to call a function automatically passes the parent table as the first parameter.

t:some_fn() -- passes "t" as the first parameter

More on this here.

 

 

And finally, another point is about modifying a module to call your function how you want (passing the variables you need), instead of how it was previously designed to be called.

You have this module:

local t = {}-- Ideally, this should be set outside the module, but we'll keep it here for simplicityt.custom_fn = function()    print("fn 2")endfunction t:some_fn()    -- again, self is available due to the colon    return self.custom_fnend

You can do this:

-- We want a number to be passed in custom_fn-- So first we must be able to receive itt.custom_fn = function(num)    print("Our number is:", num)end-- Now we want some_fn to pass the number-- First preserve the old some_fnlocal old_some_fn = t.some_fn-- Then replace some_fn with a new functionfunction t:some_fn()    -- Call the custom_fn, passing a number    self.custom_fn(123)    -- We must pass self explicitly because old_some_fn    -- doesn't have a parent so a colon can not be used.    -- Also note that the old some_fn already calls custom_fn    -- so it will be called twice.    return old_some_fn(self)end 

Which means you can also do this:

t.custom_fn_2 = function(num)    print("Our number is:", num)end-- Preserve the old some_fnlocal old_some_fn = t.some_fnfunction t:some_fn()    -- Call our new function, passing a number    self.custom_fn_2(123)        return old_some_fn(self)end

 

Edited by Blueberrys

Thank you Blueberrys, you understood what my late night ramblings meant to convey :)

 

@Kzisor,

 

Kz man, I swear you are going to make me cry.  Every response you seem to slightly shift the focus.  My original statement was that if the code calling the stored onequipfn, i.e.

    if self.onequipfn then        self.onequipfn(self.inst, owner)    end

...were actually written thus:

    if self.onequipfn then        self:onequipfn(self.inst, owner)    end

The function that you create which you are storing in the onequipfn field would need to account for self being passed to the first parameter.

 

I don't care how you create it, colon syntax or not-- it would need to account for that extra parameter or else your arguments are going to be shifted over by 1.

Thank you Blueberrys, you understood what my late night ramblings meant to convey :-)

 

@Kzisor,

 

Kz man, I swear you are going to make me cry.  Every response you seem to slightly shift the focus.  My original statement was that if the code calling the stored onequipfn, i.e.

    if self.onequipfn then        self.onequipfn(self.inst, owner)    end

...were actually written thus:

    if self.onequipfn then        self:onequipfn(self.inst, owner)    end

The function that you create which you are storing in the onequipfn field would need to account for self being passed to the first parameter.

 

I don't care how you create it, colon syntax or not-- it would need to account for that extra parameter or else your arguments are going to be shifted over by 1.

 

The problem is what you're pointing out has zero (0) to do with the actual problem which was occurring. That is what I was trying to point out and is why I think we have a misunderstanding.

 

The problem was in this line right here:

inst.components.equippable:_OnEquip(inst, owner, fname_override)

 

Because I was automatically sending the self parameter with the colon syntax to the old function is where we ran into issues. The old function was never meant to handle the self parameter. What you were saying is that I should account for that in my function by adding self to our onequip function, which would actually cause more errors because the original code never sends us the self parameter.

 

Corrosives Original Code:

local function OnEquip(self, inst, owner, fname_override)    inst.components.equippable:_OnEquip(inst, owner, fname_override)     if owner:HasTag("monster") then        owner:RemoveTag("monster")    endend local function onUnequip(self inst, owner)    inst.components.equippable:_OnUnequip(inst, owner)     owner:AddTag("monster")     if inst.components.leader then        inst.components.leader:RemoveFollowersByTag("pig")    endend

 

If we were to used your original code, we would have still had the issue because all you did was add self as a parameter to our local function. Our local function overrides the old function, but still calls it by simply moving it to a different variable (_OnEquip).

 

I'm okay with actually chalking it up to a misunderstanding because I think that is clearly what happened.

@Kzisor,

 

I think you missed first two words of my first response in which I stated "you're right."

 

 

I was trying (poorly) to explain what had gone wrong in my mind*, not your explanation.

 

 

*Edit: Which was that equippable doesn't call the stored custom function using colon syntax.

Edited by Corrosive

@Corrosive, yeah see, a complete misunderstanding. Sometimes communication is difficult when talking about code so it's understandable that we would definitely have a misunderstanding about it. It's no skin off my bones, I just hope that if other people read this they will understand what is actually happening and that it helps them to become a better modder. After all that is what we both want. 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
  • Create New...