Thanks for reading in advance.
In my little class-based fighter I’m making, one of the classes has an ability that throws a bouncing knife that damages and bleeds targets on hit. Aside from the richochet I’ve just implemented, the projectile’s behavior is working as intended. The knife is allotted ten wall-bounces before it’s destroyed on the next impact with a solid object - however, occasionally when throwing the knife it will either fail to register a raycast and destroy itself, or reflect in the same spot nigh instantaneously and use up the remaining bounces without actually moving.
local function projectile(cf, owner, ignore)
local p = game.Players:GetPlayerFromCharacter(owner)
local id = generateKey(7)
local hitdb = {}
local con = nil; local hit = nil
local knife = newPart(workspace, "Knife", size.X, size.Y, size.Z, 1, 0, false, false)
knife.CFrame = cf
if showBoxes then knife.Material = Enum.Material.Neon
knife.BrickColor = bcn("Really red"); knife.Transparency = .5
end
local o = knife.CFrame.p
local v = knife.CFrame.lookVector * speed
local bv = i_n("BodyVelocity")
bv.MaxForce = v3(math.huge, math.huge, math.huge)
bv.Velocity = v; bv.Parent = knife
pjadd:FireServer(id, o, v.unit, speed)
owner = owner or char
if owner ~= char then hitfx = nil end
knife.Touched:connect(function(hit)
local targ = hit.Parent
local hume = targ:FindFirstChild("Humanoid")
local ac = (p and isAlly(targ, p.TeamColor))
local f = vol + (pms/20)
f = math.min(f, vol * 1.5)
if not (hit:IsDescendantOf(owner) or hit:IsDescendantOf(cam)) and hit ~= ignore then
if hit.Name:lower() == "reflector" then local ref = hit:FindFirstChildWhichIsA("ObjectValue")
if ref then ref = ref.Value; else ref = hit end
local new = cfn(knife.Position, knife.Position + knife.CFrame.lookVector * -1)
knife:Destroy(); effect("clear", key); projectile(new, ref, hit)
elseif hit.CanCollide and not hume then print(id)
local normal = knife.CFrame.lookVector
local pos = knife.CFrame.p
local wr = ray(pos, normal * 100)
local hit, pos, sNorm = workspace:FindPartOnRayWithIgnoreList(wr, {char, cam, ignore})
if hit and rico < 10 then rico = rico + 1
local ref = normal - (2 * normal:Dot(sNorm) * sNorm)
local new = cfn(knife.Position, knife.Position + ref)
knife:Destroy(); effect("clear", key)
projectile(new, owner, hit)
else knife:Destroy(); effect("clear", key)
end
elseif hume and not hitdb[targ] and isAlive(targ) and not ac then hitdb[targ] = true
if checkStat(targ, "Reflect") then
local new = cfn(knife.Position, knife.Position + knife.CFrame.lookVector * -1)
knife:Destroy(); effect("clear", key); projectile(new, targ, hit)
else local loc = hit.Parent:FindFirstChild("Torso") or hrp
hitfx.bleed = {targ = targ, amt = 100, dur = 2}
bjunc(targ, knife.CFrame.p, id, f, dmg, hitfx, 3)
common("sound", {id = 160432334, v = 1, pos = loc})
end
end
end
end)
con = reflect.OnClientEvent:connect(function(k, ref)
if k == key then con:disconnect()
local new = cfn(knife.Position, knife.Position + knife.CFrame.lookVector * -1)
knife:Destroy(); effect("clear", key); projectile(new, ref, hit)
end
end)
knife.AncestryChanged:connect(function()
con:disconnect()
end)
effect("knife", {cf = knife.CFrame, col = tCol, d = v.unit, s = speed, lt = lt, key = key})
game:GetService("Debris"):AddItem(knife, lt)
end
For clarification, ‘cf’ is the origin CFrame where the projectile is created, ‘owner’ is the character (or reflector, sometimes) that shot the projectile, and ‘ignore’ is a situational argument that is used whenever the projectile is reflected that prevents the newly created reflection from triggering a hit event against the part that reflected it.
When projectiles in my game are reflected, due to the nature of visual-only replication I’m using, I don’t alter the trajectory of an existing part; I delete the old projectile and use recursion to create a new one from the position the previous projectile was reflected from, as you’ll see by examining this function.
As previously mentioned, the projectile’s behavior is completely functional aside from the ricochet, which is contained within this snippet:
elseif hit.CanCollide and not hume then print(id)
local normal = knife.CFrame.lookVector
local pos = knife.CFrame.p
local wr = ray(pos, normal * 100)
local hit, pos, sNorm = workspace:FindPartOnRayWithIgnoreList(wr, {char, cam, ignore})
if hit and rico < 10 then rico = rico + 1
local ref = normal - (2 * normal:Dot(sNorm) * sNorm)
local new = cfn(knife.Position, knife.Position + ref)
knife:Destroy(); effect("clear", key)
projectile(new, owner, hit)
else knife:Destroy(); effect("clear", key)
end
The ‘projectile’ function is localized to the ability function itself, thus explaining the absence of the ‘rico’ variable in the chunk I provided. I can post the entire ability function if needed. If anyone has any explanation for the odd behavior, I’d appreciate your time.