Sword Smith | Script Swords in Just Minutes! [FREE]

Hello developers,

I’ve seen a lot of posts about people who are struggling with creating swords. I myself have in the past struggled with this as well, I could never find a post or video that could give me a good straight-forward answer. Even after I figured out how to make an effective sword, it’d take me about 30mins-2hrs to create one. Using Sword Smith, it only take 5-10 minutes to create a good sword, every sword only has to be around 20-30 lines of code, depending on your purposes. Sword Smith handles hit detection, animation playing, adding to kills leaderstats, trails, damaging, and more. I would like to note that this is some pretty valuable information in the Basic Usage category if you’re planning on using this.


Documentation

Functions


  • :SetTrail(property, value)…Sets a property of the sword’s trail to the given value
  • :GetOwner()…Returns the player instance which has the tool
  • :Swing(dmg, ignoreList)…Plays swinging animations and damages targets
  • :On[event](callback)…Calls function when event is fired
  • :BindAction(key, debounce, callback)…Creates a special attack

Events

Some of these are pretty self-explanatory

  • Activated…Fired when :Swing function is called
  • Equipped…Fired when sword is equipped
  • Unequipped…Fired when sword is unequipped
  • HumanoidHit…Fired when an “live” target is hit when sword is swinging
  • ObjectHit…Fired when handle is touched
  • SwiningEnded…Fired when swing is officially over
  • TargetKilled…Fired when a target is killed
  • CastPointHit…Fired when a node on the sword is hit during a swing
  • CriticalHit…Fired every swing in sync with the critical interval

Basic Usage

Creating a Basic Sword

I’d like to note that since this post really isn’t a tutorial, I won’t be going super in-depth. First thing’s first, you need to insert the Sword Smith module in ServerStorage.
swordsmithinstorage

Create a sword model from scratch or find one from the tool box, personally I’ll be using The Sword of Azurewrath from the toolbox. Just make sure that when you take a sword from the tool box, you remove all objects beside the handle, and of course anything suspicious that may be inside the handle. Create a new script inside the tool.
azureswordtut

Inside this script we must first get the Sword Smith module from ServerStorage.

local ServerStorage = game:GetService("ServerStorage")
local SwordSmith = require(ServerStorage:WaitForChild("SwordSmith"))

Next we can create the sword instance using the module like so:

local ServerStorage = game:GetService("ServerStorage")
local SwordSmith = require(ServerStorage:WaitForChild("SwordSmith"))

local tool = script.Parent
local debounce = false
local data = {swingAnims={}} --This is a table of information the module uses to make the sword work
--In the swing anims table insert the IDs of swing animation(s) you would like to use in the sword

local sword = SwordSmith.new(tool, data)

Next we can create the swinging

local ServerStorage = game:GetService("ServerStorage")
local SwordSmith = require(ServerStorage:WaitForChild("SwordSmith"))

local tool = script.Parent
local debounce = false
local data = {swingAnims={}}

local sword = SwordSmith.new(tool, data)

tool.Activated:Connect(function()
	if debounce then
		return
	end
	
	debounce = true
	sword:Swing()
end);

sword:OnSwingEnded(function()
	--You can of course add extra delay if you have a short swing animation
	debounce = false
end)

This should be your final result if you followed these steps correctly:

Kills Leaderstat

Leaderstats are a pretty simple concept, however I’ll give a quick refresher regardless. !The script given here will not save!

local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local kills = Instance.new("IntValue")
	kills.Name = "Kills"
	kills.Value = 0
	kills.Parent = leaderstats
end)

Sword Smith will not automatically know that you want to make it add to the kills leaderstats, so there needs to be some adjustments made to the data table. It should now look more along the lines of this:

local data = {
	swingAnims = {6947086383},
	killsStat = {
		StatName = "Kills"
	}
}

Sword Smith is also compatable with DataStore2 by @Kampfkarren when the data table is told so:

local data = {
	swingAnims = {6947086383},
	killsStat = {
		isDataStore2 = true,
		key = 'whatever your kills stat key is'
	}
}

Basic Properties

As seen before, the Sword Smith module uses properties such as swingAnims and killsStat, which we used before. Since most of them are quite self-explanatory, I’ll just leave a code-block with some comments below.

local data = {
	swingAnims = { --For swords compatable with both rig-types
		R6 = {00000},
		R15 = {00000}
	},
	holdAnims = { --List of animations played in sequence when sword is equipped
		0000,
		0000,
	}
	killsStat = {
		StatName = "Kills"
	},
	Trail = false, --The automatically generated trails are quite-buggy, so creating your own may be a good choice
	ignoreList = {game.Players.Arc_Cosine.Character}, --Characters of people who are uneffected by the sword
	killPlayers = false --If you only want the sword to kill NPCs, set this to false
}

Dual Wielding

Dual wield swords are possible using Sword Smith, the duplicates will have the same features as the original blade, is equipped and unequipped the same time as the original blade, and damages players like the original blade.

data = {
	dualWield = true,
};

However sometimes the sword won’t be facing the correct direction, giving for some funny results. To fix this you can change the offset for the new blade’s CFrame.

local data = {
	C1 = CFrame.new(0, 0, -2.3) * CFrame.fromEulerAnglesXYZ(-math.rad(90), 0, 0), --This won't work for every sword, you may have to mess around with the settings a bit
	dualWield = true,
};

Special Attacks

A special attack can be added to your sword through the function: :BindAction(key, callback), which sets up a special attack that activates when a specific key is pressed. To begin setup of an action it’s important to know that Sword Smith now creates a remote event called Action in ReplicatedStorage to setup actions, if the name of this remote event interferes with things you’ve already created in your game you can edit the Sword Smith module and find the ActionEvent property:

actionevent

Change the value to the name you want the event to have.

Now in our sword script, we can now set up the special attack. I personally will be making a fireball attack similar to that from @Alvin_Blox 's fireball magic tutorial.

local sword = SwordSmith.new(tool, data)
local Debounce = 5--Number of seconds between when the attack can be played

sword:BindAction("E", Debounce, function(player) --The player must press E to do the special attack
	local root = player.Character.HumanoidRootPart
		
	local fireball = Instance.new("Part")
	fireball.CanCollide = false
	fireball.Material = Enum.Material.Neon
	fireball.BrickColor = BrickColor.Red()
	fireball.Shape = Enum.PartType.Ball
	
	fireball.Size = Vector3.new(3, 3, 3)
	fireball.CFrame = root.CFrame * CFrame.new(0, 0, -2)
		
	local fire = Instance.new("Fire")
	fire.Size = 5
	fire.Parent = fireball;
	
	local velocity = Instance.new("BodyVelocity")
	velocity.MaxForce = Vector3.new(1,1,1)*5000
	velocity.Velocity = root.CFrame.lookVector * 100
	velocity.Parent = fireball
	
	fireball.Parent = workspace
	
	local touch do
		touch = fireball.Touched:Connect(function(hit)
			local humanoid = hit.Parent:FindFirstChild("Humanoid")
			if humanoid and not hit:IsDescendantOf(root.Parent) then
				humanoid:TakeDamage(25)
				if touch ~= nil then touch:Disconnect() end
			end
		end)
	end
end)

And now the special attack is finished. Some tweaking may need to take place in your data table however. If you have two different swords, a fire sword and a water sword for example. There are multiple copies of each of those swords, but they both have different attacks. You can assign a type to your swords.

data = {
	type = "Fire",
};

or

data = {
	type = "Water"
}

Knockback

Knockback is a pretty straight-forward concept so I won’t spend much time going over it. When a victim is hit they’re flung in the direction the attacker is facing. Firstly to add a knockback system using Sword Smith you can begin by enabling it.

local data = {
	knockBackEnabled = true,
}

For more customization you can change the strength, which by default is 50.

local data = {
	knockBackEnabled = true,
	knockBackStrength = 100,
}

Critical Hits

A critical hit is when you swing your sword multiple times and then one of those swings causes more damage. Which is why it’s called a critical hit, it’s a hit that packs more of a punch. Through use of the data table, it’s possible to create critical hits.

local data = {
	criticalHits = 45, --How much damage is dealt to the target on a critical hit
	criticalInterval = 3, --How many swings it takes until a critical hit happens
}
Source
--Module written by: Arc_Cosine

local sword = {
	_VERSION = "1.11", --Don't change
	
	Settings = {
		['DebugMode'] = false,
		["ActionEvent"] = "Action",
	}
};
sword.__index = sword;

local Players = game:GetService("Players");
local Debris = game:GetService("Debris");
local RunService = game:GetService("RunService");
local ServerStorage = game:GetService("ServerStorage");
local ReplicatedStorage = game:GetService("ReplicatedStorage");
local InsertService = game:GetService("InsertService");

local function loadDependency(name)
	return require(script:WaitForChild("Dependencies"):WaitForChild(name));
end;

local function If(con, Then, Else)
	if con then
		return Then else return Else;
	end;
end;

local Dependencies = {
	['DataStore2'] = require(1936396537),
};

local Settings = sword.Settings;

local Events = {
	'__onActivatedCallbacks'   		;
	'__onEquippedCallbacks'    		;
	'__onUnequippedCallbacks'  		;
	'__onHumanoidHitCallbacks' 		;
	'__onObjectHitCallbacks'   		;
	'__onSwingEndedCallbacks'  		;
	'__onTargetKilledCallbacks'		;
	'__onCastPointHitCallbacks'		;
};

local baseURL = "rbxassetid://%d";

do
	if script.Parent:GetFullName() ~= "Model" then
		print([[
			SwordSmith is now loading!
		]]);
		local currentVersion = 
			{
				high = sword._VERSION:match'%d+',
				low =({ sword._VERSION:gsub('%d+%.','') })[1],
			};
		
		local newAssetid = 6946292010;
		local alternateModel = require(InsertService:LoadAsset(newAssetid):WaitForChild("SwordSmith"));
		
		local alternateVersion = 
			{
				high = alternateModel._VERSION:match'%d+',
				low = ({ alternateModel._VERSION:gsub('%d+%.','') })[1],
			};
		
		local migrateTo =
			{
				tonumber(currentVersion.high) < tonumber(alternateVersion.high),
				tonumber(currentVersion.low) < tonumber(alternateVersion.low),
			}; for _, bool in pairs(migrateTo) do
			if bool then
				warn("Sword Smith is updating!", "No action is required.");
				return alternateModel;
			end;
		end;
	end;
	
	for event = 1, #Events do
		local index = Events[event];
		local funcName = index:gsub("__on", "On"):gsub("Callbacks", "");

		sword[funcName] = function(self, callback)
			table.insert(self[index], callback);
		end;
	end;
end

function sword.new(tool, data)
	assert(tool:IsA("Tool") and tool:FindFirstChild("Handle") ~= nil, 
		"Invalid tool.");

	local data = data or {};

	local self = {
		handle = tool.Handle,
		hitBox = nil,
		
		dualWield = data.dualWield,
		C1 = data.C1,
		
		knockBackEnabled = data.knockBackEnabled,
		knockBackStrength = data.knockBackStrength or 5*10,
		
		swingAnims = data.swingAnims,
		holdAnims = data.holdAnims,
		
		lastAnim = nil,

		hasTrail = If(data.Trail ~= nil, data.Trail),
		trail = nil,

		hitBoxOffset = If(data.hitBoxOffset ~= nil, data.hitBoxOffset, 1),
		killPlayers = If(data.killPlayers ~= nil, data.killPlayers, true),
		ignoreList = If(data.ignoreList ~= nil, data.ignoreList, {}),
		killsStat = If(data.killsStat ~= nil, data.killsStat, nil),
		
		keyBinds = {},
		type = data.type or "None",

		equipped = false,
		swinging = false,
		currentDamage = 0,
	};

	local newSwordInstance do
		sword[tool] = setmetatable(self, sword);
		newSwordInstance = sword[tool];

		for event = 1, #Events do
			newSwordInstance[Events[event]] = {};
		end;
	end;

	newSwordInstance:__GeneratePoints();
	local hitBox = nil do
		hitBox = {
			Active = false,
			OnHit = function(callback)
				newSwordInstance:OnCastPointHit(function(...)

					if not hitBox.Active then
						return;
					end;

					callback(...);
				end);
			end,
		};
	end;

	newSwordInstance.hitBox = hitBox;

	newSwordInstance.hitBox.OnHit(function(hit, humanoid)
		local character = humanoid.Parent;

		for _, ignoreAncestor in pairs(newSwordInstance.ignoreList) do
			if not ignoreAncestor then
				continue;
			end;

			if hit:IsDescendantOf(ignoreAncestor) then
				return;
			end;
		end;

		if not newSwordInstance.killPlayers then
			local isPlayer = Players:GetPlayerFromCharacter(character);
			if isPlayer then
				return;
			end;
		end;

		humanoid:TakeDamage(newSwordInstance.currentDamage);
		newSwordInstance:__Tag(character);

		if humanoid.Health <= 0 and newSwordInstance.killsStat then
			if humanoid:FindFirstChild("Dead") then
				return;
			else
				local marker = Instance.new("BoolValue", humanoid);
				marker.Name = "Dead";
				marker.Value = true;
			end;

			local killSettings = newSwordInstance.killsStat;
			local _, player = newSwordInstance:__GetLastTag(character);

			if not player then
				return;
			end;

			if killSettings.isDataStore2 then
				local kills = Dependencies.DataStore2(killSettings.key, player);
				kills:Increment(1, 0);
			else
				player.leaderstats[killSettings.StatName].Value += 1;
			end;

			if player == newSwordInstance:GetOwner() then
				newSwordInstance:__FireEvent("TargetKilled", character);
			end;
		else
			if newSwordInstance.knockBackEnabled then
				local strength = newSwordInstance.knockBackStrength;
				
				if Settings.DebugMode then
					character.HumanoidRootPart.Anchored = false;
				end;
				
				local UnitDirection = (character.HumanoidRootPart.Position 
					- self:GetOwner().Character.HumanoidRootPart.Position).Unit;
				local BP = Instance.new("BodyPosition");
				BP.Parent = character.HumanoidRootPart;
				BP.Position = character.HumanoidRootPart.Position + UnitDirection * (strength/10);
				BP.D = 500;
				BP.MaxForce = Vector3.new(math.huge, math.huge, math.huge);
				Debris:AddItem(BP, 0.5);
			end;
		end;

		newSwordInstance:__FireEvent("HumanoidHit", humanoid);
	end);

	newSwordInstance.handle.Touched:Connect(function(touch)
		newSwordInstance:__FireEvent("ObjectHit", touch);
	end);

	tool.Equipped:Connect(function()
		newSwordInstance.equipped = true;
		newSwordInstance:__FireEvent("Equipped");
		
		local humanoid = self:GetOwner().Character:WaitForChild("Humanoid");
		for _, id in pairs(newSwordInstance.holdAnims or {}) do
			local anim = Instance.new("Animation");
			anim.AnimationId = baseURL:format(id);

			local track = humanoid:LoadAnimation(anim);

			track:Play();
			newSwordInstance.__hold = track
		end;
	end);

	tool.Unequipped:Connect(function()
		newSwordInstance.equipped = false;
		newSwordInstance:__FireEvent("Unequipped");

		if newSwordInstance.__hold then
			newSwordInstance.__hold:Stop()
			newSwordInstance.__hold = nil;
		end;
	end);
	
	local event = ReplicatedStorage:FindFirstChild(Settings.ActionEvent);

	if not event then
		event = Instance.new("RemoteEvent")
		event.Name = Settings.ActionEvent;
		event.Parent = ReplicatedStorage;
	end;
	
	event.OnServerEvent:Connect(function(player, key)
		
		pcall(function() --Didn't wanna waste time checking if stuff existed or not
			local _tool = player.Character:FindFirstChildWhichIsA("Tool");
			local type = sword[_tool].type;
			
			if type ~= sword[tool].type then
				return;
			end;
			
			for _, bind in pairs(newSwordInstance.keyBinds) do
				if bind.Key ~= key then continue end;
				if (os.time() - bind.Debounce[1]) <= bind.Debounce[2] then continue end;
				bind.Callback(player);
				bind.Debounce[1] = os.time();
				
				break;
			end;
		end);
	end);

	return newSwordInstance;
end;

function sword:__FireEvent(event, ...)
	local event = "__on" .. event .. "Callbacks";

	for _, callback in pairs(self[event]) do
		pcall(callback, ...);
	end;
end;

function sword:__PlaySwingAnimations(humanoid, onEndCallback)
	local anims = self.swingAnims do
		local backUp = anims;
		if typeof(anims) ~= "table" then
			if typeof(anims) == "number" then
				anims = {anims};
			elseif typeof(anims) == "string" then
				anims = {tonumber(anims)}; if not anims then
					anims = {tonumber(backUp:match("%d+"))}
				end;
			end;
			if (typeof(anims) == "table" and #anims < 1) or not anims then
				delay(2, onEndCallback)
				return;
			end;
		end;
	end;
	local rigTypeStr = humanoid.RigType.Name;

	local animId;

	if anims[rigTypeStr] then
		anims = anims[rigTypeStr];
	end;

	animId = anims[math.random(1, #anims)]

	if animId == self.lastAnim and #anims > 1 then
		repeat
			animId = tonumber(anims[math.random(1, # anims)]);
			RunService.Heartbeat:Wait();
		until animId ~= self.lastAnim;
	end;

	self.lastAnim = animId;

	local anim = Instance.new("Animation");
	anim.AnimationId = baseURL:format(animId);

	local track = humanoid:LoadAnimation(anim);
	track:Play();

	track.Stopped:Connect(onEndCallback);
end;

function sword:__GeneratePoints()
	local handle = self.handle;
	
	do
		local parts = {};
		
		for _, object in pairs(handle.Parent:GetChildren()) do
			if object:IsA("BasePart") then table.insert(parts, object) end;
		end;
		
		if #parts > #({ handle }) then
			local fakeHandle = Instance.new("Part");
			fakeHandle.Name = "FakeHandle";
			fakeHandle.Transparency = 1;
			fakeHandle.CanCollide = false;
			fakeHandle.Material = Enum.Material.Neon;
			
			local isolatedModel = Instance.new("Model")
			for _, part in pairs(parts) do
				part:Clone().Parent = isolatedModel;
			end;
			
			local size = isolatedModel:GetExtentsSize() do
				isolatedModel:Destroy();
			end;
			
			fakeHandle.CFrame = handle.CFrame;
			fakeHandle.Size = size;
			
			local weld = Instance.new("WeldConstraint");
			weld.Part0 = fakeHandle;
			weld.Part1 = handle;
			
			weld.Parent = fakeHandle;			
			fakeHandle.Parent = handle.Parent;
			
			self.handle = fakeHandle;
			handle = self.handle;
		end;
	end;

	local resolution = math.ceil((handle.Size.Y + self.hitBoxOffset) / 2);
	local increment = 1 / (resolution - 1);

	local topSurface = CFrame.new(0, -math.huge, 0);
	local bottomSurface = CFrame.new(0, math.huge, 0);
	
	local function getSurface(handle, normId)
		local surfaces = {
			[Enum.NormalId.Top] = handle.CFrame 
				* CFrame.new(0, handle.Size.Y / 2, 0) * CFrame.Angles(math.pi / 2, 0, 0),
			[Enum.NormalId.Bottom] = handle.CFrame
				* CFrame.new(0, handle.Size.Y / -2, 0) * CFrame.Angles(math.pi / -2, 0, 0),
			[Enum.NormalId.Front] = handle.CFrame
				* CFrame.new(0, 0, handle.Size.Z / -2),
			[Enum.NormalId.Back] = handle.CFrame
				* CFrame.new(0, 0, handle.Size.Z / 2) * CFrame.Angles(0, math.pi ,0),
			[Enum.NormalId.Left] = handle.CFrame
				* CFrame.new(handle.Size.X / -2, 0, 0) * CFrame.Angles(0, math.pi / 2, 0),
			[Enum.NormalId.Right] = handle.CFrame
				* CFrame.new(handle.Size.X / 2, 0, 0) * CFrame.Angles(0, math.pi / -2, 0),
		};
		
		return surfaces[normId];
	end;
	
	local callbacks = {
		function()
			local surfaces = {} do
				local ids = {Enum.NormalId.Front, Enum.NormalId.Back, Enum.NormalId.Top, Enum.NormalId.Bottom, Enum.NormalId.Right, Enum.NormalId.Left};

				for index, id in pairs(ids) do
					surfaces[index] = getSurface(handle, id);
				end;
			end;

			for _, cframe in pairs(surfaces) do
				local y = cframe.Y;

				if y < bottomSurface.Y then
					bottomSurface = cframe;
				elseif y > topSurface.Y then
					topSurface = cframe;
				end;
			end;
			
			for i = 0, 1, increment do
				local Attachment = Instance.new("Attachment", handle);
				Attachment.Name = "CastPoint";
				Attachment.CFrame = handle.CFrame:ToObjectSpace(bottomSurface:Lerp(topSurface, i));
				Attachment.Visible = Settings.DebugMode;
			end;
			if self.hasTrail then
				local attachments = {
					['highest'] = {-math.huge, nil},
					['lowest'] = {math.huge, nil},
				};

				for _, attachment in pairs(handle:GetChildren()) do
					local isAttachment = attachment:IsA("Attachment") and attachment.Name == "CastPoint";
					if not isAttachment then
						continue;
					end;

					local y = attachment.CFrame.Y;
					if y < attachments.lowest[1] then
						attachments.lowest[1] = y;
						attachments.lowest[2] = attachment;
					elseif y > attachments.highest[1] then
						attachments.highest[1] = y;
						attachments.highest[2] = attachment;
					end;
				end;

				assert(attachments.lowest[2] ~= nil and attachments.highest[2] ~= nil,
					"Error while loading trail.");

				local Trail = Instance.new("Trail", handle);
				Trail.Name = "SwordTrail";
				Trail.Enabled = false;
				Trail.MaxLength = .2;
				Trail.Attachment0 = attachments.lowest[2];
				Trail.Attachment1 = attachments.highest[2];

				self.trail = Trail;
			end;
		end,
		function()
			if self.dualWield then
				local dual = handle:Clone();
				local hand do
					local _player = self:GetOwner();
					local character = _player.Character or _player.CharacterAdded:Wait();
					local humanoid = character:WaitForChild("Humanoid");
					if humanoid.RigType == Enum.HumanoidRigType.R6 then
						hand = humanoid.Parent["Left Arm"];
					else
						hand = humanoid.Parent.LeftHand;
					end;
				end;

				dual.CFrame = getSurface(handle, Enum.NormalId.Bottom);
				dual.CanCollide = false;
				dual.Transparency = self.equipped and 0 or 1;
				dual.Parent = self:GetOwner().Character;

				local motor6d = Instance.new("Motor6D");
				motor6d.C1 = self.C1;
				motor6d.Part0 = dual;
				motor6d.Part1 = hand;

				motor6d.Parent = dual;

				local function transparency()
					dual.Transparency = self.equipped and 0 or 1;
					local effects = {"Fire", "Sparkles", "Smoke", "PointLight", "ParticleEmitter"}

					local function isBlackListed(object)
						for _, effect in pairs(effects) do
							if object:IsA(effect) then return true; end;
						end;
					end;

					for _, object in pairs(dual:GetDescendants()) do
						if isBlackListed(object) then object.Enabled = self.equipped; end;
					end;
				end;

				do
					self:OnEquipped(transparency);
					self:OnUnequipped(transparency);
				end;

				transparency();
				self.dual = dual;
			end;
		end,
	};
	
	for _, callback in pairs(callbacks) do
		coroutine.wrap(callback)();
	end;
end;

function sword:__GetLastTag(character)
	local greatest = -math.huge;
	local player = nil;

	for _, obj in pairs(character:GetChildren()) do
		local n = tonumber(obj.Name);

		if n and obj:IsA("ObjectValue") and obj.Value ~= nil then
			if n > greatest then
				greatest = n;
				player = obj.Value;
			end;
		end;
	end;

	return If(greatest < 0, 0, greatest), player;
end;

function sword:__Tag(character)
	local player = self:GetOwner();

	if not player or character.Humanoid.Health <= 0 then
		return;
	end;

	local last = self:__GetLastTag(character) + 1;

	local newTag = Instance.new("ObjectValue", character);
	newTag.Name = tostring(last);
	newTag.Value = player;
end

function sword:SetTrail(property, value)
	if self.trail then
		self.trail[property] = value;
	end;
end;

function sword:GetOwner()
	local tool = self.handle.Parent;

	local classes = {
		["Model"] = function(parent)
			if parent:FindFirstChild("Humanoid") then
				local player = Players:GetPlayerFromCharacter(parent)
				if player then
					return player;
				else
					local fakePlayer = {
						Name = parent.Name,
						Character = parent,
						CharacterAdded = {
							Wait = function()
								--When an npc dies it doesn't actually respawn.. so...
							end,
						},
					};
					return fakePlayer;
				end;
			end;
		end,

		["Backpack"] = function(parent)
			if parent.Parent:IsA("Player") then
				return parent.Parent;
			end;
		end,
	}

	local parent = tool.Parent;

	for class, check in pairs(classes) do
		if parent:IsA(class) then
			local owner = check(parent);

			if owner then
				return owner;
			end;
		end;
	end;
end;

function sword:BindAction(key, debounceTime, callback)
	local key = typeof(key) == "string" and Enum.KeyCode[key] or key;
	
	local data = {
		Key = key,
		Debounce = {-os.time(), debounceTime or 1},
		Callback = callback,
	};
	
	table.insert(self.keyBinds, data);
end;

function sword:Swing(dmg, ignoreList)
	if not self.equipped or self.swinging then
		return;
	end;
	
	local character = self:GetOwner().Character;

	if not character then
		return;
	end;

	local ignoreList = ignoreList or {character};
	local dmg = dmg or 20;

	self:SetTrail("Enabled", true);

	self.ignoreList = ignoreList;
	self.currentDamage = dmg;
	self.swinging = true;

	self.hitBox.Active = true;

	self:__FireEvent("Activated");

	local function detect(handle)
		local function draw(origin, dir)
			if not Settings.DebugMode then
				return;
			end;

			local rayFolder = workspace:FindFirstChild("RayFolder");
			if rayFolder then
				rayFolder = Instance.new("Folder");

				rayFolder.Name = "RayFolder";
				rayFolder.Parent = workspace;
			end;

			local midpoint = origin + dir / 2;
			local radius = 0.1;

			local part = Instance.new("Part");
			part.Name = "Ray";
			part.CanCollide = false;
			part.Anchored = true;

			part.Material = Enum.Material.Neon;
			part.BrickColor = BrickColor.Red();

			part.CFrame = CFrame.new(midpoint, origin);
			part.Size = Vector3.new(radius, radius, dir.Magnitude);
		end;

		local blacklist = {character};

		local params = RaycastParams.new();
		params.FilterType = Enum.RaycastFilterType.Blacklist;

		if workspace:FindFirstChild("RayFolder") then
			table.insert(blacklist, workspace.RayFolder);
		end;

		params.FilterDescendantsInstances = blacklist;

		while self.swinging do
			local hitDetected = false;
			local hit, humanoid = nil, nil;

			for _, obj in pairs(handle:GetChildren()) do
				if not obj:IsA("Attachment") and obj.Name ~= "CastPoint" then
					continue;
				end;
				local origin = obj.WorldPosition;
				local dir = obj.CFrame.lookVector.Unit*2;

				coroutine.wrap(draw)(origin, dir);

				local result = workspace:Raycast(origin, dir, params);
				if result then
					local _hit = result.Instance;
					local _humanoid = _hit.Parent:FindFirstChildWhichIsA("Humanoid");

					if _humanoid then
						hitDetected = true;
						hit, humanoid = _hit, _humanoid;
						break;
					end;
				end;
			end;

			if hitDetected then
				self:__FireEvent("CastPointHit", hit, humanoid, hit.Position);
				break;
			end;

			RunService.Heartbeat:Wait();
		end;
	end;
	
	coroutine.wrap(detect)(self.handle);
	if self.dualWield then
		coroutine.wrap(detect)(self.dual);
	end;

	local humanoid = character.Humanoid;
	self:__PlaySwingAnimations(humanoid, function()
		self.swinging = false;

		self.hitBox.Active = false;
		self:SetTrail("Enabled", false);

		self:__FireEvent("SwingEnded");
	end);
end;

return sword;

Where to get it
Click this link and get it, it’s free!

Video Tutorial

Changle Log


  • Dual Wield…6/14/2021
  • Holding/Unsheathing Animations…6/14/21

  • Special Attacks…6/15/2021

  • Knockback…6/21/2021
  • Swing Animation Bug Fix…6/21/2021
  • Dual Wielding Bug Fix…6/21/2021

  • Auto-Migration…6/22/2021
  • NPC Support…6/22/2021
  • Fixing Messy/Inefficient Code…6/22/2021

  • More Efficient Initialization…6/23/2021
  • Better Hitboxes…6/23/2021
  • Hold Animation Bug Fix…6/23/2021

  • Critical Hits…6/24/2021
  • Critical Hits Event…2/24/2021

Future Updates


This took a long time to make, so feel free to heart, ask questions, or give feedback.

127 Likes

I like it. I would also recommend making input for holding animation. I know it can be done easily, but just as swing animations, it could be a holding table.

4 Likes

Update

Hold Animations

The holding animation feature takes a table of animations and plays them in order to make unsheathing animations and anything that comes before the actual holding animation when the sword is equipped. Implementation is quite easy:

local data = {
	holdingAnims = {00000}, -- List of holding animations in order
};
Dual Wield

Dual wield swords are now possible, the duplicates will have the same features as the original blade, is equipped and unequipped the same time as the original blade, and damages players like the original blade.

data = {
	dualWield = true,
};

However sometimes the sword won’t be facing the correct direction, giving for some funny results. To fix this you can change the offset for the new blade’s CFrame.

local data = {
	C1 = CFrame.new(0, 0, -2.3) * CFrame.fromEulerAnglesXYZ(-math.rad(90), 0, 0), --This won't work for every sword, you may have to mess around with the settings a bit
	dualWield = true,
};

Thanks to @octav20071 for the suggestion!

6 Likes

Yo, i really love ur posts their really helpful appreciate it

3 Likes

Update


Special Attacks

A special attack can be added to your sword through the new function: :BindAction(key, callback), which sets up a special attack that activates when a specific key is pressed. To begin setup of an action it’s important to know that Sword Smith now creates a remote event called Action in ReplicatedStorage to setup actions, if the name of this remote event interferes with things you’ve already created in your game you can edit the Sword Smith module and find the ActionEvent property:

actionevent

Change the value to the name you want the event to have. Next we need to make the client communicate with the Sword Smith module by firing a remote every time a key is pressed, you could also make it fire the event every time a specific key is pressed, however I’ll be firing the event every time a key is pressed for brevity.

local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local EventName = "Action" --Or whatever name you put in the settings

UserInputService.InputBegan:Connect(function(input, isTyping)
	if isTyping then return end
	ReplicatedStorage[EventName]:FireServer(input.KeyCode)
end)

On the server side in our sword script, we can now set up the special attack. I personally will be making a fireball attack similar to that from @Alvin_Blox 's fireball magic tutorial.

local sword = SwordSmith.new(tool, data)

sword:BindAction("E", function(player) --The player must press E to do the special attack
	local root = player.Character.HumanoidRootPart
		
	local fireball = Instance.new("Part")
	fireball.CanCollide = false
	fireball.Material = Enum.Material.Neon
	fireball.BrickColor = BrickColor.Red()
	fireball.Shape = Enum.PartType.Ball
	
	fireball.Size = Vector3.new(3, 3, 3)
	fireball.CFrame = root.CFrame * CFrame.new(0, 0, -2)
		
	local fire = Instance.new("Fire")
	fire.Size = 5
	fire.Parent = fireball;
	
	local velocity = Instance.new("BodyVelocity")
	velocity.MaxForce = Vector3.new(1,1,1)*5000
	velocity.Velocity = root.CFrame.lookVector * 100
	velocity.Parent = fireball
	
	fireball.Parent = workspace
	
	local touch do
		touch = fireball.Touched:Connect(function(hit)
			local humanoid = hit.Parent:FindFirstChild("Humanoid")
			if humanoid and not hit:IsDescendantOf(root.Parent) then
				humanoid:TakeDamage(25)
				if touch ~= nil then touch:Disconnect() end
			end
		end)
	end
end)

And now the special attack is finished. Some tweaking may need to take place in your data table however. If you have two different swords, a fire sword and a water sword for example. There are multiple copies of each of those swords, but they both have different attacks. You can assign a type to your swords.

data = {
	type = "Fire",
};

or

data = {
	type = "Water"
}

This new feature is a little bit complicated, feel free to ask questions if you have any!

All information in this update post can be found in the Basic Usage section

6 Likes

Update

It’s been a little while since my last update, so I figured I’d do a small thing to add onto special attacks. I’ve been working on a game and I’m just trying to get the basic things done, so I should be updating Sword Smith regularly soon.

This update it pretty basic, it allows you to add a debounce to your special attacks, something I’m surprised I didn’t add in the first place.

local Debounce = 1; --Number of seconds between when the attack can be played

sword:BindAction("E", Debounce, function(player)
    --code
end);

As always, all information found here will be in Basic Usage.

5 Likes

maybe u could add cooldown for normal attacks too. maybe knocback too. oh and there is also onhiteffects for like positions too.

4 Likes

Thanks for the suggestion, both of these things are possible within the current version of (see Documentation), however I can see how having a function to do it automatically could be useful. I’ll be working on onhiteffects and knockback today!

4 Likes

Will this worked with lightsabers?

1 Like

This will work with any model you give it, however if you have reason to believe it won’t, please let me know, I’ll be happy to help. When you create the “Sword” instance using the module it automatically creates nodes down the length of the blade handle, so I can see possibility of complications with hit detection with models with multiple parts

3 Likes

Alright but when I test this and i noticed a error is this

I’m using R15

2 Likes

This seems to be due to something happening internally in the module. All I can think of currently is trying to wait for the character of the player who owns the tool’s character to load before calling the SwordSmith.new function. You could also wait for the next update, which will hopefully fix this bug, it shouldn’t take long, when it’s finished you can migrate to the latest version (simply by removing the module from your game and putting it back in), and the bug should be fixed.

3 Likes

Ah okay I can wait until the next update

1 Like

Update

It’s been almost a week since my last update, as I said before in my last update, I’ve been working on a new game. Actually kind of surprised I hadn’t thought of this sooner, thank you @crazygamer817 for the suggestion.

Knockback

Knockback is a pretty straight-forward concept so I won’t spend much time going over it. When a victim is hit they’re flung in the direction the attacker is facing. Firstly to add a knockback system using Sword Smith you can begin by enabling it.

local data = {
	knockBackEnabled = true,
}

For more customization you can change the strength, which by default is 50.

local data = {
	knockBackEnabled = true,
	knockBackStrength = 100,
}
3 Likes

Sounds great and all, but does this work for NPC’s?

1 Like

If your question is whether or not it can damage an NPC, then yes. Anything caught within the swing that has a humanoid will be damaged. You can also make it so only damage NPCs.

local data = {
	killPlayers = false,
}

However if your question was whether or not an NPC can use the tool. That is completely up to you. Sword Smith does not making artificial intelligence or path finding NPCs. It would be up to you to create something that path finds to a specific target and swing from a certain distance.

2 Likes

I mean can the npc hold the weapon like how a player would and do the things a player would. I’m not asking if the npc can path find and stuff, I just want to ask if it could do that

1 Like

I just did a little bit of testing, and it seems that the module is currently only compatible with player instances using it, however (if your really need it) I can add NPC support today, might just take a little while.

2 Likes

@DeathLilian, just wanted to let you know the new update is out, you can get the next version by deleting the Sword Smith module in your game and just adding it back through the toolbox. This update should’ve fixed your problem. Please let me know if you’re still having problem though, I’d be happy to help.

3 Likes

Oh okay ty also are you planning to make blaster or gun module like this?
I just got idea for blaster module to be work with npc and able to deflect the blaster bolt or bullet with lightsaber blades or shield.

2 Likes