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

The best way to do that would be to put a bool value into the character of the person hit, and when they are hit search if the bool is true (or false) alternatively you could add an attribute to the humanoid or the character (this could be a string or another bool value) and search for the person hit.

This will be a feature of the next Sword Smith module I’m currently in the process of making right now, the current version is incapable of doing this. However, since this option isn’t currently available, it’s worth a shot to check out Raycast Hitbox, a free resource that allows you to create hitboxes very similar to those found in Sword Smith.

You are a very good person for doing this! Thank you so much for this amazing module, I got couple of suggestions first one blocking dmg etc the second one I would recommend you do a more in depth tutorial on what all of the different settings do etc!

I Know its a little old but im struggling to get the animtions to work everything else works great just wont play my animation

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

local tool = script.Parent
local debounce = false
local data = {swingAnims={8358215765},
knockBackEnabled = true,
knockBackStrength = 50,
criticalHits = 5, --How much damage is dealt to the target on a critical hit
criticalInterval = 1,
currentDamage = 2,
}

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)

Have you made sure that the AnimationPriority is set to action? Otherwise please reply with any errors you received.

1 Like

I’ll check that I don’t believe it is on action

It worked Thank you so much great module btw

How would I make an IDLE animation with this?

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
}

Paste ur idle animation in holdAnim

So would the IDLE be the last in the sequence?

U don’t need to add sequence

holdAnims = { --List of animations played in sequence when sword is equipped
		0000,
}

it is if u have multiple idle animation which will work at random if they are not looped or change after every swing if looped

Alright! Thanks!

got an output of the module updating and now there are couldn’t load module errors along with an expected end

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.