How to Catch Combat Logging

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.

image

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

image

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

65 Likes

This was so helpful, always wondering how to catch combat logging.

4 Likes

Glad you enjoyed the tutorial and found this of use! :smile:

1 Like

It is worth adding: try not to punish too harshly - i.e. don’t over-punish beyond what would happen if they lost the battle. There are many ways to accidentally leave a game, including internet issues, server shutdown, computer crash, program crash and even just accidentally clicking the X.

You don’t want to put players at an excessive disadvantage in these situations, as that may dissuade them from playing altogether. Assuming a player has lost the fight when they leave is as far as I would personally go.

11 Likes

Yeah, I understand that, definitely. I completely agree. Which is why I don’t do something like take away their entire inventory and overpunish. The normal repercussion of dying in my game is actually losing your entire inventory, which I probably should’ve mentioned to give a better sense of perspective. Well said, I shouldve clarified something like that.

2 Likes

There is a game called “Stay alive an flex your time alive”, the way they really simply stop combat logging is they just take 60 points away from their current amount. Good and detailed post though.

1 Like

Okay? What does that have to do with me though? Every game has different mechanics. This post was just made to teach people how to catch combat logging, it’s up to you to choose how to punish them. Thanks for the nice comment though.

1 Like

I like this method of catching combat logging. Rogue Lineage uses something else, and it’s really hard to leave the game without getting caught as combat logging (it feels like they didn’t account for players leaving normally).

They probably lost a decent amount of players this way. It’s important to create a system that works most of the time that keeps false positives to a minimum.

3 Likes

Can someone make an updated version of this since it’s kind of outdated now. Also I’ve tried doing this myself but could not get it work at all. Any help would be nice and greatly appreciated.

Needed this, preciate it bro :heart:

How were you able to get yours to work? I tried doing this but I was unable to do so. Any tips?