[UNSOLVED] OOP, trying to get self from Sword:Attack()

Hey,

I am making a sword class and I am having issues trying to get self._settings and using it inside a method.

Here’s the entire class:

--!strict
local Sword = {}
Sword.__index = Sword

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Source = ReplicatedStorage.Source

local RaycastHitbox = require(Source.RaycastHitboxV4)
local Trove = require(Source.Packages.Trove)
local ContextActionUtility  = require(Source.ContextActionUtility)

local LocalPlayer = Players.LocalPlayer
local ATTACK_IMG = "rbxassetid://5743593320"

type tab = { 
	_damage: number,
	_yield: number,
	_attacktime: number,
}

type self = {
	_sword: Tool,
	_animations: Folder,
	_settings: tab,
	_connections: Trove.ClassType,
	_hitbox: RaycastHitbox.ClassType,
	_loadedAnimations: any,
	_loadedSounds: any

}

export type Sword = typeof(
	setmetatable({}, {__index = Sword})
)

function Sword.new(sword: Tool, setting: tab): Sword
	local self = setmetatable({} :: self, Sword})	
	local Animator = LocalPlayer.Character.Humanoid.Animator
	
	self._sword = sword
	self._animations = sword:FindFirstChild("Animations"):: Folder
	self._settings = setting
	self._connections = Trove.new()
	self._hitbox = RaycastHitbox.new(self._sword:FindFirstChild("Handle"))
	
	self._loadedAnimations = {}
	self._loadedSounds = {}
	
	self._loadedAnimations.Equip = Animator:LoadAnimation(self._sword
		:FindFirstChild("Animations")
		:FindFirstChild("Equip"))
	
	self._loadedAnimations.Idle = Animator:LoadAnimation(self._sword
		:FindFirstChild("Animations")
		:FindFirstChild("Idle"))
	
	self._loadedSounds.Equip = self._sword:FindFirstChild("Handle"):FindFirstChild("Equip"):: Sound
	
	self._connections:Connect(self._sword.Equipped, function(mouse: Mouse) 
		self:Equip()
		self:Bind(self._sword.Name, Enum.UserInputType.MouseButton1)
	end)

	self._connections:Connect(self._sword.Unequipped, function(mouse: Mouse) 
		self:Unequip()
		self:Unbind(self._sword.Name)
	end)
	
	return self
end

-- methods

function Sword:Equip()
	self._loadedSounds.Equip:Play()
	
	self._loadedAnimations.Equip:Play()
	self._loadedAnimations.Idle:Play()
	
	print("Equipped", self._sword.Name)
end

function Sword:Unequip()
	print("Unequipped",self._sword.Name)
	
	if self._loadedAnimations.Idle.IsPlaying then
		self._loadedAnimations.Idle:Stop()
	end
	
	if self._loadedAnimations.Equip.IsPlaying then
		self._loadedAnimations.Equip:Stop()
	end
end

function Sword:Attack(InputState) 
	if InputState ~= Enum.UserInputState.Begin then
		return
	end
	
	print(self._settings) -- prints nil, I need to get the self table
	print("Attack")
end

function Sword:Bind(ActionName: string, Input)
	ContextActionUtility:BindAction(ActionName,self.Attack,true,Input)
	
	ContextActionUtility:SetImage(ActionName,ATTACK_IMG)
end

function Sword:Unbind(ActionName: string)
	ContextActionUtility:UnbindAction(ActionName)
end

function Sword:Destroy()
	self._connections:Destroy()
end

return Sword
4 Likes

For me it works. I hope you use Attack on an already created sword via .new and not via a module. Example:

local module = require(...)
local sword = module.new(Tool, { _damage=5,_yield=4,_attacktime=2})
--must be
sword:Attack(Enum.UserInputState.Begin)
--must not be
module:Attack(Enum.UserInputState.Begin)

image

3 Likes

Try setmetatable({}, {__index = Sword}) to remove module.new().new()

You should be doing self:Bind instead of Sword:Bind

You could also do self:Equip

In your module, you need to call it on the sword instance, not the module.

3 Likes

The thing is my sword class is supposed to make it easy for me to create swords on the go.

-- not meant to be neat / for testing purps
local Sword = require(game:GetService("ReplicatedStorage").Source.Classes.Sword)

local Tool = game:GetService("ReplicatedStorage").Instances.Weapons["Overlord Scythe"]
local Settings = { -- sword params
	30, -- damage
	1, -- yield
	1, -- attacktime
}

Sword.new(Tool,Settings)
-- I don't really need to use Sword.new(Tool,Settings):Attack()
-- because the sword class should do it automatically for me.

Tool.Parent = game.Players.LocalPlayer.Backpack

Your solution didn’t really do anything but optimize my class a little more (unless I did it wrong)

Here’s the new class:

--!strict
local Sword = {}
Sword.__index = Sword

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Source = ReplicatedStorage.Source

local RaycastHitbox = require(Source.RaycastHitboxV4)
local Trove = require(Source.Packages.Trove)
local ContextActionUtility  = require(Source.ContextActionUtility)

local LocalPlayer = Players.LocalPlayer
local ATTACK_IMG = "rbxassetid://5743593320"

type tab = { 
	_damage: number,
	_yield: number,
	_attacktime: number,
}

type self = {
	_sword: Tool,
	_animations: Folder,
	_settings: tab,
	_connections: Trove.ClassType,
	_hitbox: RaycastHitbox.ClassType,
	_loadedAnimations: any,
	_loadedSounds: any

}

export type Sword = typeof(
	setmetatable({}, {__index = Sword})
)

function Sword.new(sword: Tool, setting: tab): Sword
	local self = setmetatable({}, {__index = Sword})	
	local Animator = LocalPlayer.Character.Humanoid.Animator
	
	self._sword = sword
	self._animations = sword:FindFirstChild("Animations"):: Folder
	self._settings = setting
	self._connections = Trove.new()
	self._hitbox = RaycastHitbox.new(self._sword:FindFirstChild("Handle"))
	
	self._loadedAnimations = {}
	self._loadedSounds = {}
	
	self._loadedAnimations.Equip = Animator:LoadAnimation(self._sword
		:FindFirstChild("Animations")
		:FindFirstChild("Equip"))
	
	self._loadedAnimations.Idle = Animator:LoadAnimation(self._sword
		:FindFirstChild("Animations")
		:FindFirstChild("Idle"))
	
	self._loadedSounds.Equip = self._sword:FindFirstChild("Handle"):FindFirstChild("Equip"):: Sound
	
	self._connections:Connect(self._sword.Equipped, function(mouse: Mouse) 
		self:Equip()
		self:Bind(self._sword.Name, Enum.UserInputType.MouseButton1)
	end)

	self._connections:Connect(self._sword.Unequipped, function(mouse: Mouse) 
		self:Unequip()
		self:Unbind(self._sword.Name)
	end)
	
	return self
end

-- methods

function Sword:Equip()
	self._loadedSounds.Equip:Play()
	
	self._loadedAnimations.Equip:Play()
	self._loadedAnimations.Idle:Play()
	
	print("Equipped", self._sword.Name)
end

function Sword:Unequip()
	print("Unequipped",self._sword.Name)
	
	if self._loadedAnimations.Idle.IsPlaying then
		self._loadedAnimations.Idle:Stop()
	end
	
	if self._loadedAnimations.Equip.IsPlaying then
		self._loadedAnimations.Equip:Stop()
	end
end

function Sword:Attack(InputState) 
	if InputState ~= Enum.UserInputState.Begin then
		return
	end
	
	print(self._settings) -- prints nil, I need to get the self table
	print("Attack")
end

function Sword:Bind(ActionName: string, Input)
	ContextActionUtility:BindAction(ActionName,self.Attack,true,Input)
	
	ContextActionUtility:SetImage(ActionName,ATTACK_IMG)
end

function Sword:Unbind(ActionName: string)
	ContextActionUtility:UnbindAction(ActionName)
end

function Sword:Destroy()
	self._connections:Destroy()
end

return Sword

I figured out ContextActionUtility might be the issue because when I use tool.Activated it works as normal. I’m not sure how to fix this though…

2 Likes

Where do you actually call Attack? I only see it in Bind but do you call that? Also, you don’t pass self to Attack in Bind.

ContextActionUtility:BindAction(ActionName,function()
self:Attack()
end,true,Input)

Here is how you do it

3 Likes

When I use ContextActionUtility:Bind, it binds self.Attack from the module itself. Basically like ContextActionService. I’ve already tried your method of calling it but it doesn’t print out anything for some reason.

self.Attack = function to bind to

self.Attack is already a function, so I don’t need to nest it inside a function

2 Likes

Creating a new table here inside of your constructor for the metatable is actually inefficient for memory just so you know.

If you can, you could try making ContextActionUtility:Bind a vararg function and pass self as an argument for the vararg, then pass the vararg to the bound action.

If that isn’t an option, or if you want an easier method, I think the best course of action would be to create an anonymous function inside of your ContextActionUtility:BindAction call like so:

ContextActionUtility:BindAction(ActionName, function(actionName, inputState)
    self:Attack(inputState)
end, true, input)

While I can’t be for certain since I don’t know what ContextActionUtility contains, I would suspect that if you were to print self in sword:Attack, you would get whatever ActionName is equal to. The reason you don’t get an error here is because if you try to index a string with anything (that isn’t a member of the string library), you will get nil because indexing a string is in essence the same as indexing the string library.

3 Likes

Huh, that makes sense. When I print self it does print the actionname. I’ll try this.

Here’s everything you need to know about contextactionutility: Easy Mobile Buttons - ContextActionUtility

2 Likes

Ah okay looking at that module it already takes a variadic argument for input types and keycodes so I’d stick with the second method (creating a function then calling self:Attack(inputState) in said function)

2 Likes

By the way, what do you mean by this?

local self = setmetatable({}, Sword)

is more memory efficient than

local self = setmetatable({}, {__index = Sword})

because you are creating a second table for every object. This is probably a negligible memory increase but it is one nonetheless

1 Like

Oh,

I was told to replace Sword with that. Thanks :pray:

1 Like

One table for the price of two is quite the bargain (on opposite day).

1 Like

Is self nil only for the attack function or for everything?

For the attack function only. Everything else is accessible with self

Is settings accessible from other functions?

1 Like

Yes, cody might have the solution though. I am still yet to test it (Im on mobile currently)

Doesn’t typeof() return strings. I believe the module script won’t work properly because type Sword is a string.

Key word: Typeof

I don’t think my script uses typeof. it only uses type and type is for typechecking

it’s in line 32 of your original script

1 Like