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

I believed I just fixed the issue

Ok so this is great - Amazing - makes things so much easier for someone like me not so experienced with scripting but…

I followed the tutorial exactly. Everything worked, the animation, the damage, only when I equipped the weapon, it showed up like 2 feet away from the character then glitched out of the world.
I think it’s something I did wrong, but not sure exactly what. It might be something to do with the anchoring?

EDIT: Also how can I get a pre-made effect to play when I do the basic swing?

EDIT: I fixed the first issue now. I just need to know how to play my effect at the same time.

yup its fixed thanks gonna mess around with the module a bit more

the module has a trail effect in it but Arc said it was a bit glitchy so you could just use UserInputService to activate it

1 Like

I’d like to give an update on the new SwordSmithV2 module that I alluded to a few times in some of the comments on this post. Currently, the module can’t do nearly as many things as the original, however, the things it can do, are performed more efficiently, cleaner, and in a more organized fashion. Below I’ve listed my current progress on the module:

  • Completely new infrastructure, now having a formal settings module, and a module for both the client and the server. This will allow for much more functionality than in the original.
    infrastructure
  • There will be actual signals to connect to, unlike the original module which creates a new function for every signal.
  • More advanced hit detection. I’m now detecting hits very similarly to how they’re detected in RaycastHitbox, which has yielded some very nice results. The new system raycasts on the client, once something has been hit, it fires a remote event. The hit is verified on the server before dealing damage to the target. To prevent exploitation this Clock event is fired every 2.5 seconds to verify the position of the client, if they fail to give correct data, or they give data too late, their sword will be locked until they can provide the server with accurate data 5 times. The code used to do this is below
Anti-Exploit
Clock = function(player, vector)
			if Server[player] == nil then
				return;
			end;
			
			local sword = Server[player];
			local verification = sword._verification_controller;
			local log_num = #verification._log;
			
			if log_num == 5 then
				for index in pairs(verification._log) do
					verification._log[index] = nil;
				end;
				
				log_num = 1;
			end;
			
			local clock_leeway = VerificationSettings.Clock + VerificationSettings.Clock*1.5;
			
			if vector.Y ~= 0 then
				vector = Vector3.new(vector.X, 0, vector.Z);
			end;
			
			if os.time() - verification._clock <= clock_leeway  then
				table.insert(verification._log, vector);
				
				if log_num > 1 and (verification._log[log_num] - vector).Magnitude >=
					(sword._max_walkspeed or VerificationSettings.MaxWalkSpeed)*clock_leeway 
					+ VerificationSettings.StudLeeway					
				then
					sword:ClockFailure(2);
				end;
				
				verification._clock = os.time();
				verification._checks += 1;
			else
				sword:ClockFailure(1);
			end;
			
			verification._locked = verification._checks < 5;
  • Debounces are built-in on the server and client, whereas in the previous module you would have to handle denounces yourself.
  • The new module is capable of procedurally settings HitPoints along the length of the blade with far more accuracy than the original. The new code I use to do this is below.
HitPoint Position Calculating
local topAttachment: Attachment?, bottomAttachment: Attachment? = nil, nil;

	if self._points == nil then
		self._points = {};

		local topSurface: CFrame = handle.CFrame*CFrame.new(0, handle.Size.Y/2, 0);
		local bottomSurface: CFrame = handle.CFrame*CFrame.new(0, -handle.Size.Y/2, 0);

		if self._compare_pairs == true then
			local surface_pairs = {
				{
					[Enum.NormalId.Top] = topSurface,
					[Enum.NormalId.Bottom] = bottomSurface,
				},
				{
					[Enum.NormalId.Right] = handle.CFrame*CFrame.new(handle.Size.X/2, 0, 0),
					[Enum.NormalId.Left] = handle.CFrame*CFrame.new(-handle.Size.Z/2, 0, 0),
				},
				{
					[Enum.NormalId.Back] = handle.CFrame*CFrame.new(0, 0, handle.Size.Z/2),
					[Enum.NormalId.Front] = handle.CFrame*CFrame.new(0, 0, -handle.Size.Z/2),
				},
			};

			local length: number, surfaces: {[number]: Enum.NormalId | number} = -math.huge, table.create(2);

			for index: number, pair: {[Enum.NormalId]: CFrame} in pairs(surface_pairs) do
				local surfs: {[number]: Enum.NormalId | number} = nil;

				if index == 1 then
					surfs = {Enum.NormalId.Top, Enum.NormalId.Bottom};
				elseif index == 2 then
					surfs = {Enum.NormalId.Right, Enum.NormalId.Left};
				elseif index == 3 then
					surfs = {Enum.NormalId.Back, Enum.NormalId.Front};
				end;

				local magnitude: number = (pair[surfs[1] :: Enum.NormalId].Position -
					pair[surfs[2] :: Enum.NormalId].Position).Magnitude;

				if magnitude > length then
					table.insert(surfs, index);
					length, surfaces = magnitude, surfs;
				end;
			end;

			local row = surface_pairs[surfaces[3] :: number];
			topSurface, bottomSurface = row[surfaces[1] :: Enum.NormalId], row[surfaces[2] :: Enum.NormalId];
		end;

		local res: number = math.ceil((handle.Size.Y + 1)/2);
		res = (res <= 1 and 5) :: number;

		for i: number = 0, 1, 1/(res - 1) do
			local attachment = Instance.new("Attachment");
			attachment.Name = "CastPoint";
			attachment.CFrame = handle.CFrame:ToObjectSpace(bottomSurface:Lerp(topSurface, i));
			attachment.Visible = Settings.DebugMode;
			attachment.Parent = handle;

			table.insert(self._points, attachment);
		end;
	end;

If I listed all the differences between the new and the old Sword Smith, I would be writing here all day. However, I will try to update you all on my progress regularly. For those of you who are already using Sword Smith, I will be trying to create a conversion function between the old Sword Smith and the new version.

Feel free to suggest any new features you’d like to see and ask questions!

I plan on officially releasing the module within the next week or two if possible

1 Like

I decided to try and increase the efficiency and functionality of the Raycaster object the new module will be providing, which might take a little while longer. I said I would release the new version possibly sometime this week, however, it just won’t be done in time. Here’s a sneak-peak on the Raycaster, it currently has a few bugs I’m trying to work out.

Raycaster
--!strict
--- [[ Module Definition ]] ---
local Raycaster = {};
local RaycasterImpl = newproxy(true);
local RaycasterInternalAPI = {};

--- [[Roblox Services]] ---
local RunService = game:GetService("RunService");

--- [[ Modules ]] ---
local Util = require(script:FindFirstAncestor("MainModule").Util);

--- [[ Localization ]] ---
local Trace: (Vector3, Vector3, boolean) -> () = Util.DrawRay or function() end;

--- [[ Types ]] ---
local TypeDefs = require(script.Parent.TypeDefs);

type Dictionary<KeyType, ValueType> = TypeDefs.Dictionary<KeyType, ValueType>;
type Debugger = TypeDefs.Debugger;
type Result = TypeDefs.Result;
type RaycastPoint = TypeDefs.RaycastPoint;
type Raycaster = TypeDefs.Raycaster;

--- [[ Variables ]] ---
local Stepper: RBXScriptSignal = RunService.Heartbeat;

local ERR_EXPECTED: string = "invalid argument #%d (%s expected, got %s)";
local ERR_ARG_MISSING: string = "Argument %d missing or nil";

local AncestorName: string = "SwordSmithV2";

--- [[ Functions ]] ---
local function OfficialError(msg: string, callback: Debugger?)
	((callback or error) :: Debugger)(string.format("[%s]: %q", AncestorName, msg), 2);
end;

local function ExpectedGot(arg: number, expected: string, got: string)
	if got == "nil" then
		OfficialError(ERR_ARG_MISSING:format(arg));
	else
		OfficialError(ERR_EXPECTED:format(arg, expected, got));
	end;
end;

local function __tostring(): string
	return "Raycaster";
end;

local function Update(point: RaycastPoint)
	point._last_position = point._real.WorldPosition;
end;

local function Solve(point: RaycastPoint): (Vector3, Vector3)
	local current_pos: Vector3 = point._real.WorldPosition;

	if point._last_position == nil then
		point._last_position = current_pos;

		local last_pos: Vector3 = point._last_position :: Vector3;
		return last_pos, Vector3.new(0, 0, -last_pos.Unit.Z);
	end;

	local last_pos: Vector3 = point._last_position :: Vector3;
	return last_pos, current_pos - last_pos;
end;

--- [[ Private API ]] ---
getmetatable(RaycasterImpl :: any).__tostring = __tostring;

function RaycasterInternalAPI.Stop(raycaster: Raycaster)
	raycaster[RaycasterImpl].Stop();
end;

function RaycasterInternalAPI.Destroy(raycaster: Raycaster)
	raycaster[RaycasterImpl].Destroy();
end;

function RaycasterInternalAPI.Play(raycaster: Raycaster, cast_length: number?, params: RaycastParams): Result?
	return raycaster[RaycasterImpl].Play(cast_length, params);
end;

function RaycasterInternalAPI.create(tool: Tool, ignore_list: Dictionary<number, Instance>): Raycaster
	local impl: Dictionary<string, any> = {
		_playing = false,
		_ignore_list = ignore_list,
		_points = {},
	};
	
	local handle: BasePart = (tool:FindFirstChild("FakeHandle") or tool:FindFirstChild("Handle")) :: BasePart;
	for _, child: Instance in pairs(handle:GetChildren()) do
		if not child:IsA("Attachment") then
			continue;
		end;
		
		local raycastPoint: RaycastPoint = {
			_real = child :: Attachment,
			_last_position = (child :: Attachment).WorldPosition,
		};
		table.insert(impl._points, raycastPoint);
	end;
	
	impl._ray_params = RaycastParams.new();
	impl._ray_params.FilterDescendantsInstances = impl._ignore_list;
	impl._ray_params.FilterType = Enum.RaycastFilterType.Blacklist;
	
	function impl.Stop()
		impl._playing = false;
	end;
	
	function impl.Destroy()
		if impl._playing == true then
			impl.Stop();
		end;
		
		for key: string in pairs(impl) do
			impl[key] = nil;
		end;
	end;
	
	function impl.Play(
		cast_length: number?,
		do_destroy: boolean
	): Result?
		local cast_length: number = cast_length or 2;
		local t0: number = 0;
		local collision: boolean, result: Result = false, nil;
		
		impl._playing = true;
		
		local points: Dictionary<number, RaycastPoint> = impl._points;
		for _, point: RaycastPoint in pairs(points) do
			Update(point);
		end;
		
		local kill_function: () -> () = impl[do_destroy == true and "Destroy" or "Stop"];
		local params: RaycastParams = impl._ray_params;
		
		while
			collision == false and 
			impl._playing == true and 
			t0 < cast_length 
		do
			for _, point: RaycastPoint in pairs(points) do
				local origin: Vector3, direction: Vector3 = Solve(point);
				local raycast_result: RaycastResult = workspace:Raycast(origin, direction, params);

				if raycast_result ~= nil then
					local body_part: BasePart = raycast_result.Instance;
					local character: Model = body_part.Parent :: Model;
					local humanoid: Humanoid = character:FindFirstChildWhichIsA("Humanoid") :: Humanoid;

					if humanoid == nil then
						Trace(origin, direction, false)
						return;
					end;

					result = {
						BodyPart = body_part,
						Character = character,
						Humanoid = humanoid,
						Position = raycast_result.Position,
					};

					collision = true;
					Trace(origin, direction, true);
				else
					Trace(origin, direction, false);
				end;
			end;
			
			t0 += Stepper:Wait();
		end;
		
		kill_function();
		return result;
	end;
	
	return {
		Type = "Raycaster",
		[RaycasterImpl] = impl :: any,
	};
end;

--- [[ Public API ]] ---
local RaycasterPublicMeta: Dictionary<string, any> = {
	__index = Raycaster,
	__tostring = __tostring,
};

function Raycaster.new(tool: Tool, ignore_list: Dictionary<number, Instance>): any
	return setmetatable(RaycasterInternalAPI.create(tool, ignore_list), RaycasterPublicMeta);
end;

function Raycaster:Stop()
	RaycasterInternalAPI.Stop(self);
end;

function Raycaster:Destroy()
	RaycasterInternalAPI.Destroy(self);
end;

function Raycaster:Play(cast_length: number?, params: RaycastParams)
	return RaycasterInternalAPI.Play(self, cast_length, params);
end;

return Raycaster;

Using 2.1 version the sword dont hits a player even if it touches and the player is little bit far from hitter player. Does this is an issue in collision fedility.

Pls @Q_ubit fix this bug. Would be good if ray cast comes fast

how do you use the HumanoidHit function? i tried sword:HumanoidHit(function() but it just returns an error

alright it was actually sword:OnHumanoidHit(function() not just HumanoidHit

i would really appreciate it if you would make us have the ability to label the part that deals the damage, for example. i made a spear and found that every part of the tool can hurt other player when only tip should do so.

I just finished creating a usable copy of the new Sword Smith module. I plan to create a separate post detailing how to use it. Some of the key differences are:

  • Higher fidelity hit detection
  • More accurate hit-point position calculation
  • Built-in debounce
  • Signals which can be “connected” to

Model:
https://www.roblox.com/catalog/8959389592/Sword-Sm-th-V2

Can u tell how to use it? Waiting for it documentation.

I hope to have the documentation out by the weekend.

Apparently I’m late but I’m hoping for an answer, how to create a tag system on the sword?

Yep he is actually making sworsmith2 since long time but documentation is not released. It might have the feature to make ur own raycast attachment in sword

Can you add a feature that supports NPCs it will be easier to create enemies with swords

edit: Question(do you still maintain this Framework?)

@Q_ubit How do you use TargetKilled “a sample code needed

edit: how do use the events?

This framework is no longer maintained, I currently work on:

Sword Sm🗡️th [V2] - Roblox

There’s no documentation available because I decided to take more time on it.

In the version you’re talking about, using events can be done like so:

-- TargetKilled is fired every time your sword kills something
sword:OnTargetKilled(function()
   print("Something has been killed")
end)
1 Like

I tried it but it doesn’t print out the message

edit: other events are working expect the targetkilled

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

local tool = script.Parent
local debounce = false

local data = {
	swingAnims={7049668026},
	killPlayers = false,
	knockBackEnabled = true,
	knockBackStrength = 100
}

local sword = SwordSmith.new(tool, data)



tool.Activated:Connect(function()
	if debounce then
		return
	end
	
	debounce = true
	sword:Swing(1000, game.Workspace.ragdolls:GetChildren())
end);

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

-- TargetKilled is fired every time your sword kills something
sword:OnTargetKilled(function()
	print("Something has been killed")
end)