Intro
Combat logging is logging out on a game in a middle of a fight (in ROBLOX’s case, leaving a game) to avoid dying and potential repercussions from dying, like losing items. This happens because if a player leaves mid-combat, they would be leaving at a point where they would have had no punishment, which means that it would be saved when they leave and loaded back into the next server they joined, since they left before they could potentially die in the first server. This can be a major issue for a lot of games if it isn’t accounted for, because it allows players to instantly leave mid-fight without any of the consequences of potentially dying. This can get really annoying for genuine players looking for a fight because of the fact that the other player can just leave and not lose anything whatsoever. This would make PvP unfair and probably defeat some, if not a lot of its purpose in your game.
This is why I’m making a tutorial on it so that games with PvP (especially if it’s a big part of your game) are secure against combat logging. If there is no punishment (ex. Losing inventory items) from dying in your game, then you really don’t need to account for combat logging, unless you still want to punish the player for leaving the game mid-combat.
To understand how to make an anti-combat logger, we will need to learn three things:
- Tagging players
- WHEN to tag players
- How to catch the players
Player Tagging
In a game with combat, you would probably need to keep track of who is killing who, and this is done most of the time by inserting an ObjectValue tag into the humanoid who was killed, and then setting the value of it to the player who got the kill.
Something like this, where humanoid
is the Humanoid of the victim, and tagger
is the Player object of the killer:
local debris = game:GetService("Debris")
local function tag(humanoid, tagger)
local old = humanoid:FindFirstChild("creator")
if old then
old:Destroy() -- destroy any old creator tags
end
local creator = Instance.new("ObjectValue")
creator.Value = tagger
creator.Name = "creator"
creator.Parent = humanoid
debris:AddItem(creator, 4)
end
This is simple and efficient, and allows you to have full access and do whatever you want to both sides: the victim, and the killer.
This is a use case example. It would print who killed the victim whenever the victim dies:
local player = game.Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local humanoid = char:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
local tag = humanoid:FindFirstChild("creator")
if tag then
print(player.Name, "was killed by "..tag.Value.Name .. ".")
end
end)
When to tag
You should be tagging players whenever they take damage.
ALL damage and tagging should be handled on the server. So, whenever the server passes a hit as successful and damages the victim, you should be tagging the victim right then. Make sure that you’re also clearing any tags already in the humanoid like I did in the function earlier, so that you avoid multiple tags in one player. Unless you want to do an assists system which is where you would need multiple tags, always make sure there’s only one tag in the humanoid to avoid any confusion and a script potentially returning the wrong tag when you want check for one anywhere, like when a player dies.
Example:
humanoid:TakeDamage(50) -- server applies damage
tag(humanoid, tagger) -- tag the player
Applying this method to create an anti-combat log
The previous 2 sections of this tutorial aren’t really the main focus of it though, it was just for you to get a context and understanding of how to do the actual anti-combat log, which is using the same method.
The easiest and most efficient way to do this (in my opinion) is to use Values and store them somewhere on the server. The service Debris is incredibly useful for this since Values are instances, which means we can easily clean them up using its method :AddItem(item, t)
, which would delete item
after waiting for t
seconds, just in one line of code, instead of having to write a hacky or overcomplicated method in a script to do the same thing to a non-instance. Using tables and caching in a script would look “cleaner” (not really, it’s just more tedious), but would be way harder to clean up unless you’re caching actual instances in the table, which would pretty much defeat the purpose of using the table when you could just use this method.
Setting up and implementing
Alright, let’s set this up! This isn’t hard to do at all.
First, make a folder somewhere on the server to store the combat log tags we’ll be creating. I’ll put mine in ServerStorage.
Now, we need to be actively creating combat log tags whenever a player is damaged. Let’s make a function for that, and I’ll explain how it works.
local debris = game:GetService("Debris")
local serverStorage = game:GetService("ServerStorage")
local function combatLog(humanoid)
local old = serverStorage.CombatLog:FindFirstChild(humanoid.Parent.Name) -- check for any old tag the player has in the folder
if old then
old:Destroy() -- if one is found, delete it since we're going to create one again right after,
-- which would "reset" the combat log timer
end
local combat = Instance.new("StringValue") -- the type of object you create literally doesn't matter at all, only the name of it does
combat.Name = humanoid.Parent.Name -- make sure you're setting the name of this
-- object to the name of the player who was damaged,
-- which would also be the parent of the humanoid's
-- name (the character's name)
combat.Parent = serverStorage.CombatLog
debris:AddItem(combat, 15) -- Add the tag for the amount of seconds you please.
end
Here, we clear any old tags, create a new tag, and then schedule for its deletion for 15 seconds after it was created. The amount of time you add the tag for is also the minimum amount of seconds the player needs to stay in the game for after being tagged by the anti-combat logger for a combat log not to be recorded when they leave.
Here, I set it to 15 seconds. This means, if I took damage from another player, I would have to stay in the game for at least 15 seconds after the hit, or else the anti-combat logger would catch me leaving and record it as a combat log, and then punish me. If I got hit again by someone, then the “timer” resets, and I would have to wait another 15 seconds, or else, again, a combat log would be recorded if I left.
To implement this efficiently, simply just paste this function in a module and then require the module and use the function in it whenever you are normally dealing damage to someone on the server, like the “creator” tags I explained earlier.
Module creation example
local serverStorage = game:GetService("ServerStorage")
local debris = game:GetService("Debris")
local logging = {}
function logging.combatLog(humanoid)
local old = serverStorage.CombatLog:FindFirstChild(humanoid.Parent.Name)
if old then
old:Destroy()
end
local combat = Instance.new("StringValue")
combat.Name = humanoid.Parent.Name
combat.Parent = serverStorage.CombatLog
debris:AddItem(combat, 15)
end
return logging
Module Usage
local serverStorage = game:GetService("ServerStorage")
local logModule = require(serverStorage.Modules.CombatLogModule)
-- bla bla bla, say you confirm a hit and deal damage here, and the humanoid, tagger,
-- and original tag function (shown at the beginning) are defined
humanoid:TakeDamage(50)
tag(humanoid, tagger)
logModule.combatLog(humanoid) -- paste this anywhere somewhere is taking damage, on the SERVER
Catching Combat Logs
Now that you have this set up, it’s pretty easy to catch people from here. Now, when a player leaves, you just have to check if there’s a tag with their name in the combat log folder we created earlier. If there is, you’ve caught a combat log! At this point, you’re free to do whatever you want to the player before saving their data.
Code on how this would be done:
local serverStorage = game:GetService("ServerStorage")
local debris = game:GetService("Debris")
local players = game:GetService("Players")
local logFolder = serverStorage.CombatLog
players.PlayerRemoving:Connect(function(player)
local inCombat = logFolder:FindFirstChild(player.Name) -- check for a tag
if inCombat then -- If this exists, a combat log is caught!
inCombat:Destroy() -- remove the tag, since we caught it already
-- do whatever you want to the player here before you save their data
end
end)
I only remove all of a player’s weapons (guns, melee, utility) from their inventory before saving it if a combat log is caught. I let them keep everything else.
My code to catch and punish combat loggers (This is just what I do, and I'm showing it to give you guys some ideas):
game.Players.PlayerRemoving:Connect(function(player)
if not rs:IsStudio() then
local key = tostring("Player_"..player.UserId)
local dataPack = playerData[key]
local log = combatLog:FindFirstChild(player.Name)
if log and dataPack then -- it's a combat log, PUNISH
-- print("Combat logged")
for name, tbl in pairs(dataPack["Inventory"]) do -- Removing weapons from inventory
if name == "Normals" then
for number, item in ipairs(tbl) do
if table.find(weaponNames, item) then
table.remove(tbl, number)
end
end
end
end
for index, name in pairs(dataPack["Input Order"]) do -- Remove weapons from hotbar
if name then
if table.find(weaponNames, name) then
dataPack["Input Order"][index] = false
end
end
end
end
if dataPack then
DataModule.SaveData(player, testdatastore, dataPack)
end
playerData[key] = nil
end
end)
Conclusion
Combat loggers and the ability to combat log can really ruin the balance and flow of PvP/combat in a game.
But, after successfully doing this tutorial, you should have a fool-proof anti-combat logger! As long as you’re tracking this on the server, it can’t be spoofed in any way, since all the tracking is on the server and isn’t getting or needing any info from clients. Hopefully you learned something from this, and have a good day!
Also keep this in mind, very well said by @BanTech