├── version.md ├── .gitignore ├── sound └── damagelogs │ ├── vote_no.wav │ ├── vote_yes.wav │ └── vote_failure.wav ├── lua ├── damagelogs │ ├── shared │ │ ├── chat.lua │ │ ├── rdm_manager.lua │ │ ├── defines.lua │ │ ├── lang.lua │ │ ├── privileges.lua │ │ ├── events │ │ │ ├── autoslay.lua │ │ │ ├── nades.lua │ │ │ ├── credits.lua │ │ │ ├── bodyfound.lua │ │ │ ├── dna.lua │ │ │ ├── weapons.lua │ │ │ ├── drownings.lua │ │ │ ├── equipment.lua │ │ │ ├── suicide.lua │ │ │ ├── fall_damage.lua │ │ │ ├── health.lua │ │ │ ├── ttt_radio.lua │ │ │ ├── c4.lua │ │ │ ├── kills.lua │ │ │ ├── damages.lua │ │ │ └── misc.lua │ │ ├── sync.lua │ │ ├── events.lua │ │ ├── notify.lua │ │ └── lang │ │ │ ├── simplified_chinese.lua │ │ │ ├── french.lua │ │ │ └── polish.lua │ ├── config │ │ ├── mysqloo.lua │ │ ├── config.lua │ │ └── config_loader.lua │ ├── client │ │ ├── drawcircle.lua │ │ ├── colors.lua │ │ ├── filters.lua │ │ ├── weapon_names.lua │ │ ├── tabs │ │ │ └── shoots.lua │ │ ├── info_label.lua │ │ ├── settings.lua │ │ ├── init.lua │ │ └── listview.lua │ └── server │ │ ├── sqlite.lua │ │ ├── recording.lua │ │ ├── damageinfos.lua │ │ ├── discord.lua │ │ ├── init.lua │ │ ├── oldlogs.lua │ │ └── chat.lua └── autorun │ └── damagelog_autorun.lua ├── addon.json └── README.md /version.md: -------------------------------------------------------------------------------- 1 | 3.12.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gma 2 | 3 | -------------------------------------------------------------------------------- /sound/damagelogs/vote_no.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BadgerCode/tttdamagelogs/HEAD/sound/damagelogs/vote_no.wav -------------------------------------------------------------------------------- /sound/damagelogs/vote_yes.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BadgerCode/tttdamagelogs/HEAD/sound/damagelogs/vote_yes.wav -------------------------------------------------------------------------------- /sound/damagelogs/vote_failure.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BadgerCode/tttdamagelogs/HEAD/sound/damagelogs/vote_failure.wav -------------------------------------------------------------------------------- /lua/damagelogs/shared/chat.lua: -------------------------------------------------------------------------------- 1 | DAMAGELOG_ADMIN = 1 2 | DAMAGELOG_VICTIM = 2 3 | DAMAGELOG_REPORTED = 3 4 | DAMAGELOG_OTHER = 4 5 | STOPCHAT_NOADMIN = 1 6 | STOPCHAT_FORCED = 2 -------------------------------------------------------------------------------- /lua/damagelogs/config/mysqloo.lua: -------------------------------------------------------------------------------- 1 | Damagelog.MySQL_Informations = { 2 | ip = "127.0.0.1", 3 | username = "", 4 | password = "", 5 | database = "", 6 | port = 3306 7 | } -------------------------------------------------------------------------------- /addon.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "TTT Damage Logs & RDM Manager", 3 | "description": "", 4 | "type": "servercontent", 5 | "tags": [ ], 6 | "ignore": [ 7 | ".git/*", 8 | "README.md" 9 | ] 10 | } -------------------------------------------------------------------------------- /lua/damagelogs/shared/rdm_manager.lua: -------------------------------------------------------------------------------- 1 | RDM_MANAGER_WAITING = 1 2 | RDM_MANAGER_PROGRESS = 2 3 | RDM_MANAGER_FINISHED = 3 4 | DAMAGELOG_REPORT_STANDARD = 1 5 | DAMAGELOG_REPORT_ADMIN = 2 6 | DAMAGELOG_REPORT_FORCE = 3 7 | DAMAGELOG_REPORT_CHAT = 4 -------------------------------------------------------------------------------- /lua/damagelogs/shared/defines.lua: -------------------------------------------------------------------------------- 1 | -- ROLES 2 | -- Defined in terrortown/gamemode/shared.lua 3 | -- ROLE_INNOCENT = 0 4 | -- ROLE_TRAITOR = 1 5 | -- ROLE_DETECTIVE = 2 6 | DAMAGELOG_ROLE_SPECTATOR = -3 -- Used when a player is spectating at the start of a round 7 | DAMAGELOG_ROLE_JOINAFTERROUNDSTART = -2 -- Used when a player joins after the round has already started 8 | DAMAGELOG_ROLE_DISCONNECTED = "disconnected" -- Used when a player uses a radio command on a disconnected player's dead body 9 | -------------------------------------------------------------------------------- /lua/damagelogs/shared/lang.lua: -------------------------------------------------------------------------------- 1 | DamagelogLang = DamagelogLang or {} 2 | 3 | for _, v in pairs(file.Find("damagelogs/shared/lang/*.lua", "LUA")) do 4 | local f = "damagelogs/shared/lang/" .. v 5 | 6 | if SERVER then 7 | AddCSLuaFile(f) 8 | end 9 | 10 | include(f) 11 | end 12 | 13 | function TTTLogTranslate(GetDMGLogLang, phrase, nomissing) 14 | local f = GetDMGLogLang 15 | 16 | if Damagelog.ForcedLanguage == "" then 17 | if not DamagelogLang[f] then 18 | f = "english" 19 | end 20 | else 21 | f = Damagelog.ForcedLanguage 22 | end 23 | 24 | return DamagelogLang[f][phrase] or DamagelogLang["english"][phrase] or LANG.TryTranslation(phrase) or not nomissing and "Missing: " .. tostring(phrase) 25 | end -------------------------------------------------------------------------------- /lua/autorun/damagelog_autorun.lua: -------------------------------------------------------------------------------- 1 | -- if engine.ActiveGamemode() == 'terrortown' then 2 | Damagelog = Damagelog or {} 3 | Damagelog.VERSION = "3.12.0" 4 | 5 | if not file.Exists("damagelog", "DATA") then 6 | file.CreateDir("damagelog") 7 | end 8 | 9 | Damagelog.User_rights = Damagelog.User_rights or {} 10 | Damagelog.RDM_Manager_Rights = Damagelog.RDM_Manager_Rights or {} 11 | 12 | function Damagelog:AddUser(user, rights, rdm_manager) 13 | self.User_rights[user] = rights 14 | self.RDM_Manager_Rights[user] = rdm_manager 15 | end 16 | 17 | if SERVER then 18 | AddCSLuaFile() 19 | AddCSLuaFile("damagelogs/client/init.lua") 20 | include("damagelogs/server/init.lua") 21 | else 22 | include("damagelogs/client/init.lua") 23 | end 24 | -- else 25 | -- print("Gamemode is not TTT") 26 | -- end 27 | -------------------------------------------------------------------------------- /lua/damagelogs/shared/privileges.lua: -------------------------------------------------------------------------------- 1 | -- edit the privileges on shared/config.lua 2 | local function checkSettings(self, value) 3 | local round_state = GetRoundState() 4 | 5 | if value == 1 or value == 2 then 6 | return round_state ~= ROUND_ACTIVE 7 | elseif value == 3 then 8 | return round_state ~= ROUND_ACTIVE or self:IsSpec() 9 | elseif value == 4 then 10 | return true 11 | end 12 | 13 | return false 14 | end 15 | 16 | local meta = FindMetaTable("Player") 17 | 18 | function meta:CanUseDamagelog() 19 | local value = Damagelog.User_rights[self:GetUserGroup()] 20 | if value then 21 | return checkSettings(self, value) 22 | end 23 | 24 | return checkSettings(self, 2) 25 | end 26 | 27 | function meta:CanUseRDMManager() 28 | return Damagelog.RDM_Manager_Rights[self:GetUserGroup()] 29 | end -------------------------------------------------------------------------------- /lua/damagelogs/client/drawcircle.lua: -------------------------------------------------------------------------------- 1 | -- http://wiki.garrysmod.com/page/surface/DrawPoly 2 | function Damagelog.DrawCircle(x, y, radius, seg) 3 | local cir = {} 4 | 5 | table.insert(cir, { 6 | x = x, 7 | y = y, 8 | u = 0.5, 9 | v = 0.5 10 | }) 11 | 12 | for i = 0, seg do 13 | local a = math.rad(i / seg * -360) 14 | 15 | table.insert(cir, { 16 | x = x + math.sin(a) * radius, 17 | y = y + math.cos(a) * radius, 18 | u = math.sin(a) / 2 + 0.5, 19 | v = math.cos(a) / 2 + 0.5 20 | }) 21 | end 22 | 23 | local a = math.rad(0) -- This is need for non absolute segment counts 24 | 25 | table.insert(cir, { 26 | x = x + math.sin(a) * radius, 27 | y = y + math.cos(a) * radius, 28 | u = math.sin(a) / 2 + 0.5, 29 | v = math.cos(a) / 2 + 0.5 30 | }) 31 | 32 | surface.DrawPoly(cir) 33 | end -------------------------------------------------------------------------------- /lua/damagelogs/client/colors.lua: -------------------------------------------------------------------------------- 1 | if file.Exists("damagelog/colors_v3.txt", "DATA") and not Damagelog.colors then 2 | local colors = file.Read("damagelog/colors_v3.txt", "DATA") 3 | 4 | if colors then 5 | Damagelog.colors = util.JSONToTable(colors) 6 | end 7 | end 8 | 9 | function Damagelog:AddColor(id, default) 10 | if not self.colors then 11 | self.colors = {} 12 | end 13 | 14 | if not self.colors[id] then 15 | self.colors[id] = { 16 | Default = default, 17 | Custom = default 18 | } 19 | elseif self.colors[id].Default ~= default then 20 | self.colors[id].Default = default 21 | end 22 | end 23 | 24 | function Damagelog:GetColor(index) 25 | return self.colors[index] and self.colors[index].Custom or Color(0, 0, 0) 26 | end 27 | 28 | function Damagelog:SaveColors() 29 | file.Write("damagelog/colors_v3.txt", util.TableToJSON(self.colors)) 30 | end -------------------------------------------------------------------------------- /lua/damagelogs/client/filters.lua: -------------------------------------------------------------------------------- 1 | if file.Exists("damagelog/filters.txt", "DATA") then 2 | file.Delete("damagelog/filters.txt") 3 | end 4 | 5 | if file.Exists("damagelog/filters_new.txt", "DATA") and not Damagelog.filter_settings then 6 | local settings = file.Read("damagelog/filters_new.txt", "DATA") 7 | 8 | if settings then 9 | Damagelog.filter_settings = util.JSONToTable(settings) 10 | end 11 | end 12 | 13 | function Damagelog:SaveFilters() 14 | local temp = table.Copy(self.filter_settings) 15 | file.Write("damagelog/filters_new.txt", util.TableToJSON(temp)) 16 | end 17 | 18 | Damagelog.filters = Damagelog.filters or {} 19 | Damagelog.filter_settings = Damagelog.filter_settings or {} 20 | DAMAGELOG_FILTER_BOOL = 1 21 | DAMAGELOG_FILTER_PLAYER = 2 22 | 23 | function Damagelog:AddFilter(name, filter_type, default_value) 24 | self.filters[name] = filter_type 25 | 26 | if not self.filter_settings[name] then 27 | self.filter_settings[name] = default_value 28 | end 29 | end -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/autoslay.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("DL_AslayHook") 3 | else 4 | Damagelog:AddFilter("filter_show_aslays", DAMAGELOG_FILTER_BOOL, true) 5 | Damagelog:AddColor("colors_aslays", Color(255, 128, 128, 255)) 6 | end 7 | 8 | local event = {} 9 | event.Type = "ASLAY" 10 | 11 | function event:DL_AslayHook(ply) 12 | local tbl = { 13 | [1] = ply:GetDamagelogID() 14 | } 15 | 16 | self.CallEvent(tbl) 17 | end 18 | 19 | function event:ToString(v, roles) 20 | local ply = Damagelog:InfoFromID(roles, v[1]) 21 | 22 | return string.format(TTTLogTranslate(GetDMGLogLang, "AutoSlain"), ply.nick, Damagelog:StrRole(ply.role)) 23 | end 24 | 25 | function event:IsAllowed(tbl) 26 | return Damagelog.filter_settings["filter_show_aslays"] 27 | end 28 | 29 | function event:Highlight(line, tbl, text) 30 | return table.HasValue(Damagelog.Highlighted, tbl[1]) 31 | end 32 | 33 | function event:GetColor(tbl) 34 | return Damagelog:GetColor("colors_aslays") 35 | end 36 | 37 | function event:RightClick(line, tbl, roles, text) 38 | line:ShowTooLong(true) 39 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 40 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 41 | end 42 | 43 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/client/weapon_names.lua: -------------------------------------------------------------------------------- 1 | Damagelog.NamesTable = Damagelog.NamesTable or {} 2 | 3 | function Damagelog:GetWeaponName(class) 4 | return self.NamesTable[class] or TTTLogTranslate(GetDMGLogLang, class, true) or class 5 | end 6 | 7 | local function UpdateWeaponNames() 8 | table.Empty(Damagelog.NamesTable) 9 | local entsList = {} 10 | table.Add(entsList, weapons.GetList()) 11 | table.Add(entsList, scripted_ents.GetList()) 12 | 13 | for _, v in pairs(entsList) do 14 | local printName = v.PrintName or v.t and v.t.PrintName 15 | local class = v.ClassName or v.t and v.t.ClassName 16 | 17 | if class and printName then 18 | local translated = LANG.TryTranslation(printName) 19 | 20 | if not translated then 21 | translated = LANG.TryTranslation(class) 22 | end 23 | 24 | Damagelog.NamesTable[class] = translated or printName 25 | end 26 | end 27 | end 28 | 29 | hook.Add("TTTLanguageChanged", "DL_WeaponNames", UpdateWeaponNames) 30 | 31 | -- I kinda missed stupid codes like this 32 | local function GetLangInitFunction() 33 | local LangInit = LANG.Init 34 | 35 | function LANG.Init(...) 36 | local res = LangInit(...) 37 | -- "It ain't stupid if it works", they say 38 | UpdateWeaponNames() 39 | 40 | return res 41 | end 42 | end 43 | 44 | hook.Add("Initialize", "DL_WeaponNames", GetLangInitFunction) 45 | -------------------------------------------------------------------------------- /lua/damagelogs/server/sqlite.lua: -------------------------------------------------------------------------------- 1 | Damagelog.SQLiteDatabase = {} 2 | 3 | function Damagelog.SQLiteDatabase.Query(queryString) 4 | local result = sql.Query(queryString) 5 | if(result == false) then 6 | local errorLog = string.format( 7 | "[%s] Error performing SQLite query:\n%s\n[SQL Error] %s\n\n", 8 | util.DateStamp(), 9 | queryString, 10 | sql.LastError()) 11 | 12 | file.Append("tttdamagelogs-sql-errors.txt", errorLog) 13 | 14 | local queryShort = queryString 15 | if(string.len(queryShort) > 20) then queryShort = string.sub(queryString, 1, 20) .. "..." end 16 | 17 | local errorMessage = string.format( 18 | "Error performing SQLite query\nCheck garrysmod/data/tttdamagelogs-sql-errors.txt (%s)\n%s - %s", 19 | util.DateStamp(), 20 | queryShort, 21 | sql.LastError()) 22 | 23 | assert(false, errorMessage) 24 | end 25 | 26 | return result 27 | end 28 | 29 | function Damagelog.SQLiteDatabase.QuerySingle(queryString) 30 | local result = Damagelog.SQLiteDatabase.Query(queryString) 31 | if result == nil then return nil end 32 | 33 | return result[1] 34 | end 35 | 36 | function Damagelog.SQLiteDatabase.QueryValue(queryString) 37 | local result = Damagelog.SQLiteDatabase.QuerySingle(queryString) 38 | if result == nil then return nil end 39 | 40 | for k, v in pairs(result) do return v end 41 | return result 42 | end 43 | -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/nades.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("Initialize") 3 | else 4 | Damagelog:AddFilter("filter_show_nades", DAMAGELOG_FILTER_BOOL, true) 5 | Damagelog:AddColor("color_nades", Color(0, 128, 0, 255)) 6 | end 7 | 8 | local event = {} 9 | event.Type = "NADE" 10 | 11 | function event:Initialize() 12 | for _, v in pairs(weapons.GetList()) do 13 | if v.Base == "weapon_tttbasegrenade" then 14 | v.CreateGrenade = function(gren, src, ang, vel, angimp, ply) 15 | local tbl = { 16 | [1] = gren.Owner:GetDamagelogID(), 17 | [2] = gren:GetClass() 18 | } 19 | 20 | self.CallEvent(tbl) 21 | 22 | return gren.BaseClass.CreateGrenade(gren, src, ang, vel, angimp, ply) 23 | end 24 | end 25 | end 26 | end 27 | 28 | function event:ToString(v, roles) 29 | local weapon = Damagelog:GetWeaponName(v[2]) or tostring(v[2]) 30 | local ply = Damagelog:InfoFromID(roles, v[1]) 31 | 32 | return string.format(TTTLogTranslate(GetDMGLogLang, "NadeThrown"), ply.nick, Damagelog:StrRole(ply.role), weapon) 33 | end 34 | 35 | function event:IsAllowed(tbl) 36 | return Damagelog.filter_settings["filter_show_nades"] 37 | end 38 | 39 | function event:Highlight(line, tbl, text) 40 | return table.HasValue(Damagelog.Highlighted, tbl[1]) 41 | end 42 | 43 | function event:GetColor(tbl) 44 | return Damagelog:GetColor("color_nades") 45 | end 46 | 47 | function event:RightClick(line, tbl, roles, text) 48 | line:ShowTooLong(true) 49 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 50 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 51 | end 52 | 53 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/credits.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("TTTAddCredits") 3 | Damagelog:EventHook("Initialize") 4 | else 5 | Damagelog:AddFilter("filter_show_credits", DAMAGELOG_FILTER_BOOL, false) 6 | Damagelog:AddColor("color_credits", Color(255, 155, 0)) 7 | end 8 | 9 | local event = {} 10 | event.Type = "CRED" 11 | 12 | function event:TTTAddCredits(ply, credits) 13 | if credits == 0 then 14 | return 15 | end 16 | 17 | self.CallEvent({ 18 | [1] = ply:GetDamagelogID(), 19 | [2] = credits 20 | }) 21 | end 22 | 23 | function event:Initialize() 24 | local plymeta = FindMetaTable("Player") 25 | 26 | if not plymeta then 27 | Error("FAILED TO FIND PLAYER TABLE") 28 | 29 | return 30 | end 31 | 32 | function plymeta:AddCredits(amt) 33 | self:SetCredits(self:GetCredits() + amt) 34 | hook.Call("TTTAddCredits", GAMEMODE, self, amt) 35 | end 36 | end 37 | 38 | function event:ToString(v, roles) 39 | local ply = Damagelog:InfoFromID(roles, v[1]) 40 | 41 | return string.format(TTTLogTranslate(GetDMGLogLang, "UsedCredits"), ply.nick, Damagelog:StrRole(ply.role), v[2] > 0 and TTTLogTranslate(GetDMGLogLang, "received") or TTTLogTranslate(GetDMGLogLang, "used"), v[2] > 0 and v[2] or -v[2], v[2] > 1 and "s" or "") 42 | end 43 | 44 | function event:IsAllowed(tbl) 45 | return Damagelog.filter_settings["filter_show_credits"] 46 | end 47 | 48 | function event:Highlight(line, tbl, text) 49 | return table.HasValue(Damagelog.Highlighted, tbl[1]) 50 | end 51 | 52 | function event:GetColor(tbl) 53 | return Damagelog:GetColor("color_credits") 54 | end 55 | 56 | function event:RightClick(line, tbl, roles, text) 57 | line:ShowTooLong(true) 58 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 59 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 60 | end 61 | 62 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/bodyfound.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("TTTBodyFound") 3 | else 4 | Damagelog:AddFilter("filter_show_bodies", DAMAGELOG_FILTER_BOOL, true) 5 | Damagelog:AddColor("color_found_bodies", Color(127, 0, 255)) 6 | end 7 | 8 | local event = {} 9 | event.Type = "BODY" 10 | 11 | function event:TTTBodyFound(ply, deadply, rag) 12 | local tbl = { 13 | [1] = ply:GetDamagelogID() 14 | } 15 | 16 | if IsValid(deadply) then 17 | table.insert(tbl, deadply:GetDamagelogID()) 18 | else 19 | local nick = CORPSE.GetPlayerNick(rag, TTTLogTranslate(GetDMGLogLang, "DisconnectedPlayer")) 20 | 21 | for k, v in pairs(Damagelog.Roles[#Damagelog.Roles]) do 22 | if v.nick == nick then 23 | table.insert(tbl, k) 24 | break 25 | end 26 | end 27 | end 28 | 29 | self.CallEvent(tbl) 30 | end 31 | 32 | function event:ToString(v, roles) 33 | local ply = Damagelog:InfoFromID(roles, v[1]) 34 | local deadply = Damagelog:InfoFromID(roles, v[2] or -1) 35 | 36 | return string.format(TTTLogTranslate(GetDMGLogLang, "BodyIdentified"), ply.nick, Damagelog:StrRole(ply.role), deadply.nick, Damagelog:StrRole(deadply.role)) 37 | end 38 | 39 | function event:IsAllowed(tbl) 40 | return Damagelog.filter_settings["filter_show_bodies"] 41 | end 42 | 43 | function event:Highlight(line, tbl, text) 44 | if table.HasValue(Damagelog.Highlighted, tbl[1]) or table.HasValue(Damagelog.Highlighted, tbl[2]) then 45 | return true 46 | end 47 | 48 | return false 49 | end 50 | 51 | function event:GetColor(tbl) 52 | return Damagelog:GetColor("color_found_bodies") 53 | end 54 | 55 | function event:RightClick(line, tbl, roles, text) 56 | line:ShowTooLong(true) 57 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 58 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 59 | end 60 | 61 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/dna.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("TTTFoundDNA") 3 | else 4 | Damagelog:AddFilter("filter_show_dna", DAMAGELOG_FILTER_BOOL, true) 5 | Damagelog:AddColor("color_dna", Color(20, 200, 20)) 6 | end 7 | 8 | local event = {} 9 | event.Type = "DNA" 10 | 11 | function event:TTTFoundDNA(ply, dna_owner, ent) 12 | local name = ent:GetClass() 13 | 14 | if name == "prop_ragdoll" then 15 | name = CORPSE.GetPlayerNick(ent, TTTLogTranslate(GetDMGLogLang, "PlayerDisconnected")) .. TTTLogTranslate(GetDMGLogLang, "sbody") -- Think of a better translation way since it's always english 16 | end 17 | 18 | self.CallEvent({ 19 | [1] = ply:GetDamagelogID(), 20 | [2] = dna_owner:GetDamagelogID(), 21 | [3] = name 22 | }) 23 | end 24 | 25 | function event:ToString(v, roles) 26 | local ply = Damagelog:InfoFromID(roles, v[1]) 27 | local dna_owner = Damagelog:InfoFromID(roles, v[2]) 28 | local ent = Damagelog:GetWeaponName(v[3]) or v[3] 29 | 30 | return string.format(TTTLogTranslate(GetDMGLogLang, "DNAretrieved"), ply.nick, Damagelog:StrRole(ply.role), dna_owner.nick, Damagelog:StrRole(dna_owner.role), ent) 31 | end 32 | 33 | function event:IsAllowed(tbl) 34 | return Damagelog.filter_settings["filter_show_dna"] 35 | end 36 | 37 | function event:Highlight(line, tbl, text) 38 | if table.HasValue(Damagelog.Highlighted, tbl[1]) or table.HasValue(Damagelog.Highlighted, tbl[2]) then 39 | return true 40 | end 41 | 42 | return false 43 | end 44 | 45 | function event:GetColor(tbl) 46 | return Damagelog:GetColor("color_dna") 47 | end 48 | 49 | function event:RightClick(line, tbl, roles, text) 50 | line:ShowTooLong(true) 51 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 52 | local dna_owner = Damagelog:InfoFromID(roles, tbl[2]) 53 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}, {dna_owner.nick, util.SteamIDFrom64(dna_owner.steamid64)}) 54 | end 55 | 56 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/weapons.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("TTTOrderedEquipment") 3 | else 4 | Damagelog:AddFilter("filter_show_purchases", DAMAGELOG_FILTER_BOOL, true) 5 | Damagelog:AddColor("color_purchases", Color(128, 0, 128, 255)) 6 | end 7 | 8 | local event = {} 9 | event.Type = "WEP" 10 | 11 | function event:TTTOrderedEquipment(ply, ent, is_item) 12 | self.CallEvent({ 13 | [1] = ply:GetDamagelogID(), 14 | [2] = is_item, 15 | [3] = ent 16 | }) 17 | end 18 | 19 | function event:ToString(v, roles) 20 | local weapon = v[3] 21 | 22 | if isnumber(weapon) then 23 | weapon = tonumber(weapon) 24 | 25 | for _, role in pairs(EquipmentItems) do 26 | local found = false 27 | 28 | for _, v in pairs(role) do 29 | if v.id == weapon then 30 | local translated = LANG.TryTranslation(v.name) 31 | weapon = translated or v.name 32 | found = true 33 | break 34 | end 35 | end 36 | 37 | if found then break end 38 | end 39 | else 40 | weapon = Damagelog:GetWeaponName(weapon) 41 | end 42 | 43 | local ply = Damagelog:InfoFromID(roles, v[1]) 44 | 45 | return string.format(TTTLogTranslate(GetDMGLogLang, "HasBought"), ply.nick, Damagelog:StrRole(ply.role), weapon) 46 | end 47 | 48 | function event:IsAllowed(tbl) 49 | return Damagelog.filter_settings["filter_show_purchases"] 50 | end 51 | 52 | function event:Highlight(line, tbl, text) 53 | return table.HasValue(Damagelog.Highlighted, tbl[1]) 54 | end 55 | 56 | function event:GetColor(tbl) 57 | return Damagelog:GetColor("color_purchases") 58 | end 59 | 60 | function event:RightClick(line, tbl, roles, text) 61 | line:ShowTooLong(true) 62 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 63 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 64 | end 65 | 66 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/drownings.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("DoPlayerDeath") 3 | else 4 | Damagelog:AddFilter("filter_show_drownings", DAMAGELOG_FILTER_BOOL, true) 5 | Damagelog:AddColor("color_drownings", Color(115, 161, 255, 255)) 6 | end 7 | 8 | local event = {} 9 | event.Type = "DRN" 10 | 11 | function event:DoPlayerDeath(ply, attacker, dmginfo) 12 | if attacker:IsWorld() and dmginfo:IsDamageType(DMG_DROWN) and not (ply.IsGhost and ply:IsGhost()) then 13 | Damagelog.SceneID = Damagelog.SceneID + 1 14 | local scene = Damagelog.SceneID 15 | Damagelog.SceneRounds[scene] = Damagelog.CurrentRound 16 | 17 | local tbl = { 18 | [1] = ply:GetDamagelogID(), 19 | [2] = scene 20 | } 21 | 22 | if scene then 23 | timer.Simple(0.6, function() 24 | Damagelog.Death_Scenes[scene] = table.Copy(Damagelog.Records) 25 | end) 26 | end 27 | 28 | self.CallEvent(tbl) 29 | 30 | ply.rdmInfo = { 31 | time = Damagelog.Time, 32 | round = Damagelog.CurrentRound 33 | } 34 | 35 | ply.rdmSend = true 36 | end 37 | end 38 | 39 | function event:ToString(v, roles) 40 | local info = Damagelog:InfoFromID(roles, v[1]) 41 | 42 | return string.format(TTTLogTranslate(GetDMGLogLang, "PlayerDrowned"), info.nick, Damagelog:StrRole(info.role)) 43 | end 44 | 45 | function event:IsAllowed(tbl) 46 | return Damagelog.filter_settings["filter_show_drownings"] 47 | end 48 | 49 | function event:Highlight(line, tbl, text) 50 | return table.HasValue(Damagelog.Highlighted, tbl[1]) 51 | end 52 | 53 | function event:GetColor(tbl) 54 | return Damagelog:GetColor("color_drownings") 55 | end 56 | 57 | function event:RightClick(line, tbl, roles, text) 58 | line:ShowTooLong(false) 59 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 60 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 61 | line:ShowDeathScene(tbl[1], tbl[1], tbl[2]) 62 | end 63 | 64 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/equipment.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("TTTEquipmentUse") 3 | else 4 | Damagelog:AddFilter("filter_show_equipment_usage", DAMAGELOG_FILTER_BOOL, true) 5 | end 6 | 7 | local EVENT_DETAILS = { 8 | ID = 1, 9 | UserId = 2, 10 | UserName = 3, 11 | UserRole = 4, 12 | UserSteamID = 5, 13 | EquipmentClass = 6, 14 | ExtraInfo = 7 15 | } 16 | 17 | local EVENT_IDS = { 18 | EQUIPMENT_USE = 1 19 | } 20 | 21 | local event = {} 22 | event.Type = "WEP" 23 | 24 | function event:TTTEquipmentUse(ply, equipment, info) 25 | event.CallEvent({ 26 | [EVENT_DETAILS.ID] = EVENT_IDS.EQUIPMENT_USE, 27 | [EVENT_DETAILS.UserId] = ply:GetDamagelogID(), 28 | [EVENT_DETAILS.UserName] = ply:Nick(), 29 | [EVENT_DETAILS.UserRole] = ply:GetRole(), 30 | [EVENT_DETAILS.UserSteamID] = ply:SteamID(), 31 | [EVENT_DETAILS.EquipmentClass] = IsEntity(equipment) and equipment:GetClass() or equipment, 32 | [EVENT_DETAILS.ExtraInfo] = info 33 | }) 34 | end 35 | 36 | function event:ToString(eventInfo) 37 | if eventInfo[EVENT_DETAILS.ID] == EVENT_IDS.EQUIPMENT_USE then 38 | local translateString = TTTLogTranslate(GetDMGLogLang, "HasUsed") 39 | return string.format(translateString, 40 | eventInfo[EVENT_DETAILS.UserName], 41 | Damagelog:StrRole(eventInfo[EVENT_DETAILS.UserRole]), 42 | Damagelog:GetWeaponName(eventInfo[EVENT_DETAILS.EquipmentClass]), 43 | eventInfo[EVENT_DETAILS.ExtraInfo]) 44 | end 45 | end 46 | 47 | function event:IsAllowed(tbl) 48 | return Damagelog.filter_settings["filter_show_equipment_usage"] 49 | end 50 | 51 | function event:Highlight(line, tbl, text) 52 | return table.HasValue(Damagelog.Highlighted, tbl[EVENT_DETAILS.UserId]) 53 | end 54 | 55 | function event:GetColor(tbl) 56 | return Damagelog:GetColor("color_purchases") 57 | end 58 | 59 | function event:RightClick(line, tbl, text) 60 | line:ShowTooLong(true) 61 | line:ShowCopy(true, {tbl[EVENT_DETAILS.UserName], tbl[EVENT_DETAILS.UserSteamID]}) 62 | end 63 | 64 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/sync.lua: -------------------------------------------------------------------------------- 1 | -- I heard using an entity to sync variables with all players is good 2 | -- this is what sandbox does (edit_sky, edit_sun, ..) 3 | local ENT = { 4 | Type = "anim", 5 | base = "base_anim", 6 | SetupDataTables = function(self) 7 | self:NetworkVar("Int", 0, "PlayedRounds") 8 | self:NetworkVar("Bool", 0, "LastRoundMapExists") 9 | self:NetworkVar("Int", 1, "PendingReports") 10 | end, 11 | UpdateTransmitState = function() 12 | return TRANSMIT_ALWAYS 13 | end, 14 | Draw = function() end, 15 | Initialize = function(self) 16 | self:DrawShadow(false) 17 | self:AddEFlags(EFL_KEEP_ON_RECREATE_ENTITIES) 18 | end 19 | } 20 | 21 | scripted_ents.Register(ENT, "dmglog_sync_ent") 22 | 23 | -- Getting the entity serverside or clientside 24 | -- Then all we'll have to do is using Get* and Set* functions we create using NetworkVar() 25 | function Damagelog:GetSyncEnt() 26 | if SERVER then 27 | return self.sync_ent 28 | else 29 | return ents.FindByClass("dmglog_sync_ent")[1] 30 | end 31 | end 32 | 33 | if SERVER then 34 | -- Creating the entity on InitPostEntity 35 | hook.Add("InitPostEntity", "InitPostEntity_Damagelog", function() 36 | Damagelog.sync_ent = ents.Create("dmglog_sync_ent") 37 | Damagelog.sync_ent:Spawn() 38 | Damagelog.sync_ent:Activate() 39 | Damagelog.sync_ent:SetLastRoundMapExists(Damagelog.last_round_map and true or false) 40 | 41 | if Damagelog.RDM_Manager_Enabled then 42 | for _, v in pairs(Damagelog.Reports.Current) do 43 | if v.status == RDM_MANAGER_WAITING and not v.adminReport then 44 | Damagelog.sync_ent:SetPendingReports(Damagelog.sync_ent:GetPendingReports() + 1) 45 | end 46 | end 47 | 48 | for _, v in pairs(Damagelog.Reports.Previous) do 49 | if v.status == RDM_MANAGER_WAITING and not v.adminReport then 50 | Damagelog.sync_ent:SetPendingReports(Damagelog.sync_ent:GetPendingReports() + 1) 51 | end 52 | end 53 | end 54 | 55 | end) 56 | end 57 | -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/suicide.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("DoPlayerDeath") 3 | else 4 | Damagelog:AddFilter("filter_show_suicides", DAMAGELOG_FILTER_BOOL, true) 5 | Damagelog:AddColor("color_suicides", Color(25, 25, 220, 255)) 6 | end 7 | 8 | local event = {} 9 | event.Type = "KILL" 10 | 11 | function event:DoPlayerDeath(ply, attacker, dmginfo) 12 | local class = attacker:GetClass() 13 | 14 | -- Ignore spectators 15 | if (ply.IsGhost and ply:IsGhost()) then return end 16 | 17 | -- Drowning is handled separately in the drownings.lua event file 18 | if (attacker:IsWorld() and dmginfo:IsDamageType(DMG_DROWN)) then return end 19 | 20 | -- Ignore player kills. These are handled in the kills.lua event file 21 | if (IsValid(attacker) and attacker:IsPlayer() and attacker ~= ply) then return end 22 | 23 | -- Ignore players being pushed to their death. This is handled in kills.lua 24 | if (ply:GetPlayerThatRecentlyPushedMe() ~= nil) then return end 25 | 26 | Damagelog.SceneID = Damagelog.SceneID + 1 27 | local scene = Damagelog.SceneID 28 | Damagelog.SceneRounds[scene] = Damagelog.CurrentRound 29 | 30 | local tbl = { 31 | [1] = ply:GetDamagelogID(), 32 | [2] = scene 33 | } 34 | 35 | if scene then 36 | timer.Simple(0.6, function() 37 | Damagelog.Death_Scenes[scene] = table.Copy(Damagelog.Records) 38 | end) 39 | end 40 | 41 | self.CallEvent(tbl) 42 | 43 | ply.rdmInfo = { 44 | time = Damagelog.Time, 45 | round = Damagelog.CurrentRound 46 | } 47 | 48 | ply.rdmSend = true 49 | end 50 | 51 | function event:ToString(v, roles) 52 | local info = Damagelog:InfoFromID(roles, v[1]) 53 | 54 | return string.format(TTTLogTranslate(GetDMGLogLang, "SomethingKilled"), info.nick, Damagelog:StrRole(info.role)) 55 | end 56 | 57 | function event:IsAllowed(tbl) 58 | return Damagelog.filter_settings["filter_show_suicides"] 59 | end 60 | 61 | function event:Highlight(line, tbl, text) 62 | return table.HasValue(Damagelog.Highlighted, tbl[1]) 63 | end 64 | 65 | function event:GetColor(tbl) 66 | return Damagelog:GetColor("color_suicides") 67 | end 68 | 69 | function event:RightClick(line, tbl, roles, text) 70 | line:ShowTooLong(true) 71 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 72 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 73 | line:ShowDeathScene(tbl[1], tbl[1], tbl[2]) 74 | end 75 | 76 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/client/tabs/shoots.lua: -------------------------------------------------------------------------------- 1 | function Damagelog:DrawShootsTab() 2 | self.Shoots = vgui.Create("DPanelList") 3 | self.Shoots:SetSpacing(10) 4 | self.ShootsRefresh = vgui.Create("DButton") 5 | self.ShootsRefresh:SetText(TTTLogTranslate(GetDMGLogLang, "Refresh")) 6 | 7 | self.ShootsRefresh.DoClick = function() 8 | if not tonumber(self.SelectedRound) then 9 | return 10 | end 11 | 12 | self.ShootTableTemp = {} 13 | self.ReceivingST = true 14 | self.ShootsList:Clear() 15 | self.ShootsList:AddLine(TTTLogTranslate(GetDMGLogLang, "Loading")) 16 | net.Start("DL_AskShootLogs") 17 | net.WriteInt(self.SelectedRound, 8) 18 | net.SendToServer() 19 | end 20 | 21 | self.Shoots:AddItem(self.ShootsRefresh) 22 | self.ShootsList = vgui.Create("DListView") 23 | self.ShootsList:SetHeight(575) 24 | self.ShootColumn = self.ShootsList:AddColumn("") 25 | 26 | self.ShootColumn.UpdateText = function(shootColumn) 27 | shootColumn:SetName(TTTLogTranslate(GetDMGLogLang, "CurrentRoundSelected") .. self.Round:GetValue()) 28 | end 29 | 30 | self.ShootColumn:UpdateText() 31 | self.Shoots:AddItem(self.ShootsList) 32 | 33 | self.ShootsList.OnRowRightClick = function() 34 | local Menu = DermaMenu() 35 | Menu:Open() 36 | 37 | Menu:AddOption(TTTLogTranslate(GetDMGLogLang, "CopyLines"), function() 38 | local full_text = "" 39 | local append = false 40 | 41 | for _, line in pairs(Damagelog.ShootsList:GetSelected()) do 42 | if append then 43 | full_text = full_text .. "\n" 44 | end 45 | 46 | full_text = full_text .. line:GetColumnText(1) 47 | append = true 48 | end 49 | 50 | SetClipboardText(full_text) 51 | end):SetImage("icon16/tab_edit.png") 52 | end 53 | 54 | self.Tabs:AddSheet(TTTLogTranslate(GetDMGLogLang, "SLogs"), self.Shoots, "icon16/page_white_find.png", false, false) 55 | end 56 | 57 | net.Receive("DL_SendShootLogs", function() 58 | local rolelen = net.ReadUInt(15) 59 | local roles = util.JSONToTable(util.Decompress(net.ReadData(rolelen))) 60 | local datalen = net.ReadUInt(15) 61 | local data = util.JSONToTable(util.Decompress(net.ReadData(datalen))) 62 | if not istable(data) then 63 | return 64 | end 65 | if IsValid(Damagelog.ShootsList) then 66 | Damagelog.ShootsList:Clear() 67 | Damagelog:SetDamageInfosLV(Damagelog.ShootsList, roles, nil, nil, nil, nil, data) 68 | end 69 | end) 70 | -------------------------------------------------------------------------------- /lua/damagelogs/shared/events.lua: -------------------------------------------------------------------------------- 1 | Damagelog.events = Damagelog.events or {} 2 | Damagelog.IncludedEvents = Damagelog.IncludedEvents or {} 3 | 4 | function Damagelog:AddEvent(event, f) 5 | local id = #self.events + 1 6 | 7 | function event.CallEvent(tbl, force_time, force_index) 8 | if GetRoundState() ~= ROUND_ACTIVE then 9 | return 10 | end 11 | 12 | local time 13 | 14 | if force_time then 15 | time = tbl[force_time] 16 | else 17 | time = self.Time 18 | end 19 | 20 | local infos = { 21 | id = id, 22 | time = time, 23 | infos = tbl 24 | } 25 | 26 | if force_index then 27 | self.DamageTable[tbl[force_index]] = infos 28 | else 29 | table.insert(self.DamageTable, infos) 30 | end 31 | 32 | local recip = {} 33 | 34 | for _, v in pairs(player.GetHumans()) do 35 | if v:CanUseDamagelog() then 36 | table.insert(recip, v) 37 | end 38 | end 39 | 40 | infos = util.Compress(util.TableToJSON(infos)) 41 | net.Start("DL_RefreshDamagelog") 42 | net.WriteUInt(#infos, 32) 43 | net.WriteData(infos, #infos) 44 | net.Send(recip) 45 | end 46 | 47 | self.events[id] = event 48 | table.insert(self.IncludedEvents, Damagelog.CurrentFile) 49 | end 50 | 51 | if SERVER then 52 | Damagelog.event_hooks = {} 53 | 54 | function Damagelog:InitializeEventHooks() 55 | for _, name in pairs(self.event_hooks) do 56 | hook.Add(name, "Damagelog_events_" .. name, function(...) 57 | for _, v in pairs(self.events) do 58 | if v[name] then 59 | v[name](v, ...) 60 | end 61 | end 62 | end) 63 | end 64 | end 65 | 66 | function Damagelog:EventHook(name) 67 | if not table.HasValue(self.event_hooks, name) then 68 | table.insert(self.event_hooks, name) 69 | end 70 | end 71 | end 72 | 73 | function Damagelog:InfoFromID(tbl, id) 74 | return tbl[id] or { 75 | steamid64 = -1, 76 | role = -1, 77 | nick = "" 78 | } 79 | end 80 | 81 | function Damagelog:IsTeamkill(role1, role2) 82 | if role1 == role2 then 83 | return true 84 | elseif role1 == ROLE_DETECTIVE and role2 == ROLE_INNOCENT then 85 | return true 86 | elseif role1 == ROLE_INNOCENT and role2 == ROLE_DETECTIVE then 87 | return true 88 | end 89 | return false 90 | end 91 | 92 | 93 | local function includeEventFile(f) 94 | f = "damagelogs/shared/events/" .. f 95 | 96 | if SERVER then 97 | AddCSLuaFile(f) 98 | end 99 | 100 | include(f) 101 | end 102 | 103 | for _, v in pairs(file.Find("damagelogs/shared/events/*.lua", "LUA")) do 104 | if not table.HasValue(Damagelog.IncludedEvents, v) then 105 | Damagelog.CurrentFile = v 106 | includeEventFile(v) 107 | end 108 | end 109 | 110 | if CLIENT then 111 | Damagelog:SaveColors() 112 | Damagelog:SaveFilters() 113 | else 114 | Damagelog:InitializeEventHooks() 115 | end -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/fall_damage.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("EntityTakeDamage") 3 | else 4 | Damagelog:AddFilter("filter_show_falldamage", DAMAGELOG_FILTER_BOOL, false) 5 | Damagelog:AddColor("color_fall_damages", Color(0, 0, 0)) 6 | end 7 | 8 | local event = {} 9 | event.Type = "FD" 10 | 11 | local EVENT_DETAILS = { 12 | VictimId = 1, 13 | DamageTaken = 2, 14 | WasVictimPushed = 3, 15 | AttackerId = 4 16 | } 17 | 18 | function event:EntityTakeDamage(ent, dmginfo) 19 | local att = dmginfo:GetAttacker() 20 | 21 | if not (ent.IsGhost and ent:IsGhost()) and ent:IsPlayer() and att:IsWorld() and dmginfo:GetDamageType() == DMG_FALL then 22 | local damages = dmginfo:GetDamage() 23 | 24 | if math.floor(damages) > 0 then 25 | local tbl = { 26 | [EVENT_DETAILS.VictimId] = ent:GetDamagelogID(), 27 | [EVENT_DETAILS.DamageTaken] = math.Round(damages), 28 | [EVENT_DETAILS.WasVictimPushed] = false 29 | } 30 | 31 | local playerThatPushed = ent:GetPlayerThatRecentlyPushedMe() 32 | 33 | if playerThatPushed ~= nil then 34 | tbl[EVENT_DETAILS.WasVictimPushed] = true 35 | tbl[EVENT_DETAILS.AttackerId] = playerThatPushed:GetDamagelogID() 36 | end 37 | 38 | self.CallEvent(tbl) 39 | end 40 | end 41 | end 42 | 43 | function event:ToString(tbl, roles) 44 | local ply = Damagelog:InfoFromID(roles, tbl[EVENT_DETAILS.VictimId]) 45 | local t = string.format(TTTLogTranslate(GetDMGLogLang, "FallDamage"), ply.nick, Damagelog:StrRole(ply.role), tbl[EVENT_DETAILS.DamageTaken]) 46 | 47 | if tbl[EVENT_DETAILS.WasVictimPushed] then 48 | local att = Damagelog:InfoFromID(roles, tbl[EVENT_DETAILS.AttackerId]) 49 | t = t .. string.format(TTTLogTranslate(GetDMGLogLang, "AfterPush"), att.nick, Damagelog:StrRole(att.role)) 50 | end 51 | 52 | return t 53 | end 54 | 55 | function event:IsAllowed(tbl) 56 | return Damagelog.filter_settings["filter_show_falldamage"] 57 | end 58 | 59 | function event:Highlight(line, tbl, text) 60 | return table.HasValue(Damagelog.Highlighted, tbl[EVENT_DETAILS.VictimId]) 61 | end 62 | 63 | function event:GetColor(tbl, roles) 64 | if tbl[EVENT_DETAILS.WasVictimPushed] then 65 | local ent = Damagelog:InfoFromID(roles, tbl[EVENT_DETAILS.VictimId]) 66 | local att = Damagelog:InfoFromID(roles, tbl[EVENT_DETAILS.AttackerId]) 67 | 68 | if att ~= nil and Damagelog:IsTeamkill(att.role, ent.role) then 69 | return Damagelog:GetColor("color_team_damages") 70 | end 71 | end 72 | 73 | return Damagelog:GetColor("color_fall_damages") 74 | end 75 | 76 | function event:RightClick(line, tbl, roles, text) 77 | line:ShowTooLong(true) 78 | local ply = Damagelog:InfoFromID(roles, tbl[EVENT_DETAILS.VictimId]) 79 | 80 | if tbl[EVENT_DETAILS.WasVictimPushed] then 81 | local att = Damagelog:InfoFromID(roles, tbl[EVENT_DETAILS.AttackerId]) 82 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}, {att.nick, util.SteamIDFrom64(att.steamid64)}) 83 | else 84 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 85 | end 86 | end 87 | 88 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/notify.lua: -------------------------------------------------------------------------------- 1 | local Player = FindMetaTable("Player") 2 | DAMAGELOG_NOTIFY_ALERT = 1 3 | DAMAGELOG_NOTIFY_INFO = 2 4 | 5 | if SERVER then 6 | util.AddNetworkString("DL_Notify") 7 | 8 | function Player:Damagelog_Notify(msg_type, msg, _time, sound) 9 | net.Start("DL_Notify") 10 | net.WriteUInt(msg_type, 4) 11 | net.WriteString(msg) 12 | net.WriteUInt(_time, 4) 13 | net.WriteUInt(sound and 1 or 0, 1) 14 | 15 | if sound then 16 | net.WriteString(sound) 17 | end 18 | 19 | net.Send(self) 20 | end 21 | else 22 | Damagelog.Notifications = Damagelog.Notifications or {} 23 | 24 | local icons = { 25 | [DAMAGELOG_NOTIFY_ALERT] = Material("icon16/exclamation.png"), 26 | [DAMAGELOG_NOTIFY_INFO] = Material("icon16/information.png") 27 | } 28 | 29 | function Damagelog:Notify(msg_type, msg, _time, soundFile) 30 | if soundFile ~= '' and GetConVar("ttt_dmglogs_enablesound"):GetBool() then 31 | if GetConVar("ttt_dmglogs_enablesoundoutside"):GetBool() then 32 | sound.PlayFile("sound/" .. soundFile, "", function() end) 33 | else 34 | surface.PlaySound(soundFile) 35 | end 36 | end 37 | 38 | table.insert(Damagelog.Notifications, { 39 | text = msg, 40 | icon = icons[msg_type] or icons[DAMAGELOG_NOTIFY_ALERT], 41 | _time = _time, 42 | start = UnPredictedCurTime() 43 | }) 44 | end 45 | 46 | net.Receive("DL_Notify", function() 47 | Damagelog:Notify(net.ReadUInt(4), net.ReadString(), net.ReadUInt(4), (net.ReadUInt(1) == 1) and net.ReadString() or false) 48 | end) 49 | 50 | local function DrawNotif(x, y, w, h, text, icon) 51 | local red = 75 + (175 * math.abs(math.sin(UnPredictedCurTime() * 2))) 52 | local b = 2 53 | draw.RoundedBox(0, x, y, w, h, Color(red, 75, 75, 255)) 54 | draw.RoundedBox(0, x + b, y + b, w - b * 2, h - b * 2, Color(150, 150, 150, 255)) 55 | x = x + 10 56 | y = y + h / 2 - 8 57 | surface.SetDrawColor(Color(255, 255, 255, 255)) 58 | surface.SetMaterial(icon) 59 | surface.DrawTexturedRect(x, y, 16, 16) 60 | x = x + 26 61 | surface.SetTextColor(Color(255, 255, 255, 255)) 62 | surface.SetTextPos(x, y) 63 | surface.DrawText(text) 64 | end 65 | 66 | hook.Add("HUDPaint", "RDM_Manager", function() 67 | local notifications = Damagelog.Notifications 68 | 69 | if #notifications > 0 then 70 | local curtime = UnPredictedCurTime() 71 | surface.SetFont("CenterPrintText") 72 | 73 | for k, v in pairs(notifications) do 74 | local w, h = surface.GetTextSize(v.text) 75 | w = w + 50 76 | h = h + 8 77 | local tx = ScrW() - w 78 | local ty = ScrH() * 0.2 + (h + 5) * k 79 | 80 | if v.rollBack then 81 | tx = tx + (((1 - math.max(v.start + 1 - curtime, 0)) ^ 2) * tx) 82 | DrawNotif(tx, ty, w, h, v.text, v.icon) 83 | 84 | if v.start + 1 <= curtime then 85 | table.remove(notifications, k) 86 | end 87 | else 88 | tx = tx + ((math.max(v.start + 1 - curtime, 0) ^ 2) * tx) 89 | DrawNotif(tx, ty, w, h, v.text, v.icon) 90 | 91 | if v.start + v._time <= curtime then 92 | v.rollBack = true 93 | v.start = curtime 94 | end 95 | end 96 | end 97 | end 98 | end) 99 | end -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/health.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("TTTPlayerUsedHealthStation") 3 | Damagelog:EventHook("TTTEndRound") 4 | else 5 | Damagelog:AddFilter("filter_show_healthstation", DAMAGELOG_FILTER_BOOL, false) 6 | Damagelog:AddColor("color_heal", Color(0, 255, 128)) 7 | end 8 | 9 | local usages = {} 10 | local event = {} 11 | event.Type = "HEAL" 12 | 13 | function event:TTTPlayerUsedHealthStation(ply, ent, healed) 14 | if (ply.IsGhost and ply:IsGhost()) or not ply:IsPlayer() then return end 15 | 16 | if usages[ply:SteamID()] ~= nil then 17 | usages[ply:SteamID()] = usages[ply:SteamID()] + healed 18 | return 19 | end 20 | 21 | local healthStationOwner = (ent.GetPlacer and ent:GetPlacer()) -- Vanilla TTT 22 | or (ent.GetOriginator and ent:GetOriginator()) -- TTT2 23 | or nil 24 | 25 | local timername = "HealTimer_" .. tostring(ply:SteamID()) 26 | 27 | timer.Create(timername, 5, 0, function() 28 | if not IsValid(ply) then 29 | timer.Remove(timername) 30 | elseif usages[ply:SteamID()] > 0 then 31 | self:Store(ply, healthStationOwner, usages[ply:SteamID()]) 32 | usages[ply:SteamID()] = 0 33 | else 34 | usages[ply:SteamID()] = nil 35 | self:RemoveTimer(ply:SteamID()) 36 | end 37 | end) 38 | 39 | self:Store(ply, healthStationOwner, healed) 40 | usages[ply:SteamID()] = 0 41 | end 42 | 43 | function event:Store(ply, healthStationOwner, healed) 44 | local isOwnerValid = IsValid(healthStationOwner) 45 | local tbl = { 46 | [1] = ply:GetDamagelogID(), 47 | [2] = healed, 48 | [3] = isOwnerValid, 49 | [4] = isOwnerValid and healthStationOwner:GetDamagelogID() or nil 50 | } 51 | 52 | self.CallEvent(tbl) 53 | end 54 | 55 | function event:RemoveTimer(id) 56 | local timername = "HealTimer_" .. tostring(id) 57 | 58 | if timer.Exists(timername) then 59 | timer.Remove(timername) 60 | end 61 | end 62 | 63 | function event:TTTEndRound() 64 | for k in pairs(usages) do 65 | self:RemoveTimer(k) 66 | end 67 | end 68 | 69 | function event:ToString(tbl, roles) 70 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 71 | local healerNick 72 | 73 | if tbl[3] then 74 | local healer = Damagelog:InfoFromID(roles, tbl[4]) 75 | healerNick = healer.nick .. " [" .. Damagelog:StrRole(healer.role) .. "]" 76 | else 77 | healerNick = TTTLogTranslate(GetDMGLogLang, "healerNick") 78 | end 79 | 80 | return string.format(TTTLogTranslate(GetDMGLogLang, "HealthStationHeal"), ply.nick, Damagelog:StrRole(ply.role), tbl[2], healerNick) 81 | end 82 | 83 | function event:IsAllowed(tbl) 84 | return Damagelog.filter_settings["filter_show_healthstation"] 85 | end 86 | 87 | function event:Highlight(line, tbl, text) 88 | if table.HasValue(Damagelog.Highlighted, tbl[1]) or table.HasValue(Damagelog.Highlighted, tbl[2]) then 89 | return true 90 | end 91 | 92 | return false 93 | end 94 | 95 | function event:GetColor(tbl) 96 | return Damagelog:GetColor("color_heal") 97 | end 98 | 99 | function event:RightClick(line, tbl, roles, text) 100 | line:ShowTooLong(true) 101 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 102 | 103 | if tbl[3] then 104 | local healer = Damagelog:InfoFromID(roles, tbl[4]) 105 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}, {healer.nick, util.SteamIDFrom64(healer.steamid64)}) 106 | else 107 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 108 | end 109 | end 110 | 111 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/ttt_radio.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("TTTPlayerRadioCommand") 3 | else 4 | Damagelog:AddFilter("filter_show_radiocommands", DAMAGELOG_FILTER_BOOL, false) 5 | Damagelog:AddFilter("filter_show_radiokos", DAMAGELOG_FILTER_BOOL, false) 6 | Damagelog:AddColor("color_defaultradio", Color(182, 182, 182)) 7 | Damagelog:AddColor("color_kosradio", Color(255, 0, 0)) 8 | end 9 | 10 | local event = {} 11 | event.Type = "RADIO" 12 | 13 | function event:TTTPlayerRadioCommand(ply, msg_name, msg_target) 14 | local name 15 | local name_role = false 16 | local target_steamid = false 17 | 18 | if isstring(msg_target) then 19 | name = msg_target 20 | else 21 | if IsValid(msg_target) then 22 | if msg_target:IsPlayer() then 23 | name = msg_target:Nick() 24 | name_role = msg_target:GetRole() 25 | target_steamid = msg_target:SteamID() 26 | elseif msg_target:GetClass() == "prop_ragdoll" then 27 | name = TTTLogTranslate(GetDMGLogLang, "CorpseOf") .. CORPSE.GetPlayerNick(msg_target, TTTLogTranslate(GetDMGLogLang, "DisconnectedPlayer")) 28 | name_role = "disconnected" 29 | end 30 | end 31 | end 32 | 33 | if name then 34 | self.CallEvent({ 35 | [1] = ply:GetDamagelogID(), 36 | [2] = msg_name, 37 | [3] = name, 38 | [4] = name_role, 39 | [5] = target_steamid 40 | }) 41 | end 42 | end 43 | 44 | function event:ToString(v, roles) 45 | -- copied localization from cl_voice.lua 46 | local targetply = true 47 | local param = v[3] 48 | local lang_param = LANG.GetNameParam(param) 49 | 50 | if lang_param then 51 | if lang_param == "quick_corpse_id" then 52 | -- special case where nested translation is needed 53 | param = LANG.GetPTranslation(lang_param, { 54 | player = v[5] 55 | }) 56 | else 57 | param = LANG.GetTranslation(lang_param) 58 | end 59 | elseif LANG.GetRawTranslation(param) then 60 | targetply = false 61 | param = LANG.GetTranslation(param) 62 | end 63 | 64 | local text = LANG.GetPTranslation(v[2], { 65 | player = param 66 | }) 67 | 68 | if lang_param then 69 | text = util.Capitalize(text) 70 | end 71 | 72 | local targetrole = "" 73 | 74 | if targetply then 75 | targetrole = " [" .. Damagelog:StrRole(v[4]) .. "]" 76 | end 77 | 78 | local ply = Damagelog:InfoFromID(roles, v[1]) 79 | 80 | return string.format(TTTLogTranslate(GetDMGLogLang, "RadioUsed"), ply.nick, Damagelog:StrRole(ply.role), text, targetrole) 81 | end 82 | 83 | function event:IsAllowed(tbl, roles) 84 | --local ply = Damagelog:InfoFromID(roles, tbl[1]) -- not needed! 85 | if tbl[2] ~= "quick_traitor" then 86 | return Damagelog.filter_settings["filter_show_radiocommands"] 87 | else 88 | return Damagelog.filter_settings["filter_show_radiokos"] 89 | end 90 | end 91 | 92 | function event:Highlight(line, tbl, text) 93 | if table.HasValue(Damagelog.Highlighted, tbl[1]) or table.HasValue(Damagelog.Highlighted, tbl[5]) then 94 | return true 95 | end 96 | 97 | return false 98 | end 99 | 100 | function event:GetColor(tbl, roles) 101 | --local ply = Damagelog:InfoFromID(roles, tbl[1]) -- not needed! 102 | if tbl[2] ~= "quick_traitor" then 103 | return Damagelog:GetColor("color_defaultradio") 104 | else 105 | return Damagelog:GetColor("color_kosradio") 106 | end 107 | end 108 | 109 | function event:RightClick(line, tbl, roles, text) 110 | line:ShowTooLong(true) 111 | local ply = Damagelog:InfoFromID(roles, tbl[1]) 112 | 113 | if not tbl[5] then 114 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 115 | else 116 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}, {tbl[3], tbl[5]}) 117 | end 118 | end 119 | 120 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TTT Damagelogs 2 | Created by Tommy228
3 | _Supports [TTT2](https://steamcommunity.com/sharedfiles/filedetails/?id=1357204556) and [Custom Roles](https://steamcommunity.com/sharedfiles/filedetails/?id=2421039084)_ 4 | 5 |
6 | 7 | This is an administration tool which allows server admins to handle player reports during a game of Trouble in Terrorist Town. 8 | 9 | ![Preview](https://i.imgur.com/9eFdulZ.png) 10 | 11 |
12 | 13 | ## Download 14 | ![workshop](https://i.imgur.com/68y5pjP.png)
15 | Get this addon on the Steam Workshop:
16 | https://steamcommunity.com/sharedfiles/filedetails/?id=2306802961 17 | 18 | --- 19 | 20 |
21 | 22 | ## Features 23 | - Damagelogs *with filter and highlight options* 24 | - Shot logs 25 | - Damage informations 26 | - Old logs (*which saves and displays all features listed above*) 27 | - RDM Manager 28 | - Chat system 29 | - Report and respond system 30 | - Punishment options: Autoslay and Autojail 31 | - Visual Deathscene 32 | - Translation support: English, German, French, Russian and Polish 33 | - Storage support for MySQL and SQLite 34 | - Support for ULX and Serverguard: User groups and RDM punishments 35 | - Easy configuration via config file and F1 settings menu 36 | - Discord notifications for reports 37 | - Supports [TTT2](https://steamcommunity.com/sharedfiles/filedetails/?id=1357204556) and [Custom Roles](https://steamcommunity.com/sharedfiles/filedetails/?id=2421039084) 38 | 39 |
40 | 41 | ## Latest Version 42 | 43 | The latest version can be found on the [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=2306802961).
44 | Alternatively, you can download the latest version from the [Releases](https://github.com/BadgerCode/tttdamagelogs/releases) section of this repository. 45 | 46 |
47 | 48 | ## Support 49 | For guides on using & configuring this addon, please check our [wiki](https://github.com/BadgerCode/tttdamagelogs/wiki). 50 | 51 | If you have a question, issue or feature request, please raise an issue in the [Issues](https://github.com/BadgerCode/tttdamagelogs/issues) section. 52 | 53 | 54 |
55 | 56 | ## Installation 57 | 58 | > **Note**: When updating from a version before 3.6.0, you should backup your Lua configuration file 59 | > 60 | > Otherwise, you may accidentally delete your configuration changes!
61 | > [Read more](https://github.com/BadgerCode/tttdamagelogs/wiki/Configuring-the-damage-logs) 62 | 63 |
64 | 65 | 1. Go to the [Releases](https://github.com/BadgerCode/tttdamagelogs/releases) section 66 | 2. Download the **Source Code (zip)** for the latest version 67 | 3. Extract the zip to a folder called `tttdamagelogs` 68 | * Make sure the folder name is all lowercase 69 | 4. Copy this folder into your server's addons folder 70 | 5. [Configure the addon](https://github.com/BadgerCode/tttdamagelogs/wiki/Configuring-the-damage-logs) 71 | 72 | ### Example 73 | 74 | ![Example installation](https://i.imgur.com/ihPY6EI.png) 75 | 76 | * The server's folder is `gmod-test-server` 77 | * The server's addon folder is `gmod-test-server/garrysmod/addons` 78 | * The damage logs addon folder is `gmod-test-server/garrysmod/addons/tttdamagelogs` 79 | * The config file can be found at `gmod-test-server/garrysmod/data/damagelog/config.json` 80 | 81 |
82 | 83 | --- 84 | 85 |
86 | 87 | ## Contributing 88 | If you would like to make a change to the project, please submit a [pull request](https://github.com/BadgerCode/tttdamagelogs/pulls). 89 | 90 | Please avoid making large code refactoring changes as these make pull requests harder to review. 91 | 92 | 93 | 94 |
95 | 96 | --- 97 | 98 |
99 | 100 | 101 | ## Original Version 102 | The original version of this project can be found here: https://github.com/Tommy228/TTTDamagelogs
103 | The original version is no longer being maintained. 104 | 105 |
106 | 107 | ## Improvements found in this fork of the addon 108 | This fork of the project contains various bug fixes, improves to Discord notifications, support for custom equipment events and more. 109 | 110 | Check the [releases](https://github.com/BadgerCode/tttdamagelogs/releases) section for the latest improvements. 111 | -------------------------------------------------------------------------------- /lua/damagelogs/client/info_label.lua: -------------------------------------------------------------------------------- 1 | local PANEL = {} 2 | local GRADIENT = surface.GetTextureID("gui/gradient_down") 3 | local color_halfwhite = Color(255, 255, 255, 50) 4 | 5 | function PANEL:Init() 6 | self:SetPos(4, 4) 7 | self:SetSize(self:GetWide() - 8, 24) 8 | self:SetBackgroundColor(Color(139, 174, 179, 255)) 9 | self.icon = vgui.Create("DImage", self) 10 | self.icon:SetImage("icon16/comment.png") 11 | self.icon:SizeToContents() 12 | self.label = vgui.Create("DLabel", self) 13 | self.label:SetText("") 14 | self.label:SetTextColor(color_white) 15 | self.label:SetExpensiveShadow(1, Color(0, 0, 0, 150)) 16 | end 17 | 18 | function PANEL:PerformLayout(w, h) 19 | self.icon:SetPos(4, 4) 20 | 21 | if self.textToLeft then 22 | if self.icon:IsVisible() then 23 | self.label:SetPos(self.icon.x + 8, h / 2 - self.label:GetTall() / 2) 24 | else 25 | self.label:SetPos(8, h / 2 - h / 2) 26 | end 27 | else 28 | self.label:SetPos(w / 2 - self.label:GetWide() / 2, h / 2 - self.label:GetTall() / 2) 29 | end 30 | 31 | derma.SkinHook("Layout", "Panel", self) 32 | end 33 | 34 | function PANEL:Paint(w, h) 35 | if self:GetPaintBackground() then 36 | local width, height = self:GetSize() 37 | local x, y = 0, 0 38 | 39 | if self:IsDepressed() then 40 | height = height - 4 41 | width = width - 4 42 | x = x + 2 43 | y = y + 2 44 | end 45 | 46 | local color = self:GetBackgroundColor() 47 | 48 | if self:IsButton() and self:IsHovered() then 49 | color = color_halfwhite 50 | end 51 | 52 | local cornerSize = 4 53 | draw.RoundedBox(cornerSize, x, y, width, height, Color(color.r, color.g, color.b, color.a * 0.75)) 54 | 55 | if x + cornerSize < x + width and y + cornerSize < y + height then 56 | surface.SetDrawColor(color.r, color.g, color.b, color.a) 57 | end 58 | end 59 | end 60 | 61 | function PANEL:SetTextToLeft() 62 | self.textToLeft = true 63 | end 64 | 65 | function PANEL:SetText(text) 66 | self.label:SetText(text) 67 | self.label:SizeToContents() 68 | end 69 | 70 | function PANEL:SetButton(isButton) 71 | self.isButton = isButton 72 | end 73 | 74 | function PANEL:IsButton() 75 | return self.isButton 76 | end 77 | 78 | function PANEL:SetDepressed(isDepressed) 79 | self.isDepressed = isDepressed 80 | end 81 | 82 | function PANEL:IsDepressed() 83 | return self.isDepressed 84 | end 85 | 86 | function PANEL:SetHovered(isHovered) 87 | self.isHovered = isHovered 88 | end 89 | 90 | function PANEL:IsHovered() 91 | return self.isHovered 92 | end 93 | 94 | function PANEL:SetTextColor(color) 95 | self.label:SetTextColor(color) 96 | end 97 | 98 | function PANEL:OnMousePressed(mouseCode) 99 | if self:IsButton() then 100 | self:SetDepressed(true) 101 | self:MouseCapture(true) 102 | end 103 | end 104 | 105 | function PANEL:OnCursorEntered() 106 | self:SetHovered(true) 107 | end 108 | 109 | function PANEL:OnCursorExited() 110 | self:SetHovered(false) 111 | end 112 | 113 | function PANEL:SetShowIcon(showIcon) 114 | self.icon:SetVisible(showIcon) 115 | end 116 | 117 | function PANEL:SetIcon(icon) 118 | self.icon:SetImage(icon) 119 | self.icon:SizeToContents() 120 | self.icon:SetVisible(true) 121 | end 122 | 123 | function PANEL:SetInfoColor(color) 124 | if color == "red" then 125 | self:SetBackgroundColor(Color(179, 46, 49, 255)) 126 | self:SetIcon("icon16/exclamation.png") 127 | elseif color == "orange" then 128 | self:SetBackgroundColor(Color(223, 154, 72, 255)) 129 | self:SetIcon("icon16/error.png") 130 | elseif color == "green" then 131 | self:SetBackgroundColor(Color(139, 215, 113, 255)) 132 | self:SetIcon("icon16/tick.png") 133 | elseif color == "blue" then 134 | self:SetBackgroundColor(Color(139, 174, 179, 255)) 135 | self:SetIcon("icon16/information.png") 136 | else 137 | self:SetShowIcon(false) 138 | self:SetBackgroundColor(color) 139 | end 140 | end 141 | 142 | vgui.Register("Damagelog_InfoLabel", PANEL, "DPanel") -------------------------------------------------------------------------------- /lua/damagelogs/server/recording.lua: -------------------------------------------------------------------------------- 1 | util.AddNetworkString("DL_AskDeathScene") 2 | util.AddNetworkString("DL_SendDeathScene") 3 | Damagelog.Records = Damagelog.Records or {} 4 | Damagelog.Death_Scenes = Damagelog.Death_Scenes or {} 5 | Damagelog.SceneID = Damagelog.SceneID or 0 6 | local magneto_ents = {} 7 | local table = table 8 | 9 | hook.Add("TTTBeginRound", "TTTBeginRound_SpecDMRecord", function() 10 | table.Empty(magneto_ents) 11 | table.Empty(Damagelog.Records) 12 | 13 | for _, ply in ipairs(player.GetHumans()) do 14 | ply.SpectatingLog = false 15 | end 16 | end) 17 | 18 | timer.Create("SpecDM_Recording", 0.2, 0, function() 19 | if not GetRoundState or GetRoundState() ~= ROUND_ACTIVE then 20 | return 21 | end 22 | 23 | if #Damagelog.Records >= 50 then 24 | table.remove(Damagelog.Records, 1) 25 | end 26 | 27 | local tbl = {} 28 | 29 | for _, v in pairs(magneto_ents) do 30 | if v.record and CurTime() - v.last_saw > 15 then 31 | v.record = false 32 | end 33 | end 34 | 35 | for _, v in ipairs(player.GetHumans()) do 36 | if not v:IsActive() then 37 | local rag = v.server_ragdoll 38 | 39 | if IsValid(rag) then 40 | local pos, ang = rag:GetPos(), rag:GetAngles() 41 | 42 | tbl[v:GetDamagelogID()] = { 43 | corpse = true, 44 | pos = pos, 45 | ang = ang, 46 | found = CORPSE.GetFound(rag, false) 47 | } 48 | end 49 | else 50 | local wep = v:GetActiveWeapon() 51 | 52 | tbl[v:GetDamagelogID()] = { 53 | pos = v:GetPos(), 54 | ang = v:GetAngles(), 55 | sequence = v:GetSequence(), 56 | hp = v:Health(), 57 | wep = IsValid(wep) and wep:GetClass() or "", 58 | role = v:GetRole() 59 | } 60 | 61 | if IsValid(wep) and wep:GetClass() == "weapon_zm_carry" and IsValid(wep.EntHolding) then 62 | local found = false 63 | 64 | for k, v2 in pairs(magneto_ents) do 65 | if v2.ent == wep.EntHolding then 66 | found = k 67 | break 68 | end 69 | end 70 | 71 | if found then 72 | magneto_ents[found].last_saw = CurTime() 73 | magneto_ents[found].record = true 74 | else 75 | table.insert(magneto_ents, { 76 | ent = wep.EntHolding, 77 | record = true, 78 | last_saw = CurTime() 79 | }) 80 | end 81 | end 82 | end 83 | end 84 | 85 | for _, v in pairs(magneto_ents) do 86 | if v.record and IsValid(v.ent) then 87 | table.insert(tbl, v.ent:EntIndex(), { 88 | prop = true, 89 | model = v.ent:GetModel(), 90 | pos = v.ent:GetPos(), 91 | ang = v.ent:GetAngles() 92 | }) 93 | end 94 | end 95 | 96 | table.insert(Damagelog.Records, tbl) 97 | end) 98 | 99 | net.Receive("DL_AskDeathScene", function(_, ply) 100 | local ID = net.ReadUInt(32) 101 | local ply1 = net.ReadUInt(32) 102 | local ply2 = net.ReadUInt(32) 103 | local scene = Damagelog.Death_Scenes[ID] 104 | local roles = Damagelog.Roles[Damagelog.SceneRounds[ID]] 105 | 106 | if scene and roles then 107 | local sceneString = util.TableToJSON(scene) 108 | sceneString = util.Compress(sceneString) 109 | net.Start("DL_SendDeathScene") 110 | net.WriteUInt(ply1, 32) 111 | net.WriteUInt(ply2, 32) 112 | net.WriteTable(roles) 113 | net.WriteUInt(#sceneString, 32) 114 | net.WriteData(sceneString, #sceneString) 115 | net.Send(ply) 116 | end 117 | end) 118 | 119 | hook.Add("Initialize", "DamagelogRecording", function() 120 | local old_think = GAMEMODE.KeyPress 121 | 122 | function GAMEMODE:KeyPress(ply, key) 123 | if not (ply.SpectatingLog and (key == IN_ATTACK or key == IN_ATTACK2)) then 124 | return old_think(self, ply, key) 125 | end 126 | end 127 | end) -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/c4.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("Initialize") 3 | Damagelog:EventHook("TTTC4Arm") 4 | Damagelog:EventHook("TTTC4Disarm") 5 | Damagelog:EventHook("TTTC4Destroyed") 6 | Damagelog:EventHook("TTTC4Pickup") 7 | Damagelog:EventHook("TTTC4Explode") 8 | else 9 | Damagelog:AddFilter("filter_show_c4", DAMAGELOG_FILTER_BOOL, true) 10 | Damagelog:AddColor("color_c4", Color(128, 64, 0)) 11 | end 12 | 13 | local event = {} 14 | event.Type = "C4" 15 | 16 | function event:TTTC4Arm(bomb, ply) 17 | event.CallEvent({ 18 | [1] = 1, 19 | [2] = ply:Nick(), 20 | [3] = ply:GetRole(), 21 | [4] = ply:SteamID(), 22 | [5] = IsValid(bomb:GetOwner()) and bomb:GetOwner():Nick() or TTTLogTranslate(GetDMGLogLang, "ChatDisconnected") 23 | }) 24 | end 25 | 26 | function event:TTTC4Disarm(bomb, result, ply) 27 | event.CallEvent({ 28 | [1] = 2, 29 | [2] = ply:Nick(), 30 | [3] = ply:GetRole(), 31 | [4] = ply:SteamID(), 32 | [5] = IsValid(bomb:GetOwner()) and bomb:GetOwner():Nick() or TTTLogTranslate(GetDMGLogLang, "ChatDisconnected"), 33 | [6] = result 34 | }) 35 | end 36 | 37 | function event:TTTC4Destroyed(bomb, ply) 38 | event.CallEvent({ 39 | [1] = 5, 40 | [2] = ply:Nick(), 41 | [3] = ply:GetRole(), 42 | [4] = ply:SteamID(), 43 | [5] = IsValid(bomb:GetOwner()) and bomb:GetOwner():Nick() or TTTLogTranslate(GetDMGLogLang, "ChatDisconnected") 44 | }) 45 | end 46 | 47 | function event:TTTC4Pickup(bomb, ply) 48 | event.CallEvent({ 49 | [1] = 3, 50 | [2] = ply:Nick(), 51 | [3] = ply:GetRole(), 52 | [4] = ply:SteamID(), 53 | [5] = IsValid(bomb:GetOwner()) and bomb:GetOwner():Nick() or TTTLogTranslate(GetDMGLogLang, "ChatDisconnected") 54 | }) 55 | end 56 | 57 | function event:TTTC4Explode(bomb) 58 | local owner = bomb:GetOwner() 59 | local ownervalid = IsValid(owner) 60 | 61 | self.CallEvent({ 62 | [1] = 6, 63 | [2] = ownervalid and owner:Nick() or TTTLogTranslate(GetDMGLogLang, "ChatDisconnected"), 64 | [3] = ownervalid and owner:GetRole() or -1 65 | }) 66 | end 67 | 68 | function event:Initialize() 69 | for _, v in pairs(weapons.GetList()) do 70 | if v.ClassName == "weapon_ttt_c4" then 71 | local old_stick = v.BombStick 72 | local old_drop = v.BombDrop 73 | 74 | local function LogC4(bomb) 75 | event.CallEvent({ 76 | [1] = 4, 77 | [2] = bomb.Owner:Nick(), 78 | [3] = bomb.Owner:GetRole(), 79 | [4] = bomb.Owner:SteamID() 80 | }) 81 | end 82 | 83 | v.BombStick = function(bomb) 84 | LogC4(bomb) 85 | old_stick(bomb) 86 | end 87 | 88 | v.BombDrop = function(bomb) 89 | LogC4(bomb) 90 | old_drop(bomb) 91 | end 92 | end 93 | end 94 | end 95 | 96 | function event:ToString(v) 97 | if v[1] == 1 then 98 | return string.format(TTTLogTranslate(GetDMGLogLang, "C4Armed"), v[2], Damagelog:StrRole(v[3]), v[5]) 99 | elseif v[1] == 2 then 100 | return string.format(TTTLogTranslate(GetDMGLogLang, "C4Disarmed"), v[2], Damagelog:StrRole(v[3]), v[5], v[6] and TTTLogTranslate(GetDMGLogLang, "with") or TTTLogTranslate(GetDMGLogLang, "without")) 101 | elseif v[1] == 3 then 102 | return string.format(TTTLogTranslate(GetDMGLogLang, "C4PickedUp"), v[2], Damagelog:StrRole(v[3]), v[5]) 103 | elseif v[1] == 4 then 104 | return string.format(TTTLogTranslate(GetDMGLogLang, "C4Planted"), v[2], Damagelog:StrRole(v[3])) 105 | elseif v[1] == 5 then 106 | return string.format(TTTLogTranslate(GetDMGLogLang, "C4Destroyed"), v[2], Damagelog:StrRole(v[3]), v[5]) 107 | elseif v[1] == 6 then 108 | return string.format(TTTLogTranslate(GetDMGLogLang, "C4Exploded"), v[2], v[3] == -1 and "?" or Damagelog:StrRole(v[3])) 109 | end 110 | end 111 | 112 | function event:IsAllowed(tbl) 113 | return Damagelog.filter_settings["filter_show_c4"] 114 | end 115 | 116 | function event:Highlight(line, tbl, text) 117 | return table.HasValue(Damagelog.Highlighted, tbl[3]) 118 | end 119 | 120 | function event:GetColor(tbl) 121 | return Damagelog:GetColor("color_c4") 122 | end 123 | 124 | function event:RightClick(line, tbl, text) 125 | line:ShowTooLong(true) 126 | line:ShowCopy(true, {tbl[2], tbl[4]}) 127 | end 128 | 129 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/kills.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("DoPlayerDeath") 3 | else 4 | Damagelog:AddFilter("filter_show_kills", DAMAGELOG_FILTER_BOOL, true) 5 | Damagelog:AddColor("color_team_kills", Color(255, 40, 40)) 6 | Damagelog:AddColor("color_kills", Color(255, 128, 0, 255)) 7 | end 8 | 9 | local event = {} 10 | event.Type = "KILL" 11 | 12 | function event:DoPlayerDeath(ply, attacker, dmginfo) 13 | -- If the player was pushed, set the attacker to the person that pushed them 14 | local playerThatPushed = ply:GetPlayerThatRecentlyPushedMe() 15 | if (playerThatPushed ~= nil and dmginfo:GetDamageType() == DMG_FALL and attacker:IsWorld()) then 16 | attacker = playerThatPushed 17 | end 18 | 19 | -- Ignore spectators 20 | if (ply.IsGhost and ply:IsGhost()) then return end 21 | 22 | -- Ignore suicides/drownings. These are handled in the suicide.lua event file 23 | -- This may also ignore environmental deaths, caused by another player 24 | if (IsValid(attacker) and attacker:IsPlayer() and attacker ~= ply) == false then return end 25 | 26 | local scene = false 27 | Damagelog.SceneID = Damagelog.SceneID + 1 28 | scene = Damagelog.SceneID 29 | Damagelog.SceneRounds[scene] = Damagelog.CurrentRound 30 | 31 | local tbl = { 32 | [1] = attacker:GetDamagelogID(), 33 | [2] = ply:GetDamagelogID(), 34 | [3] = Damagelog:WeaponFromDmg(dmginfo), 35 | [4] = scene 36 | } 37 | 38 | self.CallEvent(tbl) 39 | 40 | if scene then 41 | timer.Simple(0.6, function() 42 | Damagelog.Death_Scenes[scene] = table.Copy(Damagelog.Records) 43 | end) 44 | end 45 | 46 | if GetRoundState() == ROUND_ACTIVE then 47 | net.Start("DL_Ded") 48 | 49 | if attacker:GetRole() == ROLE_TRAITOR and (ply:GetRole() == ROLE_INNOCENT or ply:GetRole() == ROLE_DETECTIVE) 50 | or TTT2 and attacker:GetTeam() == TEAM_TRAITOR and not attacker:IsInTeam(ply) 51 | -- Jesters are sometimes supposed to be killed, but when they aren't they generally have their own punishment mechanism so we don't need to mark this kill as RDM 52 | -- Jesters also aren't supposed to be able to kill anyone, but there are some workshop weapons that bypass the damage blocking so we'll just allow their kills as non-RDM to be safe 53 | -- Traitors, Monsters, and Indepedents are supposed to be killing everyone else so it's only RDM when they kill their own team (there are some edge cases with independents, but we won't get into that) 54 | or CR_VERSION and (attacker:IsJesterTeam() or ply:IsJesterTeam() or ((attacker:IsTraitorTeam() or attacker:IsMonsterTeam() or attacker:IsIndependentTeam()) and not attacker:IsSameTeam(ply))) then 55 | net.WriteUInt(0, 1) 56 | else 57 | net.WriteUInt(1, 1) 58 | net.WriteString(attacker:Nick()) 59 | end 60 | 61 | net.Send(ply) 62 | ply:SetNWEntity("DL_Killer", attacker) 63 | end 64 | end 65 | 66 | function event:ToString(v, roles) 67 | local weapon = v[3] 68 | weapon = Damagelog:GetWeaponName(weapon) 69 | local attackerInfo = Damagelog:InfoFromID(roles, v[1]) 70 | local victimInfo = Damagelog:InfoFromID(roles, v[2]) 71 | 72 | return string.format(TTTLogTranslate(GetDMGLogLang, "HasKilled"), attackerInfo.nick, Damagelog:StrRole(attackerInfo.role), victimInfo.nick, Damagelog:StrRole(victimInfo.role), weapon or TTTLogTranslate(GetDMGLogLang, "UnknownWeapon")) 73 | end 74 | 75 | function event:IsAllowed(tbl) 76 | return Damagelog.filter_settings["filter_show_kills"] 77 | end 78 | 79 | function event:Highlight(line, tbl, text) 80 | if table.HasValue(Damagelog.Highlighted, tbl[1]) or table.HasValue(Damagelog.Highlighted, tbl[2]) then 81 | return true 82 | end 83 | 84 | return false 85 | end 86 | 87 | function event:GetColor(tbl, roles) 88 | local ent = Damagelog:InfoFromID(roles, tbl[1]) 89 | local att = Damagelog:InfoFromID(roles, tbl[2]) 90 | 91 | if Damagelog:IsTeamkill(att.role, ent.role) then 92 | return Damagelog:GetColor("color_team_kills") 93 | else 94 | return Damagelog:GetColor("color_kills") 95 | end 96 | end 97 | 98 | function event:RightClick(line, tbl, roles, text) 99 | local attackerInfo = Damagelog:InfoFromID(roles, tbl[1]) 100 | local victimInfo = Damagelog:InfoFromID(roles, tbl[2]) 101 | line:ShowTooLong(true) 102 | line:ShowCopy(true, {attackerInfo.nick, util.SteamIDFrom64(attackerInfo.steamid64)}, {victimInfo.nick, util.SteamIDFrom64(victimInfo.steamid64)}) 103 | line:ShowDamageInfos(tbl[1], tbl[2]) 104 | line:ShowDeathScene(tbl[2], tbl[1], tbl[4]) 105 | end 106 | 107 | Damagelog:AddEvent(event) 108 | -------------------------------------------------------------------------------- /lua/damagelogs/client/settings.lua: -------------------------------------------------------------------------------- 1 | CreateClientConVar("ttt_dmglogs_rdmpopups", "1", FCVAR_ARCHIVE) 2 | CreateClientConVar("ttt_dmglogs_currentround", "0", FCVAR_ARCHIVE) 3 | CreateClientConVar("ttt_dmglogs_updatenotifications", "1", FCVAR_ARCHIVE) 4 | CreateClientConVar("ttt_dmglogs_showpending", "1", FCVAR_ARCHIVE) 5 | CreateClientConVar("ttt_dmglogs_enablesound", "1", FCVAR_ARCHIVE) 6 | CreateClientConVar("ttt_dmglogs_enablesoundoutside", "0", FCVAR_ARCHIVE) 7 | CreateClientConVar("ttt_dmglogs_shotlogsondamagetab", "0", FCVAR_ARCHIVE) 8 | 9 | hook.Add("TTTSettingsTabs", "DamagelogsTTTSettingsTab", function(dtabs) 10 | local padding = dtabs:GetPadding() * 2 11 | local dsettings = vgui.Create("DPanelList", dtabs) 12 | dsettings:StretchToParent(0, 0, padding, 0) 13 | dsettings:EnableVerticalScrollbar(true) 14 | dsettings:SetPadding(10) 15 | dsettings:SetSpacing(10) 16 | local dgui = vgui.Create("DForm", dsettings) 17 | dgui:SetName(TTTLogTranslate(GetDMGLogLang, "Generalsettings")) 18 | local selectedcolor 19 | local dmgLang = vgui.Create("DComboBox") 20 | 21 | for k in pairs(DamagelogLang) do 22 | dmgLang:AddChoice(string.upper(string.sub(k, 1, 1)) .. string.sub(k, 2, 100)) 23 | end 24 | 25 | if Damagelog.ForcedLanguage == "" then 26 | dmgLang:SetDisabled(false) 27 | dmgLang:ChooseOption(string.upper(string.sub(GetConVar("ttt_dmglogs_language"):GetString(), 1, 1)) .. string.sub(GetConVar("ttt_dmglogs_language"):GetString(), 2, 100)) 28 | else 29 | dmgLang:SetDisabled(true) 30 | dmgLang:SetTooltip(TTTLogTranslate(GetDMGLogLang, "ForcedLanguage")) 31 | dmgLang:ChooseOption(string.upper(string.sub(Damagelog.ForcedLanguage, 1, 1)) .. string.sub(Damagelog.ForcedLanguage, 2, 100)) 32 | end 33 | 34 | dmgLang.OnSelect = function(panel, index, value, data, Damagelog) 35 | local currentLanguage = GetConVar("ttt_dmglogs_language"):GetString() 36 | local newLang = string.lower(value) 37 | 38 | if currentLanguage == newLang then 39 | return 40 | end 41 | 42 | RunConsoleCommand("ttt_dmglogs_language", newLang) 43 | end 44 | 45 | dgui:AddItem(dmgLang) 46 | dgui:CheckBox(TTTLogTranslate(GetDMGLogLang, "UpdateNotifications"), "ttt_dmglogs_updatenotifications") 47 | dgui:CheckBox(TTTLogTranslate(GetDMGLogLang, "RDMuponRDM"), "ttt_dmglogs_rdmpopups") 48 | dgui:CheckBox(TTTLogTranslate(GetDMGLogLang, "CurrentRoundLogs"), "ttt_dmglogs_currentround") 49 | dgui:CheckBox(TTTLogTranslate(GetDMGLogLang, "ShowPendingReports"), "ttt_dmglogs_showpending") 50 | dgui:CheckBox(TTTLogTranslate(GetDMGLogLang, "EnableSound"), "ttt_dmglogs_enablesound") 51 | dgui:CheckBox(TTTLogTranslate(GetDMGLogLang, "OutsideNotification"), "ttt_dmglogs_enablesoundoutside") 52 | dgui:CheckBox(TTTLogTranslate(GetDMGLogLang, "ShotLogsDamageTab"), "ttt_dmglogs_shotlogsondamagetab") 53 | dsettings:AddItem(dgui) 54 | local colorSettings = vgui.Create("DForm") 55 | colorSettings:SetName(TTTLogTranslate(GetDMGLogLang, "Colors")) 56 | local colorChoice = vgui.Create("DComboBox") 57 | 58 | for k in pairs(Damagelog.colors) do 59 | colorChoice:AddChoice(TTTLogTranslate(GetDMGLogLang, k), k) 60 | end 61 | 62 | colorChoice:ChooseOptionID(1) 63 | colorSettings:AddItem(colorChoice) 64 | local colorMixer = vgui.Create("DColorMixer") 65 | colorMixer:SetHeight(200) 66 | local found = false 67 | 68 | colorChoice.OnSelect = function(panel, index, value, data) 69 | colorMixer:SetColor(Damagelog.colors[data].Custom) 70 | selectedcolor = data 71 | end 72 | 73 | for k, v in pairs(Damagelog.colors) do 74 | if not found then 75 | colorMixer:SetColor(v.Custom) 76 | selectedcolor = k 77 | found = true 78 | break 79 | end 80 | end 81 | 82 | colorSettings:AddItem(colorMixer) 83 | local saveColor = vgui.Create("DButton") 84 | saveColor:SetText(TTTLogTranslate(GetDMGLogLang, "Save")) 85 | 86 | saveColor.DoClick = function() 87 | local c = colorMixer:GetColor() 88 | Damagelog.colors[selectedcolor].Custom = c 89 | Damagelog:SaveColors() 90 | end 91 | 92 | colorSettings:AddItem(saveColor) 93 | local defaultcolor = vgui.Create("DButton") 94 | defaultcolor:SetText(TTTLogTranslate(GetDMGLogLang, "SetDefault")) 95 | 96 | defaultcolor.DoClick = function() 97 | local c = Damagelog.colors[selectedcolor].Default 98 | colorMixer:SetColor(c) 99 | Damagelog.colors[selectedcolor].Custom = c 100 | Damagelog:SaveColors() 101 | end 102 | 103 | colorSettings:AddItem(defaultcolor) 104 | dsettings:AddItem(colorSettings) 105 | dtabs:AddSheet("Damagelogs", dsettings, "icon16/table_gear.png", false, false, TTTLogTranslate(GetDMGLogLang, "DamagelogMenuSettings")) 106 | end) 107 | -------------------------------------------------------------------------------- /lua/damagelogs/server/damageinfos.lua: -------------------------------------------------------------------------------- 1 | util.AddNetworkString("DL_AskDamageInfos") 2 | util.AddNetworkString("DL_SendDamageInfos") 3 | util.AddNetworkString("DL_AskShootLogs") 4 | util.AddNetworkString("DL_SendShootLogs") 5 | 6 | function Damagelog:shootCallback(weapon) 7 | if not self.Time then 8 | return 9 | end 10 | 11 | local owner = weapon.Owner or weapon:GetOwner() 12 | 13 | if not IsValid(owner) then 14 | return 15 | end 16 | 17 | local id = owner:GetDamagelogID() 18 | 19 | if id == -1 then 20 | return 21 | end 22 | 23 | local class = (IsValid(weapon) and weapon:GetClass() or "NoClass") 24 | local info = {id, class} 25 | 26 | if GetRoundState() == ROUND_ACTIVE then 27 | if not self.ShootTables[self.CurrentRound][self.Time] then 28 | self.ShootTables[self.CurrentRound][self.Time] = {} 29 | end 30 | 31 | table.insert(self.ShootTables[self.CurrentRound][self.Time], info) 32 | local length = #Damagelog.Records 33 | 34 | if length > 0 and Damagelog.Records[length][id] then 35 | local sound = weapon.Primary and weapon.Primary.Sound 36 | 37 | if sound then 38 | Damagelog.Records[length][id].shot = sound 39 | end 40 | 41 | local td 42 | 43 | if owner == weapon then 44 | td = { 45 | start = owner:GetPos(), 46 | entpos = owner:GetPos() + owner:GetAngles():Forward() * 10000, 47 | filter = {owner} 48 | } 49 | else 50 | td = util.GetPlayerTrace(owner) 51 | end 52 | 53 | local trace = util.TraceLine(td) 54 | Damagelog.Records[length][id].trace = {trace.StartPos, trace.HitPos} 55 | end 56 | end 57 | end 58 | 59 | hook.Add("EntityFireBullets", "BulletsCallback_DamagelogInfos", function(ent, data) 60 | if not IsValid(ent) or not ent:IsPlayer() then 61 | return 62 | end 63 | 64 | local wep = ent:GetActiveWeapon() 65 | 66 | if IsValid(wep) and wep.Base == "weapon_tttbase" then 67 | Damagelog:shootCallback(wep) 68 | end 69 | end) 70 | 71 | function Damagelog:SendDamageInfos(ply, t, att, victim, round) 72 | local results = {} 73 | local found = false 74 | local data 75 | 76 | if round == -1 then 77 | data = Damagelog.PreviousMap.ShootTable 78 | else 79 | data = Damagelog.ShootTables[round] 80 | end 81 | 82 | if not data then 83 | return 84 | end 85 | 86 | for k, v in pairs(data) do 87 | if k >= t - 10 and k <= t then 88 | for _, i in pairs(v) do 89 | if i[1] == victim or i[1] == att then 90 | if not results[k] then 91 | table.insert(results, k, {}) 92 | end 93 | 94 | table.insert(results[k], i) 95 | found = true 96 | end 97 | end 98 | end 99 | end 100 | 101 | local beg = t - 10 102 | local victimNick = self:InfoFromID(self.Roles[round], victim).nick 103 | local attNick = self:InfoFromID(self.Roles[round], att).nick 104 | net.Start("DL_SendDamageInfos") 105 | net.WriteUInt(beg, 32) 106 | net.WriteUInt(t, 32) 107 | net.WriteString(victimNick) 108 | net.WriteString(attNick) 109 | net.WriteUInt(victim, 32) 110 | net.WriteUInt(att, 32) 111 | 112 | if found then 113 | net.WriteUInt(1, 1) 114 | net.WriteTable(results) 115 | else 116 | net.WriteUInt(0, 1) 117 | end 118 | 119 | net.Send(ply) 120 | end 121 | 122 | net.Receive("DL_AskDamageInfos", function(_, ply) 123 | local time = net.ReadUInt(32) 124 | local attacker = net.ReadUInt(32) 125 | local victim = net.ReadUInt(32) 126 | local round = net.ReadUInt(32) 127 | Damagelog:SendDamageInfos(ply, time, attacker, victim, round) 128 | end) 129 | 130 | net.Receive("DL_AskShootLogs", function(_, ply) 131 | local round = net.ReadInt(8) 132 | 133 | if not ply:CanUseDamagelog() and round == Damagelog:GetSyncEnt():GetPlayedRounds() then 134 | return 135 | end 136 | 137 | local data, roles 138 | 139 | if round == -1 then 140 | data = Damagelog.PreviousMap.ShootTable 141 | roles = Damagelog.PreviousMap.Roles 142 | else 143 | data = Damagelog.ShootTables[round] 144 | roles = Damagelog.Roles[round] 145 | end 146 | 147 | if not roles or not data then 148 | return 149 | end 150 | roles = util.Compress(util.TableToJSON(roles)) 151 | data = util.Compress(util.TableToJSON(data)) 152 | net.Start("DL_SendShootLogs") 153 | net.WriteUInt(#roles, 15) 154 | net.WriteData(roles, #roles) 155 | net.WriteUInt(#data, 15) 156 | net.WriteData(data, #data) 157 | net.Send(ply) 158 | end) 159 | -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/damages.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("Initialize") 3 | Damagelog:EventHook("PlayerTakeRealDamage") 4 | else 5 | Damagelog:AddFilter("filter_show_damages", DAMAGELOG_FILTER_BOOL, true) 6 | Damagelog:AddColor("color_team_damages", Color(255, 40, 40)) 7 | Damagelog:AddColor("color_damages", Color(0, 0, 0)) 8 | end 9 | 10 | local event = {} 11 | event.Type = "DMG" 12 | event.IsDamage = true 13 | 14 | function event:Initialize() 15 | local old_func = GAMEMODE.PlayerTakeDamage 16 | 17 | function GAMEMODE:PlayerTakeDamage(ent, infl, att, amount, dmginfo) 18 | local original_dmg = dmginfo:GetDamage() 19 | 20 | if IsValid(att) then 21 | old_func(self, ent, infl, att, amount, dmginfo) 22 | end 23 | 24 | hook.Call("PlayerTakeRealDamage", GAMEMODE, ent, dmginfo, original_dmg) 25 | end 26 | end 27 | 28 | function event:PlayerTakeRealDamage(ent, dmginfo, original_dmg) 29 | local att = dmginfo:GetAttacker() 30 | 31 | if not (ent.IsGhost and ent:IsGhost()) and ent:IsPlayer() and (IsValid(att) and att:IsPlayer()) and ent ~= att then 32 | if math.floor(original_dmg) > 0 then 33 | local tbl = { 34 | [1] = ent:GetDamagelogID(), 35 | [2] = att:GetDamagelogID(), 36 | [3] = math.Round(dmginfo:GetDamage()), 37 | [4] = Damagelog:WeaponFromDmg(dmginfo), 38 | [5] = math.Round(original_dmg) 39 | } 40 | 41 | if Damagelog:IsTeamkill(att.role, ent.role) then 42 | tbl.icon = {"icon16/exclamation.png"} 43 | elseif Damagelog.Time then 44 | local found_dmg = false 45 | 46 | for _, v in pairs(Damagelog.DamageTable) do 47 | if type(v) == "table" and Damagelog.events[v.id] and Damagelog.events[v.id].IsDamage then 48 | if v.time >= Damagelog.Time - 10 and v.time <= Damagelog.Time then 49 | found_dmg = true 50 | break 51 | end 52 | end 53 | end 54 | 55 | if not found_dmg then 56 | local first 57 | local shoots = {} 58 | 59 | for k, v in pairs(Damagelog.ShootTables[Damagelog.CurrentRound] or {}) do 60 | if k >= Damagelog.Time - 10 and k <= Damagelog.Time then 61 | shoots[k] = v 62 | end 63 | end 64 | 65 | for k in pairs(shoots) do 66 | if not first or k < first then 67 | first = k 68 | end 69 | end 70 | 71 | if shoots[first] then 72 | for _, v in pairs(shoots[first]) do 73 | if v[1] == ent:Nick() then 74 | tbl.icon = {"icon16/error.png", TTTLogTranslate(GetDMGLogLang, "VictimShotFirst")} 75 | end 76 | end 77 | end 78 | end 79 | end 80 | 81 | self.CallEvent(tbl) 82 | end 83 | end 84 | end 85 | 86 | function event:ToString(tbl, roles) 87 | local weapon = tbl[4] 88 | weapon = Damagelog:GetWeaponName(weapon) 89 | local karma_reduced = tbl[3] < tbl[5] 90 | local ent = Damagelog:InfoFromID(roles, tbl[1]) 91 | local att = Damagelog:InfoFromID(roles, tbl[2]) 92 | local str = string.format(TTTLogTranslate(GetDMGLogLang, "HasDamaged"), att.nick, Damagelog:StrRole(att.role), ent.nick, Damagelog:StrRole(ent.role), tbl[3]) 93 | 94 | if karma_reduced then 95 | str = str .. string.format(" (%s)", tbl[5]) 96 | end 97 | 98 | return str .. string.format(TTTLogTranslate(GetDMGLogLang, "HPWeapon"), weapon or TTTLogTranslate(GetDMGLogLang, "UnknownWeapon")) 99 | end 100 | 101 | function event:IsAllowed(tbl) 102 | return Damagelog.filter_settings["filter_show_damages"] 103 | end 104 | 105 | function event:Highlight(line, tbl, text) 106 | if table.HasValue(Damagelog.Highlighted, tbl[1]) or table.HasValue(Damagelog.Highlighted, tbl[2]) then 107 | return true 108 | end 109 | 110 | return false 111 | end 112 | 113 | function event:GetColor(tbl, roles) 114 | local ent = Damagelog:InfoFromID(roles, tbl[1]) 115 | local att = Damagelog:InfoFromID(roles, tbl[2]) 116 | 117 | if Damagelog:IsTeamkill(att.role, ent.role) then 118 | return Damagelog:GetColor("color_team_damages") 119 | else 120 | return Damagelog:GetColor("color_damages") 121 | end 122 | end 123 | 124 | function event:RightClick(line, tbl, roles, text) 125 | line:ShowTooLong(true) 126 | local attackerInfo = Damagelog:InfoFromID(roles, tbl[1]) 127 | local victimInfo = Damagelog:InfoFromID(roles, tbl[2]) 128 | line:ShowCopy(true, {attackerInfo.nick, util.SteamIDFrom64(attackerInfo.steamid64)}, {victimInfo.nick, util.SteamIDFrom64(victimInfo.steamid64)}) 129 | line:ShowDamageInfos(tbl[2], tbl[1]) 130 | end 131 | 132 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/shared/events/misc.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | Damagelog:EventHook("TTTToggleDisguiser") 3 | Damagelog:EventHook("TTTBeginRound") 4 | Damagelog:EventHook("TTTTraitorButtonActivated") 5 | Damagelog:EventHook("TTTBoughtRoleT") 6 | Damagelog:EventHook("Initialize") 7 | else 8 | Damagelog:AddFilter("filter_show_disguises", DAMAGELOG_FILTER_BOOL, false) 9 | Damagelog:AddFilter("filter_show_teleports", DAMAGELOG_FILTER_BOOL, true) 10 | Damagelog:AddFilter("filter_show_traps", DAMAGELOG_FILTER_BOOL, true) 11 | Damagelog:AddFilter("filter_show_psroles", DAMAGELOG_FILTER_BOOL, true) 12 | Damagelog:AddColor("color_misc", Color(0, 179, 179, 255)) 13 | end 14 | 15 | local event = {} 16 | event.Type = "MISC" 17 | 18 | function event:TTTToggleDisguiser(ply, state) 19 | if ply.NoDisguise then 20 | return 21 | end 22 | 23 | local timername = "DisguiserTimer_" .. tostring(ply:SteamID()) 24 | 25 | if not timer.Exists(timername) then 26 | ply.DisguiseUses = 1 27 | ply.DisguiseTimer = 10 28 | 29 | timer.Create(timername, 1, 0, function() 30 | if not IsValid(ply) then 31 | timer.Remove(timername) 32 | else 33 | ply.DisguiseTimer = ply.DisguiseTimer - 1 34 | 35 | if ply.DisguiseTimer <= 0 then 36 | timer.Remove(timername) 37 | end 38 | 39 | if ply.DisguiseUses > 6 then 40 | ply.NoDisguise = true 41 | 42 | self.CallEvent({ 43 | [1] = 3, 44 | [2] = ply:GetDamagelogID() 45 | }) 46 | 47 | timer.Remove(timername) 48 | end 49 | end 50 | end) 51 | else 52 | if ply.DisguiseUses and ply.DisguiseTimer then 53 | ply.DisguiseUses = ply.DisguiseUses + 1 54 | ply.DisguiseTimer = ply.DisguiseTimer + 1 55 | end 56 | end 57 | 58 | self.CallEvent({ 59 | [1] = 1, 60 | [2] = ply:GetDamagelogID(), 61 | [3] = state 62 | }) 63 | end 64 | 65 | function event:TTTBeginRound() 66 | for _, v in ipairs(player.GetHumans()) do 67 | if v.NoDisguise then 68 | v.NoDisguise = false 69 | end 70 | end 71 | end 72 | 73 | function event:TTTTraitorButtonActivated(btn, ply) 74 | if not IsValid(ply) or not ply:IsActive() then 75 | return 76 | end 77 | 78 | self.CallEvent({ 79 | [1] = 4, 80 | [2] = ply:GetDamagelogID(), 81 | [3] = btn:GetDescription() 82 | }) 83 | end 84 | 85 | function event:TTTBoughtRoleT(ply) 86 | if not IsValid(ply) or not ply:IsActive() then 87 | return 88 | end 89 | 90 | self.CallEvent({ 91 | [1] = 5, 92 | [2] = ply:GetDamagelogID() 93 | }) 94 | end 95 | 96 | function event:Initialize() 97 | local weap = weapons.GetStored("weapon_ttt_teleport") 98 | local old_func = weap.TakePrimaryAmmo 99 | 100 | weap.TakePrimaryAmmo = function(wep, count) 101 | self.CallEvent({ 102 | [1] = 2, 103 | [2] = wep.Owner:GetDamagelogID() 104 | }) 105 | 106 | if old_func then 107 | return old_func(wep, count) 108 | else 109 | return wep.BaseClass.TakePrimaryAmmo(wep, count) 110 | end 111 | end 112 | end 113 | 114 | function event:ToString(v, roles) 115 | local ply = Damagelog:InfoFromID(roles, v[2]) 116 | 117 | if v[1] == 1 then 118 | return string.format(TTTLogTranslate(GetDMGLogLang, "DisguiserAct"), ply.nick, Damagelog:StrRole(ply.role), v[3] and TTTLogTranslate(GetDMGLogLang, "enabled") or TTTLogTranslate(GetDMGLogLang, "disabled")) 119 | elseif v[1] == 2 then 120 | return string.format(TTTLogTranslate(GetDMGLogLang, "Teleported"), ply.nick, Damagelog:StrRole(ply.role)) 121 | elseif v[1] == 3 then 122 | return string.format(TTTLogTranslate(GetDMGLogLang, "DisguiserSpam"), ply.nick, Damagelog:StrRole(ply.role)) 123 | elseif v[1] == 4 then 124 | return string.format(TTTLogTranslate(GetDMGLogLang, "TrapActivated"), ply.nick, v[3]) 125 | elseif v[1] == 5 then 126 | return string.format(TTTLogTranslate(GetDMGLogLang, "RoundBought"), ply.nick) 127 | end 128 | end 129 | 130 | function event:IsAllowed(tbl) 131 | if (tbl[1] == 1 or tbl[1] == 3) and not Damagelog.filter_settings["filter_show_disguises"] then 132 | return false 133 | end 134 | 135 | if tbl[1] == 2 and not Damagelog.filter_settings["filter_show_teleports"] then 136 | return false 137 | end 138 | 139 | if tbl[1] == 4 and not Damagelog.filter_settings["filter_show_traps"] then 140 | return false 141 | end 142 | 143 | if tbl[1] == 5 and not Damagelog.filter_settings["filter_show_psroles"] then 144 | return false 145 | end 146 | 147 | return true 148 | end 149 | 150 | function event:Highlight(line, tbl, text) 151 | return table.HasValue(Damagelog.Highlighted, tbl[2]) 152 | end 153 | 154 | function event:GetColor(tbl) 155 | return Damagelog:GetColor("color_misc") 156 | end 157 | 158 | function event:RightClick(line, tbl, roles, text) 159 | line:ShowTooLong(true) 160 | local ply = Damagelog:InfoFromID(roles, tbl[2]) 161 | line:ShowCopy(true, {ply.nick, util.SteamIDFrom64(ply.steamid64)}) 162 | end 163 | 164 | Damagelog:AddEvent(event) -------------------------------------------------------------------------------- /lua/damagelogs/server/discord.lua: -------------------------------------------------------------------------------- 1 | local POST_MODES = { 2 | DISABLED = 0, 3 | WHEN_ADMINS_OFFLINE = 1, 4 | ALWAYS = 2 5 | } 6 | 7 | local HTTP = HTTP 8 | local url = CreateConVar("ttt_dmglogs_discordurl", "", FCVAR_PROTECTED + FCVAR_LUA_SERVER, "TTTDamagelogs - Discord Webhook URL") 9 | local disabled = Damagelog.DiscordWebhookMode == POST_MODES.DISABLED 10 | local emitOnlyWhenAdminsOffline = Damagelog.DiscordWebhookMode == POST_MODES.WHEN_ADMINS_OFFLINE 11 | local limit = 5 12 | local reset = 0 13 | 14 | local use_chttp = pcall(require, "chttp") 15 | if use_chttp then 16 | HTTP = CHTTP 17 | end 18 | 19 | local function SendDiscordMessage(embed) 20 | local now = os.time(os.date("!*t")) 21 | 22 | if limit == 0 and now < reset then 23 | local function tcb() 24 | SendDiscordMessage(embed) 25 | end 26 | 27 | timer.Simple(reset - now, tcb) 28 | end 29 | 30 | local function successCallback(status, body, headers) 31 | limit = headers["X-RateLimit-Remaining"] 32 | reset = headers["X-RateLimit-Reset"] 33 | end 34 | 35 | HTTP({ 36 | method = "POST", 37 | url = url:GetString(), 38 | body = util.TableToJSON({ 39 | embeds = {embed} 40 | }), 41 | type = "application/json", 42 | success = successCallback 43 | }) 44 | end 45 | 46 | 47 | function Damagelog:DiscordMessage(discordUpdate) 48 | if disabled or (emitOnlyWhenAdminsOffline and discordUpdate.adminOnline) then 49 | return 50 | end 51 | 52 | local data = { 53 | title = TTTLogTranslate(nil, "webhook_header_report_submitted"):format(discordUpdate.reportId), 54 | description = TTTLogTranslate(nil, "webhook_ServerInfo"):format(game.GetMap(), discordUpdate.round), 55 | timestamp = os.date("!%Y-%m-%dT%H:%M:%S.000Z"), 56 | fields = { 57 | { 58 | name = TTTLogTranslate(nil, "Victim") .. ":", 59 | value = "[" .. discordUpdate.victim.nick:gsub("([%*_~<>\\@%]])", "\\%1") .. "](https://steamcommunity.com/profiles/" .. util.SteamIDTo64(discordUpdate.victim.steamID) .. ")\n" .. discordUpdate.victim.steamID, 60 | inline = true 61 | }, 62 | { 63 | name = TTTLogTranslate(nil, "ReportedPlayer") .. ":", 64 | value = "[" .. discordUpdate.attacker.nick:gsub("([%*_~<>\\@%]])", "\\%1") .. "](https://steamcommunity.com/profiles/" .. util.SteamIDTo64(discordUpdate.attacker.steamID) .. ")\n" .. discordUpdate.attacker.steamID, 65 | inline = true 66 | }, 67 | { 68 | name = TTTLogTranslate(nil, "VictimsReport") .. ":", 69 | value = discordUpdate.reportMessage:gsub("([%*_~<>\\@[])", "\\%1") 70 | } 71 | }, 72 | color = 0xffff00 73 | } 74 | 75 | 76 | if discordUpdate.responseMessage ~= nil then 77 | local forgivenRow = { 78 | name = TTTLogTranslate(nil, "ReportedPlayerResponse") .. ":", 79 | value = discordUpdate.responseMessage:gsub("([%*_~<>\\@[])", "\\%1") 80 | } 81 | table.insert(data.fields, forgivenRow) 82 | end 83 | 84 | 85 | if discordUpdate.reportForgiven ~= nil then 86 | local rowMessage = "" 87 | if discordUpdate.reportForgiven.forgiven then 88 | data.title = TTTLogTranslate(nil, "webhook_header_report_forgiven"):format(discordUpdate.reportId) 89 | data.color = 0x00ff00 90 | rowMessage = TTTLogTranslate(nil, "webhook_report_forgiven") 91 | else 92 | data.title = TTTLogTranslate(nil, "webhook_header_report_kept"):format(discordUpdate.reportId) 93 | data.color = 0xff0000 94 | rowMessage = TTTLogTranslate(nil, "webhook_report_kept") 95 | end 96 | 97 | local forgivenRow = { 98 | name = TTTLogTranslate(nil, "webhook_report_forgiven_or_kept") .. ":", 99 | value = rowMessage 100 | } 101 | table.insert(data.fields, forgivenRow) 102 | end 103 | 104 | 105 | if discordUpdate.reportHandled ~= nil then 106 | data.title = TTTLogTranslate(nil, "webhook_header_report_finished"):format(discordUpdate.reportId) 107 | data.color = 0x0394fc 108 | 109 | local rowMessage = "[" .. discordUpdate.reportHandled.admin.nick:gsub("([%*_~<>\\@%]])", "\\%1") .. "](https://steamcommunity.com/profiles/" .. util.SteamIDTo64(discordUpdate.reportHandled.admin.steamID) .. ")" 110 | 111 | local reportHandlerRow = { 112 | name = TTTLogTranslate(nil, "webhook_report_finished_by") .. ":", 113 | value = rowMessage, 114 | inline = true 115 | } 116 | table.insert(data.fields, reportHandlerRow) 117 | 118 | local minutesTaken = math.floor(discordUpdate.reportHandled.secondsTaken / 60) 119 | local secondsTaken = discordUpdate.reportHandled.secondsTaken % 60 120 | local reportHandlerTimeRow = { 121 | name = TTTLogTranslate(nil, "webhook_report_finished_time_taken") .. ":", 122 | value = string.format("%02d:%02d minutes", minutesTaken, secondsTaken), 123 | inline = true 124 | } 125 | table.insert(data.fields, reportHandlerTimeRow) 126 | 127 | if(discordUpdate.reportHandled.conclusion ~= nil) then 128 | local conclusionRow = { 129 | name = TTTLogTranslate(nil, "webhook_report_finished_conclusion") .. ":", 130 | value = discordUpdate.reportHandled.conclusion 131 | } 132 | table.insert(data.fields, conclusionRow) 133 | end 134 | end 135 | 136 | 137 | if emitOnlyWhenAdminsOffline == false then 138 | data.footer = { 139 | text = TTTLogTranslate(nil, discordUpdate.adminOnline and "webhook_AdminsOnline" or "webhook_NoAdminsOnline") 140 | } 141 | end 142 | 143 | SendDiscordMessage(data) 144 | end 145 | -------------------------------------------------------------------------------- /lua/damagelogs/config/config.lua: -------------------------------------------------------------------------------- 1 | -- See this wiki page on how to edit the addon's configuration 2 | -- https://github.com/BadgerCode/tttdamagelogs/wiki/Configuring-the-damage-logs 3 | 4 | 5 | --[[ 6 | When adding new configuration options: 7 | 1. Add it to this file 8 | Damagelog.MyNewProperty = "hello" 9 | 10 | 2. Add it to lua\damagelogs\config\config_loader.lua 11 | In Damagelog:getConfig 12 | config.MyNewProperty = DamageLog.MyNewProperty 13 | 14 | In Damagelog:loadConfig 15 | DamageLog.MyNewProperty = config.MyNewProperty 16 | 17 | ]] 18 | 19 | 20 | --[[User rights. 21 | 22 | First argument: name of usergroup (e. g. "user" or "admin"). 23 | 24 | Second argument: access level. Default value is 2 (will be used if a usergroup isn't here). 25 | 1 : Can't view 'Logs before your death' tab in !report frame 26 | 2 : Can't view logs of active rounds 27 | 3 : Can view logs of active rounds as a spectator 28 | 4 : Can always view logs of active rounds 29 | 30 | Everyone can view logs of previous rounds. 31 | 32 | Third argument: access to RDM Manager tab in Damagelogs (true/false). 33 | ]] 34 | -- 35 | Damagelog:AddUser("superadmin", 4, true) 36 | Damagelog:AddUser("owner", 4, true) 37 | Damagelog:AddUser("founder", 4, true) 38 | Damagelog:AddUser("admin", 4, true) 39 | Damagelog:AddUser("operator", 3, false) 40 | Damagelog:AddUser("user", 2, false) 41 | 42 | 43 | -- The F-key 44 | Damagelog.Key = KEY_F8 45 | --[[Is a message shown when an alive player opens the menu? 46 | 0 : if you want to only show it to superadmins 47 | 1 : to let others see that you have abusive admins 48 | ]] 49 | -- 50 | Damagelog.AbuseMessageMode = 0 51 | -- true to enable the RDM Manager, false to disable it 52 | Damagelog.RDM_Manager_Enabled = true 53 | -- Command to open the report menu. Don't forget the quotation marks 54 | Damagelog.RDM_Manager_Command = "!report" 55 | -- Command to open the respond menu while you're alive 56 | Damagelog.Respond_Command = "!respond" 57 | --[[Set to true if you want to enable MySQL (it needs to be configured on config/mysqloo.lua) 58 | Setting it to false will make the logs use SQLite (garrysmod/sv.db) 59 | ]] 60 | -- 61 | Damagelog.Use_MySQL = false 62 | --[[Autoslay and Autojail Mode 63 | REQUIRES ULX/SAM/sAdmin ! If you are using ServerGuard, set this to 0 (it will use ServerGuard's autoslay automatically) 64 | - 0 : Disables autoslay 65 | - 1 : Enables the !aslay and !aslayid command for ULX/SAM/SADMIN, designed to work with the logs. 66 | Works like that : !aslay target number_of_slays reason 67 | Example : !aslay tommy228 2 RDMing a traitor 68 | Example : !aslayid STEAM_0:0:1234567 2 RDMing a traitor 69 | - 2 : Enables the autojail system instead of autoslay. Replaces the !aslay and !aslay commands by !ajail and !ajailid 70 | ]] 71 | -- 72 | Damagelog.ULX_AutoslayMode = 1 73 | -- Force autoslain players to be innocents (ULX/SAM/SADMIN only) 74 | -- Do not enable this if another addon interferes with roles (Pointshop roles for example) 75 | Damagelog.ULX_Autoslay_ForceRole = true 76 | --Auto check Custom slay Reason 77 | Damagelog.Autoslay_CheckCustom = false 78 | -- Default autoslay reasons (ULX, SAM, sAdmin, and ServerGuard) 79 | Damagelog.Autoslay_DefaultReason = "Breaking Rules" 80 | Damagelog.Autoslay_DefaultReason1 = "Random Damage" 81 | Damagelog.Autoslay_DefaultReason2 = "RDM" 82 | Damagelog.Autoslay_DefaultReason3 = "2x RDM" 83 | Damagelog.Autoslay_DefaultReason4 = "Attempted Mass" 84 | Damagelog.Autoslay_DefaultReason5 = "Mass RDM" 85 | Damagelog.Autoslay_DefaultReason6 = "Super Mass" 86 | Damagelog.Autoslay_DefaultReason7 = "Ghosting" 87 | Damagelog.Autoslay_DefaultReason8 = "Hacking" 88 | Damagelog.Autoslay_DefaultReason9 = "Prop kill" 89 | Damagelog.Autoslay_DefaultReason10 = "Consistent RDM" 90 | Damagelog.Autoslay_DefaultReason11 = "Trolling" 91 | Damagelog.Autoslay_DefaultReason12 = "Minge" 92 | 93 | -- Default ban reasons (ULX and ServerGuard) 94 | Damagelog.Ban_DefaultReason1 = "Random Damage and leave" 95 | Damagelog.Ban_DefaultReason2 = "RDM and leave" 96 | Damagelog.Ban_DefaultReason3 = "2x RDM and leave" 97 | Damagelog.Ban_DefaultReason4 = "Attempted Mass and leave" 98 | Damagelog.Ban_DefaultReason5 = "Mass RDM" 99 | Damagelog.Ban_DefaultReason6 = "Super Mass" 100 | Damagelog.Ban_DefaultReason7 = "Ghosting" 101 | Damagelog.Ban_DefaultReason8 = "Hacking" 102 | Damagelog.Ban_DefaultReason9 = "Consistent RDM" 103 | Damagelog.Ban_DefaultReason10 = "Attempted RDM" 104 | Damagelog.Ban_DefaultReason11 = "Random Damage" 105 | Damagelog.Ban_DefaultReason12 = "Trolling" 106 | -- The number of days the logs last on the database (to avoid lags when opening the menu) 107 | Damagelog.LogDays = 61 108 | -- Hide the Donate button on the top-right corner 109 | Damagelog.HideDonateButton = false 110 | -- Use the Workshop to download content files 111 | Damagelog.UseWorkshop = true 112 | -- Force a language - When empty use user-defined language 113 | Damagelog.ForcedLanguage = "" 114 | -- Allow reports even with no staff online 115 | Damagelog.NoStaffReports = false 116 | -- Allow more than 2 reports per round 117 | Damagelog.MoreReportsPerRound = false 118 | -- Allow reports before playing 119 | Damagelog.ReportsBeforePlaying = false 120 | -- Private message prefix from RDM Manager 121 | Damagelog.PrivateMessagePrefix = "[RDM Manager]" 122 | -- Allow banning thru the RDMManager 123 | Damagelog.AllowBanningThruManager = true 124 | 125 | 126 | -- Discord Webhooks 127 | -- You can create a webhook on your Discord server that will automatically post messages when a report is created. 128 | -- IMPORTANT: 129 | -- Discord blocks webhooks from GMod servers. 130 | -- You will need to proxy your requests through a web server 131 | -- GMod Server -> Web Server -> Discord 132 | 133 | 134 | -- Webhook mode: 135 | -- 0 - disabled 136 | -- 1 - create messages for new reports when there are no admins online 137 | -- 2 - create messages for every report 138 | Damagelog.DiscordWebhookMode = 0 139 | 140 | 141 | -- Don't forget to set the value of "ttt_dmglogs_discordurl" convar to your webhook URL in server.cfg 142 | 143 | -- Should all players get notified about the amount of remaining slays of a slain player? 144 | 145 | Damagelog.ShowRemainingSlays = false 146 | -------------------------------------------------------------------------------- /lua/damagelogs/config/config_loader.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | When adding new configuration options: 3 | 1. Add it to lua\damagelogs\config\config.lua 4 | Damagelog.MyNewProperty = "hello" 5 | 6 | 2. Add it to this file 7 | In Damagelog:getConfig 8 | config.MyNewProperty = DamageLog.MyNewProperty 9 | 10 | In Damagelog:loadConfig 11 | DamageLog.MyNewProperty = config.MyNewProperty 12 | 13 | ]] 14 | 15 | 16 | function Damagelog:getConfig() 17 | local config = {} 18 | --Permissions 19 | config.Permissions = {} 20 | for user,right in pairs(Damagelog.User_rights) do 21 | config.Permissions[user] = {} 22 | config.Permissions[user].access_level = right 23 | config.Permissions[user].can_access_rdm_manager = Damagelog.RDM_Manager_Rights[user] 24 | end 25 | config.Key = Damagelog.Key 26 | config.AbuseMessageMode = Damagelog.AbuseMessageMode 27 | config.RDM_Manager_Enabled = Damagelog.RDM_Manager_Enabled 28 | --Commands 29 | config.Commands = {} 30 | config.Commands.RDM_Manager_Command = Damagelog.RDM_Manager_Command 31 | config.Commands.Respond_Command = Damagelog.Respond_Command 32 | 33 | config.Use_MySQL = Damagelog.Use_MySQL 34 | --Autoslay stuff 35 | config.Autoslay = {} 36 | config.Autoslay.ShowRemainingSlays = Damagelog.ShowRemainingSlays 37 | config.Autoslay.ULX_AutoslayMode = Damagelog.ULX_AutoslayMode 38 | config.Autoslay.ULX_Autoslay_ForceRole = Damagelog.ULX_Autoslay_ForceRole 39 | config.Autoslay.Autoslay_CheckCustom = Damagelog.Autoslay_CheckCustom 40 | config.Autoslay.DefaultReason = Damagelog.Autoslay_DefaultReason 41 | config.Autoslay.DefaultReason1 = Damagelog.Autoslay_DefaultReason1 42 | config.Autoslay.DefaultReason2 = Damagelog.Autoslay_DefaultReason2 43 | config.Autoslay.DefaultReason3 = Damagelog.Autoslay_DefaultReason3 44 | config.Autoslay.DefaultReason4 = Damagelog.Autoslay_DefaultReason4 45 | config.Autoslay.DefaultReason5 = Damagelog.Autoslay_DefaultReason5 46 | config.Autoslay.DefaultReason6 = Damagelog.Autoslay_DefaultReason6 47 | config.Autoslay.DefaultReason7 = Damagelog.Autoslay_DefaultReason7 48 | config.Autoslay.DefaultReason8 = Damagelog.Autoslay_DefaultReason8 49 | config.Autoslay.DefaultReason9 = Damagelog.Autoslay_DefaultReason9 50 | config.Autoslay.DefaultReason10 = Damagelog.Autoslay_DefaultReason10 51 | config.Autoslay.DefaultReason11 = Damagelog.Autoslay_DefaultReason11 52 | config.Autoslay.DefaultReason12 = Damagelog.Autoslay_DefaultReason12 53 | --Ban Stuff 54 | config.Ban = {} 55 | config.Ban.DefaultReason1 = Damagelog.Ban_DefaultReason1 56 | config.Ban.DefaultReason2 = Damagelog.Ban_DefaultReason2 57 | config.Ban.DefaultReason3 = Damagelog.Ban_DefaultReason3 58 | config.Ban.DefaultReason4 = Damagelog.Ban_DefaultReason4 59 | config.Ban.DefaultReason5 = Damagelog.Ban_DefaultReason5 60 | config.Ban.DefaultReason6 = Damagelog.Ban_DefaultReason6 61 | config.Ban.DefaultReason7 = Damagelog.Ban_DefaultReason7 62 | config.Ban.DefaultReason8 = Damagelog.Ban_DefaultReason8 63 | config.Ban.DefaultReason9 = Damagelog.Ban_DefaultReason9 64 | config.Ban.DefaultReason10 = Damagelog.Ban_DefaultReason10 65 | config.Ban.DefaultReason11 = Damagelog.Ban_DefaultReason11 66 | config.Ban.DefaultReason12 = Damagelog.Ban_DefaultReason12 67 | config.Ban.AllowBanningThruManager = Damagelog.AllowBanningThruManager 68 | 69 | config.LogDays = Damagelog.LogDays 70 | config.HideDonateButton = Damagelog.HideDonateButton 71 | config.UseWorkshop = Damagelog.UseWorkshop 72 | config.ForcedLanguage = Damagelog.ForcedLanguage 73 | --Report stuff 74 | config.Reports = {} 75 | config.Reports.NoStaffReports = Damagelog.NoStaffReports 76 | config.Reports.MoreReportsPerRound = Damagelog.MoreReportsPerRound 77 | config.Reports.ReportsBeforePlaying = Damagelog.ReportsBeforePlaying 78 | 79 | config.PrivateMessagePrefix = Damagelog.PrivateMessagePrefix 80 | config.DiscordWebhookMode = Damagelog.DiscordWebhookMode 81 | 82 | return config 83 | end 84 | 85 | function Damagelog:saveConfig() 86 | local config = Damagelog:getConfig() 87 | file.Write("damagelog/config.json",util.TableToJSON(config,true)) 88 | end 89 | 90 | function Damagelog:loadConfig() 91 | if not file.Exists("damagelog/config.json", "DATA") then --If no config exists save the default config (config.lua) and return as if we loaded one. 92 | Damagelog:saveConfig() 93 | return 94 | end 95 | local loaded_config = util.JSONToTable(file.Read("damagelog/config.json", "DATA")) 96 | if not loaded_config then 97 | ErrorNoHalt("Damagelogs: ERROR - Config Exists but is not valid JSON! Using default config.") 98 | return 99 | end 100 | 101 | Damagelog:loadConfigFromTable(loaded_config) 102 | end 103 | 104 | function Damagelog:loadConfigFromTable(loaded_config) 105 | local config = Damagelog:getConfig() 106 | 107 | --Clear out current users and rights 108 | Damagelog.User_rights = {} 109 | Damagelog.RDM_Manager_Rights = {} 110 | if loaded_config.Permissions != nil then --Have to handle Perms a bit different then other config value since we don't want to use the defaults if someone has their own configured in the json config. 111 | for user,data in pairs(loaded_config.Permissions) do 112 | if data.access_level != nil and data.can_access_rdm_manager != nil then --Need to check for nils 113 | Damagelog:AddUser(user,data.access_level,data.can_access_rdm_manager) 114 | end 115 | end 116 | else --If they don't have a valid Permission section in their JSON use the lua config / default. 117 | for user,data in pairs(config.Permissions) do 118 | Damagelog:AddUser(user,data.access_level,data.can_access_rdm_manager) 119 | end 120 | end 121 | 122 | table.Merge(config,loaded_config) --Merge loaded json config into the lua config. 123 | 124 | Damagelog.Key = config.Key 125 | Damagelog.AbuseMessageMode = config.AbuseMessageMode 126 | Damagelog.RDM_Manager_Enabled = config.RDM_Manager_Enabled 127 | Damagelog.RDM_Manager_Command = config.Commands.RDM_Manager_Command 128 | Damagelog.Respond_Command = config.Commands.Respond_Command 129 | Damagelog.Use_MySQL = Damagelog.Use_MySQL 130 | 131 | Damagelog.ShowRemainingSlays = config.Autoslay.ShowRemainingSlays 132 | 133 | Damagelog.ULX_AutoslayMode = config.Autoslay.ULX_AutoslayMode 134 | Damagelog.ULX_Autoslay_ForceRole = config.Autoslay.ULX_Autoslay_ForceRole 135 | Damagelog.Autoslay_CheckCustom = config.Autoslay.Autoslay_CheckCustom 136 | 137 | Damagelog.Autoslay_DefaultReason = config.Autoslay.DefaultReason 138 | Damagelog.Autoslay_DefaultReason1 = config.Autoslay.DefaultReason1 139 | Damagelog.Autoslay_DefaultReason2 = config.Autoslay.DefaultReason2 140 | Damagelog.Autoslay_DefaultReason3 = config.Autoslay.DefaultReason3 141 | Damagelog.Autoslay_DefaultReason4 = config.Autoslay.DefaultReason4 142 | Damagelog.Autoslay_DefaultReason5 = config.Autoslay.DefaultReason5 143 | Damagelog.Autoslay_DefaultReason6 = config.Autoslay.DefaultReason6 144 | Damagelog.Autoslay_DefaultReason7 = config.Autoslay.DefaultReason7 145 | Damagelog.Autoslay_DefaultReason8 = config.Autoslay.DefaultReason8 146 | Damagelog.Autoslay_DefaultReason9 = config.Autoslay.DefaultReason9 147 | Damagelog.Autoslay_DefaultReason10 = config.Autoslay.DefaultReason10 148 | Damagelog.Autoslay_DefaultReason11 = config.Autoslay.DefaultReason11 149 | 150 | Damagelog.AllowBanningThruManager = config.Ban.AllowBanningThruManager 151 | 152 | Damagelog.Ban_DefaultReason1 = config.Ban.DefaultReason1 153 | Damagelog.Ban_DefaultReason2 = config.Ban.DefaultReason2 154 | Damagelog.Ban_DefaultReason3 = config.Ban.DefaultReason3 155 | Damagelog.Ban_DefaultReason4 = config.Ban.DefaultReason4 156 | Damagelog.Ban_DefaultReason5 = config.Ban.DefaultReason5 157 | Damagelog.Ban_DefaultReason6 = config.Ban.DefaultReason6 158 | Damagelog.Ban_DefaultReason7 = config.Ban.DefaultReason7 159 | Damagelog.Ban_DefaultReason8 = config.Ban.DefaultReason8 160 | Damagelog.Ban_DefaultReason9 = config.Ban.DefaultReason9 161 | Damagelog.Ban_DefaultReason10 = config.Ban.DefaultReason10 162 | Damagelog.Ban_DefaultReason11 = config.Ban.DefaultReason11 163 | Damagelog.Ban_DefaultReason12 = config.Ban.DefaultReason12 164 | 165 | Damagelog.NoStaffReports = config.Reports.NoStaffReports 166 | 167 | Damagelog.MoreReportsPerRound = config.Reports.MoreReportsPerRound 168 | 169 | Damagelog.ReportsBeforePlaying = config.Reports.ReportsBeforePlaying 170 | 171 | Damagelog.LogDays = config.LogDays 172 | 173 | Damagelog.HideDonateButton = config.HideDonateButton 174 | 175 | Damagelog.UseWorkshop = config.UseWorkshop 176 | 177 | Damagelog.ForcedLanguage = config.ForcedLanguage 178 | 179 | Damagelog.PrivateMessagePrefix = config.PrivateMessagePrefix 180 | 181 | Damagelog.DiscordWebhookMode = config.DiscordWebhookMode 182 | 183 | end 184 | 185 | function Damagelog:loadMySQLConfig() 186 | if not file.Exists("damagelog/mysql.json", "DATA") then --If no mysql config exists save the default and return as if we loaded one. 187 | Damagelog:saveMySQLConfig() 188 | return 189 | end 190 | local config = util.JSONToTable(file.Read("damagelog/mysql.json", "DATA")) 191 | if not config then 192 | ErrorNoHalt("Damagelogs: ERROR - MySQL Config Exists but is not valid JSON!") 193 | return 194 | end 195 | Damagelog.MySQL_Informations = config 196 | 197 | end 198 | 199 | function Damagelog:saveMySQLConfig() 200 | file.Write("damagelog/mysql.json",util.TableToJSON(Damagelog.MySQL_Informations,true)) 201 | end 202 | 203 | -------------------------------------------------------------------------------- /lua/damagelogs/client/init.lua: -------------------------------------------------------------------------------- 1 | CreateClientConVar("ttt_dmglogs_language", "english", FCVAR_ARCHIVE) 2 | GetDMGLogLang = GetConVar("ttt_dmglogs_language"):GetString() 3 | 4 | cvars.AddChangeCallback("ttt_dmglogs_language", function(convar_name, value_old, value_new) 5 | GetDMGLogLang = value_new 6 | net.Start("DL_SendLang") 7 | net.WriteString(value_new) 8 | net.SendToServer() 9 | end) 10 | 11 | include("damagelogs/shared/defines.lua") 12 | include("damagelogs/config/config.lua") 13 | include("damagelogs/config/config_loader.lua") 14 | include("damagelogs/shared/lang.lua") 15 | include("damagelogs/client/settings.lua") 16 | include("damagelogs/shared/sync.lua") 17 | include("damagelogs/client/drawcircle.lua") 18 | include("damagelogs/client/tabs/damagetab.lua") 19 | include("damagelogs/client/tabs/shoots.lua") 20 | include("damagelogs/client/tabs/old_logs.lua") 21 | include("damagelogs/client/weapon_names.lua") 22 | include("damagelogs/client/colors.lua") 23 | include("damagelogs/client/recording.lua") 24 | include("damagelogs/client/listview.lua") 25 | include("damagelogs/client/filters.lua") 26 | include("damagelogs/shared/events.lua") 27 | include("damagelogs/shared/notify.lua") 28 | include("damagelogs/client/info_label.lua") 29 | include("damagelogs/shared/privileges.lua") 30 | include("damagelogs/shared/autoslay.lua") 31 | 32 | if Damagelog.RDM_Manager_Enabled then 33 | include("damagelogs/shared/rdm_manager.lua") 34 | include("damagelogs/shared/chat.lua") 35 | include("damagelogs/client/tabs/rdm_manager.lua") 36 | include("damagelogs/client/rdm_manager.lua") 37 | include("damagelogs/client/chat.lua") 38 | end 39 | 40 | local color_lightyellow = Color(255, 245, 148) 41 | local color_red = Color(255, 62, 62) 42 | local color_lightblue = Color(98, 176, 255) 43 | local outdated = false 44 | 45 | hook.Add("InitPostEntity", "Damagelog_InitPostHTTP", function() 46 | local client = LocalPlayer() 47 | 48 | if client:IsAdmin() or client:IsSuperAdmin() then 49 | http.Fetch("https://raw.githubusercontent.com/BadgerCode/tttdamagelogs/master/version.md", function(responseBody) 50 | local githubVersionInfo = string.Explode(".", string.Trim(responseBody)) 51 | local githubVersion = { 52 | major = tonumber(githubVersionInfo[1]), 53 | minor = tonumber(githubVersionInfo[2]), 54 | patch = tonumber(githubVersionInfo[3]) 55 | } 56 | 57 | local currentVersionInfo = string.Explode(".", Damagelog.VERSION) 58 | local currentVersion = { 59 | major = tonumber(currentVersionInfo[1]), 60 | minor = tonumber(currentVersionInfo[2]), 61 | patch = tonumber(currentVersionInfo[3]) 62 | } 63 | 64 | if (githubVersion.major > currentVersion.major) then 65 | outdated = true 66 | elseif (githubVersion.major == currentVersion.major) and (githubVersion.minor > currentVersion.minor) then 67 | outdated = true 68 | elseif (githubVersion.major == currentVersion.major) and (githubVersion.minor == currentVersion.minor) and (githubVersion.patch > currentVersion.patch) then 69 | outdated = true 70 | end 71 | end) 72 | end 73 | 74 | net.Start("DL_SendLang") 75 | net.WriteString(GetDMGLogLang) 76 | net.SendToServer() 77 | end) 78 | 79 | function Damagelog:OpenMenu() 80 | local x, y = 665, 680 81 | local show_outdated = outdated and GetConVar("ttt_dmglogs_updatenotifications"):GetBool() 82 | 83 | if show_outdated then 84 | y = y + 30 85 | end 86 | 87 | self.Menu = vgui.Create("DFrame") 88 | self.Menu:SetSize(x, y) 89 | self.Menu:SetTitle("TTT Damagelogs version " .. self.VERSION) 90 | self.Menu:SetDraggable(true) 91 | self.Menu:MakePopup() 92 | self.Menu:SetKeyboardInputEnabled(false) 93 | self.Menu:Center() 94 | self.Menu.AboutPos = 0 95 | self.Menu.AboutPosMax = 35 96 | self.Menu.AboutState = false 97 | 98 | self.Menu.About = function(self) 99 | self.AboutState = not self.AboutState 100 | end 101 | 102 | local old_think = self.Menu.Think 103 | 104 | self.Menu.Think = function(self) 105 | self.AboutMoving = true 106 | 107 | if self.AboutState and self.AboutPos < self.AboutPosMax then 108 | self.AboutPos = self.AboutPos + 15 109 | elseif not self.AboutState and self.AboutPos > 0 then 110 | self.AboutPos = self.AboutPos - 15 111 | else 112 | self.AboutMoving = false 113 | end 114 | 115 | if old_think then 116 | old_think(self) 117 | end 118 | end 119 | 120 | self.Menu.PaintOver = function(self, w, h) 121 | local _x, _y, _w, _h = x - 200, show_outdated and 80 or 50, 195, self.AboutPos 122 | surface.SetDrawColor(color_black) 123 | surface.DrawRect(_x, _y, _w, _h) 124 | surface.SetDrawColor(color_lightyellow) 125 | surface.DrawRect(_x + 1, _y + 1, _w - 2, _h - 2) 126 | 127 | if self.AboutPos >= 35 then 128 | surface.SetFont("DermaDefault") 129 | surface.SetTextColor(color_black) 130 | surface.SetTextPos(_x + 5, _y + 5) 131 | surface.DrawText("Created by Tommy228.") 132 | surface.SetTextPos(_x + 5, _y + 25) 133 | surface.DrawText("Licensed under GPL-3.0.") 134 | end 135 | end 136 | 137 | if show_outdated then 138 | local info = vgui.Create("Damagelog_InfoLabel", self.Menu) 139 | info:SetText(TTTLogTranslate(GetDMGLogLang, "UpdateNotify")) 140 | info:SetInfoColor("blue") 141 | info:SetPos(5, 30) 142 | info:SetSize(x - 10, 25) 143 | end 144 | 145 | self.Tabs = vgui.Create("DPropertySheet", self.Menu) 146 | self.Tabs:SetPos(5, show_outdated and 60 or 30) 147 | self.Tabs:SetSize(x - 10, show_outdated and y - 65 or y - 35) 148 | self:DrawDamageTab(x, y) 149 | self:DrawShootsTab(x, y) 150 | self:DrawOldLogs(x, y) 151 | 152 | if Damagelog.RDM_Manager_Enabled then 153 | self:DrawRDMManager(x, y) 154 | end 155 | 156 | self.About = vgui.Create("DButton", self.Menu) 157 | self.About:SetPos(x - 60, show_outdated and 57 or 27) 158 | self.About:SetSize(55, 19) 159 | self.About:SetText("▼" .. TTTLogTranslate(GetDMGLogLang, "About")) 160 | 161 | if not Damagelog.HideDonateButton then 162 | self.Donate = vgui.Create("DButton", self.Menu) 163 | self.Donate:SetPos(x - 120, show_outdated and 57 or 27) 164 | self.Donate:SetSize(55, 19) 165 | self.Donate:SetText(TTTLogTranslate(GetDMGLogLang, "Donate")) 166 | 167 | self.Donate.DoClick = function() 168 | gui.OpenURL("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YSJDH4CJ4N3BQ") 169 | end 170 | end 171 | 172 | self.About.DoClick = function() 173 | self.Menu:About() 174 | self.About:SetText(self.Menu.AboutState and "▲" .. TTTLogTranslate(GetDMGLogLang, "About") or "▼" .. TTTLogTranslate(GetDMGLogLang, "About")) 175 | end 176 | end 177 | 178 | concommand.Add("damagelog", function() 179 | Damagelog:OpenMenu() 180 | end) 181 | 182 | Damagelog.pressed_key = false 183 | 184 | function Damagelog:Think() 185 | if input.IsKeyDown(self.Key) and not self.pressed_key then 186 | self.pressed_key = true 187 | 188 | if not IsValid(self.Menu) then 189 | self:OpenMenu() 190 | else 191 | if self:IsRecording() then 192 | self:StopRecording() 193 | self.Menu:SetVisible(true) 194 | else 195 | self.Menu:Close() 196 | end 197 | end 198 | elseif self.pressed_key and not input.IsKeyDown(self.Key) then 199 | self.pressed_key = false 200 | end 201 | end 202 | 203 | hook.Add("Think", "Think_Damagelog", function() 204 | Damagelog:Think() 205 | end) 206 | 207 | function Damagelog:StrRole(role) 208 | if role == DAMAGELOG_ROLE_DISCONNECTED then 209 | return TTTLogTranslate(GetDMGLogLang, "disconnected") 210 | else 211 | if TTT2 then 212 | return TTTLogTranslate(GetDMGLogLang, GetRoleByIndex(role).name) 213 | elseif CR_VERSION then 214 | return TTTLogTranslate(GetDMGLogLang, ROLE_STRINGS[role]) 215 | else 216 | if role == ROLE_TRAITOR then 217 | return TTTLogTranslate(GetDMGLogLang, "traitor") 218 | elseif role == ROLE_DETECTIVE then 219 | return TTTLogTranslate(GetDMGLogLang, "detective") 220 | else 221 | return TTTLogTranslate(GetDMGLogLang, "innocent") 222 | end 223 | end 224 | end 225 | end 226 | 227 | net.Receive("DL_SendConfig", function() 228 | local config = net.ReadTable() 229 | if config then 230 | Damagelog:loadConfigFromTable(config) 231 | end 232 | end) 233 | 234 | net.Receive("DL_InformSuperAdmins", function() 235 | local nick = net.ReadString() 236 | 237 | if nick then 238 | chat.AddText(color_red, nick, color_white, " " .. TTTLogTranslate(GetDMGLogLang, "AbuseNote")) 239 | end 240 | end) 241 | 242 | net.Receive("DL_Ded", function() 243 | if Damagelog.RDM_Manager_Enabled and GetConVar("ttt_dmglogs_rdmpopups"):GetBool() and net.ReadUInt(1, 1) == 1 then 244 | local client = LocalPlayer() 245 | 246 | if client.IsGhost and client:IsGhost() then 247 | return 248 | end 249 | 250 | local death_reason = net.ReadString() 251 | 252 | if not death_reason then 253 | return 254 | end 255 | 256 | local frame = vgui.Create("DFrame") 257 | frame:SetSize(250, 120) 258 | frame:SetTitle(TTTLogTranslate(GetDMGLogLang, "PopupNote")) 259 | frame:ShowCloseButton(false) 260 | frame:Center() 261 | local reason = vgui.Create("DLabel", frame) 262 | reason:SetText(string.format(TTTLogTranslate(GetDMGLogLang, "KilledBy"), death_reason)) 263 | reason:SizeToContents() 264 | reason:SetPos(5, 32) 265 | local report = vgui.Create("DButton", frame) 266 | report:SetPos(5, 55) 267 | report:SetSize(240, 25) 268 | report:SetText(TTTLogTranslate(GetDMGLogLang, "OpenMenu")) 269 | 270 | report.DoClick = function() 271 | net.Start("DL_StartReport") 272 | net.SendToServer() 273 | frame:Close() 274 | end 275 | 276 | local report_icon = vgui.Create("DImageButton", report) 277 | report_icon:SetMaterial("materials/icon16/report_go.png") 278 | report_icon:SetPos(1, 5) 279 | report_icon:SizeToContents() 280 | local close = vgui.Create("DButton", frame) 281 | close:SetPos(5, 85) 282 | close:SetSize(240, 25) 283 | close:SetText(TTTLogTranslate(GetDMGLogLang, "WasntRDM")) 284 | 285 | close.DoClick = function() 286 | frame:Close() 287 | end 288 | 289 | local close_icon = vgui.Create("DImageButton", close) 290 | close_icon:SetPos(2, 5) 291 | close_icon:SetMaterial("materials/icon16/cross.png") 292 | close_icon:SizeToContents() 293 | frame:MakePopup() 294 | chat.AddText(color_red, "[RDM Manager] ", COLOR_WHITE, TTTLogTranslate(GetDMGLogLang, "OpenReportMenu"), color_lightblue, " ", Damagelog.RDM_Manager_Command, COLOR_WHITE, " ", TTTLogTranslate(GetDMGLogLang, "Command"), ".") 295 | end 296 | end) 297 | 298 | hook.Add("StartChat", "Damagelog_StartChat", function() 299 | if IsValid(Damagelog.Menu) then 300 | Damagelog.Menu:SetPos(ScrW() - Damagelog.Menu:GetWide(), ScrH() / 2 - Damagelog.Menu:GetTall() / 2) 301 | end 302 | end) 303 | 304 | hook.Add("FinishChat", "Damagelog_FinishChat", function() 305 | if IsValid(Damagelog.Menu) then 306 | Damagelog.Menu:Center() 307 | end 308 | end) 309 | -------------------------------------------------------------------------------- /lua/damagelogs/server/init.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile("damagelogs/shared/defines.lua") 2 | AddCSLuaFile("damagelogs/config/config.lua") 3 | AddCSLuaFile("damagelogs/config/config_loader.lua") 4 | AddCSLuaFile("damagelogs/shared/lang.lua") 5 | AddCSLuaFile("damagelogs/shared/notify.lua") 6 | AddCSLuaFile("damagelogs/client/info_label.lua") 7 | AddCSLuaFile("damagelogs/shared/sync.lua") 8 | AddCSLuaFile("damagelogs/shared/events.lua") 9 | AddCSLuaFile("damagelogs/client/weapon_names.lua") 10 | AddCSLuaFile("damagelogs/shared/privileges.lua") 11 | AddCSLuaFile("damagelogs/client/tabs/damagetab.lua") 12 | AddCSLuaFile("damagelogs/client/tabs/rdm_manager.lua") 13 | AddCSLuaFile("damagelogs/client/tabs/shoots.lua") 14 | AddCSLuaFile("damagelogs/client/tabs/old_logs.lua") 15 | AddCSLuaFile("damagelogs/client/colors.lua") 16 | AddCSLuaFile("damagelogs/client/filters.lua") 17 | AddCSLuaFile("damagelogs/client/drawcircle.lua") 18 | AddCSLuaFile("damagelogs/client/listview.lua") 19 | AddCSLuaFile("damagelogs/client/recording.lua") 20 | AddCSLuaFile("damagelogs/client/settings.lua") 21 | AddCSLuaFile("damagelogs/shared/autoslay.lua") 22 | include("damagelogs/shared/defines.lua") 23 | include("damagelogs/config/config.lua") 24 | include("damagelogs/config/mysqloo.lua") 25 | include("damagelogs/config/config_loader.lua") 26 | --Grab the configs as soon as possible since other code depends on it. 27 | Damagelog:loadMySQLConfig() 28 | Damagelog:loadConfig() 29 | Damagelog:saveConfig() 30 | Damagelog.Config = Damagelog:getConfig() --Get the config in its table state for sending to clients. 31 | 32 | include("damagelogs/server/sqlite.lua") 33 | include("damagelogs/shared/lang.lua") 34 | include("damagelogs/server/oldlogs.lua") 35 | include("damagelogs/shared/notify.lua") 36 | include("damagelogs/shared/sync.lua") 37 | include("damagelogs/shared/events.lua") 38 | include("damagelogs/shared/privileges.lua") 39 | include("damagelogs/server/damageinfos.lua") 40 | include("damagelogs/server/recording.lua") 41 | include("damagelogs/server/autoslay.lua") 42 | include("damagelogs/shared/autoslay.lua") 43 | include("damagelogs/server/discord.lua") 44 | 45 | -- Building error reporting 46 | -- Damagelog:Error(debug.getinfo(1).source, debug.getinfo(1).currentline, "connection error") 47 | function Damagelog:Error(file, line, strg) 48 | print("Damagelogs: ERROR - " .. file .. " (" .. line .. ") - " .. strg) 49 | end 50 | 51 | if Damagelog.RDM_Manager_Enabled then 52 | AddCSLuaFile("damagelogs/client/rdm_manager.lua") 53 | AddCSLuaFile("damagelogs/client/chat.lua") 54 | AddCSLuaFile("damagelogs/shared/rdm_manager.lua") 55 | AddCSLuaFile("damagelogs/shared/chat.lua") 56 | 57 | if Damagelog.UseWorkshop then 58 | resource.AddWorkshop("1129792694") 59 | else 60 | resource.AddFile("sound/damagelogs/vote_failure.wav") 61 | resource.AddFile("sound/damagelogs/vote_yes.wav") 62 | resource.AddFile("sound/damagelogs/vote_no.wav") 63 | end 64 | 65 | include("damagelogs/server/rdm_manager.lua") 66 | include("damagelogs/server/chat.lua") 67 | include("damagelogs/shared/rdm_manager.lua") 68 | include("damagelogs/shared/chat.lua") 69 | end 70 | 71 | -- Including Net Messages 72 | util.AddNetworkString("DL_AskDamagelog") 73 | util.AddNetworkString("DL_SendDamagelog") 74 | util.AddNetworkString("DL_RefreshDamagelog") 75 | util.AddNetworkString("DL_InformSuperAdmins") 76 | util.AddNetworkString("DL_Ded") 77 | util.AddNetworkString("DL_SendLang") 78 | util.AddNetworkString("DL_SendConfig") 79 | Damagelog.DamageTable = Damagelog.DamageTable or {} 80 | Damagelog.OldTables = Damagelog.OldTables or {} 81 | Damagelog.ShootTables = Damagelog.ShootTables or {} 82 | Damagelog.Roles = Damagelog.Roles or {} 83 | Damagelog.SceneRounds = Damagelog.SceneRounds or {} 84 | 85 | net.Receive("DL_SendLang", function(_, ply) 86 | ply.DMGLogLang = net.ReadString() 87 | --Send config once we know the client is running code, DL_SendLang is convenently send at the InitPostEntity event. 88 | net.Start("DL_SendConfig") 89 | net.WriteTable(Damagelog.Config) --Not recommended but makes the config sync future proof. 90 | net.Send(ply) 91 | end) 92 | 93 | local Player = FindMetaTable("Player") 94 | 95 | function Player:GetDamagelogID() 96 | return self.DamagelogID or -1 97 | end 98 | 99 | function Player:SetDamagelogID(id) 100 | self.DamagelogID = id 101 | end 102 | 103 | function Player:AddToDamagelogRoles(joinedAfterRoundStart) 104 | local id = table.insert(Damagelog.Roles[#Damagelog.Roles], { 105 | role = (joinedAfterRoundStart and DAMAGELOG_ROLE_JOINAFTERROUNDSTART) 106 | or (self:IsSpec() and DAMAGELOG_ROLE_SPECTATOR) 107 | or self:GetRole(), 108 | steamid64 = self:SteamID64(), 109 | nick = self:Nick() 110 | }) 111 | 112 | self:SetDamagelogID(id) 113 | end 114 | 115 | function Player:GetPlayerThatRecentlyPushedMe() 116 | local pushInfo = self.was_pushed 117 | if(pushInfo == nil) then return nil end 118 | 119 | -- player.was_pushed is never reset. We must always check the time on the push event. 120 | -- Copied from TTT: Only consider pushes in the last 4 seconds 121 | local pushTime = math.max(pushInfo.t or 0, pushInfo.hurt or 0) 122 | if(pushTime < CurTime() - 4) then return nil end 123 | 124 | return pushInfo.att 125 | end 126 | 127 | 128 | function Damagelog:TTTBeginRound() 129 | self.Time = 0 130 | 131 | if not timer.Exists("Damagelog_Timer") then 132 | timer.Create("Damagelog_Timer", 1, 0, function() 133 | self.Time = self.Time + 1 134 | end) 135 | end 136 | 137 | if IsValid(self:GetSyncEnt()) then 138 | local rounds = self:GetSyncEnt():GetPlayedRounds() 139 | self:GetSyncEnt():SetPlayedRounds(rounds + 1) 140 | 141 | if self.add_old then 142 | self.OldTables[rounds] = table.Copy(self.DamageTable) 143 | else 144 | self.add_old = true 145 | end 146 | 147 | self.ShootTables[rounds + 1] = {} 148 | self.Roles[rounds + 1] = {} 149 | 150 | for _, v in player.Iterator() do 151 | v:AddToDamagelogRoles(false) 152 | end 153 | 154 | self.CurrentRound = rounds + 1 155 | end 156 | 157 | table.Empty(self.DamageTable) 158 | end 159 | 160 | hook.Add("TTTBeginRound", "TTTBeginRound_Damagelog", function() 161 | Damagelog:TTTBeginRound() 162 | end) 163 | 164 | hook.Add("PlayerInitialSpawn", "PlayerInitialSpawn_Damagelog", function(ply) 165 | if GetRoundState() == ROUND_ACTIVE then 166 | local steamid64 = ply:SteamID64() 167 | local found = false 168 | 169 | for k, v in pairs(Damagelog.Roles[#Damagelog.Roles]) do 170 | if v.steamid64 == steamid64 then 171 | found = true 172 | ply:SetDamagelogID(k) 173 | break 174 | end 175 | end 176 | 177 | if not found then 178 | ply:AddToDamagelogRoles(true) 179 | end 180 | end 181 | end) 182 | 183 | local dmgStrings = { 184 | [DMG_BLAST] = "DMG_BLAST", 185 | [DMG_DIRECT] = "DMG_BURN", 186 | [DMG_BURN] = "DMG_BURN", 187 | [DMG_CRUSH] = "DMG_CRUSH", 188 | [DMG_FALL] = "DMG_CRUSH", 189 | [DMG_SLASH] = "DMG_SLASH", 190 | [DMG_CLUB] = "DMG_CLUB", 191 | [DMG_SHOCK] = "DMG_SHOCK", 192 | [DMG_ENERGYBEAM] = "DMG_ENERGYBEAM", 193 | [DMG_SONIC] = "DMG_SONIC", 194 | [DMG_PHYSGUN] = "DMG_PHYSGUN", 195 | } 196 | 197 | -- rip from TTT 198 | -- this one will return a string 199 | function Damagelog:WeaponFromDmg(dmg) 200 | local inf = dmg:GetInflictor() 201 | local wep = nil 202 | local isWorldDamage = inf ~= nil and inf.IsWorld and inf:IsWorld() 203 | 204 | if IsValid(inf) or isWorldDamage then 205 | local damageType = dmg:GetDamageType() 206 | if inf:IsWeapon() or inf.Projectile then 207 | wep = inf 208 | elseif dmgStrings[damageType] then 209 | wep = dmgStrings[damageType] 210 | elseif inf:IsPlayer() then 211 | wep = inf:GetActiveWeapon() 212 | 213 | if not IsValid(wep) then 214 | wep = IsValid(inf.dying_wep) and inf.dying_wep 215 | end 216 | end 217 | end 218 | 219 | if not isstring(wep) then 220 | return IsValid(wep) and wep:GetClass() 221 | else 222 | return wep 223 | end 224 | end 225 | 226 | function Damagelog:SendDamagelog(ply, round) 227 | if self.MySQL_Error and not ply.DL_MySQL_Error then 228 | Damagelog:Error(debug.getinfo(1).source, debug.getinfo(1).currentline, "mysql connection error") 229 | ply.DL_MySQL_Error = true 230 | end 231 | 232 | local damage_send = {} 233 | local roles = self.Roles[round] 234 | local current = false 235 | 236 | if round == -1 then 237 | if not self.last_round_map then 238 | return 239 | end 240 | 241 | if not Damagelog.PreviousMap then 242 | if Damagelog.Use_MySQL then 243 | local query = self.database:query("SELECT damagelog FROM damagelog_oldlogs_v3 WHERE date = " .. self.last_round_map .. ";") 244 | 245 | query.onSuccess = function(q) 246 | local data = q:getData() 247 | 248 | if data and data[1] then 249 | local encoded = data[1]["damagelog"] 250 | local decoded = util.JSONToTable(encoded) 251 | 252 | if not decoded then 253 | decoded = { 254 | Roles = {}, 255 | ShootTables = {}, 256 | DamageTable = {} 257 | } 258 | end 259 | 260 | self:TransferLogs(decoded.DamageTable, ply, round, decoded.Roles) 261 | Damagelog.PreviousMap = decoded 262 | end 263 | end 264 | 265 | query:start() 266 | else 267 | local query = Damagelog.SQLiteDatabase.QueryValue("SELECT damagelog FROM damagelog_oldlogs_v3 WHERE date = " .. self.last_round_map) 268 | 269 | if not query then 270 | return 271 | end 272 | 273 | local decoded = util.JSONToTable(query) 274 | 275 | if not decoded then 276 | decoded = { 277 | Roles = {}, 278 | ShootTables = {}, 279 | DamageTable = {} 280 | } 281 | end 282 | 283 | self:TransferLogs(decoded.DamageTable, ply, round, decoded.Roles) 284 | Damagelog.PreviousMap = decoded 285 | end 286 | else 287 | self:TransferLogs(Damagelog.PreviousMap.DamageTable, ply, round, Damagelog.PreviousMap.Roles) 288 | end 289 | else 290 | if round == self:GetSyncEnt():GetPlayedRounds() then 291 | if not ply:CanUseDamagelog() then 292 | return 293 | end 294 | 295 | damage_send = self.DamageTable 296 | current = true 297 | else 298 | damage_send = self.OldTables[round] 299 | end 300 | 301 | self:TransferLogs(damage_send, ply, round, roles, current) 302 | end 303 | end 304 | 305 | function Damagelog:TransferLogs(damage_send, ply, round, roles, current) 306 | local count = #damage_send 307 | net.Start("DL_SendDamagelog") 308 | net.WriteTable(roles or {}) 309 | net.WriteUInt(count, 32) 310 | 311 | for _, v in ipairs(damage_send) do 312 | net.WriteTable(v) 313 | end 314 | 315 | net.Send(ply) 316 | 317 | if current and ply:IsActive() then 318 | net.Start("DL_InformSuperAdmins") 319 | net.WriteString(ply:Nick()) 320 | 321 | if self.AbuseMessageMode == 1 then 322 | net.Send(player.GetHumans()) 323 | else 324 | local superadmins = {} 325 | 326 | for _, v in ipairs(player.GetHumans()) do 327 | if v:IsSuperAdmin() then 328 | table.insert(superadmins, v) 329 | end 330 | end 331 | 332 | net.Send(superadmins) 333 | end 334 | end 335 | end 336 | 337 | net.Receive("DL_AskDamagelog", function(_, ply) 338 | local roundnumber = net.ReadInt(32) 339 | 340 | if roundnumber and roundnumber > -2 then 341 | Damagelog:SendDamagelog(ply, roundnumber) 342 | else 343 | Damagelog:Error(debug.getinfo(1).source, debug.getinfo(1).currentline, "Roundnumber invalid or negative") 344 | end -- Because -1 is the last round from previous map 345 | end) 346 | 347 | hook.Add("PlayerDeath", "Damagelog_PlayerDeathLastLogs", function(ply) 348 | if GetRoundState() ~= ROUND_ACTIVE then 349 | return 350 | end 351 | 352 | local found_dmg = {} 353 | local count = #Damagelog.DamageTable 354 | 355 | for i = count, 1, -1 do 356 | local line = Damagelog.DamageTable[i] 357 | if not Damagelog.Time or line.time < Damagelog.Time - 10 then break end 358 | table.insert(found_dmg, line) 359 | end 360 | 361 | ply.DeathDmgLog = { 362 | logs = table.Reverse(found_dmg), 363 | roles = Damagelog.Roles[#Damagelog.Roles] 364 | } 365 | end) 366 | 367 | 368 | -------------------------------------------------------------------------------- /lua/damagelogs/server/oldlogs.lua: -------------------------------------------------------------------------------- 1 | util.AddNetworkString("DL_AskLogsList") 2 | util.AddNetworkString("DL_AskOldLog") 3 | util.AddNetworkString("DL_SendOldLog") 4 | util.AddNetworkString("DL_SendLogsList") 5 | util.AddNetworkString("DL_AskOldLogRounds") 6 | util.AddNetworkString("DL_SendOldLogRounds") 7 | Damagelog.previous_reports = {} 8 | local limit = os.time() - Damagelog.LogDays * 86400 -- 24 * 60 * 60 9 | 10 | if Damagelog.Use_MySQL then 11 | require("mysqloo") 12 | include("damagelogs/config/mysqloo.lua") 13 | Damagelog.MySQL_Error = nil 14 | file.Delete("damagelog/mysql_error.txt") 15 | local info = Damagelog.MySQL_Informations 16 | Damagelog.database = mysqloo.connect(info.ip, info.username, info.password, info.database, info.port) 17 | 18 | Damagelog.database.onConnected = function(self) 19 | Damagelog.MySQL_Connected = true 20 | local create_table1 = self:query([[CREATE TABLE IF NOT EXISTS damagelog_oldlogs_v3 ( 21 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 22 | year INTEGER NOT NULL, 23 | month INTEGER NOT NULL, 24 | day INTEGER NOT NULL, 25 | date INTEGER NOT NULL, 26 | map TINYTEXT NOT NULL, 27 | round TINYINT NOT NULL, 28 | damagelog BLOB NOT NULL, 29 | PRIMARY KEY (id)); 30 | ]]) 31 | create_table1:start() 32 | local create_table2 = self:query([[CREATE TABLE IF NOT EXISTS damagelog_weapons ( 33 | class varchar(255) NOT NULL, 34 | name varchar(255) NOT NULL, 35 | PRIMARY KEY (class)); 36 | ]]) 37 | create_table2:start() 38 | local list = self:query("SELECT MIN(date), MAX(date) FROM damagelog_oldlogs_v3;") 39 | 40 | list.onSuccess = function(query) 41 | local data = query:getData() 42 | 43 | if not data[1] then 44 | return 45 | end 46 | 47 | Damagelog.OlderDate = data[1]["MIN(date)"] 48 | Damagelog.LatestDate = data[1]["MAX(date)"] 49 | end 50 | 51 | list:start() 52 | local delete_old = self:query("DELETE FROM damagelog_oldlogs_v3 WHERE date <= " .. limit .. ";") 53 | delete_old:start() 54 | Damagelog.OldLogsDays = {} 55 | local yearsQuery = self:query("SELECT DISTINCT year FROM damagelog_oldlogs_v3;") 56 | 57 | yearsQuery.onSuccess = function(yearsQuery) 58 | local years = yearsQuery:getData() 59 | 60 | for _, year in pairs(years) do 61 | local y = tonumber(year.year) 62 | 63 | if y then 64 | Damagelog.OldLogsDays[y] = {} 65 | local monthQuery = self:query("SELECT DISTINCT month FROM damagelog_oldlogs_v3;") 66 | 67 | monthQuery.onSuccess = function(monthQuery) 68 | local months = monthQuery:getData() 69 | 70 | for _, month in pairs(months) do 71 | local m = tonumber(month.month) 72 | 73 | if m then 74 | Damagelog.OldLogsDays[y][m] = {} 75 | local dayQuery = self:query("SELECT DISTINCT day FROM damagelog_oldlogs_v3;") 76 | 77 | dayQuery.onSuccess = function(dayQuery) 78 | local days = dayQuery:getData() 79 | 80 | for _, day in pairs(days) do 81 | local d = tonumber(day.day) 82 | 83 | if d then 84 | Damagelog.OldLogsDays[y][m][d] = true 85 | end 86 | end 87 | end 88 | 89 | dayQuery:start() 90 | end 91 | end 92 | end 93 | 94 | monthQuery:start() 95 | end 96 | end 97 | end 98 | 99 | yearsQuery:start() 100 | end 101 | 102 | Damagelog.database.onConnectionFailed = function(self, err) 103 | file.Write("damagelog/mysql_error.txt", err) 104 | Damagelog.MySQL_Error = err 105 | end 106 | 107 | Damagelog.database:connect() 108 | -- year/month/day are only here to send the list of days to the client 109 | -- date is the UNIX TIME 110 | -- Get the list of days and send it to the client 111 | else 112 | if not sql.TableExists("damagelog_oldlogs_v3") then 113 | Damagelog.SQLiteDatabase.Query([[CREATE TABLE IF NOT EXISTS damagelog_oldlogs_v3 ( 114 | id INT UNSIGNED NOT NULL PRIMARY KEY, 115 | year INTEGER NOT NULL, 116 | month INTEGER NOT NULL, 117 | day INTEGER NOT NULL, 118 | date INTEGER NOT NULL, 119 | map TINYTEXT NOT NULL, 120 | round TINYINT NOT NULL, 121 | damagelog TEXT); 122 | ]]) 123 | end 124 | 125 | if not sql.TableExists("damagelog_weapons") then 126 | Damagelog.SQLiteDatabase.Query([[CREATE TABLE IF NOT EXISTS damagelog_weapons ( 127 | class varchar(255) NOT NULL, 128 | name varchar(255) NOT NULL, 129 | PRIMARY KEY (class)); 130 | ]]) 131 | end 132 | 133 | Damagelog.OlderDate = tonumber(Damagelog.SQLiteDatabase.QueryValue("SELECT MIN(date) FROM damagelog_oldlogs_v3 WHERE damagelog IS NOT NULL;")) 134 | Damagelog.LatestDate = tonumber(Damagelog.SQLiteDatabase.QueryValue("SELECT MAX(date) FROM damagelog_oldlogs_v3 WHERE damagelog IS NOT NULL;")) 135 | Damagelog.SQLiteDatabase.Query("DELETE FROM damagelog_oldlogs_v3 WHERE date <= " .. limit .. ";") 136 | Damagelog.OldLogsDays = {} 137 | local years = Damagelog.SQLiteDatabase.Query("SELECT DISTINCT year FROM damagelog_oldlogs_v3;") or {} 138 | 139 | for _, year in pairs(years) do 140 | local y = tonumber(year.year) 141 | 142 | if y then 143 | Damagelog.OldLogsDays[y] = {} 144 | local months = Damagelog.SQLiteDatabase.Query("SELECT DISTINCT month FROM damagelog_oldlogs_v3 WHERE year = " .. y .. ";") or {} 145 | 146 | for _, month in pairs(months) do 147 | local m = tonumber(month.month) 148 | 149 | if m then 150 | Damagelog.OldLogsDays[y][m] = {} 151 | local days = Damagelog.SQLiteDatabase.Query("SELECT DISTINCT day FROM damagelog_oldlogs_v3 WHERE year = " .. y .. " AND month = " .. m .. ";") or {} 152 | 153 | for _, day in pairs(days) do 154 | local d = tonumber(day.day) 155 | 156 | if d then 157 | Damagelog.OldLogsDays[y][m][d] = true 158 | end 159 | end 160 | end 161 | end 162 | end 163 | end 164 | end 165 | 166 | if file.Exists("damagelog/damagelog_lastroundmap.txt", "DATA") then 167 | Damagelog.last_round_map = tonumber(file.Read("damagelog/damagelog_lastroundmap.txt", "DATA")) 168 | file.Delete("damagelog/damagelog_lastroundmap.txt") 169 | end 170 | 171 | hook.Add("TTTEndRound", "Damagelog_EndRound", function() 172 | if Damagelog.DamageTable and Damagelog.ShootTables and Damagelog.ShootTables[Damagelog.CurrentRound] then 173 | local logs = { 174 | DamageTable = Damagelog.DamageTable, 175 | ShootTable = Damagelog.ShootTables[Damagelog.CurrentRound], 176 | Roles = Damagelog.Roles[Damagelog.CurrentRound] 177 | } 178 | 179 | logs = util.TableToJSON(logs) 180 | local t = os.time() 181 | local year = tonumber(os.date("%y")) 182 | local month = tonumber(os.date("%m")) 183 | local day = tonumber(os.date("%d")) 184 | 185 | if Damagelog.Use_MySQL and Damagelog.MySQL_Connected then 186 | local insert = string.format("INSERT INTO damagelog_oldlogs_v3(`year`, `month`, `day`, `date`, `round`, `map`, `damagelog`) VALUES(%i, %i, %i, %i, %i, \"%s\", COMPRESS(%s));", year, month, day, t, Damagelog.CurrentRound, game.GetMap(), sql.SQLStr(logs)) 187 | local query = Damagelog.database:query(insert) 188 | query:start() 189 | elseif not Damagelog.Use_MySQL then 190 | local newRowID = Damagelog.SQLiteDatabase.QueryValue("SELECT IFNULL(MAX(id), 0)+1 FROM damagelog_oldlogs_v3") 191 | Damagelog.SQLiteDatabase.Query(string.format( 192 | "INSERT INTO damagelog_oldlogs_v3(`id`, `year`, `month`, `day`, `date`, `round`, `map`, `damagelog`) " 193 | .. "VALUES(%i, %i, %i, %i, %i, %i, \"%s\", %s);", 194 | newRowID, year, month, day, t, Damagelog.CurrentRound, game.GetMap(), sql.SQLStr(logs))) 195 | end 196 | 197 | file.Write("damagelog/damagelog_lastroundmap.txt", tostring(t)) 198 | end 199 | end) 200 | 201 | net.Receive("DL_AskLogsList", function(_, ply) 202 | -- Check if there aren't any old logs available 203 | if not(Damagelog.OlderDate and Damagelog.LatestDate) then return end 204 | 205 | local payload = util.Compress(util.TableToJSON(Damagelog.OldLogsDays)) 206 | net.Start("DL_SendLogsList") 207 | net.WriteUInt(Damagelog.OlderDate, 32) 208 | net.WriteUInt(Damagelog.LatestDate, 32) 209 | net.WriteUInt(string.len(payload), 32) 210 | net.WriteData(payload, string.len(payload)) 211 | net.Send(ply) 212 | end) 213 | 214 | local function SendLogs(ply, compressed, cancel) 215 | net.Start("DL_SendOldLog") 216 | 217 | if cancel then 218 | net.WriteUInt(0, 1) 219 | else 220 | net.WriteUInt(1, 1) 221 | net.WriteUInt(#compressed, 32) 222 | net.WriteData(compressed, #compressed) 223 | end 224 | 225 | net.Send(ply) 226 | end 227 | 228 | net.Receive("DL_AskOldLogRounds", function(_, ply) 229 | local id = net.ReadUInt(32) 230 | local year = net.ReadUInt(32) 231 | local month = string.format("%02d", net.ReadUInt(32)) 232 | local day = string.format("%02d", net.ReadUInt(32)) 233 | local isnewlog = net.ReadBool() 234 | local _date = "20" .. year .. "-" .. month .. "-" .. day -- TODO: not the best way if someone uses this in 2100 too ;) 235 | 236 | if Damagelog.Use_MySQL and Damagelog.MySQL_Connected then 237 | local query_str = "SELECT date,map,round FROM damagelog_oldlogs_v3 WHERE year = ".. year .. " AND month = " .. month .. " AND day = " .. day .." ORDER BY date ASC;" 238 | local query = Damagelog.database:query(query_str) 239 | 240 | query.onSuccess = function(self) 241 | if not IsValid(ply) then 242 | return 243 | end 244 | 245 | local data = self:getData() 246 | net.Start("DL_SendOldLogRounds") 247 | net.WriteUInt(id, 32) 248 | net.WriteTable(data) 249 | net.WriteBool(isnewlog) 250 | net.Send(ply) 251 | end 252 | 253 | query:start() 254 | else 255 | local query_str = "SELECT date,map,round FROM damagelog_oldlogs_v3 WHERE year = ".. year .. " AND month = " .. month .. " AND day = " .. day .." ORDER BY date ASC;" 256 | local result = Damagelog.SQLiteDatabase.Query(query_str) 257 | 258 | if not result then 259 | result = {} 260 | end 261 | 262 | net.Start("DL_SendOldLogRounds") 263 | net.WriteUInt(id, 32) 264 | net.WriteTable(result) 265 | net.WriteBool(isnewlog) 266 | net.Send(ply) 267 | end 268 | end) 269 | 270 | net.Receive("DL_AskOldLog", function(_, ply) 271 | if IsValid(ply) and ply:IsPlayer() and (not ply.lastLogs or (CurTime() - ply.lastLogs) > 2) then 272 | local _time = net.ReadUInt(32) 273 | local isDamageTab = net.ReadBool() 274 | 275 | ply.lastLogs = CurTime() 276 | 277 | if isDamageTab then 278 | local data, roles 279 | 280 | if round == -1 then 281 | data = Damagelog.PreviousMap.ShootTable 282 | roles = Damagelog.PreviousMap.Roles 283 | else 284 | data = Damagelog.ShootTables[_time] 285 | roles = Damagelog.Roles[_time] 286 | end 287 | 288 | if not roles or not data then return end 289 | 290 | local payload = util.Compress(util.TableToJSON({ShootTable = data, Roles = roles })) 291 | 292 | SendLogs(ply, payload, false) 293 | return 294 | end 295 | 296 | 297 | if Damagelog.Use_MySQL and Damagelog.MySQL_Connected then 298 | local query = Damagelog.database:query("SELECT UNCOMPRESS(damagelog) FROM damagelog_oldlogs_v3 WHERE date = " .. _time .. ";") 299 | 300 | query.onSuccess = function(self) 301 | local data = self:getData() 302 | 303 | if data[1] and data[1]["UNCOMPRESS(damagelog)"] then 304 | local compressed = util.Compress(data[1]["UNCOMPRESS(damagelog)"]) 305 | SendLogs(ply, compressed, false) 306 | else 307 | SendLogs(ply, nil, true) 308 | end 309 | 310 | end 311 | 312 | query:start() 313 | elseif not Damagelog.Use_MySQL then 314 | local query = Damagelog.SQLiteDatabase.QueryValue("SELECT damagelog FROM damagelog_oldlogs_v3 WHERE date = " .. _time) 315 | 316 | if query then 317 | SendLogs(ply, util.Compress(query), false) 318 | else 319 | SendLogs(ply, nil, true) 320 | end 321 | end 322 | end 323 | end) 324 | -------------------------------------------------------------------------------- /lua/damagelogs/server/chat.lua: -------------------------------------------------------------------------------- 1 | util.AddNetworkString("DL_StartChat") 2 | util.AddNetworkString("DL_OpenChat") 3 | util.AddNetworkString("DL_JoinChat") 4 | util.AddNetworkString("DL_SendChatMessage") 5 | util.AddNetworkString("DL_BroadcastMessage") 6 | util.AddNetworkString("DL_JoinChatCL") 7 | util.AddNetworkString("DL_StopChat") 8 | util.AddNetworkString("DL_AddChatPlayer") 9 | util.AddNetworkString("DL_CloseChat") 10 | util.AddNetworkString("DL_LeaveChat") 11 | util.AddNetworkString("DL_LeaveChatCL") 12 | util.AddNetworkString("DL_ForceStay") 13 | util.AddNetworkString("DL_ForcePlayerStay") 14 | util.AddNetworkString("DL_ForceStayNotification") 15 | util.AddNetworkString("DL_Release") 16 | util.AddNetworkString("DL_ReleaseCL") 17 | util.AddNetworkString("DL_ViewChat") 18 | util.AddNetworkString("DL_ViewChatCL") 19 | local COLOR_VICTIM = Color(18, 190, 29) 20 | local COLOR_ATTACKER = Color(190, 18, 29) 21 | local COLOR_ADMIN = Color(160, 160, 0) 22 | local COLOR_OTHER = Color(29, 18, 190) 23 | Damagelog.ChatHistory = Damagelog.ChatHistory or {} 24 | 25 | local function GetFilter(chat, block) 26 | local filter = {} 27 | 28 | if IsValid(chat.victim) and chat.victim ~= block then 29 | table.insert(filter, chat.victim) 30 | end 31 | 32 | if IsValid(chat.attacker) and chat.attacker ~= block then 33 | table.insert(filter, chat.attacker) 34 | end 35 | 36 | local function process(tbl) 37 | for _, v in pairs(tbl) do 38 | if v ~= block and IsValid(v) then 39 | table.insert(filter, v) 40 | end 41 | end 42 | end 43 | 44 | process(chat.players) 45 | process(chat.admins) 46 | 47 | return filter 48 | end 49 | 50 | local function IsAllowed(ply, chat) 51 | if chat.victim == ply then 52 | return true, COLOR_VICTIM 53 | elseif chat.attacker == ply then 54 | return true, COLOR_ATTACKER 55 | elseif table.HasValue(chat.admins, ply) then 56 | return true, COLOR_ADMIN 57 | elseif table.HasValue(chat.players, ply) then 58 | return true, COLOR_OTHER 59 | end 60 | 61 | return false 62 | end 63 | 64 | net.Receive("DL_StartChat", function(_len, ply) 65 | local report_index = net.ReadUInt(32) 66 | 67 | if not ply:CanUseRDMManager() then 68 | return 69 | end 70 | 71 | local report = Damagelog.Reports.Current[report_index] 72 | 73 | if not report then 74 | return 75 | end 76 | 77 | local victim 78 | local attacker 79 | 80 | for _, v in ipairs(player.GetHumans()) do 81 | if v:SteamID() == report.victim then 82 | victim = v 83 | elseif v:SteamID() == report.attacker then 84 | attacker = v 85 | elseif attacker and victim then 86 | break 87 | end 88 | end 89 | 90 | if not IsValid(victim) or not IsValid(attacker) then 91 | ply:Damagelog_Notify(DAMAGELOG_NOTIFY_ALERT, TTTLogTranslate(ply.DMGLogLang, "VictimReportedDisconnected"), 5, "buttons/weapon_cant_buy.wav") 92 | 93 | return 94 | end 95 | 96 | for k, v in pairs(Damagelog.Reports.Current) do 97 | if v.chat_open and k == report_index then 98 | ply:Damagelog_Notify(DAMAGELOG_NOTIFY_ALERT, TTTLogTranslate(ply.DMGLogLang, "ChatAlready"), 5, "buttons/weapon_cant_buy.wav") 99 | 100 | return 101 | end 102 | end 103 | 104 | report.chat_open = { 105 | admins = {ply}, 106 | victim = victim, 107 | attacker = attacker, 108 | players = {} 109 | } 110 | 111 | local history = false 112 | 113 | if report.chat_opened then 114 | history = Damagelog.ChatHistory[report_index] or {} 115 | report.chat_open.players = report.chat_previousPlayers or {} 116 | else 117 | report.chat_opened = true 118 | Damagelog.ChatHistory[report_index] = {} 119 | end 120 | 121 | net.Start("DL_OpenChat") 122 | net.WriteUInt(report_index, 32) 123 | net.WriteUInt(report.adminReport and 1 or 0, 1) 124 | net.WriteEntity(ply) 125 | net.WriteEntity(victim) 126 | net.WriteEntity(attacker) 127 | net.WriteTable(report.chat_open.players) 128 | 129 | if history then 130 | net.WriteUInt(1, 1) 131 | local json = util.TableToJSON(history) 132 | local compressed = util.Compress(json) 133 | net.WriteUInt(#compressed, 32) 134 | net.WriteData(compressed, #compressed) 135 | else 136 | net.WriteUInt(0, 1) 137 | end 138 | 139 | net.Send(GetFilter(report.chat_open)) 140 | 141 | for _, v in ipairs(player.GetHumans()) do 142 | if v:CanUseRDMManager() then 143 | v:Damagelog_Notify(DAMAGELOG_NOTIFY_INFO, string.format(TTTLogTranslate(ply.DMGLogLang, "OpenChatNotification"), ply:Nick(), report_index), 5, "") 144 | v:UpdateReport(false, report_index) 145 | end 146 | end 147 | end) 148 | 149 | local function AddToChat(id, report, ply) 150 | if not report.chat_open then 151 | return 152 | end 153 | 154 | local history = Damagelog.ChatHistory[id] or {} 155 | local json = util.TableToJSON(history) 156 | local compressed = util.Compress(json) 157 | local category = DAMAGELOG_OTHER 158 | 159 | if ply:CanUseRDMManager() and not table.HasValue(report.chat_open.admins, ply) then 160 | table.insert(report.chat_open.admins, ply) 161 | category = DAMAGELOG_ADMIN 162 | end 163 | 164 | if ply:SteamID() == report.victim then 165 | report.chat_open.victim = ply 166 | category = DAMAGELOG_VICTIM 167 | elseif ply:SteamID() == report.attacker then 168 | report.chat_open.attacker = ply 169 | category = DAMAGELOG_REPORTED 170 | elseif not table.HasValue(report.chat_open.admins, ply) and not table.HasValue(report.chat_open.players, ply) then 171 | table.insert(report.chat_open.players, ply) 172 | category = DAMAGELOG_OTHER 173 | end 174 | 175 | net.Start("DL_JoinChatCL") 176 | net.WriteUInt(1, 1) 177 | net.WriteUInt(id, 32) 178 | net.WriteUInt(#compressed, 32) 179 | net.WriteData(compressed, #compressed) 180 | net.WriteTable(report.chat_open) 181 | net.Send(ply) 182 | net.Start("DL_JoinChatCL") 183 | net.WriteUInt(0, 1) 184 | net.WriteUInt(id, 32) 185 | net.WriteEntity(ply) 186 | net.WriteUInt(category, 32) 187 | net.Send(GetFilter(report.chat_open, ply)) 188 | 189 | for _, v in ipairs(player.GetHumans()) do 190 | if v:CanUseRDMManager() then 191 | v:UpdateReport(false, id) 192 | end 193 | end 194 | end 195 | 196 | net.Receive("DL_JoinChat", function(_len, ply) 197 | local id = net.ReadUInt(32) 198 | local report = Damagelog.Reports.Current[id] 199 | 200 | if not report or not report.chat_open then 201 | return 202 | end 203 | 204 | if not ply:CanUseRDMManager() then 205 | return 206 | end 207 | 208 | AddToChat(id, report, ply) 209 | end) 210 | 211 | net.Receive("DL_SendChatMessage", function(_len, ply) 212 | local id = net.ReadUInt(32) 213 | local message = net.ReadString() 214 | 215 | if #message == 0 or #message > 200 then 216 | return 217 | end 218 | 219 | local report = Damagelog.Reports.Current[id] 220 | 221 | if not report then 222 | return 223 | end 224 | 225 | local chat = report.chat_open 226 | 227 | if not chat then 228 | return 229 | end 230 | 231 | local allowed, color = IsAllowed(ply, chat) 232 | 233 | if not allowed then 234 | return 235 | end 236 | 237 | table.insert(Damagelog.ChatHistory[id], { 238 | nick = ply:Nick(), 239 | color = { 240 | r = color.r, 241 | g = color.g, 242 | b = color.b, 243 | a = color.a 244 | }, 245 | msg = message 246 | }) 247 | 248 | net.Start("DL_BroadcastMessage") 249 | net.WriteUInt(id, 32) 250 | net.WriteEntity(ply) 251 | net.WriteColor(color) 252 | net.WriteString(message) 253 | net.Send(GetFilter(chat)) 254 | end) 255 | 256 | hook.Add("PlayerDisconnected", "Damagelog_Chat", function(ply) 257 | for k, v in pairs(Damagelog.Reports.Current) do 258 | if v.chat_open then 259 | if table.HasValue(v.admins, ply) then 260 | table.RemoveByValue(v.admins, ply) 261 | 262 | if #v.admins == 1 then 263 | net.Start("DL_StopChat") 264 | net.WriteUInt(k, 32) 265 | net.WriteUInt(0, 1) 266 | net.Send(GetFilter(v.chat_open)) 267 | v.chat_open = false 268 | 269 | for _, v2 in ipairs(player.GetHumans()) do 270 | if v2:CanUseRDMManager() then 271 | v2:UpdateReport(false, id) 272 | end 273 | end 274 | end 275 | end 276 | 277 | if table.HasValue(v.players, ply) then 278 | table.RemoveByValue(v.players, ply) 279 | end 280 | end 281 | end 282 | end) 283 | 284 | net.Receive("DL_AddChatPlayer", function(_len, ply) 285 | local id = net.ReadUInt(32) 286 | local to_add = net.ReadEntity() 287 | 288 | if not ply:CanUseRDMManager() or not IsValid(to_add) or not to_add:IsPlayer() then 289 | return 290 | end 291 | 292 | local report = Damagelog.Reports.Current[id] 293 | 294 | if not report then 295 | return 296 | end 297 | 298 | AddToChat(id, report, to_add) 299 | to_add:Damagelog_Notify(DAMAGELOG_NOTIFY_INFO, TTTLogTranslate(ply.DMGLogLang, "AddedChatAdmin"), 5, "") 300 | end) 301 | 302 | net.Receive("DL_CloseChat", function(_len, ply) 303 | local id = net.ReadUInt(32) 304 | 305 | if not ply:CanUseRDMManager() then 306 | return 307 | end 308 | 309 | local report = Damagelog.Reports.Current[id] 310 | 311 | if not report then 312 | return 313 | end 314 | 315 | if report.chat_open then 316 | net.Start("DL_StopChat") 317 | net.WriteUInt(id, 32) 318 | net.WriteUInt(1, 1) 319 | net.WriteEntity(ply) 320 | net.Send(GetFilter(report.chat_open)) 321 | report.chat_previousPlayers = table.Copy(report.players) 322 | report.chat_open = false 323 | end 324 | 325 | for _, v in ipairs(player.GetHumans()) do 326 | if v:CanUseRDMManager() then 327 | v:Damagelog_Notify(DAMAGELOG_NOTIFY_INFO, string.format(TTTLogTranslate(ply.DMGLogLang, "ChatClosed"), ply:Nick(), id), 5, "") 328 | v:UpdateReport(false, id) 329 | end 330 | end 331 | end) 332 | 333 | net.Receive("DL_LeaveChat", function(_len, ply) 334 | local id = net.ReadUInt(32) 335 | 336 | if not ply:CanUseRDMManager() then 337 | return 338 | end 339 | 340 | local report = Damagelog.Reports.Current[id] 341 | 342 | if not report then 343 | return 344 | end 345 | 346 | if report.chat_open then 347 | if #report.chat_open.admins <= 1 then 348 | return 349 | end 350 | 351 | for k, v in pairs(report.chat_open.admins) do 352 | if v == ply then 353 | table.remove(report.chat_open.admins, k) 354 | break 355 | end 356 | end 357 | 358 | net.Start("DL_LeaveChatCL") 359 | net.WriteUInt(id, 32) 360 | net.WriteEntity(ply) 361 | net.Send(GetFilter(report.chat_open)) 362 | end 363 | end) 364 | 365 | net.Receive("DL_ForceStay", function(_len, ply) 366 | local id = net.ReadUInt(32) 367 | local allPlayers = net.ReadUInt(1) == 1 368 | local players 369 | 370 | if not allPlayers then 371 | players = {net.ReadEntity()} 372 | end 373 | 374 | if not ply:CanUseRDMManager() then 375 | return 376 | end 377 | 378 | local report = Damagelog.Reports.Current[id] 379 | 380 | if not report then 381 | return 382 | end 383 | 384 | if allPlayers then 385 | players = report.chat_open.players 386 | end 387 | 388 | for _, v in pairs(players) do 389 | v:SetNWInt("DL_ForcedStay", id) 390 | net.Start("DL_ForcePlayerStay") 391 | net.WriteUInt(id, 32) 392 | net.Send(v) 393 | end 394 | 395 | net.Start("DL_ForceStayNotification") 396 | net.WriteUInt(id, 32) 397 | net.WriteUInt(allPlayers and 1 or 0, 1) 398 | 399 | if not allPlayers then 400 | net.WriteEntity(players[1]) 401 | end 402 | 403 | net.WriteUInt(1, 1) 404 | net.WriteEntity(ply) 405 | net.Send(GetFilter(report.chat_open)) 406 | end) 407 | 408 | net.Receive("DL_Release", function(_len, ply) 409 | local id = net.ReadUInt(32) 410 | local allPlayers = net.ReadUInt(1) == 1 411 | local players 412 | 413 | if not allPlayers then 414 | players = {net.ReadEntity()} 415 | end 416 | 417 | if not ply:CanUseRDMManager() then 418 | return 419 | end 420 | 421 | local report = Damagelog.Reports.Current[id] 422 | 423 | if not report then 424 | return 425 | end 426 | 427 | if allPlayers then 428 | players = report.chat_open.players 429 | end 430 | 431 | for _, v in pairs(players) do 432 | v:SetNWInt("DL_ForcedStay", -1) 433 | net.Start("DL_ReleaseCL") 434 | net.WriteUInt(id, 32) 435 | net.Send(v) 436 | end 437 | 438 | net.Start("DL_ForceStayNotification") 439 | net.WriteUInt(id, 32) 440 | net.WriteUInt(allPlayers and 1 or 0, 1) 441 | 442 | if not allPlayers then 443 | net.WriteEntity(players[1]) 444 | end 445 | 446 | net.WriteUInt(0, 1) 447 | net.WriteEntity(ply) 448 | net.Send(GetFilter(report.chat_open)) 449 | end) 450 | 451 | net.Receive("DL_ViewChat", function(_len, ply) 452 | local id = net.ReadUInt(32) 453 | 454 | if not ply:CanUseRDMManager() then 455 | return 456 | end 457 | 458 | local report = Damagelog.Reports.Current[id] 459 | 460 | if not report or report.chat_open then 461 | return 462 | end 463 | 464 | local history = Damagelog.ChatHistory[id] or {} 465 | net.Start("DL_ViewChatCL") 466 | net.WriteUInt(id, 32) 467 | local json = util.TableToJSON(history) 468 | local compressed = util.Compress(json) 469 | net.WriteUInt(#compressed, 32) 470 | net.WriteData(compressed, #compressed) 471 | net.Send(ply) 472 | end) -------------------------------------------------------------------------------- /lua/damagelogs/client/listview.lua: -------------------------------------------------------------------------------- 1 | local color_what = Color(255, 0, 0, 100) 2 | local color_darkblue = Color(25, 25, 220) 3 | local color_orange = Color(255, 128, 0) 4 | 5 | function Damagelog:SetLineMenu(item, infos, tbl, roles, text, old_logs) 6 | item.ShowTooLong = function(_, b) 7 | item.ShowLong = b 8 | end 9 | 10 | item.ShowCopy = function(_, b, steamid1, steamid2) 11 | item.Copy = b 12 | item.steamid1 = steamid1 13 | item.steamid2 = steamid2 14 | end 15 | 16 | item.ShowDamageInfos = function(_, ply1, ply2) 17 | item.DamageInfos = true 18 | item.ply1 = ply1 19 | item.ply2 = ply2 20 | end 21 | 22 | item.ShowDeathScene = function(_, ply1, ply2, id) 23 | item.DeathScene = true 24 | item.ply1 = ply1 25 | item.ply2 = ply2 26 | item.sceneid = id 27 | end 28 | 29 | item.text = text 30 | item.old_logs = old_logs 31 | 32 | item.OnRightClick = function() 33 | if not item.ShowLong and not item.Copy and not item.DamageInfos then 34 | return 35 | end 36 | 37 | local menu = DermaMenu() 38 | local pnl = vgui.Create("DMenuOption", menu) 39 | local copy = DermaMenu(menu) 40 | copy:SetVisible(false) 41 | pnl:SetSubMenu(copy) 42 | pnl:SetText(TTTLogTranslate(GetDMGLogLang, "Copy")) 43 | pnl:SetImage("icon16/tab_edit.png") 44 | menu:AddPanel(pnl) 45 | 46 | copy:AddOption(TTTLogTranslate(GetDMGLogLang, "Lines"), function() 47 | local full_text = "" 48 | local append = false 49 | 50 | for _, line in pairs(item:GetListView():GetSelected()) do 51 | if append then 52 | full_text = full_text .. "\n" 53 | end 54 | 55 | full_text = full_text .. "[" .. line:GetColumnText(1) .. "] " .. line:GetColumnText(3) 56 | append = true 57 | end 58 | 59 | SetClipboardText(full_text) 60 | end) 61 | 62 | if item.Copy then 63 | copy:AddOption(TTTLogTranslate(GetDMGLogLang, "SteamIDof") .. item.steamid1[1], function() 64 | SetClipboardText(item.steamid1[2]) 65 | end) 66 | 67 | if item.steamid2 then 68 | copy:AddOption(TTTLogTranslate(GetDMGLogLang, "SteamIDof") .. item.steamid2[1], function() 69 | SetClipboardText(item.steamid2[2]) 70 | end) 71 | end 72 | end 73 | 74 | if item.DamageInfos then 75 | menu:AddOption(TTTLogTranslate(GetDMGLogLang, "ShowDamageInfos"), function() 76 | if item.old_logs then 77 | local found, result = self:FindFromOldLogs(tbl.time, item.ply1, item.ply2) 78 | self:SetDamageInfosLV(self.OldDamageInfo, roles, tbl.ply1, tbl.ply2, tbl.time, tbl.time - 10, found and result) 79 | self.DamageInfoForm:Toggle() 80 | else 81 | net.Start("DL_AskDamageInfos") 82 | net.WriteUInt(tbl.time, 32) 83 | net.WriteUInt(item.ply1, 32) 84 | net.WriteUInt(item.ply2, 32) 85 | 86 | if not tbl.round then 87 | net.WriteUInt(self.SelectedRound, 32) 88 | else 89 | net.WriteUInt(tbl.round, 32) 90 | end 91 | 92 | net.SendToServer() 93 | end 94 | end):SetImage("icon16/gun.png") 95 | end 96 | 97 | if item.DeathScene then 98 | menu:AddOption(TTTLogTranslate(GetDMGLogLang, "ShowDeathScene"), function() 99 | net.Start("DL_AskDeathScene") 100 | net.WriteUInt(item.sceneid, 32) 101 | net.WriteUInt(item.ply1, 32) 102 | net.WriteUInt(item.ply2, 32) 103 | net.SendToServer() 104 | end):SetImage("icon16/television.png") 105 | end 106 | 107 | if item.ShowLong then 108 | menu:AddOption(TTTLogTranslate(GetDMGLogLang, "FullDisplay"), function() 109 | Derma_Message(item.text, TTTLogTranslate(GetDMGLogLang, "FullDisplay"), TTTLogTranslate(GetDMGLogLang, "Close")) 110 | end):SetImage("icon16/eye.png") 111 | end 112 | 113 | menu:Open() 114 | end 115 | 116 | infos:RightClick(item, tbl.infos, roles, text) 117 | end 118 | 119 | function Damagelog:AddLogsLine(listview, tbl, roles, nofilters, old) 120 | if type(tbl) ~= "table" then 121 | return 122 | end 123 | 124 | local infos = self.events[tbl.id] 125 | 126 | if not infos then 127 | return 128 | end 129 | 130 | if not nofilters and not infos:IsAllowed(tbl.infos, roles) then 131 | return 132 | end 133 | 134 | local text = infos:ToString(tbl.infos, roles) 135 | local item = listview:AddLine(util.SimpleTime(tbl.time, "%02i:%02i"), infos.Type, text, "") 136 | 137 | if tbl.infos.icon then 138 | if tbl.infos.icon[1] then 139 | local image = vgui.Create("DImage", item.Columns[4]) 140 | image:SetImage(tbl.infos.icon[1]) 141 | image:SetSize(16, 16) 142 | image:SetPos(6, 1) 143 | end 144 | 145 | if tbl.infos.icon[2] then 146 | item:SetTooltip(TTTLogTranslate(GetDMGLogLang, "VictimShotFirst")) 147 | end 148 | end 149 | 150 | function item:PaintOver(w, h) 151 | if not self:IsSelected() and infos:Highlight(item, tbl.infos, text) then 152 | surface.SetDrawColor(color_what) 153 | surface.DrawRect(0, 0, w, h) 154 | else 155 | for _, v in pairs(item.Columns) do 156 | v:SetTextColor(infos:GetColor(tbl.infos, roles)) 157 | end 158 | end 159 | end 160 | 161 | self:SetLineMenu(item, infos, tbl, roles, text, old) 162 | 163 | return true 164 | end 165 | 166 | function Damagelog:SetListViewTable(listview, tbl, nofilters, old) 167 | if not tbl or not tbl.logs then 168 | return 169 | end 170 | 171 | if #tbl.logs > 0 then 172 | local added = false 173 | 174 | for _, v in ipairs(tbl.logs) do 175 | local line_added = self:AddLogsLine(listview, v, tbl.roles, nofilters, old) 176 | 177 | if not added and line_added then 178 | added = true 179 | end 180 | end 181 | 182 | if not added then 183 | listview:AddLine("", "", TTTLogTranslate(GetDMGLogLang, "EmptyLogsFilters")) 184 | end 185 | else 186 | listview:AddLine("", "", TTTLogTranslate(GetDMGLogLang, "EmptyLogs")) 187 | end 188 | end 189 | 190 | function Damagelog:SetRolesListView(listview, tbl) 191 | listview:Clear() 192 | 193 | if not tbl then 194 | return 195 | end 196 | 197 | for _, v in pairs(tbl) do 198 | if v.role ~= ROLE_INNOCENT or GetConVar("ttt_dmglogs_showinnocents"):GetBool() then 199 | self:AddRoleLine(listview, v.nick, v.role) 200 | end 201 | end 202 | end 203 | 204 | local role_colors = { } 205 | 206 | -- We need to wait for the TTT gamemode to init to ensure the ROLE_XXX values are defined 207 | hook.Remove("Initialize", "damagelogs_gminit_rolecolours") 208 | hook.Add("Initialize", "damagelogs_gminit_rolecolours", function() 209 | -- Roles are defined in lua/damagelogs/shared/defines.lua 210 | role_colors = { 211 | [ROLE_INNOCENT] = Color(0, 200, 0), 212 | [ROLE_TRAITOR] = Color(200, 0, 0), 213 | [ROLE_DETECTIVE] = Color(0, 0, 200), 214 | [DAMAGELOG_ROLE_DISCONNECTED] = Color(0, 0, 0) 215 | } 216 | end) 217 | 218 | function Damagelog:AddRoleLine(listview, nick, role) 219 | if role == DAMAGELOG_ROLE_JOINAFTERROUNDSTART or role == DAMAGELOG_ROLE_SPECTATOR then return end 220 | 221 | local item = listview:AddLine(nick, self:StrRole(role), "") 222 | 223 | function item:PaintOver() 224 | for _, v in pairs(item.Columns) do 225 | if role < 0 then 226 | v:SetTextColor(role_colors[DAMAGELOG_ROLE_DISCONNECTED]) 227 | else 228 | if TTT2 then 229 | v:SetTextColor(GetRoleByIndex(role).color) 230 | elseif CR_VERSION then 231 | v:SetTextColor(ROLE_COLORS[role]) 232 | else 233 | v:SetTextColor(role_colors[role]) 234 | end 235 | end 236 | end 237 | end 238 | 239 | item.Nick = nick 240 | item.Round = self.SelectedRound 241 | local sync_ent = self:GetSyncEnt() 242 | 243 | item.Think = function(panel) 244 | if GetRoundState() == ROUND_ACTIVE and sync_ent:GetPlayedRounds() == panel.Round then 245 | local ent = self.RoleNicks and self.RoleNicks[panel.Nick] 246 | 247 | if IsValid(ent) then 248 | panel:SetColumnText(3, ent:Alive() and not (ent.IsGhost and ent:IsGhost()) and not ent:IsSpec() and TTTLogTranslate(GetDMGLogLang, "Yes") or TTTLogTranslate(GetDMGLogLang, "No")) 249 | else 250 | panel:SetColumnText(3, TTTLogTranslate(GetDMGLogLang, "ChatDisconnected")) 251 | end 252 | else 253 | panel:SetColumnText(3, TTTLogTranslate(GetDMGLogLang, "RoundEnded")) 254 | end 255 | end 256 | end 257 | 258 | local shoot_colors = { 259 | [Color(46, 46, 46)] = true, 260 | [Color(66, 66, 66)] = true, 261 | [Color(125, 125, 125)] = true, 262 | [Color(255, 6, 13)] = true, 263 | [Color(0, 0, 128)] = true, 264 | [Color(0, 0, 205)] = true, 265 | [Color(79, 209, 204)] = true, 266 | [Color(165, 42, 42)] = true, 267 | [Color(238, 59, 59)] = true, 268 | [Color(210, 105, 30)] = true, 269 | [Color(255, 165, 79)] = true, 270 | [Color(107, 66, 38)] = true, 271 | [Color(166, 128, 100)] = true, 272 | [Color(0, 100, 0)] = true, 273 | [Color(34, 139, 34)] = true, 274 | [Color(124, 252, 0)] = true, 275 | [Color(78, 328, 148)] = true, 276 | [Color(139, 10, 80)] = true, 277 | [Color(205, 16, 118)] = true, 278 | [Color(205, 85, 85)] = true, 279 | [Color(110, 6, 250)] = true, 280 | [Color(30, 235, 0)] = true, 281 | [Color(205, 149, 12)] = true, 282 | [Color(0, 0, 250)] = true, 283 | [Color(219, 150, 50)] = true, 284 | [Color(255, 36, 0)] = true, 285 | [Color(205, 104, 57)] = true, 286 | [Color(191, 62, 255)] = true, 287 | [Color(99, 86, 126)] = true, 288 | [Color(133, 99, 99)] = true 289 | } 290 | 291 | function Damagelog:SetDamageInfosLV(listview, roles, att, victim, beg, t, result) 292 | if not IsValid(self.Menu) then 293 | return 294 | end 295 | 296 | for k in pairs(shoot_colors) do 297 | shoot_colors[k] = true 298 | end 299 | 300 | if beg then 301 | beg = string.FormattedTime(math.Clamp(beg, 0, 999), "%02i:%02i") 302 | end 303 | 304 | if t then 305 | t = string.FormattedTime(t, "%02i:%02i") 306 | end 307 | 308 | listview:Clear() 309 | 310 | if victim and att then 311 | listview:AddLine(string.format(TTTLogTranslate(GetDMGLogLang, "DamageInfosBetween"), victim, att, beg, t)) 312 | end 313 | 314 | if not result or table.Count(result) <= 0 then 315 | listview:AddLine(TTTLogTranslate(GetDMGLogLang, "EmptyLogs")) 316 | -- Currently unused 317 | -- Currently unused 318 | else 319 | local nums = {} 320 | local used_nicks = {} 321 | 322 | for k in pairs(result) do 323 | table.insert(nums, k) 324 | end 325 | 326 | table.sort(nums) 327 | local players = {} 328 | 329 | for _, v in ipairs(nums) do 330 | local info = result[v] 331 | local color 332 | 333 | for _, i in pairs(info) do 334 | if att and victim then 335 | if not players[1] then 336 | players[1] = i[1] 337 | elseif not players[2] then 338 | players[2] = i[1] 339 | end 340 | else 341 | if not used_nicks[i[1]] then 342 | local found = false 343 | 344 | for k, v2 in RandomPairs(shoot_colors) do 345 | if v2 and not found then 346 | color = k 347 | found = true 348 | end 349 | end 350 | 351 | if found then 352 | shoot_colors[color] = false 353 | used_nicks[i[1]] = color 354 | else 355 | used_nicks[i[1]] = COLOR_WHITE 356 | end 357 | else 358 | color = used_nicks[i[1]] 359 | end 360 | end 361 | 362 | local item 363 | 364 | if i[2] == "crowbartir" then 365 | item = listview:AddLine(string.format(TTTLogTranslate(GetDMGLogLang, "CrowbarSwung"), string.FormattedTime(v, "%02i:%02i"), self:InfoFromID(roles, i[1]).nick)) 366 | elseif i[2] == "crowbarpouss" then 367 | item = listview:AddLine(string.format(TTTLogTranslate(GetDMGLogLang, "HasPushed"), string.FormattedTime(v, "%02i:%02i"), self:InfoFromID(roles, i[1]).nick, self:InfoFromID(roles, i[3]).nick)) 368 | else 369 | wep = Damagelog:GetWeaponName(i[2]) or TTTLogTranslate(GetDMGLogLang, "UnknownWeapon") 370 | item = listview:AddLine(string.format(TTTLogTranslate(GetDMGLogLang, "HasShot"), string.FormattedTime(v, "%02i:%02i"), self:InfoFromID(roles, i[1]).nick, wep)) 371 | end 372 | 373 | item.PaintOver = function() 374 | if att and victim then 375 | if i[1] == players[1] then 376 | item.Columns[1]:SetTextColor(color_darkblue) 377 | elseif i[1] == players[2] then 378 | item.Columns[1]:SetTextColor(color_orange) 379 | end 380 | else 381 | item.Columns[1]:SetTextColor(color) 382 | end 383 | end 384 | end 385 | end 386 | end 387 | 388 | self.DamageInfoBox:Toggle() 389 | end 390 | -------------------------------------------------------------------------------- /lua/damagelogs/shared/lang/simplified_chinese.lua: -------------------------------------------------------------------------------- 1 | DamagelogLang.chinese = { 2 | DefaultReason = "未指定原因", 3 | -- Damage types 4 | DMG_BLAST = "爆炸", 5 | DMG_BURN = "火焰", 6 | DMG_CRUSH = "坠落或道具伤害", 7 | DMG_SLASH = "锐物", 8 | DMG_CLUB = "被棍棒击打致死", 9 | DMG_SHOCK = "电击", 10 | DMG_ENERGYBEAM = "激光", 11 | DMG_SONIC = "传送冲突", 12 | DMG_PHYSGUN = "大质量物体", 13 | Damagelog = "伤害日志", 14 | SLogs = " 开火日志", 15 | RDMManag = "RDM 管理器", 16 | UpdateNotify = "这个版本已经过时!你可以在 https://github.com/BadgerCode/tttdamagelogs 获取最新版本", 17 | About = "关于", 18 | Settings = "设置", 19 | KilledBy = "你被 %s 杀死", 20 | OpenMenu = "打开报告菜单", 21 | WasntRDM = "这不是 RDM。", 22 | OpenReportMenu = "你已死亡! 使用指令打开报告菜单", 23 | Command = "指令", 24 | PopupNote = "(注:你可以在 F1 中禁用这个弹窗)", 25 | AbuseNote = "是存活的,并且已加载当前回合的日志。", 26 | Loading = "加载中...", 27 | NothingFound = "找不到任何东西", 28 | RoundFilter = "回合选择/过滤器", 29 | None = "无", 30 | EditFilter = "编辑过滤器", 31 | CurrentFilter = "当前高亮玩家:", 32 | NoPlayers = "没有玩家。", 33 | plyinfo = "玩家信息", 34 | MorePlayers = "你不能一次高亮超过 3 个玩家!", 35 | Error = "错误", 36 | DmgInfo = "伤害信息", 37 | Player = "玩家", 38 | Role = "角色", 39 | Alive = "存活?", 40 | ShowInnocent = "显示无辜的玩家", 41 | Time = "时间", 42 | Type = "类型", 43 | Event = "事件", 44 | LastRound = "上一图的最后一轮", 45 | Current = "当前", 46 | Round = "回合", 47 | NoLogsAvailable = "当前地图没有可用日志", 48 | Nothinghere = "这里什么都没有...", 49 | Refresh = "刷新", 50 | ResetDefault = "恢复默认", 51 | Yoursure = "你确定吗?", 52 | Yes = "是", 53 | No = "否", 54 | WeaponEntityID = "武器/实体 ID", 55 | DisplayName = "显示名称", 56 | RDMWaiting = "等待中", 57 | RDMWaitingAttacker = "等待攻击者", 58 | RDMResponded = "攻击者已回应", 59 | RDMInProgress = "处理中", 60 | RDMFinished = "已完成", 61 | RDMCanceled = "被受害者取消", 62 | RDMNotFinished = "这份报告未完成!", 63 | RDMSetConclusion = "设定结论", 64 | Conclusion = "结论", 65 | RDMWriteConclusion = "请为这份报告撰写结论", 66 | RDMForceRespond = "强制被举报玩家回应", 67 | RDMNotValid = "被举报玩家无效!(机器人或已断开连接)", 68 | Chat = "聊天", 69 | ReopenChat = "重新开启聊天", 70 | OpenChat = "打开聊天", 71 | JoinChat = "加入聊天", 72 | DeathSceneNotFound = "找不到死亡场景", 73 | Victim = "受害者", 74 | ReportedPlayer = "被举报玩家", 75 | Status = "状态", 76 | By = "由", 77 | InChat = "(在聊天中)", 78 | NoConclusion = "选定报告无结论", 79 | Reports = "报告", 80 | PreviousMapReports = "前一张地图的报告", 81 | ShowFinishedReports = "显示已完成的报告", 82 | Set = "设定", 83 | SStatus = "设定状态", 84 | VictimsReport = "受害者报告", 85 | AdminsMessage = "管理员消息", 86 | ReportedPlayerResponse = "被举报玩家的回应", 87 | ChatOpened = "聊天已开启,玩家回应已禁用", 88 | ChatOpenedShort = "聊天已开启", 89 | NoSelectedReport = "无选定报告", 90 | LogsBeforeVictim = "受害者死亡前的日志", 91 | NoResponseYet = "尚无回应", 92 | NoWeaponOrOwner = "无武器或所有者", 93 | BeenReported = "你已被举报!请回应所有举报。", 94 | ReportedYou = "举报了你", 95 | AfterRound = "在回合结束后", 96 | PreviousMap = "在上一张地图上", 97 | Send = "发送", 98 | MinCharacters = "需要至少 10 个字符!", 99 | ResponseSubmitted = "你的回应已提交!", 100 | ReportPlayer = "举报玩家", 101 | FalseReports = "虚假举报可能会导致处罚", 102 | Killer = "凶手", 103 | ExplainSituation = "解释情况。至少需要10个字符。", 104 | Submit = "提交", 105 | SubmitEvenWithNoStaff = "即使没有在线工作人员也要提交", 106 | NotEnoughCharacters = "字符不足以提交", 107 | TooManyCharacters = "您的信息太长", 108 | LogsBeforeDeath = "你死亡前的日志", 109 | MessageFrom = "来自", 110 | AboutYourReport = "关于你的举报。", 111 | Forgive = "取消", 112 | KeepReport = "保留举报", 113 | IsAnswering = "正在回答他们的举报。", 114 | Colors = "语言和颜色", 115 | NoAdmins = "没有在线管理员!", 116 | NeedToPlay = "你需要玩游戏才能举报!", 117 | OnlyReportTwice = "你每轮只能举报两次!", 118 | InvalidAttacker = "举报错误:无效的攻击者实体!", 119 | ReportSpectator = "你不能举报旁观者!", 120 | AlreadyReported = "你已经举报过这个玩家了!", 121 | ReportCreated = "已创建新的举报!", 122 | HasReported = "%s 举报了 %s (#%s) !", 123 | HasAdminReported = "%s 以管理员身份举报了 %s (#%s) !", 124 | AdminHasReportedYou = "%s 以管理员身份举报了你!", 125 | HasReportedYou = "%s 举报了你!", 126 | HasReportedCurrentRound = "%s 在回合 %s 结束后举报了你", 127 | HasReportedPreviousMap = "%s 在上一张地图上举报了你", 128 | You = "你", 129 | GreatYou = "你", 130 | YouHaveReported = "你已举报 %s", 131 | YouHaveAdminReported = "你以管理员身份举报了 %s", 132 | HasSetReport = "%s 将举报 #%s 设定为 %s。", 133 | DealingReport = "现在正在处理举报", 134 | HandlingYourReport = "现在正在处理你的举报。", 135 | Finished = "完成", 136 | ReportIsntFinished = "这份举报还未完成!", 137 | HasSetConclusion = "已对举报做出结论", 138 | TheReportedPlayer = "被举报的玩家", 139 | HasAnsweredReport = "%s 已回答举报 #%s !", 140 | HasCanceledReport = "%s 已取消举报 #%s。", 141 | TheReport = "举报", 142 | HasCanceledByVictim = "已被受害者取消!", 143 | NoMercy = "举报中,受害者没有原谅攻击者", 144 | DidNotForgive = "%s 没有在举报 #%s 中原谅 %s !", 145 | C4Armed = "%s [%s] 启动了 %s 的C4。", 146 | C4Disarmed = "%s [%s] 拆除了 %s 的C4 %s 成功。", 147 | C4PickedUp = "%s [%s]拾起了 %s 的C4。", 148 | C4Planted = "%s [%s] 安装或丢下了一个C4。", 149 | C4Destroyed = "%s [%s] 破坏了 %s 的C4。", 150 | C4Exploded = "%s [%s] 的C4已爆炸。", 151 | with = "使用", 152 | without = "未使用", 153 | enabled = "启用", 154 | disabled = "禁用", 155 | AutoSlain = "%s [%s] 已自动被杀。", 156 | DisguiserAct = "%s [%s] %s 他们的伪装者", 157 | Teleported = "%s [%s] 已传送", 158 | DisguiserSpam = "%s [%s] 正在刷他们的伪装者。伪装日志将停止。", 159 | TrapActivated = "%s 已激活一个叛徒陷阱: %s", 160 | RoundBought = "%s 已购买一个叛徒回合", 161 | NadeThrown = "%s [%s] 投掷了 %s", 162 | UsedCredits = "%s [%s] %s %s 信用%s", 163 | received = "收到", 164 | used = "使用", 165 | BodyIdentified = "%s [%s] 已确认 %s [%s] 的尸体", 166 | VictimShotFirst = "受害者可能先开火(查看伤害信息部分以获取更多信息)!", 167 | HasDamaged = "%s [%s] 已对 %s [%s] 造成 %s", 168 | HPWeapon = " HP 使用 %s", 169 | sbody = "的尸体", 170 | DNAretrieved = "%s [%s] 已从 %s 中获取 %s [%s] 的DNA", 171 | FallDamage = "%s [%s] 跌落并丢失 %s HP", 172 | AfterPush = " 在被 %s [%s] 推后", 173 | HealthStationHeal = "%s [%s] 使用 %s 的医疗站治疗了 %s HP", 174 | HasKilled = "%s [%s] 用 %s 杀死了 %s [%s]", 175 | UnknownWeapon = "未知武器", 176 | SomethingKilled = "<某物/世界> 杀死了 %s [%s]", 177 | Radar = "一个雷达", 178 | Armor = "一套防弹衣", 179 | Disguiser = "一个伪装者", 180 | HasBought = "%s [%s] 购买了 %s", 181 | HasUsed = "%s [%s] 已使用设备 %s (%s)", 182 | CrowbarSwung = "%s - %s 挥动了他们的撬棍", 183 | HasShot = "%s - %s 使用了 %s", 184 | HasPushed = "%s - %s 用撬棍推了 %s", 185 | RadioUsed = "%s [%s] 使用了他们的无线电: %s%s", 186 | CorpseOf = "尸体属于 ", 187 | traitor = "叛徒", 188 | detective = "侦探", 189 | disconnected = "已断开连接", 190 | innocent = "无辜", 191 | CurrentRoundSelected = "当前选定的回合: ", 192 | CopyLines = "复制行", 193 | Copy = "复制", 194 | Lines = "行", 195 | FullDisplay = "全屏显示", 196 | SteamIDof = "SteamID 为 ", 197 | SteamID = "SteamID", 198 | Close = "关闭", 199 | ShowDeathScene = "展示死亡场景", 200 | ShowDamageInfos = "展示伤害信息", 201 | EmptyLogsFilters = "空的日志。请检查您的筛选条件!", 202 | EmptyLogs = "空的日志", 203 | RoundEnded = "回合结束", 204 | DamageInfosBetween = "%s 和 %s 之间的 %s 到 %s 的伤害信息。", 205 | PlayerInformation = "玩家信息", 206 | SelectDate = "选择日期:", 207 | SelectRound = "选择回合:", 208 | RoundsBetween = "%s:00 和 %s:00 之间的回合", 209 | SelectRoundToLoad = "选择需要加载的回合", 210 | PleaseSelectRound = "请选中一个回合!", 211 | LoadLogsSelected = "加载选定回合的日志", 212 | OldLogs = "旧日志", 213 | ShotLogs = "开火日志", 214 | ShotEvent = "开火事件", 215 | CopySteamID = "复制SteamID64", 216 | SpectateAttacker = "观看攻击者", 217 | SpectateVictim = "观看受害者", 218 | FreeMode = "自由模式", 219 | EnableMouse = "注意:按C键启用鼠标。", 220 | EnableSlowMotion = "启用慢动作", 221 | NoWeapon = "<无武器>", 222 | scorpse = "的尸体", 223 | TakeAction = "采取行动", 224 | RDMConclusion = "结论", 225 | ViewPreviousReports = "查看报告", 226 | YourReport = "您的报告", 227 | PlayerResponse = "玩家的回应", 228 | NoLoadedReport = "没有加载的报告", 229 | CancelReport = "取消这个报告", 230 | ReportCanceled = "报告已取消", 231 | AlreadyCanceled = "你已经取消了这个报告", 232 | ResponseStatus = "响应状态", 233 | Canceled = "已取消", 234 | ViewChat = "查看聊天", 235 | UpdateNotifications = "启用更新通知", 236 | Reported = "已报告", 237 | Admin = "管理员", 238 | ChatPlayers = "玩家", 239 | ChatAdmins = "管理员", 240 | ChatDisconnected = "<断开连接>", 241 | ChatTitle = "Damagelog的聊天系统", 242 | Actions = "行动", 243 | AddPlayer = "添加玩家", 244 | SelectPlayer = "选择玩家", 245 | AddSelected = "添加选定的玩家", 246 | ForceStay = "强制留下...", 247 | ReleaseChat = "释放...", 248 | AllPlayers = "所有玩家", 249 | InvalidPlayerChat = "无效的玩家!", 250 | CannotLeaveChat = "作为唯一的管理员,你不能离开!", 251 | AdminsDisconnectedChat = "所有管理员都断开连接了!聊天已被关闭。", 252 | ChatClosedBy = "这个聊天已被 %s 关闭。", 253 | AdminLeaveChat = "%s 已离开聊天。", 254 | AllPlayersShort = "所有玩家", 255 | ForcedNotification = "%s 已强制 %s 留在聊天中", 256 | ReleasedNotification = "%s 已将 %s 从聊天中释放", 257 | ReportHistory = "报告 %u 历史", 258 | ActiveChats = "%u 个活跃的聊天", 259 | And = "和", 260 | VictimReportedDisconnected = "受害者或被报告的玩家已断开连接!", 261 | ChatAlready = "对这个报告已经有一个聊天了!", 262 | OpenChatNotification = "%s 已为报告 #%u 打开一个聊天。", 263 | AddedChatAdmin = "你已被管理员加入到一个聊天中!", 264 | ChatClosed = "%s 已关闭报告 #%u 的聊天。", 265 | RDMManagerAuto = "(自动)", 266 | ReportHadDNA = "这一轮 %s 有你的DNA!", 267 | ReportNoDNA = "这一轮 %s 没有你的DNA。", 268 | ReportNoDNAInfo = "无法找到 %s 的 DNA 信息", 269 | AnAdministrator = "一个管理员", 270 | AdminReportID = "管理员报告 #%u", 271 | ChatOpenNoMessage = "(这个管理员报告已直接打开一个聊天)", 272 | PendingTop = "待处理", 273 | ReportsBottom = "报告", 274 | ShowPendingReports = "在 HUD 上显示待处理报告的数量", 275 | CreateReport = "创建报告", 276 | Donate = "捐赠", 277 | EnableSound = "启用通知声音", 278 | CurrentRound = "当前回合", 279 | StandardReport = "标准报告(作为玩家)", 280 | StandardAdminReport = "标准管理员报告", 281 | AdvancedAdminReportForce = "高级管理员报告:直接强制被报告的玩家回应", 282 | AdvancedAdminReportChat = "高级管理员报告:直接与被报告的玩家打开一个聊天", 283 | SlayNextRound = "下一轮添加背锅", 284 | JailNextRound = "下一轮添加监禁", 285 | SlayReportedPlayerNow = "立即杀掉被报告的玩家", 286 | SendMessage = "发送消息给", 287 | PrivateMessage = "私人消息", 288 | WhatToSay = "你想对 %s 说什么?", 289 | RemoveAutoSlays = "移除背锅给", 290 | RemoveAutoJails = "移除监禁给", 291 | RemoveOneAutoSlay = "从中移除 1 个背锅", 292 | TheVictim = "受害者", 293 | ChatActive = "聊天活跃", 294 | GoingToSlay = "你将要添加背锅给", 295 | ThisOften = "次数:", 296 | Reason = "原因: ", 297 | GoingToBan = "您将封禁", 298 | Permanently = "永久地", 299 | ForMinutes = "为期 %s 分钟", 300 | ForHours = "为期 %s 小时", 301 | ForDays = "为期 %s 天", 302 | Minutes = "分钟", 303 | Hours = "小时", 304 | Days = "天", 305 | forspace = "为了 ", 306 | AutoReasonJail = "(自动) %s 自动监禁了 %s 次,原因为 %s", 307 | AutoReasonSlay = "(自动) %s 自动背锅了 %s 次,原因为 %s", 308 | AutoReasonBan = "(自动) %s 封禁了 %s,原因为 %s", 309 | Autoslaying = "添加背锅给 %s", 310 | Autojailing = "添加监禁给 %s", 311 | DeathShotsInfo = "死亡场景照片信息", 312 | ShowAllPlayers = "显示所有玩家", 313 | PlayerDrowned = "%s [%s] 溺水了。", 314 | YouDecidedForgive = "你决定原谅 %s。", 315 | YouDecidedNotForgive = "你决定不原谅 %s。", 316 | DecidedToForgiveYou = "%s 决定原谅你。", 317 | DecidedNotToForgiveYou = "%s 不想原谅你。", 318 | -- Months 319 | January = "一月", 320 | February = "二月", 321 | March = "三月", 322 | April = "四月", 323 | May = "五月", 324 | June = "六月", 325 | July = "七月", 326 | August = "八月", 327 | September = "九月", 328 | October = "十月", 329 | November = "十一月", 330 | December = "十二月", 331 | -- Settings Tab 332 | Save = "保存", 333 | SetDefault = "重置为默认", 334 | AddWeapon = "向武器列表中添加武器或实体", 335 | EditNames = "编辑武器/实体名称 (仅超级管理员!)", 336 | WeaponID = "武器ID", 337 | WeaponNameExample = "武器名称/ID (例:weapon_ttt_deagle)", 338 | WeaponDisplayName = "武器显示名称", 339 | WeaponDisplayExample = "武器显示名称 (例:a Deagle)", 340 | RemoveSelectedWeapons = "移除选定的武器/实体", 341 | Cancel = "取消", 342 | DisconnectedPlayer = "<断开连接的玩家>", 343 | PlayerDisconnected = "<玩家断开连接>", 344 | healerNick = "一个断开连接的玩家", 345 | -- Client Settings Tab 346 | Generalsettings = "一般设置", 347 | RDMuponRDM = "在RDM时启用RDM管理器弹窗", 348 | CurrentRoundLogs = "如果你仍活着并且允许打开日志,则默认打开当前回合", 349 | OutsideNotification = "在游戏外启用通知声音", 350 | ShotLogsDamageTab = "伤害选项卡旁的攻击日志", 351 | DamagelogMenuSettings = "伤害日志菜单设置", 352 | ForcedLanguage = "服务器所有者强制使用此语言", 353 | -- Filters 354 | filter_show_bodies = "显示身体识别", 355 | filter_show_aslays = "显示自动背锅", 356 | filter_show_credits = "显示信用更改", 357 | filter_show_damages = "显示伤害", 358 | filter_show_dna = "显示DNA", 359 | filter_show_falldamage = "显示跌落伤害", 360 | filter_show_healthstation = "显示健康站使用情况", 361 | filter_show_kills = "显示击杀", 362 | filter_show_disguises = "显示伪装", 363 | filter_show_teleports = "显示传送", 364 | filter_show_traps = "显示陷阱", 365 | filter_show_psroles = "显示角色购买", 366 | filter_show_nades = "显示手榴弹投掷", 367 | filter_show_suicides = "显示自杀", 368 | filter_show_radiocommands = "显示标准无线电命令", 369 | filter_show_radiokos = "显示KOS无线电命令", 370 | filter_show_purchases = "显示武器购买", 371 | filter_show_equipment_usage = "显示装备使用情况(如果支持)", 372 | filter_show_c4 = "显示C4", 373 | filter_show_drownings = "显示溺水", 374 | -- Colors 375 | color_found_bodies = "找到的身体", 376 | colors_aslays = "自动背锅", 377 | color_credits = "信用", 378 | color_damages = "伤害", 379 | color_team_damages = "团队伤害", 380 | color_dna = "DNA", 381 | color_fall_damages = "跌落伤害", 382 | color_heal = "治疗", 383 | color_kills = "击杀", 384 | color_misc = "杂项", 385 | color_nades = "手榴弹", 386 | color_suicides = "自杀", 387 | color_defaultradio = "无线电指令", 388 | color_kosradio = "无线电KOS指令", 389 | color_purchases = "设备购买", 390 | color_c4 = "C4", 391 | color_team_kills = "团队击杀", 392 | color_drownings = "溺水", 393 | -- Discord webhook 394 | webhook_AdminsOnline = "有在线的管理员。", 395 | webhook_NoAdminsOnline = "没有在线的管理员。", 396 | webhook_ServerInfo = "当前地图: %s, 回合: %s", 397 | webhook_report_forgiven_or_kept = "原谅还是保留", 398 | webhook_report_forgiven = "原谅", 399 | webhook_report_kept = "保留", 400 | webhook_report_finished_by = "报告完成由", 401 | webhook_report_finished_time_taken = "耗时", 402 | webhook_report_finished_conclusion = "结论", 403 | webhook_header_report_submitted = "已提交报告(ID %s)", 404 | webhook_header_report_forgiven = "报告原谅 (ID %s)", 405 | webhook_header_report_kept = "报告保留 (ID %s)", 406 | webhook_header_report_finished = "报告完成 (ID %s)", 407 | -- 03-26-2023 408 | rdmmanager_action_ban = "封禁", 409 | rdmmanager_action_ban_title = "封禁", 410 | rdmmanager_action_ban_submit = "确定", 411 | dmglogs_btn_highlight = "突出高亮" 412 | } 413 | -------------------------------------------------------------------------------- /lua/damagelogs/shared/lang/french.lua: -------------------------------------------------------------------------------- 1 | DamagelogLang.french = { 2 | DefaultReason = "Aucune raison spécifiée", 3 | -- Damage types 4 | DMG_BLAST = "une explosion", 5 | DMG_BURN = "du feu", 6 | DMG_CRUSH = "dégâts de chute ou de collisions", 7 | DMG_SLASH = "un objet pointu", 8 | DMG_CLUB = "mattraqué à mort", 9 | DMG_SHOCK = "un choc électrique", 10 | DMG_ENERGYBEAM = "un laser", 11 | DMG_SONIC = "une collision de téléporteurs", 12 | DMG_PHYSGUN = "un objet massif", 13 | Damagelog = "Damagelog", 14 | SLogs = "Logs de tirs", 15 | RDMManag = "RDM Manager", 16 | UpdateNotify = "Cette version n'est pas à jour ! Téléchargez la MAJ ici : https://github.com/BadgerCode/tttdamagelogs", 17 | About = "A propos", 18 | Settings = "Options", 19 | KilledBy = "Vous avez été tué par %s", 20 | OpenMenu = "Report", 21 | WasntRDM = "Ce n'était pas du RDM.", 22 | OpenReportMenu = "Vous êtes mort ! Ouvrez le menu de report avec la commande", 23 | Command = ".", 24 | PopupNote = "(note: cette fenêtre peut être désactivée sur F1)", 25 | AbuseNote = "est vivant est a chargé les logs du round actuel.", 26 | Loading = "Chargement...", 27 | NothingFound = "Impossible de trouver quoi que ce soit", 28 | RoundFilter = "Sélection de round / Filtres", 29 | None = "aucun", 30 | EditFilter = "Filtres", 31 | CurrentFilter = "Joueurs surlignés:", 32 | NoPlayers = "Aucun joueur.", 33 | plyinfo = "Info de joueur", 34 | MorePlayers = "Vous ne pouvez pas surligner plus de 3 joueurs à la fois !", 35 | Error = "Erreur", 36 | DmgInfo = "Informations de dégâts", 37 | Player = "Joueur", 38 | Role = "Rôle", 39 | Alive = "Vivant?", 40 | ShowInnocent = "Afficher les innocents", 41 | Time = "Temps", 42 | Type = "Type", 43 | Event = "Évènement", 44 | LastRound = "Dernier round de la map précédente", 45 | Round = "Round", 46 | NoLogsAvailable = "Aucun log disponible pour la map actuelle", 47 | Nothinghere = "Rien ici...", 48 | Refresh = "Rafraichir", 49 | ResetDefault = "Mettre la valeur par défaut", 50 | Yoursure = "En êtes-vous sûr ?", 51 | Yes = "Oui", 52 | No = "Non", 53 | RDMWaiting = "En attente", 54 | RDMInProgress = "En cours", 55 | RDMFinished = "Terminé", 56 | RDMCanceled = "Annulé par la victime", 57 | RDMNotFinished = "Ce report n'est pas terminé !", 58 | RDMSetConclusion = "Changer de conclusion", 59 | Conclusion = "Conclusion", 60 | RDMWriteConclusion = "Merci d'écrire la conclusion que vous souhaitez pour ce report", 61 | RDMForceRespond = "Forcer le joueur report à répondre", 62 | RDMNotValid = "Le joueur report n'est pas valide ! (Bot ou déconnecté)", 63 | Chat = "chat", 64 | ReopenChat = "Réouvrir le chat", 65 | OpenChat = "Ouvrir un chat", 66 | JoinChat = "Rejoindre le chat", 67 | DeathSceneNotFound = "Impossible de trouver la Death Scene", 68 | Victim = "Victime", 69 | ReportedPlayer = "Joueur report", 70 | Status = "Statut", 71 | By = "par", 72 | InChat = "(en chat)", 73 | NoConclusion = "Pas de conclusion pour le report sélectionné", 74 | Reports = "Reports", 75 | PreviousMapReports = "Map précédente", 76 | ShowFinishedReports = "Affiché les reports terminés", 77 | Set = "Set", 78 | SStatus = "Set Status", 79 | VictimsReport = "Message de la victime", 80 | AdminsMessage = "Message de l'admin", 81 | ReportedPlayerResponse = "Réponse", 82 | ChatOpened = "Chat ouvert, réponse désactivée", 83 | ChatOpenedShort = "Chat ouvert", 84 | NoSelectedReport = "Aucun report sélectionné", 85 | LogsBeforeVictim = "Logs avant la mort de la victime", 86 | NoResponseYet = "Aucune réponse", 87 | BeenReported = "Vous avez été report ! Merci de répondre à vos reports.", 88 | ReportedYou = "vous a report", 89 | AfterRound = "après le round", 90 | PreviousMap = "lors de la map précédente", 91 | Send = "Envoyer", 92 | MinCharacters = "Au moins 10 caractères sont requis !", 93 | ResponseSubmitted = "Votre réponse a été envoyée !", 94 | ReportPlayer = "Report un joueur", 95 | FalseReports = "Les faux reports peuvent conduire à une sanction", 96 | Killer = "tueur", 97 | ExplainSituation = "Expliquer la situation. 10 caractères sont requis.", 98 | Submit = "Envoyer", 99 | SubmitEvenWithNoStaff = "Envoyer, bien qu'aucun membre du personnel ne soit en ligne", 100 | NotEnoughCharacters = "Pas assez de caractères", 101 | LogsBeforeDeath = "Logs avant votre mort", 102 | MessageFrom = "message de", 103 | AboutYourReport = "à propos de votre report.", 104 | Forgive = "Annuler", 105 | KeepReport = "Maintenir le report", 106 | IsAnswering = "répond à ses reports.", 107 | Colors = "Langage et couleurs", 108 | NoAdmins = "Pas d'admins en ligne !", 109 | NeedToPlay = "Vous devez jouer avant de pouvoir report !", 110 | OnlyReportTwice = "Vous ne pouvez report que deux fois par round !", 111 | InvalidAttacker = "Erreur : joueur invalide ou déconnecté", 112 | ReportSpectator = "Vous ne pouvez pas report de spectateurs !", 113 | AlreadyReported = "Vous avez déjà report ce joueur !", 114 | ReportCreated = "Un nouveau report a été créé !", 115 | HasReported = "%s a report %s (#%s) !", 116 | HasAdminReported = "%s a admin-report %s (#%s) !", 117 | AdminHasReportedYou = "%s vous a admin-report !", 118 | HasReportedYou = "%s vous a report !", 119 | HasReportedCurrentRound = "%s vous a report après le round %s", 120 | HasReportedPreviousMap = "%s vous a report à la map précédente", 121 | You = "vous", 122 | GreatYou = "Vous", 123 | YouHaveReported = "Vous avez report %s", 124 | YouHaveAdminReported = "Vous avez admin-report %s", 125 | HasSetReport = "a mis le report", 126 | DealingReport = "gère maintenant le report", 127 | HandlingYourReport = "gère maintenant votre report.", 128 | Finished = "Terminé", 129 | ReportIsntFinished = "Ce report n'est pas terminé !", 130 | HasSetConclusion = "a mis une conclusion au report", 131 | TheReportedPlayer = "Le joueur report", 132 | HasAnsweredReport = "%s a répondu au report #%s !", 133 | HasCanceledReport = "%s a annulé le report #%s.", 134 | TheReport = "Le report", 135 | HasCanceledByVictim = "a été annulé par la victime", 136 | NoMercy = "La victime n'a pas annulé le report", 137 | DidNotForgive = "%s n'a pas pardonné %s au reporté #%s !", 138 | C4Armed = "%s [%s] a armé le C4 de %s.", 139 | C4Disarmed = "%s [%s] a désarmé le C4 de %s %s success.", 140 | C4PickedUp = "%s [%s]a ramassé le C4 de %s.", 141 | C4Planted = "%s [%s] a planté ou laché C4.", 142 | C4Destroyed = "%s [%s] a détruit le C4 de %s.", 143 | with = "avec", 144 | without = "sans", 145 | enabled = "activé", 146 | disabled = "désactivé", 147 | AutoSlain = "%s [%s] a été autoslay.", 148 | DisguiserAct = "%s [%s] %s son déguisement", 149 | Teleported = "%s [%s] s'est téléporté", 150 | DisguiserSpam = "%s [%s] spam avec son déguisement. Les logs de déguisement lui seront désactivés.", 151 | TrapActivated = "%s a activé un piège: %s", 152 | RoundBought = "%s a acheté un round Traitre", 153 | NadeThrown = "%s [%s] a lancé %s", 154 | UsedCredits = "%s [%s] %s %s credit%s", 155 | received = "a reçu", 156 | used = "a utilisé", 157 | BodyIdentified = "%s [%s] a identifié le corps de %s [%s]", 158 | VictimShotFirst = "La victime a peut-être tiré en premier (voir les informations de dégâts) !", 159 | HasDamaged = "%s [%s] a endommagé %s [%s] de %s", 160 | HPWeapon = " HP avec %s", 161 | sbody = " (corps)", 162 | DNAretrieved = "%s [%s] a recueilli l'ADN de %s [%s]. Source : %s", 163 | FallDamage = "%s [%s] est tombé et a perdu %s HP", 164 | AfterPush = " après avoir été poussé par %s [%s]", 165 | HealthStationHeal = "%s [%s] s'est soigné de %s avec la station de soins de %s", 166 | HasKilled = "%s [%s] a tué %s [%s] avec %s", 167 | UnknownWeapon = "une arme inconnue", 168 | SomethingKilled = " a tué %s [%s]", 169 | HasBought = "%s [%s] a acheté %s", 170 | CrowbarSwung = "%s - %s has swung their crowbar", 171 | HasShot = "%s - %s a tiré avec %s", 172 | HasPushed = "%s - %s a poussé %s avec son pieds de biche", 173 | RadioUsed = "%s [%s] a utilisé sa radio: %s%s", 174 | CorpseOf = "corps de ", 175 | traitor = "traître", 176 | detective = "détective", 177 | disconnected = "déconnecté", 178 | innocent = "innocent", 179 | CurrentRoundSelected = "Round sélectionné: ", 180 | CopyLines = "Copier ligne(s)", 181 | Copy = "Copier", 182 | Lines = "Ligne(s)", 183 | FullDisplay = "Affichage complet", 184 | SteamIDof = "SteamID de ", 185 | SteamID = "SteamID", 186 | Close = "Fermer", 187 | ShowDeathScene = "Afficher Death Scene", 188 | ShowDamageInfos = "Informations de dégâts", 189 | EmptyLogsFilters = "Logs vides. Vérifiez vos filtres !", 190 | EmptyLogs = "Logs vides", 191 | RoundEnded = "Round terminé", 192 | DamageInfosBetween = "Informations de dégâts de %s à %s entre %s et %s.", 193 | PlayerInformation = "Informations de dégâts", 194 | SelectDate = "Choisir une date:", 195 | SelectRound = "Choisir un round:", 196 | RoundsBetween = "Rounds entre %s:00 et %s:00", 197 | SelectRoundToLoad = "Sélectionner un round à charger", 198 | PleaseSelectRound = "Merci de sélectionner un round !", 199 | LoadLogsSelected = "Charger les logs du round sélectionner", 200 | OldLogs = "Anciens logs", 201 | ShotLogs = "Logs de tirs", 202 | ShotEvent = "Tirs", 203 | CopySteamID = "Copier le SteamID", 204 | SpectateAttacker = "Observer le tueur", 205 | SpectateVictim = "Observer la victime", 206 | FreeMode = "Mode libre", 207 | EnableMouse = "Note: Cliquez sur C pour activer la souris.", 208 | EnableSlowMotion = "Activer le slowmo", 209 | NoWeapon = "", 210 | scorpse = " (corps)", 211 | TakeAction = "Agir", 212 | RDMConclusion = "Conclusion", 213 | ViewPreviousReports = "Voir ses reports", 214 | YourReport = "Votre reports", 215 | PlayerResponse = "Réponse", 216 | NoLoadedReport = "Aucun report chargé", 217 | CancelReport = "Annuler ce report", 218 | ReportCanceled = "Report annulé", 219 | AlreadyCanceled = "Vous avez annulé ce report", 220 | ResponseStatus = "Réponse", 221 | Canceled = "Annulé", 222 | ViewChat = "Voir le chat", 223 | UpdateNotifications = "Activer les notificaitons de mises à jour", 224 | Reported = "Joueur report", 225 | Admin = "Administrateur", 226 | ChatDisconnected = "", 227 | ChatTitle = "Système de chat", 228 | Actions = "Actions", 229 | AddPlayer = "Ajouter un joueur", 230 | SelectPlayer = "Sélectionner un joueur", 231 | AddSelected = "Ajouter la sélection", 232 | ForceStay = "Forcer à rester...", 233 | ReleaseChat = "Libérer...", 234 | AllPlayers = "Tous les joueurs", 235 | InvalidPlayerChat = "Joueur invalide !", 236 | CannotLeaveChat = "Vous ne pouvez pas quitter en tant que seul admin !", 237 | AdminsDisconnectedChat = "Tous les admins ont été déconnectés ! Ce chat est fermé.", 238 | ChatClosedBy = "Ce chat a été fermé par %s.", 239 | AdminLeaveChat = "%s a quité le chat.", 240 | AllPlayersShort = "tous les joueurs", 241 | ForcedNotification = "%s a forcé %s à rester sur le chat", 242 | ReleasedNotification = "%s a libéré %s du chat", 243 | ReportHistory = "Historique du report %u", 244 | ActiveChats = "%u chats actifs", 245 | And = "et", 246 | VictimReportedDisconnected = "La victime ou le joueur report sont déconnectés !", 247 | ChatAlready = "Il y a déjà un chat pour ce report", 248 | OpenChatNotification = "%s a ouvert un chat pour le report #%u.", 249 | AddedChatAdmin = "Vous avez été ajouté à ce chat par un admin !", 250 | ChatClosed = "%s a fermé le chat pour le report #%u.", 251 | RDMManagerAuto = "(Auto)", 252 | ReportHadDNA = "%s avait votre ADN ce round !", 253 | ReportNoDNA = "%s n'avait pas votre ADN pendant ce round", 254 | ReportNoDNAInfo = "Impossible de trouver les informations de d'ADN de %s", 255 | AnAdministrator = "Un administrateur", 256 | AdminReportID = "Admin report #%u", 257 | ChatOpenNoMessage = "(Cet admin-report a été directement ouvert par un chat)", 258 | PendingTop = "Reports", 259 | ReportsBottom = "en attente", 260 | ShowPendingReports = "Afficher le nombre de reports en attente sur l'HUD", 261 | CreateReport = "Nouveau", 262 | Donate = "Donations", 263 | EnableSound = "Activer les sons de notifications", 264 | CurrentRound = "Round actuel", 265 | -- Months 266 | January = "Janvier", 267 | February = "Février", 268 | March = "Mars", 269 | April = "Avril", 270 | May = "Mai", 271 | June = "Juin", 272 | July = "Juillet", 273 | August = "Août", 274 | September = "Septembre", 275 | October = "Octobre", 276 | November = "Novembre", 277 | December = "Decembre", 278 | -- Settings Tab 279 | Save = "Sauvegarder", 280 | SetDefault = "Remettre la valeur par défaut", 281 | AddWeapon = "Add weapon or entity to weapon list", 282 | EditNames = "Edit weapon/entity names (Superadmins only!)", 283 | WeaponID = "Weapon ID", 284 | WeaponNameExample = "Weapon name/ID (example: weapon_ttt_deagle)", 285 | WeaponDisplayName = "Weapon display name", 286 | WeaponDisplayExample = "Weapon display name (example: a Deagle)", 287 | RemoveSelectedWeapons = "Remove the selected weapons/entities", 288 | Cancel = "Annuler", 289 | DisconnectedPlayer = "", 290 | PlayerDisconnected = "", 291 | healerNick = "un joueur déconnecté", 292 | -- Client Settings Tab 293 | Generalsettings = "Options générales", 294 | RDMuponRDM = "Activer les popups en cas de RDM", 295 | CurrentRoundLogs = "Afficher le round en cours si vous utilisez les logs en étant vivant", 296 | OutsideNotification = "Activer les sons de notifications en dehors du jeu", 297 | DamagelogMenuSettings = "Options des damagelogs", 298 | -- Filters 299 | filter_show_bodies = "Afficher les identifications de corps", 300 | filter_show_aslays = "Afficher les autoslay", 301 | filter_show_credits = "Afficher les changement de crédits", 302 | filter_show_damages = "Afficher les dégâts", 303 | filter_show_dna = "Afficher l'ADN", 304 | filter_show_falldamage = "Afficher les dégâts de chute", 305 | filter_show_healthstation = "Afficher les soins", 306 | filter_show_kills = "Afficher les kills", 307 | filter_show_disguises = "Afficher les déguisements", 308 | filter_show_teleports = "Afficher les téléportations", 309 | filter_show_traps = "Afficher les pièges", 310 | filter_show_psroles = "Afficher les chats de rôles", 311 | filter_show_nades = "Afficher les lancers de grenades", 312 | filter_show_suicides = "Afficher les suicides", 313 | filter_show_radiocommands = "Afficher les commandes radio standards", 314 | filter_show_radiokos = "Afficher les dénonciations radio", 315 | filter_show_purchases = "Afficher les achats", 316 | filter_show_c4 = "Afficher les C4", 317 | -- Colors 318 | color_found_bodies = "Corps retrouvés", 319 | colors_aslays = "Auto Slays", 320 | color_credits = "Crédits", 321 | color_damages = "Dégâts", 322 | color_team_damages = "Dégâts d'équipe", 323 | color_dna = "ADN", 324 | color_fall_damages = "Dégâts de chute", 325 | color_heal = "Soins", 326 | color_kills = "Kills", 327 | color_misc = "Divers", 328 | color_nades = "Grenades", 329 | color_suicides = "Suicides", 330 | color_defaultradio = "Commandes radios", 331 | color_kosradio = "Dénonciations radio", 332 | color_purchases = "Achats d'équipements", 333 | color_c4 = "C4", 334 | color_team_kills = "Kills d'équipes" 335 | } -------------------------------------------------------------------------------- /lua/damagelogs/shared/lang/polish.lua: -------------------------------------------------------------------------------- 1 | DamagelogLang.polish = { 2 | DefaultReason = "Brak określonego powodu", 3 | -- Damage types 4 | DMG_BLAST = "eksplozja", 5 | DMG_BURN = "ogień", 6 | DMG_CRUSH = "upadek, bądź obrażenia od przedmiotu", 7 | DMG_SLASH = "ostry przedmiot", 8 | DMG_CLUB = "spałowany na śmierć", 9 | DMG_SHOCK = "elektryczny wstrząs", 10 | DMG_ENERGYBEAM = "laser", 11 | DMG_SONIC = "kolizja teleportacyjna", 12 | DMG_PHYSGUN = "masywny ładunek", 13 | Damagelog = "Log obrażeń", 14 | SLogs = "Log strzałów", 15 | RDMManag = "Menadżer RDMów", 16 | UpdateNotify = "Ta wersja jest przestarzała! Możesz pobrać najnowszą z https://github.com/BadgerCode/tttdamagelogs", 17 | About = "Autorzy", 18 | Settings = "Ustawienia", 19 | KilledBy = "Zostałeś zabity przez %s", 20 | OpenMenu = "Otwórz menu zgłoszeń", 21 | WasntRDM = "To nie był RDM.", 22 | OpenReportMenu = "Zginąłeś! Otwórz menu zgłoszeń używając komendy", 23 | Command = "", 24 | PopupNote = "(uwaga: możesz wyłączyć to okno pod klawiszem F1)", 25 | AbuseNote = "jest żywy i załadował logi aktualnej rundy.", 26 | Loading = "Ładowanie...", 27 | NothingFound = "Nie znaleziono niczego", 28 | RoundFilter = "Wybór i filtry rund", 29 | None = "żadni", 30 | EditFilter = "Edytuj filtry", 31 | CurrentFilter = "Aktualnie podświetlani gracze:", 32 | NoPlayers = "Brak graczy.", 33 | plyinfo = "Informacje o graczu", 34 | MorePlayers = "Nie możesz podświetlić więcej niż 3 graczy na raz!", 35 | Error = "Błąd", 36 | DmgInfo = "Informacje o obrażeniach", 37 | Player = "Gracz", 38 | Role = "Rola", 39 | Alive = "Żywy?", 40 | ShowInnocent = "Pokaż niewinnych graczy", 41 | Time = "Czas", 42 | Type = "Rodzaj", 43 | Event = "Zdarzenie", 44 | LastRound = "Ostatnia runda poprzedniej mapy", 45 | Current = "Aktualna", 46 | Round = "Runda", 47 | NoLogsAvailable = "Brak dostępnych logów dla aktualnej mapy", 48 | Nothinghere = "Nic tu nie ma...", 49 | Refresh = "Odśwież", 50 | ResetDefault = "Powrót do ustawień domyślnych", 51 | Yoursure = "Jesteś pewien?", 52 | Yes = "Tak", 53 | No = "Nie", 54 | WeaponEntityID = "ID broni/entity", 55 | DisplayName = "Wyświetlana nazwa", 56 | RDMWaiting = "Oczekiwanie", 57 | RDMWaitingAttacker = "Oczekiwanie na ODP", 58 | RDMResponded = "ODP udzielona", 59 | RDMInProgress = "W trakcie", 60 | RDMFinished = "Zakończone", 61 | RDMCanceled = "Anulowane", 62 | RDMNotFinished = "To zgłoszenie nie jest zakończone!", 63 | RDMSetConclusion = "Napisz wynik", 64 | Conclusion = "Wynik", 65 | RDMWriteConclusion = "Proszę napisz wynik tego zgłoszenia", 66 | RDMForceRespond = "Wymuś odpowiedź zgłoszonego gracza", 67 | RDMNotValid = "Zreportowany gracz nie jest prawidłowy! (Bot lub rozłączył się)", 68 | Chat = "czat", 69 | ReopenChat = "Otwórz czat ponownie", 70 | OpenChat = "Otwórz czat", 71 | JoinChat = "Dołącz do czatu", 72 | DeathSceneNotFound = "Nie znaleziono sceny śmierci", 73 | Victim = "Ofiara", 74 | ReportedPlayer = "Zgłoszony gracz", 75 | Status = "Status", -- because in French it's "Statut" 76 | By = "przez", 77 | InChat = "(na czacie)", 78 | NoConclusion = "Brak wyniku dla wybranego zgłoszenia", 79 | Reports = "Zgłoszenia", 80 | PreviousMapReports = "Poprzednia mapa", 81 | ShowFinishedReports = "Pokaż zakończone", 82 | Set = "Ustaw", 83 | SStatus = "Ustaw status", 84 | VictimsReport = "Zgłoszenie ofiary", 85 | AdminsMessage = "Wiadomość administratora", 86 | ReportedPlayerResponse = "Odpowiedź zgłoszonego gracza", 87 | ChatOpened = "Czat otwarty, odpowiedź gracza wyłączona", 88 | ChatOpenedShort = "Czat otwarty", 89 | NoSelectedReport = "Brak wybranego zgłoszenia", 90 | LogsBeforeVictim = "Logi sprzed śmierci ofiary", 91 | NoResponseYet = "[[Odpowiedź jeszcze niewysłana]]", 92 | NoWeaponOrOwner = "Brak broni, bądź właściciela", 93 | BeenReported = "Zostałeś zgłoszony! Proszę, odpowiedz na wszystkie swoje zgłoszenia.", 94 | ReportedYou = "zgłosił Ciebie", 95 | AfterRound = "po rundzie", 96 | PreviousMap = "na poprzedniej mapie", 97 | Send = "Wyślij", 98 | MinCharacters = "Wymagane jest minimum 10 znaków!", 99 | ResponseSubmitted = "Twoja odpowiedź została wysłana!", 100 | ReportPlayer = "Zgłaszanie gracza", 101 | FalseReports = "Fałszywe zgłoszenia mogą być powodem do kary", 102 | Killer = "zabójca", 103 | ExplainSituation = "Wyjaśnij sytuację. Wymagane jest minimum 10 znaków.", 104 | Submit = "Wyślij", 105 | SubmitEvenWithNoStaff = "Wyślij, nawet jeśli nie ma administracji", 106 | NotEnoughCharacters = "Niewystarczająca ilość znaków, aby wysłać zgłoszenie", 107 | LogsBeforeDeath = "Logi sprzed Twojej śmierci", 108 | MessageFrom = "wiadomość od", 109 | AboutYourReport = "o Twoim zgłoszeniu.", 110 | Forgive = "Wybacz", 111 | KeepReport = "Podtrzymaj zgłoszenie", 112 | IsAnswering = "odpowiada na swoje zgłoszenia.", 113 | Colors = "Język i kolory", 114 | NoAdmins = "Brak adminów na serwerze!", 115 | NeedToPlay = "Musisz grać, aby móc zgłaszać!", 116 | OnlyReportTwice = "Możesz wysyłać tylko dwa zgłoszenia na rundę!", 117 | InvalidAttacker = "Błąd podczas zgłaszania: Nieprawidłowe entity atakującego!", 118 | ReportSpectator = "Nie możesz zgłaszać widzów!", 119 | AlreadyReported = "Już zgłosiłeś tego gracza!", 120 | ReportCreated = "Nowe zgłoszenie zostało wysłane!", 121 | HasReported = "%s zgłosił %s (#%s)!", 122 | HasAdminReported = "%s zgłosił(a) jako administrator %s (#%s)!", 123 | AdminHasReportedYou = "%s zgłosił(a) Ciebie jako administrator!", 124 | HasReportedYou = "%s zgłosił(a) Ciebie!", 125 | HasReportedCurrentRound = "%s zgłosił(a) Ciebie po rundzie %s", 126 | HasReportedPreviousMap = "%s zgłosił(a) Ciebie na poprzedniej mapie", 127 | You = "Tobie", 128 | GreatYou = "Ty", 129 | YouHaveReported = "Zgłosiłeś %s", 130 | YouHaveAdminReported = "Zgłosiłeś jako administrator gracza %s", 131 | HasSetReport = "ustawił zgłoszenie jako", 132 | DealingReport = "zajmuje się teraz zgłoszeniem", 133 | HandlingYourReport = "zajmuje się Twoim zgłoszeniem.", 134 | Finished = "Zamknięte", 135 | ReportIsntFinished = "To zgłoszenie nie jest zakończone!", 136 | HasSetConclusion = "ustawił wynik zgłoszenia", 137 | TheReportedPlayer = "Zgłoszony gracz", 138 | HasAnsweredReport = "odpowiedział na zgłoszenie", 139 | HasCanceledReport = "anulował zgłoszenie.", 140 | TheReport = "Zgłozsenie", 141 | HasCanceledByVictim = "zostało anulowane przez ofiarę!", 142 | NoMercy = "Ofiara nie wybaczyła atakującemu w zgłoszeniu", 143 | DidNotForgive = "%s nie wybaczył(a) %s w zgłoszeniu #%s!", 144 | C4Armed = "%s [%s] podłożył(a) C4 gracza %s.", 145 | C4Disarmed = "%s [%s] rozbroił(a) C4 gracza %s %s.", 146 | C4PickedUp = "%s [%s] podniósł(a) C4 gracza %s.", 147 | C4Planted = "%s [%s] podłożył(a), bądź upuścił C4.", 148 | C4Destroyed = "%s [%s] zniszczył(a) C4 gracza %s.", 149 | with = "pomyślnie", 150 | without = "niepomyślnie", 151 | enabled = "aktywował(a)", 152 | disabled = "dezaktywował(a)", 153 | AutoSlain = "%s [%s] został(a) automatycznie zgładzony.", 154 | DisguiserAct = "%s [%s] %s swoje przebranie", 155 | Teleported = "%s [%s] przeteleportował(a) się", 156 | DisguiserSpam = "%s [%s] spamuje swoim przebraniem. Logowanie przebrania zostanie zatrzymane.", 157 | TrapActivated = "%s aktywował(a) pułapkę zdrajcy: %s", 158 | RoundBought = "%s kupił(a) rundę zdrajcą", 159 | NadeThrown = "%s [%s] rzucił(a) %s", 160 | UsedCredits = "%s [%s] %s %s kredyt%s", 161 | received = "dostał(a)", 162 | used = "użył(a)", 163 | BodyIdentified = "%s [%s] zidentyfikował(a) zwłoki %s [%s]", 164 | VictimShotFirst = "Ofiara mogła strzelić pierwsza (sprawdź sekcję Informacje o obrażeniach)!", 165 | HasDamaged = "%s [%s] zabrał(a) %s [%s] %s", 166 | HPWeapon = " HP przy użyciu %s", 167 | sbody = "- zwłoki", 168 | DNAretrieved = "%s [%s] pobrał(a) DNA gracza %s [%s] z %s", 169 | FallDamage = "%s [%s] spadł(a) i stracił(a) %s HP", 170 | AfterPush = " po zostaniu popchniętym przez %s [%s]", 171 | HealthStationHeal = "%s [%s] uleczył(a) się za %s HP stacją zdrowia gracza %s", 172 | HasKilled = "%s [%s] zabił(a) %s [%s] przy użyciu %s", 173 | UnknownWeapon = "nieznanej broni", 174 | SomethingKilled = " zabił %s [%s]", 175 | Radar = "radar", 176 | Armor = "kamizelka kuloodporna", 177 | Disguiser = "przebranie", 178 | HasBought = "%s [%s] kupił(a) %s", 179 | CrowbarSwung = "%s - %s zamachnął się swoim łomem", 180 | HasShot = "%s - %s strzelił(a) z %s", 181 | HasPushed = "%s - %s popchnął %s łomem", 182 | RadioUsed = "%s [%s] użył(a) swojego radia: %s%s", 183 | CorpseOf = "ciało ", 184 | traitor = "zdrajca", 185 | detective = "detekyw", 186 | disconnected = "rozłączony gracz", 187 | innocent = "niewinny", 188 | CurrentRoundSelected = "Aktualnie wybrana runda: ", 189 | CopyLines = "Kopiuj linie", 190 | Copy = "Kopiuj", 191 | Lines = "Linie", 192 | FullDisplay = "Pełny podgląd", 193 | SteamIDof = "SteamID gracza ", 194 | SteamID = "SteamID", 195 | Close = "Zamknij", 196 | ShowDeathScene = "Pokaż scenę śmierci", 197 | ShowDamageInfos = "Pokaż informacje o obrażeniach", 198 | EmptyLogsFilters = "Pusty log. Sprawdź swoje filtry!", 199 | EmptyLogs = "Pusty log", 200 | RoundEnded = "Runda zakończona", 201 | DamageInfosBetween = "Informacje o obrażeniach graczy %s i %s między %s, a %s.", 202 | PlayerInformation = "Informacje o graczu", 203 | SelectDate = "Wybierz datę:", 204 | SelectRound = "Wybierz rundę:", 205 | RoundsBetween = "Rundy pomiędzy %s:00, a %s:00", 206 | SelectRoundToLoad = "Wybierz rundę do załadowania", 207 | PleaseSelectRound = "Proszę wybierz rundę!", 208 | LoadLogsSelected = "Załaduj logi z wybranej rundy", 209 | OldLogs = "Stare logi", 210 | ShotLogs = "Logi wystrzałów", 211 | ShotEvent = "Wydarzenie", 212 | CopySteamID = "Kopiuj SteamID", 213 | SpectateAttacker = "Obserwuj atakującego", 214 | SpectateVictim = "Obserwuj ofiarę", 215 | FreeMode = "Tryb wolnej kamery", 216 | EnableMouse = "Uwaga: Wciśnij C, aby użyć myszy.", 217 | EnableSlowMotion = "Włącz tryb spowolnionego czasu", 218 | NoWeapon = "", 219 | scorpse = "- ciało", 220 | TakeAction = "Działania", 221 | RDMConclusion = "Wynik", 222 | ViewPreviousReports = "Zobacz zgłoszenia", 223 | YourReport = "Twoje zgłoszenie", 224 | PlayerResponse = "Odpowiedź gracza", 225 | NoLoadedReport = "Nie załadowano zgłoszenia", 226 | CancelReport = "Anuluj to zgłoszenie", 227 | ReportCanceled = "Zgłoszenie anulowane", 228 | AlreadyCanceled = "Anulowałeś to zgłoszenie", 229 | ResponseStatus = "Status odpowiedzi", 230 | Canceled = "Anulowane", 231 | ViewChat = "Zobacz czat", 232 | UpdateNotifications = "Włącz powiadomienia o aktualizacji", 233 | Reported = "Zgłoszony", 234 | Admin = "Administrator", 235 | ChatDisconnected = "", 236 | ChatTitle = "Czat", 237 | Actions = "Działania", 238 | AddPlayer = "Dodaj gracza", 239 | SelectPlayer = "Wybierz gracza", 240 | AddSelected = "Dodaj wybranych graczy", 241 | ForceStay = "Wymuś zostanie...", 242 | ReleaseChat = "Wypuść...", 243 | AllPlayers = "Wszyscy gracze", 244 | InvalidPlayerChat = "Nieprawidłowy gracz!", 245 | CannotLeaveChat = "Nie możesz wyjść jako jedyny administrator!", 246 | AdminsDisconnectedChat = "Wszyscy administratorzy rozłączyli się! Czat został zamknięty.", 247 | ChatClosedBy = "Ten czat został zamknięty przez %s.", 248 | AdminLeaveChat = "%s opuścił czat.", 249 | AllPlayersShort = "wszystkich graczy", 250 | ForcedNotification = "%s wymusił zostanie %s na czacie", 251 | ReleasedNotification = "%s wypuścił %s z czatu", 252 | ReportHistory = "Historia zgłoszenia #%u", 253 | ActiveChats = "Liczba czatów: %u", 254 | And = "i", 255 | VictimReportedDisconnected = "Ofiara, bądź zgłoszony gracz rozłączył się!", 256 | ChatAlready = "Istnieje już czat do tego zgłoszenia!", 257 | OpenChatNotification = "%s otworzył czat dla zgłoszenia #%u.", 258 | AddedChatAdmin = "Zostałeś dodany do czatu przez administratora!", 259 | ChatClosed = "%s zamknął czat dla zgłoszenia #%u.", 260 | RDMManagerAuto = "(Auto)", 261 | ReportHadDNA = "%s miał próbkę twojego DNA w tej rundzie!", 262 | ReportNoDNA = "%s nie miał próbki twojego DNA w tej rundzie.", 263 | ReportNoDNAInfo = "Nie znaleziono informacji o DNA gracza %s", 264 | AnAdministrator = "Administrator", 265 | AdminReportID = "Zgłoszenie administratora: #%u", 266 | ChatOpenNoMessage = "(To zgłoszenie administratora bezpośrednio otworzyło czat)", 267 | PendingTop = "Oczekujące", 268 | ReportsBottom = "zgłoszenia", 269 | ShowPendingReports = "Pokaż liczbę oczekujących zgłoszeń na HUDzie", 270 | CreateReport = "Zgłoś", 271 | Donate = "Wspomóż", 272 | EnableSound = "Włącz dźwięk powiadomień", 273 | CurrentRound = "Aktualna runda", 274 | StandardReport = "Zwyczajne zgłoszenie (jako gracz)", 275 | StandardAdminReport = "Zgłoszenie jako administrator", 276 | AdvancedAdminReportForce = "Zaawansowane zgłoszenie jako administrator: bezpośrednio wymuś otworzenie okna z odpowiedzią", 277 | AdvancedAdminReportChat = "Zaawansowane zgłoszenie jako administrator: bezpośrednio otwórz czat z graczem", 278 | -- Months 279 | January = "styczeń", 280 | February = "luty", 281 | March = "marzec", 282 | April = "kwiecień", 283 | May = "maj", 284 | June = "czerwiec", 285 | July = "lipiec", 286 | August = "sierpień", 287 | September = "wrzesień", 288 | October = "październik", 289 | November = "listopad", 290 | December = "grudzień", 291 | -- Settings Tab 292 | Save = "Zapisz", 293 | SetDefault = "Powrót do ustawień domyślnych", 294 | AddWeapon = "Dodaj broń lub entity do listy broni", 295 | EditNames = "Edytuj nazwy broni/entity (Tylko dla superadminów!)", 296 | WeaponID = "ID broni", 297 | WeaponNameExample = "Nazwa/ID broni (przykład: weapon_ttt_deagle)", 298 | WeaponDisplayName = "Wyświetlana nazwa broni", 299 | WeaponDisplayExample = "Wyświetlana nazwa broni (przykład: Deagle)", 300 | RemoveSelectedWeapons = "Usuń wybrane bronie/entity", 301 | Cancel = "Anuluj", 302 | DisconnectedPlayer = "", 303 | PlayerDisconnected = "", 304 | healerNick = "rozłączony gracz", 305 | -- Client Settings Tab 306 | Generalsettings = "Ustawienia ogólne", 307 | RDMuponRDM = "Włącz wyskakiwanie okna RDM Managera po RDMie", 308 | CurrentRoundLogs = "Otwórz domyślnie aktualną rundę, jeśli żyjesz i masz odpowiednie uprawnienia", 309 | OutsideNotification = "Włącz dźwięk powiadomień poza grą", 310 | DamagelogMenuSettings = "Ustawienia menu Logu obrażeń", 311 | -- Filters 312 | filter_show_bodies = "Pokaż identyfikacje zwłok", 313 | filter_show_aslays = "Pokaż automatyczne zgładzenia", 314 | filter_show_credits = "Pokaż zmiany w liczbie kredytów", 315 | filter_show_damages = "Pokaż obrażenia", 316 | filter_show_dna = "Pokaż DNA", 317 | filter_show_falldamage = "Pokaż obrażenia od upadku", 318 | filter_show_healthstation = "Pokaż użycia Stacji Zdrowia", 319 | filter_show_kills = "Pokaż zabójstwa", 320 | filter_show_disguises = "Pokaż przebrania", 321 | filter_show_teleports = "Pokaż teleportacje", 322 | filter_show_traps = "Pokaż pułapki", 323 | filter_show_psroles = "Show role purchases", 324 | filter_show_nades = "Pokaż rzucone granaty", 325 | filter_show_suicides = "Pokaż samobójstwa", 326 | filter_show_radiocommands = "Pokaż zwyczajne komendy radiowe", 327 | filter_show_radiokos = "Pokaż KOSy przez radio", 328 | filter_show_purchases = "Pokaż zakupy ekwipunku", 329 | filter_show_c4 = "Pokaż C4", 330 | -- Colors 331 | color_found_bodies = "Zidentyfikowane zwłoki", 332 | colors_aslays = "Automatyczne zgładzenia", 333 | color_credits = "Kredyty", 334 | color_damages = "Obrażenia", 335 | color_team_damages = "Ogień sojuszniczy", 336 | color_dna = "DNA", 337 | color_fall_damages = "Obrażenia od upadku", 338 | color_heal = "Uleczenia", 339 | color_kills = "Zabójstwa", 340 | color_misc = "Różne", 341 | color_nades = "Granaty", 342 | color_suicides = "Samobójstwa", 343 | color_defaultradio = "Komendy radiowe", 344 | color_kosradio = "KOSy przez radio", 345 | color_purchases = "Zakupy ekwipuneku", 346 | color_c4 = "C4", 347 | color_team_kills = "Zabójstwa sojuszników" 348 | } --------------------------------------------------------------------------------