Replacement for Roblox's TagHumanoid function (Rewards based on damage contributed OR last hit)

Damage Module & Improved TagHumanoid function


Introduction

Hi! I’m about to release an open-sourced system for applying Status Effects which utilizes this function so I’m making this post first

You might have seen something called TagHumanoid in Roblox Gear scripts, which is used to indicate the last player who killed an enemy or another player. It does not let you know how much damage each player contributed if many people team up to fight.

This function allows you to replace the outdated method by Roblox. You can check not only the last hit, but also total damage contributed by each individual player. And BTW, the attacker can be either a player or an NPC, and the target can also be either.

On top of that, I have a function for dealing damage here, it automatically does the sanity checks as well as tagging the target upon dealing damage. You can even send info like ignoring defense or immunity or attack type (If the target has Defense or AttackType . . Immunity Attributes)

Usage:

  1. Create a new ModuleScript and place it somewhere accessible by other scripts
  2. Copy and paste the code below into it
  3. Use local DamageModule = require(Path_To_Module) and DamageModule:Damage(Args)
Source code:
local Module = {}

local Players = game:GetService("Players")

function Module:TagForDamage(Attacker: Damage_Dealer, Target, Damage: number)
	local Humanoid = Target:FindFirstChildOfClass("Humanoid")
	if not Humanoid or Humanoid.Health <= 0 then return end

	local PlayerTags = Target:FindFirstChild("PlayerTags")
	if not PlayerTags then
		PlayerTags = Instance.new("Configuration")
		PlayerTags.Name = "PlayerTags"
		PlayerTags.Parent = Target
	end

	if Attacker:IsA("Player") then
		local ExistingTag = PlayerTags:GetAttribute(Attacker.UserId)
		if ExistingTag then
			PlayerTags:SetAttribute(Attacker.UserId, ExistingTag + Damage)
		else
			PlayerTags:SetAttribute(Attacker.UserId, Damage)
		end
		
		if Damage >= Humanoid.Health then
			PlayerTags:SetAttribute("LastHit", Attacker.UserId)
		end
	elseif typeof(Attacker) == "Instance" then
		local ExistingTag
		for i, v in ipairs(PlayerTags:GetChildren()) do
			if v and v:IsA("ObjectValue") and v.Value == Attacker then
				ExistingTag = v
				break
			end
		end
		
		if ExistingTag then
			ExistingTag:SetAttribute("Damage", ExistingTag:GetAttribute("Damage") + Damage)
		else
			local Tag = Instance.new("ObjectValue")
			Tag.Value = Attacker
			Tag.Name = Attacker.Name
			Tag.Parent = PlayerTags
			Tag:SetAttribute("Damage", Damage)
		end

		if Damage >= Humanoid.Health then
			local LastHit
			for i, v in ipairs(PlayerTags:GetChildren()) do
				if v and v:IsA("ObjectValue") and v.Name == "LastHit" and v.Value ~= Attacker then
					v:Destroy()
					v = nil
				end
			end
			
			local Tag = Instance.new("ObjectValue")
			Tag.Name = "LastHit"
			Tag.Value = Attacker
			Tag.Parent = PlayerTags
		end
	end
end

function Module:Damage(Attacker: Damage_Dealer, Target: Target, Damage, IgnoreDefense, IgnoreImmunity, AttackType, Cooldown)
	if Attacker and Target and Target:FindFirstChildOfClass("Humanoid") and tonumber(Damage) ~= nil then
		local TargetHum = Target:FindFirstChildOfClass("Humanoid")
		if TargetHum.Health <= 0 then return end

		local AttackerIsPlayer = false
		local Character

		if Attacker:IsA("Player") then
			AttackerIsPlayer = true
			Character = Attacker.Character
		elseif Players:GetPlayerFromCharacter(Attacker) then
			AttackerIsPlayer = true
			Attacker = Players:GetPlayerFromCharacter(Attacker)
			Character = Attacker.Character
		end

		if Cooldown then
			if AttackerIsPlayer then
				if Target:GetAttribute(Attacker.UserId) then return end
				Target:SetAttribute(Attacker.UserId, true)

				task.delay(Cooldown, function()
					Target:SetAttribute(Attacker.UserId, nil)
				end)
			else
				if Target:GetAttribute(Attacker.Name) then return end
				Target:SetAttribute(Attacker.Name, true)

				task.delay(Cooldown, function()
					Target:SetAttribute(Attacker.Name, nil)
				end)
			end
		end

		if Character and Character:GetAttribute("Voided") then
			Damage = Damage / 2
		end
		if not IgnoreDefense and Target:GetAttribute("Defense") then
			Damage = Damage - (Damage * Target:GetAttribute("Defense") / 100)
		end
		if not IgnoreImmunity and AttackType and Target:GetAttribute(AttackType .. "Immunity") then
			Damage = Damage - Damage * (Target:GetAttribute(AttackType .. "Immunity") / 100)
		end
		Damage = math.clamp(Damage, 0, TargetHum.Health)

		Module:TagForDamage(Attacker, Target, Damage)
		TargetHum:TakeDamage(Damage)

		return Damage
	end
end

return Module
  • The function used in gears (Quite outdated)
-- Don't use this, the DamageModule is above if you somehow missed it
Module.TagHumanoid = function(Humanoid, Player)
	for i, v in pairs(Humanoid:GetChildren()) do
		if v:IsA("ObjectValue") and string.lower(v.Name) == "creator" then
			v:Destroy()
		end
	end

	local Creator_Tag = Instance.new("ObjectValue")
	Creator_Tag.Name = "creator"
	Creator_Tag.Value = Player
	Creator_Tag.Parent = Humanoid
end

Examples (These are also in the model I provided above)

Here are some examples to get you understand what this can be used for.
  • 1: Reward all players who dealt damage equal to at least 15% of the target’s MaxHealth
local MinimumDamagePercentageForReward = 0.15

local Rewards = {
	Coins = 100, BadgeID = 0
}

local Character = script.Parent
local Humanoid = Character:FindFirstChild("Humanoid")

local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")

local PlayerData = require(ServerScriptService.PlayerData.Manager) -- ProfileService datastore
local FunctionBank = require(Your_Badge_Rewarding_Function)

if Character:FindFirstChild("PlayerTags") then
	local PlayerTags = Character:FindFirstChild("PlayerTags")

	for UserId, Damage: number in PlayerTags:GetAttributes() do
		UserId = tonumber(UserId)

		if UserId then
			local Player = Players:GetPlayerByUserId(UserId)
			if not Player then continue end

			local Percent = Damage / Humanoid.MaxHealth

			if Percent >= MinimumDamagePercentageForReward then
				local RewardMulti = Percent / 2
				if RewardMulti > 0.5 then
					RewardMulti = 0.5
				end

				local profile = PlayerData.Profiles[Player]
				if profile then
					PlayerData.AdjustStat(Player, profile, "Kills", 1)
					PlayerData.AdjustStat(Player, profile, "Coins", math.round(Rewards.Coins * RewardMulti))
				end
				if Rewards.BadgeID then
					FunctionBank.AwardBadge(Player, Rewards.BadgeID)
				end
			end
		end
	end
end
  • 2: Touched damage in a Roblox sword
local DamageModule = require(game:GetService("ReplicatedStorage"):FindFirstChild("DamageModule"))

function Blow(Part)
	Part.Touched:connect(function(Hit)
		if not Hit or not Hit.Parent or not Equipped or not Character or not Character.Parent then
			return
		end
		local character = Hit.Parent
		local humanoid = character:FindFirstChild("Humanoid")

		if character ~= Character and humanoid and humanoid.Health > 0 then
		if not humanoid then return end
	    if humanoid.Parent == Tool then return end
		if game:GetService("Players"):GetPlayerFromCharacter(Hit.Parent) then return end
			DamageModule:Damage(Player, Hit.Parent, (Active and Damage.Slash) or Damage.Touch)
		end
	end)
end
  • 3: Dealing damage from a script in a projectile or a minion created by a player
local DamageModule = require(game:GetService("ReplicatedStorage"):FindFirstChild("DamageModule"))

DamageModule:Damage(script:FindFirstChild("creator").Value, TempChar, Damage)

Features

  • LastHit (The player/NPC who dealt the final hit)
  • Damage contribution (You can reward players based on the damage dealt percentage)
  • Defense/Immunity (Set Attributes to use this)
  • Cooldown (Time to wait before the same player/NPC can attack again)

Support

Sirs and madams this thing is (Not) simple feel free to use it for free
3 Likes

image

image

1 Like

Fixed a mistake of mine
It’s supposed to check for the immunity of the target, not the player dealing damage, sorry
I updated the model as well

if not IgnoreImmunity and AttackType and Target:GetAttribute(AttackType .. "Immunity") then
			Damage = Damage - Damage * (Target:GetAttribute(AttackType .. "Immunity") / 100)
		end

I updated the source code and the model
The attacker can be an NPC now, its name will be used to tag, which is not a unique value to indicate the correct attacker instance so you will have to manually add an ObjectValue in the tag function

Edit: The attacker can be an NPC now, the attacker character instance will be used to tag so it should be unique