ModuleScript based MeleeSystem is broken

I have created a melee system based on one ModuleScript placed in ReplicatedStorage.

The problem is requiring the ModuleScript in multiple scripts for tools, they all act as one.
It’s like this:
Player A joins and swings knife
Player B joins and suddenly player A’s knife stops working
Player B equips knife, player A swings, but it causes player B to swing, and player B swings player B’s knife.

It seems to follow the newest created tool.

I’ve tried:

  • adding module.__index = module → no result
  • switching to OOP with .new and :function’s → calling self:function() doesn’t work, calling module:function() also doesn’t

I would like to keep the code in a ModuleScript so it’s easier to update tools.

Script that’s requiring the ModuleScript

local config = {
	["Blade"] = script.Parent.Handle,
	["Tool"] = script.Parent,
	["Range"] = 3,
	["Damage"] = {5, 15},
	["Animations"] = {
		["Swing"] = 14438327862,
		["Idle"] = 0
	},
	["Debounce"] = 1,
	["Pollingrate"] = 0.01
}

local meleemod = require(game.ReplicatedStorage.ObjectScripts.MeleeSystem)

meleemod.loadConfig(config)
meleemod.start()

ModuleScript

local module = {}

local config = {
	["Blade"] = nil,
	["Tool"] = nil,
	["Range"] = 1,
	["Damage"] = {15, 25},
	["Animations"] = {
		["Swing"] = 0,
		["Idle"] = 0
	},
	["Debounce"] = 1,
	["Pollingrate"] = 0.005
}

local db = false
local hits = {}

-- Raycast off front of blade
function module.raycastFromBlade()
	local origin = config.Blade.CFrame.Position
	local direction = ((config.Blade.CFrame.LookVector)*config.Range)
	
	local result = game.Workspace:Raycast(origin, direction)
	
	return result
end

function module.damage(target)
	target.Parent.Humanoid:TakeDamage(math.random(config.Damage[1], config.Damage[2]))
end

function module.isDamagable(target)
	if target.Parent:FindFirstChild("Humanoid") then
		return true
	end
	return false
end

function module.loadConfig(cfg)
	config = cfg
end

function playAnimation(id, priority)
	if id == nil or id == 0 then
		return
	end
	local hum = config.Tool.Parent:FindFirstChild("Humanoid")
	if hum then
		local animator = hum:WaitForChild("Animator")
		if animator then
			local anim = Instance.new("Animation")
			anim.AnimationId = "rbxassetid://"..tostring(id)

			local track = animator:LoadAnimation(anim)
			track.Priority = priority
			track:Play()
			return track
		end
	end
end

function module.start()
	local idleTrack
	config.Tool.Equipped:Connect(function()
		idleTrack = playAnimation(config.Animations.Idle, Enum.AnimationPriority.Action)
		for i, v in config.Tool:GetChildren() do
			if v:IsA("Part") or v:IsA("UnionOperation") or v:IsA("MeshPart") then
				v.CanCollide = false
			end
		end
	end)
	config.Tool.Unequipped:Connect(function()
		if idleTrack then
			idleTrack:Stop()
		end
		for i, v in config.Tool:GetChildren() do
			if v:IsA("Part") or v:IsA("UnionOperation") or v:IsA("MeshPart") then
				v.CanColli1de = true
			end
		end
	end)
	config.Tool.Activated:Connect(function()
		if not db then
			db = true
			pcall(function()
				playAnimation(config.Animations.Swing, Enum.AnimationPriority.Action2)
			end)
			local s = config.Debounce / config.Pollingrate
			local c = 0
			while c < s do
				task.wait(config.Pollingrate)
				c += 1
				local result = module.raycastFromBlade()
				if result ~= nil then
					if table.find(hits, result.Instance.Parent, 1) then
						continue
					else
						table.insert(hits, result.Instance.Parent)
						if module.isDamagable(result.Instance) then
							module.damage(result.Instance)
						end
					end
				end
			end
			
			db = false
			hits = {}
		end
	end)
end

return module

OOP attempt

local module = {}

module.__index = module


function module.new(o)
	o = o or {
		["Config"] = {
			["Blade"] = nil,
			["Tool"] = nil,
			["Range"] = 1,
			["Damage"] = {15, 25},
			["Animations"] = {
				["Swing"] = 0,
				["Idle"] = 0
			},
			["Debounce"] = 1,
			["Pollingrate"] = 0.005
		},
		["Debounce"] = false,
		["Hits"] = {}
	}
	setmetatable(o, module)
	return o
end

function module:loadConfig(cfg)
	self.Config = cfg
end


function module:raycastOffBlade()
	local origin = self.Config.Blade.CFrame.Position
	local direction = ((self.Config.Blade.CFrame.LookVector)*self.Config.Range)

	local result = game.Workspace:Raycast(origin, direction)

	return result
end

function module:damage(target)
	target.Parent.Humanoid:TakeDamage(math.random(self.Config.Damage[1], self.Config.Damage[2]))
end

function module:isDamagable(target)
	if target.Parent:FindFirstChild("Humanoid") then
		return true
	end
	return false
end

function playAnimation(config, id, priority)
	if id == nil or id == 0 then
		return
	end
	local hum = config.Tool.Parent:FindFirstChild("Humanoid")
	if hum then
		local animator = hum:WaitForChild("Animator")
		if animator then
			local anim = Instance.new("Animation")
			anim.AnimationId = "rbxassetid://"..tostring(id)

			local track = animator:LoadAnimation(anim)
			track.Priority = priority
			track:Play()
			return track
		end
	end
end

function module:start()
	local idleTrack
	self.Config.Tool.Equipped:Connect(function()
		idleTrack = playAnimation(self.Config, self.Config.Animations.Idle, Enum.AnimationPriority.Action)
		for i, v in self.Config.Tool:GetChildren() do
			--[[if v:IsA("Part") or v:IsA("UnionOperation") or v:IsA("MeshPart") then
				v.CanCollide = false
			end]]
		end
	end)
	self.Config.Tool.Unequipped:Connect(function()
		if idleTrack then
			idleTrack:Stop()
		end
		for i, v in self.Config.Tool:GetChildren() do
			--[[if v:IsA("Part") or v:IsA("UnionOperation") or v:IsA("MeshPart") then
				v.CanColli1de = true
			end]]
		end
	end)
	self.Config.Tool.Activated:Connect(function()
		if not self.Debounce then
			self.Debounce = true
			pcall(function()
				playAnimation(self.Config, self.Config.Animations.Swing, Enum.AnimationPriority.Action2)
			end)
			local s = self.Config.Debounce / self.Config.Pollingrate
			local c = 0
			while c < s do
				task.wait(self.Config.Pollingrate)
				c += 1
				local result = self:raycastFromBlade()
				if result ~= nil then
					if table.find(self.Hits, result.Instance.Parent, 1) then
						continue
					else
						table.insert(self.Hits, result.Instance.Parent)
						if self:isDamagable(result.Instance) then
							self:damage(result.Instance)
						end
					end
				end
			end

			self.Debounce = false
			self.Hits = {}
		end
	end)
end

return module

I don’t know if my OOP is wrong but help is appreciated.

So yes, you are correct that an OOP approach would likely solve this issue.

Well I don’t see any problems within the module, but it would help to see the actual script to see if there are any errors over there.

Here’s the require script for the OOP code.

local config = {
	["Blade"] = script.Parent.Caster,
	["Tool"] = script.Parent,
	["Range"] = 3,
	["Damage"] = {15, 25},
	["Animations"] = {
		["Swing"] = 18615646166,
		["Idle"] = 0
	},
	["Debounce"] = 0.5,
	["Pollingrate"] = 0.01
}

local meleemod = require(game.ReplicatedStorage.ObjectScripts.MeleeSystem)

local melee = meleemod.new()

melee:loadConfig(config)
melee:start()

Console:

  19:27:57.604  ReplicatedStorage.ObjectScripts.MeleeSystem:102: attempt to call missing method 'raycastFromBlade' of table  -  Studio
  19:27:57.605  Stack Begin  -  Studio
  19:27:57.605  Script 'ReplicatedStorage.ObjectScripts.MeleeSystem', Line 102  -  Studio
  19:27:57.605  Stack End  -  Studio

Line 102 in the OOP version is calling self:raycastFromBlade()

Alright so I went through some old OOP code to try and recreate your error, and I think I found it.

Since your “o” variable is the same as the parameter “o”, I believe your confusing the computer into thinking that you are returning the parameter which is also a table that doesn’t have the raycastOffBlade method. That’s why you’re getting your issue.

Just change either the parameter name or the variable name and you should be fine.

unm … I think you might have miss spelled the function name … there is no function called raycastFromBlade but there is a function called module:raycastOffBlade()

I didn’t realize I changed the function name when I switched it to OOP. That solved the issue. Thank you!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.