simply make a module for it instead of injecting it into every script
Hm but still wouldn’t I have to completely change how the powers work (so they use one single module)?
make a dmg calculator module. This is also useful if you have abilites that increase and reduce damage. When an ability finds a target instead of doing dmg right away fire the module, which would then change the value of the boolvalue inside the victim
Make a function (on the server side) that’s like TakeDamage, but stores the damage a player has dealt to a humanoid. We are going to make a drop-in replacement for TakeDamage that will keep track of how much damage a boss has been dealt by which players.
Make a ModuleScript that the function is in, so you don’t have to put the function in every script that deals damage to bosses.
The function, which is now better named something like PlayerDamagesHumanoid
not TakeDamage
, should be something like this—
-- ModuleScript
local module = {}
local prefix = "damage-"
function module.PlayerDamagesHumanoid(player, humanoid, damage)
-- Store the damage dealt
-- I've chosen to store the damage in IntValues in the Humanoid itself
-- The benefits are that they automatically get cleared when the
-- boss is removed from the map, and the boss's death event
-- can find the data easily
-- However, all clients can see the IntValues appear and change
-- This may not be a bad thing e.g. if you do want to make a live
-- boss damage leaderboard
-- Find or create the IntValue, which is named after the player with
-- a prefix before it (this prevents conflicts with other unrelated things
-- inside the hunanoid)
local name = prefix .. player.Name
local record = humanoid:FindFirstChild(name)
if record == nil then
record = Instance.new("IntValue")
record.Name = name
record.Parent = humanoid
end
-- Increase the value
record.Value = record.Value + damage
-- Damage the humanoid
return humanoid:TakeDamage(damage)
-- We assume that this is always successful and the Humanoid is
-- not protected by a ForceField - you'll have to deal with it
-- yourself if so
end
return module
Name the script DamageRecorder for example, put it in ServerScriptService and require it in your scripts like this:
local DamageRecorder = require(game:GetService("ServerScriptService").DamageRecorder)
Now search every single script for a :TakeDamage() against a boss.
In these scripts, require the above module. Replace all humanoid:TakeDamage(damage)
with DamageRecorder.PlayerDamagesHumanoid(player, humanoid, damage)
. Note that you need to know which player did the attack!
Eventually, the boss will die.
It should then loop through all of the children of its Humanoid (or where-ever the damage records are stored) and recover all the damage records of the players. Something like this—
-- Place in the same module as the above function
local Players = game:GetService("Players")
function module.RetrieveDamageRecords(humanoid)
local playersAndDamage = {}
for _,v in ipairs(humanoid:GetChildren()) do
-- Is this a damage record? Does its name start with the prefix?
if string.sub(v.Name, 1, #prefix) == prefix then
-- This is a damage record.
local playerName = string.sub(v.Name, #prefix + 1) -- Get player name
local playerDamage = v.Value
-- But first, is the player actually still in the game?
-- Otherwise, you might end up giving awards to players who
-- are long gone, and other annoying missteps related to various
-- player stuff being gone (no character, no leaderstats etc.)
local player = Players:FindFirstChild(playerName)
if not player then continue end
-- We need to put this info in playersAndDamage somehow
-- I opted to make it an array of record objects/tables, each
-- object containing the player and their damage.
table.insert(playersAndDamage, {
player = player,
damage = playerDamage
})
end
end
return playersAndDamage
end
The script that handles boss death has to require the module and use DamageRecorder.RetrieveDamageRecords(humanoid)
to get all the players that damaged it (if they used PlayerDamagesHumanoid
to do so!!!)
To get the best player, either iterate over all of the records and find the highest one (similarly to the ‘get nearest player’ function that’s in every zombie and such), or sort the table if you want the n
best players.
local function sortfunc(a, b) return a.damage < b.damage end
-- Your creature's humanoid
local hum
local records = DamageRecorder.RetrieveDamageRecords(hum)
-- Sort the records from most to least damage dealt
table.sort(records, sortfunc)
-- Kill the player who dealt the most damage
-- You might want to reward them instead
records[1].player.Character:BreakJoints()
Edit:
there’s a bunch of powers/things that can hit the boss, and I don’t want to have it insert a value into the boss for each attack
There isn’t much of a way around it. There needs to be some kind of evidence that it was this or that particular player who damaged something. If you leave a lego on the ground and someone steps on it, then there’s no way to know it was you who dropped it, unless your name is on the lego (i.e. putting a value on an attack that refers to who attacked – you have to do this yourself), or someone saw you drop the lego (i.e. storing who made which attack in some table like whoAttacked[brick] = player – again, nothing does this by default)
The method in my post needs only a creator
value on each projectile, and a value for each player who has hit a humanoid - it’s not hard to manage if you make it easy to manage by creating functions that hide all the pain from you.
Thank you! Hope you are safe & healthy during this time. I will definitely use this. Would you recommend involving metatables or nah?
I am, thank you too!
I don’t know how one would involve them in a useful way. At best, you could add a metatable to RetrieveDamageRecords
’s result that finds the damage and rank of a certain player in it.
So, playersAndDamage[2]
would give you the second top damager, e.g. `{player = game.Players.Player5, damage = 10000}, but this:
function getResultsOfPlayer(t, key)
for i,v in ipairs(t) do
if v.player == key then
return v.damage, i
end
end
return nil -- player not found!
end
setmetatable(playersAndDamage, {__index = getResultsOfPlayer})
would make using playersAndDamage[game.Players.Player2]
return 10000, 2
(his damage and his rank)
The reason why it’s not necessary is because
1, there’s no good reason to do it;
2, you probably run through the entire results table and reward each player in it, instead of running through every online player and rewarding it according to what its data is in the table (the other way around);
3, it’s just as simple to have any function you’d use in a metatable, as a free function
getResultsOfPlayer(playersAndDamage, player)
or as a method
playersAndDamage:getResultsOfPlayer(player)
as it is to have it be a metatable __index or such
playersAndDamage[player]
Metatables are mostly meant to hide details from you so you can focus on other things. If hiding something doesn’t make thinking about how your code works easier, then you’re best off not using metatables at all
Hellol! I was trying to use that code but it give an error
Unknown Global “prefix”
how can I fix it? you didn’t said anything about local prefix at your code
here
I am a little bit confused
Good evening, I got the notification for this thread just fine, but don’t visit the forum daily these days
I was confused about that variable, too. Looks like prefix
is "damage-"
because that’s what the script prepends to the damage record’s name.
(i.e. if HelloThere attacked a humanoid, then it’d have an intvalue named damage-HelloThere
in it and the script would have to skip to the player name in that value’s name)
I didn’t catch this because I send solutions on this forum without testing them or even just pasting them into studio for a moment to see the warnings.
While you’re here, have a look at this other blunder in my code:
-- But first, is the player actually still in the game?
-- Otherwise, you might end up giving awards to players who
-- are long gone, and other annoying missteps related to various
-- player stuff being gone (no character, no leaderstats etc.)
local player = Players:FindFirstChild(playerName)
The comment talks a lot of smack about the player being gone, but then the code does nothing about it.
You should add a check for a missing player after it:
if not player then continue end
Hello Thank you for returning and I apologize for that 2 year thing.
well I tested and there is many issue with the code!.
first of all
local players= game:GetService("Players")
local function sortfunc(a, b) return a.damage < b.damage end
function module.RetriveDamageRecords(humanoid)
local playersandDamage = {}
for i,v in ipairs(humanoid:GetChildren()) do
local prefix = "damage-"
local playerName = string.sub(v.Name, #prefix + 1)
local playerDamage = v.Value
print("Test1")
if string.sub(v.Name, 1, #prefix) == prefix then
print("Test2")
local player = players:FindFirstChild(playerName)
if not player then continue end
table.insert(playersandDamage,{player = player, damage = playerDamage})
end
table.sort(playersandDamage, sortfunc)
end
return playersandDamage
end
return module
this will print Test1 but won’t print Test2 so
after that just to make sure I replaced #prefix with 7 which is equal to #prefix since prefix is “damage-” local prefix ="damage-"
doesn’t working!
so I didn’t know how to fix it so I tried removing prefix lines like this:
function module.RetriveDamageRecords(humanoid)
local playersandDamage = {}
for i,v in ipairs(humanoid:GetChildren()) do
local playerName = v.Name
local playerDamage = v.Value
local player = players:FindFirstChild(playerName)
if not player then continue end
table.insert(playersandDamage,{player = playerName, damage = playerDamage})
table.sort(playersandDamage, sortfunc)
end
return playersandDamage
end
return module
and then
printing module.RetriveDamageRecords(Humanoid)
show this in output
so we can assume prefix is not “damage-”
second thing is even there is only 2 player in the game. table shows 6 data
like this
I wondered if you can help with them
Thanks for that!!!
and this is a bit personal but how can I give players reward only if his/her damage is bigger than %10 of npc’s max hp?
like how can I get the damage she deal from another script
sorry if this is a stupid question But I just started learning about modules
if NPCHumanoid then -- Checks Humanoid
NPCHumanoid.Died:Connect(function()
print("a Humanoid is dead")
print(module.RetriveDamageRecords(NPCHumanoid))
local npcTagger = NPCHumanoid.Hitter.Value -- the player who tagged the humanoid
local HitterTag = NPCHumanoid.Hitter -- The tag inside the Humanoid.a ObjectValue (```ObjectValue.Value = Player```)
if npcTagger ~= nil and HitterTag then --Check the player and tag
local amount = Rewards.Coins(npcTagger,NpcModule[Npc.Name].Stat,true,false,true)
print(amount)
npcTagger.leaderstats.Coins.Value += amount
print("Test2")
end
end)
end
No worries, I’m happy to help.
At a glance, you can see that these extra damage records are bogus. The damage
field is supposed to have a number in it, not a player. I don’t see any place where a broken damage record like that would be inserted.
There is only one true damage record corresponding to each player.
I can’t even guess where those are coming from.
The “damage-” prefix thing was put there to ensure that the script doesn’t see unrelated values in there and think they’re damage records.
For example, another script might put a value in the a humanoid named “DistanceTraveled”. If that humanoid dies while a player named DistanceTraveled is in the game, then the script will think DistanceTraveled did all of that damage, when in reality, he might’ve been far away.
It isn’t strictly necessary, but helps when there are other scripts putting anything at all into Humanoids.
In fact, nevermind, it needs some sort of protection against random stuff in the Humanoid. Haven’t you seen any errors?
You should’ve looked at the other code block in my original post and noticed that the prefix was put into the name there.
and this is a bit personal but how can I give players reward only if his/her damage is bigger than %10 of npc’s max hp?
like how can I get the damage she deal from another script
You’ll need to make the script actually use the values.
Right now, that code only prints the damage records, but then does nothing with them.
Here’s how that would look:
if NPCHumanoid then -- Checks Humanoid
NPCHumanoid.Died:Connect(function()
print("a Humanoid is dead")
local records = module.RetriveDamageRecords(NPCHumanoid)
print(records)
local threshold = NPCHumanoid.MaxHealth * 0.1 -- 10% of the max health
for _,record in ipairs(records) do
local player = record.player
if record.damage >= threshold then
local amount = Rewards.Coins(player,NpcModule[Npc.Name].Stat,true,false,true)
print(player.Name .. "dealt enough damage to earn " .. amount .. " coins: " .. record.damage)
player.leaderstats.Coins.Value += amount
end
print(player.Name .. "did not deal enough damage: " .. record.damage)
end
end
end)
end
Note that the sum of the damage records might be more than the humanoid’s max health, either because the humanoid healed or because something unrelated was taken to be a damage record.
I will be going back and editing my original post to fix some of the flaws in it, too.
I will test it in 30 minutes Thanks! really thanks I needed help with it so much!
They are probably caused because of other tag’s I added
Yep just tested and yeah They are caused because of the tags other scripts add to humanoid not some random stuff but.
well this is bad because in a server with 12 player there could be 36 table data could be and this is bad…
Oh I just checked you edited original post I will test is right now
ANDD Yeah It now only prints the DamageRecord Value
and how it looks with 2 players
But
I got this error while trying the code you gave for %10 thing
this is the script I used
if NPCHumanoid then -- Checks Humanoid
NPCHumanoid.Died:Connect(function()
print("a Humanoid is dead")
local DamageRecord = module.RetriveDamageRecords(NPCHumanoid)
print(module.RetriveDamageRecords(NPCHumanoid))
local MinDamageShoud = NPCHumanoid.MaxHealth * 0.1
for i, v in ipairs(DamageRecord) do
local Player = DamageRecord.player
print(MinDamageShoud)
print(DamageRecord.damage) -- it returns nil here
if DamageRecord.damage >= MinDamageShoud then
local amount = Rewards.Coins(Player,NpcModule[Npc.Name].Stat,true,false,true)
print(Player.Name .. "dealt enough damage to earn " .. amount .. " coins: " .. DamageRecord.damage)
Player.leaderstats.Coins.Value += amount
end
print(Player.Name.."Didnt dealt enough damage")
end
print("Test2")
end)
end
did I made a mistake there?
You need to learn to read the errors, use better variable names and actually slow down, think and understand the code before you change it or write more of it.
The error says “attempt to compare number <= nil”. That line has DamageRecord.damage >= MinDamageShoud
on it. Therefore, DamageRecord.damage
is nil.
DamageRecord
is singular, so it might seem to you that it’s one of the records, when it’s actually the collection of all records.
That table doesn’t have a damage
in it, it has records in it that each have a damage
.
A less misleading variable name would’ve been DamageRecords
.
Your error came from mistakenly using DamageRecord
instead of v
in that loop.
Replacing DamageRecord.player
and DamageRecord.damage
with v.player
and v.damage
should fix the error because v
is one of the damage records.
Read about generalized for
loops (the ones with pairs/ipairs instead of for i = 1, 10
etc.) to understand how they are used.
Furthermore, this:
print(Player.Name.."Didnt dealt enough damage")
will print every time, even if the player did deal enough damage.
This was probably a mis-transcription from a lack of understanding of what the code around it is supposed to be doing.
The funny part is. it was literally copy of your code.(This part is not a insult or something(Just don’t want to misunderstand)). No worries after sending it I figure out. I was thinking about posting it but roblox did not allow 3 post in a row. well I could edit the previous one but it should look ugly!
but Thanks! I really appreciate it! Have a great day