Transferring Weapon Framework into a ModuleScript Template

I am trying to create a system of melee weapons for a future game that I am making. Most of the required components have been done and are working as intended. The initial hierarchy where no problems are experienced is shown as below:

- StarterPack (StarterPack)
    - Sword (Tool)
        - MeleeWeaponScript (Script) --The melee weapon system framework goes here.
        - Handle (UnionOperation)
        - (LocalScript)
        - (Sounds)
        - Values (Folder) --For storing the weapon's basic statistics like Damage and Knockback
        - (Animations)
        - (RemoteEvents)

I am now trying to transfer over the framework in MeleeWeaponScript into a ModuleScript (which I am not experienced in using yet). The ModuleScript will handle the basics of all melee weapons in the game. This will be in case a future update of the game requires any change in the melee weapon system and that I don’t have to change the MeleeWeaponScript in all of my melee weapons one-by-one. Below is the new hierarchy:

- ServerScriptService (ServerScriptService)
    - StandardMeleeWeapon (ModuleScript) --The melee weapon system framework is now here.
- StarterPack (StarterPack)
    - Sword (Tool)
        - MeleeWeaponScript (Script) --This Script now gets the framework from the ModuleScript through a require() for it to work
        --All components other than MeleeWeaponScript in the Sword Tool are unchanged.
        - Handle (UnionOperation)
        - (LocalScript)
        - (Sounds)
        - Values (Folder) --Still for storing the weapon's basic statistics like Damage and Knockback
        - (Animations)
        - (RemoteEvents)

Before the inclusion of the StandardMeleeWeapon ModuleScript, the structure of MeleeWeaponScript was as follows:

--Variables here to control the sword. Many are initialised at this point as they are relatives with the required instances.

local function cooldown()
    --Handling cooldown of the sword after it has been unsheathed or swung to prevent overly rapid use of sword.
end

local function onEquip()
    --Handling unsheathing.
end

local function onUnequip()
    --Handling sheathing.
end

tool.Charge.OnServerEvent:Connect(function()
    --Charging up attacks, if the weapon permits charged attacks. (Communication with LocalScript required for listening to player's inputs)
end)

tool.Releasing.OnServerEvent:Connect(function()
    --Releasing charged attacks, if the weapon permits charged attacks. (Communication with LocalScript required for listening to player's inputs)
end)

local function onActivate()
    --For if the weapon does not permit charged attacks.
end

local function hit(otherPart)
    --Handling for when the sword hits something, be it another player or an obstacle.
end

tool.Equipped:Connect(onEquip)
tool.Unequipped:Connect(onUnequip)
tool.Activated:Connect(onActivate)
tool.Handle.Touched:Connect(hit)

With the StandardMeleeWeapon ModuleScript added, its structure is as follows, being highly similar to MeleeWeaponScript before the inclusion of the ModuleScript:

local standardMeleeWeapon = {}
--Same variables here to control the melee weapon that requires this ModuleScript. Much less variables are initialised as the ModuleScript is not a relative with the required instances.

function standardMeleeWeapon:coolDown()
    --Unaltered from initial MeleeWeaponScript that did not require a ModuleScript.
end

function standardMeleeWeapon:onEquip(TOOL)
    tool = TOOL
    --The variables are now initialised in here, using tool as the reference instance as it is a relative to the required instances.
end

function standardMeleeWeapon:onUnequip()
    --Unaltered from initial MeleeWeaponScript that did not require a ModuleScript.
end

function standardMeleeWeapon:onCharge()
    --Unaltered from initial MeleeWeaponScript that did not require a ModuleScript.
end

function standardMeleeWeapon:onRelease()
    --Unaltered from initial MeleeWeaponScript that did not require a ModuleScript.
end

function standardMeleeWeapon:onActivate()
    --Mostly unaltered, save for a print statement for debugging purposes.
    print(player) --Prints the name of the current player who activated this sword.
end

function standardMeleeWeapon:hit(otherPart)
    --Mostly unaltered, save for a print statement for debugging purposes.
        --After checking if the otherPart is part of a character controlled by a player from another team or if the player using this sword is in a neutral team (against everyone else), this part runs.
        print(tostring(player) .. " has hit " .. tostring(victimPlayer))
        --A further part, same as that in the original MeleeWeaponScript, subjects the victimPlayer's character to a BodyVelocity to simulate knockback from the weapon upon being hit.
end

return standardMeleeWeapon

With the ModuleScript, MeleeWeaponScript is now as follows:

local tool = script.Parent

local function init()
	meleeWeaponModule = require(game:GetService("ServerScriptService").StandardMeleeWeapon)
	meleeWeaponModule:onEquip(tool)
	tool.Charge.OnServerEvent:Connect(meleeWeaponModule.onCharge)
	tool.Release.OnServerEvent:Connect(meleeWeaponModule.onRelease)
	tool.Unequipped:Connect(meleeWeaponModule.onUnequip)
	tool.Activated:Connect(meleeWeaponModule.onActivate)
	tool.Handle.Touched:Connect(function (otherPart)
		meleeWeaponModule:hit(otherPart)
	end)
end

tool.Equipped:Connect(init)

With this implementation, three problems now arise:

  1. When hitting an enemy character with the ModuleScript reliant sword while the enemy is also holding the ModuleScript reliant sword, the enemy is subject to the intended knockback. However, there is a significant chance that the attacking player is subject to a (unintended) great amount of knockback, flinging them several hundred studs away from the scene. The lines are printed upon the initial hit:
Attacker has hit Victim
Attacker has hit Attacker
  1. Anytime a player character (say A in this case) equips the ModuleScript sword, all control everyone else has on their own ModuleScript sword is relinquished to player A. If anyone else tries to use the sword, A (instead of that someone else, as it should be) swings their sword. This ‘remote control’ seems to stop if the they unequip and re-equip their sword, with them now gaining control of their sword but everyone else, including A, losing control over their own ModuleScript sword.

  2. If a player character uneqiups and re-equips again, activating the sword calls print(player) in the ModuleScript’s onActivate() function multiple times in a single click with the weapon out. The exact number the player’s name getting printed being how many times the player character has equipped the weapon throughout their life. This number is reset to zero upon their death when the character gets a new copy of the sword from the StarterPack upon starting a new life.

Problems 1 and 2 only seem to happen if both the attacking player and victim player are holding the ModuleScript reliant sword. If either the attacking or victim player is wielding the non-ModuleScript sword or not wielding anything at all, problems 1 and 2 do not happen.

How should I go about solving all three problems? Is my idea of ModuleScripts to achieve such a purpose flawed?

Thank you very much for reading and possibly assisting me on the matter.

3 Likes

You can fix these problems using OOP.

OOP allows us to create objects using YourClass.new(). A class is like a “BluePrint” and it tells us what thing it is. The ROBLOX API has a lot of OOP such as Instance, Vector3, CFrame and so on. Each class has a constructor that constructs a seperate object from any other Object.

If you implement OOP you can then simply do Gun.new() and when calling it again. They create a seperate Object.

You can use my Class creation module to create a Class. I suggest you read this first

1 Like

Thanks for the suggestion to use OOP for this situation.

I think why problems 1 and 2 happened was because both of the swords held by the two characters referred to the same ModuleScript instance. The ModuleScript, containing most of the functionality of all melee weapons in the game, could only be utilised by one melee weapon, ironically enough. Adding a new() function into the ModuleScript for all melee weapons to refer to fixed these problems.

I also found out that problem 3 happened because every time init() was called, a new tool.Activated connection was made and the previous connection(s) remained. This could have led to a lot of optimisation issues down the road if it wasn’t fixed. tool.Handle.Touched would have been called thousands of times in a second if the sword were to come into contact with a detailed enough structure.

My working implementation for MeleeWeaponScript is now as follows:

meleeWeaponModule = require(game:GetService("ServerScriptService").StandardMeleeWeapon)
local tool = script.Parent
local weapon

local equippedEvent

local function init()
	weapon = meleeWeaponModule:new(tool)
	local chargeServerEvent
	local releaseServerEvent
	local activatedEvent
	local hitEvent
	local unequippedEvent
	chargeServerEvent = tool.Charge.OnServerEvent:Connect(function ()
		weapon:onCharge()
	end)
	releaseServerEvent = tool.Release.OnServerEvent:Connect(function ()
		weapon:onRelease()
	end)
	activatedEvent = tool.Activated:Connect(function()
		weapon:onActivate()
	end)
	hitEvent = tool.Handle.Touched:Connect(function (otherPart)
		weapon:hit(otherPart)
	end)
	unequippedEvent = tool.Unequipped:Connect(function()
		if weapon then
			weapon:onUnequip()
		end
		--Clear up unneeded memory of connections until they are initialised again.
		chargeServerEvent:Disconnect()
		releaseServerEvent:Disconnect()
		activatedEvent:Disconnect()
		hitEvent:Disconnect()
		unequippedEvent:Disconnect()
		weapon = nil
	end)
	weapon:onEquip() -- This is put last because it takes the longest to happen due to a wait(n) being inside.
end

equippedEvent = tool.Equipped:Connect(init)

I’ve also added in a lot of 'self’s into the StandardMeleeWeapon ModuleScript. A lot more than I’d like to, in fact.

3 Likes