From 4ea223b3540b21a77573cc0bdc836b7a60a0f364 Mon Sep 17 00:00:00 2001 From: Luke Tomkus Date: Sat, 1 Apr 2023 21:17:40 -0400 Subject: [PATCH 01/10] Add and run new hooks related to credit awards. - TTT2ReceivedTeamAwardCredits - TTT2ReceivedKillCredits - TTT2OnGiveFoundCredits - TTT2TransferedCredits --- .../terrortown/gamemode/server/sv_corpse.lua | 13 ++++++++++++ .../gamemode/server/sv_player_ext.lua | 21 +++++++++++++++++++ .../terrortown/gamemode/server/sv_shop.lua | 14 +++++++++++++ lua/ttt2/libraries/credits.lua | 6 ++++++ 4 files changed, 54 insertions(+) diff --git a/gamemodes/terrortown/gamemode/server/sv_corpse.lua b/gamemodes/terrortown/gamemode/server/sv_corpse.lua index ae64374b2..6d71b85b0 100644 --- a/gamemodes/terrortown/gamemode/server/sv_corpse.lua +++ b/gamemodes/terrortown/gamemode/server/sv_corpse.lua @@ -287,6 +287,8 @@ local function GiveFoundCredits(ply, rag, isLongRange) ServerLog(ply:Nick() .. " took " .. credits .. " credits from the body of " .. corpseNick .. "\n") events.Trigger(EVENT_CREDITFOUND, ply, rag, credits) + + hook.Run("TTT2OnGiveFoundCredits", ply, rag, credits) end --- @@ -809,3 +811,14 @@ end function GM:TTTOnCorpseCreated(rag, deadply) end + +--- +-- Called after a player has been given credits for searching a corpse. +-- @param Player ply The player that searched the corpse +-- @param Entity rag The ragdoll that was searched +-- @param number credits The amount of credits that were given +-- @hook +-- @realm server +function GM:TTT2OnGiveFoundCredits(ply, rag, credits) + +end \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua index 7dec0c4d3..0bf802910 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua @@ -1561,3 +1561,24 @@ end function GM:TTT2ModifyDefaultTraitorCredits(ply, credits) end + +--- +-- Hook that is called when a player recieves credits for a kill. +-- @param Player ply The player who killed another player +-- @param Player victim The player who was killed +-- @param number credits The amount of credits the player received +-- @hook +-- @realm server +function GM:TTT2ReceivedKillCredits(ply, victim, credits) + +end + +--- +-- Hook that is called when a player recieves credits as a team award. +-- @param Player ply The player who was awarded the credits +-- @param number credits The amount of credits the player received +-- @hook +-- @realm server +function GM:TTT2ReceivedTeamAwardCredits(ply, credits) + +end \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/server/sv_shop.lua b/gamemodes/terrortown/gamemode/server/sv_shop.lua index 689ee7489..9eddd95e5 100644 --- a/gamemodes/terrortown/gamemode/server/sv_shop.lua +++ b/gamemodes/terrortown/gamemode/server/sv_shop.lua @@ -233,6 +233,18 @@ function GM:TTT2CanTransferCredits(sender, recipient, credits_per_xfer) end +--- +-- Called when a player has successfully transfered a credit to another player. +-- @param Player sender Player that has sent the credits. +-- @param Player recipient Player that has received the credits. +-- @param number credits Amount of credits that have been transferred. +-- @param boolean isRecipientDead If the recipient is dead or not. +-- @hook +-- @realm server +function GM:TTT2TransferedCredits(sender, recipient, credits, isRecipientDead) + +end + local function TransferCredits(ply, cmd, args) if not IsValid(ply) then return end @@ -271,12 +283,14 @@ local function TransferCredits(ply, cmd, args) ply:SubtractCredits(credits) if target:IsTerror() and target:Alive() then target:AddCredits(credits) + hook.Run("TTT2TransferedCredits", ply, target, credits, false) else -- The would be recipient is dead, which the sender may not know. -- Instead attempt to send the credits to the target's corpse, where they can be picked up. local rag = target:FindCorpse() if IsValid(rag) then CORPSE.SetCredits(rag, CORPSE.GetCredits(rag, 0) + credits) + hook.Run("TTT2TransferedCredits", ply, target, credits, true) end end diff --git a/lua/ttt2/libraries/credits.lua b/lua/ttt2/libraries/credits.lua index 94dced34b..00379bba3 100644 --- a/lua/ttt2/libraries/credits.lua +++ b/lua/ttt2/libraries/credits.lua @@ -35,6 +35,9 @@ function credits.HandleKillCreditsAward(victim, attacker) local creditsAmount = GetConVar("ttt_credits_award_kill"):GetInt() attacker:AddCredits(creditsAmount) + + hook.Run("TTT2ReceivedKillCredits", victim, attacker, creditsAmount) + LANG.Msg(attacker, "credit_kill", {num = creditsAmount, role = LANG.NameParam(victim:GetRoleString())}, MSG_MSTACK_ROLE) end @@ -113,6 +116,9 @@ function credits.HandleKillCreditsAward(victim, attacker) -- now reward their player for their good game plyToAward:AddCredits(creditsAmount) + + hook.Run("TTT2ReceivedTeamAwardCredits", ply, creditsAmount) + LANG.Msg(plyToAward, "credit_all", {num = creditsAmount}, MSG_MSTACK_ROLE) end end From 78a95dec3b5f318af58046356b2a2d852e944f63 Mon Sep 17 00:00:00 2001 From: Luke Tomkus Date: Sun, 2 Apr 2023 16:28:54 -0400 Subject: [PATCH 02/10] Add TTT2OrderedEquipment hook run to sv_shop::RerollShopForCredit Fixed some errors. --- gamemodes/terrortown/gamemode/server/sv_shop.lua | 1 + lua/ttt2/libraries/credits.lua | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_shop.lua b/gamemodes/terrortown/gamemode/server/sv_shop.lua index 9eddd95e5..cc3d4f189 100644 --- a/gamemodes/terrortown/gamemode/server/sv_shop.lua +++ b/gamemodes/terrortown/gamemode/server/sv_shop.lua @@ -306,5 +306,6 @@ local function RerollShopForCredit(ply, cmd, args) ply:SubtractCredits(GetGlobalInt("ttt2_random_shop_reroll_cost")) RerollShop(ply) + hook.Run("TTT2OrderedEquipment", ply, "reroll_shop", false, GetGlobalInt("ttt2_random_shop_reroll_cost"), false) end concommand.Add("ttt2_reroll_shop", RerollShopForCredit) diff --git a/lua/ttt2/libraries/credits.lua b/lua/ttt2/libraries/credits.lua index 00379bba3..fd52ff6d2 100644 --- a/lua/ttt2/libraries/credits.lua +++ b/lua/ttt2/libraries/credits.lua @@ -117,7 +117,7 @@ function credits.HandleKillCreditsAward(victim, attacker) -- now reward their player for their good game plyToAward:AddCredits(creditsAmount) - hook.Run("TTT2ReceivedTeamAwardCredits", ply, creditsAmount) + hook.Run("TTT2ReceivedTeamAwardCredits", plyToAward, creditsAmount) LANG.Msg(plyToAward, "credit_all", {num = creditsAmount}, MSG_MSTACK_ROLE) end From ce275fc63bea748f19780c915a44ac45260fca1b Mon Sep 17 00:00:00 2001 From: Luke Tomkus Date: Sun, 3 Mar 2024 16:12:41 -0500 Subject: [PATCH 03/10] Merge remote-tracking branch 'upstream/master' Conflicts: gamemodes/terrortown/gamemode/server/sv_corpse.lua gamemodes/terrortown/gamemode/server/sv_player_ext.lua gamemodes/terrortown/gamemode/server/sv_shop.lua lua/ttt2/libraries/credits.lua --- .editorconfig | 3 +- .git-blame-ignore-revs | 4 + .github/ISSUE_TEMPLATE/feature_request.md | 8 +- .github/stale.yml | 16 - .github/workflows/cd.yml | 34 - .github/workflows/ci.yml | 62 +- .github/workflows/lang-parser.yml | 61 + .gitignore | 1 + .luarc.json.example | 32 + .styluaignore | 1 + CHANGELOG.md | 367 +- README.md | 163 +- RELEASE_PROCESS.md | 6 + .../models/ttt2_dna_scanner/camera.vmt | 0 .../models/ttt2_dna_scanner/camera.vtf | Bin .../models/ttt2_dna_scanner/camera_n.vtf | Bin .../models/ttt2_dna_scanner/camera_r.vtf | Bin .../models/ttt2_dna_scanner/screen.vmt | 0 .../models/ttt2_dna_scanner/screen.vtf | Bin .../models/ttt2_dna_scanner/screen/arrow.vmt | 0 .../models/ttt2_dna_scanner/screen/arrow.vtf | Bin .../ttt2_dna_scanner/screen/background.vmt | 0 .../ttt2_dna_scanner/screen/background.vtf | Bin .../models/ttt2_dna_scanner/screen/check.vmt | 0 .../models/ttt2_dna_scanner/screen/check.vtf | Bin .../models/ttt2_dna_scanner/screen/circle.vmt | 0 .../models/ttt2_dna_scanner/screen/circle.vtf | Bin .../models/ttt2_dna_scanner/screen/fail.vmt | 0 .../models/ttt2_dna_scanner/screen/fail.vtf | Bin .../materials/vgui/ttt/ammo/box_357.vmt | 10 + .../materials/vgui/ttt/ammo/box_357.vtf | Bin 0 -> 349760 bytes .../materials/vgui/ttt/ammo/box_alyxgun.vmt | 10 + .../materials/vgui/ttt/ammo/box_alyxgun.vtf | Bin 0 -> 349760 bytes .../materials/vgui/ttt/ammo/box_buckshot.vmt | 10 + .../materials/vgui/ttt/ammo/box_buckshot.vtf | Bin 0 -> 349760 bytes .../materials/vgui/ttt/ammo/box_pistol.vmt | 10 + .../materials/vgui/ttt/ammo/box_pistol.vtf | Bin 0 -> 349760 bytes .../materials/vgui/ttt/ammo/box_smg1.vmt | 10 + .../materials/vgui/ttt/ammo/box_smg1.vtf | Bin 0 -> 349760 bytes .../materials/vgui/ttt/ammo/bullet_357.vmt | 10 + .../materials/vgui/ttt/ammo/bullet_357.vtf | Bin 0 -> 349760 bytes .../vgui/ttt/ammo/bullet_alyxgun.vmt | 11 + .../vgui/ttt/ammo/bullet_alyxgun.vtf | Bin 0 -> 349760 bytes .../vgui/ttt/ammo/bullet_buckshot.vmt | 11 + .../vgui/ttt/ammo/bullet_buckshot.vtf | Bin 0 -> 349760 bytes .../materials/vgui/ttt/ammo/bullet_pistol.vmt | 11 + .../materials/vgui/ttt/ammo/bullet_pistol.vtf | Bin 0 -> 349760 bytes .../materials/vgui/ttt/ammo/bullet_smg1.vmt | 11 + .../materials/vgui/ttt/ammo/bullet_smg1.vtf | Bin 0 -> 349760 bytes .../vgui/ttt/b-draw/icon_avatar_bot.vmt | 0 .../vgui/ttt/b-draw/icon_avatar_bot.vtf | Bin .../vgui/ttt/b-draw/icon_avatar_default.vmt | 0 .../vgui/ttt/b-draw/icon_avatar_default.vtf | Bin .../vgui/ttt/dmgindicator/themes/default.png | Bin .../vgui/ttt/dmgindicator/themes/simple.png | Bin .../vgui/ttt/dmgindicator/themes/vanilla.png | Bin .../vgui/ttt/dnascanner/dna_hud.vmt | 0 .../vgui/ttt/dnascanner/dna_hud.vtf | Bin .../materials}/vgui/ttt/dynamic/base.vmt | 0 .../materials}/vgui/ttt/dynamic/base.vtf | Bin .../vgui/ttt/dynamic/base_overlay.vmt | 0 .../vgui/ttt/dynamic/base_overlay.vtf | Bin .../dynamic/hud_components/shadow_border.vmt | 0 .../dynamic/hud_components/shadow_border.vtf | Bin .../materials}/vgui/ttt/dynamic/icon_base.vmt | 0 .../materials}/vgui/ttt/dynamic/icon_base.vtf | Bin .../vgui/ttt/dynamic/icon_base_base.vmt | 0 .../vgui/ttt/dynamic/icon_base_base.vtf | Bin .../ttt/dynamic/icon_base_base_overlay.vmt | 0 .../ttt/dynamic/icon_base_base_overlay.vtf | Bin .../vgui/ttt/dynamic/roles/icon_det.vmt | 0 .../vgui/ttt/dynamic/roles/icon_det.vtf | Bin .../vgui/ttt/dynamic/roles/icon_disabled.vmt | 0 .../vgui/ttt/dynamic/roles/icon_disabled.vtf | Bin .../vgui/ttt/dynamic/roles/icon_inno.vmt | 0 .../vgui/ttt/dynamic/roles/icon_inno.vtf | Bin .../vgui/ttt/dynamic/roles/icon_no_team.vmt | 0 .../vgui/ttt/dynamic/roles/icon_no_team.vtf | Bin .../vgui/ttt/dynamic/roles/icon_none.vmt | 0 .../vgui/ttt/dynamic/roles/icon_none.vtf | Bin .../ttt/dynamic/roles/icon_shop_custom.vmt | 0 .../ttt/dynamic/roles/icon_shop_custom.vtf | Bin .../ttt/dynamic/roles/icon_shop_default.vmt | 0 .../ttt/dynamic/roles/icon_shop_default.vtf | Bin .../vgui/ttt/dynamic/roles/icon_traitor.vmt | 0 .../vgui/ttt/dynamic/roles/icon_traitor.vtf | Bin .../vgui/ttt/dynamic/sprite_base.vmt | 0 .../vgui/ttt/dynamic/sprite_base.vtf | Bin .../vgui/ttt/dynamic/sprite_base_overlay.vmt | 0 .../vgui/ttt/dynamic/sprite_base_overlay.vtf | Bin .../materials}/vgui/ttt/equip/briefcase.png | Bin .../materials}/vgui/ttt/equip/coin.png | Bin .../vgui/ttt/equip/credits_default.vmt | 0 .../vgui/ttt/equip/credits_default.vtf | Bin .../vgui/ttt/equip/credits_zero.vmt | 0 .../vgui/ttt/equip/credits_zero.vtf | Bin .../vgui/ttt/equip/icon_global_limited.vmt | 0 .../vgui/ttt/equip/icon_global_limited.vtf | Bin .../materials}/vgui/ttt/equip/icon_info.vmt | 0 .../materials}/vgui/ttt/equip/icon_info.vtf | Bin .../vgui/ttt/equip/icon_team_limited.vmt | 0 .../vgui/ttt/equip/icon_team_limited.vtf | Bin .../materials}/vgui/ttt/equip/package.png | Bin .../materials}/vgui/ttt/equip/reroll.png | Bin .../content/materials}/vgui/ttt/hud_armor.vmt | 0 .../content/materials}/vgui/ttt/hud_armor.vtf | Bin .../vgui/ttt/hud_armor_reinforced.vmt | 0 .../vgui/ttt/hud_armor_reinforced.vtf | Bin .../vgui/ttt/hud_blocking_revival.vmt | 0 .../vgui/ttt/hud_blocking_revival.vtf | Bin .../content/materials/vgui/ttt/hud_health.vmt | 10 + .../content/materials/vgui/ttt/hud_health.vtf | Bin 0 -> 349760 bytes .../materials/vgui/ttt/hud_health_low.vmt | 10 + .../materials/vgui/ttt/hud_health_low.vtf | Bin 0 -> 349760 bytes .../materials/vgui/ttt/hudhelp/ammo_drop.vmt | 10 + .../materials/vgui/ttt/hudhelp/ammo_drop.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/hudhelp/chat_global.vmt | 10 + .../vgui/ttt/hudhelp/chat_global.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/chat_team.vmt | 10 + .../materials/vgui/ttt/hudhelp/chat_team.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/flashlight.vmt | 10 + .../materials/vgui/ttt/hudhelp/flashlight.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/hudhelp/item_disguiser.vmt | 10 + .../vgui/ttt/hudhelp/item_disguiser.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/hudhelp/leave_target.vmt | 10 + .../vgui/ttt/hudhelp/leave_target.vtf | Bin 0 -> 5696 bytes .../materials}/vgui/ttt/hudhelp/lmb.vmt | 0 .../materials}/vgui/ttt/hudhelp/lmb.vtf | Bin .../materials/vgui/ttt/hudhelp/mute.vmt | 10 + .../materials/vgui/ttt/hudhelp/mute.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/player.vmt | 10 + .../materials/vgui/ttt/hudhelp/player.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/hudhelp/player_next.vmt | 10 + .../vgui/ttt/hudhelp/player_next.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/hudhelp/player_prev.vmt | 10 + .../vgui/ttt/hudhelp/player_prev.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/hudhelp/player_random.vmt | 10 + .../vgui/ttt/hudhelp/player_random.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/pointer.vmt | 10 + .../materials/vgui/ttt/hudhelp/pointer.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/possessing.vmt | 10 + .../materials/vgui/ttt/hudhelp/possessing.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/prop_back.vmt | 10 + .../materials/vgui/ttt/hudhelp/prop_back.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/prop_dash.vmt | 10 + .../materials/vgui/ttt/hudhelp/prop_dash.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/prop_front.vmt | 10 + .../materials/vgui/ttt/hudhelp/prop_front.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/prop_jump.vmt | 10 + .../materials/vgui/ttt/hudhelp/prop_jump.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/prop_left.vmt | 10 + .../materials/vgui/ttt/hudhelp/prop_left.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/prop_right.vmt | 10 + .../materials/vgui/ttt/hudhelp/prop_right.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/quickchat.vmt | 10 + .../materials/vgui/ttt/hudhelp/quickchat.vtf | Bin 0 -> 5696 bytes .../materials}/vgui/ttt/hudhelp/rmb.vmt | 0 .../materials}/vgui/ttt/hudhelp/rmb.vtf | Bin .../materials/vgui/ttt/hudhelp/save.vmt | 10 + .../materials/vgui/ttt/hudhelp/save.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/settings.vmt | 10 + .../materials/vgui/ttt/hudhelp/settings.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/hudhelp/shopping_role.vmt | 10 + .../vgui/ttt/hudhelp/shopping_role.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/showmore.vmt | 10 + .../materials/vgui/ttt/hudhelp/showmore.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/hudhelp/third_person.vmt | 10 + .../vgui/ttt/hudhelp/third_person.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/hudhelp/voice_global.vmt | 10 + .../vgui/ttt/hudhelp/voice_global.vtf | Bin 0 -> 5696 bytes .../materials/vgui/ttt/hudhelp/voice_team.vmt | 10 + .../materials/vgui/ttt/hudhelp/voice_team.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/hudhelp/weapon_drop.vmt | 10 + .../vgui/ttt/hudhelp/weapon_drop.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/huds/old_ttt/preview.png | Bin .../vgui/ttt/huds/pure_skin/preview.png | Bin .../materials}/vgui/ttt/icon_armor.vmt | 0 .../materials}/vgui/ttt/icon_armor.vtf | Bin .../materials/vgui/ttt/icon_beacon.vmt | 7 + .../materials/vgui/ttt/icon_beacon.vtf | Bin 0 -> 16592 bytes .../materials/vgui/ttt/icon_confgrenade.vmt | 7 + .../materials/vgui/ttt/icon_confgrenade.vtf | Bin 0 -> 16592 bytes .../materials/vgui/ttt/icon_credits.vmt | 7 + .../materials/vgui/ttt/icon_credits.vtf | Bin 0 -> 16464 bytes .../vgui/ttt/icon_credits_transparent.vmt | 7 + .../vgui/ttt/icon_credits_transparent.vtf | Bin 0 -> 21924 bytes .../content/materials/vgui/ttt/icon_decoy.vmt | 7 + .../content/materials/vgui/ttt/icon_decoy.vtf | Bin 0 -> 16464 bytes .../materials}/vgui/ttt/icon_drown.vmt | 0 .../materials}/vgui/ttt/icon_drown.vtf | Bin .../materials/vgui/ttt/icon_firegrenade.vmt | 7 + .../materials/vgui/ttt/icon_firegrenade.vtf | Bin 0 -> 16592 bytes .../content/materials/vgui/ttt/icon_floor.vmt | 7 + .../content/materials/vgui/ttt/icon_floor.vtf | Bin 0 -> 16464 bytes .../vgui/ttt/icon_magneto_stick.vmt | 0 .../vgui/ttt/icon_magneto_stick.vtf | Bin .../vgui/ttt/icon_nodrowningdmg.vmt | 0 .../vgui/ttt/icon_nodrowningdmg.vtf | Bin .../materials}/vgui/ttt/icon_noenergydmg.vmt | 0 .../materials}/vgui/ttt/icon_noenergydmg.vtf | Bin .../vgui/ttt/icon_noexplosiondmg.vmt | 0 .../vgui/ttt/icon_noexplosiondmg.vtf | Bin .../materials}/vgui/ttt/icon_nofalldmg.vmt | 0 .../materials}/vgui/ttt/icon_nofalldmg.vtf | Bin .../materials}/vgui/ttt/icon_nofiredmg.vmt | 0 .../materials}/vgui/ttt/icon_nofiredmg.vtf | Bin .../materials}/vgui/ttt/icon_nohazarddmg.vmt | 0 .../materials}/vgui/ttt/icon_nohazarddmg.vtf | Bin .../materials}/vgui/ttt/icon_nopropdmg.vmt | 0 .../materials}/vgui/ttt/icon_nopropdmg.vtf | Bin .../materials/vgui/ttt/icon_smokegrenade.vmt | 7 + .../materials/vgui/ttt/icon_smokegrenade.vtf | Bin 0 -> 16592 bytes .../materials}/vgui/ttt/icon_speedrun.vmt | 0 .../materials}/vgui/ttt/icon_speedrun.vtf | Bin .../materials/vgui/ttt/icon_unarmed.vmt | 8 + .../materials/vgui/ttt/icon_unarmed.vtf | Bin 0 -> 16592 bytes .../materials/vgui/ttt/icon_water_1.vmt | 7 + .../materials/vgui/ttt/icon_water_1.vtf | Bin 0 -> 16464 bytes .../materials/vgui/ttt/icon_water_2.vmt | 7 + .../materials/vgui/ttt/icon_water_2.vtf | Bin 0 -> 16464 bytes .../materials/vgui/ttt/icon_water_3.vmt | 7 + .../materials/vgui/ttt/icon_water_3.vtf | Bin 0 -> 16464 bytes .../vgui/ttt/indirect_confirmed.vmt | 0 .../vgui/ttt/indirect_confirmed.vtf | Bin .../vgui/ttt/marker_vision/beacon.vmt | 10 + .../vgui/ttt/marker_vision/beacon.vtf | Bin 0 -> 349760 bytes .../materials/vgui/ttt/marker_vision/c4.vmt | 10 + .../materials/vgui/ttt/marker_vision/c4.vtf | Bin .../vgui/ttt/marker_vision/radio.vmt | 10 + .../vgui/ttt/marker_vision/radio.vtf | Bin 0 -> 349760 bytes .../vgui/ttt/missing_equip_icon.vmt | 0 .../vgui/ttt/missing_equip_icon.vtf | Bin .../materials}/vgui/ttt/perks/hud_armor.png | Bin .../vgui/ttt/perks/hud_armor_reinforced.png | Bin .../vgui/ttt/perks/hud_disguiser.png | Bin 0 -> 5543 bytes .../vgui/ttt/perks/hud_nodrowningdmg.png | Bin .../vgui/ttt/perks/hud_noenergydmg.png | Bin .../vgui/ttt/perks/hud_noexplosiondmg.png | Bin .../vgui/ttt/perks/hud_nofalldmg.png | Bin .../vgui/ttt/perks/hud_nofiredmg.png | Bin .../vgui/ttt/perks/hud_nohazarddmg.png | Bin .../vgui/ttt/perks/hud_nopropdmg.png | Bin .../materials}/vgui/ttt/perks/hud_radar.png | Bin .../vgui/ttt/perks/hud_speedrun.png | Bin .../materials}/vgui/ttt/perks/old_ttt_bg.png | Bin .../materials}/vgui/ttt/pickup/icon_ammo.png | Bin .../materials}/vgui/ttt/pickup/icon_class.png | Bin .../materials}/vgui/ttt/pickup/icon_extra.png | Bin .../materials}/vgui/ttt/pickup/icon_heavy.png | Bin .../materials}/vgui/ttt/pickup/icon_nades.png | Bin .../vgui/ttt/pickup/icon_pistol.png | Bin .../vgui/ttt/pickup/icon_special.png | Bin .../content/materials}/vgui/ttt/revived.vmt | 0 .../content/materials}/vgui/ttt/revived.vtf | Bin .../materials}/vgui/ttt/score_logo_2.vmt | 0 .../materials}/vgui/ttt/score_logo_2.vtf | Bin .../materials}/vgui/ttt/score_logo_2_old.vmt | 0 .../materials}/vgui/ttt/score_logo_2_old.vtf | Bin .../vgui/ttt/slot/slot_weapon_carry.vmt | 11 + .../vgui/ttt/slot/slot_weapon_carry.vtf | Bin 0 -> 349760 bytes .../vgui/ttt/slot/slot_weapon_class.vmt | 11 + .../vgui/ttt/slot/slot_weapon_class.vtf | Bin 0 -> 349760 bytes .../vgui/ttt/slot/slot_weapon_extra.vmt | 11 + .../vgui/ttt/slot/slot_weapon_extra.vtf | Bin 0 -> 349760 bytes .../vgui/ttt/slot/slot_weapon_heavy.vmt | 11 + .../vgui/ttt/slot/slot_weapon_heavy.vtf | Bin .../vgui/ttt/slot/slot_weapon_melee.vmt | 11 + .../vgui/ttt/slot/slot_weapon_melee.vtf | Bin 0 -> 349760 bytes .../vgui/ttt/slot/slot_weapon_nade.vmt | 11 + .../vgui/ttt/slot/slot_weapon_nade.vtf | Bin .../vgui/ttt/slot/slot_weapon_pistol.vmt | 11 + .../vgui/ttt/slot/slot_weapon_pistol.vtf | Bin 0 -> 349760 bytes .../vgui/ttt/slot/slot_weapon_special.vmt | 11 + .../vgui/ttt/slot/slot_weapon_special.vtf | Bin .../vgui/ttt/slot/slot_weapon_unarmed.vmt | 11 + .../vgui/ttt/slot/slot_weapon_unarmed.vtf | Bin 0 -> 349760 bytes .../materials}/vgui/ttt/target_icon.vmt | 0 .../materials}/vgui/ttt/target_icon.vtf | Bin .../materials/vgui/ttt/tid/tid_ammo.vmt | 10 + .../materials/vgui/ttt/tid/tid_ammo.vtf | Bin 0 -> 5696 bytes .../vgui/ttt/tid/tid_auto_close.vmt | 0 .../vgui/ttt/tid/tid_auto_close.vtf | Bin .../vgui/ttt/tid/tid_big_ammo_deagle.vmt | 0 .../vgui/ttt/tid/tid_big_ammo_deagle.vtf | Bin .../vgui/ttt/tid/tid_big_ammo_mac10.vmt | 0 .../vgui/ttt/tid/tid_big_ammo_mac10.vtf | Bin .../vgui/ttt/tid/tid_big_ammo_pistol.vmt | 0 .../vgui/ttt/tid/tid_big_ammo_pistol.vtf | Bin .../vgui/ttt/tid/tid_big_ammo_random.vmt | 0 .../vgui/ttt/tid/tid_big_ammo_random.vtf | Bin .../vgui/ttt/tid/tid_big_ammo_rifle.vmt | 0 .../vgui/ttt/tid/tid_big_ammo_rifle.vtf | Bin .../vgui/ttt/tid/tid_big_ammo_shotgun.vmt | 0 .../vgui/ttt/tid/tid_big_ammo_shotgun.vtf | Bin .../vgui/ttt/tid/tid_big_corpse.vmt | 0 .../vgui/ttt/tid/tid_big_corpse.vtf | Bin .../materials}/vgui/ttt/tid/tid_big_door.vmt | 0 .../materials}/vgui/ttt/tid/tid_big_door.vtf | Bin .../vgui/ttt/tid/tid_big_player.vmt | 0 .../vgui/ttt/tid/tid_big_player.vtf | Bin .../vgui/ttt/tid/tid_big_role_not_known.vmt | 0 .../vgui/ttt/tid/tid_big_role_not_known.vtf | Bin .../vgui/ttt/tid/tid_big_tbutton_pointer.vmt | 0 .../vgui/ttt/tid/tid_big_tbutton_pointer.vtf | Bin .../vgui/ttt/tid/tid_big_weapon_assault.vmt | 0 .../vgui/ttt/tid/tid_big_weapon_assault.vtf | Bin 0 -> 349632 bytes .../vgui/ttt/tid/tid_big_weapon_melee.vmt | 0 .../vgui/ttt/tid/tid_big_weapon_melee.vtf | Bin .../vgui/ttt/tid/tid_big_weapon_nade.vmt | 0 .../vgui/ttt/tid/tid_big_weapon_nade.vtf | Bin 0 -> 349632 bytes .../vgui/ttt/tid/tid_big_weapon_pistol.vmt | 0 .../vgui/ttt/tid/tid_big_weapon_pistol.vtf | Bin .../vgui/ttt/tid/tid_big_weapon_random.vmt | 0 .../vgui/ttt/tid/tid_big_weapon_random.vtf | Bin .../vgui/ttt/tid/tid_big_weapon_shotgun.vmt | 0 .../vgui/ttt/tid/tid_big_weapon_shotgun.vtf | Bin .../vgui/ttt/tid/tid_big_weapon_sniper.vmt | 0 .../vgui/ttt/tid/tid_big_weapon_sniper.vtf | Bin .../vgui/ttt/tid/tid_big_weapon_special.vmt | 0 .../vgui/ttt/tid/tid_big_weapon_special.vtf | Bin 0 -> 349632 bytes .../materials}/vgui/ttt/tid/tid_credits.vmt | 0 .../materials}/vgui/ttt/tid/tid_credits.vtf | Bin .../vgui/ttt/tid/tid_destructible.vmt | 0 .../vgui/ttt/tid/tid_destructible.vtf | Bin .../materials}/vgui/ttt/tid/tid_detective.vmt | 0 .../materials}/vgui/ttt/tid/tid_detective.vtf | Bin .../materials}/vgui/ttt/tid/tid_locked.vmt | 0 .../materials}/vgui/ttt/tid/tid_locked.vtf | Bin .../materials}/vgui/ttt/ttt2_hand_filled.vmt | 0 .../materials}/vgui/ttt/ttt2_hand_filled.vtf | Bin .../materials}/vgui/ttt/ttt2_hand_line.vmt | 0 .../materials}/vgui/ttt/ttt2_hand_line.vtf | Bin .../materials}/vgui/ttt/ttt2_hand_outline.vmt | 0 .../materials}/vgui/ttt/ttt2_hand_outline.vtf | Bin .../vgui/ttt/ttt2_indicator_addondev.vmt | 0 .../vgui/ttt/ttt2_indicator_addondev.vtf | Bin .../vgui/ttt/ttt2_indicator_admin.vmt | 0 .../vgui/ttt/ttt2_indicator_admin.vtf | Bin .../vgui/ttt/ttt2_indicator_dev.vmt | 0 .../vgui/ttt/ttt2_indicator_dev.vtf | Bin .../vgui/ttt/ttt2_indicator_heroes.vmt | 0 .../vgui/ttt/ttt2_indicator_heroes.vtf | Bin .../vgui/ttt/ttt2_indicator_streamer.vmt | 0 .../vgui/ttt/ttt2_indicator_streamer.vtf | Bin .../vgui/ttt/ttt2_indicator_vip.vmt | 0 .../vgui/ttt/ttt2_indicator_vip.vtf | Bin .../materials}/vgui/ttt/vskin/card_added.vmt | 0 .../materials}/vgui/ttt/vskin/card_added.vtf | Bin .../vgui/ttt/vskin/card_removed.vmt | 0 .../vgui/ttt/vskin/card_removed.vtf | Bin .../vgui/ttt/vskin/events/base_event.vmt | 0 .../vgui/ttt/vskin/events/base_event.vtf | Bin .../vgui/ttt/vskin/events/bodyfound.vmt | 0 .../vgui/ttt/vskin/events/bodyfound.vtf | Bin .../vgui/ttt/vskin/events/c4disarm.vmt | 0 .../vgui/ttt/vskin/events/c4disarm.vtf | Bin .../vgui/ttt/vskin/events/c4explode.vmt | 0 .../vgui/ttt/vskin/events/c4explode.vtf | Bin .../vgui/ttt/vskin/events/c4plant.vmt | 0 .../vgui/ttt/vskin/events/c4plant.vtf | Bin 0 -> 349632 bytes .../vgui/ttt/vskin/events/creditfound.vmt | 0 .../vgui/ttt/vskin/events/creditfound.vtf | Bin .../vgui/ttt/vskin/events/finish.vmt | 0 .../vgui/ttt/vskin/events/finish.vtf | Bin .../materials}/vgui/ttt/vskin/events/game.vmt | 0 .../materials}/vgui/ttt/vskin/events/game.vtf | Bin .../materials}/vgui/ttt/vskin/events/kill.vmt | 0 .../materials}/vgui/ttt/vskin/events/kill.vtf | Bin .../vgui/ttt/vskin/events/respawn.vmt | 0 .../vgui/ttt/vskin/events/respawn.vtf | Bin .../vgui/ttt/vskin/events/rolechange.vmt | 0 .../vgui/ttt/vskin/events/rolechange.vtf | Bin .../vgui/ttt/vskin/events/selected.vmt | 0 .../vgui/ttt/vskin/events/selected.vtf | Bin .../vgui/ttt/vskin/events/spawn.vmt | 0 .../vgui/ttt/vskin/events/spawn.vtf | Bin .../ttt/vskin/helpscreen/accessibility.vmt | 10 + .../ttt/vskin/helpscreen/accessibility.vtf | Bin 0 -> 349760 bytes .../vgui/ttt/vskin/helpscreen/addons.vmt | 0 .../vgui/ttt/vskin/helpscreen/addons.vtf | Bin .../ttt/vskin/helpscreen/administration.vmt | 0 .../ttt/vskin/helpscreen/administration.vtf | Bin .../vgui/ttt/vskin/helpscreen/appearance.vmt | 0 .../vgui/ttt/vskin/helpscreen/appearance.vtf | Bin .../vgui/ttt/vskin/helpscreen/bindings.vmt | 0 .../vgui/ttt/vskin/helpscreen/bindings.vtf | Bin .../vgui/ttt/vskin/helpscreen/changelog.vmt | 0 .../vgui/ttt/vskin/helpscreen/changelog.vtf | Bin .../vgui/ttt/vskin/helpscreen/equipment.vmt | 0 .../vgui/ttt/vskin/helpscreen/equipment.vtf | Bin .../vgui/ttt/vskin/helpscreen/gameplay.vmt | 0 .../vgui/ttt/vskin/helpscreen/gameplay.vtf | Bin .../vgui/ttt/vskin/helpscreen/guide.vmt | 0 .../vgui/ttt/vskin/helpscreen/guide.vtf | Bin .../vgui/ttt/vskin/helpscreen/language.vmt | 0 .../vgui/ttt/vskin/helpscreen/language.vtf | Bin .../vgui/ttt/vskin/helpscreen/legacy.vmt | 0 .../vgui/ttt/vskin/helpscreen/legacy.vtf | Bin .../vgui/ttt/vskin/helpscreen/roles.vmt | 0 .../vgui/ttt/vskin/helpscreen/roles.vtf | Bin .../vgui/ttt/vskin/helpscreen/shops.vmt | 0 .../vgui/ttt/vskin/helpscreen/shops.vtf | Bin .../ttt/vskin/helpscreen/voiceandvolume.vmt | 11 + .../ttt/vskin/helpscreen/voiceandvolume.vtf | Bin 0 -> 349760 bytes .../materials}/vgui/ttt/vskin/icon_back.vmt | 0 .../materials}/vgui/ttt/vskin/icon_back.vtf | Bin .../materials}/vgui/ttt/vskin/icon_close.vmt | 0 .../materials}/vgui/ttt/vskin/icon_close.vtf | Bin .../vgui/ttt/vskin/icon_collapse_closed.vmt | 0 .../vgui/ttt/vskin/icon_collapse_closed.vtf | Bin .../vgui/ttt/vskin/icon_collapse_opened.vmt | 0 .../vgui/ttt/vskin/icon_collapse_opened.vtf | Bin .../vgui/ttt/vskin/icon_disable.vmt | 0 .../vgui/ttt/vskin/icon_disable.vtf | Bin .../vgui/ttt/vskin/icon_hattable_no.vmt | 0 .../vgui/ttt/vskin/icon_hattable_no.vtf | Bin .../vgui/ttt/vskin/icon_hattable_yes.vmt | 0 .../vgui/ttt/vskin/icon_hattable_yes.vtf | Bin .../vgui/ttt/vskin/icon_headbox_no.vmt | 0 .../vgui/ttt/vskin/icon_headbox_no.vtf | Bin .../vgui/ttt/vskin/icon_headbox_yes.vmt | 0 .../vgui/ttt/vskin/icon_headbox_yes.vtf | Bin .../materials}/vgui/ttt/vskin/icon_reset.vmt | 0 .../materials}/vgui/ttt/vskin/icon_reset.vtf | Bin .../vgui/ttt/vskin/markers/builtin.vmt | 7 + .../vgui/ttt/vskin/markers/builtin.vtf | Bin 0 -> 5696 bytes .../materials}/vgui/ttt/vskin/rhombus.vmt | 0 .../materials}/vgui/ttt/vskin/rhombus.vtf | Bin .../vgui/ttt/vskin/roundend/events.vmt | 0 .../vgui/ttt/vskin/roundend/events.vtf | Bin .../vgui/ttt/vskin/roundend/info.vmt | 0 .../vgui/ttt/vskin/roundend/info.vtf | Bin .../materials}/vgui/ttt/watching_icon.vmt | 0 .../materials}/vgui/ttt/watching_icon.vtf | Bin .../weapons/v_ttt2_dna_scanner.dx90.vtx | Bin .../models}/weapons/v_ttt2_dna_scanner.mdl | Bin .../models}/weapons/v_ttt2_dna_scanner.vvd | Bin .../weapons/w_ttt2_dna_scanner.dx90.vtx | Bin .../models}/weapons/w_ttt2_dna_scanner.mdl | Bin .../models}/weapons/w_ttt2_dna_scanner.phy | Bin .../models}/weapons/w_ttt2_dna_scanner.vvd | Bin .../entities/effects/crimescene_dummy.lua | 93 + .../entities/effects/crimescene_shot.lua | 56 + .../entities/effects/pulse_sphere.lua | 75 + .../entities/effects/teleport_beamdown.lua | 108 + .../entities/effects/teleport_beamup.lua | 106 + .../entities/entities/base_ammo_ttt.lua | 232 +- .../entities/entities/info_manipulate.lua | 78 + .../entities/entities/item_ammo_357_ttt.lua | 2 +- .../entities/item_ammo_pistol_ttt.lua | 2 +- .../entities/item_ammo_revolver_ttt.lua | 12 +- .../entities/entities/item_ammo_smg1_ttt.lua | 2 +- .../entities/item_box_buckshot_ttt.lua | 2 +- .../entities/entities/ttt_base_placeable.lua | 395 ++ .../entities/ttt_basegrenade_proj.lua | 84 + .../entities/entities/ttt_beacon.lua | 322 ++ .../entities/entities/ttt_c4/cl_init.lua | 522 +++ .../entities/entities/ttt_c4/shared.lua | 1504 ++++---- .../entities/entities/ttt_carry_handler.lua | 149 + .../entities/ttt_confgrenade_proj.lua | 185 +- .../entities/entities/ttt_credit_adjust.lua | 49 + .../entities/entities/ttt_cse_proj.lua | 246 ++ .../entities/entities/ttt_damageowner.lua | 77 + .../entities/entities/ttt_decoy.lua | 178 +- .../entities/ttt_firegrenade_proj.lua | 77 + .../entities/entities/ttt_flame.lua | 227 ++ .../entities/entities/ttt_game_text.lua | 94 +- .../entities/entities/ttt_hat_deerstalker.lua | 159 + .../entities/entities/ttt_health_station.lua | 334 +- .../entities/entities/ttt_knife_proj.lua | 244 ++ .../entities/entities/ttt_logic_role.lua | 74 +- .../entities/entities/ttt_map_settings.lua | 105 + .../entities/entities/ttt_physhammer.lua | 197 + .../entities/entities/ttt_radio.lua | 619 ++-- .../entities/entities/ttt_random_ammo.lua | 8 +- .../entities/entities/ttt_random_weapon.lua | 14 +- .../entities/ttt_smokegrenade_proj.lua | 101 + .../entities/entities/ttt_spawninfo_ent.lua | 4 +- .../entities/entities/ttt_spectator_spawn.lua | 7 + .../entities/ttt_traitor_button/init.lua | 397 +- .../entities/ttt_traitor_button/shared.lua | 113 +- .../entities/entities/ttt_traitor_check.lua | 65 +- .../entities/entities/ttt_weapon_check.lua | 143 + .../terrortown/entities/entities/ttt_win.lua | 26 +- .../entities/weapons/weapon_ttt_beacon.lua | 155 + .../weapons/weapon_ttt_binoculars.lua | 428 ++- .../entities/weapons/weapon_ttt_c4.lua | 101 + .../weapons/weapon_ttt_confgrenade.lua | 38 +- .../entities/weapons/weapon_ttt_cse.lua | 207 +- .../entities/weapons/weapon_ttt_decoy.lua | 275 +- .../entities/weapons/weapon_ttt_defuser.lua | 101 + .../entities/weapons/weapon_ttt_flaregun.lua | 241 ++ .../entities/weapons/weapon_ttt_glock.lua | 16 +- .../weapons/weapon_ttt_health_station.lua | 104 + .../entities/weapons/weapon_ttt_knife.lua | 588 +-- .../entities/weapons/weapon_ttt_m16.lua | 73 +- .../entities/weapons/weapon_ttt_phammer.lua | 458 +++ .../entities/weapons/weapon_ttt_push.lua | 311 ++ .../entities/weapons/weapon_ttt_radio.lua | 141 + .../entities/weapons/weapon_ttt_sipistol.lua | 73 + .../weapons/weapon_ttt_smokegrenade.lua | 17 +- .../weapons/weapon_ttt_spawneditor.lua | 1093 +++--- .../entities/weapons/weapon_ttt_stungun.lua | 125 + .../entities/weapons/weapon_ttt_teleport.lua | 492 +-- .../entities/weapons/weapon_ttt_unarmed.lua | 107 + .../entities/weapons/weapon_ttt_wtester.lua | 1096 +++--- .../entities/weapons/weapon_tttbase.lua | 1942 ++++++---- .../weapons/weapon_tttbasegrenade.lua | 534 ++- .../entities/weapons/weapon_zm_carry.lua | 1365 +++---- .../entities/weapons/weapon_zm_improvised.lua | 537 +-- .../entities/weapons/weapon_zm_mac10.lua | 34 +- .../entities/weapons/weapon_zm_molotov.lua | 17 +- .../entities/weapons/weapon_zm_pistol.lua | 16 +- .../entities/weapons/weapon_zm_revolver.lua | 14 +- .../entities/weapons/weapon_zm_rifle.lua | 205 +- .../entities/weapons/weapon_zm_shotgun.lua | 182 +- .../entities/weapons/weapon_zm_sledge.lua | 16 +- .../terrortown/gamemode/client/cl_armor.lua | 81 +- .../terrortown/gamemode/client/cl_awards.lua | 1445 ++++---- .../terrortown/gamemode/client/cl_changes.lua | 3297 ++++++++++------- .../terrortown/gamemode/client/cl_chat.lua | 144 +- .../gamemode/client/cl_damage_indicator.lua | 109 +- .../terrortown/gamemode/client/cl_equip.lua | 1893 +++++----- .../gamemode/client/cl_eventpopup.lua | 164 +- .../terrortown/gamemode/client/cl_help.lua | 490 +-- .../gamemode/client/cl_hud_editor.lua | 511 +-- .../gamemode/client/cl_hud_manager.lua | 214 +- .../gamemode/client/cl_hudpickup.lua | 133 +- .../gamemode/client/cl_inventory.lua | 24 +- .../terrortown/gamemode/client/cl_karma.lua | 2 +- .../terrortown/gamemode/client/cl_keys.lua | 239 +- .../terrortown/gamemode/client/cl_lang.lua | 458 ++- .../terrortown/gamemode/client/cl_main.lua | 972 ++--- .../gamemode/client/cl_marker_vision_data.lua | 214 ++ .../gamemode/client/cl_msgstack.lua | 88 +- .../gamemode/client/cl_network_sync.lua | 382 +- .../gamemode/client/cl_player_ext.lua | 535 ++- .../terrortown/gamemode/client/cl_popups.lua | 264 +- .../terrortown/gamemode/client/cl_radio.lua | 521 +-- .../terrortown/gamemode/client/cl_reroll.lua | 63 +- .../gamemode/client/cl_scoreboard.lua | 42 +- .../terrortown/gamemode/client/cl_scoring.lua | 582 +-- .../terrortown/gamemode/client/cl_search.lua | 1087 ++---- .../terrortown/gamemode/client/cl_shop.lua | 27 + .../gamemode/client/cl_shopeditor.lua | 338 +- .../terrortown/gamemode/client/cl_status.lua | 103 +- .../gamemode/client/cl_target_data.lua | 187 +- .../gamemode/client/cl_targetid.lua | 800 ++-- .../gamemode/client/cl_tbuttons.lua | 343 +- .../terrortown/gamemode/client/cl_tips.lua | 348 +- .../terrortown/gamemode/client/cl_tradio.lua | 130 +- .../gamemode/client/cl_transfer.lua | 157 +- .../terrortown/gamemode/client/cl_voice.lua | 815 ++-- .../gamemode/client/cl_vskin/default_skin.lua | 2708 ++++++++------ .../client/cl_vskin/vgui/dbinder_ttt2.lua | 67 +- .../cl_vskin/vgui/dbinderpanel_ttt2.lua | 32 +- .../client/cl_vskin/vgui/dbutton_ttt2.lua | 117 +- .../cl_vskin/vgui/dbuttonpanel_ttt2.lua | 58 +- .../client/cl_vskin/vgui/dcard_ttt2.lua | 92 +- .../cl_vskin/vgui/dcategorycollapse_ttt2.lua | 232 +- .../cl_vskin/vgui/dcategoryheader_ttt2.lua | 16 +- .../cl_vskin/vgui/dcheckboxlabel_ttt2.lua | 295 +- .../client/cl_vskin/vgui/dcoloredbox_ttt2.lua | 34 +- .../cl_vskin/vgui/dcoloredtextbox_ttt2.lua | 68 +- .../client/cl_vskin/vgui/dcombobox_ttt2.lua | 541 +-- .../cl_vskin/vgui/dcontentpanel_ttt2.lua | 61 +- .../client/cl_vskin/vgui/ddragbase_ttt2.lua | 209 +- .../client/cl_vskin/vgui/deventbox_ttt2.lua | 102 +- .../client/cl_vskin/vgui/dform_ttt2.lua | 752 ++-- .../client/cl_vskin/vgui/dframe_ttt2.lua | 466 +-- .../cl_vskin/vgui/dimagecheckbox_ttt2.lua | 291 +- .../client/cl_vskin/vgui/dinfoitem_ttt2.lua | 92 + .../client/cl_vskin/vgui/dlabel_ttt2.lua | 286 +- .../client/cl_vskin/vgui/dmenubutton_ttt2.lua | 68 +- .../client/cl_vskin/vgui/dnavpanel_ttt2.lua | 58 +- .../client/cl_vskin/vgui/dnumslider_ttt2.lua | 474 ++- .../client/cl_vskin/vgui/dpanel_ttt2.lua | 162 +- .../cl_vskin/vgui/dprofilepanel_ttt2.lua | 318 ++ .../client/cl_vskin/vgui/droleimage_ttt2.lua | 20 +- .../vgui/drolelayeringreceiver_ttt2.lua | 282 +- .../vgui/drolelayeringsender_ttt2.lua | 172 +- .../cl_vskin/vgui/dscrollpanel_ttt2.lua | 104 +- .../client/cl_vskin/vgui/dsearchbar_ttt2.lua | 142 +- .../cl_vskin/vgui/dsubmenubutton_ttt2.lua | 102 +- .../cl_vskin/vgui/dsubmenulist_ttt2.lua | 230 +- .../client/cl_vskin/vgui/dtextentry_ttt2.lua | 346 ++ .../client/cl_vskin/vgui/dtooltip_ttt2.lua | 142 +- .../client/cl_vskin/vgui/dvscrollbar_ttt2.lua | 194 +- .../cl_vskin/vgui/dweaponpreview_ttt2.lua | 334 ++ .../gamemode/client/cl_weapon_pickup.lua | 54 +- .../gamemode/client/cl_wepswitch.lua | 304 +- .../gamemode/client/vgui/cl_coloredbox.lua | 16 +- .../gamemode/client/vgui/cl_droleimage.lua | 592 +-- .../gamemode/client/vgui/cl_progressbar.lua | 86 +- .../gamemode/client/vgui/cl_sb_info.lua | 292 +- .../gamemode/client/vgui/cl_sb_main.lua | 750 ++-- .../gamemode/client/vgui/cl_sb_row.lua | 1127 +++--- .../gamemode/client/vgui/cl_sb_team.lua | 335 +- .../gamemode/client/vgui/cl_scrolllabel.lua | 94 +- .../client/vgui/cl_simpleclickicon.lua | 301 +- .../gamemode/client/vgui/cl_simpleicon.lua | 262 +- .../client/vgui/cl_simpleroleicon.lua | 104 +- .../gamemode/server/sv_addonchecker.lua | 1159 +++--- .../terrortown/gamemode/server/sv_admin.lua | 328 +- .../terrortown/gamemode/server/sv_armor.lua | 256 +- .../terrortown/gamemode/server/sv_corpse.lua | 1067 +++--- .../gamemode/server/sv_ent_replace.lua | 189 +- .../terrortown/gamemode/server/sv_entity.lua | 38 +- .../gamemode/server/sv_eventpopup.lua | 90 +- .../terrortown/gamemode/server/sv_gamemsg.lua | 606 +-- .../gamemode/server/sv_hud_manager.lua | 374 +- .../gamemode/server/sv_inventory.lua | 18 +- .../terrortown/gamemode/server/sv_karma.lua | 857 +++-- .../terrortown/gamemode/server/sv_main.lua | 1594 ++++---- .../gamemode/server/sv_network_sync.lua | 843 +++-- .../gamemode/server/sv_networking.lua | 702 ++-- .../terrortown/gamemode/server/sv_player.lua | 1896 +++++----- .../gamemode/server/sv_player_ext.lua | 1734 +++++---- .../gamemode/server/sv_propspec.lua | 276 +- .../gamemode/server/sv_roleselection.lua | 1353 +++---- .../terrortown/gamemode/server/sv_scoring.lua | 4 +- .../terrortown/gamemode/server/sv_shop.lua | 322 +- .../gamemode/server/sv_shopeditor.lua | 316 +- .../terrortown/gamemode/server/sv_status.lua | 40 +- .../terrortown/gamemode/server/sv_voice.lua | 245 +- .../gamemode/server/sv_weapon_pickup.lua | 26 +- .../gamemode/server/sv_weaponry.lua | 825 +++-- .../base_elements/base_stacking_element.lua | 46 +- .../base_elements/dynamic_hud_element.lua | 92 +- .../hud_element_base/cl_init.lua | 668 ++-- .../base_elements/old_ttt_element.lua | 325 +- .../base_elements/pure_skin_element.lua | 180 +- .../tttdnascanner/pure_skin_dnascanner.lua | 323 +- .../tttdrowning/pure_skin_drowning.lua | 130 +- .../ttteventpopup/pure_skin_eventpopup.lua | 317 +- .../tttinfopanel/old_ttt_info.lua | 521 +-- .../tttinfopanel/pure_skin_playerinfo.lua | 724 ++-- .../pure_skin_miniscoreboard.lua | 297 +- .../hud_elements/tttmstack/old_ttt_mstack.lua | 407 +- .../tttmstack/pure_skin_mstack.lua | 596 +-- .../hud_elements/tttpickup/old_ttt_pickup.lua | 258 +- .../tttpickup/pure_skin_pickup.lua | 312 +- .../tttpunchometer/old_ttt_punchometer.lua | 129 +- .../tttpunchometer/pure_skin_punchometer.lua | 202 +- .../tttrevivalinfo/old_ttt_revival.lua | 275 +- .../tttrevivalinfo/pure_skin_revival.lua | 330 +- .../tttroundinfo/pure_skin_roundinfo.lua | 243 +- .../tttsidebar/old_ttt_sidebar.lua | 204 +- .../tttsidebar/pure_skin_sidebar.lua | 459 ++- .../hud_elements/ttttarget/old_ttt_target.lua | 177 +- .../ttttarget/pure_skin_target.lua | 168 +- .../pure_skin_teamindicator.lua | 209 +- .../tttwswitch/old_ttt_wswitch.lua | 327 +- .../tttwswitch/pure_skin_wswitch.lua | 327 +- .../huds/base_huds/hud_base/cl_init.lua | 454 +-- .../huds/base_huds/scalable_hud/cl_init.lua | 92 +- .../gamemode/shared/huds/old_ttt/cl_init.lua | 36 +- .../huds/pure_skin/cl_drawing_functions.lua | 102 +- .../shared/huds/pure_skin/cl_init.lua | 79 +- .../shared/huds/pure_skin/cl_popup.lua | 4 +- .../shared/huds/pure_skin/cl_voice.lua | 8 +- .../terrortown/gamemode/shared/sh_armor.lua | 11 +- .../terrortown/gamemode/shared/sh_corpse.lua | 44 +- .../gamemode/shared/sh_cvar_handler.lua | 14 +- .../terrortown/gamemode/shared/sh_decal.lua | 390 +- .../terrortown/gamemode/shared/sh_door.lua | 646 ++-- .../gamemode/shared/sh_equip_items.lua | 2079 ++++++----- .../gamemode/shared/sh_hud_module.lua | 112 +- .../gamemode/shared/sh_hudelement_module.lua | 118 +- .../terrortown/gamemode/shared/sh_include.lua | 351 +- .../terrortown/gamemode/shared/sh_init.lua | 443 +-- .../gamemode/shared/sh_inventory.lua | 379 +- .../gamemode/shared/sh_item_module.lua | 60 +- .../terrortown/gamemode/shared/sh_lang.lua | 250 +- .../terrortown/gamemode/shared/sh_main.lua | 702 ++-- .../shared/sh_marker_vision_element.lua | 217 ++ .../gamemode/shared/sh_network_sync.lua | 10 +- .../gamemode/shared/sh_player_ext.lua | 979 +++-- .../gamemode/shared/sh_playerclass.lua | 18 + .../shared/sh_printmessage_override.lua | 60 +- .../gamemode/shared/sh_role_module.lua | 78 +- .../gamemode/shared/sh_rolelayering.lua | 325 +- .../terrortown/gamemode/shared/sh_scoring.lua | 62 +- .../terrortown/gamemode/shared/sh_shop.lua | 449 +++ .../gamemode/shared/sh_shopeditor.lua | 392 +- .../terrortown/gamemode/shared/sh_speed.lua | 148 +- .../terrortown/gamemode/shared/sh_sprint.lua | 278 +- .../terrortown/gamemode/shared/sh_sql.lua | 378 +- .../terrortown/gamemode/shared/sh_voice.lua | 4 +- .../gamemode/shared/sh_weaponry.lua | 104 +- gamemodes/terrortown/ttt2.fgd | 2 + lua/autorun/client/b-draw_lib.lua | 159 +- lua/autorun/gs_crazyphysics.lua | 649 ++-- lua/autorun/server/ttt2_force_download.lua | 212 -- .../entities/items/item_base/shared.lua | 120 +- .../entities/items/item_ttt_armor.lua | 94 +- .../entities/items/item_ttt_disguiser.lua | 249 +- .../entities/items/item_ttt_nodrowningdmg.lua | 29 +- .../entities/items/item_ttt_noenergydmg.lua | 41 +- .../items/item_ttt_noexplosiondmg.lua | 29 +- .../entities/items/item_ttt_nofalldmg.lua | 39 +- .../entities/items/item_ttt_nofiredmg.lua | 29 +- .../entities/items/item_ttt_nohazarddmg.lua | 41 +- .../entities/items/item_ttt_nopropdmg.lua | 29 +- .../entities/items/item_ttt_radar/cl_init.lua | 473 ++- .../entities/items/item_ttt_radar/init.lua | 274 +- .../entities/items/item_ttt_radar/shared.lua | 28 +- .../entities/items/item_ttt_speedrun.lua | 17 +- .../entities/roles/detective/shared.lua | 66 +- .../entities/roles/innocent/shared.lua | 33 +- lua/terrortown/entities/roles/none/shared.lua | 12 +- .../entities/roles/traitor/shared.lua | 45 +- .../entities/roles/ttt_role_base/cl_init.lua | 8 +- .../entities/roles/ttt_role_base/init.lua | 58 +- .../entities/roles/ttt_role_base/shared.lua | 205 +- lua/terrortown/events/base_event.lua | 329 +- lua/terrortown/events/bodyfound.lua | 143 +- lua/terrortown/events/c4disarm.lua | 148 +- lua/terrortown/events/c4explode.lua | 73 +- lua/terrortown/events/c4plant.lua | 73 +- lua/terrortown/events/creditfound.lua | 109 +- lua/terrortown/events/finish.lua | 245 +- lua/terrortown/events/game.lua | 46 +- lua/terrortown/events/kill.lua | 534 +-- lua/terrortown/events/respawn.lua | 43 +- lua/terrortown/events/rolechange.lua | 67 +- lua/terrortown/events/selected.lua | 117 +- lua/terrortown/events/spawn.lua | 69 +- lua/terrortown/lang/chef.lua | 30 +- lua/terrortown/lang/de.lua | 1223 +++--- lua/terrortown/lang/en.lua | 515 ++- lua/terrortown/lang/es.lua | 577 ++- lua/terrortown/lang/fr.lua | 569 ++- lua/terrortown/lang/it.lua | 575 ++- lua/terrortown/lang/ja.lua | 655 +++- lua/terrortown/lang/pl.lua | 573 ++- lua/terrortown/lang/pt_br.lua | 655 +++- lua/terrortown/lang/ru.lua | 516 ++- lua/terrortown/lang/sv.lua | 2193 +++++++++++ lua/terrortown/lang/tr.lua | 2191 +++++++++++ lua/terrortown/lang/uk.lua | 2193 +++++++++++ lua/terrortown/lang/zh_hans.lua | 609 ++- lua/terrortown/lang/zh_tw.lua | 544 ++- lua/terrortown/menus/gamemode/addons.lua | 2 +- .../menus/gamemode/administration.lua | 2 +- .../administration/administration.lua | 194 +- .../menus/gamemode/administration/chat.lua | 102 +- .../gamemode/administration/entspawn.lua | 279 +- .../menus/gamemode/administration/hud.lua | 174 +- .../gamemode/administration/inventory.lua | 130 +- .../menus/gamemode/administration/karma.lua | 340 +- .../gamemode/administration/mapentities.lua | 196 +- .../gamemode/administration/playermodels.lua | 226 +- .../administration/playersettings.lua | 210 +- .../gamemode/administration/randomshop.lua | 66 +- .../gamemode/administration/rolelayering.lua | 191 +- .../menus/gamemode/administration/roles.lua | 227 +- .../gamemode/administration/roundsetup.lua | 321 +- .../menus/gamemode/administration/sprint.lua | 72 +- .../menus/gamemode/appearance/crosshair.lua | 207 +- .../gamemode/appearance/damageindicator.lua | 94 +- .../menus/gamemode/appearance/general.lua | 84 +- .../menus/gamemode/appearance/hudswitcher.lua | 320 +- .../menus/gamemode/appearance/interface.lua | 90 +- .../gamemode/appearance/miscellaneous.lua | 10 - .../menus/gamemode/appearance/performance.lua | 26 +- .../menus/gamemode/appearance/shop.lua | 90 +- .../menus/gamemode/appearance/targetid.lua | 16 +- .../menus/gamemode/appearance/vskin.lua | 58 +- .../menus/gamemode/base_gamemodemenu.lua | 120 +- .../base_gamemodesubmenu.lua | 30 +- .../menus/gamemode/bindings/bindings.lua | 75 +- lua/terrortown/menus/gamemode/changelog.lua | 49 +- .../gamemode/changelog/base_changelog.lua | 56 +- lua/terrortown/menus/gamemode/equipment.lua | 108 +- .../gamemode/equipment/base_equipment.lua | 293 +- .../menus/gamemode/gameplay/accessibility.lua | 34 + .../menus/gamemode/gameplay/avoidroles.lua | 27 - .../menus/gamemode/gameplay/general.lua | 67 +- .../gamemode/gameplay/voiceandvolume.lua | 48 + .../menus/gamemode/guide/equipment.lua | 22 +- .../menus/gamemode/guide/gameplay.lua | 22 +- lua/terrortown/menus/gamemode/guide/roles.lua | 22 +- .../menus/gamemode/language/language.lua | 60 +- lua/terrortown/menus/gamemode/legacy.lua | 127 +- .../menus/gamemode/legacy/base_legacy.lua | 42 +- lua/terrortown/menus/gamemode/roles.lua | 59 +- .../menus/gamemode/roles/base_roles.lua | 277 +- .../menus/gamemode/server_addons.lua | 4 +- lua/terrortown/menus/gamemode/shops.lua | 50 +- .../menus/gamemode/shops/base_shops.lua | 213 +- lua/terrortown/menus/score/base_scoremenu.lua | 4 +- lua/terrortown/menus/score/events.lua | 146 +- lua/terrortown/menus/score/info.lua | 714 ++-- lua/terrortown/vskin/dark.lua | 32 +- lua/terrortown/vskin/light.lua | 32 +- lua/ttt2/extensions/cvars.lua | 497 +-- lua/ttt2/extensions/debug.lua | 72 + lua/ttt2/extensions/draw.lua | 731 ++-- lua/ttt2/extensions/input.lua | 6 +- lua/ttt2/extensions/math.lua | 7 +- lua/ttt2/extensions/net.lua | 218 +- lua/ttt2/extensions/player.lua | 21 + lua/ttt2/extensions/render.lua | 26 + lua/ttt2/extensions/sql.lua | 60 +- lua/ttt2/extensions/string.lua | 46 +- lua/ttt2/extensions/surface.lua | 19 +- lua/ttt2/extensions/table.lua | 356 +- lua/ttt2/extensions/util.lua | 655 ++-- lua/ttt2/libraries/appearance.lua | 96 +- lua/ttt2/libraries/bind.lua | 456 ++- lua/ttt2/libraries/bodysearch.lua | 1211 ++++++ lua/ttt2/libraries/classbuilder.lua | 174 +- lua/ttt2/libraries/credits.lua | 242 +- lua/ttt2/libraries/database.lua | 2424 ++++++------ lua/ttt2/libraries/door.lua | 1235 +++--- lua/ttt2/libraries/drawsc.lua | 135 +- lua/ttt2/libraries/entity_outputs.lua | 58 +- lua/ttt2/libraries/entspawn.lua | 355 +- lua/ttt2/libraries/entspawnscript.lua | 1959 +++++----- lua/ttt2/libraries/eventdata.lua | 228 +- lua/ttt2/libraries/events.lua | 316 +- lua/ttt2/libraries/fastutf8.lua | 186 + lua/ttt2/libraries/fileloader.lua | 141 +- lua/ttt2/libraries/fonts.lua | 50 +- lua/ttt2/libraries/game_effects.lua | 132 + lua/ttt2/libraries/hudelements.lua | 267 +- lua/ttt2/libraries/huds.lua | 169 +- lua/ttt2/libraries/items.lua | 471 +-- lua/ttt2/libraries/keyhelp.lua | 720 ++++ lua/ttt2/libraries/map.lua | 529 +-- lua/ttt2/libraries/marker_vision.lua | 474 +++ lua/ttt2/libraries/marks.lua | 340 +- lua/ttt2/libraries/migrations.lua | 69 + lua/ttt2/libraries/none.lua | 70 +- lua/ttt2/libraries/orm.lua | 310 +- lua/ttt2/libraries/outline.lua | 219 +- lua/ttt2/libraries/playermodels.lua | 295 +- lua/ttt2/libraries/plyspawn.lua | 279 +- lua/ttt2/libraries/pon.lua | 773 ++-- lua/ttt2/libraries/roles.lua | 695 ++-- lua/ttt2/libraries/targetid.lua | 1113 +++--- lua/ttt2/libraries/thermalvision.lua | 361 +- lua/ttt2/libraries/vguihandler.lua | 179 +- lua/ttt2/libraries/vskin.lua | 196 +- lua/ttt2/libraries/weaponrenderer.lua | 432 +++ materials/vgui/ttt/perks/hud_disguiser.png | Bin 4878 -> 0 bytes models/weapons/v_ttt2_dna_scanner.dx80.vtx | Bin 21186 -> 0 bytes models/weapons/v_ttt2_dna_scanner.sw.vtx | Bin 21170 -> 0 bytes models/weapons/w_ttt2_dna_scanner.dx80.vtx | Bin 21186 -> 0 bytes models/weapons/w_ttt2_dna_scanner.sw.vtx | Bin 21170 -> 0 bytes stylua.toml | 10 + 852 files changed, 80135 insertions(+), 49432 deletions(-) create mode 100644 .git-blame-ignore-revs delete mode 100644 .github/stale.yml delete mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/lang-parser.yml create mode 100644 .luarc.json.example create mode 100644 .styluaignore rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/camera.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/camera.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/camera_n.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/camera_r.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen/arrow.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen/arrow.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen/background.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen/background.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen/check.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen/check.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen/circle.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen/circle.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen/fail.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/models/ttt2_dna_scanner/screen/fail.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_357.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_357.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_alyxgun.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_alyxgun.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_buckshot.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_buckshot.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_pistol.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_pistol.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_smg1.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_smg1.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_357.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_357.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_alyxgun.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_alyxgun.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_buckshot.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_buckshot.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_pistol.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_pistol.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_smg1.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_smg1.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/b-draw/icon_avatar_bot.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/b-draw/icon_avatar_bot.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/b-draw/icon_avatar_default.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/b-draw/icon_avatar_default.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dmgindicator/themes/default.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dmgindicator/themes/simple.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dmgindicator/themes/vanilla.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dnascanner/dna_hud.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dnascanner/dna_hud.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/base.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/base.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/base_overlay.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/base_overlay.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/hud_components/shadow_border.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/hud_components/shadow_border.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/icon_base.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/icon_base.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/icon_base_base.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/icon_base_base.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/icon_base_base_overlay.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/icon_base_base_overlay.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_det.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_det.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_disabled.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_disabled.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_inno.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_inno.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_no_team.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_no_team.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_none.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_none.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_shop_custom.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_shop_custom.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_shop_default.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_shop_default.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_traitor.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/roles/icon_traitor.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/sprite_base.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/sprite_base.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/sprite_base_overlay.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/dynamic/sprite_base_overlay.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/briefcase.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/coin.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/credits_default.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/credits_default.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/credits_zero.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/credits_zero.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/icon_global_limited.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/icon_global_limited.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/icon_info.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/icon_info.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/icon_team_limited.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/icon_team_limited.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/package.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/equip/reroll.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/hud_armor.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/hud_armor.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/hud_armor_reinforced.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/hud_armor_reinforced.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/hud_blocking_revival.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/hud_blocking_revival.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hud_health.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hud_health.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hud_health_low.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hud_health_low.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/ammo_drop.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/ammo_drop.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_global.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_global.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_team.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_team.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/flashlight.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/flashlight.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/item_disguiser.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/item_disguiser.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/leave_target.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/leave_target.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/hudhelp/lmb.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/hudhelp/lmb.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/mute.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/mute.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_next.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_next.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_prev.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_prev.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_random.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_random.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/pointer.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/pointer.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/possessing.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/possessing.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_back.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_back.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_dash.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_dash.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_front.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_front.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_jump.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_jump.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_left.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_left.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_right.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_right.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/quickchat.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/quickchat.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/hudhelp/rmb.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/hudhelp/rmb.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/save.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/save.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/settings.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/settings.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/shopping_role.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/shopping_role.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/showmore.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/showmore.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/third_person.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/third_person.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_global.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_global.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_team.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_team.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/weapon_drop.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/weapon_drop.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/huds/old_ttt/preview.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/huds/pure_skin/preview.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_armor.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_armor.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_beacon.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_beacon.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_confgrenade.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_confgrenade.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_credits.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_credits.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_credits_transparent.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_credits_transparent.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_decoy.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_decoy.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_drown.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_drown.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_firegrenade.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_firegrenade.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_floor.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_floor.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_magneto_stick.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_magneto_stick.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_nodrowningdmg.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_nodrowningdmg.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_noenergydmg.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_noenergydmg.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_noexplosiondmg.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_noexplosiondmg.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_nofalldmg.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_nofalldmg.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_nofiredmg.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_nofiredmg.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_nohazarddmg.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_nohazarddmg.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_nopropdmg.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_nopropdmg.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_smokegrenade.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_smokegrenade.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_speedrun.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/icon_speedrun.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_unarmed.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_unarmed.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_water_1.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_water_1.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_water_2.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_water_2.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_water_3.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/icon_water_3.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/indirect_confirmed.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/indirect_confirmed.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/beacon.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/beacon.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/c4.vmt rename materials/vgui/ttt/vskin/events/c4plant.vtf => gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/c4.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/radio.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/radio.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/missing_equip_icon.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/missing_equip_icon.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_armor.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_armor_reinforced.png (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_disguiser.png rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_nodrowningdmg.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_noenergydmg.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_noexplosiondmg.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_nofalldmg.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_nofiredmg.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_nohazarddmg.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_nopropdmg.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_radar.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/hud_speedrun.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/perks/old_ttt_bg.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/pickup/icon_ammo.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/pickup/icon_class.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/pickup/icon_extra.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/pickup/icon_heavy.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/pickup/icon_nades.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/pickup/icon_pistol.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/pickup/icon_special.png (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/revived.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/revived.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/score_logo_2.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/score_logo_2.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/score_logo_2_old.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/score_logo_2_old.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_carry.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_carry.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_class.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_class.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_extra.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_extra.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_heavy.vmt rename materials/vgui/ttt/tid/tid_big_weapon_assault.vtf => gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_heavy.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_melee.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_melee.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_nade.vmt rename materials/vgui/ttt/tid/tid_big_weapon_nade.vtf => gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_nade.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_pistol.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_pistol.vtf create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_special.vmt rename materials/vgui/ttt/tid/tid_big_weapon_special.vtf => gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_special.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_unarmed.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_unarmed.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/target_icon.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/target_icon.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_ammo.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_ammo.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_auto_close.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_auto_close.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_deagle.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_deagle.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_mac10.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_mac10.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_pistol.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_pistol.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_random.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_random.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_rifle.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_rifle.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_shotgun.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_ammo_shotgun.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_corpse.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_corpse.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_door.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_door.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_player.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_player.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_role_not_known.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_role_not_known.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_tbutton_pointer.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_tbutton_pointer.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_assault.vmt (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_assault.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_melee.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_melee.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_nade.vmt (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_nade.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_pistol.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_pistol.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_random.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_random.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_shotgun.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_shotgun.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_sniper.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_sniper.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_big_weapon_special.vmt (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_special.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_credits.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_credits.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_destructible.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_destructible.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_detective.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_detective.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_locked.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/tid/tid_locked.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_hand_filled.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_hand_filled.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_hand_line.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_hand_line.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_hand_outline.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_hand_outline.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_addondev.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_addondev.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_admin.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_admin.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_dev.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_dev.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_heroes.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_heroes.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_streamer.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_streamer.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_vip.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/ttt2_indicator_vip.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/card_added.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/card_added.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/card_removed.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/card_removed.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/base_event.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/base_event.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/bodyfound.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/bodyfound.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/c4disarm.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/c4disarm.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/c4explode.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/c4explode.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/c4plant.vmt (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4plant.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/creditfound.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/creditfound.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/finish.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/finish.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/game.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/game.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/kill.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/kill.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/respawn.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/respawn.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/rolechange.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/rolechange.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/selected.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/selected.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/spawn.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/events/spawn.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/vskin/helpscreen/accessibility.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/vskin/helpscreen/accessibility.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/addons.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/addons.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/administration.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/administration.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/appearance.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/appearance.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/bindings.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/bindings.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/changelog.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/changelog.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/equipment.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/equipment.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/gameplay.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/gameplay.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/guide.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/guide.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/language.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/language.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/legacy.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/legacy.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/roles.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/roles.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/shops.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/helpscreen/shops.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/vskin/helpscreen/voiceandvolume.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/vskin/helpscreen/voiceandvolume.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_back.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_back.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_close.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_close.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_collapse_closed.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_collapse_closed.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_collapse_opened.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_collapse_opened.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_disable.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_disable.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_hattable_no.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_hattable_no.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_hattable_yes.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_hattable_yes.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_headbox_no.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_headbox_no.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_headbox_yes.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_headbox_yes.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_reset.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/icon_reset.vtf (100%) create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/vskin/markers/builtin.vmt create mode 100644 gamemodes/terrortown/content/materials/vgui/ttt/vskin/markers/builtin.vtf rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/rhombus.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/rhombus.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/roundend/events.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/roundend/events.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/roundend/info.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/vskin/roundend/info.vtf (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/watching_icon.vmt (100%) rename {materials => gamemodes/terrortown/content/materials}/vgui/ttt/watching_icon.vtf (100%) rename {models => gamemodes/terrortown/content/models}/weapons/v_ttt2_dna_scanner.dx90.vtx (100%) rename {models => gamemodes/terrortown/content/models}/weapons/v_ttt2_dna_scanner.mdl (100%) rename {models => gamemodes/terrortown/content/models}/weapons/v_ttt2_dna_scanner.vvd (100%) rename {models => gamemodes/terrortown/content/models}/weapons/w_ttt2_dna_scanner.dx90.vtx (100%) rename {models => gamemodes/terrortown/content/models}/weapons/w_ttt2_dna_scanner.mdl (100%) rename {models => gamemodes/terrortown/content/models}/weapons/w_ttt2_dna_scanner.phy (100%) rename {models => gamemodes/terrortown/content/models}/weapons/w_ttt2_dna_scanner.vvd (100%) create mode 100644 gamemodes/terrortown/entities/effects/crimescene_dummy.lua create mode 100644 gamemodes/terrortown/entities/effects/crimescene_shot.lua create mode 100644 gamemodes/terrortown/entities/effects/pulse_sphere.lua create mode 100644 gamemodes/terrortown/entities/effects/teleport_beamdown.lua create mode 100644 gamemodes/terrortown/entities/effects/teleport_beamup.lua create mode 100644 gamemodes/terrortown/entities/entities/info_manipulate.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_base_placeable.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_basegrenade_proj.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_beacon.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_c4/cl_init.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_carry_handler.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_credit_adjust.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_cse_proj.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_damageowner.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_firegrenade_proj.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_flame.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_hat_deerstalker.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_knife_proj.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_map_settings.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_physhammer.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_smokegrenade_proj.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_spectator_spawn.lua create mode 100644 gamemodes/terrortown/entities/entities/ttt_weapon_check.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_beacon.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_c4.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_defuser.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_flaregun.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_health_station.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_phammer.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_push.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_radio.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_sipistol.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_stungun.lua create mode 100644 gamemodes/terrortown/entities/weapons/weapon_ttt_unarmed.lua create mode 100644 gamemodes/terrortown/gamemode/client/cl_marker_vision_data.lua create mode 100644 gamemodes/terrortown/gamemode/client/cl_shop.lua create mode 100644 gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dinfoitem_ttt2.lua create mode 100644 gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dprofilepanel_ttt2.lua create mode 100644 gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dtextentry_ttt2.lua create mode 100644 gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dweaponpreview_ttt2.lua create mode 100644 gamemodes/terrortown/gamemode/shared/sh_marker_vision_element.lua create mode 100644 gamemodes/terrortown/gamemode/shared/sh_playerclass.lua create mode 100644 gamemodes/terrortown/gamemode/shared/sh_shop.lua delete mode 100644 lua/autorun/server/ttt2_force_download.lua create mode 100644 lua/terrortown/lang/sv.lua create mode 100644 lua/terrortown/lang/tr.lua create mode 100644 lua/terrortown/lang/uk.lua delete mode 100644 lua/terrortown/menus/gamemode/appearance/miscellaneous.lua create mode 100644 lua/terrortown/menus/gamemode/gameplay/accessibility.lua delete mode 100644 lua/terrortown/menus/gamemode/gameplay/avoidroles.lua create mode 100644 lua/terrortown/menus/gamemode/gameplay/voiceandvolume.lua create mode 100644 lua/ttt2/extensions/debug.lua create mode 100644 lua/ttt2/extensions/player.lua create mode 100644 lua/ttt2/extensions/render.lua create mode 100644 lua/ttt2/libraries/bodysearch.lua create mode 100644 lua/ttt2/libraries/fastutf8.lua create mode 100644 lua/ttt2/libraries/game_effects.lua create mode 100644 lua/ttt2/libraries/keyhelp.lua create mode 100644 lua/ttt2/libraries/marker_vision.lua create mode 100644 lua/ttt2/libraries/migrations.lua create mode 100644 lua/ttt2/libraries/weaponrenderer.lua delete mode 100644 materials/vgui/ttt/perks/hud_disguiser.png delete mode 100644 models/weapons/v_ttt2_dna_scanner.dx80.vtx delete mode 100644 models/weapons/v_ttt2_dna_scanner.sw.vtx delete mode 100644 models/weapons/w_ttt2_dna_scanner.dx80.vtx delete mode 100644 models/weapons/w_ttt2_dna_scanner.sw.vtx create mode 100644 stylua.toml diff --git a/.editorconfig b/.editorconfig index 13a0fc733..bcaf6b26b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,8 +4,9 @@ root = true charset = utf-8 end_of_line = lf insert_final_newline = true -indent_style = tab +indent_style = space trim_trailing_whitespace = true +max_line_length = 100 [*.md] trim_trailing_whitespace = false diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..f9a95c10a --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,4 @@ +# .git-blame-ignore-revs +# +# Run stylua formatter on entire codebase +df7bf4216cf31d680c0d95bd576e46193ce2dcd6 diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 066b2d920..77eebe1f9 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,14 +4,14 @@ about: Suggest an idea for this project --- -**Is your feature request related to a problem? Please describe.** +## Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -**Describe the solution you'd like** +## Describe the solution you'd like A clear and concise description of what you want to happen. -**Describe alternatives you've considered** +## Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered. -**Additional context** +## Additional context Add any other context or screenshots about the feature request here. diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 43aced167..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,16 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - accepted -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml deleted file mode 100644 index 1d042aa27..000000000 --- a/.github/workflows/cd.yml +++ /dev/null @@ -1,34 +0,0 @@ -# name: CD -# -# # Run on Pull Requests to master and on manual interaction -# on: -# pull_request_target: -# branches: [ master ] -# workflow_dispatch: -# -# jobs: -# deploy-to-dev: -# runs-on: ubuntu-latest -# environment: -# name: histalek-dev-env -# # Sadly a 'steam://connect/' url is not allowed by github -# # Current workaround is a 'redirect webpage' on GitHub Pages -# # Ref. https://github.com/TTT-2/ttt-2.github.io -# url: https://ttt-2.github.io/redirect/ttt2-dev-env -# permissions: -# contents: read -# -# steps: -# - uses: actions/checkout@v3 -# -# - name: rsync deployments -# uses: burnett01/rsync-deployments@5.2 -# with: -# switches: -avzr --delete -# # The path should end in a directory in the garrysmod addons directory -# # e.g. /garrysmod/addons/ttt2 -# remote_path: ${{ secrets.DEPLOY_PATH }} -# remote_host: ${{ secrets.DEPLOY_HOST }} -# remote_port: ${{ secrets.DEPLOY_PORT }} -# remote_user: ${{ secrets.DEPLOY_USER }} -# remote_key: ${{ secrets.DEPLOY_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b1e26dd1..ebd52cb14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,52 +1,60 @@ name: CI env: - GLUALINT_VERSION: 1.24.2 - NEODOC_VERSION: 0.1.4 + GLUALINT_VERSION: 1.26.0 + NEODOC_VERSION: 0.1.6 + STYLUA_VERSION: 0.20.0 # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] -# A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: lint: - # The type of runner that the job will run on runs-on: ubuntu-latest - # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Download & extract glualint - run: | - wget -c https://github.com/FPtje/GLuaFixer/releases/download/${GLUALINT_VERSION}/glualint-${GLUALINT_VERSION}-x86_64-linux.zip -O glualint.zip - unzip -u glualint.zip - rm glualint.zip + - name: Download & extract glualint + run: | + wget -c https://github.com/FPtje/GLuaFixer/releases/download/${GLUALINT_VERSION}/glualint-${GLUALINT_VERSION}-x86_64-linux.zip -O glualint.zip + unzip -u glualint.zip + rm glualint.zip - - name: Check code with glualint - run: ./glualint . + - name: Check code with glualint + run: ./glualint . + + style: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: JohnnyMorganz/stylua-action@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: ${{ env.STYLUA_VERSION }} + args: --check . doc-check: - # The type of runner that the job will run on runs-on: ubuntu-latest - # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Setup .NET Core - run: sudo apt-get install -y mono-complete + - name: Setup .NET Core + run: sudo apt-get install -y mono-complete - - name: Download & extract neodoc - run: | - wget -c https://github.com/TTT-2/NeoDoc/releases/download/v${NEODOC_VERSION}/NeoDoc-v${NEODOC_VERSION}.zip -O neodoc.zip - unzip -u neodoc.zip - rm neodoc.zip + - name: Download & extract neodoc + run: | + wget -c https://github.com/TTT-2/NeoDoc/releases/download/v${NEODOC_VERSION}/NeoDoc-v${NEODOC_VERSION}.zip -O neodoc.zip + unzip -u neodoc.zip + rm neodoc.zip - - name: Check code with neodoc - run: mono NeoDoc.exe . + - name: Check code with neodoc + run: mono NeoDoc.exe . diff --git a/.github/workflows/lang-parser.yml b/.github/workflows/lang-parser.yml new file mode 100644 index 000000000..8e015bcde --- /dev/null +++ b/.github/workflows/lang-parser.yml @@ -0,0 +1,61 @@ +name: LangParser + +permissions: + contents: write + pull-requests: write + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + update-language-files: + # guard against running on forks as they are probably not setup for this + if: github.repository_owner == 'TTT-2' + + runs-on: ubuntu-latest + + steps: + - name: Checkout ttt2 repo + uses: actions/checkout@v4 + with: + path: ttt2 + + - name: Checkout language parser repo + uses: actions/checkout@v4 + with: + repository: TTT-2/ttt2-language_parser + path: ttt2-language_parser + + - name: run language parser + run: python ttt2-language_parser/parse.py --in "$GITHUB_WORKSPACE/ttt2/lua/terrortown/lang" --out "$GITHUB_WORKSPACE/ttt2/lua/terrortown/lang" --base en --ignore chef + + - name: generate app token + uses: actions/create-github-app-token@v1 + id: generate-token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Create Pull Request + id: ci-update-language-files + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ steps.generate-token.outputs.token }} + path: ttt2 + add-paths: "lua/terrortown/lang/*.lua" + commit-message: Update language files + branch: ci-update-language-files + title: "Language: Update language files" + body: | + Auto-generated by github actions and the [ttt2-language_parser](https://github.com/TTT-2/ttt2-language_parser) + labels: skip-changelog + + - name: Setup PR automerge and squash + run: | + cd ttt2 + gh pr merge ci-update-language-files --auto --squash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 839d15c01..cf05f7ee9 100644 --- a/.gitignore +++ b/.gitignore @@ -55,5 +55,6 @@ Temporary Items todo.txt addon.json *.gma +.luarc.json ttt2.jpg diff --git a/.luarc.json.example b/.luarc.json.example new file mode 100644 index 000000000..a79df6aed --- /dev/null +++ b/.luarc.json.example @@ -0,0 +1,32 @@ +{ + "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", + "diagnostics.disable": [ + "duplicate-set-field", + "unused-local", + "lowercase-global", + "inject-field", + "undefined-field", + "param-type-mismatch", + "cast-local-type" + ], + "diagnostics.globals": [ + "GM", + "BaseClass", + "CLGAMEMODEMENU", + "CLGAMEMODESUBMENU", + "CLSCOREMENU", + "EVENT", + "TEXFILTER" + ], + "diagnostics.ignoredFiles": "Enable", + "format.enable": false, + "runtime.special": { + "include": "require", + "includeCS": "require" + }, + "runtime.version": "Lua 5.1", + "runtime.nonstandardSymbol": [ + "continue" + ], + "workspace.checkThirdParty": false +} diff --git a/.styluaignore b/.styluaignore new file mode 100644 index 000000000..9596620f5 --- /dev/null +++ b/.styluaignore @@ -0,0 +1 @@ +lua/terrortown/lang/*.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a4982542..9cd1c7388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,363 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel ## Unreleased ### Added + +### Changed + +### Fixed + +## [v0.13.0b](https://github.com/TTT-2/TTT2/tree/v0.13.0b) (2024-02-21) + +### Added + +- Added migrations between TTT2-versions, some breaking changes could now be migrated instead +- Added a new markerVision module that adds information to a specific point in space to replace the old C4 radar; it is currently used by these builtin weapons (by @TimGoll) + - C4 + - Radio + - Beacon +- Binoculars now retain search progress if interrupted. Progress decays based on time since last observed (by @EntranceJew) +- Reworked the way the player camera is handled (by @TimGoll) + - Added FOV change on speed change + - Added view bobbing on walking, swimming, falling and strafing + - Added convars to disable those changes +- Added `draw.Arc` and `draw.ShadowedArc` from TTTC to TTT2 to draw arcs (by @TimGoll und @Alf21) +- Added possibility to cache and remove items, similar to how it is already possible with weapons with `CacheAndStripItems` (by @TimGoll) +- Added an option for weapons to hide the pickup notification by setting `SWEP.silentPickup` to `true` (by @TimGoll) +- Added `TTT2FetchAvatar` hook for intercepting avatar URIs (by @EntranceJew) +- Added `draw.DropCacheAvatar` to allow destroying and refreshing an existing avatar, so bots can intercept avatar requests and circumvent the limited unique SteamID64s they're given (by @EntranceJew) +- `weapon_tttbase` changes to correct non-looping animations which affected ADS scoping (by @EntranceJew) + - Added `SWEP.IdleAnim` to allow specifying an idle animation. + - Added `SWEP.idleResetFix` to allow the animations for CS:S weapons to automatically be returned to an idle position. + - Added `SWEP.ShowDefaultViewModel` to prevent a weapon from drawing a ViewModel when set to `false` at all without FOV hacks or Deploy code which has no effect. +- Icon for gameplay menu +- Icon for accessibility menu +- Icon for `Voice & Volume` menu +- Added a new vgui element: `DWeaponPreview_TTT2` to render a player with their equipped weapon (by @TimGoll) + - Supports any normal weapon that has a `.HoldType` and a `.WorldModel` + - Supports any weapon that is made with the SWEP Construction Kit (boomerang, melonmine, ...) or made for our custom world model renderer +- Made beacon model and icon unique from decoy (by @EntranceJew) +- Added `SWEP:ClearHUDHelp()` to allow blanking the help text, for dynamically updating help text on equipment (by @EntranceJew) +- Added custom world and view models to some builtin weapons (by @TimGoll) + - added for: radio, beacon, decoy, binoculars, visualizer +- Added support for easy addition of custom view and world models (by @TimGoll) + - Added `AddCustomViewModel` to add custom view models + - Added `AddCustomWorldModel` to add custom world models + - Added an automatic fix for badly coded addons that break the view model fingers +- Added `ttt_base_placeable` entity that is used to handle any placeable / destroyable entity (by @TimGoll) + - moved `ttt_c4`, `ttt_health_station`, `ttt_beacon`, `ttt_decoy`, `ttt_radio` and `ttt_cse_proj` to that base + - also handles pickup of those entities +- Throwables (grenades) now have a `:GetPullTime()` accessor (by @EntranceJew) +- Throwables (grenades) show UI for the amount of time remaining before detonation (fuse time) (by @EntranceJew) +- UI for grenade throw arcs from [colemclaren's TTT fork](https://github.com/colemclaren/ttt/blob/master/addons/moat_addons/lua/weapons/weapon_tttbasegrenade.lua#L293-L353) (integrated by @EntranceJew) +- `gameEffects` library for global effects that are useful, such as starting fires (by @EntranceJew) +- Added weapon pickup sounds when picking up weapons manually (by @TimGoll) + +### Changed + +- Refactored client shop logic into separate shop-class (by @ZenBre4ker) + - Enabled shared shop class to buy and check equipment + - Removed third argument of `TTT2CanOrderEquipment`-Hook, no message is outputted anymore +- dframe_ttt2 panels can now manually enable bindings while they are open (by @ZenBre4ker) +- Binoculars now have a world model that isn't paper towels (by @EntranceJew) +- Decreased shooting accuracy while sprinting or in air (by @TimGoll) +- A player whose weapons are stripped and cached will keep `weapon_ttt_unarmed` which means they keep their crosshair (by @TimGoll) +- Updated the German localization file (by @NickCloudAT) +- Updated the Turkish localization file (by @NovaDiablox) +- Grenades have icons +- Brought `C4`, `defuser`, `flaregun`, `health_station`, `radio` weapons down from upstream (by @a7f3) +- Updated help text for `C4`, `defuser`, `flaregun`, `health_station`, `radio`, `knife`, `phammer`, `push`, and `zm_carry` weapons (by @a7f3) +- Brought down the `EFFECT`s: `crimescene_dummy`, `crimescene_shot`, `pulse_sphere`, `teleport_beamdown`, `teleport_beamup` +- Brought down the `ENT`s: `ttt_basegrenade_proj`, `ttt_carry_handler` (unused), `ttt_firegrenade_proj`, `ttt_smokegrenade_proj`, `ttt_weapon_check` +- Brought down the `SWEP`: `weapon_ttt_stungun` +- Brought down the menu for arming/defusing C4 +- Updated and improved Simplified Chinese translation (by @sbzlzh and @TheOnly8Z) +- Improved Simplified Chinese translation(by @TEGTainFan) +- Consolidated hat logic +- Player role selection logic uses `Player:CanSelectRole()` now instead of duplicating logic +- Role avoidance is no longer an option +- All `builtin` weapons can now be configured to drop via `Edit Equipment` (by @EntranceJew) +- Removed redundant checks outside of `SWEP:DrawHelp`, protected only `SWEP:DrawHelp` +- Spectator name labels now use a skin font and scaling (by @EntranceJew) +- The built-in radar now displays distances in meters (by @TimGoll) +- Converted `ttt_ragdoll_pinning` and `ttt_ragdoll_pinning_innocents` into per-role permissions. +- Magneto stick now allows right-clicking to instantly drop something, while left-clicking still releases/throws it. +- Magneto stick now shows tooltips respective to its current state. +- Scoreboard shows non-policing detective results, in sync with the miniscoreboard (by @EntranceJew) +- `ttt_flame` is visible while it is moving (by @EntranceJew) +- `ttt_flame`'s hurtbox is more accurate to its visuals (by @EntranceJew) +- The built-in DNA scanner now displays distances in meters (by @TimGoll) +- Noisy prints are now gated behind various levels of `developer` convar (by @EntranceJew) +- Any warnings developers should fix will now print with stack traces (by @EntranceJew) +- Changed the way the role overhead icon is rendered (by @TimGoll) + - It now tracks the players head position + - Rendering order is based on distance, no more weird visual glitches + - Hidden when observing a player in first person view +- Your own spectator nametag will not display when looking directly up in post-round (by @EntranceJew) +- Made sure the last weapon is selected by default if the current weapon is removed; overwrite `OnRemove` to prevent that (by @TimGoll) +- Changed the way weapon icon caching is working to make sure all weapons always have a cached icon material (by @TimGoll) + +### Fixed + +- Fixed database now properly saving boolean `false` values (by @ZenBre4ker) +- Fixed cached weapons not being selected after giving them back to the owner (by @TimGoll) +- The roundendscreen can now be closed with the correct Binding (by @ZenBre4ker) +- Fixed last seen player being wrongly visible for every search instead of only public policing role search (by @TimGoll) +- Fixed the crosshair being offcenter on some UI scales (by @TimGoll) +- Fixed to wrong line calculations for wrapped text (by @NickCloudAT) +- Fixed marks library having self zfailing and color issues (by @WardenPotato) +- Fixed `IsPlayer` failing if a non-entity is passed to it (by @TimGoll) +- Fixed draw.Arc when `gmod_mcore_test` is set to 1 (by @WardenPotato) +- Fixed weapon help box width for wide bindings with short descriptions (by @TimGoll) +- Fixed `GM:TTTBodySearchPopulate` using the wrong data variable (by @TimGoll) +- Fixed font initialization to not trip engine font fallback behavior (by @EntranceJew) +- Fixed the decoy producing a wrong colored icon for other teams (by @NickCloudAT) +- Fixed the scoreboard being stuck open sometimes if the inflictor was no weapon (by @TimGoll) +- Fixed door health displaying as a humongous string of decimals (by @EntranceJew) +- Fixed weapons that use the wrong weapon base from throwing errors in the F1 menu (by @TimGoll) + +### Removed + +- Removed some crosshair related convars and replaced them with other ones, see the crosshair settings menu for details +- Removed DX8/SW models that aren't used +- Removed the convar `ttt_damage_own_healthstation` as it was inconsistent and probably unused as well +- Removed `ttt_fire_fallback`, there's no situation where the fire shouldn't draw anymore. +- Removed `resource.AddFile` calls, server operators should use the workshop version or manually bundle loose files. + +### Breaking Changes + +- Moved global shared `EquipmentIsBuyable(tbl, ply)` to `shop.CanBuyEquipment(ply, equipmentName)` + - Returned text and result are now replaced by a statusCode +- No more `plymeta:GetAvoidRole(role)` or `plymeta:GetAvoidDetective()` +- Moved global `TEAMBUYTABLE` to `shop.teamBuyTable` and separated `BUYTABLE` into `shop.buyTable` and `shop.globalBuyTable` + - Use new Accessors `shop.IsBoughtFor(ply, equipmentName)`, `shop.IsGlobalBought(equipmentName)` and `shop.IsTeamBoughtFor(ply, equipmentName)` + - Use new Setter `shop.SetEquipmentBought(ply, equipmentName)`, `shop.SetEquipmentGlobalBought(equipmentName)` and `shop.SetEquipmentTeamBought(ply, equipmentName)` + +## [v0.12.3b](https://github.com/TTT-2/TTT2/tree/v0.12.3b) (2024-01-07) + +### Added + +- Added some missing vanilla TTT entities into TTT2 +- Added debug.print(message) + - This puts quotation marks around print statements + - Can handle single values or a sequential table to be printed + - Can handle `nil` entries in a nearly sequential table +- Added new hooks `TTT2BeaconDetectPlayer` and `TTT2BeaconDeathNotify` to allow preventing / overriding a beacon's player detection & alerts (by @spanospy) +- Added indentation to subsettings in F1 menu (by @TimGoll) + +### Changed + +- Updated the Turkish localization file (by @NovaDiablox) +- Crosshair now spreads while sprinting instead of being hidden +- Keyhelp and weapon HUD Help now use the global scale factor + +### Fixed + +- Fixed targetID hints for old addons now correctly working for all entities +- Fixed visualizer having pickup hint even though player is unable to pick up +- Targetid wasn't showing named corpse's role, information which was already present on the scoreboard (by @EntranceJew) +- Damage Scaling now has a help description +- Fixed the database module setting a global variable called `callback` which breaks addons such as PointShop2 +- Fixed voicechat keybinds being shown even if voice is disabled +- Coerced ammo types to lowercase for better matching in HUD +- The binocular zoom now uses a DataTable that is not already used by its weaponbase +- Fixed round scoreboard tooltips not being wide enough for their strings (by @EntranceJew) +- Errors when looking at a player's corpse that disconnected (by @EntranceJew) +- Fixed `TTT2FinishedLoading` hook not called on server on hot reload (by @TimGoll) +- Shopeditor now correctly shows resetted and default values + +## [v0.12.2b](https://github.com/TTT-2/TTT2/tree/v0.12.2b) (2023-12-20) + +### Added + +- Added the beacon back into TTT2, an equipment that was disabled long ago in base TTT + - Can only be bought by policing roles + - Creates a wallhack in a sphere around it, which is visible to everyone +- Added recognizable badge for `builtin` equipment and roles (by @EntranceJew) + - Buy Equipment menu has `builtin` indicators, replacing the `(C)` custom marker decorating a majority of equipment + - `F1 > Edit Equipment` now has `builtin` indicators on equipment + - Added tooltip to `F1 > Edit Equipment` menu with the equipment's class name. + - `F1 > Role Settings` now has `builtin` indicators for roles + - `F1 > Edit Shops` now has `builtin` indicators for roles + +### Changed + +- Updated the Turkish localization file (by @NovaDiablox) +- Radio can now only be picked up by placer +- Radar now clears existing waypoints when removed or on changing role (by @EntranceJew) +- Comboboxes can now handle numbers and strings as values + - Defaults work now with numbers + - OnChange-Callback is called with the correct type for ConVars +- AFK/Idle timer now reads inputs instead of angle/pos checks to circumvent cheese + +### Fixed + +- Binoculars scan no longer gets interrupted when changing zoom level +- Fixed missing water level icon breaking scoreboard +- DNA Tester works now with more than one fingerprint on a weapon +- TraitorButton config files should now actually work +- Translation strings not rendering on detective's body search mode combobox +- C4 defusal prompt now suggesting the right key +- Disable to unscope from weapons without ironsights +- Fixed typo preventing targetid from showing role icons correctly +- Mitigated issue with CTakeDamageInfo becoming ephemeral outside their hook of origin +- `ttt_game_text` can now properly send to "All except traitors", as described. +- Fixed corpses not listing their kills +- Comboboxes now show correct values for database driven entries +- Database-Callbacks are now called with the correct valuetype + +## [v0.12.1b](https://github.com/TTT-2/TTT2/tree/v0.12.1b) (2023-12-12) + +### Added + +- Added a new `fastutf8` library that provides faster utf8 functions (added by @saibotk, created by @blitmap) +- Added new hooks: `TTT2MapRegisterWeaponSpawns`, `TTT2MapRegisterAmmoSpawns`, `TTT2MapRegisterPlayerSpawns` to allow converting a wider variety of source map ports (by @EntranceJew) + +### Fixed + +- Fixed the UI being unable to handle wrapping text with non-utf8 languages that do not use ASCII whitespaces (by @TimGoll & @saibotk) +- Fixed ttt_game_text not working due to a refactor +- Fixed dete call HUD being invisible +- Fixed edgecase where undefined killer angle or pos were accessed +- Fixed fallback ammo icon missing +- Fixed a null entity error in the miniscoreboard +- Fixed missing bodysearch information if victim was killed without leaving a trace caused by a weapon hit +- Fixed "body_confirm" MSTACK noise by batching all the kills from a body into one message. (by @EntranceJew) +- Fixed "body_confirm" message sending before corpse confirmation message. + +## [v0.12.0b](https://github.com/TTT-2/TTT2/tree/v0.12.0b) (2023-12-11) + +### Added + +- Added the ability to edit slider numbers directly via an input field by clicking on the number (by @NickCloudAT) +- Added a new way to alter player volume separately from the scoreboard (by @EntranceJew): + - `VOICE.(Get/Set)PreferredPlayerVoiceVolume` for setting the voice volume instead of `Player:SetVoiceVolumeScale` + - `VOICE.(Get/Set)PreferredPlayerVoiceMute` for setting the voice mute instead of `Player:SetMuted` + - `VOICE.UpdatePlayerVoiceVolume` commits / updates the voice setting according to player preferences +- Added client submenu options for clients to change audio settings under `F1 > Gameplay > General` (by @EntranceJew): + - Added a convar `ttt2_voice_scaling` to control voice volume scaling, options like "power4" or "log" cause the volume scaling to have a greater perceptual impact between discrete volume settings. + - Added convars `ttt2_voice_duck_spectator` and `ttt2_voice_duck_spectator_amount` to lower spectator voice volume automatically. + - A value of `0.13` ducks someone's volume at 90% down to effectively 78%, according to the client's scaling mode. +- Added the option for `DButtonTTT2` to have an icon next to the title (by @TimGoll) +- Added a cached equipment item icon to its table as `.iconMaterial` (by @TimGoll) +- Added a new `bodysearch` library that handles the search (by @TimGoll) +- Completely reworked the body search UI (by @TimGoll) + - new UI that fits the UI rework + - added player model to UI + - highlighted player role and team in the UI + - redesigned data list so that everything can be seen without clicking through a list + - added more details to list like: water level, ground type, kill distance, kill direction, hit group, last damage amount + - The UI is now more responsive, it is updated when the server changes states on the body and timers are updated live in the UI +- Added that the healthbar will pulsate when below 25% health. Toggleable in F1 Menu (by @NickCloudAT) +- Added new menu section in F1 menu under `Appearance > Hud Switcher` for HudElement based features (by @NickCloudAT) +- Brought in code files for `ttt_hat_deerstalker`, `weapon_ttt_phammer`, `ttt_flame`, and `weapon_ttt_push`. +- Translated all strings still needed to german (by @NickCloudAT) +- Added new sidebar information, when the scoreboard is open (by @TimGoll) +- Added keybinding information to the bottom of the screen (by @TimGoll) + - Can be disabled in Appearance->Interface + - Shows binding name when scoreboard is opened +- Added option to render rotated text on screen (by @TimGoll) +- Added `TTT2GiveFoundCredits` hook for preventing / overriding the transfer of credits from a body to a player (by @Spanospy) +- Added Ukrainian translation from base TTT (by @ErickMaksimets) +- Added Swedish translation from base TTT (by @Kefta) +- Added Turkish translation (by @NovaDiablox) +- Added `ttt_dropclip` to drop loaded ammo from your active weapon. (by @wgetJane, implemented by @EntranceJew) +- Added window flash and noise to alert players they're being revived (by @EntranceJew) +- Added sql database access to panel elements + - `DNumSliderTTT2`, `DCheckBoxLabelTTT2`, `DComboBoxTTT2` +- Added dashing to propspec (by @TimGoll) +- Added new functions to database module + - `database.SetDefaultValuesFromItem(accessName, itemName, item)` + +### Changed + +- Changed sprint stamina to also consume while in air +- Updated Simplified Chinese and Traditional Chinese localization files (by @sbzlzh): + - Add the missing `L.c4_disarm_t` translation in C4 + - Remove redundant string translations and spaces + - Added all new translation strings +- Updated file code to read from `data_static` as fallback in new location allowed in .gma (by @EntranceJew) +- Scoreboard now sets preferred player volume and mute state in client's new `ttt2_voice` table (by @EntranceJew) + - Keyed by steamid64, making it more reliable than UniqueID or the per-session mute and volume levels. +- Changed the body search convars and reworked the UI accordingly (by @TimGoll) + - Moved `ttt2_confirm_detective_only` and `ttt2_inspect_detective_only` to a new covar: `ttt2_inspect_confirm_mode` + - mode 0: default mode, normal TTT. Everyone can search and identify corpses. However now a player has to be confirmed first to take credits + - mode 1: everyone can see information, but only public policing roles can actually confirm bodies + - mode 2: only public policing roles can see informatiom. They have to confirm bodies so that other people are able to see this information as well + - to comply with mode 1 and 2 now everyone is able to see in the targetID if a player was searched by a public policing role +- renamed `search_result` to `bodySearchResult` which contains the search result data +- changed the credit text color from yellow to gold (by @TimGoll) +- Updated the disguiser to make it more clear in the HUD if it is enabled or not +- Updated the equipment HUD help boxes in a new style and added missing help boxes (by @TimGoll) +- Changed LMB press behavior in observer mode to iterate backwards through player list instead of slecting a random player (by @TimGoll) +- Improved translation of some Simplified Chinese strings (by @TheOnly8Z) +- Dropping ammo with `ttt_dropammo` drops from reserve ammo instead of your active weapon's clip (by @wgetJane, implemented by @EntranceJew) +- Added item name for `ttt_hat_deerstalker` (by @EntranceJew) +- Changed syncing of database module to use whole tables instead of custom method +- Replaced equipmenteditor syncing with database module +- Replaced internal equipment syncing with database module +- Moved reset buttons onto the left (by @a7f3) +- Added ammo icons to the weapon switch HUD and player status HUD elements (by @EntranceJew) +- Changed the disguiser icon to be more fitting (by @TimGoll) + +### Fixed + +- Fixed prediction of the sprinting system, for high ping situations (by @saibotk, thanks to @wgetJane) +- Fixed removing the convar change callback in `DComboboxTTT2`, `DCheckBoxLabelTTT2`, `DNumSliderTTT2` (by @saibotk) +- Multiple internal fixes + - biggest teamkiller award should now work + - item model caching should now work properly + - role info popup for traitors should now show teammembers again if the traitor shop is disabled + - `pon` and `table` libraries got a small fix respectively + - the shop and roleselection now reference `roles.INNOCENT` instead of the removed `INNOCENT` global, same for `TRAITOR` and `DETECTIVE` + - Fixed wrong translation % in F1-Menu when changing language (by @NickCloudAT) +- Fixed disguiser breaking UI on hot reload (by @TimGoll) +- Fixed blurred box rendering for boxes not starting at `0,0` (by @TimGoll) +- Fixed spectated entity not being reset properly which can cause issues (by @TimGoll) +- Optimized allocations by using global Vector / Angle when possible. +- Fixed the dynamic armor damage calculation being wrong when damage can only get partially reduced +- Fixed propspec inputs behaving sometimes unexpectedly (by @TimGoll) +- Fixed ComboBoxes not working with integer values (by @NickCloudAT) +- net.SendStream() can now also handle tables larger than 256kB, which exceeded the maximum net receive buffer +- Fixed nil value of SetValue in `DNumSliderTTT2` , `DCheckBoxLabelTTT2`. And fix nil value for boxCache[name] in `PlayerModels` (by @sbzlzh) +- Prevent weapon_tttbase Lua errors with NPCs (by @BuzzHaddaBig in base TTT) +- Fix miniscoreboard HUD from showing confirmed players that switched to spectator as having been revived (by @EntranceJew) + +### Deprecated + +- Deprecated `AccessorFuncDT()`, Addons should remove the function call and replace `DTVar()` calls with `NetworkVar()` + +### Removed + +- Removed `ttt_confirm_death` and `ttt_call_detective` as they are now handled via proper net messages +- Removed spectator texts from the UI in favor of the new key binding information (by @TimGoll) +- Removed double tap sprinting, for easier prediction handling (by @saibotk) +- Removed explicit "Sprint" key bind, please use the GMod native sprint key binding (by @saibotk) +- Removed unused clientside `Player.preventSprint` flag (by @saibotk) + +## [v0.11.7b](https://github.com/TTT-2/TTT2/tree/v0.11.7b) (2023-08-27) + +### Added + - Added a new font in default_skin.lua to fit the localization (by @Satton2) +- Fixed knife death effect being permanently applied on every following death +- Added `PANEL:MakeTextEntry(data)` to `DFormTTT2` for strings or string-backed cvars (by @EntranceJew) +- Allow admin spectators to enter "Spawn Edit" mode. (by @EntranceJew) +- Added cvar `ttt2_bots_lock_on_death` (default: 0) to prevent bots from causing log-spam while wandering as spectators. (by @EntranceJew) +- Added `TTT2ModifyFinalRoles` hook for last minute opportunity to override role distribution prior to them being announced for the first time (by @EntranceJew) +- `weapon_tttbase`: + - Added `SWEP:ShouldRemove` to facilitate intercepting `SWEP:Remove` (by @EntranceJew) + - Added `SWEP.damageScaling` for weapons that utilize `ShootBullet` (by @EntranceJew) +- Edit Equipment Menu + - `AllowDrop` can now be overridden per-weapon (by @EntranceJew) + - `Kind` can now be overridden per-weapon (by @EntranceJew) + - `overrideDropOnDeath` now permits forcing weapons to be dropped instead of removed on death (by @EntranceJew) + - "Damage Scaling" editable under "Balance Settings" (by @EntranceJew) +- `vgui.CreateTTT2Form` passes the name on so that it can be accessed via `Panel:GetName()` (by @EntranceJew) +- Added two GAMEMODE hooks to provide the ability for additional addons to extend role/equipment menus. + - `GM:TTT2OnEquipmentAddToSettingsMenu(equipment, parent)` + - Called after `ITEM:AddToSettingsMenu(parent)`. + - `GM:TTT2OnRoleAddToSettingsMenu(role, parent)` + - Called after `ROLE:AddToSettingsMenu(parent)` ### Changed @@ -25,6 +381,7 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel - Simplified Chinese and Traditional Chinese localization updates (by @sbzlzh): - Update Simplified Chinese Translation - Improve translation (by @TheOnly8Z) +- Localization parameters for `{walkkey} + {usekey}` prompts made into the predominant style. ### Fixed @@ -35,6 +392,10 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel - Fixed an issue in `table.GetEqualEntryKeys` when nil is provided instead of a table. (by @sbzlzh): - This fixes spawn problems on maps with invalid spawn points - This fixes errors in the F1 Menu language selection +- Fixed the check for dynamic armor being inverted (`1` disabled it, `0` enabled it) +- Fixed two unmatched ConVars in performance menu (by @NickCloudAT) +- Fixed Round End Scoreboard (Round Begin) error if a player disconnected while round with no score events (by @NickCloudAT) +- Fixed behavior of `entspawn.SpawnRandomAmmo` to produce non-deagle ammo. (by @NickCloudAT, mostly) ## [v0.11.6b](https://github.com/TTT-2/TTT2/tree/v0.11.6b) (2022-09-25) @@ -120,7 +481,7 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel ### Added -- Added four new Karma multipliers as role variables. They are applied **after** all other Karma calculations are done_ +- Added four new Karma multipliers as role variables. They are applied **after** all other Karma calculations are done - `ROLE.karma.teamKillPenaltyMultiplier`: The multiplier that is used to calculate the Karma penalty for a team kill - `ROLE.karma.teamHurtPenaltyMultiplier`: The multiplier that is used to calculate the Karma penalty for team damage - `ROLE.karma.enemyKillBonusMultiplier`: The multiplier that is used to calculate the Karma given to the killer if a player from an enemy team is killed @@ -587,8 +948,8 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel ### Added - Added new convars to change the behavior of the armor - - `ttt_item_armor_block_headshots (default: 0)` - Block headshots. Thanks @TheNickSkater - - `ttt_item_armor_block_blastdmg (default: 0)` - Block blast damage. Thanks @Pustekuchen98 + - `ttt_item_armor_block_headshots (default: 0)` - Block headshots. Thanks @TheNickSkater + - `ttt_item_armor_block_blastdmg (default: 0)` - Block blast damage. Thanks @Pustekuchen98 - Added essential items: 8 different types of items that are often used in other addons. You can remove them from the shop if you don't like them. - Added server proxy for `EPOP:AddMessage()` - Added `PrintMessage` overwrites so this function now uses TTT2 systems diff --git a/README.md b/README.md index f6ace1490..379434b54 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,84 @@ -# TTT2 - Trouble in Terrorist Town 2 (Advanced Update) -![CI](https://github.com/TTT-2/TTT2/workflows/CI/badge.svg?branch=master) -[![Steam Subscriptions](https://img.shields.io/steam/subscriptions/1357204556)](https://steamcommunity.com/sharedfiles/filedetails/?id=1357204556) -[![Steam Downloads](https://img.shields.io/steam/downloads/1357204556)](https://steamcommunity.com/sharedfiles/filedetails/?id=1357204556) +# TTT2 - Trouble in Terrorist Town 2 + +![CI](https://github.com/TTT-2/TTT2/workflows/CI/badge.svg?branch=master) +[![Steam Subscriptions](https://img.shields.io/steam/subscriptions/1357204556)](https://steamcommunity.com/sharedfiles/filedetails/?id=1357204556) +[![Steam Downloads](https://img.shields.io/steam/downloads/1357204556)](https://steamcommunity.com/sharedfiles/filedetails/?id=1357204556) [![Discord](https://img.shields.io/discord/442107660955942932)](https://discord.gg/9njYXGY) -**This is still a beta version. Bugs are expected and a documentation is available [here](https://docs.ttt2.neoxult.de/). [Please report bugs and suggestions here](https://github.com/TTT-2/TTT2/issues)! Please make sure to check out [the FAQ page](https://docs.ttt2.neoxult.de/troubleshooting/) before asking the same questions each and every day.** +**This is still a beta version.** + +Please make sure to check out [our FAQ page](https://docs.ttt2.neoxult.de/troubleshooting/) +and browse [existing issues](https://github.com/TTT-2/TTT2/issues) +before [reporting bugs](https://github.com/TTT-2/TTT2/issues/new?assignees=&labels=&projects=&template=bug_report.md) +or [suggestions](https://github.com/TTT-2/TTT2/issues/new?assignees=&labels=&projects=&template=feature_request.md)! * Discord: [https://discord.gg/Npcbb4W](https://discord.gg/Npcbb4W) * Design-Guidelines: [https://github.com/TTT-2/TTT2/blob/master/DESIGNGUIDELINES.md](https://github.com/TTT-2/TTT2/blob/master/DESIGNGUIDELINES.md) -* Documentation: [https://docs.ttt2.neoxult.de/](https://docs.ttt2.neoxult.de/) (WIP) -* API-Documentation: [https://api-docs.ttt2.neoxult.de/](https://api-docs.ttt2.neoxult.de/) (WIP) +* Documentation: [https://docs.ttt2.neoxult.de/](https://docs.ttt2.neoxult.de/) +* API-Documentation: [https://api-docs.ttt2.neoxult.de/](https://api-docs.ttt2.neoxult.de/) * Steam Workshop: [https://steamcommunity.com/sharedfiles/filedetails/?id=1357204556](https://steamcommunity.com/sharedfiles/filedetails/?id=1357204556) - + ## MOTIVATION -TTT2 (TTT 2.0) was the next logical step after TTT was such a massive success. It is a spiritual successor to this classic gamemode by Bad King Urgrain which aims to introduce many new features, to fix old bugs and to lift the user interface into a modern era. +TTT2 was the next logical step after TTT was such a massive success. +It is a spiritual successor to this classic gamemode by Bad King Urgrain. +Aiming to introduce new features, fix old bugs and modernize the user interface. ## ADDONS -We know that TTT lives from its huge community and all its great addons. Because of this, **compatibility** is one of the highest priorities. Almost every item that works with TTT also works well with TTT2. There are a few minor exceptions though. Addons that modify the hud, eg. Octagonal HUD, will not work with TTT2. If you plan on creating a new HUD for TTT2, you have to use the way more powerful hud system from TTT2. Additionally addons that rely on roles may not work as intended with the newly added roles. - -Additionally there's a compatibility checker, that prints a list of incompatible or outdated addons in the server console. More on this in our documentation [here](https://docs.ttt2.neoxult.de/troubleshooting/#addon-checker). -**Hint:** You do not need addons like “Better Equipment Menu”, “Drowning Indicator” or “TTT Sprint”, because features like these are included in TTT2 by default. - +We know that TTT lives from its huge community and all its great addons. +Because of this, **compatibility** is one of the highest priorities. +Almost every item that works with TTT also works with TTT2. +HUD and role related addons are the exceptions. + +Additionally we have introduced a compatibility checker. +On server start it prints incompatible or outdated addons to the server console. +More on this in our documentation [here](https://docs.ttt2.neoxult.de/troubleshooting/#addon-checker). + +TTT2 also features some additions which TTT needed addons for: + +* overhauled equipment menu +* drowning indicator +* sprinting and stamina + ## NEW GAMEMODES -There are a few new gamemodes based on TTT2. Click on these icons to open a list of needed addons to play these currently available official gamemodes. +There are a few new gamemodes based on TTT2. +Click on these icons to open the steam collection to these official gamemodes. -[![https://i.imgur.com/5JAsxin.png](https://i.imgur.com/5JAsxin.png)](https://steamcommunity.com/sharedfiles/filedetails/?id=1672031318) -[![https://i.imgur.com/qwEkCPb.png](https://i.imgur.com/qwEkCPb.png)](https://steamcommunity.com/sharedfiles/filedetails/?id=1672014264) -[![https://i.imgur.com/WVuPmxP.png](https://i.imgur.com/WVuPmxP.png)](https://steamcommunity.com/sharedfiles/filedetails/?id=1737047642) +[![TTT2 Totem](https://i.imgur.com/5JAsxin.png)](https://steamcommunity.com/sharedfiles/filedetails/?id=1672031318) +[![TTT2 Fate](https://i.imgur.com/qwEkCPb.png)](https://steamcommunity.com/sharedfiles/filedetails/?id=1672014264) +[![TTT2 Heroes](https://i.imgur.com/WVuPmxP.png)](https://steamcommunity.com/sharedfiles/filedetails/?id=1737047642) ## SETUP -The setup is fairly easy. Just subscribe to TTT2 and the addons you want to use and get started. It’s recommended to use ULX to set it up ingame, there’s an [ULX addon for TTT2](https://steamcommunity.com/sharedfiles/filedetails/?id=1362430347). Commands for the admin shop editor etc. are found in [this wiki article](https://docs.ttt2.neoxult.de/). - -
-
- -

- -

- -Custom roles are a very big part of TTT2. It adds the possibility to introduce new roles to the game and to change the gameplay to everybodies preferences. All of this is achieved while maintaining an easy to access framework to create new roles and a high level of compatibility. - -[Check out this workshop list](https://steamcommunity.com/workshop/filedetails/?id=1737053146) for a overview of roles for TTT2. - -If you plan on creating your own custom role, check out [this wiki article](https://docs.ttt2.neoxult.de/features/roles/). - -
-
- -

- -

- -Inspired by TTT Fate and vastly improved for TTT Heroes, classes are another possibility to change the default gameplay. They add the possibility to have a custom property besides roles. Classes can contain whatever the author likes to introduce. From passive effects to traitor items to custom abilities like in TTT Heroes. You have to use the addon [TTTC](https://steamcommunity.com/sharedfiles/filedetails/?id=1368035687) in order to play with custom classes. - -[Check out this workshop list](https://steamcommunity.com/workshop/filedetails/?id=1368039514) for a overview of classes for TTT2. - -If you plan on creating your own custom class, check out [this wiki article](https://docs.ttt2.neoxult.de/developers/content-creation/creating-a-class/). - -
-
- -

- -

- -The user interface was long overdue and became a huge new project. It started with a simple icon swap and evolved into an complete overhaul. Besides a new default skin, called PureSkin, which was developed with continuous community feedback, is the new and powerful HUD management system, called HudSwitcher. It is now possible to change HUDs ingame from the F1-menu without changing or editing any files. Additionally the HudSwitcher allows for customizability. Also, it is now possible to move and resize every element, even setting a base color for your HUD is possible. - -
-
- -## Currently Available Themes: - -* TTT Old Hud \[built-in\]: The classic look of TTT -* TTT Pure skin \[built-in\]: our new redesign of the TTT user interface. Modern and elegant! -* TTT Octagonal Hud: [\[Download here\]](https://steamcommunity.com/sharedfiles/filedetails/?id=1795267605): A reinterpretation of the beloved HUD addon - -If you plan on adding your own HUD design check out [this wiki article](https://docs.ttt2.neoxult.de/developers/content-creation/creating-a-hud-theme/). The nice thing about the new system is, that you only have to extend the base class in order to have features like rescaling, positioning and on-the-fly hud switching. Besides relying on these features for a new hud-design, addons can use this system for an easy UI integration too. - -
-
- -

- -

- -The passive item system was changed completely. Previously TTT limited the amount of passive items to 16. You might have noticed that after buying a passive item, it wasn’t buyable anymore but at the same time wasn’t transparent. Additionally perks and status effects are displayed in a sidebar. Learn [here](https://docs.ttt2.neoxult.de/developers/content-creation/creating-a-hud-theme/) how to add this feature to your addon. - -
-
- -

- -

- -Inspired by the ideas in TOT and BEM, we created a new shop system. The shop now has a search bar and a system to set favorites. You can even edit your shop when you're dead by pressing C and selecting a role. Additionally features like an admin shop editor and a random shop (team or role basis) are implemented. The admin shop editor is a very powerful yet simple tool to edit team-based shops, link them together and set preferences to each item (team limitation, credit cost, ...). But you don't have to. You can always use the default settings. Learn [here](https://docs.ttt2.neoxult.de/server-owners/manual-install/) how to configure your server. - -
-
- -

- -

- -A main aspect of TTT2 is the new dev interface which allow for greater compatibility between different addons. Check them out [in our documentation](https://docs.ttt2.neoxult.de/)! [This design guideline](https://docs.ttt2.neoxult.de/developers/content-creation/icon-and-design-guideline/) helps you with creating custom addons visually fitting seamless into TTT2. A WIP autogenerated API-Docu is available as well [here](https://api-docs.ttt2.neoxult.de/). - -Again: This is an open-source beta addon. You can report issues on GitHub and contribute to the source code! - -**Make sure to check out [the FAQ page](https://docs.ttt2.neoxult.de/troubleshooting/) before asking questions or reporting bugs!** - -
+The setup is fairly easy. Just subscribe to TTT2 and the addons you want to use. + +Removal of the 'vanilla' TTT files is **not** needed and actively harmful as those +files are still needed for TTT2. ## THE HISTORY OF TTT2 -The idea of TTT2 was born in early 2018 by Alf21. He was annoyed of all these different role mods (such as [TTT Totem](https://steamcommunity.com/sharedfiles/filedetails/?id=828347015) and [Town Of Terror](https://steamcommunity.com/sharedfiles/filedetails/?id=1092556189)) that wouldn’t work together. So he created a new role system and called it TTT2. Mineotopia was the first one to join his team. He is by himself a very active player and server admin and liked the concept. There was only one big problem: The icons were the definition of ugly. He offered to help with graphics and over time he became an active member of TTT2. - -The next big step was a project with two german youtubers, Dhalucard and PietSmiet. We created an exclusive addon for TTT2, called TTT Heroes. At this point, Tobse joined the team and the development rate increased. The idea of a completely revamped user interface was born! +The idea of TTT2 was born in early 2018 by Alf21. +He was annoyed of all these different role mods +(such as [TTT Totem](https://steamcommunity.com/sharedfiles/filedetails/?id=828347015) +and [Town Of Terror](https://steamcommunity.com/sharedfiles/filedetails/?id=1092556189)) +that wouldn’t work together. +So he created a new role system and called it TTT2. + +Mineotopia was the first one to join his team. +He is by himself a very active player and server admin and liked the concept. +There was only one big problem: The icons were the definition of ugly. +He offered to help with graphics and over time he became an active member of TTT2. + +The next big step was a project with two german youtubers, Dhalucard and PietSmiet. +We created an exclusive addon for TTT2, called TTT Heroes. +At this point, Saibotk joined the team and the development rate increased. +The idea of a completely revamped user interface was born! ## Credits -Credits by https://sketchfab.com/rubiez for creating the DNA Scanner model. It was ported to source engine and animated. The original can be found here: https://sketchfab.com/3d-models/flir-e5-c5c37e8cd607424fbdb06c7ae2924924. It is licensed under the CC BY 4.0 license (https://creativecommons.org/licenses/by/4.0/). +Credit to [rubiez](https://sketchfab.com/rubiez) for creating the DNA Scanner model. +It was ported to source engine and animated. +The original can be found [here](https://sketchfab.com/3d-models/flir-e5-c5c37e8cd607424fbdb06c7ae2924924) +It is licensed under the [CC BY 4.0 license](https://creativecommons.org/licenses/by/4.0/). diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md index 04a8554ea..164549ade 100644 --- a/RELEASE_PROCESS.md +++ b/RELEASE_PROCESS.md @@ -36,6 +36,12 @@ The following steps will document our release process to prevent mistakes and co - Update the `GM.Version` string to the new version. - Check if other `GM` strings are up to date as well. + 1. Clean up the language files with our leanguage cleanup tool + + - Tool: https://github.com/TTT-2/ttt2-language_parser + - Run from folder as: `python parse.py --in "../TTT2/lua/terrortown/lang" --out "../TTT2/lua/terrortown/lang" --base "en" --ignore "chef"` + - Makes sure all language files align with the english translation + 1. Open a Pull-Request prefixed with `[Release]` with the changes from above. 1. Once the Pull-Request is approved and merged, proceed. diff --git a/materials/models/ttt2_dna_scanner/camera.vmt b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/camera.vmt similarity index 100% rename from materials/models/ttt2_dna_scanner/camera.vmt rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/camera.vmt diff --git a/materials/models/ttt2_dna_scanner/camera.vtf b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/camera.vtf similarity index 100% rename from materials/models/ttt2_dna_scanner/camera.vtf rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/camera.vtf diff --git a/materials/models/ttt2_dna_scanner/camera_n.vtf b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/camera_n.vtf similarity index 100% rename from materials/models/ttt2_dna_scanner/camera_n.vtf rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/camera_n.vtf diff --git a/materials/models/ttt2_dna_scanner/camera_r.vtf b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/camera_r.vtf similarity index 100% rename from materials/models/ttt2_dna_scanner/camera_r.vtf rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/camera_r.vtf diff --git a/materials/models/ttt2_dna_scanner/screen.vmt b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen.vmt similarity index 100% rename from materials/models/ttt2_dna_scanner/screen.vmt rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen.vmt diff --git a/materials/models/ttt2_dna_scanner/screen.vtf b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen.vtf similarity index 100% rename from materials/models/ttt2_dna_scanner/screen.vtf rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen.vtf diff --git a/materials/models/ttt2_dna_scanner/screen/arrow.vmt b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/arrow.vmt similarity index 100% rename from materials/models/ttt2_dna_scanner/screen/arrow.vmt rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/arrow.vmt diff --git a/materials/models/ttt2_dna_scanner/screen/arrow.vtf b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/arrow.vtf similarity index 100% rename from materials/models/ttt2_dna_scanner/screen/arrow.vtf rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/arrow.vtf diff --git a/materials/models/ttt2_dna_scanner/screen/background.vmt b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/background.vmt similarity index 100% rename from materials/models/ttt2_dna_scanner/screen/background.vmt rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/background.vmt diff --git a/materials/models/ttt2_dna_scanner/screen/background.vtf b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/background.vtf similarity index 100% rename from materials/models/ttt2_dna_scanner/screen/background.vtf rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/background.vtf diff --git a/materials/models/ttt2_dna_scanner/screen/check.vmt b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/check.vmt similarity index 100% rename from materials/models/ttt2_dna_scanner/screen/check.vmt rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/check.vmt diff --git a/materials/models/ttt2_dna_scanner/screen/check.vtf b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/check.vtf similarity index 100% rename from materials/models/ttt2_dna_scanner/screen/check.vtf rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/check.vtf diff --git a/materials/models/ttt2_dna_scanner/screen/circle.vmt b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/circle.vmt similarity index 100% rename from materials/models/ttt2_dna_scanner/screen/circle.vmt rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/circle.vmt diff --git a/materials/models/ttt2_dna_scanner/screen/circle.vtf b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/circle.vtf similarity index 100% rename from materials/models/ttt2_dna_scanner/screen/circle.vtf rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/circle.vtf diff --git a/materials/models/ttt2_dna_scanner/screen/fail.vmt b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/fail.vmt similarity index 100% rename from materials/models/ttt2_dna_scanner/screen/fail.vmt rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/fail.vmt diff --git a/materials/models/ttt2_dna_scanner/screen/fail.vtf b/gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/fail.vtf similarity index 100% rename from materials/models/ttt2_dna_scanner/screen/fail.vtf rename to gamemodes/terrortown/content/materials/models/ttt2_dna_scanner/screen/fail.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_357.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_357.vmt new file mode 100644 index 000000000..eafe3f11b --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_357.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/ammo/box_357" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_357.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_357.vtf new file mode 100644 index 0000000000000000000000000000000000000000..b380cf0b41f2bc0fca390776f85f7d1ec4d2dc85 GIT binary patch literal 349760 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 NMF4)N(ZF#%{6DwrcP;<` literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_buckshot.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_buckshot.vmt new file mode 100644 index 000000000..f6b316785 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_buckshot.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/ammo/box_buckshot" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_buckshot.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_buckshot.vtf new file mode 100644 index 0000000000000000000000000000000000000000..790e0ace6d0c93b7532896b1db98f6aa5f24c140 GIT binary patch literal 349760 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 U`~W}D2;hev4bTIAkSIU+e;-9bPXGV_ literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_pistol.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_pistol.vmt new file mode 100644 index 000000000..422e53120 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_pistol.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/ammo/box_pistol" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_pistol.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/box_pistol.vtf new file mode 100644 index 0000000000000000000000000000000000000000..349624a5488f3d7767f31e8c253518d017ee8e13 GIT binary patch literal 349760 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 X6an}F{D2|=KU8S|J-`n<6jepweG>!ku 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 b56}bjKoNi+zz-+_@I!+J&;$G+(0=g$Dvw#Z literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_357.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_357.vmt new file mode 100644 index 000000000..f554a8bc4 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_357.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/ammo/bullet_357" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_357.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_357.vtf new file mode 100644 index 0000000000000000000000000000000000000000..3721592382f27d27b8f93666d00affbea0b613bc GIT binary patch literal 349760 zcmeI54{#jSeaBaSz_PK>83=y@Mq+Frj6*C6RbdQ~nGnSyVQ`v+=1)mw6UIqe8Bg1x zorzt^FjJC-B1O;=z(G#vgf^kEiQ)3U7I6mKm|}sGAX~x6V2tZv`-}i%Ea~;VUH$g% zt$jY7bbH#n`)I~I-tFDqzVG|~dGGDs?oFR=a;7+rQ?CCl)c^GVlsk2fQ)d3({cCS@ z9QkKnXm701|D9H?{}YQP-)1)HzmdZ`4=epQS>=^idB>rYE-NZ3{`|5_+RZCU?bNSK zpFVub=9#bl^QSKM>W=fg)7&+?xvNs@Q&s!!k3JiFzfv!oJvb++WsiLS=UFZvA<;UU@+8e>kD{Ke~MsL8yF7>_pp6*a;aI3t3-~Nv3e!WYnCy&?r{war_ zSu-@he1Uwvd|$`R{&;nd)PwI&Uw7N-6X)3P8{hU3uYb;y{Z;;?ec+=<41VL=U-I~m z|M}QZy!8tW9YeDJZ@z8gKjw9X zx&M9F`}*?QpX=|Nf1Uomw&eF+m*w{bk7uOn8{ew-XIi@Mtg$zUbNrjYwIj&PwaQ` zCA)QNr5-F7{B-?_zQ>Q7p9RtXO7nMj^S8>RUH83b@=P^KJy_njK@T?8uh=fH2k}eR zquKxOjKo_Xx_zn7U$xsaAZgDrrAyOkACT8i?tMJXcqz!|$!UL;GW=cS^LI)j{&knW z9vhu+sFu?%nR;KPv`4Kn$DJHcsHfa^)XDbPuN#-|I4-|~?G4uR)Ba$4t5nk+sd*>u zNLT*L?%uY&>)-n;+5XgKJ=hpmd-K!&iMChTDP^T<{`Pgv-_hgw;PFqio}2#9b$idy z-N?J+FPSmj?usJ)BfUVZxFBJ^FP<^tqSz-sWoBoIoQ5T z_0jfkG4HEft=9`&hsoBT#h0WE@_Aa#U3xv@c;l~3#~1c?uf4m=Ha{F|{`x5f%T7E&{$k z76u-F@sycg-r~=nXYINDN8N2h8zjdT`0GD~(VnDVa=ZqCTYWuycvSVqTMuqh`p>K1 zxABOn)JolXe~_1v&{MEdU()wJ$J>nC#$Yq3AN!XLktI(|`a z|EhqVFFqV!l&WX-nkQK4|6tktm{|-S_Wk`D{M|J|!6|C}f6DV(&nf3;=BuV(pGeQr z4!_*sHm^?VpZRIWivG1xoL_r+6gB+AKAuehJ=duCk}E!*{(Qak<5dLKmO~1ejMxTR}Y){ z`k`&|{?+(%Dtscxy&kz3LZ~y7a#-UpI{7dHi-h!0^K@_T~kp z;^*`?{cRA)72hX1-YvAxM=rnJ-^)4veC&shF+R^Fza!V*54!&4_xFR0*IYnyP`5^I&6sU!cDkuX#Kb{Eq2A z?{wyv$1@fBdbLXT_f6h@&-mU0!yDec)juD8LE=cqP}8aY@73jdLo4a;ocedR{HWIV z7dls85+VIteM!fWjqfIZPg3_rj`=`;PilW>;z!Q;Kz~QNzbDFv+Cu?czq94T zDPO;Tc+0y>u95tx{bAePeJOd8X@6{Y=`!Z=Tkh}*-Vd%ce=nWHv)J43@vL;(TkQQw z`&>r*^I!0o7#=}?pl;&{9^j!7@ckwle7M2Azhv_MsAuS#Z#{L@&~_P*Cy#f;TcP!j zKj@dyXixHZIUW}QzrVl(JRIk9>&@>`@6o>@^Z4-5C!RCEC-a{-@Z+?K%`~E@kB*b@;``#{vVd1k+P-b=NAeb)H<5B<9x0sk#Nj-O8+zf0}&*M8s5^NJ&JbF<#0-ZSVq zlm49p>d2J!%gW__xx*LzqkoM6{&zzEn-aL6>*qJdTYup`g3t3ib%O_>=yRgw!`}VD zek1$2{N3@6@4e$o-@oYJMF{?%kpJoVlZju2xqXiN{O%>YMSo{*cW>66vpkpmrYo=h zzWzPiPcF^D_&yW<=-)*M{kt#-AB$b@pAF>sym$4(g!iP3*YC}3o(rz&%Fq8?4Zf@; zFWd8nKl(@i8i6ABzq*(8{5<9PIa8l*GK+Eg&+UExJbAwAQ{Bb>qqXvVWlJXO`IzV* z{TIIe9|+`m*?Uf9;+YhEznR}W7hR_-&sLjP?O1tD9>({czoqwg^pE}vPyaQ4FnRvU zu;}Z9?=}68QM=TmR-W^k8mw&`o`mr|`bXb|r|)214eNYA?~?lDIDKuj@;vA_mmf73 z^9mRsxvc~u5AN@yP|33(f-_J=jEFL}@-8>gvrz@|0q;05QOJ24I{VY0l#{c+3 z{{3N2lYPC+40ZdRSv~)DlGg1+&%yE&PwI!QYst&&(b2cd?h~xY7}CqA#Q%{(#P z+kVYMs#`r*I%#cDvq=bZFzJ>5sdFM@eBQzqW;$f*5lTN zJU=h%zJG8-iXW7HH(KL^rMD^7*tp!kKIi(P8Q-J-Qq*^FJd$s{XBsDzefO?O^@FAQ z_qCP!ki_ssVrgzaUc=GI?@(;-Nj*^|fPc)Cve-!nP-t9aSol7}wP2146^@Vu7{Y3Se>(8VADCi%( zpIGmrdnvDYnc+h`7 z^pD=N)BDeqxw!c4ZKC&2Dt)soeTkyf(Z7x7`0+shVdx*d+x!&WrYjG4_q*?r?FSBa zRHOHN{N4cl=R*JJeS&&dE77}#Q2cNFdG7VY*guc{$D@Dro}Jzg2J;=gy8z~2??Hb+ z|6|cVde2VpF9h`d!cNBX2EobKyVsA&@d5gG5eZ!XgWf%!2wyXm$LO7WcL5aKl6nj3 zZ?fy?-}C@__jo9L%~Yay8?Wf<+dIzm$5)#ANAEU&(YuXDdc9=QQ=x4y4gI5co8Rc& z#-nuf9`vW;)IWN+`HkLfJW5yZ#i@VvZu1+x+jyY&ba)hI{-bxhKhe952YMeX9)+QQ z^ltYjdbjaF@8iWIn)*lYc7JBldvEZ18_vJG019sSgUzQT9?{T0dbj&Wc$=<#61}5u zjezhYsb`NzW3Nw+{?U7OdS4gN`#SO+{TGn_(K~uK2uwV$JRHZKZ@k|{AaH$q62EZr zAH93L6TW9Ew*~Y4=XcHI_wV?H@FuC(T>DvW^pD=N(>vGC+jxb??_BGp*!DL*{iAoA zzv9#BO7w2yh2Eor$N2RBrKg|kPYN2!ABR4Cew3O%Xs_ps;s@XX9tHvU;PD{pdZ;}ZZ|#pSnk)S^ zT-khPMMbjzRHxgHI@x~uu19C~{kYZtJzUh=$MJbQl>P$`@GuC#2agBn0X>wG9?st% zZ~a=+`XSMO?f$*p`sUjD&*p*Dr@hja*t%wBpVZNdjT3l)M=ALSe24GwJ-XxP`gV6d z@87R#`sVoJkt6BzU2zamxMsT`XjM-xa*zga6@w_+NDW4}GG~QqpHtZ@l%P zo1XLM@AbW#OxWu`n+Iv@J?{Jb_5IrYda}Qc^?kt$Ke2fP9^hdRKp!3t&;xo1Qx7#A zWKk#^v zYdyr)+VlC5lfUtAX38aa=e!(n+fgU`t@&NYitSHSbNxQ^eUISG_gx%BAGy{$`}1f9 zz8((np}6@FoX;OEZ|?fiW%Yi(FHGciJ}^2Au2PoZZ~T^LMzO&oeTv z=W}roeUNYDTPfume7E^NeUF~cH|%>&{9w3BKg>q|Nxjg@xg%3ovA&<|KlpC*9X!Az z)!*O)d?;oRaH+_Nrd@W;x0A`waKLkk$Vc*# ze8dlm)ei=|`TSo>`%c~+U-FiI+Pu`&Kw;14{dS}oryQ+lzw^QcuW&w}^Z8!TaX#P0 zL3G3Thw%^NpW+$+T(wKj=ij-lat~Hg!niG?l)r_9r>eqS9@^f zHLUL^`A}&5=6LaEgMjE6KEQ`k;zRI$xAO<$tzT4O2b#V~AlAq*fDdlIO@jZCI+l$`H?el2G2a2VvSIrMrZeGRuK6y{x8w42d zxI7Sl0}oFY;Njsg*?b7z@3yKp-a2Pbw|_pqeX{R&JDMZHm#CHV2h95ZV67ZiO-J|4 z{YoEB^g)qJF8Xyv(nJH~6isC-49dg8=&Qc#vy7%-L^WZyy`f=jVsbnzYS3>f^&>nSQ=*>iT|N z59|8|N#Rqj`ON-oo`MH>pf~(R{PwI3@zyUibojrYuX$;2w@E13PHFqaMDeIS)W-V0 zhE#AQk8FN{2Y46+(1*tZ^e{0!1h2=Nu_J!#pd0a{`%SQZZ;%kZ<+`luRjRvfNZ*0b z!@HjKeHRG91ATkE01pob_y8Xa0_b6UdYE>=UB90r_$@xz@X9dj_dcHlm(a>ix8Ley z|MLe}-$&2*ndpIh_k0~Z#+PsK$L7!3FX+5)`08%qQ%mR5-R|-|=j&ad1ebi3r|oJ> zY=0r&X)_5G^ejuqRVV0~Zu2R+;T z1rP8r2%ry-2k61p!^H>Vx6W$1Cwcy!>+)TIL@!Y*&)B6W`!&nA%W=VHo6q0@9tHvQ z;qd@HpodgF#PoXpBMrA)B>GUjT(6gkkB>`4#zCLbznpAThy3;ZSm)!_{rV@Ba-8TJ zeW&slKEMa~!1zz{VcF_<>%7~SCg=C7$@^4&2u_hH6Rv;x>-*DQtr^<>WHouu_1a#r zalN*SgXop=3^{}VnGyZ_NX+ke2LxbO%*pZ>DVdh))f+t1^<2V3;L z^30#z?y%}DYU-*=J>-!6&v>5kfI(jH$agt+Wa=u`_mlYJf3^>R2Y96V6MTp^AA)%w z>up%PdsBDb@;%%9x-_wVXiCcZz$x!G-S)_jXRtmH(s=CJt?KZ`#^pPtpU`u(^-TZJ zKjbgt^K^b)Xn8N~NLSYXvBnw?xYrM?o#Ee4j`Ib!AEv`mUT;z6{7z?&)X#Zy<)vJ| zkDmx17+=_W0uS(r@_4J*@?Lx>T^Z|TJdn!AbU4ZD!Mtx#E33(S@?Q8szL9UKegYq& z&4*IUd-17sCF22`ujIXr2Yv<~1_AL8_y8XYi4Wwx%~#QJy0X;91LVEU5BwcG3d!H$ zD|}7mD|v78AzjXo_4Q&K50LjZKk!fRNcB(n03Qm859Gbg*RkgMc;8oO;{o#C<_G=` z9);xZ@D;wM@|C=|`7qub&(-ImHXb1FZGPaN;F0Q|@Buy)5+BHWo3FXb?XdO}91omo z?mw_Ly!#K3_clN9ckn1Ae}}K|HI=XAz0HTPayno8brKzkkLB=wMC85A5Bw86QvDM? zz=uNO19@-rHD9?rS>KcGc!0dO`GLQKM_enusCqk0@=E6-Y1UX$Q{BcsnNz~62CfCqR) zIqyoHpX9x*~XRFCUVdhTD*_mi3a{J+%oMEh+$fd_by|IxW0BYAJ@F{-&5+2dO4 z;#Vifx3KeZ?ys*Q5j@B{TVLP-9tHvQ;qf5fdLZv@{Rs~#$A`fHqY>P@GuC7U%-dl@`1d!@f4m>hJ(um``Qv) z9=(~oC+|Iik@qeR;y2`*?K|L+TRg~n8&A<4WjMHe=uLHWO3m`^f-md)HqQ7Pco+mk zAMgP_p2%ry-2NTr;d2j1U zctsfwF66z9>qPa-=fTq;AbtfO;KPadK;GN@5ZzFQg9~|YP+iz=tsTK;GN@65UaTg9~|YP+iz=tsTK;GN@65UaTgUg>Ll$uh*_4&dt{N3gmcz}mN z0DX8o$h98Gds|<^GsK5D>qC5AY#OK9Kh|zeIPG;ow5v+qmNI;9(FDzkv_%Axu7y_cp&o zca-7aLf+fB;_u*L5D>qC5AY#OK9Kh|zeIPG;ow5v+qmNI;9(FDzkv_%Axu7y_cp&o zca-7aLf+fB;_u*L5D>qC5AY#OK9Kh|zeIPG;ow5v+qmNI;9(FDzkv_%Axu7y_cp&o zca-7aLf+fB;_u*L5D>qC5AY#OK9Kh|zeIPG;ow5v+qmNI;9(FDzkv_%Axu7y_cp&o zca-7aLf+fB;_u*L5D>qC5AY#OK9Kh|zeIPG;ow5v+qmNI;9(FDzkv_%Axu7y_cp&o zca-7aLf+fB;_u*L5D>qC5AY#OK9Kh|zeIPG;ow5v+qmNI;9(FDzkv_%Axu7y_cp&o zca-7aLf+fB;_u*L5D>qC5AY#OK9Kh|zeIPG;ow5v+qmNI;9(FDzkv_%Axu7y_cp&o zca-7aLf+fB;_u*L5D>qC5AY#OK9Kh|zeIPG;ow5v+qmNI;9(FDzkv_%Axu7y_cp&o zca-7aLf+fB;_u*L5D>qC5AY#OK9Kh|zeIPG;owsLh8mnwb6%g|TkkCBn%&$rTk80o zjXQXNhd}^+csxK46Vn5EZ{sJrq6`NY=Jz(P_!oE>1jKLP1AGXR59GbgFVP)kIJm@m zncs)0XY#`4EqH(jdP|Nc$$MK5#IDc>BjNgNYK|uTm zKEQ`u@`1d!`6N1{3P+iz=tsTK;GN@65UaTg9~|YP+iz=tsTK;GN@65UaTg9~|YP+iz=wSDA*LF>dd1Sk7YY9;ZT^AB#PA^R zZT#?e@GuC7ufPZRFqwQ!`_D6z*C}nDg2(akAn$El@OSVq z2#8<62l$XrK1_WhzG&{w^~&d8!l`p+B&z&+ncW|Z2W&h9C(7gFLf+fB;P2pJ5D>qB z5AY$Md}!&6FPe9P`Fwq%Ezzh)13o_k8OG!}mhsJL3VHui1_V$a@<<{0}?|$^YOh ze1)&c`(ZL3u=Nle50LjZKKLhi;GaqVL=WhpnDkKU;{oy>Jr|Rn$tU;>pBb;oyf#}u zDE9FHd5=HhkND$w{V`iV<^A|gA^T0Cj|a$m^jFCGBR}9T{AIivmGNrS#{=X&eu-b= zmr?Oc#&7shG5gU(#{=!og09)kU9&~6nf52|lajngU&X91@&kUuZ^pBJ-jzjL51H~i z_`G8$;nX=Z5>@Hi7u?^Rgc_VO{crl@{q=$I1AZB8e$YSk4|ztOiGSk<#q9^d{9di} zWPib8f4-mXcmTc>H(%&q`j_zm;{(PAo(~k8A09Iv=v7L+d)w90-?1%iLp>|2`=wrN z?WaBD8~H}Q;cvy6Z}@w5{=Uqa`f%#`fAGr=Kkx!Cg8+Hr@c=!b2lRj+;D`93i$KJ$ zmm#mnEAooGBCp6R@=7B>Ub{5F5AXy006)MFJs-di@k9I&Kg195LxVuR^5Fjg5TJ!X literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_alyxgun.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_alyxgun.vmt new file mode 100644 index 000000000..5fae4d23f --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_alyxgun.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/ammo/bullet_alyxgun" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} + diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_alyxgun.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_alyxgun.vtf new file mode 100644 index 0000000000000000000000000000000000000000..377ef595552f10d32be2b6730a2c4739f14b418b GIT binary patch literal 349760 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_pistol.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_pistol.vmt new file mode 100644 index 000000000..70028520a --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_pistol.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/ammo/bullet_pistol" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} + diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_pistol.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_pistol.vtf new file mode 100644 index 0000000000000000000000000000000000000000..bece24cfa48ef8a755e700b9e8205644d783bc14 GIT binary patch literal 349760 zcmeHQ3v^Z0nZ6__4+RoX5d$Vj#>cb*ownD$TuHlJlxvGJvn=D9E^COk9j!^|S~IMf zDKx-p7t^tABGWErRGLg{>&$3}0QPp;+9no@6bnWiF%kt-G}Z@P3=$~!vS;se@}GPD zO^)Zj?|FP#wVQpwnXR+nOf9Yi_Tazah zf@*1r_V1n(huV)@{YgI;BQrWaSwqWq}JxofoEYPr7p#m$a*=e@mA$EEGL?azd72(j&-zCDHJ ze=XbbIc`tazwU?1Z{?r0yhr+X8+&zF`kS$Tq9V58@P(8gB9ZaSBAquW`i@SIJTK>; zm5_SRcK?m-*v4O;{^b9Qx6C_mP>#pkzPQ~Ch}Eaxo!S#dX<+d?a$J@PwF%jG!=p6g zouv8Cq@?t7TmH;=oKEA{exGnxZ(?S9CVVK)_@~W|w|stOo0T7C{cp5Y96R$jg&wEV z`p$W?H&NN~)k{hh|7jxLQX^BCwLjB1fA1EP?jLW|GbY}Rcb{xae&Ec-*8E!hvi^Oo zvEDjbuJWPv)q_1PG9MO)|8C=@{cvgvjYsX9?aA$3w~Ueh3p4(F$LoXx{hlozvyO=2 zgSL7(oxgNF=QjRgkC(>n)arO#w)Ia(=lm>X)jyYJTpwgM{)=4n>$IOm`+YvwbInJ4 zlLL<^J?rsx>yJJDOKRgSzmg?BtxwG2er5fhuP-$^i{@DCak2kaF=WN^4HWaT+A5UF(MWSL{UAk|k4-fWrJkOX8=GpztEFRg$ z&ujCazcJo27;pIi<>N=6RsHW=PW?&0RQ*j;sh|7)>+#n4fWCo%rH9<`xJEm_am68d zJiP1&R()~n&gfp*vHIkjnYMSlyf#&9TmF*QB|p}to}}f|{K#$pvAl^}#(18R^RKBm zL-Wsga*?_iBLDb$iq`)kKi_xhk@(ev>(j@Fh$oLjzIaUE7hit#NLM<(N6UP?sPXI{ z-f2i46vrBfZ~2ZlY|H9+r8 z#XHOUXLgI8IRo;NB;j?FkT-)wn|;fN+t)NC2Rl{=*6;I;H^teh^&1}+tNWK?o&7YP zzt8x3pm|*6rmu2OFLt~MH>cL?eE7oSy{o->zCAa7l{?-}JfPpp&4=0h#JkS~$ltvl zZ;HE9TWmfoyXm5g$jVmvOt?{oIgUH5)BPUrdo+Fj z%YVn;e*EMy%g=8>m5`cQ5MCKmT%KppU;Ou1;;x z;;qMD@4qPP_Zr*r$60&`cpeB_y!H6&{h;#!@#b}%9)G>>cRnEA*~MFrzuxyc9}w@t z##@iS-Y+*F5bx5)TaUlqFE$?#Z*Rq0kH6k`<-;p#JQdb?riCZk=GVlRSl2_YIU=5F zh~8!O{dWFb$0O{0H$MDStljj7*rw;{_&n@*6>t3d`{2s-y!DHXQ@f=D(DTjvtm|WC z+8^ur-=J+@`KDHY*Fu9X?d9H&$;ia{?pd` z;TElv{hQP8TW(kVze-rwPv`;$Ilb{I zgVU0|v_Hke3;KuNBm$QIoBHs3|06GPegE|_DfXR8VCMM5$7UM;u=@17N88W7xjyxb z?RyT@3*`8E($9b89xv$MAVR?SpF1D?q5tXH{fgH-6>qt3Usqb+7tQjOzO(hA&nz(> z7P7`W<1eHi$XA%qdNPkc^bh?@1U&A4>iZ{y@jK-AUQBS&t!VQ)WwqC?gP z>HD=ak0{i92*>;K}`;vRno%|AZa|FhKt9jWa!E@wZ0{-OWU>;H>d z{eJEX@vj{I6Y@FfHQ#pnv3B`B@KX)69_Zgtxs^|x@i6DZ^upqGiTr+){e1hkUi0_+ zlSx;8X(P{7G)Bg)7=!=F{|3`Ne*EhNN#adD}C zV14TO&0}3Jfc~L>i9nwIx9t(7_uF0}9d8ti)|ua@FD|cXduZPq=N-G{$6p>?y`OMO z#~b>G{-`ZwOSM|})i{uyXl%c1WN zJg(TgWy#>yvEC;O{f9ySd$oFfua>{7<@42ju}(|>)tVp7|3I+5hZS_+dcTO>dwZ<& zaT;g(c;ED^bRYT;qW<0VPP}kxUH!o4-$Qd6cTj$>KirVo-jN#Pcpv(Q-kslD?Zkih zfvFoOe9Hd&4rBl7Xg_Ly^7A=^J!94T(0>s0pKpFAp5)rX54`0EAKW89?%R<5K3KZG z*Y&RVQUCkF56Z1~H=lqXl-LixX>8BR_vdcim|?z${-O8m>wh=>>(k)}MfQVnk&2Jp zoW5R@dA^7Ky{Lb$=XYJ-+4kWF`T4;G@_RfF#5(^#xVKpEL)G=1Z6Ec2Is72#dMBPb zweSNMKX}BLZq@gb2Rc>{Y|G?)9`s)hJ-gLkVb{Bp576)M1MUY`4~f+Fu3N0{8@TZ+ z_vy(0#qbO0o%5M=o@pC4Ke&8E4A=Iq8sqsK6UW^3?Y2DhUkLp}@A>K7%@=~^2h&dp z(IAdJN#ovKu;Y0*z3bDV|6KGBy%$&Sqzk9kZ+>u}`kr>|d)E28+rPDbj(i_V3F>=Y z?|L8l&qV*wdwKQl_a(kLfA6Mmx9R@S z{}Ju|bw{-K*M*+HcrT|x^o)5BTD|Lj;7$EQ?`E7b<}>vF-uj2$89wRr7v9i+)p4Vq zx4!R;{0{vq1akI2=-ol@Ca=n^|7uy!*F>AG_f3mxeBU`QzK1>kL+=^ty*&Dd-c8(d z&SzoQw?2O8Jx9HlM*mZXWqyy}He&G|`JKhf!bRsb`eWk+y)#_W^}DX;Z2QGI{=e$= zeMgfgd*wZo=Ki1s4hn&cc>ui|gdw~Nr+1Tw>Gc&y|1-XOs!OCcL;vtU#$)31SuOO= za7yb_*K48snd$#uxA6OIjQ*C@0~AMnf4}~xJ#EPoZEwTNUk}h|FzNJDl!ED%aTh_iT{+C!B7Zlosbu^n=-mK}aMJmNKIc;!4{z!}y?^ing#hu|YN2<77=(w;7xYctxZ~pw z{p;}zU3~z(L+`dcpz9&`zCZP^k2i3B0KG%+MbWzftL=H9VCdiXegM5g?3B@sV8o%0`r-NBgw8+o4Ft@`Wv%l( zI)97T4`%*%!zFbm33g-7Dq381IIa~b0 zsej!c^7VtgV$%KNZGJGK-k)-Ph4uWjzCH9!o>Kh-Jj#U!^gotgXYB{o##*d;zPh%3 z)&8C!elObODfC({y%rjO=-)up<@oC?Ke$e)_an`u{8fEH!>WNmejm2be8c)O;alna z)lL5g%r{qA`aqw@4|o6%i9lfVVMNn59`rfCfd}xA2v~XtAHWCj0eUcffQ|=&2k=k` zfDa}Qpag^u>RAxvhUl z#J^+z{*?S4Wrae3=GXgj`As+3majP)zxFZp$kix+radON|5C#0FYH(QH)j`32fw_{ zFT@A&vFjgv03X1IbUrM!9;yaZer$P|^5eOW##?Hd&sh1h;hp&0*KGN=vwx@2qxt>T zPWkcNWP@!y7ydDR_25$p`ID)$Lq>hG^$O}28gHx*eg;1)o}WQq&=>TT*4O*h@p<%@ z*75m8uf)IksxAL@C;XkyDNh%wt?O=AClBPGsiUf)_>zHX#~P^LXw(Pmtn(Z28~6?U zCha%S1M~nr6i*LT=K1&Wl#eD4$>a5Nw_Evr^5Gr7l^rX8(R^sRE_1~rHSy+(KDjDd z{=8>pef7EUN@3G^80AMC0{95 z+mz7G7d#QaWpG+@0__j<4!viickl�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 aBm(dQ_<=+KerV7DdVn9m58wxuAO1h%Bx(Tw literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_smg1.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_smg1.vmt new file mode 100644 index 000000000..97f3fee79 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_smg1.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/ammo/bullet_smg1" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} + diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_smg1.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ammo/bullet_smg1.vtf new file mode 100644 index 0000000000000000000000000000000000000000..5f3d01c91a9358f2e78c292041cba3ee61d8a83e GIT binary patch literal 349760 zcmeHQ4Rlo1oqrG%1VRR=RU2bsiHe0(aP8C{i<8~jNRB6#v$bx!Y)c5ouhxW|(<8e* zg)@Y1ORcSe)Yg88Np-h%dv+VZI(zJ{B^6hxSV?ue22uf$FSWvi5I!fldq47j^X?_X z%p~*i-n?IqdcE^z-h21=|NVad`|i8%zB})zu_juUHJ<;wi2uv~XS`KmS>we2%fHnj z%c6h92dhIR{J)cn`F{d|_}9cH{x@`T_esY8@>PCDHo~$P<0n&8RCG&YML?VvW4rk$ zQ>R+r4Lteysvk^Nb-rP?*lO*k`nbJ;haUgh0RR3Tu?K7Bce;15)BhTv?bfvuhxa!m zw-Zpj#YTPiN%8yBR&85l?YgP__p;7P=ij#4nmUi_@!zZMH|&ozmaaG&ljqNWKjXPm z{T$A6s-Ly0H&^mq((j9QZ7ja2`B(h=>i2KHbxOb=qVL~$I27Bpsj-*p=J784O{_E4 zJnh@``w6?lv7Sv;0xx;K=JylVMQi!rFeA=Ap4yyjd$Gi;Z36wiXqQ$0(}V52N$U4Q zf3>No%bHq2^|-E_C^|eBaO4@ z_iJ8`G!32R%^Pmt7tWNNn#e@RReLY*V|$itU)e+Ti>-y6Pa^)Q;d#y6NaIbxCYw(m zeruaJ82?khH_Pw6&e)RBpnbfD(mfvI{0%nP=f|#5gfNdo^^Y7D=h9zJ^%;jY6?1$> z;Oofk9sho$c=5(*hv@goj>oQ(`?2|R-3{TDJsVR$o*Bmuu zDct|$`ChQus_&HgRP{4f_`?fUPvfjj)*jhfM|<5-Z#Vqzs%?>_C(j5Cs`Fzz&#%{s zFNFI_LeWdT(*BQqQuyuCxIb7AHj9?p?WJ}nD)&lUY`)|GwSf zL!lVY`YK)v7OpKzE&pbx{qGq5^!EpY`<(qFI?vIT-)Evf{>Urz`}@D0YI{vSXQusc z8SQ;*|B9xV?82+#(Rq%#yjbqX_OIOoqit_y+J8=a8SgCeF72q>m&mt=KmX{zhgR-< zX7v5LxZjPg{kd-M{e$dmc{tvGPkjDnd(y3s%>P3spR*j_DA)5Xyh}~|IhoL}(`{d} z?~mO6*WO)mBzCGZ-k(asPde{ZOVQp5j`l`#)?O12)&42ZHh(MDQ}Q152idpI-+xTC zo%B8Tw0Gq82d&z5!{aeB(m__crn08NzP_h+q*}VSy(70j?k|cDaXi%b&%xti`MI5s z-r%_|6rQ?^@Hv(@KAY`JRv%gaiSbb1pZo{*i_n**^8R;fzc=^ip1O(8kaD_&*F#=! zZ(*K)g`KZe`!0J;Tu=Q@j3>)@R{bNd-*%kmGgW_9et!+`uMHXTHu-?MM8MWVe(<>M zFCw1t@#fjPBaKhK%)_aoXR)c*$`iBe^m&5vmT#>c>}g$P`*qxp^3y)QAOEjy{VMze zQ6Arej`-d&tpJ)ulW>@Avp*y8RR7c-MXVDNZ+au6S5Po9Y=aXA1gE zvioZ_p6Q<{J!BgHT<`B|Z;1P8@%zE-=FDDSCF9Et?}Z1qwyxSq-^+FT{dT;+oZ>WAvijN1VeAUK+e_Q%*8~&H-^5Y4C z@pe7Ke3$OE%B$&s9Fxx~g{!^Iy`% zqtL$p1>^dQ@$ujyeh-=WGv;!ve2D4se(Z2)<;e2^mH!l4d@|jh?MA#G_-*jW;9wT7 zzf8YK!iTn>@N+p8zg{T1koCRPnvMIP{@}~p{}JP7ciGq5Y{G-X^M6(Q$EzZi`9sCLC1UYY|+=~dx`k`F5+F#@uoOadE48MmSOxQK3-zxbFce3#hJ

!07<`18`Ma*Au#M@)zEq0t~To&;@Z~xrn=d8b+ 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 v1wBDe&=dO8$_LdgY`z}yAMzjaAMzjaAM#%zw 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 Gdiek1z2Fc4 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hud_health_low.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hud_health_low.vmt new file mode 100644 index 000000000..3f0498f47 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hud_health_low.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hud_health_low" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hud_health_low.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hud_health_low.vtf new file mode 100644 index 0000000000000000000000000000000000000000..0999a4fea3510dd1607df8b1a145d08cdaea8e18 GIT binary patch literal 349760 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 W;#}?bERd4-R!`FI`{>R)w(sBS!uEFn literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_global.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_global.vmt new file mode 100644 index 000000000..cdeb3239e --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_global.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/chat_global" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_global.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_global.vtf new file mode 100644 index 0000000000000000000000000000000000000000..bc87df11441c422129ee7771402636bc53101a37 GIT binary patch literal 5696 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_team.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_team.vmt new file mode 100644 index 000000000..a7642aa66 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_team.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/chat_team" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_team.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/chat_team.vtf new file mode 100644 index 0000000000000000000000000000000000000000..ba8b798481f401f2307cfd9def186d2f4434b6ef GIT binary patch literal 5696 zcmc&&3s6&68a_7KJv##elAls)+As)}Yk_Q>lqX3Iwl24Ajk~S8ykKeM zitAU3_12A_rLzsI{}q$DhAZ?H>o44NSwlpH^p1Zx?QTb>Z%G}9`ojcI#9XW60D#ED z*rr#`TfRK71VH4YJ@QmOQBAM{h`d0bJxcNl8wDWp6IJePjW5{Uc| zK;#P+ET7*P`DX$k@;2bSq3TNS)|RXWk-wyeHHV;0dj>${F`~I(_Vn#*dQ~Eiluxqq zk&7B1K;-q2+O+-%v*xqPfXFZGWi*7;IsqyJ69_44*Dfq>9dh_WC)$zn9nzmV0@|II z;||7D+W(YY13<|BRJD2)*w-3NB?f>+HT0b=HN+19nE-`8gf81=({}*2K>&$$h`(6m z^*qGx76}gm)k4MVu9!xEk4MbNW?A`qa(uSQn0MQm*Fe`-Pn?XTyklG+BjFarQ6}BtbOCGLK(=C#~TaQ@6+iSdKWYldD4Uw`QJleP}LsXPo`qx_Q=yC*e+7v&+-2svKw$@Z99 z(Rzq+z0pmNY{GJsZ#016D@3tbiPZlI$-eaR85^`YN4shK>h*Ec_Ia_tcRu#x`-G$q zy)0Y*YWVtO{{VjlSl*y<-I`3My|m2fFhFc}R>QUooZ@o)PO9%Hw*%wnh}AXI=`P5_ zc)>+dG-`%XR(UNQ0}uhErm?u7lAyDgiVbU4bPk84Xo5MMVLKvC;C zcvSvGA4$)UzCE>e+{y65{6yJ6KJw|en9qI)UM1v5nosEHCp?GnFG`meUbh9^6mxjV z<$qobOBp@EJG9=9Z4){&gYnQT@W-P3t>Db{gz`D&zn<_5fqGl#BTAvYTO@mf!Ss?T)IZ}W$@b7KMjAg$#O4sbLktPArZ%yk#F=LW zOCEr*1qx~ru!hIGqxB>;K3-D4tSxn*^CJbcpZb3g_y4V@*7T(~E>5jp7X1GAyPNla z^*rWbLdQ9ae0}Bqb=l*UF4Ck2hFGlA%cI=fBtX@?#~1s zODu{AsRCopK)flY^?Xka;Q2Y@y&oqkhwS$i5d5$Dh7UbESIw4=E)x7zSGJ60+YME2^ zEOCCO3qR{Og!_M44}4C(W$~l`iSfh|GSZ0gN=tld5A?TmiiIs$#3PN#C*@PW*(SN?8*$g5Eoc3b;;M#JWt}LF5DmAB&KS^ z_zQx*X1wHlq_r4x5~ck@jf612vn9DD%yB$l>iB;DXD(XqXQB!rISv6PbBSLxFFAO~ z=|JIxeu{xH?H$1?1$lTZFv%uDtZdBVdQcN|o}Dc1?{s`S`NZCL;>XhYPNjz$%3JsK z1GxsxS}zY6sZ)|;f)aVOS$V*Nnpk0?TAE0P5MvEJ^&PiYVS4+p3` zG{FijfhyWQRgcLr!xg7;dsFFn++J8i^F3e3Js(CnI^Q$w`(oX;;Pq{V*QD`Wry|XM z%rt+2u%4PH@!!Het<$kqz!-4aHAT4pP87#uLP&kkeh%8ZulFMB_Y}H|#CpTbab5J^ zP%88P9fF>Z^DmHE<=}~8Jh5C?{^b59$I8Rw@|H^e#;AE76lC~WQ^`l7K5=TDZ{&)v z(RviCaA^qOrbXKj-?FxEOKg}ws&>`S92VOl)XNNENyK^hR4=VZGiJ1OzM}J)X#pcn zFEj}GK9vRe0b~B-{UoB^;y+pbOEF$iVfpx;uyU6(H_@1bRUWR6`K*)4czd`@iNA-S z{aKb%9T?{e*6bGaMR({{&+hc^@_cJ}K4@2%8n;eR45s5$ef3=A*Lw@A7gZgh*v!&& ziNTBKaf(s>AFW?c`jcaBTSpr%NA;zKoEqy_4E76ojpOwCvevdb@%&CZySFoa|EKh8 z9_$XfGCN%KeGceD-Hu{r^IA153xY;nTSg;4i!+-z$Ln8GG(>j4)40t}`+P0tz zq94l>RH*HYR_ezJPOC%Fz$T7I5rO`Q@N|CZ0qbty>lTevKM*eF+_ofl#ruhZ{#@U< Wn5*sOPxq_u{Ow)0wEti_;Qs@40oV5c literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/flashlight.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/flashlight.vmt new file mode 100644 index 000000000..c904dec71 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/flashlight.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/flashlight" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/flashlight.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/flashlight.vtf new file mode 100644 index 0000000000000000000000000000000000000000..10fb75fb2ee06c06f2cb7e7967deaa330d5840d2 GIT binary patch literal 5696 zcmdT|YfKbZ6uvXdz(S;~)I};HSaz!te1%6_Y21NQ0$An!%J-Q4M|Hnq2$AVqzi9IhI^&7iyie%#y7uC0xbDw#yQ9;aVU#2@j30>R z<`EIs86C=gaFbT_^k#5j&u+kVb{Nq1)9#rsE0+BB*A+tH%3r|k$B~i^Gc&pTf&xFk zCH6pl;fyZ&vFXkaZx_^)T$d%cAxmySd)v)Ve_dOfQ+Ye&sCUh*mF34p{!;lTN4CEj zR^cH;Fl276C+lIo%Yy4yfIS*fntn~z@P|;cdfnQ+^C(q#_=qftOm|*5*+msTwDsEi zg%{;F)8*pksXYomi_cWvwb2?tqVOcppHCX6n__T3VQwZjqRlsM4kq|?;-kyYA&HlE^iVikjRkVCP z$zk!2{=)dt<4@Mg;rpGs(L@kiUX|BCil&X+Gw z?4_TdfOtiI$aP7Yuj)g84W}ZvZz5XXn_H((eoxAId7#ETtaFf50V=wKBbu-y+513Em z9j}+=L(?RN2l{B}ACOEte5V!JF8awVlQU#mI?7PfGm_N!KpEqIW*@AVixaf>=k5jB z>4SX1dTF^o z+8_m=*__y&z2~JZ=h-~GF3#$6hGYN4`LXDSf%rWK@oUL#((D!T2D0}IV*3L7ut)Rt zh*yyjFYh@Uq49xoU_L}v-x*$rha|PWD*Q<1iC+L2!5_l;0^=b=3}o+U&r_0HXPj^1 zQNO@(P*)KZ_Rrz(kIRRjyjV=$ZJ2@kW4=5fBw+@)0A*2hJktM08?S!9uX;~;{6Bhp z%%1;%U(d@(`Iyir&vrX2pg*&G8T0&)!g!NQ)OsWdNp$ThtIWTy;1?!QANn2p?eP8& zG_L2v(0?|vPT?%lwbJIJBNcT^yR<@mHXDu@nd6L_+5H&z4@72KhT=BP_k0hE0@nK~38u)O0 z*$e9z6)Q4?FurzhlajvxS31YN^H05l2B^=s(Ut?@?eo!Faag`peCNq)F3aNT-%)lr z9EA9bs;%q3WA%rFf)v$0)PGSepFZqYjxoR!h@U`sHJtx;zwWc$WOS%}Jc74mHyjsUiRplPb-L^-$`GCb`}32ASMPrxIn%&DgTL-=3l5(uA5eN8;bXk}SC3aO&mFi?=FcwmsQ87I{-NhjJs(p#XtOP+Nb5Hl zQR6Is_4oqGd}G?G81?soV3wpgcj&Rf;0!0I*lUxIw8#{VB8U(z_J K&w=m-n*STEazLK| literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/item_disguiser.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/item_disguiser.vmt new file mode 100644 index 000000000..82293f683 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/item_disguiser.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/item_disguiser" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/item_disguiser.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/item_disguiser.vtf new file mode 100644 index 0000000000000000000000000000000000000000..8f085489810d832d22edd8f713f6eee21624a29f GIT binary patch literal 5696 zcmd5=4RBP|6+Z9nzWfl9eS~ZY#7q*|fI%owB_oQ-qs5d3iaWTEWQ5X$)e$NYhM}m? zunU7?Y-xTPew+p@ScVy~Gla@k2NNS`4bqlYij7^#hC&U$?j{75CA)ij&VBnJY+!<> zw7r?#yWhL#p8tE!eVJ=7f_MNB@nge}f`5<=AmDF4_R58zfA|Aep$X^sWc-rl>tuWh zzuE#h395SBFr_0K#+@B=-!|XJlNU6?)~3K?d1|SmH2RD=20^IY<~x-B5I~^9_gLN@ zShcz>0MOj5s*OGXb(48gTOeuPXYBw@0pIFjNyHbQ&$ZA0X$L^AlzY4_FxY|OejA-v z(en~pN%8uZG)*$R_sVbX*|TTU+C&#A`QWZ&^~Ir3sQZbAi^2G=i(ayLNbFr2&rrFH~y*NBj@X?4L`ZW8m#;sVsCAE1&Q$X_r6W9Y2np4hrndXml z&$X)1nX0GT0-9f#&@PD8-5)j~fAUYa;CxkZ{HS0%`j7l%>|vMf?y?NVG=ITQ0DcWA z7r1}WZ%Ga7gBwsA#x(!s9az6Fg2>A>KZ=XonV#Q=htq!Yr(yd9Q^zusvJL%Cex1dv zwHB+mkulA`8a|j9#j9Z3Xg`e47KonwttSfkSj7>G#hSR%U#)!ZNCo;sH7A$L2S@ZU z)6*W(_kL(s$6TuSf8Sm1Abrk3aii4gD)+em8shd8?+0TLNqJG|_T^vh6^AhtR#u`{L+ zW0ssG5Tx%#tUKra?!pa0we zlAk+il6gc2+5SK)t>ex$Q<$}GT79X9)&cx#!~pPfYwGhFE-qjOXt!9ujrJk6^D^YN zo_xwvU(rhE*sm_1Z-nWX54D!sI)M4T;+u`!ZWV87jT__VIMvgJ>&MNx@o(=8$CFCP ze@-(%k*{T`XMGs+5#=4f3Nx6s%#*(r{Q=-A9uF1oU1`VQFp9r~y2e!^Ka6(;)rDev zNo;<_;_PE9mxkJwV#?$GHE%$1I#h`xK?w6)P_mhaj$c4#vxi^kR~#irY{X z*`QFq1B_Ge!%Sd76Q1uQPa^w9j@cnR-yE+oY{&H-V^L3{e?bikFg8ER&(+@de8MnJSRr9M;-OvcM_WzmY)+32 zEgn$$$Jg=vQdPTsp!)r%_X}C>@(i~Fqk{IC&1yu1K zzig%R$gy2j7eM!9pF!+Aj=qJ2ImAiRr7> zGwY|8nc8`y{rbK;ox`*VOnUrmsplIet!~0}61;!2 ze)1K@>hOxI>2Fgb0vZ#ozf@7+&w0H3$RvF3tDS=`WQX7(W`!Rg|+7o-;2Z}Ac{?{70 zerR9VXieRLiAWgB9Srhfy?QCk>p9jdNMz40d_|9!fNuan<_*O?74@H>GA#d!GujU^ zf`iUDi$2ilF`yxmclddJ-tBmu7{@+)&uG>9A<5(C*{Yvs^{(F!B7${udZku>%+f8+ zhY!%A8+A!MmqsrSJe~?Z&wR$6iOIk*#W!c!&G8<5>E#x>Ka)Qxby=5|z=Ub^nGG|= z(Kf9<+OWY7G`_L>zw;^v16w4?#c-R=ftI5zsOfi0eQu}cU3#AEcluvv)UvsI0CwXn zVxQpp4*b4&4Xm4|S*!0Mer9v;_d`vdF8BXs4#Lcxmg(zwy|B}LK}#P#-AVoHL;p^5 zieE9ECj0R9xZnH6|Jn~lY;ES}uVwQ4k_|^6d%mVWn4z*7Cd&qH|2OD||8;+ewgKJu zWBpZh7+=QYwD%h{UyY~Y#NR)o`TuvCzxjRXYx~E;Mx z{RQv)cfCK#oBr@)y&q$HQ{m*FOp8ztt{*;tPImN5ys2sWN@^lq-5d_>et;!~`?J^m i4Cz@Nb`Xu;rot)b!@LhreZzh@bW7$ET^_;#+xKrjII*k% literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/leave_target.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/leave_target.vmt new file mode 100644 index 000000000..0fec68c65 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/leave_target.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/leave_target" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/leave_target.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/leave_target.vtf new file mode 100644 index 0000000000000000000000000000000000000000..8d1b1942134d9052be8d93869243f6682f72539d GIT binary patch literal 5696 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 VubofeR#V@+x&D_de*bU$zXLMWxWWJc literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/hudhelp/lmb.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/lmb.vmt similarity index 100% rename from materials/vgui/ttt/hudhelp/lmb.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/lmb.vmt diff --git a/materials/vgui/ttt/hudhelp/lmb.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/lmb.vtf similarity index 100% rename from materials/vgui/ttt/hudhelp/lmb.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/lmb.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/mute.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/mute.vmt new file mode 100644 index 000000000..60d195bfb --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/mute.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/mute" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/mute.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/mute.vtf new file mode 100644 index 0000000000000000000000000000000000000000..e3339f3b65f65262ed2b1173324646018799e759 GIT binary patch literal 5696 zcmeHLe^gV~9ltNhBZ&}`N&=h$Gia<>w=>#G3bSjTwIbPR>zm6{&q_UjZRffYsNL~w zRVrjyYkO9ZBic@Wc;=})2WLfFveRu1S=pwpwbCCbLJJ=Gci($o9*}ev z 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 VUG8zc`=N2e7ME_18gQif{V&PWp`QQ% literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_next.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_next.vmt new file mode 100644 index 000000000..40e1d03b6 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_next.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/player_next" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_next.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_next.vtf new file mode 100644 index 0000000000000000000000000000000000000000..3a4a20e7c4dd64cb74994f560ca5c724bafc629c GIT binary patch literal 5696 zcmd5=eNa@_6+ds62fGN%5_uJZlqFeBorZ|5GB`G2ZK}gYOg#l>utF1Y>IkVrMK&%b+7hc~v1wQcyL)@?eebPy z`D6LePA>y@&)$2_$M2kb&bgFJ%OM2-4EQVJuLFO;2EgHGHmpzz`Y-#Ut~3MZlyv-K zwZ^uwCH!+DXal)d5CWm@EoP(xp{1tPqRT-y$~d+T0xS}Gd)q?Sp2e@sCxQhgheO=< zU+X-d-WK}7yh;FLAk>-tsPDL?Ekw^(xtfDZO{aIra&xfW+V(bA7zjOB%^TrTGxBlO z)_#BU7UYWKnX@+d-GKuKc5W~+Th8q{_+tsu(xP8mU0w9S3kPQp+6$_yJAV;1jlO1> z*Oayw^(-`-dE?>Ct^F;TxdX%ot1By|uq|g3tb1z{A*UAa;NCe#(tp%(l7H_@59(3T z(Q(QAQL-ynsU8c2RJ)1vP7;+*h-&|gv8Af)1?pV#q%gJ8Mh%XpZbN8E(2?GZu_g$Ia=-+iMak$moZ zgb)=+zFAq5yZGj;0ixn*T-Wu=@uDKq}XkD&8tNN9U9P;$P;qlY+q9|65 z<4+xrBj*1&y5x*f?N@5vC8WS4$nu=rYovvI?rxy*ho=^OLa2W_k_ZVfJZAoykkZoU zb5*@udn|z?HGUrXLW1VQ+e*|*zA$j+yb zonzZDazEp~tl@r}KUBQuzUmG4{hX9gynQ{hvzHMi9fgwtlrLkXrgvMvQ1$EHLiKy~ zMytnvc$J^>lW;T~LE(z~IZC5=0X}XYGM4@_pODMhF45&9##M%rq%4f@dTYQ1Tk5Jq!6$jeH!# z?*lU-b8x+GGciDq$+-vR6__CH7zRYy`;@=*8-Q}@X*pj0Wa+d~^k4DXh14F@mdm-R zZ2yww3&ax@T6?5I!ygG8WXXG4{?KTH^S(#G#Bh=v0QUxwUu)h4m7g^E`x|$kCTmZk z{j-QtlUuz0+e|-!=jRW0Y5vyiK^*9y>re(JFz%;%Ep(A#`nS1ZL{WdST z=_O;OI$o32f4cjP4=L4Klr=10VtSO%k6~zzHnRAfN_^{fOn6=hsXnmS^!?MdXS(N2 z7sH20R}gzN=6_Dt>u${{SG+^#+Qa>fKQ`La)qlF@L)?z0*{q&mH^K|@`Y4Ns@SU=M zh7b9Xv6L7;Zqr|By`r=zNz<-Z)3yH|%>Vx6HNU0$$TG)$#Vu&s>iieekK<+O-p^6} zf4ZwR{rUZrCiDNH+w;N*rCXlT>C{2&-@0uqzhgv+Nv~(D#D3_0y6%t!-i-44-fixp zwEU!)u%DW)eevg$KE-;ctYi9M`s2w0uIToAZ@!un*84*TE<`zW);=T4i?Rf8j?zxr zuV6nQ%W##^9oV1H{#1Z(V!h9;pqQY2*f5lKFAq?P(zKLwA9;toT0Nw4lijR8VEIUv zrAfap;_XXB7m(p9``UfXzdRN^suvyqt2tUrrPVFgB&z9QfIE~f82jD7C*_a_>IW1@WZ zeJ}r^(bM1JnXvuoH;bz-D;tCKJdZ=?qtBgC)?r_D<_e|7tgNiC(fO?+NMd?DiTaHh zPQPJ)qMqTy3zCCy;7X0*!^qJD09)qGVij`!T~4$%|3p0oQ}Befch=@1q@uH+SHG>zAS3(5ivZXD`+X6lN1!t39e=&c{sm&W)`2PvjUq~Bv6<6hIG`0gW zzBe6E(1aPCsXs1$rfi4G4@iXZW%rZegDJsO@v(OQK>QB9f1{1Denf0L{Jj5YA=8(> z_v7um({xT8UWH_3Rgu;|11HFNv|rkw#uw_(lg-qMra$SpJkv^@pKsCW0vdlAymmi8 n;!k^DJK3t??-<_?nOn-&>;B=;Kgm#r_et%a@c%*M`ltT@R?#@& literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_prev.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_prev.vmt new file mode 100644 index 000000000..63f80f1cb --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_prev.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/player_prev" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_prev.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/player_prev.vtf new file mode 100644 index 0000000000000000000000000000000000000000..dea999a53a3282f8e46793e2c5e2e8c791f996ec GIT binary patch literal 5696 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 UG9KrdQ$;UD`Ql_bzZZZ12bS4&UjP6A literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/pointer.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/pointer.vmt new file mode 100644 index 000000000..eb23f3011 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/pointer.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/pointer" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/pointer.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/pointer.vtf new file mode 100644 index 0000000000000000000000000000000000000000..416831a36c3706e371ce02a201f2e647dc51ba7a GIT binary patch literal 5696 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 Zeg+~EnC-Kgd`XQlKfYX{^5gsMe*ybzxOD&k literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_back.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_back.vmt new file mode 100644 index 000000000..1cb895002 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_back.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/prop_back" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_back.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_back.vtf new file mode 100644 index 0000000000000000000000000000000000000000..7f8c662553a2e13735c04860758d77be02b8e5cd GIT binary patch literal 5696 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_front.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_front.vmt new file mode 100644 index 000000000..648e586ad --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_front.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/prop_front" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_front.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_front.vtf new file mode 100644 index 0000000000000000000000000000000000000000..f6369ab7255b0bcf23486db07d06b7b585fd2f94 GIT binary patch literal 5696 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_left.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_left.vmt new file mode 100644 index 000000000..695aee1b5 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_left.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/prop_left" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_left.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/prop_left.vtf new file mode 100644 index 0000000000000000000000000000000000000000..5f35517aff8802ebcbdc2019a5fddbdc90e32d2e GIT binary patch literal 5696 zcmds4QD|FL7(VwVw{1GIMC!%5Av7%M9PXimC7>%VKD1JCTB>IYtb?vy1t)Eu)Crbq zyA6w=r4Z-C%3#x%_F)t0eNbG(DqZJrK4mDuIOw!R8J#U{h$i{{|8shq5*KdMEw=L{ zHz()*_niO#zVrR(o^$ZkMpn)kQ}JrXD~KOf!+(b3^{#Z7nietzH$MR7W_Hs7kYQ%8HFWIt3<|Hpwi$-6T z2m#3tfW!lk75}O}?Y&!$UjWIM8oA}#cd08*{fW1#MeI5oev0~2#8&;yc<&WI@vDxc z!!w4)D1PEOv%s)f%6W@jU=a0A6wtdVv8aKLjP~7O=r`=}9=Ds6+^tn++4A{%( zy(jv14$m=ms5KN?um8G}VtoBcFuD#mPIwbEKf05hg2o{)&Nsm6Jr1Nk$6o>!z}Pl; z|A`;*ORW$VEC1DLqbnXbUMuzGv%R!re3TaRYxnpt=~j&|<~P?i=iRIm!k+Kn{s@k5 zbWHakADl=0!C9m8c7`1Exg!W5H=gXjsx9#3+NPgEbs_(O?tK^n`S(XS56;3b(Aa&L z{4?f=OZgWHwH_*V{LS|K7KX{*G-M~d2v^Ti`{@ReH=HZX{TGTSl8H)tb8xf8 zUu-;&5}v>Zze+qBQx_)BXH&FZ$ago@RLr`*WqQ!Ux`I}QT znOFqOm3drmd&BY1(4O>5Tn}+RaRMK|aWG-xqnTVUuC?|nHGZ-GY}&R(;NzO&S~4ns zTh2$E&XVJ^;^BVrFUs{bB=PB5<$}>!cU|gJ%v>zxC2AI zYMN4;y>-siD`n(^j<3|?6_5XBcqVW>PH`!k#%pOWnsVWhhS2~2@#nwqme@cX=>XL(zHl_n1$au-LzmfJ)8JhU1s72SF3=&$-$m&L?7hLAh{HRkwQW z44&61k2KyGqt*6fv0tD&_sh>_!*8V*=iSNn?YKoG+$a|gHrzg?2EUa&;Ck~jv9k8{ z=3&Ec#ZOf#)ZM?#56Xor^SIvp9v0^XB<5fFRNsP!pWgHY@%J#Cf2{E5+wuH6Xuh9N dE+lCQG?+j^-`;@Ao$uQQqj=vrvQekFFlet>M0 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 literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/hudhelp/rmb.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/rmb.vmt similarity index 100% rename from materials/vgui/ttt/hudhelp/rmb.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/rmb.vmt diff --git a/materials/vgui/ttt/hudhelp/rmb.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/rmb.vtf similarity index 100% rename from materials/vgui/ttt/hudhelp/rmb.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/rmb.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/save.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/save.vmt new file mode 100644 index 000000000..5a2da999b --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/save.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/save" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/save.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/save.vtf new file mode 100644 index 0000000000000000000000000000000000000000..c87b09511ee74aa5eb31f34d75fd1131d10fd4f6 GIT binary patch literal 5696 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/showmore.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/showmore.vmt new file mode 100644 index 000000000..301227ec3 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/showmore.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/showmore" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/showmore.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/showmore.vtf new file mode 100644 index 0000000000000000000000000000000000000000..9895bc5b683650a5aa94de17f3e96504114369f1 GIT binary patch literal 5696 zcmeHKUuauZ82@gPYt|;&z0_T*8)MbAOFB_0NKr&{^ zmQ~qgxRNS9Y@GV!$lggYrp8<8s`0&#Nl*TqN(0CC_!AO; z^UT+Mgu}1HuU`$e!|XfgM=r;-Zws--?@pIFC%=a~w3))_j>5Z1iRUJ?nR8Dro*i8z z+%b%C%`6`}P&g>?#2jNPQOf>CF%JynyY;=851E${UKm~M&%`;G`4RZ=>3NA$PF3*R zZN=w-fO7^V2vgoh_-ih?*XPT6YJT2{7e(D$DnG9r z7uZ#}nQu-s@;%&=@9EX_J$o*XT}VTy3oi4I`eHbV2bYY>E-G}MG1FOq1YmItUemyj=7M(}ww6)3_MoAd5R#)<$u z8nEGk$tc;!w@7SoiO8iQ*$2p`6Yvt+&kOt8aQ-9~_%y%s)>X2P{I)n&c2e-ae2DmH zf0FoQzZ2(|&=Y*p3$*blgtBnFhwK9s$5ehP9~QoM6sCCHI^3N9-j^Hsgzv>Cy?TAJ zcQ1akJs*pDuRR|>AAUY?diwQ169Jn)AO0WnA?kfU<@%9Xp*SwS3On&DC*pI|_vxQL zZ*2$ZjqdjJbcz2xed+J2zKkc=?b%zqJ~;(&#@<>L*L6)xl+#mky&&uQ$Zexkk1H_n zXgnLdLVVY;Ya5ML`KV$(KR7q35#Q1^c=cKuZBdi@e8>#aKZPTg&W_9*mCpOsuV@4L zRNt7mpY)I1Zq!eddrQeWdvFrBNyg0wNuN26ZQl6#Jg;0{^+);DqMyCX8}uSaasToC zh}cgs*LCIdJov28XO_i>G@CYOD!%pl{lD|lv{=u{{A~Bue^QUR(T{0=^Tv+?Tb4Cj zy)!MsYvFIRJ>&ZL&2heRUX7bK?*78dCn^fB*JJ$HZtYUM$k893Q~N6npJDumsKn{I zg$ph|1YHm^HxfUtCs%6v+r9NC>Ct)(S>+cxX#Pdd^xQ6 zaO#S2721V=nx?Zvne>c)bv^eNV$My4%K%%SemgJb6WGq$`9u=|o0<<*cz7~$;d9B? zbZh@Lk~4l&dJ?bdM=p++iXX;A{_aP=0%0?F?6aD_^mkQ1PVw-m4rLEcCQG2pdbAy9 z&G#GM7h?TRe0+Q?xaTmvuY^Ai;?2JQu$~+Ao1gDy^@Dr*Gs<6`>ZPfEHmmR7-&o&% QKKy*tzwi8d_;1z2-@2^t>Hq)$ literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/third_person.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/third_person.vmt new file mode 100644 index 000000000..299a7304a --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/third_person.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/third_person" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/third_person.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/third_person.vtf new file mode 100644 index 0000000000000000000000000000000000000000..6c4b2d38064bc8b881705c0609e406859d2f48b2 GIT binary patch literal 5696 zcmd5=4^Wfm8GpVn`Ai}NVrdl1F_LS<*^|!ElG|0=d~PRg+)cABuW;u-kli|OV%WOd zyrD}(w7qQ}ltFvFLET*KpQ%^1lK$76w3E)NtsNbM4QMMkoenmE5KZ##dA=_nt-)Pz zoU<<%-aPN$=l6U5yziS8-eNESfQTOteq{Uu8-Re{)$+ju0NRII0lAyHWNm4QTC~&*A*uGoGICC)v1E zYfo5j$9hf6AhwTemyU#Oa4f9@*Vo$_h?lku)>xQ14P_f_>nmXu-@(yXyn6)W@AvNA z`^dTs8)385R(`+a`F;B?=ay%EamU53)q{Hb`o(Qm;Ydgv4rScSxeZ$96wQgiBQ^P( zcIQ7G#!X9CmEQ5@(DM1|rh_@Xdb{M@y&v9nHEu0^fRs4p(X*zLc7;Jgj>pv35mf9{EQ1 zyVc|4@XnLz^Qsg)k{~EG!kcvcMBGo#rtm}au7LZPqe`4Ze0aT|KR)tb2v+6<*DqA; zWwly)NXr+b{p4}e{OgTRzvzk?_=unSWg3u_Pj0pR?4J;PaD5Kxhw!cc4afi5UbTxy zTs)Ge&A-)nllRZ_1uJqeWL5l(We;%9V|AGF$XHXiztqV`46ay6T@O!`&zb#`^+|hn zz<18MPT@n4B<4AfeDKNhu>j9jmnU(1G9Y;Fr;ud72(y1$T{OshWShScbvfsDJ12X6m;} zlEe>_9_oDhrp>!qb$k;{F9Rsj);AEIWm-P)dh_wJoD)>v3(RU|t?gq7XBU1*JAmB& zt+B47(La-n;UjZ#Mim(7J*W1I@A0s~KZFxhSxDk5nQvPOCi_G_lQ1BJ%NsU+P0MfC zkjCAG&S1H69w1}PMemnV?L7n3-{5F2OY{SSxcghnaBFpcysuxJM?deVvX`-3cu2X= zQ$LCm&I487hVl3m-@95D#V3}ACoY262N^a^E*u;1oegfRCRq@q3HO)VhihZ$ic270 zsK;F|Be?CchiN=x9?;)S{*~LQ%Gi*6KK~mhS@tUV9N7W=0UWxE z=M9tQy@ImywZ!@oWDPqX>rdn(N#pG^^`f|`CG>cK!nZ7YCY&FJfoNU7^Lq(L%m%uXVX^bI|&`X-2-y4d4L@57lPOg?3rKLHy9(9K=0=2N`T-LO5Ts$cT_{{SrV^*8_k literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_global.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_global.vmt new file mode 100644 index 000000000..7a524e66e --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_global.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/voice_global" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_global.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_global.vtf new file mode 100644 index 0000000000000000000000000000000000000000..f18f9da68030c61483ecb0736c4ba956632302ef GIT binary patch literal 5696 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 R_mczXLo~7Sd_+I(`)}0e&5{5B literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_team.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_team.vmt new file mode 100644 index 000000000..a40e6d297 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_team.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/voice_team" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_team.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/voice_team.vtf new file mode 100644 index 0000000000000000000000000000000000000000..6350ffb0225879f43f14974a278eb6511dd6120c GIT binary patch literal 5696 zcmeHLeQ*=k5r22mSu$8iadZS~8{317@r1UJVrK|Rbr|YK5GF^e12ZTcVFvm!gJj4I zWde+dn;Bvtu}%3%h_Q(o2*F8X60FckYaElYG2cUKu&q>fYA2pfC=xXw3_88;-bvV1 z(h)Qv{U^_Ne%jmjcK5e$ci+2PXfGle<_|5^=j7!4C=&9AC^Ubl%w&fa z<2QwVmiJidY(rDXl1-Z5d(Xb$FCX%Ux-WhV z=I;sU3&r(&53XCUT5_wy$sDS$uixR&??u_>{Zp3NNlMD7mec2rPZb<+3>+W#-0GGs zroR@Kg-;#}*MMFF!)V?j^Xn{6)>)?SCuAYm6*Q*zAO2IuF2}&~Ja%8>J9O-v!mBM) z-bVYLX=&aVVrOacln#e;9 zQz=#bsB3SSmR8@OOY2en`FEzh%F_P2xB=D;{i^%2;#bMPY_FnJ^)LNHo}Q4YHcD^% z(a&bTrF52Y{I;f$>hE}ah?{8l3a`j$f5(oT&-h>?bJ;^}F$d{1+E z^$C1l>U5SSlAnlACom=g7o~x9476c=sC}e)#2Jxz-yZe9uRZ?)u!<6C2Kus6iokzNWTpGkkjj5U-Gr@%nF(8I>m6c1shiZZenO@%qWeRh^zs zK?UTz*i2+Xg0pOoJG=HnxSF`76Q>brT^7RF#3G`Z-#kLLd`-8ts2fXycf8rmzwUc&x zY#+K^R_Sz7iq#^A1OWwsd zYipOHem*_@=%f#KdHQx`mQboMG@H|p(kAqBr8k-%8tPMMut)X(Pdez3mF_Z8x?t-@ zve8+1Ci2<($fne$P@UBG1_lEibAv!xm}YF_Z5TH0LjnG8J^Lf$cB?yd!!=RChZ|gc=WBkXjz{^y+Q;`SLC32C~?5l%GoICv`R70|2bcFqNS?*K2$003>X00LMHOwC&pL__T9|$VzB-uqFk$QiWlFYK|!`cYwztn&mfAuYf)=3tR)op)s#(hM*01()f$0{}@KFA>9Qp2{xo50!W) zq9i`#U*ih5x(ATYL4vB|hUG8VYz?=0!pHHslFyh_(C+qpX$U7RTM~^Aut(BT+XYMU zvL#VHJS|6Q!Qu5$`S^YW_L=Q=)=O*Y`4x(PVWLD0%@f<&)p&z> zB*MOx1>GJ0#N@>K8ADIQw-#(T3dVEg*=^DI)B-KTy9ha4tLTGY_LMfGqcdv1ASV9) zkSIRl`KfaXN_??spofy9#1^Y_QLpr{?Ab! zybUwcDA1%*@h^CjjNb=zYPe113qpk0c8+VWRMrp6>8hii@F&NH<8QKDbVdHc9l`PU z<2-E+V*MVfMfuo%e{zj$&^w%e1;IDv<5pR*7u=Ju9#Gu_CX!wGswxk@2kl0wDt`P$ zKK8(7*|7%s`1)pw6uAZqeyip$$MO0rR#`_MU9Iq8xTf3h?)R3ce1_v614}7=cT&t= zT8l!5 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/weapon_drop.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/weapon_drop.vmt new file mode 100644 index 000000000..a18fb7b02 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/weapon_drop.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/hudhelp/weapon_drop" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/weapon_drop.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/hudhelp/weapon_drop.vtf new file mode 100644 index 0000000000000000000000000000000000000000..90d5beafe563237127c50dd4d2c66e619dde5bb4 GIT binary patch literal 5696 zcmd5=32+nF8UA;rwIq-&fvsTIF_DU0QmCiEl*1g=k^@i-K{L@aB!wUhAu#v~ZJC6l z77huJNo!+6>P|4`8YqDoLJ|w^XZ&#}j5yk_w z9o8d1|NH;%KfeEXPtI{o0UZGF_;KP##2>H%aQL_UxTT+i`jKCF>?T}w@%YPZj*7`F z{MCKlrYD5(v+Do|nP+VbPS%rina~*2+Q_dY=fvhOlh0~61AEVV;E}4p(?7P)??16I z2r!%E%xMhT^fO)}B>15fEawPW)r@A!#_1b_C1xi+%pmF~>RO9>^a&a5b5Kr_=FEC_ zrsB!jO`QI{ef##kJR!}@-UL=Id4}iWcu`DkQsN$6?aTLjs!Cq_REd8N?>nFfuG z>qg&#RCFP^PQh>Zm!+-muE`Ep1r!`9@8r`Z4azHcs)<(dRB5YT!P$7a!G2}AX>k+! z!*GV0;`XVW6;`7@hPNsoyZ?Ua2{NAL+CjCleG6r@i%;K*^+BF@7X6W9c;}9jX+m;! z%YE^FI7k^DgzV#AYsKKNQ?pE;11Z7l`|V~rB(;|9e-G$Mrfx82BTr7vuqOzEC1RPQ;R{p+d0@e7VqI<8K? z&mkp!Y+2-AbS=cz5Y}IGZw+{ruoflS-xGaJN#jW@KNpEjnDeog8e+I#U6TLum2YeKeyRjra#m% zxDBgG#sd*xD|;wyl>f?sX(j2|<>}16%jL?soBqD+t9)+fByZvh;29pbmOZiZIf}tf z-+}d$j1v?%+AfdgsC_=;HK|0(pn_a7IT?-9H$Z_4zo zga{4r4^~Z|EsWBA)YrgVIe(FOh|nXS4`W4TKL=rO^d@*N3}v(hkHC;N4cces@r(x0 zLUso&rJ-t^cZ>!Io3r0Ade;by)*?No2ceuPQ3FFrMuf5xcLs7I6Qv{5eIR$aQkkjVlLWzf9wa5H^l< za+MX83LhY36vq#JMPrp|QG}(i0?*q>yx-H`{Dmn6t-b;oe@+mD0igk_8uo-GwqJBQ z#Udr&s~FAtEAay6tFGS&h33F`%eQ897lT;N5A?ciAR+RMX3sbL<%(A|o-9VQcm}na zE7m3Dy{+&?t_|Z+{R1a)afiI?N23YG*P_KB$NPSC>i2TK@Vq0`_-q~zMQ^G4Q^M2B z(+wHXe01~hR#qflobY|lvvV<_O>1)BkjTH9xE#yAAxrp(8c2es)!lYZ$Yzb$Q`% zCXet7y~f}0C>GUc3ivJ_E$)`>5tPq7jNtr9$q$~N;)BkU*!>}1_XuYzi^kg}7|M*l ztI;z5IIUJ*Z}(5dL0uQM*;~-rmsL8{52MBuM`&|cfc04$9GuGUchNWBZ@W2_(H3mD zE)dF0j!H$I!+bAh*33~nHiwIEGW6B`DObI7>rEjYqg_q}sE>Km7hgDs{m@YmWcpY? z=$h87%%wNKU^KRynAC};Z>zU19z3_#o9=X49-c>X{fgKy8gP`U^=(1A-?gqYw7WDq zuhclyHL-K;>ICa#R4eu2LIr)r8_RF! zHsJm4ofAyIAXqHj9Pry#{N>SzJ#B(K57hI8-1GgV-&a=)!w50chqq;UPTyWniJkVa zj<#1bK4ue=WALLtgb@(;Z9PQ%iq=P1FKVn{bgTPgFTjSLC`W@a${+da0J>h-T#dm!a==~u~@3Y0>?${`|jbK27<4%lyT0OD1&EYJ2$PJk@_VJHU!l@dl%vE}k zmC-nU?(Fw~GT>DCJJvP?1;DDl=PPRF`VYoGwDB*y_Gw{nBm2H8&jYT3pILt*TbVCO zNlC6x!kFr=QaRr&Fg32bt%q;W7CTC-{K3>Wsot=07o%DEDEHU0dr+GDpb_Wc zVmUw9&SX&Uy0iwba(>|Tl$bsb9ig28^}M*V-!ZQ5;IdODqxnw`(AKFoqpTk%s!RKT z^Xoz5z1RH~|J6Xau28;D$>#-rkHse%U;j&ZSwAC2?|*Sp=|uMb7M5?(dGGW2Uj6%D b%j-&)7YvESBM5|~ZYgG8S(JSK7vlR5@BiKk literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/huds/old_ttt/preview.png b/gamemodes/terrortown/content/materials/vgui/ttt/huds/old_ttt/preview.png similarity index 100% rename from materials/vgui/ttt/huds/old_ttt/preview.png rename to gamemodes/terrortown/content/materials/vgui/ttt/huds/old_ttt/preview.png diff --git a/materials/vgui/ttt/huds/pure_skin/preview.png b/gamemodes/terrortown/content/materials/vgui/ttt/huds/pure_skin/preview.png similarity index 100% rename from materials/vgui/ttt/huds/pure_skin/preview.png rename to gamemodes/terrortown/content/materials/vgui/ttt/huds/pure_skin/preview.png diff --git a/materials/vgui/ttt/icon_armor.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_armor.vmt similarity index 100% rename from materials/vgui/ttt/icon_armor.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_armor.vmt diff --git a/materials/vgui/ttt/icon_armor.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_armor.vtf similarity index 100% rename from materials/vgui/ttt/icon_armor.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_armor.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_beacon.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_beacon.vmt new file mode 100644 index 000000000..1743de409 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/icon_beacon.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/icon_beacon" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_beacon.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_beacon.vtf new file mode 100644 index 0000000000000000000000000000000000000000..5eba044ddcc0d7632dc713e66005a7cf001ff15d GIT binary patch literal 16592 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_credits_transparent.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_credits_transparent.vmt new file mode 100644 index 000000000..5dbca5556 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/icon_credits_transparent.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/icon_credits_transparent" + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_credits_transparent.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_credits_transparent.vtf new file mode 100644 index 0000000000000000000000000000000000000000..c88ce76516fce80bc5133fc0e8bd43e791494fca GIT binary patch literal 21924 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 W1C2J&XakKl&}ajVHt@e{1OEr(WUz?< literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_decoy.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_decoy.vmt new file mode 100644 index 000000000..2dbc18906 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/icon_decoy.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/icon_decoy" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_decoy.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_decoy.vtf new file mode 100644 index 0000000000000000000000000000000000000000..385466137b6c9b14009e570a6a14ec721dd691d7 GIT binary patch literal 16464 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 MpL6$s{~g=^4~;HQtN;K2 literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/icon_drown.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_drown.vmt similarity index 100% rename from materials/vgui/ttt/icon_drown.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_drown.vmt diff --git a/materials/vgui/ttt/icon_drown.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_drown.vtf similarity index 100% rename from materials/vgui/ttt/icon_drown.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_drown.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_firegrenade.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_firegrenade.vmt new file mode 100644 index 000000000..9e8cbc0c6 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/icon_firegrenade.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/icon_firegrenade" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_firegrenade.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_firegrenade.vtf new file mode 100644 index 0000000000000000000000000000000000000000..26c926a697dab886ba24ecfd4d30a1429820f747 GIT binary patch literal 16592 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_floor.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_floor.vmt new file mode 100644 index 000000000..2918c4a41 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/icon_floor.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/icon_floor" + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_floor.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_floor.vtf new file mode 100644 index 0000000000000000000000000000000000000000..f8f48a1cf0e8c3ad3232fd9e51617e78bedc984a GIT binary patch literal 16464 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 P4%Gksv&n=17q9;t2HOj< literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/icon_magneto_stick.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_magneto_stick.vmt similarity index 100% rename from materials/vgui/ttt/icon_magneto_stick.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_magneto_stick.vmt diff --git a/materials/vgui/ttt/icon_magneto_stick.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_magneto_stick.vtf similarity index 100% rename from materials/vgui/ttt/icon_magneto_stick.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_magneto_stick.vtf diff --git a/materials/vgui/ttt/icon_nodrowningdmg.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_nodrowningdmg.vmt similarity index 100% rename from materials/vgui/ttt/icon_nodrowningdmg.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_nodrowningdmg.vmt diff --git a/materials/vgui/ttt/icon_nodrowningdmg.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_nodrowningdmg.vtf similarity index 100% rename from materials/vgui/ttt/icon_nodrowningdmg.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_nodrowningdmg.vtf diff --git a/materials/vgui/ttt/icon_noenergydmg.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_noenergydmg.vmt similarity index 100% rename from materials/vgui/ttt/icon_noenergydmg.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_noenergydmg.vmt diff --git a/materials/vgui/ttt/icon_noenergydmg.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_noenergydmg.vtf similarity index 100% rename from materials/vgui/ttt/icon_noenergydmg.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_noenergydmg.vtf diff --git a/materials/vgui/ttt/icon_noexplosiondmg.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_noexplosiondmg.vmt similarity index 100% rename from materials/vgui/ttt/icon_noexplosiondmg.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_noexplosiondmg.vmt diff --git a/materials/vgui/ttt/icon_noexplosiondmg.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_noexplosiondmg.vtf similarity index 100% rename from materials/vgui/ttt/icon_noexplosiondmg.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_noexplosiondmg.vtf diff --git a/materials/vgui/ttt/icon_nofalldmg.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_nofalldmg.vmt similarity index 100% rename from materials/vgui/ttt/icon_nofalldmg.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_nofalldmg.vmt diff --git a/materials/vgui/ttt/icon_nofalldmg.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_nofalldmg.vtf similarity index 100% rename from materials/vgui/ttt/icon_nofalldmg.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_nofalldmg.vtf diff --git a/materials/vgui/ttt/icon_nofiredmg.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_nofiredmg.vmt similarity index 100% rename from materials/vgui/ttt/icon_nofiredmg.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_nofiredmg.vmt diff --git a/materials/vgui/ttt/icon_nofiredmg.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_nofiredmg.vtf similarity index 100% rename from materials/vgui/ttt/icon_nofiredmg.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_nofiredmg.vtf diff --git a/materials/vgui/ttt/icon_nohazarddmg.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_nohazarddmg.vmt similarity index 100% rename from materials/vgui/ttt/icon_nohazarddmg.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_nohazarddmg.vmt diff --git a/materials/vgui/ttt/icon_nohazarddmg.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_nohazarddmg.vtf similarity index 100% rename from materials/vgui/ttt/icon_nohazarddmg.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_nohazarddmg.vtf diff --git a/materials/vgui/ttt/icon_nopropdmg.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_nopropdmg.vmt similarity index 100% rename from materials/vgui/ttt/icon_nopropdmg.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_nopropdmg.vmt diff --git a/materials/vgui/ttt/icon_nopropdmg.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_nopropdmg.vtf similarity index 100% rename from materials/vgui/ttt/icon_nopropdmg.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_nopropdmg.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_smokegrenade.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_smokegrenade.vmt new file mode 100644 index 000000000..55c697b61 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/icon_smokegrenade.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/icon_smokegrenade" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_smokegrenade.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_smokegrenade.vtf new file mode 100644 index 0000000000000000000000000000000000000000..63e39becda4088f4f4ae31696b1ffeae3650c15d GIT binary patch literal 16592 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�uszrW6e~Ze&QF!;vaQJd3339q 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 M`P!=g|8Ld*0qAtX2><{9 literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/icon_speedrun.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_speedrun.vmt similarity index 100% rename from materials/vgui/ttt/icon_speedrun.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_speedrun.vmt diff --git a/materials/vgui/ttt/icon_speedrun.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_speedrun.vtf similarity index 100% rename from materials/vgui/ttt/icon_speedrun.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/icon_speedrun.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_unarmed.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_unarmed.vmt new file mode 100644 index 000000000..41ae38242 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/icon_unarmed.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/icon_unarmed" + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} + diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_unarmed.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_unarmed.vtf new file mode 100644 index 0000000000000000000000000000000000000000..eebad48be9e48196abce46197559d1946e3c5a7a GIT binary patch literal 16592 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_1.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_1.vmt new file mode 100644 index 000000000..85c38dc75 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_1.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/icon_water_1" + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_1.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_1.vtf new file mode 100644 index 0000000000000000000000000000000000000000..9cc58d73207f9783ec56b549e944300353db39da GIT binary patch literal 16464 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 I`~TJcZ}+tS9RL6T literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_2.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_2.vmt new file mode 100644 index 000000000..fefe05414 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_2.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/icon_water_2" + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_2.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_2.vtf new file mode 100644 index 0000000000000000000000000000000000000000..60a8bcf5fdfc31fb40c432d400843a987c869a7d GIT binary patch literal 16464 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 Hzt{d>rX`nH literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_3.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_3.vmt new file mode 100644 index 000000000..a2aad522c --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_3.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/icon_water_3" + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_3.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/icon_water_3.vtf new file mode 100644 index 0000000000000000000000000000000000000000..af8c8255229508fd04cb02a24be4de2e23458f45 GIT binary patch literal 16464 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 Nb(wEr+yAff{{T1mblU&` literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/indirect_confirmed.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/indirect_confirmed.vmt similarity index 100% rename from materials/vgui/ttt/indirect_confirmed.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/indirect_confirmed.vmt diff --git a/materials/vgui/ttt/indirect_confirmed.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/indirect_confirmed.vtf similarity index 100% rename from materials/vgui/ttt/indirect_confirmed.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/indirect_confirmed.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/beacon.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/beacon.vmt new file mode 100644 index 000000000..935313580 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/beacon.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/marker_vision/beacon" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/beacon.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/beacon.vtf new file mode 100644 index 0000000000000000000000000000000000000000..6e5a914a6af45ffa767eeb6568cd9828a786ee77 GIT binary patch literal 349760 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 Wd;oj^d;oj^d;oj^d|+ttf&T>>*GSO- literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/c4.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/c4.vmt new file mode 100644 index 000000000..2273d71b9 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/c4.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/marker_vision/c4" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/materials/vgui/ttt/vskin/events/c4plant.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/c4.vtf similarity index 100% rename from materials/vgui/ttt/vskin/events/c4plant.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/c4.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/radio.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/radio.vmt new file mode 100644 index 000000000..a2a236567 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/radio.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/marker_vision/radio" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/radio.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/marker_vision/radio.vtf new file mode 100644 index 0000000000000000000000000000000000000000..04c454bf3b6ebd33a6f57cffceb97d9893de7fd6 GIT binary patch literal 349760 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 SP}Sc5SM{`wwaPV|$^Qq=WsUs+ literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/perks/hud_nodrowningdmg.png b/gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_nodrowningdmg.png similarity index 100% rename from materials/vgui/ttt/perks/hud_nodrowningdmg.png rename to gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_nodrowningdmg.png diff --git a/materials/vgui/ttt/perks/hud_noenergydmg.png b/gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_noenergydmg.png similarity index 100% rename from materials/vgui/ttt/perks/hud_noenergydmg.png rename to gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_noenergydmg.png diff --git a/materials/vgui/ttt/perks/hud_noexplosiondmg.png b/gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_noexplosiondmg.png similarity index 100% rename from materials/vgui/ttt/perks/hud_noexplosiondmg.png rename to gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_noexplosiondmg.png diff --git a/materials/vgui/ttt/perks/hud_nofalldmg.png b/gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_nofalldmg.png similarity index 100% rename from materials/vgui/ttt/perks/hud_nofalldmg.png rename to gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_nofalldmg.png diff --git a/materials/vgui/ttt/perks/hud_nofiredmg.png b/gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_nofiredmg.png similarity index 100% rename from materials/vgui/ttt/perks/hud_nofiredmg.png rename to gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_nofiredmg.png diff --git a/materials/vgui/ttt/perks/hud_nohazarddmg.png b/gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_nohazarddmg.png similarity index 100% rename from materials/vgui/ttt/perks/hud_nohazarddmg.png rename to gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_nohazarddmg.png diff --git a/materials/vgui/ttt/perks/hud_nopropdmg.png b/gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_nopropdmg.png similarity index 100% rename from materials/vgui/ttt/perks/hud_nopropdmg.png rename to gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_nopropdmg.png diff --git a/materials/vgui/ttt/perks/hud_radar.png b/gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_radar.png similarity index 100% rename from materials/vgui/ttt/perks/hud_radar.png rename to gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_radar.png diff --git a/materials/vgui/ttt/perks/hud_speedrun.png b/gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_speedrun.png similarity index 100% rename from materials/vgui/ttt/perks/hud_speedrun.png rename to gamemodes/terrortown/content/materials/vgui/ttt/perks/hud_speedrun.png diff --git a/materials/vgui/ttt/perks/old_ttt_bg.png b/gamemodes/terrortown/content/materials/vgui/ttt/perks/old_ttt_bg.png similarity index 100% rename from materials/vgui/ttt/perks/old_ttt_bg.png rename to gamemodes/terrortown/content/materials/vgui/ttt/perks/old_ttt_bg.png diff --git a/materials/vgui/ttt/pickup/icon_ammo.png b/gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_ammo.png similarity index 100% rename from materials/vgui/ttt/pickup/icon_ammo.png rename to gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_ammo.png diff --git a/materials/vgui/ttt/pickup/icon_class.png b/gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_class.png similarity index 100% rename from materials/vgui/ttt/pickup/icon_class.png rename to gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_class.png diff --git a/materials/vgui/ttt/pickup/icon_extra.png b/gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_extra.png similarity index 100% rename from materials/vgui/ttt/pickup/icon_extra.png rename to gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_extra.png diff --git a/materials/vgui/ttt/pickup/icon_heavy.png b/gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_heavy.png similarity index 100% rename from materials/vgui/ttt/pickup/icon_heavy.png rename to gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_heavy.png diff --git a/materials/vgui/ttt/pickup/icon_nades.png b/gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_nades.png similarity index 100% rename from materials/vgui/ttt/pickup/icon_nades.png rename to gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_nades.png diff --git a/materials/vgui/ttt/pickup/icon_pistol.png b/gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_pistol.png similarity index 100% rename from materials/vgui/ttt/pickup/icon_pistol.png rename to gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_pistol.png diff --git a/materials/vgui/ttt/pickup/icon_special.png b/gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_special.png similarity index 100% rename from materials/vgui/ttt/pickup/icon_special.png rename to gamemodes/terrortown/content/materials/vgui/ttt/pickup/icon_special.png diff --git a/materials/vgui/ttt/revived.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/revived.vmt similarity index 100% rename from materials/vgui/ttt/revived.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/revived.vmt diff --git a/materials/vgui/ttt/revived.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/revived.vtf similarity index 100% rename from materials/vgui/ttt/revived.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/revived.vtf diff --git a/materials/vgui/ttt/score_logo_2.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/score_logo_2.vmt similarity index 100% rename from materials/vgui/ttt/score_logo_2.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/score_logo_2.vmt diff --git a/materials/vgui/ttt/score_logo_2.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/score_logo_2.vtf similarity index 100% rename from materials/vgui/ttt/score_logo_2.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/score_logo_2.vtf diff --git a/materials/vgui/ttt/score_logo_2_old.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/score_logo_2_old.vmt similarity index 100% rename from materials/vgui/ttt/score_logo_2_old.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/score_logo_2_old.vmt diff --git a/materials/vgui/ttt/score_logo_2_old.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/score_logo_2_old.vtf similarity index 100% rename from materials/vgui/ttt/score_logo_2_old.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/score_logo_2_old.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_carry.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_carry.vmt new file mode 100644 index 000000000..e88254034 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_carry.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/slot/slot_weapon_carry" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 + "$ignorez" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_carry.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_carry.vtf new file mode 100644 index 0000000000000000000000000000000000000000..7c882e991ca7b5e1928169cdcf9663bf63bb3045 GIT binary patch literal 349760 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_class.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_class.vmt new file mode 100644 index 000000000..baf9c52d7 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_class.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/slot/slot_weapon_class" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 + "$ignorez" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_class.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_class.vtf new file mode 100644 index 0000000000000000000000000000000000000000..36c5dc0b2ae189bee132972ff261764a5dd0f58c GIT binary patch literal 349760 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 literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_extra.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_extra.vmt new file mode 100644 index 000000000..e0d9fac0f --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_extra.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/slot/slot_weapon_extra" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 + "$ignorez" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_extra.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_extra.vtf new file mode 100644 index 0000000000000000000000000000000000000000..f2d48e929726eeb5a25cc8ea895f954c13ef8263 GIT binary patch literal 349760 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 UeE=LleW-{5;sNzRMD@Y{2Z*4_-~a#s literal 0 HcmV?d00001 diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_nade.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_nade.vmt new file mode 100644 index 000000000..24bb9b575 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_nade.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/slot/slot_weapon_nade" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 + "$ignorez" 1 +} diff --git a/materials/vgui/ttt/tid/tid_big_weapon_nade.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_nade.vtf similarity index 100% rename from materials/vgui/ttt/tid/tid_big_weapon_nade.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_nade.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_pistol.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_pistol.vmt new file mode 100644 index 000000000..e159da5b0 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_pistol.vmt @@ -0,0 +1,11 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/slot/slot_weapon_pistol" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 + "$ignorez" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_pistol.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/slot/slot_weapon_pistol.vtf new file mode 100644 index 0000000000000000000000000000000000000000..2ef9162f486f8fe545f503f59d3acd7dafbd010e GIT binary patch literal 349760 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 Ee}O*UV}#ftub@4XRaUd7Y}r&kKC(uH^S$fF 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 YFPd@yA50v;2k^l_0P)d)Ays<#|I5fvYXATM literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/target_icon.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/target_icon.vmt similarity index 100% rename from materials/vgui/ttt/target_icon.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/target_icon.vmt diff --git a/materials/vgui/ttt/target_icon.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/target_icon.vtf similarity index 100% rename from materials/vgui/ttt/target_icon.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/target_icon.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_ammo.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_ammo.vmt new file mode 100644 index 000000000..2765d2b98 --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_ammo.vmt @@ -0,0 +1,10 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/tid/tid_ammo" + "$nocull" 1 + "$nodecal" 1 + "$nolod" 1 + "$translucent" 1 + "$vertexalpha" 1 + "$vertexcolor" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_ammo.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_ammo.vtf new file mode 100644 index 0000000000000000000000000000000000000000..931daed3780b76005be91262637664f819dcc449 GIT binary patch literal 5696 zcmeHLPe>F|7=P=Ei{PI{Bm{d5pxX&VklPeM-TSh3plN_N1e+ zW*vt)c7G!bR)3;g%K1UKaQJPPf`xE9wD&oM9t=UH&2EFM$A}Au14}k6)Seqg{LMILlq);*c&$;!rWRi5eAyxz5q|9$ws8A5qx}>g+_PP0HoG1# z@!Bcb2}19Ap%=$ba=cg$bz}ctrFh0Zf;bf_!gzRZGByT+cPf;?Nob0cgqkFKIaMb)fw;;@O2o+E4@e31MNi-EbH}8dfO%c^52M^ z@Au8G#eci}lASBp`V*fq1;n47mU13rs>pwwuionX*XA!R%-4*!%0DlPhoL}J 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 GY5yNi`P-QQ literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/tid/tid_big_weapon_melee.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_melee.vmt similarity index 100% rename from materials/vgui/ttt/tid/tid_big_weapon_melee.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_melee.vmt diff --git a/materials/vgui/ttt/tid/tid_big_weapon_melee.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_melee.vtf similarity index 100% rename from materials/vgui/ttt/tid/tid_big_weapon_melee.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_melee.vtf diff --git a/materials/vgui/ttt/tid/tid_big_weapon_nade.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_nade.vmt similarity index 100% rename from materials/vgui/ttt/tid/tid_big_weapon_nade.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_nade.vmt diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_nade.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_big_weapon_nade.vtf new file mode 100644 index 0000000000000000000000000000000000000000..00c3be56443b8ef9da91e3779cfbc9be6d96bc57 GIT binary patch literal 349632 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 W58;RKL-?UU0P(=!0rcQ(J^X*Qb{CKU literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/tid/tid_credits.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_credits.vmt similarity index 100% rename from materials/vgui/ttt/tid/tid_credits.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_credits.vmt diff --git a/materials/vgui/ttt/tid/tid_credits.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_credits.vtf similarity index 100% rename from materials/vgui/ttt/tid/tid_credits.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_credits.vtf diff --git a/materials/vgui/ttt/tid/tid_destructible.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_destructible.vmt similarity index 100% rename from materials/vgui/ttt/tid/tid_destructible.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_destructible.vmt diff --git a/materials/vgui/ttt/tid/tid_destructible.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_destructible.vtf similarity index 100% rename from materials/vgui/ttt/tid/tid_destructible.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_destructible.vtf diff --git a/materials/vgui/ttt/tid/tid_detective.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_detective.vmt similarity index 100% rename from materials/vgui/ttt/tid/tid_detective.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_detective.vmt diff --git a/materials/vgui/ttt/tid/tid_detective.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_detective.vtf similarity index 100% rename from materials/vgui/ttt/tid/tid_detective.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_detective.vtf diff --git a/materials/vgui/ttt/tid/tid_locked.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_locked.vmt similarity index 100% rename from materials/vgui/ttt/tid/tid_locked.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_locked.vmt diff --git a/materials/vgui/ttt/tid/tid_locked.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_locked.vtf similarity index 100% rename from materials/vgui/ttt/tid/tid_locked.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/tid/tid_locked.vtf diff --git a/materials/vgui/ttt/ttt2_hand_filled.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_filled.vmt similarity index 100% rename from materials/vgui/ttt/ttt2_hand_filled.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_filled.vmt diff --git a/materials/vgui/ttt/ttt2_hand_filled.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_filled.vtf similarity index 100% rename from materials/vgui/ttt/ttt2_hand_filled.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_filled.vtf diff --git a/materials/vgui/ttt/ttt2_hand_line.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_line.vmt similarity index 100% rename from materials/vgui/ttt/ttt2_hand_line.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_line.vmt diff --git a/materials/vgui/ttt/ttt2_hand_line.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_line.vtf similarity index 100% rename from materials/vgui/ttt/ttt2_hand_line.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_line.vtf diff --git a/materials/vgui/ttt/ttt2_hand_outline.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_outline.vmt similarity index 100% rename from materials/vgui/ttt/ttt2_hand_outline.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_outline.vmt diff --git a/materials/vgui/ttt/ttt2_hand_outline.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_outline.vtf similarity index 100% rename from materials/vgui/ttt/ttt2_hand_outline.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_hand_outline.vtf diff --git a/materials/vgui/ttt/ttt2_indicator_addondev.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_addondev.vmt similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_addondev.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_addondev.vmt diff --git a/materials/vgui/ttt/ttt2_indicator_addondev.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_addondev.vtf similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_addondev.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_addondev.vtf diff --git a/materials/vgui/ttt/ttt2_indicator_admin.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_admin.vmt similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_admin.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_admin.vmt diff --git a/materials/vgui/ttt/ttt2_indicator_admin.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_admin.vtf similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_admin.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_admin.vtf diff --git a/materials/vgui/ttt/ttt2_indicator_dev.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_dev.vmt similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_dev.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_dev.vmt diff --git a/materials/vgui/ttt/ttt2_indicator_dev.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_dev.vtf similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_dev.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_dev.vtf diff --git a/materials/vgui/ttt/ttt2_indicator_heroes.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_heroes.vmt similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_heroes.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_heroes.vmt diff --git a/materials/vgui/ttt/ttt2_indicator_heroes.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_heroes.vtf similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_heroes.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_heroes.vtf diff --git a/materials/vgui/ttt/ttt2_indicator_streamer.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_streamer.vmt similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_streamer.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_streamer.vmt diff --git a/materials/vgui/ttt/ttt2_indicator_streamer.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_streamer.vtf similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_streamer.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_streamer.vtf diff --git a/materials/vgui/ttt/ttt2_indicator_vip.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_vip.vmt similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_vip.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_vip.vmt diff --git a/materials/vgui/ttt/ttt2_indicator_vip.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_vip.vtf similarity index 100% rename from materials/vgui/ttt/ttt2_indicator_vip.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/ttt2_indicator_vip.vtf diff --git a/materials/vgui/ttt/vskin/card_added.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/card_added.vmt similarity index 100% rename from materials/vgui/ttt/vskin/card_added.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/card_added.vmt diff --git a/materials/vgui/ttt/vskin/card_added.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/card_added.vtf similarity index 100% rename from materials/vgui/ttt/vskin/card_added.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/card_added.vtf diff --git a/materials/vgui/ttt/vskin/card_removed.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/card_removed.vmt similarity index 100% rename from materials/vgui/ttt/vskin/card_removed.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/card_removed.vmt diff --git a/materials/vgui/ttt/vskin/card_removed.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/card_removed.vtf similarity index 100% rename from materials/vgui/ttt/vskin/card_removed.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/card_removed.vtf diff --git a/materials/vgui/ttt/vskin/events/base_event.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/base_event.vmt similarity index 100% rename from materials/vgui/ttt/vskin/events/base_event.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/base_event.vmt diff --git a/materials/vgui/ttt/vskin/events/base_event.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/base_event.vtf similarity index 100% rename from materials/vgui/ttt/vskin/events/base_event.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/base_event.vtf diff --git a/materials/vgui/ttt/vskin/events/bodyfound.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/bodyfound.vmt similarity index 100% rename from materials/vgui/ttt/vskin/events/bodyfound.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/bodyfound.vmt diff --git a/materials/vgui/ttt/vskin/events/bodyfound.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/bodyfound.vtf similarity index 100% rename from materials/vgui/ttt/vskin/events/bodyfound.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/bodyfound.vtf diff --git a/materials/vgui/ttt/vskin/events/c4disarm.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4disarm.vmt similarity index 100% rename from materials/vgui/ttt/vskin/events/c4disarm.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4disarm.vmt diff --git a/materials/vgui/ttt/vskin/events/c4disarm.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4disarm.vtf similarity index 100% rename from materials/vgui/ttt/vskin/events/c4disarm.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4disarm.vtf diff --git a/materials/vgui/ttt/vskin/events/c4explode.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4explode.vmt similarity index 100% rename from materials/vgui/ttt/vskin/events/c4explode.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4explode.vmt diff --git a/materials/vgui/ttt/vskin/events/c4explode.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4explode.vtf similarity index 100% rename from materials/vgui/ttt/vskin/events/c4explode.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4explode.vtf diff --git a/materials/vgui/ttt/vskin/events/c4plant.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4plant.vmt similarity index 100% rename from materials/vgui/ttt/vskin/events/c4plant.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4plant.vmt diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4plant.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/events/c4plant.vtf new file mode 100644 index 0000000000000000000000000000000000000000..cbbc077edc75092de8a5247ba85da5c641b0a272 GIT binary patch literal 349632 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ð}U15 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 literal 0 HcmV?d00001 diff --git a/materials/vgui/ttt/vskin/icon_back.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_back.vmt similarity index 100% rename from materials/vgui/ttt/vskin/icon_back.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_back.vmt diff --git a/materials/vgui/ttt/vskin/icon_back.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_back.vtf similarity index 100% rename from materials/vgui/ttt/vskin/icon_back.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_back.vtf diff --git a/materials/vgui/ttt/vskin/icon_close.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_close.vmt similarity index 100% rename from materials/vgui/ttt/vskin/icon_close.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_close.vmt diff --git a/materials/vgui/ttt/vskin/icon_close.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_close.vtf similarity index 100% rename from materials/vgui/ttt/vskin/icon_close.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_close.vtf diff --git a/materials/vgui/ttt/vskin/icon_collapse_closed.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_collapse_closed.vmt similarity index 100% rename from materials/vgui/ttt/vskin/icon_collapse_closed.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_collapse_closed.vmt diff --git a/materials/vgui/ttt/vskin/icon_collapse_closed.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_collapse_closed.vtf similarity index 100% rename from materials/vgui/ttt/vskin/icon_collapse_closed.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_collapse_closed.vtf diff --git a/materials/vgui/ttt/vskin/icon_collapse_opened.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_collapse_opened.vmt similarity index 100% rename from materials/vgui/ttt/vskin/icon_collapse_opened.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_collapse_opened.vmt diff --git a/materials/vgui/ttt/vskin/icon_collapse_opened.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_collapse_opened.vtf similarity index 100% rename from materials/vgui/ttt/vskin/icon_collapse_opened.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_collapse_opened.vtf diff --git a/materials/vgui/ttt/vskin/icon_disable.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_disable.vmt similarity index 100% rename from materials/vgui/ttt/vskin/icon_disable.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_disable.vmt diff --git a/materials/vgui/ttt/vskin/icon_disable.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_disable.vtf similarity index 100% rename from materials/vgui/ttt/vskin/icon_disable.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_disable.vtf diff --git a/materials/vgui/ttt/vskin/icon_hattable_no.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_hattable_no.vmt similarity index 100% rename from materials/vgui/ttt/vskin/icon_hattable_no.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_hattable_no.vmt diff --git a/materials/vgui/ttt/vskin/icon_hattable_no.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_hattable_no.vtf similarity index 100% rename from materials/vgui/ttt/vskin/icon_hattable_no.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_hattable_no.vtf diff --git a/materials/vgui/ttt/vskin/icon_hattable_yes.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_hattable_yes.vmt similarity index 100% rename from materials/vgui/ttt/vskin/icon_hattable_yes.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_hattable_yes.vmt diff --git a/materials/vgui/ttt/vskin/icon_hattable_yes.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_hattable_yes.vtf similarity index 100% rename from materials/vgui/ttt/vskin/icon_hattable_yes.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_hattable_yes.vtf diff --git a/materials/vgui/ttt/vskin/icon_headbox_no.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_headbox_no.vmt similarity index 100% rename from materials/vgui/ttt/vskin/icon_headbox_no.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_headbox_no.vmt diff --git a/materials/vgui/ttt/vskin/icon_headbox_no.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_headbox_no.vtf similarity index 100% rename from materials/vgui/ttt/vskin/icon_headbox_no.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_headbox_no.vtf diff --git a/materials/vgui/ttt/vskin/icon_headbox_yes.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_headbox_yes.vmt similarity index 100% rename from materials/vgui/ttt/vskin/icon_headbox_yes.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_headbox_yes.vmt diff --git a/materials/vgui/ttt/vskin/icon_headbox_yes.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_headbox_yes.vtf similarity index 100% rename from materials/vgui/ttt/vskin/icon_headbox_yes.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_headbox_yes.vtf diff --git a/materials/vgui/ttt/vskin/icon_reset.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_reset.vmt similarity index 100% rename from materials/vgui/ttt/vskin/icon_reset.vmt rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_reset.vmt diff --git a/materials/vgui/ttt/vskin/icon_reset.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_reset.vtf similarity index 100% rename from materials/vgui/ttt/vskin/icon_reset.vtf rename to gamemodes/terrortown/content/materials/vgui/ttt/vskin/icon_reset.vtf diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/vskin/markers/builtin.vmt b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/markers/builtin.vmt new file mode 100644 index 000000000..8469bf7ca --- /dev/null +++ b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/markers/builtin.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "vgui/ttt/vskin/markers/builtin" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} diff --git a/gamemodes/terrortown/content/materials/vgui/ttt/vskin/markers/builtin.vtf b/gamemodes/terrortown/content/materials/vgui/ttt/vskin/markers/builtin.vtf new file mode 100644 index 0000000000000000000000000000000000000000..a6cc58dba430c7d975b9cce93d0b13b926cc81a6 GIT binary patch literal 5696 zcmds5eN0=|6~FJ<4kn9hr;9ajv<=nbplW4uw856Fn;+RqY(-V9)(>1Ys4mU6NLw(K zrYKD}*&^$@7G;<$RQZF1v`k7>6tPCD)lLB{P3_W>g>HNy>oDV)7r+P<&R$Q{JhHgK$U&o(WU)#=x?daAy>I+H0=EF zrI{UY-*{o-3)$Da#x z+>a(lkRSQcGX?VczFO=W^#ZRq;)PMF1fTSYZgH=+DZ0QHVy1T{>d5JWUU3N=Fd zuk?(s%h7z4@Ir{Bo}QfMzpA0Q-JInYjxnLnt|sJ9_SNi`dpvfy)QuK)y4;pxlX3r! z5TqZ6SueoAGYYn+4p%lzRz^Oebrx*K^-JjM>LZ^$l`iq7caXeLAe{6yf7S;J9q6yY zb_rX4KR1Ixh{>h+0hs3UH-4}`(x2G(EsCGzLDgw{*1Y$9n>Nym`O~X5CJ%i)7g_u? zG6rz3m6>dABflNZkdDGU+B4Wr_V}9TMi76imjBSRl?@;5Zhk5DckPQwJ7TEQWh%BT(Kow3M9#m8 z>xKVI`V*0o${#=1n<-@#ZQr+i<1(}kB=)^ea$;dst^WY^d%GL$sdr#JU@0_(K%t@dSU~N+jWp;|scd=4)s^ znxB7=`1#{W(uXumGv5_~Z^m}lsOs{3c&AmL$8=WZvoHXaCWZ86vU>>qhph$uI@peK zRY7;$q0_h?I$kPA{cwFORGVf8D4#jrvAiymXOv$kyfE3b#!p4sRhcPv4Eo=V)C2r(&QIBi3_%KasbOyE!lfS+D?=2dhkBkKaO2Pi1`0nwy@^ipx zu}Fp5+rjhwNHFK0Go)3V=O6uo-_g^-f?Ky+^ctH3A9b&+TF@M2& zZzD4ae9_XJ9jlao7SG!xerO+^FGhJ_mK+Q}h4Q`O^+n8diLK%C6tu@!Kd`>MXnwxc zLHse^OU`qO&i{e^hPY|c&ZbR`@@BF(7zhL`W7Q1{sR-VGF;%X2|1j)dDGn>fUzQvv zTYt^(gNTs8Bnuta&nFgC)d_Yvo}-NT_?SoST0dxm#EaL;t*^S}#=y()^ul+%{-!gW2df{S($K!Uyf1QTG`$Xg$B^>EN zP469#>GxwXZnU4hJZxY5XUh%8%RK5`80gcIy8W6|-0Jej_YjTc&#CKiqy0+xzb2WA zj91o|5PwOsIQI?_`0c;Y}w1`c3gUI(1qIDPrF&E&VOD?9@@N9 zy#FW|?`8R9s4vEO=j6G_L_*+|Azlmhhpxvh>kam9_&7RquGPl#2anf+?KRqOr01nC zB0f%Vd@#wW626!@ZqM3RaK04m7m~+*`R(jJ1>@Vad6$SUQ-aIzY}EgMO&jZnCYj>L zvhUkWQdRR9bijLiMLh7xKRS-7?0WKA`onOJHje^$+w-(0vFb9NpQ5TO CurTime() then + self.Alpha = 1 - ((self.FadeIn - CurTime()) / self.FadeTime) + elseif self.FadeOut < CurTime() then + self.Alpha = 1 - ((CurTime() - self.FadeOut) / self.FadeTime) + end + + return IsValid(self.Dummy) +end + +--- +--@ignore +-- @realm client +function EFFECT:Render() + render.SuppressEngineLighting(true) + render.SetColorModulation(0.4, 0.4, 1) + render.SetBlend(0.8 * self.Alpha) + + if self.Dummy then + --self.Dummy:ClearPoseParameters() + self.Dummy:DrawModel() + end + + render.SetBlend(1) + render.SetColorModulation(1, 1, 1) + render.SuppressEngineLighting(false) +end diff --git a/gamemodes/terrortown/entities/effects/crimescene_shot.lua b/gamemodes/terrortown/entities/effects/crimescene_shot.lua new file mode 100644 index 000000000..73775295f --- /dev/null +++ b/gamemodes/terrortown/entities/effects/crimescene_shot.lua @@ -0,0 +1,56 @@ +--- +-- @class EFFECT +-- @section crimescene_shot + +local shot_mat = Material("cable/blue_elec") +--local clr = Color(0, 0, 100, 255) + +--- +--@ignore +-- @realm client +function EFFECT:Init(data) + self.ShotStart = data:GetStart() + self.ShotEnd = data:GetOrigin() + + -- ws = worldspace + self:SetRenderBoundsWS(self.ShotStart, self.ShotEnd) + + self.HitBox = data:GetMagnitude() + + self.Duration = data:GetScale() or 0 + self.EndTime = CurTime() + self.Duration + + self.FadeTime = 3 + + self.FadeIn = CurTime() + self.FadeTime + self.FadeOut = self.EndTime - self.FadeTime + + self.Width = 0 + self.WidthMax = 5 +end + +--- +--@ignore +-- @realm client +function EFFECT:Think() + if self.EndTime < CurTime() then + return false + end + + if self.FadeIn > CurTime() then + self.Width = self.WidthMax * (1 - ((self.FadeIn - CurTime()) / self.FadeTime)) + elseif self.FadeOut < CurTime() then + self.Width = self.WidthMax * (1 - ((CurTime() - self.FadeOut) / self.FadeTime)) + end + + return true +end + +--- +--@ignore +-- @realm client +function EFFECT:Render() + render.SetMaterial(shot_mat) + + render.DrawBeam(self.ShotStart, self.ShotEnd, self.Width, 0, 0, self.Color) +end diff --git a/gamemodes/terrortown/entities/effects/pulse_sphere.lua b/gamemodes/terrortown/entities/effects/pulse_sphere.lua new file mode 100644 index 000000000..6243a64e2 --- /dev/null +++ b/gamemodes/terrortown/entities/effects/pulse_sphere.lua @@ -0,0 +1,75 @@ +--- +-- @class EFFECT +-- @section pulse_sphere + +local model_orb = Model("models/Combine_Helicopter/helicopter_bomb01.mdl") +local mat_orb = Material("models/effects/splodearc_sheet") + +--- +--@ignore +-- @realm client +function EFFECT:Init(data) + self:SetPos(data:GetOrigin()) + + self.Radius = data:GetRadius() + + local rh = self.Radius + self:SetRenderBounds(Vector(-rh, -rh, -rh), Vector(rh, rh, rh)) + + self.EndTime = CurTime() + data:GetScale() + self.FadeTime = data:GetMagnitude() + + self.FadeIn = CurTime() + self.FadeTime + self.FadeOut = self.EndTime - self.FadeTime + + self.Alpha = 0 + + self.Orb = ClientsideModel(model_orb, RENDERGROUP_TRANSLUCENT) + self.Orb:SetPos(data:GetOrigin()) + self.Orb:AddEffects(EF_NODRAW) + + local r = 28 / 2 -- hardcoded because stuff like :BoundingRadius won't work here + + self.EndScale = self.Radius / r +end + +--- +--@ignore +-- @realm client +function EFFECT:Think() + if self.EndTime < CurTime() then + SafeRemoveEntity(self.Orb) + return false + end + + if self.FadeIn > CurTime() then + self.Alpha = 1 - ((self.FadeIn - CurTime()) / self.FadeTime) + elseif self.FadeOut < CurTime() then + self.Alpha = 1 - ((CurTime() - self.FadeOut) / self.FadeTime) + end + + self.Orb:SetModelScale(self.EndScale * self.Alpha, 0) + + local ang = self.Orb:GetAngles() + ang.y = ang.y + 500 * FrameTime() + self.Orb:SetAngles(ang) + + return IsValid(self.Orb) +end + +--- +--@ignore +-- @realm client +function EFFECT:Render() + render.MaterialOverride(mat_orb) + render.SuppressEngineLighting(true) + render.SetColorModulation(0, 0, 1) + render.SetBlend(0.8 * self.Alpha) + + self.Orb:DrawModel() + + render.SetBlend(1) + render.SetColorModulation(1, 1, 1) + render.SuppressEngineLighting(false) + render.MaterialOverride() +end diff --git a/gamemodes/terrortown/entities/effects/teleport_beamdown.lua b/gamemodes/terrortown/entities/effects/teleport_beamdown.lua new file mode 100644 index 000000000..575e8e55a --- /dev/null +++ b/gamemodes/terrortown/entities/effects/teleport_beamdown.lua @@ -0,0 +1,108 @@ +--- +-- @class EFFECT +-- @section teleport_beamdown + +local loopsound = Sound("ambient/levels/labs/teleport_mechanism_windup1.wav") + +local mat_rising = Material("models/props_combine/stasisshield_sheet") +local top = 80 +local final_height = top +local vector_up = Vector(0, 0, 1) + +--- +--@ignore +-- @realm client +function EFFECT:Init(data) + self.EffectOwner = data:GetEntity() + + self:SetPos(data:GetOrigin()) + self:SetAngles(data:GetAngles()) + + self.BasePos = self:GetPos() + self.BeamDownPos = self.BasePos + Vector(0, 0, final_height) + + self.DrawTop = true + + self.BeamDownTime = CurTime() + data:GetMagnitude() + self.EndTime = self.BeamDownTime + data:GetRadius() + + self.BeamDown = false + + if IsValid(self.EffectOwner) then + self.Dummy = ClientsideModel(self.EffectOwner:GetModel(), RENDERGROUP_OPAQUE) + self.Dummy:SetPos(self.BasePos) + self.Dummy:SetAngles(data:GetAngles()) + self.Dummy:AddEffects(EF_NODRAW) + + local s = self.Dummy:LookupSequence("idle_all") + self.Dummy:SetSequence(s) + else + self.Dummy = nil + end + + sound.Play(loopsound, self:GetPos(), 50, 100) +end + +--- +--@ignore +-- @realm client +function EFFECT:Think() + if self.EndTime < CurTime() then + SafeRemoveEntity(self.Dummy) + return + end + + if not (IsValid(self.EffectOwner) and IsValid(self.Dummy)) then + return false + end + + -- first draw same effect as beamup + if self.BeamDownTime >= CurTime() then + local pos = self:GetPos() + if pos.z - self.BasePos.z < final_height then + pos.z = pos.z + (90 * FrameTime()) + self:SetPos(pos) + end + else + -- then move to beamdown effects + local pos = self:GetPos() + if pos.z > self.BeamDownPos.z - final_height then + pos.z = pos.z - (90 * FrameTime()) + self:SetPos(pos) + else + self.DrawTop = false + end + + self.BeamDown = true + end + + return true +end + +--- +--@ignore +-- @realm client +function EFFECT:Render() + local norm = vector_up * -1 + local pos = self:GetPos() + local dist = norm:Dot(pos) + + render.MaterialOverride(mat_rising) + + render.EnableClipping(true) + render.PushCustomClipPlane(norm, dist) + if not self.BeamDown then + self.Dummy:DrawModel() + else + self.EffectOwner:DrawModel() + end + render.PopCustomClipPlane() + render.EnableClipping(false) + + render.MaterialOverride() + + if self.DrawTop then + render.SetMaterial(mat_rising) + render.DrawQuadEasy(pos, vector_up, 30, 30, COLOR_RED, 0) + end +end diff --git a/gamemodes/terrortown/entities/effects/teleport_beamup.lua b/gamemodes/terrortown/entities/effects/teleport_beamup.lua new file mode 100644 index 000000000..cf851b2f6 --- /dev/null +++ b/gamemodes/terrortown/entities/effects/teleport_beamup.lua @@ -0,0 +1,106 @@ +--- +-- @class EFFECT +-- @section teleport_beamup + +local mat_rising = Material("models/props_combine/stasisshield_sheet") +local top = 80 +local final_height = top +local vector_up = Vector(0, 0, 1) +local loopsound = Sound("ambient/levels/labs/teleport_mechanism_windup1.wav") + +--- +--@ignore +-- @realm client +function EFFECT:Init(data) + self.EffectOwner = data:GetEntity() + + self:SetPos(data:GetOrigin()) + self:SetAngles(data:GetAngles()) + + self.BasePos = self:GetPos() + self.BeamDownPos = self.BasePos + Vector(0, 0, final_height) + + self.BeamDownTime = CurTime() + data:GetMagnitude() + self.EndTime = self.BeamDownTime + data:GetRadius() + self.DrawTop = true + self.BeamDown = false + + if IsValid(self.EffectOwner) then + self.Dummy = ClientsideModel(self.EffectOwner:GetModel(), RENDERGROUP_OPAQUE) + self.Dummy:SetPos(self.BasePos) + self.Dummy:SetAngles(self:GetAngles()) + self.Dummy:AddEffects(EF_NODRAW) + + local s = self.Dummy:LookupSequence("idle_all") + self.Dummy:SetSequence(s) + else + self.Dummy = nil + end + + sound.Play(loopsound, self:GetPos(), 50, 100) +end + +--- +--@ignore +-- @realm client +function EFFECT:Think() + if self.EndTime < CurTime() then + SafeRemoveEntity(self.Dummy) + return + end + + if not (IsValid(self.EffectOwner) and IsValid(self.Dummy)) then + return false + end + + if self.BeamDownTime >= CurTime() then + local pos = self:GetPos() + if pos.z - self.BasePos.z < final_height then + pos.z = pos.z + (90 * FrameTime()) + self:SetPos(pos) + end + else + -- then move to beamdown effects + local pos = self:GetPos() + if pos.z > self.BeamDownPos.z - final_height then + pos.z = pos.z - (90 * FrameTime()) + self:SetPos(pos) + else + self.DrawTop = false + end + + self.BeamDown = true + end + + return true +end + +--- +--@ignore +-- @realm client +function EFFECT:Render() + -- clipping positioning + local norm = vector_up * -1 + local pos = self:GetPos() + local dist = norm:Dot(pos) + + -- do rendering + render.MaterialOverride(mat_rising) + + render.EnableClipping(true) + render.PushCustomClipPlane(norm, dist) + if not self.BeamDown then + self.EffectOwner:DrawModel() + else + self.Dummy:DrawModel() + end + render.PopCustomClipPlane() + render.EnableClipping(false) + + render.MaterialOverride() + + if self.DrawTop then + render.SetMaterial(mat_rising) + render.DrawQuadEasy(pos, vector_up, 30, 30, COLOR_RED, 0) + end +end diff --git a/gamemodes/terrortown/entities/entities/base_ammo_ttt.lua b/gamemodes/terrortown/entities/entities/base_ammo_ttt.lua index d2db1e996..5fc97dbc3 100644 --- a/gamemodes/terrortown/entities/entities/base_ammo_ttt.lua +++ b/gamemodes/terrortown/entities/entities/base_ammo_ttt.lua @@ -4,7 +4,7 @@ -- @section BaseAmmo if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local util = util @@ -17,14 +17,12 @@ ENT.AmmoType = "Pistol" ENT.AmmoAmount = 1 ENT.AmmoMax = 10 ENT.AmmoEntMax = 1 -ENT.Model = Model("models/items/boxsrounds.mdl") +ENT.Model = "models/items/boxsrounds.mdl" --- -- bw compat -- @realm shared -function ENT:RealInit() - -end +function ENT:RealInit() end --- -- Some subclasses want to do stuff before/after initing (eg. setting color) @@ -32,18 +30,18 @@ end -- Subclasses can easily call this whenever they want to -- @realm shared function ENT:Initialize() - self:SetModel(self.Model) + self:SetModel(self.Model) - self:PhysicsInit(SOLID_VPHYSICS) - self:SetMoveType(MOVETYPE_VPHYSICS) - self:AddSolidFlags(FSOLID_TRIGGER) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:AddSolidFlags(FSOLID_TRIGGER) - self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) - self:UseTriggerBounds(true, 24) + self:UseTriggerBounds(true, 24) - self.tickRemoval = false - self.AmmoEntMax = self.AmmoAmount + self.tickRemoval = false + self.AmmoEntMax = self.AmmoAmount end --- @@ -53,30 +51,31 @@ end -- @return boolean -- @realm shared function ENT:PlayerCanPickup(ply) - if ply == self:GetOwner() then - return false - end - - --- - -- @realm shared - local result = hook.Run("TTTCanPickupAmmo", ply, self) - if result then - return result - end - - local phys = self:GetPhysicsObject() - local spos = phys:IsValid() and phys:GetPos() or self:OBBCenter() - local epos = ply:GetShootPos() -- equiv to EyePos in SDK - - local tr = util.TraceLine({ - start = spos, - endpos = epos, - filter = {ply, self}, - mask = MASK_SOLID - }) - - -- can pickup if trace was not stopped - return tr.Fraction == 1.0 + if ply == self:GetOwner() then + return false + end + + --- + -- @realm shared + -- stylua: ignore + local result = hook.Run("TTTCanPickupAmmo", ply, self) + if result then + return result + end + + local phys = self:GetPhysicsObject() + local spos = phys:IsValid() and phys:GetPos() or self:OBBCenter() + local epos = ply:GetShootPos() -- equiv to EyePos in SDK + + local tr = util.TraceLine({ + start = spos, + endpos = epos, + filter = { ply, self }, + mask = MASK_SOLID, + }) + + -- can pickup if trace was not stopped + return tr.Fraction == 1.0 end --- @@ -84,38 +83,38 @@ end -- @return boolean -- @realm shared function ENT:CheckForWeapon(ply) - if not self.CachedWeapons then - -- create a cache of what weapon classes use this ammo - local tbl = {} - local weps = weapons.GetList() - local cls = self:GetClass() - - local WEPSGetClass = WEPS.GetClass - - for i = 1, #weps do - local v = weps[i] - - if v.AmmoEnt == cls then - tbl[#tbl + 1] = WEPSGetClass(v) - end - end - - self.CachedWeapons = tbl - end - - local plyHasWeapon = ply.HasWeapon - local cached = self.CachedWeapons - - -- Check if player has a weapon that we know needs us. This is called in - -- Touch, which is called many a time, so we use the cache here to avoid - -- looping through every weapon the player has to check their AmmoEnt. - for i = 1, #cached do - if plyHasWeapon(ply, cached[i]) then - return true - end - end - - return false + if not self.CachedWeapons then + -- create a cache of what weapon classes use this ammo + local tbl = {} + local weps = weapons.GetList() + local cls = self:GetClass() + + local WEPSGetClass = WEPS.GetClass + + for i = 1, #weps do + local v = weps[i] + + if v.AmmoEnt == cls then + tbl[#tbl + 1] = WEPSGetClass(v) + end + end + + self.CachedWeapons = tbl + end + + local plyHasWeapon = ply.HasWeapon + local cached = self.CachedWeapons + + -- Check if player has a weapon that we know needs us. This is called in + -- Touch, which is called many a time, so we use the cache here to avoid + -- looping through every weapon the player has to check their AmmoEnt. + for i = 1, #cached do + if plyHasWeapon(ply, cached[i]) then + return true + end + end + + return false end --- @@ -123,58 +122,65 @@ end -- @param Entity ply The touching entity that is probably a player -- @realm shared function ENT:Touch(ply) - if CLIENT - or self.tickRemoval - or not ply:IsValid() - or not ply:IsPlayer() - or not self:CheckForWeapon(ply) - or not self:PlayerCanPickup(ply) - then return end + if + CLIENT + or self.tickRemoval + or not ply:IsValid() + or not ply:IsPlayer() + or not self:CheckForWeapon(ply) + or not self:PlayerCanPickup(ply) + then + return + end - local ammo = ply:GetAmmoCount(self.AmmoType) + local ammo = ply:GetAmmoCount(self.AmmoType) - -- need clipmax info and room for at least 1/4th - if self.AmmoMax < ammo + math.ceil(self.AmmoAmount * 0.25) then return end + -- need clipmax info and room for at least 1/4th + if self.AmmoMax < ammo + math.ceil(self.AmmoAmount * 0.25) then + return + end - local given = math.min(self.AmmoAmount, self.AmmoMax - ammo) + local given = math.min(self.AmmoAmount, self.AmmoMax - ammo) - ply:GiveAmmo(given, self.AmmoType) + ply:GiveAmmo(given, self.AmmoType) - self.AmmoAmount = self.AmmoAmount - given + self.AmmoAmount = self.AmmoAmount - given - if self.AmmoAmount > 0 and math.ceil(self.AmmoEntMax * 0.25) <= self.AmmoAmount then return end + if self.AmmoAmount > 0 and math.ceil(self.AmmoEntMax * 0.25) <= self.AmmoAmount then + return + end - self.tickRemoval = true - self:Remove() + self.tickRemoval = true + self:Remove() end if SERVER then - --- - -- This Think hook is used as a hack to force ammo to physwake, because it can't be done - -- in init. If it is done in init, the entities will fall through the world on the client - -- but not on the server. This leads to inconsistencies between server and client. - -- @realm server - function ENT:Think() - if self.firstThinkDone then return end - - self:PhysWake() - - self.firstThinkDone = true - - -- Immediately unhook the Think to save cycles. The firstThinkDone thing is - -- just there in case it still Thinks somehow in the future. - self.Think = nil - end - - --- - -- Hook that is called when an ammo entity is about to be picked up. With this hook - -- the pickup can be canceled. It is called after all previous checks have passed. - -- @param Player ply The player that attempts to pick up the entity - -- @param Entity ent The ammo entity that is about to be picked up - -- @return boolean Return false to cancel the pickup event - -- @hook - -- @realm server - function GAMEMODE:TTTCanPickupAmmo(ply, ent) - - end + --- + -- This Think hook is used as a hack to force ammo to physwake, because it can't be done + -- in init. If it is done in init, the entities will fall through the world on the client + -- but not on the server. This leads to inconsistencies between server and client. + -- @realm server + function ENT:Think() + if self.firstThinkDone then + return + end + + self:PhysWake() + + self.firstThinkDone = true + + -- Immediately unhook the Think to save cycles. The firstThinkDone thing is + -- just there in case it still Thinks somehow in the future. + self.Think = nil + end + + --- + -- Hook that is called when an ammo entity is about to be picked up. With this hook + -- the pickup can be canceled. It is called after all previous checks have passed. + -- @param Player ply The player that attempts to pick up the entity + -- @param Entity ent The ammo entity that is about to be picked up + -- @return boolean Return false to cancel the pickup event + -- @hook + -- @realm server + function GAMEMODE:TTTCanPickupAmmo(ply, ent) end end diff --git a/gamemodes/terrortown/entities/entities/info_manipulate.lua b/gamemodes/terrortown/entities/entities/info_manipulate.lua new file mode 100644 index 000000000..bc674a21b --- /dev/null +++ b/gamemodes/terrortown/entities/entities/info_manipulate.lua @@ -0,0 +1,78 @@ +--- +-- @class ENT +-- @desc Dummy entity to convert ZM info_manipulate traps to TTT ones +-- @section InfoManipulate + +ENT.Type = "point" +ENT.Base = "base_point" + +--- +-- @realm server +function ENT:Think() + if not self.Replaced then + self:CreateReplacement() + + self:Remove() + end +end + +--- +-- Sets Hammer key values on an entity. +-- @param string key The internal key name +-- @param string value The value to set +-- @realm server +function ENT:KeyValue(key, value) + if key == "OnPressed" then + -- store raw, will be feeding this into the replacement's StoreOutput() + self.RawOutputs = self.RawOutputs or {} + + self.RawOutputs[#self.RawOutputs + 1] = value + elseif key == "Cost" then + self[key] = tonumber(value) + elseif key == "Active" or key == "RemoveOnTrigger" then + self[key] = tobool(value) + elseif key == "Description" then + self[key] = tostring(value) + end +end + +--- +-- @realm server +function ENT:CreateReplacement() + local tgt = ents.Create("ttt_traitor_button") + + if not IsValid(tgt) then + return + end + + self.Replaced = true + + -- feed in our properties into replacement as keyvals + tgt:SetPos(self:GetPos()) + tgt:SetKeyValue("targetname", self:GetName()) + + if not self.Active then + -- start locked + tgt:SetKeyValue("spawnflags", tostring(2048)) + end + + if self.Description and self.Description ~= "" then + tgt:SetKeyValue("description", self.Description) + end + + if self.Cost then + tgt:SetKeyValue("wait", tostring(self.Cost)) + end + + if self.RemoveOnTrigger then + tgt:SetKeyValue("RemoveOnPress", tostring(true)) + end + + if self.RawOutputs then + for k, v in pairs(self.RawOutputs) do + tgt:SetKeyValue("OnPressed", tostring(v)) + end + end + + tgt:Spawn() +end diff --git a/gamemodes/terrortown/entities/entities/item_ammo_357_ttt.lua b/gamemodes/terrortown/entities/entities/item_ammo_357_ttt.lua index b88bae588..c13ab628f 100644 --- a/gamemodes/terrortown/entities/entities/item_ammo_357_ttt.lua +++ b/gamemodes/terrortown/entities/entities/item_ammo_357_ttt.lua @@ -1,5 +1,5 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ENT.Type = "anim" diff --git a/gamemodes/terrortown/entities/entities/item_ammo_pistol_ttt.lua b/gamemodes/terrortown/entities/entities/item_ammo_pistol_ttt.lua index 1f88dd514..b13a5ed7b 100644 --- a/gamemodes/terrortown/entities/entities/item_ammo_pistol_ttt.lua +++ b/gamemodes/terrortown/entities/entities/item_ammo_pistol_ttt.lua @@ -1,5 +1,5 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ENT.Type = "anim" diff --git a/gamemodes/terrortown/entities/entities/item_ammo_revolver_ttt.lua b/gamemodes/terrortown/entities/entities/item_ammo_revolver_ttt.lua index f17014336..cc68d6316 100644 --- a/gamemodes/terrortown/entities/entities/item_ammo_revolver_ttt.lua +++ b/gamemodes/terrortown/entities/entities/item_ammo_revolver_ttt.lua @@ -1,21 +1,23 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end +DEFINE_BASECLASS("base_ammo_ttt") + ENT.Type = "anim" ENT.Base = "base_ammo_ttt" ENT.AmmoType = "AlyxGun" ENT.AmmoAmount = 12 ENT.AmmoMax = 36 -ENT.Model = Model("models/items/357ammo.mdl") +ENT.Model = "models/items/357ammo.mdl" ENT.AutoSpawnable = true ENT.spawnType = AMMO_TYPE_DEAGLE --- -- @ignore function ENT:Initialize() - -- Differentiate from rifle ammo - self:SetColor(Color(255, 100, 100, 255)) + -- Differentiate from rifle ammo + self:SetColor(Color(255, 100, 100, 255)) - return self.BaseClass.Initialize(self) + return BaseClass.Initialize(self) end diff --git a/gamemodes/terrortown/entities/entities/item_ammo_smg1_ttt.lua b/gamemodes/terrortown/entities/entities/item_ammo_smg1_ttt.lua index f970ef4d3..96a310277 100644 --- a/gamemodes/terrortown/entities/entities/item_ammo_smg1_ttt.lua +++ b/gamemodes/terrortown/entities/entities/item_ammo_smg1_ttt.lua @@ -1,5 +1,5 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ENT.Type = "anim" diff --git a/gamemodes/terrortown/entities/entities/item_box_buckshot_ttt.lua b/gamemodes/terrortown/entities/entities/item_box_buckshot_ttt.lua index 61fae4c86..ac404c709 100644 --- a/gamemodes/terrortown/entities/entities/item_box_buckshot_ttt.lua +++ b/gamemodes/terrortown/entities/entities/item_box_buckshot_ttt.lua @@ -1,5 +1,5 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ENT.Type = "anim" diff --git a/gamemodes/terrortown/entities/entities/ttt_base_placeable.lua b/gamemodes/terrortown/entities/entities/ttt_base_placeable.lua new file mode 100644 index 000000000..28e6b1399 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_base_placeable.lua @@ -0,0 +1,395 @@ +--- +-- @class ENT +-- @desc A base that handles everything around placeable and destroyable entities +-- @section ttt_base_placeable + +if SERVER then + AddCSLuaFile() +end + +ENT.Type = "anim" + +-- if set to false, the entity can not be destroyed by damage +ENT.isDestructible = true + +ENT.pickupWeaponClass = nil + +--- +-- @realm shared +function ENT:Initialize() + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + + if SERVER then + self:PrecacheGibs() + end + + local phys = self:GetPhysicsObject() + + if IsValid(phys) then + phys:SetMass(40) + end + + self:SetHealth(100) +end + +--- +-- @realm shared +function ENT:SetupDataTables() + self:NetworkVar("Entity", 0, "Originator") +end + +if SERVER then + local soundRumble = { + Sound("physics/concrete/concrete_break2.wav"), + Sound("physics/concrete/concrete_break3.wav"), + } + + local soundBreak = { + Sound("physics/metal/metal_box_break1.wav"), + Sound("physics/metal/metal_box_break2.wav"), + } + + local soundGlass = { + Sound("physics/glass/glass_bottle_break1.wav"), + Sound("physics/glass/glass_bottle_break2.wav"), + Sound("physics/glass/glass_cup_break1.wav"), + Sound("physics/glass/glass_cup_break2.wav"), + Sound("physics/glass/glass_pottery_break1.wav"), + Sound("physics/glass/glass_pottery_break2.wav"), + Sound("physics/glass/glass_pottery_break3.wav"), + Sound("physics/glass/glass_pottery_break4.wav"), + } + + local soundWeld = Sound("weapons/c4/c4_plant.wav") + + local soundThrow = Sound("Weapon_SLAM.SatchelThrow") + + local soundDeny = Sound("HL2Player.UseDeny") + + local soundWeaponPickup = Sound("items/ammo_pickup.wav") + + AccessorFunc(ENT, "hitNormal", "HitNormal", FORCE_VECTOR) + AccessorFunc(ENT, "stickRotation", "StickRotation", FORCE_ANGLE) + + --- + -- @param CTakeDamageInfo dmgInfo + -- @realm server + function ENT:OnTakeDamage(dmgInfo) + -- we add a flag here because stuff can happen in the WasDestroyed + -- hook that could create an infinite loop that crashes the game + if self.isDestroyed then + return + end + + if not self:IsWeldedToSurface() then + self:TakePhysicsDamage(dmgInfo) + end + + if not self.isDestructible then + return + end + + local pos = self:GetPos() + local amountDamage = dmgInfo:GetDamage() + local attacker = dmgInfo:GetAttacker() + local originator = self:GetOriginator() + + self:SetHealth(self:Health() - amountDamage) + + if IsValid(attacker) and attacker:IsPlayer() then + DamageLog( + Format( + "DMG: \t %s [%s] damaged '%s' [owner: %s] for %d dmg", + attacker:Nick(), + attacker:GetRoleString(), + self:GetClass(), + (IsValid(originator) and originator:IsPlayer()) and originator:Nick() + or "", + amountDamage + ) + ) + end + + if self:Health() <= 0 then + self:SetSolid(SOLID_NONE) + + self:GibBreakClient(Vector(0, 0, 100)) + + local effect = EffectData() + effect:SetOrigin(pos) + + util.Effect("cball_explode", effect) + + sound.Play(table.Random(soundRumble), pos, 75) + sound.Play(table.Random(soundBreak), pos, 50) + sound.Play(table.Random(soundGlass), pos, 65) + + self.isDestroyed = true + + local decal = self:WasDestroyed(pos, dmgInfo) or "FadingScorch" + + local vecHitNormal = self:GetHitNormal() + + if vecHitNormal then + local tr = util.TraceLine({ + start = pos, + endpos = pos - vecHitNormal * 256, + filter = { self }, + mask = MASK_SOLID, + }) + + util.Decal(decal, tr.HitPos + tr.HitNormal, tr.HitPos - tr.HitNormal, self) + else + util.PaintDown(pos, decal, self) + end + + self:Remove() + else + local effect = EffectData() + effect:SetOrigin(pos) + + util.Effect("ThumperDust", effect) + end + end + + --- + -- Called when the entity was destroyed and is not yet removed. Can be used to trigger special things. + -- @param Vector pos The position of the entitiy + -- @param CTakeDamageInfo dmgInfo The damage info object that killed the entity + -- @return nil|string The decal name that should be painted on destruction + -- @hook + -- @realm server + function ENT:WasDestroyed(pos, dmgInfo) end + + --- + -- Welds the entity to the nearest surface. + -- @param boolean stateWelding The welding state; true to weld, false to unweld + -- @realm server + function ENT:WeldToSurface(stateWelding) + self.stateWelding = stateWelding + + if stateWelding then + local vecHitNormal = self:GetHitNormal() + + if vecHitNormal then + self:SetAngles(vecHitNormal:Angle() + (self:GetStickRotation() or Angle(0, 0, 0))) + end + + local pos = self:GetPos() + local ignore = player.GetAll() + + ignore[#ignore + 1] = { self } + + local tr = util.TraceEntity({ + start = pos, + endpos = pos - Vector(0, 0, 16), + filter = ignore, + mask = MASK_SOLID, + }, self) + + sound.Play(soundWeld, pos, 75) + + if tr.Hit and (IsValid(tr.Entity) or tr.HitWorld) then + local phys = self:GetPhysicsObject() + + if IsValid(phys) then + if tr.HitWorld then + phys:EnableMotion(false) + else + self.originalMass = phys:GetMass() + phys:SetMass(150) + end + end + + -- only weld to objects we cannot pick up + local entphys = tr.Entity:GetPhysicsObject() + if IsValid(entphys) and entphys:GetMass() > CARRY_WEIGHT_LIMIT then + constraint.Weld(self, tr.Entity, 0, 0, 0, true) + end + end + else + constraint.RemoveConstraints(self, "Weld") + + local phys = self:GetPhysicsObject() + + if IsValid(phys) then + phys:EnableMotion(true) + phys:SetMass(self.originalMass or 10) + end + end + end + + --- + -- Checks if the entity is welded to a surface. + -- @return boolean Returns true if the entity is welded to a surface + -- @realm server + function ENT:IsWeldedToSurface() + return self.stateWelding or false + end + + --- + -- Hook that is called if a player uses their use key while focusing on the entity. + -- @note When overwriting this function BaseClass.UseOverwrite has to be called if + -- the entity pickup system should be used. + -- @param Player activator The player that used their use key + -- @hook + -- @realm server + function ENT:UseOverride(activator) + if not IsValid(activator) or not activator:IsTerror() or not self.pickupWeaponClass then + return + end + + if not self:PlayerCanPickupWeapon(activator) then + LANG.Msg(activator, "pickup_fail", nil, MSG_MSTACK_WARN) + + self:EmitSound(soundDeny) + + return + end + + local wep = activator:GetWeapon(self.pickupWeaponClass) + + if IsValid(wep) and wep:Clip1() < wep.Primary.ClipSize then + wep:SetClip1(wep:Clip1() + 1) + + activator:EmitSound(soundWeaponPickup) + + activator:SelectWeapon(self.pickupWeaponClass) + else + -- picks up weapon and drops blocking weapon if slot is already in use + wep = activator:SafePickupWeaponClass(self.pickupWeaponClass, true) + + -- if pickup has failed, the in-world entity should not be removed + if not IsValid(wep) then + LANG.Msg(activator, "pickup_no_room", nil, MSG_MSTACK_WARN) + + self:EmitSound(soundDeny) + + return + end + end + + if self:IsWeldedToSurface() then + sound.Play(soundWeld, self:GetPos(), 75) + end + + self:OnPickup(activator, wep) + + self:Remove() + end + + --- + -- Run if a valid player tries to pick up this entity to check if this pickup is accepted. + -- @param Player activator The player that used their use key + -- @return[default=true] boolean Return true to allow pickup + -- @hook + -- @realm server + function ENT:PlayerCanPickupWeapon(activator) + return true + end + + --- + -- Called when this entity is picked up and about to be removed. + -- @param Player activator The player that used their use key + -- @param Weapon wep The weapon that is added to their inventory + -- @hook + -- @realm server + function ENT:OnPickup(activator, wep) end + + --- + -- Helper function for a weapon that wants to throw the entity. Already handles everything. + -- @param Player ply The player that throws the entity, the owner + -- @param[opt] Angle rotationalOffset The model's rotational offset that should be applied + -- @return boolean Returns true on success + -- @realm server + function ENT:ThrowEntity(ply, rotationalOffset) + local posThrow = ply:GetShootPos() - Vector(0, 0, 15) + local vecAim = ply:GetAimVector() + + if not ply:HasDropSpace(posThrow, vecAim) then + LANG.Msg(ply, "throw_no_room", nil, MSG_MSTACK_WARN) + + return false + end + + ply:SetAnimation(PLAYER_ATTACK1) + + rotationalOffset = rotationalOffset or Angle(0, 0, 0) + + local velocity = ply:GetVelocity() + local velocityThrow = velocity + vecAim * 250 + + self:SetPos(posThrow + vecAim * 10) + self:SetOriginator(ply) + self:Spawn() + self:PointAtEntity(ply) + + local ang = self:GetAngles() + ang:RotateAroundAxis(ang:Right(), rotationalOffset.pitch) + ang:RotateAroundAxis(ang:Up(), rotationalOffset.yaw) + ang:RotateAroundAxis(ang:Forward(), rotationalOffset.roll) + + self:SetAngles(ang) + self:PhysWake() + + local phys = self:GetPhysicsObject() + + if IsValid(phys) then + phys:SetVelocity(velocityThrow) + end + + self:EmitSound(soundThrow) + + return true + end + + --- + -- Helper function for a weapon that wants to stick the entity to a surface. Already handles everything. + -- @param Player ply The player that sticks the entity, the owner + -- @param[opt] Angle rotationalOffset The model's rotational offset that should be applied + -- @param[opt] number angleCondition The angle condition that has to be met to apply the rotational offset + -- @note On the rotations: A model ca be rotated in three axis. This can be set in `rotationalOffset`. It is + -- also possible to tie this to an `angleCondition` that has to be met so that this offset is applied. Such + -- an angle condition is any possible angle: if the angle of the hit normal is greater then the provided + -- condition, the offset is applied. + -- @return boolean Returns true on success + -- @realm server + function ENT:StickEntity(ply, rotationalOffset, angleCondition) + ply:SetAnimation(PLAYER_ATTACK1) + + rotationalOffset = rotationalOffset or Angle(0, 0, 0) + + local pos = ply:GetShootPos() + + local tr = util.TraceLine({ + start = pos, + endpos = pos + ply:GetAimVector() * 100, + mask = MASK_NPCWORLDSTATIC, + filter = { self, ply }, + }) + + if not tr.Hit then + return false + end + + self:SetPos(tr.HitPos) + self:SetOriginator(ply) + self:Spawn() + self:SetHitNormal(tr.HitNormal) + + if tr.HitNormal.x == 0 and tr.HitNormal.y == 0 and tr.HitNormal.z == 1 then + rotationalOffset.yaw = rotationalOffset.yaw + ply:GetAngles().yaw + 180 + end + + if not angleCondition or math.abs(tr.HitNormal:Angle().pitch) >= angleCondition then + self:SetStickRotation(rotationalOffset) + end + + self:WeldToSurface(true) + + return true + end +end diff --git a/gamemodes/terrortown/entities/entities/ttt_basegrenade_proj.lua b/gamemodes/terrortown/entities/entities/ttt_basegrenade_proj.lua new file mode 100644 index 000000000..659bf95b0 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_basegrenade_proj.lua @@ -0,0 +1,84 @@ +--- +-- common grenade projectile code +-- @class ENT +-- @section ttt_basegrenade_proj + +if SERVER then + AddCSLuaFile() +end + +ENT.Type = "anim" + +ENT.Model = "models/weapons/w_eq_flashbang_thrown.mdl" + +AccessorFunc(ENT, "thrower", "Thrower") + +--- +-- @ignore +function ENT:SetupDataTables() + self:NetworkVar("Float", 0, "ExplodeTime") +end + +--- +-- @ignore +function ENT:Initialize() + self:SetModel(self.Model) + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_BBOX) + self:SetCollisionGroup(COLLISION_GROUP_PROJECTILE) + + if SERVER then + self:SetExplodeTime(0) + end +end + +--- +-- @ignore +function ENT:SetDetonateTimer(length) + self:SetDetonateExact(CurTime() + length) +end + +--- +-- @ignore +function ENT:SetDetonateExact(t) + self:SetExplodeTime(t or CurTime()) +end + +--- +-- override to describe what happens when the nade explodes +-- @ignore +function ENT:Explode(tr) + ErrorNoHaltWithStack("ERROR: BaseGrenadeProjectile explosion code not overridden!\n") +end + +--- +-- @ignore +function ENT:Think() + local etime = self:GetExplodeTime() or 0 + if etime ~= 0 and etime < CurTime() then + -- if thrower disconnects before grenade explodes, just don't explode + if SERVER and (not IsValid(self:GetThrower())) then + self:Remove() + etime = 0 + return + end + + -- find the ground if it's near and pass it to the explosion + local spos = self:GetPos() + local tr = util.TraceLine({ + start = spos, + endpos = spos + Vector(0, 0, -32), + mask = MASK_SHOT_HULL, + filter = self.thrower, + }) + + local success, err = pcall(self.Explode, self, tr) + if not success then + -- prevent effect spam on Lua error + self:Remove() + ErrorNoHaltWithStack("ERROR CAUGHT: ttt_basegrenade_proj: " .. err .. "\n") + end + end +end diff --git a/gamemodes/terrortown/entities/entities/ttt_beacon.lua b/gamemodes/terrortown/entities/entities/ttt_beacon.lua new file mode 100644 index 000000000..fa709f31a --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_beacon.lua @@ -0,0 +1,322 @@ +--- +-- @class ENT +-- @section ttt_beacon + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("ttt_base_placeable") + +if CLIENT then + ENT.Icon = "vgui/ttt/icon_beacon" + ENT.PrintName = "Beacon" +end + +ENT.Base = "ttt_base_placeable" + +ENT.Model = "models/props_lab/reciever01a.mdl" + +ENT.CanHavePrints = true + +ENT.CanUseKey = true +ENT.pickupWeaponClass = "weapon_ttt_beacon" + +ENT.timeLastBeep = CurTime() +ENT.lastPlysFound = {} + +local beaconDetectionRange = 135 + +--- +-- @realm shared +function ENT:Initialize() + self:SetModel(self.Model) + + BaseClass.Initialize(self) + + if SERVER then + self:SetMaxHealth(100) + end + self:SetHealth(100) + + if SERVER then + self:SetUseType(SIMPLE_USE) + self:NextThink(CurTime() + 1) + + local mvObject = self:AddMarkerVision("beacon_owner") + mvObject:SetOwner(ROLE_DETECTIVE) + mvObject:SetVisibleFor(VISIBLE_FOR_ROLE) + mvObject:SyncToClients() + end +end + +if SERVER then + local soundBeep = Sound("weapons/c4/cc4_beep1.wav") + + --- + -- @realm server + function ENT:WasDestroyed() + local originator = self:GetOriginator() + + if not IsValid(originator) then + return + end + + LANG.Msg(originator, "msg_beacon_destroyed", nil, MSG_MSTACK_WARN) + end + + --- + -- @realm server + function ENT:Think() + if self.timeLastBeep + 5 >= CurTime() then + sound.Play(soundBeep, self:GetPos(), 100, 80) + + self.timeLastBeep = CurTime() + end + + local entsFound = ents.FindInSphere(self:GetPos(), beaconDetectionRange) + local plysFound = {} + local affectedPlayers = {} + + for i = 1, #entsFound do + local ent = entsFound[i] + + if not IsValid(ent) or not ent:IsPlayer() or not ent:IsTerror() then + continue + end + + --- + -- @realm server + -- stylua: ignore + if hook.Run("TTT2BeaconDetectPlayer", ent, self) == false then continue end + + plysFound[ent] = true + affectedPlayers[ent] = true + end + + table.Merge(affectedPlayers, self.lastPlysFound) + + for ply in pairs(affectedPlayers) do + if plysFound[ply] and not self.lastPlysFound[ply] then + -- newly added player in range + local mvObject = ply:AddMarkerVision("beacon_player") + mvObject:SetOwner(self:GetOriginator()) + mvObject:SetVisibleFor(VISIBLE_FOR_ALL) + mvObject:SetColor(roles.DETECTIVE.color) + mvObject:SyncToClients() + elseif not plysFound[ply] and self.lastPlysFound[ply] then + -- player lost in range + ply:RemoveMarkerVision("beacon_player") + end + end + + self.lastPlysFound = plysFound + + self:NextThink(CurTime() + 0.25) + + return true + end + + --- + -- @realm server + function ENT:OnRemove() + for ply in pairs(self.lastPlysFound) do + ply:RemoveMarkerVision("beacon_player") + end + + self:RemoveMarkerVision("beacon_owner") + end + + --- + -- @param Player activator + -- @realm server + function ENT:PlayerCanPickupWeapon(activator) + return self:GetOriginator() == activator + end + + --- + -- @realm server + function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS + end + + hook.Add("PlayerDeath", "BeaconTrackPlayerDeath", function(victim) + local entsFound = ents.FindInSphere(victim:GetPos(), beaconDetectionRange) + local beaconsFound = {} + local playersNotified = {} + + for i = 1, #entsFound do + local ent = entsFound[i] + + if not IsValid(ent) or ent:GetClass() ~= "ttt_beacon" then + continue + end + + beaconsFound[#beaconsFound + 1] = ent + end + + for i = 1, #beaconsFound do + local beacon = beaconsFound[i] + local beaconOwner = beacon:GetOriginator() + + if not IsValid(beaconOwner) or table.HasValue(playersNotified, beaconOwner) then + continue + end + + --- + -- @realm server + -- stylua: ignore + if hook.Run("TTT2BeaconDeathNotify", victim, beacon) == false then continue end + + LANG.Msg(beaconOwner, "msg_beacon_death", nil, MSG_MSTACK_WARN) + + -- make sure a player is only notified once, even if multiple beacons are triggered + playersNotified[#playersNotified + 1] = beaconOwner + end + end) + + --- + -- Hook that is called when a player is about to be found by a beacon. + -- This hook can be used to cancel the detection. + -- @param Player ply The player that the beacon has found + -- @param Entity ent The beacon entity that found the player + -- @return boolean Return false to cancel the player being detected + -- @hook + -- @realm server + function GAMEMODE:TTT2BeaconDetectPlayer(ply, ent) end + + --- + -- Hook that is called when a beacon is about to report a death. + -- This hook can be used to cancel the notification. + -- @param Player victim The player that died + -- @param Entity beacon The beacon entity that the player died near + -- @return boolean Return false to cancel the death being reported + -- @hook + -- @realm server + function GAMEMODE:TTT2BeaconDeathNotify(victim, beacon) end +end + +if CLIENT then + local TryT = LANG.TryTranslation + local ParT = LANG.GetParamTranslation + + local baseOpacity = 35 + local factorRenderDistance = 3 + + local materialBeacon = Material("vgui/ttt/marker_vision/beacon") + local materialPlayer = Material("vgui/ttt/tid/tid_big_role_not_known") + + -- handle looking at Beacon + hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDBeacon", function(tData) + local client = LocalPlayer() + local ent = tData:GetEntity() + + if + not IsValid(client) + or not client:IsTerror() + or not client:Alive() + or not IsValid(ent) + or tData:GetEntityDistance() > 100 + or ent:GetClass() ~= "ttt_beacon" + then + return + end + + -- enable targetID rendering + tData:EnableText() + tData:EnableOutline() + tData:SetOutlineColor(client:GetRoleColor()) + + tData:SetTitle(TryT(ent.PrintName)) + + if ent:GetOriginator() == client then + tData:SetKeyBinding("+use") + tData:SetSubtitle(ParT("target_pickup", { usekey = Key("+use", "USE") })) + else + tData:AddIcon(roles.DETECTIVE.iconMaterial) + tData:SetSubtitle(TryT("beacon_pickup_disabled")) + end + + tData:AddDescriptionLine(TryT("beacon_short_desc")) + end) + + hook.Add("TTT2RenderMarkerVisionInfo", "HUDDrawMarkerVisionBeacon", function(mvData) + local ent = mvData:GetEntity() + local mvObject = mvData:GetMarkerVisionObject() + + if not mvObject:IsObjectFor(ent, "beacon_owner") then + return + end + + local owner = ent:GetOriginator() + local nick = IsValid(owner) and owner:Nick() or "---" + + local distance = math.Round(util.HammerUnitsToMeters(mvData:GetEntityDistance()), 1) + + mvData:EnableText() + + mvData:AddIcon(materialBeacon) + mvData:SetTitle(TryT(ent.PrintName)) + + mvData:AddDescriptionLine(ParT("marker_vision_owner", { owner = nick })) + mvData:AddDescriptionLine(ParT("marker_vision_distance", { distance = distance })) + + mvData:AddDescriptionLine(TryT(mvObject:GetVisibleForTranslationKey()), COLOR_SLATEGRAY) + end) + + hook.Add("TTT2RenderMarkerVisionInfo", "HUDDrawMarkerVisionBeaconPlys", function(mvData) + local ent = mvData:GetEntity() + local mvObject = mvData:GetMarkerVisionObject() + + if not mvObject:IsObjectFor(ent, "beacon_player") or ent == LocalPlayer() then + return + end + + mvData:EnableText() + + mvData:AddIcon(materialPlayer) + mvData:SetTitle(TryT("beacon_marker_vision_player")) + + mvData:AddDescriptionLine(TryT("beacon_marker_vision_player_tracked")) + + mvData:AddDescriptionLine(TryT(mvObject:GetVisibleForTranslationKey()), COLOR_SLATEGRAY) + end) + + hook.Add("PostDrawTranslucentRenderables", "BeaconRenderRadius", function(_, bSkybox) + if bSkybox then + return + end + + local client = LocalPlayer() + + if not client:GetSubRoleData().isPolicingRole then + return + end + + local maxRenderDistance = beaconDetectionRange * factorRenderDistance + local entities = ents.FindInSphere(client:GetPos(), maxRenderDistance) + + local colorSphere = util.ColorLighten(roles.DETECTIVE.color, 120) + + for i = 1, #entities do + local ent = entities[i] + + if ent:GetClass() ~= "ttt_beacon" or ent:GetOriginator() ~= client then + continue + end + + local distance = math.max(beaconDetectionRange, client:GetPos():Distance(ent:GetPos())) + colorSphere.a = baseOpacity + * math.max(maxRenderDistance - distance, 0) + / maxRenderDistance + + render.SetColorMaterial() + + render.DrawSphere(ent:GetPos(), beaconDetectionRange, 30, 30, colorSphere) + render.CullMode(MATERIAL_CULLMODE_CW) + + render.DrawSphere(ent:GetPos(), beaconDetectionRange, 30, 30, colorSphere) + render.CullMode(MATERIAL_CULLMODE_CCW) + end + end) +end diff --git a/gamemodes/terrortown/entities/entities/ttt_c4/cl_init.lua b/gamemodes/terrortown/entities/entities/ttt_c4/cl_init.lua new file mode 100644 index 000000000..bde1e872e --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_c4/cl_init.lua @@ -0,0 +1,522 @@ +-- bomb menus + +include("shared.lua") + +local starttime = C4_MINIMUM_TIME + +local T = LANG.GetTranslation +local PT = LANG.GetParamTranslation + +---- ARMING + +--- +-- Initial bomb arming +-- @param ttt_c4 bomb The bomb to configure +-- @realm client +function ShowC4Config(bomb) + local dframe = vgui.Create("DFrame") + local w, h = 350, 270 + dframe:SetSize(w, h) + dframe:Center() + dframe:SetTitle(T("c4_arm")) + dframe:SetVisible(true) + dframe:ShowCloseButton(true) + dframe:SetMouseInputEnabled(true) + + local m = 5 + + local bg = vgui.Create("DPanel", dframe) + bg:SetPaintBackground(false) + bg:SetPos(0, 0) + bg:StretchToParent(m, m * 5, m, m) + + -- Time + local dformtime = vgui.Create("DForm", bg) + dformtime:SetPos(m, m) + dformtime:SetSize(w - m * 4, h / 2) + dformtime:SetName(T("c4_arm_timer")) + + local dclock = vgui.Create("DLabel", dformtime) + dclock:SetFont("TimeLeft") + dclock:SetText(util.SimpleTime(starttime, "%02i:%02i")) + dclock:SizeToContents() + dclock:SetPos(m * 2, m * 2) + + dformtime:AddItem(dclock) + + local dtime = vgui.Create("DNumSlider", dformtime) + dtime:SetWide(w - m * 4) + dtime:SetText(T("c4_arm_seconds")) + dtime:SetDark(false) + dtime:SetMin(C4_MINIMUM_TIME) + dtime:SetMax(C4_MAXIMUM_TIME) + dtime:SetDecimals(0) + dtime:SetValue(starttime) + dtime.Label:SetWrap(true) + + local dwires + + dtime.OnValueChanged = function(self, val) + if not (IsValid(dclock) and IsValid(dwires)) then + return + end + dclock:SetText(util.SimpleTime(val, "%02i:%02i")) + + dwires:Update(val) + end + + dformtime:AddItem(dtime) + + dwires = vgui.Create("DLabel", dformtime) + dwires:SetText("") + dwires:SetWrap(true) + dwires:SetTall(30) + + local SafeWires = bomb.SafeWiresForTime + dwires.Update = function(s, t) + s:SetText(PT("c4_arm_attempts", { num = C4_WIRE_COUNT - SafeWires(t) })) + + s:InvalidateLayout() + end + + dwires:Update(starttime) + + dformtime:AddItem(dwires) + + local dformmisc = vgui.Create("DForm", bg) + dformmisc:SetAutoSize(false) + dformmisc:SetPos(m, m + 140) + dformmisc:SetSize(w - m * 4, h / 2) + dformmisc:SetPadding(20) + dformmisc:SetName(T("c4_remove_title")) + + -- Buttons + local by = 200 + + local bw, bh = 110, 25 + + local dgrab = vgui.Create("DButton", dformmisc) + dgrab:SetPos(m * 6, m * 5) + dgrab:SetSize(bw, bh) + dgrab:SetText(T("c4_remove_pickup")) + dgrab:SetDisabled(false) + dgrab.DoClick = function() + if not LocalPlayer() or not LocalPlayer():Alive() then + return + end + + RunConsoleCommand("ttt_c4_pickup", bomb:EntIndex()) + dframe:Close() + end + + --dformmisc:AddItem(dgrab) + + local ddestroy = vgui.Create("DButton", dformmisc) + ddestroy:SetPos(w - m * 4 - bw - m * 6, m * 5) + ddestroy:SetSize(bw, bh) + ddestroy:SetText(T("c4_remove_destroy1")) + ddestroy:SetDisabled(false) + ddestroy.Confirmed = false + ddestroy.DoClick = function(s) + if not LocalPlayer() or not LocalPlayer():Alive() then + return + end + + if not s.Confirmed then + s:SetText(T("c4_remove_destroy2")) + s.Confirmed = true + else + RunConsoleCommand("ttt_c4_destroy", bomb:EntIndex()) + dframe:Close() + end + end + + local dconf = vgui.Create("DButton", bg) + dconf:SetPos(m * 2, m + by) + dconf:SetSize(bw, bh) + dconf:SetText(T("c4_arm")) + dconf.DoClick = function() + if not LocalPlayer() or not LocalPlayer():Alive() then + return + end + + local t = dtime:GetValue() + if t and tonumber(t) then + RunConsoleCommand("ttt_c4_config", bomb:EntIndex(), t) + dframe:Close() + end + end + + local dcancel = vgui.Create("DButton", bg) + dcancel:SetPos(w - m * 4 - bw, m + by) + dcancel:SetSize(bw, bh) + dcancel:SetText(T("cancel")) + dcancel.DoClick = function() + dframe:Close() + end + + dframe:MakePopup() +end + +---- DISARM + +local disarm_beep = Sound("buttons/blip2.wav") +local wire_cut = Sound("ttt/wirecut.wav") + +local c4_bomb_mat = Material("vgui/ttt/c4_bomb") +local c4_cut_mat = Material("vgui/ttt/c4_cut") +local c4_wire_mat = Material("vgui/ttt/c4_wire") +local c4_wirecut_mat = Material("vgui/ttt/c4_wire_cut") + +--- Disarm panels +local on_wire_cut = nil + +-- Wire +local WIREPANEL = {} + +local wire_colors = { + Color(200, 0, 0, 255), -- red + Color(255, 255, 0, 255), -- yellow + Color(90, 90, 250, 255), -- blue + Color(255, 255, 255, 255), -- white/grey + Color(20, 200, 20, 255), -- green + Color(255, 160, 50, 255), -- brown +} + +--- +-- @realm client +function WIREPANEL:Init() + self.BaseClass.Init(self) + + self:NoClipping(true) + self:SetMouseInputEnabled(true) + self:MoveToFront() + + self.IsCut = false +end + +local c4_cut_tex = surface.GetTextureID(c4_cut_mat:GetName()) + +--- +-- @realm client +function WIREPANEL:PaintOverHovered() + surface.SetTexture(c4_cut_tex) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(175, -20, 32, 32) + + draw.SimpleText( + PT("c4_disarm_cut", { num = self.Index }), + "DermaDefault", + 85, + -10, + COLOR_WHITE, + 0, + 0 + ) +end + +--- +-- @realm client +WIREPANEL.OnMousePressed = DButton.OnMousePressed + +--- +-- @realm client +WIREPANEL.OnMouseReleased = DButton.OnMouseReleased + +--- +-- @realm client +function WIREPANEL:OnCursorEntered() + if not self.IsCut then + self.PaintOver = self.PaintOverHovered + end +end + +--- +-- @realm client +function WIREPANEL:OnCursorExited() + self.PaintOver = self.BaseClass.PaintOver +end + +--- +-- @realm client +function WIREPANEL:DoClick() + if self:GetParent():GetDisabled() then + return + end + + self.IsCut = true + + self.PaintOver = self.BaseClass.PaintOver + + self.m_Image:SetMaterial(c4_wirecut_mat) + + surface.PlaySound(wire_cut) + + if on_wire_cut then + on_wire_cut(self.Index) + end +end + +--- +-- @param number i The index of the wire +-- @realm client +function WIREPANEL:GetWireColor(i) + i = i or 1 + i = i % (#wire_colors + 1) + + return wire_colors[i] or COLOR_WHITE +end + +--- +-- @param number i The index of the wire +-- @realm client +function WIREPANEL:SetWireIndex(i) + self.m_Image:SetImageColor(self:GetWireColor(i)) + + self.Index = i +end + +vgui.Register("DisarmWire", WIREPANEL, "DImageButton") + +-- Bomb +local BOMBPANEL = {} + +AccessorFunc(BOMBPANEL, "wirecount", "WireCount") + +--- +-- @realm client +function BOMBPANEL:Init() + self.Bomb = vgui.Create("DImage", self) + self.Bomb:SetSize(256, 256) + self.Bomb:SetPos(0, 0) + self.Bomb:SetMaterial(c4_bomb_mat) + + self:SetWireCount(C4_WIRE_COUNT) + + self.Wires = {} + + local wx, wy = -84, 70 + for i = 1, self:GetWireCount() do + local w = vgui.Create("DisarmWire", self) + w:SetPos(wx, wy) + w:SetImage(c4_wire_mat:GetName()) + w:SizeToContents() + + w:SetWireIndex(i) + + table.insert(self.Wires, w) + + wy = wy + 27 + end + + self:SetPaintBackground(false) +end + +vgui.Register("DisarmPanel", BOMBPANEL, "DPanel") + +surface.CreateFont("C4Timer", { + font = "TabLarge", + size = 30, + weight = 750, +}) + +local disarm_success, disarm_fail + +--- +-- @param ttt_c4 bomb The bomb to disarm +-- @realm client +function ShowC4Disarm(bomb) + local dframe = vgui.Create("DFrame") + local w, h = 420, 340 + dframe:SetSize(w, h) + dframe:Center() + dframe:SetTitle(T("c4_disarm")) + dframe:SetVisible(true) + dframe:ShowCloseButton(true) + dframe:SetMouseInputEnabled(true) + + local m = 5 + local title_h = 20 + + local left_w, left_h = 270, 270 + local right_w, right_h = 135, left_h + + local bw, bh = 100, 25 + + local dleft = vgui.Create("ColoredBox", dframe) + dleft:SetColor(Color(50, 50, 50)) + dleft:SetSize(left_w, left_h) + dleft:SetPos(m, m + title_h) + + local dright = vgui.Create("ColoredBox", dframe) + dright:SetColor(Color(50, 50, 50)) + dright:SetSize(right_w, right_h) + dright:SetPos(left_w + m * 2, m + title_h) + + local dtimer = vgui.Create("DLabel", dright) + dtimer:SetText("99:99:99") + dtimer:SetFont("C4Timer") + dtimer:SetTextColor(Color(200, 0, 0, 255)) + dtimer:SetExpensiveShadow(1, COLOR_BLACK) + dtimer:SizeToContents() + dtimer:SetWide(120) + dtimer:SetPos(10, m) + + dtimer.Bomb = bomb + dtimer.Stop = false + + dtimer.Think = function(s) + if not IsValid(bomb) or s.Stop then + return + end + + local t = bomb:GetExplodeTime() + if t then + local r = t - CurTime() + if r > 0 then + s:SetText(util.SimpleTime(r, "%02i:%02i:%02i")) + end + end + end + + local dstatus = vgui.Create("DLabel", dright) + dstatus:SetText(T("c4_status_armed")) + dstatus:SetFont("HealthAmmo") + dstatus:SetTextColor(Color(200, 0, 0, 255)) + dstatus:SetExpensiveShadow(1, COLOR_BLACK) + dstatus:SizeToContents() + dstatus:SetPos(m, m * 2 + 30) + dstatus:CenterHorizontal() + + local dgrab = vgui.Create("DButton", dright) + dgrab:SetPos(m, right_h - m * 2 - bh * 2) + dgrab:SetSize(bw, bh) + dgrab:CenterHorizontal() + dgrab:SetText(T("c4_remove_pickup")) + dgrab:SetDisabled(true) + dgrab.DoClick = function() + if not LocalPlayer():Alive() then + return + end + + RunConsoleCommand("ttt_c4_pickup", bomb:EntIndex()) + dframe:Close() + end + + local ddestroy = vgui.Create("DButton", dright) + ddestroy:SetPos(m, right_h - m - bh) + ddestroy:SetSize(bw, bh) + ddestroy:CenterHorizontal() + ddestroy:SetText(T("c4_remove_destroy1")) + ddestroy:SetDisabled(true) + ddestroy.Confirmed = false + ddestroy.DoClick = function(s) + if not LocalPlayer():Alive() then + return + end + + if not s.Confirmed then + s:SetText(T("c4_remove_destroy2")) + s.Confirmed = true + else + RunConsoleCommand("ttt_c4_destroy", bomb:EntIndex()) + dframe:Close() + end + end + + local desc_h = 45 + + local ddesc = vgui.Create("DLabel", dleft) + ddesc:SetBright(true) + ddesc:SetFont("DermaDefaultBold") + ddesc:SetSize(256, desc_h) + ddesc:SetWrap(true) + if LocalPlayer():IsTraitor() then + ddesc:SetText(T("c4_disarm_t")) + elseif LocalPlayer() == bomb:GetOriginator() then + ddesc:SetText(T("c4_disarm_owned")) + else + ddesc:SetText(T("c4_disarm_other")) + end + ddesc:SetPos(m, m) + + local bg = vgui.Create("ColoredBox", dleft) + bg:StretchToParent(m, m + desc_h, m, m) + bg:SetColor(Color(20, 20, 20, 255)) + + local dbomb = vgui.Create("DisarmPanel", bg) + dbomb:SetSize(256, 256) + dbomb:Center() + + local dcancel = vgui.Create("DButton", dframe) + dcancel:SetPos(w - bw - m, h - bh - m) + dcancel:SetSize(bw, bh) + dcancel:CenterHorizontal() + dcancel:SetText(T("close")) + dcancel.DoClick = function() + dframe:Close() + end + + dframe:MakePopup() + + disarm_success = function() + surface.PlaySound(disarm_beep) + dtimer.Stop = true + + dtimer:SetTextColor(COLOR_GREEN) + + dstatus:SetTextColor(COLOR_GREEN) + dstatus:SetText(T("c4_status_disarmed")) + dstatus:SizeToContents() + dstatus:CenterHorizontal() + + ddestroy:SetDisabled(false) + dgrab:SetDisabled(false) + end + + disarm_fail = function() + dframe:Close() + end + + on_wire_cut = function(idx) + if IsValid(dbomb) then + dbomb:SetDisabled(true) + -- disabled lowers alpha, looks weird here so work around + -- that + dbomb:SetAlpha(255) + end + + if IsValid(bomb) then + RunConsoleCommand("ttt_c4_disarm", tostring(bomb:EntIndex()), tostring(idx)) + end + end +end + +---- Communication + +local function C4ConfigHook() + local bomb = net.ReadEntity() + + if IsValid(bomb) then + if not bomb:GetArmed() then + ShowC4Config(bomb) + else + ShowC4Disarm(bomb) + end + end +end +net.Receive("TTT_C4Config", C4ConfigHook) + +local function C4DisarmResultHook() + local bomb = net.ReadEntity() + local correct = net.ReadBit() == 1 + + if IsValid(bomb) then + if correct and disarm_success then + disarm_success() + elseif disarm_fail then + disarm_fail() + end + end +end +net.Receive("TTT_C4DisarmResult", C4DisarmResultHook) diff --git a/gamemodes/terrortown/entities/entities/ttt_c4/shared.lua b/gamemodes/terrortown/entities/entities/ttt_c4/shared.lua index 0f8635fdf..3e2e41be0 100644 --- a/gamemodes/terrortown/entities/entities/ttt_c4/shared.lua +++ b/gamemodes/terrortown/entities/entities/ttt_c4/shared.lua @@ -12,38 +12,31 @@ local IsValid = IsValid local defuserNearRadius = 90000 if SERVER then - AddCSLuaFile("cl_init.lua") - AddCSLuaFile("shared.lua") + AddCSLuaFile("cl_init.lua") + AddCSLuaFile("shared.lua") end +DEFINE_BASECLASS("ttt_base_placeable") + if CLIENT then - -- this entity can be DNA-sampled so we need some display info - ENT.Icon = "vgui/ttt/icon_c4" - ENT.PrintName = "C4" - - local GetPTranslation = LANG.GetParamTranslation - local hint_params = {usekey = Key("+use", "USE")} - - ENT.TargetIDHint = { - name = "C4", - hint = "c4_hint", - fmt = function(ent, txt) - return GetPTranslation(txt, hint_params) - end - } + -- this entity can be DNA-sampled so we need some display info + ENT.Icon = "vgui/ttt/icon_c4" + ENT.PrintName = "C4" end -C4_WIRE_COUNT = 6 +C4_WIRE_COUNT = 6 C4_MINIMUM_TIME = 45 C4_MAXIMUM_TIME = 600 -ENT.Type = "anim" -ENT.Model = Model("models/weapons/w_c4_planted.mdl") +ENT.Base = "ttt_base_placeable" +ENT.Model = "models/weapons/w_c4_planted.mdl" ENT.CanHavePrints = true ENT.CanUseKey = true ENT.Avoidable = true +ENT.isDestructible = false + --- -- @accessor Entity -- @realm shared @@ -57,31 +50,22 @@ AccessorFunc(ENT, "radius", "Radius", FORCE_NUMBER) --- -- @accessor number -- @realm shared -AccessorFunc(ENT, "dmg", "Dmg", FORCE_NUMBER) +AccessorFunc(ENT, "radius_inner", "RadiusInner", FORCE_NUMBER) --- -- @accessor number -- @realm shared -AccessorFunc(ENT, "arm_time", "ArmTime", FORCE_NUMBER) +AccessorFunc(ENT, "dmg", "Dmg", FORCE_NUMBER) --- -- @accessor number -- @realm shared -AccessorFunc(ENT, "timer_length", "TimerLength", FORCE_NUMBER) - --- Generate accessors for DT vars. This way all consumer code can keep accessing --- the vars as they always did, the only difference is that behind the scenes --- they are set up as DT vars. +AccessorFunc(ENT, "arm_time", "ArmTime", FORCE_NUMBER) --- -- @accessor number -- @realm shared -AccessorFuncDT(ENT, "explode_time", "ExplodeTime") - ---- --- @accessor boolean --- @realm shared -AccessorFuncDT(ENT, "armed", "Armed", FORCE_BOOL) +AccessorFunc(ENT, "timer_length", "TimerLength", FORCE_NUMBER) ENT.timeBeep = 0 ENT.safeWires = nil @@ -90,64 +74,64 @@ ENT.safeWires = nil -- Initializes the data -- @realm shared function ENT:SetupDataTables() - self:DTVar("Int", 0, "explode_time") - self:DTVar("Bool", 0, "armed") + BaseClass.SetupDataTables(self) + + self:NetworkVar("Int", 0, "ExplodeTime") + self:NetworkVar("Bool", 0, "Armed") end --- -- Initializes the C4 -- @realm shared function ENT:Initialize() - self:SetModel(self.Model) + self:SetModel(self.Model) - if SERVER then - self:PhysicsInit(SOLID_VPHYSICS) - end + BaseClass.Initialize(self) - self:SetMoveType(MOVETYPE_VPHYSICS) - self:SetSolid(SOLID_BBOX) - self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + if SERVER then + self:SetUseType(SIMPLE_USE) + end - if SERVER then - self:SetUseType(SIMPLE_USE) - end + self.safeWires = nil + self.timeBeep = 0 + self.DisarmCausedExplosion = false - self.safeWires = nil - self.timeBeep = 0 - self.DisarmCausedExplosion = false + self:SetTimerLength(0) + self:SetExplodeTime(0) + self:SetArmed(false) - self:SetTimerLength(0) - self:SetExplodeTime(0) - self:SetArmed(false) + if not self:GetThrower() then + self:SetThrower(nil) + end - if not self:GetThrower() then - self:SetThrower(nil) - end + if not self:GetRadiusInner() then + self:SetRadiusInner(750) + end - if not self:GetRadius() then - self:SetRadius(1000) - end + if not self:GetRadius() then + self:SetRadius(1000) + end - if not self:GetDmg() then - self:SetDmg(200) - end + if not self:GetDmg() then + self:SetDmg(200) + end end --- -- @param number length time -- @realm shared function ENT:SetDetonateTimer(length) - self:SetTimerLength(length) - self:SetExplodeTime(CurTime() + length) + self:SetTimerLength(length) + self:SetExplodeTime(CurTime() + length) end --- -- @param Entity activator -- @realm shared function ENT:UseOverride(activator) - if IsValid(activator) and activator:IsPlayer() then - self:ShowC4Config(activator) - end + if IsValid(activator) and activator:IsPlayer() then + self:ShowC4Config(activator) + end end --- @@ -155,76 +139,19 @@ end -- @return number -- @realm shared function ENT.SafeWiresForTime(t) - local m = t / 60 - - if m > 4 then - return 1 - elseif m > 3 then - return 2 - elseif m > 2 then - return 3 - elseif m > 1 then - return 4 - else - return 5 - end -end - ---- --- @param boolean state --- @realm shared -function ENT:WeldToGround(state) - if self.IsOnWall then return end - - if state then - -- getgroundentity does not work for non-players - -- so sweep ent downward to find what we're lying on - local ignore = player.GetAll() - ignore[#ignore + 1] = self - - local tr = util.TraceEntity({ - start = self:GetPos(), - endpos = self:GetPos() - Vector(0, 0, 16), - filter = ignore, - mask = MASK_SOLID - }, self) - - -- Start by increasing weight/making uncarryable - local phys = self:GetPhysicsObject() - - if IsValid(phys) then - -- Could just use a pickup flag for this. However, then it's easier to - -- push it around. - self.OrigMass = phys:GetMass() - - phys:SetMass(150) - end - - if tr.Hit and (IsValid(tr.Entity) or tr.HitWorld) then - -- "Attach" to a brush if possible - if IsValid(phys) and tr.HitWorld then - phys:EnableMotion(false) - end - - -- Else weld to objects we cannot pick up - local entphys = tr.Entity:GetPhysicsObject() - - if IsValid(entphys) and entphys:GetMass() > CARRY_WEIGHT_LIMIT then - constraint.Weld(self, tr.Entity, 0, 0, 0, true) - end - - -- Worst case, we are still uncarryable - end - else - constraint.RemoveConstraints(self, "Weld") - - local phys = self:GetPhysicsObject() - - if IsValid(phys) then - phys:EnableMotion(true) - phys:SetMass(self.OrigMass or 10) - end - end + local m = t / 60 + + if m > 4 then + return 1 + elseif m > 3 then + return 2 + elseif m > 2 then + return 3 + elseif m > 1 then + return 4 + else + return 5 + end end --- @@ -233,44 +160,48 @@ end -- @param number radius -- @realm shared function ENT:SphereDamage(dmgowner, center, radius) - -- It seems intuitive to use FindInSphere here, but that will find all ents - -- in the radius, whereas there exist only ~16 players. Hence it is more - -- efficient to cycle through all those players and do a Lua-side distance - -- check. - - local r = radius * radius -- square so we can compare with dot product directly - - -- pre-declare to avoid realloc - local d = 0.0 - local diff = nil - local dmg = 0 - local plys = player.GetAll() - - for i = 1, #plys do - local ply = plys[i] - - if ply:Team() ~= TEAM_TERROR then continue end - - -- dot of the difference with itself is distance squared - diff = center - ply:GetPos() - d = diff:Dot(diff) - - if d >= r then continue end - - -- deadly up to a certain range, then a quick falloff within 100 units - d = math.max(0, math.sqrt(d) - 490) - dmg = -0.01 * (d * d) + 125 - - local dmginfo = DamageInfo() - dmginfo:SetDamage(dmg) - dmginfo:SetAttacker(dmgowner) - dmginfo:SetInflictor(self) - dmginfo:SetDamageType(DMG_BLAST) - dmginfo:SetDamageForce(center - ply:GetPos()) - dmginfo:SetDamagePosition(ply:GetPos()) - - ply:TakeDamageInfo(dmginfo) - end + -- It seems intuitive to use FindInSphere here, but that will find all ents + -- in the radius, whereas there exist only ~16 players. Hence it is more + -- efficient to cycle through all those players and do a Lua-side distance + -- check. + + local r = radius * radius -- square so we can compare with dot product directly + + -- pre-declare to avoid realloc + local d = 0.0 + local diff = nil + local dmg = 0 + local plys = player.GetAll() + + for i = 1, #plys do + local ply = plys[i] + + if ply:Team() ~= TEAM_TERROR then + continue + end + + -- dot of the difference with itself is distance squared + diff = center - ply:GetPos() + d = diff:Dot(diff) + + if d >= r then + continue + end + + -- deadly up to a certain range, then a quick falloff within 100 units + d = math.max(0, math.sqrt(d) - 490) + dmg = -0.01 * (d * d) + 125 + + local dmginfo = DamageInfo() + dmginfo:SetDamage(dmg) + dmginfo:SetAttacker(dmgowner) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_BLAST) + dmginfo:SetDamageForce(center - ply:GetPos()) + dmginfo:SetDamagePosition(ply:GetPos()) + + ply:TakeDamageInfo(dmginfo) + end end local c4boom = Sound("c4.explode") @@ -279,106 +210,107 @@ local c4boom = Sound("c4.explode") -- @param table tr Trace Structure -- @realm shared function ENT:Explode(tr) - --- - -- @realm shared - local result, message = hook.Run("TTTC4Explode", self) + --- + -- @realm shared + -- stylua: ignore + local result, message = hook.Run("TTTC4Explode", self) - if result == false then - if SERVER and message then - LANG.Msg(self:GetThrower(), message, nil, MSG_MSTACK_WARN) - end + if result == false then + if SERVER and message then + LANG.Msg(self:GetThrower(), message, nil, MSG_MSTACK_WARN) + end - return - end + return + end - if SERVER then - self:SetNoDraw(true) - self:SetSolid(SOLID_NONE) + if SERVER then + self:SetNoDraw(true) + self:SetSolid(SOLID_NONE) - -- pull out of the surface - if tr.Fraction ~= 1.0 then - self:SetPos(tr.HitPos + tr.HitNormal * 0.6) - end + -- pull out of the surface + if tr.Fraction ~= 1.0 then + self:SetPos(tr.HitPos + tr.HitNormal * 0.6) + end - local pos = self:GetPos() + local pos = self:GetPos() - if util.PointContents(pos) == CONTENTS_WATER or GetRoundState() ~= ROUND_ACTIVE then - self:Remove() - self:SetExplodeTime(0) + if util.PointContents(pos) == CONTENTS_WATER or GetRoundState() ~= ROUND_ACTIVE then + self:Remove() + self:SetExplodeTime(0) - return - end + return + end - local dmgowner = self:GetThrower() - dmgowner = IsValid(dmgowner) and dmgowner or self + local dmgowner = self:GetThrower() + dmgowner = IsValid(dmgowner) and dmgowner or self - local r_inner = 750 - local r_outer = self:GetRadius() + local r_inner = self:GetRadiusInner() + local r_outer = self:GetRadius() - if self.DisarmCausedExplosion then - r_inner = r_inner / 2.5 - r_outer = r_outer / 2.5 - end + if self.DisarmCausedExplosion then + r_inner = r_inner / 2.5 + r_outer = r_outer / 2.5 + end - -- damage through walls - self:SphereDamage(dmgowner, pos, r_inner) + -- damage through walls + self:SphereDamage(dmgowner, pos, r_inner) - -- explosion damage - util.BlastDamage(self, dmgowner, pos, r_outer, self:GetDmg()) + -- explosion damage + util.BlastDamage(self, dmgowner, pos, r_outer, self:GetDmg()) - local effect = EffectData() - effect:SetStart(pos) - effect:SetOrigin(pos) + local effect = EffectData() + effect:SetStart(pos) + effect:SetOrigin(pos) - -- these don't have much effect with the default Explosion - effect:SetScale(r_outer) - effect:SetRadius(r_outer) - effect:SetMagnitude(self:GetDmg()) + -- these don't have much effect with the default Explosion + effect:SetScale(r_outer) + effect:SetRadius(r_outer) + effect:SetMagnitude(self:GetDmg()) - if tr.Fraction ~= 1.0 then - effect:SetNormal(tr.HitNormal) - end + if tr.Fraction ~= 1.0 then + effect:SetNormal(tr.HitNormal) + end - effect:SetOrigin(pos) + effect:SetOrigin(pos) - util.Effect("Explosion", effect, true, true) - util.Effect("HelicopterMegaBomb", effect, true, true) + util.Effect("Explosion", effect, true, true) + util.Effect("HelicopterMegaBomb", effect, true, true) - timer.Simple(0.1, function() - sound.Play(c4boom, pos, 100, 100) - end) + timer.Simple(0.1, function() + sound.Play(c4boom, pos, 100, 100) + end) - -- extra push - local phexp = ents.Create("env_physexplosion") - phexp:SetPos(pos) - phexp:SetKeyValue("magnitude", self:GetDmg()) - phexp:SetKeyValue("radius", r_outer) - phexp:SetKeyValue("spawnflags", "19") - phexp:Spawn() - phexp:Fire("Explode", "", 0) + -- extra push + local phexp = ents.Create("env_physexplosion") + phexp:SetPos(pos) + phexp:SetKeyValue("magnitude", self:GetDmg()) + phexp:SetKeyValue("radius", r_outer) + phexp:SetKeyValue("spawnflags", "19") + phexp:Spawn() + phexp:Fire("Explode", "", 0) - -- few fire bits to ignite things - timer.Simple(0.2, function() - StartFires(pos, tr, 4, 5, true, dmgowner) - end) + -- few fire bits to ignite things + timer.Simple(0.2, function() + gameEffects.StartFires(pos, tr, 4, 5, true, dmgowner, 500, false, 132, 0) + end) - self:SetExplodeTime(0) + self:SetExplodeTime(0) - events.Trigger(EVENT_C4EXPLODE, dmgowner) + events.Trigger(EVENT_C4EXPLODE, dmgowner) - self:Remove() - else - local spos = self:GetPos() - local trs = util.TraceLine({ - start = spos + Vector(0, 0, 64), - endpos = spos + Vector(0, 0, -128), - filter = self - }) + self:Remove() + else + local spos = self:GetPos() + local trs = util.TraceLine({ + start = spos + Vector(0, 0, 64), + endpos = spos + Vector(0, 0, -128), + filter = self, + }) - util.Decal("Scorch", trs.HitPos + trs.HitNormal, trs.HitPos - trs.HitNormal) + util.Decal("Scorch", trs.HitPos + trs.HitNormal, trs.HitPos - trs.HitNormal) - self:SetExplodeTime(0) - end + self:SetExplodeTime(0) + end end --- @@ -386,26 +318,28 @@ end -- @return[default=false] boolean -- @realm shared function ENT:IsDefuserInRange() - local center = self:GetPos() - local d = 0.0 - local diff = nil - local plys = player.GetAll() + local center = self:GetPos() + local d = 0.0 + local diff = nil + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - if not ply:IsActive() or not ply:GetSubRoleData().isPolicingRole then continue end + if not ply:IsActive() or not ply:GetSubRoleData().isPolicingRole then + continue + end - -- dot of the difference with itself is distance squared - diff = center - ply:GetPos() - d = diff:Dot(diff) + -- dot of the difference with itself is distance squared + diff = center - ply:GetPos() + d = diff:Dot(diff) - if d < defuserNearRadius and ply:HasWeapon("weapon_ttt_defuser") then - return true - end - end + if d < defuserNearRadius and ply:HasWeapon("weapon_ttt_defuser") then + return true + end + end - return false + return false end local soundBeep = Sound("weapons/c4/c4_beep1.wav") @@ -414,69 +348,71 @@ local MAX_MOVE_RANGE = 1000000 -- sq of 1000 --- -- @realm shared function ENT:Think() - if not self:GetArmed() then return end - - if SERVER then - local curpos = self:GetPos() - - if self.LastPos and self.LastPos:DistToSqr(curpos) > MAX_MOVE_RANGE then - self:Disarm(nil) - - return - end - - self.LastPos = curpos - end - - local etime = self:GetExplodeTime() - - if self:GetArmed() and etime ~= 0 and etime < CurTime() then - -- find the ground if it's near and pass it to the explosion - local spos = self:GetPos() - local tr = util.TraceLine({ - start = spos, - endpos = spos + Vector(0, 0, -32), - mask = MASK_SHOT_HULL, - filter = self:GetThrower() - }) - - local success, err = pcall(self.Explode, self, tr) - if not success then - -- prevent effect spam on Lua error - self:Remove() - - ErrorNoHalt("ERROR CAUGHT: ttt_c4: " .. err .. "\n") - end - elseif self:GetArmed() and CurTime() > self.timeBeep then - local amp = 48 - - if self:IsDefuserInRange() then - amp = 65 - - local dlight = CLIENT and DynamicLight(self:EntIndex()) - if dlight then - dlight.Pos = self:GetPos() - dlight.r = 255 - dlight.g = 0 - dlight.b = 0 - dlight.Brightness = 1 - dlight.Size = 128 - dlight.Decay = 500 - dlight.DieTime = CurTime() + 0.1 - end - elseif SERVER then - -- volume lower for long fuse times, bottoms at 50 at +5mins - amp = amp + math.max(0, 12 - (0.03 * self:GetTimerLength())) - end - - if SERVER then - sound.Play(soundBeep, self:GetPos(), amp, 100) - end - - local btime = (etime - CurTime()) / 30 - - self.timeBeep = CurTime() + btime - end + if not self:GetArmed() then + return + end + + if SERVER then + local curpos = self:GetPos() + + if self.LastPos and self.LastPos:DistToSqr(curpos) > MAX_MOVE_RANGE then + self:Disarm(nil) + + return + end + + self.LastPos = curpos + end + + local etime = self:GetExplodeTime() + + if self:GetArmed() and etime ~= 0 and etime < CurTime() then + -- find the ground if it's near and pass it to the explosion + local spos = self:GetPos() + local tr = util.TraceLine({ + start = spos, + endpos = spos + Vector(0, 0, -32), + mask = MASK_SHOT_HULL, + filter = self:GetThrower(), + }) + + local success, err = pcall(self.Explode, self, tr) + if not success then + -- prevent effect spam on Lua error + self:Remove() + + ErrorNoHaltWithStack("ERROR CAUGHT: ttt_c4: " .. err .. "\n") + end + elseif self:GetArmed() and CurTime() > self.timeBeep then + local amp = 48 + + if self:IsDefuserInRange() then + amp = 65 + + local dlight = CLIENT and DynamicLight(self:EntIndex()) + if dlight then + dlight.Pos = self:GetPos() + dlight.r = 255 + dlight.g = 0 + dlight.b = 0 + dlight.Brightness = 1 + dlight.Size = 128 + dlight.Decay = 500 + dlight.DieTime = CurTime() + 0.1 + end + elseif SERVER then + -- volume lower for long fuse times, bottoms at 50 at +5mins + amp = amp + math.max(0, 12 - (0.03 * self:GetTimerLength())) + end + + if SERVER then + sound.Play(soundBeep, self:GetPos(), amp, 100) + end + + local btime = (etime - CurTime()) / 30 + + self.timeBeep = CurTime() + btime + end end --- @@ -484,440 +420,514 @@ end -- @see ENT:GetArmed -- @realm shared function ENT:Defusable() - return self:GetArmed() + return self:GetArmed() end -- Timer configuration handling if SERVER then - --- - -- Inform traitors about us - -- @param boolean armed - -- @realm server - function ENT:SendWarn(armed) - net.Start("TTT_C4Warn") - net.WriteUInt(self:EntIndex(), 16) - net.WriteBit(armed) - - if armed then - net.WriteVector(self:GetPos()) - net.WriteFloat(self:GetExplodeTime()) - net.WriteString(self:GetOwner():GetTeam()) - end - - --net.Send(GetTeamFilter(self:GetOwner():GetTeam(), true)) - net.Broadcast() - end - - --- - -- @realm server - function ENT:OnRemove() - self:SendWarn(false) - end - - --- - -- @param Player ply - -- @realm server - function ENT:Disarm(ply) - local owner = self:GetOwner() - - events.Trigger(EVENT_C4DISARM, owner, ply, true) - - if ply ~= owner and IsValid(owner) then - LANG.Msg(owner, "c4_disarm_warn") - end + --- + -- @realm server + function ENT:OnRemove() + self:RemoveMarkerVision("c4_owner") + end - self:SetExplodeTime(0) - self:SetArmed(false) - self:WeldToGround(false) - self:SendWarn(false) - - self.DisarmCausedExplosion = false - end - - --- - -- @param Player ply - -- @realm server - function ENT:FailedDisarm(ply) - self.DisarmCausedExplosion = true - - events.Trigger(EVENT_C4DISARM, self:GetOwner(), ply, false) - - -- tiny moment of zen and realization before the bang - self:SetExplodeTime(CurTime() + 0.1) - end - - --- - -- @param Player ply - -- @param number time - -- @realm server - function ENT:Arm(ply, time) - -- Initialize armed state - self:SetDetonateTimer(time) - self:SetArmTime(CurTime()) - - self:SetArmed(true) - self:WeldToGround(true) - - self.DisarmCausedExplosion = false - - -- ply may be a different player than he who dropped us. - -- Arming player should be the damage owner = "thrower" - self:SetThrower(ply) - - -- Owner determines who gets messages and can quick-disarm if traitor, - -- make that the armer as well for now. Theoretically the dropping player - -- should also be able to quick-disarm, but that's going to be rare. - self:SetOwner(ply) - - -- Wire stuff: - - self.safeWires = {} - - -- list of possible wires to make safe - local choices = {} - - for i = 1, C4_WIRE_COUNT do - choices[i] = i - end - - -- random selection process, lot like traitor selection - local safe_count = self.SafeWiresForTime(time) - local picked = 0 - - while picked < safe_count do - local pick = math.random(#choices) - local w = choices[pick] - - if not self.safeWires[w] then - self.safeWires[w] = true - - table.remove(choices, pick) - - -- owner will end up having the last safe wire on his corpse - ply.bomb_wire = w - - picked = picked + 1 - end - end - - events.Trigger(EVENT_C4PLANT, ply) - - -- send indicator to traitors - self:SendWarn(true) - end - - --- - -- @param Player ply - -- @realm server - function ENT:ShowC4Config(ply) - -- show menu to player to configure or disarm us - net.Start("TTT_C4Config") - net.WriteEntity(self) - net.Send(ply) - end - - local function ReceiveC4Config(ply, cmd, args) - if not (IsValid(ply) and ply:IsTerror() and #args == 2) then return end + --- + -- @param Player ply + -- @realm server + function ENT:Disarm(ply) + local owner = self:GetOriginator() - local idx = tonumber(args[1]) - local time = tonumber(args[2]) - - if not idx or not time then return end - - local bomb = ents.GetByIndex(idx) + events.Trigger(EVENT_C4DISARM, owner, ply, true) - if IsValid(bomb) and bomb:GetClass() == "ttt_c4" and (not bomb:GetArmed()) then - if bomb:GetPos():Distance(ply:GetPos()) > 256 then - -- These cases should never arise in normal play, so no messages - return - elseif time < C4_MINIMUM_TIME or time > C4_MAXIMUM_TIME then - return - elseif IsValid(bomb:GetPhysicsObject()) and bomb:GetPhysicsObject():HasGameFlag(FVPHYSICS_PLAYER_HELD) then - return - else - --- - -- @realm server - local result, message = hook.Run("TTTC4Arm", bomb, ply) + if ply ~= owner and IsValid(owner) then + LANG.Msg(owner, "c4_disarm_warn") + end - if result ~= false then - LANG.Msg(ply, "c4_armed", nil, MSG_MSTACK_ROLE) + self:SetExplodeTime(0) + self:SetArmed(false) - bomb:Arm(ply, time) - elseif message then - LANG.Msg(ply, message, nil, MSG_MSTACK_WARN) - end - end - end - end - concommand.Add("ttt_c4_config", ReceiveC4Config) + self:RemoveMarkerVision("c4_owner") - local function SendDisarmResult(ply, bomb, disarmResult) - --- - -- @realm server - local result, message = hook.Run("TTTC4Disarm", bomb, result, ply) + self.DisarmCausedExplosion = false + end - if result == false then - if message then - LANG.Msg(ply, message, nil, MSG_MSTACK_WARN) - end - - return - end - - net.Start("TTT_C4DisarmResult") - net.WriteEntity(bomb) - net.WriteBit(disarmResult) -- this way we can squeeze this bit into 16 - net.Send(ply) - end - - local function ReceiveC4Disarm(ply, cmd, args) - if not (IsValid(ply) and ply:IsTerror() and #args == 2) then return end - - local idx = tonumber(args[1]) - local wire = tonumber(args[2]) - - if not idx or not wire then return end + --- + -- @param Player ply + -- @realm server + function ENT:FailedDisarm(ply) + self.DisarmCausedExplosion = true + + events.Trigger(EVENT_C4DISARM, self:GetOriginator(), ply, false) + + -- tiny moment of zen and realization before the bang + self:SetExplodeTime(CurTime() + 0.1) + end + + --- + -- @param Player ply + -- @param number time + -- @realm server + function ENT:Arm(ply, time) + -- Initialize armed state + self:SetDetonateTimer(time) + self:SetArmTime(CurTime()) + + self:SetArmed(true) + + self.DisarmCausedExplosion = false + + -- ply may be a different player than he who dropped us. + -- Arming player should be the damage owner = "thrower" + self:SetThrower(ply) + + -- Owner determines who gets messages and can quick-disarm if traitor, + -- make that the armer as well for now. Theoretically the dropping player + -- should also be able to quick-disarm, but that's going to be rare. + self:SetOriginator(ply) + + -- Wire stuff: + + self.safeWires = {} + + -- list of possible wires to make safe + local choices = {} + + for i = 1, C4_WIRE_COUNT do + choices[i] = i + end + + -- random selection process, lot like traitor selection + local safe_count = self.SafeWiresForTime(time) + local picked = 0 + + while picked < safe_count do + local pick = math.random(#choices) + local w = choices[pick] + + if not self.safeWires[w] then + self.safeWires[w] = true + + table.remove(choices, pick) + + -- owner will end up having the last safe wire on his corpse + ply.bomb_wire = w + + picked = picked + 1 + end + end + + events.Trigger(EVENT_C4PLANT, ply) + + local mvObject = self:AddMarkerVision("c4_owner") + mvObject:SetOwner(ply) + mvObject:SetVisibleFor(VISIBLE_FOR_TEAM) + mvObject:SyncToClients() + end + + --- + -- @param Player ply + -- @realm server + function ENT:ShowC4Config(ply) + -- show menu to player to configure or disarm us + net.Start("TTT_C4Config") + net.WriteEntity(self) + net.Send(ply) + end + + local function ReceiveC4Config(ply, cmd, args) + if not (IsValid(ply) and ply:IsTerror() and #args == 2) then + return + end + + local idx = tonumber(args[1]) + local time = tonumber(args[2]) + + if not idx or not time then + return + end + + local bomb = ents.GetByIndex(idx) + + if IsValid(bomb) and bomb:GetClass() == "ttt_c4" and (not bomb:GetArmed()) then + if bomb:GetPos():Distance(ply:GetPos()) > 256 then + -- These cases should never arise in normal play, so no messages + return + elseif time < C4_MINIMUM_TIME or time > C4_MAXIMUM_TIME then + return + elseif + IsValid(bomb:GetPhysicsObject()) + and bomb:GetPhysicsObject():HasGameFlag(FVPHYSICS_PLAYER_HELD) + then + return + else + --- + -- @realm server + -- stylua: ignore + local result, message = hook.Run("TTTC4Arm", bomb, ply) + + if result ~= false then + LANG.Msg(ply, "c4_armed", nil, MSG_MSTACK_ROLE) + + bomb:Arm(ply, time) + elseif message then + LANG.Msg(ply, message, nil, MSG_MSTACK_WARN) + end + end + end + end + concommand.Add("ttt_c4_config", ReceiveC4Config) + + local function SendDisarmResult(ply, bomb, disarmResult) + --- + -- @realm server + -- stylua: ignore + local result, message = hook.Run("TTTC4Disarm", bomb, disarmResult, ply) + + if result == false then + if message then + LANG.Msg(ply, message, nil, MSG_MSTACK_WARN) + end + + return + end + + net.Start("TTT_C4DisarmResult") + net.WriteEntity(bomb) + net.WriteBit(disarmResult) -- this way we can squeeze this bit into 16 + net.Send(ply) + end + + local function ReceiveC4Disarm(ply, cmd, args) + if not (IsValid(ply) and ply:IsTerror() and #args == 2) then + return + end + + local idx = tonumber(args[1]) + local wire = tonumber(args[2]) + + if not idx or not wire then + return + end + + local bomb = ents.GetByIndex(idx) + + if + IsValid(bomb) + and bomb:GetClass() == "ttt_c4" + and not bomb.DisarmCausedExplosion + and bomb:GetArmed() + then + if bomb:GetPos():Distance(ply:GetPos()) > 256 then + return + elseif bomb.safeWires[wire] or ply:IsTraitor() or ply == bomb:GetOriginator() then + LANG.Msg(ply, "c4_disarmed") + + bomb:Disarm(ply) + + -- only case with success net message + SendDisarmResult(ply, bomb, true) + else + SendDisarmResult(ply, bomb, false) + + -- wrong wire = bomb goes boom + bomb:FailedDisarm(ply) + end + end + end + concommand.Add("ttt_c4_disarm", ReceiveC4Disarm) + + local function ReceiveC4Pickup(ply, cmd, args) + if not (IsValid(ply) and ply:IsTerror() and #args == 1) then + return + end + + local idx = tonumber(args[1]) + if not idx then + return + end + + local bomb = ents.GetByIndex(idx) + + if IsValid(bomb) and bomb:GetClass() == "ttt_c4" and not bomb:GetArmed() then + if bomb:GetPos():Distance(ply:GetPos()) > 256 then + return + else + local prints = bomb.fingerprints or {} + + --- + -- @realm server + -- stylua: ignore + local result, message = hook.Run("TTTC4Pickup", bomb, ply) + + if result == false then + if message then + LANG.Msg(ply, message, nil, MSG_MSTACK_WARN) + end + + return + end + + -- picks up weapon, switches if possible and needed, returns weapon if successful + local wep = ply:SafePickupWeaponClass("weapon_ttt_c4", true) + + if not IsValid(wep) then + LANG.Msg(ply, "c4_no_room") + + return + end + + wep.fingerprints = wep.fingerprints or {} + + table.Add(wep.fingerprints, prints) + + bomb:Remove() + end + end + end + concommand.Add("ttt_c4_pickup", ReceiveC4Pickup) + + local function ReceiveC4Destroy(ply, cmd, args) + if not (IsValid(ply) and ply:IsTerror() and #args == 1) then + return + end + + local idx = tonumber(args[1]) + if not idx then + return + end + + local bomb = ents.GetByIndex(idx) + + if + not IsValid(bomb) + or bomb:GetClass() ~= "ttt_c4" + or bomb:GetArmed() + or bomb:GetPos():Distance(ply:GetPos()) > 256 + then + return + end + + --- + -- @realm server + -- stylua: ignore + local result, message = hook.Run("TTTC4Destroyed", bomb, ply) + + if result == false then + if message then + LANG.Msg(ply, message, nil, MSG_MSTACK_WARN) + end + + return + end + + -- spark to show onlookers we destroyed this bomb + util.EquipmentDestroyed(bomb:GetPos()) + + bomb:Remove() + end + concommand.Add("ttt_c4_destroy", ReceiveC4Destroy) + + --- + -- This hook is run when the C4 is about to be armed. + -- @param Entity bomb The C4 bomb entity + -- @param Player ply The player that armed the C4 + -- @return boolean Return false to cancel arming + -- @return string Return a string with a reason why the arming was canceled, + -- this message is send as a @{MSTACK} to the player that tried to arm the C4 + -- @hook + -- @realm server + function GAMEMODE:TTTC4Arm(bomb, ply) end + + --- + -- This hook is run when the C4 is about to be disarmed. + -- @param Entity bomb The C4 bomb entity + -- @param boolean result The disarming attempt result + -- @param Player ply The player that armed the C4 + -- @return boolean Return false to cancel disarming + -- @return string Return a string with a reason why the disarming was canceled, + -- this message is send as a @{MSTACK} to the player that tried to disarm the C4 + -- @hook + -- @realm server + function GAMEMODE:TTTC4Disarm(bomb, result, ply) end + + --- + -- This hook is run when the C4 is about to be picked up. + -- @param Entity bomb The C4 bomb entity + -- @param Player ply The player that tries to picl up the C4 + -- @return boolean Return false to cancel the pickup + -- @return string Return a string with a reason why the pickup was canceled, + -- this message is send as a @{MSTACK} to the player that tried to pickup the C4 + -- @hook + -- @realm server + function GAMEMODE:TTTC4Pickup(bomb, ply) end + + --- + -- This hook is run when the C4 is about to be armed. + -- @param Entity bomb The C4 bomb entity + -- @param Player ply The player that tries to destroy the C4 + -- @return boolean Return false to cancel the destruction + -- @return string Return a string with a reason why the destruction was canceled, + -- this message is send as a @{MSTACK} to the player that tried to destroy the C4 + -- @hook + -- @realm server + function GAMEMODE:TTTC4Destroyed(bomb, ply) end +else -- CLIENT + local materialC4 = Material("vgui/ttt/marker_vision/c4") + + local TryT = LANG.TryTranslation + local ParT = LANG.GetParamTranslation + + local key_params = { + primaryfire = Key("+attack", "MOUSE1"), + usekey = Key("+use", "USE"), + walkkey = Key("+walk", "WALK"), + } + + surface.CreateFont("C4ModelTimer", { + font = "Default", + size = 13, + weight = 0, + antialias = false, + }) + + --- + -- @return table pos + -- @realm client + function ENT:GetTimerPos() + local att = self:GetAttachment(self:LookupAttachment("controlpanel0_ur")) + if att then + return att + else + local ang = self:GetAngles() + + ang:RotateAroundAxis(self:GetUp(), -90) + + local pos = self:GetPos() + + self:GetForward() * 4.5 + + self:GetUp() * 9.0 + + self:GetRight() * 7.8 + return { + Pos = pos, + Ang = ang, + } + end + end + + local strtime = util.SimpleTime + local max = math.max + + --- + -- @realm client + function ENT:Draw() + self:DrawModel() + + if not self:GetArmed() then + return + end + + local angpos_ur = self:GetTimerPos() + if not angpos_ur then + return + end + + cam.Start3D2D(angpos_ur.Pos, angpos_ur.Ang, 0.2) + + draw.DrawText( + strtime(max(0, self:GetExplodeTime() - CurTime()), "%02i:%02i"), + "C4ModelTimer", + -1, + 1, + COLOR_RED, + TEXT_ALIGN_RIGHT + ) + + cam.End3D2D() + end + + -- handle looking at C4 + hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDC4", function(tData) + local client = LocalPlayer() + local ent = tData:GetEntity() + local c_wep = client:GetActiveWeapon() + + if + not client:IsTerror() + or not IsValid(ent) + or tData:GetEntityDistance() > 100 + or ent:GetClass() ~= "ttt_c4" + then + return + end + + local defuser_useable = (IsValid(c_wep) and ent:GetArmed()) + and c_wep:GetClass() == "weapon_ttt_defuser" + or false + + -- enable targetID rendering + tData:EnableText() + tData:EnableOutline() + tData:SetOutlineColor(client:GetRoleColor()) + + tData:SetTitle(TryT(ent.PrintName)) + + if ent:GetArmed() and defuser_useable then + tData:SetSubtitle(ParT("target_c4_armed_defuser", key_params)) + elseif ent:GetArmed() then + tData:SetSubtitle(ParT("target_c4_armed", key_params)) + else + tData:SetSubtitle(ParT("target_c4", key_params)) + end + + tData:SetKeyBinding(defuser_useable and "+attack" or "+use") + tData:AddDescriptionLine(TryT("c4_short_desc")) + end) + + hook.Add("TTT2RenderMarkerVisionInfo", "HUDDrawMarkerVisionC4", function(mvData) + local ent = mvData:GetEntity() + local mvObject = mvData:GetMarkerVisionObject() + + if not mvObject:IsObjectFor(ent, "c4_owner") then + return + end + + local owner = ent:GetOriginator() + local nick = IsValid(owner) and owner:Nick() or "---" + + local time = util.SimpleTime(ent:GetExplodeTime() - CurTime(), "%02i:%02i") + local distance = math.Round(util.HammerUnitsToMeters(mvData:GetEntityDistance()), 1) + + mvData:EnableText() + + mvData:SetTitle(TryT(ent.PrintName)) + + mvData:AddDescriptionLine(ParT("marker_vision_owner", { owner = nick })) + mvData:AddDescriptionLine(ParT("c4_marker_vision_time", { time = time })) + mvData:AddDescriptionLine(ParT("marker_vision_distance", { distance = distance })) + + mvData:AddDescriptionLine(TryT(mvObject:GetVisibleForTranslationKey()), COLOR_SLATEGRAY) + + local color = COLOR_WHITE + + if mvData:GetEntityDistance() > ent:GetRadius() then + mvData:AddDescriptionLine(TryT("c4_marker_vision_safe_zone"), COLOR_GREEN) + elseif mvData:GetEntityDistance() > ent:GetRadiusInner() then + mvData:AddDescriptionLine(TryT("c4_marker_vision_damage_zone"), COLOR_ORANGE) - local bomb = ents.GetByIndex(idx) + color = COLOR_ORANGE + else + mvData:AddDescriptionLine(TryT("c4_marker_vision_kill_zone"), COLOR_RED) + + color = COLOR_RED + end + + mvData:AddIcon( + materialC4, + (mvData:IsOffScreen() or not mvData:IsOnScreenCenter()) and color + ) - if IsValid(bomb) and bomb:GetClass() == "ttt_c4" and not bomb.DisarmCausedExplosion and bomb:GetArmed() then - if bomb:GetPos():Distance(ply:GetPos()) > 256 then - return - elseif bomb.safeWires[wire] or ply:IsTraitor() or ply == bomb:GetOwner() then - LANG.Msg(ply, "c4_disarmed") - - bomb:Disarm(ply) - - -- only case with success net message - SendDisarmResult(ply, bomb, true) - else - SendDisarmResult(ply, bomb, false) - - -- wrong wire = bomb goes boom - bomb:FailedDisarm(ply) - end - end - end - concommand.Add("ttt_c4_disarm", ReceiveC4Disarm) - - local function ReceiveC4Pickup(ply, cmd, args) - if not (IsValid(ply) and ply:IsTerror() and #args == 1) then return end - - local idx = tonumber(args[1]) - if not idx then return end - - local bomb = ents.GetByIndex(idx) - - if IsValid(bomb) and bomb:GetClass() == "ttt_c4" and not bomb:GetArmed() then - if bomb:GetPos():Distance(ply:GetPos()) > 256 then - return - else - local prints = bomb.fingerprints or {} - - --- - -- @realm server - local result, message = hook.Run("TTTC4Pickup", bomb, ply) - - if result == false then - if message then - LANG.Msg(ply, message, nil, MSG_MSTACK_WARN) - end - - return - end - - -- picks up weapon, switches if possible and needed, returns weapon if successful - local wep = ply:SafePickupWeaponClass("weapon_ttt_c4", true) - - if not IsValid(wep) then - LANG.Msg(ply, "c4_no_room") - - return - end - - wep.fingerprints = wep.fingerprints or {} - - table.Add(wep.fingerprints, prints) - - bomb:Remove() - end - end - end - concommand.Add("ttt_c4_pickup", ReceiveC4Pickup) - - local function ReceiveC4Destroy(ply, cmd, args) - if not (IsValid(ply) and ply:IsTerror() and #args == 1) then return end - - local idx = tonumber(args[1]) - if not idx then return end - - local bomb = ents.GetByIndex(idx) - - if not IsValid(bomb) - or bomb:GetClass() ~= "ttt_c4" - or bomb:GetArmed() - or bomb:GetPos():Distance(ply:GetPos()) > 256 - then return end - - --- - -- @realm server - local result, message = hook.Run("TTTC4Destroyed", bomb, ply) - - if result == false then - if message then - LANG.Msg(ply, message, nil, MSG_MSTACK_WARN) - end - - return - end - - -- spark to show onlookers we destroyed this bomb - util.EquipmentDestroyed(bomb:GetPos()) - - bomb:Remove() - end - concommand.Add("ttt_c4_destroy", ReceiveC4Destroy) - - --- - -- This hook is run when the C4 is about to be armed. - -- @param Entity bomb The C4 bomb entity - -- @param Player ply The player that armed the C4 - -- @return boolean Return false to cancel arming - -- @return string Return a string with a reason why the arming was canceled, - -- this message is send as a @{MSTACK} to the player that tried to arm the C4 - -- @hook - -- @realm server - function GAMEMODE:TTTC4Arm(bomb, ply) - - end - - --- - -- This hook is run when the C4 is about to be disarmed. - -- @param Entity bomb The C4 bomb entity - -- @param boolean result The disarming attempt result - -- @param Player ply The player that armed the C4 - -- @return boolean Return false to cancel disarming - -- @return string Return a string with a reason why the disarming was canceled, - -- this message is send as a @{MSTACK} to the player that tried to disarm the C4 - -- @hook - -- @realm server - function GAMEMODE:TTTC4Disarm(bomb, result, ply) - - end - - --- - -- This hook is run when the C4 is about to be picked up. - -- @param Entity bomb The C4 bomb entity - -- @param Player ply The player that tries to picl up the C4 - -- @return boolean Return false to cancel the pickup - -- @return string Return a string with a reason why the pickup was canceled, - -- this message is send as a @{MSTACK} to the player that tried to pickup the C4 - -- @hook - -- @realm server - function GAMEMODE:TTTC4Pickup(bomb, ply) - - end - - --- - -- This hook is run when the C4 is about to be armed. - -- @param Entity bomb The C4 bomb entity - -- @param Player ply The player that tries to destroy the C4 - -- @return boolean Return false to cancel the destruction - -- @return string Return a string with a reason why the destruction was canceled, - -- this message is send as a @{MSTACK} to the player that tried to destroy the C4 - -- @hook - -- @realm server - function GAMEMODE:TTTC4Destroyed(bomb, ply) - - end -else -- CLIENT - local TryT = LANG.TryTranslation - local GetPT = LANG.GetParamTranslation - - local key_params = { - usekey = Key("+use", "USE"), - walkkey = Key("+walk", "WALK") - } - - surface.CreateFont("C4ModelTimer", { - font = "Default", - size = 13, - weight = 0, - antialias = false - }) - - --- - -- @return table pos - -- @realm client - function ENT:GetTimerPos() - local att = self:GetAttachment(self:LookupAttachment("controlpanel0_ur")) - if att then - return att - else - local ang = self:GetAngles() - - ang:RotateAroundAxis(self:GetUp(), -90) - - local pos = self:GetPos() + self:GetForward() * 4.5 + self:GetUp() * 9.0 + self:GetRight() * 7.8 - return { - Pos = pos, - Ang = ang - } - end - end - - local strtime = util.SimpleTime - local max = math.max - - --- - -- @realm client - function ENT:Draw() - self:DrawModel() - - if not self:GetArmed() then return end - - local angpos_ur = self:GetTimerPos() - if not angpos_ur then return end - - cam.Start3D2D(angpos_ur.Pos, angpos_ur.Ang, 0.2) - - draw.DrawText(strtime(max(0, self:GetExplodeTime() - CurTime()), "%02i:%02i"), "C4ModelTimer", -1, 1, COLOR_RED, TEXT_ALIGN_RIGHT) - - cam.End3D2D() - end - - -- handle looking at C4 - hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDC4", function(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - local c_wep = client:GetActiveWeapon() - - if not IsValid(client) or not client:IsTerror() or not client:Alive() - or not IsValid(ent) or tData:GetEntityDistance() > 100 or ent:GetClass() ~= "ttt_c4" then - return - end - - local defuser_useable = (IsValid(c_wep) and ent:GetArmed()) and c_wep:GetClass() == "weapon_ttt_defuser" or false - - -- enable targetID rendering - tData:EnableText() - tData:EnableOutline() - tData:SetOutlineColor(client:GetRoleColor()) - - tData:SetTitle(TryT(ent.PrintName)) - - if ent:GetArmed() and defuser_useable then - tData:SetSubtitle(GetPT("target_c4_armed_defuser", key_params)) - elseif ent:GetArmed() then - tData:SetSubtitle(GetPT("target_c4_armed", key_params)) - else - tData:SetSubtitle(GetPT("target_c4", key_params)) - end - - tData:SetKeyBinding(defuser_useable and "+attack" or "+use") - tData:AddDescriptionLine(TryT("c4_short_desc")) - end) + mvData:SetCollapsedLine(time) + end) end --- @@ -929,6 +939,4 @@ end -- @return string Return a string with a reason why the explosion was canceled -- @hook -- @realm shared -function GAMEMODE:TTTC4Explode(bomb) - -end +function GAMEMODE:TTTC4Explode(bomb) end diff --git a/gamemodes/terrortown/entities/entities/ttt_carry_handler.lua b/gamemodes/terrortown/entities/entities/ttt_carry_handler.lua new file mode 100644 index 000000000..b1f13a015 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_carry_handler.lua @@ -0,0 +1,149 @@ +--- +-- @class ENT +-- @desc A persistent version of the invisible entity weapon_zm_carry uses to drag things around. +-- @section ttt_carry_handler + +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Carried = nil +ENT.CarriedMass = 0 +ENT.PrevThink = 0 +ENT.TargetPos = Vector(0, 0, 0) +ENT.TargetAng = Angle(0, 0, 0) +ENT.Owner = nil + +--- +-- @ignore +function ENT:Initialize() + if SERVER and IsValid(self.Carried) then + -- self:SetModel("models/weapons/w_bugbait.mdl") + self:SetModel(self.Carried:GetModel()) + -- self:SetSkin(self.Carried:GetSkin()) + -- self:SetColor(se.Carried:GetColor()) + end + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_NONE) + -- self:SetSolid(SOLID_NONE) + self:SetNoDraw(true) + -- self:SetHealth(9999) + + -- local ply = self:GetOwner() + -- self.Owner = ply + -- if IsValid(ply) then + -- self.TargetPos = ply:GetShootPos() + (ply:GetAimVector() * 70) + -- self.TargetAng = ply:GetAimVector() + -- end + + if SERVER and IsValid(self.Carried) then + local phys = self:GetPhysicsObject() + local carphys = self.Carried:GetPhysicsObject() + + if IsValid(phys) and IsValid(carphys) then + phys:Wake() + carphys:Wake() + + phys:SetMass(9999) + + phys:SetDamping(0, 1000) + carphys:SetDamping(0, 1000) + + -- if not carphys:IsPenetrating() then + -- phys:SetPos(carphys:GetPos()) + -- phys:SetAngle(carphys:GetAngle()) + -- carphys:SetPos( phys:GetPos() ) + -- carphys:SetAngle( phys:GetAngle() ) + -- end + end + + self.Carried:SetGravity(false) + self.Carried:SetOwner(self:GetOwner()) + -- self.Carried:SetNoDraw(true) + -- self.Carried:SetSolid(SOLID_NONE) + end +end + +--- +-- @ignore +function ENT:OnRemove() + if IsValid(self.Carried) then + self.Carried:SetGravity(true) + self.Carried:SetOwner(nil) + -- self.Carried:SetNoDraw(false) + -- self.Carried:SetSolid(SOLID_VPHYSICS) + self.Carried:SetMoveType(MOVETYPE_VPHYSICS) + + local carphys = self.Carried:GetPhysicsObject() + if IsValid(carphys) then + carphys:SetDamping(0, 0) + end + + self.Carried:PhysWake() + end +end + +-- function ENT:Think() +-- if CLIENT then return end + +-- -- Check on all entities involved + +-- local obj = self.Carried +-- local ply = self:GetOwner() +-- if not IsValid(obj) or not IsValid(ply) or not ply:Alive() then +-- self:Remove() +-- return +-- end + +-- -- Check some other requirements +-- local spos = ply:GetShootPos() +-- if ply:GetGroundEntity() == obj or obj:NearestPoint(spos):Distance(spos) > 150 then +-- self:Remove() +-- return +-- end + +-- self.TargetPos = spos + (ply:GetAimVector() * 70) +-- self.TargetAng = ply:GetAimVector() + +-- local phys = self:GetPhysicsObject() +-- local carryphys = obj:GetPhysicsObject() +-- if IsValid(phys) and IsValid(carryphys) then +-- if phys:IsPenetrating() then +-- self:Remove() +-- return +-- -- self.TargetPos = phys:GetPos() + Vector(0,0,5) +-- -- phys:SetPos(self.TargetPos) +-- end + +-- carryphys:SetPos(phys:GetPos()) +-- carryphys:SetAngle(phys:GetAngles()) +-- carryphys:SetVelocity(phys:GetVelocity()) +-- end +-- end + +-- function ENT:PhysicsSimulate(phys, delta) +-- phys:Wake() + +-- local p = {} +-- p.pos = self.TargetPos +-- p.angle = self.TargetAng +-- p.secondstoarrive = 0.05 +-- p.maxangular = 100 +-- p.maxangulardamp = 10000 +-- p.maxspeed = 100 +-- p.maxspeeddamp = 1000 +-- p.dampfactor = 0.8 +-- p.teleportdistance = 0 +-- p.deltatime = delta + +-- phys:ComputeShadowControl(p) +-- end + +--- +-- @ignore +function ENT:OnTakeDamage(dmg) + -- do nothing +end diff --git a/gamemodes/terrortown/entities/entities/ttt_confgrenade_proj.lua b/gamemodes/terrortown/entities/entities/ttt_confgrenade_proj.lua index ea028360b..2defab949 100644 --- a/gamemodes/terrortown/entities/entities/ttt_confgrenade_proj.lua +++ b/gamemodes/terrortown/entities/entities/ttt_confgrenade_proj.lua @@ -5,11 +5,12 @@ local ttt_allow_jump if SERVER then - AddCSLuaFile() + AddCSLuaFile() - --- - -- @realm server - ttt_allow_jump = CreateConVar("ttt_allow_discomb_jump", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) + --- + -- @realm server + -- stylua: ignore + ttt_allow_jump = CreateConVar("ttt_allow_discomb_jump", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) end ENT.Type = "anim" @@ -17,61 +18,67 @@ ENT.Base = "ttt_basegrenade_proj" ENT.Model = Model("models/weapons/w_eq_fraggrenade_thrown.mdl") local function PushPullRadius(pos, pusher) - local radius = 400 - local phys_force = 1500 - local push_force = 256 - local entsInSphere = ents.FindInSphere(pos, radius) - - -- pull physics objects and push players - for i = 1, #entsInSphere do - local target = entsInSphere[i] - - if not IsValid(target) then continue end - - local tpos = target:LocalToWorld(target:OBBCenter()) - local dir = (tpos - pos):GetNormal() - local phys = target:GetPhysicsObject() - - if target:IsPlayer() and not target:IsFrozen() and (not target.was_pushed or target.was_pushed.t ~= CurTime()) then - -- always need an upwards push to prevent the ground's friction from - -- stopping nearly all movement - dir.z = math.abs(dir.z) + 1 - - local push = dir * push_force - - -- try to prevent excessive upwards force - local vel = target:GetVelocity() + push - vel.z = math.min(vel.z, push_force) - - -- mess with discomb jumps - if pusher == target and not ttt_allow_jump:GetBool() then - vel = VectorRand() * vel:Length() - vel.z = math.abs(vel.z) - end - - target:SetVelocity(vel) - - target.was_pushed = { - att = pusher, - t = CurTime(), - wep = "weapon_ttt_confgrenade" - } - elseif IsValid(phys) then - phys:ApplyForceCenter(dir * -1 * phys_force) - end - end - - local phexp = ents.Create("env_physexplosion") - - if IsValid(phexp) then - phexp:SetPos(pos) - phexp:SetKeyValue("magnitude", 100) --max - phexp:SetKeyValue("radius", radius) - -- 1 = no dmg, 2 = push ply, 4 = push radial, 8 = los, 16 = viewpunch - phexp:SetKeyValue("spawnflags", 1 + 2 + 16) - phexp:Spawn() - phexp:Fire("Explode", "", 0.2) - end + local radius = 400 + local phys_force = 1500 + local push_force = 256 + local entsInSphere = ents.FindInSphere(pos, radius) + + -- pull physics objects and push players + for i = 1, #entsInSphere do + local target = entsInSphere[i] + + if not IsValid(target) then + continue + end + + local tpos = target:LocalToWorld(target:OBBCenter()) + local dir = (tpos - pos):GetNormal() + local phys = target:GetPhysicsObject() + + if + target:IsPlayer() + and not target:IsFrozen() + and (not target.was_pushed or target.was_pushed.t ~= CurTime()) + then + -- always need an upwards push to prevent the ground's friction from + -- stopping nearly all movement + dir.z = math.abs(dir.z) + 1 + + local push = dir * push_force + + -- try to prevent excessive upwards force + local vel = target:GetVelocity() + push + vel.z = math.min(vel.z, push_force) + + -- mess with discomb jumps + if pusher == target and not ttt_allow_jump:GetBool() then + vel = VectorRand() * vel:Length() + vel.z = math.abs(vel.z) + end + + target:SetVelocity(vel) + + target.was_pushed = { + att = pusher, + t = CurTime(), + wep = "weapon_ttt_confgrenade", + } + elseif IsValid(phys) then + phys:ApplyForceCenter(dir * -1 * phys_force) + end + end + + local phexp = ents.Create("env_physexplosion") + + if IsValid(phexp) then + phexp:SetPos(pos) + phexp:SetKeyValue("magnitude", 100) --max + phexp:SetKeyValue("radius", radius) + -- 1 = no dmg, 2 = push ply, 4 = push radial, 8 = los, 16 = viewpunch + phexp:SetKeyValue("spawnflags", 1 + 2 + 16) + phexp:Spawn() + phexp:Fire("Explode", "", 0.2) + end end local zapsound = Sound("npc/assassin/ball_zap1.wav") @@ -79,44 +86,44 @@ local zapsound = Sound("npc/assassin/ball_zap1.wav") --- -- @ignore function ENT:Explode(tr) - if SERVER then - self:SetNoDraw(true) - self:SetSolid(SOLID_NONE) + if SERVER then + self:SetNoDraw(true) + self:SetSolid(SOLID_NONE) - -- pull out of the surface - if tr.Fraction ~= 1.0 then - self:SetPos(tr.HitPos + tr.HitNormal * 0.6) - end + -- pull out of the surface + if tr.Fraction ~= 1.0 then + self:SetPos(tr.HitPos + tr.HitNormal * 0.6) + end - local pos = self:GetPos() + local pos = self:GetPos() - -- make sure we are removed, even if errors occur later - self:Remove() + -- make sure we are removed, even if errors occur later + self:Remove() - PushPullRadius(pos, self:GetThrower()) + PushPullRadius(pos, self:GetThrower()) - local effect = EffectData() - effect:SetStart(pos) - effect:SetOrigin(pos) + local effect = EffectData() + effect:SetStart(pos) + effect:SetOrigin(pos) - if tr.Fraction ~= 1.0 then - effect:SetNormal(tr.HitNormal) - end + if tr.Fraction ~= 1.0 then + effect:SetNormal(tr.HitNormal) + end - util.Effect("Explosion", effect, true, true) - util.Effect("cball_explode", effect, true, true) + util.Effect("Explosion", effect, true, true) + util.Effect("cball_explode", effect, true, true) - sound.Play(zapsound, pos, 100, 100) - else -- CLIENT - local spos = self:GetPos() - local trs = util.TraceLine({ - start = spos + Vector(0,0,64), - endpos = spos + Vector(0,0,-128), - filter = self - }) + sound.Play(zapsound, pos, 100, 100) + else -- CLIENT + local spos = self:GetPos() + local trs = util.TraceLine({ + start = spos + Vector(0, 0, 64), + endpos = spos + Vector(0, 0, -128), + filter = self, + }) - util.Decal("SmallScorch", trs.HitPos + trs.HitNormal, trs.HitPos - trs.HitNormal) + util.Decal("SmallScorch", trs.HitPos + trs.HitNormal, trs.HitPos - trs.HitNormal) - self:SetDetonateExact(0) - end + self:SetDetonateExact(0) + end end diff --git a/gamemodes/terrortown/entities/entities/ttt_credit_adjust.lua b/gamemodes/terrortown/entities/entities/ttt_credit_adjust.lua new file mode 100644 index 000000000..e9ff61841 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_credit_adjust.lua @@ -0,0 +1,49 @@ +--- +-- @class ENT +-- @desc Handles player credit interactions with the map +-- @section CreditAdjust + +ENT.Type = "point" +ENT.Base = "base_point" + +ENT.Credits = 0 + +--- +-- Sets Hammer key values on an entity. +-- @param string key The internal key name +-- @param string value The value to set +-- @realm server +function ENT:KeyValue(key, value) + if key == "OnSuccess" or key == "OnFail" then + self:StoreOutput(key, value) + elseif key == "credits" then + self.Credits = tonumber(value) or 0 + + if not tonumber(value) then + ErrorNoHalt(tostring(self) .. " has bad 'credits' setting.\n") + end + end +end + +--- +-- Called when another entity fires an event to this entity. +-- @param string name The name of the input that was triggered +-- @param Entity activator The initial cause for the input getting triggered; e.g. the player who pushed a button +-- @realm server +function ENT:AcceptInput(name, activator) + if name ~= "TakeCredits" then + return + end + + if IsValid(activator) and activator:IsPlayer() then + if activator:GetCredits() >= self.Credits then + activator:SubtractCredits(self.Credits) + + self:TriggerOutput("OnSuccess", activator) + else + self:TriggerOutput("OnFail", activator) + end + end + + return true +end diff --git a/gamemodes/terrortown/entities/entities/ttt_cse_proj.lua b/gamemodes/terrortown/entities/entities/ttt_cse_proj.lua new file mode 100644 index 000000000..bff300dfb --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_cse_proj.lua @@ -0,0 +1,246 @@ +--- +-- @class ENT +-- @desc Visualizer projective thrown in the world +-- @section CSEProj + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("ttt_base_placeable") + +ENT.Base = "ttt_base_placeable" + +ENT.Model = "models/Items/battery.mdl" + +ENT.Range = 128 +ENT.MaxScenesPerPulse = 3 +ENT.SceneDuration = 10 +ENT.PulseDelay = 10 + +ENT.CanUseKey = true +ENT.pickupWeaponClass = "weapon_ttt_cse" + +--- +-- @realm shared +function ENT:Initialize() + self:SetModel(self.Model) + + BaseClass.Initialize(self) + + self:SetSolid(SOLID_VPHYSICS) + + if SERVER then + self:SetMaxHealth(50) + self:NextThink(CurTime() + 1) + end + + self:SetHealth(50) +end + +--- +-- @realm shared +function ENT:GetNearbyCorpses() + local pos = self:GetPos() + + local near = ents.FindInSphere(pos, self.Range) + if not near then + return + end + + local near_corpses = {} + + for i = 1, #near do + local ent = near[i] + if IsValid(ent) and ent.player_ragdoll and ent.scene then + table.insert(near_corpses, { ent = ent, dist = pos:LengthSqr() }) + end + end + + return near_corpses +end + +local scanloop = Sound("weapons/gauss/chargeloop.wav") +local dummy_keys = { "victim", "killer" } + +--- +-- @param Entity corpse +-- @realm shared +function ENT:ShowSceneForCorpse(corpse) + local scene = corpse.scene + local hit = scene.hit_trace + local dur = self.SceneDuration + + if hit then + -- line showing bullet trajectory + local e = EffectData() + e:SetEntity(corpse) + e:SetStart(hit.StartPos) + e:SetOrigin(hit.HitPos) + e:SetMagnitude(hit.HitBox) + e:SetScale(dur) + + util.Effect("crimescene_shot", e) + end + + if not scene then + return + end + + for i = 1, #dummy_keys do + local dummy_key = dummy_keys[i] + local dummy = scene[dummy_key] + + if not dummy then + continue + end + + -- Horrible sins committed here to get all the data we need over the + -- wire, the pose parameters are going to be truncated etc. but + -- everything sort of works out. If you know a better way to get this + -- much data to an effect, let me know. + local e = EffectData() + e:SetEntity(corpse) + e:SetOrigin(dummy.pos) + e:SetAngles(dummy.ang) + e:SetColor(dummy.sequence) + e:SetScale(dummy.cycle) + e:SetStart(Vector(dummy.aim_yaw, dummy.aim_pitch, dummy.move_yaw)) + e:SetRadius(dur) + + util.Effect("crimescene_dummy", e) + end +end + +--- +-- @realm shared +function ENT:StartScanSound() + if not self.ScanSound then + self.ScanSound = CreateSound(self, scanloop) + end + + if not self.ScanSound:IsPlaying() then + self.ScanSound:PlayEx(0.5, 100) + end +end + +--- +-- @param number force +-- @realm shared +function ENT:StopScanSound(force) + if self.ScanSound and self.ScanSound:IsPlaying() then + self.ScanSound:FadeOut(0.5) + end + + if self.ScanSound and force then + self.ScanSound:Stop() + end +end + +--- +-- @realm shared +function ENT:OnRemove() + self:StopScanSound(true) +end + +if SERVER then + --- + -- @realm server + function ENT:Think() + -- prevent starting effects when round is about to restart + if GetRoundState() == ROUND_POST then + return + end + + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + local corpses = self:GetNearbyCorpses() + if #corpses > self.MaxScenesPerPulse then + table.SortByMember(corpses, "dist", true) + end + + local e = EffectData() + e:SetOrigin(self:GetPos()) + e:SetRadius(128) + e:SetMagnitude(0.5) + e:SetScale(4) + util.Effect("pulse_sphere", e) + + -- show scenes for nearest corpses + for i = 1, self.MaxScenesPerPulse do + local corpse = corpses[i] + + if corpse and IsValid(corpse.ent) then + self:ShowSceneForCorpse(corpse.ent) + end + end + + if #corpses > 0 then + self:StartScanSound() + else + self:StopScanSound() + end + + -- "schedule" next show pulse, return true to enable NextThink + self:NextThink(CurTime() + self.PulseDelay) + + return true + end + + --- + -- @param Player activator + -- @realm server + function ENT:PlayerCanPickupWeapon(activator) + local roleDataActivator = activator:GetSubRoleData() + + return roleDataActivator.isPolicingRole and roleDataActivator.isPublicRole + end +end + +if CLIENT then + local TryT = LANG.TryTranslation + local ParT = LANG.GetParamTranslation + + local glow = Material("sprites/blueglow2") + + --- + -- @realm client + function ENT:DrawTranslucent() + render.SetMaterial(glow) + + render.DrawSprite(self:LocalToWorld(self:OBBCenter()), 32, 32, COLOR_WHITE) + end + + hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDVisualizer", function(tData) + local client = LocalPlayer() + local ent = tData:GetEntity() + local roleData = client:GetSubRoleData() + + if + not IsValid(client) + or not client:IsTerror() + or not client:Alive() + or tData:GetEntityDistance() > 100 + or ent:GetClass() ~= "ttt_cse_proj" + then + return + end + + -- enable targetID rendering + tData:EnableText() + tData:EnableOutline() + tData:SetOutlineColor(client:GetRoleColor()) + + tData:SetTitle(TryT("vis_name")) + + if roleData.isPublicRole and roleData.isPolicingRole then + tData:SetSubtitle(ParT("target_pickup", { usekey = Key("+use", "USE") })) + tData:SetKeyBinding("+use") + else + tData:SetSubtitle(TryT("vis_no_pickup")) + tData:AddIcon(roles.DETECTIVE.iconMaterial) + end + + tData:AddDescriptionLine(TryT("vis_short_desc")) + end) +end diff --git a/gamemodes/terrortown/entities/entities/ttt_damageowner.lua b/gamemodes/terrortown/entities/entities/ttt_damageowner.lua new file mode 100644 index 000000000..e5df2f773 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_damageowner.lua @@ -0,0 +1,77 @@ +--- +-- @class ENT +-- @desc Map event damage owner +-- @section DamageOwner + +ENT.Type = "point" +ENT.Base = "base_point" + +ENT.Damager = nil +ENT.KillName = nil + +--- +-- Sets Hammer key values on an entity. +-- @param string key The internal key name +-- @param string value The value to set +-- @realm server +function ENT:KeyValue(key, value) + if key == "damager" then + self.Damager = tostring(value) + elseif key == "killname" then + self.KillName = tostring(value) + end +end + +--- +-- Called when another entity fires an event to this entity. +-- @param string name The name of the input that was triggered +-- @param Entity activator The initial cause for the input getting triggered; e.g. the player who pushed a button +-- @param Entity caller The entity that directly triggered the input; e.g. the button that was pushed +-- @param string data The data passed +-- @realm server +function ENT:AcceptInput(name, activator, caller, data) + if name == "SetActivatorAsDamageOwner" then + if not self.Damager then + return + end + + if IsValid(activator) and activator:IsPlayer() then + local damagerEnts = ents.FindByName(self.Damager) + + for i = 1, #damagerEnts do + local ent = damagerEnts[i] + + if not IsValid(ent) or not ent.SetDamageOwner then + continue + end + + ent:SetDamageOwner(activator) + ent.ScoreName = self.KillName + + Dev(2, "Setting damageowner on", ent, ent:GetName()) + end + end + + return true + elseif name == "ClearDamageOwner" then + if not self.Damager then + return + end + + local damagerEnts = ents.FindByName(self.Damager) + + for i = 1, #damagerEnts do + local ent = damagerEnts[i] + + if not IsValid(ent) or not ent.SetDamageOwner then + continue + end + + ent:SetDamageOwner(nil) + + Dev(2, "Clearing damageowner on", ent, ent:GetName()) + end + + return true + end +end diff --git a/gamemodes/terrortown/entities/entities/ttt_decoy.lua b/gamemodes/terrortown/entities/entities/ttt_decoy.lua index dc970f6dc..129ba1ab2 100644 --- a/gamemodes/terrortown/entities/entities/ttt_decoy.lua +++ b/gamemodes/terrortown/entities/entities/ttt_decoy.lua @@ -5,126 +5,104 @@ -- @section ttt_decoy if SERVER then - AddCSLuaFile() + AddCSLuaFile() end -ENT.Type = "anim" -ENT.Model = Model("models/props_lab/reciever01b.mdl") -ENT.CanHavePrints = false -ENT.CanUseKey = true - ---- --- @realm shared -function ENT:Initialize() - self:SetModel(self.Model) - - if SERVER then - self:PhysicsInit(SOLID_VPHYSICS) - end +DEFINE_BASECLASS("ttt_base_placeable") - self:SetMoveType(MOVETYPE_VPHYSICS) - self:SetSolid(SOLID_VPHYSICS) - self:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) +ENT.Base = "ttt_base_placeable" - if SERVER then - self:SetMaxHealth(100) - end +ENT.Model = "models/props_lab/reciever01b.mdl" - self:SetHealth(100) - - -- can pick this up if we own it - if SERVER then - self:SetUseType(SIMPLE_USE) - - local weptbl = util.WeaponForClass("weapon_ttt_decoy") +ENT.CanHavePrints = false - if weptbl and weptbl.Kind then - self.WeaponKind = weptbl.Kind - else - self.WeaponKind = WEAPON_EQUIP2 - end - end -end +ENT.CanUseKey = true +ENT.pickupWeaponClass = "weapon_ttt_decoy" --- --- @param Player activator -- @realm shared -function ENT:UseOverride(activator) - if not IsValid(activator) or not activator:HasTeam() or self:GetNWString("decoy_owner_team", "none") ~= activator:GetTeam() then return end +function ENT:Initialize() + self:SetModel(self.Model) - -- picks up weapon, switches if possible and needed, returns weapon if successful - local wep = activator:SafePickupWeaponClass("weapon_ttt_decoy", true) + BaseClass.Initialize(self) - if not IsValid(wep) then - LANG.Msg(activator, "decoy_no_room") + if SERVER then + self:SetMaxHealth(100) + end - return - end + self:SetHealth(100) - self:Remove() + -- can pick this up if we own it + if SERVER then + self:SetUseType(SIMPLE_USE) + end end -local zapsound = Sound("npc/assassin/ball_zap1.wav") - ---- --- @param DamageInfo dmginfo -- @realm shared -function ENT:OnTakeDamage(dmginfo) - self:TakePhysicsDamage(dmginfo) - self:SetHealth(self:Health() - dmginfo:GetDamage()) - - if self:Health() > 0 then return end - - self:Remove() - - local effect = EffectData() - effect:SetOrigin(self:GetPos()) - - util.Effect("cball_explode", effect) - sound.Play(zapsound, self:GetPos()) +function ENT:OnRemove() + if not IsValid(self:GetOriginator()) then + return + end - if IsValid(self:GetOwner()) then - LANG.Msg(self:GetOwner(), "decoy_broken") - end + self:GetOriginator().decoy = nil end ---- --- @realm shared -function ENT:OnRemove() - if not IsValid(self:GetOwner()) then return end - - self:GetOwner().decoy = nil +if SERVER then + --- + -- @realm server + function ENT:WasDestroyed() + local originator = self:GetOriginator() + + if not IsValid(originator) then + return + end + + LANG.Msg(originator, "decoy_broken", nil, MSG_MSTACK_WARN) + end + + --- + -- @param Player activator + -- @realm server + function ENT:PlayerCanPickupWeapon(activator) + return activator:HasTeam() + and self:GetNWString("decoy_owner_team", "none") == activator:GetTeam() + end end if CLIENT then - local TryT = LANG.TryTranslation - local ParT = LANG.GetParamTranslation - - -- handle looking at decoy - hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDDecoy", function(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - - if not IsValid(client) or not client:IsTerror() or not client:Alive() - or not IsValid(ent) or tData:GetEntityDistance() > 100 or ent:GetClass() ~= "ttt_decoy" then - return - end - - -- enable targetID rendering - tData:EnableText() - tData:EnableOutline() - tData:SetOutlineColor(client:GetRoleColor()) - - tData:SetTitle(TryT("decoy_name")) - tData:SetSubtitle(ParT("target_pickup", {usekey = Key("+use", "USE")})) - tData:SetKeyBinding("+use") - tData:AddDescriptionLine(TryT("decoy_short_desc")) - - if ent:GetNWString("decoy_owner_team", "none") == client:GetTeam() then return end - - tData:AddDescriptionLine( - TryT("decoy_pickup_wrong_team"), - COLOR_ORANGE - ) - end) + local TryT = LANG.TryTranslation + local ParT = LANG.GetParamTranslation + + -- handle looking at decoy + hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDDecoy", function(tData) + local client = LocalPlayer() + local ent = tData:GetEntity() + + if + not IsValid(client) + or not client:IsTerror() + or not client:Alive() + or not IsValid(ent) + or tData:GetEntityDistance() > 100 + or ent:GetClass() ~= "ttt_decoy" + then + return + end + + -- enable targetID rendering + tData:EnableText() + tData:EnableOutline() + tData:SetOutlineColor(client:GetRoleColor()) + + tData:SetTitle(TryT("decoy_name")) + tData:SetSubtitle(ParT("target_pickup", { usekey = Key("+use", "USE") })) + tData:SetKeyBinding("+use") + tData:AddDescriptionLine(TryT("decoy_short_desc")) + + if ent:GetNWString("decoy_owner_team", "none") == client:GetTeam() then + return + end + + tData:AddDescriptionLine(TryT("decoy_pickup_wrong_team"), COLOR_ORANGE) + end) end diff --git a/gamemodes/terrortown/entities/entities/ttt_firegrenade_proj.lua b/gamemodes/terrortown/entities/entities/ttt_firegrenade_proj.lua new file mode 100644 index 000000000..1d02cf2c5 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_firegrenade_proj.lua @@ -0,0 +1,77 @@ +--- +-- burning nade projectile +-- @class ENT +-- @section ttt_firegrenade_proj + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("ttt_basegrenade_proj") + +ENT.Type = "anim" +ENT.Base = "ttt_basegrenade_proj" +ENT.Model = Model("models/weapons/w_eq_flashbang_thrown.mdl") + +AccessorFunc(ENT, "radius", "Radius", FORCE_NUMBER) +AccessorFunc(ENT, "dmg", "Dmg", FORCE_NUMBER) + +--- +-- @ignore +function ENT:Initialize() + if not self:GetRadius() then + self:SetRadius(256) + end + if not self:GetDmg() then + self:SetDmg(25) + end + + return BaseClass.Initialize(self) +end + +--- +-- @ignore +function ENT:Explode(tr) + if SERVER then + self:SetNoDraw(true) + self:SetSolid(SOLID_NONE) + + -- pull out of the surface + if tr.Fraction ~= 1.0 then + self:SetPos(tr.HitPos + tr.HitNormal * 0.6) + end + + local pos = self:GetPos() + + if util.PointContents(pos) == CONTENTS_WATER then + self:Remove() + return + end + + local effect = EffectData() + effect:SetStart(pos) + effect:SetOrigin(pos) + effect:SetScale(self:GetRadius() * 0.3) + effect:SetRadius(self:GetRadius()) + effect:SetMagnitude(self.dmg) + + if tr.Fraction ~= 1.0 then + effect:SetNormal(tr.HitNormal) + end + + util.Effect("Explosion", effect, true, true) + + util.BlastDamage(self, self:GetThrower(), pos, self:GetRadius(), self:GetDmg()) + + gameEffects.StartFires(pos, tr, 10, 20, false, self:GetThrower(), 500, false, 128, 2) + + self:SetDetonateExact(0) + + self:Remove() + else + local spos = self:GetPos() + util.PaintDown(spos, "Scorch", self) + + self:SetDetonateExact(0) + end +end diff --git a/gamemodes/terrortown/entities/entities/ttt_flame.lua b/gamemodes/terrortown/entities/entities/ttt_flame.lua new file mode 100644 index 000000000..fc174c863 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_flame.lua @@ -0,0 +1,227 @@ +--- +-- fire handler that does owned damage +-- @class ENT +-- @section ttt_flame + +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Model = Model("models/weapons/w_eq_flashbang_thrown.mdl") + +--- +-- @accessor Player +-- @realm shared +AccessorFunc(ENT, "dmgparent", "DamageParent") + +ENT.firechild = nil + +ENT.real_scale = nil +ENT.fire_scale_base = 0.9 +ENT.fire_scale_variance = 0.2 + +ENT.next_hurt = 0 +ENT.hurt_interval = 1 + +ENT.low_detail_fps = 12 +ENT.low_detail_size = 10 + +ENT.hurt_radius = nil +ENT.hurt_base = 5 +ENT.hurt_variance = 1 +ENT.hurt_radius_scale_factor = 0.4 + +ENT.trail = nil +ENT.trail_color = Color(255, 56, 56, 255) +ENT.trail_width = 32 +ENT.trail_end_width = 0 +ENT.trail_lifetime = 0.25 +ENT.trail_res = 16 +ENT.trail_texture = "trails/physbeam" + +ENT.debug_viz = false + +--- +-- @realm shared +function ENT:SetupDataTables() + self:NetworkVar("Bool", 0, "Burning") + self:NetworkVar("Bool", 1, "Immobile") + self:NetworkVar("Bool", 2, "ExplodeOnDeath") + self:NetworkVar("Float", 0, "FlameSize") + self:NetworkVar("Float", 1, "LifeSpan") + self:NetworkVar("Float", 2, "DieTime") +end + +--- +-- @realm shared +function ENT:Initialize() + self:SetModel(self.Model) + self:DrawShadow(false) + self:SetNoDraw(false) + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self:SetHealth(99999) + + if SERVER then + self.trail = util.SpriteTrail( + self, + 0, + self.trail_color, + true, + self.trail_width, + self.trail_end_width, + self.trail_lifetime, + self.trail_lifetime, + self.trail_texture + ) + self:DeleteOnRemove(self.trail) + end + + self.real_scale = self:GetFlameSize() + * (self.fire_scale_base + math.Rand(-self.fire_scale_variance, self.fire_scale_variance)) + self.hurt_radius = self.real_scale * self.hurt_radius_scale_factor + + self.next_hurt = CurTime() + self.hurt_interval + math.Rand(0, 3) + + self:SetBurning(false) + + if self:GetDieTime() == 0 then + self:SetDieTime(CurTime() + self:GetLifeSpan()) + end +end + +--- +-- @realm shared +function ENT:Explode() + local pos = self:GetPos() + + local effect = EffectData() + effect:SetStart(pos) + effect:SetOrigin(pos) + effect:SetScale(256) + effect:SetRadius(256) + effect:SetMagnitude(50) + + util.Effect("Explosion", effect, true, true) + + local dmgowner = self:GetDamageParent() + if not IsValid(dmgowner) then + dmgowner = self + end + util.BlastDamage(self, dmgowner, pos, 300, 40) +end + +if SERVER then + --- + -- @realm server + function ENT:Think() + if self:GetDieTime() < CurTime() then + if self:GetExplodeOnDeath() then + local success, err = pcall(self.Explode, self) + + if not success then + ErrorNoHaltWithStack("ERROR CAUGHT: ttt_flame: " .. err .. "\n") + end + end + + self:Remove() + + return + end + + if self:WaterLevel() > 0 then + self:SetDieTime(0) + + return + end + + if IsValid(self.firechild) then + if self.next_hurt < CurTime() then + -- deal damage + local dmg = DamageInfo() + dmg:SetDamageType(DMG_BURN) + dmg:SetDamage(self.hurt_base + math.random(-self.hurt_variance, self.hurt_variance)) + if IsValid(self:GetDamageParent()) then + dmg:SetAttacker(self:GetDamageParent()) + else + dmg:SetAttacker(self) + end + dmg:SetInflictor(self.firechild) + + gameEffects.RadiusDamage(dmg, self:GetPos(), self.hurt_radius, self) + + self.next_hurt = CurTime() + self.hurt_interval + end + + return + else + if self:GetBurning() then + -- we already were lit once, but now our child is missing + -- we should just die, instead + self:SetDieTime(0) + else + -- wait until we're still before creating a fire + if self:GetVelocity() == Vector(0, 0, 0) then + self:StartFire() + end + end + end + end +end + +--- +-- Begins the burning effect and activates flames. +-- @realm shared +function ENT:StartFire() + util.PaintDown(self:GetPos(), "Scorch", self) + + self.firechild = gameEffects.SpawnFire( + self:GetPos(), + self.real_scale, + self:GetLifeSpan(), + self:GetDamageParent(), + self + ) + self:DeleteOnRemove(self.firechild) + + self:SetBurning(true) + + if self:GetImmobile() then + self:SetMoveType(MOVETYPE_NONE) + local physobj = self:GetPhysicsObject() + physobj:EnableMotion(false) + end +end + +if CLIENT then + local flamesprites = { + Material("particles/flamelet1"), + Material("particles/flamelet2"), + Material("particles/flamelet3"), + Material("particles/flamelet4"), + Material("particles/flamelet5"), + } + + --- + -- @realm client + function ENT:Draw() + if self:GetBurning() and self.debug_viz then + render.DrawWireframeSphere(self:GetPos(), self.hurt_radius, 16, 9, COLOR_RED) + elseif not self:GetBurning() then + local frame = math.floor( + ((SysTime() * self.low_detail_fps) + self:EntIndex()) % (#flamesprites - 1) + ) + 1 + cam.Start3D() + render.SetMaterial(flamesprites[frame]) + render.DrawSprite( + self:GetPos(), + self.low_detail_size, + self.low_detail_size, + color_white + ) + cam.End3D() + end + end +end diff --git a/gamemodes/terrortown/entities/entities/ttt_game_text.lua b/gamemodes/terrortown/entities/entities/ttt_game_text.lua index b691f524a..180453364 100644 --- a/gamemodes/terrortown/entities/entities/ttt_game_text.lua +++ b/gamemodes/terrortown/entities/entities/ttt_game_text.lua @@ -22,28 +22,28 @@ ENT.Receiver = RECEIVE_ACTIVATOR -- @param string|number value -- @realm shared function ENT:KeyValue(key, value) - if key == "message" then - self.Message = tostring(value) or "ERROR: bad value" - elseif key == "color" then - local mr, mg, mb = string.match(value, "(%d*) (%d*) (%d*)") + if key == "message" then + self.Message = tostring(value) or "ERROR: bad value" + elseif key == "color" then + local mr, mg, mb = string.match(value, "(%d*) (%d*) (%d*)") - self.Color = Color(tonumber(mr) or 255, tonumber(mg) or 255, tonumber(mb) or 255) - elseif key == "receive" then - self.teamReceiver = nil + self.Color = Color(tonumber(mr) or 255, tonumber(mg) or 255, tonumber(mb) or 255) + elseif key == "receive" then + self.teamReceiver = nil - if isstring(value) and _G[value] then - self.teamReceiver = _G[value] - value = RECEIVE_CUSTOMROLE - end + if isstring(value) and _G[value] then + self.teamReceiver = _G[value] + value = RECEIVE_CUSTOMROLE + end - self.Receiver = tonumber(value) + self.Receiver = tonumber(value) - if not self.Receiver or self.Receiver < 0 or self.Receiver > 5 then - ErrorNoHalt("ERROR: ttt_game_text has invalid receiver value\n") + if not self.Receiver or self.Receiver < 0 or self.Receiver > 5 then + ErrorNoHalt("ERROR: ttt_game_text has invalid inputReceiver value\n") - self.Receiver = RECEIVE_ACTIVATOR - end - end + self.Receiver = RECEIVE_ACTIVATOR + end + end end --- @@ -52,30 +52,38 @@ end -- @return[default=true] boolean -- @realm shared function ENT:AcceptInput(name, activator) - if name == "Display" then - local recv = activator - - local r = self.Receiver - if r == RECEIVE_ALL then - recv = nil - elseif r == RECEIVE_DETECTIVE then - recv = GetRoleChatFilter(ROLE_DETECTIVE) - elseif r == RECEIVE_TRAITOR then - recv = GetTeamChatFilter(TEAM_TRAITOR) - elseif r == RECEIVE_INNOCENT then - recv = GetTeamChatFilter(TEAM_INNOCENT) - elseif r == RECEIVE_ACTIVATOR then - if not IsValid(activator) or not activator:IsPlayer() then - ErrorNoHalt("ttt_game_text tried to show message to invalid !activator\n") - - return true - end - elseif r == RECEIVE_CUSTOMROLE and self.teamReceiver then - recv = GetTeamChatFilter(self.teamReceiver) - end - - CustomMsg(recv, self.Message, self.Color) - - return true - end + if name ~= "Display" then + return false + end + + local inputReceiver = self.Receiver + local messageReceiver = activator + + if inputReceiver == RECEIVE_ALL then + messageReceiver = nil + elseif inputReceiver == RECEIVE_DETECTIVE then + messageReceiver = GetRoleChatFilter(ROLE_DETECTIVE) + elseif inputReceiver == RECEIVE_TRAITOR then + messageReceiver = GetTeamChatFilter(TEAM_TRAITOR) + elseif inputReceiver == RECEIVE_INNOCENT then + -- TTT originally defined this as "All except traitors" even though it is labeled as "RECEIVE_INNOCENT", + -- but the implementation literally only checked that a player was not a traitor, therefore the intent is + -- preserved here since maps aren't likely to be updated + messageReceiver = GetPlayerFilter(function(p) + local plyRoleData = ply:GetSubRoleData() + return p:GetTeam() ~= TEAM_TRAITOR and not plyRoleData.disabledTeamChatRecv + end) + elseif inputReceiver == RECEIVE_ACTIVATOR then + if not IsValid(activator) or not activator:IsPlayer() then + ErrorNoHalt("ttt_game_text tried to show message to invalid !activator\n") + + return true + end + elseif inputReceiver == RECEIVE_CUSTOMROLE and self.teamReceiver then + messageReceiver = GetTeamChatFilter(self.teamReceiver) + end + + CustomMsg(messageReceiver, self.Message, self.Color) + + return true end diff --git a/gamemodes/terrortown/entities/entities/ttt_hat_deerstalker.lua b/gamemodes/terrortown/entities/entities/ttt_hat_deerstalker.lua new file mode 100644 index 000000000..11eb043a4 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_hat_deerstalker.lua @@ -0,0 +1,159 @@ +--- +-- @class ENT +-- @section ttt_hat_deerstalker + +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "hat_deerstalker_name" +ENT.Model = Model("models/ttt/deerstalker.mdl") +ENT.CanHavePrints = false +ENT.CanUseKey = true + +--- +-- @realm shared +function ENT:SetupDataTables() + self:NetworkVar("Bool", 0, "BeingWorn") +end + +--- +-- @realm shared +function ENT:Initialize() + self:SetModel(self.Model) + + self:DrawShadow(false) + + -- don't physicsinit the ent here, because physicsing and then setting + -- movetype none is 1) a waste of memory, 2) broken + + if SERVER then + if IsValid(self:GetParent()) then + self:EquipTo(self:GetParent()) + else + self:Drop() + end + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self:SetUseType(SIMPLE_USE) + self:AddEffects(bit.bor(EF_BONEMERGE, EF_BONEMERGE_FASTCULL, EF_PARENT_ANIMATES)) + end +end + +if SERVER then + --- + -- @realm server + -- stylua: ignore + local ttt_hats_reclaim = CreateConVar("ttt_detective_hats_reclaim", "1") + + --- + -- @realm server + -- stylua: ignore + local ttt_hats_innocent = CreateConVar("ttt_detective_hats_reclaim_any", "0") + + --- + -- @realm server + function ENT:OnRemove() + -- only focus on cleaning up external links, we're not long for this world + if self.Wearer and self.Wearer.hat == self then + self.Wearer.hat = nil + end + end + + --- + -- @param Player ply The player to put the hat on. + -- @realm server + function ENT:EquipTo(ply) + self.Wearer = ply + ply.hat = self + + self:SetBeingWorn(true) + + self:SetMoveType(MOVETYPE_NONE) + self:SetSolid(SOLID_NONE) + + local pos, ang = playermodels.GetHatPosition(ply) + self:SetPos(pos) + self:SetAngles(ang) + self:SetParent(ply) + end + + --- + -- @param Vector dir The drop direction. + -- @realm server + function ENT:Drop(dir) + local ply = self:GetParent() + + ply.hat = nil + self:SetParent(nil) + + self:SetBeingWorn(false) + + -- only now physics this entity + self:PhysicsInit(SOLID_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + + -- if we're not already on the player's head, + if IsValid(ply) then + local pos, ang = playermodels.GetHatPosition(ply) + self:SetPos(pos) + self:SetAngles(ang) + end + + -- physics push + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:SetMass(10) + + if IsValid(ply) then + phys:SetVelocityInstantaneous(ply:GetVelocity()) + end + + if not dir then + phys:ApplyForceCenter(Vector(0, 0, 1200)) + else + phys:ApplyForceCenter(Vector(0, 0, 700) + dir * 500) + end + + phys:AddAngleVelocity(VectorRand() * 200) + + phys:Wake() + end + end + + local function CanEquipHat(ply) + local rd = ply:GetSubRoleData() + return not IsValid(ply.hat) + and (ttt_hats_innocent:GetBool() or (rd.isPolicingRole and rd.isPublicRole)) + end + + --- + -- @param Player ply + -- @realm server + function ENT:UseOverride(ply) + if not ttt_hats_reclaim:GetBool() then + return + end + + if IsValid(ply) and not self:GetBeingWorn() then + if GetRoundState() ~= ROUND_ACTIVE then + SafeRemoveEntity(self) + return + elseif not CanEquipHat(ply) then + return + end + + sound.Play("weapon.ImpactSoft", self:GetPos(), 75, 100, 1) + + self:EquipTo(ply) + + LANG.Msg(ply, "hat_retrieve") + end + end + + local function TestHat(ply, cmd, args) + playermodels.ApplyPlayerHat(ply, nil, args[1] or "ttt_hat_deerstalker") + end + concommand.Add("ttt_debug_testhat", TestHat, nil, nil, FCVAR_CHEAT) +end diff --git a/gamemodes/terrortown/entities/entities/ttt_health_station.lua b/gamemodes/terrortown/entities/entities/ttt_health_station.lua index 5a8b4af02..8e488070b 100644 --- a/gamemodes/terrortown/entities/entities/ttt_health_station.lua +++ b/gamemodes/terrortown/entities/entities/ttt_health_station.lua @@ -4,15 +4,18 @@ -- @section ttt_health_station if SERVER then - AddCSLuaFile() -else -- CLIENT - -- this entity can be DNA-sampled so we need some display info - ENT.Icon = "vgui/ttt/icon_health" - ENT.PrintName = "hstation_name" + AddCSLuaFile() end -ENT.Type = "anim" -ENT.Model = Model("models/props/cs_office/microwave.mdl") +DEFINE_BASECLASS("ttt_base_placeable") + +if CLIENT then + ENT.Icon = "vgui/ttt/icon_health" + ENT.PrintName = "hstation_name" +end + +ENT.Base = "ttt_base_placeable" +ENT.Model = "models/props/cs_office/microwave.mdl" --ENT.CanUseKey = true ENT.CanHavePrints = true @@ -25,61 +28,49 @@ ENT.NextHeal = 0 ENT.HealRate = 1 ENT.HealFreq = 0.2 ---- --- @accessor number --- @realm shared -AccessorFuncDT(ENT, "StoredHealth", "StoredHealth") - ---- --- @accessor Player --- @realm shared -AccessorFunc(ENT, "Placer", "Placer") - --- -- @realm shared function ENT:SetupDataTables() - self:DTVar("Int", 0, "StoredHealth") + BaseClass.SetupDataTables(self) + + self:NetworkVar("Int", 0, "StoredHealth") end --- -- @realm shared function ENT:Initialize() - self:SetModel(self.Model) + self:SetModel(self.Model) - self:PhysicsInit(SOLID_VPHYSICS) - self:SetMoveType(MOVETYPE_VPHYSICS) - self:SetSolid(SOLID_BBOX) + BaseClass.Initialize(self) - local b = 32 + local b = 32 - self:SetCollisionBounds(Vector(-b, -b, -b), Vector(b,b,b)) - self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetCollisionBounds(Vector(-b, -b, -b), Vector(b, b, b)) - if SERVER then - self:SetMaxHealth(200) + if SERVER then + self:SetMaxHealth(200) - local phys = self:GetPhysicsObject() - if IsValid(phys) then - phys:SetMass(200) - end + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:SetMass(200) + end - self:SetUseType(CONTINUOUS_USE) - end + self:SetUseType(CONTINUOUS_USE) + end - self:SetHealth(200) - self:SetColor(Color(180, 180, 250, 255)) - self:SetStoredHealth(200) - self:SetPlacer(nil) + self:SetHealth(200) + self:SetColor(Color(180, 180, 250, 255)) + self:SetStoredHealth(200) - self.NextHeal = 0 - self.fingerprints = {} + self.NextHeal = 0 + self.fingerprints = {} end --- -- @param number amount -- @realm shared function ENT:AddToStorage(amount) - self:SetStoredHealth(math.min(self.MaxStored, self:GetStoredHealth() + amount)) + self:SetStoredHealth(math.min(self.MaxStored, self:GetStoredHealth() + amount)) end --- @@ -87,12 +78,12 @@ end -- @return number -- @realm shared function ENT:TakeFromStorage(amount) - -- if we only have 5 healthpts in store, that is the amount we heal - amount = math.min(amount, self:GetStoredHealth()) + -- if we only have 5 healthpts in store, that is the amount we heal + amount = math.min(amount, self:GetStoredHealth()) - self:SetStoredHealth(math.max(0, self:GetStoredHealth() - amount)) + self:SetStoredHealth(math.max(0, self:GetStoredHealth() - amount)) - return amount + return amount end local soundHealing = Sound("items/medshot4.wav") @@ -108,9 +99,7 @@ local timeLastSound = 0 -- @return boolean Return false to cancel the heal tick -- @hook -- @realm server -function GAMEMODE:TTTPlayerUsedHealthStation(ply, ent, healed) - -end +function GAMEMODE:TTTPlayerUsedHealthStation(ply, ent, healed) end --- -- @param Player ply @@ -118,145 +107,138 @@ end -- @return boolean -- @realm shared function ENT:GiveHealth(ply, healthMax) - if self:GetStoredHealth() > 0 then - healthMax = healthMax or self.MaxHeal - - local dmg = ply:GetMaxHealth() - ply:Health() - if dmg > 0 then - -- constant clamping, no risks - local healed = self:TakeFromStorage(math.min(healthMax, dmg)) - local new = math.min(ply:GetMaxHealth(), ply:Health() + healed) - - --- - -- @realm shared - if hook.Run("TTTPlayerUsedHealthStation", ply, self, healed) == false then - return false - end - - ply:SetHealth(new) - - if timeLastSound + 2 < CurTime() then - self:EmitSound(soundHealing) - - timeLastSound = CurTime() - end - - if not table.HasValue(self.fingerprints, ply) then - self.fingerprints[#self.fingerprints + 1] = ply - end - - return true - else - self:EmitSound(soundFail) - end - else - self:EmitSound(soundFail) - end - - return false + if self:GetStoredHealth() > 0 then + healthMax = healthMax or self.MaxHeal + + local dmg = ply:GetMaxHealth() - ply:Health() + if dmg > 0 then + -- constant clamping, no risks + local healed = self:TakeFromStorage(math.min(healthMax, dmg)) + local new = math.min(ply:GetMaxHealth(), ply:Health() + healed) + + --- + -- @realm shared + -- stylua: ignore + if hook.Run("TTTPlayerUsedHealthStation", ply, self, healed) == false then + return false + end + + ply:SetHealth(new) + + if timeLastSound + 2 < CurTime() then + self:EmitSound(soundHealing) + + timeLastSound = CurTime() + end + + if not table.HasValue(self.fingerprints, ply) then + self.fingerprints[#self.fingerprints + 1] = ply + end + + return true + else + self:EmitSound(soundFail) + end + else + self:EmitSound(soundFail) + end + + return false end --- -- @param Player ply -- @realm shared function ENT:Use(ply) - if not IsValid(ply) or not ply:IsPlayer() or not ply:IsActive() then return end + if not IsValid(ply) or not ply:IsPlayer() or not ply:IsActive() then + return + end - local t = CurTime() - if t < self.NextHeal then return end + local t = CurTime() + if t < self.NextHeal then + return + end - local healed = self:GiveHealth(ply, self.HealRate) + local healed = self:GiveHealth(ply, self.HealRate) - self.NextHeal = t + (self.HealFreq * (healed and 1 or 2)) + self.NextHeal = t + (self.HealFreq * (healed and 1 or 2)) end if SERVER then - -- recharge - local nextcharge = 0 - - --- - -- @realm server - function ENT:Think() - if nextcharge > CurTime() then return end - - self:AddToStorage(self.RechargeRate) - - nextcharge = CurTime() + self.RechargeFreq - end - - --- - -- @realm server - local ttt_damage_own_healthstation = CreateConVar("ttt_damage_own_healthstation", "0", FCVAR_NONE, "0 as detective cannot damage their own health station") - - --- - -- traditional equipment destruction effects - -- @param DamageInfo dmginfo - -- @realm server - function ENT:OnTakeDamage(dmginfo) - if dmginfo:GetAttacker() == self:GetPlacer() and not ttt_damage_own_healthstation:GetBool() then return end - - self:TakePhysicsDamage(dmginfo) - self:SetHealth(self:Health() - dmginfo:GetDamage()) - - local att = dmginfo:GetAttacker() - local placer = self:GetPlacer() - - if IsPlayer(att) then - DamageLog(Format("DMG: \t %s [%s] damaged health station [%s] for %d dmg", att:Nick(), att:GetRoleString(), IsPlayer(placer) and placer:Nick() or "", dmginfo:GetDamage())) - end - - if self:Health() > 0 then return end - - self:Remove() - - util.EquipmentDestroyed(self:GetPos()) - - if IsValid(self:GetPlacer()) then - LANG.Msg(self:GetPlacer(), "hstation_broken") - end - end -else -- CLIENT - local TryT = LANG.TryTranslation - local ParT = LANG.GetParamTranslation - - local key_params = { - usekey = Key("+use", "USE"), - walkkey = Key("+walk", "WALK") - } - - -- handle looking at healthstation - hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDHealthStation", function(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - - if not IsValid(client) or not client:IsTerror() or not client:Alive() - or not IsValid(ent) or tData:GetEntityDistance() > 100 or ent:GetClass() ~= "ttt_health_station" then - return - end - - -- enable targetID rendering - tData:EnableText() - tData:EnableOutline() - tData:SetOutlineColor(client:GetRoleColor()) - - tData:SetTitle(TryT(ent.PrintName)) - tData:SetSubtitle(ParT("hstation_subtitle", key_params)) - tData:SetKeyBinding("+use") - - local hstation_charge = ent:GetStoredHealth() or 0 - - tData:AddDescriptionLine(TryT("hstation_short_desc")) - - tData:AddDescriptionLine( - (hstation_charge > 0) and ParT("hstation_charge", {charge = hstation_charge}) or TryT("hstation_empty"), - (hstation_charge > 0) and roles.DETECTIVE.ltcolor or COLOR_ORANGE - ) - - if client:Health() < client:GetMaxHealth() then return end - - tData:AddDescriptionLine( - TryT("hstation_maxhealth"), - COLOR_ORANGE - ) - end) + -- recharge + local nextcharge = 0 + + --- + -- @realm server + function ENT:Think() + if nextcharge > CurTime() then + return + end + + self:AddToStorage(self.RechargeRate) + + nextcharge = CurTime() + self.RechargeFreq + end + + --- + -- @realm server + function ENT:WasDestroyed() + local originator = self:GetOriginator() + + if not IsValid(originator) then + return + end + + LANG.Msg(originator, "hstation_broken", nil, MSG_MSTACK_WARN) + end +else + local TryT = LANG.TryTranslation + local ParT = LANG.GetParamTranslation + + local key_params = { + usekey = Key("+use", "USE"), + walkkey = Key("+walk", "WALK"), + } + + -- handle looking at healthstation + hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDHealthStation", function(tData) + local client = LocalPlayer() + local ent = tData:GetEntity() + + if + not IsValid(client) + or not client:IsTerror() + or not client:Alive() + or not IsValid(ent) + or tData:GetEntityDistance() > 100 + or ent:GetClass() ~= "ttt_health_station" + then + return + end + + -- enable targetID rendering + tData:EnableText() + tData:EnableOutline() + tData:SetOutlineColor(client:GetRoleColor()) + + tData:SetTitle(TryT(ent.PrintName)) + tData:SetSubtitle(ParT("hstation_subtitle", key_params)) + tData:SetKeyBinding("+use") + + local hstation_charge = ent:GetStoredHealth() or 0 + + tData:AddDescriptionLine(TryT("hstation_short_desc")) + + tData:AddDescriptionLine( + (hstation_charge > 0) and ParT("hstation_charge", { charge = hstation_charge }) + or TryT("hstation_empty"), + (hstation_charge > 0) and roles.DETECTIVE.ltcolor or COLOR_ORANGE + ) + + if client:Health() < client:GetMaxHealth() then + return + end + + tData:AddDescriptionLine(TryT("hstation_maxhealth"), COLOR_ORANGE) + end) end diff --git a/gamemodes/terrortown/entities/entities/ttt_knife_proj.lua b/gamemodes/terrortown/entities/entities/ttt_knife_proj.lua new file mode 100644 index 000000000..424abd795 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_knife_proj.lua @@ -0,0 +1,244 @@ +--- +-- @class ENT +-- @desc Thrown knife entity +-- @section KnifeProjectile + +if SERVER then + AddCSLuaFile() +end + +if CLIENT then + ENT.PrintName = "knife_thrown" + ENT.Icon = "vgui/ttt/icon_knife" +end + +ENT.Type = "anim" +ENT.Model = Model("models/weapons/w_knife_t.mdl") + +-- When true, score code considers us a weapon +ENT.Projectile = true + +ENT.Stuck = false +ENT.Weaponised = false +ENT.CanHavePrints = false +ENT.IsSilent = true +ENT.CanPickup = false + +ENT.WeaponID = AMMO_KNIFE + +ENT.Damage = 50 + +--- +-- @realm shared +function ENT:Initialize() + self:SetModel(self.Model) + self:PhysicsInit(SOLID_VPHYSICS) + + if SERVER then + self:SetGravity(0.4) + self:SetFriction(1.0) + self:SetElasticity(0.45) + + self.StartPos = self:GetPos() + + self:NextThink(CurTime()) + end + + self.Weaponised = false + self.Stuck = false +end + +--- +-- @param table other +-- @param table tr +-- @realm shared +function ENT:HitPlayer(other, tr) + local range_dmg = math.max(self.Damage, self.StartPos:Distance(self:GetPos()) / 3) + + if other:Health() < range_dmg + 10 then + self:KillPlayer(other, tr) + elseif SERVER then + local dmg = DamageInfo() + dmg:SetDamage(range_dmg) + dmg:SetAttacker(self:GetOwner()) + dmg:SetInflictor(self) + dmg:SetDamageForce(self:EyeAngles():Forward()) + dmg:SetDamagePosition(self:GetPos()) + dmg:SetDamageType(DMG_SLASH) + + local ang = Angle(-28, 0, 0) + tr.Normal:Angle() + ang:RotateAroundAxis(ang:Right(), -90) + other:DispatchTraceAttack(dmg, self:GetPos() + ang:Forward() * 3, other:GetPos()) + + if not self.Weaponised then + self:BecomeWeaponDelayed() + end + end + + -- As a thrown knife, after we hit a target we can never hit one again. + -- If we are picked up and re-thrown, a new knife_proj entity is created. + -- To make sure we can never deal damage twice, make HitPlayer do nothing. + self.HitPlayer = util.noop +end + +--- +-- @param table other +-- @param table tr +-- @realm shared +function ENT:KillPlayer(other, tr) + local dmg = DamageInfo() + dmg:SetDamage(2000) + dmg:SetAttacker(self:GetOwner()) + dmg:SetInflictor(self) + dmg:SetDamageForce(self:EyeAngles():Forward()) + dmg:SetDamagePosition(self:GetPos()) + dmg:SetDamageType(DMG_SLASH) + + -- this bone is why we need the trace + local bone = tr.PhysicsBone + local pos = tr.HitPos + local norm = tr.Normal + local ang = Angle(-28, 0, 0) + norm:Angle() + + ang:RotateAroundAxis(ang:Right(), -90) + pos = pos - (ang:Forward() * 8) + + local knife = self + local prints = self.fingerprints + + other.effect_fn = function(rag) + if not IsValid(knife) or not IsValid(rag) then + return + end + + knife:SetPos(pos) + knife:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + knife:SetAngles(ang) + + knife:SetMoveCollide(MOVECOLLIDE_DEFAULT) + knife:SetMoveType(MOVETYPE_VPHYSICS) + + knife.fingerprints = prints + knife:SetNWBool("HasPrints", true) + + -- knife needs to be trace-able to get prints + local phys = knife:GetPhysicsObject() + if IsValid(phys) then + phys:EnableCollisions(false) + end + + constraint.Weld(rag, knife, bone, 0, 0, true) + + rag:CallOnRemove("ttt_knife_cleanup", function() + SafeRemoveEntity(knife) + end) + end + + other:DispatchTraceAttack(dmg, self:GetPos() + ang:Forward() * 3, other:GetPos()) + + self.Stuck = true +end + +if SERVER then + --- + -- @realm server + function ENT:Think() + if self.Stuck then + return + end + + local vel = self:GetVelocity() + + if vel == vector_origin then + return + end + local tr = util.TraceLine({ + start = self:GetPos(), + endpos = self:GetPos() + vel:GetNormal() * 20, + filter = { self, self:GetOwner() }, + mask = MASK_SHOT_HULL, + }) + + if tr.Hit and tr.HitNonWorld and IsValid(tr.Entity) then + local other = tr.Entity + if other:IsPlayer() then + self:HitPlayer(other, tr) + end + end + + self:NextThink(CurTime()) + + return true + end + + --- + -- When this entity touches anything that is not a player, it should turn into a + -- weapon ent again. If it touches a player it sticks in it. + -- @realm server + function ENT:BecomeWeapon() + self.Weaponised = true + + local wep = ents.Create("weapon_ttt_knife") + wep:SetPos(self:GetPos()) + wep:SetAngles(self:GetAngles()) + wep.IsDropped = true + + wep.fingerprints = table.Copy(self.fingerprints or {}) + + self:Remove() + + wep:Spawn() + + return wep + end + + --- + -- @realm server + function ENT:BecomeWeaponDelayed() + -- delay the weapon-replacement a tick because Source gets very angry + -- if you do fancy stuff in a physics callback + local knife = self + timer.Simple(0, function() + if not IsValid(knife) or knife.Weaponised then + return + end + + knife:BecomeWeapon() + end) + end + + --- + -- @param table data + -- @param table phys + -- @realm server + function ENT:PhysicsCollide(data, phys) + if self.Stuck then + return false + end + + local other = data.HitEntity + + if not IsValid(other) and not other:IsWorld() then + return + end + + if other:IsPlayer() then + local tr = util.TraceLine({ + start = self:GetPos(), + endpos = other:LocalToWorld(other:OBBCenter()), + filter = { self, self:GetOwner() }, + mask = MASK_SHOT_HULL, + }) + + if tr.Hit and tr.Entity == other then + self:HitPlayer(other, tr) + end + + return true + end + + if not self.Weaponised then + self:BecomeWeaponDelayed() + end + end +end diff --git a/gamemodes/terrortown/entities/entities/ttt_logic_role.lua b/gamemodes/terrortown/entities/entities/ttt_logic_role.lua index d55ea3d24..50222201a 100644 --- a/gamemodes/terrortown/entities/entities/ttt_logic_role.lua +++ b/gamemodes/terrortown/entities/entities/ttt_logic_role.lua @@ -9,7 +9,9 @@ ROLE_NONE = ROLE_NONE or 3 ENT.checkingRole = ROLE_NONE -if CLIENT then return end +if CLIENT then + return +end local IsValid = IsValid @@ -18,22 +20,22 @@ local IsValid = IsValid -- @param string|number value -- @realm server function ENT:KeyValue(key, value) - if key == "OnPass" or key == "OnFail" then - -- this is our output, so handle it as such - self:StoreOutput(key, value) - elseif key == "Role" then - if isstring(value) then - value = _G[value] or value - end + if key == "OnPass" or key == "OnFail" then + -- this is our output, so handle it as such + self:StoreOutput(key, value) + elseif key == "Role" then + if isstring(value) then + value = _G[value] or value + end - self.checkingRole = tonumber(value) + self.checkingRole = tonumber(value) - if not self.checkingRole then - ErrorNoHalt("ttt_logic_role: bad value for Role key, not a number\n") + if not self.checkingRole then + ErrorNoHalt("ttt_logic_role: bad value for Role key, not a number\n") - self.checkingRole = ROLE_NONE - end - end + self.checkingRole = ROLE_NONE + end + end end --- @@ -45,29 +47,33 @@ end -- @return[default=true] boolean Return true if the default action should be supressed -- @realm server function ENT:AcceptInput(name, activator, caller, data) - if name == "TestActivator" then - if not IsValid(activator) or not activator:IsPlayer() then return end + if name == "TestActivator" then + if not IsValid(activator) or not activator:IsPlayer() then + return + end - --- - -- @realm server - local role, team = hook.Run("TTT2ModifyLogicRoleCheck", activator, self, activator, caller, data) - local activatorRole = roles.GetByIndex(role, roles.INNOCENT):GetBaseRole() - local activatorTeam = (GetRoundState() == ROUND_PREP) and TEAM_INNOCENT or team + --- + -- @realm server + -- stylua: ignore + local role, team = hook.Run("TTT2ModifyLogicRoleCheck", activator, self, activator, caller, data) + local activatorRole = roles.GetByIndex(role, roles.INNOCENT):GetBaseRole() + local activatorTeam = (GetRoundState() == ROUND_PREP) and TEAM_INNOCENT or team - if self.checkingRole == ROLE_TRAITOR and util.IsEvilTeam(activatorTeam) - or self.checkingRole == ROLE_INNOCENT and not util.IsEvilTeam(activatorTeam) - or self.checkingRole == activatorRole and not (self.checkingRole == ROLE_TRAITOR or self.checkingRole == ROLE_INNOCENT) - or self.checkingRole == ROLE_NONE - then - Dev(2, activator, "passed logic_role test of", self:GetName()) + if + self.checkingRole == ROLE_TRAITOR and util.IsEvilTeam(activatorTeam) + or self.checkingRole == ROLE_INNOCENT and not util.IsEvilTeam(activatorTeam) + or self.checkingRole == activatorRole and not (self.checkingRole == ROLE_TRAITOR or self.checkingRole == ROLE_INNOCENT) + or self.checkingRole == ROLE_NONE + then + Dev(2, activator, "passed logic_role test of", self:GetName()) - self:TriggerOutput("OnPass", activator) - else - Dev(2, activator, "failed logic_role test of", self:GetName()) + self:TriggerOutput("OnPass", activator) + else + Dev(2, activator, "failed logic_role test of", self:GetName()) - self:TriggerOutput("OnFail", activator) - end + self:TriggerOutput("OnFail", activator) + end - return true - end + return true + end end diff --git a/gamemodes/terrortown/entities/entities/ttt_map_settings.lua b/gamemodes/terrortown/entities/entities/ttt_map_settings.lua new file mode 100644 index 000000000..655abb0e6 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_map_settings.lua @@ -0,0 +1,105 @@ +--- +-- @class ENT +-- @desc Map settings entity +-- @section MapSettings + +ENT.Type = "point" +ENT.Base = "base_point" + +--- +-- @realm server +function ENT:Initialize() + timer.Simple(0, function() + self:TriggerOutput("MapSettingsSpawned", self) + end) +end + +--- +-- Sets Hammer key values on an entity. +-- @param string key The internal key name +-- @param string value The value to set +-- @realm server +function ENT:KeyValue(key, value) + if key == "cbar_doors" then + Dev(2, "ttt_map_settings: crowbar door unlocking = " .. value) + + local opens = (value == "1") + + GAMEMODE.crowbar_unlocks[OPEN_DOOR] = opens + GAMEMODE.crowbar_unlocks[OPEN_ROT] = opens + elseif key == "cbar_buttons" then + Dev(2, "ttt_map_settings: crowbar button unlocking = " .. value) + + GAMEMODE.crowbar_unlocks[OPEN_BUT] = (value == "1") + elseif key == "cbar_other" then + Dev(2, "ttt_map_settings: crowbar movelinear unlocking = " .. value) + + GAMEMODE.crowbar_unlocks[OPEN_NOTOGGLE] = (value == "1") + elseif key == "plymodel" and value ~= "" then -- can ignore if empty + if util.IsValidModel(value) then + Dev(2, "ttt_map_settings: set player model to be " .. value) + + util.PrecacheModel(value) + + GAMEMODE.force_plymodel = value + else + Dev(2, "ttt_map_settings: FAILED to set player model due to invalid path: " .. value) + end + elseif key == "propspec_named" or key == "propspec_allow_named" then + Dev(2, "ttt_map_settings: propspec possessing named props = " .. value) + + GAMEMODE.propspec_allow_named = (value == "1") + elseif + key == "MapSettingsSpawned" + or key == "RoundEnd" + or key == "RoundPreparation" + or key == "RoundStart" + then + self:StoreOutput(key, value) + end +end + +--- +-- Called when another entity fires an event to this entity. +-- @param string name The name of the input that was triggered +-- @param Entity activator The initial cause for the input getting triggered; e.g. the player who pushed a button +-- @param Entity caller The entity that directly triggered the input; e.g. the button that was pushed +-- @param string data The data passed +-- @realm server +function ENT:AcceptInput(name, activator, caller, data) + if name == "SetPlayerModels" then + local mdlname = tostring(data) + + if not mdlname then + ErrorNoHalt("ttt_map_settings: Invalid parameter to SetPlayerModels input!\n") + + return false + elseif not util.IsValidModel(mdlname) then + ErrorNoHalt("ttt_map_settings: Invalid model given: " .. mdlname .. "\n") + + return false + end + + Dev(2, "ttt_map_settings: input set player model to be " .. mdlname) + + GAMEMODE.force_plymodel = Model(mdlname) + + return true + end +end + +--- +-- Fire an output when the round changes. +-- @param number roundState +-- @param any data +-- @realm server +function ENT:RoundStateTrigger(roundState, data) + if roundState == ROUND_PREP then + self:TriggerOutput("RoundPreparation", self) + elseif roundState == ROUND_ACTIVE then + self:TriggerOutput("RoundStart", self) + elseif roundState == ROUND_POST then + -- RoundEnd has the type of win condition as param + self:TriggerOutput("RoundEnd", self, tostring(data)) + end +end diff --git a/gamemodes/terrortown/entities/entities/ttt_physhammer.lua b/gamemodes/terrortown/entities/entities/ttt_physhammer.lua new file mode 100644 index 000000000..0cd60fa96 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_physhammer.lua @@ -0,0 +1,197 @@ +--- +-- @class ENT +-- @desc Phys Hammer in world entity +-- @section PhysHammer + +if SERVER then + AddCSLuaFile() +end + +ENT.Type = "anim" +ENT.Model = Model("models/Items/combine_rifle_ammo01.mdl") + +ENT.Stuck = false +ENT.Weaponised = false + +ENT.PunchMax = 6 +ENT.PunchRemaining = 6 + +--- +-- @realm shared +function ENT:Initialize() + self:SetModel(self.Model) + + self:SetSolid(SOLID_NONE) + + if SERVER then + self:SetGravity(0.4) + self:SetFriction(1.0) + self:SetElasticity(0.45) + + self:NextThink(CurTime() + 1) + end + + self:SetColor(Color(55, 50, 250, 255)) + + self.Stuck = false + self.PunchMax = 6 + self.PunchRemaining = self.PunchMax +end + +--- +-- @param Entity ent +-- @realm shared +function ENT:StickTo(ent) + if not IsValid(ent) or ent:IsPlayer() or ent:GetMoveType() ~= MOVETYPE_VPHYSICS then + return false + end + + local phys = ent:GetPhysicsObject() + + if not IsValid(phys) or not phys:IsMoveable() then + return false + end + + self:SetParent(ent) + + ent:SetPhysicsAttacker(self:GetOwner()) + ent:SetNWBool("punched", true) + + self.PunchEntity = ent + self:StartEffects() + self.Stuck = true + + return true +end + +--- +-- @hook +-- @realm shared +function ENT:OnRemove() + if IsValid(self.BallSprite) then + self.BallSprite:Remove() + end + + if IsValid(self.PunchEntity) then + self.PunchEntity:SetPhysicsAttacker(self.PunchEntity) + self.PunchEntity:SetNWBool("punched", false) + end +end + +--- +-- @realm shared +function ENT:StartEffects() + local sprite = ents.Create("env_sprite") + if IsValid(sprite) then + -- sometimes attachments don't work (Lua-side) on dedicated servers, + -- so have to fudge it + local ang = self:GetAngles() + local pos = self:GetPos() + self:GetAngles():Up() * 6 + sprite:SetPos(pos) + sprite:SetAngles(ang) + sprite:SetParent(self) + + sprite:SetKeyValue("model", "sprites/combineball_glow_blue_1.vmt") + sprite:SetKeyValue("spawnflags", "1") + sprite:SetKeyValue("scale", "0.25") + sprite:SetKeyValue("rendermode", "5") + sprite:SetKeyValue("renderfx", "7") + + sprite:Spawn() + sprite:Activate() + + self.BallSprite = sprite + end + + local effect = EffectData() + effect:SetStart(self:GetPos()) + effect:SetOrigin(self:GetPos()) + effect:SetNormal(self:GetAngles():Up()) + util.Effect("ManhackSparks", effect, true, true) + + if SERVER then + local ball = self:LookupAttachment("attach_ball") + + util.SpriteTrail( + self, + ball, + Color(250, 250, 250), + false, + 30, + 0, + 1, + 0.07, + "trails/physbeam.vmt" + ) + end +end + +if SERVER then + local diesound = Sound("weapons/physcannon/energy_disintegrate4.wav") + local punchsound = Sound("weapons/ar2/ar2_altfire.wav") + + --- + -- @realm server + function ENT:Think() + if not self.Stuck then + return + end + + if self.PunchRemaining <= 0 then + local pos = self:GetPos() + + util.BlastDamage(self, self:GetOwner(), pos, 300, 125) + sound.Play(diesound, pos, 100, 100) + + self:Remove() + + local effect = EffectData() + effect:SetStart(pos) + effect:SetOrigin(pos) + + util.Effect("Explosion", effect, true, true) + else + self.PunchRemaining = self.PunchRemaining - 1 + + if IsValid(self.PunchEntity) and IsValid(self.PunchEntity:GetPhysicsObject()) then + local punchphys = self.PunchEntity:GetPhysicsObject() + + -- Make physexplosion + local phexp = ents.Create("env_physexplosion") + if IsValid(phexp) then + phexp:SetPos(self:GetPos()) + phexp:SetKeyValue("magnitude", 100) + phexp:SetKeyValue("radius", 128) + phexp:SetKeyValue("spawnflags", 1 + 2) + phexp:Spawn() + phexp:Fire("Explode", "", 0) + end + + local norm = self:GetAngles():Up() * -1 + local base = 120 + local bonus = punchphys:GetMass() * 2 + local vel = math.max(base * 2, base + bonus) + + punchphys:AddVelocity(norm * vel) + + util.BlastDamage(self, self:GetOwner(), self:GetPos(), 200, 50) + + local effect = EffectData() + effect:SetStart(self:GetPos()) + effect:SetOrigin(self:GetPos()) + effect:SetNormal(norm * -1) + effect:SetRadius(16) + effect:SetScale(1) + util.Effect("ManhackSparks", effect, true, true) + + sound.Play(punchsound, self:GetPos(), 80, 100) + end + end + + local delay = math.max(0.1, self.PunchRemaining / self.PunchMax) * 3 + + self:NextThink(CurTime() + delay) + + return true + end +end diff --git a/gamemodes/terrortown/entities/entities/ttt_radio.lua b/gamemodes/terrortown/entities/entities/ttt_radio.lua index 6b13b0b62..3b8577c2a 100644 --- a/gamemodes/terrortown/entities/entities/ttt_radio.lua +++ b/gamemodes/terrortown/entities/entities/ttt_radio.lua @@ -4,212 +4,212 @@ -- @section ttt_radio if SERVER then - AddCSLuaFile() -else - -- this entity can be DNA-sampled so we need some display info - ENT.Icon = "vgui/ttt/icon_radio" - ENT.PrintName = "radio_name" + AddCSLuaFile() end -ENT.Type = "anim" -ENT.Model = Model("models/props/cs_office/radio.mdl") +DEFINE_BASECLASS("ttt_base_placeable") + +if CLIENT then + -- this entity can be DNA-sampled so we need some display info + ENT.Icon = "vgui/ttt/icon_radio" + ENT.PrintName = "radio_name" +end + +ENT.Base = "ttt_base_placeable" + +ENT.Model = "models/props/cs_office/radio.mdl" -ENT.CanUseKey = true ENT.CanHavePrints = false + +ENT.CanUseKey = true +ENT.pickupWeaponClass = "weapon_ttt_radio" + ENT.SoundLimit = 5 ENT.SoundDelay = 0.5 --- -- @realm shared function ENT:Initialize() - self:SetModel(self.Model) - - self:PhysicsInit(SOLID_VPHYSICS) - self:SetMoveType(MOVETYPE_VPHYSICS) - self:SetSolid(SOLID_VPHYSICS) - self:SetCollisionGroup(COLLISION_GROUP_NONE) + self:SetModel(self.Model) - if SERVER then - self:SetMaxHealth(40) - end + BaseClass.Initialize(self) - self:SetHealth(40) + if SERVER then + self:SetMaxHealth(40) + end - if SERVER then - self:SetUseType(SIMPLE_USE) - end + self:SetHealth(40) - -- Register with owner - if CLIENT then - local client = LocalPlayer() + if SERVER then + self:SetUseType(SIMPLE_USE) - if client == self:GetOwner() then - client.radio = self - end - end + local mvObject = self:AddMarkerVision("radio_owner") + mvObject:SetOwner(self:GetOriginator()) + mvObject:SetVisibleFor(VISIBLE_FOR_TEAM) + mvObject:SyncToClients() + end - self.SoundQueue = {} - self.Playing = false - self.fingerprints = {} -end - ---- --- @param Player activator --- @realm shared -function ENT:UseOverride(activator) - if IsValid(activator) and activator:IsPlayer() and activator:IsInTeam(self:GetOwner()) then - local prints = self.fingerprints or {} - - -- picks up weapon, switches if possible and needed, returns weapon if successful - local wep = activator:SafePickupWeaponClass("weapon_ttt_radio", true) + -- Register with owner + if CLIENT then + local client = LocalPlayer() - if not IsValid(wep) then return end + if client == self:GetOriginator() then + client.radio = self + end + end - self:Remove() - - wep.fingerprints = wep.fingerprints or {} - - table.Add(wep.fingerprints, prints) - else - LANG.Msg(activator, "radio_pickup_wrong_team") - end + self.SoundQueue = {} + self.Playing = false + self.fingerprints = {} end -local zapsound = Sound("npc/assassin/ball_zap1.wav") +if SERVER then + --- + -- @realm server + function ENT:WasDestroyed() + local originator = self:GetOriginator() + + if not IsValid(originator) then + return + end + + LANG.Msg(originator, "radio_broken", nil, MSG_MSTACK_WARN) + end + + --- + -- @param Player activator + -- @realm server + function ENT:PlayerCanPickupWeapon(activator) + return activator == self:GetOriginator() + end + + --- + -- @param Player activator + -- @param Weapon wep + -- @realm server + function ENT:OnPickup(activator, wep) + local prints = self.fingerprints or {} + + wep.fingerprints = wep.fingerprints or {} + + table.Add(wep.fingerprints, prints) + end +end --- --- @param DamageInfo dmginfo -- @realm shared -function ENT:OnTakeDamage(dmginfo) - self:TakePhysicsDamage(dmginfo) - self:SetHealth(self:Health() - dmginfo:GetDamage()) - - if self:Health() > 0 then return end - - self:Remove() - - local effect = EffectData() - effect:SetOrigin(self:GetPos()) - - util.Effect("cball_explode", effect) - sound.Play(zapsound, self:GetPos()) - - if IsValid(self:GetOwner()) then - LANG.Msg(self:GetOwner(), "radio_broken") - end -end - -if CLIENT then - --- - -- @realm client - function ENT:OnRemove() - local client = LocalPlayer() - - if client ~= self:GetOwner() then return end - - client.radio = nil - end +function ENT:OnRemove() + if CLIENT then + local client = LocalPlayer() + + if client ~= self:GetOriginator() then + return + end + + client.radio = nil + else + self:RemoveMarkerVision("radio_owner") + end end - --- -- @param Sound snd -- @realm shared function ENT:AddSound(snd) - if #self.SoundQueue < self.SoundLimit then - self.SoundQueue[#self.SoundQueue + 1] = snd - end + if #self.SoundQueue < self.SoundLimit then + self.SoundQueue[#self.SoundQueue + 1] = snd + end end local simplesounds = { - scream = { - Sound("vo/npc/male01/pain07.wav"), - Sound("vo/npc/male01/pain08.wav"), - Sound("vo/npc/male01/pain09.wav"), - Sound("vo/npc/male01/no02.wav") - }, - explosion = { - Sound("BaseExplosionEffect.Sound") - } + scream = { + Sound("vo/npc/male01/pain07.wav"), + Sound("vo/npc/male01/pain08.wav"), + Sound("vo/npc/male01/pain09.wav"), + Sound("vo/npc/male01/no02.wav"), + }, + explosion = { + Sound("BaseExplosionEffect.Sound"), + }, } local serialsounds = { - footsteps = { - sound = { - { - Sound("player/footsteps/concrete1.wav"), - Sound("player/footsteps/concrete2.wav") - }, - { - Sound("player/footsteps/concrete3.wav"), - Sound("player/footsteps/concrete4.wav") - } - }, - times = {8, 16}, - delay = 0.35, - ampl = 80 - }, - burning = { - sound = { - Sound("General.BurningObject"), - Sound("General.StopBurning") - }, - times = {2, 2}, - delay = 4, - }, - beeps = { - sound = { - Sound("weapons/c4/c4_beep1.wav") - }, - delay = 0.75, - times = {8, 12}, - ampl = 70 - } + footsteps = { + sound = { + { + Sound("player/footsteps/concrete1.wav"), + Sound("player/footsteps/concrete2.wav"), + }, + { + Sound("player/footsteps/concrete3.wav"), + Sound("player/footsteps/concrete4.wav"), + }, + }, + times = { 8, 16 }, + delay = 0.35, + ampl = 80, + }, + burning = { + sound = { + Sound("General.BurningObject"), + Sound("General.StopBurning"), + }, + times = { 2, 2 }, + delay = 4, + }, + beeps = { + sound = { + Sound("weapons/c4/c4_beep1.wav"), + }, + delay = 0.75, + times = { 8, 12 }, + ampl = 70, + }, } local gunsounds = { - shotgun = { - sound = Sound("Weapon_XM1014.Single"), - delay = 0.8, - times = {1, 3}, - burst = false - }, - pistol = { - sound = Sound("Weapon_FiveSeven.Single"), - delay = 0.4, - times = {2, 4}, - burst = false - }, - mac10 = { - sound = Sound("Weapon_mac10.Single"), - delay = 0.065, - times = {5, 10}, - burst = true - }, - deagle = { - sound = Sound("Weapon_Deagle.Single"), - delay = 0.6, - times = {1, 3}, - burst = false - }, - m16 = { - sound = Sound("Weapon_M4A1.Single"), - delay = 0.2, - times = {1, 5}, - burst = true - }, - rifle = { - sound = Sound("weapons/scout/scout_fire-1.wav"), - delay = 1.5, - times = {1, 1}, - burst = false, - ampl = 80 - }, - huge = { - sound = Sound("Weapon_m249.Single"), - delay = 0.055, - times = {6, 12}, - burst = true - } + shotgun = { + sound = Sound("Weapon_XM1014.Single"), + delay = 0.8, + times = { 1, 3 }, + burst = false, + }, + pistol = { + sound = Sound("Weapon_FiveSeven.Single"), + delay = 0.4, + times = { 2, 4 }, + burst = false, + }, + mac10 = { + sound = Sound("Weapon_mac10.Single"), + delay = 0.065, + times = { 5, 10 }, + burst = true, + }, + deagle = { + sound = Sound("Weapon_Deagle.Single"), + delay = 0.6, + times = { 1, 3 }, + burst = false, + }, + m16 = { + sound = Sound("Weapon_M4A1.Single"), + delay = 0.2, + times = { 1, 5 }, + burst = true, + }, + rifle = { + sound = Sound("weapons/scout/scout_fire-1.wav"), + delay = 1.5, + times = { 1, 1 }, + burst = false, + ampl = 80, + }, + huge = { + sound = Sound("Weapon_m249.Single"), + delay = 0.055, + times = { 6, 12 }, + burst = true, + }, } --- @@ -218,67 +218,73 @@ local gunsounds = { -- @param boolean last -- @realm shared function ENT:PlayDelayedSound(snd, ampl, last) - -- maybe we can get destroyed while a timer is still up - if not IsValid(self) then return end + -- maybe we can get destroyed while a timer is still up + if not IsValid(self) then + return + end - if istable(snd) then - snd = table.Random(snd) - end + if istable(snd) then + snd = table.Random(snd) + end - sound.Play(snd, self:GetPos(), ampl) + sound.Play(snd, self:GetPos(), ampl) - self.Playing = not last + self.Playing = not last end --- -- @param Sound snd -- @realm shared function ENT:PlaySound(snd) - local pos = self:GetPos() - local slf = self - - if simplesounds[snd] then - sound.Play(table.Random(simplesounds[snd]), pos) - elseif gunsounds[snd] then - local gunsound = gunsounds[snd] - local times = math.random(gunsound.times[1], gunsound.times[2]) - local t = 0 - - for i = 1, times do - timer.Simple(t, function() - if not IsValid(slf) then return end - - slf:PlayDelayedSound(gunsound.sound, gunsound.ampl or 90, i == times) - end) - - if gunsound.burst then - t = t + gunsound.delay - else - t = t + math.Rand(gunsound.delay, gunsound.delay * 2) - end - end - elseif serialsounds[snd] then - local serialsound = serialsounds[snd] - local num = #serialsound.sound - local times = math.random(serialsound.times[1], serialsound.times[2]) - local t = 0 - local idx = 1 - - for i = 1, times do - timer.Simple(t, function() - if not IsValid(slf) then return end - - slf:PlayDelayedSound(serialsound.sound[idx], serialsound.ampl or 75, i == times) - end) - - t = t + serialsound.delay - idx = idx + 1 - - if idx > num then - idx = 1 - end - end - end + local pos = self:GetPos() + local slf = self + + if simplesounds[snd] then + sound.Play(table.Random(simplesounds[snd]), pos) + elseif gunsounds[snd] then + local gunsound = gunsounds[snd] + local times = math.random(gunsound.times[1], gunsound.times[2]) + local t = 0 + + for i = 1, times do + timer.Simple(t, function() + if not IsValid(slf) then + return + end + + slf:PlayDelayedSound(gunsound.sound, gunsound.ampl or 90, i == times) + end) + + if gunsound.burst then + t = t + gunsound.delay + else + t = t + math.Rand(gunsound.delay, gunsound.delay * 2) + end + end + elseif serialsounds[snd] then + local serialsound = serialsounds[snd] + local num = #serialsound.sound + local times = math.random(serialsound.times[1], serialsound.times[2]) + local t = 0 + local idx = 1 + + for i = 1, times do + timer.Simple(t, function() + if not IsValid(slf) then + return + end + + slf:PlayDelayedSound(serialsound.sound[idx], serialsound.ampl or 75, i == times) + end) + + t = t + serialsound.delay + idx = idx + 1 + + if idx > num then + idx = 1 + end + end + end end local nextplay = 0 @@ -286,79 +292,130 @@ local nextplay = 0 --- -- @realm shared function ENT:Think() - if CurTime() <= nextplay or #self.SoundQueue <= 0 then return end + if CurTime() <= nextplay or not istable(self.SoundQueue) or #self.SoundQueue <= 0 then + return + end - if not self.Playing then - self:PlaySound(table.remove(self.SoundQueue, 1)) - end + if not self.Playing then + self:PlaySound(table.remove(self.SoundQueue, 1)) + end - -- always do slf, makes timing work out a little better - nextplay = CurTime() + self.SoundDelay + -- always do slf, makes timing work out a little better + nextplay = CurTime() + self.SoundDelay end if CLIENT then - local TryT = LANG.TryTranslation - local ParT = LANG.GetParamTranslation - - -- handle looking at radio - hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDRadio", function(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - - if not IsValid(client) or not client:IsTerror() or not client:Alive() - or tData:GetEntityDistance() > 100 or ent:GetClass() ~= "ttt_radio" then - return - end - - -- enable targetID rendering - tData:EnableText() - tData:EnableOutline() - tData:SetOutlineColor(client:GetRoleColor()) - - tData:SetTitle(TryT(ent.PrintName)) - tData:SetSubtitle(ParT("target_pickup", {usekey = Key("+use", "USE")})) - tData:SetKeyBinding("+use") - tData:AddDescriptionLine(TryT("radio_short_desc")) - end) -end + local materialRadio = Material("vgui/ttt/marker_vision/radio") -if SERVER then - local soundtypes = { - "scream", - "shotgun", - "explosion", - "pistol", - "mac10", - "deagle", - "m16", - "rifle", - "huge", - "burning", - "beeps", - "footsteps", - } + local TryT = LANG.TryTranslation + local ParT = LANG.GetParamTranslation + + -- handle looking at radio + hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDRadio", function(tData) + local client = LocalPlayer() + local ent = tData:GetEntity() + + if + not IsValid(client) + or not client:IsTerror() + or not client:Alive() + or tData:GetEntityDistance() > 100 + or ent:GetClass() ~= "ttt_radio" + then + return + end + + -- enable targetID rendering + tData:EnableText() + tData:EnableOutline() + tData:SetOutlineColor(client:GetRoleColor()) - local function RadioCmd(ply, cmd, args) - if not IsValid(ply) or not ply:IsActive() or #args ~= 2 then return end + tData:SetTitle(TryT(ent.PrintName)) - local eidx = tonumber(args[1]) - local snd = tostring(args[2]) + if ent:GetOriginator() == client then + tData:SetSubtitle(ParT("target_pickup", { usekey = Key("+use", "USE") })) + else + tData:SetSubtitle(TryT("entity_pickup_owner_only")) + end - if not eidx or not snd then return end + tData:SetKeyBinding("+use") + tData:AddDescriptionLine(TryT("radio_short_desc")) + end) - local radio = Entity(eidx) + hook.Add("TTT2RenderMarkerVisionInfo", "HUDDrawMarkerVisionRadio", function(mvData) + local ent = mvData:GetEntity() + local mvObject = mvData:GetMarkerVisionObject() - if ply:GetTeam() ~= radio:GetOwner():GetTeam() then return end + if not mvObject:IsObjectFor(ent, "radio_owner") then + return + end - if not IsValid(radio) or radio:GetOwner() ~= ply or radio:GetClass() ~= "ttt_radio" then return end + local originator = ent:GetOriginator() + local nick = IsValid(originator) and originator:Nick() or "---" - if not table.HasValue(soundtypes, snd) then - print("Received radio sound not in table from", ply) + local distance = math.Round(util.HammerUnitsToMeters(mvData:GetEntityDistance()), 1) - return - end + mvData:EnableText() - radio:AddSound(snd) - end - concommand.Add("ttt_radio_play", RadioCmd) + mvData:SetTitle(TryT(ent.PrintName)) + mvData:AddIcon(materialRadio) + + mvData:AddDescriptionLine(ParT("marker_vision_owner", { owner = nick })) + mvData:AddDescriptionLine(ParT("marker_vision_distance", { distance = distance })) + + mvData:AddDescriptionLine(TryT(mvObject:GetVisibleForTranslationKey()), COLOR_SLATEGRAY) + end) +end + +if SERVER then + local soundtypes = { + "scream", + "shotgun", + "explosion", + "pistol", + "mac10", + "deagle", + "m16", + "rifle", + "huge", + "burning", + "beeps", + "footsteps", + } + + local function RadioCmd(ply, cmd, args) + if not IsValid(ply) or not ply:IsActive() or #args ~= 2 then + return + end + + local eidx = tonumber(args[1]) + local snd = tostring(args[2]) + + if not eidx or not snd then + return + end + + local radio = Entity(eidx) + + if ply:GetTeam() ~= radio:GetOriginator():GetTeam() then + return + end + + if + not IsValid(radio) + or radio:GetOriginator() ~= ply + or radio:GetClass() ~= "ttt_radio" + then + return + end + + if not table.HasValue(soundtypes, snd) then + ErrorNoHaltWithStack("Received radio sound not in table from", ply) + + return + end + + radio:AddSound(snd) + end + concommand.Add("ttt_radio_play", RadioCmd) end diff --git a/gamemodes/terrortown/entities/entities/ttt_random_ammo.lua b/gamemodes/terrortown/entities/entities/ttt_random_ammo.lua index cb96d971e..f37c980b9 100644 --- a/gamemodes/terrortown/entities/entities/ttt_random_ammo.lua +++ b/gamemodes/terrortown/entities/entities/ttt_random_ammo.lua @@ -7,11 +7,11 @@ ENT.Type = "point" ENT.Base = "base_point" --- --- @note Only used to forceSpawn ammo after map cleanup +-- @note Only used to forceSpawn ammo after map cleanup -- otherwise these entities are only used to mark the spots for random ammo spawns -- @realm shared function ENT:Initialize() - if entspawn.IsForcedRandomSpawnEnabled() then - entspawn.SpawnRandomAmmo(self) - end + if entspawn.IsForcedRandomSpawnEnabled() then + entspawn.SpawnRandomAmmo(self) + end end diff --git a/gamemodes/terrortown/entities/entities/ttt_random_weapon.lua b/gamemodes/terrortown/entities/entities/ttt_random_weapon.lua index 2214ad254..551606a65 100644 --- a/gamemodes/terrortown/entities/entities/ttt_random_weapon.lua +++ b/gamemodes/terrortown/entities/entities/ttt_random_weapon.lua @@ -13,17 +13,17 @@ ENT.autoAmmoAmount = 0 -- @param string|number value -- @realm shared function ENT:KeyValue(key, value) - if key == "auto_ammo" then - self.autoAmmoAmount = tonumber(value) - end + if key == "auto_ammo" then + self.autoAmmoAmount = tonumber(value) + end end --- --- @note Only used to forceSpawn weapons after map cleanup +-- @note Only used to forceSpawn weapons after map cleanup -- otherwise these entities are only used to mark the spots for random weapon spawns -- @realm shared function ENT:Initialize() - if entspawn.IsForcedRandomSpawnEnabled() then - entspawn.SpawnRandomWeapon(self) - end + if entspawn.IsForcedRandomSpawnEnabled() then + entspawn.SpawnRandomWeapon(self) + end end diff --git a/gamemodes/terrortown/entities/entities/ttt_smokegrenade_proj.lua b/gamemodes/terrortown/entities/entities/ttt_smokegrenade_proj.lua new file mode 100644 index 000000000..c39369a7c --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_smokegrenade_proj.lua @@ -0,0 +1,101 @@ +--- +-- @class ENT +-- @section ttt_smokegrenade_proj + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("ttt_basegrenade_proj") + +ENT.Type = "anim" +ENT.Base = "ttt_basegrenade_proj" +ENT.Model = Model("models/weapons/w_eq_smokegrenade_thrown.mdl") + +AccessorFunc(ENT, "radius", "Radius", FORCE_NUMBER) + +--- +-- @ignore +function ENT:Initialize() + if not self:GetRadius() then + self:SetRadius(20) + end + + return BaseClass.Initialize(self) +end + +if CLIENT then + local smokeparticles = { + Model("particle/particle_smokegrenade"), + Model("particle/particle_noisesphere"), + } + + --- + -- @ignore + function ENT:CreateSmoke(center) + local em = ParticleEmitter(center) + + local r = self:GetRadius() + for i = 1, 20 do + local prpos = VectorRand() * r + prpos.z = prpos.z + 32 + local p = em:Add(table.Random(smokeparticles), center + prpos) + if p then + local gray = math.random(75, 200) + p:SetColor(gray, gray, gray) + p:SetStartAlpha(255) + p:SetEndAlpha(200) + p:SetVelocity(VectorRand() * math.Rand(900, 1300)) + p:SetLifeTime(0) + + p:SetDieTime(math.Rand(50, 70)) + + p:SetStartSize(math.random(140, 150)) + p:SetEndSize(math.random(1, 40)) + p:SetRoll(math.random(-180, 180)) + p:SetRollDelta(math.Rand(-0.1, 0.1)) + p:SetAirResistance(600) + + p:SetCollide(true) + p:SetBounce(0.4) + + p:SetLighting(false) + end + end + + em:Finish() + end +end + +--- +-- @ignore +function ENT:Explode(tr) + if SERVER then + self:SetNoDraw(true) + self:SetSolid(SOLID_NONE) + + -- pull out of the surface + if tr.Fraction ~= 1.0 then + self:SetPos(tr.HitPos + tr.HitNormal * 0.6) + end + + self:Remove() + else + local spos = self:GetPos() + util.PaintDown(spos, "SmallScorch", self) + + self:SetDetonateExact(0) + + if tr.Fraction ~= 1.0 then + spos = tr.HitPos + tr.HitNormal * 0.6 + end + + -- Smoke particles can't get cleaned up when a round restarts, so prevent + -- them from existing post-round. + if GetRoundState() == ROUND_POST then + return + end + + self:CreateSmoke(spos) + end +end diff --git a/gamemodes/terrortown/entities/entities/ttt_spawninfo_ent.lua b/gamemodes/terrortown/entities/entities/ttt_spawninfo_ent.lua index 7536cb7e5..76cefc688 100644 --- a/gamemodes/terrortown/entities/entities/ttt_spawninfo_ent.lua +++ b/gamemodes/terrortown/entities/entities/ttt_spawninfo_ent.lua @@ -4,7 +4,7 @@ -- @section ttt_spawninfo_ent if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ENT.Type = "anim" @@ -12,5 +12,5 @@ ENT.Base = "base_anim" -- @realm server function ENT:UpdateTransmitState() - return TRANSMIT_ALWAYS + return TRANSMIT_ALWAYS end diff --git a/gamemodes/terrortown/entities/entities/ttt_spectator_spawn.lua b/gamemodes/terrortown/entities/entities/ttt_spectator_spawn.lua new file mode 100644 index 000000000..6abd38329 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_spectator_spawn.lua @@ -0,0 +1,7 @@ +--- +-- @class ENT +-- @desc A spawn that is used for spectators spawning the game +-- @section SpectatorSpawn + +ENT.Type = "point" +ENT.Base = "base_point" diff --git a/gamemodes/terrortown/entities/entities/ttt_traitor_button/init.lua b/gamemodes/terrortown/entities/entities/ttt_traitor_button/init.lua index c2650632b..08e732693 100644 --- a/gamemodes/terrortown/entities/entities/ttt_traitor_button/init.lua +++ b/gamemodes/terrortown/entities/entities/ttt_traitor_button/init.lua @@ -14,142 +14,158 @@ local dir = "ttt2tbuttons" local path = dir .. "/" .. game.GetMap() .. ".json" local function ReadMapConfig() - file.CreateDir(dir) + file.CreateDir(dir) - local modTime = (not file.Exists(path, "DATA") and (lastRead + 1)) or file.Time(path, "DATA") + local modTime = (not file.Exists("data_static/" .. path, "GAME") and (lastRead + 1)) + or file.Time("data_static/" .. path, "GAME") + modTime = modTime <= lastRead and (not file.Exists(path, "DATA") and (lastRead + 1)) + or file.Time(path, "DATA") - if modTime <= lastRead then - return TButtonMapConfig - end + if modTime <= lastRead then + return TButtonMapConfig + end - lastRead = modTime - local content = file.Read(path, "DATA") + lastRead = modTime + local content = (file.Exists(path, "DATA") and file.Read(path, "DATA")) + or file.Read("data_static/" .. path, "GAME") - if not content then - return TButtonMapConfig - end + if not content then + return TButtonMapConfig + end - TButtonMapConfig = util.JSONToTable(content) or {} + TButtonMapConfig = util.JSONToTable(content) or {} - for id in pairs(TButtonMapConfig) do - local ent = ents.GetMapCreatedEntity(tonumber(id)) - if IsValid(ent) then - MapButtonEntIndexMapping[ent:EntIndex()] = id - end - end + for id in pairs(TButtonMapConfig) do + local ent = ents.GetMapCreatedEntity(tonumber(id)) + if IsValid(ent) then + MapButtonEntIndexMapping[ent:EntIndex()] = id + end + end - return TButtonMapConfig + return TButtonMapConfig end local function SendMapConfig(skipRead, ply) - if not skipRead then - ReadMapConfig() - end - - net.Start("TTT2SendTButtonConfig") - net.WriteTable(TButtonMapConfig) - net.WriteTable(MapButtonEntIndexMapping) - - if IsValid(ply) and ply:IsPlayer() then - net.Send(ply) - else - net.Broadcast() - end + if not skipRead then + ReadMapConfig() + end + + net.Start("TTT2SendTButtonConfig") + net.WriteTable(TButtonMapConfig) + net.WriteTable(MapButtonEntIndexMapping) + + if IsValid(ply) and ply:IsPlayer() then + net.Send(ply) + else + net.Broadcast() + end end local function UpdateMapConfig(ent, roleRawString, team, teamMode) - if not IsValid(ent) then return false end - - local mapID = ent:MapCreationID() - if mapID == -1 then return false end - - local currentJSON = ReadMapConfig() - currentJSON[mapID] = currentJSON[mapID] or {} - currentJSON[mapID].Override = currentJSON[mapID].Override or {} - currentJSON[mapID].Override.Role = currentJSON[mapID].Override.Role or {} - currentJSON[mapID].Override.Team = currentJSON[mapID].Override.Team or {} - currentJSON[mapID].Description = ent:GetDescription() - - if teamMode then - local cur = currentJSON[mapID].Override.Team[team] - if cur == nil then - currentJSON[mapID].Override.Team[team] = true - elseif cur then - currentJSON[mapID].Override.Team[team] = false - else - currentJSON[mapID].Override.Team[team] = nil - end - else - local cur = currentJSON[mapID].Override.Role[roleRawString] - if cur == nil then - currentJSON[mapID].Override.Role[roleRawString] = true - elseif cur then - currentJSON[mapID].Override.Role[roleRawString] = false - else - currentJSON[mapID].Override.Role[roleRawString] = nil - end - end - - file.Write(path, util.TableToJSON(currentJSON, true)) - TButtonMapConfig = currentJSON - MapButtonEntIndexMapping[mapID] = ent:EntIndex() - - return true + if not IsValid(ent) then + return false + end + + local mapID = ent:MapCreationID() + if mapID == -1 then + return false + end + + local currentJSON = ReadMapConfig() + currentJSON[mapID] = currentJSON[mapID] or {} + currentJSON[mapID].Override = currentJSON[mapID].Override or {} + currentJSON[mapID].Override.Role = currentJSON[mapID].Override.Role or {} + currentJSON[mapID].Override.Team = currentJSON[mapID].Override.Team or {} + currentJSON[mapID].Description = ent:GetDescription() + + if teamMode then + local cur = currentJSON[mapID].Override.Team[team] + if cur == nil then + currentJSON[mapID].Override.Team[team] = true + elseif cur then + currentJSON[mapID].Override.Team[team] = false + else + currentJSON[mapID].Override.Team[team] = nil + end + else + local cur = currentJSON[mapID].Override.Role[roleRawString] + if cur == nil then + currentJSON[mapID].Override.Role[roleRawString] = true + elseif cur then + currentJSON[mapID].Override.Role[roleRawString] = false + else + currentJSON[mapID].Override.Role[roleRawString] = nil + end + end + + file.Write(path, util.TableToJSON(currentJSON, true)) + TButtonMapConfig = currentJSON + MapButtonEntIndexMapping[mapID] = ent:EntIndex() + + return true end net.Receive("TTT2RequestTButtonConfig", function(len, ply) - SendMapConfig(false, ply) + SendMapConfig(false, ply) end) hook.Add("TTTInitPostEntity", "TTT2TButtonsCacheInitialize", function() - -- Initially send the map config - SendMapConfig() + -- Initially send the map config + SendMapConfig() end) net.Receive("TTT2ToggleTButton", function(len, ply) - local ent = net.ReadEntity() - local teamMode = net.ReadBool() + local ent = net.ReadEntity() + local teamMode = net.ReadBool() - if not IsValid(ply) or not IsValid(ent) or not ply:IsAdmin() then return end + if not IsValid(ply) or not IsValid(ent) or not ply:IsAdmin() then + return + end - --- - -- @realm server - local use, message = hook.Run("TTTCanToggleTraitorButton", ent, ply) + --- + -- @realm server + -- stylua: ignore + local use, message = hook.Run("TTTCanToggleTraitorButton", ent, ply) - if not use then - if message then - LANG.Msg(ply, message, nil, MSG_MSTACK_ROLE) - end + if not use then + if message then + LANG.Msg(ply, message, nil, MSG_MSTACK_ROLE) + end - return - end + return + end - UpdateMapConfig(ent, ply:GetRoleStringRaw(), ply:GetTeam(), teamMode) - SendMapConfig(true) + UpdateMapConfig(ent, ply:GetRoleStringRaw(), ply:GetTeam(), teamMode) + SendMapConfig(true) end) local function ActivateTButton(ply, ent) - if not IsValid(ply) or not IsValid(ent) or ent:GetClass() ~= "ttt_traitor_button" then return end + if not IsValid(ply) or not IsValid(ent) or ent:GetClass() ~= "ttt_traitor_button" then + return + end - if not ent.PlayerRoleCanUse or not ent:PlayerRoleCanUse(ply) or not ent.TraitorUse then return end + if not ent.PlayerRoleCanUse or not ent:PlayerRoleCanUse(ply) or not ent.TraitorUse then + return + end - --- - -- @realm server - local use, message = hook.Run("TTTCanUseTraitorButton", ent, ply) + --- + -- @realm server + -- stylua: ignore + local use, message = hook.Run("TTTCanUseTraitorButton", ent, ply) - if not use then - if message then - LANG.Msg(ply, message, nil, MSG_MSTACK_ROLE) - end + if not use then + if message then + LANG.Msg(ply, message, nil, MSG_MSTACK_ROLE) + end - return - end + return + end - ent:TraitorUse(ply) + ent:TraitorUse(ply) end net.Receive("TTT2ActivateTButton", function(len, ply) - ActivateTButton(ply, net.ReadEntity()) + ActivateTButton(ply, net.ReadEntity()) end) ENT.RemoveOnPress = false @@ -159,37 +175,37 @@ ENT.Model = Model("models/weapons/w_bugbait.mdl") --- -- @realm server function ENT:Initialize() - self:SetModel(self.Model) + self:SetModel(self.Model) - self:SetNoDraw(true) - self:DrawShadow(false) - self:SetSolid(SOLID_NONE) - self:SetMoveType(MOVETYPE_NONE) + self:SetNoDraw(true) + self:DrawShadow(false) + self:SetSolid(SOLID_NONE) + self:SetMoveType(MOVETYPE_NONE) - self:SetDelay(self.RawDelay or 1) + self:SetDelay(self.RawDelay or 1) - if self:GetDelay() < 0 then - self.RemoveOnPress = true -- func_button can be made single use by setting delay to be negative, so mimic that here - end + if self:GetDelay() < 0 then + self.RemoveOnPress = true -- func_button can be made single use by setting delay to be negative, so mimic that here + end - if self.RemoveOnPress then - self:SetDelay(-1) -- tells client we're single use - end + if self.RemoveOnPress then + self:SetDelay(-1) -- tells client we're single use + end - if self:GetUsableRange() < 1 then - self:SetUsableRange(1024) - end + if self:GetUsableRange() < 1 then + self:SetUsableRange(1024) + end - self:SetNextUseTime(0) - self:SetLocked(self:HasSpawnFlags(2048)) + self:SetNextUseTime(0) + self:SetLocked(self:HasSpawnFlags(2048)) - self:SetDescription(self.RawDescription or "?") + self:SetDescription(self.RawDescription or "?") - self:SetRole(self.Role or "none") - self:SetTeam(self.Team or TEAM_NONE) + self:SetRole(self.Role or "none") + self:SetTeam(self.Team or TEAM_NONE) - self.RawDelay = nil - self.RawDescription = nil + self.RawDelay = nil + self.RawDescription = nil end --- @@ -197,25 +213,25 @@ end -- @param string|number value -- @realm server function ENT:KeyValue(key, value) - if key == "OnPressed" then - self:StoreOutput(key, value) - elseif key == "wait" then -- as Delay Before Reset in func_button - self.RawDelay = tonumber(value) - elseif key == "description" then - self.RawDescription = tostring(value) - - if self.RawDescription and string.len(self.RawDescription) < 1 then - self.RawDescription = nil - end - elseif key == "RemoveOnPress" then - self.RemoveOnPress = tobool(value) - elseif key == "role" then - self.Role = tostring(value) - elseif key == "team" then - self.Team = tostring(value) - else - self:SetNetworkKeyValue(key, value) - end + if key == "OnPressed" then + self:StoreOutput(key, value) + elseif key == "wait" then -- as Delay Before Reset in func_button + self.RawDelay = tonumber(value) + elseif key == "description" then + self.RawDescription = tostring(value) + + if self.RawDescription and string.len(self.RawDescription) < 1 then + self.RawDescription = nil + end + elseif key == "RemoveOnPress" then + self.RemoveOnPress = tobool(value) + elseif key == "role" then + self.Role = tostring(value) + elseif key == "team" then + self.Team = tostring(value) + else + self:SetNetworkKeyValue(key, value) + end end --- @@ -223,19 +239,19 @@ end -- @param Player activator -- @realm server function ENT:AcceptInput(name, activator) - if name == "Toggle" then - self:SetLocked(not self:GetLocked()) + if name == "Toggle" then + self:SetLocked(not self:GetLocked()) - return true - elseif name == "Hide" or name == "Lock" then - self:SetLocked(true) + return true + elseif name == "Hide" or name == "Lock" then + self:SetLocked(true) - return true - elseif name == "Unhide" or name == "Unlock" then - self:SetLocked(false) + return true + elseif name == "Unhide" or name == "Unlock" then + self:SetLocked(false) - return true - end + return true + end end --- @@ -247,7 +263,7 @@ end -- @hook -- @realm server function GAMEMODE:TTTCanUseTraitorButton(ent, ply) - return true + return true end --- @@ -259,7 +275,7 @@ end -- @hook -- @realm server function GAMEMODE:TTTCanToggleTraitorButton(ent, ply) - return true + return true end --- @@ -268,44 +284,45 @@ end -- @param Player ply The player that used this button -- @hook -- @realm server -function GAMEMODE:TTTTraitorButtonActivated(ent, ply) - -end +function GAMEMODE:TTTTraitorButtonActivated(ent, ply) end --- -- @param Player ply -- @return boolean -- @realm server function ENT:TraitorUse(ply) - if not IsValid(ply) then - return false - end - - if not self:PlayerRoleCanUse(ply) - or not self:IsUsable() - or self:GetPos():Distance(ply:GetPos()) > self:GetUsableRange() then - return false - end - - net.Start("TTT_ConfirmUseTButton") - net.Send(ply) - - -- send output to all entities linked to us - self:TriggerOutput("OnPressed", ply) - - if self.RemoveOnPress then - self:SetLocked(true) - self:Remove() - else - -- lock ourselves until we should be usable again - self:SetNextUseTime(CurTime() + self:GetDelay()) - end - - --- - -- @realm server - hook.Run("TTTTraitorButtonActivated", self, ply) - - return true + if not IsValid(ply) then + return false + end + + if + not self:PlayerRoleCanUse(ply) + or not self:IsUsable() + or self:GetPos():Distance(ply:GetPos()) > self:GetUsableRange() + then + return false + end + + net.Start("TTT_ConfirmUseTButton") + net.Send(ply) + + -- send output to all entities linked to us + self:TriggerOutput("OnPressed", ply) + + if self.RemoveOnPress then + self:SetLocked(true) + self:Remove() + else + -- lock ourselves until we should be usable again + self:SetNextUseTime(CurTime() + self:GetDelay()) + end + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTTTraitorButtonActivated", self, ply) + + return true end --- @@ -313,18 +330,22 @@ end -- @return[default=TRANSMIT_ALWAYS] number -- @realm server function ENT:UpdateTransmitState() - return TRANSMIT_ALWAYS + return TRANSMIT_ALWAYS end --- -- keep the noombmessage (aka. concommand) for compatibility local function TraitorUseCmd(ply, cmd, args) - if #args ~= 1 or not IsValid(ply) then return end + if #args ~= 1 or not IsValid(ply) then + return + end - local idx = tonumber(args[1]) + local idx = tonumber(args[1]) - if not idx then return end + if not idx then + return + end - ActivateTButton(Entity(idx)) + ActivateTButton(Entity(idx)) end concommand.Add("ttt_use_tbutton", TraitorUseCmd) diff --git a/gamemodes/terrortown/entities/entities/ttt_traitor_button/shared.lua b/gamemodes/terrortown/entities/entities/ttt_traitor_button/shared.lua index ff0ee1f7f..212ffc62f 100644 --- a/gamemodes/terrortown/entities/entities/ttt_traitor_button/shared.lua +++ b/gamemodes/terrortown/entities/entities/ttt_traitor_button/shared.lua @@ -4,30 +4,31 @@ -- @section ttt_traitor_button if SERVER then - --- - -- @realm server - local cv_tbutton = CreateConVar("ttt2_tbutton_admin_show", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "Always show the buttons to admins in range", 0, 1) + --- + -- @realm server + -- stylua: ignore + local cv_tbutton = CreateConVar("ttt2_tbutton_admin_show", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "Always show the buttons to admins in range", 0, 1) - hook.Add("TTT2SyncGlobals", "AddTButtonGlobals", function() - SetGlobalBool(cv_tbutton:GetName(), cv_tbutton:GetBool()) - end) + hook.Add("TTT2SyncGlobals", "AddTButtonGlobals", function() + SetGlobalBool(cv_tbutton:GetName(), cv_tbutton:GetBool()) + end) - cvars.AddChangeCallback(cv_tbutton:GetName(), function(cv, old, new) - SetGlobalBool(cv_tbutton:GetName(), tobool(tonumber(new))) - end) + cvars.AddChangeCallback(cv_tbutton:GetName(), function(cv, old, new) + SetGlobalBool(cv_tbutton:GetName(), tobool(tonumber(new))) + end) end if CLIENT then - net.Receive("TTT2SendTButtonConfig", function(len, ply) - TButtonMapConfig = net.ReadTable() - MapButtonEntIndexMapping = net.ReadTable() - TBHUD:CacheEnts() - end) + net.Receive("TTT2SendTButtonConfig", function(len, ply) + TButtonMapConfig = net.ReadTable() + MapButtonEntIndexMapping = net.ReadTable() + TBHUD:CacheEnts() + end) - hook.Add("TTTInitPostEntity", "TTT2TButtonsInitialize", function() - net.Start("TTT2RequestTButtonConfig") - net.SendToServer() - end) + hook.Add("TTTInitPostEntity", "TTT2TButtonsInitialize", function() + net.Start("TTT2RequestTButtonConfig") + net.SendToServer() + end) end ENT.Type = "anim" @@ -36,20 +37,20 @@ ENT.Base = "base_anim" --- -- @realm shared function ENT:SetupDataTables() - self:NetworkVar("Float", 0, "Delay") - self:NetworkVar("Float", 1, "NextUseTime") - self:NetworkVar("Bool", 0, "Locked") - self:NetworkVar("String", 0, "Description") - self:NetworkVar("String", 1, "Role") - self:NetworkVar("String", 2, "Team") - self:NetworkVar("Int", 0, "UsableRange", {KeyName = "UsableRange"}) + self:NetworkVar("Float", 0, "Delay") + self:NetworkVar("Float", 1, "NextUseTime") + self:NetworkVar("Bool", 0, "Locked") + self:NetworkVar("String", 0, "Description") + self:NetworkVar("String", 1, "Role") + self:NetworkVar("String", 2, "Team") + self:NetworkVar("Int", 0, "UsableRange", { KeyName = "UsableRange" }) end --- -- @return boolean -- @realm shared function ENT:IsUsable() - return not self:GetLocked() and self:GetNextUseTime() < CurTime() + return not self:GetLocked() and self:GetNextUseTime() < CurTime() end --- @@ -58,33 +59,41 @@ end -- @return boolean access, overrideRole, overrideTeam, roleIntend, teamIntend -- @realm shared function ENT:PlayerRoleCanUse(ply) - local role = self:GetRole() - local team = self:GetTeam() - local curRol = ply:GetRoleStringRaw() - local curTeam = ply:GetTeam() - local mapID = MapButtonEntIndexMapping and MapButtonEntIndexMapping[self:EntIndex()] or -1 + local role = self:GetRole() + local team = self:GetTeam() + local curRol = ply:GetRoleStringRaw() + local curTeam = ply:GetTeam() + local mapID = MapButtonEntIndexMapping and MapButtonEntIndexMapping[self:EntIndex()] or -1 + local overrideRole = nil + local overrideTeam = nil - if TButtonMapConfig and TButtonMapConfig[mapID] and TButtonMapConfig[mapID].Override then - local overrideRole = nil - local overrideTeam = nil + if TButtonMapConfig and TButtonMapConfig[mapID] and TButtonMapConfig[mapID].Override then + if TButtonMapConfig[mapID].Override.Role then + overrideRole = TButtonMapConfig[mapID].Override.Role[curRol] + end - if TButtonMapConfig[mapID].Override.Role then - overrideRole = TButtonMapConfig[mapID].Override.Role[curRol] - end - if TButtonMapConfig[mapID].Override.Team then - overrideTeam = TButtonMapConfig[mapID].Override.Team[curTeam] - end + if TButtonMapConfig[mapID].Override.Team then + overrideTeam = TButtonMapConfig[mapID].Override.Team[curTeam] + end - if (overrideRole ~= nil or overrideTeam ~= nil) then - return overrideRole or (overrideRole ~= false and overrideTeam), overrideRole, overrideTeam, role, team - end - end + if overrideRole ~= nil or overrideTeam ~= nil then + return overrideRole or (overrideRole ~= false and overrideTeam), + overrideRole, + overrideTeam, + role, + team + end + end - if self:IsGeneric() then - return ply:GetSubRoleData():CanUseTraitorButton(), overrideRole, overrideTeam, role, team - else - return (role == "none" or role == curRol) and (team == TEAM_NONE or team == curTeam), overrideRole, overrideTeam, role, team - end + if self:IsGeneric() then + return ply:GetSubRoleData():CanUseTraitorButton(), overrideRole, overrideTeam, role, team + else + return (role == "none" or role == curRol) and (team == TEAM_NONE or team == curTeam), + overrideRole, + overrideTeam, + role, + team + end end --- @@ -92,8 +101,8 @@ end -- @return boolean -- @realm shared function ENT:IsGeneric() - local role = self:GetRole() - local team = self:GetTeam() + local role = self:GetRole() + local team = self:GetTeam() - return role == "none" and team == TEAM_NONE + return role == "none" and team == TEAM_NONE end diff --git a/gamemodes/terrortown/entities/entities/ttt_traitor_check.lua b/gamemodes/terrortown/entities/entities/ttt_traitor_check.lua index bd3780b68..5adfb25f0 100644 --- a/gamemodes/terrortown/entities/entities/ttt_traitor_check.lua +++ b/gamemodes/terrortown/entities/entities/ttt_traitor_check.lua @@ -5,7 +5,9 @@ ENT.Type = "brush" ENT.Base = "base_brush" -if CLIENT then return end +if CLIENT then + return +end --- -- Called when the engine sets a value for this scripted entity. @@ -13,10 +15,10 @@ if CLIENT then return end -- @param string value The new value -- @realm server function ENT:KeyValue(key, value) - if key == "TraitorsFound" then - -- this is our output, so handle it as such - self:StoreOutput(key, value) - end + if key == "TraitorsFound" then + -- this is our output, so handle it as such + self:StoreOutput(key, value) + end end --- @@ -27,29 +29,38 @@ end -- @return[default=0] number The amount of evil valid players found -- @realm server function ENT:CountValidPlayers(activator, caller, data) - local mins = self:LocalToWorld(self:OBBMins()) - local maxs = self:LocalToWorld(self:OBBMaxs()) + local mins = self:LocalToWorld(self:OBBMins()) + local maxs = self:LocalToWorld(self:OBBMaxs()) - local plys = player.GetAll() - local count = 0 + local plys = player.GetAll() + local count = 0 - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - -- only count if it is a valid player that is in range - if not IsValid(ply) or not ply:Alive() or not util.VectorInBounds(ply:GetPos(), mins, maxs) then continue end + -- only count if it is a valid player that is in range + if + not IsValid(ply) + or not ply:Alive() + or not util.VectorInBounds(ply:GetPos(), mins, maxs) + then + continue + end - --- - -- @realm server - local _, team = hook.Run("TTT2ModifyLogicRoleCheck", ply, self, activator, caller, data) + --- + -- @realm server + -- stylua: ignore + local _, team = hook.Run("TTT2ModifyLogicRoleCheck", ply, self, activator, caller, data) - -- only count if it is a evil role - if not util.IsEvilTeam(team) then continue end + -- only count if it is a evil role + if not util.IsEvilTeam(team) then + continue + end - count = count + 1 - end + count = count + 1 + end - return count + return count end --- @@ -61,13 +72,13 @@ end -- @return[default=true] boolean Return true if the default action should be supressed -- @realm server function ENT:AcceptInput(name, activator, caller, data) - if name == "CheckForTraitor" then - local traitorCount = self:CountValidPlayers(activator, caller, data) + if name == "CheckForTraitor" then + local traitorCount = self:CountValidPlayers(activator, caller, data) - self:TriggerOutput("TraitorsFound", activator, traitorCount) + self:TriggerOutput("TraitorsFound", activator, traitorCount) - return true - end + return true + end end --- @@ -84,5 +95,5 @@ end -- @hook -- @realm server function GAMEMODE:TTT2ModifyLogicRoleCheck(ply, ent, activator, caller, data) - return ply:GetBaseRole(), ply:GetTeam() + return ply:GetBaseRole(), ply:GetTeam() end diff --git a/gamemodes/terrortown/entities/entities/ttt_weapon_check.lua b/gamemodes/terrortown/entities/entities/ttt_weapon_check.lua new file mode 100644 index 000000000..775a503a6 --- /dev/null +++ b/gamemodes/terrortown/entities/entities/ttt_weapon_check.lua @@ -0,0 +1,143 @@ +--- +-- @class ENT +-- @section ttt_weapon_check + +ENT.Type = "brush" +ENT.Base = "base_brush" + +--- +-- @ignore +function ENT:KeyValue(key, value) + if key == "WeaponsFound" then + -- this is our output, so handle it as such + self:StoreOutput(key, value) + end +end + +--- +-- @ignore +local function VectorInside(vec, mins, maxs) + return vec.x > mins.x + and vec.x < maxs.x + and vec.y > mins.y + and vec.y < maxs.y + and vec.z > mins.z + and vec.z < maxs.z +end + +-- We use stuff from weaponry.lua here, like weapon types + +--- +-- @ignore +local function HasWeaponType(ply, weptype) + for _, wep in ipairs(ply:GetWeapons()) do + if IsValid(wep) and WEPS.TypeForWeapon(wep:GetClass()) == weptype then + return true + end + end + return false +end + +--- +-- @ignore +local function HasPrimary(ply) + return HasWeaponType(ply, WEAPON_HEAVY) +end + +--- +-- @ignore +local function HasSecondary(ply) + return HasWeaponType(ply, WEAPON_PISTOL) +end + +--- +-- @ignore +local function HasEquipment(ply) + return ply:HasEquipment() +end + +--- +-- @ignore +local function HasNade(ply) + return HasWeaponType(ply, WEAPON_NADE) +end + +--- +-- @ignore +local function HasAny(ply) + return HasPrimary(ply) or HasSecondary(ply) or HasEquipment(ply) or HasNade(ply) +end + +--- +-- @ignore +local function HasNamed(name) + --- + -- @ignore + return function(ply) + return ply:HasWeapon(name) + end +end + +local checkers = { + [1] = HasPrimary, + [2] = HasSecondary, + [3] = HasEquipment, + [4] = HasNade, + [5] = HasAny, +} + +--- +-- @ignore +function ENT:GetWeaponChecker(check) + if isstring(check) then + return HasNamed(check) + else + return checkers[check] + end +end + +--- +-- @ignore +function ENT:TestWeapons(weptype) + local mins = self:LocalToWorld(self:OBBMins()) + local maxs = self:LocalToWorld(self:OBBMaxs()) + + local check = self:GetWeaponChecker(weptype) + + if check == nil then + ErrorNoHalt("ttt_weapon_check: invalid parameter\n") + return 0 + end + + for _, ply in ipairs(player.GetAll()) do + if IsValid(ply) and ply:IsTerror() then + local pos = ply:GetPos() + local center = ply:LocalToWorld(ply:OBBCenter()) + if VectorInside(pos, mins, maxs) or VectorInside(center, mins, maxs) and check(ply) then + return 1 + end + end + end + + return 0 +end + +--- +-- @ignore +function ENT:AcceptInput(name, activator, caller, data) + if name == "CheckForType" or name == "CheckForClass" then + local weptype = tonumber(data) + local wepname = tostring(data) + + if not weptype and not wepname then + ErrorNoHalt("ttt_weapon_check: Invalid parameter to CheckForWeapons input!\n") + return false + end + + local weapons = self:TestWeapons(weptype or wepname) + + self:TriggerOutput("WeaponsFound", activator, weapons) + + return true + end +end diff --git a/gamemodes/terrortown/entities/entities/ttt_win.lua b/gamemodes/terrortown/entities/entities/ttt_win.lua index 386c3473b..21066beb7 100644 --- a/gamemodes/terrortown/entities/entities/ttt_win.lua +++ b/gamemodes/terrortown/entities/entities/ttt_win.lua @@ -14,21 +14,21 @@ local string = string -- @return[default=true] boolean -- @realm shared function ENT:AcceptInput(name, activator, caller) - if name == "TraitorWin" then - GAMEMODE:MapTriggeredEnd(WIN_TRAITOR) + if name == "TraitorWin" then + GAMEMODE:MapTriggeredEnd(WIN_TRAITOR) - return true - elseif name == "InnocentWin" then - GAMEMODE:MapTriggeredEnd(WIN_INNOCENT) + return true + elseif name == "InnocentWin" then + GAMEMODE:MapTriggeredEnd(WIN_INNOCENT) - return true - elseif string.len(name) > 3 and string.sub(name, -3) == "Win" then - name = "TEAM_" .. string.upper(string.sub(name, 1, -4)) + return true + elseif string.len(name) > 3 and string.sub(name, -3) == "Win" then + name = "TEAM_" .. string.upper(string.sub(name, 1, -4)) - if TEAMS[name] then - GAMEMODE:MapTriggeredEnd(name) + if TEAMS[name] then + GAMEMODE:MapTriggeredEnd(name) - return true - end - end + return true + end + end end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_beacon.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_beacon.lua new file mode 100644 index 000000000..5e597ccde --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_beacon.lua @@ -0,0 +1,155 @@ +--- +-- @class SWEP +-- @section weapon_ttt_beacon + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +SWEP.HoldType = "slam" + +if CLIENT then + SWEP.PrintName = "beacon_name" + SWEP.Slot = 6 + + SWEP.ViewModelFOV = 70 + SWEP.ViewModelFlip = false + + SWEP.UseHands = true + SWEP.ShowDefaultViewModel = false + SWEP.ShowDefaultWorldModel = false + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "beacon_desc", + } + + SWEP.Icon = "vgui/ttt/icon_beacon" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.ViewModel = "models/weapons/cstrike/c_c4.mdl" +SWEP.WorldModel = "models/props_lab/reciever01a.mdl" + +SWEP.Primary.ClipSize = 3 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "slam" +SWEP.Primary.Delay = 1.0 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 1.0 + +SWEP.Kind = WEAPON_EQUIP + +SWEP.CanBuy = { ROLE_DETECTIVE } +SWEP.LimitedStock = true -- only buyable once +SWEP.WeaponID = AMMO_BEACON +SWEP.builtin = true + +SWEP.Spawnable = true +SWEP.AllowDrop = false +SWEP.NoSights = true + +SWEP.builtin = true + +--- +-- @realm shared +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + if SERVER and self:CanPrimaryAttack() then + local beacon = ents.Create("ttt_beacon") + + if beacon:ThrowEntity(self:GetOwner(), Angle(90, 0, 0)) then + self:PlacedBeacon() + end + end +end + +--- +-- @realm shared +function SWEP:SecondaryAttack() + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + + if SERVER and self:CanPrimaryAttack() then + local beacon = ents.Create("ttt_beacon") + + if beacon:StickEntity(self:GetOwner()) then + self:PlacedBeacon() + end + end +end + +--- +-- @realm shared +function SWEP:PlacedBeacon() + self:TakePrimaryAmmo(1) + + if not self:CanPrimaryAttack() then + self:Remove() + end +end + +--- +-- @param Player buyer +-- @realm shared +function SWEP:WasBought(buyer) + self:SetClip1(self:Clip1() + 2) +end + +--- +-- @realm shared +function SWEP:Reload() + return false +end + +if CLIENT then + --- + -- @realm client + function SWEP:Initialize() + self:AddTTT2HUDHelp("beacon_help_pri", "beacon_help_sec") + + self.BaseClass.Initialize(self) + end + + --- + -- @realm client + function SWEP:InitializeCustomModels() + self:AddCustomViewModel("vmodel", { + type = "Model", + model = "models/props_lab/reciever01a.mdl", + bone = "ValveBiped.Bip01_R_Finger2", + rel = "", + pos = Vector(2.2, 6.5, -1), + angle = Angle(120, 10, 0), + size = Vector(0.6, 0.6, 0.6), + color = Color(255, 255, 255, 255), + surpresslightning = false, + material = "", + skin = 0, + bodygroup = {}, + }) + + self:AddCustomWorldModel("wmodel", { + type = "Model", + model = "models/props_lab/reciever01a.mdl", + bone = "ValveBiped.Bip01_R_Hand", + rel = "", + pos = Vector(6.7, 7, -1), + angle = Angle(-60, 35, 0), + size = Vector(0.6, 0.6, 0.6), + color = Color(255, 255, 255, 255), + surpresslightning = false, + material = "", + skin = 0, + bodygroup = {}, + }) + end +end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_binoculars.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_binoculars.lua index 7fc5dd7c4..667bc81d7 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_binoculars.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_binoculars.lua @@ -3,33 +3,32 @@ -- @section weapon_ttt_binoculars if SERVER then - AddCSLuaFile() + AddCSLuaFile() end -DEFINE_BASECLASS "weapon_tttbase" +DEFINE_BASECLASS("weapon_tttbase") -SWEP.HoldType = "normal" +SWEP.HoldType = "camera" if CLIENT then - SWEP.PrintName = "binoc_name" - SWEP.Slot = 7 + SWEP.PrintName = "binoc_name" + SWEP.Slot = 7 - SWEP.ViewModelFOV = 10 - SWEP.ViewModelFlip = false - SWEP.DrawCrosshair = false + SWEP.ShowDefaultViewModel = false + SWEP.ShowDefaultWorldModel = false - SWEP.EquipMenuData = { - type = "item_weapon", - desc = "binoc_desc" - } + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "binoc_desc", + } - SWEP.Icon = "vgui/ttt/icon_binoc" + SWEP.Icon = "vgui/ttt/icon_binoc" end SWEP.Base = "weapon_tttbase" SWEP.ViewModel = "models/weapons/v_crowbar.mdl" -SWEP.WorldModel = "models/props/cs_office/paper_towels.mdl" +SWEP.WorldModel = "models/Items/combine_rifle_cartridge01.mdl" SWEP.Primary.ClipSize = -1 SWEP.Primary.DefaultClip = -1 @@ -44,43 +43,58 @@ SWEP.Secondary.Ammo = "none" SWEP.Secondary.Delay = 0.2 SWEP.Kind = WEAPON_EQUIP2 -SWEP.CanBuy = {ROLE_DETECTIVE} -- only detectives can buy +SWEP.CanBuy = { ROLE_DETECTIVE } -- only detectives can buy SWEP.WeaponID = AMMO_BINOCULARS +SWEP.builtin = true SWEP.AllowDrop = true SWEP.ZoomLevels = { - 0, - 30, - 20, - 10 + 0, + 30, + 20, + 10, } SWEP.ProcessingDelay = 5 +SWEP.decayRate = 0.5 --- -- @ignore function SWEP:SetupDataTables() - self:DTVar("Bool", 0, "processing") - self:DTVar("Float", 0, "start_time") - self:DTVar("Int", 1, "zoom") + self:NetworkVar("Entity", 0, "ProcessTarget") + self:NetworkVar("Float", 0, "Progress") + self:NetworkVar("Float", 1, "AwayTime") + self:NetworkVar("Int", 0, "ZoomAmount") - return self.BaseClass.SetupDataTables(self) + return BaseClass.SetupDataTables(self) +end + +--- +-- @param Entity target The new processing target. +-- @realm shared +function SWEP:SetNewTarget(target) + self:SetProcessTarget(target) + self:SetProgress(0) + self:SetAwayTime(0) end --- -- @ignore function SWEP:PrimaryAttack() - self:SetNextPrimaryFire(CurTime() + 0.1) + self:SetNextPrimaryFire(CurTime() + 0.1) + + local corpse = self:GetTargetingCorpse() - if not self:IsTargetingCorpse() or self.dt.processing then return end + if corpse == nil or self:GetProcessTarget() ~= NULL and self:GetProcessTarget() == corpse then + return + end - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - if SERVER then - self.dt.processing = true - self.dt.start_time = CurTime() - end + if SERVER then + self:SetNewTarget(corpse) + end end local click = Sound("weapons/sniper/sniper_zoomin.wav") @@ -88,89 +102,112 @@ local click = Sound("weapons/sniper/sniper_zoomin.wav") --- -- @ignore function SWEP:SecondaryAttack() - self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) - - if CLIENT and IsFirstTimePredicted() then - LocalPlayer():EmitSound(click) - end + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) - self:CycleZoom() + if CLIENT and IsFirstTimePredicted() then + LocalPlayer():EmitSound(click) + end - self.dt.processing = false - self.dt.start_time = 0 + self:CycleZoom() end --- -- @ignore function SWEP:SetZoomLevel(level) - if CLIENT then return end + if CLIENT then + return + end - local owner = self:GetOwner() + local owner = self:GetOwner() - self.dt.zoom = level + self:SetZoomAmount(level) - owner:SetFOV(self.ZoomLevels[level], 0.3) - owner:DrawViewModel(false) + owner:SetFOV(self.ZoomLevels[level], 0.3) end --- -- @ignore function SWEP:CycleZoom() - self.dt.zoom = self.dt.zoom + 1 + self:SetZoomAmount(self:GetZoomAmount() + 1) - if not self.ZoomLevels[self.dt.zoom] then - self.dt.zoom = 1 - end + if not self.ZoomLevels[self:GetZoomAmount()] then + self:SetZoomAmount(1) + end - self:SetZoomLevel(self.dt.zoom) + self:SetZoomLevel(self:GetZoomAmount()) end --- -- @ignore function SWEP:PreDrop() - self:SetZoomLevel(1) + self:SetZoomLevel(1) - self.dt.processing = false + self:SetNewTarget(NULL) - return self.BaseClass.PreDrop(self) + return BaseClass.PreDrop(self) end --- -- @ignore function SWEP:Holster() - self:SetZoomLevel(1) + self:SetZoomLevel(1) - self.dt.processing = false + self:SetNewTarget(NULL) - return true + return true end --- -- @ignore function SWEP:Deploy() - if SERVER and IsValid(self:GetOwner()) then - self:GetOwner():DrawViewModel(false) - end + self:SetZoomLevel(1) - self:SetZoomLevel(1) - - return true + return BaseClass.Deploy(self) end --- -- @ignore function SWEP:Reload() - return false + local playSound = false + if self:GetZoomAmount() > 1 or self:GetProgress() > 0 then + self:SetZoomLevel(1) + playSound = true + end + + if CLIENT and IsFirstTimePredicted() and playSound then + LocalPlayer():EmitSound(click) + end + + self:SetNewTarget(NULL) + + return false end --- -- @return boolean -- @realm shared function SWEP:IsTargetingCorpse() - local tr = self:GetOwner():GetEyeTrace(MASK_SHOT) - local ent = tr.Entity + local tr = self:GetOwner():GetEyeTrace(MASK_SHOT) + local ent = tr.Entity - return IsValid(ent) and ent:GetClass() == "prop_ragdoll" and CORPSE.GetPlayerNick(ent, false) ~= false + return IsValid(ent) + and ent:GetClass() == "prop_ragdoll" + and CORPSE.GetPlayerNick(ent, false) ~= false +end + +--- +-- @return Entity +-- @realm shared +function SWEP:GetTargetingCorpse() + local ent = self:GetOwner():GetEyeTrace(MASK_SHOT).Entity + + if + IsValid(ent) + and ent:GetClass() == "prop_ragdoll" + and CORPSE.GetPlayerNick(ent, false) ~= false + then + return ent + end end local confirm = Sound("npc/turret_floor/click1.wav") @@ -178,134 +215,157 @@ local confirm = Sound("npc/turret_floor/click1.wav") --- -- @realm shared function SWEP:IdentifyCorpse() - local owner = self:GetOwner() + local owner = self:GetOwner() - if SERVER then - local tr = owner:GetEyeTrace(MASK_SHOT) + if SERVER then + local tr = owner:GetEyeTrace(MASK_SHOT) - CORPSE.ShowSearch(owner, tr.Entity, false, true) - elseif IsFirstTimePredicted() then - LocalPlayer():EmitSound(confirm) - end + CORPSE.ShowSearch(owner, tr.Entity, false, true) + elseif IsFirstTimePredicted() then + LocalPlayer():EmitSound(confirm) + end end --- -- @ignore function SWEP:Think() - BaseClass.Think(self) - - if not self.dt.processing then return end - - if not self:IsTargetingCorpse() then - self.dt.processing = false - self.dt.start_time = 0 - - return - end - - if CurTime() > (self.dt.start_time + self.ProcessingDelay) then - self:IdentifyCorpse() - - self.dt.processing = false - self.dt.start_time = 0 - end + BaseClass.Think(self) + + if self:GetProcessTarget() == NULL then + return + end + + local tickDirection = engine.TickInterval() + local awayTime = self:GetAwayTime() + + if self:GetTargetingCorpse() ~= self:GetProcessTarget() then + awayTime = awayTime + tickDirection + self:SetAwayTime(awayTime) + tickDirection = tickDirection * (-self.decayRate * awayTime) + else + self:SetAwayTime(0) + end + + local newProgress = self:GetProgress() + tickDirection + self:SetProgress(newProgress) + + if newProgress < 0 then + self:SetNewTarget(NULL) + elseif newProgress > self.ProcessingDelay then + self:IdentifyCorpse() + self:SetNewTarget(NULL) + end end if CLIENT then - local TryT = LANG.TryTranslation - local GetPT = LANG.GetParamTranslation - local mathRound = math.Round - local mathClamp = math.Clamp - - local hud_color = Color(60, 220, 20, 255) - - local cv_thickness - - --- - -- @ignore - function SWEP:OnRemove() - local owner = self:GetOwner() - - if not IsValid(owner) or owner ~= LocalPlayer() or not owner:Alive() then return end - - RunConsoleCommand("lastinv") - end - - --- - -- @ignore - function SWEP:Initialize() - self:AddTTT2HUDHelp("binoc_help_pri", "binoc_help_sec") - cv_thickness = GetConVar("ttt_crosshair_thickness") - - return self.BaseClass.Initialize(self) - end - - --- - -- @ignore - function SWEP:DrawWorldModel() - if IsValid(self:GetOwner()) then return end - - self:DrawModel() - end - - --- - -- @ignore - function SWEP:AdjustMouseSensitivity() - if self.dt.zoom > 0 then - return 1 / self.dt.zoom - end - - return -1 - end - - hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDBinocular", function(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - - if not IsValid(client) or not client:IsTerror() or not client:Alive() then return end - - local c_wep = client:GetActiveWeapon() - - if not IsValid(c_wep) then return end - - if not IsValid(ent) or ent:GetClass() ~= "prop_ragdoll" or c_wep:GetClass() ~= "weapon_ttt_binoculars" then return end - - -- draw progress - if not c_wep.dt.processing then return end - - local progress = mathRound(mathClamp((CurTime() - c_wep.dt.start_time) / c_wep.ProcessingDelay * 100, 0, 100)) - - tData:AddDescriptionLine( - GetPT("binoc_progress", {progress = progress}), - hud_color - ) - end) - - --- - -- @ignore - function SWEP:DrawHUD() - self:DrawHelp() - - local length = 35 - local gap = 15 - local thickness = math.floor(cv_thickness and cv_thickness:GetFloat() or 1) - local offset = thickness * 0.5 - - surface.SetDrawColor(clr(hud_color)) - - -- change scope when looking at corpse - if self:IsTargetingCorpse() then - gap = thickness * 2 - end - - local x = ScrW() * 0.5 - local y = ScrH() * 0.5 - - surface.DrawRect(x - length, y - offset, length - gap, thickness) - surface.DrawRect(x + gap, y - offset, length - gap, thickness) - surface.DrawRect(x - offset, y - length, thickness, length - gap) - surface.DrawRect(x - offset, y + gap, thickness, length - gap) - - draw.ShadowedText(TryT("binoc_zoom_level") .. ": " .. self.dt.zoom, "TargetID_Description", x + length + 10, y - length, hud_color, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - end + local TryT = LANG.TryTranslation + local GetPT = LANG.GetParamTranslation + local mathRound = math.Round + local mathClamp = math.Clamp + + local hud_color = Color(60, 220, 20, 255) + + local cv_thickness + + --- + -- @ignore + function SWEP:Initialize() + self:AddTTT2HUDHelp("binoc_help_pri", "binoc_help_sec") + self:AddHUDHelpLine("binoc_help_reload", Key("+reload", "R")) + + cv_thickness = GetConVar("ttt_crosshair_thickness") + + self.BaseClass.Initialize(self) + end + + --- + -- @realm client + function SWEP:InitializeCustomModels() + self:AddCustomWorldModel("wmodel", { + type = "Model", + model = "models/Items/combine_rifle_cartridge01.mdl", + bone = "ValveBiped.Bip01_R_Hand", + rel = "", + pos = Vector(4, 5, 0), + angle = Angle(0, 80, -20), + size = Vector(0.7, 0.7, 0.7), + color = Color(255, 255, 255, 255), + surpresslightning = false, + material = "", + skin = 0, + bodygroup = {}, + }) + end + + --- + -- @ignore + function SWEP:AdjustMouseSensitivity() + if self:GetZoomAmount() > 0 then + return 1 / self:GetZoomAmount() + end + + return -1 + end + + hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDBinocular", function(tData) + local client = LocalPlayer() + local ent = tData:GetEntity() + + if not IsValid(client) or not client:IsTerror() or not client:Alive() then + return + end + + local c_wep = client:GetActiveWeapon() + + if + not IsValid(ent) + or not IsValid(c_wep) + or ent:GetClass() ~= "prop_ragdoll" + or c_wep:GetClass() ~= "weapon_ttt_binoculars" + or c_wep:GetProcessTarget() ~= ent + then + return + end + + local progress = + mathRound(mathClamp((c_wep:GetProgress() / c_wep.ProcessingDelay) * 100, 0, 100)) + + tData:AddDescriptionLine(GetPT("binoc_progress", { progress = progress }), hud_color) + end) + + --- + -- @ignore + function SWEP:DrawHUD() + self:DrawHelp() + + local length = 35 + local gap = 15 + local thickness = math.floor(cv_thickness and cv_thickness:GetFloat() or 1) + local offset = thickness * 0.5 + + surface.SetDrawColor(clr(hud_color)) + + -- change scope when looking at corpse + if self:IsTargetingCorpse() then + gap = thickness * 2 + end + + local x = ScrW() * 0.5 + local y = ScrH() * 0.5 + + surface.DrawRect(x - length, y - offset, length - gap, thickness) + surface.DrawRect(x + gap, y - offset, length - gap, thickness) + surface.DrawRect(x - offset, y - length, thickness, length - gap) + surface.DrawRect(x - offset, y + gap, thickness, length - gap) + + draw.ShadowedText( + TryT("binoc_zoom_level") .. ": " .. self:GetZoomAmount(), + "TargetID_Description", + x + length + 10, + y - length, + hud_color, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) + end end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_c4.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_c4.lua new file mode 100644 index 000000000..13159ff0b --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_c4.lua @@ -0,0 +1,101 @@ +--- +-- @class SWEP +-- @section weapon_ttt_c4 +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +if CLIENT then + SWEP.PrintName = "C4" + SWEP.Slot = 6 + + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 + + SWEP.EquipMenuData = { + type = "item_weapon", + name = "C4", + desc = "c4_desc", + } + + SWEP.Icon = "vgui/ttt/icon_c4" + SWEP.IconLetter = "I" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.HoldType = "slam" + +SWEP.Kind = WEAPON_EQUIP +SWEP.CanBuy = { ROLE_TRAITOR } -- only traitors can buy +SWEP.WeaponID = AMMO_C4 + +SWEP.builtin = true + +SWEP.UseHands = true +SWEP.ViewModel = "models/weapons/cstrike/c_c4.mdl" +SWEP.WorldModel = "models/weapons/w_c4.mdl" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 5.0 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 1.0 + +SWEP.NoSights = true + +--- +-- @ignore +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + if SERVER then + local bomb = ents.Create("ttt_c4") + + if bomb:ThrowEntity(self:GetOwner(), Angle(30, 180, 0)) then + bomb.fingerprints = self.fingerprints + + self:Remove() + end + end +end + +--- +-- @ignore +function SWEP:SecondaryAttack() + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + + if SERVER then + local bomb = ents.Create("ttt_c4") + + if bomb:StickEntity(self:GetOwner(), Angle(-90, 0, 180)) then + bomb.fingerprints = self.fingerprints + + self:Remove() + end + end +end + +--- +-- @ignore +function SWEP:Reload() + return false +end + +if CLIENT then + --- + -- @ignore + function SWEP:Initialize() + self:AddTTT2HUDHelp("c4_help_primary", "c4_help_secondary") + + return BaseClass.Initialize(self) + end +end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_confgrenade.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_confgrenade.lua index 40313097e..1c4e65c32 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_confgrenade.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_confgrenade.lua @@ -1,23 +1,24 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end SWEP.HoldType = "grenade" if CLIENT then - SWEP.PrintName = "confgrenade_name" - SWEP.Slot = 3 + SWEP.PrintName = "confgrenade_name" + SWEP.Slot = 3 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_nades" - SWEP.IconLetter = "h" + SWEP.Icon = "vgui/ttt/icon_confgrenade" + SWEP.IconLetter = "h" end SWEP.Base = "weapon_tttbasegrenade" SWEP.WeaponID = AMMO_DISCOMB +SWEP.builtin = true SWEP.Kind = WEAPON_NADE SWEP.spawnType = WEAPON_TYPE_NADE @@ -34,19 +35,18 @@ SWEP.Weight = 5 -- really the only difference between grenade weapons: the model and the thrown ent. -- @ignore function SWEP:GetGrenadeName() - return "ttt_confgrenade_proj" + return "ttt_confgrenade_proj" end if CLIENT then - --- - -- @ignore - function SWEP:AddToSettingsMenu(parent) - local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") - - form:MakeCheckBox({ - serverConvar = "ttt_allow_discomb_jump", - label = "label_allow_discomb_jump", - }) - end + --- + -- @ignore + function SWEP:AddToSettingsMenu(parent) + local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") + + form:MakeCheckBox({ + serverConvar = "ttt_allow_discomb_jump", + label = "label_allow_discomb_jump", + }) + end end - diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_cse.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_cse.lua index 9301ed50d..c5f19af1b 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_cse.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_cse.lua @@ -3,31 +3,36 @@ -- @section weapon_ttt_cse if SERVER then - AddCSLuaFile() + AddCSLuaFile() end -SWEP.HoldType = "normal" +DEFINE_BASECLASS("weapon_tttbase") + +SWEP.HoldType = "grenade" if CLIENT then - SWEP.PrintName = "vis_name" - SWEP.Slot = 6 + SWEP.PrintName = "vis_name" + SWEP.Slot = 6 + + SWEP.ViewModelFOV = 70 + SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 10 - SWEP.ViewModelFlip = false - SWEP.DrawCrosshair = false + SWEP.UseHands = true + SWEP.ShowDefaultViewModel = false + SWEP.ShowDefaultWorldModel = false - SWEP.EquipMenuData = { - type = "item_weapon", - desc = "vis_desc" - } + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "vis_desc", + } - SWEP.Icon = "vgui/ttt/icon_cse" + SWEP.Icon = "vgui/ttt/icon_cse" end SWEP.Base = "weapon_tttbase" -SWEP.ViewModel = Model("models/weapons/v_crowbar.mdl") -SWEP.WorldModel = Model("models/Items/battery.mdl") +SWEP.ViewModel = "models/weapons/cstrike/c_eq_fraggrenade.mdl" +SWEP.WorldModel = "models/Items/battery.mdl" SWEP.Primary.ClipSize = -1 SWEP.Primary.DefaultClip = -1 @@ -39,11 +44,12 @@ SWEP.Secondary.ClipSize = -1 SWEP.Secondary.DefaultClip = -1 SWEP.Secondary.Automatic = true SWEP.Secondary.Ammo = "none" -SWEP.Secondary.Delay = 0.2 +SWEP.Secondary.Delay = 1.0 SWEP.Kind = WEAPON_EQUIP -SWEP.CanBuy = {ROLE_DETECTIVE} -- only detectives can buy +SWEP.CanBuy = { ROLE_DETECTIVE } -- only detectives can buy SWEP.WeaponID = AMMO_CSE +SWEP.builtin = true SWEP.LimitedStock = true -- only buyable once SWEP.NoSights = true @@ -54,130 +60,93 @@ SWEP.DeathScanDelay = 15 --- -- @ignore function SWEP:PrimaryAttack() - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - self:DropDevice() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + if SERVER then + local cse = ents.Create("ttt_cse_proj") + + if cse:ThrowEntity(self:GetOwner()) then + self:Remove() + end + end end --- -- @ignore function SWEP:SecondaryAttack() - self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) - self:DropDevice() -end + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) ---- --- @ignore -function SWEP:DrawWorldModel() + if SERVER then + local cse = ents.Create("ttt_cse_proj") -end - ---- --- @ignore -function SWEP:OnDrop() - self:Remove() + if cse:ThrowEntity(self:GetOwner()) then + self:Remove() + end + end end --- -- @ignore function SWEP:PreDrop(isdeath) - if not isdeath then return end + if not isdeath then + return + end - local cse = self:DropDevice() + local cse = self:DropDevice() - if not IsValid(cse) then return end + if not IsValid(cse) then + return + end - cse:SetDetonateTimer(self.DeathScanDelay or 10) + cse:SetDetonateTimer(self.DeathScanDelay or 10) end --- -- @ignore function SWEP:Reload() - return false -end - ---- --- @ignore -function SWEP:OnRemove() - if SERVER then return end - - local owner = self:GetOwner() - - if IsValid(owner) and owner == LocalPlayer() and owner:Alive() then - RunConsoleCommand("lastinv") - end -end - -local throwsound = Sound("Weapon_SLAM.SatchelThrow") - ---- --- @realm shared -function SWEP:DropDevice() - if CLIENT then return end - - self:EmitSound(throwsound) - - local cse = nil - local ply = self:GetOwner() - - if not IsValid(ply) or self.Planted then return end - - local vsrc = ply:GetShootPos() - local vang = ply:GetAimVector() - local vvel = ply:GetVelocity() - local vthrow = vvel + vang * 200 - - cse = ents.Create("ttt_cse_proj") - - if not IsValid(cse) then return end - - cse:SetPos(vsrc + vang * 10) - cse:SetOwner(ply) - cse:SetThrower(ply) - cse:Spawn() - cse:PhysWake() - - local phys = cse:GetPhysicsObject() - - if IsValid(phys) then - phys:SetVelocity(vthrow) - end - - self:Remove() - - self.Planted = true - - return cse + return false end if CLIENT then - local TryT = LANG.TryTranslation - local ParT = LANG.GetParamTranslation - - --- - -- @ignore - function SWEP:Initialize() - self:AddTTT2HUDHelp("vis_help_pri") - - return self.BaseClass.Initialize(self) - end - - hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDVisualizer", function(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - - if not IsValid(client) or not client:IsTerror() or not client:Alive() - or tData:GetEntityDistance() > 100 or ent:GetClass() ~= "ttt_cse_proj" then - return - end - - -- enable targetID rendering - tData:EnableText() - tData:EnableOutline() - tData:SetOutlineColor(client:GetRoleColor()) - - tData:SetTitle(TryT("vis_name")) - tData:SetSubtitle(ParT("target_pickup", {usekey = Key("+use", "USE")})) - tData:SetKeyBinding("+use") - tData:AddDescriptionLine(TryT("vis_short_desc")) - end) + --- + -- @ignore + function SWEP:Initialize() + self:AddTTT2HUDHelp("vis_help_pri") + + self.BaseClass.Initialize(self) + end + + --- + -- @realm client + function SWEP:InitializeCustomModels() + self:AddCustomViewModel("vmodel", { + type = "Model", + model = "models/Items/battery.mdl", + bone = "ValveBiped.Bip01_R_Finger2", + rel = "", + pos = Vector(1.5, 1.5, 2.7), + angle = Angle(180, 20, 0), + size = Vector(0.65, 0.65, 0.65), + color = Color(255, 255, 255, 255), + surpresslightning = false, + material = "", + skin = 0, + bodygroup = {}, + }) + + self:AddCustomWorldModel("wmodel", { + type = "Model", + model = "models/Items/battery.mdl", + bone = "ValveBiped.Bip01_R_Hand", + rel = "", + pos = Vector(3.2, 2.5, 2.7), + angle = Angle(180, -100, 0), + size = Vector(0.65, 0.65, 0.65), + color = Color(255, 255, 255, 255), + surpresslightning = false, + material = "", + skin = 0, + bodygroup = {}, + }) + end end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_decoy.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_decoy.lua index d3904bd02..393f4dd7b 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_decoy.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_decoy.lua @@ -3,34 +3,41 @@ -- @section weapon_ttt_decoy if SERVER then - AddCSLuaFile() -else -- CLIENT - SWEP.PrintName = "decoy_name" - SWEP.Slot = 7 + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +if CLIENT then + SWEP.PrintName = "decoy_name" + SWEP.Slot = 7 - SWEP.ViewModelFOV = 10 - SWEP.ViewModelFlip = false - SWEP.DrawCrosshair = false + SWEP.ViewModelFOV = 70 + SWEP.ViewModelFlip = false - SWEP.EquipMenuData = { - type = "item_weapon", - desc = "decoy_desc" - } + SWEP.UseHands = true + SWEP.ShowDefaultViewModel = false + SWEP.ShowDefaultWorldModel = false - SWEP.Icon = "vgui/ttt/icon_beacon" + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "decoy_desc", + } + + SWEP.Icon = "vgui/ttt/icon_decoy" end SWEP.Base = "weapon_tttbase" -SWEP.HoldType = "normal" +SWEP.HoldType = "slam" -SWEP.ViewModel = "models/weapons/v_crowbar.mdl" +SWEP.ViewModel = "models/weapons/cstrike/c_c4.mdl" SWEP.WorldModel = "models/props_lab/reciever01b.mdl" -SWEP.Primary.ClipSize = -1 -SWEP.Primary.DefaultClip = -1 +SWEP.Primary.ClipSize = 1 +SWEP.Primary.DefaultClip = 1 SWEP.Primary.Automatic = true -SWEP.Primary.Ammo = "none" +SWEP.Primary.Ammo = "slam" SWEP.Primary.Delay = 1.0 SWEP.Secondary.ClipSize = -1 @@ -40,200 +47,116 @@ SWEP.Secondary.Ammo = "none" SWEP.Secondary.Delay = 1.0 SWEP.Kind = WEAPON_EQUIP2 -SWEP.CanBuy = {ROLE_TRAITOR} +SWEP.CanBuy = { ROLE_TRAITOR } SWEP.LimitedStock = true -- only buyable once SWEP.WeaponID = AMMO_DECOY +SWEP.builtin = true SWEP.AllowDrop = false SWEP.NoSights = true --- --- @ignore -function SWEP:OnDrop() - self:Remove() -end - ---- --- @ignore +-- @realm shared function SWEP:PrimaryAttack() - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - if SERVER then - self:DecoyStick() - end -end + if SERVER and self:CanPrimaryAttack() then + local decoy = ents.Create("ttt_decoy") ---- --- @ignore -function SWEP:SecondaryAttack() - self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + if decoy:ThrowEntity(self:GetOwner(), Angle(90, 0, 0)) then + decoy:SetNWString("decoy_owner_team", self:GetOwner():GetTeam()) - if SERVER then - self:DecoyStick() - end + self:PlacedDecoy(decoy) + end + end end -local throwsound = Sound("Weapon_SLAM.SatchelThrow") - --- --- Drop is disabled to prevent traitors from placing the decoy in unreachable places. -- @realm shared -function SWEP:DecoyDrop() - if SERVER then - local ply = self:GetOwner() - - if not IsValid(ply) or self.Planted then return end - - local vsrc = ply:GetShootPos() - local vang = ply:GetAimVector() - local vvel = ply:GetVelocity() - - local vthrow = vvel + vang * 200 - local decoy = ents.Create("ttt_decoy") - - if not IsValid(decoy) then return end - - decoy:SetPos(vsrc + vang * 10) - decoy:SetOwner(ply) - decoy:SetNWString("decoy_owner_team", ply:GetTeam()) - decoy:Spawn() - decoy:PointAtEntity(ply) - - local ang = decoy:GetAngles() - ang:RotateAroundAxis(ang:Right(), 90) - - decoy:SetAngles(ang) - decoy:PhysWake() +function SWEP:SecondaryAttack() + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) - local phys = decoy:GetPhysicsObject() - if IsValid(phys) then - phys:SetVelocity(vthrow) - end + if SERVER and self:CanPrimaryAttack() then + local decoy = ents.Create("ttt_decoy") - self:PlacedDecoy(decoy) - end + if decoy:StickEntity(self:GetOwner()) then + decoy:SetNWString("decoy_owner_team", self:GetOwner():GetTeam()) - self:EmitSound(throwsound) + self:PlacedDecoy(decoy) + end + end end if SERVER then - --- - -- @realm server - function SWEP:DecoyStick() - local ply = self:GetOwner() - - if not IsValid(ply) or self.Planted then return end - - local ignore = {ply, self} - local spos = ply:GetShootPos() - local epos = spos + ply:GetAimVector() * 80 - - local tr = util.TraceLine({ - start = spos, - endpos = epos, - filter = ignore, - mask = MASK_SOLID - }) - - if not tr.HitWorld then return end - - local decoy = ents.Create("ttt_decoy") - if not IsValid(decoy) then return end - - decoy:PointAtEntity(ply) - - local tr_ent = util.TraceEntity({ - start = spos, - endpos = epos, - filter = ignore, - mask = MASK_SOLID - }, decoy) - - if not tr_ent.HitWorld then return end - - local ang = tr_ent.HitNormal:Angle() - - decoy:SetPos(tr_ent.HitPos + ang:Forward() * 2.5) - decoy:SetAngles(ang) - decoy:SetOwner(ply) - decoy:SetNWString("decoy_owner_team", ply:GetTeam()) - decoy:Spawn() - - local phys = decoy:GetPhysicsObject() - if IsValid(phys) then - phys:EnableMotion(false) - end - - decoy.IsOnWall = true - - self:PlacedDecoy(decoy) - end - - -- add hook that changes all decoys - hook.Add("TTT2UpdateTeam", "TTT2DecoyUpdateTeam", function(ply, oldTeam, newTeam) - if not IsValid(ply.decoy) then return end - - ply.decoy:SetNWString("decoy_owner_team", newTeam) - end) + -- add hook that changes all decoys + hook.Add("TTT2UpdateTeam", "TTT2DecoyUpdateTeam", function(ply, oldTeam, newTeam) + if not IsValid(ply.decoy) then + return + end + + ply.decoy:SetNWString("decoy_owner_team", newTeam) + end) end --- -- @param Entity decoy -- @realm shared function SWEP:PlacedDecoy(decoy) - self:GetOwner().decoy = decoy + self:GetOwner().decoy = decoy - self:TakePrimaryAmmo(1) + self:TakePrimaryAmmo(1) - if not self:CanPrimaryAttack() then - self:Remove() - - self.Planted = true - end + if not self:CanPrimaryAttack() then + self:Remove() + end end --- -- @ignore function SWEP:Reload() - return false + return false end if CLIENT then - --- - -- @ignore - function SWEP:OnRemove() - if not IsValid(self:GetOwner()) or self:GetOwner() ~= LocalPlayer() or not self:GetOwner():Alive() then return end - - RunConsoleCommand("lastinv") - end - - --- - -- @ignore - function SWEP:Initialize() - self:AddTTT2HUDHelp("decoy_help_pri") - - return self.BaseClass.Initialize(self) - end -end - ---- --- @ignore -function SWEP:Deploy() - self:GetOwner():DrawViewModel(false) - - return true -end - ---- --- @ignore -function SWEP:DrawWorldModel() - if IsValid(self:GetOwner()) then return end - - self:DrawModel() -end - ---- --- @ignore -function SWEP:DrawWorldModelTranslucent() - + --- + -- @ignore + function SWEP:Initialize() + self:AddTTT2HUDHelp("decoy_help_primary", "decoy_help_secondary") + + self.BaseClass.Initialize(self) + end + + --- + -- @realm client + function SWEP:InitializeCustomModels() + self:AddCustomViewModel("vmodel", { + type = "Model", + model = "models/props_lab/reciever01b.mdl", + bone = "ValveBiped.Bip01_R_Finger2", + rel = "", + pos = Vector(2.0, 4.8, -0.5), + angle = Angle(120, 10, 0), + size = Vector(0.6, 0.6, 0.6), + color = Color(255, 255, 255, 255), + surpresslightning = false, + material = "", + skin = 0, + bodygroup = {}, + }) + + self:AddCustomWorldModel("wmodel", { + type = "Model", + model = "models/props_lab/reciever01b.mdl", + bone = "ValveBiped.Bip01_R_Hand", + rel = "", + pos = Vector(6, 5.4, -1), + angle = Angle(-60, 35, 0), + size = Vector(0.6, 0.6, 0.6), + color = Color(255, 255, 255, 255), + surpresslightning = false, + material = "", + skin = 0, + bodygroup = {}, + }) + end end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_defuser.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_defuser.lua new file mode 100644 index 000000000..aa5f9ecf5 --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_defuser.lua @@ -0,0 +1,101 @@ +--- +-- @class SWEP +-- @desc defuser +-- @section weapon_ttt_defuser + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +SWEP.HoldType = "slam" + +if CLIENT then + SWEP.PrintName = "defuser_name" + SWEP.Slot = 7 + + SWEP.ShowDefaultViewModel = false + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "defuser_desc", + } + + SWEP.Icon = "vgui/ttt/icon_defuser" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.ViewModel = "models/weapons/v_crowbar.mdl" +SWEP.WorldModel = "models/weapons/w_defuser.mdl" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Delay = 1 +SWEP.Primary.Ammo = "none" + +SWEP.Kind = WEAPON_EQUIP2 +SWEP.CanBuy = { ROLE_DETECTIVE } -- only detectives can buy +SWEP.WeaponID = AMMO_DEFUSER + +SWEP.builtin = true + +local defuse = Sound("c4.disarmfinish") + +--- +-- @ignore +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + local spos = self:GetOwner():GetShootPos() + local sdest = spos + (self:GetOwner():GetAimVector() * 80) + + local tr = + util.TraceLine({ start = spos, endpos = sdest, filter = self:GetOwner(), mask = MASK_SHOT }) + + if IsValid(tr.Entity) and tr.Entity.Defusable then + local bomb = tr.Entity + if bomb.Defusable == true or bomb:Defusable() then + if SERVER and bomb.Disarm then + bomb:Disarm(self:GetOwner()) + sound.Play(defuse, bomb:GetPos()) + end + + self:SetNextPrimaryFire(CurTime() + (self.Primary.Delay * 2)) + end + end +end + +--- +-- @ignore +function SWEP:SecondaryAttack() end + +if CLIENT then + --- + -- @ignore + function SWEP:Initialize() + self:AddTTT2HUDHelp("defuser_help_primary") + + return BaseClass.Initialize(self) + end + + --- + -- @ignore + function SWEP:DrawWorldModel() + if not IsValid(self:GetOwner()) then + self:DrawModel() + end + end +end + +--- +-- @ignore +function SWEP:Reload() + return false +end + +--- +-- @ignore +function SWEP:OnDrop() end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_flaregun.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_flaregun.lua new file mode 100644 index 000000000..3bb67058b --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_flaregun.lua @@ -0,0 +1,241 @@ +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +SWEP.HoldType = "pistol" + +if CLIENT then + SWEP.PrintName = "flare_name" + SWEP.Slot = 6 + + SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "flare_desc", + } + + SWEP.Icon = "vgui/ttt/icon_flare" +end + +SWEP.Base = "weapon_tttbase" + +-- if I run out of ammo types, this weapon is one I could move to a custom ammo +-- handling strategy, because you never need to pick up ammo for it +SWEP.Primary.Ammo = "AR2AltFire" +SWEP.Primary.Recoil = 4 +SWEP.Primary.Damage = 7 +SWEP.Primary.Delay = 1.0 +SWEP.Primary.Cone = 0.01 +SWEP.Primary.ClipSize = 4 +SWEP.Primary.Automatic = false +SWEP.Primary.DefaultClip = 4 +SWEP.Primary.ClipMax = 4 +SWEP.Primary.Sound = Sound("Weapon_USP.SilencedShot") + +SWEP.Kind = WEAPON_EQUIP +SWEP.CanBuy = { ROLE_TRAITOR } -- only traitors can buy +SWEP.LimitedStock = true -- only buyable once +SWEP.WeaponID = AMMO_FLARE + +SWEP.builtin = true + +SWEP.Tracer = "AR2Tracer" + +SWEP.UseHands = true +SWEP.ViewModel = Model("models/weapons/c_357.mdl") +SWEP.WorldModel = Model("models/weapons/w_357.mdl") + +if CLIENT then + --- + -- @ignore + function SWEP:Initialize() + self:AddTTT2HUDHelp("flaregun_help_primary") + + return BaseClass.Initialize(self) + end +end + +local function RunIgniteTimer(ent, timer_name) + if IsValid(ent) and ent:IsOnFire() then + if ent:WaterLevel() > 0 then + ent:Extinguish() + elseif CurTime() > ent.burn_destroy then + ent:SetNotSolid(true) + ent:Remove() + else + -- keep on burning + return + end + end + + timer.Remove(timer_name) -- stop running timer +end + +local SendScorches + +if CLIENT then + local function ReceiveScorches() + local ent = net.ReadEntity() + local num = net.ReadUInt(8) + for i = 1, num do + util.PaintDown(net.ReadVector(), "FadingScorch", ent) + end + + if IsValid(ent) then + util.PaintDown(ent:LocalToWorld(ent:OBBCenter()), "Scorch", ent) + end + end + net.Receive("TTT_FlareScorch", ReceiveScorches) +else + -- it's sad that decals are so unreliable when drawn serverside, failing to + -- draw more often than they work, that I have to do this + SendScorches = function(ent, tbl) + net.Start("TTT_FlareScorch") + net.WriteEntity(ent) + net.WriteUInt(#tbl, 8) + for _, p in ipairs(tbl) do + net.WriteVector(p) + end + net.Broadcast() + end +end + +local function ScorchUnderRagdoll(ent) + if SERVER then + local postbl = {} + -- small scorches under limbs + for i = 0, ent:GetPhysicsObjectCount() - 1 do + local subphys = ent:GetPhysicsObjectNum(i) + if IsValid(subphys) then + local pos = subphys:GetPos() + util.PaintDown(pos, "FadingScorch", ent) + + table.insert(postbl, pos) + end + end + + SendScorches(ent, postbl) + end + + -- big scorch at center + local mid = ent:LocalToWorld(ent:OBBCenter()) + mid.z = mid.z + 25 + util.PaintDown(mid, "Scorch", ent) +end + +--- +-- @param Entity att +-- @param table path +-- @param CTakeDamageInfo dmginfo +-- @realm shared +function IgniteTarget(att, path, dmginfo) + local ent = path.Entity + + if not IsValid(ent) then + return + end + + if CLIENT and IsFirstTimePredicted() then + if ent:GetClass() == "prop_ragdoll" then + ScorchUnderRagdoll(ent) + end + return + end + + if SERVER then + local dur = ent:IsPlayer() and 5 or 10 + + -- disallow if prep or post round + if ent:IsPlayer() and (not GAMEMODE:AllowPVP()) then + return + end + + ent:Ignite(dur, 100) + + ent.ignite_info = { att = dmginfo:GetAttacker(), infl = dmginfo:GetInflictor() } + + if ent:IsPlayer() then + timer.Simple(dur + 0.1, function() + if IsValid(ent) then + ent.ignite_info = nil + end + end) + elseif ent:GetClass() == "prop_ragdoll" then + ScorchUnderRagdoll(ent) + + local burn_time = 6 + local tname = Format("ragburn_%d_%d", ent:EntIndex(), math.ceil(CurTime())) + + ent.burn_destroy = CurTime() + burn_time + + timer.Create( + tname, + 0.1, + math.ceil(1 + burn_time / 0.1), -- upper limit, failsafe + function() + RunIgniteTimer(ent, tname) + end + ) + end + end +end + +--- +-- @ignore +function SWEP:ShootFlare() + local cone = self.Primary.Cone + local bullet = {} + bullet.Num = 1 + bullet.Src = self:GetOwner():GetShootPos() + bullet.Dir = self:GetOwner():GetAimVector() + bullet.Spread = Vector(cone, cone, 0) + bullet.Tracer = 1 + bullet.Force = 2 + bullet.Damage = self.Primary.Damage + bullet.TracerName = self.Tracer + bullet.Callback = IgniteTarget + + self:GetOwner():FireBullets(bullet) +end + +--- +-- @ignore +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + if not self:CanPrimaryAttack() then + return + end + + self:EmitSound(self.Primary.Sound) + + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + + self:ShootFlare() + + self:TakePrimaryAmmo(1) + + if IsValid(self:GetOwner()) then + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + + self:GetOwner():ViewPunch( + Angle( + math.Rand(-0.2, -0.1) * self.Primary.Recoil, + math.Rand(-0.1, 0.1) * self.Primary.Recoil, + 0 + ) + ) + end + + if (game.SinglePlayer() and SERVER) or CLIENT then + self:SetNWFloat("LastShootTime", CurTime()) + end +end + +--- +-- @ignore +function SWEP:SecondaryAttack() end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_glock.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_glock.lua index 105dbd47d..c86f7d9d5 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_glock.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_glock.lua @@ -1,18 +1,18 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end SWEP.HoldType = "pistol" if CLIENT then - SWEP.PrintName = "Glock" - SWEP.Slot = 1 + SWEP.PrintName = "Glock" + SWEP.Slot = 1 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_glock" - SWEP.IconLetter = "c" + SWEP.Icon = "vgui/ttt/icon_glock" + SWEP.IconLetter = "c" end SWEP.Base = "weapon_tttbase" @@ -34,6 +34,7 @@ SWEP.AmmoEnt = "item_ammo_pistol_ttt" SWEP.Kind = WEAPON_PISTOL SWEP.WeaponID = AMMO_GLOCK +SWEP.builtin = true SWEP.spawnType = WEAPON_TYPE_PISTOL SWEP.HeadshotMultiplier = 1.75 @@ -41,5 +42,6 @@ SWEP.HeadshotMultiplier = 1.75 SWEP.UseHands = true SWEP.ViewModel = "models/weapons/cstrike/c_pist_glock18.mdl" SWEP.WorldModel = "models/weapons/w_pist_glock18.mdl" +SWEP.idleResetFix = true SWEP.IronSightsPos = Vector(-5.79, -3.9982, 2.8289) diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_health_station.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_health_station.lua new file mode 100644 index 000000000..5fd077c5f --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_health_station.lua @@ -0,0 +1,104 @@ +--- +-- @class SWEP +-- @desc health staion +-- @section weapon_ttt_health_station + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +SWEP.HoldType = "normal" + +if CLIENT then + SWEP.PrintName = "hstation_name" + SWEP.Slot = 6 + + SWEP.ShowDefaultViewModel = false + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "hstation_desc", + } + + SWEP.Icon = "vgui/ttt/icon_health" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.ViewModel = "models/weapons/v_crowbar.mdl" +SWEP.WorldModel = "models/props/cs_office/microwave.mdl" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 1.0 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 1.0 + +-- This is special equipment +SWEP.Kind = WEAPON_EQUIP +SWEP.CanBuy = { ROLE_DETECTIVE } -- only detectives can buy +SWEP.LimitedStock = true -- only buyable once +SWEP.WeaponID = AMMO_HEALTHSTATION + +SWEP.builtin = true + +SWEP.AllowDrop = false +SWEP.NoSights = true + +SWEP.drawColor = Color(180, 180, 250, 255) + +--- +-- @ignore +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + if SERVER then + local health = ents.Create("ttt_health_station") + + if health:ThrowEntity(self:GetOwner(), Angle(90, -90, 0)) then + self:Remove() + end + end +end + +--- +-- @ignore +function SWEP:Reload() + return false +end + +--- +-- @realm shared +function SWEP:Initialize() + if CLIENT then + self:AddTTT2HUDHelp("hstation_help_primary") + end + + self:SetColor(self.drawColor) + + return BaseClass.Initialize(self) +end + +if CLIENT then + --- + -- @realm client + function SWEP:DrawWorldModel() + if IsValid(self:GetOwner()) then + return + end + + self:DrawModel() + end + + --- + -- @realm client + function SWEP:DrawWorldModelTranslucent() end +end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_knife.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_knife.lua index dc4585dfa..dd5d59315 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_knife.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_knife.lua @@ -3,26 +3,27 @@ -- @section weapon_ttt_knife if SERVER then - AddCSLuaFile() + AddCSLuaFile() end +DEFINE_BASECLASS("weapon_tttbase") + SWEP.HoldType = "knife" if CLIENT then - SWEP.PrintName = "knife_name" - SWEP.Slot = 6 + SWEP.PrintName = "knife_name" + SWEP.Slot = 6 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 - SWEP.DrawCrosshair = false + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.EquipMenuData = { - type = "item_weapon", - desc = "knife_desc" - } + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "knife_desc", + } - SWEP.Icon = "vgui/ttt/icon_knife" - SWEP.IconLetter = "j" + SWEP.Icon = "vgui/ttt/icon_knife" + SWEP.IconLetter = "j" end SWEP.Base = "weapon_tttbase" @@ -30,6 +31,7 @@ SWEP.Base = "weapon_tttbase" SWEP.UseHands = true SWEP.ViewModel = "models/weapons/cstrike/c_knife_t.mdl" SWEP.WorldModel = "models/weapons/w_knife_t.mdl" +SWEP.idleResetFix = true SWEP.Primary.Damage = 50 SWEP.Primary.ClipSize = -1 @@ -45,9 +47,10 @@ SWEP.Secondary.Ammo = "none" SWEP.Secondary.Delay = 1.4 SWEP.Kind = WEAPON_EQUIP -SWEP.CanBuy = {ROLE_TRAITOR} -- only traitors can buy +SWEP.CanBuy = { ROLE_TRAITOR } -- only traitors can buy SWEP.LimitedStock = true -- only buyable once SWEP.WeaponID = AMMO_KNIFE +SWEP.builtin = true SWEP.IsSilent = true @@ -57,85 +60,86 @@ SWEP.DeploySpeed = 2 --- -- @ignore function SWEP:PrimaryAttack() - local owner = self:GetOwner() - - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) - - if not IsValid(owner) then return end - - owner:LagCompensation(true) - - local spos = owner:GetShootPos() - local sdest = spos + owner:GetAimVector() * 100 - - local kmins = Vector(-10, -10, -10) - local kmaxs = Vector(10, 10, 10) - - local tr = util.TraceHull({ - start = spos, - endpos = sdest, - filter = owner, - mask = MASK_SHOT_HULL, - mins = kmins, - maxs = kmaxs - }) - - -- Hull might hit environment stuff that line does not hit - if not IsValid(tr.Entity) then - tr = util.TraceLine({ - start = spos, - endpos = sdest, - filter = owner, - mask = MASK_SHOT_HULL - }) - end - - local hitEnt = tr.Entity - - -- effects - if IsValid(hitEnt) then - self:SendWeaponAnim(ACT_VM_HITCENTER) - - local edata = EffectData() - edata:SetStart(spos) - edata:SetOrigin(tr.HitPos) - edata:SetNormal(tr.Normal) - edata:SetEntity(hitEnt) - - if hitEnt:IsPlayer() or hitEnt:GetClass() == "prop_ragdoll" then - util.Effect("BloodImpact", edata) - end - else - self:SendWeaponAnim(ACT_VM_MISSCENTER) - end - - if SERVER then - owner:SetAnimation(PLAYER_ATTACK1) - end - - - if SERVER and tr.Hit and tr.HitNonWorld and IsValid(hitEnt) and hitEnt:IsPlayer() then - -- knife damage is never karma'd, so don't need to take that into - -- account we do want to avoid rounding error strangeness caused by - -- other damage scaling, causing a death when we don't expect one, so - -- when the target's health is close to kill-point we just kill - if hitEnt:Health() < (self.Primary.Damage + 10) then - self:StabKill(tr, spos, sdest) - else - local dmg = DamageInfo() - dmg:SetDamage(self.Primary.Damage) - dmg:SetAttacker(owner) - dmg:SetInflictor(self) - dmg:SetDamageForce(owner:GetAimVector() * 5) - dmg:SetDamagePosition(owner:GetPos()) - dmg:SetDamageType(DMG_SLASH) - - hitEnt:DispatchTraceAttack(dmg, spos + (owner:GetAimVector() * 3), sdest) - end - end - - owner:LagCompensation(false) + local owner = self:GetOwner() + + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + + if not IsValid(owner) then + return + end + + owner:LagCompensation(true) + + local spos = owner:GetShootPos() + local sdest = spos + owner:GetAimVector() * 100 + + local kmins = Vector(-10, -10, -10) + local kmaxs = Vector(10, 10, 10) + + local tr = util.TraceHull({ + start = spos, + endpos = sdest, + filter = owner, + mask = MASK_SHOT_HULL, + mins = kmins, + maxs = kmaxs, + }) + + -- Hull might hit environment stuff that line does not hit + if not IsValid(tr.Entity) then + tr = util.TraceLine({ + start = spos, + endpos = sdest, + filter = owner, + mask = MASK_SHOT_HULL, + }) + end + + local hitEnt = tr.Entity + + -- effects + if IsValid(hitEnt) then + self:SendWeaponAnim(ACT_VM_HITCENTER) + + local edata = EffectData() + edata:SetStart(spos) + edata:SetOrigin(tr.HitPos) + edata:SetNormal(tr.Normal) + edata:SetEntity(hitEnt) + + if hitEnt:IsPlayer() or hitEnt:GetClass() == "prop_ragdoll" then + util.Effect("BloodImpact", edata) + end + else + self:SendWeaponAnim(ACT_VM_MISSCENTER) + end + + if SERVER then + owner:SetAnimation(PLAYER_ATTACK1) + end + + if SERVER and tr.Hit and tr.HitNonWorld and IsValid(hitEnt) and hitEnt:IsPlayer() then + -- knife damage is never karma'd, so don't need to take that into + -- account we do want to avoid rounding error strangeness caused by + -- other damage scaling, causing a death when we don't expect one, so + -- when the target's health is close to kill-point we just kill + if hitEnt:Health() < (self.Primary.Damage + 10) then + self:StabKill(tr, spos, sdest) + else + local dmg = DamageInfo() + dmg:SetDamage(self.Primary.Damage) + dmg:SetAttacker(owner) + dmg:SetInflictor(self) + dmg:SetDamageForce(owner:GetAimVector() * 5) + dmg:SetDamagePosition(owner:GetPos()) + dmg:SetDamageType(DMG_SLASH) + + hitEnt:DispatchTraceAttack(dmg, spos + (owner:GetAimVector() * 3), sdest) + end + end + + owner:LagCompensation(false) end --- @@ -144,228 +148,238 @@ end -- @param Vector sdest -- @realm shared function SWEP:StabKill(tr, spos, sdest) - local owner = self:GetOwner() - local target = tr.Entity - - local dmg = DamageInfo() - dmg:SetDamage(2000) - dmg:SetAttacker(owner) - dmg:SetInflictor(self) - dmg:SetDamageForce(owner:GetAimVector()) - dmg:SetDamagePosition(owner:GetPos()) - dmg:SetDamageType(DMG_SLASH) - - -- now that we use a hull trace, our hitpos is guaranteed to be - -- terrible, so try to make something of it with a separate trace and - -- hope our effect_fn trace has more luck - - -- first a straight up line trace to see if we aimed nicely - local retr = util.TraceLine({ - start = spos, - endpos = sdest, - filter = owner, - mask = MASK_SHOT_HULL - }) - - -- if that fails, just trace to worldcenter so we have SOMETHING - if retr.Entity ~= target then - local center = target:LocalToWorld(target:OBBCenter()) - - retr = util.TraceLine({ - start = spos, - endpos = center, - filter = owner, - mask = MASK_SHOT_HULL - }) - end - - -- create knife effect creation fn - local bone = retr.PhysicsBone - local pos = retr.HitPos - local norm = tr.Normal - local ang = Angle(-28, 0, 0) + norm:Angle() - - ang:RotateAroundAxis(ang:Right(), -90) - - pos = pos - (ang:Forward() * 7) - - target.effect_fn = function(rag) - -- we might find a better location - local rtr = util.TraceLine({ - start = pos, - endpos = pos + norm * 40, - filter = owner, - mask = MASK_SHOT_HULL - }) - - if IsValid(rtr.Entity) and rtr.Entity == rag then - bone = rtr.PhysicsBone - pos = rtr.HitPos - - ang = Angle(-28, 0, 0) + rtr.Normal:Angle() - ang:RotateAroundAxis(ang:Right(), -90) - - pos = pos - (ang:Forward() * 10) - end - - local knife = ents.Create("prop_physics") - knife:SetModel("models/weapons/w_knife_t.mdl") - knife:SetPos(pos) - knife:SetCollisionGroup(COLLISION_GROUP_DEBRIS) - knife:SetAngles(ang) - - knife.CanPickup = false - - knife:Spawn() - - local phys = knife:GetPhysicsObject() - - if IsValid(phys) then - phys:EnableCollisions(false) - end - - constraint.Weld(rag, knife, bone, 0, 0, true) - - -- need to close over knife in order to keep a valid ref to it - rag:CallOnRemove("ttt_knife_cleanup", function() - SafeRemoveEntity(knife) - end) - end - - -- seems the spos and sdest are purely for effects/forces? - target:DispatchTraceAttack(dmg, spos + (owner:GetAimVector() * 3), sdest) - - -- target appears to die right there, so we could theoretically get to - -- the ragdoll in here... - self:Remove() + local owner = self:GetOwner() + local target = tr.Entity + + local dmg = DamageInfo() + dmg:SetDamage(2000) + dmg:SetAttacker(owner) + dmg:SetInflictor(self) + dmg:SetDamageForce(owner:GetAimVector()) + dmg:SetDamagePosition(owner:GetPos()) + dmg:SetDamageType(DMG_SLASH) + + -- now that we use a hull trace, our hitpos is guaranteed to be + -- terrible, so try to make something of it with a separate trace and + -- hope our effect_fn trace has more luck + + -- first a straight up line trace to see if we aimed nicely + local retr = util.TraceLine({ + start = spos, + endpos = sdest, + filter = owner, + mask = MASK_SHOT_HULL, + }) + + -- if that fails, just trace to worldcenter so we have SOMETHING + if retr.Entity ~= target then + local center = target:LocalToWorld(target:OBBCenter()) + + retr = util.TraceLine({ + start = spos, + endpos = center, + filter = owner, + mask = MASK_SHOT_HULL, + }) + end + + -- create knife effect creation fn + local bone = retr.PhysicsBone + local pos = retr.HitPos + local norm = tr.Normal + local ang = Angle(-28, 0, 0) + norm:Angle() + + ang:RotateAroundAxis(ang:Right(), -90) + + pos = pos - (ang:Forward() * 7) + + target.effect_fn = function(rag) + -- we might find a better location + local rtr = util.TraceLine({ + start = pos, + endpos = pos + norm * 40, + filter = owner, + mask = MASK_SHOT_HULL, + }) + + if IsValid(rtr.Entity) and rtr.Entity == rag then + bone = rtr.PhysicsBone + pos = rtr.HitPos + + ang = Angle(-28, 0, 0) + rtr.Normal:Angle() + ang:RotateAroundAxis(ang:Right(), -90) + + pos = pos - (ang:Forward() * 10) + end + + local knife = ents.Create("prop_physics") + knife:SetModel("models/weapons/w_knife_t.mdl") + knife:SetPos(pos) + knife:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + knife:SetAngles(ang) + + knife.CanPickup = false + + knife:Spawn() + + local phys = knife:GetPhysicsObject() + + if IsValid(phys) then + phys:EnableCollisions(false) + end + + constraint.Weld(rag, knife, bone, 0, 0, true) + + -- need to close over knife in order to keep a valid ref to it + rag:CallOnRemove("ttt_knife_cleanup", function() + SafeRemoveEntity(knife) + end) + end + + -- seems the spos and sdest are purely for effects/forces? + target:DispatchTraceAttack(dmg, spos + (owner:GetAimVector() * 3), sdest) + + -- target appears to die right there, so we could theoretically get to + -- the ragdoll in here... + self:Remove() end --- -- @ignore function SWEP:SecondaryAttack() - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) - self:SendWeaponAnim(ACT_VM_MISSCENTER) + self:SendWeaponAnim(ACT_VM_MISSCENTER) - if CLIENT then return end + if CLIENT then + return + end - local ply = self:GetOwner() + local ply = self:GetOwner() - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply:SetAnimation(PLAYER_ATTACK1) + ply:SetAnimation(PLAYER_ATTACK1) - local ang = ply:EyeAngles() + local ang = ply:EyeAngles() - if ang.p < 90 then - ang.p = -10 + ang.p * ((90 + 10) / 90) - else - ang.p = 360 - ang.p - ang.p = -10 + ang.p * -((90 + 10) / 90) - end + if ang.p < 90 then + ang.p = -10 + ang.p * ((90 + 10) / 90) + else + ang.p = 360 - ang.p + ang.p = -10 + ang.p * -((90 + 10) / 90) + end - local vel = math.Clamp((90 - ang.p) * 5.5, 550, 800) - local vfw = ang:Forward() - local vrt = ang:Right() + local vel = math.Clamp((90 - ang.p) * 5.5, 550, 800) + local vfw = ang:Forward() + local vrt = ang:Right() - local src = ply:GetPos() + (ply:Crouching() and ply:GetViewOffsetDucked() or ply:GetViewOffset()) - src = src + (vfw * 1) + (vrt * 3) + local src = ply:GetPos() + + (ply:Crouching() and ply:GetViewOffsetDucked() or ply:GetViewOffset()) + src = src + (vfw * 1) + (vrt * 3) - local thr = vfw * vel + ply:GetVelocity() + local thr = vfw * vel + ply:GetVelocity() - local knife_ang = Angle(-28, 0, 0) + ang - knife_ang:RotateAroundAxis(knife_ang:Right(), -90) + local knife_ang = Angle(-28, 0, 0) + ang + knife_ang:RotateAroundAxis(knife_ang:Right(), -90) - local knife = ents.Create("ttt_knife_proj") + local knife = ents.Create("ttt_knife_proj") - if not IsValid(knife) then return end + if not IsValid(knife) then + return + end - knife:SetPos(src) - knife:SetAngles(knife_ang) - knife:Spawn() - knife:SetOwner(ply) + knife:SetPos(src) + knife:SetAngles(knife_ang) + knife:Spawn() + knife:SetOwner(ply) - knife.Damage = self.Primary.Damage + knife.Damage = self.Primary.Damage - local phys = knife:GetPhysicsObject() + local phys = knife:GetPhysicsObject() - if IsValid(phys) then - phys:SetVelocity(thr) - phys:AddAngleVelocity(Vector(0, 1500, 0)) - phys:Wake() - end + if IsValid(phys) then + phys:SetVelocity(thr) + phys:AddAngleVelocity(Vector(0, 1500, 0)) + phys:Wake() + end - self:Remove() + self:Remove() end if SERVER then - --- - -- @ignore - function SWEP:Equip() - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay * 1.5) - self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay * 1.5) - end - - --- - -- @ignore - function SWEP:PreDrop() - -- for consistency, dropped knife should not have DNA/prints - self.fingerprints = {} - end -end - ---- --- @ignore -function SWEP:OnRemove() - if SERVER then return end - - local owner = self:GetOwner() - - if IsValid(owner) and owner == LocalPlayer() and owner:Alive() then - RunConsoleCommand("lastinv") - end + --- + -- @ignore + function SWEP:Equip() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay * 1.5) + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay * 1.5) + end + + --- + -- @ignore + function SWEP:PreDrop() + -- for consistency, dropped knife should not have DNA/prints + self.fingerprints = {} + end end if CLIENT then - local TryT = LANG.TryTranslation - - hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDKnife", function(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - - if not IsValid(client) or not client:IsTerror() or not client:Alive() - or tData:GetEntityDistance() > 100 or not ent:IsPlayer() then - return - end - - local c_wep = client:GetActiveWeapon() - local role_color = client:GetRoleColor() - - if not IsValid(c_wep) or c_wep:GetClass() ~= "weapon_ttt_knife" or c_wep.Primary.Damage + 10 < ent:Health() then return end - - -- enable targetID rendering - tData:EnableOutline() - tData:SetOutlineColor(client:GetRoleColor()) - - tData:AddDescriptionLine( - TryT("knife_instant"), - role_color - ) - - -- draw instant-kill maker - local x = ScrW() * 0.5 - local y = ScrH() * 0.5 - - surface.SetDrawColor(clr(role_color)) - - local outer = 20 - local inner = 10 - - surface.DrawLine(x - outer, y - outer, x - inner, y - inner) - surface.DrawLine(x + outer, y + outer, x + inner, y + inner) - - surface.DrawLine(x - outer, y + outer, x - inner, y + inner) - surface.DrawLine(x + outer, y - outer, x + inner, y - inner) - end) + local TryT = LANG.TryTranslation + + --- + -- @ignore + function SWEP:Initialize() + self:AddTTT2HUDHelp("knife_help_primary", "knife_help_secondary") + return BaseClass.Initialize(self) + end + + hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDKnife", function(tData) + local client = LocalPlayer() + local ent = tData:GetEntity() + + if + not IsValid(client) + or not client:IsTerror() + or not client:Alive() + or tData:GetEntityDistance() > 100 + or not ent:IsPlayer() + then + return + end + + local c_wep = client:GetActiveWeapon() + local role_color = client:GetRoleColor() + + if + not IsValid(c_wep) + or c_wep:GetClass() ~= "weapon_ttt_knife" + or c_wep.Primary.Damage + 10 < ent:Health() + then + return + end + + -- enable targetID rendering + tData:EnableOutline() + tData:SetOutlineColor(client:GetRoleColor()) + + tData:AddDescriptionLine(TryT("knife_instant"), role_color) + + -- draw instant-kill maker + local x = ScrW() * 0.5 + local y = ScrH() * 0.5 + + surface.SetDrawColor(clr(role_color)) + + local outer = 20 + local inner = 10 + + surface.DrawLine(x - outer, y - outer, x - inner, y - inner) + surface.DrawLine(x + outer, y + outer, x + inner, y + inner) + + surface.DrawLine(x - outer, y + outer, x - inner, y + inner) + surface.DrawLine(x + outer, y - outer, x + inner, y - inner) + end) end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_m16.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_m16.lua index e0fe81caf..9f85f470f 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_m16.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_m16.lua @@ -1,24 +1,27 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end +DEFINE_BASECLASS("weapon_tttbase") + SWEP.HoldType = "ar2" if CLIENT then - SWEP.PrintName = "M16" - SWEP.Slot = 2 + SWEP.PrintName = "M16" + SWEP.Slot = 2 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 64 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 64 - SWEP.Icon = "vgui/ttt/icon_m16" - SWEP.IconLetter = "w" + SWEP.Icon = "vgui/ttt/icon_m16" + SWEP.IconLetter = "w" end SWEP.Base = "weapon_tttbase" SWEP.Kind = WEAPON_HEAVY SWEP.WeaponID = AMMO_M16 +SWEP.builtin = true SWEP.spawnType = WEAPON_TYPE_HEAVY SWEP.Primary.Delay = 0.19 @@ -39,6 +42,7 @@ SWEP.AmmoEnt = "item_ammo_pistol_ttt" SWEP.UseHands = true SWEP.ViewModel = "models/weapons/cstrike/c_rif_m4a1.mdl" SWEP.WorldModel = "models/weapons/w_rif_m4a1.mdl" +SWEP.idleResetFix = true SWEP.IronSightsPos = Vector(-7.58, -9.2, 0.55) SWEP.IronSightsAng = Vector(2.599, -1.3, -3.6) @@ -46,54 +50,65 @@ SWEP.IronSightsAng = Vector(2.599, -1.3, -3.6) --- -- @ignore function SWEP:SetZoom(state) - local owner = self:GetOwner() + local owner = self:GetOwner() - if not IsValid(owner) or not owner:IsPlayer() then return end + if not IsValid(owner) or not owner:IsPlayer() then + return + end - if state then - owner:SetFOV(35, 0.5) - else - owner:SetFOV(0, 0.2) - end + if state then + owner:SetFOV(42, 0.5) + else + owner:SetFOV(0, 0.2) + end end --- -- Add some zoom to ironsights for this gun -- @ignore function SWEP:SecondaryAttack() - if not self.IronSightsPos or self:GetNextSecondaryFire() > CurTime() then return end + if not self.IronSightsPos or self:GetNextSecondaryFire() > CurTime() then + return + end + + local bIronsights = not self:GetIronsights() - local bIronsights = not self:GetIronsights() + self:SetIronsights(bIronsights) + self:SetZoom(bIronsights) - self:SetIronsights(bIronsights) - self:SetZoom(bIronsights) - self:SetNextSecondaryFire(CurTime() + 0.3) + self:SetNextSecondaryFire(CurTime() + 0.3) end --- -- @ignore function SWEP:PreDrop() - self:SetZoom(false) - self:SetIronsights(false) + self:SetIronsights(false) + self:SetZoom(false) - return self.BaseClass.PreDrop(self) + return BaseClass.PreDrop(self) end --- -- @ignore function SWEP:Reload() - if self:Clip1() == self.Primary.ClipSize or self:GetOwner():GetAmmoCount(self.Primary.Ammo) <= 0 then return end + if + self:Clip1() == self.Primary.ClipSize + or self:GetOwner():GetAmmoCount(self.Primary.Ammo) <= 0 + then + return + end + + self:DefaultReload(ACT_VM_RELOAD) - self:DefaultReload(ACT_VM_RELOAD) - self:SetIronsights(false) - self:SetZoom(false) + self:SetIronsights(false) + self:SetZoom(false) end --- -- @ignore function SWEP:Holster() - self:SetIronsights(false) - self:SetZoom(false) + self:SetIronsights(false) + self:SetZoom(false) - return true + return true end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_phammer.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_phammer.lua new file mode 100644 index 000000000..cba074dc0 --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_phammer.lua @@ -0,0 +1,458 @@ +--- +-- @class SWEP +-- @section weapon_ttt_phammer + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +SWEP.HoldType = "ar2" + +if CLIENT then + SWEP.PrintName = "polter_name" + SWEP.Slot = 7 + + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "polter_desc", + } + + SWEP.Icon = "vgui/ttt/icon_polter" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.Primary.Recoil = 0.1 +SWEP.Primary.Delay = 12.0 +SWEP.Primary.Cone = 0.02 +SWEP.Primary.ClipSize = 6 +SWEP.Primary.DefaultClip = 6 +SWEP.Primary.ClipMax = 6 +SWEP.Primary.Ammo = "Gravity" +SWEP.Primary.Automatic = false +SWEP.Primary.Sound = Sound("weapons/airboat/airboat_gun_energy1.wav") + +SWEP.Secondary.Automatic = false + +SWEP.Kind = WEAPON_EQUIP2 +SWEP.CanBuy = { ROLE_TRAITOR } -- only traitors can buy +SWEP.WeaponID = AMMO_POLTER +SWEP.builtin = true + +SWEP.UseHands = true +SWEP.ViewModel = "models/weapons/c_irifle.mdl" +SWEP.WorldModel = "models/weapons/w_IRifle.mdl" + +SWEP.NoSights = true + +SWEP.IsCharging = false +SWEP.NextCharge = 0 +SWEP.MaxRange = 800 + +local math = math + +-- Returns if an entity is a valid physhammer punching target. Does not take +-- distance into account. +local function ValidTarget(ent) + -- NOTE: cannot check for motion disabled on client + return IsValid(ent) + and ent:GetMoveType() == MOVETYPE_VPHYSICS + and ent:GetPhysicsObject() + and not ent:IsWeapon() + and not ent:GetNWBool("punched", false) + and not ent:IsPlayer() +end + +--- +-- @ignore +function SWEP:SetupDataTables() + self:NetworkVar("Float", 0, "Charge") +end + +local ghostmdl = Model("models/Items/combine_rifle_ammo01.mdl") + +--- +-- @ignore +function SWEP:Initialize() + if CLIENT then + -- create ghosted indicator + local ghost = ents.CreateClientProp(ghostmdl) + if IsValid(ghost) then + ghost:SetPos(self:GetPos()) + ghost:Spawn() + + -- PhysPropClientside whines here about not being able to parse the + -- physmodel. This is not important as we won't use that anyway, and it + -- happens in sandbox as well for the ghosted ents used there. + ghost:SetSolid(SOLID_NONE) + ghost:SetMoveType(MOVETYPE_NONE) + ghost:SetNotSolid(true) + ghost:SetRenderMode(RENDERMODE_TRANSCOLOR) + ghost:AddEffects(EF_NOSHADOW) + ghost:SetNoDraw(true) + + self.Ghost = ghost + end + self:AddTTT2HUDHelp("polter_help_primary", "polter_help_secondary") + end + + self.IsCharging = false + self:SetCharge(0) + + return BaseClass.Initialize(self) +end + +--- +-- @ignore +function SWEP:PreDrop() + self.IsCharging = false + self:SetCharge(0) + + -- OnDrop does not happen on client + self:CallOnClient("HideGhost", "") +end + +--- +-- @ignore +function SWEP:HideGhost() + if IsValid(self.Ghost) then + self.Ghost:SetNoDraw(true) + end +end + +--- +-- @ignore +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + 0.1) + + if not self:CanPrimaryAttack() or IsValid(self.hammer) then + return + end + + if SERVER then + if self.IsCharging then + return + end + + local ply = self:GetOwner() + if not IsValid(ply) then + return + end + + local tr = util.TraceLine({ + start = ply:GetShootPos(), + endpos = ply:GetShootPos() + ply:GetAimVector() * self.MaxRange, + filter = { ply, self.Entity }, + mask = MASK_SOLID, + }) + + if + tr.HitNonWorld + and ValidTarget(tr.Entity) + and tr.Entity:GetPhysicsObject():IsMoveable() + then + self:CreateHammer(tr.Entity, tr.HitPos) + self:EmitSound(self.Primary.Sound) + self:TakePrimaryAmmo(1) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + end + end +end + +--- +-- @ignore +function SWEP:SecondaryAttack() + if self.IsCharging then + return + end + + self:SetNextSecondaryFire(CurTime() + 0.1) + + if not (self:CanPrimaryAttack() and (self:GetNextPrimaryFire() - CurTime()) <= 0) then + return + end + if IsValid(self.hammer) then + return + end + + if SERVER then + local ply = self:GetOwner() + if not IsValid(ply) then + return + end + + local range = 30000 + + local tr = util.TraceLine({ + start = ply:GetShootPos(), + endpos = ply:GetShootPos() + ply:GetAimVector() * range, + filter = { ply, self.Entity }, + mask = MASK_SOLID, + }) + + if + tr.HitNonWorld + and ValidTarget(tr.Entity) + and tr.Entity:GetPhysicsObject():IsMoveable() + then + if self.IsCharging and self:GetCharge() >= 1 then + return + elseif tr.Fraction * range > self.MaxRange then + self.IsCharging = true + end + end + end +end + +--- +-- @ignore +function SWEP:CreateHammer(tgt, pos) + local hammer = ents.Create("ttt_physhammer") + + if not IsValid(hammer) then + return + end + + local ang = self:GetOwner():GetAimVector():Angle() + ang:RotateAroundAxis(ang:Right(), 90) + + hammer:SetPos(pos) + hammer:SetAngles(ang) + hammer:Spawn() + hammer:SetOwner(self:GetOwner()) + + local stuck = hammer:StickTo(tgt) + + if not stuck then + hammer:Remove() + end + + self.hammer = hammer +end + +--- +-- @realm shared +function SWEP:OnRemove() + BaseClass.OnRemove(self) + + if CLIENT and IsValid(self.Ghost) then + self.Ghost:Remove() + end + + self.IsCharging = false + self:SetCharge(0) +end + +--- +-- @ignore +function SWEP:Holster() + if CLIENT and IsValid(self.Ghost) then + self.Ghost:SetNoDraw(true) + end + + self.IsCharging = false + self:SetCharge(0) + + return true +end + +if SERVER then + local CHARGE_AMOUNT = 0.015 + local CHARGE_DELAY = 0.025 + + --- + -- @ignore + function SWEP:Think() + BaseClass.Think(self) + + if not IsValid(self:GetOwner()) then + return + end + + if self.IsCharging and self:GetOwner():KeyDown(IN_ATTACK2) then + local tr = self:GetOwner():GetEyeTrace(MASK_SOLID) + + if tr.HitNonWorld and ValidTarget(tr.Entity) then + if self:GetCharge() >= 1 then + self:CreateHammer(tr.Entity, tr.HitPos) + + self:EmitSound(self.Primary.Sound) + + self:TakePrimaryAmmo(1) + + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + self.IsCharging = false + self:SetCharge(0) + + return true + elseif self.NextCharge < CurTime() then + local d = tr.Entity:GetPos():Distance(self:GetOwner():GetPos()) + local f = math.max(1, math.floor(d / self.MaxRange)) + + self:SetCharge(math.min(1, self:GetCharge() + (CHARGE_AMOUNT / f))) + + self.NextCharge = CurTime() + CHARGE_DELAY + end + else + self.IsCharging = false + self:SetCharge(0) + end + elseif self:GetCharge() > 0 then + -- owner let go of rmouse + self:SetCharge(0) + self.IsCharging = false + end + end +end + +local function around(val) + return math.Round(val * (10 ^ 3)) / (10 ^ 3) +end + +if CLIENT then + local surface = surface + + --- + -- @ignore + function SWEP:UpdateGhost(pos, c, a) + if IsValid(self.Ghost) and self.Ghost:GetPos() ~= pos then + self.Ghost:SetPos(pos) + local ang = LocalPlayer():GetAimVector():Angle() + ang:RotateAroundAxis(ang:Right(), 90) + + self.Ghost:SetAngles(ang) + + self.Ghost:SetColor(Color(c.r, c.g, c.b, a)) + + self.Ghost:SetNoDraw(false) + end + end + + local linex = 0 + local liney = 0 + local laser = Material("trails/laser") + + --- + -- @ignore + function SWEP:ViewModelDrawn() + BaseClass.ViewModelDrawn(self) + + local client = LocalPlayer() + local vm = client:GetViewModel() + if not IsValid(vm) then + return + end + + local plytr = client:GetEyeTrace() + + local muzzle_angpos = vm:GetAttachment(1) + local spos = muzzle_angpos.Pos + muzzle_angpos.Ang:Forward() * 10 + local epos = client:GetShootPos() + client:GetAimVector() * self.MaxRange + + -- Painting beam + local tr = util.TraceLine({ + start = spos, + endpos = epos, + filter = client, + mask = MASK_ALL, + }) + + local c = COLOR_RED + local a = 150 + local d = (plytr.StartPos - plytr.HitPos):Length() + if plytr.HitNonWorld and ValidTarget(plytr.Entity) then + if d < self.MaxRange then + c = COLOR_GREEN + a = 255 + else + c = COLOR_YELLOW + end + end + + self:UpdateGhost(plytr.HitPos, c, a) + + render.SetMaterial(laser) + render.DrawBeam(spos, tr.HitPos, 5, 0, 0, c) + + -- Charge indicator + local vm_ang = muzzle_angpos.Ang + local cpos = muzzle_angpos.Pos + + (vm_ang:Up() * -8) + + (vm_ang:Forward() * -5.5) + + (vm_ang:Right() * 0) + local cang = vm:GetAngles() + cang:RotateAroundAxis(cang:Forward(), 90) + cang:RotateAroundAxis(cang:Right(), 90) + cang:RotateAroundAxis(cang:Up(), 90) + + cam.Start3D2D(cpos, cang, 0.05) + + surface.SetDrawColor(255, 55, 55, 50) + surface.DrawOutlinedRect(0, 0, 50, 15, 1) + + local sz = 48 + local next = self:GetNextPrimaryFire() + local ready = (next - CurTime()) <= 0 + local frac = 1.0 + if not ready then + frac = 1 - ((next - CurTime()) / self.Primary.Delay) + sz = sz * math.max(0, frac) + end + + surface.SetDrawColor(255, 10, 10, 170) + surface.DrawRect(1, 1, sz, 13) + + surface.SetTextColor(255, 255, 255, 15) + surface.SetFont("Default") + surface.SetTextPos(2, 0) + surface.DrawText(string.format("%.3f", around(frac))) + + surface.SetDrawColor(0, 0, 0, 80) + surface.DrawRect(linex, 1, 3, 13) + + surface.DrawLine(1, liney, 48, liney) + + linex = linex + 3 > 48 and 0 or linex + 1 + liney = liney > 13 and 0 or liney + 1 + + cam.End3D2D() + end + + --- + -- @ignore + function SWEP:DrawHUD() + self:DrawHelp() + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + + local charge = self:GetCharge() + + if charge > 0 then + y = y + (y / 3) + + local w, h = 100, 20 + + surface.DrawOutlinedRect(x - w / 2, y - h, w, h, 1) + + if LocalPlayer():IsTraitor() then + surface.SetDrawColor(255, 0, 0, 155) + else + surface.SetDrawColor(0, 255, 0, 155) + end + + surface.DrawRect(x - w / 2, y - h, w * charge, h) + + surface.SetFont("TabLarge") + surface.SetTextColor(255, 255, 255, 180) + surface.SetTextPos((x - w / 2) + 3, y - h - 15) + surface.DrawText("CHARGE") + end + end +end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_push.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_push.lua new file mode 100644 index 000000000..9c5f7c143 --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_push.lua @@ -0,0 +1,311 @@ +--- +-- @class SWEP +-- @section weapon_ttt_push + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +SWEP.HoldType = "physgun" + +if CLIENT then + SWEP.PrintName = "newton_name" + SWEP.Slot = 7 + + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "newton_desc", + } + + SWEP.Icon = "vgui/ttt/icon_launch" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.Primary.Ammo = "none" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Delay = 3 +SWEP.Primary.Cone = 0.005 +SWEP.Primary.Sound = Sound("weapons/ar2/fire1.wav") +SWEP.Primary.SoundLevel = 54 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 0.5 + +SWEP.NoSights = true + +SWEP.Kind = WEAPON_EQUIP2 +SWEP.CanBuy = { ROLE_TRAITOR } +SWEP.WeaponID = AMMO_PUSH +SWEP.builtin = true + +SWEP.UseHands = true +SWEP.ViewModel = "models/weapons/c_superphyscannon.mdl" +SWEP.WorldModel = "models/weapons/w_physics.mdl" + +SWEP.IsCharging = false +SWEP.NextCharge = 0 + +local CHARGE_AMOUNT = 0.02 +local CHARGE_DELAY = 0.025 + +local math = math + +--- +-- @realm shared +function SWEP:Initialize() + if SERVER then + self:SetSkin(1) + end + + if CLIENT then + self:AddTTT2HUDHelp("newton_help_primary", "newton_help_secondary") + end + + self.IsCharging = false + self:SetCharge(0) + + return BaseClass.Initialize(self) +end + +--- +-- @realm shared +function SWEP:SetupDataTables() + self:NetworkVar("Float", 0, "Charge") +end + +--- +-- @realm shared +function SWEP:PrimaryAttack() + if self.IsCharging then + return + end + + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + + self:FirePulse(600, 300) +end + +--- +-- @realm shared +function SWEP:SecondaryAttack() + if self.IsCharging then + return + end + + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + + self.IsCharging = true +end + +--- +-- @ignore +function SWEP:FirePulse(force_fwd, force_up) + if not IsValid(self:GetOwner()) then + return + end + + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + + sound.Play(self.Primary.Sound, self:GetPos(), self.Primary.SoundLevel) + + self:SendWeaponAnim(ACT_VM_IDLE) + + local cone = self.Primary.Cone or 0.1 + local num = 6 + + local bullet = {} + bullet.Num = num + bullet.Src = self:GetOwner():GetShootPos() + bullet.Dir = self:GetOwner():GetAimVector() + bullet.Spread = Vector(cone, cone, 0) + bullet.Tracer = 1 + bullet.Force = force_fwd / 10 + bullet.Damage = 1 + bullet.TracerName = "AirboatGunHeavyTracer" + + local owner = self:GetOwner() + local fwd = force_fwd / num + local up = force_up / num + + bullet.Callback = function(att, tr, dmginfo) + local ply = tr.Entity + if SERVER and IsValid(ply) and ply:IsPlayer() and (not ply:IsFrozen()) then + local pushvel = tr.Normal * fwd + + pushvel.z = math.max(pushvel.z, up) + + ply:SetGroundEntity(nil) + ply:SetLocalVelocity(ply:GetVelocity() + pushvel) + + ply.was_pushed = { + att = owner, + t = CurTime(), + wep = self:GetClass(), + } + end + end + + self:GetOwner():FireBullets(bullet) +end + +local CHARGE_FORCE_FWD_MIN = 300 +local CHARGE_FORCE_FWD_MAX = 700 +local CHARGE_FORCE_UP_MIN = 100 +local CHARGE_FORCE_UP_MAX = 350 + +--- +-- @ignore +function SWEP:ChargedAttack() + local charge = math.Clamp(self:GetCharge(), 0, 1) + + self.IsCharging = false + self:SetCharge(0) + + if charge <= 0 then + return + end + + local max = CHARGE_FORCE_FWD_MAX + local diff = max - CHARGE_FORCE_FWD_MIN + + local force_fwd = ((charge * diff) - diff) + max + + max = CHARGE_FORCE_UP_MAX + diff = max - CHARGE_FORCE_UP_MIN + + local force_up = ((charge * diff) - diff) + max + + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + self:FirePulse(force_fwd, force_up) +end + +--- +-- @ignore +function SWEP:PreDrop() + self.IsCharging = false + self:SetCharge(0) +end + +--- +-- @realm shared +function SWEP:OnRemove() + BaseClass.OnRemove(self) + + self.IsCharging = false + self:SetCharge(0) +end + +--- +-- @ignore +function SWEP:Deploy() + self.IsCharging = false + self:SetCharge(0) + + return true +end + +--- +-- @ignore +function SWEP:Holster() + return not self.IsCharging +end + +--- +-- @ignore +function SWEP:Think() + BaseClass.Think(self) + + if self.IsCharging and IsValid(self:GetOwner()) and self:GetOwner():IsTerror() then + -- on client this is prediction + if not self:GetOwner():KeyDown(IN_ATTACK2) then + self:ChargedAttack() + return true + end + + if SERVER and self:GetCharge() < 1 and self.NextCharge < CurTime() then + self:SetCharge(math.min(1, self:GetCharge() + CHARGE_AMOUNT)) + + self.NextCharge = CurTime() + CHARGE_DELAY + end + end +end + +if CLIENT then + local surface = surface + local TryT = LANG.TryTranslation + + --- + -- @ignore + function SWEP:DrawHUD() + self:DrawHelp() + + local x = ScrW() / 2.0 + local y = ScrH() / 2.0 + + local nxt = self:GetNextPrimaryFire() + local charge = self:GetCharge() + + if LocalPlayer():IsTraitor() then + surface.SetDrawColor(255, 0, 0, 255) + else + surface.SetDrawColor(0, 255, 0, 255) + end + + if nxt < CurTime() or CurTime() % 0.5 < 0.2 or charge > 0 then + local length = 10 + local gap = 5 + + surface.DrawLine(x - length, y, x - gap, y) + surface.DrawLine(x + length, y, x + gap, y) + surface.DrawLine(x, y - length, x, y - gap) + surface.DrawLine(x, y + length, x, y + gap) + end + + if nxt > CurTime() and charge == 0 then + local w = 40 + + w = (w * (math.max(0, nxt - CurTime()) / self.Primary.Delay)) / 2 + + local bx = x + 30 + surface.DrawLine(bx, y - w, bx, y + w) + + bx = x - 30 + surface.DrawLine(bx, y - w, bx, y + w) + end + + if charge > 0 then + y = y + (y / 3) + + local w, h = 100, 20 + + surface.DrawOutlinedRect(x - w / 2, y - h, w, h, 1) + + if LocalPlayer():IsTraitor() then + surface.SetDrawColor(255, 0, 0, 155) + else + surface.SetDrawColor(0, 255, 0, 155) + end + + surface.DrawRect(x - w / 2, y - h, w * charge, h) + + surface.SetFont("TabLarge") + surface.SetTextColor(255, 255, 255, 180) + surface.SetTextPos((x - w / 2) + 3, y - h - 15) + surface.DrawText(TryT("newton_force")) + end + end +end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_radio.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_radio.lua new file mode 100644 index 000000000..3d81c6998 --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_radio.lua @@ -0,0 +1,141 @@ +--- +-- @class SWEP +-- @desc radio +-- @section weapon_ttt_radio + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +SWEP.HoldType = "grenade" + +if CLIENT then + SWEP.PrintName = "radio_name" + SWEP.Slot = 7 + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "radio_desc", + } + + SWEP.ViewModelFOV = 70 + SWEP.ViewModelFlip = false + + SWEP.UseHands = true + SWEP.ShowDefaultViewModel = false + SWEP.ShowDefaultWorldModel = false + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "radio_desc", + } + + SWEP.Icon = "vgui/ttt/icon_radio" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.ViewModel = "models/weapons/cstrike/c_eq_fraggrenade.mdl" +SWEP.WorldModel = "models/props/cs_office/radio.mdl" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 1.0 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 1.0 + +SWEP.Kind = WEAPON_EQUIP2 +SWEP.CanBuy = { ROLE_TRAITOR } -- only traitors can buy +SWEP.LimitedStock = true -- only buyable once +SWEP.WeaponID = AMMO_RADIO + +SWEP.builtin = true + +SWEP.AllowDrop = false +SWEP.NoSights = true + +--- +-- @ignore +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + if SERVER then + local radio = ents.Create("ttt_radio") + + if radio:ThrowEntity(self:GetOwner(), Angle(120, 0, 0)) then + self:Remove() + end + end +end + +--- +-- @ignore +function SWEP:SecondaryAttack() + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + + if SERVER then + local radio = ents.Create("ttt_radio") + + if radio:StickEntity(self:GetOwner(), Angle(90, 0, 0), 45) then + self:Remove() + end + end +end + +--- +-- @ignore +function SWEP:Reload() + return false +end + +if CLIENT then + --- + -- @ignore + function SWEP:Initialize() + self:AddTTT2HUDHelp("radio_help_primary", "radio_help_secondary") + + self.BaseClass.Initialize(self) + end + + --- + -- @realm client + function SWEP:InitializeCustomModels() + self:AddCustomViewModel("vmodel", { + type = "Model", + model = "models/props/cs_office/radio.mdl", + bone = "ValveBiped.Bip01_R_Finger2", + rel = "", + pos = Vector(7, 3.5, 2), + angle = Angle(10, 75, 200), + size = Vector(0.725, 0.725, 0.725), + color = Color(255, 255, 255, 255), + surpresslightning = false, + material = "", + skin = 0, + bodygroup = {}, + }) + + self:AddCustomWorldModel("wmodel", { + type = "Model", + model = "models/props/cs_office/radio.mdl", + bone = "ValveBiped.Bip01_R_Hand", + rel = "", + pos = Vector(4, 6, 0), + angle = Angle(20, 15, 190), + size = Vector(0.625, 0.625, 0.625), + color = Color(255, 255, 255, 255), + surpresslightning = false, + material = "", + skin = 0, + bodygroup = {}, + }) + end +end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_sipistol.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_sipistol.lua new file mode 100644 index 000000000..b1929ef6a --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_sipistol.lua @@ -0,0 +1,73 @@ +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +SWEP.HoldType = "pistol" + +if CLIENT then + SWEP.PrintName = "sipistol_name" + SWEP.Slot = 6 + + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "sipistol_desc", + } + + SWEP.Icon = "vgui/ttt/icon_silenced" + SWEP.IconLetter = "a" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.Primary.Recoil = 1.35 +SWEP.Primary.Damage = 28 +SWEP.Primary.Delay = 0.38 +SWEP.Primary.Cone = 0.02 +SWEP.Primary.ClipSize = 20 +SWEP.Primary.Automatic = true +SWEP.Primary.DefaultClip = 20 +SWEP.Primary.ClipMax = 60 +SWEP.Primary.Ammo = "Pistol" +SWEP.Primary.Sound = Sound("Weapon_USP.SilencedShot") +SWEP.Primary.SoundLevel = 50 + +SWEP.Kind = WEAPON_EQUIP +SWEP.CanBuy = { ROLE_TRAITOR } -- only traitors can buy +SWEP.WeaponID = AMMO_SIPISTOL +SWEP.builtin = true + +SWEP.AmmoEnt = "item_ammo_pistol_ttt" +SWEP.IsSilent = true + +SWEP.UseHands = true +SWEP.ViewModel = "models/weapons/cstrike/c_pist_usp.mdl" +SWEP.WorldModel = "models/weapons/w_pist_usp_silencer.mdl" +SWEP.idleResetFix = true + +SWEP.IronSightsPos = Vector(-5.91, -4, 2.84) +SWEP.IronSightsAng = Vector(-0.5, 0, 0) + +SWEP.PrimaryAnim = ACT_VM_PRIMARYATTACK_SILENCED +SWEP.ReloadAnim = ACT_VM_RELOAD_SILENCED +SWEP.IdleAnim = ACT_VM_IDLE_SILENCED + +--- +--@ignore +function SWEP:Deploy() + self:SendWeaponAnim(ACT_VM_DRAW_SILENCED) + return BaseClass.Deploy(self) +end + +--- +-- We were bought as special equipment, and we have an extra to give +--@ignore +function SWEP:WasBought(buyer) + if IsValid(buyer) then -- probably already self:GetOwner() + buyer:GiveAmmo(20, "Pistol") + end +end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_smokegrenade.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_smokegrenade.lua index ac20aef48..177d5284b 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_smokegrenade.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_smokegrenade.lua @@ -1,23 +1,24 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end SWEP.HoldType = "grenade" if CLIENT then - SWEP.PrintName = "grenade_smoke" - SWEP.Slot = 3 + SWEP.PrintName = "grenade_smoke" + SWEP.Slot = 3 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_nades" - SWEP.IconLetter = "Q" + SWEP.Icon = "vgui/ttt/icon_smokegrenade" + SWEP.IconLetter = "Q" end SWEP.Base = "weapon_tttbasegrenade" SWEP.WeaponID = AMMO_SMOKE +SWEP.builtin = true SWEP.Kind = WEAPON_NADE SWEP.spawnType = WEAPON_TYPE_NADE @@ -33,5 +34,5 @@ SWEP.Spawnable = true -- really the only difference between grenade weapons: the model and the thrown ent. -- @ignore function SWEP:GetGrenadeName() - return "ttt_smokegrenade_proj" + return "ttt_smokegrenade_proj" end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_spawneditor.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_spawneditor.lua index 19251a228..e8c507b91 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_spawneditor.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_spawneditor.lua @@ -1,15 +1,20 @@ +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + SWEP.Base = "weapon_tttbase" if CLIENT then - SWEP.ViewModelFOV = 45 - SWEP.DrawCrosshair = false - SWEP.ViewModelFlip = false - - SWEP.EquipMenuData = { - type = "item_weapon", - name = "spawneditor_name", - desc = "spawneditor_desc" - } + SWEP.ViewModelFOV = 45 + SWEP.ViewModelFlip = false + + SWEP.EquipMenuData = { + type = "item_weapon", + name = "spawneditor_name", + desc = "spawneditor_desc", + } end SWEP.Kind = WEAPON_EQUIP2 @@ -44,538 +49,592 @@ SWEP.lastReload = 0 SWEP.AllowDrop = false +SWEP.builtin = true + local previewData = {} if SERVER then - util.AddNetworkString("weapon_ttt_spawneditor_spawninfo_ent") - - --- - -- @ignore - function SWEP:Deploy() - -- add entity which is used for the targetID integration - self.entSpawnInfo = ents.Create("ttt_spawninfo_ent") - self.entSpawnInfo:Spawn() - - -- Send SpawnInfo-Entity next frame, so it can be created first - timer.Simple(0, function() - if not IsValid(self) or not IsValid(self.entSpawnInfo) then return end - - net.Start("weapon_ttt_spawneditor_spawninfo_ent") - net.WriteEntity(self.entSpawnInfo) - net.Send(self:GetOwner()) - end) - - self.BaseClass.Deploy(self) - end - - --- - -- @ignore - function SWEP:OnRemove() - -- using the on remove hook to store the updated spawns because it is called in the following scenarios: - -- * manual stop of the spawn edit process - -- * stop of the spawn edit process triggered by a death - -- * stop of the spawn edit process triggered by a new round - -- * stop of the spawn edit process triggered by a mapchange - entspawnscript.UpdateSpawnFile() - - -- remove entity which is used for the targetID integration - if not IsValid(self.entSpawnInfo) then return end - - self.entSpawnInfo:Remove() - end - - --- - -- @ignore - function SWEP:OnDrop() - self:Remove() - end + util.AddNetworkString("weapon_ttt_spawneditor_spawninfo_ent") + + --- + -- @ignore + function SWEP:Deploy() + -- add entity which is used for the targetID integration + self.entSpawnInfo = ents.Create("ttt_spawninfo_ent") + self.entSpawnInfo:Spawn() + + -- Send SpawnInfo-Entity next frame, so it can be created first + timer.Simple(0, function() + if not IsValid(self) or not IsValid(self.entSpawnInfo) then + return + end + + net.Start("weapon_ttt_spawneditor_spawninfo_ent") + net.WriteEntity(self.entSpawnInfo) + net.Send(self:GetOwner()) + end) + + BaseClass.Deploy(self) + end + + --- + -- @ignore + function SWEP:OnRemove() + -- using the on remove hook to store the updated spawns because it is called in the following scenarios: + -- * manual stop of the spawn edit process + -- * stop of the spawn edit process triggered by a death + -- * stop of the spawn edit process triggered by a new round + -- * stop of the spawn edit process triggered by a mapchange + entspawnscript.UpdateSpawnFile() + + -- remove entity which is used for the targetID integration + if not IsValid(self.entSpawnInfo) then + return + end + + self.entSpawnInfo:Remove() + end + + --- + -- @ignore + function SWEP:OnDrop() + self:Remove() + end end if CLIENT then - local draw = draw - local camStart2D = cam.Start2D - local camEnd2D = cam.End2D - local camStart3D2D = cam.Start3D2D - local camEnd3D2D = cam.End3D2D - local renderPushRenderTarget = render.PushRenderTarget - local renderSetColorMaterial = render.SetColorMaterial - local renderDrawSphere = render.DrawSphere - local camStart3D = cam.Start3D - local camEnd3D = cam.End3D - local Vector = Vector - local mathPi = math.pi - local mathTan = math.tan - local mathMax = math.max - local mathMin = math.min - local ColorAlpha = ColorAlpha - - local centerX = 0.5 * ScrW() - local centerY = 0.5 * ScrH() - local sphereRadius = 10 - local tolerance = 32 * sphereRadius - - local maxEditDistance = 1500 - local distWalls = 7.5 - - local colorPreview = Color(255, 255, 255, 100) - - local matScreen = Material("models/weapons/v_toolgun/screen") - local screenSize = 256 - local padding = 16 - local iconSize = 64 - local iconX = 0.5 * (screenSize - iconSize) - local iconY = 2 * padding - local textX = 0.5 * screenSize - local textY = iconY + iconSize + padding - local lineY = screenSize - 48 - local lineW = screenSize - 2 * padding - local circleS = 6 - local circleY = screenSize - 24 - - local RTTexture = GetRenderTarget("TTT2SpawnPlacer", screenSize, screenSize) - - local colorBasic = Color(255, 255, 255, 100) - local colorSelect = Color(255, 255, 255, 235) - - local function GetSteps(distance3D) - if not isnumber(distance3D) then - return 10 - end - - -- Norm Steps to be the maximum 10 at 200 units and minimum of 5 at 400 units seems reasonable - -- Dont go below 5, as you wont recognize it as a sphere anymore - return mathMin(10, mathMax(5, 10 * 200 / distance3D)) - end - - local function IsHighlighted(pos, screenPos) - local localPlayer = LocalPlayer() - local dist3d = localPlayer:EyePos():Distance(pos) - - if dist3d > maxEditDistance then - return false, dist3d - end - - if math.Distance(screenPos.x, screenPos.y, centerX, centerY) > tolerance * ScrW() / localPlayer:GetFOV() / dist3d then - return false, dist3d - end - - return true, dist3d - end - - local function PaintSpawns(spawnType, entTable, color, proximitySpawns) - local screenPos - - -- While iterating over the table containing all spawn ents for one spawn - -- type most spheres are directly drawn. However a small selection of those - -- speres is stored in the proximity spawns table. These spawns spheres are - -- so close to the crosshair that they might be highligted right now. To - -- detect which one is highlighted right now, we later have to sort this table - -- by 3d distance. But since sorting is a heavy task, especially when done - -- in the rendering queue, only the selected minority is sorted. - - for entType, spawns in pairs(entTable) do - for i = 1, #spawns do - local spawn = spawns[i] - local pos = spawn.pos - - -- If pos is nil, continue to the next iteration - if not pos then continue end - - -- the screenPos has to be calculatet inside a non modified cam3D space - -- to yield correct results - camStart3D() - screenPos = pos:ToScreen() - camEnd3D() - - if util.IsOffScreen(screenPos) then continue end - - local isHighlighted, dist3d = IsHighlighted(pos, screenPos) - - if dist3d > maxEditDistance then continue end - - local steps = GetSteps(dist3d) - - if not isHighlighted then - renderDrawSphere( - pos, - 10, - steps, - steps, - color - ) - else - proximitySpawns[#proximitySpawns + 1] = { - entType = entType, - spawnType = spawnType, - spawn = spawn, - dist3d = dist3d, - screenPos = screenPos, - id = i - } - end - end - end - end - - local function RenderForType(spawnType, spawnEntList, proximitySpawns) - local spawnEnts = spawnEntList[spawnType] or {} - local colorEnt = ColorAlpha(entspawnscript.GetColorFromSpawnType(spawnType), 100) - - -- draw spawn bubbles and put bubbles that might be highlighted in - -- the 'proximitySpawns' table - PaintSpawns(spawnType, spawnEnts, colorEnt, proximitySpawns) - - return colorEnt - end - - local function RenderHook() - entspawnscript.SetFocusedSpawn(nil) - - local spawnEntList = entspawnscript.GetSpawns() - local proximitySpawns = {} - local colorSpawnTypes = {} - - if not spawnEntList then return end - - renderSetColorMaterial() - - local spawnTypesTable = entspawnscript.GetSpawnTypes() - - for i = 1, #spawnTypesTable do - local spawnType = spawnTypesTable[i] - - colorSpawnTypes[spawnType] = RenderForType(spawnType, spawnEntList, proximitySpawns) - end - - -- sort the proximity spawns by distance - table.sort(proximitySpawns, function(a, b) - return a.dist3d < b.dist3d - end) - - -- draw the proximity spawns and highlight the first one - for i = 1, #proximitySpawns do - local proximitySpawn = proximitySpawns[i] - local spawn = proximitySpawn.spawn - local spawnType = proximitySpawn.spawnType - local pos = proximitySpawn.spawn.pos - local entType = proximitySpawn.entType - local id = proximitySpawn.id - local dist3d = proximitySpawn.dist3d - local color = colorSpawnTypes[spawnType] - local steps = GetSteps(dist3d) - - screenPos = proximitySpawn.screenPos - - -- only highlight the first in the table because this is the foucused spawn bubble - if i == 1 then - local client = LocalPlayer() - - -- make sure there is nothing in the way - local trace = util.TraceLine({ - start = client:EyePos(), - endpos = client:EyePos() + client:EyeAngles():Forward() * dist3d, - filter = {client}, - mask = MASK_SOLID - }) - - if not trace.HitWorld then - entspawnscript.SetFocusedSpawn(spawnType, entType, id, spawn) - end - - renderDrawSphere( - pos, - sphereRadius * (trace.HitWorld and 1 or 1.1), - steps, - steps, - ColorAlpha(color, 245) - ) - else - renderDrawSphere( - pos, sphereRadius, - steps, - steps, - ColorAlpha(color, 100) - ) - end - end - - if not previewData.inRange then return end - - local colorSphere - - if previewData.inPlacement then - colorSphere = ColorAlpha(entspawnscript.GetColorFromSpawnType(previewData.spawnType), 245) - - -- render circle on ground - camStart3D2D( - previewData.posBase + previewData.normalBase * 2, - previewData.normalBase:Angle() + Angle(90, 0, 0), - 0.25 - ) - - draw.Circle(0, 0, 30, colorPreview) - - camEnd3D2D() - - -- render line that shows shift - camStart3D2D( - previewData.posBase + previewData.normalBase * 2, - Angle(0, LocalPlayer():GetAngles().y - 90, 90), - 0.25 - ) - - draw.Box(-1, 0, 2, 4 * (previewData.heightShift or 0), colorPreview) - - camEnd3D2D() - else - colorSphere = colorPreview - end - - local steps = GetSteps(dist3d) - - renderDrawSphere( - previewData.currentPos, - sphereRadius, - steps, - steps, - colorSphere - ) - end - - --- - -- @ignore - function SWEP:OnRemove() - hook.Remove("PostDrawTranslucentRenderables", "RenderWeaponSpawnEdit") - - -- clear the local cache to prevent flickering after reset - entspawnscript.ClearLocalCache() - end - - --- - -- @ignore - function SWEP:Initialize() - self.modes = entspawnscript.GetSpawnTypeList() - self.selectedMode = 1 - - self:AddTTT2HUDHelp("spawneditor_place", "spawneditor_remove") - self:AddHUDHelpLine("spawneditor_change", Key("+reload", "R")) - self:AddHUDHelpLine("spawneditor_ammo_edit", Key("+walk", "WALK")) - - hook.Add("PostDrawTranslucentRenderables", "RenderWeaponSpawnEdit", RenderHook) - end - - --- - -- @ignore - function SWEP:RenderScreen() - -- Set the material of the screen to our render target - matScreen:SetTexture("$basetexture", RTTexture) - - -- Set up our view for drawing to the texture - renderPushRenderTarget(RTTexture) - - local mode = self.modes[self.selectedMode] - - camStart2D() - draw.Box(0, 0, screenSize, screenSize, entspawnscript.GetColorFromSpawnType(mode.spawnType)) - - draw.ShadowedTexture(iconX, iconY, iconSize, iconSize, entspawnscript.GetIconFromSpawnType(mode.spawnType, mode.entType), 255, COLOR_WHITE) - - draw.ShadowedText( - LANG.TryTranslation(entspawnscript.GetLangIdentifierFromSpawnType(mode.spawnType, mode.entType)), - "DermaTTT2SubMenuButtonTitle", - textX, - textY, - COLOR_WHITE, - TEXT_ALIGN_CENTER, - TEXT_ALIGN_TOP - ) - - draw.Box(padding, lineY, lineW, 2, colorBasic) - - for i = 1, #self.modes do - if i == self.selectedMode then - draw.Circle(i * padding, circleY, circleS, colorSelect) - else - draw.Circle(i * padding, circleY, circleS, colorBasic) - end - end - camEnd2D() - - render.PopRenderTarget() - end - - net.Receive("weapon_ttt_spawneditor_spawninfo_ent", function() - entspawnscript.SetSpawnInfoEntity(net.ReadEntity()) - end) - - --- - -- @ignore - function SWEP:Think() - local client = LocalPlayer() - - -- Make sure the user is currently not typing anything, to prevent unwanted execution of a placement - if vgui.GetKeyboardFocus() or client:IsTyping() or gui.IsConsoleVisible() or vguihandler.IsOpen() then return end - - local focusedSpawn = entspawnscript.GetFocusedSpawn() - local mode = self.modes[self.selectedMode] - - -- always set spawnType - previewData.spawnType = mode.spawnType - - if input.IsBindingDown("+attack") and not self.wasAttackDown then - -- first key down of the attack button: get basepos - self.wasAttackDown = true - - if focusedSpawn then return end - - local trace = client:GetEyeTrace() - - local posEye = client:EyePos() - local posBase = trace.HitPos - local distance3d = posEye:Distance(posBase) - - if not trace.Hit or distance3d > maxEditDistance then return end - - previewData.normalBase = trace.HitNormal - previewData.inPlacement = true - previewData.posBase = posBase - previewData.distance3d = distance3d - - -- find limits for top and bottom - local traceLimitUpper = util.TraceLine({ - start = posBase + trace.HitNormal, - endpos = posBase + Vector(0, 0, maxEditDistance), - mask = MASK_PLAYERSOLID_BRUSHONLY - }) - - previewData.posLimitUpper = traceLimitUpper.HitPos - previewData.normalLimitUpper = traceLimitUpper.HitNormal - - local traceLimitLower = util.TraceLine({ - start = posBase + trace.HitNormal, - endpos = posBase - Vector(0, 0, maxEditDistance), - mask = MASK_PLAYERSOLID_BRUSHONLY - }) - - previewData.posLimitLower = traceLimitLower.HitPos - previewData.normalLimitLower = traceLimitLower.HitNormal - elseif input.IsBindingDown("+attack") and previewData.inPlacement then - -- hold attack key: update preview position - local posBase = previewData.posBase - - local posEye = client:EyePos() - local posGround = Vector(posEye.x, posEye.y, posBase.z) - - local distance2d = posGround:Distance(posBase) - local distance3d = posEye:Distance(posBase) - - local angle = LocalPlayer():EyeAngles().p / 180 * mathPi -- angle in rad - local diff = distance2d * mathTan(angle) - - local currentPos = Vector(posBase.x, posBase.y, posEye.z - diff) - - -- limit current position in between valid range - currentPos.z = math.min(currentPos.z, previewData.posLimitUpper.z - distWalls) - currentPos.z = math.max(currentPos.z, previewData.posLimitLower.z + distWalls) - - previewData.currentPos = currentPos - previewData.distance3d = distance3d - previewData.heightShift = posBase.z - previewData.currentPos.z - elseif not input.IsBindingDown("+attack") and self.wasAttackDown then - -- attack key released: set spawn - self.wasAttackDown = false - - if not previewData.inPlacement then return end - - previewData.inPlacement = false - previewData.heightShift = 0 - - if focusedSpawn then return end - - entspawnscript.AddSpawn(mode.spawnType, mode.entType, previewData.currentPos, client:GetAngles(), 0, true) - else - -- just store current position for rendering of preview - local trace = client:GetEyeTrace() - local posEye = client:EyePos() - local posBase = trace.HitPos - local distance3d = posEye:Distance(posBase) - - previewData.currentPos = trace.HitPos - previewData.normalBase = trace.HitNormal - previewData.distance3d = distance3d - previewData.inRange = distance3d <= maxEditDistance and not focusedSpawn - end - end + local draw = draw + local camStart2D = cam.Start2D + local camEnd2D = cam.End2D + local camStart3D2D = cam.Start3D2D + local camEnd3D2D = cam.End3D2D + local renderPushRenderTarget = render.PushRenderTarget + local renderSetColorMaterial = render.SetColorMaterial + local renderDrawSphere = render.DrawSphere + local camStart3D = cam.Start3D + local camEnd3D = cam.End3D + local Vector = Vector + local mathPi = math.pi + local mathTan = math.tan + local mathMax = math.max + local mathMin = math.min + local ColorAlpha = ColorAlpha + + local centerX = 0.5 * ScrW() + local centerY = 0.5 * ScrH() + local sphereRadius = 10 + local tolerance = 32 * sphereRadius + + local maxEditDistance = 1500 + local distWalls = 7.5 + + local colorPreview = Color(255, 255, 255, 100) + + local matScreen = Material("models/weapons/v_toolgun/screen") + local screenSize = 256 + local padding = 16 + local iconSize = 64 + local iconX = 0.5 * (screenSize - iconSize) + local iconY = 2 * padding + local textX = 0.5 * screenSize + local textY = iconY + iconSize + padding + local lineY = screenSize - 48 + local lineW = screenSize - 2 * padding + local circleS = 6 + local circleY = screenSize - 24 + + local RTTexture = GetRenderTarget("TTT2SpawnPlacer", screenSize, screenSize) + + local colorBasic = Color(255, 255, 255, 100) + local colorSelect = Color(255, 255, 255, 235) + + local function GetSteps(distance3D) + if not isnumber(distance3D) then + return 10 + end + + -- Norm Steps to be the maximum 10 at 200 units and minimum of 5 at 400 units seems reasonable + -- Dont go below 5, as you wont recognize it as a sphere anymore + return mathMin(10, mathMax(5, 10 * 200 / distance3D)) + end + + local function IsHighlighted(pos, screenPos) + local localPlayer = LocalPlayer() + local dist3d = localPlayer:EyePos():Distance(pos) + + if dist3d > maxEditDistance then + return false, dist3d + end + + if + math.Distance(screenPos.x, screenPos.y, centerX, centerY) + > tolerance * ScrW() / localPlayer:GetFOV() / dist3d + then + return false, dist3d + end + + return true, dist3d + end + + local function PaintSpawns(spawnType, entTable, color, proximitySpawns) + local screenPos + + -- While iterating over the table containing all spawn ents for one spawn + -- type most spheres are directly drawn. However a small selection of those + -- speres is stored in the proximity spawns table. These spawns spheres are + -- so close to the crosshair that they might be highligted right now. To + -- detect which one is highlighted right now, we later have to sort this table + -- by 3d distance. But since sorting is a heavy task, especially when done + -- in the rendering queue, only the selected minority is sorted. + + for entType, spawns in pairs(entTable) do + for i = 1, #spawns do + local spawn = spawns[i] + local pos = spawn.pos + + -- If pos is nil, continue to the next iteration + if not pos then + continue + end + + -- the screenPos has to be calculatet inside a non modified cam3D space + -- to yield correct results + camStart3D() + screenPos = pos:ToScreen() + camEnd3D() + + if util.IsOffScreen(screenPos) then + continue + end + + local isHighlighted, dist3d = IsHighlighted(pos, screenPos) + + if dist3d > maxEditDistance then + continue + end + + local steps = GetSteps(dist3d) + + if not isHighlighted then + renderDrawSphere(pos, 10, steps, steps, color) + else + proximitySpawns[#proximitySpawns + 1] = { + entType = entType, + spawnType = spawnType, + spawn = spawn, + dist3d = dist3d, + screenPos = screenPos, + id = i, + } + end + end + end + end + + local function RenderForType(spawnType, spawnEntList, proximitySpawns) + local spawnEnts = spawnEntList[spawnType] or {} + local colorEnt = ColorAlpha(entspawnscript.GetColorFromSpawnType(spawnType), 100) + + -- draw spawn bubbles and put bubbles that might be highlighted in + -- the 'proximitySpawns' table + PaintSpawns(spawnType, spawnEnts, colorEnt, proximitySpawns) + + return colorEnt + end + + local function RenderHook() + entspawnscript.SetFocusedSpawn(nil) + + local spawnEntList = entspawnscript.GetSpawns() + local proximitySpawns = {} + local colorSpawnTypes = {} + + if not spawnEntList then + return + end + + renderSetColorMaterial() + + local spawnTypesTable = entspawnscript.GetSpawnTypes() + + for i = 1, #spawnTypesTable do + local spawnType = spawnTypesTable[i] + + colorSpawnTypes[spawnType] = RenderForType(spawnType, spawnEntList, proximitySpawns) + end + + -- sort the proximity spawns by distance + table.sort(proximitySpawns, function(a, b) + return a.dist3d < b.dist3d + end) + + -- draw the proximity spawns and highlight the first one + for i = 1, #proximitySpawns do + local proximitySpawn = proximitySpawns[i] + local spawn = proximitySpawn.spawn + local spawnType = proximitySpawn.spawnType + local pos = proximitySpawn.spawn.pos + local entType = proximitySpawn.entType + local id = proximitySpawn.id + local dist3d = proximitySpawn.dist3d + local color = colorSpawnTypes[spawnType] + local steps = GetSteps(dist3d) + + screenPos = proximitySpawn.screenPos + + -- only highlight the first in the table because this is the foucused spawn bubble + if i == 1 then + local client = LocalPlayer() + + -- make sure there is nothing in the way + local trace = util.TraceLine({ + start = client:EyePos(), + endpos = client:EyePos() + client:EyeAngles():Forward() * dist3d, + filter = { client }, + mask = MASK_SOLID, + }) + + if not trace.HitWorld then + entspawnscript.SetFocusedSpawn(spawnType, entType, id, spawn) + end + + renderDrawSphere( + pos, + sphereRadius * (trace.HitWorld and 1 or 1.1), + steps, + steps, + ColorAlpha(color, 245) + ) + else + renderDrawSphere(pos, sphereRadius, steps, steps, ColorAlpha(color, 100)) + end + end + + if not previewData.inRange then + return + end + + local colorSphere + + if previewData.inPlacement then + colorSphere = + ColorAlpha(entspawnscript.GetColorFromSpawnType(previewData.spawnType), 245) + + -- render circle on ground + camStart3D2D( + previewData.posBase + previewData.normalBase * 2, + previewData.normalBase:Angle() + Angle(90, 0, 0), + 0.25 + ) + + draw.Circle(0, 0, 30, colorPreview) + + camEnd3D2D() + + -- render line that shows shift + camStart3D2D( + previewData.posBase + previewData.normalBase * 2, + Angle(0, LocalPlayer():GetAngles().y - 90, 90), + 0.25 + ) + + draw.Box(-1, 0, 2, 4 * (previewData.heightShift or 0), colorPreview) + + camEnd3D2D() + else + colorSphere = colorPreview + end + + local steps = GetSteps(previewData.distance3d) + + renderDrawSphere(previewData.currentPos, sphereRadius, steps, steps, colorSphere) + end + + --- + -- @realm client + function SWEP:OnRemove() + BaseClass.OnRemove(self) + + hook.Remove("PostDrawTranslucentRenderables", "RenderWeaponSpawnEdit") + + -- clear the local cache to prevent flickering after reset + entspawnscript.ClearLocalCache() + end + + --- + -- @ignore + function SWEP:Initialize() + self.modes = entspawnscript.GetSpawnTypeList() + self.selectedMode = 1 + + self:AddTTT2HUDHelp("spawneditor_place", "spawneditor_remove") + self:AddHUDHelpLine("spawneditor_change", Key("+reload", "R")) + self:AddHUDHelpLine("spawneditor_ammo_edit", Key("+walk", "WALK")) + + hook.Add("PostDrawTranslucentRenderables", "RenderWeaponSpawnEdit", RenderHook) + end + + --- + -- @ignore + function SWEP:RenderScreen() + -- Set the material of the screen to our render target + matScreen:SetTexture("$basetexture", RTTexture) + + -- Set up our view for drawing to the texture + renderPushRenderTarget(RTTexture) + + local mode = self.modes[self.selectedMode] + + camStart2D() + draw.Box(0, 0, screenSize, screenSize, entspawnscript.GetColorFromSpawnType(mode.spawnType)) + + draw.ShadowedTexture( + iconX, + iconY, + iconSize, + iconSize, + entspawnscript.GetIconFromSpawnType(mode.spawnType, mode.entType), + 255, + COLOR_WHITE + ) + + draw.ShadowedText( + LANG.TryTranslation( + entspawnscript.GetLangIdentifierFromSpawnType(mode.spawnType, mode.entType) + ), + "DermaTTT2SubMenuButtonTitle", + textX, + textY, + COLOR_WHITE, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_TOP + ) + + draw.Box(padding, lineY, lineW, 2, colorBasic) + + for i = 1, #self.modes do + if i == self.selectedMode then + draw.Circle(i * padding, circleY, circleS, colorSelect) + else + draw.Circle(i * padding, circleY, circleS, colorBasic) + end + end + camEnd2D() + + render.PopRenderTarget() + end + + net.Receive("weapon_ttt_spawneditor_spawninfo_ent", function() + entspawnscript.SetSpawnInfoEntity(net.ReadEntity()) + end) + + --- + -- @ignore + function SWEP:Think() + local client = LocalPlayer() + + -- Make sure the user is currently not typing anything, to prevent unwanted execution of a placement + if + vgui.GetKeyboardFocus() + or client:IsTyping() + or gui.IsConsoleVisible() + or vguihandler.IsOpen() + then + return + end + + local focusedSpawn = entspawnscript.GetFocusedSpawn() + local mode = self.modes[self.selectedMode] + + -- always set spawnType + previewData.spawnType = mode.spawnType + + if input.IsBindingDown("+attack") and not self.wasAttackDown then + -- first key down of the attack button: get basepos + self.wasAttackDown = true + + if focusedSpawn then + return + end + + local trace = client:GetEyeTrace() + + local posEye = client:EyePos() + local posBase = trace.HitPos + local distance3d = posEye:Distance(posBase) + + if not trace.Hit or distance3d > maxEditDistance then + return + end + + previewData.normalBase = trace.HitNormal + previewData.inPlacement = true + previewData.posBase = posBase + previewData.distance3d = distance3d + + -- find limits for top and bottom + local traceLimitUpper = util.TraceLine({ + start = posBase + trace.HitNormal, + endpos = posBase + Vector(0, 0, maxEditDistance), + mask = MASK_PLAYERSOLID_BRUSHONLY, + }) + + previewData.posLimitUpper = traceLimitUpper.HitPos + previewData.normalLimitUpper = traceLimitUpper.HitNormal + + local traceLimitLower = util.TraceLine({ + start = posBase + trace.HitNormal, + endpos = posBase - Vector(0, 0, maxEditDistance), + mask = MASK_PLAYERSOLID_BRUSHONLY, + }) + + previewData.posLimitLower = traceLimitLower.HitPos + previewData.normalLimitLower = traceLimitLower.HitNormal + elseif input.IsBindingDown("+attack") and previewData.inPlacement then + -- hold attack key: update preview position + local posBase = previewData.posBase + + local posEye = client:EyePos() + local posGround = Vector(posEye.x, posEye.y, posBase.z) + + local distance2d = posGround:Distance(posBase) + local distance3d = posEye:Distance(posBase) + + local angle = LocalPlayer():EyeAngles().p / 180 * mathPi -- angle in rad + local diff = distance2d * mathTan(angle) + + local currentPos = Vector(posBase.x, posBase.y, posEye.z - diff) + + -- limit current position in between valid range + currentPos.z = math.min(currentPos.z, previewData.posLimitUpper.z - distWalls) + currentPos.z = math.max(currentPos.z, previewData.posLimitLower.z + distWalls) + + previewData.currentPos = currentPos + previewData.distance3d = distance3d + previewData.heightShift = posBase.z - previewData.currentPos.z + elseif not input.IsBindingDown("+attack") and self.wasAttackDown then + -- attack key released: set spawn + self.wasAttackDown = false + + if not previewData.inPlacement then + return + end + + previewData.inPlacement = false + previewData.heightShift = 0 + + if focusedSpawn then + return + end + + entspawnscript.AddSpawn( + mode.spawnType, + mode.entType, + previewData.currentPos, + client:GetAngles(), + 0, + true + ) + else + -- just store current position for rendering of preview + local trace = client:GetEyeTrace() + local posEye = client:EyePos() + local posBase = trace.HitPos + local distance3d = posEye:Distance(posBase) + + previewData.currentPos = trace.HitPos + previewData.normalBase = trace.HitNormal + previewData.distance3d = distance3d + previewData.inRange = distance3d <= maxEditDistance and not focusedSpawn + end + end end --- -- @ignore function SWEP:PrimaryAttack() - if SERVER or not IsFirstTimePredicted() then return end + if SERVER or not IsFirstTimePredicted() then + return + end - local nextUseTime = CurTime() + self.Primary.Delay - self:SetNextPrimaryFire(nextUseTime) - self:SetNextSecondaryFire(nextUseTime) + local nextUseTime = CurTime() + self.Primary.Delay + self:SetNextPrimaryFire(nextUseTime) + self:SetNextSecondaryFire(nextUseTime) - local focusedSpawn = entspawnscript.GetFocusedSpawn() + local focusedSpawn = entspawnscript.GetFocusedSpawn() - if input.IsBindingDown("+walk") then - if not focusedSpawn then return end + if input.IsBindingDown("+walk") then + if not focusedSpawn then + return + end - local spawnType = focusedSpawn.spawnType - local entType = focusedSpawn.entType - local id = focusedSpawn.id - local ammo = focusedSpawn.spawn.ammo + local spawnType = focusedSpawn.spawnType + local entType = focusedSpawn.entType + local id = focusedSpawn.id + local ammo = focusedSpawn.spawn.ammo - entspawnscript.UpdateSpawn(spawnType, entType, id, nil, nil, ammo + 1, true) - end + entspawnscript.UpdateSpawn(spawnType, entType, id, nil, nil, ammo + 1, true) + end end --- -- @ignore function SWEP:SecondaryAttack() - if SERVER or not IsFirstTimePredicted() then return end - - local nextUseTime = CurTime() + self.Primary.Delay - self:SetNextPrimaryFire(nextUseTime) - self:SetNextSecondaryFire(nextUseTime) - - local focusedSpawn = entspawnscript.GetFocusedSpawn() - - if not focusedSpawn then return end - - local spawnType = focusedSpawn.spawnType - local entType = focusedSpawn.entType - local id = focusedSpawn.id - local ammo = focusedSpawn.spawn.ammo - - if input.IsBindingDown("+walk") then - entspawnscript.UpdateSpawn(spawnType, entType, id, nil, nil, math.max(ammo - 1, 0), true) - else - entspawnscript.RemoveSpawnById(focusedSpawn.spawnType, focusedSpawn.entType, focusedSpawn.id, true) - end + if SERVER or not IsFirstTimePredicted() then + return + end + + local nextUseTime = CurTime() + self.Primary.Delay + self:SetNextPrimaryFire(nextUseTime) + self:SetNextSecondaryFire(nextUseTime) + + local focusedSpawn = entspawnscript.GetFocusedSpawn() + + if not focusedSpawn then + return + end + + local spawnType = focusedSpawn.spawnType + local entType = focusedSpawn.entType + local id = focusedSpawn.id + local ammo = focusedSpawn.spawn.ammo + + if input.IsBindingDown("+walk") then + entspawnscript.UpdateSpawn(spawnType, entType, id, nil, nil, math.max(ammo - 1, 0), true) + else + entspawnscript.RemoveSpawnById( + focusedSpawn.spawnType, + focusedSpawn.entType, + focusedSpawn.id, + true + ) + end end --- -- @ignore function SWEP:Reload() - if SERVER or not IsFirstTimePredicted() then return end - - if self.lastReload + 0.175 > CurTime() then return end - - self.lastReload = CurTime() - - if input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) then - if self.selectedMode == 1 then - self.selectedMode = #self.modes - else - self.selectedMode = self.selectedMode - 1 - end - else - if self.selectedMode == #self.modes then - self.selectedMode = 1 - else - self.selectedMode = self.selectedMode + 1 - end - end + if SERVER or not IsFirstTimePredicted() then + return + end + + if self.lastReload + 0.175 > CurTime() then + return + end + + self.lastReload = CurTime() + + if input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) then + if self.selectedMode == 1 then + self.selectedMode = #self.modes + else + self.selectedMode = self.selectedMode - 1 + end + else + if self.selectedMode == #self.modes then + self.selectedMode = 1 + else + self.selectedMode = self.selectedMode + 1 + end + end end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_stungun.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_stungun.lua new file mode 100644 index 000000000..8618d4c30 --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_stungun.lua @@ -0,0 +1,125 @@ +--- +-- @class SWEP +-- @section weapon_ttt_stungun + +AddCSLuaFile() + +SWEP.HoldType = "ar2" + +if CLIENT then + SWEP.PrintName = "stungun_name" + SWEP.Slot = 6 + + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "ump_desc", + } + + SWEP.Icon = "vgui/ttt/icon_ump" + SWEP.IconLetter = "q" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.Kind = WEAPON_EQUIP +SWEP.WeaponID = AMMO_STUN +SWEP.CanBuy = { ROLE_DETECTIVE } +SWEP.LimitedStock = false +SWEP.AmmoEnt = "item_ammo_smg1_ttt" +SWEP.builtin = true + +SWEP.Primary.Damage = 9 +SWEP.Primary.Delay = 0.1 +SWEP.Primary.Cone = 0.02 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.ClipMax = 60 +SWEP.Primary.DefaultClip = 30 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "SMG1" +SWEP.Primary.Recoil = 1.2 +SWEP.Primary.Sound = Sound("Weapon_UMP45.Single") + +SWEP.UseHands = true +SWEP.ViewModel = "models/weapons/cstrike/c_smg_ump45.mdl" +SWEP.WorldModel = "models/weapons/w_smg_ump45.mdl" + +SWEP.IronSightsPos = Vector(-8.735, -10, 4.039) +SWEP.IronSightsAng = Vector(-1.201, -0.201, -2) + +SWEP.HeadshotMultiplier = 4.5 -- brain fizz +--SWEP.DeploySpeed = 3 + +--- +-- @ignore +function SWEP:ShootBullet(dmg, recoil, numbul, cone) + local sights = self:GetIronsights() + + numbul = numbul or 1 + cone = cone or 0.01 + + -- 10% accuracy bonus when sighting + cone = sights and (cone * 0.9) or cone + + local bullet = {} + bullet.Num = numbul + bullet.Src = self:GetOwner():GetShootPos() + bullet.Dir = self:GetOwner():GetAimVector() + bullet.Spread = Vector(cone, cone, 0) + bullet.Tracer = 4 + bullet.Force = 5 + bullet.Damage = dmg + + bullet.Callback = function(att, tr, dmginfo) + if SERVER or (CLIENT and IsFirstTimePredicted()) then + local ent = tr.Entity + if (not tr.HitWorld) and IsValid(ent) then + local edata = EffectData() + + edata:SetEntity(ent) + edata:SetMagnitude(3) + edata:SetScale(2) + + util.Effect("TeslaHitBoxes", edata) + + if SERVER and ent:IsPlayer() then + local eyeang = ent:EyeAngles() + + local j = 10 + eyeang.pitch = math.Clamp(eyeang.pitch + math.Rand(-j, j), -90, 90) + eyeang.yaw = math.Clamp(eyeang.yaw + math.Rand(-j, j), -90, 90) + ent:SetEyeAngles(eyeang) + end + end + end + end + + self:GetOwner():FireBullets(bullet) + self:SendWeaponAnim(self.PrimaryAnim) + + -- Owner can die after firebullets, giving an error at muzzleflash + if not IsValid(self:GetOwner()) or not self:GetOwner():Alive() then + return + end + + self:GetOwner():MuzzleFlash() + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + + if self:GetOwner():IsNPC() then + return + end + + if + (game.SinglePlayer() and SERVER) + or ((not game.SinglePlayer()) and CLIENT and IsFirstTimePredicted()) + then + -- reduce recoil if ironsighting + recoil = sights and (recoil * 0.75) or recoil + + local eyeang = self:GetOwner():EyeAngles() + eyeang.pitch = eyeang.pitch - recoil + self:GetOwner():SetEyeAngles(eyeang) + end +end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_teleport.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_teleport.lua index 212c2a7a7..13747a2c9 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_teleport.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_teleport.lua @@ -5,30 +5,31 @@ local ttt_telefrags if SERVER then - AddCSLuaFile() + AddCSLuaFile() - --- - -- @realm server - ttt_telefrags = CreateConVar("ttt_teleport_telefrags", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) + --- + -- @realm server + -- stylua: ignore + ttt_telefrags = CreateConVar("ttt_teleport_telefrags", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) end +DEFINE_BASECLASS("weapon_tttbase") + SWEP.HoldType = "normal" if CLIENT then - SWEP.PrintName = "tele_name" - SWEP.Slot = 7 + SWEP.PrintName = "tele_name" + SWEP.Slot = 7 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 10 - SWEP.DrawCrosshair = false - SWEP.CSMuzzleFlashes = false + SWEP.ShowDefaultViewModel = false + SWEP.CSMuzzleFlashes = false - SWEP.EquipMenuData = { - type = "item_weapon", - desc = "tele_desc" - } + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "tele_desc", + } - SWEP.Icon = "vgui/ttt/icon_tport" + SWEP.Icon = "vgui/ttt/icon_tport" end SWEP.Base = "weapon_tttbase" @@ -48,8 +49,9 @@ SWEP.Secondary.Ammo = "none" SWEP.Secondary.Delay = 1.0 SWEP.Kind = WEAPON_EQUIP2 -SWEP.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +SWEP.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } SWEP.WeaponID = AMMO_TELEPORT +SWEP.builtin = true SWEP.AllowDrop = true SWEP.NoSights = true @@ -60,332 +62,334 @@ local delay_beamdown = 1 --- -- @ignore function SWEP:SetTeleportMark(pos, ang) - self.teleport = {pos = pos, ang = ang} + self.teleport = { pos = pos, ang = ang } end --- -- @ignore function SWEP:GetTeleportMark() - return self.teleport + return self.teleport end --- -- @ignore function SWEP:PrimaryAttack() - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - if self:Clip1() <= 0 then - self:DryFire(self.SetNextSecondaryFire) + if self:Clip1() <= 0 then + self:DryFire(self.SetNextSecondaryFire) - return - end + return + end - -- Disallow initiating teleports during post, as it will occur across the - -- restart and allow the user an advantage during prep - if GetRoundState() == ROUND_POST then return end + -- Disallow initiating teleports during post, as it will occur across the + -- restart and allow the user an advantage during prep + if GetRoundState() == ROUND_POST then + return + end - if SERVER then - self:TeleportRecall() - else - surface.PlaySound("buttons/combine_button7.wav") - end + if SERVER then + self:TeleportRecall() + else + surface.PlaySound("buttons/combine_button7.wav") + end end --- -- @ignore function SWEP:SecondaryAttack() - self:SetNextSecondaryFire( CurTime() + self.Secondary.Delay ) + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) - if SERVER then - self:TeleportStore() - else - surface.PlaySound("ui/buttonrollover.wav") - end + if SERVER then + self:TeleportStore() + else + surface.PlaySound("ui/buttonrollover.wav") + end end local zap = Sound("ambient/levels/labs/electric_explosion4.wav") local unzap = Sound("ambient/levels/labs/electric_explosion2.wav") local function Telefrag(victim, attacker, weapon) - if not IsValid(victim) then return end - - local dmginfo = DamageInfo() - dmginfo:SetDamage(5000) - dmginfo:SetDamageType(DMG_SONIC) - dmginfo:SetAttacker(attacker) - dmginfo:SetInflictor(weapon) - dmginfo:SetDamageForce(Vector(0,0,10)) - dmginfo:SetDamagePosition(attacker:GetPos()) - - victim:TakeDamageInfo(dmginfo) + if not IsValid(victim) then + return + end + + local dmginfo = DamageInfo() + dmginfo:SetDamage(5000) + dmginfo:SetDamageType(DMG_SONIC) + dmginfo:SetAttacker(attacker) + dmginfo:SetInflictor(weapon) + dmginfo:SetDamageForce(Vector(0, 0, 10)) + dmginfo:SetDamagePosition(attacker:GetPos()) + + victim:TakeDamageInfo(dmginfo) end - local function ShouldCollide(ent) - local g = ent:GetCollisionGroup() + local g = ent:GetCollisionGroup() - return g ~= COLLISION_GROUP_WEAPON - and g ~= COLLISION_GROUP_DEBRIS - and g ~= COLLISION_GROUP_DEBRIS_TRIGGER - and g ~= COLLISION_GROUP_INTERACTIVE_DEBRIS + return g ~= COLLISION_GROUP_WEAPON + and g ~= COLLISION_GROUP_DEBRIS + and g ~= COLLISION_GROUP_DEBRIS_TRIGGER + and g ~= COLLISION_GROUP_INTERACTIVE_DEBRIS end -- Teleport a player to a {pos, ang} local function TeleportPlayer(ply, teleport) - local oldpos = ply:GetPos() - local pos = teleport.pos - local ang = teleport.ang + local oldpos = ply:GetPos() + local pos = teleport.pos + local ang = teleport.ang - -- print decal on destination - util.PaintDown(pos + Vector(0,0,25), "GlassBreak", ply) + -- print decal on destination + util.PaintDown(pos + Vector(0, 0, 25), "GlassBreak", ply) - -- perform teleport - ply:SetPos(pos) - ply:SetEyeAngles(ang) -- ineffective due to freeze... + -- perform teleport + ply:SetPos(pos) + ply:SetEyeAngles(ang) -- ineffective due to freeze... - timer.Simple(delay_beamdown, function () - if not IsValid(ply) then return end + timer.Simple(delay_beamdown, function() + if not IsValid(ply) then + return + end - ply:Freeze(false) - end) + ply:Freeze(false) + end) - sound.Play(zap, oldpos, 65, 100) - sound.Play(unzap, pos, 55, 100) + sound.Play(zap, oldpos, 65, 100) + sound.Play(unzap, pos, 55, 100) - -- print decal on source now that we're gone, because else it will refuse - -- to draw for some reason - util.PaintDown(oldpos + Vector(0,0,25), "GlassBreak", ply) + -- print decal on source now that we're gone, because else it will refuse + -- to draw for some reason + util.PaintDown(oldpos + Vector(0, 0, 25), "GlassBreak", ply) end -- Checks teleport destination. Returns bool and table, if bool is true then -- location is blocked by world or prop. If table is non-nil it contains a list -- of blocking players. local function CanTeleportToPos(ply, pos) - -- first check if we can teleport here at all, because any solid object or - -- brush will make us stuck and therefore kills/blocks us instead, so the - -- trace checks for anything solid to players that isn't a player - local tr = nil - local traceData = { - start = pos, - endpos = pos, - mask = MASK_PLAYERSOLID, - filter = player.GetAll() - } - local collide = false - - -- This thing is unnecessary if we can supply a collision group to trace - -- functions, like we can in source and sanity suggests we should be able - -- to do so, but I have not found a way to do so yet. Until then, re-trace - -- while extending our filter whenever we hit something we don't want to - -- hit (like weapons or ragdolls). - repeat - tr = util.TraceEntity(traceData, ply) - - if tr.HitWorld then - collide = true - elseif IsValid(tr.Entity) then - if ShouldCollide(tr.Entity) then - collide = true - else - table.insert(traceData.filter, tr.Entity) - end - end - until (not tr.Hit) or collide - - if collide then - return true, nil - else - -- find all players in the place where we will be and telefrag them - local blockers = ents.FindInBox(pos + Vector(-16, -16, 0), pos + Vector(16, 16, 64)) - - local blocking_plys = {} - - for i = 1, #blockers do - local block = blockers[i] - - if not IsValid(block) - or not block:IsPlayer() - or block == ply - or not block:IsTerror() - then continue end - - blocking_plys[#blocking_plys + 1] = block - end - - return false, blocking_plys - end - - return false, nil + -- first check if we can teleport here at all, because any solid object or + -- brush will make us stuck and therefore kills/blocks us instead, so the + -- trace checks for anything solid to players that isn't a player + local tr = nil + local traceData = { + start = pos, + endpos = pos, + mask = MASK_PLAYERSOLID, + filter = player.GetAll(), + } + local collide = false + + -- This thing is unnecessary if we can supply a collision group to trace + -- functions, like we can in source and sanity suggests we should be able + -- to do so, but I have not found a way to do so yet. Until then, re-trace + -- while extending our filter whenever we hit something we don't want to + -- hit (like weapons or ragdolls). + repeat + tr = util.TraceEntity(traceData, ply) + + if tr.HitWorld then + collide = true + elseif IsValid(tr.Entity) then + if ShouldCollide(tr.Entity) then + collide = true + else + table.insert(traceData.filter, tr.Entity) + end + end + until (not tr.Hit) or collide + + if collide then + return true, nil + else + -- find all players in the place where we will be and telefrag them + local blockers = ents.FindInBox(pos + Vector(-16, -16, 0), pos + Vector(16, 16, 64)) + + local blocking_plys = {} + + for i = 1, #blockers do + local block = blockers[i] + + if + not IsValid(block) + or not block:IsPlayer() + or block == ply + or not block:IsTerror() + then + continue + end + + blocking_plys[#blocking_plys + 1] = block + end + + return false, blocking_plys + end + + return false, nil end local function DoTeleport(ply, teleport, weapon) - if IsValid(ply) and ply:IsTerror() and teleport then - local fail = false - - local block_world, block_plys = CanTeleportToPos(ply, teleport.pos) - - if block_world then - -- if blocked by prop/world, always fail - fail = true - elseif block_plys and #block_plys > 0 then - -- if blocked by player, maybe telefrag - if ttt_telefrags:GetBool() then - for i = 1, #block_plys do - Telefrag(block_plys[i], ply, weapon) - end - else - fail = true - end - end - - if not fail then - TeleportPlayer(ply, teleport) - else - ply:Freeze(false) - LANG.Msg(ply, "tele_failed", nil, MSG_MSTACK_ROLE) - end - elseif IsValid(ply) then - -- should never happen, but at least unfreeze - ply:Freeze(false) - LANG.Msg(ply, "tele_failed", nil, MSG_MSTACK_ROLE) - end + if IsValid(ply) and ply:IsTerror() and teleport then + local fail = false + + local block_world, block_plys = CanTeleportToPos(ply, teleport.pos) + + if block_world then + -- if blocked by prop/world, always fail + fail = true + elseif block_plys and #block_plys > 0 then + -- if blocked by player, maybe telefrag + if ttt_telefrags:GetBool() then + for i = 1, #block_plys do + Telefrag(block_plys[i], ply, weapon) + end + else + fail = true + end + end + + if not fail then + TeleportPlayer(ply, teleport) + else + ply:Freeze(false) + LANG.Msg(ply, "tele_failed", nil, MSG_MSTACK_ROLE) + end + elseif IsValid(ply) then + -- should never happen, but at least unfreeze + ply:Freeze(false) + LANG.Msg(ply, "tele_failed", nil, MSG_MSTACK_ROLE) + end end local function StartTeleport(ply, teleport, weapon) - if not IsValid(ply) or not ply:IsTerror() or not teleport then return end + if not IsValid(ply) or not ply:IsTerror() or not teleport then + return + end - teleport.ang = ply:EyeAngles() + teleport.ang = ply:EyeAngles() - timer.Simple(delay_beamup, function() - DoTeleport(ply, teleport, weapon) - end) + timer.Simple(delay_beamup, function() + DoTeleport(ply, teleport, weapon) + end) - local ang = ply:GetAngles() + local ang = ply:GetAngles() - local edata_up = EffectData() - edata_up:SetOrigin(ply:GetPos()) - edata_up:SetAngles(Angle(0, ang.y, ang.r)) -- deep copy - edata_up:SetEntity(ply) - edata_up:SetMagnitude(delay_beamup) - edata_up:SetRadius(delay_beamdown) + local edata_up = EffectData() + edata_up:SetOrigin(ply:GetPos()) + edata_up:SetAngles(Angle(0, ang.y, ang.r)) -- deep copy + edata_up:SetEntity(ply) + edata_up:SetMagnitude(delay_beamup) + edata_up:SetRadius(delay_beamdown) - util.Effect("teleport_beamup", edata_up) + util.Effect("teleport_beamup", edata_up) - local edata_dn = EffectData() - edata_dn:SetOrigin(teleport.pos) - edata_dn:SetAngles(Angle(0, ang.y, ang.r)) -- deep copy - edata_dn:SetEntity(ply) - edata_dn:SetMagnitude(delay_beamup) - edata_dn:SetRadius(delay_beamdown) + local edata_dn = EffectData() + edata_dn:SetOrigin(teleport.pos) + edata_dn:SetAngles(Angle(0, ang.y, ang.r)) -- deep copy + edata_dn:SetEntity(ply) + edata_dn:SetMagnitude(delay_beamup) + edata_dn:SetRadius(delay_beamdown) - util.Effect("teleport_beamdown", edata_dn) + util.Effect("teleport_beamdown", edata_dn) end --- -- @ignore function SWEP:TeleportRecall() - local ply = self:GetOwner() + local ply = self:GetOwner() - if not IsValid(ply) or not ply:IsTerror() then return end + if not IsValid(ply) or not ply:IsTerror() then + return + end - local mark = self:GetTeleportMark() + local mark = self:GetTeleportMark() - if mark then - local g = ply:GetGroundEntity() + if mark then + local g = ply:GetGroundEntity() - if g ~= game.GetWorld() and not IsValid(g) then - LANG.Msg(ply, "tele_no_ground", nil, MSG_MSTACK_WARN) + if g ~= game.GetWorld() and not IsValid(g) then + LANG.Msg(ply, "tele_no_ground", nil, MSG_MSTACK_WARN) - return - end + return + end - if ply:Crouching() then - LANG.Msg(ply, "tele_no_crouch", nil, MSG_MSTACK_WARN) + if ply:Crouching() then + LANG.Msg(ply, "tele_no_crouch", nil, MSG_MSTACK_WARN) - return - end + return + end - ply:Freeze(true) + ply:Freeze(true) - self:TakePrimaryAmmo(1) + self:TakePrimaryAmmo(1) - timer.Simple(0.2, function() StartTeleport(ply, mark, self) end) - else - LANG.Msg(ply, "tele_no_mark", nil, MSG_MSTACK_ROLE) - end + timer.Simple(0.2, function() + StartTeleport(ply, mark, self) + end) + else + LANG.Msg(ply, "tele_no_mark", nil, MSG_MSTACK_ROLE) + end end local function CanStoreTeleportPos(ply, pos) - local g = ply:GetGroundEntity() + local g = ply:GetGroundEntity() - if g ~= game.GetWorld() or (IsValid(g) and g:GetMoveType() ~= MOVETYPE_NONE) then - return false, "tele_no_mark_ground" - elseif ply:Crouching() then - return false, "tele_no_mark_crouch" - end + if g ~= game.GetWorld() or (IsValid(g) and g:GetMoveType() ~= MOVETYPE_NONE) then + return false, "tele_no_mark_ground" + elseif ply:Crouching() then + return false, "tele_no_mark_crouch" + end - return true, nil + return true, nil end --- -- @ignore function SWEP:TeleportStore() - local ply = self:GetOwner() + local ply = self:GetOwner() - if IsValid(ply) and ply:IsTerror() then - local allow, msg = CanStoreTeleportPos(ply, self:GetPos()) + if IsValid(ply) and ply:IsTerror() then + local allow, msg = CanStoreTeleportPos(ply, self:GetPos()) - if not allow then - LANG.Msg(ply, msg, nil, MSG_MSTACK_WARN) + if not allow then + LANG.Msg(ply, msg, nil, MSG_MSTACK_WARN) - return - end + return + end - self:SetTeleportMark(ply:GetPos(), ply:EyeAngles()) + self:SetTeleportMark(ply:GetPos(), ply:EyeAngles()) - LANG.Msg(ply, "tele_marked", nil, MSG_MSTACK_ROLE) - end + LANG.Msg(ply, "tele_marked", nil, MSG_MSTACK_ROLE) + end end --- -- @ignore function SWEP:Reload() - return false + return false end if CLIENT then - --- - -- @ignore - function SWEP:Initialize() - self:AddHUDHelp("tele_help_pri", "tele_help_sec", true) - - return self.BaseClass.Initialize(self) - end - - --- - -- @ignore - function SWEP:AddToSettingsMenu(parent) - local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") - - form:MakeCheckBox({ - serverConvar = "ttt_teleport_telefrags", - label = "label_teleport_telefrags" - }) - end + --- + -- @ignore + function SWEP:Initialize() + self:AddTTT2HUDHelp("tele_help_pri", "tele_help_sec") + + return BaseClass.Initialize(self) + end + + --- + -- @ignore + function SWEP:AddToSettingsMenu(parent) + local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") + + form:MakeCheckBox({ + serverConvar = "ttt_teleport_telefrags", + label = "label_teleport_telefrags", + }) + end end --- -- @ignore -function SWEP:Deploy() - if SERVER and IsValid(self:GetOwner()) then - self:GetOwner():DrawViewModel(false) - end - - return true -end - ---- --- @ignore -function SWEP:ShootEffects() - -end +function SWEP:ShootEffects() end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_unarmed.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_unarmed.lua new file mode 100644 index 000000000..a7d55d147 --- /dev/null +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_unarmed.lua @@ -0,0 +1,107 @@ +--- +-- @class SWEP +-- @section weapon_ttt_unarmed + +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("weapon_tttbase") + +SWEP.HoldType = "normal" + +if CLIENT then + SWEP.PrintName = "unarmed_name" + SWEP.Slot = 5 + + SWEP.ShowDefaultViewModel = false + + SWEP.EquipMenuData = { + type = "item_weapon", + } + + SWEP.Icon = "vgui/ttt/icon_unarmed" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.ViewModel = "models/weapons/v_crowbar.mdl" +SWEP.WorldModel = "models/weapons/w_crowbar.mdl" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.Kind = WEAPON_UNARMED +SWEP.InLoadoutFor = { ROLE_INNOCENT, ROLE_TRAITOR, ROLE_DETECTIVE } + +SWEP.AllowDelete = false +SWEP.AllowDrop = false +SWEP.overrideDropOnDeath = DROP_ON_DEATH_TYPE_DENY +SWEP.NoSights = true + +SWEP.silentPickup = true + +SWEP.builtin = true + +--- +-- @ignore +function SWEP:PrimaryAttack() end + +--- +-- @ignore +function SWEP:SecondaryAttack() end + +--- +-- @ignore +function SWEP:Reload() end + +--- +-- @ignore +function SWEP:Holster() + return true +end + +--- +-- @ignore +function SWEP:OnDrop() + self:Remove() +end + +--- +-- @ignore +function SWEP:ShouldDropOnDie() + return false +end + +if CLIENT then + --- + -- @realm client + function SWEP:DrawWorldModel() + if IsValid(self:GetOwner()) then + return + end + + self:DrawModel() + end + + --- + -- @realm client + function SWEP:DrawWorldModelTranslucent() end +end + +if SERVER then + --- + -- Always return true to be removed instead of dropped + -- @return boolean + -- @realm client + function SWEP:ShouldRemove() + return true + end +end diff --git a/gamemodes/terrortown/entities/weapons/weapon_ttt_wtester.lua b/gamemodes/terrortown/entities/weapons/weapon_ttt_wtester.lua index 1d2b4a54d..872c24db3 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_ttt_wtester.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_ttt_wtester.lua @@ -3,67 +3,69 @@ -- @section weapon_ttt_wtester if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local math = math local table = table -DEFINE_BASECLASS "weapon_tttbase" +DEFINE_BASECLASS("weapon_tttbase") SWEP.HoldType = "pistol" if CLIENT then - SWEP.PrintName = "dna_name" - SWEP.Slot = 8 + SWEP.PrintName = "dna_name" + SWEP.Slot = 8 - SWEP.ViewModelFOV = 54 - SWEP.ViewModelFlip = false - SWEP.UseHands = true - SWEP.DrawCrosshair = false + SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.UseHands = true - SWEP.EquipMenuData = { - type = "item_weapon", - desc = "dna_desc" - } + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "dna_desc", + } - SWEP.Icon = "vgui/ttt/icon_wtester" + SWEP.Icon = "vgui/ttt/icon_wtester" else - --network messages - util.AddNetworkString("TTT2ScannerFeedback") - util.AddNetworkString("TTT2ScannerUpdate") - - -- ConVar syncing - - --- - -- @realm server - local dna_mode = CreateConVar("ttt2_dna_radar", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Toggles the DNA Scanner functionality") - - --- - -- @realm server - local dna_slots = CreateConVar("ttt2_dna_scanner_slots", "4", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Defines the maximum amount of DNA slots") - - --- - -- @realm server - local dna_radar_cd = CreateConVar("ttt2_dna_radar_cooldown", "5.0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Defines the cooldown in seconds") - - hook.Add("TTT2SyncGlobals", "TTT2SyncDNAScannerGlobals", function() - SetGlobalBool(dna_mode:GetName(), dna_mode:GetBool()) - SetGlobalInt(dna_slots:GetName(), dna_slots:GetInt()) - SetGlobalFloat(dna_radar_cd:GetName(), dna_radar_cd:GetFloat()) - end) - - cvars.AddChangeCallback(dna_mode:GetName(), function(name, old, new) - SetGlobalBool(dna_mode:GetName(), tobool(new)) - end, dna_mode:GetName()) - - cvars.AddChangeCallback(dna_slots:GetName(), function(name, old, new) - SetGlobalInt(dna_slots:GetName(), tonumber(new)) - end, dna_slots:GetName()) - - cvars.AddChangeCallback(dna_radar_cd:GetName(), function(name, old, new) - SetGlobalFloat(dna_radar_cd:GetName(), tonumber(new)) - end, dna_radar_cd:GetName()) + --network messages + util.AddNetworkString("TTT2ScannerFeedback") + util.AddNetworkString("TTT2ScannerUpdate") + + -- ConVar syncing + + --- + -- @realm server + -- stylua: ignore + local dna_mode = CreateConVar("ttt2_dna_radar", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Toggles the DNA Scanner functionality") + + --- + -- @realm server + -- stylua: ignore + local dna_slots = CreateConVar("ttt2_dna_scanner_slots", "4", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Defines the maximum amount of DNA slots") + + --- + -- @realm server + -- stylua: ignore + local dna_radar_cd = CreateConVar("ttt2_dna_radar_cooldown", "5.0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Defines the cooldown in seconds") + + hook.Add("TTT2SyncGlobals", "TTT2SyncDNAScannerGlobals", function() + SetGlobalBool(dna_mode:GetName(), dna_mode:GetBool()) + SetGlobalInt(dna_slots:GetName(), dna_slots:GetInt()) + SetGlobalFloat(dna_radar_cd:GetName(), dna_radar_cd:GetFloat()) + end) + + cvars.AddChangeCallback(dna_mode:GetName(), function(name, old, new) + SetGlobalBool(dna_mode:GetName(), tobool(new)) + end, dna_mode:GetName()) + + cvars.AddChangeCallback(dna_slots:GetName(), function(name, old, new) + SetGlobalInt(dna_slots:GetName(), tonumber(new)) + end, dna_slots:GetName()) + + cvars.AddChangeCallback(dna_radar_cd:GetName(), function(name, old, new) + SetGlobalFloat(dna_radar_cd:GetName(), tonumber(new)) + end, dna_radar_cd:GetName()) end SWEP.Base = "weapon_tttbase" @@ -86,6 +88,7 @@ SWEP.Secondary.Delay = 0 SWEP.Kind = WEAPON_ROLE SWEP.CanBuy = nil -- no longer a buyable thing SWEP.WeaponID = AMMO_WTESTER +SWEP.builtin = true SWEP.AutoSpawnable = false SWEP.NoSights = true @@ -109,85 +112,61 @@ local dna_screen_fail = Material("models/ttt2_dna_scanner/screen/fail") local dna_screen_arrow = Material("models/ttt2_dna_scanner/screen/arrow") local dna_screen_circle = Material("models/ttt2_dna_scanner/screen/circle") ---- --- @ignore -function SWEP:Initialize() - if CLIENT then - -- Create render target - self.scannerScreenTex = GetRenderTarget("scanner_screen_tex", 512, 512) - - self.scannerScreenMat = CreateMaterial("scanner_screen_mat", "UnlitGeneric", { - ["$basetexture"] = self.scannerScreenTex, - ["$basetexturetransform"] = "center .5 .5 scale 1 1 rotate 180 translate 0 0" - }) - self.scannerScreenMat:SetTexture("$basetexture", self.scannerScreenTex) - - self:SetSubMaterial(0, "!scanner_screen_mat") - - surface.CreateAdvancedFont("DNAScannerDistanceFont", {font = "Trebuchet24", size = 32, weight = 1200}) - - if isfunction(self.AddTTT2HUDHelp) then - self:AddTTT2HUDHelp("dna_help_primary", "dna_help_secondary") - self:AddHUDHelpLine("dna_help_reload", Key("+reload", "R")) - else - ErrorNoHalt("[TTT2][ERROR] You are using an add-on that overwrites the 'weapon_tttbase.lua' file while not providing essential functions. That will lead to several incompatibilites.") - end - end - - return self.BaseClass.Initialize(self) -end - --- -- @ignore function SWEP:PrimaryAttack() - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) - local owner = self:GetOwner() + local owner = self:GetOwner() - -- will be tracing against players - owner:LagCompensation(true) + -- will be tracing against players + owner:LagCompensation(true) - local spos = owner:GetShootPos() - local sdest = spos + owner:GetAimVector() * self.Range + local spos = owner:GetShootPos() + local sdest = spos + owner:GetAimVector() * self.Range - local tr = util.TraceLine({ - start = spos, - endpos = sdest, - filter = owner, - mask = MASK_SHOT - }) + local tr = util.TraceLine({ + start = spos, + endpos = sdest, + filter = owner, + mask = MASK_SHOT, + }) - local ent = tr.Entity + local ent = tr.Entity - owner:LagCompensation(false) + owner:LagCompensation(false) - if SERVER then - self:GatherDNA(ent) - end + if SERVER then + self:GatherDNA(ent) + end end --- -- @ignore function SWEP:SecondaryAttack() - if not IsFirstTimePredicted() then return end + if not IsFirstTimePredicted() then + return + end - self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) - self.ActiveSample = (self.ActiveSample % GetGlobalBool("ttt2_dna_scanner_slots")) + 1 + self.ActiveSample = (self.ActiveSample % GetGlobalBool("ttt2_dna_scanner_slots")) + 1 - if CLIENT then - self:RadarScan() - end + if CLIENT then + self:RadarScan() + end end --- -- @ignore function SWEP:Reload() - if not IsFirstTimePredicted() then return end + if not IsFirstTimePredicted() then + return + end - self:RemoveSample() + self:RemoveSample() end --- @@ -196,89 +175,89 @@ end -- @param boolean oldFound -- @realm shared function SWEP:Report(successful, msg, oldFound) - if msg then - LANG.Msg(self:GetOwner(), msg, nil, MSG_MSTACK_ROLE) - end + if msg then + LANG.Msg(self:GetOwner(), msg, nil, MSG_MSTACK_ROLE) + end - net.Start("TTT2ScannerFeedback") - net.WriteBool(successful) - net.WriteBool(oldFound or false) + net.Start("TTT2ScannerFeedback") + net.WriteBool(successful) + net.WriteBool(oldFound or false) - if successful or oldFound then - net.WriteUInt(self.ActiveSample, 8) - net.WriteEntity(self.CachedTargets[self.ActiveSample]) - end + if successful or oldFound then + net.WriteUInt(self.ActiveSample, 8) + net.WriteEntity(self.CachedTargets[self.ActiveSample]) + end - net.Send(self:GetOwner()) + net.Send(self:GetOwner()) end --- -- @param Entity ent -- @realm shared function SWEP:GatherDNA(ent) - if not IsValid(ent) or ent:IsPlayer() then - self:Report(false) - - return - end - - if ent:GetClass() == "prop_ragdoll" and ent.killer_sample then - self:GatherRagdollSample(ent) - elseif ent.fingerprints and #ent.fingerprints > 0 then - self:GatherObjectSample(ent) - else - self:Report(false, "dna_notfound") - end + if not IsValid(ent) or ent:IsPlayer() then + self:Report(false) + + return + end + + if ent:GetClass() == "prop_ragdoll" and ent.killer_sample then + self:GatherRagdollSample(ent) + elseif ent.fingerprints and #ent.fingerprints > 0 then + self:GatherObjectSample(ent) + else + self:Report(false, "dna_notfound") + end end --- -- @param Entity ent -- @realm shared function SWEP:GatherRagdollSample(ent) - local sample = ent.killer_sample or {t = 0, killer = nil} - local ply = sample.killer - - if not IsValid(ply) and sample.killer_sid64 then - ply = player.GetBySteamID64(sample.killer_sid64) - end - - if IsValid(ply) then - if sample.t < CurTime() then - self:Report(false, "dna_decayed") - - return - end - - self:AddPlayerSample(ent, ply) - elseif not ply then - -- not valid but not nil -> disconnected? - self:Report(false, "dna_no_killer") - else - self:Report(false, "dna_notfound") - end + local sample = ent.killer_sample or { t = 0, killer = nil } + local ply = sample.killer + + if not IsValid(ply) and sample.killer_sid64 then + ply = player.GetBySteamID64(sample.killer_sid64) + end + + if IsValid(ply) then + if sample.t < CurTime() then + self:Report(false, "dna_decayed") + + return + end + + self:AddPlayerSample(ent, ply) + elseif not ply then + -- not valid but not nil -> disconnected? + self:Report(false, "dna_no_killer") + else + self:Report(false, "dna_notfound") + end end --- -- @param Entity ent -- @realm shared function SWEP:GatherObjectSample(ent) - if ent:GetClass() == "ttt_c4" and ent:GetArmed() then - self:Report(false, "dna_armed") - else - self:AddItemSample(ent) - end + if ent:GetClass() == "ttt_c4" and ent:GetArmed() then + self:Report(false, "dna_armed") + else + self:AddItemSample(ent) + end end local function firstFreeIndex(tbl, max, best) - if not tbl[best] then - return best - end - - for i = 1, max do - if not tbl[i] then - return i - end - end + if not tbl[best] then + return best + end + + for i = 1, max do + if not tbl[i] then + return i + end + end end --- @@ -286,381 +265,506 @@ end -- @param Player killer -- @realm shared function SWEP:AddPlayerSample(corpse, killer) - if table.Count(self.ItemSamples) >= GetGlobalBool("ttt2_dna_scanner_slots") then - self:Report(false, "dna_limit") - - return - end - - local owner = self:GetOwner() - - if table.HasValue(self.ItemSamples, killer) then - self.ActiveSample = table.KeyFromValue(self.ItemSamples, killer) - self:Report(false, "dna_duplicate", true) - else - local index = firstFreeIndex(self.ItemSamples, GetGlobalBool("ttt2_dna_scanner_slots"), self.ActiveSample) - - self.ActiveSample = index - self.ItemSamples[index] = killer - self.CachedTargets[index] = self:GetScanTarget(killer) - - DamageLog("SAMPLE:\t " .. owner:Nick() .. " retrieved DNA of " .. (IsValid(killer) and killer:Nick() or "") .. " from corpse of " .. (IsValid(corpse) and CORPSE.GetPlayerNick(corpse) or "")) - - --- - -- @realm shared - hook.Run("TTTFoundDNA", owner, killer, corpse) - - self:Report(true, "dna_killer") - end + if table.Count(self.ItemSamples) >= GetGlobalBool("ttt2_dna_scanner_slots") then + self:Report(false, "dna_limit") + + return + end + + local owner = self:GetOwner() + + if table.HasValue(self.ItemSamples, killer) then + self.ActiveSample = table.KeyFromValue(self.ItemSamples, killer) + self:Report(false, "dna_duplicate", true) + else + local index = firstFreeIndex( + self.ItemSamples, + GetGlobalBool("ttt2_dna_scanner_slots"), + self.ActiveSample + ) + + self.ActiveSample = index + self.ItemSamples[index] = killer + self.CachedTargets[index] = self:GetScanTarget(killer) + + DamageLog( + "SAMPLE:\t " + .. owner:Nick() + .. " retrieved DNA of " + .. (IsValid(killer) and killer:Nick() or "") + .. " from corpse of " + .. (IsValid(corpse) and CORPSE.GetPlayerNick(corpse) or "") + ) + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTTFoundDNA", owner, killer, corpse) + + self:Report(true, "dna_killer") + end end --- -- @param Entity ent -- @realm shared function SWEP:AddItemSample(ent) - if table.Count(self.ItemSamples) >= GetGlobalBool("ttt2_dna_scanner_slots") then - self:Report(false, "dna_limit") + if table.Count(self.ItemSamples) >= GetGlobalBool("ttt2_dna_scanner_slots") then + self:Report(false, "dna_limit") - return - end + return + end - local owner = self:GetOwner() + local owner = self:GetOwner() - for i = #ent.fingerprints, 1 do - local ply = ent.fingerprints[i] + for i = #ent.fingerprints, 1, -1 do + local ply = ent.fingerprints[i] - if ply == self:GetOwner() then continue end + if ply == self:GetOwner() then + continue + end - if table.HasValue(self.ItemSamples, ply) then - self.ActiveSample = table.KeyFromValue(self.ItemSamples, ply) + if table.HasValue(self.ItemSamples, ply) then + self.ActiveSample = table.KeyFromValue(self.ItemSamples, ply) - self:Report(false, "dna_duplicate", true) + self:Report(false, "dna_duplicate", true) - return - else - local index = firstFreeIndex(self.ItemSamples, GetGlobalBool("ttt2_dna_scanner_slots"), self.ActiveSample) + return + else + local index = firstFreeIndex( + self.ItemSamples, + GetGlobalBool("ttt2_dna_scanner_slots"), + self.ActiveSample + ) - self.ActiveSample = index - self.ItemSamples[index] = ply - self.CachedTargets[index] = self:GetScanTarget(ply) + self.ActiveSample = index + self.ItemSamples[index] = ply + self.CachedTargets[index] = self:GetScanTarget(ply) - DamageLog("SAMPLE:\t " .. owner:Nick() .. " retrieved DNA of " .. (IsValid(ply) and ply:Nick() or "") .. " from " .. ent:GetClass()) + DamageLog( + "SAMPLE:\t " + .. owner:Nick() + .. " retrieved DNA of " + .. (IsValid(ply) and ply:Nick() or "") + .. " from " + .. ent:GetClass() + ) - --- - -- @realm shared - hook.Run("TTTFoundDNA", owner, ply, ent) + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTTFoundDNA", owner, ply, ent) - self:Report(true, "dna_object") + self:Report(true, "dna_object") - return - end - end + return + end + end - self:Report(false, "dna_notfound") + self:Report(false, "dna_notfound") end --- -- @realm shared function SWEP:RemoveSample() - local idx = self.ActiveSample + local idx = self.ActiveSample - if not self.ItemSamples[idx] then return end + if not self.ItemSamples[idx] then + return + end - self.ItemSamples[idx] = nil + self.ItemSamples[idx] = nil - if CLIENT then - self:RadarScan() + if CLIENT then + self:RadarScan() - return - else - self.CachedTargets[idx] = nil - end + return + else + self.CachedTargets[idx] = nil + end end --- -- @realm shared function SWEP:PassiveThink() - if not IsValid(self:GetOwner()) then return end + if not IsValid(self:GetOwner()) then + return + end - if SERVER then - self:UpdateTargets() + if SERVER then + self:UpdateTargets() - return - end + return + end - if GetGlobalBool("ttt2_dna_radar") and self.LastRadar + GetGlobalFloat("ttt2_dna_radar_cooldown") < CurTime() then - local target = self.ItemSamples[self.ActiveSample] + if + GetGlobalBool("ttt2_dna_radar") + and self.LastRadar + GetGlobalFloat("ttt2_dna_radar_cooldown") < CurTime() + then + local target = self.ItemSamples[self.ActiveSample] - if not IsValid(target) then return end + if not IsValid(target) then + return + end - self.RadarPos = target:LocalToWorld(target:OBBCenter()) - self.LastRadar = CurTime() + self.RadarPos = target:LocalToWorld(target:OBBCenter()) + self.LastRadar = CurTime() - self:RadarScan() - end + self:RadarScan() + end end if SERVER then - --- - -- @param Player ply - -- @return Player - -- @realm server - function SWEP:GetScanTarget(ply) - if not IsValid(ply) then return end - - -- decoys always take priority, even after death - if IsValid(ply.decoy) then - ply = ply.decoy - elseif not ply:IsTerror() then - -- fall back to ragdoll, as long as it's not destroyed - ply = ply.server_ragdoll - - if not IsValid(ply) then return end - end - - return ply - end - - --- - -- @realm server - function SWEP:UpdateTargets() - for i = 1, GetGlobalBool("ttt2_dna_scanner_slots") do - local ply = self.ItemSamples[i] - - if not IsValid(ply) then continue end - - local target = self:GetScanTarget(ply) - - if target ~= self.CachedTargets[i] then - self.CachedTargets[i] = target - - net.Start("TTT2ScannerUpdate") - net.WriteUInt(i, 8) - net.WriteEntity(target) - net.Send(self:GetOwner()) - end - end - end - - --- - -- Hook that is called for each fingerprint found. - -- @param Player finder The finder that used the DNA scanner to find a fingerprint - -- @param Player toucher The player whose fingerprint was found, for example the killer - -- @param Entity ent The entity where the fingerprint was found - -- @hook - -- @realm server - function GAMEMODE:TTTFoundDNA(finder, toucher, ent) - - end -else -- CLIENT - local TryT = LANG.TryTranslation - local screen_bgcolor = Color(220, 220, 220, 255) - local screen_fontcolor = Color(144, 210, 235, 255) - - local function DrawTexturedRectRotatedPoint(x, y, w, h, rot, x0, y0) - local c = math.cos(math.rad(rot)) - local s = math.sin(math.rad(rot)) - - local newx = y0 * s - x0 * c - local newy = y0 * c + x0 * s - - surface.DrawTexturedRectRotated(x + newx, y + newy, w, h, rot) - end - - --- - -- @realm client - function SWEP:FillScannerScreen() - if self.scannerScreenTex == nil then return end - - local showFeedback = CurTime() > self.ScanTime + 0.5 - local target = self.ItemSamples[self.ActiveSample] - - -- Draw to the render target - render.PushRenderTarget(self.scannerScreenTex) - render.Clear(screen_bgcolor.r, screen_bgcolor.g, screen_bgcolor.b, screen_bgcolor.a, true, true) - - cam.Start2D() - - --draw background - draw.FilteredTexture(0, 0, 512, 512, dna_screen_background, 255, COLOR_WHITE) - - --draw current slot - local identifier = string.char(64 + self.ActiveSample) - - draw.AdvancedText(identifier, "DNAScannerDistanceFont", 65, 64, screen_fontcolor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER , false, 1.75) - - if showFeedback then - local owner = self:GetOwner() - - if IsValid(target) and IsValid(owner) then - local targetPos = GetGlobalBool("ttt2_dna_radar") and self.RadarPos or target:LocalToWorld(target:OBBCenter()) - local scannerPos = owner:GetPos() - local vectorToPos = targetPos - scannerPos - local angleToPos = vectorToPos:Angle() - local arrowRotation = angleToPos.yaw - EyeAngles().yaw - local distance = math.max(LocalPlayer():GetPos():Distance(targetPos) - 47, 0) - - surface.SetDrawColor(96, 255, 96 , 255) - surface.SetMaterial(dna_screen_arrow) - DrawTexturedRectRotatedPoint(256, 256, 120, 120, arrowRotation, 0, -130) - - draw.AdvancedText(math.Round(distance), "DNAScannerDistanceFont", 256, 256, screen_fontcolor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER , false, 2.25) - - draw.FilteredTexture(146, 146, 220, 220, dna_screen_circle, 255, screen_fontcolor) - else - draw.AdvancedText(TryT("dna_screen_ready"), "DNAScannerDistanceFont", 256, 256, screen_fontcolor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER , false, 2.5) - end - else - if self.ScanSuccess == 1 then - draw.FilteredTexture(192, 192, 128, 128, dna_screen_success, 255, screen_fontcolor) - elseif self.ScanSuccess == 2 then - draw.AdvancedText(TryT("dna_screen_match"), "DNAScannerDistanceFont", 256, 256, screen_fontcolor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER , false, 2.5) - else - draw.FilteredTexture(192, 192, 128, 128, dna_screen_fail, 255, screen_fontcolor) - end - end - - cam.End2D() - - render.PopRenderTarget() - end - - --- - -- @ignore - function SWEP:PreDrawViewModel() - self:FillScannerScreen() - self:GetOwner():GetViewModel():SetSubMaterial(0, "!scanner_screen_mat") - end - - --- - -- @ignore - function SWEP:PostDrawViewModel() - self:GetOwner():GetViewModel():SetSubMaterial(0, nil) - end - - --- - -- @ignore - function SWEP:DrawWorldModel() - self:FillScannerScreen() - self:DrawModel() - end - - --- - -- @realm client - function SWEP:RadarScan() - local target = self.ItemSamples[self.ActiveSample] - - if not IsValid(target) or not GetGlobalBool("ttt2_dna_radar") then - RADAR.samples = {} - RADAR.samples_count = 0 - - self.RadarPos = nil - - return - end - - self.RadarPos = target:LocalToWorld(target:OBBCenter()) - - RADAR.samples = {{pos = self.RadarPos}} - RADAR.samples_count = 1 - end - - --- - -- @ignore - function SWEP:AddToSettingsMenu(parent) - local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") - - form:MakeHelp({ - label = "help_killer_dna_range" - }) - - form:MakeSlider({ - serverConvar = "ttt_killer_dna_range", - label = "label_killer_dna_range", - min = 0, - max = 1000, - decimal = 0 - }) - - form:MakeHelp({ - label = "help_killer_dna_basetime" - }) - - form:MakeSlider({ - serverConvar = "ttt_killer_dna_basetime", - label = "label_killer_dna_basetime", - min = 0, - max = 200, - decimal = 0 - }) - - form:MakeSlider({ - serverConvar = "ttt2_dna_scanner_slots", - label = "label_dna_scanner_slots", - min = 0, - max = 10, - decimal = 0 - }) - - form:MakeHelp({ - label = "help_dna_radar" - }) - - local enb = form:MakeCheckBox({ - serverConvar = "ttt2_dna_radar", - label = "label_dna_radar" - }) - - form:MakeSlider({ - serverConvar = "ttt2_dna_radar_cooldown", - label = "label_dna_radar_cooldown", - min = 0, - max = 60, - decimal = 1, - master = enb - }) - end - - local function ScannerFeedback() - if not LocalPlayer():HasWeapon("weapon_ttt_wtester") then return end - - local scanner = LocalPlayer():GetWeapon("weapon_ttt_wtester") - - local successful = net.ReadBool() - local oldFound = net.ReadBool() - - if successful or oldFound then - scanner.ActiveSample = net.ReadUInt(8) - scanner.ItemSamples[scanner.ActiveSample] = net.ReadEntity() - scanner.NewSample = scanner.ActiveSample - - scanner:RadarScan() - end - - scanner.ScanTime = CurTime() - - if successful then - scanner:EmitSound(beep_success) - scanner.ScanSuccess = 1 - elseif oldFound then - scanner:EmitSound(beep_match) - scanner.ScanSuccess = 2 - else - scanner:EmitSound(beep_miss) - scanner.ScanSuccess = 0 - end - end - net.Receive("TTT2ScannerFeedback", ScannerFeedback) - - local function ScannerUpdate() - local client = LocalPlayer() - - if not client:HasWeapon("weapon_ttt_wtester") then return end - - local scanner = client:GetWeapon("weapon_ttt_wtester") - local idx = net.ReadUInt(8) - - scanner.ItemSamples[idx] = net.ReadEntity() - end - net.Receive("TTT2ScannerUpdate", ScannerUpdate) + --- + -- @param Player ply + -- @return Player + -- @realm server + function SWEP:GetScanTarget(ply) + if not IsValid(ply) then + return + end + + -- decoys always take priority, even after death + if IsValid(ply.decoy) then + ply = ply.decoy + elseif not ply:IsTerror() then + -- fall back to ragdoll, as long as it's not destroyed + ply = ply.server_ragdoll + + if not IsValid(ply) then + return + end + end + + return ply + end + + --- + -- @realm server + function SWEP:UpdateTargets() + for i = 1, GetGlobalBool("ttt2_dna_scanner_slots") do + local ply = self.ItemSamples[i] + + if not IsValid(ply) then + continue + end + + local target = self:GetScanTarget(ply) + + if target ~= self.CachedTargets[i] then + self.CachedTargets[i] = target + + net.Start("TTT2ScannerUpdate") + net.WriteUInt(i, 8) + net.WriteEntity(target) + net.Send(self:GetOwner()) + end + end + end + + --- + -- Hook that is called for each fingerprint found. + -- @param Player finder The finder that used the DNA scanner to find a fingerprint + -- @param Player toucher The player whose fingerprint was found, for example the killer + -- @param Entity ent The entity where the fingerprint was found + -- @hook + -- @realm server + function GAMEMODE:TTTFoundDNA(finder, toucher, ent) end +end -- SERVER + +if CLIENT then + local TryT = LANG.TryTranslation + local screen_bgcolor = Color(220, 220, 220, 255) + local screen_fontcolor = Color(144, 210, 235, 255) + + surface.CreateAdvancedFont( + "DNAScannerDistanceFont", + { font = "Tahoma", size = 32, weight = 1200, extended = true } + ) + + local function DrawTexturedRectRotatedPoint(x, y, w, h, rot, x0, y0) + local c = math.cos(math.rad(rot)) + local s = math.sin(math.rad(rot)) + + local newx = y0 * s - x0 * c + local newy = y0 * c + x0 * s + + surface.DrawTexturedRectRotated(x + newx, y + newy, w, h, rot) + end + + --- + -- @realm client + function SWEP:Initialize() + -- Create render target + self.scannerScreenTex = GetRenderTarget("scanner_screen_tex", 512, 512) + + self.scannerScreenMat = CreateMaterial("scanner_screen_mat", "UnlitGeneric", { + ["$basetexture"] = self.scannerScreenTex, + ["$basetexturetransform"] = "center .5 .5 scale 1 1 rotate 180 translate 0 0", + }) + self.scannerScreenMat:SetTexture("$basetexture", self.scannerScreenTex) + + self:SetSubMaterial(0, "!scanner_screen_mat") + + self:AddTTT2HUDHelp("dna_help_primary", "dna_help_secondary") + self:AddHUDHelpLine("dna_help_reload", Key("+reload", "R")) + + return BaseClass.Initialize(self) + end + + --- + -- @realm client + function SWEP:FillScannerScreen() + if self.scannerScreenTex == nil then + return + end + + local showFeedback = CurTime() > self.ScanTime + 0.5 + local target = self.ItemSamples[self.ActiveSample] + + -- Draw to the render target + render.PushRenderTarget(self.scannerScreenTex) + render.Clear( + screen_bgcolor.r, + screen_bgcolor.g, + screen_bgcolor.b, + screen_bgcolor.a, + true, + true + ) + + cam.Start2D() + + --draw background + draw.FilteredTexture(0, 0, 512, 512, dna_screen_background, 255, COLOR_WHITE) + + --draw current slot + local identifier = string.char(64 + self.ActiveSample) + + draw.AdvancedText( + identifier, + "DNAScannerDistanceFont", + 65, + 64, + screen_fontcolor, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + false, + 1.75 + ) + + if showFeedback then + local owner = self:GetOwner() + + if IsValid(target) and IsValid(owner) then + local targetPos = GetGlobalBool("ttt2_dna_radar") and self.RadarPos + or target:LocalToWorld(target:OBBCenter()) + local scannerPos = owner:GetPos() + local vectorToPos = targetPos - scannerPos + local angleToPos = vectorToPos:Angle() + local arrowRotation = angleToPos.yaw - EyeAngles().yaw + local distance = tostring( + math.Round( + util.HammerUnitsToMeters(LocalPlayer():EyePos():Distance(targetPos)), + 0 + ) + ) .. "m" + + surface.SetDrawColor(96, 255, 96, 255) + surface.SetMaterial(dna_screen_arrow) + DrawTexturedRectRotatedPoint(256, 256, 120, 120, arrowRotation, 0, -130) + + draw.AdvancedText( + distance, + "DNAScannerDistanceFont", + 256, + 256, + screen_fontcolor, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + false, + 2.25 + ) + + draw.FilteredTexture(146, 146, 220, 220, dna_screen_circle, 255, screen_fontcolor) + else + draw.AdvancedText( + TryT("dna_screen_ready"), + "DNAScannerDistanceFont", + 256, + 256, + screen_fontcolor, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + false, + 2.5 + ) + end + else + if self.ScanSuccess == 1 then + draw.FilteredTexture(192, 192, 128, 128, dna_screen_success, 255, screen_fontcolor) + elseif self.ScanSuccess == 2 then + draw.AdvancedText( + TryT("dna_screen_match"), + "DNAScannerDistanceFont", + 256, + 256, + screen_fontcolor, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + false, + 2.5 + ) + else + draw.FilteredTexture(192, 192, 128, 128, dna_screen_fail, 255, screen_fontcolor) + end + end + + cam.End2D() + + render.PopRenderTarget() + end + + --- + -- @ignore + function SWEP:PreDrawViewModel() + self:FillScannerScreen() + self:GetOwner():GetViewModel():SetSubMaterial(0, "!scanner_screen_mat") + end + + --- + -- @ignore + function SWEP:PostDrawViewModel() + self:GetOwner():GetViewModel():SetSubMaterial(0, nil) + end + + --- + -- @ignore + function SWEP:DrawWorldModel() + self:FillScannerScreen() + self:DrawModel() + end + + --- + -- @realm client + function SWEP:RadarScan() + local target = self.ItemSamples[self.ActiveSample] + + if not IsValid(target) or not GetGlobalBool("ttt2_dna_radar") then + RADAR.samples = {} + RADAR.samples_count = 0 + + self.RadarPos = nil + + return + end + + self.RadarPos = target:LocalToWorld(target:OBBCenter()) + + RADAR.samples = { { pos = self.RadarPos } } + RADAR.samples_count = 1 + end + + --- + -- @ignore + function SWEP:AddToSettingsMenu(parent) + local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") + + form:MakeHelp({ + label = "help_killer_dna_range", + }) + + form:MakeSlider({ + serverConvar = "ttt_killer_dna_range", + label = "label_killer_dna_range", + min = 0, + max = 1000, + decimal = 0, + }) + + form:MakeHelp({ + label = "help_killer_dna_basetime", + }) + + form:MakeSlider({ + serverConvar = "ttt_killer_dna_basetime", + label = "label_killer_dna_basetime", + min = 0, + max = 200, + decimal = 0, + }) + + form:MakeSlider({ + serverConvar = "ttt2_dna_scanner_slots", + label = "label_dna_scanner_slots", + min = 0, + max = 10, + decimal = 0, + }) + + form:MakeHelp({ + label = "help_dna_radar", + }) + + local enb = form:MakeCheckBox({ + serverConvar = "ttt2_dna_radar", + label = "label_dna_radar", + }) + + form:MakeSlider({ + serverConvar = "ttt2_dna_radar_cooldown", + label = "label_dna_radar_cooldown", + min = 0, + max = 60, + decimal = 1, + master = enb, + }) + end + + local function ScannerFeedback() + if not LocalPlayer():HasWeapon("weapon_ttt_wtester") then + return + end + + local scanner = LocalPlayer():GetWeapon("weapon_ttt_wtester") + + local successful = net.ReadBool() + local oldFound = net.ReadBool() + + if successful or oldFound then + scanner.ActiveSample = net.ReadUInt(8) + scanner.ItemSamples[scanner.ActiveSample] = net.ReadEntity() + scanner.NewSample = scanner.ActiveSample + + scanner:RadarScan() + end + + scanner.ScanTime = CurTime() + + if successful then + scanner:EmitSound(beep_success) + scanner.ScanSuccess = 1 + elseif oldFound then + scanner:EmitSound(beep_match) + scanner.ScanSuccess = 2 + else + scanner:EmitSound(beep_miss) + scanner.ScanSuccess = 0 + end + end + net.Receive("TTT2ScannerFeedback", ScannerFeedback) + + local function ScannerUpdate() + local client = LocalPlayer() + + if not client:HasWeapon("weapon_ttt_wtester") then + return + end + + local scanner = client:GetWeapon("weapon_ttt_wtester") + local idx = net.ReadUInt(8) + + scanner.ItemSamples[idx] = net.ReadEntity() + end + net.Receive("TTT2ScannerUpdate", ScannerUpdate) end diff --git a/gamemodes/terrortown/entities/weapons/weapon_tttbase.lua b/gamemodes/terrortown/entities/weapons/weapon_tttbase.lua index 90501a1da..74762e320 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_tttbase.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_tttbase.lua @@ -1,7 +1,7 @@ --- -- @class SWEP -- @desc Custom weapon base, used to derive from CS one, still very similar. --- See Weapon +-- See Weapon -- @section weapon_tttbase local math = math @@ -14,11 +14,19 @@ local draw = draw local weaponMetaTable = FindMetaTable("Weapon") if SERVER then - AddCSLuaFile() -else -- CLIENT - -- hud help font - surface.CreateFont("weapon_hud_help", {font = "Trebuchet24", size = 17, weight = 600}) - surface.CreateFont("weapon_hud_help_key", {font = "Trebuchet24", size = 13, weight = 1200}) + AddCSLuaFile() +end + +if CLIENT then + -- hud help font + surface.CreateAdvancedFont( + "weapon_hud_help", + { font = "Tahoma", size = 16, weight = 600, extended = true } + ) + surface.CreateAdvancedFont( + "weapon_hud_help_key", + { font = "Tahoma", size = 13, weight = 1200, extended = true } + ) end -- TTT SPECIAL EQUIPMENT FIELDS @@ -39,35 +47,51 @@ SWEP.CanBuy = nil SWEP.notBuyable = false if CLIENT then - -- If this is a buyable weapon (ie. CanBuy is not nil) EquipMenuData must be - -- a table containing some information to show in the Equipment Menu. See - -- default equipment weapons for real-world examples. - SWEP.EquipMenuData = nil - - -- Example data: - -- SWEP.EquipMenuData = { - -- - -- Type tells players if it's a weapon or item - -- type = "Weapon", - -- - -- Desc is the description in the menu. Needs manual linebreaks (via \n). - -- desc = "Text." - -- } - - -- This sets the icon shown for the weapon in the DNA sampler, search window, - -- equipment menu (if buyable), etc. - SWEP.Icon = "vgui/ttt/icon_nades" -- most generic icon I guess - - -- You can make your own weapon icon using the template in: - -- /garrysmod/gamemodes/terrortown/template/ - - -- Open one of TTT's icons with VTFEdit to see what kind of settings to use - -- when exporting to VTF. Once you have a VTF and VMT, you can - -- resource.AddFile("materials/vgui/...") them here. GIVE YOUR ICON A UNIQUE - -- FILENAME, or it WILL be overwritten by other servers! Gmod does not check - -- if the files are different, it only looks at the name. I recommend you - -- create your own directory so that this does not happen, - -- eg. /materials/vgui/ttt/mycoolserver/mygun.vmt + -- If this is a buyable weapon (ie. CanBuy is not nil) EquipMenuData must be + -- a table containing some information to show in the Equipment Menu. See + -- default equipment weapons for real-world examples. + SWEP.EquipMenuData = nil + + -- Example data: + -- SWEP.EquipMenuData = { + -- + -- Type tells players if it's a weapon or item + -- type = "Weapon", + -- + -- Desc is the description in the menu. Needs manual linebreaks (via \n). + -- desc = "Text." + -- } + + -- This sets the icon shown for the weapon in the DNA sampler, search window, + -- equipment menu (if buyable), etc. + SWEP.Icon = "vgui/ttt/icon_nades" -- most generic icon I guess + + -- You can make your own weapon icon using the template in: + -- /garrysmod/gamemodes/terrortown/template/ + + -- Open one of TTT's icons with VTFEdit to see what kind of settings to use + -- when exporting to VTF. Once you have a VTF and VMT, you can + -- resource.AddFile("materials/vgui/...") them here. GIVE YOUR ICON A UNIQUE + -- FILENAME, or it WILL be overwritten by other servers! Gmod does not check + -- if the files are different, it only looks at the name. I recommend you + -- create your own directory so that this does not happen, + -- eg. /materials/vgui/ttt/mycoolserver/mygun.vmt + + -- Set this to true ONLY if a weapon uses CS:S viewmodels that fail to recenter after firing. + -- Also requires SWEP.IdleAnim to be set to the appropriate animation, usually ACT_VM_IDLE or ACT_VM_IDLE_SILENCED. + SWEP.idleResetFix = false + + -- It set to true, hands are drawn in the view model + SWEP.UseHands = false + + -- If set to true, the default world model of the weapon is drawn. If set to false + -- the hands are still drawn in the position of the SWEP.HoldType. + SWEP.ShowDefaultWorldModel = true + + -- If set to true, the default view model of the weapon is drawn, otherwise it + -- is hidden and no view model is drawn. Set SWEP.UseHands to true to only hide + -- the weapon but still draw the hands holding it. + SWEP.ShowDefaultViewModel = true end -- MISC TTT-SPECIFIC BEHAVIOUR CONFIGURATION @@ -96,6 +120,9 @@ SWEP.IsSilent = false -- in close proximity to this weapon when spawned. SWEP.autoAmmoAmount = 0 +-- It this is set to true, there will be no pickup notification when receiving this weapon. +SWEP.silentPickup = false + -- Set Keys like { "HeadshotMultiplier", "Weight", { "Primary", "Recoil" }, { "Secondary", "Ammo" } } if you want the data to be persistent after hotreloads -- Empty it before a hotreload to reset data after a hotreload, otherwise this data keep persisting until you do a map reload or restart your server -- Can be useful if you have multiple instances, that rely on global variables stored via weapons.GetStored() @@ -113,10 +140,10 @@ SWEP.HotReloadableKeys = {} -- YE OLDE SWEP STUFF if CLIENT then - SWEP.DrawCrosshair = false - SWEP.ViewModelFOV = 82 - SWEP.ViewModelFlip = true - SWEP.CSMuzzleFlashes = true + SWEP.DrawCrosshair = false + SWEP.ViewModelFOV = 82 + SWEP.ViewModelFlip = true + SWEP.CSMuzzleFlashes = true end SWEP.Base = "weapon_base" @@ -158,33 +185,53 @@ SWEP.DeploySpeed = 1.4 SWEP.PrimaryAnim = ACT_VM_PRIMARYATTACK SWEP.ReloadAnim = ACT_VM_RELOAD +SWEP.IdleAnim = ACT_VM_IDLE SWEP.fingerprints = {} --[[ - -- The position offset applied when entering the ironsight - SWEP.IronSightsPos = Vector(0, 0, 0) - -- The rotational offset applied when entering the ironsight - SWEP.IronSightsAng = Vector(0, 0, 0) + -- The position offset applied when entering the ironsight + SWEP.IronSightsPos = Vector(0, 0, 0) + -- The rotational offset applied when entering the ironsight + SWEP.IronSightsAng = Vector(0, 0, 0) --]] local skipWeapons = {} --- -- Checks if the weapon should be skipped. Skips all weapons not based on weapon_tttbase --- @param weapon swep the weapon to check +-- @param Weapon swep the weapon to check -- @realm shared -- @internal local function shouldSkipWeapon(swep) - local className = swep:GetClass() - local skipWeapon = skipWeapons[className] + local className = swep:GetClass() + local skipWeapon = skipWeapons[className] + + if skipWeapon == nil then + skipWeapon = not weapons.IsBasedOn(className, "weapon_tttbase") + skipWeapons[className] = skipWeapon + end + + return skipWeapon +end - if skipWeapon == nil then - skipWeapon = not weapons.IsBasedOn(className, "weapon_tttbase") - skipWeapons[className] = skipWeapon - end +-- The original Remove is not saved in the weaponMetaTable, it only exists on Entity. +local oldRemove = FindMetaTable("Entity").Remove - return skipWeapon +--- +-- This changes the function Remove of all weapons, but only affects ones that implement ShouldRemove +-- This enables changing weapon drop behavior against a convention of being removed +-- @param any ... A variable amount of arguments passed to this event +-- @realm shared +function weaponMetaTable:Remove(...) + if self.ShouldRemove and isfunction(self.ShouldRemove) then + local res = self:ShouldRemove(...) + if not res then + return res + end + end + + return oldRemove(self, ...) end -- The original SetNextPrimaryFire saved in the weaponMetaTable @@ -195,338 +242,641 @@ local tickInterval = engine.TickInterval() -- This changes the function SetNextPrimaryFire of all weapons, but filters out all weapons not based on the weapon_tttbase -- This compensates for weapons not having the same timesteps as the serverside-tickrate, which otherwise would lead to a lower firerate on average -- @param number nextTime The time you want to have the next primary attack available --- @param[opt] bool skipTickrateFix If you want to use the old function and just SetNextPrimaryFire without Tickrate Fix +-- @param[opt] boolean skipTickrateFix If you want to use the old function and just SetNextPrimaryFire without Tickrate Fix -- @realm shared function weaponMetaTable:SetNextPrimaryFire(nextTime, skipTickrateFix) - if not skipTickrateFix and not shouldSkipWeapon(self) then - local diff = CurTime() - self:GetNextPrimaryFire() + if not skipTickrateFix and not shouldSkipWeapon(self) then + local diff = CurTime() - self:GetNextPrimaryFire() - if diff > 0 and diff < tickInterval then - nextTime = nextTime - diff - end - end + if diff > 0 and diff < tickInterval then + nextTime = nextTime - diff + end + end - oldSetNextPrimaryFire(self, nextTime) + oldSetNextPrimaryFire(self, nextTime) end --- -- @realm client +-- stylua: ignore local sparkle = CLIENT and CreateConVar("ttt_crazy_sparks", "0", FCVAR_ARCHIVE, "Toggles whether the `cball_bounce` Effect should get triggered on the hit position") or nil --- -- @realm client +-- stylua: ignore local ttt2_hold_aim = CLIENT and CreateConVar("ttt2_hold_aim", 0, FCVAR_ARCHIVE, "Toogles whether you have to hold the key to aim", 0, 1) or nil -- crosshair if CLIENT then - local GetPTranslation = LANG.GetParamTranslation - local TryT = LANG.TryTranslation - - --- - -- @realm client - local sights_opacity = CreateConVar("ttt_ironsights_crosshair_opacity", "0.8", FCVAR_ARCHIVE) - - --- - -- @realm client - local crosshair_size = CreateConVar("ttt_crosshair_size", "1.0", FCVAR_ARCHIVE) - - --- - -- @realm client - local enable_crosshair = CreateConVar("ttt_enable_crosshair", "1", FCVAR_ARCHIVE) - - --- - -- @realm client - local enable_gap_crosshair = CreateConVar("ttt_crosshair_gap_enable", "0", FCVAR_ARCHIVE) - - --- - -- @realm client - local crosshair_gap = CreateConVar("ttt_crosshair_gap", "0", FCVAR_ARCHIVE) - - --- - -- @realm client - local crosshair_opacity = CreateConVar("ttt_crosshair_opacity", "1", FCVAR_ARCHIVE) - - --- - -- @realm client - local crosshair_static = CreateConVar("ttt_crosshair_static", "0", FCVAR_ARCHIVE) - - --- - -- @realm client - local crosshair_weaponscale = CreateConVar("ttt_crosshair_weaponscale", "1", FCVAR_ARCHIVE) - - --- - -- @realm client - local crosshair_thickness = CreateConVar("ttt_crosshair_thickness", "1", FCVAR_ARCHIVE) - - --- - -- @realm client - local crosshair_outlinethickness = CreateConVar("ttt_crosshair_outlinethickness", "0", FCVAR_ARCHIVE) - - --- - -- @realm client - local enable_dot_crosshair = CreateConVar("ttt_crosshair_dot", "0", FCVAR_ARCHIVE) - - --- - -- @realm client - local enable_crosshair_lines = CreateConVar("ttt_crosshair_lines", "1", FCVAR_ARCHIVE) - - local icon_help_primary = Material("vgui/ttt/hudhelp/lmb") - local icon_help_secondary = Material("vgui/ttt/hudhelp/rmb") - - --- - -- @see https://wiki.facepunch.com/gmod/WEAPON:DrawHUD - -- @realm client - function SWEP:DrawHUD() - if self.HUDHelp then - self:DrawHelp() - end - - local client = LocalPlayer() - - if not enable_crosshair:GetBool() or not IsValid(client) or client.isSprinting and not GetGlobalBool("ttt2_sprint_crosshair", false) then return end - - local sights = not self.NoSights and self:GetIronsights() - local x = math.floor(ScrW() * 0.5) - local y = math.floor(ScrH() * 0.5) - local scale = crosshair_weaponscale:GetBool() and math.max(0.2, 10 * self:GetPrimaryCone()) or 1 - local timescale = 1 - - if not crosshair_static:GetBool() then - timescale = (2 - math.Clamp((CurTime() - self:LastShootTime()) * 5, 0.0, 1.0)) - end - - local alpha = sights and sights_opacity:GetFloat() or crosshair_opacity:GetFloat() - local gap = enable_gap_crosshair:GetBool() and math.floor(timescale * crosshair_gap:GetFloat()) or math.floor(20 * scale * timescale * (sights and 0.8 or 1)) - local thickness = crosshair_thickness:GetFloat() - local outline = math.floor(crosshair_outlinethickness:GetFloat()) - local length = math.floor(gap + 25 * crosshair_size:GetFloat() * scale * timescale) - local offset = thickness * 0.5 - - if outline > 0 then - surface.SetDrawColor(0, 0, 0, 255 * alpha) - surface.DrawRect(x - length - outline, y - offset - outline, length - gap + outline * 2, thickness + outline * 2) - surface.DrawRect(x + gap - outline, y - offset - outline, length - gap + outline * 2, thickness + outline * 2) - surface.DrawRect(x - offset - outline, y - length - outline, thickness + outline * 2, length - gap + outline * 2) - surface.DrawRect(x - offset - outline, y + gap - outline, thickness + outline * 2, length - gap + outline * 2) - end - - -- set up crosshair color - local color = client.GetRoleColor and client:GetRoleColor() or roles.INNOCENT.color - - color = appearance.SelectFocusColor(color) - - surface.SetDrawColor( - color.r, - color.g, - color.b, - 255 * alpha - ) - - -- draw crosshair dot - if enable_dot_crosshair:GetBool() then - surface.DrawRect(x - thickness * 0.5, y - thickness * 0.5, thickness, thickness) - end - - -- draw crosshair lines - if enable_crosshair_lines:GetBool() then - surface.DrawRect(x - length, y - offset, length - gap, thickness) - surface.DrawRect(x + gap, y - offset, length - gap, thickness) - surface.DrawRect(x - offset, y - length, thickness, length - gap) - surface.DrawRect(x - offset, y + gap, thickness, length - gap) - end - end - - --- - -- @param number x - -- @param number y - -- @param string key - -- @realm client - function SWEP:DrawKeyBox(x, y, key) - local pad = 3 - local pad2 = pad * 2 - - x = x - pad + 1 - y = y - pad2 * 0.5 + 1 - - local key_box_w, key_box_h = draw.GetTextSize(key, "weapon_hud_help_key") - - key_box_w = key_box_w + 3 * pad - key_box_h = key_box_h + pad2 - - local key_box_x = x - key_box_w + 1.5 * pad - local key_box_y = y - key_box_h + 0.5 * pad2 - - surface.SetDrawColor(0, 0, 0, 150) - surface.DrawRect(key_box_x, key_box_y, key_box_w, key_box_h) - draw.ShadowedText(key, "weapon_hud_help_key", x, y, COLOR_WHITE, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM) - draw.OutlinedShadowedBox(key_box_x, key_box_y, key_box_w, key_box_h, 1, COLOR_WHITE) - end - - --- - -- Draws a line on the screen - -- @param number x x coordinate of the line - -- @param number y y coordinate of the line - -- @param string text text for the line - -- @param[opt] Material|string icon_or_key icon or description for the concerning key - -- @realm client - function SWEP:DrawHelpLine(x, y, text, icon_or_key) - local icon_size = 18 - local valid_icon = true - - if isstring(icon_or_key) then - self:DrawKeyBox(x, y, icon_or_key) - elseif icon_or_key then - draw.FilteredShadowedTexture(x - icon_size + 2, y - 17, icon_size, icon_size, icon_or_key, 255, COLOR_WHITE) - else - valid_icon = false - end - - draw.ShadowedText(TryT(text), "weapon_hud_help", x + 20, y, COLOR_WHITE, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) - - return valid_icon - end - - --- - -- Draws the help text to the bottom of the screen - -- @realm client - function SWEP:DrawHelp() - local data = self.HUDHelp - local additional_lines = data.additional_lines - local x = ScrW() * 0.5 - data.max_length * 0.5 - local y_start = ScrH() - 25 - local y = y_start - local delta_y = 25 - local valid_icon = false - - for i = #additional_lines, 1, -1 do - local line = additional_lines[i] - local drawn_icon = self:DrawHelpLine(x, y, line.text, line.icon) - - valid_icon = valid_icon or drawn_icon - y = y - delta_y - end - - if valid_icon then - local line_x = x + 10 - - draw.ShadowedLine(line_x, y_start + 2, line_x, y + 8, COLOR_WHITE) - end - end - - -- mousebuttons are enough for most weapons - local default_key_params = { - primaryfire = Key("+attack", "MOUSE1"), - secondaryfire = Key("+attack2", "MOUSE2"), - usekey = Key("+use", "USE") - } - - --- - -- Adds a help text for the weapon to the HUD. - -- TTT legacy function. - -- @param[opt] string primary_text first line of the help text - -- @param[optchain] string secondary_text second line of the help text - -- @param[optchain][default=false] bool translate should the text get translated - -- @param[optchain] table extra_params parameters for @{Lang.GetParamTranslation} - -- @realm client - function SWEP:AddHUDHelp(primary_text, secondary_text, translate, extra_params) - local primary = primary_text - local secondary = secondary_text - - if translate then - extra_params = extra_params or {} - translate_params = table.Merge(extra_params, default_key_params) - primary = primary and GetPTranslation(primary, translate_params) - secondary = secondary and GetPTranslation(secondary, translate_params) - end - - --find mouse keys in the texts to add respective icons - primary_key = primary and string.find(primary, "MOUSE1") and Key("+attack", "MOUSE1") or nil - secondary_key = secondary and string.find(secondary, "MOUSE2") and Key("+attack2", "MOUSE2") or nil - - self:AddTTT2HUDHelp() - - if primary then - self:AddHUDHelpLine(primary, primary_key) - end - - if secondary then - self:AddHUDHelpLine(secondary, secondary_key) - end - end - - --- - -- Adds a help text for the weapon to the HUD. - -- @param[opt] string primary_text description for primaryfire - -- @param[optchain] string secondary_text description for secondaryfire - -- @realm client - function SWEP:AddTTT2HUDHelp(primary, secondary) - self.HUDHelp = { - additional_lines = {}, - max_length = 0 - } - - if primary then - self:AddHUDHelpLine(primary, Key("+attack", "MOUSE1")) - end - - if secondary then - self:AddHUDHelpLine(secondary, Key("+attack2", "MOUSE2")) - end - end - - --- - -- Adds an additional line to the help text. - -- @{SWEP:AddTTT2HUDHelp} needs to be called first - -- @param string text text to be displayed on the line - -- @param[opt] Material|string icon_or_key icon or description for the concerning key - -- @realm client - function SWEP:AddHUDHelpLine(text, icon_or_key) - if not self.HUDHelp then return end - - --replace MOUSE1/MOUSE2 strings with respective icons - if isstring(icon_or_key) then - if icon_or_key == "MOUSE1" then - icon_or_key = icon_help_primary - elseif icon_or_key == "MOUSE2" then - icon_or_key = icon_help_secondary - end - end - - local width = draw.GetTextSize(text, "weapon_hud_help") - - self.HUDHelp.additional_lines[#self.HUDHelp.additional_lines + 1] = {text = text, icon = icon_or_key} - self.HUDHelp.max_length = math.max(self.HUDHelp.max_length, width) - end - - --- - -- This hook draws the selection icon in the weapon selection menu. - -- @see https://wiki.facepunch.com/gmod/WEAPON:DrawWeaponSelection - -- @realm client - function SWEP:DrawWeaponSelection() - - end - - --- - -- @realm client - function SWEP:CalcViewModel() - if not IsFirstTimePredicted() then return end - - self.bIron = self:GetIronsights() - self.fIronTime = self:GetIronsightsTime() - self.fCurrentTime = CurTime() - self.fCurrentSysTime = SysTime() - end - - --- - -- This hook can be used by swep addons to populate the equipment settings page - -- with custom convars. The parent is the submenu, where a new form has to - -- be added. - -- @param DPanel parent The parent panel which is the submenu - -- @hook - -- @realm client - function SWEP:AddToSettingsMenu(parent) - - end + local TryT = LANG.TryTranslation + local ParT = LANG.GetParamTranslation + + local mathRound = math.Round + local mathClamp = math.Clamp + local mathCeil = math.ceil + local mathMax = math.max + + local CROSSHAIR_MODE_DOT_AND_LINES = 0 + local CROSSHAIR_MODE_LINES_ONLY = 1 + local CROSSHAIR_MODE_DOT_ONLY = 2 + + --- + -- @realm client + -- stylua: ignore + local cvOpacitySights = CreateConVar("ttt_ironsights_crosshair_opacity", "0.8", FCVAR_ARCHIVE) + + --- + -- @realm client + -- stylua: ignore + local cvSizeCrosshair = CreateConVar("ttt_crosshair_size", "1.0", FCVAR_ARCHIVE) + + --- + -- @realm client + -- stylua: ignore + local cvEnableCrosshair = CreateConVar("ttt_enable_crosshair", "1", FCVAR_ARCHIVE) + + --- + -- @realm client + -- stylua: ignore + local cvOpacityCrosshair = CreateConVar("ttt_crosshair_opacity", "1", FCVAR_ARCHIVE) + + --- + -- @realm client + -- stylua: ignore + local cvCrosshairUseWeaponscale = CreateConVar("ttt_crosshair_weaponscale", "1", FCVAR_ARCHIVE) + + --- + -- @realm client + -- stylua: ignore + local cvCrosshairStaticLength = CreateConVar("ttt_crosshair_static_length", "0", FCVAR_ARCHIVE) + + --- + -- @realm client + -- stylua: ignore + local cvThicknessCrosshair = CreateConVar("ttt_crosshair_thickness", "1", FCVAR_ARCHIVE) + + --- + -- @realm client + -- stylua: ignore + local cvEnableOutlineCrosshair = CreateConVar("ttt_crosshair_outline_enable", "0", FCVAR_ARCHIVE) + + --- + -- @realm client + -- stylua: ignore + local cvThicknessOutlineCrosshair = CreateConVar("ttt_crosshair_outline_thickness", "1", FCVAR_ARCHIVE) + + --- + -- @realm client + -- stylua: ignore + local cvHighContrastOutlineCrosshair = CreateConVar("ttt_crosshair_outline_high_contrast", "0", FCVAR_ARCHIVE) + + --- + -- @realm client + -- stylua: ignore + local cvCrosshairMode = CreateConVar("ttt_crosshair_mode", "0", FCVAR_ARCHIVE) + + local materialKeyLMB = Material("vgui/ttt/hudhelp/lmb") + local materialKeyRMB = Material("vgui/ttt/hudhelp/rmb") + + local animData = { + timeStart = 0, + timeEnd = 0, + valueStart = 0, + valueEnd = 0, + } + + --- + -- @see https://wiki.facepunch.com/gmod/WEAPON:DrawHUD + -- @realm client + function SWEP:DrawHUD() + if self.HUDHelp then + self:DrawHelp() + end + + if not cvEnableCrosshair:GetBool() then + return + end + + local client = LocalPlayer() + local sights = not self.NoSights and self:GetIronsights() + + local xCenter = mathCeil(ScrW() * 0.5) + local yCenter = mathCeil(ScrH() * 0.5) + local scale = appearance.GetGlobalScale() + local baseConeWeapon = mathMax(0.2, 10 * self:GetPrimaryConeBase()) + local scaleWeapon = cvCrosshairUseWeaponscale:GetBool() + and math.max(0.2, 10 * self:GetPrimaryCone()) + or 1 + local timescale = 2 - mathClamp((CurTime() - self:LastShootTime()) * 5, 0.0, 1.0) + + -- handle size animation + if scaleWeapon ~= animData.valueEnd then + animData = { + timeStart = CurTime(), + timeEnd = CurTime() + 0.25, + valueStart = animData.valueEnd, + valueEnd = scaleWeapon, + } + end + + scaleWeapon = Lerp( + math.ease.OutQuint(math.TimeFraction(animData.timeStart, animData.timeEnd, CurTime())), + animData.valueStart, + animData.valueEnd + ) + + local alpha = sights and cvOpacitySights:GetFloat() or cvOpacityCrosshair:GetFloat() + local gap = mathCeil(25 * scaleWeapon * timescale * scale * cvSizeCrosshair:GetFloat()) + local thicknessLine = mathCeil(cvThicknessCrosshair:GetFloat() * scale) + local thicknessOutline = mathCeil(cvThicknessOutlineCrosshair:GetFloat() * scale) + local lengthLine = mathCeil( + gap + + 25 + * cvSizeCrosshair:GetFloat() + * (cvCrosshairStaticLength:GetBool() and baseConeWeapon or scaleWeapon) + * timescale + * scale + ) + local offsetLine = mathCeil(thicknessLine * 0.5) + + -- set up crosshair color + local color = appearance.SelectFocusColor( + client.GetRoleColor and client:GetRoleColor() or roles.INNOCENT.color + ) + local colorOutline = cvHighContrastOutlineCrosshair:GetBool() + and util.GetDefaultColor(color) + or COLOR_BLACK + + -- draw crosshair dot + if + cvCrosshairMode:GetInt() == CROSSHAIR_MODE_DOT_AND_LINES + or cvCrosshairMode:GetInt() == CROSSHAIR_MODE_DOT_ONLY + then + local xDot = xCenter - offsetLine + local yDot = yCenter - offsetLine + + if cvEnableOutlineCrosshair:GetBool() then + surface.SetDrawColor( + colorOutline.r, + colorOutline.g, + colorOutline.b, + colorOutline.a * alpha + ) + + surface.DrawRect( + xDot - thicknessOutline, + yDot - thicknessOutline, + thicknessLine + thicknessOutline * 2, + thicknessLine + thicknessOutline * 2 + ) + end + + surface.SetDrawColor(color.r, color.g, color.b, color.a * alpha) + + surface.DrawRect(xDot, yDot, thicknessLine, thicknessLine) + end + + -- draw crosshair lines + if + cvCrosshairMode:GetInt() == CROSSHAIR_MODE_DOT_AND_LINES + or cvCrosshairMode:GetInt() == CROSSHAIR_MODE_LINES_ONLY + then + if cvEnableOutlineCrosshair:GetBool() then + surface.SetDrawColor( + colorOutline.r, + colorOutline.g, + colorOutline.b, + colorOutline.a * alpha + ) + + surface.DrawRect( + xCenter - lengthLine - thicknessOutline, + yCenter - offsetLine - thicknessOutline, + lengthLine - gap + thicknessOutline * 2, + thicknessLine + thicknessOutline * 2 + ) + surface.DrawRect( + xCenter + gap - thicknessOutline, + yCenter - offsetLine - thicknessOutline, + lengthLine - gap + thicknessOutline * 2, + thicknessLine + thicknessOutline * 2 + ) + surface.DrawRect( + xCenter - offsetLine - thicknessOutline, + yCenter - lengthLine - thicknessOutline, + thicknessLine + thicknessOutline * 2, + lengthLine - gap + thicknessOutline * 2 + ) + surface.DrawRect( + xCenter - offsetLine - thicknessOutline, + yCenter + gap - thicknessOutline, + thicknessLine + thicknessOutline * 2, + lengthLine - gap + thicknessOutline * 2 + ) + end + + surface.SetDrawColor(color.r, color.g, color.b, color.a * alpha) + + surface.DrawRect( + xCenter - lengthLine, + yCenter - offsetLine, + lengthLine - gap, + thicknessLine + ) + surface.DrawRect(xCenter + gap, yCenter - offsetLine, lengthLine - gap, thicknessLine) + surface.DrawRect( + xCenter - offsetLine, + yCenter - lengthLine, + thicknessLine, + lengthLine - gap + ) + surface.DrawRect(xCenter - offsetLine, yCenter + gap, thicknessLine, lengthLine - gap) + end + end + + local colorBox = Color(0, 0, 0, 100) + local colorDarkBox = Color(0, 0, 0, 150) + + local sizeIcon = 16 + local padYKey = 3 + local padXKey = 5 + + local function ProcessHelpText(lines, scale) + local widthBinding, widthDescription = 0, 0 + local processedData = {} + + for i = 1, #lines do + local line = lines[i] + local binding = line.binding -- can be an icon or key + local description = line.text + + local wBinding, hBinding = 0, 0 + local isIcon = false + + if isstring(binding) then + local wKey, hKey = draw.GetTextSize(binding, "weapon_hud_help_key", scale) + + wBinding = wKey + 2 * padXKey * scale + hBinding = hKey + 2 * padYKey * scale + + isIcon = false + elseif binding then + wBinding = sizeIcon * scale + hBinding = sizeIcon * scale + isIcon = true + else + continue + end + + local translatedDescription = TryT(description) + local wDescription = draw.GetTextSize(translatedDescription, "weapon_hud_help", scale) + + processedData[i] = { + w = wBinding, + h = hBinding, + isIcon = isIcon, + binding = binding, + description = translatedDescription, + wDescription = wDescription, + } + + widthBinding = math.max(widthBinding, wBinding) + widthDescription = math.max(widthDescription, wDescription) + end + + return widthBinding, widthDescription, processedData + end + + --- + -- Draws the help text to the bottom of the screen + -- @realm client + function SWEP:DrawHelp() + if not self.HUDHelp or not #self.HUDHelp.bindingLines then + return + end + local scale = appearance.GetGlobalScale() + + local baseWidthBinding, baseWidthDescription, processedData = + ProcessHelpText(self.HUDHelp.bindingLines, scale) + + local padding = 10 * scale + local hLine = 23 * scale + + local wBox = baseWidthBinding + baseWidthDescription + 4 * padding + local hBox = hLine * #processedData + 2 * padding + local xBox = 0.5 * (ScrW() - wBox) + local yBox = ScrH() - hBox + local xDivider = xBox + baseWidthBinding + 2 * padding + local yDividerStart = yBox + padding + local yLine = yDividerStart + 10 * scale + local xDescription = xDivider + padding + + if GetConVar("ttt2_hud_enable_box_blur"):GetBool() then + draw.BlurredBox(xBox, yBox, wBox, hBox) + draw.Box(xBox, yBox, wBox, hBox, colorBox) -- background color + draw.Box(xBox, yBox, wBox, mathRound(1 * scale), colorBox) -- top line shadow + draw.Box(xBox, yBox, wBox, mathRound(2 * scale), colorBox) -- top line shadow + draw.Box(xBox, yBox - mathRound(2 * scale), wBox, mathRound(2 * scale), COLOR_WHITE) -- white top line + end + + draw.ShadowedBox( + xDivider, + yDividerStart, + mathRound(scale), + hBox - 2 * padding, + COLOR_WHITE, + scale + ) + + for i = 1, #processedData do + local line = processedData[i] + + local w = line.w + local h = line.h + local xBinding = xDivider - padding - w + local yBinding = yLine - 0.5 * h + + if line.isIcon then + draw.FilteredShadowedTexture( + xBinding, + yBinding, + w, + h, + line.binding, + 255, + COLOR_WHITE, + scale + ) + else + draw.Box(xBinding, yBinding + 1, w, h, colorDarkBox) + draw.OutlinedShadowedBox(xBinding, yBinding + 1, w, h, 1, COLOR_WHITE) + + draw.AdvancedText( + line.binding, + "weapon_hud_help_key", + xBinding + 0.5 * w, + yLine, + COLOR_WHITE, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + true, + scale + ) + end + + draw.AdvancedText( + line.description, + "weapon_hud_help", + xDescription, + yLine, + COLOR_WHITE, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + true, + scale + ) + + yLine = yLine + hLine + end + end + + -- mousebuttons are enough for most weapons + local defaultKeyParams = { + primaryfire = Key("+attack", "MOUSE1"), + secondaryfire = Key("+attack2", "MOUSE2"), + usekey = Key("+use", "USE"), + } + + --- + -- Adds a help text for the weapon to the HUD. + -- @deprecated TTT legacy function. Do not use for new addons! + -- @param[opt] string primary_text first line of the help text + -- @param[optchain] string secondary_text second line of the help text + -- @param[optchain][default=false] boolean translate should the text get translated + -- @param[optchain] table extraKeyParams parameters for @{Lang.GetParamTranslation} + -- @realm client + function SWEP:AddHUDHelp(primary_text, secondary_text, translate, extraKeyParams) + local primary = primary_text + local secondary = secondary_text + + if translate then + extraKeyParams = extraKeyParams or {} + translate_params = table.Merge(extraKeyParams, defaultKeyParams) + primary = primary and ParT(primary, translate_params) + secondary = secondary and ParT(secondary, translate_params) + end + + --find mouse keys in the texts to add respective icons + primary_key = primary and string.find(primary, "MOUSE1") and Key("+attack", "MOUSE1") or nil + secondary_key = secondary and string.find(secondary, "MOUSE2") and Key("+attack2", "MOUSE2") + or nil + + self:ClearHUDHelp() + + if primary then + self:AddHUDHelpLine(primary, primary_key) + end + + if secondary then + self:AddHUDHelpLine(secondary, secondary_key) + end + end + + --- + -- Adds a help text for the weapon to the HUD. + -- @param[opt] string primary_text description for primaryfire + -- @param[optchain] string secondary_text description for secondaryfire + -- @realm client + function SWEP:AddTTT2HUDHelp(primary, secondary) + self:ClearHUDHelp() + + if primary then + self:AddHUDHelpLine(primary, Key("+attack", "MOUSE1")) + end + + if secondary then + self:AddHUDHelpLine(secondary, Key("+attack2", "MOUSE2")) + end + end + + --- + -- Utility for removing all existing help lines so more can be added. + -- This can be useful for dynamically updating help text. + -- @realm client + function SWEP:ClearHUDHelp() + self.HUDHelp = { + bindingLines = {}, + max_length = 0, + } + end + + --- + -- Adds an additional line to the help text. + -- @{SWEP:AddTTT2HUDHelp} needs to be called first + -- @param string text text to be displayed on the line + -- @param[opt] Material|string materialOrBinding icon or description for the concerning key + -- @realm client + function SWEP:AddHUDHelpLine(text, materialOrBinding) + if not self.HUDHelp then + return + end + + --replace MOUSE1/MOUSE2 strings with respective icons + if isstring(materialOrBinding) then + if materialOrBinding == "MOUSE1" then + materialOrBinding = materialKeyLMB + elseif materialOrBinding == "MOUSE2" then + materialOrBinding = materialKeyRMB + end + end + + local width = draw.GetTextSize(text, "weapon_hud_help") + + self.HUDHelp.bindingLines[#self.HUDHelp.bindingLines + 1] = + { text = text, binding = materialOrBinding } + self.HUDHelp.max_length = math.max(self.HUDHelp.max_length or 0, width) + end + + --- + -- This hook draws the selection icon in the weapon selection menu. + -- @see https://wiki.facepunch.com/gmod/WEAPON:DrawWeaponSelection + -- @realm client + function SWEP:DrawWeaponSelection() end + + --- + -- @realm client + function SWEP:OnRemove() + local owner = self:GetOwner() + + if IsValid(owner) and owner == LocalPlayer() and owner:IsTerror() then + RunConsoleCommand("lastinv") + end + end + + --- + -- @realm client + function SWEP:CalcViewModel() + if not IsFirstTimePredicted() then + return + end + + self.bIron = self:GetIronsights() + self.fIronTime = self:GetIronsightsTime() + self.fCurrentTime = CurTime() + self.fCurrentSysTime = SysTime() + end + + --- + -- Adds a custom view model. + -- @note Multiple view models can be added, they are all rendered at once. + -- @note Call this in @{SWEP:InitializeCustomModels}. + -- @param string identifier The name of the added view model + -- @param ModelData modelData The model data table + -- @realm client + function SWEP:AddCustomViewModel(identifier, modelData) + self.customViewModelElements = self.customViewModelElements or {} + + self.customViewModelElements[identifier] = weaponrenderer.CreateModel(self, modelData) + end + + --- + -- Adds a custom world model. + -- @note Multiple world models can be added, they are all rendered at once. + -- @note Call this in @{SWEP:InitializeCustomModels}. + -- @param string identifier The name of the added world model + -- @param ModelData modelData The model data table + -- @realm client + function SWEP:AddCustomWorldModel(identifier, modelData) + self.customWorldModelElements = self.customWorldModelElements or {} + + self.customWorldModelElements[identifier] = weaponrenderer.CreateModel(self, modelData) + end + + --- + -- Adds modifications to view model bones. + -- @param string identifier The identifier for this bone + -- @param BoneData boneData The bone data table + -- @realm client + function SWEP:ApplyViewModelBoneMods(identifier, boneData) + self.customViewModelBoneMods = self.customViewModelBoneMods or {} + self.customViewModelBoneMods[identifier] = boneData + end + + --- + -- Initialization function that is only used to initialize custom view and world models with + -- @{SWEP:AddCustomViewModel} and @{SWEP:AddCustomWorldModel}. + -- @warning This function is also called in the setup for clientside weapon entities for the UI. + -- Therefore this function should only contain the setup calls for custom view and world models. + -- Entity specific function calls might throw errors in this function. + -- @realm client + function SWEP:InitializeCustomModels() end + + --- + -- Called straight after the view model has been drawn. This is called before + -- @{GM:PostDrawViewModel} and @{WEAPON:PostDrawViewModel}. + -- @warning If you override ViewModelDrawn in your SWEP and you are using a custom world or + -- view model, you should call BaseClass.ViewModelDrawn(self) so as not to break viewmodels. + -- @param Entity viewModel Player's view model + -- @see https://wiki.facepunch.com/gmod/WEAPON:ViewModelDrawn + -- @realm client + function SWEP:ViewModelDrawn(viewModel) + weaponrenderer.RenderViewModel(self, self.customViewModelElements, viewModel) + end + + --- + -- Called when we are about to draw the world model. + -- @realm client + function SWEP:DrawWorldModel() + local client = LocalPlayer() + + -- draw view model when spectating player + if + client:GetObserverMode() == OBS_MODE_IN_EYE + and client:GetObserverTarget() == self:GetOwner() + then + return + end + + weaponrenderer.RenderWorldModel(self, self, self.customWorldModelElements, self:GetOwner()) + end + + --- + -- Allows you to modify viewmodel while the weapon in use before it is drawn. + -- @warning This hook only works if you haven't overridden @{GM:PreDrawViewModel}. + -- @param Entity viewModel This is the view model entity before it is drawn + -- @param Player ply The the owner of the view model + -- @param Weapon wep This is the weapon that is from the view model + -- @return boolean Return true to prevent the default view model rendering. This also affects @{GM:PostDrawViewModel} + -- @realm client + hook.Add("PreDrawViewModel", "TTT2ViewModelHider", function(viewModel, ply, wep) + -- special case: Hands should be shown, but the view model weapon shouldn't be; in this + -- case we have to apply this debug material to make it invisible because returning true + -- in this hook would prevent both the hands and the weapon from rendering + if wep.UseHands and not wep.ShowDefaultViewModel then + viewModel:SetMaterial("vgui/hsv") + + return + end + + -- default case: Normal view model texture is used and view model draw is defined + -- with the SWEP.ShowDefaultViewModel variable + viewModel:SetMaterial("") + + -- only return something if we actually want to hide it because otherwise the SWEP + -- hook is never called even if the view model is rendered + if not wep.ShowDefaultViewModel then + return true + end + end) + + --- + -- This hook can be used by swep addons to populate the equipment settings page + -- with custom convars. The parent is the submenu, where a new form has to + -- be added. + -- @param DPanel parent The parent panel which is the submenu + -- @hook + -- @realm client + function SWEP:AddToSettingsMenu(parent) end end --- @@ -536,38 +886,53 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:PrimaryAttack -- @realm shared function SWEP:PrimaryAttack(worldsnd) - self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - - if not self:CanPrimaryAttack() then return end - - if not worldsnd then - self:EmitSound(self.Primary.Sound, self.Primary.SoundLevel) - elseif SERVER then - sound.Play(self.Primary.Sound, self:GetPos(), self.Primary.SoundLevel) - end - - self:ShootBullet(self.Primary.Damage, self.Primary.Recoil, self.Primary.NumShots, self:GetPrimaryCone()) - self:TakePrimaryAmmo(1) - - local owner = self:GetOwner() - - if not IsValid(owner) or owner:IsNPC() or not owner.ViewPunch then return end - - owner:ViewPunch(Angle(util.SharedRandom(self:GetClass(), -0.2, -0.1, 0) * self.Primary.Recoil, util.SharedRandom(self:GetClass(), -0.1, 0.1, 1) * self.Primary.Recoil, 0)) + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + if not self:CanPrimaryAttack() then + return + end + + if not worldsnd then + self:EmitSound(self.Primary.Sound, self.Primary.SoundLevel) + elseif SERVER then + sound.Play(self.Primary.Sound, self:GetPos(), self.Primary.SoundLevel) + end + + self:ShootBullet( + self.Primary.Damage, + self:GetPrimaryRecoil(), + self.Primary.NumShots, + self:GetPrimaryCone() + ) + self:TakePrimaryAmmo(1) + + local owner = self:GetOwner() + + if not IsValid(owner) or owner:IsNPC() or not owner.ViewPunch then + return + end + + owner:ViewPunch( + Angle( + util.SharedRandom(self:GetClass(), -0.2, -0.1, 0) * self:GetPrimaryRecoil(), + util.SharedRandom(self:GetClass(), -0.1, 0.1, 1) * self:GetPrimaryRecoil(), + 0 + ) + ) end --- -- @param function setnext -- @realm shared function SWEP:DryFire(setnext) - if CLIENT and LocalPlayer() == self:GetOwner() then - self:EmitSound("Weapon_Pistol.Empty") - end + if CLIENT and LocalPlayer() == self:GetOwner() then + self:EmitSound("Weapon_Pistol.Empty") + end - setnext(self, CurTime() + 0.2) + setnext(self, CurTime() + 0.2) - self:Reload() + self:Reload() end --- @@ -576,15 +941,17 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:CanPrimaryAttack -- @realm shared function SWEP:CanPrimaryAttack() - if not IsValid(self:GetOwner()) then return end + if not IsValid(self:GetOwner()) then + return + end - if self:Clip1() <= 0 then - self:DryFire(self.SetNextPrimaryFire) + if self:Clip1() <= 0 then + self:DryFire(self.SetNextPrimaryFire) - return false - end + return false + end - return true + return true end --- @@ -593,101 +960,164 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:CanSecondaryAttack -- @realm shared function SWEP:CanSecondaryAttack() - if not IsValid(self:GetOwner()) then return end + if not IsValid(self:GetOwner()) then + return + end - if self:Clip2() <= 0 then - self:DryFire(self.SetNextSecondaryFire) + if self:Clip2() <= 0 then + self:DryFire(self.SetNextSecondaryFire) - return false - end + return false + end - return true + return true end local function Sparklies(attacker, tr, dmginfo) - if not tr.HitWorld or tr.MatType ~= MAT_METAL then return end + if not tr.HitWorld or tr.MatType ~= MAT_METAL then + return + end - local eff = EffectData() - eff:SetOrigin(tr.HitPos) - eff:SetNormal(tr.HitNormal) + local eff = EffectData() + eff:SetOrigin(tr.HitPos) + eff:SetNormal(tr.HitNormal) - util.Effect("cball_bounce", eff) + util.Effect("cball_bounce", eff) end --- -- A convenience function to shoot bullets --- @param DamageInfo dmg +-- @param CTakeDamageInfo dmg -- @param number recoil -- @param number numbul -- @param number cone -- @see https://wiki.facepunch.com/gmod/WEAPON:ShootBullet -- @realm shared function SWEP:ShootBullet(dmg, recoil, numbul, cone) - self:SendWeaponAnim(self.PrimaryAnim) - - self:GetOwner():MuzzleFlash() - self:GetOwner():SetAnimation(PLAYER_ATTACK1) - - local sights = self:GetIronsights() - - numbul = numbul or 1 - cone = cone or 0.01 - - local bullet = {} - bullet.Num = numbul - bullet.Src = self:GetOwner():GetShootPos() - bullet.Dir = self:GetOwner():GetAimVector() - bullet.Spread = Vector(cone, cone, 0) - bullet.Tracer = 4 - bullet.TracerName = self.Tracer or "Tracer" - bullet.Force = 10 - bullet.Damage = dmg - - if CLIENT and sparkle:GetBool() then - bullet.Callback = Sparklies - end - - self:GetOwner():FireBullets(bullet) + self:SendWeaponAnim(self.PrimaryAnim) + + self:GetOwner():MuzzleFlash() + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + + numbul = numbul or 1 + cone = cone or 0.02 + + local bullet = {} + bullet.Num = numbul + bullet.Src = self:GetOwner():GetShootPos() + bullet.Dir = self:GetOwner():GetAimVector() + bullet.Spread = Vector(cone, cone, 0) + bullet.Tracer = 4 + bullet.TracerName = self.Tracer or "Tracer" + bullet.Force = 10 + bullet.Damage = dmg * (self.damageScaling or 1) + + if CLIENT and sparkle:GetBool() then + bullet.Callback = Sparklies + end + + self:GetOwner():FireBullets(bullet) + + -- Owner can die after firebullets + if not IsValid(self:GetOwner()) or self:GetOwner():IsNPC() or not self:GetOwner():Alive() then + return + end + + if + SERVER and game.SinglePlayer() + or CLIENT and not game.SinglePlayer() and IsFirstTimePredicted() + then + local eyeang = self:GetOwner():EyeAngles() + eyeang.pitch = eyeang.pitch - recoil + + self:GetOwner():SetEyeAngles(eyeang) + end +end - -- Owner can die after firebullets - if not IsValid(self:GetOwner()) or not self:GetOwner():Alive() or self:GetOwner():IsNPC() then return end +--- +-- @return number +-- @realm shared +function SWEP:GetPrimaryConeFactor() + local owner = self:GetOwner() + + if not IsValid(owner) then + return 1 + end + + if SPRINT:IsSprinting(owner) and not owner:IsOnGround() then + return 2.0 + elseif SPRINT:IsSprinting(owner) or not owner:IsOnGround() then + return 1.6 + elseif self:GetIronsights() then + return 0.8 + else + return 1 + end +end - if game.SinglePlayer() and SERVER - or not game.SinglePlayer() and CLIENT and IsFirstTimePredicted() then - -- reduce recoil if ironsighting - recoil = sights and (recoil * 0.6) or recoil +--- +-- @return number +-- @realm shared +function SWEP:GetPrimaryRecoilFactor() + local owner = self:GetOwner() + + if not IsValid(owner) then + return 1 + end + + if SPRINT:IsSprinting(owner) and not owner:IsOnGround() then + return 2.8 + elseif SPRINT:IsSprinting(owner) or not owner:IsOnGround() then + return 2.2 + elseif self:GetIronsights() then + return 0.6 + else + return 1 + end +end - local eyeang = self:GetOwner():EyeAngles() - eyeang.pitch = eyeang.pitch - recoil +--- +-- @return number +-- @realm shared +function SWEP:GetPrimaryCone() + return self:GetPrimaryConeBase() * self:GetPrimaryConeFactor() +end - self:GetOwner():SetEyeAngles(eyeang) - end +--- +-- @return number +-- @realm shared +function SWEP:GetPrimaryConeBase() + return self.Primary.Cone or 0.02 end --- -- @return number -- @realm shared -function SWEP:GetPrimaryCone() - local cone = self.Primary.Cone or 0.2 +function SWEP:GetPrimaryRecoil() + return self:GetPrimaryRecoilBase() * self:GetPrimaryRecoilFactor() +end - -- 10% accuracy bonus when sighting - return self:GetIronsights() and (cone * 0.85) or cone +--- +-- @return number +-- @realm shared +function SWEP:GetPrimaryRecoilBase() + return self.Primary.Recoil or 1.5 end --- -- @param Player victim --- @param DamageInfo dmginfo +-- @param CTakeDamageInfo dmginfo -- @return number -- @realm shared function SWEP:GetHeadshotMultiplier(victim, dmginfo) - return self.HeadshotMultiplier + return self.HeadshotMultiplier end --- -- @return boolean -- @realm shared function SWEP:IsEquipment() - return WEPS.IsEquipment(self) + return WEPS.IsEquipment(self) end --- @@ -696,13 +1126,16 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:SecondaryAttack -- @realm shared function SWEP:SecondaryAttack() - if self.NoSights or not self.IronSightsPos then return end + if self.NoSights or not self.IronSightsPos then + return + end - local bNotIronsights = not self:GetIronsights() + local bNotIronsights = not self:GetIronsights() - self:SetIronsights(bNotIronsights) - self:SetZoom(bNotIronsights) - self:SetNextSecondaryFire(CurTime() + 0.3) + self:SetIronsights(bNotIronsights) + self:SetZoom(bNotIronsights) + + self:SetNextSecondaryFire(CurTime() + 0.3) end --- @@ -711,10 +1144,10 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:Deploy -- @realm shared function SWEP:Deploy() - self:SetIronsights(false) - self:SetZoom(false) + self:SetIronsights(false) + self:SetZoom(false) - return true + return true end --- @@ -722,11 +1155,17 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:Reload -- @realm shared function SWEP:Reload() - if self:Clip1() == self.Primary.ClipSize or self:GetOwner():GetAmmoCount(self.Primary.Ammo) <= 0 then return end + if + self:Clip1() == self.Primary.ClipSize + or self:GetOwner():GetAmmoCount(self.Primary.Ammo) <= 0 + then + return + end + + self:DefaultReload(self.ReloadAnim) - self:DefaultReload(self.ReloadAnim) - self:SetIronsights(false) - self:SetZoom(false) + self:SetIronsights(false) + self:SetZoom(false) end --- @@ -734,10 +1173,10 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:OnRestore -- @realm shared function SWEP:OnRestore() - self.NextSecondaryAttack = 0 + self.NextSecondaryAttack = 0 - self:SetIronsights(false) - self:SetZoom(false) + self:SetIronsights(false) + self:SetZoom(false) end --- @@ -746,140 +1185,160 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:Ammo1 -- @realm shared function SWEP:Ammo1() - return IsValid(self:GetOwner()) and self:GetOwner():GetAmmoCount(self.Primary.Ammo) or false + return IsValid(self:GetOwner()) and self:GetOwner():GetAmmoCount(self.Primary.Ammo) or false end if SERVER then - --- - -- The OnDrop() hook is useless for this as it happens AFTER the drop. OwnerChange - -- does not occur when a drop happens for some reason. Hence this thing. - -- @realm server - function SWEP:PreDrop() - if not IsValid(self:GetOwner()) or self.Primary.Ammo == "none" then return end - - local ammo = self:Ammo1() - - -- Do not drop ammo if we have another gun that uses this type - local weps = self:GetOwner():GetWeapons() - - for i = 1, #weps do - local w = weps[i] - - if not IsValid(w) or w == self or w:GetPrimaryAmmoType() ~= self:GetPrimaryAmmoType() then continue end - - ammo = 0 - end - - self.StoredAmmo = ammo - - if ammo > 0 then - self:GetOwner():RemoveAmmo(ammo, self.Primary.Ammo) - end - end - - --- - -- Helper function to slow down dropped weapons - -- @realm server - function SWEP:DampenDrop() - -- For some reason gmod drops guns on death at a speed of 400 units, which - -- catapults them away from the body. Here we want people to actually be able - -- to find a given corpse's weapon, so we override the velocity here and call - -- this when dropping guns on death. - local phys = self:GetPhysicsObject() - - if IsValid(phys) then - phys:SetVelocityInstantaneous(Vector(0, 0, - 75) + phys:GetVelocity() * 0.001) - phys:AddAngleVelocity(phys:GetAngleVelocity() * -0.99) - end - end - - local SF_WEAPON_START_CONSTRAINED = 1 - - --- - -- Called when a player has picked the weapon up - -- Transfers the currently stored ammo to the new player and updates the fingerprints - -- @param Player newowner - -- @see https://wiki.facepunch.com/gmod/WEAPON:Equip - -- @realm server - function SWEP:Equip(newowner) - if self:IsOnFire() then - self:Extinguish() - end - - self.fingerprints = self.fingerprints or {} - - if not table.HasValue(self.fingerprints, newowner) then - self.fingerprints[#self.fingerprints + 1] = newowner - end - - if self:HasSpawnFlags(SF_WEAPON_START_CONSTRAINED) then - -- If this weapon started constrained, unset that spawnflag, or the - -- weapon will be re-constrained and float - local flags = self:GetSpawnFlags() - local newflags = bit.band(flags, bit.bnot(SF_WEAPON_START_CONSTRAINED)) - - self:SetKeyValue("spawnflags", newflags) - end - - if IsValid(newowner) and self.StoredAmmo > 0 and self.Primary.Ammo ~= "none" then - local ammo = newowner:GetAmmoCount(self.Primary.Ammo) - local given = math.min(self.StoredAmmo, self.Primary.ClipMax - ammo) - - newowner:GiveAmmo(given, self.Primary.Ammo) - - self.StoredAmmo = 0 - end - end - - --- - -- We were bought as special equipment, some weapons will want to do something - -- extra for their buyer - -- @param Player buyer - -- @realm server - function SWEP:WasBought(buyer) - - end - - --- - -- Experimental. Enables a feature that causes a player who is using his ironsights and is killed (by a gun, and not a headshot) to fire an inaccurate dying shot. - -- @return boolean - -- @see http://www.troubleinterroristtown.com/config-and-commands/convars#TOC-Other-gameplay-settings - -- @realm server - function SWEP:DyingShot() - if not self:GetIronsights() then - return false - end - - self:SetIronsights(false) - self:SetZoom(false) - - if self:GetNextPrimaryFire() > CurTime() then - return false - end - - -- Owner should still be alive here - local owner = self:GetOwner() - if not IsValid(owner) then - return false - end - - local punch = self.Primary.Recoil or 5 - - -- Punch view to disorient aim before firing dying shot - local eyeang = owner:EyeAngles() - eyeang.pitch = eyeang.pitch - math.Rand(-punch, punch) - eyeang.yaw = eyeang.yaw - math.Rand(-punch, punch) - - owner:SetEyeAngles(eyeang) - - MsgN(owner:Nick() .. " fired his DYING SHOT") - - owner.dying_wep = self - - self:PrimaryAttack(true) - - return true - end + --- + -- This allows us to override behavior of PreDrop/OnDrop calls that request equipment be dropped. + -- @realm server + function SWEP:ShouldRemove() + local should_force = self.overrideDropOnDeath + and self.overrideDropOnDeath == DROP_ON_DEATH_TYPE_FORCE + local deathDrop = self.IsDroppedBecauseDeath + if deathDrop and should_force then + return false + end + + return true + end + + --- + -- The OnDrop() hook is useless for this as it happens AFTER the drop. OwnerChange + -- does not occur when a drop happens for some reason. Hence this thing. + -- @realm server + function SWEP:PreDrop() + if not IsValid(self:GetOwner()) or self.Primary.Ammo == "none" then + return + end + + local ammo = self:Ammo1() + + -- Do not drop ammo if we have another gun that uses this type + local weps = self:GetOwner():GetWeapons() + + for i = 1, #weps do + local w = weps[i] + + if + not IsValid(w) + or w == self + or w:GetPrimaryAmmoType() ~= self:GetPrimaryAmmoType() + then + continue + end + + ammo = 0 + end + + self.StoredAmmo = ammo + + if ammo > 0 then + self:GetOwner():RemoveAmmo(ammo, self.Primary.Ammo) + end + end + + --- + -- Helper function to slow down dropped weapons + -- @realm server + function SWEP:DampenDrop() + -- For some reason gmod drops guns on death at a speed of 400 units, which + -- catapults them away from the body. Here we want people to actually be able + -- to find a given corpse's weapon, so we override the velocity here and call + -- this when dropping guns on death. + local phys = self:GetPhysicsObject() + + if IsValid(phys) then + phys:SetVelocityInstantaneous(Vector(0, 0, -75) + phys:GetVelocity() * 0.001) + phys:AddAngleVelocity(phys:GetAngleVelocity() * -0.99) + end + end + + local SF_WEAPON_START_CONSTRAINED = 1 + + --- + -- Called when a player has picked the weapon up + -- Transfers the currently stored ammo to the new player and updates the fingerprints + -- @param Player newowner + -- @see https://wiki.facepunch.com/gmod/WEAPON:Equip + -- @realm server + function SWEP:Equip(newowner) + if self:IsOnFire() then + self:Extinguish() + end + + self.fingerprints = self.fingerprints or {} + + if not table.HasValue(self.fingerprints, newowner) then + self.fingerprints[#self.fingerprints + 1] = newowner + end + + if self:HasSpawnFlags(SF_WEAPON_START_CONSTRAINED) then + -- If this weapon started constrained, unset that spawnflag, or the + -- weapon will be re-constrained and float + local flags = self:GetSpawnFlags() + local newflags = bit.band(flags, bit.bnot(SF_WEAPON_START_CONSTRAINED)) + + self:SetKeyValue("spawnflags", newflags) + end + + if IsValid(newowner) and self.StoredAmmo > 0 and self.Primary.Ammo ~= "none" then + local ammo = newowner:GetAmmoCount(self.Primary.Ammo) + local given = math.min(self.StoredAmmo, self.Primary.ClipMax - ammo) + + newowner:GiveAmmo(given, self.Primary.Ammo) + + self.StoredAmmo = 0 + end + end + + --- + -- We were bought as special equipment, some weapons will want to do something + -- extra for their buyer + -- @param Player buyer + -- @realm server + function SWEP:WasBought(buyer) end + + --- + -- Experimental. Enables a feature that causes a player who is using his ironsights and is killed (by a gun, and not a headshot) to fire an inaccurate dying shot. + -- @return boolean + -- @see http://www.troubleinterroristtown.com/config-and-commands/convars#TOC-Other-gameplay-settings + -- @realm server + function SWEP:DyingShot() + if not self:GetIronsights() then + return false + end + + self:SetIronsights(false) + self:SetZoom(false) + + if self:GetNextPrimaryFire() > CurTime() then + return false + end + + -- Owner should still be alive here + local owner = self:GetOwner() + if not IsValid(owner) then + return false + end + + local punch = self.Primary.Recoil or 5 + + -- Punch view to disorient aim before firing dying shot + local eyeang = owner:EyeAngles() + eyeang.pitch = eyeang.pitch - math.Rand(-punch, punch) + eyeang.yaw = eyeang.yaw - math.Rand(-punch, punch) + + owner:SetEyeAngles(eyeang) + + Dev(1, owner:Nick() .. " fired his DYING SHOT") + + owner.dying_wep = self + + self:PrimaryAttack(true) + + return true + end end --- @@ -887,23 +1346,23 @@ end -- Look at weapon_ttt_m16 for an example of how to use this. -- @param boolean zoomIn -- @realm shared -function SWEP:SetZoom(zoomIn) - -end +function SWEP:SetZoom(zoomIn) end --- -- Sets the flag signaling whether or not the ironsights should be used -- @param boolean b -- @realm shared function SWEP:SetIronsights(b) - if b == self:GetIronsights() then return end + if b == self:GetIronsights() then + return + end - self:SetIronsightsPredicted(b) - self:SetIronsightsTime(CurTime()) + self:SetIronsightsPredicted(b) + self:SetIronsightsTime(CurTime()) - if CLIENT then - self:CalcViewModel() - end + if CLIENT then + self:CalcViewModel() + end end --- @@ -911,7 +1370,7 @@ end -- @return boolean -- @realm shared function SWEP:GetIronsights() - return self:GetIronsightsPredicted() + return self:GetIronsightsPredicted() end --- @@ -920,16 +1379,14 @@ end -- @return[default=-1] number -- @realm shared function SWEP:GetIronsightsTime() - return -1 + return -1 end --- -- Dummy functions that will be replaced when SetupDataTables runs. These are -- here for when that does not happen (due to e.g. stacking base classes) -- @realm shared -function SWEP:SetIronsightsTime() - -end +function SWEP:SetIronsightsTime() end --- -- Dummy functions that will be replaced when SetupDataTables runs. These are @@ -937,16 +1394,14 @@ end -- @return[default=false] boolean -- @realm shared function SWEP:GetIronsightsPredicted() - return false + return false end --- -- Dummy functions that will be replaced when SetupDataTables runs. These are -- here for when that does not happen (due to e.g. stacking base classes) -- @realm shared -function SWEP:SetIronsightsPredicted() - -end +function SWEP:SetIronsightsPredicted() end --- -- Called when the SWEP should set up its Data Tables. @@ -955,8 +1410,8 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:SetupDataTables -- @realm shared function SWEP:SetupDataTables() - self:NetworkVar("Bool", 3, "IronsightsPredicted") - self:NetworkVar("Float", 3, "IronsightsTime") + self:NetworkVar("Bool", 3, "IronsightsPredicted") + self:NetworkVar("Float", 3, "IronsightsTime") end --- @@ -964,21 +1419,53 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:Initialize -- @realm shared function SWEP:Initialize() - if CLIENT and self:Clip1() == -1 then - self:SetClip1(self.Primary.DefaultClip) - elseif SERVER then - self.fingerprints = {} + if CLIENT then + self:InitializeCustomModels() + end + + if CLIENT and self:Clip1() == -1 then + self:SetClip1(self.Primary.DefaultClip) + elseif SERVER then + self.fingerprints = {} - self:SetIronsights(false) - self:SetZoom(false) - end + self:SetZoom(false) + self:SetIronsights(false) + end - self:SetDeploySpeed(self.DeploySpeed) + self:SetDeploySpeed(self.DeploySpeed) + self:SetHoldType(self.HoldType or "pistol") +end - -- compat for gmod update - if self.SetHoldType then - self:SetHoldType(self.HoldType or "pistol") - end +local idle_activities = { + [ACT_VM_IDLE] = true, + [ACT_VM_IDLE_TO_LOWERED] = true, + [ACT_VM_IDLE_LOWERED] = true, + [ACT_VM_IDLE_SILENCED] = true, + [ACT_VM_IDLE_EMPTY_LEFT] = true, + [ACT_VM_IDLE_EMPTY] = true, + [ACT_VM_IDLE_DEPLOYED_EMPTY] = true, + [ACT_VM_IDLE_8] = true, + [ACT_VM_IDLE_7] = true, + [ACT_VM_IDLE_6] = true, + [ACT_VM_IDLE_5] = true, + [ACT_VM_IDLE_4] = true, + [ACT_VM_IDLE_3] = true, + [ACT_VM_IDLE_2] = true, + [ACT_VM_IDLE_1] = true, + [ACT_VM_IDLE_DEPLOYED] = true, + [ACT_VM_IDLE_DEPLOYED_8] = true, + [ACT_VM_IDLE_DEPLOYED_7] = true, + [ACT_VM_IDLE_DEPLOYED_6] = true, + [ACT_VM_IDLE_DEPLOYED_5] = true, + [ACT_VM_IDLE_DEPLOYED_4] = true, + [ACT_VM_IDLE_DEPLOYED_3] = true, + [ACT_VM_IDLE_DEPLOYED_2] = true, + [ACT_VM_IDLE_DEPLOYED_1] = true, + [ACT_VM_IDLE_M203] = true, +} + +local function IsIdleActivity(vm) + return idle_activities[vm:GetSequenceActivity(vm:GetSequence())] or false end --- @@ -987,106 +1474,127 @@ end -- @see https://wiki.facepunch.com/gmod/WEAPON:Think -- @realm shared function SWEP:Think() - if CLIENT then - self:CalcViewModel() - end + local viewModel = self:GetOwner():GetViewModel() + + if + self.idleResetFix + and self.ViewModel + and viewModel:GetCycle() >= 1 + and not IsIdleActivity(viewModel) + then + self:SendWeaponAnim(self.IdleAnim or ACT_VM_IDLE) + end + + if CLIENT then + self:CalcViewModel() + end end if CLIENT then - --- - -- @realm client - local ttt_lowered = CreateConVar("ttt_ironsights_lowered", "1", FCVAR_ARCHIVE) - - local host_timescale = GetConVar("host_timescale") - - local LOWER_POS = Vector(0, 0, -2) - local IRONSIGHT_TIME = 0.25 - - --- - -- This hook allows you to adjust view model position and angles. - -- @param Vector pos - -- @param Angle ang - -- @return Vector - -- @return Angle - -- @see https://wiki.facepunch.com/gmod/WEAPON:GetViewModelPosition - -- @realm client - function SWEP:GetViewModelPosition(pos, ang) - if not self.IronSightsPos or self.bIron == nil then - return pos, ang - end - - local bIron = self.bIron - local time = self.fCurrentTime + (SysTime() - self.fCurrentSysTime) * game.GetTimeScale() * host_timescale:GetFloat() - - if bIron then - self.SwayScale = 0.3 - self.BobScale = 0.1 - else - self.SwayScale = 1.0 - self.BobScale = 1.0 - end - - local fIronTime = self.fIronTime - - if not bIron and fIronTime < time - IRONSIGHT_TIME then - return pos, ang - end - - local mul = 1.0 - - if fIronTime > time - IRONSIGHT_TIME then - mul = math.Clamp((time - fIronTime) / IRONSIGHT_TIME, 0, 1) - - if not bIron then - mul = 1 - mul - end - end - - local offset = self.IronSightsPos + (ttt_lowered:GetBool() and LOWER_POS or vector_origin) - - if self.IronSightsAng then - ang = Angle(ang) - ang:RotateAroundAxis(ang:Right(), self.IronSightsAng.x * mul) - ang:RotateAroundAxis(ang:Up(), self.IronSightsAng.y * mul) - ang:RotateAroundAxis(ang:Forward(), self.IronSightsAng.z * mul) - end - - pos = pos + offset.x * ang:Right() * mul - pos = pos + offset.y * ang:Forward() * mul - pos = pos + offset.z * ang:Up() * mul - - return pos, ang - end + --- + -- @realm client + -- stylua: ignore + local ttt_lowered = CreateConVar("ttt_ironsights_lowered", "1", FCVAR_ARCHIVE) + + local host_timescale = GetConVar("host_timescale") + + local LOWER_POS = Vector(0, 0, -2) + local IRONSIGHT_TIME = 0.25 + + --- + -- This hook allows you to adjust view model position and angles. + -- @param Vector pos + -- @param Angle ang + -- @return Vector + -- @return Angle + -- @see https://wiki.facepunch.com/gmod/WEAPON:GetViewModelPosition + -- @realm client + function SWEP:GetViewModelPosition(pos, ang) + if not self.IronSightsPos or self.bIron == nil then + return pos, ang + end + + local bIron = self.bIron + local time = self.fCurrentTime + + (SysTime() - self.fCurrentSysTime) + * game.GetTimeScale() + * host_timescale:GetFloat() + + if bIron then + self.SwayScale = 0.3 + self.BobScale = 0.1 + else + self.SwayScale = 1.0 + self.BobScale = 1.0 + end + + local fIronTime = self.fIronTime + + if not bIron and fIronTime < time - IRONSIGHT_TIME then + return pos, ang + end + + local mul = 1.0 + + if fIronTime > time - IRONSIGHT_TIME then + mul = math.Clamp((time - fIronTime) / IRONSIGHT_TIME, 0, 1) + + if not bIron then + mul = 1 - mul + end + end + + local offset = self.IronSightsPos + (ttt_lowered:GetBool() and LOWER_POS or vector_origin) + + if self.IronSightsAng then + ang = Angle(ang) + ang:RotateAroundAxis(ang:Right(), self.IronSightsAng.x * mul) + ang:RotateAroundAxis(ang:Up(), self.IronSightsAng.y * mul) + ang:RotateAroundAxis(ang:Forward(), self.IronSightsAng.z * mul) + end + + pos = pos + offset.x * ang:Right() * mul + pos = pos + offset.y * ang:Forward() * mul + pos = pos + offset.z * ang:Up() * mul + + return pos, ang + end end hook.Add("KeyRelease", "TTT2ResetIronSights", function(ply, key) - if key ~= IN_ATTACK2 or not IsValid(ply) then return end + if key ~= IN_ATTACK2 or not IsValid(ply) then + return + end - if not (CLIENT and ttt2_hold_aim:GetBool() or SERVER and ply.holdAim) then return end + if not (CLIENT and ttt2_hold_aim:GetBool() or SERVER and ply.holdAim) then + return + end - local wep = ply:GetActiveWeapon() + local wep = ply:GetActiveWeapon() - if not IsValid(wep) or not wep:GetIronsights() then return end + if not IsValid(wep) or (wep.GetIronsights and not wep:GetIronsights()) then + return + end - wep:SetIronsights(false) - wep:SetZoom(false) + wep:SetIronsights(false) + wep:SetZoom(false) end) if CLIENT then - --- - -- Tell the server about the users preference regarding holding aim or toggle aim, - -- necessary to avoid prediction issues - local function UpdateHoldAimCV() - net.Start("TTT2UpdateHoldAimConvar") - net.WriteBool(ttt2_hold_aim:GetBool()) - net.SendToServer() - end - - hook.Add("InitPostEntity", "TTT2InitHoldAimCV", UpdateHoldAimCV) - - cvars.AddChangeCallback("ttt2_hold_aim", UpdateHoldAimCV) + --- + -- Tell the server about the users preference regarding holding aim or toggle aim, + -- necessary to avoid prediction issues + local function UpdateHoldAimCV() + net.Start("TTT2UpdateHoldAimConvar") + net.WriteBool(ttt2_hold_aim:GetBool()) + net.SendToServer() + end + + hook.Add("InitPostEntity", "TTT2InitHoldAimCV", UpdateHoldAimCV) + + cvars.AddChangeCallback("ttt2_hold_aim", UpdateHoldAimCV) else - net.Receive("TTT2UpdateHoldAimConvar", function(_, ply) - ply.holdAim = net.ReadBool() - end) + net.Receive("TTT2UpdateHoldAimConvar", function(_, ply) + ply.holdAim = net.ReadBool() + end) end diff --git a/gamemodes/terrortown/entities/weapons/weapon_tttbasegrenade.lua b/gamemodes/terrortown/entities/weapons/weapon_tttbasegrenade.lua index d59be550d..6dd4510a9 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_tttbasegrenade.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_tttbasegrenade.lua @@ -4,23 +4,22 @@ -- @section weapon_tttbasegrenade if SERVER then - AddCSLuaFile() + AddCSLuaFile() end -DEFINE_BASECLASS "weapon_tttbase" +DEFINE_BASECLASS("weapon_tttbase") SWEP.HoldReady = "grenade" SWEP.HoldNormal = "slam" if CLIENT then - SWEP.PrintName = "Incendiary grenade" - SWEP.Instructions = "Burn." - SWEP.Slot = 3 + SWEP.PrintName = "Incendiary grenade" + SWEP.Instructions = "Burn." + SWEP.Slot = 3 - SWEP.ViewModelFlip = true - SWEP.DrawCrosshair = false + SWEP.ViewModelFlip = true - SWEP.Icon = "vgui/ttt/icon_nades" + SWEP.Icon = "vgui/ttt/icon_nades" end SWEP.Base = "weapon_tttbase" @@ -49,259 +48,492 @@ SWEP.IsGrenade = true SWEP.was_thrown = false SWEP.detonate_timer = 5 SWEP.DeploySpeed = 1.5 +SWEP.throwForce = 1 --- -- @accessor number -- @realm shared AccessorFunc(SWEP, "det_time", "DetTime") +--- +-- @accessor number +-- @realm shared +AccessorFunc(SWEP, "pull_time", "PullTime", FORCE_NUMBER) + --- -- @realm server +-- stylua: ignore CreateConVar("ttt_nade_throw_during_prep", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) --- -- @ignore function SWEP:SetupDataTables() - self:NetworkVar("Bool", 0, "Pin") - self:NetworkVar("Int", 0, "ThrowTime") + self:NetworkVar("Bool", 0, "Pin") + self:NetworkVar("Int", 0, "ThrowTime") end --- -- @ignore function SWEP:PrimaryAttack() - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - if GetRoundState() == ROUND_PREP and not GetConVar("ttt_nade_throw_during_prep"):GetBool() then - return - end + if GetRoundState() == ROUND_PREP and not GetConVar("ttt_nade_throw_during_prep"):GetBool() then + return + end - self:PullPin() + self:PullPin() end --- -- @ignore -function SWEP:SecondaryAttack() +function SWEP:SecondaryAttack() end +--- +-- @param Vector currentIterationPosition The initial position for this iteration cycle. +-- @param number launchVelocity The launch velocity for this iteration cycle. +-- @param number stepDistance The step size for the current iteration cycle. +-- @return Vector The next location to iterate from. +local function PositionFromPhysicsParams(currentIterationPosition, launchVelocity, stepDistance) + local activeGravity = physenv.GetGravity() + + return currentIterationPosition + + (launchVelocity * stepDistance + 0.5 * activeGravity * stepDistance ^ 2) +end + +if CLIENT then + --- + -- @realm client + -- stylua: ignore + local cvEnableTrajectoryUI = CreateConVar("ttt2_grenade_trajectory_ui", 0, FCVAR_ARCHIVE) + + local function AlphaLerp(from, frac, max) + local fr = frac ^ 0.5 + return ColorAlpha(from, Lerp(fr, 0, math.min(max, 255))) + end + + --- + -- @param Player ply + -- @realm client + function SWEP:DrawDefaultThrowPath(ply) + local stepSize = 0.005 + + local owner = self:GetOwner() + local currentIterationPosition, _ = + self:GetViewModelPosition(owner:EyePos(), owner:EyeAngles()) + local launchPosition, launchVelocity = self:GetThrowVelocity() + currentIterationPosition = currentIterationPosition - ply:EyePos() + launchPosition + local previousIterationPosition = + PositionFromPhysicsParams(currentIterationPosition, launchVelocity, stepSize) + + local fractionalFrameTime = (SysTime() % 1) * 2 + local i = fractionalFrameTime > 1 and 1 or 0 + fractionalFrameTime = fractionalFrameTime - math.floor(fractionalFrameTime) + + local arcColor = appearance.ShouldUseGlobalFocusColor() and appearance.GetFocusColor() + or self:GetOwner():GetRoleColor() + local arcAlpha = math.Round(GetConVar("ttt_crosshair_opacity"):GetFloat() * 255) + local arcWidth = 0.6 + local arcWidthDiminishOverDistanceFactor = 0.25 + local arcSegmentLengthFactor = 0.5 + local arcImpactCrossLength = 1 + + render.SetColorMaterial() + cam.Start3D(EyePos(), EyeAngles()) + + for stepDistance = stepSize * 2, 1, stepSize do + local drawColor = AlphaLerp(arcColor, stepDistance, arcAlpha) + + local pos = + PositionFromPhysicsParams(currentIterationPosition, launchVelocity, stepDistance) + local t = util.TraceLine({ + start = previousIterationPosition, + endpos = pos, + filter = { ply, self }, + }) + + local from = previousIterationPosition + local to = t.Hit and t.HitPos or pos + local norm = to - from + norm:Normalize() + + local denom = (stepDistance / stepSize) ^ arcWidthDiminishOverDistanceFactor + local arcSegmentLength = from:DistToSqr(to) ^ arcSegmentLengthFactor + i = (i + 1) % 2 + if i == 0 then + render.DrawBeam( + from, + from + norm * (fractionalFrameTime * arcSegmentLength), + arcWidth * denom, + 0, + 1, + drawColor + ) + else + render.DrawBeam( + to - norm * ((1 - fractionalFrameTime) * arcSegmentLength), + to, + arcWidth * denom, + 0, + 1, + drawColor + ) + end + + if t.Hit then + local hitPosition = t.HitPos + t.HitNormal * (t.FractionLeftSolid * denom) + local impactCrossSegmentLength = arcImpactCrossLength * denom + local impactCrossVector = Vector(0, 0, impactCrossSegmentLength) + + local angleLeft = Angle(t.HitNormal:Angle()) + angleLeft:RotateAroundAxis(t.HitNormal, -45) + local left = Vector(impactCrossVector) + left:Rotate(angleLeft) + render.DrawBeam( + hitPosition - (left * impactCrossSegmentLength), + hitPosition + left * impactCrossSegmentLength, + arcWidth * denom, + 0, + 1, + drawColor + ) + + local angleUp = Angle(t.HitNormal:Angle()) + angleUp:RotateAroundAxis(t.HitNormal, 45) + local up = Vector(impactCrossVector) + up:Rotate(angleUp) + render.DrawBeam( + hitPosition - (up * impactCrossSegmentLength), + hitPosition + up * impactCrossSegmentLength, + arcWidth * denom, + 0, + 1, + drawColor + ) + break + end + previousIterationPosition = pos + end + + cam.End3D() + end + + --- + -- @param Entity vm + -- @param Weapon weapon + -- @param Player ply + -- @realm client + function SWEP:PostDrawViewModel(vm, weapon, ply) + if cvEnableTrajectoryUI:GetBool() then + self:DrawDefaultThrowPath(ply) + end + end end --- -- @ignore function SWEP:PullPin() - if self:GetPin() then return end + if self:GetPin() then + return + end - local ply = self:GetOwner() - if not IsValid(ply) then return end + local ply = self:GetOwner() + if not IsValid(ply) then + return + end - self:SendWeaponAnim(ACT_VM_PULLPIN) + self:SendWeaponAnim(ACT_VM_PULLPIN) - if self.SetHoldType then - self:SetHoldType(self.HoldReady) - end + if self.SetHoldType then + self:SetHoldType(self.HoldReady) + end - self:SetPin(true) + self:SetPin(true) + self:SetPullTime(CurTime()) - self:SetDetTime(CurTime() + self.detonate_timer) + self:SetDetTime(CurTime() + self.detonate_timer) end --- -- @ignore function SWEP:Think() - BaseClass.Think(self) - - local ply = self:GetOwner() - - if not IsValid(ply) then return end - - -- pin pulled and attack loose = throw - if self:GetPin() then - -- we will throw now - if not ply:KeyDown(IN_ATTACK) then - self:StartThrow() - - self:SetPin(false) - self:SendWeaponAnim(ACT_VM_THROW) - - if SERVER then - self:GetOwner():SetAnimation( PLAYER_ATTACK1 ) - end - else - -- still cooking it, see if our time is up - if SERVER and self:GetDetTime() < CurTime() then - self:BlowInFace() - end - end - elseif self:GetThrowTime() > 0 and self:GetThrowTime() < CurTime() then - self:Throw() - end + BaseClass.Think(self) + + local ply = self:GetOwner() + + if not IsValid(ply) then + return + end + + -- pin pulled and attack loose = throw + if self:GetPin() then + -- we will throw now + if not ply:KeyDown(IN_ATTACK) then + self:StartThrow() + + self:SetPin(false) + self:SendWeaponAnim(ACT_VM_THROW) + + if SERVER then + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + end + else + -- still cooking it, see if our time is up + if SERVER and self:GetDetTime() < CurTime() then + self:BlowInFace() + end + end + elseif self:GetThrowTime() > 0 and self:GetThrowTime() < CurTime() then + self:Throw() + end end --- -- @ignore function SWEP:BlowInFace() - local ply = self:GetOwner() - if not IsValid(ply) then return end + local ply = self:GetOwner() + if not IsValid(ply) then + return + end - if self.was_thrown then return end + if self.was_thrown then + return + end - self.was_thrown = true + self.was_thrown = true - -- drop the grenade so it can immediately explode + -- drop the grenade so it can immediately explode - local ang = ply:GetAngles() - local src = ply:GetPos() + (ply:Crouching() and ply:GetViewOffsetDucked() or ply:GetViewOffset()) - src = src + (ang:Right() * 10) + local ang = ply:GetAngles() + local src = ply:GetPos() + + (ply:Crouching() and ply:GetViewOffsetDucked() or ply:GetViewOffset()) + src = src + (ang:Right() * 10) - self:CreateGrenade(src, Angle(0,0,0), Vector(0,0,1), Vector(0,0,1), ply) + self:CreateGrenade(src, Angle(0, 0, 0), Vector(0, 0, 1), Vector(0, 0, 1), ply) - self:SetThrowTime(0) - self:Remove() + self:SetThrowTime(0) + self:Remove() end --- -- @ignore function SWEP:StartThrow() - self:SetThrowTime(CurTime() + 0.1) + self:SetThrowTime(CurTime() + 0.1) +end + +--- +-- @return Vector, Vector The point of origin for the thrown projectile, and its force. +-- @realm shared +function SWEP:GetThrowVelocity() + local ply = self:GetOwner() + local ang = ply:EyeAngles() + local src = ply:GetPos() + + (ply:Crouching() and ply:GetViewOffsetDucked() or ply:GetViewOffset()) + + (ang:Forward() * 8) + + (ang:Right() * 10) + local target = ply:GetEyeTraceNoCursor().HitPos + -- A target angle to actually throw the grenade to the crosshair instead of forwards + local tang = (target - src):Angle() + -- Makes the grenade go upwards + if tang.p < 90 then + tang.p = -10 + tang.p * ((90 + 10) / 90) + else + tang.p = 360 - tang.p + tang.p = -10 + tang.p * -((90 + 10) / 90) + end + + -- Makes the grenade not go backwards :/ + tang.p = math.Clamp(tang.p, -90, 90) + local vel = math.min(800, (90 - tang.p) * 6) + local force = tang:Forward() * vel * self.throwForce + ply:GetVelocity() + return src, force end --- -- @ignore function SWEP:Throw() - if CLIENT then - self:SetThrowTime(0) - elseif SERVER then - local ply = self:GetOwner() - if not IsValid(ply) then return end - - if self.was_thrown then return end - - self.was_thrown = true - - local ang = ply:EyeAngles() - local src = ply:GetPos() + (ply:Crouching() and ply:GetViewOffsetDucked() or ply:GetViewOffset()) + ang:Forward() * 8 + ang:Right() * 10 - local target = ply:GetEyeTraceNoCursor().HitPos - local tang = (target-src):Angle() -- A target angle to actually throw the grenade to the crosshair instead of fowards - - -- Makes the grenade go upgwards - if tang.p < 90 then - tang.p = -10 + tang.p * ((90 + 10) / 90) - else - tang.p = 360 - tang.p - tang.p = -10 + tang.p * -((90 + 10) / 90) - end - - tang.p = math.Clamp(tang.p,-90,90) -- Makes the grenade not go backwards :/ - - local vel = math.min(800, (90 - tang.p) * 6) - local thr = tang:Forward() * vel + ply:GetVelocity() - self:CreateGrenade(src, Angle(0,0,0), thr, Vector(600, math.random(-1200, 1200), 0), ply) - - self:SetThrowTime(0) - self:Remove() - end + if CLIENT then + self:SetThrowTime(0) + elseif SERVER then + local ply = self:GetOwner() + if not IsValid(ply) then + return + end + + if self.was_thrown then + return + end + + self.was_thrown = true + + local src, force = self:GetThrowVelocity() + self:CreateGrenade( + src, + Angle(0, 0, 0), + force, + Vector(600, math.random(-1200, 1200), 0), + ply + ) + + self:SetThrowTime(0) + self:Remove() + end end --- -- Subclasses must override with their own grenade ent. -- @realm shared function SWEP:GetGrenadeName() - ErrorNoHalt("SWEP BASEGRENADE ERROR: GetGrenadeName not overridden! This is probably wrong!\n") + ErrorNoHaltWithStack( + "SWEP BASEGRENADE ERROR: GetGrenadeName not overridden! This is probably wrong!\n" + ) - return "ttt_firegrenade_proj" + return "ttt_firegrenade_proj" end --- -- @ignore function SWEP:CreateGrenade(src, ang, vel, angimp, ply) - local gren = ents.Create(self:GetGrenadeName()) + local gren = ents.Create(self:GetGrenadeName()) - if not IsValid(gren) then return end + if not IsValid(gren) then + return + end - gren:SetPos(src) - gren:SetAngles(ang) - gren:SetOwner(ply) - gren:SetThrower(ply) - gren:SetGravity(0.4) - gren:SetFriction(0.2) - gren:SetElasticity(0.45) - gren:Spawn() - gren:PhysWake() + gren:SetPos(src) + gren:SetAngles(ang) + gren:SetOwner(ply) + gren:SetThrower(ply) + gren:SetGravity(0.4) + gren:SetFriction(0.2) + gren:SetElasticity(0.45) + gren:Spawn() + gren:PhysWake() - local phys = gren:GetPhysicsObject() + local phys = gren:GetPhysicsObject() - if IsValid(phys) then - phys:SetVelocity(vel) - phys:AddAngleVelocity(angimp) - end + if IsValid(phys) then + phys:SetVelocity(vel) + phys:AddAngleVelocity(angimp) + end - -- This has to happen AFTER Spawn() calls gren's Initialize() - gren:SetDetonateExact(self:GetDetTime()) + -- This has to happen AFTER Spawn() calls gren's Initialize() + gren:SetDetonateExact(self:GetDetTime()) - return gren + return gren end --- -- @ignore function SWEP:PreDrop() - -- if owner dies or drops us while the pin has been pulled, create the armed - -- grenade anyway - if self:GetPin() then - self:BlowInFace() - end + -- if owner dies or drops us while the pin has been pulled, create the armed + -- grenade anyway + if self:GetPin() then + self:BlowInFace() + end end --- -- @ignore function SWEP:Deploy() - if self.SetHoldType then - self:SetHoldType(self.HoldNormal) - end + if self.SetHoldType then + self:SetHoldType(self.HoldNormal) + end - self:SetThrowTime(0) - self:SetPin(false) + self:SetThrowTime(0) + self:SetPin(false) - return true + return true end --- -- @ignore function SWEP:Holster() - if self:GetPin() then - return false -- no switching after pulling pin - end + if self:GetPin() then + return false -- no switching after pulling pin + end - self:SetThrowTime(0) - self:SetPin(false) + self:SetThrowTime(0) + self:SetPin(false) - return true + return true end --- -- @ignore function SWEP:Reload() - return false + return false end --- -- @ignore function SWEP:Initialize() - if self.SetHoldType then - self:SetHoldType(self.HoldNormal) - end + if self.SetHoldType then + self:SetHoldType(self.HoldNormal) + end - self:SetDeploySpeed(self.DeploySpeed) - self:SetDetTime(0) - self:SetThrowTime(0) - self:SetPin(false) + self:SetDeploySpeed(self.DeploySpeed) + self:SetDetTime(0) + self:SetPullTime(0) + self:SetThrowTime(0) + self:SetPin(false) - self.was_thrown = false + self.was_thrown = false end ---- --- @ignore -function SWEP:OnRemove() - local owner = self:GetOwner() - - if CLIENT and IsValid(owner) and owner == LocalPlayer() and owner:Alive() then - RunConsoleCommand("use", "weapon_ttt_unarmed") - end +if CLIENT then + local draw = draw + local TryT = LANG.TryTranslation + local hudTextColor = Color(255, 255, 255, 180) + + --- + -- @realm client + function SWEP:OnRemove() + local owner = self:GetOwner() + + if IsValid(owner) and owner == LocalPlayer() and owner:IsTerror() then + RunConsoleCommand("use", "weapon_ttt_unarmed") + end + end + + --- + -- @ignore + function SWEP:DrawHUD() + if self.HUDHelp then + self:DrawHelp() + end + + if self:GetPin() and self:GetPullTime() > 0 then + local client = LocalPlayer() + + local x = ScrW() * 0.5 + local y = ScrH() * 0.5 + y = y + (y / 3) + + local pct = 1 + - math.Clamp( + (CurTime() - self:GetPullTime()) / (self:GetDetTime() - self:GetPullTime()), + 0, + 1 + ) + + local scale = appearance.GetGlobalScale() + local w, h = 100 * scale, 20 * scale + local drawColor = appearance.SelectFocusColor(client:GetRoleColor()) + + draw.AdvancedText( + TryT("grenade_fuse"), + "PureSkinBar", + x - w / 2, + y - h, + hudTextColor, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_BOTTOM, + true, + scale + ) + draw.Box(x - w / 2 + scale, y - h + scale, w * pct, h, COLOR_BLACK) + draw.OutlinedShadowedBox(x - w / 2, y - h, w, h, scale, drawColor) + draw.Box(x - w / 2, y - h, w * pct, h, drawColor) + end + end end diff --git a/gamemodes/terrortown/entities/weapons/weapon_zm_carry.lua b/gamemodes/terrortown/entities/weapons/weapon_zm_carry.lua index c489ead0e..0b941c6dc 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_zm_carry.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_zm_carry.lua @@ -3,10 +3,10 @@ -- @section weapon_zm_carry if SERVER then - AddCSLuaFile() + AddCSLuaFile() end -DEFINE_BASECLASS "weapon_tttbase" +DEFINE_BASECLASS("weapon_tttbase") local player = player local IsValid = IsValid @@ -16,19 +16,18 @@ local CurTime = CurTime -- gameplay purposes CARRY_WEIGHT_LIMIT = 45 -local PIN_RAG_RANGE = 90 +local down = Vector(0, 0, -1) local color_cached = Color(50, 250, 50, 240) SWEP.HoldType = "pistol" if CLIENT then - SWEP.PrintName = "magnet_name" - SWEP.Slot = 4 + SWEP.PrintName = "magnet_name" + SWEP.Slot = 4 - SWEP.Icon = "vgui/ttt/icon_magneto_stick" + SWEP.Icon = "vgui/ttt/icon_magneto_stick" - SWEP.DrawCrosshair = false - SWEP.ViewModelFlip = false + SWEP.ViewModelFlip = false end SWEP.Base = "weapon_tttbase" @@ -56,164 +55,201 @@ SWEP.Kind = WEAPON_CARRY SWEP.AllowDelete = false SWEP.AllowDrop = false +SWEP.overrideDropOnDeath = DROP_ON_DEATH_TYPE_DENY SWEP.NoSights = true -SWEP.EntHolding = nil SWEP.CarryHack = nil SWEP.Constr = nil SWEP.PrevOwner = nil --- ConVar syncing -if SERVER then +SWEP.builtin = true + +-- constants +SWEP.pickupRangeRagdoll = 75 +SWEP.pickupRangeMisc = 100 +SWEP.moveForce = 6000 +SWEP.carryDistance = 70 +SWEP.standCheckFrequency = 0.1 +SWEP.entDiffDistanceThreshold = 40000 +SWEP.entDiffCheckFrequency = 1 +SWEP.pinBoneAttachFactor = 0.9 +SWEP.pinBoneLengthFactor = 0.1 +SWEP.pinRopeWidth = 1 +SWEP.pinMaterial = "cable/rope" +SWEP.pinnedHealth = 999999 +SWEP.constraintType = "Rope" +SWEP.dropWakeForce = 500 +SWEP.dropAngleThreshold = 0.75 +SWEP.carryMinDamping = 0 +SWEP.carryMaxDamping = 1000 +SWEP.carryMass = 200 +SWEP.carryHealth = 999 +SWEP.carryPushDelay = 0.03 +SWEP.carryPickupDelay = 0.5 +SWEP.carryPickupRagdollDelay = 0.8 +SWEP.carryPickupReleaseDelay = 0.3 +SWEP.moveObjectLightObjectMass = 50 +SWEP.moveObjectLightObjectAdditionalMass = 0.5 +SWEP.moveObjectLightObjectMassScale = 0.02 +SWEP.moveObjectRagdollForceMultiplier = 2 +SWEP.moveObjectRemapSpeedInMin = 0 +SWEP.moveObjectRemapSpeedInMax = 125 +SWEP.moveObjectRemapSpeedOutMin = 1 +SWEP.pinRagRange = 90 + +local flags = { FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED } + +--- +-- @realm shared +-- stylua: ignore +local cvAllowRagCarry = CreateConVar("ttt_ragdoll_carrying", "1", flags) + +--- +-- @realm server +-- stylua: ignore +local cvPropForce = CreateConVar("ttt_prop_carrying_force", "60000", flags) +--- +-- @realm shared +-- stylua: ignore +local cvPropThrow = CreateConVar("ttt_prop_throwing", "1", flags) + +--- +-- @note Allowing weapon pickups can allow players to cause a crash in the physics +-- system (ie. not fixable). Tuning the range seems to make this more +-- difficult. Not sure why. It's that kind of crash. +-- @realm server +-- stylua: ignore +local cvAllowWepCarry = CreateConVar("ttt_weapon_carrying", "0", flags) - --- - -- @realm server - local cvAllowRagCarry = CreateConVar("ttt_ragdoll_carrying", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - local cvPropForce = CreateConVar("ttt_prop_carrying_force", "60000", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - local cvPropThrow = CreateConVar("ttt_prop_throwing", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - local cvAllowRagPin = CreateConVar("ttt_ragdoll_pinning", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - local cvAllowRagPinInno = CreateConVar("ttt_ragdoll_pinning_innocents", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @note Allowing weapon pickups can allow players to cause a crash in the physics - -- system (ie. not fixable). Tuning the range seems to make this more - -- difficult. Not sure why. It's that kind of crash. - -- @realm server - local cvAllowWepCarry = CreateConVar("ttt_weapon_carrying", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @note Allowing weapon pickups can allow players to cause a crash in the physics - -- system (ie. not fixable). Tuning the range seems to make this more - -- difficult. Not sure why. It's that kind of crash. - -- @realm server - local cvWepCarryRange = CreateConVar("ttt_weapon_carrying_range", "50", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - hook.Add("TTT2SyncGlobals", "TTT2SyncCarryGlobals", function() - SetGlobalBool(cvAllowRagCarry:GetName(), cvAllowRagCarry:GetBool()) - SetGlobalInt(cvPropForce:GetName(), cvPropForce:GetInt()) - SetGlobalBool(cvPropThrow:GetName(), cvPropThrow:GetBool()) - SetGlobalBool(cvAllowRagPin:GetName(), cvAllowRagPin:GetBool()) - SetGlobalBool(cvAllowRagPinInno:GetName(), cvAllowRagPinInno:GetBool()) - - SetGlobalBool(cvAllowWepCarry:GetName(), cvAllowWepCarry:GetBool()) - SetGlobalInt(cvWepCarryRange:GetName(), cvWepCarryRange:GetInt()) - end) - - cvars.AddChangeCallback(cvAllowRagCarry:GetName(), function(name, _, new) - SetGlobalBool(name, tonumber(new) == 1) - end, cvAllowRagCarry:GetName()) - - cvars.AddChangeCallback(cvPropForce:GetName(), function(name, _, new) - SetGlobalInt(name, tonumber(new)) - end, cvPropForce:GetName()) - - cvars.AddChangeCallback(cvPropThrow:GetName(), function(name, _, new) - SetGlobalBool(name, tonumber(new) == 1) - end, cvPropThrow:GetName()) - - cvars.AddChangeCallback(cvAllowRagPin:GetName(), function(name, _, new) - SetGlobalBool(name, tonumber(new) == 1) - end, cvAllowRagPin:GetName()) - - cvars.AddChangeCallback(cvAllowRagPinInno:GetName(), function(name, _, new) - SetGlobalBool(name, tonumber(new) == 1) - end, cvAllowRagPinInno:GetName()) - - cvars.AddChangeCallback(cvAllowWepCarry:GetName(), function(name, _, new) - SetGlobalBool(name, tonumber(new) == 1) - end, cvAllowWepCarry:GetName()) - - cvars.AddChangeCallback(cvWepCarryRange:GetName(), function(name, _, new) - SetGlobalInt(name, tonumber(new)) - end, cvWepCarryRange:GetName()) +--- +-- @note Allowing weapon pickups can allow players to cause a crash in the physics +-- system (ie. not fixable). Tuning the range seems to make this more +-- difficult. Not sure why. It's that kind of crash. +-- @realm server +-- stylua: ignore +local cvWepCarryRange = CreateConVar("ttt_weapon_carrying_range", "50", flags) + +CARRY_TYPE_NONE = 0 +CARRY_TYPE_PROP = 1 +CARRY_TYPE_WEAPON = 2 +CARRY_TYPE_RAGDOLL = 3 + +--- +-- @param Entity ent The entity whose carryability will be assessed +-- @return number CARRY_TYPE_* enum +-- @realm shared +function DetermineCarryType(ent) + if IsValid(ent) then + if ent.IsWeapon and ent:IsWeapon() then + return CARRY_TYPE_WEAPON + elseif ent:GetClass() == "prop_ragdoll" then + return CARRY_TYPE_RAGDOLL + else + return CARRY_TYPE_PROP + end + else + return CARRY_TYPE_NONE + end +end + +--- +-- @return number CARRY_TYPE_* enum +-- @realm shared +function SWEP:GetCarryType() + return DetermineCarryType(self:GetCarryTarget()) end local function SetSubPhysMotionEnabled(ent, enable) - if not IsValid(ent) then return end + if not IsValid(ent) then + return + end - for i = 0, ent:GetPhysicsObjectCount() - 1 do - local subphys = ent:GetPhysicsObjectNum(i) + for i = 0, ent:GetPhysicsObjectCount() - 1 do + local subphys = ent:GetPhysicsObjectNum(i) - if not IsValid(subphys) then continue end + if not IsValid(subphys) then + continue + end - subphys:EnableMotion(enable) + subphys:EnableMotion(enable) - if not enable then continue end + if not enable then + continue + end - subphys:Wake() - end + subphys:Wake() + end end local function KillVelocity(ent) - ent:SetVelocity(vector_origin) + ent:SetVelocity(vector_origin) - -- The only truly effective way to prevent all kinds of velocity and - -- inertia is motion disabling the entire ragdoll for a tick - -- for non-ragdolls this will do the same for their single physobj - SetSubPhysMotionEnabled(ent, false) + -- The only truly effective way to prevent all kinds of velocity and + -- inertia is motion disabling the entire ragdoll for a tick + -- for non-ragdolls this will do the same for their single physobj + SetSubPhysMotionEnabled(ent, false) - timer.Simple(0, function() - if not IsValid(ent) then return end + timer.Simple(0, function() + if not IsValid(ent) then + return + end - SetSubPhysMotionEnabled(ent, true) - end) + SetSubPhysMotionEnabled(ent, true) + end) end --- --- @ignore +-- @realm shared +function SWEP:CanRagPin() + return GetConVar("ttt2_ragdoll_pinning_" .. self:GetOwner():GetSubRoleData().name):GetBool() +end + +--- +-- @param boolean keep_velocity +-- @realm shared function SWEP:Reset(keep_velocity) - if IsValid(self.CarryHack) then - self.CarryHack:Remove() - end - - if IsValid(self.Constr) then - self.Constr:Remove() - end - - if IsValid(self.EntHolding) then - -- it is possible for weapons to be already equipped at this point - -- changing the owner in such a case would cause problems - if not self.EntHolding:IsWeapon() then - if not IsValid(self.PrevOwner) then - self.EntHolding:SetOwner(nil) - else - self.EntHolding:SetOwner(self.PrevOwner) - end - end - - -- the below ought to be unified with self:Drop() - local phys = self.EntHolding:GetPhysicsObject() - - if IsValid(phys) then - phys:ClearGameFlag(FVPHYSICS_PLAYER_HELD) - phys:AddGameFlag(FVPHYSICS_WAS_THROWN) - phys:EnableCollisions(true) - phys:EnableGravity(true) - phys:EnableDrag(true) - phys:EnableMotion(true) - end - - if not keep_velocity and (not GetGlobalBool("ttt_prop_throwing") or self.EntHolding:GetClass() == "prop_ragdoll") then - KillVelocity(self.EntHolding) - end - end - - self.dt.carried_rag = nil - self.EntHolding = nil - self.CarryHack = nil - self.Constr = nil + if IsValid(self.CarryHack) then + self.CarryHack:Remove() + end + + if IsValid(self.Constr) then + self.Constr:Remove() + end + + local ctarget = self:GetCarryTarget() + local ctype = DetermineCarryType(ctarget) + + if IsValid(ctarget) then + -- it is possible for weapons to be already equipped at this point + -- changing the owner in such a case would cause problems + if ctype ~= CARRY_TYPE_WEAPON then + if not IsValid(self.PrevOwner) then + ctarget:SetOwner(nil) + else + ctarget:SetOwner(self.PrevOwner) + end + end + + -- the below ought to be unified with self:Drop() + local phys = ctarget:GetPhysicsObject() + + if IsValid(phys) then + phys:ClearGameFlag(FVPHYSICS_PLAYER_HELD) + phys:AddGameFlag(FVPHYSICS_WAS_THROWN) + phys:EnableCollisions(true) + phys:EnableGravity(true) + phys:EnableDrag(true) + phys:EnableMotion(true) + end + + if not keep_velocity then + KillVelocity(ctarget) + end + end + + self:SetCarryTarget(NULL) + self.CarryHack = nil + self.Constr = nil end SWEP.reset = SWEP.Reset @@ -222,89 +258,44 @@ SWEP.reset = SWEP.Reset -- @return boolean -- @realm shared function SWEP:CheckValidity() - if not IsValid(self.EntHolding) or not IsValid(self.CarryHack) or not IsValid(self.Constr) then - -- if one of them is not valid but another is non-nil... - if self.EntHolding or self.CarryHack or self.Constr then - self:Reset() - end - - return false - else - return true - end + local ctarget = self:GetCarryTarget() + if + (ctarget and not IsValid(ctarget)) + or (self.Constr and not IsValid(self.Constr)) + or (self.CarryHack and not IsValid(self.CarryHack)) + then + self:Reset() + + return false + else + return true + end end local function PlayerStandsOn(ent) - local plys = player.GetAll() - - for i = 1, #plys do - local ply = plys[i] - - if ply:GetGroundEntity() == ent and ply:IsTerror() then - return true - end - end + local plys = player.GetAll() - return false -end - -if SERVER then - local ent_diff = vector_origin - local ent_diff_time = CurTime() - local stand_time = 0 - - --- - -- @ignore - function SWEP:Think() - BaseClass.Think(self) - - if not self:CheckValidity() then return end - - -- If we are too far from our object, force a drop. To avoid doing this - -- vector math extremely often (esp. when everyone is carrying something) - -- even though the occurrence is very rare, limited to once per - -- second. This should be plenty to catch the rare glitcher. - if CurTime() > ent_diff_time then - ent_diff = self:GetPos() - self.EntHolding:GetPos() - - if ent_diff:Dot(ent_diff) > 40000 then - self:Reset() + for i = 1, #plys do + local ply = plys[i] - return - end + if ply:GetGroundEntity() == ent and ply:IsTerror() then + return true + end + end - ent_diff_time = CurTime() + 1 - end - - if CurTime() > stand_time then - if PlayerStandsOn(self.EntHolding) then - self:Reset() - - return - end - - stand_time = CurTime() + 0.1 - end - - local owner = self:GetOwner() - - self.CarryHack:SetPos(owner:EyePos() + owner:GetAimVector() * 70) - self.CarryHack:SetAngles(owner:GetAngles()) - - self.EntHolding:PhysWake() - end + return false end --- -- @ignore function SWEP:PrimaryAttack() - self:DoAttack(false) + self:DoAttack(false) end --- -- @ignore function SWEP:SecondaryAttack() - self:DoAttack(true) + self:DoAttack(true) end --- @@ -314,507 +305,637 @@ end -- @param boolean is_ragdoll -- @realm shared function SWEP:MoveObject(phys, pdir, maxforce, is_ragdoll) - if not IsValid(phys) then return end - - local speed = phys:GetVelocity():Length() - - -- remap speed from 0 -> 125 to force 1 -> 4000 - local force = maxforce + (1 - maxforce) * (speed / 125) - - if is_ragdoll then - force = force * 2 - end - - pdir = pdir * force - - local mass = phys:GetMass() - - -- scale more for light objects - if mass < 50 then - pdir = pdir * (mass + 0.5) * 0.02 - end - - phys:ApplyForceCenter(pdir) + if not IsValid(phys) then + return + end + + local speed = phys:GetVelocity():Length() + -- we're scaling down our desired force (maxforce) by the difference in the object's existing speed from self.moveObjectRemapSpeedInMax + -- effectively clamping the amount of force we can add to an object every tick + local force = math.Remap( + speed, + self.moveObjectRemapSpeedInMin, + self.moveObjectRemapSpeedInMax, + maxforce, + self.moveObjectRemapSpeedOutMin + ) + + if is_ragdoll then + force = force * self.moveObjectRagdollForceMultiplier + end + + pdir = pdir * force + + local mass = phys:GetMass() + + -- scale more for light objects + if mass < self.moveObjectLightObjectMass then + pdir = pdir + * (mass + self.moveObjectLightObjectAdditionalMass) + * self.moveObjectLightObjectMassScale + end + + phys:ApplyForceCenter(pdir) end --- -- @param Entity target -- @realm shared function SWEP:GetRange(target) - if IsValid(target) and target:IsWeapon() and GetGlobalBool("ttt_weapon_carrying") then - return GetGlobalInt("ttt_weapon_carrying_range") - elseif IsValid(target) and target:GetClass() == "prop_ragdoll" then - return 75 - else - return 100 - end + local ctype = DetermineCarryType(target) + + if IsValid(target) then + if ctype == CARRY_TYPE_WEAPON and cvAllowWepCarry:GetBool() then + return cvWepCarryRange:GetInt() + elseif ctype == CARRY_TYPE_RAGDOLL then + return self.pickupRangeRagdoll + end + end + + return self.pickupRangeMisc end --- -- @param Entity target -- @realm shared function SWEP:AllowPickup(target) - local phys = target:GetPhysicsObject() - local ply = self:GetOwner() - - return IsValid(phys) and IsValid(ply) - and not phys:HasGameFlag(FVPHYSICS_NO_PLAYER_PICKUP) - and phys:GetMass() < CARRY_WEIGHT_LIMIT - and not PlayerStandsOn(target) - and target.CanPickup ~= false - and (target:GetClass() ~= "prop_ragdoll" or GetGlobalBool("ttt_ragdoll_carrying")) - and (not target:IsWeapon() or GetGlobalBool("ttt_weapon_carrying")) - --- - -- @realm shared - and not hook.Run("TTT2PlayerPreventPickupEnt", ply, target) + local phys = target:GetPhysicsObject() + local ply = self:GetOwner() + local ctype = DetermineCarryType(target) + + return IsValid(phys) + and IsValid(ply) + and not phys:HasGameFlag(FVPHYSICS_NO_PLAYER_PICKUP) + and phys:GetMass() < CARRY_WEIGHT_LIMIT + and not PlayerStandsOn(target) + and target.CanPickup ~= false + and (ctype ~= CARRY_TYPE_RAGDOLL or cvAllowRagCarry:GetBool()) + and (ctype ~= CARRY_TYPE_WEAPON or cvAllowWepCarry:GetBool()) + --- + -- @realm shared + -- stylua: ignore + and not hook.Run("TTT2PlayerPreventPickupEnt", ply, target) end --- --- @param boolean pickup +-- @param Entity target -- @realm shared -function SWEP:DoAttack(pickup) - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) - - if IsValid(self.EntHolding) then - self:SendWeaponAnim(ACT_VM_MISSCENTER) - - if not pickup and self.EntHolding:GetClass() == "prop_ragdoll" then - -- see if we can pin this ragdoll to a wall in front of us - if not self:PinRagdoll() then - -- else just drop it as usual - self:Drop() - end - else - self:Drop() - end +function SWEP:CanBeMoved(target) + local phys = target:GetPhysicsObject() - self:SetNextSecondaryFire(CurTime() + 0.3) + if not IsValid(phys) or not phys:IsMoveable() or phys:HasGameFlag(FVPHYSICS_PLAYER_HELD) then + return false + end - return - end - - local ply = self:GetOwner() - local trace = ply:GetEyeTrace(MASK_SHOT) - local trEnt = trace.Entity - - if IsValid(trEnt) then - local phys = trEnt:GetPhysicsObject() - - if not IsValid(phys) or not phys:IsMoveable() or phys:HasGameFlag(FVPHYSICS_PLAYER_HELD) then return end - - -- if we let the client mess with physics, desync ensues - if CLIENT then return end - - if pickup then - if (ply:EyePos() - trace.HitPos):Length() < self:GetRange(trEnt) then - if self:AllowPickup(trEnt) then - self:Pickup() - self:SendWeaponAnim(ACT_VM_HITCENTER) - - -- make the refire slower to avoid immediately dropping - local delay = (trEnt:GetClass() == "prop_ragdoll") and 0.8 or 0.5 - - self:SetNextSecondaryFire(CurTime() + delay) - - return - else - local is_ragdoll = trEnt:GetClass() == "prop_ragdoll" - - -- pull heavy stuff - local phys2 = trEnt:GetPhysicsObject() - local pdir = trace.Normal * -1 - - if is_ragdoll then - phys2 = trEnt:GetPhysicsObjectNum(trace.PhysicsBone) - -- increase refire to make rags easier to drag - --self.Weapon:SetNextSecondaryFire(CurTime() + 0.04) - end - - if IsValid(phys2) then - self:MoveObject(phys2, pdir, 6000, is_ragdoll) - - return - end - end - end - else - if (ply:EyePos() - trace.HitPos):Length() < 100 then - local phys2 = trEnt:GetPhysicsObject() - - if IsValid(phys2) then - local pdir = trace.Normal + return true +end - self:MoveObject(phys2, pdir, 6000, trEnt:GetClass() == "prop_ragdoll") +--- +-- @param Entity trEnt +-- @param TraceResult trace +-- @param boolean pull Whether to pull it, instead of push +-- @realm shared +function SWEP:PushObject(trEnt, trace, pull) + -- pull heavy stuff + local phys2 = trEnt:GetPhysicsObject() + local pdir = trace.Normal + if pull then + pdir = pdir * -1 + end + + local is_ragdoll = DetermineCarryType(trEnt) == CARRY_TYPE_RAGDOLL + if is_ragdoll then + phys2 = trEnt:GetPhysicsObjectNum(trace.PhysicsBone) + end + + if IsValid(phys2) then + self:MoveObject(phys2, pdir, self.moveForce, is_ragdoll) + + return true + end +end - self:SetNextPrimaryFire(CurTime() + 0.03) - end - end - end - end +--- +-- @param boolean pickup +-- @realm shared +function SWEP:DoAttack(pickup) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + + -- allows us to "drop" on SecondaryAttack, and "release" with PrimaryAttack + local want_throw = cvPropThrow:GetBool() and not pickup + local ctarget = self:GetCarryTarget() + local ctype = DetermineCarryType(ctarget) + + if IsValid(ctarget) then + self:SendWeaponAnim(ACT_VM_MISSCENTER) + + if not pickup and ctype == CARRY_TYPE_RAGDOLL then + -- see if we can pin this ragdoll to a wall in front of us + if not self:PinRagdoll() then + -- else just drop it as usual + self:Drop(false) + end + else + self:Drop(want_throw) + end + + self:SetNextSecondaryFire(CurTime() + self.carryPickupReleaseDelay) + + return + end + + -- if we let the client mess with physics, desync ensues + if CLIENT then + return + end + + local ply = self:GetOwner() + local trace = ply:GetEyeTrace() + local trEnt = trace.Entity + + if not IsValid(trEnt) or not self:CanBeMoved(trEnt) then + return + end + + local within_range = (ply:EyePos() - trace.HitPos):Length() < self:GetRange(trEnt) + + if not within_range then + return + end + + if pickup then + if self:AllowPickup(trEnt) then + self:Pickup() + self:SendWeaponAnim(ACT_VM_HITCENTER) + + -- make the refire slower to avoid immediately dropping + local delay = (ctype == CARRY_TYPE_RAGDOLL) and self.carryPickupRagdoll + or self.carryPickupDelay + + self:SetNextSecondaryFire(CurTime() + delay) + + return + else + -- pull heavy stuff + self:PushObject(trEnt, trace, true) + end + elseif self:PushObject(trEnt, trace, false) then + self:SetNextPrimaryFire(CurTime() + self.carryPushDelay) + end end --- -- Perform a pickup -- @realm shared function SWEP:Pickup() - if CLIENT or IsValid(self.EntHolding) then return end + local ctarget = self:GetCarryTarget() - local ply = self:GetOwner() - local trace = ply:GetEyeTrace(MASK_SHOT) - local ent = trace.Entity - local entphys = ent:GetPhysicsObject() + if CLIENT or IsValid(ctarget) then + return + end - self.EntHolding = ent + local ply = self:GetOwner() + local trace = ply:GetEyeTrace(MASK_SHOT) + local ent = trace.Entity + local entphys = ent:GetPhysicsObject() - if IsValid(ent) and IsValid(entphys) then - local carryHack = ents.Create("prop_physics") + local ctype = DetermineCarryType(ent) + ctarget = ent + self:SetCarryTarget(ent) - if IsValid(carryHack) then - carryHack:SetPos(ent:GetPos()) + if IsValid(ent) and IsValid(entphys) then + local carryHack = ents.Create("prop_physics") - carryHack:SetModel("models/weapons/w_bugbait.mdl") + if IsValid(carryHack) then + carryHack:SetPos(ent:GetPos()) - carryHack:SetColor(color_cached) - carryHack:SetNoDraw(true) - carryHack:DrawShadow(false) + carryHack:SetModel("models/weapons/w_bugbait.mdl") - carryHack:SetHealth(999) - carryHack:SetOwner(ply) - carryHack:SetCollisionGroup(COLLISION_GROUP_DEBRIS) - carryHack:SetSolid(SOLID_NONE) + carryHack:SetColor(color_cached) + carryHack:SetNoDraw(true) + carryHack:DrawShadow(false) - -- set the desired angles before adding the constraint - carryHack:SetAngles(ply:GetAngles()) + carryHack:SetHealth(self.carryHealth) + carryHack:SetOwner(ply) + carryHack:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + carryHack:SetSolid(SOLID_NONE) - carryHack:Spawn() + -- set the desired angles before adding the constraint + carryHack:SetAngles(ply:GetAngles()) - -- if we already are owner before pickup, we will not want to disown - -- this entity when we drop it - -- weapons should not have their owner changed in this way - if not ent:IsWeapon() then - self.PrevOwner = ent:GetOwner() + carryHack:Spawn() - ent:SetOwner(ply) - end + -- if we already are owner before pickup, we will not want to disown + -- this entity when we drop it + -- weapons should not have their owner changed in this way + if ctype ~= CARRY_TYPE_WEAPON then + self.PrevOwner = ent:GetOwner() - local phys = carryHack:GetPhysicsObject() + ent:SetOwner(ply) + end - if IsValid(phys) then - phys:SetMass(200) - phys:SetDamping(0, 1000) - phys:EnableGravity(false) - phys:EnableCollisions(false) - phys:EnableMotion(false) - phys:AddGameFlag(FVPHYSICS_PLAYER_HELD) - end + local phys = carryHack:GetPhysicsObject() - entphys:AddGameFlag(FVPHYSICS_PLAYER_HELD) + if IsValid(phys) then + phys:SetMass(self.carryMass) + phys:SetDamping(self.carryMinDamping, self.carryMaxDamping) + phys:EnableGravity(false) + phys:EnableCollisions(false) + phys:EnableMotion(false) + phys:AddGameFlag(FVPHYSICS_PLAYER_HELD) + end - local bone = math.Clamp(trace.PhysicsBone, 0, 1) - local max_force = GetGlobalInt("ttt_prop_carrying_force") + entphys:AddGameFlag(FVPHYSICS_PLAYER_HELD) - if ent:GetClass() == "prop_ragdoll" then - self.dt.carried_rag = ent - bone = trace.PhysicsBone - max_force = 0 - else - self.dt.carried_rag = nil - end + local bone = math.Clamp(trace.PhysicsBone, 0, 1) + local max_force = cvPropForce:GetInt() - self.Constr = constraint.Weld(carryHack, ent, 0, bone, max_force, true) - end + if ctype == CARRY_TYPE_RAGDOLL then + self:SetCarryTarget(ent) + bone = trace.PhysicsBone + max_force = 0 + end - self.CarryHack = carryHack - end -end + self.Constr = constraint.Weld(carryHack, ent, 0, bone, max_force, true, false) + end -local down = Vector(0, 0, -1) + self.CarryHack = carryHack + end +end --- -- @return boolean -- @realm shared function SWEP:AllowEntityDrop() - local ply = self:GetOwner() - local ent = self.CarryHack + local ply = self:GetOwner() + local ent = self.CarryHack - if not IsValid(ply) or not IsValid(ent) then - return false - end + if not IsValid(ply) or not IsValid(ent) then + return false + end - local ground = ply:GetGroundEntity() + local ground = ply:GetGroundEntity() - if ground and (ground:IsWorld() or IsValid(ground)) then - return true - end + if ground and (ground:IsWorld() or IsValid(ground)) then + return true + end - local diff = (ent:GetPos() - ply:GetShootPos()):GetNormalized() + local diff = (ent:GetPos() - ply:GetShootPos()):GetNormalized() - return down:Dot(diff) <= 0.75 + return down:Dot(diff) <= self.dropAngleThreshold end --- --- @ignore -function SWEP:Drop() - if not self:CheckValidity() or not self:AllowEntityDrop() then return end - - if SERVER then - self.Constr:Remove() - self.CarryHack:Remove() - - local ent = self.EntHolding - local phys = ent:GetPhysicsObject() - - if IsValid(phys) then - phys:EnableCollisions(true) - phys:EnableGravity(true) - phys:EnableDrag(true) - phys:EnableMotion(true) - phys:Wake() - phys:ApplyForceCenter(self:GetOwner():GetAimVector() * 500) - - phys:ClearGameFlag(FVPHYSICS_PLAYER_HELD) - phys:AddGameFlag(FVPHYSICS_WAS_THROWN) - end - - -- Try to limit ragdoll slinging - if not GetGlobalBool("ttt_prop_throwing") or ent:GetClass() == "prop_ragdoll" then - KillVelocity(ent) - end - - ent:SetPhysicsAttacker(self:GetOwner()) - end - - self:Reset() -end - -local CONSTRAINT_TYPE = "Rope" - -local function RagdollPinnedTakeDamage(rag, dmginfo) - local att = dmginfo:GetAttacker() - if not IsValid(att) then return end - - -- drop from pinned position upon dmg - constraint.RemoveConstraints(rag, CONSTRAINT_TYPE) - - rag:PhysWake() - rag:SetHealth(0) - - rag.is_pinned = false +-- @param boolean keep_velocity +-- @realm shared +function SWEP:Drop(keep_velocity) + if not self:CheckValidity() or not self:AllowEntityDrop() then + return + end + + if SERVER then + local ctarget = self:GetCarryTarget() + local ctype = DetermineCarryType(ctarget) + local ent = ctarget + + if not ctarget then + self:Reset(keep_velocity) + return + end + local phys = ent:GetPhysicsObject() + + if IsValid(phys) then + phys:EnableCollisions(true) + phys:EnableGravity(true) + phys:EnableDrag(true) + phys:EnableMotion(true) + phys:Wake() + phys:ApplyForceCenter(self:GetOwner():GetAimVector() * self.dropWakeForce) + + phys:ClearGameFlag(FVPHYSICS_PLAYER_HELD) + phys:AddGameFlag(FVPHYSICS_WAS_THROWN) + end + + -- Try to limit ragdoll slinging + if not keep_velocity or ctype == CARRY_TYPE_RAGDOLL then + KillVelocity(ent) + end + + ent:SetPhysicsAttacker(self:GetOwner()) + end + + self:Reset(keep_velocity) end --- -- @realm shared function SWEP:PinRagdoll() - if not GetGlobalBool("ttt_ragdoll_pinning") - or self:GetOwner():GetTeam() == TEAM_INNOCENT - and not GetGlobalBool("ttt_ragdoll_pinning_innocents") - then return end - - local rag = self.EntHolding - local ply = self:GetOwner() - - local tr = util.TraceLine({ - start = ply:EyePos(), - endpos = ply:EyePos() + ply:GetAimVector() * PIN_RAG_RANGE, - filter = { - ply, - self, - rag, - self.CarryHack - }, - mask = MASK_SOLID - }) - - if tr.HitWorld and not tr.HitSky then - -- find bone we're holding the ragdoll by - local bone = self.Constr.Bone2 - - -- only allow one rope per bone - for _, c in pairs(constraint.FindConstraints(rag, CONSTRAINT_TYPE)) do - if c.Bone1 == bone then - c.Constraint:Remove() - end - end - - local bonephys = rag:GetPhysicsObjectNum(bone) - - if not IsValid(bonephys) then return end - - local bonepos = bonephys:GetPos() - local attachpos = tr.HitPos - local length = (bonepos - attachpos):Length() * 0.9 - - -- we need to convert using this particular physobj to get the right - -- coordinates - bonepos = bonephys:WorldToLocal(bonepos) - - constraint.Rope(rag, tr.Entity, bone, 0, bonepos, attachpos, length, length * 0.1, 6000, 1, "cable/rope", false) - - rag.is_pinned = true - rag.OnPinnedDamage = RagdollPinnedTakeDamage - - -- lets EntityTakeDamage run for the ragdoll - rag:SetHealth(999999) - - self:Reset(true) - end + if CLIENT or not self:CanRagPin() then + return + end + + local ctarget = self:GetCarryTarget() + local ply = self:GetOwner() + + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:GetAimVector() * self.pinRagRange, + filter = { + ply, + self, + ctarget, + self.CarryHack, + }, + mask = MASK_SOLID, + }) + + if tr.HitWorld and not tr.HitSky then + -- find bone we're holding the ragdoll by + local bone = self.Constr.Bone2 + + -- only allow one rope per bone + for _, c in pairs(constraint.FindConstraints(ctarget, self.constraintType)) do + if c.Bone1 == bone then + c.Constraint:Remove() + end + end + + local bonephys = ctarget:GetPhysicsObjectNum(bone) + + if not IsValid(bonephys) then + return + end + + local bonepos = bonephys:GetPos() + local attachpos = tr.HitPos + local length = (bonepos - attachpos):Length() * self.pinBoneAttachFactor + + -- we need to convert using this particular physobj to get the right + -- coordinates + bonepos = bonephys:WorldToLocal(bonepos) + + -- the constraint has to be created to break with at least as much force as the moveForce or else + -- pinned bodies couldn't be taken down by innocents + constraint.Rope( + ctarget, + tr.Entity, + bone, + 0, + bonepos, + attachpos, + length, + length * self.pinBoneLengthFactor, + self.moveForce, + self.pinRopeWidth, + self.pinMaterial, + false, + color_white + ) + + ctarget.pinned_constraint_type = self.constraintType + ctarget.is_pinned = true + --- + -- @param Entity rag + -- @param DamageInfo dmginfo + -- @realm server + ctarget.OnPinnedDamage = function(rag, dmginfo) + local att = dmginfo:GetAttacker() + if not IsValid(att) then + return + end + + -- drop from pinned position upon dmg + constraint.RemoveConstraints(rag, self.pinned_constraint_type) + + rag:PhysWake() + rag:SetHealth(0) + + rag.pinned_constraint_type = nil + rag.is_pinned = false + end + + -- lets EntityTakeDamage run for the ragdoll + ctarget:SetHealth(self.pinnedHealth) + + self:Reset(true) + return true + end end --- -- @ignore function SWEP:SetupDataTables() - -- we've got these dt slots anyway, might as well use them instead of a - -- globalvar, probably cheaper - self:DTVar("Bool", 0, "can_rag_pin") - self:DTVar("Bool", 0, "can_rag_pin_inno") - - -- client actually has no idea what we're holding, and almost never needs to - -- know - self:DTVar("Entity", 0, "carried_rag") + -- client actually has no idea what we're holding, and almost never needs to know + self:NetworkVar("Entity", 0, "CarryTarget") - return self.BaseClass.SetupDataTables(self) -end - -if SERVER then - --- - -- @ignore - function SWEP:Initialize() - self.dt.can_rag_pin = GetGlobalBool("ttt_ragdoll_pinning") - self.dt.can_rag_pin_inno = GetGlobalBool("ttt_ragdoll_pinning_innocents") - self.dt.carried_rag = nil - - return self.BaseClass.Initialize(self) - end + return BaseClass.SetupDataTables(self) end --- --- @ignore +-- @realm client function SWEP:OnRemove() - self:Reset() + BaseClass.OnRemove(self) + + self:Reset() end --- -- @ignore function SWEP:Deploy() - self:Reset() + self:Reset() - return true + return true end --- -- @ignore function SWEP:Holster() - self:Reset() + self:Reset() - return true + return true end ---- --- @ignore -function SWEP:ShouldDropOnDie() - return false -end - ---- --- @ignore -function SWEP:OnDrop() - self:Remove() +if SERVER then + --- + -- @ignore + function SWEP:Initialize() + self:SetCarryTarget(NULL) + + return BaseClass.Initialize(self) + end + + --- + -- A cancelable hook that is called once a player tries to pickup an entity. + -- @note This hook is not called if prior checks prevent the pickup already + -- @param Player ply The player that tries to pick up an entity + -- @param Entity ent The entity that is about to be picked up + -- @return boolean Return true to cancel the pickup + -- @hook + -- @realm server + function GAMEMODE:TTT2PlayerPreventPickupEnt(ply, ent) end + + local ent_diff = vector_origin + local ent_diff_time = CurTime() + local stand_time = 0 + + --- + -- @realm server + function SWEP:Think() + BaseClass.Think(self) + + local ctarget = self:GetCarryTarget() + local ctype = DetermineCarryType(ctarget) + + if ctype == CARRY_TYPE_NONE or not self:CheckValidity() then + return + end + + -- If we are too far from our object, force a drop. To avoid doing this + -- vector math extremely often (esp. when everyone is carrying something) + -- even though the occurrence is very rare, limited to once per + -- second. This should be plenty to catch the rare glitcher. + if CurTime() > ent_diff_time then + ent_diff = self:GetPos() - ctarget:GetPos() + + if ent_diff:Dot(ent_diff) > self.entDiffDistanceThreshold then + self:Reset() + + return + end + + ent_diff_time = CurTime() + self.entDiffCheckFrequency + end + + if CurTime() > stand_time then + if PlayerStandsOn(ctarget) then + self:Reset() + + return + end + + stand_time = CurTime() + self.standCheckFrequency + end + + local owner = self:GetOwner() + + if IsValid(self.CarryHack) then + self.CarryHack:SetPos(owner:EyePos() + owner:GetAimVector() * self.carryDistance) + self.CarryHack:SetAngles(owner:GetAngles()) + end + + ctarget:PhysWake() + end end -if SERVER then - --- - -- A cancelable hook that is called once a player tries to pickup an entity. - -- @note This hook is not called if prior checks prevent the pickup already - -- @param Player ply The player that tries to pick up an entity - -- @param Entity ent The entity that is about to be picked up - -- @return boolean Return true to cancel the pickup - -- @hook - -- @realm server - function GAMEMODE:TTT2PlayerPreventPickupEnt(ply, ent) - - end - -else -- CLIENT - local draw = draw - - local PT = LANG.GetParamTranslation - local key_params = {primaryfire = Key("+attack", "LEFT MOUSE")} - - --- - -- @ignore - function SWEP:DrawHUD() - self.BaseClass.DrawHUD(self) - - if self.dt.can_rag_pin and IsValid(self.dt.carried_rag) then - local client = LocalPlayer() - - if client:IsSpec() or not client:IsTraitor() and not self.dt.can_rag_pin_inno then return end - - local tr = util.TraceLine({ - start = client:EyePos(), - endpos = client:EyePos() + client:GetAimVector() * PIN_RAG_RANGE, - filter = { - client, - self, - self.dt.carried_rag - }, - mask = MASK_SOLID - }) - - if tr.HitWorld and not tr.HitSky then - draw.SimpleText(PT("magnet_help", key_params), "TabLarge", ScrW() * 0.5, ScrH() * 0.5 - 50, COLOR_RED, TEXT_ALIGN_CENTER) - end - end - end - - --- - -- @ignore - function SWEP:AddToSettingsMenu(parent) - local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") - - local enbRagCarry = form:MakeCheckBox({ - serverConvar = "ttt_ragdoll_carrying", - label = "label_ragdoll_carrying" - }) - - form:MakeCheckBox({ - serverConvar = "ttt_ragdoll_pinning", - label = "label_ragdoll_pinning", - master = enbRagCarry - }) - - form:MakeCheckBox({ - serverConvar = "ttt_ragdoll_pinning_innocents", - label = "label_ragdoll_pinning_innocents", - master = enbRagCarry - }) - - local enbWepCarry = form:MakeCheckBox({ - serverConvar = "ttt_weapon_carrying", - label = "label_weapon_carrying" - }) - - form:MakeSlider({ - serverConvar = "ttt_weapon_carrying_range", - label = "label_weapon_carrying_range", - min = 0, - max = 150, - decimal = 0, - master = enbWepCarry - }) - - form:MakeSlider({ - serverConvar = "ttt_prop_carrying_force", - label = "label_prop_carrying_force", - min = 0, - max = 250000, - decimal = 0 - }) - - form:MakeCheckBox({ - serverConvar = "ttt_prop_throwing", - label = "label_prop_throwing" - }) - end +if CLIENT then + --- + -- @ignore + function SWEP:Initialize() + self:RefreshTTT2HUDHelp() + + return BaseClass.Initialize(self) + end + + --- + -- @ignore + function SWEP:Think() + self:RefreshTTT2HUDHelp() + end + + --- + -- Show relevant strings for the current action. + -- @realm shared + function SWEP:RefreshTTT2HUDHelp() + local ctarget = self:GetCarryTarget() + local ctype = DetermineCarryType(ctarget) + + if ctype ~= CARRY_TYPE_NONE and IsValid(ctarget) then + if ctype == CARRY_TYPE_RAGDOLL then + self:AddTTT2HUDHelp( + self:CanPinCurrentRag() and "magneto_stick_help_carry_rag_pin" + or "magneto_stick_help_carry_rag_drop", + "magneto_stick_help_carry_rag_drop" + ) + elseif ctype == CARRY_TYPE_PROP or ctype == CARRY_TYPE_WEAPON then + self:AddTTT2HUDHelp( + cvPropThrow:GetBool() and "magneto_stick_help_carry_prop_release" + or "magneto_stick_help_carry_prop_drop", + "magneto_stick_help_carry_prop_drop" + ) + end + else + self:AddTTT2HUDHelp("magneto_help_primary", "magneto_help_secondary") + end + end + + --- + -- @return boolean + -- @realm client + function SWEP:CanPinCurrentRag() + local ctarget = self:GetCarryTarget() + local ctype = DetermineCarryType(ctarget) + local ply = self:GetOwner() + if + ctype == CARRY_TYPE_RAGDOLL + and IsValid(ctarget) + and self:CanRagPin() + and ply:IsTerror() + then + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:GetAimVector() * self.pinRagRange, + filter = { + ply, + self, + ctarget, + }, + mask = MASK_SOLID, + }) + + if tr.HitWorld and not tr.HitSky then + return true + end + end + + return false + end + + --- + -- @ignore + function SWEP:AddToSettingsMenu(parent) + local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") + + form:MakeCheckBox({ + serverConvar = "ttt_ragdoll_carrying", + label = "label_ragdoll_carrying", + }) + + local enbWepCarry = form:MakeCheckBox({ + serverConvar = "ttt_weapon_carrying", + label = "label_weapon_carrying", + }) + + form:MakeSlider({ + serverConvar = "ttt_weapon_carrying_range", + label = "label_weapon_carrying_range", + min = 0, + max = 150, + decimal = 0, + master = enbWepCarry, + }) + + form:MakeSlider({ + serverConvar = "ttt_prop_carrying_force", + label = "label_prop_carrying_force", + min = 0, + max = 250000, + decimal = 0, + }) + + form:MakeCheckBox({ + serverConvar = "ttt_prop_throwing", + label = "label_prop_throwing", + }) + end end diff --git a/gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua b/gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua index e1097a72d..a23a1c5ea 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua @@ -5,32 +5,36 @@ local cvCrowbarUnlocks, cvCrowbarPushForce, cvCrowbarDelay if SERVER then - AddCSLuaFile() - - --- - -- @realm server - cvCrowbarDelay = CreateConVar("ttt2_crowbar_shove_delay", "1.0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - cvCrowbarUnlocks = CreateConVar("ttt_crowbar_unlocks", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) - - --- - -- @realm server - cvCrowbarPushForce = CreateConVar("ttt_crowbar_pushforce", "395", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) + AddCSLuaFile() + + --- + -- @realm server + -- stylua: ignore + cvCrowbarDelay = CreateConVar("ttt2_crowbar_shove_delay", "1.0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + --- + -- @realm server + -- stylua: ignore + cvCrowbarUnlocks = CreateConVar("ttt_crowbar_unlocks", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) + + --- + -- @realm server + -- stylua: ignore + cvCrowbarPushForce = CreateConVar("ttt_crowbar_pushforce", "395", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) end +DEFINE_BASECLASS("weapon_tttbase") + SWEP.HoldType = "melee" if CLIENT then - SWEP.PrintName = "crowbar_name" - SWEP.Slot = 0 + SWEP.PrintName = "crowbar_name" + SWEP.Slot = 0 - SWEP.DrawCrosshair = false - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_cbar" + SWEP.Icon = "vgui/ttt/icon_cbar" end SWEP.Base = "weapon_tttbase" @@ -56,6 +60,7 @@ SWEP.Secondary.Delay = 5 SWEP.Kind = WEAPON_MELEE SWEP.WeaponID = AMMO_CROWBAR +SWEP.builtin = true SWEP.NoSights = true SWEP.IsSilent = true @@ -65,6 +70,7 @@ SWEP.AutoSpawnable = false SWEP.AllowDelete = false -- never removed for weapon reduction SWEP.AllowDrop = false +SWEP.overrideDropOnDeath = DROP_ON_DEATH_TYPE_DENY local sound_single = Sound("Weapon_Crowbar.Single") @@ -72,19 +78,19 @@ local sound_single = Sound("Weapon_Crowbar.Single") -- open) and are the right class. Opening behaviour also differs per class, so -- return one of the OPEN_ values local pmnc_tbl = { - prop_door_rotating = OPEN_ROT, - func_door = OPEN_DOOR, - func_door_rotating = OPEN_DOOR, - func_button = OPEN_BUT, - func_movelinear = OPEN_NOTOGGLE + prop_door_rotating = OPEN_ROT, + func_door = OPEN_DOOR, + func_door_rotating = OPEN_DOOR, + func_button = OPEN_BUT, + func_movelinear = OPEN_NOTOGGLE, } local function OpenableEnt(ent) - return pmnc_tbl[ent:GetClass()] or OPEN_NO + return pmnc_tbl[ent:GetClass()] or OPEN_NO end local function CrowbarCanUnlock(t) - return not GAMEMODE.crowbar_unlocks or GAMEMODE.crowbar_unlocks[t] + return not GAMEMODE.crowbar_unlocks or GAMEMODE.crowbar_unlocks[t] end --- @@ -93,254 +99,267 @@ end -- @return number Entity types a crowbar might open -- @realm shared function SWEP:OpenEnt(hitEnt) - -- Get ready for some prototype-quality code, all ye who read this - if SERVER and cvCrowbarUnlocks:GetBool() then - local openable = OpenableEnt(hitEnt) - - if openable == OPEN_DOOR or openable == OPEN_ROT then - local unlock = CrowbarCanUnlock(openable) - if unlock then - hitEnt:Fire("Unlock", nil, 0) - end - - if unlock or hitEnt:HasSpawnFlags(SF_NPC_LONG_RANGE) then -- Long Visibility/Shoot - if openable == OPEN_ROT then - hitEnt:Fire("OpenAwayFrom", self:GetOwner(), 0) - end - - hitEnt:Fire("Toggle", nil, 0) - else - return OPEN_NO - end - elseif openable == OPEN_BUT then - if CrowbarCanUnlock(openable) then - hitEnt:Fire("Unlock", nil, 0) - hitEnt:Fire("Press", nil, 0) - else - return OPEN_NO - end - elseif openable == OPEN_NOTOGGLE then - if CrowbarCanUnlock(openable) then - hitEnt:Fire("Open", nil, 0) - else - return OPEN_NO - end - end - - return openable - else - return OPEN_NO - end + -- Get ready for some prototype-quality code, all ye who read this + if SERVER and cvCrowbarUnlocks:GetBool() then + local openable = OpenableEnt(hitEnt) + + if openable == OPEN_DOOR or openable == OPEN_ROT then + local unlock = CrowbarCanUnlock(openable) + if unlock then + hitEnt:Fire("Unlock", nil, 0) + end + + if unlock or hitEnt:HasSpawnFlags(SF_NPC_LONG_RANGE) then -- Long Visibility/Shoot + if openable == OPEN_ROT then + hitEnt:Fire("OpenAwayFrom", self:GetOwner(), 0) + end + + hitEnt:Fire("Toggle", nil, 0) + else + return OPEN_NO + end + elseif openable == OPEN_BUT then + if CrowbarCanUnlock(openable) then + hitEnt:Fire("Unlock", nil, 0) + hitEnt:Fire("Press", nil, 0) + else + return OPEN_NO + end + elseif openable == OPEN_NOTOGGLE then + if CrowbarCanUnlock(openable) then + hitEnt:Fire("Open", nil, 0) + else + return OPEN_NO + end + end + + return openable + else + return OPEN_NO + end end --- -- @ignore function SWEP:PrimaryAttack() - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - - local owner = self:GetOwner() - if not IsValid(owner) then return end - - if isfunction(owner.LagCompensation) then -- for some reason not always true - owner:LagCompensation(true) - end - - local spos = owner:GetShootPos() - local sdest = spos + owner:GetAimVector() * 100 - - local tr_main = util.TraceLine({ - start = spos, - endpos = sdest, - filter = owner, - mask = MASK_SHOT_HULL - }) - - local hitEnt = tr_main.Entity - - self:EmitSound(sound_single) - - if IsValid(hitEnt) or tr_main.HitWorld then - self:SendWeaponAnim(ACT_VM_HITCENTER) - - if SERVER or IsFirstTimePredicted() then - local edata = EffectData() - edata:SetStart(spos) - edata:SetOrigin(tr_main.HitPos) - edata:SetNormal(tr_main.Normal) - edata:SetSurfaceProp(tr_main.SurfaceProps) - edata:SetHitBox(tr_main.HitBox) - --edata:SetDamageType(DMG_CLUB) - edata:SetEntity(hitEnt) - - if hitEnt:IsPlayer() or hitEnt:GetClass() == "prop_ragdoll" then - util.Effect("BloodImpact", edata) - - -- does not work on players rah - --util.Decal("Blood", tr_main.HitPos + tr_main.HitNormal, tr_main.HitPos - tr_main.HitNormal) - - -- do a bullet just to make blood decals work sanely - -- need to disable lagcomp because firebullets does its own - owner:LagCompensation(false) - owner:FireBullets({ - Num = 1, - Src = spos, - Dir = owner:GetAimVector(), - Spread = Vector(0, 0, 0), - Tracer = 0, - Force = 1, - Damage = 0 - }) - else - util.Effect("Impact", edata) - end - end - else - self:SendWeaponAnim(ACT_VM_MISSCENTER) - end - - if SERVER then - - -- Do another trace that sees nodraw stuff like func_button - local tr_all = util.TraceLine({ - start = spos, - endpos = sdest, - filter = owner - }) - - local trEnt = tr_all.Entity - - owner:SetAnimation(PLAYER_ATTACK1) - - if IsValid(hitEnt) then - if self:OpenEnt(hitEnt) == OPEN_NO and IsValid(trEnt) then - self:OpenEnt(trEnt) -- See if there's a nodraw thing we should open - end - - local dmg = DamageInfo() - dmg:SetDamage(self.Primary.Damage) - dmg:SetAttacker(owner) - dmg:SetInflictor(self) - dmg:SetDamageForce(owner:GetAimVector() * 1500) - dmg:SetDamagePosition(owner:GetPos()) - dmg:SetDamageType(DMG_CLUB) - - hitEnt:DispatchTraceAttack(dmg, spos + owner:GetAimVector() * 3, sdest) - elseif IsValid(trEnt) then -- See if our nodraw trace got the goods - self:OpenEnt(trEnt) - end - end - - if isfunction(owner.LagCompensation) then - owner:LagCompensation(false) - end + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + local owner = self:GetOwner() + if not IsValid(owner) then + return + end + + if isfunction(owner.LagCompensation) then -- for some reason not always true + owner:LagCompensation(true) + end + + local spos = owner:GetShootPos() + local sdest = spos + owner:GetAimVector() * 100 + + local tr_main = util.TraceLine({ + start = spos, + endpos = sdest, + filter = owner, + mask = MASK_SHOT_HULL, + }) + + local hitEnt = tr_main.Entity + + self:EmitSound(sound_single) + + if IsValid(hitEnt) or tr_main.HitWorld then + self:SendWeaponAnim(ACT_VM_HITCENTER) + + if SERVER or IsFirstTimePredicted() then + local edata = EffectData() + edata:SetStart(spos) + edata:SetOrigin(tr_main.HitPos) + edata:SetNormal(tr_main.Normal) + edata:SetSurfaceProp(tr_main.SurfaceProps) + edata:SetHitBox(tr_main.HitBox) + --edata:SetDamageType(DMG_CLUB) + edata:SetEntity(hitEnt) + + if hitEnt:IsPlayer() or hitEnt:GetClass() == "prop_ragdoll" then + util.Effect("BloodImpact", edata) + + -- does not work on players rah + --util.Decal("Blood", tr_main.HitPos + tr_main.HitNormal, tr_main.HitPos - tr_main.HitNormal) + + -- do a bullet just to make blood decals work sanely + -- need to disable lagcomp because firebullets does its own + owner:LagCompensation(false) + owner:FireBullets({ + Num = 1, + Src = spos, + Dir = owner:GetAimVector(), + Spread = Vector(0, 0, 0), + Tracer = 0, + Force = 1, + Damage = 0, + }) + else + util.Effect("Impact", edata) + end + end + else + self:SendWeaponAnim(ACT_VM_MISSCENTER) + end + + if SERVER then + -- Do another trace that sees nodraw stuff like func_button + local tr_all = util.TraceLine({ + start = spos, + endpos = sdest, + filter = owner, + }) + + local trEnt = tr_all.Entity + + owner:SetAnimation(PLAYER_ATTACK1) + + if IsValid(hitEnt) then + if self:OpenEnt(hitEnt) == OPEN_NO and IsValid(trEnt) then + self:OpenEnt(trEnt) -- See if there's a nodraw thing we should open + end + + local dmg = DamageInfo() + dmg:SetDamage(self.Primary.Damage) + dmg:SetAttacker(owner) + dmg:SetInflictor(self) + dmg:SetDamageForce(owner:GetAimVector() * 1500) + dmg:SetDamagePosition(owner:GetPos()) + dmg:SetDamageType(DMG_CLUB) + + hitEnt:DispatchTraceAttack(dmg, spos + owner:GetAimVector() * 3, sdest) + elseif IsValid(trEnt) then -- See if our nodraw trace got the goods + self:OpenEnt(trEnt) + end + end + + if isfunction(owner.LagCompensation) then + owner:LagCompensation(false) + end end --- -- @ignore function SWEP:SecondaryAttack() - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - self:SetNextSecondaryFire(CurTime() + 0.1) - - local owner = self:GetOwner() - if not IsValid(owner) then return end - - if isfunction(owner.LagCompensation) then - owner:LagCompensation(true) - end - - local tr = owner:GetEyeTrace(MASK_SHOT) - local ply = tr.Entity - - if tr.Hit and IsValid(ply) and ply:IsPlayer() and (owner:EyePos() - tr.HitPos):Length() < 100 then - --- - -- @realm shared - if SERVER and not ply:IsFrozen() and not hook.Run("TTT2PlayerPreventPush", owner, ply) then - local pushvel = tr.Normal * cvCrowbarPushForce:GetFloat() - pushvel.z = math.Clamp(pushvel.z, 50, 100) -- limit the upward force to prevent launching - - ply:SetVelocity(ply:GetVelocity() + pushvel) - owner:SetAnimation(PLAYER_ATTACK1) - - ply.was_pushed = { - att = owner, - t = CurTime(), - wep = self:GetClass(), - -- infl = self - } - end - - self:EmitSound(sound_single) - self:SendWeaponAnim(ACT_VM_HITCENTER) - self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) - end - - if isfunction(owner.LagCompensation) then - owner:LagCompensation(false) - end + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + 0.1) + + local owner = self:GetOwner() + if not IsValid(owner) then + return + end + + if isfunction(owner.LagCompensation) then + owner:LagCompensation(true) + end + + local tr = owner:GetEyeTrace(MASK_SHOT) + local ply = tr.Entity + + if + tr.Hit + and IsValid(ply) + and ply:IsPlayer() + and (owner:EyePos() - tr.HitPos):Length() < 100 + then + --- + -- @realm shared + -- stylua: ignore + if SERVER and not ply:IsFrozen() and not hook.Run("TTT2PlayerPreventPush", owner, ply) then + local pushvel = tr.Normal * cvCrowbarPushForce:GetFloat() + pushvel.z = math.Clamp(pushvel.z, 50, 100) -- limit the upward force to prevent launching + + ply:SetVelocity(ply:GetVelocity() + pushvel) + owner:SetAnimation(PLAYER_ATTACK1) + + ply.was_pushed = { + att = owner, + t = CurTime(), + wep = self:GetClass(), + -- infl = self + } + end + + self:EmitSound(sound_single) + self:SendWeaponAnim(ACT_VM_HITCENTER) + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + end + + if isfunction(owner.LagCompensation) then + owner:LagCompensation(false) + end end ---- --- @ignore -function SWEP:OnDrop() - self:Remove() +if SERVER then + --- + -- A cancelable hook that is called if a player tries to push another player. + -- @param Player ply The player that tries to push + -- @param Player pushPly The player that is about to be pushed + -- @return boolean Return true to cancel the push + -- @hook + -- @realm server + function GAMEMODE:TTT2PlayerPreventPush(ply, pushPly) end end if SERVER then - --- - -- A cancelable hook that is called if a player tries to push another player. - -- @param Player ply The player that tries to push - -- @param Player pushPly The player that is about to be pushed - -- @return boolean Return true to cancel the push - -- @hook - -- @realm server - function GAMEMODE:TTT2PlayerPreventPush(ply, pushPly) - - end + -- manipulate shove attack for all crowbar alikes + local function ChangeShoveDelay() + local weps = weapons.GetList() + + for i = 1, #weps do + local wep = weps[i] + + --all weapons on the WEAPON_MELEE slot should be Crowbars or Crowbar alikes + if not wep.Kind or wep.Kind ~= WEAPON_MELEE then + continue + end + + wep.Secondary.Delay = cvCrowbarDelay:GetFloat() + end + end + + cvars.AddChangeCallback(cvCrowbarDelay:GetName(), ChangeShoveDelay, "TTT2CrowbarShoveDelay") + + hook.Add("TTT2Initialize", "TTT2ChangeMeleesSecondaryDelay", ChangeShoveDelay) end -if SERVER then - -- manipulate shove attack for all crowbar alikes - local function ChangeShoveDelay() - local weps = weapons.GetList() - - for i = 1, #weps do - local wep = weps[i] - - --all weapons on the WEAPON_MELEE slot should be Crowbars or Crowbar alikes - if not wep.Kind or wep.Kind ~= WEAPON_MELEE then continue end - - wep.Secondary.Delay = cvCrowbarDelay:GetFloat() - end - end - - cvars.AddChangeCallback(cvCrowbarDelay:GetName(), ChangeShoveDelay, "TTT2CrowbarShoveDelay") - - hook.Add("TTT2Initialize", "TTT2ChangeMeleesSecondaryDelay", ChangeShoveDelay) -else -- CLIENT - --- - -- @ignore - function SWEP:AddToSettingsMenu(parent) - local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") - - form:MakeCheckBox({ - serverConvar = "ttt_crowbar_unlocks", - label = "label_crowbar_unlocks" - }) - - form:MakeSlider({ - serverConvar = "ttt_crowbar_pushforce", - label = "label_crowbar_pushforce", - min = 0, - max = 750, - decimal = 0 - }) - - form:MakeSlider({ - serverConvar = "ttt2_crowbar_shove_delay", - label = "label_crowbar_shove_delay", - min = 0, - max = 10, - decimal = 1 - }) - end +if CLIENT then + --- + -- @ignore + function SWEP:Initialize() + self:AddTTT2HUDHelp("crowbar_help_primary", "crowbar_help_secondary") + + return BaseClass.Initialize(self) + end + + --- + -- @ignore + function SWEP:AddToSettingsMenu(parent) + local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") + + form:MakeCheckBox({ + serverConvar = "ttt_crowbar_unlocks", + label = "label_crowbar_unlocks", + }) + + form:MakeSlider({ + serverConvar = "ttt_crowbar_pushforce", + label = "label_crowbar_pushforce", + min = 0, + max = 750, + decimal = 0, + }) + + form:MakeSlider({ + serverConvar = "ttt2_crowbar_shove_delay", + label = "label_crowbar_shove_delay", + min = 0, + max = 10, + decimal = 1, + }) + end end diff --git a/gamemodes/terrortown/entities/weapons/weapon_zm_mac10.lua b/gamemodes/terrortown/entities/weapons/weapon_zm_mac10.lua index a16a8a498..80937968d 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_zm_mac10.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_zm_mac10.lua @@ -1,24 +1,25 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end SWEP.HoldType = "ar2" if CLIENT then - SWEP.PrintName = "MAC10" - SWEP.Slot = 2 + SWEP.PrintName = "MAC10" + SWEP.Slot = 2 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_mac" - SWEP.IconLetter = "l" + SWEP.Icon = "vgui/ttt/icon_mac" + SWEP.IconLetter = "l" end SWEP.Base = "weapon_tttbase" SWEP.Kind = WEAPON_HEAVY SWEP.WeaponID = AMMO_MAC10 +SWEP.builtin = true SWEP.spawnType = WEAPON_TYPE_HEAVY SWEP.Primary.Damage = 12 @@ -28,7 +29,7 @@ SWEP.Primary.ClipSize = 30 SWEP.Primary.ClipMax = 60 SWEP.Primary.DefaultClip = 30 SWEP.Primary.Automatic = true -SWEP.Primary.Ammo = "smg1" +SWEP.Primary.Ammo = "SMG1" SWEP.Primary.Recoil = 1.15 SWEP.Primary.Sound = Sound("Weapon_mac10.Single") @@ -38,6 +39,7 @@ SWEP.AmmoEnt = "item_ammo_smg1_ttt" SWEP.UseHands = true SWEP.ViewModel = "models/weapons/cstrike/c_smg_mac10.mdl" SWEP.WorldModel = "models/weapons/w_smg_mac10.mdl" +SWEP.idleResetFix = true SWEP.IronSightsPos = Vector(-8.921, -9.528, 2.9) SWEP.IronSightsAng = Vector(0.699, -5.301, -7) @@ -47,15 +49,15 @@ SWEP.DeploySpeed = 3 --- -- @ignore function SWEP:GetHeadshotMultiplier(victim, dmginfo) - local att = dmginfo:GetAttacker() + local att = dmginfo:GetAttacker() - if not IsValid(att) then - return 2 - end + if not IsValid(att) then + return 2 + end - local dist = victim:GetPos():Distance(att:GetPos()) - local d = math.max(0, dist - 150) + local dist = victim:GetPos():Distance(att:GetPos()) + local d = math.max(0, dist - 150) - -- decay from 3.2 to 1.7 - return 1.7 + math.max(0, 1.5 - 0.002 * (d ^ 1.25)) + -- decay from 3.2 to 1.7 + return 1.7 + math.max(0, 1.5 - 0.002 * (d ^ 1.25)) end diff --git a/gamemodes/terrortown/entities/weapons/weapon_zm_molotov.lua b/gamemodes/terrortown/entities/weapons/weapon_zm_molotov.lua index 368026e42..67eec5bd6 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_zm_molotov.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_zm_molotov.lua @@ -1,24 +1,25 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end SWEP.HoldType = "grenade" if CLIENT then - SWEP.PrintName = "grenade_fire" - SWEP.Slot = 3 + SWEP.PrintName = "grenade_fire" + SWEP.Slot = 3 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_nades" - SWEP.IconLetter = "P" + SWEP.Icon = "vgui/ttt/icon_firegrenade" + SWEP.IconLetter = "P" end SWEP.Base = "weapon_tttbasegrenade" SWEP.Kind = WEAPON_NADE SWEP.WeaponID = AMMO_MOLOTOV +SWEP.builtin = true SWEP.spawnType = WEAPON_TYPE_NADE SWEP.UseHands = true @@ -33,5 +34,5 @@ SWEP.Spawnable = true -- really the only difference between grenade weapons: the model and the thrown ent. -- @ignore function SWEP:GetGrenadeName() - return "ttt_firegrenade_proj" + return "ttt_firegrenade_proj" end diff --git a/gamemodes/terrortown/entities/weapons/weapon_zm_pistol.lua b/gamemodes/terrortown/entities/weapons/weapon_zm_pistol.lua index 235ba15e0..a2c305337 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_zm_pistol.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_zm_pistol.lua @@ -1,24 +1,25 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end SWEP.HoldType = "pistol" if CLIENT then - SWEP.PrintName = "pistol_name" - SWEP.Slot = 1 + SWEP.PrintName = "pistol_name" + SWEP.Slot = 1 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_pistol" - SWEP.IconLetter = "u" + SWEP.Icon = "vgui/ttt/icon_pistol" + SWEP.IconLetter = "u" end SWEP.Base = "weapon_tttbase" SWEP.Kind = WEAPON_PISTOL SWEP.WeaponID = AMMO_PISTOL +SWEP.builtin = true SWEP.spawnType = WEAPON_TYPE_PISTOL SWEP.Primary.Recoil = 1.5 @@ -38,6 +39,7 @@ SWEP.AmmoEnt = "item_ammo_pistol_ttt" SWEP.UseHands = true SWEP.ViewModel = "models/weapons/cstrike/c_pist_fiveseven.mdl" SWEP.WorldModel = "models/weapons/w_pist_fiveseven.mdl" +SWEP.idleResetFix = true SWEP.IronSightsPos = Vector(-5.95, -4, 2.799) SWEP.IronSightsAng = Vector(0, 0, 0) diff --git a/gamemodes/terrortown/entities/weapons/weapon_zm_revolver.lua b/gamemodes/terrortown/entities/weapons/weapon_zm_revolver.lua index 5203d1cbd..86bd4230e 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_zm_revolver.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_zm_revolver.lua @@ -1,23 +1,24 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end SWEP.HoldType = "pistol" if CLIENT then - SWEP.PrintName = "Deagle" - SWEP.Slot = 1 + SWEP.PrintName = "Deagle" + SWEP.Slot = 1 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_deagle" + SWEP.Icon = "vgui/ttt/icon_deagle" end SWEP.Base = "weapon_tttbase" SWEP.Kind = WEAPON_PISTOL SWEP.WeaponID = AMMO_DEAGLE +SWEP.builtin = true SWEP.spawnType = WEAPON_TYPE_PISTOL SWEP.Primary.Ammo = "AlyxGun" -- hijack an ammo type we don't use otherwise @@ -40,6 +41,7 @@ SWEP.AmmoEnt = "item_ammo_revolver_ttt" SWEP.UseHands = true SWEP.ViewModel = "models/weapons/cstrike/c_pist_deagle.mdl" SWEP.WorldModel = "models/weapons/w_pist_deagle.mdl" +SWEP.idleResetFix = true SWEP.IronSightsPos = Vector(-6.361, -3.701, 2.15) SWEP.IronSightsAng = Vector(0, 0, 0) diff --git a/gamemodes/terrortown/entities/weapons/weapon_zm_rifle.lua b/gamemodes/terrortown/entities/weapons/weapon_zm_rifle.lua index 749e05a58..1c7960059 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_zm_rifle.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_zm_rifle.lua @@ -1,24 +1,27 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end +DEFINE_BASECLASS("weapon_tttbase") + SWEP.HoldType = "ar2" if CLIENT then - SWEP.PrintName = "rifle_name" - SWEP.Slot = 2 + SWEP.PrintName = "rifle_name" + SWEP.Slot = 2 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_scout" - SWEP.IconLetter = "n" + SWEP.Icon = "vgui/ttt/icon_scout" + SWEP.IconLetter = "n" end SWEP.Base = "weapon_tttbase" SWEP.Kind = WEAPON_HEAVY SWEP.WeaponID = AMMO_RIFLE +SWEP.builtin = true SWEP.spawnType = WEAPON_TYPE_SNIPER SWEP.Primary.Delay = 1.5 @@ -43,6 +46,7 @@ SWEP.AmmoEnt = "item_ammo_357_ttt" SWEP.UseHands = true SWEP.ViewModel = Model("models/weapons/cstrike/c_snip_scout.mdl") SWEP.WorldModel = Model("models/weapons/w_snip_scout.mdl") +SWEP.idleResetFix = true SWEP.IronSightsPos = Vector(5, -15, -2) SWEP.IronSightsAng = Vector(2.6, 1.37, 3.5) @@ -50,132 +54,141 @@ SWEP.IronSightsAng = Vector(2.6, 1.37, 3.5) --- -- @ignore function SWEP:SetZoom(state) - local owner = self:GetOwner() + local owner = self:GetOwner() - if not IsValid(owner) or not owner:IsPlayer() then return end + if not IsValid(owner) or not owner:IsPlayer() then + return + end - if state then - owner:SetFOV(20, 0.3) - else - owner:SetFOV(0, 0.2) - end + if state then + owner:SetFOV(20, 0.3) + else + owner:SetFOV(0, 0.2) + end end --- -- @ignore function SWEP:PrimaryAttack(worldsnd) - self.BaseClass.PrimaryAttack(self, worldsnd) + BaseClass.PrimaryAttack(self, worldsnd) - self:SetNextSecondaryFire(CurTime() + 0.1) + self:SetNextSecondaryFire(CurTime() + 0.1) end --- -- Add some zoom to ironsights for this gun -- @ignore function SWEP:SecondaryAttack() - if not self.IronSightsPos or self:GetNextSecondaryFire() > CurTime() then return end + if not self.IronSightsPos or self:GetNextSecondaryFire() > CurTime() then + return + end - local bIronsights = not self:GetIronsights() + local bIronsights = not self:GetIronsights() - self:SetIronsights(bIronsights) - self:SetZoom(bIronsights) + self:SetIronsights(bIronsights) + self:SetZoom(bIronsights) - if CLIENT then - self:EmitSound(self.Secondary.Sound) - end + if CLIENT then + self:EmitSound(self.Secondary.Sound) + end - self:SetNextSecondaryFire(CurTime() + 0.3) + self:SetNextSecondaryFire(CurTime() + 0.3) end --- -- @ignore function SWEP:PreDrop() - self:SetZoom(false) - self:SetIronsights(false) + self:SetIronsights(false) + self:SetZoom(false) - return self.BaseClass.PreDrop(self) + return BaseClass.PreDrop(self) end --- -- @ignore function SWEP:Reload() - if self:Clip1() == self.Primary.ClipSize or self:GetOwner():GetAmmoCount(self.Primary.Ammo) <= 0 then return end + if + self:Clip1() == self.Primary.ClipSize + or self:GetOwner():GetAmmoCount(self.Primary.Ammo) <= 0 + then + return + end + + self:DefaultReload(ACT_VM_RELOAD) - self:DefaultReload(ACT_VM_RELOAD) - self:SetIronsights(false) - self:SetZoom(false) + self:SetIronsights(false) + self:SetZoom(false) end --- -- @ignore function SWEP:Holster() - self:SetIronsights(false) - self:SetZoom(false) + self:SetIronsights(false) + self:SetZoom(false) - return true + return true end if CLIENT then - local scope = surface.GetTextureID("sprites/scope") - - --- - -- @ignore - function SWEP:DrawHUD() - if self:GetIronsights() then - surface.SetDrawColor(0, 0, 0, 255) - - local scrW = ScrW() - local scrH = ScrH() - - local x = 0.5 * scrW - local y = 0.5 * scrH - local scope_size = scrH - - -- crosshair - local gap = 80 - local length = scope_size - - surface.DrawLine(x - length, y, x - gap, y) - surface.DrawLine(x + length, y, x + gap, y) - surface.DrawLine(x, y - length, x, y - gap) - surface.DrawLine(x, y + length, x, y + gap) - - gap = 0 - length = 50 - - surface.DrawLine(x - length, y, x - gap, y) - surface.DrawLine(x + length, y, x + gap, y) - surface.DrawLine(x, y - length, x, y - gap) - surface.DrawLine(x, y + length, x, y + gap) - - - -- cover edges - local sh = 0.5 * scope_size - local w = x - sh + 2 - - surface.DrawRect(0, 0, w, scope_size) - surface.DrawRect(x + sh - 2, 0, w, scope_size) - - -- cover gaps on top and bottom of screen - surface.DrawLine(0, 0, scrW, 0) - surface.DrawLine(0, scrH - 1, scrW, scrH - 1) - - surface.SetDrawColor(255, 0, 0, 255) - surface.DrawLine(x, y, x + 1, y + 1) - - -- scope - surface.SetTexture(scope) - surface.SetDrawColor(255, 255, 255, 255) - - surface.DrawTexturedRectRotated(x, y, scope_size, scope_size, 0) - else - return self.BaseClass.DrawHUD(self) - end - end - - --- - -- @ignore - function SWEP:AdjustMouseSensitivity() - return self:GetIronsights() and 0.2 or nil - end + local scope = surface.GetTextureID("sprites/scope") + + --- + -- @ignore + function SWEP:DrawHUD() + if self:GetIronsights() then + surface.SetDrawColor(0, 0, 0, 255) + + local scrW = ScrW() + local scrH = ScrH() + + local x = 0.5 * scrW + local y = 0.5 * scrH + local scope_size = scrH + + -- crosshair + local gap = 80 + local length = scope_size + + surface.DrawLine(x - length, y, x - gap, y) + surface.DrawLine(x + length, y, x + gap, y) + surface.DrawLine(x, y - length, x, y - gap) + surface.DrawLine(x, y + length, x, y + gap) + + gap = 0 + length = 50 + + surface.DrawLine(x - length, y, x - gap, y) + surface.DrawLine(x + length, y, x + gap, y) + surface.DrawLine(x, y - length, x, y - gap) + surface.DrawLine(x, y + length, x, y + gap) + + -- cover edges + local sh = 0.5 * scope_size + local w = x - sh + 2 + + surface.DrawRect(0, 0, w, scope_size) + surface.DrawRect(x + sh - 2, 0, w, scope_size) + + -- cover gaps on top and bottom of screen + surface.DrawLine(0, 0, scrW, 0) + surface.DrawLine(0, scrH - 1, scrW, scrH - 1) + + surface.SetDrawColor(255, 0, 0, 255) + surface.DrawLine(x, y, x + 1, y + 1) + + -- scope + surface.SetTexture(scope) + surface.SetDrawColor(255, 255, 255, 255) + + surface.DrawTexturedRectRotated(x, y, scope_size, scope_size, 0) + else + return BaseClass.DrawHUD(self) + end + end + + --- + -- @ignore + function SWEP:AdjustMouseSensitivity() + return self:GetIronsights() and 0.2 or nil + end end diff --git a/gamemodes/terrortown/entities/weapons/weapon_zm_shotgun.lua b/gamemodes/terrortown/entities/weapons/weapon_zm_shotgun.lua index 6ce721be6..b5d0c1770 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_zm_shotgun.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_zm_shotgun.lua @@ -1,24 +1,27 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end +DEFINE_BASECLASS("weapon_tttbase") + SWEP.HoldType = "shotgun" if CLIENT then - SWEP.PrintName = "shotgun_name" - SWEP.Slot = 2 + SWEP.PrintName = "shotgun_name" + SWEP.Slot = 2 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_shotgun" - SWEP.IconLetter = "B" + SWEP.Icon = "vgui/ttt/icon_shotgun" + SWEP.IconLetter = "B" end SWEP.Base = "weapon_tttbase" SWEP.Kind = WEAPON_HEAVY SWEP.WeaponID = AMMO_SHOTGUN +SWEP.builtin = true SWEP.spawnType = WEAPON_TYPE_SHOTGUN SWEP.Primary.Ammo = "Buckshot" @@ -40,6 +43,7 @@ SWEP.AmmoEnt = "item_box_buckshot_ttt" SWEP.UseHands = true SWEP.ViewModel = "models/weapons/cstrike/c_shot_xm1014.mdl" SWEP.WorldModel = "models/weapons/w_shot_xm1014.mdl" +SWEP.idleResetFix = true SWEP.IronSightsPos = Vector(-6.881, -9.214, 2.66) SWEP.IronSightsAng = Vector(-0.101, -0.7, -0.201) @@ -47,119 +51,129 @@ SWEP.IronSightsAng = Vector(-0.101, -0.7, -0.201) --- -- @ignore function SWEP:SetupDataTables() - self:NetworkVar("Bool", 0, "Reloading") - self:NetworkVar("Float", 0, "ReloadTimer") + self:NetworkVar("Bool", 0, "Reloading") + self:NetworkVar("Float", 0, "ReloadTimer") - return self.BaseClass.SetupDataTables(self) + return BaseClass.SetupDataTables(self) end --- -- @ignore function SWEP:Reload() - if self:GetReloading() or self:Clip1() > self.Primary.ClipSize - or self:GetOwner():GetAmmoCount(self.Primary.Ammo) < 0 - then return end - - self:StartReload() + if + self:GetReloading() + or self:Clip1() > self.Primary.ClipSize + or self:GetOwner():GetAmmoCount(self.Primary.Ammo) < 0 + then + return + end + + self:StartReload() end --- -- @ignore function SWEP:StartReload() - if self:GetReloading() then - return false - end + if self:GetReloading() then + return false + end - self:SetIronsights(false) - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetIronsights(false) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - local owner = self:GetOwner() + local owner = self:GetOwner() - if not owner or owner:GetAmmoCount(self.Primary.Ammo) <= 0 then - return false - end + if not owner or owner:GetAmmoCount(self.Primary.Ammo) <= 0 then + return false + end - if self:Clip1() >= self.Primary.ClipSize then - return false - end + if self:Clip1() >= self.Primary.ClipSize then + return false + end - self:SendWeaponAnim(ACT_SHOTGUN_RELOAD_START) - self:SetReloadTimer(CurTime() + self:SequenceDuration()) - self:SetReloading(true) + self:SendWeaponAnim(ACT_SHOTGUN_RELOAD_START) + self:SetReloadTimer(CurTime() + self:SequenceDuration()) + self:SetReloading(true) - return true + return true end --- -- @ignore function SWEP:PerformReload() - local owner = self:GetOwner() + local owner = self:GetOwner() - -- prevent normal shooting in between reloads - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + -- prevent normal shooting in between reloads + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - if not owner or owner:GetAmmoCount(self.Primary.Ammo) <= 0 - or self:Clip1() >= self.Primary.ClipSize - then return end + if + not owner + or owner:GetAmmoCount(self.Primary.Ammo) <= 0 + or self:Clip1() >= self.Primary.ClipSize + then + return + end - owner:RemoveAmmo(1, self.Primary.Ammo, false) + owner:RemoveAmmo(1, self.Primary.Ammo, false) - self:SetClip1(self:Clip1() + 1) - self:SendWeaponAnim(ACT_VM_RELOAD) - self:SetReloadTimer(CurTime() + self:SequenceDuration()) + self:SetClip1(self:Clip1() + 1) + self:SendWeaponAnim(ACT_VM_RELOAD) + self:SetReloadTimer(CurTime() + self:SequenceDuration()) end --- -- @ignore function SWEP:FinishReload() - self:SetReloading(false) - self:SendWeaponAnim(ACT_SHOTGUN_RELOAD_FINISH) + self:SetReloading(false) + self:SendWeaponAnim(ACT_SHOTGUN_RELOAD_FINISH) - self:SetReloadTimer(CurTime() + self:SequenceDuration()) + self:SetReloadTimer(CurTime() + self:SequenceDuration()) end --- -- @ignore function SWEP:CanPrimaryAttack() - if self:Clip1() <= 0 then - self:EmitSound("Weapon_Shotgun.Empty") - self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + if self:Clip1() <= 0 then + self:EmitSound("Weapon_Shotgun.Empty") + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - return false - end + return false + end - return true + return true end --- -- @ignore function SWEP:Think() - self.BaseClass.Think(self) - - if not self:GetReloading() then return end - - local owner = self:GetOwner() - - if owner:KeyDown(IN_ATTACK) then - self:FinishReload() - elseif self:GetReloadTimer() <= CurTime() then - if owner:GetAmmoCount(self.Primary.Ammo) <= 0 then - self:FinishReload() - elseif self:Clip1() < self.Primary.ClipSize then - self:PerformReload() - else - self:FinishReload() - end - end + BaseClass.Think(self) + + if not self:GetReloading() then + return + end + + local owner = self:GetOwner() + + if owner:KeyDown(IN_ATTACK) then + self:FinishReload() + elseif self:GetReloadTimer() <= CurTime() then + if owner:GetAmmoCount(self.Primary.Ammo) <= 0 then + self:FinishReload() + elseif self:Clip1() < self.Primary.ClipSize then + self:PerformReload() + else + self:FinishReload() + end + end end --- -- @ignore function SWEP:Deploy() - self:SetReloading(false) - self:SetReloadTimer(0) + self:SetReloading(false) + self:SetReloadTimer(0) - return self.BaseClass.Deploy(self) + return BaseClass.Deploy(self) end --- @@ -169,26 +183,28 @@ end -- lucky headshots relatively easily due to the spread. -- @ignore function SWEP:GetHeadshotMultiplier(victim, dmginfo) - local att = dmginfo:GetAttacker() + local att = dmginfo:GetAttacker() - if not IsValid(att) then - return 3 - end + if not IsValid(att) then + return 3 + end - local dist = victim:GetPos():Distance(att:GetPos()) - local d = math.max(0, dist - 140) + local dist = victim:GetPos():Distance(att:GetPos()) + local d = math.max(0, dist - 140) - -- Decay from 2 to 1 slowly as distance increases. Note that this used to be - -- 3+, but at that time shotgun bullets were treated like in HL2 where half - -- of them were hull traces that could not headshot. - return 1 + math.max(0, 1.0 - 0.002 * (d ^ 1.25)) + -- Decay from 2 to 1 slowly as distance increases. Note that this used to be + -- 3+, but at that time shotgun bullets were treated like in HL2 where half + -- of them were hull traces that could not headshot. + return 1 + math.max(0, 1.0 - 0.002 * (d ^ 1.25)) end --- -- @ignore function SWEP:SecondaryAttack() - if self.NoSights or (not self.IronSightsPos) or self:GetReloading() then return end + if self.NoSights or not self.IronSightsPos or self:GetReloading() then + return + end - self:SetIronsights(not self:GetIronsights()) - self:SetNextSecondaryFire(CurTime() + 0.3) + self:SetIronsights(not self:GetIronsights()) + self:SetNextSecondaryFire(CurTime() + 0.3) end diff --git a/gamemodes/terrortown/entities/weapons/weapon_zm_sledge.lua b/gamemodes/terrortown/entities/weapons/weapon_zm_sledge.lua index b333316c2..1cd293308 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_zm_sledge.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_zm_sledge.lua @@ -1,18 +1,18 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end SWEP.HoldType = "crossbow" if CLIENT then - SWEP.PrintName = "H.U.G.E-249" - SWEP.Slot = 2 + SWEP.PrintName = "H.U.G.E-249" + SWEP.Slot = 2 - SWEP.ViewModelFlip = false - SWEP.ViewModelFOV = 54 + SWEP.ViewModelFlip = false + SWEP.ViewModelFOV = 54 - SWEP.Icon = "vgui/ttt/icon_m249" - SWEP.IconLetter = "z" + SWEP.Icon = "vgui/ttt/icon_m249" + SWEP.IconLetter = "z" end SWEP.Base = "weapon_tttbase" @@ -22,6 +22,7 @@ SWEP.AutoSpawnable = true SWEP.Kind = WEAPON_HEAVY SWEP.WeaponID = AMMO_M249 +SWEP.builtin = true SWEP.spawnType = WEAPON_TYPE_HEAVY SWEP.Primary.Damage = 7 @@ -38,6 +39,7 @@ SWEP.Primary.Sound = Sound("Weapon_m249.Single") SWEP.UseHands = true SWEP.ViewModel = "models/weapons/cstrike/c_mach_m249para.mdl" SWEP.WorldModel = "models/weapons/w_mach_m249para.mdl" +SWEP.idleResetFix = true SWEP.HeadshotMultiplier = 2.2 diff --git a/gamemodes/terrortown/gamemode/client/cl_armor.lua b/gamemodes/terrortown/gamemode/client/cl_armor.lua index 37bdeded3..0a1ee4074 100644 --- a/gamemodes/terrortown/gamemode/client/cl_armor.lua +++ b/gamemodes/terrortown/gamemode/client/cl_armor.lua @@ -8,62 +8,71 @@ ARMOR = {} -- init icons -- @realm client function ARMOR:Initialize() - STATUS:RegisterStatus("ttt_armor_status", { - hud = { - Material("vgui/ttt/perks/hud_armor.png"), - Material("vgui/ttt/perks/hud_armor_reinforced.png") - }, - type = "good" - }) + STATUS:RegisterStatus("ttt_armor_status", { + hud = { + Material("vgui/ttt/perks/hud_armor.png"), + Material("vgui/ttt/perks/hud_armor_reinforced.png"), + }, + type = "good", + name = { + "item_armor", + "item_armor_reinforced", + }, + sidebarDescription = "item_armor_sidebar", + }) end -- switch between icons local function HandleArmorStatusIcons(ply) - -- removed armor - if ply.armor <= 0 then - if STATUS:Active("ttt_armor_status") then - STATUS:RemoveStatus("ttt_armor_status") - end + -- removed armor + if ply.armor <= 0 then + if STATUS:Active("ttt_armor_status") then + STATUS:RemoveStatus("ttt_armor_status") + end - return - end + return + end - -- check if reinforced - local icon_id = 1 + -- check if reinforced + local icon_id = 1 - if GetGlobalBool("ttt_armor_dynamic", false) then - icon_id = ply:ArmorIsReinforced() and 2 or 1 - end + if GetGlobalBool("ttt_armor_dynamic", false) then + icon_id = ply:ArmorIsReinforced() and 2 or 1 + end - -- normal armor level change (update) - if STATUS:Active("ttt_armor_status") then - STATUS:SetActiveIcon("ttt_armor_status", icon_id) + -- normal armor level change (update) + if STATUS:Active("ttt_armor_status") then + STATUS:SetActiveIcon("ttt_armor_status", icon_id) - return - end + return + end - -- added armor if not active - STATUS:AddStatus("ttt_armor_status", icon_id) + -- added armor if not active + STATUS:AddStatus("ttt_armor_status", icon_id) end -- SERVER -> CLIENT ARMOR SYNCING net.Receive("ttt2_sync_armor", function() - local client = LocalPlayer() + local client = LocalPlayer() - -- prevent error from netmessage prior to the client beeing ready - if not IsValid(client) then return end + -- prevent error from netmessage prior to the client beeing ready + if not IsValid(client) then + return + end - client.armor = net.ReadUInt(16) + client.armor = net.ReadUInt(16) - -- UPDATE STATUS ICONS - HandleArmorStatusIcons(client) + -- UPDATE STATUS ICONS + HandleArmorStatusIcons(client) end) net.Receive("ttt2_sync_armor_max", function() - local client = LocalPlayer() + local client = LocalPlayer() - -- prevent error from netmessage prior to the client beeing ready - if not IsValid(client) then return end + -- prevent error from netmessage prior to the client beeing ready + if not IsValid(client) then + return + end - client.armor_max = net.ReadUInt(16) + client.armor_max = net.ReadUInt(16) end) diff --git a/gamemodes/terrortown/gamemode/client/cl_awards.lua b/gamemodes/terrortown/gamemode/client/cl_awards.lua index cf7359bc3..bd9703cec 100644 --- a/gamemodes/terrortown/gamemode/client/cl_awards.lua +++ b/gamemodes/terrortown/gamemode/client/cl_awards.lua @@ -19,24 +19,24 @@ local PT = LANG.GetParamTranslation AWARDS = {} local function is_dmg(dmg_t, bit) - -- deal with large-number workaround for TableToJSON by - -- parsing back to number here - return util.BitSet(tonumber(dmg_t), bit) + -- deal with large-number workaround for TableToJSON by + -- parsing back to number here + return util.BitSet(tonumber(dmg_t), bit) end -- a common pattern local function FindHighest(tbl) - local m_num = 0 - local m_id + local m_num = 0 + local m_id - for id, num in pairs(tbl) do - if num > m_num then - m_id = id - m_num = num - end - end + for id, num in pairs(tbl) do + if num > m_num then + m_id = id + m_num = num + end + end - return m_id, m_num + return m_id, m_num end --- @@ -46,37 +46,39 @@ end -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.FirstSuicide(events, scores, players, traitors) - local fs - local fnum = 0 - - for _, e in pairs(events) do - if e.id == EVENT_KILL and e.att.sid64 == e.vic.sid64 then - fnum = fnum + 1 - - if not fs then - fs = e - end - end - end - - if fs then - local award = {nick = fs.att.ni} - - if not award.nick then return end - - if fnum > 1 then - award.title = T("aw_sui1_title") - award.text = T("aw_sui1_text") - else - award.title = T("aw_sui2_title") - award.text = T("aw_sui2_text") - end - - -- only high interest if many people died this way - award.priority = fnum - - return award - end + local fs + local fnum = 0 + + for _, e in pairs(events) do + if e.id == EVENT_KILL and e.att.sid64 == e.vic.sid64 then + fnum = fnum + 1 + + if not fs then + fs = e + end + end + end + + if fs then + local award = { nick = fs.att.ni } + + if not award.nick then + return + end + + if fnum > 1 then + award.title = T("aw_sui1_title") + award.text = T("aw_sui1_text") + else + award.title = T("aw_sui2_title") + award.text = T("aw_sui2_text") + end + + -- only high interest if many people died this way + award.priority = fnum + + return award + end end FirstSuicide = AWARDS.FirstSuicide -- just for compatibility @@ -87,33 +89,35 @@ FirstSuicide = AWARDS.FirstSuicide -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.ExplosiveGrant(events, scores, players, traitors) - local bombers = {} + local bombers = {} - for _, e in pairs(events) do - if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_BLAST) then - bombers[e.att.sid64] = (bombers[e.att.sid64] or 0) + 1 - end - end + for _, e in pairs(events) do + if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_BLAST) then + bombers[e.att.sid64] = (bombers[e.att.sid64] or 0) + 1 + end + end - local award = {title = T("aw_exp1_title")} + local award = { title = T("aw_exp1_title") } - if not table.IsEmpty(bombers) then - for sid64, num in pairs(bombers) do - -- award goes to whoever reaches this first I guess - if num > 2 then - award.nick = players[sid64] + if not table.IsEmpty(bombers) then + for sid64, num in pairs(bombers) do + -- award goes to whoever reaches this first I guess + if num > 2 then + award.nick = players[sid64] - if not award.nick then return end -- if player disconnected or something + if not award.nick then + return + end -- if player disconnected or something - award.text = PT("aw_exp1_text", {num = num}) + award.text = PT("aw_exp1_text", { num = num }) - -- rare award, high interest - award.priority = 10 + num + -- rare award, high interest + award.priority = 10 + num - return award - end - end - end + return award + end + end + end end ExplosiveGrant = AWARDS.ExplosiveGrant -- just for compatibility @@ -124,16 +128,16 @@ ExplosiveGrant = AWARDS.ExplosiveGrant -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.ExplodedSelf(events, scores, players, traitors) - for _, e in pairs(events) do - if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_BLAST) and e.att.sid64 == e.vic.sid64 then - return { - title = T("aw_exp2_title"), - text = T("aw_exp2_text"), - nick = e.vic.ni, - priority = math.random(4) - } - end - end + for _, e in pairs(events) do + if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_BLAST) and e.att.sid64 == e.vic.sid64 then + return { + title = T("aw_exp2_title"), + text = T("aw_exp2_text"), + nick = e.vic.ni, + priority = math.random(4), + } + end + end end ExplodedSelf = AWARDS.ExplodedSelf -- just for compatibility @@ -144,39 +148,41 @@ ExplodedSelf = AWARDS.ExplodedSelf -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.FirstBlood(events, scores, players, traitors) - for _, e in pairs(events) do - if e.id == EVENT_KILL and e.att.sid64 ~= e.vic.sid64 and e.att.sid64 ~= -1 then - local award = {nick = e.att.ni} - - if not award.nick or award.nick == "" then return end - - local vtr = e.vic.t - local atr = e.att.t - - if atr == TEAM_TRAITOR then - if atr ~= vtr then -- traitor legit k - award.title = T("aw_fst1_title") - award.text = T("aw_fst1_text") - else -- traitor tk - award.title = T("aw_fst2_title") - award.text = T("aw_fst2_text") - end - elseif atr then - if atr == vtr then -- inno tk - award.title = T("aw_fst3_title") - award.text = T("aw_fst3_text") - else -- inno legit k - award.title = T("aw_fst4_title") - award.text = T("aw_fst4_text") - end - end - - -- more interesting if there were many players and therefore many kills - award.priority = math.random(-3, math.Round(table.Count(players) * 0.25)) - - return award - end - end + for _, e in pairs(events) do + if e.id == EVENT_KILL and e.att.sid64 ~= e.vic.sid64 and e.att.sid64 ~= -1 then + local award = { nick = e.att.ni } + + if not award.nick or award.nick == "" then + return + end + + local vtr = e.vic.t + local atr = e.att.t + + if atr == TEAM_TRAITOR then + if atr ~= vtr then -- traitor legit k + award.title = T("aw_fst1_title") + award.text = T("aw_fst1_text") + else -- traitor tk + award.title = T("aw_fst2_title") + award.text = T("aw_fst2_text") + end + elseif atr then + if atr == vtr then -- inno tk + award.title = T("aw_fst3_title") + award.text = T("aw_fst3_text") + else -- inno legit k + award.title = T("aw_fst4_title") + award.text = T("aw_fst4_text") + end + end + + -- more interesting if there were many players and therefore many kills + award.priority = math.random(-3, math.Round(table.Count(players) * 0.25)) + + return award + end + end end FirstBlood = AWARDS.FirstBlood -- just for compatibility @@ -188,55 +194,55 @@ FirstBlood = AWARDS.FirstBlood -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.AllKills(events, scores, players, traitors) - local killedTraitors, killedNoTraitors - - for id, s in pairs(scores) do - for i = 1, #s.ev do - if s.ev[i].v == TEAM_TRAITOR then - if not killedTraitors then - killedTraitors = id - elseif killedTraitors ~= id then - killedTraitors = nil - - break - end - else - if not killedNoTraitors then - killedNoTraitors = id - elseif killedNoTraitors ~= id then - killedNoTraitors = nil - - break - end - end - end - end - - if killedTraitors and not table.HasValue(traitors, killedTraitors) then - local killer = players[killedTraitors] - - if killer then - return { - nick = killer, - title = T("aw_all1_title"), - text = T("aw_all1_text"), - priority = math.random(table.Count(players)) - } - end - end - - if killedNoTraitors and table.HasValue(traitors, killedNoTraitors) then - local killer = players[killedNoTraitors] - - if killer then - return { - nick = killer, - title = T("aw_all2_title"), - text = T("aw_all2_text"), - priority = math.random(table.Count(players)) - } - end - end + local killedTraitors, killedNoTraitors + + for id, s in pairs(scores) do + for i = 1, #s.ev do + if s.ev[i].v == TEAM_TRAITOR then + if not killedTraitors then + killedTraitors = id + elseif killedTraitors ~= id then + killedTraitors = nil + + break + end + else + if not killedNoTraitors then + killedNoTraitors = id + elseif killedNoTraitors ~= id then + killedNoTraitors = nil + + break + end + end + end + end + + if killedTraitors and not table.HasValue(traitors, killedTraitors) then + local killer = players[killedTraitors] + + if killer then + return { + nick = killer, + title = T("aw_all1_title"), + text = T("aw_all1_text"), + priority = math.random(table.Count(players)), + } + end + end + + if killedNoTraitors and table.HasValue(traitors, killedNoTraitors) then + local killer = players[killedNoTraitors] + + if killer then + return { + nick = killer, + title = T("aw_all2_title"), + text = T("aw_all2_text"), + priority = math.random(table.Count(players)), + } + end + end end AllKills = AWARDS.AllKills -- just for compatibility @@ -247,25 +253,25 @@ AllKills = AWARDS.AllKills -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.FallDeath(events, scores, players, traitors) - for _, e in pairs(events) do - if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_FALL) then - if e.att.ni ~= "" then - return { - title = T("aw_fal1_title"), - nick = e.att.ni, - text = T("aw_fal1_text"), - priority = math.random(7, 15) - } - else - return { - title = T("aw_fal2_title"), - nick = e.vic.ni, - text = T("aw_fal2_text"), - priority = math.random(5) - } - end - end - end + for _, e in pairs(events) do + if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_FALL) then + if e.att.ni ~= "" then + return { + title = T("aw_fal1_title"), + nick = e.att.ni, + text = T("aw_fal1_text"), + priority = math.random(7, 15), + } + else + return { + title = T("aw_fal2_title"), + nick = e.vic.ni, + text = T("aw_fal2_text"), + priority = math.random(5), + } + end + end + end end FallDeath = AWARDS.FallDeath -- just for compatibility @@ -276,16 +282,21 @@ FallDeath = AWARDS.FallDeath -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.FallKill(events, scores, players, traitors) - for _, e in pairs(events) do - if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_CRUSH) and is_dmg(e.dmg.t, DMG_PHYSGUN) and e.att.ni ~= "" then - return { - title = T("aw_fal3_title"), - nick = e.att.ni, - text = T("aw_fal3_text"), - priority = math.random(10, 15) - } - end - end + for _, e in pairs(events) do + if + e.id == EVENT_KILL + and is_dmg(e.dmg.t, DMG_CRUSH) + and is_dmg(e.dmg.t, DMG_PHYSGUN) + and e.att.ni ~= "" + then + return { + title = T("aw_fal3_title"), + nick = e.att.ni, + text = T("aw_fal3_text"), + priority = math.random(10, 15), + } + end + end end FallKill = AWARDS.FallKill -- just for compatibility @@ -296,43 +307,49 @@ FallKill = AWARDS.FallKill -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.Headshots(events, scores, players, traitors) - local hs = {} - - for _, e in pairs(events) do - if e.id == EVENT_KILL and e.dmg.h and is_dmg(e.dmg.t, DMG_BULLET) then - hs[e.att.sid64] = (hs[e.att.sid64] or 0) + 1 - end - end - - if table.IsEmpty(hs) then return end - - -- find the one with the most shots - local m_id, m_num = FindHighest(hs) - if not m_id then return end - - local nick = players[m_id] - if not nick then return end - - local award = { - nick = nick, - priority = m_num * 0.5 - } - - if m_num > 1 and m_num < 4 then - award.title = T("aw_hed1_title") - award.text = PT("aw_hed1_text", {num = m_num}) - elseif m_num >= 4 and m_num < 6 then - award.title = T("aw_hed2_title") - award.text = PT("aw_hed2_text", {num = m_num}) - elseif m_num >= 6 then - award.title = T("aw_hed3_title") - award.text = PT("aw_hed3_text", {num = m_num}) - award.priority = m_num + 5 - else - return - end - - return award + local hs = {} + + for _, e in pairs(events) do + if e.id == EVENT_KILL and e.dmg.h and is_dmg(e.dmg.t, DMG_BULLET) then + hs[e.att.sid64] = (hs[e.att.sid64] or 0) + 1 + end + end + + if table.IsEmpty(hs) then + return + end + + -- find the one with the most shots + local m_id, m_num = FindHighest(hs) + if not m_id then + return + end + + local nick = players[m_id] + if not nick then + return + end + + local award = { + nick = nick, + priority = m_num * 0.5, + } + + if m_num > 1 and m_num < 4 then + award.title = T("aw_hed1_title") + award.text = PT("aw_hed1_text", { num = m_num }) + elseif m_num >= 4 and m_num < 6 then + award.title = T("aw_hed2_title") + award.text = PT("aw_hed2_text", { num = m_num }) + elseif m_num >= 6 then + award.title = T("aw_hed3_title") + award.text = PT("aw_hed3_text", { num = m_num }) + award.priority = m_num + 5 + else + return + end + + return award end Headshots = AWARDS.Headshots -- just for compatibility @@ -342,24 +359,28 @@ Headshots = AWARDS.Headshots -- just for compatibility -- @return table -- @realm client function AWARDS.UsedAmmoMost(events, ammotype) - local user = {} + local user = {} - for _, e in pairs(events) do - if e.id == EVENT_KILL and e.dmg.g == ammotype then - user[e.att.sid64] = (user[e.att.sid64] or 0) + 1 - end - end + for _, e in pairs(events) do + if e.id == EVENT_KILL and e.dmg.g == ammotype then + user[e.att.sid64] = (user[e.att.sid64] or 0) + 1 + end + end - if table.IsEmpty(user) then return end + if table.IsEmpty(user) then + return + end - local m_id, m_num = FindHighest(user) + local m_id, m_num = FindHighest(user) - if not m_id then return end + if not m_id then + return + end - return { - sid64 = m_id, - kills = m_num - } + return { + sid64 = m_id, + kills = m_num, + } end UsedAmmoMost = AWARDS.UsedAmmoMost -- just for compatibility @@ -370,33 +391,37 @@ UsedAmmoMost = AWARDS.UsedAmmoMost -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.CrowbarUser(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_CROWBAR) + local most = UsedAmmoMost(events, AMMO_CROWBAR) - if not most then return end + if not most then + return + end - local nick = players[most.sid64] + local nick = players[most.sid64] - if not nick then return end + if not nick then + return + end - local award = { - nick = nick, - priority = most.kills + math.random(0, 4) - } + local award = { + nick = nick, + priority = most.kills + math.random(0, 4), + } - local kills = most.kills + local kills = most.kills - if kills > 1 and kills < 3 then - award.title = T("aw_cbr1_title") - award.text = PT("aw_cbr1_text", {num = kills}) - elseif kills >= 3 then - award.title = T("aw_cbr2_title") - award.text = PT("aw_cbr2_text", {num = kills}) - award.priority = kills + math.random(5, 10) - else - return - end + if kills > 1 and kills < 3 then + award.title = T("aw_cbr1_title") + award.text = PT("aw_cbr1_text", { num = kills }) + elseif kills >= 3 then + award.title = T("aw_cbr2_title") + award.text = PT("aw_cbr2_text", { num = kills }) + award.priority = kills + math.random(5, 10) + else + return + end - return award + return award end CrowbarUser = AWARDS.CrowbarUser -- just for compatibility @@ -407,33 +432,37 @@ CrowbarUser = AWARDS.CrowbarUser -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.PistolUser(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_PISTOL) + local most = UsedAmmoMost(events, AMMO_PISTOL) - if not most then return end + if not most then + return + end - local nick = players[most.sid64] + local nick = players[most.sid64] - if not nick then return end + if not nick then + return + end - local award = { - nick = nick, - priority = most.kills - } + local award = { + nick = nick, + priority = most.kills, + } - local kills = most.kills + local kills = most.kills - if kills > 1 and kills < 4 then - award.title = T("aw_pst1_title") - award.text = PT("aw_pst1_text", {num = kills}) - elseif kills >= 4 then - award.title = T("aw_pst2_title") - award.text = PT("aw_pst2_text", {num = kills}) - award.priority = award.priority + math.random(0, 5) - else - return - end + if kills > 1 and kills < 4 then + award.title = T("aw_pst1_title") + award.text = PT("aw_pst1_text", { num = kills }) + elseif kills >= 4 then + award.title = T("aw_pst2_title") + award.text = PT("aw_pst2_text", { num = kills }) + award.priority = award.priority + math.random(0, 5) + else + return + end - return award + return award end PistolUser = AWARDS.PistolUser -- just for compatibility @@ -444,33 +473,37 @@ PistolUser = AWARDS.PistolUser -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.ShotgunUser(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_SHOTGUN) + local most = UsedAmmoMost(events, AMMO_SHOTGUN) - if not most then return end + if not most then + return + end - local nick = players[most.sid64] + local nick = players[most.sid64] - if not nick then return end + if not nick then + return + end - local award = { - nick = nick, - priority = most.kills - } + local award = { + nick = nick, + priority = most.kills, + } - local kills = most.kills + local kills = most.kills - if kills > 1 and kills < 4 then - award.title = T("aw_sgn1_title") - award.text = PT("aw_sgn1_text", {num = kills}) - award.priority = math.Round(kills * 0.5) - elseif kills >= 4 then - award.title = T("aw_sgn2_title") - award.text = PT("aw_sgn2_text", {num = kills}) - else - return - end + if kills > 1 and kills < 4 then + award.title = T("aw_sgn1_title") + award.text = PT("aw_sgn1_text", { num = kills }) + award.priority = math.Round(kills * 0.5) + elseif kills >= 4 then + award.title = T("aw_sgn2_title") + award.text = PT("aw_sgn2_text", { num = kills }) + else + return + end - return award + return award end ShotgunUser = AWARDS.ShotgunUser -- just for compatibility @@ -481,33 +514,37 @@ ShotgunUser = AWARDS.ShotgunUser -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.RifleUser(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_RIFLE) + local most = UsedAmmoMost(events, AMMO_RIFLE) - if not most then return end + if not most then + return + end - local nick = players[most.sid64] + local nick = players[most.sid64] - if not nick then return end + if not nick then + return + end - local award = { - nick = nick, - priority = most.kills - } + local award = { + nick = nick, + priority = most.kills, + } - local kills = most.kills + local kills = most.kills - if kills > 1 and kills < 4 then - award.title = T("aw_rfl1_title") - award.text = PT("aw_rfl1_text", {num = kills}) - award.priority = math.Round(kills * 0.5) - elseif kills >= 4 then - award.title = T("aw_rfl2_title") - award.text = PT("aw_rfl2_text", {num = kills}) - else - return - end + if kills > 1 and kills < 4 then + award.title = T("aw_rfl1_title") + award.text = PT("aw_rfl1_text", { num = kills }) + award.priority = math.Round(kills * 0.5) + elseif kills >= 4 then + award.title = T("aw_rfl2_title") + award.text = PT("aw_rfl2_text", { num = kills }) + else + return + end - return award + return award end RifleUser = AWARDS.RifleUser -- just for compatibility @@ -518,34 +555,38 @@ RifleUser = AWARDS.RifleUser -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.RDeagleUser(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_DEAGLE) - - if not most then return end - - local nick = players[most.sid64] - - if not nick then return end - - local award = { - nick = nick, - priority = most.kills - } - - local kills = most.kills - - if kills > 1 and kills < 4 then - award.title = T("aw_dgl1_title") - award.text = PT("aw_dgl1_text", {num = kills}) - award.priority = math.Round(kills * 0.5) - elseif kills >= 4 then - award.title = T("aw_dgl2_title") - award.text = PT("aw_dgl2_text", {num = kills}) - award.priority = kills + math.random(2, 6) - else - return - end - - return award + local most = UsedAmmoMost(events, AMMO_DEAGLE) + + if not most then + return + end + + local nick = players[most.sid64] + + if not nick then + return + end + + local award = { + nick = nick, + priority = most.kills, + } + + local kills = most.kills + + if kills > 1 and kills < 4 then + award.title = T("aw_dgl1_title") + award.text = PT("aw_dgl1_text", { num = kills }) + award.priority = math.Round(kills * 0.5) + elseif kills >= 4 then + award.title = T("aw_dgl2_title") + award.text = PT("aw_dgl2_text", { num = kills }) + award.priority = kills + math.random(2, 6) + else + return + end + + return award end DeagleUser = AWARDS.DeagleUser -- just for compatibility @@ -556,33 +597,37 @@ DeagleUser = AWARDS.DeagleUser -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.MAC10User(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_MAC10) + local most = UsedAmmoMost(events, AMMO_MAC10) - if not most then return end + if not most then + return + end - local nick = players[most.sid64] + local nick = players[most.sid64] - if not nick then return end + if not nick then + return + end - local award = { - nick = nick, - priority = most.kills - } + local award = { + nick = nick, + priority = most.kills, + } - local kills = most.kills + local kills = most.kills - if kills > 1 and kills < 4 then - award.title = T("aw_mac1_title") - award.text = PT("aw_mac1_text", {num = kills}) - award.priority = math.Round(kills * 0.5) - elseif kills >= 4 then - award.title = T("aw_mac2_title") - award.text = PT("aw_mac2_text", {num = kills}) - else - return - end + if kills > 1 and kills < 4 then + award.title = T("aw_mac1_title") + award.text = PT("aw_mac1_text", { num = kills }) + award.priority = math.Round(kills * 0.5) + elseif kills >= 4 then + award.title = T("aw_mac2_title") + award.text = PT("aw_mac2_text", { num = kills }) + else + return + end - return award + return award end MAC10User = AWARDS.MAC10User -- just for compatibility @@ -593,32 +638,36 @@ MAC10User = AWARDS.MAC10User -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.SilencedPistolUser(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_SIPISTOL) + local most = UsedAmmoMost(events, AMMO_SIPISTOL) - if not most then return end + if not most then + return + end - local nick = players[most.sid64] + local nick = players[most.sid64] - if not nick then return end + if not nick then + return + end - local award = { - nick = nick, - priority = most.kills - } + local award = { + nick = nick, + priority = most.kills, + } - local kills = most.kills + local kills = most.kills - if kills > 1 and kills < 3 then - award.title = T("aw_sip1_title") - award.text = PT("aw_sip1_text", {num = kills}) - elseif kills >= 3 then - award.title = T("aw_sip2_title") - award.text = PT("aw_sip2_text", {num = kills}) - else - return - end + if kills > 1 and kills < 3 then + award.title = T("aw_sip1_title") + award.text = PT("aw_sip1_text", { num = kills }) + elseif kills >= 3 then + award.title = T("aw_sip2_title") + award.text = PT("aw_sip2_text", { num = kills }) + else + return + end - return award + return award end SilencedPistolUser = AWARDS.SilencedPistolUser -- just for compatibility @@ -629,42 +678,46 @@ SilencedPistolUser = AWARDS.SilencedPistolUser -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.KnifeUser(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_KNIFE) - - if not most then return end - - local nick = players[most.sid64] - - if not nick then return end - - local award = { - nick = nick, - priority = most.kills - } - - local kills = most.kills - - if kills == 1 then - if table.HasValue(traitors, most.sid64) then - award.title = T("aw_knf1_title") - award.text = PT("aw_knf1_text", {num = kills}) - award.priority = 0 - else - award.title = T("aw_knf2_title") - award.text = PT("aw_knf2_text", {num = kills}) - award.priority = 5 - end - elseif kills > 1 and kills < 4 then - award.title = T("aw_knf3_title") - award.text = PT("aw_knf3_text", {num = kills}) - elseif kills >= 4 then - award.title = T("aw_knf4_title") - award.text = PT("aw_knf4_text", {num = kills}) - else - return - end - - return award + local most = UsedAmmoMost(events, AMMO_KNIFE) + + if not most then + return + end + + local nick = players[most.sid64] + + if not nick then + return + end + + local award = { + nick = nick, + priority = most.kills, + } + + local kills = most.kills + + if kills == 1 then + if table.HasValue(traitors, most.sid64) then + award.title = T("aw_knf1_title") + award.text = PT("aw_knf1_text", { num = kills }) + award.priority = 0 + else + award.title = T("aw_knf2_title") + award.text = PT("aw_knf2_text", { num = kills }) + award.priority = 5 + end + elseif kills > 1 and kills < 4 then + award.title = T("aw_knf3_title") + award.text = PT("aw_knf3_text", { num = kills }) + elseif kills >= 4 then + award.title = T("aw_knf4_title") + award.text = PT("aw_knf4_text", { num = kills }) + else + return + end + + return award end KnifeUser = AWARDS.KnifeUser -- just for compatibility @@ -675,32 +728,36 @@ KnifeUser = AWARDS.KnifeUser -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.FlareUser(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_FLARE) + local most = UsedAmmoMost(events, AMMO_FLARE) - if not most then return end + if not most then + return + end - local nick = players[most.sid64] + local nick = players[most.sid64] - if not nick then return end + if not nick then + return + end - local award = { - nick = nick, - priority = most.kills - } + local award = { + nick = nick, + priority = most.kills, + } - local kills = most.kills + local kills = most.kills - if kills > 1 and kills < 3 then - award.title = T("aw_flg1_title") - award.text = PT("aw_flg1_text", {num = kills}) - elseif kills >= 3 then - award.title = T("aw_flg2_title") - award.text = PT("aw_flg2_text", {num = kills}) - else - return - end + if kills > 1 and kills < 3 then + award.title = T("aw_flg1_title") + award.text = PT("aw_flg1_text", { num = kills }) + elseif kills >= 3 then + award.title = T("aw_flg2_title") + award.text = PT("aw_flg2_text", { num = kills }) + else + return + end - return award + return award end FlareUser = AWARDS.FlareUser -- just for compatibility @@ -711,32 +768,36 @@ FlareUser = AWARDS.FlareUser -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.M249User(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_M249) + local most = UsedAmmoMost(events, AMMO_M249) - if not most then return end + if not most then + return + end - local nick = players[most.sid64] + local nick = players[most.sid64] - if not nick then return end + if not nick then + return + end - local award = { - nick = nick, - priority = most.kills - } + local award = { + nick = nick, + priority = most.kills, + } - local kills = most.kills + local kills = most.kills - if kills > 1 and kills < 4 then - award.title = T("aw_hug1_title") - award.text = PT("aw_hug1_text", {num = kills}) - elseif kills >= 4 then - award.title = T("aw_hug2_title") - award.text = PT("aw_hug2_text", {num = kills}) - else - return - end + if kills > 1 and kills < 4 then + award.title = T("aw_hug1_title") + award.text = PT("aw_hug1_text", { num = kills }) + elseif kills >= 4 then + award.title = T("aw_hug2_title") + award.text = PT("aw_hug2_text", { num = kills }) + else + return + end - return award + return award end M249User = AWARDS.M249User -- just for compatibility @@ -747,32 +808,36 @@ M249User = AWARDS.M249User -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.M16User(events, scores, players, traitors) - local most = UsedAmmoMost(events, AMMO_M16) + local most = UsedAmmoMost(events, AMMO_M16) - if not most then return end + if not most then + return + end - local nick = players[most.sid64] + local nick = players[most.sid64] - if not nick then return end + if not nick then + return + end - local award = { - nick = nick, - priority = most.kills - } + local award = { + nick = nick, + priority = most.kills, + } - local kills = most.kills + local kills = most.kills - if kills > 1 and kills < 4 then - award.title = T("aw_msx1_title") - award.text = PT("aw_msx1_text", {num = kills}) - elseif kills >= 4 then - award.title = T("aw_msx2_title") - award.text = PT("aw_msx2_text", {num = kills}) - else - return - end + if kills > 1 and kills < 4 then + award.title = T("aw_msx1_title") + award.text = PT("aw_msx1_text", { num = kills }) + elseif kills >= 4 then + award.title = T("aw_msx2_title") + award.text = PT("aw_msx2_text", { num = kills }) + else + return + end - return award + return award end M16User = AWARDS.M16User -- just for compatibility @@ -783,80 +848,84 @@ M16User = AWARDS.M16User -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.TeamKiller(events, scores, players, traitors) - local tker - local tktbl = {} - local pct, tka = 0, 0 - - -- find biggest tker - for id, s in pairs(scores) do - for i = 1, #s.ev do - local ev = s.ev[i] - - tktbl[id] = tktbl[id] or {} - - if ev.t == TEAM_NONE or ev.t ~= ev.v or TEAMS[ev.t].alone then - tktbl[id].k = (tktbl[id].k or 0) + 1 - else - tktbl[id].tk = (tktbl[id].tk or 0) + 1 - end - end - end - - for tk, tbl in pairs(tktbl) do - if tbl.tk and tbl.k then - local tmp = tbl.tk / tbl.k - - if tmp > pct then - tker = id - tka = tbl.tk - pct = tmp - end - end - end - - -- no tks - if pct == 0 or not tker then return end - - local nick = players[tker] - - if not nick then return end - - local was_traitor = table.HasValue(traitors, tker) - local award = { - nick = nick, - priority = tka - } - - if tka == 1 then - award.title = T("aw_tkl1_title") - award.text = T("aw_tkl1_text") - award.priority = 0 - elseif tka == 2 then - award.title = T("aw_tkl2_title") - award.text = T("aw_tkl2_text") - elseif tka == 3 then - award.title = T("aw_tkl3_title") - award.text = T("aw_tkl3_text") - elseif pct >= 1.0 then - award.title = T("aw_tkl4_title") - award.text = T("aw_tkl4_text") - award.priority = tka + math.random(3, 6) - elseif pct >= 0.75 and not was_traitor then - award.title = T("aw_tkl5_title") - award.text = T("aw_tkl5_text") - award.priority = tka + 10 - elseif pct > 0.5 then - award.title = T("aw_tkl6_title") - award.text = T("aw_tkl6_text") - award.priority = tka + math.random(2, 7) - elseif pct >= 0.25 then - award.title = T("aw_tkl7_title") - award.text = T("aw_tkl7_text") - else - return - end - - return award + local tker + local tktbl = {} + local pct, tka = 0, 0 + + -- find biggest tker + for id, s in pairs(scores) do + for i = 1, #s.ev do + local ev = s.ev[i] + + tktbl[id] = tktbl[id] or {} + + if ev.t == TEAM_NONE or ev.t ~= ev.v or TEAMS[ev.t].alone then + tktbl[id].k = (tktbl[id].k or 0) + 1 + else + tktbl[id].tk = (tktbl[id].tk or 0) + 1 + end + end + end + + for tk, tbl in pairs(tktbl) do + if tbl.tk and tbl.k then + local tmp = tbl.tk / tbl.k + + if tmp > pct then + tker = tk + tka = tbl.tk + pct = tmp + end + end + end + + -- no tks + if pct == 0 or not tker then + return + end + + local nick = players[tker] + + if not nick then + return + end + + local was_traitor = table.HasValue(traitors, tker) + local award = { + nick = nick, + priority = tka, + } + + if tka == 1 then + award.title = T("aw_tkl1_title") + award.text = T("aw_tkl1_text") + award.priority = 0 + elseif tka == 2 then + award.title = T("aw_tkl2_title") + award.text = T("aw_tkl2_text") + elseif tka == 3 then + award.title = T("aw_tkl3_title") + award.text = T("aw_tkl3_text") + elseif pct >= 1.0 then + award.title = T("aw_tkl4_title") + award.text = T("aw_tkl4_text") + award.priority = tka + math.random(3, 6) + elseif pct >= 0.75 and not was_traitor then + award.title = T("aw_tkl5_title") + award.text = T("aw_tkl5_text") + award.priority = tka + 10 + elseif pct > 0.5 then + award.title = T("aw_tkl6_title") + award.text = T("aw_tkl6_text") + award.priority = tka + math.random(2, 7) + elseif pct >= 0.25 then + award.title = T("aw_tkl7_title") + award.text = T("aw_tkl7_text") + else + return + end + + return award end TeamKiller = AWARDS.TeamKiller -- just for compatibility @@ -867,43 +936,49 @@ TeamKiller = AWARDS.TeamKiller -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.Burner(events, scores, players, traitors) - local brn = {} - - for _, e in pairs(events) do - if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_BURN) then - brn[e.att.sid64] = (brn[e.att.sid64] or 0) + 1 - end - end - - if table.IsEmpty(brn) then return end - - -- find the one with the most burnings - local m_id, m_num = FindHighest(brn) - if not m_id then return end - - local nick = players[m_id] - if not nick then return end - - local award = { - nick = nick, - priority = m_num * 2 - } - - if m_num > 1 and m_num < 4 then - award.title = T("aw_brn1_title") - award.text = T("aw_brn1_text") - elseif m_num >= 4 and m_num < 7 then - award.title = T("aw_brn2_title") - award.text = T("aw_brn2_text") - elseif m_num >= 7 then - award.title = T("aw_brn3_title") - award.text = T("aw_brn3_text") - award.priority = m_num + math.random(0, 4) - else - return - end - - return award + local brn = {} + + for _, e in pairs(events) do + if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_BURN) then + brn[e.att.sid64] = (brn[e.att.sid64] or 0) + 1 + end + end + + if table.IsEmpty(brn) then + return + end + + -- find the one with the most burnings + local m_id, m_num = FindHighest(brn) + if not m_id then + return + end + + local nick = players[m_id] + if not nick then + return + end + + local award = { + nick = nick, + priority = m_num * 2, + } + + if m_num > 1 and m_num < 4 then + award.title = T("aw_brn1_title") + award.text = T("aw_brn1_text") + elseif m_num >= 4 and m_num < 7 then + award.title = T("aw_brn2_title") + award.text = T("aw_brn2_text") + elseif m_num >= 7 then + award.title = T("aw_brn3_title") + award.text = T("aw_brn3_text") + award.priority = m_num + math.random(0, 4) + else + return + end + + return award end Burner = AWARDS.Burner -- just for compatibility @@ -914,42 +989,48 @@ Burner = AWARDS.Burner -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.Coroner(events, scores, players, traitors) - local finders = {} - - for _, e in pairs(events) do - if e.id == EVENT_BODYFOUND then - finders[e.sid64] = (finders[e.sid64] or 0) + 1 - end - end - - if table.IsEmpty(finders) then return end - - local m_id, m_num = FindHighest(finders) - if not m_id then return end - - local nick = players[m_id] - if not nick then return end - - local award = { - nick = nick, - priority = m_num - } - - if m_num > 2 and m_num < 6 then - award.title = T("aw_fnd1_title") - award.text = PT("aw_fnd1_text", {num = m_num}) - elseif m_num >= 6 and m_num < 10 then - award.title = T("aw_fnd2_title") - award.text = PT("aw_fnd2_text", {num = m_num}) - elseif m_num >= 10 then - award.title = T("aw_fnd3_title") - award.text = PT("aw_fnd3_text", {num = m_num}) - award.priority = m_num + math.random(0, 4) - else - return - end - - return award + local finders = {} + + for _, e in pairs(events) do + if e.id == EVENT_BODYFOUND then + finders[e.sid64] = (finders[e.sid64] or 0) + 1 + end + end + + if table.IsEmpty(finders) then + return + end + + local m_id, m_num = FindHighest(finders) + if not m_id then + return + end + + local nick = players[m_id] + if not nick then + return + end + + local award = { + nick = nick, + priority = m_num, + } + + if m_num > 2 and m_num < 6 then + award.title = T("aw_fnd1_title") + award.text = PT("aw_fnd1_text", { num = m_num }) + elseif m_num >= 6 and m_num < 10 then + award.title = T("aw_fnd2_title") + award.text = PT("aw_fnd2_text", { num = m_num }) + elseif m_num >= 10 then + award.title = T("aw_fnd3_title") + award.text = PT("aw_fnd3_text", { num = m_num }) + award.priority = m_num + math.random(0, 4) + else + return + end + + return award end Coroner = AWARDS.Coroner -- just for compatibility @@ -960,33 +1041,39 @@ Coroner = AWARDS.Coroner -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.CreditFound(events, scores, players, traitors) - local finders = {} - - for _, e in pairs(events) do - if e.id == EVENT_CREDITFOUND then - finders[e.sid64] = (finders[e.sid64] or 0) + e.cr - end - end - - if table.IsEmpty(finders) then return end - - local m_id, m_num = FindHighest(finders) - if not m_id then return end - - local nick = players[m_id] - if not nick then return end - - local award = {nick = nick} - - if m_num > 2 then - award.title = T("aw_crd1_title") - award.text = PT("aw_crd1_text", {num = m_num}) - award.priority = m_num + math.random(0, m_num) - else - return - end - - return award + local finders = {} + + for _, e in pairs(events) do + if e.id == EVENT_CREDITFOUND then + finders[e.sid64] = (finders[e.sid64] or 0) + e.cr + end + end + + if table.IsEmpty(finders) then + return + end + + local m_id, m_num = FindHighest(finders) + if not m_id then + return + end + + local nick = players[m_id] + if not nick then + return + end + + local award = { nick = nick } + + if m_num > 2 then + award.title = T("aw_crd1_title") + award.text = PT("aw_crd1_text", { num = m_num }) + award.priority = m_num + math.random(0, m_num) + else + return + end + + return award end CreditFound = AWARDS.CreditFound -- just for compatibility @@ -997,34 +1084,38 @@ CreditFound = AWARDS.CreditFound -- just for compatibility -- @param table traitors list of @{Player}s with key = steamid64 and value = nickname of the @{Player} -- @realm client function AWARDS.TimeOfDeath(events, scores, players, traitors) - local near = 10 - local time_near_start = CLSCORE.StartTime + near - - local time_near_end, traitor_win, e - - for i = #events, 1, -1 do - e = events[i] - - if e.id == EVENT_FINISH then - time_near_end = e.t - near - traitor_win = e.win == WIN_TRAITOR or e.win == TEAM_TRAITOR - elseif e.id == EVENT_KILL and e.vic then - if time_near_end and e.t > time_near_end and (e.vic.t == TEAM_TRAITOR) == traitor_win then - return { - nick = e.vic.ni, - title = T("aw_tod1_title"), - text = T("aw_tod1_text"), - priority = (e.t - time_near_end) * 2 - } - elseif e.t < time_near_start then - return { - nick = e.vic.ni, - title = T("aw_tod2_title"), - text = T("aw_tod2_text"), - priority = (time_near_start - e.t) * 2 - } - end - end - end + local near = 10 + local time_near_start = CLSCORE.StartTime + near + + local time_near_end, traitor_win, e + + for i = #events, 1, -1 do + e = events[i] + + if e.id == EVENT_FINISH then + time_near_end = e.t - near + traitor_win = e.win == WIN_TRAITOR or e.win == TEAM_TRAITOR + elseif e.id == EVENT_KILL and e.vic then + if + time_near_end + and e.t > time_near_end + and (e.vic.t == TEAM_TRAITOR) == traitor_win + then + return { + nick = e.vic.ni, + title = T("aw_tod1_title"), + text = T("aw_tod1_text"), + priority = (e.t - time_near_end) * 2, + } + elseif e.t < time_near_start then + return { + nick = e.vic.ni, + title = T("aw_tod2_title"), + text = T("aw_tod2_text"), + priority = (time_near_start - e.t) * 2, + } + end + end + end end TimeOfDeath = AWARDS.TimeOfDeath -- just for compatibility diff --git a/gamemodes/terrortown/gamemode/client/cl_changes.lua b/gamemodes/terrortown/gamemode/client/cl_changes.lua index f31964872..6160a84ed 100644 --- a/gamemodes/terrortown/gamemode/client/cl_changes.lua +++ b/gamemodes/terrortown/gamemode/client/cl_changes.lua @@ -9,6 +9,7 @@ local table = table --- -- @realm client -- @internal +-- stylua: ignore local changesVersion = CreateConVar("changes_version", "v0.0.0.0", FCVAR_ARCHIVE) local changes, currentVersion @@ -20,13 +21,17 @@ local changes, currentVersion -- @param[opt] number date the date when this update got released -- @realm client function AddChange(version, text, date) - changes = changes or {} + changes = changes or {} - -- adding entry to table - -- if no date is given, a negative index is stored as secondary sort parameter - table.insert(changes, 1, {version = version, text = text, date = date or -1 * (#changes + 1)}) + -- adding entry to table + -- if no date is given, a negative index is stored as secondary sort parameter + table.insert( + changes, + 1, + { version = version, text = text, date = date or (-1 * (#changes + 1)) } + ) - currentVersion = version + currentVersion = version end --- @@ -34,1304 +39,2006 @@ end -- @realm client -- @internal function CreateChanges() - if changes then return end - - changes = {} - - AddChange("TTT2 Base - v0.3.5.6b", [[ -

    -
  • many fixes
  • -
  • enabled picking up grenades in prep time
  • -
  • roundend scoreboard fix
  • -
  • disabled useless prints that have spammed the console
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.5.7b", [[ -
    -
  • code optimization
  • -
    -
  • added different CVars
  • -
    -
  • fixed loadout and model issue on changing the role
  • -
  • bugfixes
  • -
    -
  • removed PLAYER:UpdateRole() function and hook TTT2RoleTypeSet
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.5.8b", [[ -
    -
  • selection system update
  • -
    -
  • added different ConVars
  • -
  • added possibility to force a role in the next roleselection
  • -
    -
  • bugfixes
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.6b", [[ -
    -
  • new networking system
  • -
    -
  • added gs_crazyphysics detector
  • -
  • added RoleVote support
  • -
  • added new multilayed icons (+ multilayer system)
  • -
  • added credits
  • -
  • added changes window
  • -
  • added detection for registered incompatible add-ons
  • -
  • added autoslay support
  • -
    -
  • karma system improvement
  • -
  • huge amount of bugfixes
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.7b", [[ -
    -
  • added hook TTT2ToggleRole
  • -
  • added GetActiveRoles()
  • -
    -
  • fixed TTT spec label issue
  • -
    -
  • renamed hook TTT_UseCustomPlayerModels into TTTUseCustomPlayerModels
  • -
  • removed Player:SetSubRole() and Player:SetBaseRole()
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.7.1b", [[ -
    -
  • added possibility to disable roundend if a player is reviving
  • -
  • added own Item Info functions
  • -
  • added debugging function
  • -
  • added TEAM param .alone
  • -
  • added possibility to set the cost (credits) for any equipment
  • -
  • added Player:RemoveEquipmentItem(id) and Player:RemoveItem(id) / Player:RemoveBought(id)
  • -
  • added possibility to change velocity with hook TTT2ModifyRagdollVelocity
  • -
    -
  • improved external icon addon support
  • -
  • improved binding system
  • -
    -
  • fixed loadout bug
  • -
  • fixed loadout item reset bug
  • -
  • fixed respawn loadout issue
  • -
  • fixed bug that items were removed on changing the role
  • -
  • fixed search icon display issue
  • -
  • fixed model reset bug on changing role
  • -
  • fixed Player:GiveItem(...) bug used on round start
  • -
  • fixed dete glasses bug
  • -
  • fixed rare corpse bug
  • -
  • fixed Disguiser
  • -
  • Some more small fixes
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.7.2b", [[ -
    -
  • reworked the credit system (Thanks to Nick!)
  • -
  • added possibility to override the init.lua and cl_init.lua file in TTT2
  • -
    -
  • fixed server errors in combination with TTT Totem (now, TTT2 will just not work)
  • -
  • fixed ragdoll collision server crash (issue is still in the normal TTT)
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.7.3b", [[ -
    -
  • reworked the selection system (balancing and bugfixes)
  • -
    -
  • fixed playermodel issue
  • -
  • fixed toggling role issue
  • -
  • fixed loadout doubling issue
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.7.4b", [[ -
    -
  • fixed playermodel reset bug (+ compatibility with PointShop 1)
  • -
  • fixed external HUD support
  • -
  • fixed credits bug
  • -
  • fixed detective hat bug
  • -
  • fixed selection and jester selection bug
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.8b", [[ -
    -
  • added new TTT2 Logo
  • -
  • added ShopEditor missing icons (by Mineotopia)
  • -
    -
  • reworked weaponshop → ShopEditor
  • -
  • changed file based shopsystem into sql
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.8.1b", [[ -
    -
  • fixed credit issue in ShopEditor (all Data will load and save correct now)
  • -
  • fixed item credits and minPlayers issue
  • -
  • fixed radar, disguiser and armor issue in ItemEditor
  • -
  • fixed shop syncing issue with ShopEditor
  • -
    -
  • small performance improvements
  • -
  • cleaned up some useless functions
  • -
  • removed ALL_WEAPONS table (SWEP will cache the initialized data now in it's own table / entity data)
  • -
  • some function renaming
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.8.2b", [[ -
    -
  • added russian translation (by Satton2)
  • -
  • added .minPlayers indicator for the shop
  • -
    -
  • replaced .globalLimited param with .limited param to toggle whether an item is just one time per round buyable for each player
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.8.3b", [[ -
    -
  • ammoboxes will store the correct amount of ammo now -
  • connected radio commands with scoreboard #tagging (https://github.com/Exho1/TTT-ScoreboardTagging/blob/master/lua/client/ttt_scoreboardradiocmd.lua)
  • -
- ]]) - - AddChange("TTT2 Base - v0.3.9b", [[ -
    -
  • reimplemented all these nice unaccepted PullRequest for TTT on GitHub: -
      -
    • Custom Crosshairs (F1 → Settings): 'https://github.com/Facepunch/garrysmod/pull/1376/' by nubpro
    • -
    • Performance improvements with the help of 'https://github.com/Facepunch/garrysmod/pull/1287' by markusmarkusz and Alf21
    • -
    -
  • -
    -
  • added possibility to use random shops! (ShopEditor → Options)
  • -
  • balanced karma system (Roles that are aware of their teammates can't earn karma anymore. This will lower teamkilling)
  • -
- ]]) - - AddChange("TTT2 Base - v0.4.0b", [[ -
    -
  • ShopEditor: -
      -
    • added .notBuyable param to hide item in the shops
    • -
    • added .globalLimited and .teamLimited params to limit equipment
    • -
    -
  • -
    -
  • added new shop (by tkindanight)
  • -
      -
    • you can toggle the shop everytime (your role doesn't matter)
    • -
    • the shop will update in real time
    • -
    - -
    -
  • added new icons
  • -
  • added warming up for randomness to make things happen... yea... random
  • -
  • added auto-converter for old items
  • -
    -
  • decreased speed modification lags
  • -
  • improved performance
  • -
  • implemented new item system! TTT2 now supports more than 16 passive items!
  • -
  • huge amount of fixes
  • -
- ]]) - - AddChange("TTT2 Base - v0.5.0b", [[ -

New:

-
    -
  • Added new HUD system
  • -
      -
    • Added HUDSwitcher (F1 → Settings → HUDSwitcher)
    • -
    • Added HUDEditor (F1 → Settings → HUDSwitcher)
    • -
    • Added old TTT HUD
    • -
    • Added new PureSkin HUD, an improved and fully integrated new HUD
    • -
        -
      • Added a Miniscoreboard / Confirm view at the top
      • -
      • Added support for SubRole informations in the player info (eg. used by TTT Heroes)
      • -
      • Added a team icon to the top
      • -
      • Redesign of all other HUD elements...
      • -
      - -
    - -
  • Added possibility to give every player his own random shop
  • -
  • Added sprint to TTT2
  • -
  • Added a drowning indicator into TTT2
  • -
  • Added possibility to toggle auto afk
  • -
  • Added possibility to modify the time a player can dive
  • -
  • Added parameter to the bindings to detect if a player released a key
  • -
  • Added possibility to switch between team-synced and individual random shops
  • -
  • Added some nice badges to the scoreboard
  • -
  • Added a sql library for saving and loading specific elements (used by the HUD system)
  • -
  • Added and fixed Bulls draw lib
  • -
  • Added an improved player-target add-on
  • -
-
-

Changed:

-
    -
  • Changed convar ttt2_confirm_killlist to default 1
  • -
  • Improved the changelog (as you can see :D)
  • -
  • Improved Workshop page :)
  • -
  • Reworked F1 menu
  • -
  • Reworked the slot system (by LeBroomer)
  • -
      -
    • Amount of carriable weapons are customizable
    • -
    • Weapons won't block slots anymore automatically
    • -
    • Slot positions are generated from weapon types
    • -
    - -
  • Reworked the ROLES system
  • -
      -
    • Roles can now be created like weapons utilizing the new roles lua module
    • -
    • They can also inherit from other roles
    • -
    -
-
  • Improved the MSTACK to also support messages with images
  • -
  • Confirming a player will now display an imaged message in the message stack
  • - -
    -

    Fixed:

    -
      -
    • Fixed TTT2 binding system
    • -
    • Fixed Ammo pickup
    • -
    • Fixed karma issue with detective
    • -
    • Fixed DNA Scanner bug with the radar
    • -
    • Fixed loading issue in the items module
    • -
    • Fixed critical bug in the process of saving the items data
    • -
    • Fixed the Sidekick color in corpses
    • -
    • Fixed bug that detective were not able to transfer credits
    • -
    • Fixed kill-list confirm role syncing bug
    • -
    • Fixed crosshair reset bug
    • -
    • Fixed confirm button on corpse search still showing after the corpse was confirmed or when a player was spectating
    • -
    • Fixed draw.GetWrappedText() containing a ' ' in its first row
    • -
    • Fixed shop item information not readable when the panel is too small -> added a scrollbar
    • -
    • Fixed shop item being displayed as unbuyable when the items price is set to 0 credits
    • -
    • Other small bugfixes
    • -
    - ]], os.time({year = 2019, month = 03, day = 03})) - - AddChange("TTT2 Base - v0.5.1b", [[ -

    Improved:

    -
      -
    • Improved the binding library and extended the functions
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed target reset bug
    • -
    • Fixed / Adjusted hud element resize handling
    • -
    • Fixed strange weapon switch error
    • -
    • Fixed critical model reset bug
    • -
    • Fixed hudelements borders, childs will now extend the border of their parents
    • -
    • Fixed mstack shadowed text alpha fadeout
    • -
    • Fixed / Adjusted scaling calculations
    • -
    • Fixed render order
    • -
    • Fixed "player has no SteamID64" bug
    • -
    - ]], os.time({year = 2019, month = 03, day = 05})) - - AddChange("TTT2 Base - v0.5.2b", [[ -

    New:

    -
      -
    • Added spectator indicator in the Miniscoreboard
    • -
    • Added icons with higher resolution (native 512x512)
    • -
    • Added Heroes badge
    • -
    • Added HUD documentation
    • -
    • Added some more hooks to modify TTT2 externally
    • -
    -
    -

    Improved:

    -
      -
    • Improved the project structure / Code refactoring
    • -
    • Improved HUD sidebar of items / perks
    • -
    • Improved HUD loading
    • -
    • Improved the sql library
    • -
    • Improved item handling and item converting
    • -
    • Improved performance / performance optimization
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed SetModel bugs
    • -
    • Fixed some model-selector bugs
    • -
    • Fixed a sprint bug
    • -
    • Fixed some ConVars async bugs
    • -
    • Fixed some shopeditor bugs
    • -
    • Fixed an HUD scaling bug
    • -
    • Fixed old_ttt HUD
    • -
    • Fixed border's alpha value
    • -
    • Fixed confirmed player ordering in the Miniscoreboard
    • -
    • Fixed critical TTT2 bug
    • -
    • Fixed a crash that randomly happens in the normal TTT
    • -
    • Fixed PS2 incompatibility
    • -
    • Fixed spectator bug with Roundtime and Haste Mode
    • -
    • Fixed many small HUD bugs
    • -
    - ]], os.time({year = 2019, month = 04, day = 32})) - - AddChange("TTT2 Base - v0.5.3b", [[ -

    New:

    -
  • Added a Reroll System for the Random Shop
  • -
      -
    • Rerolls can be done in the Traitor Shop similar to the credit transferring
    • -
    • Various parameters in the ShopEditor are possible (reroll, cost, reroll per buy)
    • -
    • Added the reroll possibility for the Team Random Shop (very funny)
    • -
    -
    -

    Improved:

    -
      -
    • Added a separate slot for class items
    • -
    • Added help text to the "Not Alive"-Shop
    • -
    • Minor design tweaks
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed critical crash issue (ty to @nick)
    • -
    - ]], os.time({year = 2019, month = 05, day = 09})) - - AddChange("TTT2 Base - v0.5.4b", [[ -

    New:

    -
      -
    • Added a noTeam indicator to the HUD
    • -
    • intriduced a new drowned death symbol
    • -
    • ttt2_crowbar_shove_delay is now used to set the crowbar attack delay
    • -
    • introduced a status system alongside the perk system
    • -
    -
    -

    Improved:

    -
      -
    • Included LeBroomer in the TTT2 logo
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed a problem with colors (seen with sidekick death confirms)
    • -
    • Fixed the team indicator when in spectator mode
    • -
    • Credits now can be transfered to everyone, this fixes a bug with the spy
    • -
    - ]], os.time({year = 2019, month = 06, day = 18})) - - AddChange("TTT2 Base - v0.5.5b", [[ -

    New:

    -
      -
    • Added convars to hide scoreboard badges
    • -
    • A small text that explains the spectator mode
    • -
    • Weapon pickup notifications are now moved to the new HUD system
    • -
    • Weapon pickup notifications support now pure_skin
    • -
    • New shadowed text rendering with font mipmapping
    • -
    -
    -

    Improved:

    -
      -
    • Refactored the code to move all language strings into the language files
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed the reroll per buy bug
    • -
    • Fixed HUD switcher bug, now infinite amounts of HUDs are supported
    • -
    • Fixed the outline border of the sidebar beeing wrong
    • -
    • Fixed problem with the element restriction
    • -
    - ]], os.time({year = 2019, month = 07, day = 07})) - - AddChange("TTT2 Base - v0.5.6b", [[ -

    New:

    -
      -
    • Marks module
    • -
    • New sprint and stamina hooks for add-ons
    • -
    • Added a documentation of TTT2
    • -
    • Added GetColumns function for the scoreboard
    • -
    -
    -

    Improved:

    -
      -
    • Restrict HUD element movement when element is not rendered
    • -
    • Sidebar icons now turn black if the hudcolor is too bright
    • -
    • Binding functions are no longer called when in chat or in console
    • -
    • Improved the functionality of the binding system
    • -
    • Improved rendering of overhead role icons
    • -
    • Improved equipment searching
    • -
    • Improved the project structure
    • -
    • Improved shadow color for dark texts
    • -
    • Some small performance improvements
    • -
    • Removed some unnecessary debug messages
    • -
    • Updated the TTT2 .fgd file
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed the shop bug that occured since the new GMod update
    • -
    • Fixed search sometimes not working
    • -
    • Fixed a shop reroll bug
    • -
    • Fixed GetAvoidDetective compatibility
    • -
    • Fixel two cl_radio errors
    • -
    • Fixed animation names
    • -
    • Fixed wrong karma print
    • -
    • Fixed a language module bug
    • -
    • Fixed an inconsistency in TellTraitorsAboutTraitors function
    • -
    • Fixed the empty weaponswitch bug on first spawn
    • -
    • Fixed an error in the shadow rendering of a font
    • -
    • Small bugfixes
    • -
    - ]], os.time({year = 2019, month = 09, day = 03})) - - AddChange("TTT2 Base - v0.5.6b-h1 (Hotfix)", [[ -

    Fixed:

    -
      -
    • Traitor shop bug (caused by the september'19 GMod update)
    • -
    • ShopEditor bugs and added a response for non-admins
    • -
    • Glitching head icons
    • -
    - ]], os.time({year = 2019, month = 09, day = 06})) - - AddChange("TTT2 Base - v0.5.7b", [[ -

    New:

    -
      -
    • New Loadout Give/Remove functions to cleanup role code and fix item raceconditions
    • -
        -
      • Roles now always get their equipment on time
      • -
      • On role changes, old equipment gets removed first
      • -
      -
    • New armor system
    • -
        -
      • Armor is now displayed in the HUD
      • -
      • Previously it was a simple stacking percetual calculation, which got stupid with multiple armor effects. Three times armoritems with 70% damage each resulted in 34% damage recveived
      • -
      • The new system has an internal armor value that is displayed in the HUD
      • -
      • It works basically the same as the vanilla system, only the stacking is a bit different
      • -
      • Reaching an armor value of 50 (by default) increases its strength
      • -
      • Armor depletes over time
      • -
      -
    • Allowed items to be bought multiple times, if .limited is set to true
    • -
    -
    -

    Improved:

    -
      -
    • Dynamic loading of role icons
    • -
    • Improved performance slightly
    • -
    • Improved code consistency
    • -
    • Caching role icons in ROLE.iconMaterial to prevent recreation of icon materials
    • -
    • Improved bindings menu and added language support
    • -
    • Improved SQL module
    • -
    • Improved radar icon
    • -
    • Made parameterized overheadicon function callable
    • -
    • Improved codereadablity by refactoring huge parts
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed sometimes unavailable shops even if set
    • -
    • Team confirmation convar issue in network-syncing
    • -
    • Reset radar timer on item remove, fixes problems with rolechanges
    • -
    • Fixed an exploitable vulnerability
    • -
    - ]], os.time({year = 2019, month = 10, day = 06})) - - AddChange("TTT2 Base - v0.6b", [[ -

    New:

    -
      -
    • Added new weapon switch system
    • -
        -
      • Players can now manually pick up focused weapons
      • -
      • If the slot is blocked, the current weapon is automatically dropped
      • -
      • Added new convar to prevent auto pickup: ttt_weapon_autopickup (default: 1)
      • -
      -
    • Added new targetID system
    • -
        -
      • Looking at entities shows now more detailed ans structured info
      • -
      • Integrated into the new weapon switch system
      • -
      • Supports all TTT entities by default
      • -
      • Supports doors
      • -
      • Added a new hook to add targetID support to custom entities: TTTRenderEntityInfo
      • -
      -
    • Added the outline module for better performance
    • -
    • TTT2DropAmmo hook to prevent/change the ammo drop of a weapon
    • -
    • Added new HUD element: the eventpopup
    • -
    • Added a new TTT2PlayerReady hook that is called once a player is ingame and can move around
    • -
    • Added new removable decals
    • -
    • Added a new default loading screen
    • -
    • Added new convar to allow Enhanced Player Model Selector to overwrite TTT2 models: ttt_enforce_playermodel (default: 1)
    • -
    • Added new hooks to jam the chat
    • -
    • Allow any key double tap sprint
    • -
    • Regenerate sprint stamina after a delay if exhausted
    • -
    • Moved voice bindings to the TTT2 binding system (F1 menu)
    • -
    • Added new voice and text chat hooks to prevent the usage in certain situations
    • -
    • Added new client only convar (F1->Gameplay->Hold to aim) to change to a "hold rightclick to aim" mode
    • -
    • Added a new language file system for addons, language files have to be in lua/lang//.lua
    • -
    • New network data system including network data tables to better manage state updates on different clients
    • -
    -
    -

    Improved:

    -
      -
    • Microoptimization to improve code performance
    • -
    • Improved the icon rendering for the pure_skin HUD
    • -
    • Improved multi line text rendering in the MSTACK
    • -
    • Improved role color handling
    • -
    • Improved language (german, english, russian)
    • -
    • Improved traitor buttons
    • -
        -
      • By default only players in the traitor team can use them
      • -
      • Each role has a convar to enable traitor button usage for them - yes, innocents can use traitor buttons if you want to
      • -
      • There is an admin mode to edit each button individually
      • -
      • Uses the new targetID
      • -
      -
    • Improved damage indicator overlay with customizability in the F1 settings
    • -
    • Improved hud help font
    • -
    • Added a new flag to items to hide them from the bodysearch panel
    • -
    • Moved missing hardcoded texts to language files
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed a bug with baserole initialization
    • -
    • Small other bugfixes
    • -
    • added legacy supported for limited items
    • -
    • Fixed C4 defuse for non-traitor roles
    • -
    • Fixed radio, works now for all roles
    • -
    • Restricted huds are hidden in HUD Switcher
    • -
    • Fixed ragdoll skins (hairstyles, outfits, ...)
    • -
    • Prevent give_equipment timer to block the shop in the next round
    • -
    • Fix sprint consuming stamina when there is no move input
    • -
    • Fix confirmation of players with no team
    • -
    • Fix voice still sending after death
    • -
    - ]], os.time({year = 2020, month = 02, day = 16})) - - AddChange("TTT2 Base - v0.6.1b", [[ -

    Fixed:

    -
      -
    • Fixed a bug with the spawn wave interval
    • -
    - ]], os.time({year = 2020, month = 02, day = 17})) - - AddChange("TTT2 Base - v0.6.2b", [[ -

    Fixed:

    -
      -
    • Increased the maximum number of roles that can be used. (Fixes weird role issues with many roles installed)
    • -
    - ]], os.time({year = 2020, month = 03, day = 1})) - - AddChange("TTT2 Base - v0.6.3b", [[ -

    New:

    -
      -
    • Added a Polish translation (Thanks @Wukerr)
    • -
    • Added fallback icons for equipment
    • -
    -
    -

    Fixed:

    -
      -
    • Fix body_found for bots
    • -
    • Fix NWVarSyncing when using TTT2NET:Set()
    • -
    - ]], os.time({year = 2020, month = 03, day = 05})) - - AddChange("TTT2 Base - v0.6.4b", [[ -

    New:

    -
      -
    • Added an Italian translation (Thanks @PinoMartirio)
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed a rare bug where the player had the default GMod sprint on top of the TTT2 sprint
    • -
    • Fixed some convars that did not save in ulx by removing them all from the gamemode file
    • -
    • Fixed a bug that happened when TTT2 is installed but not the active gamemode
    • -
    • Fixed a few Polish language strings
    • -
    - ]], os.time({year = 2020, month = 04, day = 03})) - - AddChange("TTT2 Base - v0.7.0b", [[ -

    New:

    -
      -
    • Added two new convars to change the behavior of the armor
    • -
    • Added two new convars to change the confirmation behaviour
    • -
    • Added essential items: 8 different types of items that are often used in other addons. You can remove them from the shop if you don't like them.
    • -
    • Added a new HUD element to show information about an ongoing revival to the player that is revived
    • -
    • Added the possibility to change the radar time
    • -
    • Added a few new modules that are used by TTT2 and can be used by different addons
    • -
    -
    -

    Improved:

    -
      -
    • Updated addon checker list
    • -
    • Migrated the HUDManager settings to the new network sync system
    • -
    • Reworked the old DNA Scanner and replaced it with an improved version
    • -
        -
      • New world- and viewmodel with an interactive screen
      • -
      • Removed the overcomplicated UI menu (simple handling with default keys instead)
      • -
      • The new default scanner behavior shows the direction and distance to the target
      • -
      -
    • Changed TargetID colors for confirmed bodies
    • -
    • Improved the player revival system
    • -
        -
      • Revive makes now sure the position is valid and the player is not stuck in the wall
      • -
      • All revival related messages are now localized
      • -
      • Integration with the newly added revival HUD element
      • -
      -
    • Improved the player spawn handling, no more invalid spawn points where a player will be stuck or spawn in the air and fall to their death
    • -
    • Refactored the role selection code to reside in its own module and cleaned up the code
    • -
    • Improved the round end screen to support longer round end texts
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed round info (the top panel with the miniscoreboard) being displayed in other HUDs
    • -
    • Fixed an error with the pickup system in singleplayer
    • -
    • Fixed propsurfing with the magneto stick
    • -
    • Fixed healthstation TargetID text
    • -
    • Fixed keyinfo for doors where no key can be used
    • -
    • Fixed role selection issues with subroles not properly replacing their baserole etc
    • -
    • Fixed map lock/unlock trigger of doors not updating targetID
    • -
    • Fixed roles having sometimes the wrong radar color
    • -
    • Fixed miniscoreboard update issue and players not getting shown when entering force-spec mode
    • -
    - ]], os.time({year = 2020, month = 06, day = 01})) - - AddChange("TTT2 Base - v0.7.1b", [[ -

    Fixed:

    -
      -
    • Fixed max roles / max base roles interaction with the roleselection. Also does not crash with values != 0 anymore.
    • -
    - ]], os.time({year = 2020, month = 06, day = 02})) - - AddChange("TTT2 Base - v0.7.2b", [[ -

    New:

    -
      -
    • Added Hooks to the targetID system to modify the displayed data
    • -
    • Added Hooks to interact with door destruction
    • -
    • Added a new function to force a new radar scan
    • -
    • Added a new convar to change the default radar time for players without custom radar times: ttt2_radar_charge_time
    • -
    • Added a new client ConVar ttt_crosshair_lines to add the possibility to disable the crosshair lines
    • -
    -
    -

    Improved:

    -
      -
    • Moved the disguiser icon to the status system to be only displayed when the player is actually disguised
    • -
    • Reworked the addonchecker and added a command to execute the checker at a later point
    • -
    • Updated Italian translation (Thanks @ThePlatinumGhost)
    • -
    • Removed Is[ROLE] functions of all roles except default TTT ones
    • -
    • ttt_end_round now resets when the map changes
    • -
    • Reworked the SWEP HUD help (legacy function SWEP:AddHUDHelp is still supported)
    • -
    • Players who disconnect now leave a corpse
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed shadow texture of the "Pure Skin HUD" for low texture quality settings
    • -
    • Fixed inno subrole upgrading if many roles are installed
    • -
    • Fixed and improved the radar role/team modification hook
    • -
    • Fixed area portals on servers for destroyed doors
    • -
    • Fixed revive fail function reference reset
    • -
    • Removed the DNA Scanner hudelement for spectators
    • -
    • Fixed the image in the confirmation notification whenever a bot's corpse gets identified
    • -
    • Fixed bad role selection due to RNG reseeding
    • -
    • Fixed missing role column translation
    • -
    • Fixed viewmodel not showing correct hands on model change
    • -
    - ]], os.time({year = 2020, month = 06, day = 26})) - - AddChange("TTT2 Base - v0.7.3b", [[ -

    New:

    -
      -
    • Added a new custom file loader that loads lua files from lua/terrortown/autorun/
    • -
        -
      • it basically works the same as the native file loader
      • -
      • there are three subfolders: client, server and shared
      • -
      • the files inside this folder are loaded after all TTT2 gamemode files and library extensions are loaded
      • -
      -
    • Added Spanish version for base addon (by @Tekiad and @DennisWolfgang)
    • -
    • Added Chinese Simplified translation (by @TheOnly8Z)
    • -
    • Added double-click buying
    • -
    • Added a default avatar for players and an avatar for bots
    • -
    -
    -

    Improved:

    -
      -
    • Roles are now only getting synced to clients if the role is known, not just the body being confirmed
    • -
    • Airborne players can no longer replenish stamina
    • -
    • Detective overhead icon is now shown to innocents and traitors
    • -
    • moved language files from lua/lang/ to lua/terrortown/lang
    • -
    • Stopped teleporting players to players they're not spectating if they press the "duck"-Key while roaming
    • -
    • Moved shop's equipment list generation into a coroutine
    • -
    • Removed TTT2PlayerAuthedCacheReady hook
    • -
    • Internal changes to the b-draw library for fetching avatars
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed death handling spawning multiple corpses when killed multiple times in the same frame
    • -
    • Radar now shows bombs again, that do not have the team property set
    • -
    • Fix HUDManager not saving forcedHUD and defaultHUD values
    • -
    • Fixed wrong parameter default in EPOP:AddMessage documentation
    • -
    • Fixed shop switching language issue
    • -
    • Fixed shop refresh activated even not buyable equipments
    • -
    • Fixed wrong shop view displayed as forced spectator
    • -
    - ]], os.time({year = 2020, month = 08, day = 09})) - - AddChange("TTT2 Base - v0.7.4b", [[ -

    New:

    -
      -
    • Added ConVar to toggle double-click buying
    • -
    • Added Japanese translation (by @Westoon)
    • -
    • Added table.ExtractRandomEntry(tbl, filterFn) function
    • -
    • Added a team indicator in front of every name in the scoreboard (just known teams will be displayed)
    • -
    • Added a hook TTT2ModifyCorpseCallRadarRecipients that is called once "call detective" is pressed
    • -
    -
    -

    Improved:

    -
      -
    • The weapon pickup system has been improved to increase stability and remove edge cases in temporary weapon teleportation
    • -
    • Updated Spanish translation (by @DennisWolfgang)
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed foregoing avatar fetch fix
    • -
    • Fixed HUD savingKeys variable not being unique across all HUDs
    • -
    • Fixed drawing web images, seamless web images and avatar images
    • -
    • Fixed correctly saving setting a bind to NONE, while a default is defined
    • -
    • Fixed a weapon pickup targetID bug where the +use key was displayed even though pickup has its own keybind
    • -
    • Fixed DNA scanner crash if using an old/different weapon base
    • -
    • Fixed rare initialization bug in the speed calculation when joining as a spectator
    • -
    - ]], os.time({year = 2020, month = 09, day = 28})) - - AddChange("TTT2 Base - v0.8.0b", [[ -

    New:

    -
      -
    • Added new vgui system with new F1 menu
    • -
    • Introduced a global scale factor based on screen resolution to scale HUD elements accordingly
    • -
    • Added automatical scale factor change on resolution change that works even if the resolution was changed while TTT2 wasn't loaded
    • -
    • Added Drag&Drop role layering VGUI (preview), accessible with the console command ttt2_edit_rolelayering
    • -
    • Added a new event system
    • -
    • Credits can now be transferred across teams and from roles whom the recipient does not know
    • -
    -
    -

    Improved:

    -
      -
    • TargetID text is now scaled with the global scale factor
    • -
    • Removed C4 defuse restriction for teammates
    • -
    • Changed the language identifiers to generic english names
    • -
    • Updated Simplified Chinese localization (by @TheOnly8Z)
    • -
    • Updated Italian localization (by @ThePlatynumGhost)
    • -
    • Updated English localization (by @Satton2)
    • -
    • Updated Russian localization (by @scientistnt and @Satton2)
    • -
    • Updated German translation (by @Creyox)
    • -
    -
    -

    Fixed:

    -
      -
    • Fixed weapon pickup bug, where weapons would not get dropped but stayed in inventory
    • -
    • Fixed a roleselection bug, where forced roles would not be deducted from the available roles
    • -
    • Fixed a credit award bug, where detectives would receive a pointless notification about being awarded with 0 credits
    • -
    • Fixed a karma bug, where damage would still be reduced even though the karma system was disabled
    • -
    • Fixed a roleselection bug, where invalid layers led to skipping the next layer too
    • -
    • Fixed Magneto Stick ragdoll pinning instructions not showing for innocents when ttt_ragdoll_pinning_innocents is enabled
    • -
    • Fixed a bug where the targetID info broke if the pickup key is unbound
    • -
    - ]], os.time({year = 2021, month = 02, day = 06})) - - AddChange("TTT2 Base - v0.8.1b", [[ -

    Fixed:

    -
      -
    • Fixed inheriting from the same base using the classbuilder in different folders
    • -
    - ]], os.time({year = 2021, month = 02, day = 19})) - - AddChange("TTT2 Base - v0.8.2b", [[ -

    Improved:

    -
      -
    • Added global alias for IsOffScreen function to util.IsOffScreen
    • -
    • Updated Japanese localization (by @Westoon)
    • -
    • Moved rendering modules to libraries
    • -
    • Assigned PvP category to the gamemode.
    • -
    -

    Fixed:

    -
      -
    • TTT: fix instant reload of dropped weapon (by @svdm)
    • -
    • TTT: fix ragdoll pinning HUD for innocents (by @Flapchik)
    • -
    • Fixed outline library not working
    • -
    - ]], os.time({year = 2021, month = 03, day = 25})) - - AddChange("TTT2 Base - v0.9.0b", [[ -

    Added:

    -
      -
    • All new roundend menu
    • -
        -
      • new info panel that shows detailed role distribution during the round
      • -
      • info panel also states detailed score events
      • -
      • new timeline that displays the events that happened during the round
      • -
      • added two new round end conditions: time up and no one wins
      • -
      -
    • Added ROLE_NONE (ID 3 by default)
    • -
        -
      • Players now default to ROLE_NONE instead of ROLE_INNOCENT
      • -
      • Enables the possibility to give Innocents access to a custom shop (shopeditor)
      • -
      -
    • Karma now stores changes
    • -
        -
      • Is shown in roundend menu
      • -
      -
    • Added a new hook TTT2ModifyLogicCheckRole that can be used to modify the tested role for map related role checks
    • -
    • Added the ConVar ttt2_random_shop_items for the number of items in the randomshop
    • -
    • Added per-player voice control by hovering over the mute icon and scrolling
    • -
    - -

    Fixed

    -
      -
    • Updated French translation (by @MisterClems)
    • -
    • Fixed IsOffScreen function being global for compatibility
    • -
    • Fixed a German translation string (by @FaRLeZz)
    • -
    • Fixed a Polish translation by adding new lines (by @Wuker)
    • -
    • Fixed a data initialization bug that appeared on the first (initial) spawn
    • -
    • Fixed silent Footsteps, while crouched bhopping
    • -
    • Fixed issue where base innocents could bypass the TTT2AvoidGeneralChat and TTT2AvoidTeamChat hooks with the team chat key
    • -
    • Fixed issue where roles with unknownTeam could see messages sent with the team chat key
    • -
    • Fixed the admin section label not being visible in the main menu
    • -
    • Fixed the auto resizing of the buttons based on the availability of a scrollbar not working
    • -
    • Fixed reopening submenus of the legacy addons in F1 menu not working
    • -
    • TTT: Fixed karma autokick evasion
    • -
    • TTT: Fixed karma being applied to weapon damage even though karma is disabled
    • -
    - -

    Changed

    -
      -
    • Microoptimization to improve code performance
    • -
    • Converted roles, huds, hudelements, items and pon modules into libraries
    • -
    • Moved bind library to the libraries folder
    • -
    • Moved favorites functions for equipment to the equipment shop and made them local functions
    • -
    • Code cleanup and removed silly negations
    • -
    • Extended some ttt2net functions
    • -
    • Changed bees win to nones win
    • -
    • By default all evil roles are now counted as traitor roles for map related checks
    • -
    • Changed the ConVar ttt2_random_shops to only disable the random shop (if set to 0)
    • -
    • Shopeditor settings are now available in the F1 Menu
    • -
    • Moved the F1 menu generating system from a hook based system to a file based system
    • -
        -
      • removed the hooks TTT2ModifyHelpMainMenu and TTT2ModifyHelpSubMenu
      • -
      • menus are now generated based on files located in lua/terrortown/menus/gamemode/
      • -
      • submenus are generated from files located in folders with the menu name
      • -
      -
    • Menus without content are now always hidden in the main menu
    • -
    • Moved Custom Shopeditor and linking shop to roles to the F1 menu
    • -
    • Moved inclusion of cl_help to the bottom as nothing depends on it, but menus created by it could depend on other client files
    • -
    • Shopeditor equipment is now available in F1 menu
    • -
    • Moved the role layering menu to the F1 menu (administration submenu)
    • -
        -
      • removed the command ttt2_edit_rolelayering
      • -
      -
    • moved the internal path of lang/, vskin/ and events/ (this doesn't change anything for addons)
    • -
    • Sort teammates first in credit transfer selection and add an indicator to them
    • -
    - -

    Removed

    -
      -
    • Removed the custom loading screen (GMOD now only accepts http(s) URLs for sv_loadingurl)
    • -
    - -

    Breaking Changes

    -
      -
    • Adjusted Player:HasRole() and Player:HasTeam() to support simplified role and team checks (no parameter are supported anymore, use Player:GetRole() or Player:GetTeam() instead)
    • -
    • Moved global roleData to the roles library (e.g. INNOCENT to roles.INNOCENT). INNOCENT, TRAITOR etc. is not supported anymore. ROLE_ is still supported and won't be changed.
    • -
    • Shopeditor function ShopEditor.ReadItemData() now only updates a number of key-parameters, must be given as UInt. Messages were changed accordingly (TTT2SESaveItem,TTT2SyncDBItems)
    • -
    • Equipment shop favorite functions are now local and not global anymore (CreateFavTable, AddFavorite, RemoveFavorite, GetFavorites & IsFavorite)
    • -
    - ]], os.time({year = 2021, month = 06, day = 19})) - - AddChange("TTT2 Base - v0.9.1b", [[ -

    Fixed

    -
      -
    • Fixed shop convars not being shared / breaking the shop
    • -
    - ]], os.time({year = 2021, month = 06, day = 19})) - - AddChange("TTT2 Base - v0.9.2b", [[ -

    Fixed

    -
      -
    • Fixed low karma autokick convar
    • -
    • Fixed multi-layer inheritance by introducing a recursion based approach
    • -
    - ]], os.time({year = 2021, month = 06, day = 20})) - - AddChange("TTT2 Base - v0.9.3b", [[ -

    Added

    -
      -
    • Add Traditional Chinese Translation (by @TEGTianFan)
    • -
    • Added a searchbar to submenus
    • -
    • Added full-sized icons to the equipment-editor
    • -
    • Hotreload functionality for weapons, they are now fully compatible to TTT2 after hotreload
    • -
    • Added experimental SWEP.HotReloadableKeys a list of strings to weapons, that makes data saved with weapons.GetStored() persistent across hotreloads
    • -
    • Extended cvars library to support manipulation of serverside ConVars
    • -
    • Added possibility to manipulate serverside ConVars with Checkboxes and Sliders
    • -
        -
      • Just add .serverConvar with the conVarName to the given data similar to .convar
      • -
      -
    - -

    Fixed

    -
      -
    • Updated Japanese translation (by @westooooo)
    • -
    • Fixed text positioning in pure_skin bar (by @LukasMandok)
    • -
    • Fixed data being not persistent after hot reloading
    • -
        -
      • HUDs are now still available
      • -
      • ttt2net keeps its data
      • -
      • bindings are not lost on reload
      • -
      -
    - -

    Changed

    -
      -
    • Revise and additions simplified Chinese (by @TEGTianFan)
    • -
    • Prevent spectators from gathering info on players if they're about to revive (by @AaronMcKenney)
    • -
    • ROLE_NONE does not count as a special role anymore (by @TheNickSkater)
    • -
    - -

    Internal Breaking Changes

    -
      -
    • Removed first argument of GetEquipmentBase(data, equipment), it only takes the equipment as argument now GetEquipmentBase(equipment) and generally merges it with EquipMenuData
    • -
    • Added equipment as argument to InitDefaultEquipmentForRole(roleData), it now only initializes the given equipment not all InitDefaultEquipmentForRole(roleData, equipment)
    • -
    • Added equipment as argument to CleanUpDefaultCanBuyIndices(), it now only initializes the given equipment not all CleanUpDefaultCanBuyIndices(equipment)
    • -
    - ]], os.time({ year = 2021, month = 09, day = 25 })) - - AddChange("TTT2 Base - v0.10.0b", [[ -

    Added

    -
      -
    • Added a new scoring variable named score.survivePenaltyMultiplier to punish surviving players of a losing team
    • -
    • Added in game spawn editor system that can be found in F1->Administration
    • -
    • Moved all TTT weapons to this repository (with cleaned up code)
    • -
    • Added in four new libraries
    • -
        -
      • map: A library which handles map specific data
      • -
      • entspawn: A library that handles the spawning and spawns of all entity types
      • -
      • entspawnscript: A library that handles the new TTT2 entity spawn script to customize spawns
      • -
      • plyspawn: A library that builds on top of entspawn to handle the more complex player spawn (originally named spawn, see Breaking changes)
      • -
      -
    • Added a new submenu to the administration settings regarding basic role setup
    • -
    • Added a new menu to the F1 menu to set up and configure all installed menus
    • -
    • Added two new hooks to modify the contents of the newly added menu
    • -
        -
      • ROLE:AddToSettingsMenu(parent)
      • -
      • ROLE:AddToSettingsMenuCreditsForm(parent)
      • -
      -
    • Added a new in-game player model selector
    • -
        -
      • Added new convars that can change the way playermodels are selected (these can be found in the gamemode menu)
      • -
      • Added a new ConVar ttt2_use_custom_models (def: 0) to enable the custom player model selector
      • -
      • Added indicator that shows if a model has a headshot hitbox
      • -
      • Added possibility to enable/disable detective hats for individual player models
      • -
      -
    • Added a new admin only menu for server addon settings
    • -
    • Added automatic default values for serverConVars
    • -
    • Added two new role variables:
    • -
        -
      • isPublicRole: This makes the role behave like a detective in such a way, that the role is public known and shown in the scoreboard. This means other roles can use this without special role syncing; additionally roles with that flag will be handled like a detective if killed by an 'evil' role, meaning that they will receive a credit bonus
      • -
      • isPolicingRole: This rolevar adds all "detective-like" features to the detective, for example the ability to be called to a corpse etc.
      • -
      -
    • Added two new role conVar variables:
    • -
        -
      • creditsAwardDeadEnable: To award this role if a certain percentage of players from the enemy teams died
      • -
      • creditsAwardKillEnable: To award this role if they killed a high value public role
      • -
      -
    - -

    Changed

    -
      -
    • Split up kill, suicide and teamkill in the round end screen to make it more clear
    • -
    • Decreased the minimum cost of equipment in the equipment editor to 0
    • -
    • Changed disguise such that every role can now use the function
    • -
    • Completely reworked how weapons, ammo and players spawn in the world
    • -
    • Sliders only update ConVars on mouseRelease now
    • -
    • Changed the way credits on kills are distributed in a way that non-default roles can easily use this as well
    • -
    - -

    Breaking Changes

    -
      -
    • Removed the (unused?) ConVar ttt2_custom_models
    • -
    • Removed the function GetRandomPlayerModel(), use playermodels.GetRandomPlayerModel() instead
    • -
    • Renamed the spawn module to plyspawn
    • -
    • Hook PlayerSelectSpawn doesnt return a spawnEntity anymore
    • -
    • SpawnWillingPlayers is deleted and not available anymore
    • -
    • renamed the ttt_credits_starting to ttt_traitor_credits_starting to be more in-line with all other roles
    • -
    • WARNING: This means that every traitor now starts with 0 credits until the convar reset button is pressed (on existing servers)
    • -
    • removed the alone_bonus convar because it only complicated the credits system further without adding much benefit
    • -
    - ]], os.time({ year = 2021, month = 10, day = 14 })) - - AddChange("TTT2 Base - v0.10.1b", [[ -

    Fixed

    -
      -
    • Fixed Playermodels not correctly loading changes on game start
    • -
    • Fixed setting defaults before assigning a resetButton not throwing an error anymore
    • -
    • Fixed invisible preview for entity spawn placements
    • -
    - ]], os.time({ year = 2021, month = 10, day = 15 })) - - AddChange("TTT2 Base - v0.10.2b", [[ -

    Added

    -
      -
    • Added a new hook GM:TTT2ModifyRadioTarget to modify the current radio target
    • -
    • Added documentation to all hooks
    • -
    - -

    Fixed

    -
      -
    • Fixed the reset button not working for Sliders in the F1 Menu
    • -
    • Fixed defuser only working for detectives
    • -
    • Fixed some weapon packs like ArcCW to be working again, weapons are now initialized with ttt2 variables after the InitPostEntity hook
      - Note: This might only take effect after a reinstall or a reset of the server; if you don't want to reset your server, you have to change the spawnability manually in the equipment editor or delete the sql table "ttt2_items" of the sv.db to force a reset only on equipment
    • -
    - -

    Changed

    -
      -
    • Changed the Sliders to only update after dragging ends, no matter where you clicked on the slider before dragging
    • -
    • Changed TTTPlayerUsedHealthStation hook, return false to cancel health regeneration tick
    • -
    • Changed all C4 hooks to be cancelable
    • -
    - -

    Removed

    -
      -
    • Removed old concommand shopeditor and the old shopeditor
    • -
    - -

    Breaking Changes

    -
      -
    • Renamed hook GM:TTT2CheckWeaponForID to GM:TTT2RegisterWeaponID better fitting its purpose as its probably nowhere used yet anyway
    • -
    - ]], os.time({ year = 2021, month = 10, day = 21 })) - - AddChange("TTT2 Base - v0.10.3b", [[ -

    Fixed

    -
      -
    • Fixed the hook scope in the disguiser causing an error
    • -
    • Fixed the classic entity spawn mode breaking on maps without all three spawn types
    • -
    • Fixed weapons not using their average firerate with a tickrate dependent fix. Function SWEP:SetNextPrimaryFire(nextTime) was overwritten with our fix SWEP:SetNextPrimaryFire(nextTime, skipTickrateFix) -
    - -

    Changed

    -
      -
    • Added new param skipTickrateFix to SWEP:SetNextPrimaryFire(nextTime, skipTickrateFix) to skip our inbuilt tickrate fix
    • -
    - ]], os.time({ year = 2021, month = 10, day = 29 })) - - AddChange("TTT2 Base - v0.11.0b", [[ -

    Added

    -
      -
    • Added the hook GM:TTT2CalledPolicingRole that is called after all policing role players were called to a corpse
    • -
    • Added all TTT2 convars into the F1 menu
    • -
        -
      • most convars are located in the administration menu
      • -
      • equipment specific settings can be found in the edit equipment menu
      • -
      -
    • Added icon to the magneto stick
    • -
    • Added the function AddToSettingsMenu to both SWEP and ITEM to add settings to the equipment menu
    • -
    • Added the role flag .isOmniscientRole; if set to true the role is able to see missing in action players and the haste mode time
    • -
    • Added GM:TTT2ModifyOverheadIcon to add, remove or modify the overhead icons of players
    • -
    - -

    Fixed

    -
      -
    • Fixed that every policing player could be called to a corpse, this is now again restricted to alive only players
    • -
    • Fixed inconsistency between .disabledTeamChatRecv and .disabledTeamChatRec
    • -
    • Fixed non-public policing roles having hats and therefore confirming them
    • -
    • Fixed triggered spawns on maps like ttt_lttp_kakariko_a5 with the vases and ttt_mc_jondome with the chests
    • -
    • Fixed roleselection layering with base roles to ensure layer order is considered correctly when selecting roles
    • -
    • Fixed hotreloading items
    • -
    • Fixed random playermodel selection on map change not working
    • -
    • Fixed ply:Give sometimes picking up all surrounding weapon entities, if auto pickup is enabled
    • -
    • Fixes weapon pickup sometimes causing floating weapons
    • -
    • Fixes weapon pickup sometimes failing if a weapon with the same class as a weapon in the inventory should be picked up
    • -
    - -

    Changed

    -
      -
    • All public policing roles now appear as detectives in the chat
    • -
    • Change blocking revival mode from true/false to
    • -
        -
      • REVIVAL_BLOCK_NONE: don't block the winning condition during the revival process [default, previously nil/false]
      • -
      • REVIVAL_BLOCK_AS_ALIVE: only block the winning condition, if the player being alive would change the outcome [previously true]
      • -
      • REVIVAL_BLOCK_ALL: block the winning condition until the revival process is ended
      • -
      • the old arguments still work, they are automatically converted
      • -
      -
    • Changed logs folder to terrortown/logs/ to be inline with everything else
    • -
    • Added more role agnostics
    • -
        -
      • voice drain rate is now no longer bound to Detectives but to all public policing roles
      • -
      • Karma multiplier is now no longer bound to Detectives but to all public policing roles
      • -
      • all non-innocent roles are now able to pin ragdolls if enabled (previous only Traitors could do this)
      • -
      -
    • Overhead icons are now also either colored black or white depending on the role's color
    • -
    - -

    Breaking Changes

    -
      -
    • Renamed some convars to be inline with our opt-in style, all values were changed so that the default value is kept
    • -
        -
      • ttt_no_prop_throwing is now ttt_prop_throwing
      • -
      • ttt_limit_spectator_chat is now ttt_spectators_chat_globally
      • -
      • ttt_no_nade_throw_during_prep is now ttt_nade_throw_during_prep
      • -
      • ttt_armor_classic is now ttt_armor_dynamic
      • -
      -
    - ]], os.time({ year = 2021, month = 11, day = 15 })) - - AddChange("TTT2 Base - v0.11.1b", [[ -

    Added

    -
      -
    • Added four new Karma multipliers as role variables. They are applied **after** all other Karma calculations are done:
    • -
        -
      • ROLE.karma.teamKillPenaltyMultiplier: The multiplier that is used to calculate the Karma penalty for a team kill
      • -
      • ROLE.karma.teamHurtPenaltyMultiplier: The multiplier that is used to calculate the Karma penalty for team damage
      • -
      • ROLE.karma.enemyKillBonusMultiplier: The multiplier that is used to calculate the Karma given to the killer if a player from an enemy team is killed
      • -
      • ROLE.karma.enemyHurtBonusMultiplier: The multiplier that is used to calculate the Karma given to the attacker if a player from an enemy team is damaged
      • -
      -
    - -

    Fixed

    -
      -
    • Fixed ply:Give(weapon) to work again, when weapons are cached, fixing the spawneditor to work again
    • -
    • Fixed spawneditor not causing errors, when going through walls due to many steps
    • -
    • Set default traitor button variable back to 0
    • -
    • Fixed unchanged or unscaled damage being sent to the client, leading to a wrongly working damage-overlay
    • -
    - ]], os.time({ year = 2021, month = 11, day = 16 })) - - AddChange("TTT2 Base - v0.11.2b", [[ -

    Fixed

    -
      -
    • Fixed correct role Karma multipliers used in Karma-module
    • -
    - ]], os.time({ year = 2021, month = 11, day = 17 })) - - AddChange("TTT2 Base - v0.11.3b", [[ -

    Fixed

    -
      -
    • Fixed Equipment-Editor not showing the current synced values, but the cached ones
    • -
    - -

    Changed

    -
      -
    • Changed serverConVars not indexing with 0 in tables (could cause issues when iterating)
    • -
    - ]], os.time({ year = 2021, month = 11, day = 18 })) - - AddChange("TTT2 Base - v0.11.4b", [[ -

    Changed

    -
      -
    • Switched from the voicerecord commands to the GMod permission system due to a recent GMod update breaking the old voice chat
    • -
    • Updated Japanese translation (by @westooooo)
    • -
    - ]], os.time({ year = 2021, month = 12, day = 17 })) - - AddChange("TTT2 Base - v0.11.5b", [[ -

    Added

    -
      -
    • Reworked our simplified Dropdowns MakePanel PANEL:MakeComboBox(data) version
    • -
        -
      • Added possibility to manipulate serverside ConVars with Dropdowns. Just add .serverConvar with the conVarName to the given data similar to .convar
      • -
      • .serverConVar and .conVar are also supported
      • -
      • data.choices can now be a table containing {title, value, select, icon, additionalData}
      • -
      • data.selectValue is added, use it instead of data.selectName to choose the value you set
      • -
      • data.selectTitle is added and shall replace data.selectName
      • -
      -
    • New setting to disable session limits entirely. (by @Reispfannenfresser)
    • -
    • Added GM:TTT2AdminCheck hook
    • -
        -
      • Replaced all IsSuperAdmin() checks with this hook
      • -
      • This hook can be used to allow custom usergroups through these checks
      • -
      -
    • Added convars to modify how fall damage is applied
    • -
        -
      • ttt2_falldmg_enable (default: 1) toggles whether or not to apply fall damage at all
      • -
      • ttt2_falldmg_min_velocity (default: 450) sets the minimum velocity threshold for fall damage to occur
      • -
      • ttt2_falldmg_exponent (default: 1.75) sets the exponent to increase fall damage in relation to velocity
      • -
      • All these convars can also be adjusted in the F1->Administration->Player Settings menu
      • -
      -
    • Added portuguese translation
    • -
    • Added a database library, that handles shared Interaction with the sql database
    • -
    - -

    Breaking Changes

    -
      -
    • Reworked Dropdowns Panel DComboBoxTTT2 itself
    • -
        -
      • PANEL:AddChoice(title, value, select, icon, data) now uses the second argument as value string for setting convars, use the fifth argument for special data instead
      • -
      • PANEL:ChooseOption(title, index, ignoreConVar) is deprecated and no longer chooses the displayed text, only per index
      • -
      -
    • Reworked our simplified Dropdowns MakePanel PANEL:MakeComboBox(data) version
    • -
        -
      • data.OnChange(value, additionalData, comboBoxPanel) is now called with the two important arguments at first. They are the value that e.g. convars are set, the additionalData and the Panel
      • -
      -
    - -

    Changed

    -
      -
    • Corrected incorrect translation (by @sbzlzh)
    • -
    • Optimized damage indicator vgui images to be smaller
    • -
    • Improved hotreload of TTT2 roles library with RoleList not being global
    • -
    - -

    Fixed

    -
      -
    • Fixed addon compatibility checker fussing over disabled addons
    • -
    • Fixed ammo entities blocking +use traces
    • -
    • Fixed double call of GM:TTT2UpdateTeam, when a role change leads to a team change
    • -
    - ]], os.time({ year = 2022, month = 08, day = 21 })) - - AddChange("TTT2 Base - v0.11.6b", [[ -

    Changed

    -
      -
    • Fixed and updated the Chinese translation file (by @sbzlzh)
    • -
    • Updated Japanese translation (by @westooooo)
    • -
    • Updated Simplified and Traditional Chinese (by @TEGTianFan)
    • -
    • Add placeholder message to the ingame ttt2 guide (F1 Menu)
    • -
    - -

    Fixed

    -
      -
    • Fixed the spawn editor tool not having a TargetID in some scenarios by always rendering the 'ttt_spawninfo_ent' (by @NickCloudAT)
    • -
    • Roleselection for a lot of roles now considers all possible subroles one after another
    • -
    • Fixed portuguese translation of the equipment editor not working
    • -
    - ]], os.time({ year = 2022, month = 09, day = 25 })) - - --- - -- run hook for other addons to add their changelog as well - -- @realm client - hook.Run("TTT2AddChange", changes, currentVersion) + if changes then + return + end + + changes = {} + + AddChange( + "TTT2 Base - v0.3.5.6b", + [[ +
      +
    • many fixes
    • +
    • enabled picking up grenades in prep time
    • +
    • roundend scoreboard fix
    • +
    • disabled useless prints that have spammed the console
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.5.7b", + [[ +
      +
    • code optimization
    • +
      +
    • added different CVars
    • +
      +
    • fixed loadout and model issue on changing the role
    • +
    • bugfixes
    • +
      +
    • removed PLAYER:UpdateRole() function and hook TTT2RoleTypeSet
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.5.8b", + [[ +
      +
    • selection system update
    • +
      +
    • added different ConVars
    • +
    • added possibility to force a role in the next roleselection
    • +
      +
    • bugfixes
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.6b", + [[ +
      +
    • new networking system
    • +
      +
    • added gs_crazyphysics detector
    • +
    • added RoleVote support
    • +
    • added new multilayed icons (+ multilayer system)
    • +
    • added credits
    • +
    • added changes window
    • +
    • added detection for registered incompatible add-ons
    • +
    • added autoslay support
    • +
      +
    • karma system improvement
    • +
    • huge amount of bugfixes
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.7b", + [[ +
      +
    • added hook TTT2ToggleRole
    • +
    • added GetActiveRoles()
    • +
      +
    • fixed TTT spec label issue
    • +
      +
    • renamed hook TTT_UseCustomPlayerModels into TTTUseCustomPlayerModels
    • +
    • removed Player:SetSubRole() and Player:SetBaseRole()
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.7.1b", + [[ +
      +
    • added possibility to disable roundend if a player is reviving
    • +
    • added own Item Info functions
    • +
    • added debugging function
    • +
    • added TEAM param .alone
    • +
    • added possibility to set the cost (credits) for any equipment
    • +
    • added Player:RemoveEquipmentItem(id) and Player:RemoveItem(id) / Player:RemoveBought(id)
    • +
    • added possibility to change velocity with hook TTT2ModifyRagdollVelocity
    • +
      +
    • improved external icon addon support
    • +
    • improved binding system
    • +
      +
    • fixed loadout bug
    • +
    • fixed loadout item reset bug
    • +
    • fixed respawn loadout issue
    • +
    • fixed bug that items were removed on changing the role
    • +
    • fixed search icon display issue
    • +
    • fixed model reset bug on changing role
    • +
    • fixed Player:GiveItem(...) bug used on round start
    • +
    • fixed dete glasses bug
    • +
    • fixed rare corpse bug
    • +
    • fixed Disguiser
    • +
    • Some more small fixes
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.7.2b", + [[ +
      +
    • reworked the credit system (Thanks to Nick!)
    • +
    • added possibility to override the init.lua and cl_init.lua file in TTT2
    • +
      +
    • fixed server errors in combination with TTT Totem (now, TTT2 will just not work)
    • +
    • fixed ragdoll collision server crash (issue is still in the normal TTT)
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.7.3b", + [[ +
      +
    • reworked the selection system (balancing and bugfixes)
    • +
      +
    • fixed playermodel issue
    • +
    • fixed toggling role issue
    • +
    • fixed loadout doubling issue
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.7.4b", + [[ +
      +
    • fixed playermodel reset bug (+ compatibility with PointShop 1)
    • +
    • fixed external HUD support
    • +
    • fixed credits bug
    • +
    • fixed detective hat bug
    • +
    • fixed selection and jester selection bug
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.8b", + [[ +
      +
    • added new TTT2 Logo
    • +
    • added ShopEditor missing icons (by Mineotopia)
    • +
      +
    • reworked weaponshop → ShopEditor
    • +
    • changed file based shopsystem into sql
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.8.1b", + [[ +
      +
    • fixed credit issue in ShopEditor (all Data will load and save correct now)
    • +
    • fixed item credits and minPlayers issue
    • +
    • fixed radar, disguiser and armor issue in ItemEditor
    • +
    • fixed shop syncing issue with ShopEditor
    • +
      +
    • small performance improvements
    • +
    • cleaned up some useless functions
    • +
    • removed ALL_WEAPONS table (SWEP will cache the initialized data now in it's own table / entity data)
    • +
    • some function renaming
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.8.2b", + [[ +
      +
    • added russian translation (by Satton2)
    • +
    • added .minPlayers indicator for the shop
    • +
      +
    • replaced .globalLimited param with .limited param to toggle whether an item is just one time per round buyable for each player
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.8.3b", + [[ +
      +
    • ammoboxes will store the correct amount of ammo now +
    • connected radio commands with scoreboard #tagging (https://github.com/Exho1/TTT-ScoreboardTagging/blob/master/lua/client/ttt_scoreboardradiocmd.lua)
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.3.9b", + [[ +
      +
    • reimplemented all these nice unaccepted PullRequest for TTT on GitHub: +
        +
      • Custom Crosshairs (F1 → Settings): 'https://github.com/Facepunch/garrysmod/pull/1376/' by nubpro
      • +
      • Performance improvements with the help of 'https://github.com/Facepunch/garrysmod/pull/1287' by markusmarkusz and Alf21
      • +
      +
    • +
      +
    • added possibility to use random shops! (ShopEditor → Options)
    • +
    • balanced karma system (Roles that are aware of their teammates can't earn karma anymore. This will lower teamkilling)
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.4.0b", + [[ +
      +
    • ShopEditor: +
        +
      • added .notBuyable param to hide item in the shops
      • +
      • added .globalLimited and .teamLimited params to limit equipment
      • +
      +
    • +
      +
    • added new shop (by tkindanight)
    • +
        +
      • you can toggle the shop everytime (your role doesn't matter)
      • +
      • the shop will update in real time
      • +
      + +
      +
    • added new icons
    • +
    • added warming up for randomness to make things happen... yea... random
    • +
    • added auto-converter for old items
    • +
      +
    • decreased speed modification lags
    • +
    • improved performance
    • +
    • implemented new item system! TTT2 now supports more than 16 passive items!
    • +
    • huge amount of fixes
    • +
    + ]] + ) + + AddChange( + "TTT2 Base - v0.5.0b", + [[ +

    New:

    +
      +
    • Added new HUD system
    • +
        +
      • Added HUDSwitcher (F1 → Settings → HUDSwitcher)
      • +
      • Added HUDEditor (F1 → Settings → HUDSwitcher)
      • +
      • Added old TTT HUD
      • +
      • Added new PureSkin HUD, an improved and fully integrated new HUD
      • +
          +
        • Added a Miniscoreboard / Confirm view at the top
        • +
        • Added support for SubRole informations in the player info (eg. used by TTT Heroes)
        • +
        • Added a team icon to the top
        • +
        • Redesign of all other HUD elements...
        • +
        + +
      + +
    • Added possibility to give every player his own random shop
    • +
    • Added sprint to TTT2
    • +
    • Added a drowning indicator into TTT2
    • +
    • Added possibility to toggle auto afk
    • +
    • Added possibility to modify the time a player can dive
    • +
    • Added parameter to the bindings to detect if a player released a key
    • +
    • Added possibility to switch between team-synced and individual random shops
    • +
    • Added some nice badges to the scoreboard
    • +
    • Added a sql library for saving and loading specific elements (used by the HUD system)
    • +
    • Added and fixed Bulls draw lib
    • +
    • Added an improved player-target add-on
    • +
    +
    +

    Changed:

    +
      +
    • Changed convar ttt2_confirm_killlist to default 1
    • +
    • Improved the changelog (as you can see :D)
    • +
    • Improved Workshop page :)
    • +
    • Reworked F1 menu
    • +
    • Reworked the slot system (by LeBroomer)
    • +
        +
      • Amount of carriable weapons are customizable
      • +
      • Weapons won't block slots anymore automatically
      • +
      • Slot positions are generated from weapon types
      • +
      + +
    • Reworked the ROLES system
    • +
        +
      • Roles can now be created like weapons utilizing the new roles lua module
      • +
      • They can also inherit from other roles
      • +
      +
    +
  • Improved the MSTACK to also support messages with images
  • +
  • Confirming a player will now display an imaged message in the message stack
  • + +
    +

    Fixed:

    +
      +
    • Fixed TTT2 binding system
    • +
    • Fixed Ammo pickup
    • +
    • Fixed karma issue with detective
    • +
    • Fixed DNA Scanner bug with the radar
    • +
    • Fixed loading issue in the items module
    • +
    • Fixed critical bug in the process of saving the items data
    • +
    • Fixed the Sidekick color in corpses
    • +
    • Fixed bug that detective were not able to transfer credits
    • +
    • Fixed kill-list confirm role syncing bug
    • +
    • Fixed crosshair reset bug
    • +
    • Fixed confirm button on corpse search still showing after the corpse was confirmed or when a player was spectating
    • +
    • Fixed draw.GetWrappedText() containing a ' ' in its first row
    • +
    • Fixed shop item information not readable when the panel is too small -> added a scrollbar
    • +
    • Fixed shop item being displayed as unbuyable when the items price is set to 0 credits
    • +
    • Other small bugfixes
    • +
    + ]], + os.time({ year = 2019, month = 03, day = 03 }) + ) + + AddChange( + "TTT2 Base - v0.5.1b", + [[ +

    Improved:

    +
      +
    • Improved the binding library and extended the functions
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed target reset bug
    • +
    • Fixed / Adjusted hud element resize handling
    • +
    • Fixed strange weapon switch error
    • +
    • Fixed critical model reset bug
    • +
    • Fixed hudelements borders, childs will now extend the border of their parents
    • +
    • Fixed mstack shadowed text alpha fadeout
    • +
    • Fixed / Adjusted scaling calculations
    • +
    • Fixed render order
    • +
    • Fixed "player has no SteamID64" bug
    • +
    + ]], + os.time({ year = 2019, month = 03, day = 05 }) + ) + + AddChange( + "TTT2 Base - v0.5.2b", + [[ +

    New:

    +
      +
    • Added spectator indicator in the Miniscoreboard
    • +
    • Added icons with higher resolution (native 512x512)
    • +
    • Added Heroes badge
    • +
    • Added HUD documentation
    • +
    • Added some more hooks to modify TTT2 externally
    • +
    +
    +

    Improved:

    +
      +
    • Improved the project structure / Code refactoring
    • +
    • Improved HUD sidebar of items / perks
    • +
    • Improved HUD loading
    • +
    • Improved the sql library
    • +
    • Improved item handling and item converting
    • +
    • Improved performance / performance optimization
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed SetModel bugs
    • +
    • Fixed some model-selector bugs
    • +
    • Fixed a sprint bug
    • +
    • Fixed some ConVars async bugs
    • +
    • Fixed some shopeditor bugs
    • +
    • Fixed an HUD scaling bug
    • +
    • Fixed old_ttt HUD
    • +
    • Fixed border's alpha value
    • +
    • Fixed confirmed player ordering in the Miniscoreboard
    • +
    • Fixed critical TTT2 bug
    • +
    • Fixed a crash that randomly happens in the normal TTT
    • +
    • Fixed PS2 incompatibility
    • +
    • Fixed spectator bug with Roundtime and Haste Mode
    • +
    • Fixed many small HUD bugs
    • +
    + ]], + os.time({ year = 2019, month = 04, day = 32 }) + ) + + AddChange( + "TTT2 Base - v0.5.3b", + [[ +

    New:

    +
  • Added a Reroll System for the Random Shop
  • +
      +
    • Rerolls can be done in the Traitor Shop similar to the credit transferring
    • +
    • Various parameters in the ShopEditor are possible (reroll, cost, reroll per buy)
    • +
    • Added the reroll possibility for the Team Random Shop (very funny)
    • +
    +
    +

    Improved:

    +
      +
    • Added a separate slot for class items
    • +
    • Added help text to the "Not Alive"-Shop
    • +
    • Minor design tweaks
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed critical crash issue (ty to @nick)
    • +
    + ]], + os.time({ year = 2019, month = 05, day = 09 }) + ) + + AddChange( + "TTT2 Base - v0.5.4b", + [[ +

    New:

    +
      +
    • Added a noTeam indicator to the HUD
    • +
    • intriduced a new drowned death symbol
    • +
    • ttt2_crowbar_shove_delay is now used to set the crowbar attack delay
    • +
    • introduced a status system alongside the perk system
    • +
    +
    +

    Improved:

    +
      +
    • Included LeBroomer in the TTT2 logo
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed a problem with colors (seen with sidekick death confirms)
    • +
    • Fixed the team indicator when in spectator mode
    • +
    • Credits now can be transfered to everyone, this fixes a bug with the spy
    • +
    + ]], + os.time({ year = 2019, month = 06, day = 18 }) + ) + + AddChange( + "TTT2 Base - v0.5.5b", + [[ +

    New:

    +
      +
    • Added convars to hide scoreboard badges
    • +
    • A small text that explains the spectator mode
    • +
    • Weapon pickup notifications are now moved to the new HUD system
    • +
    • Weapon pickup notifications support now pure_skin
    • +
    • New shadowed text rendering with font mipmapping
    • +
    +
    +

    Improved:

    +
      +
    • Refactored the code to move all language strings into the language files
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed the reroll per buy bug
    • +
    • Fixed HUD switcher bug, now infinite amounts of HUDs are supported
    • +
    • Fixed the outline border of the sidebar beeing wrong
    • +
    • Fixed problem with the element restriction
    • +
    + ]], + os.time({ year = 2019, month = 07, day = 07 }) + ) + + AddChange( + "TTT2 Base - v0.5.6b", + [[ +

    New:

    +
      +
    • Marks module
    • +
    • New sprint and stamina hooks for add-ons
    • +
    • Added a documentation of TTT2
    • +
    • Added GetColumns function for the scoreboard
    • +
    +
    +

    Improved:

    +
      +
    • Restrict HUD element movement when element is not rendered
    • +
    • Sidebar icons now turn black if the hudcolor is too bright
    • +
    • Binding functions are no longer called when in chat or in console
    • +
    • Improved the functionality of the binding system
    • +
    • Improved rendering of overhead role icons
    • +
    • Improved equipment searching
    • +
    • Improved the project structure
    • +
    • Improved shadow color for dark texts
    • +
    • Some small performance improvements
    • +
    • Removed some unnecessary debug messages
    • +
    • Updated the TTT2 .fgd file
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed the shop bug that occured since the new GMod update
    • +
    • Fixed search sometimes not working
    • +
    • Fixed a shop reroll bug
    • +
    • Fixed GetAvoidDetective compatibility
    • +
    • Fixel two cl_radio errors
    • +
    • Fixed animation names
    • +
    • Fixed wrong karma print
    • +
    • Fixed a language module bug
    • +
    • Fixed an inconsistency in TellTraitorsAboutTraitors function
    • +
    • Fixed the empty weaponswitch bug on first spawn
    • +
    • Fixed an error in the shadow rendering of a font
    • +
    • Small bugfixes
    • +
    + ]], + os.time({ year = 2019, month = 09, day = 03 }) + ) + + AddChange( + "TTT2 Base - v0.5.6b-h1 (Hotfix)", + [[ +

    Fixed:

    +
      +
    • Traitor shop bug (caused by the september'19 GMod update)
    • +
    • ShopEditor bugs and added a response for non-admins
    • +
    • Glitching head icons
    • +
    + ]], + os.time({ year = 2019, month = 09, day = 06 }) + ) + + AddChange( + "TTT2 Base - v0.5.7b", + [[ +

    New:

    +
      +
    • New Loadout Give/Remove functions to cleanup role code and fix item raceconditions
    • +
        +
      • Roles now always get their equipment on time
      • +
      • On role changes, old equipment gets removed first
      • +
      +
    • New armor system
    • +
        +
      • Armor is now displayed in the HUD
      • +
      • Previously it was a simple stacking percetual calculation, which got stupid with multiple armor effects. Three times armoritems with 70% damage each resulted in 34% damage recveived
      • +
      • The new system has an internal armor value that is displayed in the HUD
      • +
      • It works basically the same as the vanilla system, only the stacking is a bit different
      • +
      • Reaching an armor value of 50 (by default) increases its strength
      • +
      • Armor depletes over time
      • +
      +
    • Allowed items to be bought multiple times, if .limited is set to true
    • +
    +
    +

    Improved:

    +
      +
    • Dynamic loading of role icons
    • +
    • Improved performance slightly
    • +
    • Improved code consistency
    • +
    • Caching role icons in ROLE.iconMaterial to prevent recreation of icon materials
    • +
    • Improved bindings menu and added language support
    • +
    • Improved SQL module
    • +
    • Improved radar icon
    • +
    • Made parameterized overheadicon function callable
    • +
    • Improved codereadablity by refactoring huge parts
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed sometimes unavailable shops even if set
    • +
    • Team confirmation convar issue in network-syncing
    • +
    • Reset radar timer on item remove, fixes problems with rolechanges
    • +
    • Fixed an exploitable vulnerability
    • +
    + ]], + os.time({ year = 2019, month = 10, day = 06 }) + ) + + AddChange( + "TTT2 Base - v0.6b", + [[ +

    New:

    +
      +
    • Added new weapon switch system
    • +
        +
      • Players can now manually pick up focused weapons
      • +
      • If the slot is blocked, the current weapon is automatically dropped
      • +
      • Added new convar to prevent auto pickup: ttt_weapon_autopickup (default: 1)
      • +
      +
    • Added new targetID system
    • +
        +
      • Looking at entities shows now more detailed ans structured info
      • +
      • Integrated into the new weapon switch system
      • +
      • Supports all TTT entities by default
      • +
      • Supports doors
      • +
      • Added a new hook to add targetID support to custom entities: TTTRenderEntityInfo
      • +
      +
    • Added the outline module for better performance
    • +
    • TTT2DropAmmo hook to prevent/change the ammo drop of a weapon
    • +
    • Added new HUD element: the eventpopup
    • +
    • Added a new TTT2PlayerReady hook that is called once a player is ingame and can move around
    • +
    • Added new removable decals
    • +
    • Added a new default loading screen
    • +
    • Added new convar to allow Enhanced Player Model Selector to overwrite TTT2 models: ttt_enforce_playermodel (default: 1)
    • +
    • Added new hooks to jam the chat
    • +
    • Allow any key double tap sprint
    • +
    • Regenerate sprint stamina after a delay if exhausted
    • +
    • Moved voice bindings to the TTT2 binding system (F1 menu)
    • +
    • Added new voice and text chat hooks to prevent the usage in certain situations
    • +
    • Added new client only convar (F1->Gameplay->Hold to aim) to change to a "hold rightclick to aim" mode
    • +
    • Added a new language file system for addons, language files have to be in lua/lang//.lua
    • +
    • New network data system including network data tables to better manage state updates on different clients
    • +
    +
    +

    Improved:

    +
      +
    • Microoptimization to improve code performance
    • +
    • Improved the icon rendering for the pure_skin HUD
    • +
    • Improved multi line text rendering in the MSTACK
    • +
    • Improved role color handling
    • +
    • Improved language (german, english, russian)
    • +
    • Improved traitor buttons
    • +
        +
      • By default only players in the traitor team can use them
      • +
      • Each role has a convar to enable traitor button usage for them - yes, innocents can use traitor buttons if you want to
      • +
      • There is an admin mode to edit each button individually
      • +
      • Uses the new targetID
      • +
      +
    • Improved damage indicator overlay with customizability in the F1 settings
    • +
    • Improved hud help font
    • +
    • Added a new flag to items to hide them from the bodysearch panel
    • +
    • Moved missing hardcoded texts to language files
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed a bug with baserole initialization
    • +
    • Small other bugfixes
    • +
    • added legacy supported for limited items
    • +
    • Fixed C4 defuse for non-traitor roles
    • +
    • Fixed radio, works now for all roles
    • +
    • Restricted huds are hidden in HUD Switcher
    • +
    • Fixed ragdoll skins (hairstyles, outfits, ...)
    • +
    • Prevent give_equipment timer to block the shop in the next round
    • +
    • Fix sprint consuming stamina when there is no move input
    • +
    • Fix confirmation of players with no team
    • +
    • Fix voice still sending after death
    • +
    + ]], + os.time({ year = 2020, month = 02, day = 16 }) + ) + + AddChange( + "TTT2 Base - v0.6.1b", + [[ +

    Fixed:

    +
      +
    • Fixed a bug with the spawn wave interval
    • +
    + ]], + os.time({ year = 2020, month = 02, day = 17 }) + ) + + AddChange( + "TTT2 Base - v0.6.2b", + [[ +

    Fixed:

    +
      +
    • Increased the maximum number of roles that can be used. (Fixes weird role issues with many roles installed)
    • +
    + ]], + os.time({ year = 2020, month = 03, day = 1 }) + ) + + AddChange( + "TTT2 Base - v0.6.3b", + [[ +

    New:

    +
      +
    • Added a Polish translation (Thanks @Wukerr)
    • +
    • Added fallback icons for equipment
    • +
    +
    +

    Fixed:

    +
      +
    • Fix body_found for bots
    • +
    • Fix NWVarSyncing when using TTT2NET:Set()
    • +
    + ]], + os.time({ year = 2020, month = 03, day = 05 }) + ) + + AddChange( + "TTT2 Base - v0.6.4b", + [[ +

    New:

    +
      +
    • Added an Italian translation (Thanks @PinoMartirio)
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed a rare bug where the player had the default GMod sprint on top of the TTT2 sprint
    • +
    • Fixed some convars that did not save in ulx by removing them all from the gamemode file
    • +
    • Fixed a bug that happened when TTT2 is installed but not the active gamemode
    • +
    • Fixed a few Polish language strings
    • +
    + ]], + os.time({ year = 2020, month = 04, day = 03 }) + ) + + AddChange( + "TTT2 Base - v0.7.0b", + [[ +

    New:

    +
      +
    • Added two new convars to change the behavior of the armor
    • +
    • Added two new convars to change the confirmation behaviour
    • +
    • Added essential items: 8 different types of items that are often used in other addons. You can remove them from the shop if you don't like them.
    • +
    • Added a new HUD element to show information about an ongoing revival to the player that is revived
    • +
    • Added the possibility to change the radar time
    • +
    • Added a few new modules that are used by TTT2 and can be used by different addons
    • +
    +
    +

    Improved:

    +
      +
    • Updated addon checker list
    • +
    • Migrated the HUDManager settings to the new network sync system
    • +
    • Reworked the old DNA Scanner and replaced it with an improved version
    • +
        +
      • New world- and viewmodel with an interactive screen
      • +
      • Removed the overcomplicated UI menu (simple handling with default keys instead)
      • +
      • The new default scanner behavior shows the direction and distance to the target
      • +
      +
    • Changed TargetID colors for confirmed bodies
    • +
    • Improved the player revival system
    • +
        +
      • Revive makes now sure the position is valid and the player is not stuck in the wall
      • +
      • All revival related messages are now localized
      • +
      • Integration with the newly added revival HUD element
      • +
      +
    • Improved the player spawn handling, no more invalid spawn points where a player will be stuck or spawn in the air and fall to their death
    • +
    • Refactored the role selection code to reside in its own module and cleaned up the code
    • +
    • Improved the round end screen to support longer round end texts
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed round info (the top panel with the miniscoreboard) being displayed in other HUDs
    • +
    • Fixed an error with the pickup system in singleplayer
    • +
    • Fixed propsurfing with the magneto stick
    • +
    • Fixed healthstation TargetID text
    • +
    • Fixed keyinfo for doors where no key can be used
    • +
    • Fixed role selection issues with subroles not properly replacing their baserole etc
    • +
    • Fixed map lock/unlock trigger of doors not updating targetID
    • +
    • Fixed roles having sometimes the wrong radar color
    • +
    • Fixed miniscoreboard update issue and players not getting shown when entering force-spec mode
    • +
    + ]], + os.time({ year = 2020, month = 06, day = 01 }) + ) + + AddChange( + "TTT2 Base - v0.7.1b", + [[ +

    Fixed:

    +
      +
    • Fixed max roles / max base roles interaction with the roleselection. Also does not crash with values != 0 anymore.
    • +
    + ]], + os.time({ year = 2020, month = 06, day = 02 }) + ) + + AddChange( + "TTT2 Base - v0.7.2b", + [[ +

    New:

    +
      +
    • Added Hooks to the targetID system to modify the displayed data
    • +
    • Added Hooks to interact with door destruction
    • +
    • Added a new function to force a new radar scan
    • +
    • Added a new convar to change the default radar time for players without custom radar times: ttt2_radar_charge_time
    • +
    • Added a new client ConVar ttt_crosshair_lines to add the possibility to disable the crosshair lines
    • +
    +
    +

    Improved:

    +
      +
    • Moved the disguiser icon to the status system to be only displayed when the player is actually disguised
    • +
    • Reworked the addonchecker and added a command to execute the checker at a later point
    • +
    • Updated Italian translation (Thanks @ThePlatinumGhost)
    • +
    • Removed Is[ROLE] functions of all roles except default TTT ones
    • +
    • ttt_end_round now resets when the map changes
    • +
    • Reworked the SWEP HUD help (legacy function SWEP:AddHUDHelp is still supported)
    • +
    • Players who disconnect now leave a corpse
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed shadow texture of the "Pure Skin HUD" for low texture quality settings
    • +
    • Fixed inno subrole upgrading if many roles are installed
    • +
    • Fixed and improved the radar role/team modification hook
    • +
    • Fixed area portals on servers for destroyed doors
    • +
    • Fixed revive fail function reference reset
    • +
    • Removed the DNA Scanner hudelement for spectators
    • +
    • Fixed the image in the confirmation notification whenever a bot's corpse gets identified
    • +
    • Fixed bad role selection due to RNG reseeding
    • +
    • Fixed missing role column translation
    • +
    • Fixed viewmodel not showing correct hands on model change
    • +
    + ]], + os.time({ year = 2020, month = 06, day = 26 }) + ) + + AddChange( + "TTT2 Base - v0.7.3b", + [[ +

    New:

    +
      +
    • Added a new custom file loader that loads lua files from lua/terrortown/autorun/
    • +
        +
      • it basically works the same as the native file loader
      • +
      • there are three subfolders: client, server and shared
      • +
      • the files inside this folder are loaded after all TTT2 gamemode files and library extensions are loaded
      • +
      +
    • Added Spanish version for base addon (by @Tekiad and @DennisWolfgang)
    • +
    • Added Chinese Simplified translation (by @TheOnly8Z)
    • +
    • Added double-click buying
    • +
    • Added a default avatar for players and an avatar for bots
    • +
    +
    +

    Improved:

    +
      +
    • Roles are now only getting synced to clients if the role is known, not just the body being confirmed
    • +
    • Airborne players can no longer replenish stamina
    • +
    • Detective overhead icon is now shown to innocents and traitors
    • +
    • moved language files from lua/lang/ to lua/terrortown/lang
    • +
    • Stopped teleporting players to players they're not spectating if they press the "duck"-Key while roaming
    • +
    • Moved shop's equipment list generation into a coroutine
    • +
    • Removed TTT2PlayerAuthedCacheReady hook
    • +
    • Internal changes to the b-draw library for fetching avatars
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed death handling spawning multiple corpses when killed multiple times in the same frame
    • +
    • Radar now shows bombs again, that do not have the team property set
    • +
    • Fix HUDManager not saving forcedHUD and defaultHUD values
    • +
    • Fixed wrong parameter default in EPOP:AddMessage documentation
    • +
    • Fixed shop switching language issue
    • +
    • Fixed shop refresh activated even not buyable equipments
    • +
    • Fixed wrong shop view displayed as forced spectator
    • +
    + ]], + os.time({ year = 2020, month = 08, day = 09 }) + ) + + AddChange( + "TTT2 Base - v0.7.4b", + [[ +

    New:

    +
      +
    • Added ConVar to toggle double-click buying
    • +
    • Added Japanese translation (by @Westoon)
    • +
    • Added table.ExtractRandomEntry(tbl, filterFn) function
    • +
    • Added a team indicator in front of every name in the scoreboard (just known teams will be displayed)
    • +
    • Added a hook TTT2ModifyCorpseCallRadarRecipients that is called once "call detective" is pressed
    • +
    +
    +

    Improved:

    +
      +
    • The weapon pickup system has been improved to increase stability and remove edge cases in temporary weapon teleportation
    • +
    • Updated Spanish translation (by @DennisWolfgang)
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed foregoing avatar fetch fix
    • +
    • Fixed HUD savingKeys variable not being unique across all HUDs
    • +
    • Fixed drawing web images, seamless web images and avatar images
    • +
    • Fixed correctly saving setting a bind to NONE, while a default is defined
    • +
    • Fixed a weapon pickup targetID bug where the +use key was displayed even though pickup has its own keybind
    • +
    • Fixed DNA scanner crash if using an old/different weapon base
    • +
    • Fixed rare initialization bug in the speed calculation when joining as a spectator
    • +
    + ]], + os.time({ year = 2020, month = 09, day = 28 }) + ) + + AddChange( + "TTT2 Base - v0.8.0b", + [[ +

    New:

    +
      +
    • Added new vgui system with new F1 menu
    • +
    • Introduced a global scale factor based on screen resolution to scale HUD elements accordingly
    • +
    • Added automatical scale factor change on resolution change that works even if the resolution was changed while TTT2 wasn't loaded
    • +
    • Added Drag&Drop role layering VGUI (preview), accessible with the console command ttt2_edit_rolelayering
    • +
    • Added a new event system
    • +
    • Credits can now be transferred across teams and from roles whom the recipient does not know
    • +
    +
    +

    Improved:

    +
      +
    • TargetID text is now scaled with the global scale factor
    • +
    • Removed C4 defuse restriction for teammates
    • +
    • Changed the language identifiers to generic english names
    • +
    • Updated Simplified Chinese localization (by @TheOnly8Z)
    • +
    • Updated Italian localization (by @ThePlatynumGhost)
    • +
    • Updated English localization (by @Satton2)
    • +
    • Updated Russian localization (by @scientistnt and @Satton2)
    • +
    • Updated German translation (by @Creyox)
    • +
    +
    +

    Fixed:

    +
      +
    • Fixed weapon pickup bug, where weapons would not get dropped but stayed in inventory
    • +
    • Fixed a roleselection bug, where forced roles would not be deducted from the available roles
    • +
    • Fixed a credit award bug, where detectives would receive a pointless notification about being awarded with 0 credits
    • +
    • Fixed a karma bug, where damage would still be reduced even though the karma system was disabled
    • +
    • Fixed a roleselection bug, where invalid layers led to skipping the next layer too
    • +
    • Fixed Magneto Stick ragdoll pinning instructions not showing for innocents when ttt_ragdoll_pinning_innocents is enabled
    • +
    • Fixed a bug where the targetID info broke if the pickup key is unbound
    • +
    + ]], + os.time({ year = 2021, month = 02, day = 06 }) + ) + + AddChange( + "TTT2 Base - v0.8.1b", + [[ +

    Fixed:

    +
      +
    • Fixed inheriting from the same base using the classbuilder in different folders
    • +
    + ]], + os.time({ year = 2021, month = 02, day = 19 }) + ) + + AddChange( + "TTT2 Base - v0.8.2b", + [[ +

    Improved:

    +
      +
    • Added global alias for IsOffScreen function to util.IsOffScreen
    • +
    • Updated Japanese localization (by @Westoon)
    • +
    • Moved rendering modules to libraries
    • +
    • Assigned PvP category to the gamemode.
    • +
    +

    Fixed:

    +
      +
    • TTT: fix instant reload of dropped weapon (by @svdm)
    • +
    • TTT: fix ragdoll pinning HUD for innocents (by @Flapchik)
    • +
    • Fixed outline library not working
    • +
    + ]], + os.time({ year = 2021, month = 03, day = 25 }) + ) + + AddChange( + "TTT2 Base - v0.9.0b", + [[ +

    Added:

    +
      +
    • All new roundend menu
    • +
        +
      • new info panel that shows detailed role distribution during the round
      • +
      • info panel also states detailed score events
      • +
      • new timeline that displays the events that happened during the round
      • +
      • added two new round end conditions: time up and no one wins
      • +
      +
    • Added ROLE_NONE (ID 3 by default)
    • +
        +
      • Players now default to ROLE_NONE instead of ROLE_INNOCENT
      • +
      • Enables the possibility to give Innocents access to a custom shop (shopeditor)
      • +
      +
    • Karma now stores changes
    • +
        +
      • Is shown in roundend menu
      • +
      +
    • Added a new hook TTT2ModifyLogicCheckRole that can be used to modify the tested role for map related role checks
    • +
    • Added the ConVar ttt2_random_shop_items for the number of items in the randomshop
    • +
    • Added per-player voice control by hovering over the mute icon and scrolling
    • +
    + +

    Fixed

    +
      +
    • Updated French translation (by @MisterClems)
    • +
    • Fixed IsOffScreen function being global for compatibility
    • +
    • Fixed a German translation string (by @FaRLeZz)
    • +
    • Fixed a Polish translation by adding new lines (by @Wuker)
    • +
    • Fixed a data initialization bug that appeared on the first (initial) spawn
    • +
    • Fixed silent Footsteps, while crouched bhopping
    • +
    • Fixed issue where base innocents could bypass the TTT2AvoidGeneralChat and TTT2AvoidTeamChat hooks with the team chat key
    • +
    • Fixed issue where roles with unknownTeam could see messages sent with the team chat key
    • +
    • Fixed the admin section label not being visible in the main menu
    • +
    • Fixed the auto resizing of the buttons based on the availability of a scrollbar not working
    • +
    • Fixed reopening submenus of the legacy addons in F1 menu not working
    • +
    • TTT: Fixed karma autokick evasion
    • +
    • TTT: Fixed karma being applied to weapon damage even though karma is disabled
    • +
    + +

    Changed

    +
      +
    • Microoptimization to improve code performance
    • +
    • Converted roles, huds, hudelements, items and pon modules into libraries
    • +
    • Moved bind library to the libraries folder
    • +
    • Moved favorites functions for equipment to the equipment shop and made them local functions
    • +
    • Code cleanup and removed silly negations
    • +
    • Extended some ttt2net functions
    • +
    • Changed bees win to nones win
    • +
    • By default all evil roles are now counted as traitor roles for map related checks
    • +
    • Changed the ConVar ttt2_random_shops to only disable the random shop (if set to 0)
    • +
    • Shopeditor settings are now available in the F1 Menu
    • +
    • Moved the F1 menu generating system from a hook based system to a file based system
    • +
        +
      • removed the hooks TTT2ModifyHelpMainMenu and TTT2ModifyHelpSubMenu
      • +
      • menus are now generated based on files located in lua/terrortown/menus/gamemode/
      • +
      • submenus are generated from files located in folders with the menu name
      • +
      +
    • Menus without content are now always hidden in the main menu
    • +
    • Moved Custom Shopeditor and linking shop to roles to the F1 menu
    • +
    • Moved inclusion of cl_help to the bottom as nothing depends on it, but menus created by it could depend on other client files
    • +
    • Shopeditor equipment is now available in F1 menu
    • +
    • Moved the role layering menu to the F1 menu (administration submenu)
    • +
        +
      • removed the command ttt2_edit_rolelayering
      • +
      +
    • moved the internal path of lang/, vskin/ and events/ (this doesn't change anything for addons)
    • +
    • Sort teammates first in credit transfer selection and add an indicator to them
    • +
    + +

    Removed

    +
      +
    • Removed the custom loading screen (GMOD now only accepts http(s) URLs for sv_loadingurl)
    • +
    + +

    Breaking Changes

    +
      +
    • Adjusted Player:HasRole() and Player:HasTeam() to support simplified role and team checks (no parameter are supported anymore, use Player:GetRole() or Player:GetTeam() instead)
    • +
    • Moved global roleData to the roles library (e.g. INNOCENT to roles.INNOCENT). INNOCENT, TRAITOR etc. is not supported anymore. ROLE_ is still supported and won't be changed.
    • +
    • Shopeditor function ShopEditor.ReadItemData() now only updates a number of key-parameters, must be given as UInt. Messages were changed accordingly (TTT2SESaveItem,TTT2SyncDBItems)
    • +
    • Equipment shop favorite functions are now local and not global anymore (CreateFavTable, AddFavorite, RemoveFavorite, GetFavorites & IsFavorite)
    • +
    + ]], + os.time({ year = 2021, month = 06, day = 19 }) + ) + + AddChange( + "TTT2 Base - v0.9.1b", + [[ +

    Fixed

    +
      +
    • Fixed shop convars not being shared / breaking the shop
    • +
    + ]], + os.time({ year = 2021, month = 06, day = 19 }) + ) + + AddChange( + "TTT2 Base - v0.9.2b", + [[ +

    Fixed

    +
      +
    • Fixed low karma autokick convar
    • +
    • Fixed multi-layer inheritance by introducing a recursion based approach
    • +
    + ]], + os.time({ year = 2021, month = 06, day = 20 }) + ) + + AddChange( + "TTT2 Base - v0.9.3b", + [[ +

    Added

    +
      +
    • Add Traditional Chinese Translation (by @TEGTianFan)
    • +
    • Added a searchbar to submenus
    • +
    • Added full-sized icons to the equipment-editor
    • +
    • Hotreload functionality for weapons, they are now fully compatible to TTT2 after hotreload
    • +
    • Added experimental SWEP.HotReloadableKeys a list of strings to weapons, that makes data saved with weapons.GetStored() persistent across hotreloads
    • +
    • Extended cvars library to support manipulation of serverside ConVars
    • +
    • Added possibility to manipulate serverside ConVars with Checkboxes and Sliders
    • +
        +
      • Just add .serverConvar with the conVarName to the given data similar to .convar
      • +
      +
    + +

    Fixed

    +
      +
    • Updated Japanese translation (by @westooooo)
    • +
    • Fixed text positioning in pure_skin bar (by @LukasMandok)
    • +
    • Fixed data being not persistent after hot reloading
    • +
        +
      • HUDs are now still available
      • +
      • ttt2net keeps its data
      • +
      • bindings are not lost on reload
      • +
      +
    + +

    Changed

    +
      +
    • Revise and additions simplified Chinese (by @TEGTianFan)
    • +
    • Prevent spectators from gathering info on players if they're about to revive (by @AaronMcKenney)
    • +
    • ROLE_NONE does not count as a special role anymore (by @TheNickSkater)
    • +
    + +

    Internal Breaking Changes

    +
      +
    • Removed first argument of GetEquipmentBase(data, equipment), it only takes the equipment as argument now GetEquipmentBase(equipment) and generally merges it with EquipMenuData
    • +
    • Added equipment as argument to InitDefaultEquipmentForRole(roleData), it now only initializes the given equipment not all InitDefaultEquipmentForRole(roleData, equipment)
    • +
    • Added equipment as argument to CleanUpDefaultCanBuyIndices(), it now only initializes the given equipment not all CleanUpDefaultCanBuyIndices(equipment)
    • +
    + ]], + os.time({ year = 2021, month = 09, day = 25 }) + ) + + AddChange( + "TTT2 Base - v0.10.0b", + [[ +

    Added

    +
      +
    • Added a new scoring variable named score.survivePenaltyMultiplier to punish surviving players of a losing team
    • +
    • Added in game spawn editor system that can be found in F1->Administration
    • +
    • Moved all TTT weapons to this repository (with cleaned up code)
    • +
    • Added in four new libraries
    • +
        +
      • map: A library which handles map specific data
      • +
      • entspawn: A library that handles the spawning and spawns of all entity types
      • +
      • entspawnscript: A library that handles the new TTT2 entity spawn script to customize spawns
      • +
      • plyspawn: A library that builds on top of entspawn to handle the more complex player spawn (originally named spawn, see Breaking changes)
      • +
      +
    • Added a new submenu to the administration settings regarding basic role setup
    • +
    • Added a new menu to the F1 menu to set up and configure all installed menus
    • +
    • Added two new hooks to modify the contents of the newly added menu
    • +
        +
      • ROLE:AddToSettingsMenu(parent)
      • +
      • ROLE:AddToSettingsMenuCreditsForm(parent)
      • +
      +
    • Added a new in-game player model selector
    • +
        +
      • Added new convars that can change the way playermodels are selected (these can be found in the gamemode menu)
      • +
      • Added a new ConVar ttt2_use_custom_models (def: 0) to enable the custom player model selector
      • +
      • Added indicator that shows if a model has a headshot hitbox
      • +
      • Added possibility to enable/disable detective hats for individual player models
      • +
      +
    • Added a new admin only menu for server addon settings
    • +
    • Added automatic default values for serverConVars
    • +
    • Added two new role variables:
    • +
        +
      • isPublicRole: This makes the role behave like a detective in such a way, that the role is public known and shown in the scoreboard. This means other roles can use this without special role syncing; additionally roles with that flag will be handled like a detective if killed by an 'evil' role, meaning that they will receive a credit bonus
      • +
      • isPolicingRole: This rolevar adds all "detective-like" features to the detective, for example the ability to be called to a corpse etc.
      • +
      +
    • Added two new role conVar variables:
    • +
        +
      • creditsAwardDeadEnable: To award this role if a certain percentage of players from the enemy teams died
      • +
      • creditsAwardKillEnable: To award this role if they killed a high value public role
      • +
      +
    + +

    Changed

    +
      +
    • Split up kill, suicide and teamkill in the round end screen to make it more clear
    • +
    • Decreased the minimum cost of equipment in the equipment editor to 0
    • +
    • Changed disguise such that every role can now use the function
    • +
    • Completely reworked how weapons, ammo and players spawn in the world
    • +
    • Sliders only update ConVars on mouseRelease now
    • +
    • Changed the way credits on kills are distributed in a way that non-default roles can easily use this as well
    • +
    + +

    Breaking Changes

    +
      +
    • Removed the (unused?) ConVar ttt2_custom_models
    • +
    • Removed the function GetRandomPlayerModel(), use playermodels.GetRandomPlayerModel() instead
    • +
    • Renamed the spawn module to plyspawn
    • +
    • Hook PlayerSelectSpawn doesnt return a spawnEntity anymore
    • +
    • SpawnWillingPlayers is deleted and not available anymore
    • +
    • renamed the ttt_credits_starting to ttt_traitor_credits_starting to be more in-line with all other roles
    • +
    • WARNING: This means that every traitor now starts with 0 credits until the convar reset button is pressed (on existing servers)
    • +
    • removed the alone_bonus convar because it only complicated the credits system further without adding much benefit
    • +
    + ]], + os.time({ year = 2021, month = 10, day = 14 }) + ) + + AddChange( + "TTT2 Base - v0.10.1b", + [[ +

    Fixed

    +
      +
    • Fixed Playermodels not correctly loading changes on game start
    • +
    • Fixed setting defaults before assigning a resetButton not throwing an error anymore
    • +
    • Fixed invisible preview for entity spawn placements
    • +
    + ]], + os.time({ year = 2021, month = 10, day = 15 }) + ) + + AddChange( + "TTT2 Base - v0.10.2b", + [[ +

    Added

    +
      +
    • Added a new hook GM:TTT2ModifyRadioTarget to modify the current radio target
    • +
    • Added documentation to all hooks
    • +
    + +

    Fixed

    +
      +
    • Fixed the reset button not working for Sliders in the F1 Menu
    • +
    • Fixed defuser only working for detectives
    • +
    • Fixed some weapon packs like ArcCW to be working again, weapons are now initialized with ttt2 variables after the InitPostEntity hook
      + Note: This might only take effect after a reinstall or a reset of the server; if you don't want to reset your server, you have to change the spawnability manually in the equipment editor or delete the sql table "ttt2_items" of the sv.db to force a reset only on equipment
    • +
    + +

    Changed

    +
      +
    • Changed the Sliders to only update after dragging ends, no matter where you clicked on the slider before dragging
    • +
    • Changed TTTPlayerUsedHealthStation hook, return false to cancel health regeneration tick
    • +
    • Changed all C4 hooks to be cancelable
    • +
    + +

    Removed

    +
      +
    • Removed old concommand shopeditor and the old shopeditor
    • +
    + +

    Breaking Changes

    +
      +
    • Renamed hook GM:TTT2CheckWeaponForID to GM:TTT2RegisterWeaponID better fitting its purpose as its probably nowhere used yet anyway
    • +
    + ]], + os.time({ year = 2021, month = 10, day = 21 }) + ) + + AddChange( + "TTT2 Base - v0.10.3b", + [[ +

    Fixed

    +
      +
    • Fixed the hook scope in the disguiser causing an error
    • +
    • Fixed the classic entity spawn mode breaking on maps without all three spawn types
    • +
    • Fixed weapons not using their average firerate with a tickrate dependent fix. Function SWEP:SetNextPrimaryFire(nextTime) was overwritten with our fix SWEP:SetNextPrimaryFire(nextTime, skipTickrateFix) +
    + +

    Changed

    +
      +
    • Added new param skipTickrateFix to SWEP:SetNextPrimaryFire(nextTime, skipTickrateFix) to skip our inbuilt tickrate fix
    • +
    + ]], + os.time({ year = 2021, month = 10, day = 29 }) + ) + + AddChange( + "TTT2 Base - v0.11.0b", + [[ +

    Added

    +
      +
    • Added the hook GM:TTT2CalledPolicingRole that is called after all policing role players were called to a corpse
    • +
    • Added all TTT2 convars into the F1 menu
    • +
        +
      • most convars are located in the administration menu
      • +
      • equipment specific settings can be found in the edit equipment menu
      • +
      +
    • Added icon to the magneto stick
    • +
    • Added the function AddToSettingsMenu to both SWEP and ITEM to add settings to the equipment menu
    • +
    • Added the role flag .isOmniscientRole; if set to true the role is able to see missing in action players and the haste mode time
    • +
    • Added GM:TTT2ModifyOverheadIcon to add, remove or modify the overhead icons of players
    • +
    + +

    Fixed

    +
      +
    • Fixed that every policing player could be called to a corpse, this is now again restricted to alive only players
    • +
    • Fixed inconsistency between .disabledTeamChatRecv and .disabledTeamChatRec
    • +
    • Fixed non-public policing roles having hats and therefore confirming them
    • +
    • Fixed triggered spawns on maps like ttt_lttp_kakariko_a5 with the vases and ttt_mc_jondome with the chests
    • +
    • Fixed roleselection layering with base roles to ensure layer order is considered correctly when selecting roles
    • +
    • Fixed hotreloading items
    • +
    • Fixed random playermodel selection on map change not working
    • +
    • Fixed ply:Give sometimes picking up all surrounding weapon entities, if auto pickup is enabled
    • +
    • Fixes weapon pickup sometimes causing floating weapons
    • +
    • Fixes weapon pickup sometimes failing if a weapon with the same class as a weapon in the inventory should be picked up
    • +
    + +

    Changed

    +
      +
    • All public policing roles now appear as detectives in the chat
    • +
    • Change blocking revival mode from true/false to
    • +
        +
      • REVIVAL_BLOCK_NONE: don't block the winning condition during the revival process [default, previously nil/false]
      • +
      • REVIVAL_BLOCK_AS_ALIVE: only block the winning condition, if the player being alive would change the outcome [previously true]
      • +
      • REVIVAL_BLOCK_ALL: block the winning condition until the revival process is ended
      • +
      • the old arguments still work, they are automatically converted
      • +
      +
    • Changed logs folder to terrortown/logs/ to be inline with everything else
    • +
    • Added more role agnostics
    • +
        +
      • voice drain rate is now no longer bound to Detectives but to all public policing roles
      • +
      • Karma multiplier is now no longer bound to Detectives but to all public policing roles
      • +
      • all non-innocent roles are now able to pin ragdolls if enabled (previous only Traitors could do this)
      • +
      +
    • Overhead icons are now also either colored black or white depending on the role's color
    • +
    + +

    Breaking Changes

    +
      +
    • Renamed some convars to be inline with our opt-in style, all values were changed so that the default value is kept
    • +
        +
      • ttt_no_prop_throwing is now ttt_prop_throwing
      • +
      • ttt_limit_spectator_chat is now ttt_spectators_chat_globally
      • +
      • ttt_no_nade_throw_during_prep is now ttt_nade_throw_during_prep
      • +
      • ttt_armor_classic is now ttt_armor_dynamic
      • +
      +
    + ]], + os.time({ year = 2021, month = 11, day = 15 }) + ) + + AddChange( + "TTT2 Base - v0.11.1b", + [[ +

    Added

    +
      +
    • Added four new Karma multipliers as role variables. They are applied **after** all other Karma calculations are done:
    • +
        +
      • ROLE.karma.teamKillPenaltyMultiplier: The multiplier that is used to calculate the Karma penalty for a team kill
      • +
      • ROLE.karma.teamHurtPenaltyMultiplier: The multiplier that is used to calculate the Karma penalty for team damage
      • +
      • ROLE.karma.enemyKillBonusMultiplier: The multiplier that is used to calculate the Karma given to the killer if a player from an enemy team is killed
      • +
      • ROLE.karma.enemyHurtBonusMultiplier: The multiplier that is used to calculate the Karma given to the attacker if a player from an enemy team is damaged
      • +
      +
    + +

    Fixed

    +
      +
    • Fixed ply:Give(weapon) to work again, when weapons are cached, fixing the spawneditor to work again
    • +
    • Fixed spawneditor not causing errors, when going through walls due to many steps
    • +
    • Set default traitor button variable back to 0
    • +
    • Fixed unchanged or unscaled damage being sent to the client, leading to a wrongly working damage-overlay
    • +
    + ]], + os.time({ year = 2021, month = 11, day = 16 }) + ) + + AddChange( + "TTT2 Base - v0.11.2b", + [[ +

    Fixed

    +
      +
    • Fixed correct role Karma multipliers used in Karma-module
    • +
    + ]], + os.time({ year = 2021, month = 11, day = 17 }) + ) + + AddChange( + "TTT2 Base - v0.11.3b", + [[ +

    Fixed

    +
      +
    • Fixed Equipment-Editor not showing the current synced values, but the cached ones
    • +
    + +

    Changed

    +
      +
    • Changed serverConVars not indexing with 0 in tables (could cause issues when iterating)
    • +
    + ]], + os.time({ year = 2021, month = 11, day = 18 }) + ) + + AddChange( + "TTT2 Base - v0.11.4b", + [[ +

    Changed

    +
      +
    • Switched from the voicerecord commands to the GMod permission system due to a recent GMod update breaking the old voice chat
    • +
    • Updated Japanese translation (by @westooooo)
    • +
    + ]], + os.time({ year = 2021, month = 12, day = 17 }) + ) + + AddChange( + "TTT2 Base - v0.11.5b", + [[ +

    Added

    +
      +
    • Reworked our simplified Dropdowns MakePanel PANEL:MakeComboBox(data) version
    • +
        +
      • Added possibility to manipulate serverside ConVars with Dropdowns. Just add .serverConvar with the conVarName to the given data similar to .convar
      • +
      • .serverConVar and .conVar are also supported
      • +
      • data.choices can now be a table containing {title, value, select, icon, additionalData}
      • +
      • data.selectValue is added, use it instead of data.selectName to choose the value you set
      • +
      • data.selectTitle is added and shall replace data.selectName
      • +
      +
    • New setting to disable session limits entirely. (by @Reispfannenfresser)
    • +
    • Added GM:TTT2AdminCheck hook
    • +
        +
      • Replaced all IsSuperAdmin() checks with this hook
      • +
      • This hook can be used to allow custom usergroups through these checks
      • +
      +
    • Added convars to modify how fall damage is applied
    • +
        +
      • ttt2_falldmg_enable (default: 1) toggles whether or not to apply fall damage at all
      • +
      • ttt2_falldmg_min_velocity (default: 450) sets the minimum velocity threshold for fall damage to occur
      • +
      • ttt2_falldmg_exponent (default: 1.75) sets the exponent to increase fall damage in relation to velocity
      • +
      • All these convars can also be adjusted in the F1->Administration->Player Settings menu
      • +
      +
    • Added portuguese translation
    • +
    • Added a database library, that handles shared Interaction with the sql database
    • +
    + +

    Breaking Changes

    +
      +
    • Reworked Dropdowns Panel DComboBoxTTT2 itself
    • +
        +
      • PANEL:AddChoice(title, value, select, icon, data) now uses the second argument as value string for setting convars, use the fifth argument for special data instead
      • +
      • PANEL:ChooseOption(title, index, ignoreConVar) is deprecated and no longer chooses the displayed text, only per index
      • +
      +
    • Reworked our simplified Dropdowns MakePanel PANEL:MakeComboBox(data) version
    • +
        +
      • data.OnChange(value, additionalData, comboBoxPanel) is now called with the two important arguments at first. They are the value that e.g. convars are set, the additionalData and the Panel
      • +
      +
    + +

    Changed

    +
      +
    • Corrected incorrect translation (by @sbzlzh)
    • +
    • Optimized damage indicator vgui images to be smaller
    • +
    • Improved hotreload of TTT2 roles library with RoleList not being global
    • +
    + +

    Fixed

    +
      +
    • Fixed addon compatibility checker fussing over disabled addons
    • +
    • Fixed ammo entities blocking +use traces
    • +
    • Fixed double call of GM:TTT2UpdateTeam, when a role change leads to a team change
    • +
    + ]], + os.time({ year = 2022, month = 08, day = 21 }) + ) + + AddChange( + "TTT2 Base - v0.11.6b", + [[ +

    Changed

    +
      +
    • Fixed and updated the Chinese translation file (by @sbzlzh)
    • +
    • Updated Japanese translation (by @westooooo)
    • +
    • Updated Simplified and Traditional Chinese (by @TEGTianFan)
    • +
    • Add placeholder message to the ingame ttt2 guide (F1 Menu)
    • +
    + +

    Fixed

    +
      +
    • Fixed the spawn editor tool not having a TargetID in some scenarios by always rendering the 'ttt_spawninfo_ent' (by @NickCloudAT)
    • +
    • Roleselection for a lot of roles now considers all possible subroles one after another
    • +
    • Fixed portuguese translation of the equipment editor not working
    • +
    + ]], + os.time({ year = 2022, month = 09, day = 25 }) + ) + + AddChange( + "TTT2 Base - v0.11.7b", + [[ +

    Added

    +
      +
    • Added a new font in default_skin.lua to fit the localization (by @Satton2)
    • +
    • Fixed knife death effect being permanently applied on every following death
    • +
    • Added PANEL:MakeTextEntry(data) to DFormTTT2 for strings or string-backed cvars (by @EntranceJew)
    • +
    • Allow admin spectators to enter "Spawn Edit" mode. (by @EntranceJew)
    • +
    • Added cvar ttt2_bots_lock_on_death (default: 0) to prevent bots from causing log-spam while wandering as spectators. (by @EntranceJew)
    • +
    • Added TTT2ModifyFinalRoles hook for last minute opportunity to override role distribution prior to them being announced for the first time (by @EntranceJew)
    • +
    • weapon_tttbase:
    • +
        +
      • Added SWEP:ShouldRemove to facilitate intercepting SWEP:Remove (by @EntranceJew)
      • +
      • Added SWEP.damageScaling for weapons that utilize ShootBullet (by @EntranceJew)
      • +
      +
    • Edit Equipment Menu
    • +
        +
      • AllowDrop can now be overridden per-weapon (by @EntranceJew)
      • +
      • Kind can now be overridden per-weapon (by @EntranceJew)
      • +
      • overrideDropOnDeath now permits forcing weapons to be dropped instead of removed on death (by @EntranceJew)
      • +
      • "Damage Scaling" editable under "Balance Settings" (by @EntranceJew)
      • +
      +
    • vgui.CreateTTT2Form passes the name on so that it can be accessed via Panel:GetName() (by @EntranceJew)
    • +
    • Added two GAMEMODE hooks to provide the ability for additional addons to extend role/equipment menus.
    • +
        +
      • GM:TTT2OnEquipmentAddToSettingsMenu(equipment, parent)
      • +
          +
        • Called after ITEM:AddToSettingsMenu(parent).
        • +
        +
      • GM:TTT2OnRoleAddToSettingsMenu(role, parent)
      • +
          +
        • Called after ROLE:AddToSettingsMenu(parent)
        • +
        +
      +
    + +

    Changed

    +
      +
    • weapon_tttbase:
    • +
        +
      • Removal of SWEP.IronSightsTime as it was completely unused and conflicts with a networked value intended for the same purpose
      • +
      • Commented-out default values for SWEP.IronSightsPos and SWEP.IronSightsAng to match vanilla TTT behaviour
      • +
          +
        • SWEPs can still use these names as normal, they just don't have a base value to inherit anymore
        • +
        +
      +
    • Updated Russian and English localization files (by @Satton2):
    • +
        +
      • Updated strings in English localization file
      • +
      • Localized outdated and new strings into Russian
      • +
      +
    • Updated all localization files (by @Satton2):
    • +
        +
      • Added missing and new strings
      • +
      • Marked (out-) updated strings
      • +
      • Removed some duplicated strings
      • +
      • Removed some old unused strings
      • +
      • Fixed some broken source strings (line names)
      • +
      +
    • Simplified Chinese and Traditional Chinese localization updates (by @sbzlzh):
    • +
        +
      • Update Simplified Chinese Translation
      • +
      • Improve translation (by @TheOnly8Z)
      • +
      +
    • Localization parameters for {walkkey} + {usekey} prompts made into the predominant style.
    • +
    + +

    Fixed

    +
      +
    • Fixed hotreload of TTT2 roles library by a fresh reinitialization
    • +
    • Fixed a wrong localization line call in roles.lua (by @Satton2)
    • +
    • Fixed +zoom bind
    • +
    • Fixed ttt_quickslot command
    • +
    • Fixed an issue in table.GetEqualEntryKeys when nil is provided instead of a table. (by @sbzlzh):
    • +
        +
      • This fixes spawn problems on maps with invalid spawn points
      • +
      • This fixes errors in the F1 Menu language selection
      • +
      +
    • Fixed the check for dynamic armor being inverted (1 disabled it, 0 enabled it)
    • +
    • Fixed two unmatched ConVars in performance menu (by @NickCloudAT)
    • +
    • Fixed Round End Scoreboard (Round Begin) error if a player disconnected while round with no score events (by @NickCloudAT)
    • +
    • Fixed behavior of entspawn.SpawnRandomAmmo to produce non-deagle ammo. (by @NickCloudAT, mostly)
    • +
    + ]], + os.time({ year = 2023, month = 08, day = 27 }) + ) + + AddChange( + "TTT2 Base - v0.12.0b", + [[ +

    Added

    +
      +
    • added the ability to edit slider numbers directly via an input field by clicking on the number (by @nickcloudat)
    • +
    • Added a new way to alter player volume separately from the scoreboard (by @EntranceJew):
    • +
        +
      • VOICE.(Get/Set)PreferredPlayerVoiceVolume for setting the voice volume instead of Player:SetVoiceVolumeScale
      • +
      • VOICE.(Get/Set)PreferredPlayerVoiceMute for setting the voice mute instead of Player:SetMuted
      • +
      • VOICE.UpdatePlayerVoiceVolume commits / updates the voice setting according to player preferences
      • +
      +
        +
      • Added a convar ttt2_voice_scaling to control voice volume scaling, options like "power4" or "log" cause the volume scaling to have a greater perceptual impact between discrete volume settings.
      • +
      • Added convars ttt2_voice_duck_spectator and ttt2_voice_duck_spectator_amount to lower spectator voice volume automatically.
      • +
          +
        • A value of 0.13 ducks someone's volume at 90% down to effectively 78%, according to the client's scaling mode.
        • +
        +
      +
    • Added the option for DButtonTTT2 to have an icon next to the title (by @TimGoll)
    • +
    • Added a cached equipment item icon to its table as .iconMaterial (by @TimGoll)
    • +
    • Added a new bodysearch library that handles the search (by @TimGoll)
    • +
    • Completely reworked the body search UI (by @TimGoll)
    • +
        +
      • new UI that fits the UI rework
      • +
      • added player model to UI
      • +
      • highlighted player role and team in the UI
      • +
      • redesigned data list so that everything can be seen without clicking through a list
      • +
      • added more details to list like: water level, ground type, kill distance, kill direction, hit group, last damage amount
      • +
      • The UI is now more responsive, it is updated when the server changes states on the body and timers are updated live in the UI
      • +
      +
    • Added that the healthbar will pulsate when below 25% health. Toggleable in F1 Menu (by @NickCloudAT)
    • +
    • Added new menu section in F1 menu under Appearance > Hud Switcher for HudElement based features (by @NickCloudAT)
    • +
    • Brought in code files for ttt_hat_deerstalker, weapon_ttt_phammer, ttt_flame, and weapon_ttt_push.
    • +
    • Translated all strings still needed to german (by @NickCloudAT)
    • +
    • Added new sidebar information, when the scoreboard is open (by @TimGoll)
    • +
    • Added keybinding information to the bottom of the screen (by @TimGoll)
    • +
        +
      • Can be disabled in Appearance->Interface
      • +
      • Shows binding name when scoreboard is opened
      • +
      +
    • Added option to render rotated text on screen (by @TimGoll)
    • +
    • Added TTT2GiveFoundCredits hook for preventing / overriding the transfer of credits from a body to a player (by @Spanospy)
    • +
    • Added Ukrainian translation from base TTT (by @ErickMaksimets)
    • +
    • Added Swedish translation from base TTT (by @Kefta)
    • +
    • Added Turkish translation (by @NovaDiablox)
    • +
    • Added ttt_dropclip to drop loaded ammo from your active weapon. (by @wgetJane, implemented by @EntranceJew)
    • +
    • Added window flash and noise to alert players they're being revived (by @EntranceJew)
    • +
    • Added sql database access to panel elements
    • +
        +
      • DNumSliderTTT2, DCheckBoxLabelTTT2, DComboBoxTTT2
      • +
      +
    • Added dashing to propspec (by @TimGoll)
    • +
    • Added new functions to database module
    • +
        +
      • database.SetDefaultValuesFromItem(accessName, itemName, item)
      • +
      +
    + +

    Changed

    +
      +
    • Changed sprint stamina to also consume while in air
    • +
    • Updated Simplified Chinese and Traditional Chinese localization files (by @sbzlzh):
    • +
        +
      • Add the missing L.c4_disarm_t translation in C4
      • +
      • Remove redundant string translations and spaces
      • +
      • Added all new translation strings
      • +
      +
    • Updated file code to read from data_static as fallback in new location allowed in .gma (by @EntranceJew)
    • +
    • Scoreboard now sets preferred player volume and mute state in client's new ttt2_voice table (by @EntranceJew)
    • +
        +
      • Keyed by steamid64, making it more reliable than UniqueID or the per-session mute and volume levels.
      • +
      +
    • Changed the body search convars and reworked the UI accordingly (by @TimGoll)
    • +
        +
      • Moved ttt2_confirm_detective_only and ttt2_inspect_detective_only to a new covar: ttt2_inspect_confirm_mode
      • +
          +
        • mode 0: default mode, normal TTT. Everyone can search and identify corpses. However now a player has to be confirmed first to take credits
        • +
        • mode 1: everyone can see information, but only public policing roles can actually confirm bodies
        • +
        • mode 2: only public policing roles can see informatiom. They have to confirm bodies so that other people are able to see this information as well
        • +
        +
      • to comply with mode 1 and 2 now everyone is able to see in the targetID if a player was searched by a public policing role
      • +
      +
    • renamed search_result to bodySearchResult which contains the search result data
    • +
    • changed the credit text color from yellow to gold (by @TimGoll)
    • +
    • Updated the disguiser to make it more clear in the HUD if it is enabled or not
    • +
    • Updated the equipment HUD help boxes in a new style and added missing help boxes (by @TimGoll)
    • +
    • Changed LMB press behavior in observer mode to iterate backwards through player list instead of slecting a random player (by @TimGoll)
    • +
    • Improved translation of some Simplified Chinese strings (by @TheOnly8Z)
    • +
    • Dropping ammo with ttt_dropammo drops from reserve ammo instead of your active weapon's clip (by @wgetJane, implemented by @EntranceJew)
    • +
    • Added item name for ttt_hat_deerstalker (by @EntranceJew)
    • +
    • Changed syncing of database module to use whole tables instead of custom method
    • +
    • Replaced equipmenteditor syncing with database module
    • +
    • Replaced internal equipment syncing with database module
    • +
    • Moved reset buttons onto the left (by @a7f3)
    • +
    • Added ammo icons to the weapon switch HUD and player status HUD elements (by @EntranceJew)
    • +
    • Changed the disguiser icon to be more fitting (by @TimGoll)
    • +
    + +

    Fixed

    +
      +
    • Fixed prediction of the sprinting system, for high ping situations (by @saibotk, thanks to @wgetJane)
    • +
    • Fixed removing the convar change callback in DComboboxTTT2, DCheckBoxLabelTTT2, DNumSliderTTT2 (by @saibotk)
    • +
    • Multiple internal fixes
    • +
        +
      • biggest teamkiller award should now work
      • +
      • item model caching should now work properly
      • +
      • role info popup for traitors should now show teammembers again if the traitor shop is disabled
      • +
      • pon and table libraries got a small fix respectively
      • +
      • the shop and roleselection now reference roles.INNOCENT instead of the removed INNOCENT global, same for TRAITOR and DETECTIVE
      • +
      • Fixed wrong translation % in F1-Menu when changing language (by @NickCloudAT)
      • +
          +
        • Fixed disguiser breaking UI on hot reload (by @TimGoll)
        • +
        • Fixed blurred box rendering for boxes not starting at 0,0 (by @TimGoll)
        • +
        • Fixed spectated entity not being reset properly which can cause issues (by @TimGoll)
        • +
        • Optimized allocations by using global Vector / Angle when possible.
        • +
        • Fixed the dynamic armor damage calculation being wrong when damage can only get partially reduced
        • +
        • Fixed propspec inputs behaving sometimes unexpectedly (by @TimGoll)
        • +
        • Fixed ComboBoxes not working with integer values (by @NickCloudAT)
        • +
        • net.SendStream() can now also handle tables larger than 256kB, which exceeded the maximum net receive buffer
        • +
        • Fixed nil value of SetValue in DNumSliderTTT2 , DCheckBoxLabelTTT2. And fix nil value for boxCache[name] in PlayerModels (by @sbzlzh)
        • +
        • Prevent weapon_tttbase Lua errors with NPCs (by @BuzzHaddaBig in base TTT)
        • +
        • Fix miniscoreboard HUD from showing confirmed players that switched to spectator as having been revived (by @EntranceJew)
        • +
        + +

        Deprecated

        +
          +
        • Deprecated AccessorFuncDT(), Addons should remove the function call and replace DTVar() calls with NetworkVar()
        • +
        + +

        Removed

        +
          +
        • Removed ttt_confirm_death and ttt_call_detective as they are now handled via proper net messages
        • +
        • Removed spectator texts from the UI in favor of the new key binding information (by @TimGoll)
        • +
        • Removed double tap sprinting, for easier prediction handling (by @saibotk)
        • +
        • Removed explicit "Sprint" key bind, please use the GMod native sprint key binding (by @saibotk)
        • +
        • Removed unused clientside Player.preventSprint flag (by @saibotk)
        • +
        + ]], + os.time({ year = 2023, month = 12, day = 11 }) + ) + + AddChange( + "TTT2 Base - v0.12.1b", + [[ +

        Added

        +
          +
        • Added a new `fastutf8` library that provides faster utf8 functions (added by @saibotk, created by @blitmap)
        • +
        + +

        Changed

        +
          +
        + +

        Fixed

        +
          +
        • Fixed the UI being unable to handle wrapping text with non-utf8 languages that do not use ASCII whitespaces (by @TimGoll & @saibotk)
        • +
        • Fixed ttt_game_text not working due to a refactor
        • +
        • Fixed dete call HUD being invisible
        • +
        • Fixed edgecase where undefined killer angle or pos were accessed
        • +
        • Fixed fallback ammo icon missing
        • +
        • Fixed a null entity error in the miniscoreboard
        • +
        • Fixed missing bodysearch information if victim was killed without leaving a trace caused by a weapon hit
        • +
        + ]], + os.time({ year = 2023, month = 12, day = 12 }) + ) + + AddChange( + "TTT2 Base - v0.12.2b", + [[ +

        Added

        +
          +
        • Added the beacon back into TTT2, an equipment that was disabled long ago in base TTT
        • +
            +
          • Can only be bought by policing roles
          • +
          • Creates a wallhack in a sphere around it, which is visible to everyone
          • +
          +
        • Added recognizable badge for builtin equipment and roles (by @EntranceJew)
        • +
            +
          • Buy Equipment menu has builtin indicators, replacing the (C) custom marker decorating a majority of equipment
          • +
          • F1 > Edit Equipment now has builtin indicators on equipment
          • +
          • Added tooltip to F1 > Edit Equipment menu with the equipment's class name.
          • +
          • F1 > Role Settings now has builtin indicators for roles
          • +
          • F1 > Edit Shops now has builtin indicators for roles
          • +
          +
        + +

        Changed

        +
          +
        • Updated the Turkish localization file (by @NovaDiablox)
        • +
        • Radio can now only be picked up by placer
        • +
        • Radar now clears existing waypoints when removed or on changing role (by @EntranceJew)
        • +
        • Comboboxes can now handle numbers and strings as values
        • +
            +
          • Defaults work now with numbers
          • +
          • OnChange-Callback is called with the correct type for ConVars
          • +
          +
        + +

        Fixed

        +
          +
        • Binoculars scan no longer gets interrupted when changing zoom level
        • +
        • Fixed missing water level icon breaking scoreboard
        • +
        • DNA Tester works now with more than one fingerprint on a weapon
        • +
        • TraitorButton config files should now actually work
        • +
        • Translation strings not rendering on detective's body search mode combobox
        • +
        • C4 defusal prompt now suggesting the right key
        • +
        • Disable to unscope from weapons without ironsights
        • +
        • Fixed typo preventing targetid from showing role icons correctly
        • +
        • Mitigated issue with CTakeDamageInfo becoming ephemeral outside their hook of origin
        • +
        • ttt_game_text can now properly send to "All except traitors", as described.
        • +
        • Fixed corpses not listing their kills
        • +
        • Comboboxes now show correct values for database driven entries
        • +
        • Database-Callbacks are now called with the correct valuetype
        • +
        + ]], + os.time({ year = 2023, month = 12, day = 20 }) + ) + + AddChange( + "TTT2 Base - v0.12.3b", + [[ +

        Added

        +
          +
        • Added some missing vanilla TTT entities into TTT2
        • +
        • Added debug.print(message)
        • +
            +
          • This puts quotation marks around print statements
          • +
          • Can handle single values or a sequential table to be printed
          • +
          • Can handle `nil` entries in a nearly sequential table
          • +
          +
        • Added new hooks `TTT2BeaconDetectPlayer` and `TTT2BeaconDeathNotify` to allow preventing / overriding a beacon's player detection & alerts (by @spanospy)
        • +
        • Added indentation to subsettings in F1 menu (by @TimGoll)
        • +
        + +

        Changed

        +
          +
        • Updated the Turkish localization file (by @NovaDiablox)
        • +
        • Keyhelp and weapon HUD Help now use the global scale factor
        • +
        + +

        Fixed

        +
          +
        • Fixed targetID hints for old addons now correctly working for all entities
        • +
        • Fixed visualizer having pickup hint even though player is unable to pick up
        • +
        • targetid wasn't showing named corpse's role, information which was already present on the scoreboard (by @EntranceJew)
        • +
        • Damage Scaling now has a help description
        • +
        • Fixed the database module setting a global variable called `callback` which breaks addons such as PointShop2
        • +
        • Fixed voicechat keybinds being shown even if voice is disabled
        • +
        • Coerced ammo types to lowercase for better matching in HUD
        • +
        • The binocular zoom now uses a DataTable that is not already used by its weaponbase
        • +
        • Fixed round scoreboard tooltips not being wide enough for their strings (by @EntranceJew)
        • +
        • Errors when looking at a player's corpse that disconnected (by @EntranceJew)
        • +
        • Fixed `TTT2FinishedLoading` hook not called on server on hot reload (by @TimGoll)
        • +
        • Shopeditor now correctly shows resetted and default values
        • +
        + ]], + os.time({ year = 2024, month = 01, day = 07 }) + ) + + AddChange( + "TTT2 Base - v0.13.0b", + [[ +

        Added

        +
          +
        • Added migrations between TTT2-versions, some breaking changes could now be migrated instead
        • +
        • Added a new markerVision module that adds information to a specific point in space to replace the old C4 radar; it is currently used by these builtin weapons (by @TimGoll)
        • +
            +
          • C4
          • +
          • Radio
          • +
          • Beacon
          • +
          +
        • Binoculars now retain search progress if interrupted. Progress decays based on time since last observed (by @EntranceJew)
        • +
        • Reworked the way the player camera is handled (by @TimGoll)
        • +
            +
          • Added FOV change on speed change
          • +
          • Added view bobbing on walking, swimming, falling and strafing
          • +
          • Added convars to disable those changes
          • +
          +
        • Added draw.Arc and draw.ShadowedArc from TTTC to TTT2 to draw arcs (by @TimGoll und @Alf21)
        • +
        • Added possibility to cache and remove items, similar to how it is already possible with weapons with CacheAndStripItems (by @TimGoll)
        • +
        • Added an option for weapons to hide the pickup notification by setting SWEP.silentPickup to true (by @TimGoll)
        • +
        • Added TTT2FetchAvatar hook for intercepting avatar URIs (by @EntranceJew)
        • +
        • Added draw.DropCacheAvatar to allow destroying and refreshing an existing avatar, so bots can intercept avatar requests and circumvent the limited unique SteamID64s they're given (by @EntranceJew)
        • +
        • weapon_tttbase changes to correct non-looping animations which affected ADS scoping (by @EntranceJew)
        • +
            +
          • Added SWEP.IdleAnim to allow specifying an idle animation.
          • +
          • Added SWEP.idleResetFix to allow the animations for CS:S weapons to automatically be returned to an idle position.
          • +
          • Added SWEP.ShowDefaultViewModel to prevent a weapon from drawing a ViewModel when set to false at all without FOV hacks or Deploy code which has no effect.
          • +
          +
        • Added a new vgui element: DWeaponPreview_TTT2 to render a player with their equipped weapon (by @TimGoll)
        • +
            +
          • Supports any normal weapon that has a .HoldType and a .WorldModel
          • +
          • Supports any weapon that is made with the SWEP Construction Kit (boomerang, melonmine, ...) or made for our custom world model renderer
          • +
          +
        • Made beacon model and icon unique from decoy (by @EntranceJew)
        • +
        • Added SWEP:ClearHUDHelp() to allow blanking the help text, for dynamically updating help text on equipment (by @EntranceJew)
        • +
        • Added custom world and view models to some builtin weapons (by @TimGoll)
        • +
        • Added custom world and view models to some builtin weapons (by @TimGoll)
        • +
            +
          • added for: radio, beacon, decoy, binoculars, visualizer
          • +
          +
        • Added support for easy addition of custom view and world models (by @TimGoll)
        • +
            +
          • Added AddCustomViewModel to add custom view models
          • +
          • Added AddCustomWorldModel to add custom world models
          • +
          • Added an automatic fix for badly coded addons that break the view model fingers
          • +
          +
        • Added ttt_base_placeable entity that is used to handle any placeable / destroyable entity (by @TimGoll)
        • +
            +
          • moved ttt_c4, ttt_health_station, ttt_beacon, ttt_decoy, ttt_radio and ttt_cse_proj to that base
          • +
          • also handles pickup of those entities
          • +
          +
        • Throwables (grenades) now have a :GetPullTime() accessor (by @EntranceJew)
        • +
        • Throwables (grenades) show UI for the amount of time remaining before detonation (fuse time) (by @EntranceJew)
        • +
        • UI for grenade throw arcs from colemclaren's TTT fork (integrated by @EntranceJew)
        • +
        • gameEffects library for global effects that are useful, such as starting fires (by @EntranceJew)
        • +
        • Added weapon pickup sounds when picking up weapons manually (by @TimGoll)
        • +
        + +

        Changed

        +
          +
        • Refactored client shop logic into separate shop-class (by @ZenBre4ker
        • +
            +
          • Enabled shared shop class to buy and check equipment
          • +
          • Removed third argument of TTT2CanOrderEquipment-Hook, no message is outputted anymore
          • +
          +
        • dframe_ttt2 panels can now manually enable bindings while they are open (by @ZenBre4ker)
        • +
        • Binoculars now have a world model that isn't paper towels (by @EntranceJew)
        • +
        • Decreased shooting accuracy while sprinting or in air (by @TimGoll)
        • +
        • A player whose weapons are stripped and cached will keep weapon_ttt_unarmed which means they keep their crosshair (by @TimGoll)
        • +
        • Updated the German localization file (by @NickCloudAT)
        • +
        • Updated the Turkish localization file (by @NovaDiablox)
        • +
        • Grenades have icons
        • +
        • Brought C4, defuser, flaregun, health_station, radio weapons down from upstream (by @a7f3)
        • +
        • Updated help text for C4, defuser, flaregun, health_station, radio, knife, phammer, push, and zm_carry weapons (by @a7f3)
        • +
        • Brought down the EFFECTs: crimescene_dummy, crimescene_shot, pulse_sphere, teleport_beamdown, teleport_beamup
        • +
        • Brought down the ENTs: ttt_basegrenade_proj, ttt_carry_handler (unused), ttt_firegrenade_proj, ttt_smokegrenade_proj, ttt_weapon_check
        • +
        • Brought down the SWEP: weapon_ttt_stungun
        • +
        • Brought down the menu for arming/defusing C4
        • +
        • Updated and improved Simplified Chinese translation (by @sbzlzh and @TheOnly8Z)
        • +
        • Improved Simplified Chinese translation(by @TEGTainFan)
        • +
        • Consolidated hat logic
        • +
        • Player role selection logic uses Player:CanSelectRole() now instead of duplicating logic
        • +
        • Role avoidance is no longer an option
        • +
        • All builtin weapons can now be configured to drop via Edit Equipment (by @EntranceJew)
        • +
        • Removed redundant checks outside of SWEP:DrawHelp, protected only SWEP:DrawHelp
        • +
        • Spectator name labels now use a skin font and scaling (by @EntranceJew)
        • +
        • The built-in radar now displays distances in meters (by @TimGoll)
        • +
        • Converted ttt_ragdoll_pinning and ttt_ragdoll_pinning_innocents into per-role permissions
        • +
        • Magneto stick now allows right-clicking to instantly drop something, while left-clicking still releases/throws it
        • +
        • Magneto stick now shows tooltips respective to its current state
        • +
        • Scoreboard shows non-policing detective results, in sync with the miniscoreboard (by @EntranceJew)
        • +
        • ttt_flame is visible while it is moving (by @EntranceJew)
        • +
        • ttt_flame's hurtbox is more accurate to its visuals (by @EntranceJew)
        • +
        • The built-in DNA scanner now displays distances in meters (by @TimGoll)
        • +
        • Noisy prints are now gated behind various levels of developer convar (by @EntranceJew)
        • +
        • Any warnings developers should fix will now print with stack traces (by @EntranceJew)
        • +
        • Changed the way the role overhead icon is rendered (by @TimGoll)
        • +
            +
          • It now tracks the players head position
          • +
          • Rendering order is based on distance, no more weird visual glitches
          • +
          • Hidden when observing a player in first person view
          • +
          +
        • Your own spectator nametag will not display when looking directly up in post-round (by @EntranceJew)
        • +
        • Made sure the last weapon is selected by default if the current weapon is removed; overwrite OnRemove to prevent that (by @TimGoll)
        • +
        • Changed the way weapon icon caching is working to make sure all weapons always have a cached icon material (by @TimGoll)
        • +
        + +

        Fixed

        +
          +
        • Fixed database now properly saving boolean false values (by @ZenBre4ker)
        • +
        • Fixed cached weapons not being selected after giving them back to the owner (by @TimGoll)
        • +
        • The roundendscreen can now be closed with the correct Binding (by @ZenBre4ker)
        • +
        • Fixed last seen player being wrongly visible for every search instead of only public policing role search (by @TimGoll)
        • +
        • Fixed the crosshair being offcenter on some UI scales (by @TimGoll)
        • +
        • Fixed to wrong line calculations for wrapped text (by @NickCloudAT)
        • +
        • Fixed marks library having self zfailing and color issues (by @WardenPotato)
        • +
        • Fixed IsPlayer failing if a non-entity is passed to it (by @TimGoll)
        • +
        • Fixed draw.Arc when gmod_mcore_test is set to 1 (by @WardenPotato)
        • +
        • Fixed weapon help box width for wide bindings with short descriptions (by @TimGoll)
        • +
        • Fixed GM:TTTBodySearchPopulate using the wrong data variable (by @TimGoll)
        • +
        • Fixed font initialization to not trip engine font fallback behavior (by @EntranceJew)
        • +
        • Fixed the decoy producing a wrong colored icon for other teams (by @NickCloudAT)
        • +
        • Fixed the scoreboard being stuck open sometimes if the inflictor was no weapon (by @TimGoll)
        • +
        • Fixed door health displaying as a humongous string of decimals (by @EntranceJew)
        • +
        • Fixed weapons that use the wrong weapon base from throwing errors in the F1 menu (by @TimGoll)
        • +
        + +

        Removed

        +
          +
        • Removed some crosshair related convars and replaced them with other ones, see the crosshair settings menu for details
        • +
        • Removed DX8/SW models that aren't used
        • +
        • Removed the convar ttt_damage_own_healthstation as it was inconsistent and probably unused as well
        • +
        • Removed ttt_fire_fallback, there's no situation where the fire shouldn't draw anymore
        • +
        • Removed resource.AddFile calls, server operators should use the workshop version or manually bundle loose files
        • +
        + +

        Breaking Changes

        +
          +
        • Moved global shared EquipmentIsBuyable(tbl, ply) to shop.CanBuyEquipment(ply, equipmentName)
        • +
            +
          • Returned text and result are now replaced by a statusCode
          • +
          +
        • No more plymeta:GetAvoidRole(role) or plymeta:GetAvoidDetective()
        • +
        • Moved global TEAMBUYTABLE to shop.teamBuyTable and separated BUYTABLE into shop.buyTable and shop.globalBuyTable
        • +
            +
          • Use new Accessors shop.IsBoughtFor(ply, equipmentName), shop.IsGlobalBought(equipmentName) and shop.IsTeamBoughtFor(ply, equipmentName)
          • +
          • Use new Setter shop.SetEquipmentBought(ply, equipmentName), shop.SetEquipmentGlobalBought(equipmentName) and shop.SetEquipmentTeamBought(ply, equipmentName)
          • +
          +
        + ]], + os.time({ year = 2024, month = 02, day = 23 }) + ) + + --- + -- run hook for other addons to add their changelog as well + -- @realm client + -- stylua: ignore + hook.Run("TTT2AddChange", changes, currentVersion) end --- -- @realm client function GetSortedChanges() - CreateChanges() - - -- sort changes list by date - table.sort(changes, function(a, b) - if a.date < 0 and b.date < 0 then - return a.date < b.date - else - return a.date > b.date - end - end) - - return changes + CreateChanges() + + -- sort changes list by date + table.sort(changes, function(a, b) + if a.date < 0 and b.date < 0 then + return a.date < b.date + else + return a.date > b.date + end + end) + + return changes end net.Receive("TTT2DevChanges", function(len) - if changesVersion:GetString() == GAMEMODE.Version then return end + if changesVersion:GetString() == GAMEMODE.Version then + return + end - --ShowChanges() + --ShowChanges() - RunConsoleCommand("changes_version", GAMEMODE.Version) + RunConsoleCommand("changes_version", GAMEMODE.Version) end) --- @@ -1341,6 +2048,4 @@ end) -- @param string currentVersionNumber The current version number -- @hook -- @realm client -function GM:TTT2AddChange(changesTbl, currentVersionNumber) - -end +function GM:TTT2AddChange(changesTbl, currentVersionNumber) end diff --git a/gamemodes/terrortown/gamemode/client/cl_chat.lua b/gamemodes/terrortown/gamemode/client/cl_chat.lua index a18ce0401..ae91010cd 100644 --- a/gamemodes/terrortown/gamemode/client/cl_chat.lua +++ b/gamemodes/terrortown/gamemode/client/cl_chat.lua @@ -16,40 +16,42 @@ local color_4 = Color(255, 200, 20) local color_5 = Color(255, 255, 200) local function LastWordsRecv() - local sender = net.ReadEntity() - local words = net.ReadString() - - local validSender = IsValid(sender) - - local was_detective = validSender and sender:IsDetective() - local nick = validSender and sender:Nick() or "" - - chat.AddText( - color_1, - Format("(%s) ", string.upper(GetTranslation("last_words"))), - was_detective and color_2 or color_3, - nick, - COLOR_WHITE, - ": " .. words - ) + local sender = net.ReadEntity() + local words = net.ReadString() + + local validSender = IsValid(sender) + + local was_detective = validSender and sender:IsDetective() + local nick = validSender and sender:Nick() or "" + + chat.AddText( + color_1, + Format("(%s) ", string.upper(GetTranslation("last_words"))), + was_detective and color_2 or color_3, + nick, + COLOR_WHITE, + ": " .. words + ) end net.Receive("TTT_LastWordsMsg", LastWordsRecv) local function TTT_RoleChat() - local sender = net.ReadEntity() - if not IsValid(sender) then return end - - local text = net.ReadString() - local roleData = sender:GetSubRoleData() -- use cached role - - chat.AddText( - sender:GetRoleColor(), - Format("(%s) ", string.upper(GetTranslation(roleData.name))), - color_4, - sender:Nick(), - color_5, - ": " .. text - ) + local sender = net.ReadEntity() + if not IsValid(sender) then + return + end + + local text = net.ReadString() + local roleData = sender:GetSubRoleData() -- use cached role + + chat.AddText( + sender:GetRoleColor(), + Format("(%s) ", string.upper(GetTranslation(roleData.name))), + color_4, + sender:Nick(), + color_5, + ": " .. text + ) end net.Receive("TTT_RoleChat", TTT_RoleChat) @@ -76,14 +78,14 @@ net.Receive("TTT_RoleChat", TTT_RoleChat) -- @ref https://wiki.facepunch.com/gmod/GM:ChatText -- @local function GM:ChatText(idx, name, text, type) - if type == "joinleave" and string.find(text, "Changed name during a round") then - -- prevent nick from showing up - chat.AddText(LANG.GetTranslation("name_kick")) + if type == "joinleave" and string.find(text, "Changed name during a round") then + -- prevent nick from showing up + chat.AddText(LANG.GetTranslation("name_kick")) - return true - end + return true + end - return BaseClass.ChatText(self, idx, name, text, type) + return BaseClass.ChatText(self, idx, name, text, type) end --- @@ -92,7 +94,7 @@ end -- @param string text -- @realm client function AddDetectiveText(ply, text) - chat.AddText(roles.DETECTIVE.color, ply:Nick(), COLOR_WHITE, ": " .. text) + chat.AddText(roles.DETECTIVE.color, ply:Nick(), COLOR_WHITE, ": " .. text) end --- @@ -109,34 +111,34 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:OnPlayerChat -- @local function GM:OnPlayerChat(ply, text, teamChat, isDead) - if not IsValid(ply) then - return BaseClass.OnPlayerChat(self, ply, text, teamChat, isDead) - end + if not IsValid(ply) then + return BaseClass.OnPlayerChat(self, ply, text, teamChat, isDead) + end - local plyRoleData = ply:GetSubRoleData() + local plyRoleData = ply:GetSubRoleData() - -- send detective-like message if role is public policing role - -- note: while it seems like is not nessecary to check if the role is public because - -- only a public role is synced, this is still checked here to make sure it works - -- well with revivals. A non-public role won't be counted as public policing role - -- if they were revived and their role is known. - if ply:IsActive() and plyRoleData.isPolicingRole and plyRoleData.isPublicRole then - AddDetectiveText(ply, text) + -- send detective-like message if role is public policing role + -- note: while it seems like is not nessecary to check if the role is public because + -- only a public role is synced, this is still checked here to make sure it works + -- well with revivals. A non-public role won't be counted as public policing role + -- if they were revived and their role is known. + if ply:IsActive() and plyRoleData.isPolicingRole and plyRoleData.isPublicRole then + AddDetectiveText(ply, text) - return true - end + return true + end - local sTeam = ply:Team() == TEAM_SPEC + local sTeam = ply:Team() == TEAM_SPEC - if sTeam and not isDead then - isDead = true - end + if sTeam and not isDead then + isDead = true + end - if teamChat and (sTeam or ply:GetSubRoleData().unknownTeam) then - teamChat = false - end + if teamChat and (sTeam or ply:GetSubRoleData().unknownTeam) then + teamChat = false + end - return BaseClass.OnPlayerChat(self, ply, text, teamChat, isDead) + return BaseClass.OnPlayerChat(self, ply, text, teamChat, isDead) end local last_chat = "" @@ -149,7 +151,7 @@ local last_chat = "" -- @ref https://wiki.facepunch.com/gmod/GM:ChatTextChanged -- @local function GM:ChatTextChanged(text) - last_chat = text + last_chat = text end --- @@ -157,20 +159,20 @@ end -- @realm client -- @internal function ChatInterrupt() - local client = LocalPlayer() - local id = net.ReadUInt(32) + local client = LocalPlayer() + local id = net.ReadUInt(32) - local last_seen = IsValid(client.last_id) and client.last_id:EntIndex() or 0 - local last_words = "." + local last_seen = IsValid(client.last_id) and client.last_id:EntIndex() or 0 + local last_words = "." - if last_chat == "" then - if RADIO.LastRadio.t > CurTime() - 2 then - last_words = RADIO.LastRadio.msg - end - else - last_words = last_chat - end + if last_chat == "" then + if RADIO.LastRadio.t > CurTime() - 2 then + last_words = RADIO.LastRadio.msg + end + else + last_words = last_chat + end - RunConsoleCommand("_deathrec", tostring(id), tostring(last_seen), last_words) + RunConsoleCommand("_deathrec", tostring(id), tostring(last_seen), last_words) end net.Receive("TTT_InterruptChat", ChatInterrupt) diff --git a/gamemodes/terrortown/gamemode/client/cl_damage_indicator.lua b/gamemodes/terrortown/gamemode/client/cl_damage_indicator.lua index a14bab246..452f82148 100644 --- a/gamemodes/terrortown/gamemode/client/cl_damage_indicator.lua +++ b/gamemodes/terrortown/gamemode/client/cl_damage_indicator.lua @@ -2,30 +2,36 @@ -- @module dmgindicator dmgindicator = { - themes = {}, - - -- setup convars - cv = { - --- - -- @realm client - enable = CreateConVar("ttt_dmgindicator_enable", "1", FCVAR_ARCHIVE), - - --- - -- @realm client - mode = CreateConVar("ttt_dmgindicator_mode", "default", FCVAR_ARCHIVE), - - --- - -- @realm client - duration = CreateConVar("ttt_dmgindicator_duration", "1.5", FCVAR_ARCHIVE), - - --- - -- @realm client - maxdamage = CreateConVar("ttt_dmgindicator_maxdamage", "50.0", FCVAR_ARCHIVE), - - --- - -- @realm client - maxalpha = CreateConVar("ttt_dmgindicator_maxalpha", "255", FCVAR_ARCHIVE) - } + themes = {}, + + -- setup convars + cv = { + --- + -- @realm client + -- stylua: ignore + enable = CreateConVar("ttt_dmgindicator_enable", "1", FCVAR_ARCHIVE), + + --- + -- @realm client + -- stylua: ignore + mode = CreateConVar("ttt_dmgindicator_mode", "default", FCVAR_ARCHIVE), + + --- + -- @realm client + -- stylua: ignore + duration = CreateConVar("ttt_dmgindicator_duration", "1.5", FCVAR_ARCHIVE), + + --- + -- @realm client + -- stylua: ignore + maxdamage = CreateConVar("ttt_dmgindicator_maxdamage", "50.0", FCVAR_ARCHIVE), + + --- + -- @realm client + -- stylua: ignore + maxalpha = CreateConVar("ttt_dmgindicator_maxalpha", "255", FCVAR_ARCHIVE) +, + }, } local lastDamage = CurTime() @@ -36,25 +42,30 @@ local materialStringBase = "vgui/ttt/dmgindicator/themes/" local pathBase = "materials/" .. materialStringBase local function CollectDmgIndicatorTextures() - local materials = file.Find(pathBase .. "*.png", "GAME") + local materials = file.Find(pathBase .. "*.png", "GAME") - for i = 1, #materials do - local material = materials[i] - local materialName = string.StripExtension(material) + for i = 1, #materials do + local material = materials[i] + local materialName = string.StripExtension(material) - dmgindicator.themes[materialName] = Material(materialStringBase .. material) - end + dmgindicator.themes[materialName] = Material(materialStringBase .. material) + end end CollectDmgIndicatorTextures() net.Receive("ttt2_damage_received", function() - local damageReceived = net.ReadFloat() - if damageReceived <= 0 then return end - - lastDamage = CurTime() - maxDamageAmount = math.min(1.0, math.max(damageAmount, 0.0) + damageReceived / dmgindicator.cv.maxdamage:GetFloat()) - damageAmount = maxDamageAmount + local damageReceived = net.ReadFloat() + if damageReceived <= 0 then + return + end + + lastDamage = CurTime() + maxDamageAmount = math.min( + 1.0, + math.max(damageAmount, 0.0) + damageReceived / dmgindicator.cv.maxdamage:GetFloat() + ) + damageAmount = maxDamageAmount end) --- @@ -66,20 +77,24 @@ end) -- @ref https://wiki.facepunch.com/gmod/GM:HUDPaintBackground -- @local function GM:HUDPaintBackground() - if not dmgindicator.cv.enable:GetBool() then return end + if not dmgindicator.cv.enable:GetBool() then + return + end - local indicatorDuration = dmgindicator.cv.duration:GetFloat() + local indicatorDuration = dmgindicator.cv.duration:GetFloat() - if damageAmount > 0 then - local theme = dmgindicator.themes[dmgindicator.cv.mode:GetString()] or dmgindicator.themes["Default"] - local remainingTimeFactor = math.max(0, indicatorDuration - (CurTime() - lastDamage)) / indicatorDuration + if damageAmount > 0 then + local theme = dmgindicator.themes[dmgindicator.cv.mode:GetString()] + or dmgindicator.themes["Default"] + local remainingTimeFactor = math.max(0, indicatorDuration - (CurTime() - lastDamage)) + / indicatorDuration - damageAmount = maxDamageAmount * remainingTimeFactor + damageAmount = maxDamageAmount * remainingTimeFactor - surface.SetDrawColor(255, 255, 255, dmgindicator.cv.maxalpha:GetInt() * damageAmount) - surface.SetMaterial(theme) - surface.DrawTexturedRect(0, 0, ScrW(), ScrH()) - end + surface.SetDrawColor(255, 255, 255, dmgindicator.cv.maxalpha:GetInt() * damageAmount) + surface.SetMaterial(theme) + surface.DrawTexturedRect(0, 0, ScrW(), ScrH()) + end end --- @@ -87,5 +102,5 @@ end -- @return table A table with names of all themes -- @realm client function dmgindicator.GetThemeNames() - return table.GetKeys(dmgindicator.themes) + return table.GetKeys(dmgindicator.themes) end diff --git a/gamemodes/terrortown/gamemode/client/cl_equip.lua b/gamemodes/terrortown/gamemode/client/cl_equip.lua index b729dc30d..ecac1150a 100644 --- a/gamemodes/terrortown/gamemode/client/cl_equip.lua +++ b/gamemodes/terrortown/gamemode/client/cl_equip.lua @@ -15,34 +15,42 @@ local hook = hook --- -- @realm client +-- stylua: ignore local numColsVar = CreateConVar("ttt_bem_cols", 5, FCVAR_ARCHIVE, "Sets the number of columns in the Traitor/Detective menu's item list.") --- -- @realm client +-- stylua: ignore local numRowsVar = CreateConVar("ttt_bem_rows", 6, FCVAR_ARCHIVE, "Sets the number of rows in the Traitor/Detective menu's item list.") --- -- @realm client +-- stylua: ignore local itemSizeVar = CreateConVar("ttt_bem_size", 64, FCVAR_ARCHIVE, "Sets the item size in the Traitor/Detective menu's item list.") --- -- @realm client +-- stylua: ignore local showCustomVar = CreateConVar("ttt_bem_marker_custom", 1, FCVAR_ARCHIVE, "Should custom items get a marker?") --- -- @realm client +-- stylua: ignore local showFavoriteVar = CreateConVar("ttt_bem_marker_fav", 1, FCVAR_ARCHIVE, "Should favorite items get a marker?") --- -- @realm client +-- stylua: ignore local showSlotVar = CreateConVar("ttt_bem_marker_slot", 1, FCVAR_ARCHIVE, "Should items get a slot-marker?") --- -- @realm client +-- stylua: ignore local alwaysShowShopVar = CreateConVar("ttt_bem_always_show_shop", 1, FCVAR_ARCHIVE, "Should the shop be opened/closed instead of the score menu during preparing / at the end of a round?") --- -- @realm client +-- stylua: ignore local enableDoubleClickBuy = CreateConVar("ttt_bem_enable_doubleclick_buy", 1, FCVAR_ARCHIVE, "Sets if you will be able to double click on an Item to buy it.") -- get serverside ConVars @@ -58,10 +66,10 @@ local serverSizeVar = GetConVar("ttt_bem_sv_size") -- Creates the fav table if it not already exists -- @realm client local function CreateFavTable() - if not sql.TableExists("ttt_bem_fav") then - local query = "CREATE TABLE ttt_bem_fav (guid TEXT, role TEXT, weapon_id TEXT)" - sql.Query(query) - end + if not sql.TableExists("ttt_bem_fav") then + local query = "CREATE TABLE ttt_bem_fav (guid TEXT, role TEXT, weapon_id TEXT)" + sql.Query(query) + end end --- @@ -71,8 +79,16 @@ end -- @param string id the @{WEAPON} or @{ITEM} id -- @realm client local function AddFavorite(steamid, subrole, id) - local query = ("INSERT INTO ttt_bem_fav VALUES('" .. steamid .. "','" .. subrole .. "','" .. id .. "')") - sql.Query(query) + local query = ( + "INSERT INTO ttt_bem_fav VALUES('" + .. steamid + .. "','" + .. subrole + .. "','" + .. id + .. "')" + ) + sql.Query(query) end --- @@ -82,8 +98,16 @@ end -- @param string id the @{WEAPON} or @{ITEM} id -- @realm client local function RemoveFavorite(steamid, subrole, id) - local query = ("DELETE FROM ttt_bem_fav WHERE guid = '" .. steamid .. "' AND role = '" .. subrole .. "' AND weapon_id = '" .. id .. "'") - sql.Query(query) + local query = ( + "DELETE FROM ttt_bem_fav WHERE guid = '" + .. steamid + .. "' AND role = '" + .. subrole + .. "' AND weapon_id = '" + .. id + .. "'" + ) + sql.Query(query) end --- @@ -93,9 +117,15 @@ end -- @return table list of all favorites based on the subrole -- @realm client local function GetFavorites(steamid, subrole) - local query = ("SELECT weapon_id FROM ttt_bem_fav WHERE guid = '" .. steamid .. "' AND role = '" .. subrole .. "'") - - return sql.Query(query) + local query = ( + "SELECT weapon_id FROM ttt_bem_fav WHERE guid = '" + .. steamid + .. "' AND role = '" + .. subrole + .. "'" + ) + + return sql.Query(query) end --- @@ -105,55 +135,52 @@ end -- @return boolean -- @realm client local function IsFavorite(favorites, id) - for _, value in pairs(favorites) do - local dbid = value["weapon_id"] + for _, value in pairs(favorites) do + local dbid = value["weapon_id"] - if dbid == tostring(id) then - return true - end - end + if dbid == tostring(id) then + return true + end + end - return false + return false end - local color_bad = Color(244, 67, 54, 255) --local color_good = Color(76, 175, 80, 255) local color_darkened = Color(255, 255, 255, 80) -local fallback_mat = Material("vgui/ttt/missing_equip_icon") - -- Buyable weapons are loaded automatically. Buyable items are defined in -- equip_items_shd.lua -local eqframe = eqframe -local dlist = dlist -local curSearch = curSearch +local eqframe = nil +local dlist = nil +local curSearch = nil -- -- GENERAL HELPER FUNCTIONS -- local function RolenameToRole(val) - local rlsList = roles.GetList() + local rlsList = roles.GetList() - for i = 1, #rlsList do - local v = rlsList[i] + for i = 1, #rlsList do + local v = rlsList[i] - if SafeTranslate(v.name) == val then - return v.index - end - end + if SafeTranslate(v.name) == val then + return v.index + end + end - return 0 + return 0 end local function ItemIsWeapon(item) - return not items.IsItem(item.id) + return not items.IsItem(item.id) end local function CanCarryWeapon(item) - return LocalPlayer():CanCarryWeapon(item) + return LocalPlayer():CanCarryWeapon(item) end -- @@ -161,121 +188,158 @@ end -- local function PreqLabels(parent, x, y) - local client = LocalPlayer() - - local tbl = {} - tbl.credits = vgui.Create("DLabel", parent) - --tbl.credits:SetTooltip(GetTranslation("equip_help_cost")) - tbl.credits:SetPos(x, y) - - -- coins icon - tbl.credits.img = vgui.Create("DImage", parent) - tbl.credits.img:SetSize(32, 32) - tbl.credits.img:CopyPos(tbl.credits) - tbl.credits.img:MoveLeftOf(tbl.credits) - tbl.credits.img:SetImage("vgui/ttt/equip/coin.png") - - -- remaining credits text - tbl.credits.Check = function(s, sel) - local credits = client:GetCredits() - local cr = sel and sel.credits or 1 - - return credits >= cr, " " .. cr .. " / " .. credits, GetPTranslation("equip_cost", {num = credits}) - end - - tbl.owned = vgui.Create("DLabel", parent) - --tbl.owned:SetTooltip(GetTranslation("equip_help_carry")) - tbl.owned:CopyPos(tbl.credits) - tbl.owned:MoveRightOf(tbl.credits, y * 5) - - -- carry icon - tbl.owned.img = vgui.Create("DImage", parent) - tbl.owned.img:SetSize(32, 32) - tbl.owned.img:CopyPos(tbl.owned) - tbl.owned.img:MoveLeftOf(tbl.owned) - tbl.owned.img:SetImage("vgui/ttt/equip/briefcase.png") - - tbl.owned.Check = function(s, sel) - if ItemIsWeapon(sel) and not CanCarryWeapon(sel) then - return false, MakeKindValid(sel.Kind), GetPTranslation("equip_carry_slot", {slot = MakeKindValid(sel.Kind)}) - elseif not ItemIsWeapon(sel) and sel.limited and client:HasEquipmentItem(sel.id) then - return false, "X", GetTranslation("equip_carry_own") - else - if ItemIsWeapon(sel) then - local cv_maxCount = GetConVar(ORDERED_SLOT_TABLE[MakeKindValid(sel.Kind)]) - - local maxCount = cv_maxCount and cv_maxCount:GetInt() or 0 - maxCount = maxCount < 0 and "∞" or maxCount - - return true, " " .. #client:GetWeaponsOnSlot(MakeKindValid(sel.Kind)) .. " / " .. maxCount, GetTranslation("equip_carry") - else - return true, "✔", GetTranslation("equip_carry") - end - end - end - - -- TODO add global limited - tbl.bought = vgui.Create("DLabel", parent) - --tbl.bought:SetTooltip(GetTranslation("equip_help_stock")) - tbl.bought:CopyPos(tbl.credits) - tbl.bought:MoveBelow(tbl.credits, y * 2) - - -- stock icon - tbl.bought.img = vgui.Create("DImage", parent) - tbl.bought.img:SetSize(32, 32) - tbl.bought.img:CopyPos(tbl.bought) - tbl.bought.img:MoveLeftOf(tbl.bought) - tbl.bought.img:SetImage("vgui/ttt/equip/package.png") - - tbl.bought.Check = function(s, sel) - if sel.limited and client:HasBought(tostring(sel.id)) then - return false, "X", GetTranslation("equip_stock_deny") - else - return true, "✔", GetTranslation("equip_stock_ok") - end - end - - -- custom info - tbl.info = vgui.Create("DLabel", parent) - --tbl.info:SetTooltip(GetTranslation("equip_help_stock")) - tbl.info:CopyPos(tbl.bought) - tbl.info:MoveRightOf(tbl.bought, y * 5) - - -- stock icon - tbl.info.img = vgui.Create("DImage", parent) - tbl.info.img:SetSize(32, 32) - tbl.info.img:CopyPos(tbl.info) - tbl.info.img:MoveLeftOf(tbl.info) - tbl.info.img:SetImage("vgui/ttt/equip/icon_info") - - tbl.info.Check = function(s, sel) - return EquipmentIsBuyable(sel, client) - end - - for _, pnl in pairs(tbl) do - pnl:SetFont("DermaLarge") - pnl:SetText(" - ") - end - - return function(selected) - local allow = true - - for _, pnl in pairs(tbl) do - local result, text, tooltip = pnl:Check(selected) - - pnl:SetTextColor(result and COLOR_WHITE or color_bad) - pnl:SetText(text) - pnl:SizeToContents() - pnl:SetTooltip(tooltip) - - pnl.img:SetImageColor(result and COLOR_WHITE or color_bad) - pnl.img:SetTooltip(tooltip) - - allow = allow and result - end - - return allow - end + local client = LocalPlayer() + + local tbl = {} + tbl.credits = vgui.Create("DLabel", parent) + --tbl.credits:SetTooltip(GetTranslation("equip_help_cost")) + tbl.credits:SetPos(x, y) + + -- coins icon + tbl.credits.img = vgui.Create("DImage", parent) + tbl.credits.img:SetSize(32, 32) + tbl.credits.img:CopyPos(tbl.credits) + tbl.credits.img:MoveLeftOf(tbl.credits) + tbl.credits.img:SetImage("vgui/ttt/equip/coin.png") + + -- remaining credits text + tbl.credits.Check = function(s, sel) + local credits = client:GetCredits() + local cr = sel and sel.credits or 1 + + return credits >= cr, + " " .. cr .. " / " .. credits, + GetPTranslation("equip_cost", { num = credits }) + end + + tbl.owned = vgui.Create("DLabel", parent) + --tbl.owned:SetTooltip(GetTranslation("equip_help_carry")) + tbl.owned:CopyPos(tbl.credits) + tbl.owned:MoveRightOf(tbl.credits, y * 5) + + -- carry icon + tbl.owned.img = vgui.Create("DImage", parent) + tbl.owned.img:SetSize(32, 32) + tbl.owned.img:CopyPos(tbl.owned) + tbl.owned.img:MoveLeftOf(tbl.owned) + tbl.owned.img:SetImage("vgui/ttt/equip/briefcase.png") + + tbl.owned.Check = function(s, sel) + if ItemIsWeapon(sel) and not CanCarryWeapon(sel) then + return false, + MakeKindValid(sel.Kind), + GetPTranslation("equip_carry_slot", { slot = MakeKindValid(sel.Kind) }) + elseif not ItemIsWeapon(sel) and sel.limited and client:HasEquipmentItem(sel.id) then + return false, "X", GetTranslation("equip_carry_own") + else + if ItemIsWeapon(sel) then + local cv_maxCount = GetConVar(ORDERED_SLOT_TABLE[MakeKindValid(sel.Kind)]) + + local maxCount = cv_maxCount and cv_maxCount:GetInt() or 0 + maxCount = maxCount < 0 and "∞" or maxCount + + return true, + " " .. #client:GetWeaponsOnSlot(MakeKindValid(sel.Kind)) .. " / " .. maxCount, + GetTranslation("equip_carry") + else + return true, "✔", GetTranslation("equip_carry") + end + end + end + + -- TODO add global limited + tbl.bought = vgui.Create("DLabel", parent) + --tbl.bought:SetTooltip(GetTranslation("equip_help_stock")) + tbl.bought:CopyPos(tbl.credits) + tbl.bought:MoveBelow(tbl.credits, y * 2) + + -- stock icon + tbl.bought.img = vgui.Create("DImage", parent) + tbl.bought.img:SetSize(32, 32) + tbl.bought.img:CopyPos(tbl.bought) + tbl.bought.img:MoveLeftOf(tbl.bought) + tbl.bought.img:SetImage("vgui/ttt/equip/package.png") + + tbl.bought.Check = function(s, sel) + if sel.limited and client:HasBought(tostring(sel.id)) then + return false, "X", GetTranslation("equip_stock_deny") + else + return true, "✔", GetTranslation("equip_stock_ok") + end + end + + -- custom info + tbl.info = vgui.Create("DLabel", parent) + --tbl.info:SetTooltip(GetTranslation("equip_help_stock")) + tbl.info:CopyPos(tbl.bought) + tbl.info:MoveRightOf(tbl.bought, y * 5) + + -- stock icon + tbl.info.img = vgui.Create("DImage", parent) + tbl.info.img:SetSize(32, 32) + tbl.info.img:CopyPos(tbl.info) + tbl.info.img:MoveLeftOf(tbl.info) + tbl.info.img:SetImage("vgui/ttt/equip/icon_info") + + tbl.info.Check = function(s, sel) + if not istable(sel) then + return false, "X", "No table given." + end + + local isBuyable, statusCode = shop.CanBuyEquipment(client, sel.id) + local iconText = isBuyable and "✔" or "X" + local tooltipText + + if statusCode == shop.statusCode.SUCCESS then + tooltipText = "Ok" + elseif statusCode == shop.statusCode.INVALIDID then + ErrorNoHaltWithStack("[TTT2][ERROR] Missing id in table:", sel) + PrintTable(sel) + tooltipText = "No ID" + elseif statusCode == shop.statusCode.NOTBUYABLE then + tooltipText = "This equipment cannot be bought." + elseif statusCode == shop.statusCode.NOTENOUGHPLAYERS then + iconText = " " .. #util.GetActivePlayers() .. " / " .. sel.minPlayers + tooltipText = "Minimum amount of active players needed." + elseif statusCode == shop.statusCode.LIMITEDBOUGHT then + tooltipText = "This equipment is limited and is already bought." + elseif statusCode == shop.statusCode.GLOBALLIMITEDBOUGHT then + tooltipText = "This equipment is globally limited and is already bought by someone." + elseif statusCode == shop.statusCode.TEAMLIMITEDBOUGHT then + tooltipText = "This equipment is limited in team and is already bought by a teammate." + elseif statusCode == shop.statusCode.NOTBUYABLEFORROLE then + tooltipText = "Your role can't buy this equipment." + else + tooltipText = "Undefined statusCode " .. tostring(statusCode) + end + + return isBuyable, iconText, tooltipText + end + + for _, pnl in pairs(tbl) do + pnl:SetFont("DermaLarge") + pnl:SetText(" - ") + end + + return function(selected) + local allow = true + + for _, pnl in pairs(tbl) do + local result, text, tooltip = pnl:Check(selected) + + pnl:SetTextColor(result and COLOR_WHITE or color_bad) + pnl:SetText(text) + pnl:SizeToContents() + pnl:SetTooltip(tooltip) + + pnl.img:SetImageColor(result and COLOR_WHITE or color_bad) + pnl.img:SetTooltip(tooltip) + + allow = allow and result + end + + return allow + end end -- @@ -286,8 +350,8 @@ end local PANEL = {} local function DrawSelectedEquipment(pnl) - surface.SetDrawColor(255, 200, 0, 255) - surface.DrawOutlinedRect(0, 0, pnl:GetWide(), pnl:GetTall()) + surface.SetDrawColor(255, 200, 0, 255) + surface.DrawOutlinedRect(0, 0, pnl:GetWide(), pnl:GetTall()) end --- @@ -295,15 +359,17 @@ end -- @realm client -- @local function PANEL:SelectPanel(pnl) - if not pnl then return end + if not pnl then + return + end - pnl.PaintOver = nil + pnl.PaintOver = nil - self.BaseClass.SelectPanel(self, pnl) + self.BaseClass.SelectPanel(self, pnl) - if pnl then - pnl.PaintOver = DrawSelectedEquipment - end + if pnl then + pnl.PaintOver = DrawSelectedEquipment + end end vgui.Register("EquipSelect", PANEL, "DPanelSelect") @@ -312,220 +378,240 @@ vgui.Register("EquipSelect", PANEL, "DPanelSelect") -- local function PerformStarLayout(s) - s:AlignTop(2) - s:AlignRight(2) - s:SetSize(12, 12) + s:AlignTop(2) + s:AlignRight(2) + s:SetSize(12, 12) end local function PerformMarkerLayout(s) - s:AlignBottom(2) - s:AlignRight(2) - s:SetSize(16, 16) + s:AlignBottom(2) + s:AlignRight(2) + s:SetSize(16, 16) end local function CreateEquipmentList(t) - t = t or {} - - setmetatable(t, { - __index = { - search = nil, - role = nil, - notalive = false - } - }) - - if t.search == LANG.GetTranslation("shop_search") .. "..." or t.search == "" then - t.search = nil - end - - -- icon size = 64 x 64 - if IsValid(dlist) then - dlist:Clear() - else - TraitorMenuPopup() - - return - end - - local ply = LocalPlayer() - local currole = ply:GetSubRole() - local credits = ply:GetCredits() + t = t or {} + + setmetatable(t, { + __index = { + search = nil, + role = nil, + notalive = false, + }, + }) + + if t.search == LANG.GetTranslation("shop_search") .. "..." or t.search == "" then + t.search = nil + end + + -- icon size = 64 x 64 + if IsValid(dlist) then + ---@cast dlist -nil + dlist:Clear() + else + TraitorMenuPopup() - local itemSize = 64 + return + end - if allowChangeVar:GetBool() then - itemSize = itemSizeVar:GetInt() - end - - -- make sure that the players old role is not used anymore - if t.notalive then - currole = t.role or ROLE_NONE - end + local client = LocalPlayer() + local currole = client:GetSubRole() + local credits = client:GetCredits() - -- Determine if we already have equipment - local owned_ids = {} - local weps = ply:GetWeapons() - - for i = 1, #weps do - local wep = weps[i] - - if wep.IsEquipment and wep:IsEquipment() then - owned_ids[#owned_ids + 1] = wep:GetClass() - end - end - - -- Stick to one value for no equipment - if #owned_ids == 0 then - owned_ids = nil - end + local itemSize = 64 - local itms = {} - local tmp = GetEquipmentForRole(ply, currole, t.notalive) - - for i = 1, #tmp do - if not tmp[i].notBuyable then - itms[#itms + 1] = tmp[i] - end - end - - if #itms == 0 and not t.notalive then - ply:ChatPrint("[TTT2][SHOP] You need to run 'shopeditor' as admin in the developer console to create a shop for this role. Link it with another shop or click on the icons to add weapons and items to the shop.") - - return - end - - -- temp table for sorting - local paneltablefav = {} - local paneltable = {} - local steamid = ply:SteamID64() - local col = ply:GetRoleColor() - - for k = 1, #itms do - local item = itms[k] - local equipName = GetEquipmentTranslation(item.name, item.PrintName) - - if t.search and string.find(string.lower(equipName), string.lower(t.search)) or not t.search then - local ic = nil - - -- Create icon panel - if item.ttt2_cached_material then - ic = vgui.Create("LayeredIcon", dlist) - - if item.custom and showCustomVar:GetBool() then - -- Custom marker icon - local marker = vgui.Create("DImage") - marker:SetImage("vgui/ttt/custom_marker") - - marker.PerformLayout = PerformMarkerLayout - - marker:SetTooltip(GetTranslation("equip_custom")) - - ic:AddLayer(marker) - ic:EnableMousePassthrough(marker) - end - - -- Favorites marker icon - ic.favorite = false - - local favorites = GetFavorites(steamid, currole) - - if favorites and IsFavorite(favorites, item.id) then - ic.favorite = true - - if showFavoriteVar:GetBool() then - local star = vgui.Create("DImage") - star:SetImage("icon16/star.png") - - star.PerformLayout = PerformStarLayout - - star:SetTooltip("Favorite") - - ic:AddLayer(star) - ic:EnableMousePassthrough(star) - end - end - - -- Slot marker icon - if ItemIsWeapon(item) and showSlotVar:GetBool() then - local slot = vgui.Create("SimpleIconLabelled") - slot:SetIcon("vgui/ttt/slotcap") - slot:SetIconColor(col or COLOR_GREY) - slot:SetIconSize(16) - slot:SetIconText(MakeKindValid(item.Kind)) - slot:SetIconProperties(COLOR_WHITE, - "DefaultBold", - {opacity = 220, offset = 1}, - {10, 8} - ) - - ic:AddLayer(slot) - ic:EnableMousePassthrough(slot) - end - - ic:SetIconSize(itemSize or 64) - ic:SetMaterial(item.ttt2_cached_material) - elseif item.ttt2_cached_model then - ic = vgui.Create("SpawnIcon", dlist) - ic:SetModel(item.ttt2_cached_model) - else - print("Equipment item does not have model or material specified: " .. tostring(item) .. "\n") - - continue - end - - ic.item = item - - ic:SetTooltip(equipName .. " (" .. SafeTranslate(item.type) .. ")") - - -- If we cannot order this item, darken it - if not t.notalive and (( - -- already owned - table.HasValue(owned_ids, item.id) - or items.IsItem(item.id) and item.limited and ply:HasEquipmentItem(item.id) - -- already carrying a weapon for this slot - or ItemIsWeapon(item) and not CanCarryWeapon(item) - or not EquipmentIsBuyable(item, ply) - -- already bought the item before - or item.limited and ply:HasBought(item.id) - ) or (item.credits or 1) > credits - ) then - ic:SetIconColor(color_darkened) - end - - if ic.favorite then - paneltablefav[k] = ic - else - paneltable[k] = ic - end - - -- icon doubleclick to buy - ic.PressedLeftMouse = function(self, doubleClick) - if not doubleClick or self.item.disabledBuy or not enableDoubleClickBuy:GetBool() then return end - - net.Start("TTT2OrderEquipment") - net.WriteString(self.item.id) - net.SendToServer() - - eqframe:Close() - end - end - end - - -- add favorites first - for _, panel in pairs(paneltablefav) do - dlist:AddPanel(panel) - end - - -- non favorites second - for _, panel in pairs(paneltable) do - dlist:AddPanel(panel) - end + if allowChangeVar:GetBool() then + itemSize = itemSizeVar:GetInt() + end + + -- make sure that the players old role is not used anymore + if t.notalive then + currole = t.role or ROLE_NONE + end + + -- Determine if we already have equipment + local owned_ids = {} + local weps = client:GetWeapons() + + for i = 1, #weps do + local wep = weps[i] + + if wep.IsEquipment and wep:IsEquipment() then + owned_ids[#owned_ids + 1] = wep:GetClass() + end + end + + -- Stick to one value for no equipment + if #owned_ids == 0 then + owned_ids = nil + end + + local itms = {} + local tmp = GetEquipmentForRole(client, currole, t.notalive) + + for i = 1, #tmp do + if not tmp[i].notBuyable then + itms[#itms + 1] = tmp[i] + end + end + + if #itms == 0 and not t.notalive then + client:ChatPrint( + "[TTT2][SHOP] You need to run 'shopeditor' as admin in the developer console to create a shop for this role. Link it with another shop or click on the icons to add weapons and items to the shop." + ) + + return + end + + -- temp table for sorting + local paneltablefav = {} + local paneltable = {} + local steamid = client:SteamID64() + local col = client:GetRoleColor() + + for k = 1, #itms do + local item = itms[k] + local equipName = GetEquipmentTranslation(item.name, item.PrintName) + + if + t.search and string.find(string.lower(equipName), string.lower(t.search)) + or not t.search + then + local ic = nil + + -- Create icon panel + if item.iconMaterial then + ic = vgui.Create("LayeredIcon", dlist) + + if item.builtin and showCustomVar:GetBool() then + -- Custom marker icon + local marker = vgui.Create("DImage") + marker:SetImage("vgui/ttt/vskin/markers/builtin") + marker:SetImageColor(col) + + marker.PerformLayout = PerformMarkerLayout + + marker:SetTooltip(GetTranslation("builtin_marker")) + + ic:AddLayer(marker) + ic:EnableMousePassthrough(marker) + end + + -- Favorites marker icon + ic.favorite = false + + local favorites = GetFavorites(steamid, currole) + + if favorites and IsFavorite(favorites, item.id) then + ic.favorite = true + + if showFavoriteVar:GetBool() then + local star = vgui.Create("DImage") + star:SetImage("icon16/star.png") + + star.PerformLayout = PerformStarLayout + + star:SetTooltip("Favorite") + + ic:AddLayer(star) + ic:EnableMousePassthrough(star) + end + end + + -- Slot marker icon + if ItemIsWeapon(item) and showSlotVar:GetBool() then + local slot = vgui.Create("SimpleIconLabelled") + slot:SetIcon("vgui/ttt/slotcap") + slot:SetIconColor(col or COLOR_LGRAY) + slot:SetIconSize(16) + slot:SetIconText(MakeKindValid(item.Kind)) + slot:SetIconProperties( + COLOR_WHITE, + "DefaultBold", + { opacity = 220, offset = 1 }, + { 10, 8 } + ) + + ic:AddLayer(slot) + ic:EnableMousePassthrough(slot) + end + + ic:SetIconSize(itemSize or 64) + ic:SetMaterial(item.iconMaterial) + elseif item.itemModel then + ic = vgui.Create("SpawnIcon", dlist) + ic:SetModel(item.itemModel) + else + ErrorNoHaltWithStack("Equipment item does not have model or material specified.") + PrintTable(item) + + continue + end + + ic.item = item + + ic:SetTooltip(equipName .. " (" .. SafeTranslate(item.type) .. ")") + + -- If we cannot order this item, darken it + if + not t.notalive + and ( + ( + -- already owned +table.HasValue(owned_ids, item.id) + or items.IsItem(item.id) and item.limited and client:HasEquipmentItem( + item.id + ) + -- already carrying a weapon for this slot + or ItemIsWeapon(item) and not CanCarryWeapon(item) + or not shop.CanBuyEquipment(client, item.id) + -- already bought the item before + or item.limited and client:HasBought(item.id) + ) or (item.credits or 1) > credits + ) + then + ic:SetIconColor(color_darkened) + end + + if ic.favorite then + paneltablefav[k] = ic + else + paneltable[k] = ic + end + + -- icon doubleclick to buy + ic.PressedLeftMouse = function(self, doubleClick) + if + not doubleClick + or self.item.disabledBuy + or not enableDoubleClickBuy:GetBool() + then + return + end + + shop.BuyEquipment(client, self.item.id) + + ---@cast eqframe -nil + eqframe:Close() + end + end + end + + -- add favorites first + for _, panel in pairs(paneltablefav) do + dlist:AddPanel(panel) + end + + -- non favorites second + for _, panel in pairs(paneltable) do + dlist:AddPanel(panel) + end end local currentEquipmentCoroutine = coroutine.create(function(tbl) - while true do - tbl = coroutine.yield(CreateEquipmentList(tbl)) - end + while true do + tbl = coroutine.yield(CreateEquipmentList(tbl)) + end end) -- @@ -549,394 +635,453 @@ local color_bggrey = Color(90, 90, 95, 255) -- Creates / opens the shop frame -- @realm client function TraitorMenuPopup() - local ply = LocalPlayer() - - if not IsValid(ply) then return end - - local subrole = ply:GetSubRole() - local fallbackRole = GetShopFallback(subrole) - local rd = roles.GetByIndex(fallbackRole) - local notalive = false - local fallback = GetGlobalString("ttt_" .. rd.abbr .. "_shop_fallback") - - if ply:Alive() and ply:IsActive() and fallback == SHOP_DISABLED then return end - - -- calculate dimensions - local numCols, numRows, itemSize - - if allowChangeVar:GetBool() then - numCols = numColsVar:GetInt() - numRows = numRowsVar:GetInt() - itemSize = itemSizeVar:GetInt() - else - numCols = serverColsVar:GetInt() - numRows = serverRowsVar:GetInt() - itemSize = serverSizeVar:GetInt() - end - - -- margin - local m = 5 - local itemSizePad = itemSize + 2 - - -- item list width - local dlistw = itemSizePad * numCols + 13 - local dlisth = itemSizePad * numRows + 13 - - -- right column width - local diw = 270 - - -- frame size - local w = dlistw + diw + m * 4 - local h = dlisth + 75 - - -- Close shop if player clicks button again - if eqframe and IsValid(eqframe) then - eqframe:Close() - - return - end - - -- if the player is not alive / the round is not active let him choose his shop - if not ply:Alive() or not ply:IsActive() then - notalive = true - end - - -- Close any existing traitor menu - if eqframe and IsValid(eqframe) then - eqframe:Close() - end - - local credits = ply:GetCredits() - local can_order = true - local name = GetTranslation("equip_title") - - if GetGlobalBool("ttt2_random_shops") then - name = name .. " (RANDOM)" - end - - local dframe = vgui.Create("DFrame") - dframe:SetSize(w, h) - dframe:Center() - dframe:SetTitle(name) - dframe:SetVisible(true) - dframe:ShowCloseButton(true) - dframe:SetMouseInputEnabled(true) - dframe:SetDeleteOnClose(true) - - local dsheet = vgui.Create("DPropertySheet", dframe) - - -- Add a callback when switching tabs - local oldfunc = dsheet.SetActiveTab - - dsheet.SetActiveTab = function(self, new) - if not IsValid(new) then return end - - if self.m_pActiveTab ~= new and self.OnTabChanged then - self:OnTabChanged(self.m_pActiveTab, new) - end - - oldfunc(self, new) - end - - dsheet:SetPos(0, 0) - dsheet:StretchToParent(m, m + 25, m, m) - - local padding = dsheet:GetPadding() - - local dequip = vgui.Create("DPanel", dsheet) - dequip:SetPaintBackground(false) - dequip:StretchToParent(padding, padding, padding, padding) - - -- Construct icon listing - -- icon size = 64 x 64 - dlist = vgui.Create("EquipSelect", dequip) - -- local dlistw = 288 - dlist:SetPos(0, 0) - dlist:SetSize(dlistw, dlisth) - dlist:EnableVerticalScrollbar(true) - dlist:EnableHorizontal(true) - - coroutine.resume(currentEquipmentCoroutine, {notalive = notalive}) - - local bw, bh = 100, 25 -- button size - local dsph = 30 -- search panel height - local drsh = 30 -- role select DComboBox height - local depw = diw - m -- dequip panel width - - local depanel = vgui.Create("DPanel", dequip) - depanel:SetSize(depw, dsph + m * 2) - depanel:SetPos(dlistw + m, 0) - depanel:SetMouseInputEnabled(true) - depanel:SetPaintBackground(false) - - local dsearch = vgui.Create("DTextEntry", depanel) - dsearch:SetSize(depw - 20, dsph) - dsearch:SetPos(5, 5) - dsearch:SetUpdateOnType(true) - dsearch:SetEditable(true) - dsearch:SetText(LANG.GetTranslation("shop_search") .. "...") - - dsearch.selectAll = true - - local dbph = bh + m - local dbpw = bw + bh + m * 2 - - local dbtnpnl = vgui.Create("DPanel", dequip) - dbtnpnl:SetSize(dbpw, dbph) - dbtnpnl:SetPos(dlistw + m, dlisth - dbph + 2) - dbtnpnl:SetPaintBackground(false) - - local dconfirm = vgui.Create("DButton", dbtnpnl) - dconfirm:SetPos(m, m) - dconfirm:SetSize(bw, bh) - dconfirm:SetDisabled(true) - dconfirm:SetText(GetTranslation("equip_confirm")) - - --add a favorite button - dfav = vgui.Create("DButton", dbtnpnl) - dfav:CopyPos(dconfirm) - dfav:MoveRightOf(dconfirm) - dfav:SetSize(bh, bh) - dfav:SetDisabled(false) - dfav:SetText("") - dfav:SetImage("icon16/star.png") - - --add a cancel button - local dcancel = vgui.Create("DButton", dframe) - dcancel:SetPos(w - m * 3 - bw, h - bh - m * 3) - dcancel:SetSize(bw, bh) - dcancel:SetDisabled(false) - dcancel:SetText(GetTranslation("close")) - - local _, bpy = dbtnpnl:GetPos() - - local dinfobg = vgui.Create("DPanel", dequip) - dinfobg:SetPaintBackground(false) - dinfobg:SetPos(dlistw + m, 40) - - local drolesel = nil - - if notalive then - dnotaliveHelp = vgui.Create("DLabel", dequip) - dnotaliveHelp:SetText(GetTranslation("equip_not_alive")) - dnotaliveHelp:SetFont("DermaLarge") - dnotaliveHelp:SetWrap(true) - dnotaliveHelp:DockMargin(10, 0, depanel:GetWide() + 10, 0) - dnotaliveHelp:Dock(FILL) - - depanel:SetSize(depanel:GetWide(), depanel:GetTall() + drsh + m) - - drolesel = vgui.Create("DComboBox", depanel) - drolesel:SetSize(depw - 20, drsh) - drolesel:SetPos(m, dsph + m * 2) - drolesel:MoveBelow(dsearch, m) - - local rlsList = roles.GetList() - - for k = 1, #rlsList do - local v = rlsList[k] - - if v:IsShoppingRole() then - drolesel:AddChoice(SafeTranslate(v.name)) - end - end - - drolesel:SetValue(LANG.GetTranslation("shop_role_select") .. " ...") - - drolesel.OnSelect = function(panel, index, value) - print(LANG.GetParamTranslation("shop_role_selected", {role = value})) - - dnotaliveHelp:SetText("") - - coroutine.resume(currentEquipmentCoroutine, {role = RolenameToRole(value), search = dsearch:GetValue(), notalive = notalive}) - end - - dinfobg:MoveBelow(depanel, m) - end - - local _, ibgy = dinfobg:GetPos() - - dinfobg:SetSize(diw - m, bpy - ibgy - 120) -- -90 to let the help panel have more size - - dsearch.OnValueChange = function(slf, text) - if text == "" then - text = nil - end - - curSearch = text - - local crole - - if drolesel then - crole = RolenameToRole(drolesel:GetValue()) - end - - coroutine.resume(currentEquipmentCoroutine, {search = text, role = crole, notalive = notalive}) - end - - -- item info pane - local dinfo = vgui.Create("DScrollPanel", dinfobg) - dinfo:SetBackgroundColor(color_bggrey) - dinfo:SetPaintBackground(true) - dinfo:SetPos(0, 0) - dinfo:StretchToParent(0, 0, m * 2, m * 2) - - local dfields = {} - local _tmp = {"name", "type", "desc"} - - for i = 1, #_tmp do - local k = _tmp[i] - - dfields[k] = vgui.Create("DLabel", dinfo) - dfields[k]:SetTooltip(GetTranslation("equip_spec_" .. k)) - dfields[k]:SetPos(m * 3, m * 2) - dfields[k]:SetWidth(diw - m * 6) - dfields[k]:SetText("") - end - - dfields.name:SetFont("TabLarge") - - dfields.type:SetFont("DermaDefault") - dfields.type:MoveBelow(dfields.name) - - dfields.desc:SetFont("DermaDefaultBold") - dfields.desc:SetContentAlignment(7) - dfields.desc:MoveBelow(dfields.type, 1) - - local dhelp = vgui.Create("DPanel", dequip) - dhelp:SetPaintBackground(false) - dhelp:SetSize(diw, 116) - dhelp:SetPos(dlistw + m, 0) - dhelp:MoveBelow(dinfobg, 0) - - local update_preqs = PreqLabels(dhelp, m * 7, m * 2) - - dhelp:SizeToContents() - - dsheet:AddSheet(GetTranslation("equip_tabtitle"), dequip, "icon16/bomb.png", false, false, GetTranslation("equip_tooltip_main")) - - -- Item control - if ply:HasEquipmentItem("item_ttt_radar") then - local dradar = RADAR.CreateMenu(dsheet, dframe) - - dsheet:AddSheet(GetTranslation("radar_name"), dradar, "icon16/magnifier.png", false, false, GetTranslation("equip_tooltip_radar")) - end - - -- Weapon/item control - if IsValid(ply.radio) or ply:HasWeapon("weapon_ttt_radio") then - local dradio = TRADIO.CreateMenu(dsheet) - - dsheet:AddSheet(GetTranslation("radio_name"), dradio, "icon16/transmit.png", false, false, GetTranslation("equip_tooltip_radio")) - end - - -- Credit transferring - if credits > 0 then - local dtransfer = CreateTransferMenu(dsheet) - - dsheet:AddSheet(GetTranslation("xfer_name"), dtransfer, "icon16/group_gear.png", false, false, GetTranslation("equip_tooltip_xfer")) - end - - -- Random Shop Rerolling - if GetGlobalBool("ttt2_random_shops") and GetGlobalBool("ttt2_random_shop_reroll") then - local dtransfer = CreateRerollMenu(dsheet) + local client = LocalPlayer() + + if not IsValid(client) then + return + end + + local subrole = client:GetSubRole() + local fallbackRole = GetShopFallback(subrole) + local rd = roles.GetByIndex(fallbackRole) + local notalive = false + local fallback = GetGlobalString("ttt_" .. rd.abbr .. "_shop_fallback") + + if client:Alive() and client:IsActive() and fallback == SHOP_DISABLED then + return + end + + -- calculate dimensions + local numCols, numRows, itemSize + + if allowChangeVar:GetBool() then + numCols = numColsVar:GetInt() + numRows = numRowsVar:GetInt() + itemSize = itemSizeVar:GetInt() + else + numCols = serverColsVar:GetInt() + numRows = serverRowsVar:GetInt() + itemSize = serverSizeVar:GetInt() + end + + -- margin + local m = 5 + local itemSizePad = itemSize + 2 + + -- item list width + local dlistw = itemSizePad * numCols + 13 + local dlisth = itemSizePad * numRows + 13 + + -- right column width + local diw = 270 + + -- frame size + local w = dlistw + diw + m * 4 + local h = dlisth + 75 + + -- Close shop if player clicks button again + if IsValid(eqframe) then + ---@cast eqframe -nil + eqframe:Close() + + return + end + + -- if the player is not alive / the round is not active let him choose his shop + if not client:Alive() or not client:IsActive() then + notalive = true + end + + -- Close any existing traitor menu + if IsValid(eqframe) then + ---@cast eqframe -nil + eqframe:Close() + end + + local credits = client:GetCredits() + local can_order = true + local name = GetTranslation("equip_title") + + if GetGlobalBool("ttt2_random_shops") then + name = name .. " (RANDOM)" + end + + local dframe = vgui.Create("DFrame") + dframe:SetSize(w, h) + dframe:Center() + dframe:SetTitle(name) + dframe:SetVisible(true) + dframe:ShowCloseButton(true) + dframe:SetMouseInputEnabled(true) + dframe:SetDeleteOnClose(true) + + local dsheet = vgui.Create("DPropertySheet", dframe) + + -- Add a callback when switching tabs + local oldfunc = dsheet.SetActiveTab + + dsheet.SetActiveTab = function(self, new) + if not IsValid(new) then + return + end + + if self.m_pActiveTab ~= new and self.OnTabChanged then + self:OnTabChanged(self.m_pActiveTab, new) + end + + oldfunc(self, new) + end + + dsheet:SetPos(0, 0) + dsheet:StretchToParent(m, m + 25, m, m) + + local padding = dsheet:GetPadding() + + local dequip = vgui.Create("DPanel", dsheet) + dequip:SetPaintBackground(false) + dequip:StretchToParent(padding, padding, padding, padding) + + -- Construct icon listing + -- icon size = 64 x 64 + dlist = vgui.Create("EquipSelect", dequip) + -- local dlistw = 288 + dlist:SetPos(0, 0) + dlist:SetSize(dlistw, dlisth) + dlist:EnableVerticalScrollbar(true) + dlist:EnableHorizontal(true) + + coroutine.resume(currentEquipmentCoroutine, { notalive = notalive }) + + local bw, bh = 100, 25 -- button size + local dsph = 30 -- search panel height + local drsh = 30 -- role select DComboBox height + local depw = diw - m -- dequip panel width + + local depanel = vgui.Create("DPanel", dequip) + depanel:SetSize(depw, dsph + m * 2) + depanel:SetPos(dlistw + m, 0) + depanel:SetMouseInputEnabled(true) + depanel:SetPaintBackground(false) + + local dsearch = vgui.Create("DTextEntry", depanel) + dsearch:SetSize(depw - 20, dsph) + dsearch:SetPos(5, 5) + dsearch:SetUpdateOnType(true) + dsearch:SetEditable(true) + dsearch:SetText(LANG.GetTranslation("shop_search") .. "...") + + dsearch.selectAll = true + + local dbph = bh + m + local dbpw = bw + bh + m * 2 + + local dbtnpnl = vgui.Create("DPanel", dequip) + dbtnpnl:SetSize(dbpw, dbph) + dbtnpnl:SetPos(dlistw + m, dlisth - dbph + 2) + dbtnpnl:SetPaintBackground(false) + + local dconfirm = vgui.Create("DButton", dbtnpnl) + dconfirm:SetPos(m, m) + dconfirm:SetSize(bw, bh) + dconfirm:SetEnabled(false) + dconfirm:SetText(GetTranslation("equip_confirm")) + + --add a favorite button + dfav = vgui.Create("DButton", dbtnpnl) + dfav:CopyPos(dconfirm) + dfav:MoveRightOf(dconfirm) + dfav:SetSize(bh, bh) + dfav:SetEnabled(true) + dfav:SetText("") + dfav:SetImage("icon16/star.png") + + --add a cancel button + local dcancel = vgui.Create("DButton", dframe) + dcancel:SetPos(w - m * 3 - bw, h - bh - m * 3) + dcancel:SetSize(bw, bh) + dcancel:SetEnabled(true) + dcancel:SetText(GetTranslation("close")) + + local _, bpy = dbtnpnl:GetPos() + + local dinfobg = vgui.Create("DPanel", dequip) + dinfobg:SetPaintBackground(false) + dinfobg:SetPos(dlistw + m, 40) + + local drolesel = nil + + if notalive then + dnotaliveHelp = vgui.Create("DLabel", dequip) + dnotaliveHelp:SetText(GetTranslation("equip_not_alive")) + dnotaliveHelp:SetFont("DermaLarge") + dnotaliveHelp:SetWrap(true) + dnotaliveHelp:DockMargin(10, 0, depanel:GetWide() + 10, 0) + dnotaliveHelp:Dock(FILL) + + depanel:SetSize(depanel:GetWide(), depanel:GetTall() + drsh + m) + + drolesel = vgui.Create("DComboBox", depanel) + drolesel:SetSize(depw - 20, drsh) + drolesel:SetPos(m, dsph + m * 2) + drolesel:MoveBelow(dsearch, m) + + local rlsList = roles.GetList() + + for k = 1, #rlsList do + local v = rlsList[k] + + if v:IsShoppingRole() then + drolesel:AddChoice(SafeTranslate(v.name)) + end + end + + drolesel:SetValue(LANG.GetTranslation("shop_role_select") .. " ...") + + drolesel.OnSelect = function(panel, index, value) + Dev(2, LANG.GetParamTranslation("shop_role_selected", { role = value })) + + dnotaliveHelp:SetText("") + + coroutine.resume( + currentEquipmentCoroutine, + { role = RolenameToRole(value), search = dsearch:GetValue(), notalive = notalive } + ) + end + + dinfobg:MoveBelow(depanel, m) + end + + local _, ibgy = dinfobg:GetPos() + + dinfobg:SetSize(diw - m, bpy - ibgy - 120) -- -90 to let the help panel have more size + + dsearch.OnValueChange = function(slf, text) + if text == "" then + text = nil + end + + curSearch = text + + local crole + + if drolesel then + crole = RolenameToRole(drolesel:GetValue()) + end + + coroutine.resume( + currentEquipmentCoroutine, + { search = text, role = crole, notalive = notalive } + ) + end + + -- item info pane + local dinfo = vgui.Create("DScrollPanel", dinfobg) + dinfo:SetBackgroundColor(color_bggrey) + dinfo:SetPaintBackground(true) + dinfo:SetPos(0, 0) + dinfo:StretchToParent(0, 0, m * 2, m * 2) + + local dfields = {} + local _tmp = { "name", "type", "desc" } + + for i = 1, #_tmp do + local k = _tmp[i] + + dfields[k] = vgui.Create("DLabel", dinfo) + dfields[k]:SetTooltip(GetTranslation("equip_spec_" .. k)) + dfields[k]:SetPos(m * 3, m * 2) + dfields[k]:SetWidth(diw - m * 6) + dfields[k]:SetText("") + end + + dfields.name:SetFont("TabLarge") + + dfields.type:SetFont("DermaDefault") + dfields.type:MoveBelow(dfields.name) + + dfields.desc:SetFont("DermaDefaultBold") + dfields.desc:SetContentAlignment(7) + dfields.desc:MoveBelow(dfields.type, 1) + + local dhelp = vgui.Create("DPanel", dequip) + dhelp:SetPaintBackground(false) + dhelp:SetSize(diw, 116) + dhelp:SetPos(dlistw + m, 0) + dhelp:MoveBelow(dinfobg, 0) + + local update_preqs = PreqLabels(dhelp, m * 7, m * 2) + + dhelp:SizeToContents() + + dsheet:AddSheet( + GetTranslation("equip_tabtitle"), + dequip, + "icon16/bomb.png", + false, + false, + GetTranslation("equip_tooltip_main") + ) + + -- Item control + if client:HasEquipmentItem("item_ttt_radar") then + local dradar = RADAR.CreateMenu(dsheet, dframe) + + dsheet:AddSheet( + GetTranslation("radar_name"), + dradar, + "icon16/magnifier.png", + false, + false, + GetTranslation("equip_tooltip_radar") + ) + end + + -- Weapon/item control + if IsValid(client.radio) or client:HasWeapon("weapon_ttt_radio") then + local dradio = TRADIO.CreateMenu(dsheet) + + dsheet:AddSheet( + GetTranslation("radio_name"), + dradio, + "icon16/transmit.png", + false, + false, + GetTranslation("equip_tooltip_radio") + ) + end + + -- Credit transferring + if credits > 0 then + local dtransfer = CreateTransferMenu(dsheet) + + dsheet:AddSheet( + GetTranslation("xfer_name"), + dtransfer, + "icon16/group_gear.png", + false, + false, + GetTranslation("equip_tooltip_xfer") + ) + end + + -- Random Shop Rerolling + if shop.CanRerollShop(client) then + local dtransfer = CreateRerollMenu(dsheet) + + dsheet:AddSheet( + GetTranslation("reroll_name"), + dtransfer, + "vgui/ttt/equip/reroll.png", + false, + false, + GetTranslation("equip_tooltip_reroll") + ) + end + + --- + -- @realm client + -- stylua: ignore + hook.Run("TTTEquipmentTabs", dsheet) + + -- couple panelselect with info + dlist.OnActivePanelChanged = function(_, _, new) + if not IsValid(new) or not new.item then + return + end + + for k, v in pairs(new.item) do + if dfields[k] then + if k == "name" and new.item.PrintName then + dfields[k]:SetText(GetEquipmentTranslation(new.item.name, new.item.PrintName)) + else + dfields[k]:SetText(SafeTranslate(v)) + end + + dfields[k]:SetAutoStretchVertical(true) + dfields[k]:SetWrap(true) + end + end + + -- Trying to force everything to update to + -- the right size is a giant pain, so just + -- force a good size. + dfields.desc:SetTall(70) + + can_order = update_preqs(new.item) + + -- Easy accessable var for double-click buying + new.item.disabledBuy = not can_order + + dconfirm:SetEnabled(can_order) + end + + -- select first + dlist:SelectPanel(dlist:GetItems()[1]) + + -- prep confirm action + dconfirm.DoClick = function() + local pnl = dlist.SelectedPanel + + if not pnl or not pnl.item then + return + end + + local choice = pnl.item + + shop.BuyEquipment(client, choice.id) + + dframe:Close() + end + + -- update some basic info, may have changed in another tab + -- specifically the number of credits in the preq list + dsheet.OnTabChanged = function(_, _, new) + if not IsValid(new) or not IsValid(dlist.SelectedPanel) or new:GetPanel() ~= dequip then + return + end + + can_order = update_preqs(dlist.SelectedPanel.item) + + dlist.SelectedPanel.item.disabledBuy = not can_order + + dconfirm:SetEnabled(can_order) + end + + dcancel.DoClick = function() + dframe:Close() + end + + dfav.DoClick = function() + local pnl = dlist.SelectedPanel + local role = drolesel and RolenameToRole(drolesel:GetValue()) or client:GetSubRole() + + if not pnl or not pnl.item then + return + end + + local choice = pnl.item + local weapon = choice.id + local steamid = client:SteamID64() + + CreateFavTable() + + if pnl.favorite then + RemoveFavorite(steamid, role, weapon) + else + AddFavorite(steamid, role, weapon) + end + + -- Reload item list + coroutine.resume( + currentEquipmentCoroutine, + { role = role, search = curSearch, notalive = notalive } + ) + end - dsheet:AddSheet(GetTranslation("reroll_name"), dtransfer, "vgui/ttt/equip/reroll.png", false, false, GetTranslation("equip_tooltip_reroll")) - end + dframe:MakePopup() + dframe:SetKeyboardInputEnabled(false) - --- - -- @realm client - hook.Run("TTTEquipmentTabs", dsheet) - - -- couple panelselect with info - dlist.OnActivePanelChanged = function(_, _, new) - if not IsValid(new) or not new.item then return end - - for k, v in pairs(new.item) do - if dfields[k] then - if k == "name" and new.item.PrintName then - dfields[k]:SetText(GetEquipmentTranslation(new.item.name, new.item.PrintName)) - else - dfields[k]:SetText(SafeTranslate(v)) - end - - dfields[k]:SetAutoStretchVertical(true) - dfields[k]:SetWrap(true) - end - end - - -- Trying to force everything to update to - -- the right size is a giant pain, so just - -- force a good size. - dfields.desc:SetTall(70) - - can_order = update_preqs(new.item) - - -- Easy accessable var for double-click buying - new.item.disabledBuy = not can_order - - dconfirm:SetDisabled(not can_order) - end - - -- select first - dlist:SelectPanel(dlist:GetItems()[1]) - - -- prep confirm action - dconfirm.DoClick = function() - local pnl = dlist.SelectedPanel - - if not pnl or not pnl.item then return end - - local choice = pnl.item - - net.Start("TTT2OrderEquipment") - net.WriteString(choice.id) - net.SendToServer() - - dframe:Close() - end - - -- update some basic info, may have changed in another tab - -- specifically the number of credits in the preq list - dsheet.OnTabChanged = function(_, _, new) - if not IsValid(new) or not IsValid(dlist.SelectedPanel) or new:GetPanel() ~= dequip then return end - - can_order = update_preqs(dlist.SelectedPanel.item) - - dlist.SelectedPanel.item.disabledBuy = not can_order - - dconfirm:SetDisabled(not can_order) - end - - dcancel.DoClick = function() - dframe:Close() - end - - dfav.DoClick = function() - local pnl = dlist.SelectedPanel - local role = drolesel and RolenameToRole(drolesel:GetValue()) or ply:GetSubRole() - - if not pnl or not pnl.item then return end - - local choice = pnl.item - local weapon = choice.id - local steamid = ply:SteamID64() - - CreateFavTable() - - if pnl.favorite then - RemoveFavorite(steamid, role, weapon) - else - AddFavorite(steamid, role, weapon) - end - - -- Reload item list - coroutine.resume(currentEquipmentCoroutine, {role = role, search = curSearch, notalive = notalive}) - end - - dframe:MakePopup() - dframe:SetKeyboardInputEnabled(false) - - eqframe = dframe + eqframe = dframe end concommand.Add("ttt_cl_traitorpopup", TraitorMenuPopup) @@ -945,9 +1090,10 @@ concommand.Add("ttt_cl_traitorpopup", TraitorMenuPopup) -- local function ForceCloseTraitorMenu(ply, cmd, args) - if IsValid(eqframe) then - eqframe:Close() - end + if IsValid(eqframe) then + ---@cast eqframe -nil + eqframe:Close() + end end concommand.Add("ttt_cl_traitorpopup_close", ForceCloseTraitorMenu) @@ -955,114 +1101,55 @@ concommand.Add("ttt_cl_traitorpopup_close", ForceCloseTraitorMenu) -- NET RELATED STUFF: -- -local function ReceiveEquipment() - local ply = LocalPlayer() - if not IsValid(ply) then return end - - local eqAmount = net.ReadUInt(16) - local tmp = {} - local toRem = {} - - for i = 1, eqAmount do - tmp[i] = net.ReadString() - end - - local equipItems = ply:GetEquipmentItems() - - -- reset all old items - for k = 1, #equipItems do - local v = equipItems[k] - - if table.HasValue(tmp, v) then continue end - - local item = items.GetStored(v) - if item and isfunction(item.Reset) then - item:Reset(ply) - end - - table.insert(toRem, 1, k) - end - - -- remove finally - for i = 1, #toRem do - table.remove(equipItems, toRem[i]) - end - - -- now equip the items the player doesn't own - for k = 1, #tmp do - local v = tmp[k] - - if not table.HasValue(equipItems, v) then - equipItems[#equipItems + 1] = v - - local item = items.GetStored(v) - if item and isfunction(item.Equip) then - item:Equip(ply) - end - end - end -end -net.Receive("TTT_Equipment", ReceiveEquipment) - -local function ReceiveCredits() - local ply = LocalPlayer() - if not IsValid(ply) then return end - - ply.equipment_credits = net.ReadUInt(8) -end -net.Receive("TTT_Credits", ReceiveCredits) - local r = 0 local function ReceiveBought() - local ply = LocalPlayer() - if not IsValid(ply) then return end - - ply.bought = {} - - local num = net.ReadUInt(8) - - for i = 1, num do - local s = net.ReadString() - if s ~= "" then - ply.bought[#ply.bought + 1] = s - - BUYTABLE[s] = true - - local team = ply:GetTeam() - if team then - TEAMBUYTABLE[team] = TEAMBUYTABLE[team] or {} - TEAMBUYTABLE[team][s] = true - end - end - end - - -- This usermessage sometimes fails to contain the last weapon that was - -- bought, even though resending then works perfectly. Possibly a bug in - -- bf_read. Anyway, this hack is a workaround: we just request a new umsg. - if num ~= #ply.bought and r < 10 then -- r is an infinite loop guard - RunConsoleCommand("ttt_resend_bought") - - r = r + 1 - else - r = 0 - end + local client = LocalPlayer() + if not IsValid(client) then + return + end + + client.bought = {} + + local num = net.ReadUInt(8) + + for i = 1, num do + local equipmentName = net.ReadString() + if equipmentName ~= "" then + client.bought[#client.bought + 1] = equipmentName + + shop.SetEquipmentBought(LocalPlayer(), equipmentName) + shop.SetEquipmentTeamBought(client, equipmentName) + end + end + + -- This usermessage sometimes fails to contain the last weapon that was + -- bought, even though resending then works perfectly. Possibly a bug in + -- bf_read. Anyway, this hack is a workaround: we just request a new umsg. + if num ~= #client.bought and r < 10 then -- r is an infinite loop guard + RunConsoleCommand("ttt_resend_bought") + + r = r + 1 + else + r = 0 + end end net.Receive("TTT_Bought", ReceiveBought) -- Player received the item he has just bought, so run clientside init local function ReceiveBoughtItem() - local id = net.ReadString() - - local item = items.GetStored(id) - if item and isfunction(item.Bought) then - item:Bought(LocalPlayer()) - end - - --- - -- I can imagine custom equipment wanting this, so making a hook - -- @realm client - hook.Run("TTTBoughtItem", item ~= nil, (item and item.oldId or nil) or id) + local id = net.ReadString() + + local item = items.GetStored(id) + if item and isfunction(item.Bought) then + item:Bought(LocalPlayer()) + end + + --- + -- I can imagine custom equipment wanting this, so making a hook + -- @realm client + -- stylua: ignore + hook.Run("TTTBoughtItem", item ~= nil, (item and item.oldId or nil) or id) end net.Receive("TTT_BoughtItem", ReceiveBoughtItem) @@ -1078,90 +1165,76 @@ net.Receive("TTT_BoughtItem", ReceiveBoughtItem) -- @ref https://wiki.facepunch.com/gmod/GM:OnContextMenuOpen -- @local function GM:OnContextMenuOpen() - local rs = GetRoundState() + local rs = GetRoundState() - if (rs == ROUND_PREP or rs == ROUND_POST) and not alwaysShowShopVar:GetBool() then - CLSCORE:Toggle() + if (rs == ROUND_PREP or rs == ROUND_POST) and not alwaysShowShopVar:GetBool() then + CLSCORE:Toggle() - return - end + return + end - -- this will close the CLSCORE panel if its currently visible - if IsValid(CLSCORE.Panel) and CLSCORE.Panel:IsVisible() then - CLSCORE.Panel:SetVisible(false) + -- this will close the CLSCORE panel if its currently visible + if IsValid(CLSCORE.Panel) and CLSCORE.Panel:IsVisible() then + CLSCORE.Panel:SetVisible(false) - return - end + return + end - --- - -- @realm client - if hook.Run("TTT2PreventAccessShop", LocalPlayer()) then return end + --- + -- @realm client + -- stylua: ignore + if hook.Run("TTT2PreventAccessShop", LocalPlayer()) then return end - if IsValid(eqframe) then - eqframe:Close() - else - RunConsoleCommand("ttt_cl_traitorpopup") - end + if IsValid(eqframe) then + ---@cast eqframe -nil + eqframe:Close() + else + RunConsoleCommand("ttt_cl_traitorpopup") + end end ---- --- Caches the material or model --- @param table item --- @realm client -function TTT2CacheEquipMaterials(item) - --if there is no material or model, the item should probably not be available in the shop - if item.material and item.material ~= "vgui/ttt/icon_id" then - item.ttt2_cached_material = Material(item.material) - if item.ttt2_cached_material:IsError() then - -- Setting fallback material - item.ttt2_cached_material = fallback_mat - end - elseif item.model and item.model ~= "models/weapons/w_bugbait.mdl" then - --do not use fallback mat and use model instead - item.ttt2_cached_material = nil - item.ttt2_cached_model = model - end -end - --- Preload materials for the shop -hook.Add("PostInitPostEntity", "TTT2CacheEquipMaterials", function() - local itms = ShopEditor.GetEquipmentForRoleAll() - - for _, item in pairs(itms) do - TTT2CacheEquipMaterials(item) - end -end) - -- Closes menu when roles are selected hook.Add("TTTBeginRound", "TTTBEMCleanUp", function() - if not IsValid(eqframe) then return end + if not IsValid(eqframe) then + return + end - eqframe:Close() + ---@cast eqframe -nil + eqframe:Close() end) -- Closes menu when round is overwritten hook.Add("TTTEndRound", "TTTBEMCleanUp", function() - if not IsValid(eqframe) then return end + if not IsValid(eqframe) then + return + end - eqframe:Close() + ---@cast eqframe -nil + eqframe:Close() end) -- Search text field focus hooks local function getKeyboardFocus(pnl) - if IsValid(eqframe) and pnl:HasParent(eqframe) then - eqframe:SetKeyboardInputEnabled(true) - end + if IsValid(eqframe) and pnl:HasParent(eqframe) then + ---@cast eqframe -nil + eqframe:SetKeyboardInputEnabled(true) + end - if not pnl.selectAll then return end + if not pnl.selectAll then + return + end - pnl:SelectAllText() + pnl:SelectAllText() end hook.Add("OnTextEntryGetFocus", "BEM_GetKeyboardFocus", getKeyboardFocus) local function loseKeyboardFocus(pnl) - if not IsValid(eqframe) or not pnl:HasParent(eqframe) then return end + if not IsValid(eqframe) or not pnl:HasParent(eqframe) then + return + end - eqframe:SetKeyboardInputEnabled(false) + ---@cast eqframe -nil + eqframe:SetKeyboardInputEnabled(false) end hook.Add("OnTextEntryLoseFocus", "BEM_LoseKeyboardFocus", loseKeyboardFocus) @@ -1171,9 +1244,7 @@ hook.Add("OnTextEntryLoseFocus", "BEM_LoseKeyboardFocus", loseKeyboardFocus) -- @param DPropertySheet dSheet The property sheet where contents can be added -- @hook -- @realm client -function GM:TTTEquipmentTabs(dSheet) - -end +function GM:TTTEquipmentTabs(dSheet) end --- -- A clientside hook that is called on the client of the player @@ -1183,9 +1254,7 @@ end -- @param string idOrCls The id of the @{ITEM} or @{Weapon}, old id for @{ITEM} and class for @{Weapon} -- @hook -- @realm client -function GM:TTTBoughtItem(isItem, idOrCls) - -end +function GM:TTTBoughtItem(isItem, idOrCls) end --- -- Cancelable hook to prevent the usage of the shop on the client. @@ -1193,6 +1262,4 @@ end -- @return boolean Return true to prevent shop access -- @hook -- @realm client -function GM:TTT2PreventAccessShop(ply) - -end +function GM:TTT2PreventAccessShop(ply) end diff --git a/gamemodes/terrortown/gamemode/client/cl_eventpopup.lua b/gamemodes/terrortown/gamemode/client/cl_eventpopup.lua index 3b65d0843..8d15bff26 100644 --- a/gamemodes/terrortown/gamemode/client/cl_eventpopup.lua +++ b/gamemodes/terrortown/gamemode/client/cl_eventpopup.lua @@ -6,14 +6,14 @@ local TryT = LANG.TryTranslation local defaultMessage = { - title = { - text = LANG.TryTranslation("testpopup_title") - }, - subtitle = { - text = LANG.TryTranslation("testpopup_subtitle") - }, - iconTable = {}, - time = CurTime() + 5 + title = { + text = LANG.TryTranslation("testpopup_title"), + }, + subtitle = { + text = LANG.TryTranslation("testpopup_subtitle"), + }, + iconTable = {}, + time = CurTime() + 5, } local counter = 0 @@ -27,13 +27,15 @@ EPOP.messageQueue = EPOP.messageQueue or {} -- @realm client -- @internal function EPOP:Think() - if #self.messageQueue == 0 then return end + if #self.messageQueue == 0 then + return + end - local elem = self.messageQueue[1] + local elem = self.messageQueue[1] - if elem.time and CurTime() >= elem.time then - EPOP:RemoveMessage(elem.id) - end + if elem.time and CurTime() >= elem.time then + EPOP:RemoveMessage(elem.id) + end end --- @@ -46,35 +48,35 @@ end -- @return string Returns a unique id generated for this message -- @realm client function EPOP:AddMessage(title, subtitle, displayTime, iconTable, blocking) - local id = "epop_unique_id_" .. counter + local id = "epop_unique_id_" .. counter - counter = counter + 1 + counter = counter + 1 - local queueSize = #self.messageQueue + local queueSize = #self.messageQueue - -- delete previous message if it is nonblocking - if queueSize > 0 and not self.messageQueue[queueSize].blocking then - table.remove(self.messageQueue, queueSize) + -- delete previous message if it is nonblocking + if queueSize > 0 and not self.messageQueue[queueSize].blocking then + table.remove(self.messageQueue, queueSize) - queueSize = queueSize - 1 - end + queueSize = queueSize - 1 + end - -- add the new message to the queue - self.messageQueue[queueSize + 1] = { - id = id, - title = istable(title) and title or {text = title or ""}, - subtitle = istable(subtitle) and subtitle or {text = subtitle or ""}, - displayTime = displayTime or 4, - iconTable = iconTable or {}, - blocking = blocking == nil and false or blocking - } + -- add the new message to the queue + self.messageQueue[queueSize + 1] = { + id = id, + title = istable(title) and title or { text = title or "" }, + subtitle = istable(subtitle) and subtitle or { text = subtitle or "" }, + displayTime = displayTime or 4, + iconTable = iconTable or {}, + blocking = blocking == nil and false or blocking, + } - -- the new element is the first one in the list and should be therefore activated - if queueSize == 0 then - self:ActivateMessage() - end + -- the new element is the first one in the list and should be therefore activated + if queueSize == 0 then + self:ActivateMessage() + end - return id + return id end --- @@ -82,13 +84,15 @@ end -- @internal -- @realm client function EPOP:ActivateMessage() - if #self.messageQueue == 0 then return end + if #self.messageQueue == 0 then + return + end - local elem = self.messageQueue[1] + local elem = self.messageQueue[1] - elem.time = CurTime() + elem.displayTime + elem.time = CurTime() + elem.displayTime - print("[TTT2] " .. elem.title.text .. " // " .. elem.subtitle.text) + Dev(1, "[TTT2] " .. elem.title.text .. " // " .. elem.subtitle.text) end --- @@ -96,7 +100,7 @@ end -- @return boolean Returns if the message shoould be rendered -- @realm client function EPOP:ShouldRender() - return #self.messageQueue > 0 + return #self.messageQueue > 0 end --- @@ -104,7 +108,7 @@ end -- @return table The message table -- @realm client function EPOP:GetMessage() - return self.messageQueue[1] + return self.messageQueue[1] end --- @@ -112,7 +116,7 @@ end -- @return table The message table -- @realm client function EPOP:GetDefaultMessage() - return table.Copy(defaultMessage) + return table.Copy(defaultMessage) end --- @@ -120,12 +124,12 @@ end -- @param number index The nimeric index -- @realm client function EPOP:RemoveMessageByIndex(index) - table.remove(self.messageQueue, index) + table.remove(self.messageQueue, index) - -- if the removed message was the first in the queue, the next one should be activated - if index == 1 then - EPOP:ActivateMessage() - end + -- if the removed message was the first in the queue, the next one should be activated + if index == 1 then + EPOP:ActivateMessage() + end end --- @@ -135,58 +139,60 @@ end -- @return boolean Returns true if a message got deleted -- @realm client function EPOP:RemoveMessage(id) - if #self.messageQueue == 0 then - return false - end + if #self.messageQueue == 0 then + return false + end - if id then - for i = 1, #self.messageQueue do - local elem = self.messageQueue[i] + if id then + for i = 1, #self.messageQueue do + local elem = self.messageQueue[i] - if elem.id ~= id then continue end + if elem.id ~= id then + continue + end - self:RemoveMessageByIndex(i) + self:RemoveMessageByIndex(i) - return true - end - else - self:RemoveMessageByIndex(1) + return true + end + else + self:RemoveMessageByIndex(1) - return true - end + return true + end - return false + return false end --- -- Clears the whole message queue -- @realm client function EPOP:Clear() - self.messageQueue = {} + self.messageQueue = {} end net.Receive("ttt2_eventpopup", function() - local title, subtitle + local title, subtitle - if net.ReadBool() then - title = {} + if net.ReadBool() then + title = {} - title.text = TryT(net.ReadString()) + title.text = TryT(net.ReadString()) - if net.ReadBool() then - title.color = net.ReadColor() - end - end + if net.ReadBool() then + title.color = net.ReadColor() + end + end - if net.ReadBool() then - subtitle = {} + if net.ReadBool() then + subtitle = {} - subtitle.text = TryT(net.ReadString()) + subtitle.text = TryT(net.ReadString()) - if net.ReadBool() then - subtitle.color = net.ReadColor() - end - end + if net.ReadBool() then + subtitle.color = net.ReadColor() + end + end - EPOP:AddMessage(title, subtitle, net.ReadUInt(16), nil, net.ReadBool()) + EPOP:AddMessage(title, subtitle, net.ReadUInt(16), nil, net.ReadBool()) end) diff --git a/gamemodes/terrortown/gamemode/client/cl_help.lua b/gamemodes/terrortown/gamemode/client/cl_help.lua index e9aed0446..8079abf01 100644 --- a/gamemodes/terrortown/gamemode/client/cl_help.lua +++ b/gamemodes/terrortown/gamemode/client/cl_help.lua @@ -5,109 +5,115 @@ local IsValid = IsValid --- -- @realm client +-- stylua: ignore CreateConVar("ttt_spectator_mode", "0", FCVAR_ARCHIVE) --- -- @realm client +-- stylua: ignore CreateConVar("ttt_mute_team_check", "0") local function SpectateCallback(cv, old, new) - local num = tonumber(new) + local num = tonumber(new) - if num and (num == 0 or num == 1) then - RunConsoleCommand("ttt_spectate", num) - end + if num and (num == 0 or num == 1) then + RunConsoleCommand("ttt_spectate", num) + end end cvars.AddChangeCallback("ttt_spectator_mode", SpectateCallback) local function MuteTeamCallback(cv, old, new) - net.Start("TTT2MuteTeam") - net.WriteBool(tobool(new)) - net.SendToServer() + net.Start("TTT2MuteTeam") + net.WriteBool(tobool(new)) + net.SendToServer() end cvars.AddChangeCallback("ttt_mute_team_check", MuteTeamCallback) -- LOAD HELP MENU PAGE CLASSES local function ShouldInherit(t, base) - return t.base ~= t.type + return t.base ~= t.type end -- callback function that is called once the submenu class is loaded local function OnSubmenuClassLoaded(class, path, name) - class.type = name - class.base = class.base or "base_gamemodesubmenu" + class.type = name + class.base = class.base or "base_gamemodesubmenu" - MsgN("Added TTT2 gamemode submenu file: ", path, name) + Dev(1, "Added TTT2 gamemode submenu file: ", path, name) end -- load submenu base from specific folder local submenuBase = classbuilder.BuildFromFolder( - "terrortown/menus/gamemode/base_gamemodemenu/", - CLIENT_FILE, - "CLGAMEMODESUBMENU", -- class scope - OnSubmenuClassLoaded, -- on class loaded - true, -- should inherit - ShouldInherit -- special inheritance check + "terrortown/menus/gamemode/base_gamemodemenu/", + CLIENT_FILE, + "CLGAMEMODESUBMENU", -- class scope + OnSubmenuClassLoaded, -- on class loaded + true, -- should inherit + ShouldInherit -- special inheritance check ) -- callback function that is called once the menu class is loaded; -- also used to load submenus for this menu local function OnMenuClassLoaded(class, path, name) - class.type = name - class.base = class.base or "base_gamemodemenu" + class.type = name + class.base = class.base or "base_gamemodemenu" - MsgN("Added TTT2 gamemode menu file: ", path, name) + Dev(1, "Added TTT2 gamemode menu file: ", path, name) end -- initialize the submenus after they were loaded local function InitSubmenu(class, path, name) - class:Initialize() + class:Initialize() end -- once the classes are set up and inherited from their base, they -- are ready to be used, i.e. their submenus can be added local function LoadSubmenus(class, path, name) - -- do not load the submenu base again - if name == "base_gamemodemenu" then return end - - -- now search for submenus in the corresponding folder - local submenus = classbuilder.BuildFromFolder( - "terrortown/menus/gamemode/" .. name .. "/", - CLIENT_FILE, - "CLGAMEMODESUBMENU", -- class scope - OnSubmenuClassLoaded, -- on class loaded - true, -- should inherit - ShouldInherit, -- special inheritance check - submenuBase, -- passing through additional menu table for inheritance - InitSubmenu -- post inheritance callback - ) - - -- transfer mnus into indexed table and sort by priority - local submenusIndexed = {} - - for _, submenu in pairs(submenus) do - if submenu.type == "base_gamemodesubmenu" then continue end - - submenusIndexed[#submenusIndexed + 1] = submenu - end - - table.SortByMember(submenusIndexed, "priority") - - class:SetSubmenuTable(submenusIndexed) - - class:Initialize() + -- do not load the submenu base again + if name == "base_gamemodemenu" then + return + end + + -- now search for submenus in the corresponding folder + local submenus = classbuilder.BuildFromFolder( + "terrortown/menus/gamemode/" .. name .. "/", + CLIENT_FILE, + "CLGAMEMODESUBMENU", -- class scope + OnSubmenuClassLoaded, -- on class loaded + true, -- should inherit + ShouldInherit, -- special inheritance check + submenuBase, -- passing through additional menu table for inheritance + InitSubmenu -- post inheritance callback + ) + + -- transfer mnus into indexed table and sort by priority + local submenusIndexed = {} + + for _, submenu in pairs(submenus) do + if submenu.type == "base_gamemodesubmenu" then + continue + end + + submenusIndexed[#submenusIndexed + 1] = submenu + end + + table.SortByMember(submenusIndexed, "priority") + + class:SetSubmenuTable(submenusIndexed) + + class:Initialize() end local menus = classbuilder.BuildFromFolder( - "terrortown/menus/gamemode/", - CLIENT_FILE, - "CLGAMEMODEMENU", -- class scope - OnMenuClassLoaded, -- on class loaded callback - true, -- should inherit - ShouldInherit, -- special inheritance check - nil, -- don't pass through additional classes - LoadSubmenus -- post inheritance callback + "terrortown/menus/gamemode/", + CLIENT_FILE, + "CLGAMEMODEMENU", -- class scope + OnMenuClassLoaded, -- on class loaded callback + true, -- should inherit + ShouldInherit, -- special inheritance check + nil, -- don't pass through additional classes + LoadSubmenus -- post inheritance callback ) -- transfer mnus into indexed table and sort by priority @@ -115,9 +121,11 @@ local menus = classbuilder.BuildFromFolder( menusIndexed = {} for _, menu in pairs(menus) do - if menu.type == "base_gamemodemenu" then continue end + if menu.type == "base_gamemodemenu" then + continue + end - menusIndexed[#menusIndexed + 1] = menu + menusIndexed[#menusIndexed + 1] = menu end table.SortByMember(menusIndexed, "priority") @@ -146,19 +154,19 @@ local heightButtonPanel = 80 local heightAdminSeperator = 50 local function AddMenuButtons(menuTbl, parent, widthButton, heightButton) - for i = 1, #menuTbl do - local menuClass = menuTbl[i] - - local settingsButton = parent:Add("DMenuButtonTTT2") - settingsButton:SetSize(widthButton, heightButton) - settingsButton:SetTitle(menuClass.title or menuClass.type) - settingsButton:SetDescription(menuClass.description) - settingsButton:SetImage(menuClass.icon) - - settingsButton.DoClick = function(slf) - HELPSCRN:ShowSubmenu(menuClass) - end - end + for i = 1, #menuTbl do + local menuClass = menuTbl[i] + + local settingsButton = parent:Add("DMenuButtonTTT2") + settingsButton:SetSize(widthButton, heightButton) + settingsButton:SetTitle(menuClass.title or menuClass.type) + settingsButton:SetDescription(menuClass.description) + settingsButton:SetImage(menuClass.icon) + + settingsButton.DoClick = function(slf) + HELPSCRN:ShowSubmenu(menuClass) + end + end end -- since the main menu has no ID, it has this static ID @@ -168,74 +176,80 @@ local MAIN_MENU = "main" -- Opens the help screen -- @realm client function HELPSCRN:ShowMainMenu() - local frame = self.menuFrame + local frame = self.menuFrame - -- IF MENU ELEMENT DOES NOT ALREADY EXIST, CREATE IT - if IsValid(frame) then - frame:ClearFrame(nil, nil, "help_title") - else - frame = vguihandler.GenerateFrame(width, height, "help_title", true) - end + -- IF MENU ELEMENT DOES NOT ALREADY EXIST, CREATE IT + if IsValid(frame) then + frame:ClearFrame(nil, nil, "help_title") + else + frame = vguihandler.GenerateFrame(width, height, "help_title") + end - self.menuFrame = frame + self.menuFrame = frame - -- INIT MAIN MENU SPECIFIC STUFF - frame:SetPadding(self.padding, self.padding, self.padding, self.padding) + -- INIT MAIN MENU SPECIFIC STUFF + frame:SetPadding(self.padding, self.padding, self.padding, self.padding) - -- MARK AS MAIN MENU - self.currentMenuId = MAIN_MENU + -- MARK AS MAIN MENU + self.currentMenuId = MAIN_MENU - -- MAKE MAIN FRAME SCROLLABLE - local scrollPanel = vgui.Create("DScrollPanelTTT2", frame) - scrollPanel:Dock(FILL) + -- MAKE MAIN FRAME SCROLLABLE + local scrollPanel = vgui.Create("DScrollPanelTTT2", frame) + scrollPanel:Dock(FILL) - -- SPLIT FRAME INTO A GRID LAYOUT - local dsettings = vgui.Create("DIconLayout", scrollPanel) - dsettings:Dock(FILL) - dsettings:SetSpaceX(self.padding) - dsettings:SetSpaceY(self.padding) + -- SPLIT FRAME INTO A GRID LAYOUT + local dsettings = vgui.Create("DIconLayout", scrollPanel) + dsettings:Dock(FILL) + dsettings:SetSpaceX(self.padding) + dsettings:SetSpaceY(self.padding) - -- SPLIT INTO NORMAL AND ADMIN MENUS - local menusNormal, menusAdmin = {}, {} + -- SPLIT INTO NORMAL AND ADMIN MENUS + local menusNormal, menusAdmin = {}, {} - for i = 1, #menusIndexed do - local menu = menusIndexed[i] + for i = 1, #menusIndexed do + local menu = menusIndexed[i] - if not menu:ShouldShow() then continue end + if not menu:ShouldShow() then + continue + end - if menu:IsAdminMenu() then - menusAdmin[#menusAdmin + 1] = menu - else - menusNormal[#menusNormal + 1] = menu - end - end + if menu:IsAdminMenu() then + menusAdmin[#menusAdmin + 1] = menu + else + menusNormal[#menusNormal + 1] = menu + end + end - local rows = math.ceil(#menusNormal / 3) + math.ceil(#menusAdmin / 3) - local maxHeight = rows * heightMainMenuButton + (rows - 1) * self.padding + ((#menusAdmin == 0) and 0 or heightAdminSeperator) - local heightScroll = height - vskin.GetHeaderHeight() - vskin.GetBorderSize() - 2 * self.padding + local rows = math.ceil(#menusNormal / 3) + math.ceil(#menusAdmin / 3) + local maxHeight = rows * heightMainMenuButton + + (rows - 1) * self.padding + + ((#menusAdmin == 0) and 0 or heightAdminSeperator) + local heightScroll = height - vskin.GetHeaderHeight() - vskin.GetBorderSize() - 2 * self.padding - local scrollSize = (heightScroll < maxHeight and 20 or 0) + local scrollSize = (heightScroll < maxHeight and 20 or 0) - local widthMainMenuButton = (width - 4 * self.padding - scrollSize) / 3 + local widthMainMenuButton = (width - 4 * self.padding - scrollSize) / 3 - AddMenuButtons(menusNormal, dsettings, widthMainMenuButton, heightMainMenuButton) + AddMenuButtons(menusNormal, dsettings, widthMainMenuButton, heightMainMenuButton) - -- only show admin section if player is admin and - -- there are menues to be shown - if #menusAdmin == 0 then return end + -- only show admin section if player is admin and + -- there are menues to be shown + if #menusAdmin == 0 then + return + end - local labelSpacer = dsettings:Add("DLabelTTT2") - labelSpacer.OwnLine = true - labelSpacer:SetText("label_menu_admin_spacer") - labelSpacer:SetSize(width - 2 * self.padding - scrollSize, heightAdminSeperator) - labelSpacer:SetFont("DermaTTT2TextLarge") - labelSpacer.Paint = function(slf, w, h) - derma.SkinHook("Paint", "LabelSpacerTTT2", slf, w, h) + local labelSpacer = dsettings:Add("DLabelTTT2") + labelSpacer.OwnLine = true + labelSpacer:SetText("label_menu_admin_spacer") + labelSpacer:SetSize(width - 2 * self.padding - scrollSize, heightAdminSeperator) + labelSpacer:SetFont("DermaTTT2TextLarge") + labelSpacer.Paint = function(slf, w, h) + derma.SkinHook("Paint", "LabelSpacerTTT2", slf, w, h) - return true - end + return true + end - AddMenuButtons(menusAdmin, dsettings, widthMainMenuButton, heightMainMenuButton) + AddMenuButtons(menusAdmin, dsettings, widthMainMenuButton, heightMainMenuButton) end --- @@ -249,13 +263,15 @@ HELPSCRN.Show = HELPSCRN.ShowMainMenu -- @return[default=nil] string The id of the opened menu or nil -- @realm client function HELPSCRN:GetOpenMenu() - if not self:IsVisible() then return end + if not self:IsVisible() then + return + end - if self.currentMenuId == MAIN_MENU then - return MAIN_MENU - end + if self.currentMenuId == MAIN_MENU then + return MAIN_MENU + end - return self.currentMenuId .. "_" .. self.submenuClass.type + return self.currentMenuId .. "_" .. self.submenuClass.type end --- @@ -264,49 +280,52 @@ end -- @param table submenuClass The submenu class -- @realm client function HELPSCRN:SetupContentArea(parent, submenuClass) - self.parent = parent - self.lastSubmenuClass = self.submenuClass - self.submenuClass = submenuClass + self.parent = parent + self.lastSubmenuClass = self.submenuClass + self.submenuClass = submenuClass end --- -- Builds the content area, the data has to be set previously. -- @realm client function HELPSCRN:BuildContentArea() - local parent = self.parent + local parent = self.parent - if not IsValid(parent) then return end + if not IsValid(parent) then + return + end - --- - -- @realm client - if hook.Run("TTT2OnHelpSubmenuClear", parent, self.currentMenuId, self.lastMenuData, self.submenuClass) == false then return end + --- + -- @realm client + -- stylua: ignore + if hook.Run("TTT2OnHelpSubmenuClear", parent, self.currentMenuId, self.lastMenuData, self.submenuClass) == false then return end - parent:Clear() + parent:Clear() - local width2, height2 = parent:GetSize() - local _, paddingTop, _, paddingBottom = parent:GetDockPadding() + local width2, height2 = parent:GetSize() + local _, paddingTop, _, paddingBottom = parent:GetDockPadding() - -- CALCULATE SIZE BASED ON EXISTENCE OF BUTTON PANEL - if self.submenuClass:HasButtonPanel() then - height2 = height2 - heightButtonPanel - end + -- CALCULATE SIZE BASED ON EXISTENCE OF BUTTON PANEL + if self.submenuClass:HasButtonPanel() then + height2 = height2 - heightButtonPanel + end - -- ADD CONTENT BOX AND CONTENT - local contentAreaScroll = vgui.Create("DScrollPanelTTT2", parent) - contentAreaScroll:SetVerticalScrollbarEnabled(true) - contentAreaScroll:SetSize(width2, height2 - paddingTop - paddingBottom) - contentAreaScroll:Dock(TOP) + -- ADD CONTENT BOX AND CONTENT + local contentAreaScroll = vgui.Create("DScrollPanelTTT2", parent) + contentAreaScroll:SetVerticalScrollbarEnabled(true) + contentAreaScroll:SetSize(width2, height2 - paddingTop - paddingBottom) + contentAreaScroll:Dock(TOP) - self.submenuClass:Populate(contentAreaScroll) + self.submenuClass:Populate(contentAreaScroll) - -- ADD BUTTON BOX AND BUTTONS - if self.submenuClass:HasButtonPanel() then - local buttonArea = vgui.Create("DButtonPanelTTT2", parent) - buttonArea:SetSize(width2, heightButtonPanel) - buttonArea:Dock(BOTTOM) + -- ADD BUTTON BOX AND BUTTONS + if self.submenuClass:HasButtonPanel() then + local buttonArea = vgui.Create("DButtonPanelTTT2", parent) + buttonArea:SetSize(width2, heightButtonPanel) + buttonArea:Dock(BOTTOM) - self.submenuClass:PopulateButtonPanel(buttonArea) - end + self.submenuClass:PopulateButtonPanel(buttonArea) + end end --- @@ -314,65 +333,72 @@ end -- @param table menuClass The class of the submenu -- @realm client function HELPSCRN:ShowSubmenu(menuClass) - local frame = self.menuFrame - - -- IF MENU ELEMENT DOES NOT ALREADY EXIST, CREATE IT - if IsValid(frame) then - frame:ClearFrame(nil, nil, menuClass.title or menuClass.type) - else - frame = vguihandler.GenerateFrame(width, height, menuClass.title or menuClass.type) - end - - -- INIT SUB MENU SPECIFIC STUFF - frame:ShowBackButton(true) - frame:SetPadding(0, 0, 0, 0) - - frame:RegisterBackFunction(function() - self:ShowMainMenu() - end) - - -- MARK AS SUBMENU - self.currentMenuId = menuClass.type - - -- BUILD GENERAL BOX STRUCTURE - local navArea = vgui.Create("DNavPanelTTT2", frame) - navArea:SetWide(widthNav) - navArea:SetPos(0, 0) - navArea:DockPadding(0, self.padding, 1, self.padding) - navArea:Dock(LEFT) - - local contentArea = vgui.Create("DContentPanelTTT2", frame) - contentArea:SetSize(widthContent, heightContent - vskin.GetHeaderHeight() - vskin.GetBorderSize()) - contentArea:SetPos(widthNav, 0) - contentArea:DockPadding(self.padding, self.padding, self.padding, self.padding) - contentArea:Dock(TOP) - - -- MAKE SEPARATE SUBMENULIST ON THE NAVAREA WITH A CONTENT AREA - local submenuList = vgui.Create("DSubmenuListTTT2", navArea) - submenuList:Dock(FILL) - submenuList:SetPadding(self.padding) - submenuList:SetBasemenuClass(menuClass, contentArea) - submenuList:EnableSearchBar(menuClass:HasSearchbar()) - - -- REFRESH SIZE OF SUBMENULIST FOR CORRECT SUBMENU DEPENDENT SIZE - submenuList:InvalidateLayout(true) - - -- REGISTER REBUILD CALLBACK - frame.OnRebuild = function(slf) - -- do not rebuild if the main menu is open, only if submenu is open - if HELPSCRN.currentMenuId == MAIN_MENU then return end - - HELPSCRN:BuildContentArea() - end + local frame = self.menuFrame + + -- IF MENU ELEMENT DOES NOT ALREADY EXIST, CREATE IT + if IsValid(frame) then + frame:ClearFrame(nil, nil, menuClass.title or menuClass.type) + else + frame = vguihandler.GenerateFrame(width, height, menuClass.title or menuClass.type) + end + + -- INIT SUB MENU SPECIFIC STUFF + frame:ShowBackButton(true) + frame:SetPadding(0, 0, 0, 0) + + frame:RegisterBackFunction(function() + self:ShowMainMenu() + end) + + -- MARK AS SUBMENU + self.currentMenuId = menuClass.type + + -- BUILD GENERAL BOX STRUCTURE + local navArea = vgui.Create("DNavPanelTTT2", frame) + navArea:SetWide(widthNav) + navArea:SetPos(0, 0) + navArea:DockPadding(0, self.padding, 1, self.padding) + navArea:Dock(LEFT) + + local contentArea = vgui.Create("DContentPanelTTT2", frame) + contentArea:SetSize( + widthContent, + heightContent - vskin.GetHeaderHeight() - vskin.GetBorderSize() + ) + contentArea:SetPos(widthNav, 0) + contentArea:DockPadding(self.padding, self.padding, self.padding, self.padding) + contentArea:Dock(TOP) + + -- MAKE SEPARATE SUBMENULIST ON THE NAVAREA WITH A CONTENT AREA + local submenuList = vgui.Create("DSubmenuListTTT2", navArea) + submenuList:Dock(FILL) + submenuList:SetPadding(self.padding) + submenuList:SetBasemenuClass(menuClass, contentArea) + submenuList:EnableSearchBar(menuClass:HasSearchbar()) + + -- REFRESH SIZE OF SUBMENULIST FOR CORRECT SUBMENU DEPENDENT SIZE + submenuList:InvalidateLayout(true) + + -- REGISTER REBUILD CALLBACK + frame.OnRebuild = function(slf) + -- do not rebuild if the main menu is open, only if submenu is open + if HELPSCRN.currentMenuId == MAIN_MENU then + return + end + + HELPSCRN:BuildContentArea() + end end --- -- Unhides the helpscreen if it was hidden. -- @realm client function HELPSCRN:Unhide() - if not self.menuFrame or not self.menuFrame:IsFrameHidden() then return end + if not self.menuFrame or not self.menuFrame:IsFrameHidden() then + return + end - self.menuFrame:ShowFrame() + self.menuFrame:ShowFrame() end --- @@ -381,27 +407,35 @@ end -- @return boolean Returns true if the menu is visible -- @realm client function HELPSCRN:IsVisible() - return IsValid(self.menuFrame) + return IsValid(self.menuFrame) end local function ShowTTTHelp(ply, cmd, args) - -- F1 PRESSED: CLOSE MAIN MENU IF MENU IS ALREADY OPENED - if HELPSCRN.currentMenuId == MAIN_MENU and HELPSCRN:IsVisible() and not HELPSCRN.menuFrame:IsFrameHidden() then - HELPSCRN.menuFrame:CloseFrame() - - return - end - - -- F1 PRESSED AND MENU IS HIDDEN: UNHIDE - if HELPSCRN.currentMenuId and IsValid(HELPSCRN.menuFrame) and HELPSCRN.menuFrame:IsFrameHidden() then - HELPSCRN.menuFrame:ShowFrame() - - return - end - - -- F1 PRESSED: CLOSE SUB MENU IF MENU IS ALREADY OPENED - -- AND OPEN MAIN MENU IN GENERAL - HELPSCRN:ShowMainMenu() + -- F1 PRESSED: CLOSE MAIN MENU IF MENU IS ALREADY OPENED + if + HELPSCRN.currentMenuId == MAIN_MENU + and HELPSCRN:IsVisible() + and not HELPSCRN.menuFrame:IsFrameHidden() + then + HELPSCRN.menuFrame:CloseFrame() + + return + end + + -- F1 PRESSED AND MENU IS HIDDEN: UNHIDE + if + HELPSCRN.currentMenuId + and IsValid(HELPSCRN.menuFrame) + and HELPSCRN.menuFrame:IsFrameHidden() + then + HELPSCRN.menuFrame:ShowFrame() + + return + end + + -- F1 PRESSED: CLOSE SUB MENU IF MENU IS ALREADY OPENED + -- AND OPEN MAIN MENU IN GENERAL + HELPSCRN:ShowMainMenu() end concommand.Add("ttt_helpscreen", ShowTTTHelp) @@ -414,6 +448,4 @@ concommand.Add("ttt_helpscreen", ShowTTTHelp) -- @param table submenuClass The menu data of the menu that will be opened -- @hook -- @realm client -function GM:TTT2OnHelpSubmenuClear(parent, currentMenuId, lastMenuData, submenuClass) - -end +function GM:TTT2OnHelpSubmenuClear(parent, currentMenuId, lastMenuData, submenuClass) end diff --git a/gamemodes/terrortown/gamemode/client/cl_hud_editor.lua b/gamemodes/terrortown/gamemode/client/cl_hud_editor.lua index 74b00fa71..1431704c0 100644 --- a/gamemodes/terrortown/gamemode/client/cl_hud_editor.lua +++ b/gamemodes/terrortown/gamemode/client/cl_hud_editor.lua @@ -5,292 +5,313 @@ local mathRound = math.Round local mathClamp = math.Clamp if not HUDEditor then - HUDEditor = {} - HUDEditor.IsEditing = false + HUDEditor = {} + HUDEditor.IsEditing = false end local function CreateEditOptions(x, y) - local client = LocalPlayer() + local client = LocalPlayer() - client.editOptionsX = x - client.editOptionsY = y + client.editOptionsX = x + client.editOptionsY = y - local menu = DermaMenu() + local menu = DermaMenu() - local editReset = menu:AddOption(LANG.GetTranslation("button_reset")) - editReset.OnMousePressed = function(slf, keyCode) - local hud = huds.GetStored(HUDManager.GetHUD()) + local editReset = menu:AddOption(LANG.GetTranslation("button_reset")) + editReset.OnMousePressed = function(slf, keyCode) + local hud = huds.GetStored(HUDManager.GetHUD()) - if hud then - hud:Reset() - end + if hud then + hud:Reset() + end - menu:Remove() - end + menu:Remove() + end - local editClose = menu:AddOption(LANG.GetTranslation("button_close")) - editClose.OnMousePressed = function(slf, keyCode) - menu:Remove() + local editClose = menu:AddOption(LANG.GetTranslation("button_close")) + editClose.OnMousePressed = function(slf, keyCode) + menu:Remove() - HELPSCRN:Unhide() - end + HELPSCRN:Unhide() + end - -- Open the menu - menu:Open() + -- Open the menu + menu:Open() - client.editOptions = menu + client.editOptions = menu end local function GetClickedElement(x, y) - local hud = huds.GetStored(HUDManager.GetHUD()) - if not hud then return end - - local elems = hud:GetElements() - - for i = 1, #elems do - local elObj = hudelements.GetStored(elems[i]) - if elObj and elObj:IsInPos(x, y) then - return elObj - end - end + local hud = huds.GetStored(HUDManager.GetHUD()) + if not hud then + return + end + + local elems = hud:GetElements() + + for i = 1, #elems do + local elObj = hudelements.GetStored(elems[i]) + if elObj and elObj:IsInPos(x, y) then + return elObj + end + end end local function TryResizeLocalHUD(elem, trans_data, dif_x, dif_y, shift_pressed) - local client = LocalPlayer() - - -- calc base data while checking for the shift key - local additional_w, additional_h - - if shift_pressed and trans_data.edge or elem:AspectRatioIsLocked() then - if dif_x * trans_data.direction_x * client.size.h > dif_y * trans_data.direction_y * client.size.w then - dif_x = mathRound(dif_y * trans_data.direction_y * client.aspect) * trans_data.direction_x - else - dif_y = mathRound(dif_x * trans_data.direction_x / client.aspect) * trans_data.direction_y - end - end - - additional_w = dif_x * trans_data.direction_x - additional_h = dif_y * trans_data.direction_y - - -- calc and clamp new data - local new_w_p, new_w_m, new_h_p, new_h_m = 0, 0, 0, 0 - - if trans_data.x_p then - new_w_p = mathClamp(additional_w, -1 * client.size.w, ScrW() - (client.size.w + client.base.x)) - end - - if trans_data.x_m then - new_w_m = mathClamp(additional_w, -1 * client.size.w, client.base.x) - end - - if trans_data.y_p then - new_h_p = mathClamp(additional_h, -1 * client.size.h, ScrH() - (client.size.h + client.base.y)) - end - - if trans_data.y_m then - new_h_m = mathClamp(additional_h, -1 * client.size.h, client.base.y) - end - - -- get min data for this element - local min = elem:GetMinSize() - - -- combine new size data - local new_w, new_h, new_x, new_y - if client.size.w + new_w_p < min.w and new_w_m == 0 then -- limit scale of only the right side of the element - new_w = min.w - new_x = client.base.x - elseif client.size.w + new_w_m < min.w and new_w_p == 0 then -- limit scale of only the left side of the element - new_w = min.w - new_x = client.base.x + client.size.w - min.w - elseif client.size.w + new_w_p + new_w_m < min.w then -- limit scale of both sides of the element - new_w = min.w - new_x = client.base.x + mathRound((client.size.w - min.w) * 0.5) - else - new_w = client.size.w + new_w_p + new_w_m - new_x = client.base.x - new_w_m - end - - if client.size.h + new_h_p < min.h and new_h_m == 0 then -- limit scale of only the bottom side of the element - new_h = min.h - new_y = client.base.y - elseif client.size.h + new_h_m < min.h and new_h_p == 0 then -- limit scale of only the top side of the element - new_h = min.h - new_y = client.base.y + client.size.h - min.h - elseif client.size.h + new_h_p + new_h_m < min.h then -- limit scale of both sides of the element - new_h = min.h - new_y = client.base.y + mathRound((client.size.h - min.h) * 0.5) - else - new_h = client.size.h + new_h_p + new_h_m - new_y = client.base.y - new_h_m - end - - -- make sure the element does not leave the screen when the aspect ratio is fixed - if elem:AspectRatioIsLocked() then - new_w = (new_w < new_h) and new_w or new_h - new_h = new_w - end - - elem:SetSize(new_w, new_h) - elem:SetBasePos(new_x, new_y) + local client = LocalPlayer() + + -- calc base data while checking for the shift key + local additional_w, additional_h + + if shift_pressed and trans_data.edge or elem:AspectRatioIsLocked() then + if + dif_x * trans_data.direction_x * client.size.h + > dif_y * trans_data.direction_y * client.size.w + then + dif_x = mathRound(dif_y * trans_data.direction_y * client.aspect) + * trans_data.direction_x + else + dif_y = mathRound(dif_x * trans_data.direction_x / client.aspect) + * trans_data.direction_y + end + end + + additional_w = dif_x * trans_data.direction_x + additional_h = dif_y * trans_data.direction_y + + -- calc and clamp new data + local new_w_p, new_w_m, new_h_p, new_h_m = 0, 0, 0, 0 + + if trans_data.x_p then + new_w_p = + mathClamp(additional_w, -1 * client.size.w, ScrW() - (client.size.w + client.base.x)) + end + + if trans_data.x_m then + new_w_m = mathClamp(additional_w, -1 * client.size.w, client.base.x) + end + + if trans_data.y_p then + new_h_p = + mathClamp(additional_h, -1 * client.size.h, ScrH() - (client.size.h + client.base.y)) + end + + if trans_data.y_m then + new_h_m = mathClamp(additional_h, -1 * client.size.h, client.base.y) + end + + -- get min data for this element + local min = elem:GetMinSize() + + -- combine new size data + local new_w, new_h, new_x, new_y + if client.size.w + new_w_p < min.w and new_w_m == 0 then -- limit scale of only the right side of the element + new_w = min.w + new_x = client.base.x + elseif client.size.w + new_w_m < min.w and new_w_p == 0 then -- limit scale of only the left side of the element + new_w = min.w + new_x = client.base.x + client.size.w - min.w + elseif client.size.w + new_w_p + new_w_m < min.w then -- limit scale of both sides of the element + new_w = min.w + new_x = client.base.x + mathRound((client.size.w - min.w) * 0.5) + else + new_w = client.size.w + new_w_p + new_w_m + new_x = client.base.x - new_w_m + end + + if client.size.h + new_h_p < min.h and new_h_m == 0 then -- limit scale of only the bottom side of the element + new_h = min.h + new_y = client.base.y + elseif client.size.h + new_h_m < min.h and new_h_p == 0 then -- limit scale of only the top side of the element + new_h = min.h + new_y = client.base.y + client.size.h - min.h + elseif client.size.h + new_h_p + new_h_m < min.h then -- limit scale of both sides of the element + new_h = min.h + new_y = client.base.y + mathRound((client.size.h - min.h) * 0.5) + else + new_h = client.size.h + new_h_p + new_h_m + new_y = client.base.y - new_h_m + end + + -- make sure the element does not leave the screen when the aspect ratio is fixed + if elem:AspectRatioIsLocked() then + new_w = (new_w < new_h) and new_w or new_h + new_h = new_w + end + + elem:SetSize(new_w, new_h) + elem:SetBasePos(new_x, new_y) end local function TryMoveLocalHUD(elem, dif_x, dif_y, shift_pressed) - if not elem:ShouldDraw() then return end -- restrict movement when element is hidden - - local client = LocalPlayer() - - -- move on axis when shift is pressed - local new_x, new_y - - if shift_pressed then - if math.abs(dif_x) > math.abs(dif_y) then - new_x = dif_x + client.base.x - new_y = client.base.y - else - new_x = client.base.x - new_y = dif_y + client.base.y - end - else -- default movement - new_x = dif_x + client.base.x - new_y = dif_y + client.base.y - end - - -- clamp values between min and max - new_x = mathClamp(new_x, 0, ScrW() - client.size.w - (client.pos.x - client.base.x)) - new_y = mathClamp(new_y, 0, ScrH() - client.size.h - (client.pos.y - client.base.y)) - - elem:SetBasePos(new_x, new_y) + if not elem:ShouldDraw() then + return + end -- restrict movement when element is hidden + + local client = LocalPlayer() + + -- move on axis when shift is pressed + local new_x, new_y + + if shift_pressed then + if math.abs(dif_x) > math.abs(dif_y) then + new_x = dif_x + client.base.x + new_y = client.base.y + else + new_x = client.base.x + new_y = dif_y + client.base.y + end + else -- default movement + new_x = dif_x + client.base.x + new_y = dif_y + client.base.y + end + + -- clamp values between min and max + new_x = mathClamp(new_x, 0, ScrW() - client.size.w - (client.pos.x - client.base.x)) + new_y = mathClamp(new_y, 0, ScrH() - client.size.h - (client.pos.y - client.base.y)) + + elem:SetBasePos(new_x, new_y) end local function Think_EditLocalHUD() - local client = LocalPlayer() - local x, y = mathRound(gui.MouseX()), mathRound(gui.MouseY()) - - local mouse_down = input.IsMouseDown(MOUSE_LEFT) - - -- open context menu when rightclicked - if input.IsMouseDown(MOUSE_RIGHT) and (client.editOptionsX ~= x or client.editOptionsY ~= y) then - if IsValid(client.editOptions) then - client.editOptions:Remove() - end - - CreateEditOptions(x, y) - - -- remove contextmenu when leftclicked - elseif input.IsMouseDown(MOUSE_LEFT) then - if IsValid(client.editOptions) then - client.editOptions:Remove() - end - end - - -- mouse rising/falling edge detection - if not client.mouse_clicked_prev and mouse_down then - client.mouse_clicked = true - client.mouse_clicked_prev = true - elseif client.mouse_clicked_prev and not mouse_down then - client.mouse_clicked_prev = false - end - - -- cache old data for the next frame to calculate the difference - client.oldMX = x - client.oldMY = y - - -- cache active element only when the mouse is pressed - client.activeElement = mouse_down and client.activeElement or nil - - -- handle moving when left mouse buttin is pressed - if mouse_down then - local shift_pressed = input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) - local alt_pressed = input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_LALT) - - -- set to true to get new click zone, because this sould only happen ONCE; this zone is now the active zone until the button is released - if client.mouse_clicked then - client.activeElement = GetClickedElement(x, y) - - -- completely stop with the hdu editing, when no element is clicked - if not client.activeElement then return end - - client.activeElement:SetMouseClicked(client.mouse_clicked, x, y) - - -- save initial mouse position - client.mouse_start_X = x - client.mouse_start_Y = y - - -- save initial element data - client.size = client.activeElement:GetSize() -- initial size - client.pos = client.activeElement:GetPos() -- initial pos - client.base = client.activeElement:GetBasePos() -- initial base pos - - -- store aspect ratio for shift-rescaling - client.aspect = math.abs(client.size.w / client.size.h) - - -- reset clicked because this block sould be only executed once - client.mouse_clicked = false - end - - -- get data about the element, it returns the transformation direction - trans_data = client.activeElement:GetClickedArea(x, y, alt_pressed) - - if trans_data then - -- track mouse movement - local dif_x = x - client.mouse_start_X - local dif_y = y - client.mouse_start_Y - - if trans_data.move then -- move mode - TryMoveLocalHUD(client.activeElement, dif_x, dif_y, shift_pressed) - else -- resize mode - TryResizeLocalHUD(client.activeElement, trans_data, dif_x, dif_y, shift_pressed) - end - - client.activeElement:PerformLayout() - end - end + local client = LocalPlayer() + local x, y = mathRound(gui.MouseX()), mathRound(gui.MouseY()) + + local mouse_down = input.IsMouseDown(MOUSE_LEFT) + + -- open context menu when rightclicked + if + input.IsMouseDown(MOUSE_RIGHT) and (client.editOptionsX ~= x or client.editOptionsY ~= y) + then + if IsValid(client.editOptions) then + client.editOptions:Remove() + end + + CreateEditOptions(x, y) + + -- remove contextmenu when leftclicked + elseif input.IsMouseDown(MOUSE_LEFT) then + if IsValid(client.editOptions) then + client.editOptions:Remove() + end + end + + -- mouse rising/falling edge detection + if not client.mouse_clicked_prev and mouse_down then + client.mouse_clicked = true + client.mouse_clicked_prev = true + elseif client.mouse_clicked_prev and not mouse_down then + client.mouse_clicked_prev = false + end + + -- cache old data for the next frame to calculate the difference + client.oldMX = x + client.oldMY = y + + -- cache active element only when the mouse is pressed + client.activeElement = mouse_down and client.activeElement or nil + + -- handle moving when left mouse buttin is pressed + if mouse_down then + local shift_pressed = input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) + local alt_pressed = input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_LALT) + + -- set to true to get new click zone, because this sould only happen ONCE; this zone is now the active zone until the button is released + if client.mouse_clicked then + client.activeElement = GetClickedElement(x, y) + + -- completely stop with the hdu editing, when no element is clicked + if not client.activeElement then + return + end + + client.activeElement:SetMouseClicked(client.mouse_clicked, x, y) + + -- save initial mouse position + client.mouse_start_X = x + client.mouse_start_Y = y + + -- save initial element data + client.size = client.activeElement:GetSize() -- initial size + client.pos = client.activeElement:GetPos() -- initial pos + client.base = client.activeElement:GetBasePos() -- initial base pos + + -- store aspect ratio for shift-rescaling + client.aspect = math.abs(client.size.w / client.size.h) + + -- reset clicked because this block sould be only executed once + client.mouse_clicked = false + end + + -- get data about the element, it returns the transformation direction + trans_data = client.activeElement:GetClickedArea(x, y, alt_pressed) + + if trans_data then + -- track mouse movement + local dif_x = x - client.mouse_start_X + local dif_y = y - client.mouse_start_Y + + if trans_data.move then -- move mode + TryMoveLocalHUD(client.activeElement, dif_x, dif_y, shift_pressed) + else -- resize mode + TryResizeLocalHUD(client.activeElement, trans_data, dif_x, dif_y, shift_pressed) + end + + client.activeElement:PerformLayout() + end + end end --- -- Enables the HUD editing state -- @realm client function HUDEditor.EditHUD() - if HUDEditor.IsEditing == true then return end + if HUDEditor.IsEditing == true then + return + end - local curHUD = HUDManager.GetHUD() - local curHUDTbl = huds.GetStored(curHUD) + local curHUD = HUDManager.GetHUD() + local curHUDTbl = huds.GetStored(curHUD) - -- don't let the user edit a hud that explicitly disallows editing - if not curHUDTbl or curHUDTbl.disableHUDEditor then return end + -- don't let the user edit a hud that explicitly disallows editing + if not curHUDTbl or curHUDTbl.disableHUDEditor then + return + end - HUDEditor.IsEditing = true + HUDEditor.IsEditing = true - gui.EnableScreenClicker(true) + gui.EnableScreenClicker(true) - local colorText = Color(150, 210, 255) - local TryT = LANG.TryTranslation + local colorText = Color(150, 210, 255) + local TryT = LANG.TryTranslation - chat.AddText(colorText, TryT("hudeditor_chat_hint1")) - chat.AddText(colorText, TryT("hudeditor_chat_hint2")) - chat.AddText(colorText, TryT("hudeditor_chat_hint3")) - chat.AddText(colorText, TryT("hudeditor_chat_hint4")) + chat.AddText(colorText, TryT("hudeditor_chat_hint1")) + chat.AddText(colorText, TryT("hudeditor_chat_hint2")) + chat.AddText(colorText, TryT("hudeditor_chat_hint3")) + chat.AddText(colorText, TryT("hudeditor_chat_hint4")) - hook.Add("Think", "TTT2EditHUD", Think_EditLocalHUD) + hook.Add("Think", "TTT2EditHUD", Think_EditLocalHUD) end --- -- Disables the HUD editing state -- @realm client function HUDEditor.StopEditHUD() - if HUDEditor.IsEditing == false then return end + if HUDEditor.IsEditing == false then + return + end - HUDEditor.IsEditing = false + HUDEditor.IsEditing = false - hud = huds.GetStored(HUDManager.GetHUD()) + hud = huds.GetStored(HUDManager.GetHUD()) - gui.EnableScreenClicker(false) + gui.EnableScreenClicker(false) - hook.Remove("Think", "TTT2EditHUD") + hook.Remove("Think", "TTT2EditHUD") - if hud then - hud:SaveData() - end + if hud then + hud:SaveData() + end end --- @@ -299,13 +320,15 @@ end -- @realm client -- @internal function HUDEditor.DrawElem(elem) - if not HUDEditor.IsEditing then return end + if not HUDEditor.IsEditing then + return + end - local client = LocalPlayer() + local client = LocalPlayer() - elem:DrawSize() + elem:DrawSize() - if not client.activeElement then - elem:DrawHovered(mathRound(gui.MouseX()), mathRound(gui.MouseY())) - end + if not client.activeElement then + elem:DrawHovered(mathRound(gui.MouseX()), mathRound(gui.MouseY())) + end end diff --git a/gamemodes/terrortown/gamemode/client/cl_hud_manager.lua b/gamemodes/terrortown/gamemode/client/cl_hud_manager.lua index 17f6c251d..be65a219e 100644 --- a/gamemodes/terrortown/gamemode/client/cl_hud_manager.lua +++ b/gamemodes/terrortown/gamemode/client/cl_hud_manager.lua @@ -3,6 +3,7 @@ --- -- @realm client +-- stylua: ignore local current_hud_cvar = CreateConVar("ttt2_current_hud", ttt2net.GetGlobal({"hud_manager", "defaultHUD"}) or "pure_skin", {FCVAR_ARCHIVE, FCVAR_USERINFO}) local current_hud_table = nil @@ -13,9 +14,11 @@ HUDManager = {} -- Draws the current selected HUD -- @realm client function HUDManager.DrawHUD() - if not current_hud_table or not current_hud_table.Draw then return end + if not current_hud_table or not current_hud_table.Draw then + return + end - current_hud_table:Draw() + current_hud_table:Draw() end --- @@ -29,41 +32,63 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:HUDPaint -- @local function GM:HUDPaint() - local client = LocalPlayer() - - --- - -- @realm client - if hook.Run("HUDShouldDraw", "TTTTargetID") then - --- - -- @realm client - hook.Run("HUDDrawTargetID") - end - - --- - -- @realm client - if hook.Run("HUDShouldDraw", "TTT2HUD") then - HUDManager.DrawHUD() - end - - if not client:Alive() or client:Team() == TEAM_SPEC then return end - - --- - -- @realm client - if hook.Run("HUDShouldDraw", "TTTRadar") then - RADAR:Draw(client) - end - - --- - -- @realm client - if hook.Run("HUDShouldDraw", "TTTTButton") then - TBHUD:Draw(client) - end - - --- - -- @realm client - if hook.Run("HUDShouldDraw", "TTTVoice") then - VOICE.Draw(client) - end + local client = LocalPlayer() + + --- + -- @realm client + -- stylua: ignore + if hook.Run("HUDShouldDraw", "TTTTButton") then + TBHUD:Draw(client) + end + + --- + -- @realm client + -- stylua: ignore + if hook.Run("HUDShouldDraw", "TTTTargetID") then + --- + -- @realm client + -- stylua: ignore + hook.Run("HUDDrawTargetID") + end + + --- + -- @realm client + -- stylua: ignore + if hook.Run("HUDShouldDraw", "TTT2HUD") then + HUDManager.DrawHUD() + end + + --- + -- @realm client + -- stylua: ignore + if hook.Run("HUDShouldDraw", "TTT2KeyHelp") then + keyhelp.Draw() + end + + --- + -- @realm client + -- stylua: ignore + if hook.Run("HUDShouldDraw", "TTT2MarkerVision") then + markerVision.Draw() + end + + --- + -- @realm client + -- stylua: ignore + if hook.Run("HUDShouldDraw", "TTTRadar") then + RADAR:Draw(client) + end + + if not client:Alive() or client:Team() == TEAM_SPEC then + return + end + + --- + -- @realm client + -- stylua: ignore + if hook.Run("HUDShouldDraw", "TTTVoice") then + VOICE.Draw(client) + end end --- @@ -74,7 +99,7 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PostDrawHUD -- @local function GM:PostDrawHUD() - vguihandler.DrawBackground() + vguihandler.DrawBackground() end --- @@ -88,18 +113,18 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:OnScreenSizeChanged -- @realm client function GM:OnScreenSizeChanged(oldScrW, oldScrH) - -- resolution has changed, update resolution in appearance - -- to handle dynamic resolution changes - appearance.UpdateResolution(ScrW(), ScrH()) + -- resolution has changed, update resolution in appearance + -- to handle dynamic resolution changes + appearance.UpdateResolution(ScrW(), ScrH()) end -- Hide the standard HUD stuff local gmodhud = { - ["CHudHealth"] = true, - ["CHudBattery"] = true, - ["CHudAmmo"] = true, - ["CHudSecondaryAmmo"] = true, - ["CHudDamageIndicator"] = true + ["CHudHealth"] = true, + ["CHudBattery"] = true, + ["CHudAmmo"] = true, + ["CHudSecondaryAmmo"] = true, + ["CHudDamageIndicator"] = true, } --- @@ -107,47 +132,48 @@ local gmodhud = { -- @note This hook is called HUNDREDS of times per second (more than 5 times per frame on average). -- You shouldn't be performing any computationally intensive operations. -- @param string name The name of the HUD element. You can find a full list of HUD elements for this hook --- here. +-- here. -- @return boolean Return false to prevent the given element from being drawn on the client's screen. -- @hook -- @realm client -- @ref https://wiki.facepunch.com/gmod/GM:HUDShouldDraw -- @local function GM:HUDShouldDraw(name) - if gmodhud[name] then - return false - end + if gmodhud[name] then + return false + end - return self.BaseClass.HUDShouldDraw(self, name) + return self.BaseClass.HUDShouldDraw(self, name) end local function UpdateHUD(name) - local hudEl = huds.GetStored(name) - if not hudEl then - MsgN("Error: HUD with name " .. name .. " was not found!") + local hudEl = huds.GetStored(name) + if not hudEl then + ErrorNoHaltWithStack("Error: HUD with name " .. name .. " was not found!") - return - end + return + end - HUDEditor.StopEditHUD() + HUDEditor.StopEditHUD() - -- save the old HUDs values - if current_hud_table then - current_hud_table:SaveData() - end + -- save the old HUDs values + if current_hud_table then + current_hud_table:SaveData() + end - current_hud_cvar:SetString(name) + current_hud_cvar:SetString(name) - current_hud_table = hudEl + current_hud_table = hudEl - -- Initialize elements - hudEl:Initialize() - hudEl:LoadData() + -- Initialize elements + hudEl:Initialize() + hudEl:LoadData() - --- - -- Call all listeners - -- @realm client - hook.Run("TTT2HUDUpdated", name) + --- + -- Call all listeners + -- @realm client + -- stylua: ignore + hook.Run("TTT2HUDUpdated", name) end --- @@ -155,13 +181,13 @@ end -- @return string -- @realm client function HUDManager.GetHUD() - local hudvar = current_hud_cvar:GetString() + local hudvar = current_hud_cvar:GetString() - if not huds.GetStored(hudvar) then - hudvar = ttt2net.GetGlobal({"hud_manager", "defaultHUD"}) or "pure_skin" - end + if not huds.GetStored(hudvar) then + hudvar = ttt2net.GetGlobal({ "hud_manager", "defaultHUD" }) or "pure_skin" + end - return hudvar + return hudvar end --- @@ -171,43 +197,45 @@ end -- @param string name The name of the HUD -- @realm client function HUDManager.SetHUD(name) - local currentHUD = HUDManager.GetHUD() + local currentHUD = HUDManager.GetHUD() - net.Start("TTT2RequestHUD") - net.WriteString(name or currentHUD) - net.WriteString(currentHUD) - net.SendToServer() + net.Start("TTT2RequestHUD") + net.WriteString(name or currentHUD) + net.WriteString(currentHUD) + net.SendToServer() end --- -- Resets the current HUD if possible -- @realm client function HUDManager.ResetHUD() - local hud = huds.GetStored(HUDManager.GetHUD()) + local hud = huds.GetStored(HUDManager.GetHUD()) - if not hud then return end + if not hud then + return + end - hud:Reset() - hud:SaveData() + hud:Reset() + hud:SaveData() end --- -- Initializes all @{HUD}s and loads the SQL stored data -- @realm client function HUDManager.LoadAllHUDS() - local hudsTbl = huds.GetList() + local hudsTbl = huds.GetList() - for i = 1, #hudsTbl do - local hud = hudsTbl[i] + for i = 1, #hudsTbl do + local hud = hudsTbl[i] - hud:Initialize() - hud:LoadData() - end + hud:Initialize() + hud:LoadData() + end end -- if forced or requested, modified by server restrictions net.Receive("TTT2ReceiveHUD", function() - UpdateHUD(net.ReadString()) + UpdateHUD(net.ReadString()) end) --- @@ -216,6 +244,4 @@ end) -- @param string name The name of the new HUD -- @hook -- @realm client -function GM:TTT2HUDUpdated(name) - -end +function GM:TTT2HUDUpdated(name) end diff --git a/gamemodes/terrortown/gamemode/client/cl_hudpickup.lua b/gamemodes/terrortown/gamemode/client/cl_hudpickup.lua index b3b88e6cb..9cc097b3d 100644 --- a/gamemodes/terrortown/gamemode/client/cl_hudpickup.lua +++ b/gamemodes/terrortown/gamemode/client/cl_hudpickup.lua @@ -13,21 +13,21 @@ PICKUP_ITEM = 1 PICKUP_AMMO = 2 local function InsertNewPickupItem() - local pickup = {} - pickup.time = CurTime() - pickup.holdtime = 5 - pickup.fadein = 0.04 - pickup.fadeout = 0.3 + local pickup = {} + pickup.time = CurTime() + pickup.holdtime = 5 + pickup.fadein = 0.04 + pickup.fadeout = 0.3 - if PICKUP.last >= pickup.time then - pickup.time = PICKUP.last + 0.05 - end + if PICKUP.last >= pickup.time then + pickup.time = PICKUP.last + 0.05 + end - PICKUP.items[#PICKUP.items + 1] = pickup + PICKUP.items[#PICKUP.items + 1] = pickup - PICKUP.last = pickup.time + PICKUP.last = pickup.time - return pickup + return pickup end --- @@ -38,18 +38,25 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:HUDWeaponPickedUp -- @local function GM:HUDWeaponPickedUp(wep) - if not IsValid(wep) then return end + if not IsValid(wep) or wep.silentPickup then + return + end - local client = LocalPlayer() + local client = LocalPlayer() - if not IsValid(client) or not client:Alive() then return end + if not IsValid(client) or not client:Alive() then + return + end - local name = GetEquipmentTranslation(wep:GetClass(), (wep.GetPrintName and wep:GetPrintName()) or wep:GetClass()) + local name = GetEquipmentTranslation( + wep:GetClass(), + (wep.GetPrintName and wep:GetPrintName()) or wep:GetClass() + ) - local pickup = InsertNewPickupItem() - pickup.name = string.upper(name) - pickup.type = PICKUP_WEAPON - pickup.kind = MakeKindValid(wep.Kind) + local pickup = InsertNewPickupItem() + pickup.name = string.upper(name) + pickup.type = PICKUP_WEAPON + pickup.kind = MakeKindValid(wep.Kind) end --- @@ -60,13 +67,15 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:HUDItemPickedUp -- @local function GM:HUDItemPickedUp(itemName) - local client = LocalPlayer() + local client = LocalPlayer() - if not IsValid(client) or not client:Alive() then return end + if not IsValid(client) or not client:Alive() then + return + end - local pickup = InsertNewPickupItem() - pickup.name = "#" .. itemName - pickup.type = PICKUP_ITEM + local pickup = InsertNewPickupItem() + pickup.name = "#" .. itemName + pickup.type = PICKUP_ITEM end --- @@ -78,33 +87,35 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:HUDAmmoPickedUp -- @local function GM:HUDAmmoPickedUp(itemName, amount) - local client = LocalPlayer() + local client = LocalPlayer() - if not IsValid(client) or not client:Alive() then return end + if not IsValid(client) or not client:Alive() then + return + end - local itemname_trans = TryTranslation(string.lower("ammo_" .. itemName)) + local itemname_trans = TryTranslation(string.lower("ammo_" .. itemName)) - local cachedPickups = PICKUP.items - if cachedPickups then - local localized_name = string.upper(itemname_trans) + local cachedPickups = PICKUP.items + if cachedPickups then + local localized_name = string.upper(itemname_trans) - for k = 1, #cachedPickups do - local v = cachedPickups[k] + for k = 1, #cachedPickups do + local v = cachedPickups[k] - if v.name == localized_name and CurTime() - v.firstTime < 0.5 then - v.amount = tostring(tonumber(v.amount) + amount) - v.time = CurTime() - v.fadein + if v.name == localized_name and CurTime() - v.firstTime < 0.5 then + v.amount = tostring(tonumber(v.amount) + amount) + v.time = CurTime() - v.fadein - return - end - end - end + return + end + end + end - local pickup = InsertNewPickupItem() - pickup.firstTime = CurTime() - pickup.name = string.upper(itemname_trans) - pickup.amount = tostring(amount) - pickup.type = PICKUP_AMMO + local pickup = InsertNewPickupItem() + pickup.firstTime = CurTime() + pickup.name = string.upper(itemname_trans) + pickup.amount = tostring(amount) + pickup.type = PICKUP_AMMO end --- @@ -112,21 +123,21 @@ end -- @realm client -- @internal function PICKUP.RemoveOutdatedValues() - local cachedPickups = PICKUP.items - local itemCount = #cachedPickups - local j = 1 - - for i = 1, itemCount do - if not cachedPickups[i].remove then - if i ~= j then - -- Keep i's value, move it to j's pos. - cachedPickups[j] = cachedPickups[i] - cachedPickups[i] = nil - end - - j = j + 1 - else - cachedPickups[i] = nil - end - end + local cachedPickups = PICKUP.items + local itemCount = #cachedPickups + local j = 1 + + for i = 1, itemCount do + if not cachedPickups[i].remove then + if i ~= j then + -- Keep i's value, move it to j's pos. + cachedPickups[j] = cachedPickups[i] + cachedPickups[i] = nil + end + + j = j + 1 + else + cachedPickups[i] = nil + end + end end diff --git a/gamemodes/terrortown/gamemode/client/cl_inventory.lua b/gamemodes/terrortown/gamemode/client/cl_inventory.lua index eab66ff59..f95f73083 100644 --- a/gamemodes/terrortown/gamemode/client/cl_inventory.lua +++ b/gamemodes/terrortown/gamemode/client/cl_inventory.lua @@ -2,22 +2,28 @@ -- @section Inventory net.Receive("TTT2CleanupInventory", function() - local client = LocalPlayer() - if not IsValid(client) then return end + local client = LocalPlayer() + if not IsValid(client) then + return + end - client.refresh_inventory_cache = true + client.refresh_inventory_cache = true end) net.Receive("TTT2AddWeaponToInventory", function() - local client = LocalPlayer() - if not IsValid(client) then return end + local client = LocalPlayer() + if not IsValid(client) then + return + end - client.refresh_inventory_cache = true + client.refresh_inventory_cache = true end) net.Receive("TTT2RemoveWeaponFromInventory", function() - local client = LocalPlayer() - if not IsValid(client) then return end + local client = LocalPlayer() + if not IsValid(client) then + return + end - client.refresh_inventory_cache = true + client.refresh_inventory_cache = true end) diff --git a/gamemodes/terrortown/gamemode/client/cl_karma.lua b/gamemodes/terrortown/gamemode/client/cl_karma.lua index 114736454..dce3044af 100644 --- a/gamemodes/terrortown/gamemode/client/cl_karma.lua +++ b/gamemodes/terrortown/gamemode/client/cl_karma.lua @@ -8,5 +8,5 @@ KARMA = {} -- @return boolean -- @realm client function KARMA.IsEnabled() - return GetGlobalBool("ttt_karma", false) + return GetGlobalBool("ttt_karma", false) end diff --git a/gamemodes/terrortown/gamemode/client/cl_keys.lua b/gamemodes/terrortown/gamemode/client/cl_keys.lua index 093cb1270..9c20da916 100644 --- a/gamemodes/terrortown/gamemode/client/cl_keys.lua +++ b/gamemodes/terrortown/gamemode/client/cl_keys.lua @@ -3,15 +3,14 @@ -- @section key_manager local IsValid = IsValid -local UpdateInputSprint = UpdateInputSprint local cv_sv_cheats = GetConVar("sv_cheats") local function SendWeaponDrop() - RunConsoleCommand("ttt_dropweapon") + RunConsoleCommand("ttt_dropweapon") - -- Turn off weapon switch display if you had it open while dropping, to avoid - -- inconsistencies. - WSWITCH:Disable() + -- Turn off weapon switch display if you had it open while dropping, to avoid + -- inconsistencies. + WSWITCH:Disable() end --- @@ -21,7 +20,7 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:OnSpawnMenuOpen -- @local function GM:OnSpawnMenuOpen() - SendWeaponDrop() + SendWeaponDrop() end --- @@ -38,137 +37,99 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerBindPress -- @local function GM:PlayerBindPress(ply, bindName, pressed) - if not IsValid(ply) then return end - - if bindName == "invnext" and pressed then - if ply:IsSpec() then - TIPS.Next() - else - WSWITCH:SelectNext() - end - - return true - elseif bindName == "invprev" and pressed then - if ply:IsSpec() then - TIPS.Prev() - else - WSWITCH:SelectPrev() - end - - return true - elseif bindName == "+attack" then - if WSWITCH:PreventAttack() then - if not pressed then - WSWITCH:ConfirmSelection() - end - - return true - end - elseif bindName == "+use" and pressed then - if ply:IsSpec() then - RunConsoleCommand("ttt_spec_use") - - return true - elseif TBHUD:PlayerIsFocused() then - if ply:KeyDown(IN_WALK) then - -- Try to change the access to the button for your current role or team - return TBHUD:ToggleFocused(input.IsButtonDown(KEY_LSHIFT)) - else - -- Try to use the button that is currently focused - return TBHUD:UseFocused() - end - end - elseif string.sub(bindName, 1, 4) == "slot" and pressed then - local idx = tonumber(string.sub(bindName, 5, - 1)) or 1 - - -- if radiomenu is open, override weapon select - if RADIO.Show then - RADIO:SendCommand(idx) - else - WSWITCH:SelectSlot(idx) - end - - return true - elseif bindName == "+zoom" and pressed then - -- open or close radio - RADIO:ShowRadioCommands(not RADIO.Show) - - return true - elseif bindName == "+voicerecord" then - -- This blocks the old Garry's Mod bind - return true - elseif bindName == "-voicerecord" then - -- This blocks the old Garry's Mod bind - return true - elseif bindName == "gm_showteam" and pressed and ply:IsSpec() then - local m = VOICE.CycleMuteState() - - RunConsoleCommand("ttt_mute_team", m) - - return true - elseif bindName == "+duck" and pressed and ply:IsSpec() then - if not IsValid(ply:GetObserverTarget()) then - if GAMEMODE.ForcedMouse then - gui.EnableScreenClicker(false) - - GAMEMODE.ForcedMouse = false - else - gui.EnableScreenClicker(true) - - GAMEMODE.ForcedMouse = true - end - end - elseif bindName == "noclip" and pressed then - if not cv_sv_cheats:GetBool() then - RunConsoleCommand("ttt_equipswitch") - - return true - end - elseif (bindName == "gmod_undo" or bindName == "undo") and pressed then - RunConsoleCommand("ttt_dropammo") - - return true - elseif bindName == "phys_swap" and pressed then - RunConsoleCommand("ttt_quickslot", "5") - end -end - ---- --- Called whenever a @{Player} pressed a key included within the IN keys.
        --- For a more general purpose function that handles all kinds of input, see @{GM:PlayerButtonDown}. --- @warning Due to this being a predicted hook, ParticleEffects --- created only serverside from this hook will not be networked to the client, so make sure to do that on both realms --- @predicted --- @param Player ply The @{Player} pressing the key. If running client-side, this will always be LocalPlayer --- @param number key The key that the player pressed using IN_Enums. --- @note Note that for some reason KeyPress and KeyRelease are called multiple times --- for the same key event in multiplayer. --- @hook --- @realm client --- @ref https://wiki.facepunch.com/gmod/GM:KeyPress --- @local -function GM:KeyPress(ply, key) - if not IsFirstTimePredicted() or not IsValid(ply) or ply ~= LocalPlayer() then return end - - if key == IN_FORWARD or key == IN_BACK or key == IN_MOVERIGHT or key == IN_MOVELEFT then - UpdateInputSprint(ply, key, true) - end -end - ---- --- Runs when a IN key was released by a player.
        --- For a more general purpose function that handles all kinds of input, see @{GM:PlayerButtonUp}. --- @param Player ply The @{Player} releasing the key. If running client-side, this will always be LocalPlayer --- @param number key The key that the player released using IN_Enums. --- @predicted --- @hook --- @realm client --- @ref https://wiki.facepunch.com/gmod/GM:KeyRelease --- @local -function GM:KeyRelease(ply, key) - if not IsFirstTimePredicted() or not IsValid(ply) or ply ~= LocalPlayer() then return end - - if key == IN_FORWARD or key == IN_BACK or key == IN_MOVERIGHT or key == IN_MOVELEFT then - UpdateInputSprint(ply, key, false) - end + if not IsValid(ply) then + return + end + + if bindName == "invnext" and pressed then + if ply:IsSpec() then + TIPS.Next() + else + WSWITCH:SelectNext() + end + + return true + elseif bindName == "invprev" and pressed then + if ply:IsSpec() then + TIPS.Prev() + else + WSWITCH:SelectPrev() + end + + return true + elseif bindName == "+attack" then + if WSWITCH:PreventAttack() then + if not pressed then + WSWITCH:ConfirmSelection() + end + + return true + end + elseif bindName == "+use" and pressed then + if ply:IsSpec() then + RunConsoleCommand("ttt_spec_use") + + return true + elseif TBHUD:PlayerIsFocused() then + if ply:KeyDown(IN_WALK) then + -- Try to change the access to the button for your current role or team + return TBHUD:ToggleFocused(input.IsButtonDown(KEY_LSHIFT)) + else + -- Try to use the button that is currently focused + return TBHUD:UseFocused() + end + end + elseif string.sub(bindName, 1, 4) == "slot" and pressed then + local idx = tonumber(string.sub(bindName, 5, -1)) or 1 + + -- if radiomenu is open, override weapon select + if RADIO.Show then + RADIO:SendCommand(idx) + else + WSWITCH:SelectSlot(idx) + end + + return true + elseif bindName == "+zoom" and pressed then + -- open or close radio + RADIO:ShowRadioCommands(not RADIO.Show) + + return true + elseif bindName == "+voicerecord" then + -- This blocks the old Garry's Mod bind + return true + elseif bindName == "-voicerecord" then + -- This blocks the old Garry's Mod bind + return true + elseif bindName == "gm_showteam" and pressed and ply:IsSpec() then + local m = VOICE.CycleMuteState() + + RunConsoleCommand("ttt_mute_team", m) + + return true + elseif bindName == "+duck" and pressed and ply:IsSpec() then + if not IsValid(ply:GetObserverTarget()) then + if GAMEMODE.ForcedMouse then + gui.EnableScreenClicker(false) + + GAMEMODE.ForcedMouse = false + else + gui.EnableScreenClicker(true) + + GAMEMODE.ForcedMouse = true + end + end + elseif bindName == "noclip" and pressed then + if not cv_sv_cheats:GetBool() then + RunConsoleCommand("ttt_equipswitch") + + return true + end + elseif (bindName == "gmod_undo" or bindName == "undo") and pressed then + RunConsoleCommand("ttt_dropammo") + + return true + elseif bindName == "phys_swap" and pressed then + RunConsoleCommand("ttt_quickslot", "5") + end end diff --git a/gamemodes/terrortown/gamemode/client/cl_lang.lua b/gamemodes/terrortown/gamemode/client/cl_lang.lua index f3b39a69f..54e66918f 100644 --- a/gamemodes/terrortown/gamemode/client/cl_lang.lua +++ b/gamemodes/terrortown/gamemode/client/cl_lang.lua @@ -14,6 +14,7 @@ local pairs = pairs --- -- @realm client +-- stylua: ignore local cv_ttt_language = CreateConVar("ttt_language", "auto", FCVAR_ARCHIVE) LANG.DefaultLanguage = "en" @@ -32,28 +33,30 @@ local colorWarn = Color(255, 70, 45) -- @return table initialized language table that should be extended with translated @{string}s -- @realm client function LANG.CreateLanguage(langName) - if not langName then return end - - langName = string.lower(langName) - - if not LANG.IsLanguage(langName) then - -- Empty string is very convenient to have, so init with that. - LANG.Strings[langName] = {[""] = ""} - end - - if langName == LANG.DefaultLanguage then - cachedDefault = LANG.Strings[langName] - - -- when a string is not found in the active or the default language, an - -- error message is shown - setmetatable(LANG.Strings[langName], { - __index = function(tbl, name) - return Format("[ERROR: Translation of %s not found]", name), false - end - }) - end - - return LANG.Strings[langName] + if not langName then + return + end + + langName = string.lower(langName) + + if not LANG.IsLanguage(langName) then + -- Empty string is very convenient to have, so init with that. + LANG.Strings[langName] = { [""] = "" } + end + + if langName == LANG.DefaultLanguage then + cachedDefault = LANG.Strings[langName] + + -- when a string is not found in the active or the default language, an + -- error message is shown + setmetatable(LANG.Strings[langName], { + __index = function(tbl, name) + return Format("[ERROR: Translation of %s not found]", name), false + end, + }) + end + + return LANG.Strings[langName] end --- @@ -66,17 +69,23 @@ end -- @return string The inserted stringName parameter -- @realm client function LANG.AddToLanguage(langName, stringName, stringText) - langName = LANG.GetNameFromAlias(langName) + langName = LANG.GetNameFromAlias(langName) - if not LANG.IsLanguage(langName) then - ErrorNoHalt(Format("Failed to add '%s' to language '%s': language does not exist.\n", tostring(stringName), tostring(langName))) + if not LANG.IsLanguage(langName) then + ErrorNoHalt( + Format( + "Failed to add '%s' to language '%s': language does not exist.\n", + tostring(stringName), + tostring(langName) + ) + ) - return stringName - end + return stringName + end - LANG.Strings[langName][stringName] = stringText + LANG.Strings[langName][stringName] = stringText - return stringName + return stringName end --- @@ -86,7 +95,7 @@ end -- @return nil|string -- @realm client function LANG.GetTranslation(name) - return cachedActive[name] + return cachedActive[name] end --- @@ -98,7 +107,7 @@ end -- @return nil|string -- @realm client function LANG.GetRawTranslation(name) - return rawget(cachedActive, name) or rawget(cachedDefault, name) + return rawget(cachedActive, name) or rawget(cachedDefault, name) end -- A common idiom @@ -110,7 +119,7 @@ local GetRaw = LANG.GetRawTranslation -- @return nil|string raw translated @{string} or the name parameter if not available -- @realm client function LANG.TryTranslation(name) - return GetRaw(name) or name + return GetRaw(name) or name end local interp = string.Interp @@ -126,10 +135,33 @@ local interp = string.Interp -- @realm client -- @see LANG.GetPTranslation function LANG.GetParamTranslation(name, params) - -- remove the second return variable by caching it - local trans = interp(cachedActive[name], params) + -- remove the second return variable by caching it + local trans = interp(cachedActive[name], params) + + return trans +end - return trans +--- +-- Translates a given string and automatically decides between param translation and normal +-- translation based on the given data. Can also translate the params if so desired. +-- @param string name string key identifier for the translated @{string} +-- @param[opt] table params The params that can be insterted into the translated string +-- @param[opt] boolean translateParams Whether the params should be translated as well +-- @return string The translated string +-- @realm client +function LANG.GetDynamicTranslation(name, params, translateParams) + -- process params (translation) + if params and translateParams then + for k, v in pairs(params) do + params[k] = LANG.TryTranslation(v) + end + end + + if params then + return LANG.GetParamTranslation(name, params) + else + return LANG.TryTranslation(name) + end end --- @@ -152,9 +184,11 @@ LANG.GetPTranslation = LANG.GetParamTranslation -- @return nil|string -- @realm client function LANG.GetTranslationFromLanguage(name, langName) - if langName == nil then return end + if langName == nil then + return + end - return rawget(LANG.Strings[string.lower(langName)], name) + return rawget(LANG.Strings[string.lower(langName)], name) end --- @@ -163,7 +197,7 @@ end -- @return string The translated name of the language -- @realm client function LANG.GetTranslatedLanguageName(langName) - return LANG.GetTranslationFromLanguage("lang_name", langName) or "" + return LANG.GetTranslationFromLanguage("lang_name", langName) or "" end --- @@ -175,7 +209,7 @@ end -- @return table -- @realm client function LANG.GetUnsafeLanguageTable() - return cachedActive + return cachedActive end --- @@ -187,23 +221,32 @@ end -- @internal -- @realm client function LANG.GetNameFromAlias(langName) - if langName == nil then return end - - langName = string.lower(langName) - - if LANG.IsLanguage(langName) then - return langName - end - - for name, tbl in pairs(LANG.Strings) do - if tbl.__alias and string.lower(tbl.__alias) == langName then - MsgN("[DEPRECATION WARNING]: Language name identifier deprecated, please switch from '" .. langName .. "' to '" .. name .. "'.") - - return name - end - end - - return langName + if langName == nil then + return + end + + langName = string.lower(langName) + + if LANG.IsLanguage(langName) then + return langName + end + + for name, tbl in pairs(LANG.Strings) do + if tbl.__alias and string.lower(tbl.__alias) == langName then + Dev( + 1, + "[DEPRECATION WARNING]: Language name identifier deprecated, please switch from '" + .. langName + .. "' to '" + .. name + .. "'." + ) + + return name + end + end + + return langName end --- @@ -212,15 +255,17 @@ end -- @return nil|table -- @realm client function LANG.GetUnsafeNamed(langName) - langName = LANG.GetNameFromAlias(langName) + langName = LANG.GetNameFromAlias(langName) - if not LANG.IsLanguage(langName) then - ErrorNoHalt(Format("Failed to get '%s': language does not exist.\n", tostring(langName))) + if not LANG.IsLanguage(langName) then + ErrorNoHaltWithStack( + Format("Failed to get '%s': language does not exist.\n", tostring(langName)) + ) - return - end + return + end - return LANG.Strings[langName] + return LANG.Strings[langName] end --- @@ -229,13 +274,13 @@ end -- @return nil|table -- @realm client function LANG.GetLanguageTableReference(langName) - langName = LANG.GetNameFromAlias(langName) + langName = LANG.GetNameFromAlias(langName) - if not LANG.IsLanguage(langName) then - LANG.CreateLanguage(langName) - end + if not LANG.IsLanguage(langName) then + LANG.CreateLanguage(langName) + end - return LANG.Strings[langName] + return LANG.Strings[langName] end --- @@ -245,13 +290,13 @@ end -- @return table -- @realm client function LANG.GetLanguageTable(langName) - langName = LANG.GetNameFromAlias(langName) or LANG.ActiveLanguage + langName = LANG.GetNameFromAlias(langName) or LANG.ActiveLanguage - local cpy = table.Copy(LANG.Strings[langName]) + local cpy = table.Copy(LANG.Strings[langName]) - LANG.SetFallback(cpy) + LANG.SetFallback(cpy) - return cpy + return cpy end --- @@ -259,19 +304,21 @@ end -- @param table tbl -- @realm client function LANG.SetFallback(tbl) - -- languages may deal with this themselves, or may already have the fallback - local m = getmetatable(tbl) - - if m and m.__index then return end - - -- Set the __index of the metatable to use the default lang, which makes any - -- keys not found in the table to be looked up in the default. This is faster - -- than using branching ("return lang[x] or default[x] or errormsg") and - -- allows fallback to occur even when consumer code directly accesses the - -- lang table for speed (eg. in a rendering hook). - setmetatable(tbl, { - __index = cachedDefault - }) + -- languages may deal with this themselves, or may already have the fallback + local m = getmetatable(tbl) + + if m and m.__index then + return + end + + -- Set the __index of the metatable to use the default lang, which makes any + -- keys not found in the table to be looked up in the default. This is faster + -- than using branching ("return lang[x] or default[x] or errormsg") and + -- allows fallback to occur even when consumer code directly accesses the + -- lang table for speed (eg. in a rendering hook). + setmetatable(tbl, { + __index = cachedDefault, + }) end --- @@ -279,35 +326,44 @@ end -- @param string langName The new language name -- @realm client function LANG.SetActiveLanguage(langName) - if langName == nil then return end - - langName = LANG.GetNameFromAlias(langName) - - if LANG.IsLanguage(langName) then - local oldName = LANG.ActiveLanguage - - LANG.ActiveLanguage = langName - - -- cache ref to table to avoid hopping through LANG and Strings every time - cachedActive = LANG.Strings[langName] - - -- set the default lang as fallback, if it hasn't yet - LANG.SetFallback(cachedActive) - - -- some interface elements will want to know so they can update themselves - if oldName ~= langName then - --- - -- @realm client - hook.Run("TTTLanguageChanged", oldName, langName) - end - else - MsgN(Format("The language '%s' does not exist on this server. Falling back to English...", langName)) - - -- fall back to default if possible - if langName ~= LANG.DefaultLanguage then - LANG.SetActiveLanguage(LANG.DefaultLanguage) - end - end + if langName == nil then + return + end + + langName = LANG.GetNameFromAlias(langName) + + if LANG.IsLanguage(langName) then + local oldName = LANG.ActiveLanguage + + LANG.ActiveLanguage = langName + + -- cache ref to table to avoid hopping through LANG and Strings every time + cachedActive = LANG.Strings[langName] + + -- set the default lang as fallback, if it hasn't yet + LANG.SetFallback(cachedActive) + + -- some interface elements will want to know so they can update themselves + if oldName ~= langName then + --- + -- @realm client + -- stylua: ignore + hook.Run("TTTLanguageChanged", oldName, langName) + end + else + Dev( + 1, + Format( + "The language '%s' does not exist on this server. Falling back to English...", + langName + ) + ) + + -- fall back to default if possible + if langName ~= LANG.DefaultLanguage then + LANG.SetActiveLanguage(LANG.DefaultLanguage) + end + end end --- @@ -315,15 +371,15 @@ end -- @realm client -- @internal function LANG.Init() - local langName = cv_ttt_language and cv_ttt_language:GetString() or LANG.ServerLanguage + local langName = cv_ttt_language and cv_ttt_language:GetString() or LANG.ServerLanguage - -- if we want to use the server language, we'll be switching to it as soon as - -- we hear from the server which one it is, for now use default - if LANG.IsServerDefault(langName) then - langName = LANG.ServerLanguage - end + -- if we want to use the server language, we'll be switching to it as soon as + -- we hear from the server which one it is, for now use default + if LANG.IsServerDefault(langName) then + langName = LANG.ServerLanguage + end - LANG.SetActiveLanguage(langName) + LANG.SetActiveLanguage(langName) end --- @@ -332,9 +388,9 @@ end -- @return boolean -- @realm client function LANG.IsServerDefault(langName) - langName = string.lower(langName) + langName = string.lower(langName) - return langName == "server default" or langName == "auto" + return langName == "server default" or langName == "auto" end --- @@ -344,24 +400,26 @@ end -- @{nil} check and without automatic string lowering -- @realm client function LANG.IsLanguage(langName) - if not langName then return end + if not langName then + return + end - return LANG.Strings[string.lower(langName)] ~= nil + return LANG.Strings[string.lower(langName)] ~= nil end local function LanguageChanged(cv, old, new) - if new and new ~= LANG.ActiveLanguage then - if LANG.IsServerDefault(new) then - new = LANG.ServerLanguage - end + if new and new ~= LANG.ActiveLanguage then + if LANG.IsServerDefault(new) then + new = LANG.ServerLanguage + end - LANG.SetActiveLanguage(new) - end + LANG.SetActiveLanguage(new) + end end cvars.AddChangeCallback("ttt_language", LanguageChanged) local function ForceReload() - LANG.SetActiveLanguage(LANG.DefaultLanguage) + LANG.SetActiveLanguage(LANG.DefaultLanguage) end concommand.Add("ttt_reloadlang", ForceReload) @@ -370,55 +428,55 @@ concommand.Add("ttt_reloadlang", ForceReload) -- @return table -- @realm client function LANG.GetLanguages() - local langs = {} + local langs = {} - for lang, strings in pairs(LANG.Strings) do - langs[#langs + 1] = lang - end + for lang, strings in pairs(LANG.Strings) do + langs[#langs + 1] = lang + end - return langs + return langs end --- -- Table of styles that can take a string and display it in some position, -- colour, etc. LANG.Styles = { - [MSG_MSTACK_ROLE] = function(text) - MSTACK:AddColoredBgMessage(text, LocalPlayer():GetRoleColor()) + [MSG_MSTACK_ROLE] = function(text) + MSTACK:AddColoredBgMessage(text, LocalPlayer():GetRoleColor()) - print("[TTT2] Role: " .. text) - end, + Dev(2, "[TTT2] Role: " .. text) + end, - [MSG_MSTACK_WARN] = function(text) - MSTACK:AddColoredBgMessage(text, colorWarn) + [MSG_MSTACK_WARN] = function(text) + MSTACK:AddColoredBgMessage(text, colorWarn) - print("[TTT2] Warn: " .. text) - end, + Dev(2, "[TTT2] Warn: " .. text) + end, - [MSG_MSTACK_PLAIN] = function(text) - MSTACK:AddMessage(text) + [MSG_MSTACK_PLAIN] = function(text) + MSTACK:AddMessage(text) - print("[TTT2]: " .. text) - end, + Dev(2, "[TTT2]: " .. text) + end, - [MSG_CHAT_ROLE] = function(text) - chat.AddText(LocalPlayer():GetRoleColor(), text) - end, + [MSG_CHAT_ROLE] = function(text) + chat.AddText(LocalPlayer():GetRoleColor(), text) + end, - [MSG_CHAT_WARN] = function(text) - chat.AddText(colorWarn, text) - end, + [MSG_CHAT_WARN] = function(text) + chat.AddText(colorWarn, text) + end, - [MSG_CHAT_PLAIN] = chat.AddText, + [MSG_CHAT_PLAIN] = chat.AddText, - [MSG_CONSOLE] = print + [MSG_CONSOLE] = print, } LANG.StylesOld = { - default = LANG.Styles[MSG_MSTACK_PLAIN], - rolecolour = LANG.Styles[MSG_MSTACK_ROLE], - chat_warn = LANG.Styles[MSG_CHAT_WARN], - chat_plain = LANG.Styles[MSG_CHAT_PLAIN] + default = LANG.Styles[MSG_MSTACK_PLAIN], + rolecolour = LANG.Styles[MSG_MSTACK_ROLE], + chat_warn = LANG.Styles[MSG_CHAT_WARN], + chat_plain = LANG.Styles[MSG_CHAT_PLAIN], } --- @@ -434,16 +492,16 @@ LANG.MsgStyle = {} -- @return function style table -- @realm client function LANG.GetStyle(name, mode) - -- use this as a fallback in case a style is registered - if LANG.MsgStyle[name] then - return LANG.MsgStyle[name] - end + -- use this as a fallback in case a style is registered + if LANG.MsgStyle[name] then + return LANG.MsgStyle[name] + end - if mode and LANG.Styles[mode] then - return LANG.Styles[mode] - end + if mode and LANG.Styles[mode] then + return LANG.Styles[mode] + end - return LANG.Styles[MSG_MSTACK_PLAIN] + return LANG.Styles[MSG_MSTACK_PLAIN] end --- @@ -452,13 +510,13 @@ end -- @param string|number|function style style name or @{function} -- @realm client function LANG.SetStyle(name, style) - if isnumber(style) then - style = LANG.Styles[style] - elseif isstring(style) then - style = LANG.StylesOld[style] - end + if isnumber(style) then + style = LANG.Styles[style] + elseif isstring(style) then + style = LANG.StylesOld[style] + end - LANG.MsgStyle[name] = style + LANG.MsgStyle[name] = style end --- @@ -467,7 +525,7 @@ end -- @param function style -- @realm client function LANG.ShowStyledMsg(text, style) - style(text) + style(text) end --- @@ -477,25 +535,25 @@ end -- @param number mode The print mode -- @realm client function LANG.ProcessMsg(name, params, mode) - local raw = LANG.TryTranslation(name) - local text = raw + local raw = LANG.TryTranslation(name) + local text = raw - if params then - -- some of our params may be string names themselves - for k, v in pairs(params) do - if isstring(v) then - local name2 = LANG.GetNameParam(v) + if params then + -- some of our params may be string names themselves + for k, v in pairs(params) do + if isstring(v) then + local name2 = LANG.GetNameParam(v) - if name2 then - params[k] = LANG.GetTranslation(name2) - end - end - end + if name2 then + params[k] = LANG.GetTranslation(name2) + end + end + end - text = interp(raw, params) - end + text = interp(raw, params) + end - LANG.ShowStyledMsg(text, LANG.GetStyle(name, mode)) + LANG.ShowStyledMsg(text, LANG.GetStyle(name, mode)) end --- @@ -505,16 +563,16 @@ end -- @return number The coverage in percent -- @realm client function LANG.GetDefaultCoverage(langName) - -- if language is set to auto, get server default - if langName == "auto" then - langName = LANG.ServerLanguage - end + -- if language is set to auto, get server default + if langName == "auto" then + langName = LANG.ServerLanguage + end - local englishTbl = LANG.Strings[LANG.DefaultLanguage] + local englishTbl = LANG.Strings[LANG.DefaultLanguage] - langName = LANG.GetNameFromAlias(langName) + langName = LANG.GetNameFromAlias(langName) - return table.GetEqualEntriesAmount(LANG.Strings[langName], englishTbl) / table.Count(englishTbl) + return table.GetEqualEntriesAmount(LANG.Strings[langName], englishTbl) / table.Count(englishTbl) end --- @@ -522,7 +580,7 @@ end -- @return string The name of the active language -- @realm client function LANG.GetActiveLanguageName() - return cv_ttt_language:GetString() + return cv_ttt_language:GetString() end --- @@ -531,6 +589,4 @@ end -- @param string newLang The name of the new language -- @hook -- @realm client -function GM:TTTLanguageChanged(oldLang, newLang) - -end +function GM:TTTLanguageChanged(oldLang, newLang) end diff --git a/gamemodes/terrortown/gamemode/client/cl_main.lua b/gamemodes/terrortown/gamemode/client/cl_main.lua index 2c81c5bd9..3a161f82e 100644 --- a/gamemodes/terrortown/gamemode/client/cl_main.lua +++ b/gamemodes/terrortown/gamemode/client/cl_main.lua @@ -11,9 +11,12 @@ local surface = surface local hook = hook -- Define GM12 fonts for compatibility -surface.CreateFont("DefaultBold", {font = "Tahoma", size = 13, weight = 1000}) -surface.CreateFont("TabLarge", {font = "Tahoma", size = 13, weight = 700, shadow = true, antialias = false}) -surface.CreateFont("Trebuchet22", {font = "Trebuchet MS", size = 22, weight = 900}) +surface.CreateFont("DefaultBold", { font = "Tahoma", size = 13, weight = 1000 }) +surface.CreateFont( + "TabLarge", + { font = "Tahoma", size = 13, weight = 700, shadow = true, antialias = false } +) +surface.CreateFont("Trebuchet22", { font = "Trebuchet MS", size = 22, weight = 900 }) ttt_include("sh_init") @@ -22,6 +25,7 @@ ttt_include("sh_cvar_handler") ttt_include("sh_network_sync") ttt_include("sh_sprint") ttt_include("sh_main") +ttt_include("sh_shop") ttt_include("sh_shopeditor") ttt_include("sh_rolelayering") ttt_include("sh_scoring") @@ -33,6 +37,7 @@ ttt_include("sh_door") ttt_include("sh_voice") ttt_include("sh_printmessage_override") ttt_include("sh_speed") +ttt_include("sh_marker_vision_element") ttt_include("vgui__cl_coloredbox") ttt_include("vgui__cl_droleimage") @@ -62,6 +67,7 @@ ttt_include("cl_vskin__vgui__dlabel") ttt_include("cl_vskin__vgui__dcombobox") ttt_include("cl_vskin__vgui__dcheckboxlabel") ttt_include("cl_vskin__vgui__dnumslider") +ttt_include("cl_vskin__vgui__dtextentry") ttt_include("cl_vskin__vgui__dbinderpanel") ttt_include("cl_vskin__vgui__dscrollpanel") ttt_include("cl_vskin__vgui__dvscrollbar") @@ -73,7 +79,10 @@ ttt_include("cl_vskin__vgui__ddragbase") ttt_include("cl_vskin__vgui__drolelayeringreceiver") ttt_include("cl_vskin__vgui__drolelayeringsender") ttt_include("cl_vskin__vgui__dsearchbar") +ttt_include("cl_vskin__vgui__dprofilepanel") +ttt_include("cl_vskin__vgui__dinfoitem") ttt_include("cl_vskin__vgui__dsubmenulist") +ttt_include("cl_vskin__vgui__dweaponpreview") ttt_include("cl_changes") ttt_include("cl_network_sync") @@ -85,6 +94,7 @@ ttt_include("cl_transfer") ttt_include("cl_reroll") ttt_include("cl_targetid") ttt_include("cl_target_data") +ttt_include("cl_marker_vision_data") ttt_include("cl_search") ttt_include("cl_tbuttons") ttt_include("cl_scoreboard") @@ -96,6 +106,7 @@ ttt_include("cl_keys") ttt_include("cl_wepswitch") ttt_include("cl_scoring") ttt_include("cl_popups") +ttt_include("cl_shop") ttt_include("cl_equip") ttt_include("cl_shopeditor") ttt_include("cl_chat") @@ -113,11 +124,11 @@ ttt_include("cl_weapon_pickup") ttt_include("cl_help") -- Creates Menus which depend on other client files. Should be loaded as late as possible fileloader.LoadFolder("terrortown/autorun/client/", false, CLIENT_FILE, function(path) - MsgN("Added TTT2 client autorun file: ", path) + Dev(1, "Added TTT2 client autorun file: ", path) end) fileloader.LoadFolder("terrortown/autorun/shared/", false, SHARED_FILE, function(path) - MsgN("Added TTT2 shared autorun file: ", path) + Dev(1, "Added TTT2 shared autorun file: ", path) end) -- all files are loaded @@ -125,17 +136,20 @@ local TryT = LANG.TryTranslation --- -- @realm client +-- stylua: ignore local ttt_cl_soundcues = CreateConVar("ttt_cl_soundcues", "0", FCVAR_ARCHIVE, "Optional sound cues on round start and end") local cues = { - Sound("ttt/thump01e.mp3"), - Sound("ttt/thump02e.mp3") + Sound("ttt/thump01e.mp3"), + Sound("ttt/thump02e.mp3"), } local function PlaySoundCue() - if not ttt_cl_soundcues:GetBool() then return end + if not ttt_cl_soundcues:GetBool() then + return + end - surface.PlaySound(cues[math.random(#cues)]) + surface.PlaySound(cues[math.random(#cues)]) end --- @@ -145,52 +159,66 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:Initialize -- @local function GM:Initialize() - MsgN("TTT2 Client initializing...") + Dev(1, "TTT2 Client initializing...") - --- - -- @realm client - hook.Run("TTT2Initialize") + -- Migrate all changes of TTT2 + migrations.Apply() - self.round_state = ROUND_WAIT - self.roundCount = 0 + --- + -- @realm client + -- stylua: ignore + hook.Run("TTT2Initialize") - -- load default TTT2 language files or mark them as downloadable on the server - -- load addon language files in a second pass, the core language files are loaded earlier - fileloader.LoadFolder("terrortown/lang/", true, CLIENT_FILE, function(path) - MsgN("Added TTT2 language file: ", path) - end) + self.round_state = ROUND_WAIT + self.roundCount = 0 - fileloader.LoadFolder("lang/", true, CLIENT_FILE, function(path) - MsgN("[DEPRECATION WARNING]: Loaded language file from 'lang/', this folder is deprecated. Please switch to 'terrortown/lang/'") - MsgN("Added TTT2 language file: ", path) - end) + -- load default TTT2 language files or mark them as downloadable on the server + -- load addon language files in a second pass, the core language files are loaded earlier + fileloader.LoadFolder("terrortown/lang/", true, CLIENT_FILE, function(path) + Dev(1, "Added TTT2 language file: ", path) + end) - -- load vskin files - fileloader.LoadFolder("terrortown/vskin/", false, CLIENT_FILE, function(path) - MsgN("Added TTT2 vskin file: ", path) - end) + fileloader.LoadFolder("lang/", true, CLIENT_FILE, function(path) + ErrorNoHaltWithStack( + "[DEPRECATION WARNING]: Loaded language file from 'lang/', this folder is deprecated. Please switch to 'terrortown/lang/'. Source: \"" + .. path + .. "\"" + ) + Dev(1, "Added TTT2 language file: ", path) + end) - -- initialize scale callbacks - appearance.RegisterScaleChangeCallback(HUDManager.ResetHUD) + -- load vskin files + fileloader.LoadFolder("terrortown/vskin/", false, CLIENT_FILE, function(path) + Dev(1, "Added TTT2 vskin file: ", path) + end) - LANG.Init() + -- initialize scale callbacks + appearance.RegisterScaleChangeCallback(HUDManager.ResetHUD) - self.BaseClass:Initialize() + LANG.Init() - ARMOR:Initialize() - SPEED:Initialize() + self.BaseClass:Initialize() - local skinName = vskin.GetVSkinName() + ARMOR:Initialize() + SPEED:Initialize() - vskin.UpdatedVSkin(skinName, skinName) + local skinName = vskin.GetVSkinName() - --- - -- @realm client - hook.Run("TTT2FinishedLoading") + vskin.UpdatedVSkin(skinName, skinName) - --- - -- @realm client - hook.Run("PostInitialize") + keyhelp.InitializeBasicKeys() + + ShopEditor.BuildValidEquipmentCache() + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2FinishedLoading") + + --- + -- @realm client + -- stylua: ignore + hook.Run("PostInitialize") end --- @@ -200,9 +228,10 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PostCleanupMap -- @local function GM:PostCleanupMap() - --- - -- @realm client - hook.Run("TTT2PostCleanupMap") + --- + -- @realm client + -- stylua: ignore + hook.Run("TTT2PostCleanupMap") end --- @@ -217,100 +246,107 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:InitPostEntity -- @local function GM:InitPostEntity() - MsgN("TTT Client post-init...") + Dev(1, "TTT Client post-init...") + + --- + -- @realm client + -- stylua: ignore + hook.Run("TTTInitPostEntity") - --- - -- @realm client - hook.Run("TTTInitPostEntity") + items.MigrateLegacyItems() + items.OnLoaded() - items.MigrateLegacyItems() - items.OnLoaded() + -- load all HUDs + huds.OnLoaded() - -- load all HUDs - huds.OnLoaded() + -- load all HUD elements + hudelements.OnLoaded() - -- load all HUD elements - hudelements.OnLoaded() + HUDManager.LoadAllHUDS() + HUDManager.SetHUD() - HUDManager.LoadAllHUDS() - HUDManager.SetHUD() + local sweps = weapons.GetList() - local sweps = weapons.GetList() + -- load sweps + for i = 1, #sweps do + local eq = sweps[i] - -- load sweps - for i = 1, #sweps do - local eq = sweps[i] + -- Check if an equipment has an id or ignore it + -- @realm server + -- stylua: ignore + if not hook.Run("TTT2RegisterWeaponID", eq) then continue end - -- Check if an equipment has an id or ignore it - -- @realm server - if not hook.Run("TTT2RegisterWeaponID", eq) then continue end + -- Insert data into role fallback tables + InitDefaultEquipment(eq) - -- Insert data into role fallback tables - InitDefaultEquipment(eq) + eq.CanBuy = {} -- reset normal weapons equipment + end - eq.CanBuy = {} -- reset normal weapons equipment - end + local roleList = roles.GetList() - local roleList = roles.GetList() + -- reset normal equipment tables + for i = 1, #roleList do + Equipment[roleList[i].index] = {} + end - -- reset normal equipment tables - for i = 1, #roleList do - Equipment[roleList[i].index] = {} - end + -- initialize fallback shops + InitFallbackShops() - -- initialize fallback shops - InitFallbackShops() + --- + -- @realm client + -- stylua: ignore + hook.Run("PostInitPostEntity") - --- - -- @realm client - hook.Run("PostInitPostEntity") + --- + -- @realm client + -- stylua: ignore + hook.Run("InitFallbackShops") - --- - -- @realm client - hook.Run("InitFallbackShops") + --- + -- @realm client + -- stylua: ignore + hook.Run("LoadedFallbackShops") - --- - -- @realm client - hook.Run("LoadedFallbackShops") + net.Start("TTT2SyncShopsWithServer") + net.SendToServer() + TTT2ShopFallbackInitialized = true - net.Start("TTT2SyncShopsWithServer") - net.SendToServer() - TTT2ShopFallbackInitialized = true + net.Start("TTT_Spectate") + net.WriteBool(GetConVar("ttt_spectator_mode"):GetBool()) + net.SendToServer() - net.Start("TTT_Spectate") - net.WriteBool(GetConVar("ttt_spectator_mode"):GetBool()) - net.SendToServer() + if not game.SinglePlayer() then + timer.Create("idlecheck", 5, 0, CheckIdle) + end - if not game.SinglePlayer() then - timer.Create("idlecheck", 5, 0, CheckIdle) - end + local client = LocalPlayer() - local client = LocalPlayer() + client:SetSettingOnServer("enable_dynamic_fov", GetConVar("ttt2_enable_dynamic_fov"):GetBool()) - -- make sure player class extensions are loaded up, and then do some - -- initialization on them - if IsValid(client) and client.GetTraitor then - self:ClearClientState() - end + -- make sure player class extensions are loaded up, and then do some + -- initialization on them + if IsValid(client) and client.GetTraitor then + self:ClearClientState() + end - -- cache players avatar - local plys = player.GetAll() + -- cache players avatar + local plys = player.GetAll() - for i = 1, #plys do - local plyid64 = plys[i]:SteamID64() + for i = 1, #plys do + local plyid64 = plys[i]:SteamID64() - -- caching - draw.CacheAvatar(plyid64, "small") - draw.CacheAvatar(plyid64, "medium") - draw.CacheAvatar(plyid64, "large") - end + -- caching + draw.CacheAvatar(plyid64, "small") + draw.CacheAvatar(plyid64, "medium") + draw.CacheAvatar(plyid64, "large") + end - timer.Create("cache_ents", 1, 0, function() - self:DoCacheEnts() - end) + timer.Create("cache_ents", 1, 0, function() + self:DoCacheEnts() + end) - RunConsoleCommand("_ttt_request_serverlang") - RunConsoleCommand("_ttt_request_rolelist") + RunConsoleCommand("_ttt_request_serverlang") + RunConsoleCommand("_ttt_request_rolelist") end --- @@ -319,35 +355,54 @@ end -- @realm client -- @ref https://wiki.facepunch.com/gmod/GM:OnReloaded function GM:OnReloaded() - -- load all roles - roles.OnLoaded() + -- load all roles + roles.OnLoaded() + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2RolesLoaded") + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2BaseRoleInit") + + -- load all items + items.OnLoaded() + + -- load all HUDs + huds.OnLoaded() - --- - -- @realm shared - hook.Run("TTT2RolesLoaded") + -- load all HUD elements + hudelements.OnLoaded() - --- - -- @realm shared - hook.Run("TTT2BaseRoleInit") + -- re-request the HUD to be loaded + HUDManager.LoadAllHUDS() + HUDManager.SetHUD() - -- load all items - items.OnLoaded() + ARMOR:Initialize() + SPEED:Initialize() - -- load all HUDs - huds.OnLoaded() + -- rebuild menues on game reload + vguihandler.Rebuild() - -- load all HUD elements - hudelements.OnLoaded() + local skinName = vskin.GetVSkinName() + vskin.UpdatedVSkin(skinName, skinName) - -- re-request the HUD to be loaded - HUDManager.LoadAllHUDS() - HUDManager.SetHUD() + keyhelp.InitializeBasicKeys() - -- rebuild menues on game reload - vguihandler.Rebuild() + ShopEditor.BuildValidEquipmentCache() - local skinName = vskin.GetVSkinName() - vskin.UpdatedVSkin(skinName, skinName) + LocalPlayer():SetSettingOnServer( + "enable_dynamic_fov", + GetConVar("ttt2_enable_dynamic_fov"):GetBool() + ) + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2FinishedLoading") end --- @@ -356,8 +411,8 @@ end -- @hook -- @realm client function GM:DoCacheEnts() - RADAR:CacheEnts() - TBHUD:CacheEnts() + RADAR:CacheEnts() + TBHUD:CacheEnts() end --- @@ -366,8 +421,8 @@ end -- @hook -- @realm client function GM:HUDClear() - RADAR:Clear() - TBHUD:Clear() + RADAR:Clear() + TBHUD:Clear() end --- @@ -375,160 +430,174 @@ end -- @return boolean -- @realm client function GetRoundState() - return GAMEMODE.round_state + return GAMEMODE.round_state end local function RoundStateChange(o, n) - local plys = player.GetAll() - - if n == ROUND_PREP then - -- prep starts - GAMEMODE:ClearClientState() - GAMEMODE:CleanUpMap() - - EPOP:Clear() - - -- show warning to spec mode players - if GetConVar("ttt_spectator_mode"):GetBool() and IsValid(LocalPlayer()) then - LANG.Msg("spec_mode_warning", nil, MSG_CHAT_WARN) - end - - -- reset cached server language in case it has changed - RunConsoleCommand("_ttt_request_serverlang") - - GAMEMODE.roundCount = GAMEMODE.roundCount + 1 - - -- clear decals in cache from previous round - util.ClearDecals() - elseif n == ROUND_ACTIVE then - -- round starts - VOICE.CycleMuteState(MUTE_NONE) - - CLSCORE:ClearPanel() - - -- people may have died and been searched during prep - for i = 1, #plys do - plys[i].search_result = nil - end - - -- clear blood decals produced during prep - util.ClearDecals() - - GAMEMODE.StartingPlayers = #util.GetAlivePlayers() - - PlaySoundCue() - elseif n == ROUND_POST then - RunConsoleCommand("ttt_cl_traitorpopup_close") - - PlaySoundCue() - end - - -- stricter checks when we're talking about hooks, because this function may - -- be called with for example o = WAIT and n = POST, for newly connecting - -- players, which hooking code may not expect - if n == ROUND_PREP then - --- - -- Can enter PREP from any phase due to ttt_roundrestart - -- @realm shared - hook.Run("TTTPrepareRound") - elseif o == ROUND_PREP and n == ROUND_ACTIVE then - --- - -- @realm shared - hook.Run("TTTBeginRound") - elseif o == ROUND_ACTIVE and n == ROUND_POST then - --- - -- @realm shared - hook.Run("TTTEndRound") - end - - -- whatever round state we get, clear out the voice flags - local winTeams = roles.GetWinTeams() - - for i = 1, #plys do - local pl = plys[i] - - for k = 1, #winTeams do - pl[winTeams[k] .. "_gvoice"] = false - end - end + local plys = player.GetAll() + + if n == ROUND_PREP then + -- prep starts + GAMEMODE:ClearClientState() + GAMEMODE:CleanUpMap() + + EPOP:Clear() + + -- show warning to spec mode players + if GetConVar("ttt_spectator_mode"):GetBool() and IsValid(LocalPlayer()) then + LANG.Msg("spec_mode_warning", nil, MSG_CHAT_WARN) + end + + -- reset cached server language in case it has changed + RunConsoleCommand("_ttt_request_serverlang") + + GAMEMODE.roundCount = GAMEMODE.roundCount + 1 + + -- clear decals in cache from previous round + util.ClearDecals() + + -- resets bone positions that fixes broken fingers on bad addons + weaponrenderer.ResetBonePositions(LocalPlayer():GetViewModel()) + elseif n == ROUND_ACTIVE then + -- round starts + VOICE.CycleMuteState(MUTE_NONE) + + CLSCORE:ClearPanel() + + -- people may have died and been searched during prep + for i = 1, #plys do + bodysearch.ResetSearchResult(plys[i]) + end + + -- clear blood decals produced during prep + util.ClearDecals() + + GAMEMODE.StartingPlayers = #util.GetAlivePlayers() + + PlaySoundCue() + elseif n == ROUND_POST then + RunConsoleCommand("ttt_cl_traitorpopup_close") + + PlaySoundCue() + end + + -- stricter checks when we're talking about hooks, because this function may + -- be called with for example o = WAIT and n = POST, for newly connecting + -- players, which hooking code may not expect + if n == ROUND_PREP then + --- + -- Can enter PREP from any phase due to ttt_roundrestart + -- @realm shared + -- stylua: ignore + hook.Run("TTTPrepareRound") + elseif o == ROUND_PREP and n == ROUND_ACTIVE then + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTTBeginRound") + elseif o == ROUND_ACTIVE and n == ROUND_POST then + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTTEndRound") + end + + -- whatever round state we get, clear out the voice flags + local winTeams = roles.GetWinTeams() + + for i = 1, #plys do + local pl = plys[i] + + for k = 1, #winTeams do + pl[winTeams[k] .. "_gvoice"] = false + end + end end local function ttt_print_playercount() - print(GAMEMODE.StartingPlayers) + Dev(2, GAMEMODE.StartingPlayers) end concommand.Add("ttt_print_playercount", ttt_print_playercount) -- usermessages local function ReceiveRole() - local client = LocalPlayer() - if not IsValid(client) then return end - - local subrole = net.ReadUInt(ROLE_BITS) - local team = net.ReadString() + local client = LocalPlayer() + if not IsValid(client) then + return + end - -- after a mapswitch, server might have sent us this before we are even done - -- loading our code - if not isfunction(client.SetRole) then return end + local subrole = net.ReadUInt(ROLE_BITS) + local team = net.ReadString() - client:SetRole(subrole, team) + -- after a mapswitch, server might have sent us this before we are even done + -- loading our code + if not isfunction(client.SetRole) then + return + end - Msg("You are: ") - MsgN(string.upper(roles.GetByIndex(subrole).name)) + client:SetRole(subrole, team) end net.Receive("TTT_Role", ReceiveRole) local function ReceiveRoleReset() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - plys[i]:SetRole(ROLE_NONE, TEAM_NONE) - end + for i = 1, #plys do + plys[i]:SetRole(ROLE_NONE, TEAM_NONE) + end end net.Receive("TTT_RoleReset", ReceiveRoleReset) -- role test local function TTT2TestRole() - local client = LocalPlayer() - if not IsValid(client) then return end + local client = LocalPlayer() + if not IsValid(client) then + return + end - client:ChatPrint("Your current role is: '" .. client:GetSubRoleData().name .. "'") + client:ChatPrint("Your current role is: '" .. client:GetSubRoleData().name .. "'") end net.Receive("TTT2TestRole", TTT2TestRole) local function ReceiveRoleList() - local subrole = net.ReadUInt(ROLE_BITS) - local team = net.ReadString() - local num_ids = net.ReadUInt(8) - - for i = 1, num_ids do - local eidx = net.ReadUInt(7) + 1 -- we - 1 worldspawn=0 - local ply = player.GetByID(eidx) - - if IsValid(ply) and ply.SetRole then - ply:SetRole(subrole, team) - - local plyrd = ply:GetSubRoleData() - - if team ~= TEAM_NONE and not plyrd.unknownTeam and not plyrd.disabledTeamVoice and not TEAMS[team].alone then - ply[team .. "_gvoice"] = false -- assume role's chat by default - end - end - end + local subrole = net.ReadUInt(ROLE_BITS) + local team = net.ReadString() + local num_ids = net.ReadUInt(8) + + for i = 1, num_ids do + local eidx = net.ReadUInt(7) + 1 -- we - 1 worldspawn=0 + local ply = player.GetByID(eidx) + + if IsValid(ply) and ply.SetRole then + ply:SetRole(subrole, team) + + local plyrd = ply:GetSubRoleData() + + if + team ~= TEAM_NONE + and not plyrd.unknownTeam + and not plyrd.disabledTeamVoice + and not TEAMS[team].alone + then + ply[team .. "_gvoice"] = false -- assume role's chat by default + end + end + end end net.Receive("TTT_RoleList", ReceiveRoleList) -- Round state comm local function ReceiveRoundState() - local o = GetRoundState() + local o = GetRoundState() - GAMEMODE.round_state = net.ReadUInt(3) + GAMEMODE.round_state = net.ReadUInt(3) - if o ~= GAMEMODE.round_state then - RoundStateChange(o, GAMEMODE.round_state) - end + if o ~= GAMEMODE.round_state then + RoundStateChange(o, GAMEMODE.round_state) + end - MsgN("Round state: " .. GAMEMODE.round_state) + Dev(1, "Round state: " .. GAMEMODE.round_state) end net.Receive("TTT_RoundState", ReceiveRoundState) @@ -538,48 +607,53 @@ net.Receive("TTT_RoundState", ReceiveRoundState) -- @hook -- @realm client function GM:ClearClientState() - self:HUDClear() + self:HUDClear() - local client = LocalPlayer() - if not client.SetRole then return end -- code not loaded yet + local client = LocalPlayer() + if not client.SetRole then + return + end -- code not loaded yet - client:SetRole(ROLE_NONE) + client:SetRole(ROLE_NONE) - client.equipmentItems = {} - client.equipment_credits = 0 - client.bought = {} - client.last_id = nil - client.radio = nil - client.called_corpses = {} - client.sprintProgress = 1 + client.equipmentItems = {} + client.equipment_credits = 0 + client.bought = {} + client.last_id = nil + client.radio = nil + client.called_corpses = {} - client:SetTargetPlayer(nil) + client:SetTargetPlayer(nil) - VOICE.InitBattery() + VOICE.InitBattery() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - local pl = plys[i] - if not IsValid(pl) then continue end + for i = 1, #plys do + local pl = plys[i] + if not IsValid(pl) then + continue + end - pl.sb_tag = nil + pl.sb_tag = nil - pl:SetRole(ROLE_NONE) + pl:SetRole(ROLE_NONE) - pl.search_result = nil - end + bodysearch.ResetSearchResult(pl) + end - VOICE.CycleMuteState(MUTE_NONE) + VOICE.CycleMuteState(MUTE_NONE) - RunConsoleCommand("ttt_mute_team_check", "0") + RunConsoleCommand("ttt_mute_team_check", "0") - if not self.ForcedMouse then return end + if not self.ForcedMouse then + return + end - gui.EnableScreenClicker(false) + gui.EnableScreenClicker(false) end net.Receive("TTT_ClearClientState", function() - GAMEMODE:ClearClientState() + GAMEMODE:ClearClientState() end) local color_trans = Color(0, 0, 0, 0) @@ -590,56 +664,56 @@ local color_trans = Color(0, 0, 0, 0) -- @hook -- @realm client function GM:CleanUpMap() - --remove thermal vision - thermalvision.Clear() + --remove thermal vision + thermalvision.Clear() - -- Ragdolls sometimes stay around on clients. Deleting them can create issues - -- so all we can do is try to hide them. - local ragdolls = ents.FindByClass("prop_ragdoll") + -- Ragdolls sometimes stay around on clients. Deleting them can create issues + -- so all we can do is try to hide them. + local ragdolls = ents.FindByClass("prop_ragdoll") - for i = 1, #ragdolls do - local ent = ragdolls[i] + for i = 1, #ragdolls do + local ent = ragdolls[i] - if not IsValid(ent) or CORPSE.GetPlayerNick(ent, "") == "" then continue end + if not IsValid(ent) or CORPSE.GetPlayerNick(ent, "") == "" then + continue + end - ent:SetNoDraw(true) - ent:SetSolid(SOLID_NONE) - ent:SetColor(color_trans) + ent:SetNoDraw(true) + ent:SetSolid(SOLID_NONE) + ent:SetColor(color_trans) - -- Horrible hack to make targetid ignore this ent, because we can't - -- modify the collision group clientside. - ent.NoTarget = true - end + -- Horrible hack to make targetid ignore this ent, because we can't + -- modify the collision group clientside. + ent.NoTarget = true + end - game.CleanUpMap() + game.CleanUpMap() end -net.Receive("TTT2SyncDBItems", function() - if not ShopEditor then return end - - ShopEditor.ReadItemData() -end) - -- server tells us to call this when our LocalPlayer has spawned local function PlayerSpawn() - local as_spec = net.ReadBit() == 1 - if as_spec then - TIPS.Show() - else - TIPS.Hide() - end - - -- TTT Totem prevention - if LocalPlayer().GetRoleTable then - print("[TTT2][ERROR] You have TTT Totem activated! You really should disable it!\n-- Disable it by unsubscribe it! --\nI know, that's not nice, but there's no way. It's an internally problem of GMod...") - end + local as_spec = net.ReadBit() == 1 + if as_spec then + TIPS.Show() + else + TIPS.Hide() + end + + -- TTT Totem prevention + if LocalPlayer().GetRoleTable then + ErrorNoHaltWithStack( + "[TTT2][ERROR] You have TTT Totem activated! You really should disable it!\n-- Disable it by unsubscribe it! --\nI know, that's not nice, but there's no way. It's an internally problem of GMod..." + ) + end end net.Receive("TTT_PlayerSpawned", PlayerSpawn) local function PlayerDeath() - if not TIPS then return end + if not TIPS then + return + end - TIPS.Show() + TIPS.Show() end net.Receive("TTT_PlayerDied", PlayerDeath) @@ -647,7 +721,7 @@ net.Receive("TTT_PlayerDied", PlayerDeath) -- Called to determine if the LocalPlayer should be drawn. -- @note If you're using this hook to draw a @{Player} for a @{GM:CalcView} hook, -- then you may want to consider using the drawviewer variable you can use in your --- CamData structure +-- CamData structure -- table instead. -- @important You should visit the linked reference, there could be related issues -- @param Player ply The @{Player} @@ -657,10 +731,10 @@ net.Receive("TTT_PlayerDied", PlayerDeath) -- @ref https://wiki.facepunch.com/gmod/GM:ShouldDrawLocalPlayer -- @local function GM:ShouldDrawLocalPlayer(ply) - return false + return false end -local view = {origin = vector_origin, angles = angle_zero, fov = 0} +local view = { origin = vector_origin, angles = angle_zero, fov = 0 } --- -- Allows override of the default view. @@ -671,43 +745,43 @@ local view = {origin = vector_origin, angles = angle_zero, fov = 0} -- @param number znear Distance to near clipping plane -- @param number zfar Distance to far clipping plane -- @return table View data table. See --- CamData structure +-- CamData structure -- structure -- @hook -- @realm client -- @ref https://wiki.facepunch.com/gmod/GM:CalcView -- @local function GM:CalcView(ply, origin, angles, fov, znear, zfar) - view.origin = origin - view.angles = angles - view.fov = fov - - -- first person ragdolling - if ply:Team() == TEAM_SPEC and ply:GetObserverMode() == OBS_MODE_IN_EYE then - local tgt = ply:GetObserverTarget() - - if IsValid(tgt) and not tgt:IsPlayer() then - -- assume if we are in_eye and not speccing a player, we spec a ragdoll - local eyes = tgt:LookupAttachment("eyes") or 0 - eyes = tgt:GetAttachment(eyes) - - if eyes then - view.origin = eyes.Pos - view.angles = eyes.Ang - end - end - end - - local wep = ply:GetActiveWeapon() - - if IsValid(wep) then - local func = wep.CalcView - if func then - view.origin, view.angles, view.fov = func(wep, ply, origin * 1, angles * 1, fov) - end - end - - return view + view.origin = origin + view.angles = angles + view.fov = fov + + -- first person ragdolling + if ply:Team() == TEAM_SPEC and ply:GetObserverMode() == OBS_MODE_IN_EYE then + local tgt = ply:GetObserverTarget() + + if IsValid(tgt) and not tgt:IsPlayer() then + -- assume if we are in_eye and not speccing a player, we spec a ragdoll + local eyes = tgt:LookupAttachment("eyes") or 0 + eyes = tgt:GetAttachment(eyes) + + if eyes then + view.origin = eyes.Pos + view.angles = eyes.Ang + end + end + end + + local wep = ply:GetActiveWeapon() + + if IsValid(wep) then + local func = wep.CalcView + if func then + view.origin, view.angles, view.fov = func(wep, ply, origin * 1, angles * 1, fov) + end + end + + return view end --- @@ -721,9 +795,7 @@ end -- @realm client -- @ref https://wiki.facepunch.com/gmod/GM:AddDeathNotice -- @local -function GM:AddDeathNotice(attacker, attackerTeam, inflictor, victim, victimTeam) - -end +function GM:AddDeathNotice(attacker, attackerTeam, inflictor, victim, victimTeam) end --- -- This hook is called every frame to draw all of the current death notices. @@ -733,78 +805,75 @@ end -- @realm client -- @ref https://wiki.facepunch.com/gmod/GM:DrawDeathNotice -- @local -function GM:DrawDeathNotice(x, y) - -end +function GM:DrawDeathNotice(x, y) end -- Simple client-based idle checking local idle = { - ang = nil, - pos = nil, - mx = 0, - my = 0, - t = 0 + btn = 0, + mx = 0, + my = 0, + t = 0, } +--- +-- Sniff for any button inputs to prevent scamming the AFK timer. +-- @realm client +hook.Add("SetupMove", "TTT2IdleCheck", function(_, mv) + if not GetGlobalBool("ttt_idle", false) then + return + end + + if mv:GetButtons() ~= idle.btn then + idle.t = CurTime() + end + + idle.btn = mv:GetButtons() +end) + --- -- Checks whether a the local @{Player} is idle and handles everything on it's own -- @note This is doing every 5 seconds by the "idlecheck" timer -- @realm client -- @internal function CheckIdle() - if not GetGlobalBool("ttt_idle", false) then return end - - local client = LocalPlayer() - if not IsValid(client) then return end - - if not idle.ang or not idle.pos then - -- init things - idle.ang = client:GetAngles() - idle.pos = client:GetPos() - idle.mx = gui.MouseX() - idle.my = gui.MouseY() - idle.t = CurTime() - - return - end - - if GetRoundState() == ROUND_ACTIVE and client:IsTerror() and client:Alive() then - local idle_limit = GetGlobalInt("ttt_idle_limit", 300) or 300 - - if idle_limit <= 0 then -- networking sucks sometimes - idle_limit = 300 - end - - if client:GetAngles() ~= idle.ang then - -- Normal players will move their viewing angles all the time - idle.ang = client:GetAngles() - idle.t = CurTime() - elseif gui.MouseX() ~= idle.mx or gui.MouseY() ~= idle.my then - -- Players in eg. the Help will move their mouse occasionally - idle.mx = gui.MouseX() - idle.my = gui.MouseY() - idle.t = CurTime() - elseif client:GetPos():Distance(idle.pos) > 10 then - -- Even if players don't move their mouse, they might still walk - idle.pos = client:GetPos() - idle.t = CurTime() - elseif CurTime() > idle.t + idle_limit then - RunConsoleCommand("say", TryT("automoved_to_spec")) - - timer.Simple(0, function() -- move client into the spectator team in the next frame - RunConsoleCommand("ttt_spectator_mode", 1) - - net.Start("TTT_Spectate") - net.WriteBool(true) - net.SendToServer() - - RunConsoleCommand("ttt_cl_idlepopup") - end) - elseif CurTime() > idle.t + idle_limit * 0.5 then - -- will repeat - LANG.Msg("idle_warning") - end - end + if not GetGlobalBool("ttt_idle", false) then + return + end + + local client = LocalPlayer() + if not IsValid(client) then + return + end + + if GetRoundState() == ROUND_ACTIVE and client:IsTerror() and client:Alive() then + local idle_limit = GetGlobalInt("ttt_idle_limit", 300) or 300 + + if idle_limit <= 0 then -- networking sucks sometimes + idle_limit = 300 + end + + if gui.MouseX() ~= idle.mx or gui.MouseY() ~= idle.my then + -- Players in eg. the Help will move their mouse occasionally + idle.mx = gui.MouseX() + idle.my = gui.MouseY() + idle.t = CurTime() + elseif CurTime() > idle.t + idle_limit then + RunConsoleCommand("say", TryT("automoved_to_spec")) + + timer.Simple(0, function() -- move client into the spectator team in the next frame + RunConsoleCommand("ttt_spectator_mode", 1) + + net.Start("TTT_Spectate") + net.WriteBool(true) + net.SendToServer() + + RunConsoleCommand("ttt_cl_idlepopup") + end) + elseif CurTime() > idle.t + idle_limit * 0.5 then + -- will repeat + LANG.Msg("idle_warning") + end + end end --- @@ -819,45 +888,46 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:OnEntityCreated -- @local function GM:OnEntityCreated(ent) - -- Make ragdolls look like the player that has died - if ent:IsRagdoll() then - local ply = CORPSE.GetPlayer(ent) - - if IsValid(ply) then - -- Only copy any decals if this ragdoll was recently created - if ent:GetCreationTime() > CurTime() - 1 then - ent:SnatchModelInstance(ply) - end - - -- Copy the color for the PlayerColor matproxy - local playerColor = ply:GetPlayerColor() - - ent.GetPlayerColor = function() - return playerColor - end - end - end - - return self.BaseClass.OnEntityCreated(self, ent) + -- Make ragdolls look like the player that has died + if ent:IsRagdoll() then + local ply = CORPSE.GetPlayer(ent) + + if IsValid(ply) then + -- Only copy any decals if this ragdoll was recently created + if ent:GetCreationTime() > CurTime() - 1 then + ent:SnatchModelInstance(ply) + end + + -- Copy the color for the PlayerColor matproxy + local playerColor = ply:GetPlayerColor() + + ent.GetPlayerColor = function() + return playerColor + end + end + end + + return self.BaseClass.OnEntityCreated(self, ent) end net.Receive("TTT2PlayerAuthedShared", function(len) - local steamid64 = net.ReadString() - local name = net.ReadString() - - -- checking for bots - if steamid64 == "" then - steamid64 = nil - end - - -- cache avatars - draw.CacheAvatar(steamid64, "small") - draw.CacheAvatar(steamid64, "medium") - draw.CacheAvatar(steamid64, "large") - - --- - -- @realm shared - hook.Run("TTT2PlayerAuthed", steamid64, name) + local steamid64 = net.ReadString() + local name = net.ReadString() + + -- checking for bots + if steamid64 == "" then + steamid64 = nil + end + + -- cache avatars + draw.CacheAvatar(steamid64, "small") + draw.CacheAvatar(steamid64, "medium") + draw.CacheAvatar(steamid64, "large") + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2PlayerAuthed", steamid64, name) end) --- @@ -867,6 +937,4 @@ end) -- @param string name The player's name -- @hook -- @realm client -function GM:TTT2PlayerAuthed(steamID64, name) - -end +function GM:TTT2PlayerAuthed(steamID64, name) end diff --git a/gamemodes/terrortown/gamemode/client/cl_marker_vision_data.lua b/gamemodes/terrortown/gamemode/client/cl_marker_vision_data.lua new file mode 100644 index 000000000..7f935a942 --- /dev/null +++ b/gamemodes/terrortown/gamemode/client/cl_marker_vision_data.lua @@ -0,0 +1,214 @@ +--- +-- @author Mineotopia +-- @class MARKER_VISION_DATA + +MARKER_VISION_DATA = {} + +--- +-- Initializes the @{MARKER_VISION_DATA} object +-- @param Entity ent The focused Entity +-- @param boolean isOffScreen If the radar is off screen or on screen +-- @param boolean isOnScreenCenter If the radar icon is on screen center +-- @param number distance The distance to the focused entity +-- @param MARKER_VISION_ELEMENT mvObject The marker vision element that carries the data +-- @return MARKER_VISION_DATA The object to be used in the hook +-- @internal +-- @realm client +function MARKER_VISION_DATA:Initialize(ent, isOffScreen, isOnScreenCenter, distance, mvObject) + -- combine data into a table to read them inside a hook + local data = { + ent = ent, + isOffScreen = isOffScreen, + isOnScreenCenter = isOnScreenCenter, + distance = distance, + mvObject = mvObject, + } + + -- preset a table of values that can be changed with a hook + local params = { + drawInfo = nil, + displayInfo = { + icon = {}, + title = { + icons = {}, + text = "", + color = COLOR_WHITE, + }, + desc = {}, + }, + } + + return MARKER_VISION_DATA:BindTarget(data, params) +end + +--- +-- Binds two target data tables to the @{MARKER_VISION_DATA} object +-- @param table data The data table about the focused entity +-- @param table params The default table with the params that should be modified by the hook +-- @return MARKER_VISION_DATA The object to be used in the hook +-- @internal +-- @realm client +function MARKER_VISION_DATA:BindTarget(data, params) + self.data = data + self.params = params + + return self +end + +--- +-- Returns the currently focused entity +-- @return Entity The focused entity +-- @realm client +function MARKER_VISION_DATA:GetEntity() + return self.data.ent +end + +--- +-- Returns if the radar entity is off screen +-- @return boolean Whether it is off screen +-- @realm client +function MARKER_VISION_DATA:IsOffScreen() + return self.data.isOffScreen +end + +--- +-- Returns if the radar entity is on screen center +-- @return boolean Whether it is on screen center +-- @realm client +function MARKER_VISION_DATA:IsOnScreenCenter() + return self.data.isOnScreenCenter +end + +--- +-- Returns the distance to the focused entity +-- @return number The distance to the focused entity +-- @realm client +function MARKER_VISION_DATA:GetEntityDistance() + return self.data.distance +end + +--- +-- Enables/Disables the radar vision text and icons, can't be enabled if set +-- to false from another call +-- @param[default=true] boolean enableText A boolean defining the text state +-- @realm client +function MARKER_VISION_DATA:EnableText(enableText) + -- only set if not already set to false + if self.params.drawInfo == false then + return + end + + -- set to true if true or nil, but keep false + self.params.drawInfo = (enableText == nil) and true or enableText +end + +--- +-- Adds a icon to the icon list on the left side of the radar vision element. +-- @param Material material The material of the icon that should be rendered +-- @param[default=Color(255, 255, 255, 255)] Color color The color of the icon +-- @return number The amount of icons that are currently in the table +-- @realm client +function MARKER_VISION_DATA:AddIcon(material, color) + local amount = #self.params.displayInfo.icon + 1 + + self.params.displayInfo.icon[amount] = { + material = material, + color = IsColor(color) and color or COLOR_WHITE, + } + + return amount +end + +--- +-- Sets the title of the specific radar vision element +-- @param[default=""] string text The text that should be displayed +-- @param[default=Color(255, 255, 255, 255)] Color color The color of the line +-- @param[opt] table inline_icons A table of materials that should be rendered in front of the text +-- @realm client +function MARKER_VISION_DATA:SetTitle(text, color, inline_icons) + self.params.displayInfo.title = { + text = text or "", + color = IsColor(color) and color or COLOR_WHITE, + icons = inline_icons or {}, + } +end + +--- +-- Adds a line of text to the description area of the radar vision element +-- @param[default=""] string text The text that should be displayed +-- @param[default=Color(255, 255, 255, 255)] Color color The color of the line +-- @param[opt] table inline_icons A table of materials that should be rendered in front of the text +-- @return number The amount of description lines that are currently in the table +-- @realm client +function MARKER_VISION_DATA:AddDescriptionLine(text, color, inline_icons) + local amount = #self.params.displayInfo.desc + 1 + + self.params.displayInfo.desc[amount] = { + text = text or "", + color = IsColor(color) and color or COLOR_WHITE, + icons = inline_icons or {}, + } + + return amount +end + +--- +-- Sets the collapsed line that is shown at greater distance of the specific radar vision element +-- @param[default=""] string text The text that should be displayed +-- @param[default=Color(255, 255, 255, 255)] Color color The color of the line +-- @realm client +function MARKER_VISION_DATA:SetCollapsedLine(text, color) + self.params.displayInfo.collapsedLine = { + text = text or "", + color = IsColor(color) and color or COLOR_WHITE, + } +end + +--- +-- Returns whether or not a title has been set +-- @return boolean True if a title is set +-- @realm client +function MARKER_VISION_DATA:HasTitle() + return self.params.displayInfo.title and self.params.displayInfo.title.text ~= "" +end + +--- +-- Returns the amount of set description lines +-- @return number Amount of existing description lines +-- @realm client +function MARKER_VISION_DATA:GetAmountDescriptionLines() + return #self.params.displayInfo.desc +end + +--- +-- Returns the amount of set icons +-- @return number Amount of existing icons +-- @realm client +function MARKER_VISION_DATA:GetAmountIcons() + return #self.params.displayInfo.icon +end + +--- +-- Returns whether or not a collapsed line has been set +-- @return boolean True if a title is set +-- @realm client +function MARKER_VISION_DATA:HasCollapsedLine() + return self.params.displayInfo.collapsedLine + and self.params.displayInfo.collapsedLine.text ~= "" +end + +--- +-- Returns the raw data tables of the radar vision element to me modified by experienced users +-- @return table,table The table of the entity data, the table of the radar vision element parameters +-- @realm client +function MARKER_VISION_DATA:GetRaw() + return self.data, self.params +end + +--- +-- Returns the marker vision object linked to this. +-- @return MARKER_VISION_ELEMENT The marker vision object +-- @realm client +function MARKER_VISION_DATA:GetMarkerVisionObject() + return self.data.mvObject +end diff --git a/gamemodes/terrortown/gamemode/client/cl_msgstack.lua b/gamemodes/terrortown/gamemode/client/cl_msgstack.lua index 95ac36008..1b7234f5e 100644 --- a/gamemodes/terrortown/gamemode/client/cl_msgstack.lua +++ b/gamemodes/terrortown/gamemode/client/cl_msgstack.lua @@ -19,11 +19,11 @@ local traitor_msg_bg = Color(255, 0, 0, 255) -- @param Color c -- @realm client function MSTACK:AddColoredMessage(text, c) - local item = {} - item.text = text - item.col = c + local item = {} + item.text = text + item.col = c - self:AddMessageEx(item) + self:AddMessageEx(item) end --- @@ -32,11 +32,11 @@ end -- @param Color bg_clr -- @realm client function MSTACK:AddColoredBgMessage(text, bg_clr) - local item = {} - item.text = text - item.bg = bg_clr + local item = {} + item.text = text + item.bg = bg_clr - self:AddMessageEx(item) + self:AddMessageEx(item) end --- @@ -46,12 +46,12 @@ end -- @param string title -- @realm client function MSTACK:AddImagedMessage(text, image, title) - local item = {} - item.text = text - item.title = title - item.image = image + local item = {} + item.text = text + item.title = title + item.image = image - self:AddMessageEx(item) + self:AddMessageEx(item) end --- @@ -62,13 +62,13 @@ end -- @param string title -- @realm client function MSTACK:AddColoredImagedMessage(text, bg_clr, image, title) - local item = {} - item.text = text - item.title = title - item.bg = bg_clr - item.image = image + local item = {} + item.text = text + item.title = title + item.bg = bg_clr + item.image = image - self:AddMessageEx(item) + self:AddMessageEx(item) end --- @@ -78,18 +78,18 @@ end -- @internal -- @todo add table structure function MSTACK:AddMessageEx(item) - item.time = CurTime() - item.sounded = false + item.time = CurTime() + item.sounded = false - -- Stagger the fading a bit - if self.last > item.time - 1 then - item.time = self.last + 1 - end + -- Stagger the fading a bit + if self.last > item.time - 1 then + item.time = self.last + 1 + end - -- Insert at the top - table.insert(self.msgs, 1, item) + -- Insert at the top + table.insert(self.msgs, 1, item) - self.last = item.time + self.last = item.time end --- @@ -100,34 +100,34 @@ end -- @param boolean traitor_only -- @realm client function MSTACK:AddMessage(text, traitor_only) - if traitor_only then - self:AddColoredBgMessage(text, traitor_msg_bg) - else - self:AddColoredMessage(text) - end + if traitor_only then + self:AddColoredBgMessage(text, traitor_msg_bg) + else + self:AddColoredMessage(text) + end end -- Game state message channel local function ReceiveGameMsg() - local text = net.ReadString() - local special = net.ReadBit() == 1 + local text = net.ReadString() + local special = net.ReadBit() == 1 - print(text) + Dev(2, text) - MSTACK:AddMessage(text, special) + MSTACK:AddMessage(text, special) end net.Receive("TTT_GameMsg", ReceiveGameMsg) local function ReceiveCustomMsg() - local text = net.ReadString() - local c = table.Copy(COLOR_WHITE) + local text = net.ReadString() + local c = table.Copy(COLOR_WHITE) - c.r = net.ReadUInt(8) - c.g = net.ReadUInt(8) - c.b = net.ReadUInt(8) + c.r = net.ReadUInt(8) + c.g = net.ReadUInt(8) + c.b = net.ReadUInt(8) - print(text) + Dev(2, text) - MSTACK:AddColoredMessage(text, c) + MSTACK:AddColoredMessage(text, c) end net.Receive("TTT_GameMsgColor", ReceiveCustomMsg) diff --git a/gamemodes/terrortown/gamemode/client/cl_network_sync.lua b/gamemodes/terrortown/gamemode/client/cl_network_sync.lua index 37ab90b12..31446e5d7 100644 --- a/gamemodes/terrortown/gamemode/client/cl_network_sync.lua +++ b/gamemodes/terrortown/gamemode/client/cl_network_sync.lua @@ -23,19 +23,21 @@ ttt2net.dataListeners = {} -- @return any The value at the given path or nil if the path does not exist, the value is actually nil or there is no metadata entry for the path -- @realm client function ttt2net.Get(path) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Prevent wrong data being returned, when no metadata entry exists - if table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) == nil then return end + -- Prevent wrong data being returned, when no metadata entry exists + if table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) == nil then + return + end - return table.GetWithPath(ttt2net.dataStore, tmpPath) + return table.GetWithPath(ttt2net.dataStore, tmpPath) end --- @@ -47,19 +49,19 @@ end -- @return any The value at the given path or nil if the path does not exist, the value is actually nil or there is no metadata entry for the path -- @realm client function ttt2net.GetGlobal(path) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table - table.insert(tmpPath, 1, "global") + -- Add the prefix for the correct table + table.insert(tmpPath, 1, "global") - return ttt2net.Get(tmpPath) + return ttt2net.Get(tmpPath) end --- @@ -72,33 +74,33 @@ end -- @return any The value at the given path or nil if the path does not exist, the value is actually nil or there is no metadata entry for the path -- @realm client function ttt2net.GetOnPlayer(path, ply) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table with the specific player - table.insert(tmpPath, 1, "players") - table.insert(tmpPath, 2, ply:EntIndex()) + -- Add the prefix for the correct table with the specific player + table.insert(tmpPath, 1, "players") + table.insert(tmpPath, 2, ply:EntIndex()) - return ttt2net.Get(tmpPath, client) + return ttt2net.Get(tmpPath, ply) end --- -- Prints out all ttt2net related tables, for debugging purposes. -- @realm client function ttt2net.Debug() - print("[TTT2NET] Debug:") - print("Registered listeners:") - PrintTable(ttt2net.dataListeners) - print("Meta data table:") - PrintTable(ttt2net.dataStoreMetadata) - print("Data store table:") - PrintTable(ttt2net.dataStore) + Dev(2, "[TTT2NET] Debug:") + Dev(2, "Registered listeners:") + PrintTable(ttt2net.dataListeners) + Dev(2, "Meta data table:") + PrintTable(ttt2net.dataStoreMetadata) + Dev(2, "Data store table:") + PrintTable(ttt2net.dataStore) end --- @@ -109,27 +111,27 @@ end -- @internal -- @realm client local function CallCallbacksOnTree(oldData, curPath) - local tmpPath = not istable(curPath) and { curPath } or table.Copy(curPath) or {} - local curNode = table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) - - -- Go through all keys that the current node has - for key in pairs(curNode) do - local nextNode = curNode[key] - local nextPath = table.Copy(tmpPath) - nextPath[#nextPath + 1] = key - - if nextNode.type then - -- The node is a leaf node -> is a node with meta data - local oldval = table.GetWithPath(oldData, nextPath) - local newval = ttt2net.Get(nextPath) - - -- Call the listeners for this change - ttt2net.CallOnUpdate(nextPath, oldval, newval) - else - -- Descend a level deeper - CallCallbacksOnTree(oldData, nextPath) - end - end + local tmpPath = not istable(curPath) and { curPath } or table.Copy(curPath) or {} + local curNode = table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) + + -- Go through all keys that the current node has + for key in pairs(curNode) do + local nextNode = curNode[key] + local nextPath = table.Copy(tmpPath) + nextPath[#nextPath + 1] = key + + if nextNode.type then + -- The node is a leaf node -> is a node with meta data + local oldval = table.GetWithPath(oldData, nextPath) + local newval = ttt2net.Get(nextPath) + + -- Call the listeners for this change + ttt2net.CallOnUpdate(nextPath, oldval, newval) + else + -- Descend a level deeper + CallCallbacksOnTree(oldData, nextPath) + end + end end --- @@ -141,16 +143,16 @@ end -- @internal -- @realm client local function ReceiveFullStateUpdate(result) - -- Temporarily save the old tables, to use let the update callbacks know of the previous value - local oldData = ttt2net.dataStore - local oldMetaData = ttt2net.dataStoreMetadata + -- Temporarily save the old tables, to use let the update callbacks know of the previous value + local oldData = ttt2net.dataStore + local oldMetaData = ttt2net.dataStoreMetadata - -- Replace tables with the result - ttt2net.dataStoreMetadata = result.meta - ttt2net.dataStore = result.data + -- Replace tables with the result + ttt2net.dataStoreMetadata = result.meta + ttt2net.dataStore = result.data - -- Call all callback functions for all paths - CallCallbacksOnTree(oldData, oldMetaData) + -- Call all callback functions for all paths + CallCallbacksOnTree(oldData, oldMetaData) end net.ReceiveStream(ttt2net.NET_STREAM_FULL_STATE_UPDATE, ReceiveFullStateUpdate) @@ -162,20 +164,20 @@ net.ReceiveStream(ttt2net.NET_STREAM_FULL_STATE_UPDATE, ReceiveFullStateUpdate) -- @internal -- @realm client local function ReceiveMetaDataUpdate() - local path = ttt2net.NetReadPath() - local metadata = ttt2net.NetReadMetaData() + local path = ttt2net.NetReadPath() + local metadata = ttt2net.NetReadMetaData() - -- Set the new metadata - table.SetWithPath(ttt2net.dataStoreMetadata, path, metadata) + -- Set the new metadata + table.SetWithPath(ttt2net.dataStoreMetadata, path, metadata) - -- Store the old value if there is any known - local oldval = table.GetWithPath(ttt2net.dataStore, path) + -- Store the old value if there is any known + local oldval = table.GetWithPath(ttt2net.dataStore, path) - -- Clear the saved value, as the metadata changed - table.SetWithPath(ttt2net.dataStore, path, nil) + -- Clear the saved value, as the metadata changed + table.SetWithPath(ttt2net.dataStore, path, nil) - -- Call callbacks that registered on data updates - ttt2net.CallOnUpdate(path, oldval, nil) + -- Call callbacks that registered on data updates + ttt2net.CallOnUpdate(path, oldval, nil) end net.Receive(ttt2net.NETMSG_META_UPDATE, ReceiveMetaDataUpdate) @@ -187,22 +189,22 @@ net.Receive(ttt2net.NETMSG_META_UPDATE, ReceiveMetaDataUpdate) -- @internal -- @realm client local function ReceiveDataUpdate() - local path = ttt2net.NetReadPath() + local path = ttt2net.NetReadPath() - -- Get the metadata for the path, this will also error if there is no metadata entry for the path - local metadata = table.GetWithPath(ttt2net.dataStoreMetadata, path) + -- Get the metadata for the path, this will also error if there is no metadata entry for the path + local metadata = table.GetWithPath(ttt2net.dataStoreMetadata, path) - -- Read the new value based on the metadata entry - local newval = ttt2net.NetReadData(metadata) + -- Read the new value based on the metadata entry + local newval = ttt2net.NetReadData(metadata) - -- Save the old value as we need to tell the update listeners about it - local oldval = ttt2net.Get(path) + -- Save the old value as we need to tell the update listeners about it + local oldval = ttt2net.Get(path) - -- Save the new data - table.SetWithPath(ttt2net.dataStore, path, newval) + -- Save the new data + table.SetWithPath(ttt2net.dataStore, path, newval) - -- Call the update listeners - ttt2net.CallOnUpdate(path, oldval, newval) + -- Call the update listeners + ttt2net.CallOnUpdate(path, oldval, newval) end net.Receive(ttt2net.NETMSG_DATA_UPDATE, ReceiveDataUpdate) @@ -215,50 +217,52 @@ net.Receive(ttt2net.NETMSG_DATA_UPDATE, ReceiveDataUpdate) -- @param any newval The new value, after the update -- @realm client function ttt2net.CallOnUpdate(path, oldval, newval) - -- Skip if the value did not change - if oldval == newval then return end + -- Skip if the value did not change + if oldval == newval then + return + end - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - local currentPath = table.Copy(tmpPath) - local reversePath = {} + local currentPath = table.Copy(tmpPath) + local reversePath = {} - -- Traverse the path tree up from the bottom and call all their listeners - for y = 1, #tmpPath do - -- Add the special key for the "callbacks" to the path - currentPath[#currentPath + 1] = "__callbacks" + -- Traverse the path tree up from the bottom and call all their listeners + for y = 1, #tmpPath do + -- Add the special key for the "callbacks" to the path + currentPath[#currentPath + 1] = "__callbacks" - -- Get all registered callbacks - local callbacks = table.GetWithPath(ttt2net.dataListeners, currentPath) or {} + -- Get all registered callbacks + local callbacks = table.GetWithPath(ttt2net.dataListeners, currentPath) or {} - -- Call all registered callbacks for the current path - for i = 1, #callbacks do - callbacks[i](oldval, newval, reversePath) - end + -- Call all registered callbacks for the current path + for i = 1, #callbacks do + callbacks[i](oldval, newval, reversePath) + end - -- Remove the "callbacks" path entry - currentPath[#currentPath] = nil + -- Remove the "callbacks" path entry + currentPath[#currentPath] = nil - -- Remove the last path element and save in reversePath to let the parent callbacks know which value has changed - table.insert(reversePath, 1, currentPath[#currentPath]) + -- Remove the last path element and save in reversePath to let the parent callbacks know which value has changed + table.insert(reversePath, 1, currentPath[#currentPath]) - currentPath[#currentPath] = nil - end + currentPath[#currentPath] = nil + end end --- -- Request a full state update from the server. -- @realm client function ttt2net.RequestFullStateUpdate() - net.Start(ttt2net.NETMSG_REQUEST_FULL_STATE_UPDATE) - net.SendToServer() + net.Start(ttt2net.NETMSG_REQUEST_FULL_STATE_UPDATE) + net.SendToServer() end --- @@ -267,9 +271,9 @@ end -- @return table The path table -- @realm client function ttt2net.NetReadPath() - local result = net.ReadString() + local result = net.ReadString() - return pon.decode(result) + return pon.decode(result) end --- @@ -278,19 +282,21 @@ end -- @return table The metadata table -- @realm client function ttt2net.NetReadMetaData() - -- If the null flag is set, then return null - if net.ReadBool() then return end + -- If the null flag is set, then return null + if net.ReadBool() then + return + end - local metadata = {} - metadata.type = net.ReadString() + local metadata = {} + metadata.type = net.ReadString() - -- The int type has some extra information - if metadata.type == "int" then - metadata.bits = net.ReadUInt(6) - metadata.unsigned = net.ReadBool() - end + -- The int type has some extra information + if metadata.type == "int" then + metadata.bits = net.ReadUInt(6) + metadata.unsigned = net.ReadBool() + end - return metadata + return metadata end --- @@ -301,24 +307,26 @@ end -- @return any The data that was read -- @realm client function ttt2net.NetReadData(metadata) - -- If the null flag is set, then return null - if net.ReadBool() then return end - - if metadata.type == "int" then - if metadata.unsigned then - return net.ReadUInt(metadata.bits or 32) - else - return net.ReadInt(metadata.bits or 32) - end - elseif metadata.type == "bool" then - return net.ReadBool() - elseif metadata.type == "float" then - return net.ReadFloat() - elseif metadata.type == "table" then - return pon.decode(net.ReadString()) - else - return net.ReadString() - end + -- If the null flag is set, then return null + if net.ReadBool() then + return + end + + if metadata.type == "int" then + if metadata.unsigned then + return net.ReadUInt(metadata.bits or 32) + else + return net.ReadInt(metadata.bits or 32) + end + elseif metadata.type == "bool" then + return net.ReadBool() + elseif metadata.type == "float" then + return net.ReadFloat() + elseif metadata.type == "table" then + return pon.decode(net.ReadString()) + else + return net.ReadString() + end end --- @@ -329,31 +337,33 @@ end -- @param function func -- @realm client function ttt2net.OnUpdate(path, func) - assert(isfunction(func), "[TTT2NET] OnUpdate called with an invalid function.") + assert(isfunction(func), "[TTT2NET] OnUpdate called with an invalid function.") - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the special callback key to the path - tmpPath[#tmpPath + 1] = "__callbacks" + -- Add the special callback key to the path + tmpPath[#tmpPath + 1] = "__callbacks" - -- Get the registered callbacks table for the given path - local registeredCallbacks = table.GetWithPath(ttt2net.dataListeners, tmpPath) or {} + -- Get the registered callbacks table for the given path + local registeredCallbacks = table.GetWithPath(ttt2net.dataListeners, tmpPath) or {} - -- Check if this function is not already registered, to avoid duplicates - if table.HasValue(registeredCallbacks, func) then return end + -- Check if this function is not already registered, to avoid duplicates + if table.HasValue(registeredCallbacks, func) then + return + end - -- Add the function to the callback table - registeredCallbacks[#registeredCallbacks + 1] = func + -- Add the function to the callback table + registeredCallbacks[#registeredCallbacks + 1] = func - -- Set the callback table again - table.SetWithPath(ttt2net.dataListeners, tmpPath, registeredCallbacks) + -- Set the callback table again + table.SetWithPath(ttt2net.dataListeners, tmpPath, registeredCallbacks) end --- @@ -363,19 +373,19 @@ end -- @param function func The callback function that should be executed -- @realm client function ttt2net.OnUpdateGlobal(path, func) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table - table.insert(tmpPath, 1, "global") + -- Add the prefix for the correct table + table.insert(tmpPath, 1, "global") - ttt2net.OnUpdate(tmpPath, func) + ttt2net.OnUpdate(tmpPath, func) end --- @@ -386,18 +396,18 @@ end -- @param function func The callback function that should be executed -- @realm client function ttt2net.OnUpdateOnPlayer(path, ply, func) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table with the specific player - table.insert(tmpPath, 1, "players") - table.insert(tmpPath, 2, ply:EntIndex()) + -- Add the prefix for the correct table with the specific player + table.insert(tmpPath, 1, "players") + table.insert(tmpPath, 2, ply:EntIndex()) - ttt2net.OnUpdate(tmpPath, func) + ttt2net.OnUpdate(tmpPath, func) end diff --git a/gamemodes/terrortown/gamemode/client/cl_player_ext.lua b/gamemodes/terrortown/gamemode/client/cl_player_ext.lua index 5d3ae8099..7a8470796 100644 --- a/gamemodes/terrortown/gamemode/client/cl_player_ext.lua +++ b/gamemodes/terrortown/gamemode/client/cl_player_ext.lua @@ -2,77 +2,96 @@ local net = net local plymeta = FindMetaTable("Player") if not plymeta then - Error("FAILED TO FIND PLAYER TABLE") + ErrorNoHaltWithStack("FAILED TO FIND PLAYER TABLE") - return + return end +--- +-- @realm client +-- stylua: ignore +local cvEnableBobbing = CreateConVar("ttt2_enable_bobbing", "1", FCVAR_ARCHIVE) + +--- +-- @realm client +-- stylua: ignore +local cvEnableBobbingStrafe = CreateConVar("ttt2_enable_bobbing_strafe", "1", FCVAR_ARCHIVE) + +-- @realm client +-- stylua: ignore +local cvEnableDynamicFOV = CreateConVar("ttt2_enable_dynamic_fov", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + +cvars.AddChangeCallback("ttt2_enable_dynamic_fov", function(_, _, valueNew) + LocalPlayer():SetSettingOnServer("enable_dynamic_fov", tobool(valueNew)) +end) + --- -- Applies a animation gesture -- @param ACT act The @{ACT} or sequence that should be played -- @param number weight The weight this slot should be set to. Value must be ranging from 0 to 1. -- @realm client --- @see https://wiki.garrysmod.com/page/Player/AnimRestartGesture --- @see https://wiki.garrysmod.com/page/Player/AnimSetGestureWeight +-- @see https://wiki.facepunch.com/gmod/Player:AnimRestartGesture +-- @see https://wiki.facepunch.com/gmod/Player:AnimSetGestureWeight function plymeta:AnimApplyGesture(act, weight) - self:AnimRestartGesture(GESTURE_SLOT_CUSTOM, act, true) -- true = autokill - self:AnimSetGestureWeight(GESTURE_SLOT_CUSTOM, weight) + self:AnimRestartGesture(GESTURE_SLOT_CUSTOM, act, true) -- true = autokill + self:AnimSetGestureWeight(GESTURE_SLOT_CUSTOM, weight) end local function MakeSimpleRunner(act) - return function(ply, w) - -- just let this gesture play itself and get out of its way - if w == 0 then - ply:AnimApplyGesture(act, 1) - - return 1 - else - return 0 - end - end + return function(ply, w) + -- just let this gesture play itself and get out of its way + if w == 0 then + ply:AnimApplyGesture(act, 1) + + return 1 + else + return 0 + end + end end -- act -> gesture runner fn local act_runner = { - -- ear grab needs weight control - -- sadly it's currently the only one - [ACT_GMOD_IN_CHAT] = function(ply, w) - local dest = ply:IsSpeaking() and 1 or 0 - - w = math.Approach(w, dest, FrameTime() * 10) - if w > 0 then - ply:AnimApplyGesture(ACT_GMOD_IN_CHAT, w) - end - - return w - end + -- ear grab needs weight control + -- sadly it's currently the only one + [ACT_GMOD_IN_CHAT] = function(ply, w) + local dest = ply:IsSpeaking() and 1 or 0 + + w = math.Approach(w, dest, FrameTime() * 10) + if w > 0 then + ply:AnimApplyGesture(ACT_GMOD_IN_CHAT, w) + end + + return w + end, } -- Insert all the "simple" gestures that do not need weight control local gestTbl = { - ACT_GMOD_GESTURE_AGREE, - ACT_GMOD_GESTURE_DISAGREE, - ACT_GMOD_GESTURE_WAVE, - ACT_GMOD_GESTURE_BECON, - ACT_GMOD_GESTURE_BOW, - ACT_GMOD_TAUNT_SALUTE, - ACT_GMOD_TAUNT_CHEER, - ACT_SIGNAL_FORWARD, - ACT_SIGNAL_HALT, - ACT_SIGNAL_GROUP, - ACT_GMOD_GESTURE_ITEM_PLACE, - ACT_GMOD_GESTURE_ITEM_DROP, - ACT_GMOD_GESTURE_ITEM_GIVE + ACT_GMOD_GESTURE_AGREE, + ACT_GMOD_GESTURE_DISAGREE, + ACT_GMOD_GESTURE_WAVE, + ACT_GMOD_GESTURE_BECON, + ACT_GMOD_GESTURE_BOW, + ACT_GMOD_TAUNT_SALUTE, + ACT_GMOD_TAUNT_CHEER, + ACT_SIGNAL_FORWARD, + ACT_SIGNAL_HALT, + ACT_SIGNAL_GROUP, + ACT_GMOD_GESTURE_ITEM_PLACE, + ACT_GMOD_GESTURE_ITEM_DROP, + ACT_GMOD_GESTURE_ITEM_GIVE, } for _i = 1, #gestTbl do - local a = gestTbl[_i] + local a = gestTbl[_i] - act_runner[a] = MakeSimpleRunner(a) + act_runner[a] = MakeSimpleRunner(a) end --- -- @realm client +-- stylua: ignore local cv_ttt_show_gestures = CreateConVar("ttt_show_gestures", "1", FCVAR_ARCHIVE) --- @@ -83,30 +102,34 @@ local cv_ttt_show_gestures = CreateConVar("ttt_show_gestures", "1", FCVAR_ARCHIV -- @return boolean success? -- @realm client function plymeta:AnimPerformGesture(act, custom_runner) - if not cv_ttt_show_gestures or cv_ttt_show_gestures:GetInt() == 0 then return end + if not cv_ttt_show_gestures or cv_ttt_show_gestures:GetInt() == 0 then + return + end - local runner = custom_runner or act_runner[act] - if not runner then - return false - end + local runner = custom_runner or act_runner[act] + if not runner then + return false + end - self.GestureWeight = 0 - self.GestureRunner = runner + self.GestureWeight = 0 + self.GestureRunner = runner - return true + return true end --- -- Perform a gesture update -- @realm client function plymeta:AnimUpdateGesture() - if not self.GestureRunner then return end + if not self.GestureRunner then + return + end - self.GestureWeight = self:GestureRunner(self.GestureWeight) + self.GestureWeight = self:GestureRunner(self.GestureWeight) - if self.GestureWeight <= 0 then - self.GestureRunner = nil - end + if self.GestureWeight <= 0 then + self.GestureRunner = nil + end end --- @@ -117,145 +140,218 @@ end -- @return any ? -- @realm client function GM:UpdateAnimation(ply, vel, maxseqgroundspeed) - ply:AnimUpdateGesture() + ply:AnimUpdateGesture() - return self.BaseClass.UpdateAnimation(self, ply, vel, maxseqgroundspeed) + return self.BaseClass.UpdateAnimation(self, ply, vel, maxseqgroundspeed) end --- -- @param Player ply -- @hook -- @realm client -function GM:GrabEarAnimation(ply) - -end +function GM:GrabEarAnimation(ply) end local function TTT_PerformGesture() - local ply = net.ReadEntity() - local act = net.ReadUInt(16) + local ply = net.ReadEntity() + local act = net.ReadUInt(16) - if not IsValid(ply) or act == nil then return end + if not IsValid(ply) or act == nil then + return + end - ply:AnimPerformGesture(act) + ply:AnimPerformGesture(act) end net.Receive("TTT_PerformGesture", TTT_PerformGesture) local function StartDrowning() - local client = LocalPlayer() - if not IsValid(client) then return end + local client = LocalPlayer() + if not IsValid(client) then + return + end - local bool = net.ReadBool() + local bool = net.ReadBool() - client:StartDrowning(bool, bool and net.ReadUInt(16), bool and net.ReadUInt(16)) + client:StartDrowning(bool, bool and net.ReadUInt(16), bool and net.ReadUInt(16)) end net.Receive("StartDrowning", StartDrowning) local function TargetPlayer() - local client = LocalPlayer() - local target = net.ReadEntity() + local client = LocalPlayer() + local target = net.ReadEntity() - if not IsValid(client) then return end + if not IsValid(client) then + return + end - if not IsValid(target) or not target:IsPlayer() or target:IsWorld() then - target = nil - end + if not IsValid(target) or not target:IsPlayer() or target:IsWorld() then + target = nil + end - if target == nil or IsValid(target) and target:IsActive() and target:Alive() then - client:SetTargetPlayer(target) - end + if target == nil or IsValid(target) and target:IsActive() and target:Alive() then + client:SetTargetPlayer(target) + end end net.Receive("TTT2TargetPlayer", TargetPlayer) +local function UpdateCredits() + local client = LocalPlayer() + if not IsValid(client) then + return + end + + client.equipment_credits = net.ReadUInt(8) +end +net.Receive("TTT_Credits", UpdateCredits) + +local function UpdateEquipment() + local client = LocalPlayer() + if not IsValid(client) then + return + end + + local mode = net.ReadUInt(2) + + local equipItems = client:GetEquipmentItems() + + if mode == EQUIPITEMS_RESET then + for i = #equipItems, 1, -1 do + local itemName = equipItems[i] + local item = items.GetStored(itemName) + + if item and isfunction(item.Reset) then + item:Reset(client) + end + end + + table.Empty(equipItems) + else + local itemName = net.ReadString() + local item = items.GetStored(itemName) + + if mode == EQUIPITEMS_ADD then + equipItems[#equipItems + 1] = itemName + + if item and isfunction(item.Equip) then + item:Equip(client) + end + elseif mode == EQUIPITEMS_REMOVE then + table.RemoveByValue(equipItems, itemName) + + if item and isfunction(item.Reset) then + item:Reset(client) + end + end + end +end +net.Receive("TTT_Equipment", UpdateEquipment) + --- -- SetupMove is called before the engine process movements. This allows us -- to override the players movement. -- @param Player ply The @{Player} that sets up its movement --- @param MoveData mv The move data to override/use +-- @param CMoveData mv The move data to override/use -- @param CUserCmd cmd The command data -- @hook -- @realm client -- @ref https://wiki.facepunch.com/gmod/GM:SetupMove -- @local function GM:SetupMove(ply, mv, cmd) - if not IsValid(ply) or ply:IsReady() then return end - - ply.isReady = true - - net.Start("TTT2SetPlayerReady") - net.SendToServer() - - --- - -- @realm shared - hook.Run("TTT2PlayerReady", ply) - - -- check if a resolution change happened while - -- the gamemode was inactive - oldScrW = appearance.GetLastWidth() - oldScrH = appearance.GetLastHeight() - - if oldScrH ~= ScrH() or oldScrW ~= ScrW() then - --- - -- @realm client - hook.Run("OnScreenSizeChanged", oldScrW, oldScrH) - end + if not IsValid(ply) or ply:IsReady() then + return + end + + ply.isReady = true + + net.Start("TTT2SetPlayerReady") + net.SendToServer() + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2PlayerReady", ply) + + -- check if a resolution change happened while + -- the gamemode was inactive + oldScrW = appearance.GetLastWidth() + oldScrH = appearance.GetLastHeight() + + if oldScrH ~= ScrH() or oldScrW ~= ScrW() then + --- + -- @realm client + -- stylua: ignore + hook.Run("OnScreenSizeChanged", oldScrW, oldScrH) + end end --- -- Sets a revival reason that is displayed in the revival HUD element. -- It supports a language identifier for translated strings. --- @param[default=nil] string name The text or the language identifer, nil to reset +-- @param[default=nil] string name The text or the language identifier, nil to reset -- @param[opt] table params The params table used for @{LANG.GetParamTranslation} -- @realm client function plymeta:SetRevivalReason(name, params) - self.revivalReason = {} - self.revivalReason.name = name - self.revivalReason.params = params + self.revivalReason = {} + self.revivalReason.name = name + self.revivalReason.params = params end net.Receive("TTT2SetRevivalReason", function() - local client = LocalPlayer() + local client = LocalPlayer() - if not IsValid(client) then return end + if not IsValid(client) then + return + end - local isReset = net.ReadBool() - local name, params + local isReset = net.ReadBool() + local name, params - if not isReset then - name = net.ReadString() + if not isReset then + name = net.ReadString() - local paramsAmount = net.ReadUInt(8) + local paramsAmount = net.ReadUInt(8) - if paramsAmount > 0 then - params = {} + if paramsAmount > 0 then + params = {} - for i = 1, paramsAmount do - params[net.ReadString()] = net.ReadString() - end - end - end + for i = 1, paramsAmount do + params[net.ReadString()] = net.ReadString() + end + end + end - client:SetRevivalReason(name, params) + client:SetRevivalReason(name, params) end) -- plays an error sound only on the local player, not for all players net.Receive("TTT2RevivalStopped", function() - LocalPlayer():EmitSound("buttons/button8.wav") + LocalPlayer():EmitSound("buttons/button8.wav") end) net.Receive("TTT2RevivalUpdate_IsReviving", function() - LocalPlayer().isReviving = net.ReadBool() + local client = LocalPlayer() + + client.isReviving = net.ReadBool() + + if not client.isReviving then + return + end + + if not system.HasFocus() then + system.FlashWindow() + end + client:EmitSound("items/smallmedkit1.wav") end) net.Receive("TTT2RevivalUpdate_RevivalBlockMode", function() - LocalPlayer().revivalBlockMode = net.ReadUInt(REVIVAL_BITS) + LocalPlayer().revivalBlockMode = net.ReadUInt(REVIVAL_BITS) end) net.Receive("TTT2RevivalUpdate_RevivalStartTime", function() - LocalPlayer().revivalStartTime = net.ReadFloat() + LocalPlayer().revivalStartTime = net.ReadFloat() end) net.Receive("TTT2RevivalUpdate_RevivalDuration", function() - LocalPlayer().revivalDurarion = net.ReadFloat() + LocalPlayer().revivalDurarion = net.ReadFloat() end) --- @@ -263,7 +359,8 @@ end) -- @return[default=false] boolean Returns if a player has a revival reason -- @realm client function plymeta:HasRevivalReason() - return (self.revivalReason and self.revivalReason.name and self.revivalReason.name ~= "") or false + return (self.revivalReason and self.revivalReason.name and self.revivalReason.name ~= "") + or false end --- @@ -271,5 +368,191 @@ end -- @return[default={}] table The revival reason table -- @realm client function plymeta:GetRevivalReason() - return self.revivalReason or {} + return self.revivalReason or {} end + +--- +-- Sets a shared playersetting from the client on both server and client. Make sure the +-- variable is registered with `RegisterSettingOnServer` as the setting is otherwise discarded. +-- @param string identifier The identifier of the shared setting +-- @param any value The setting's value, it is parsed as a string before transmitting +-- @realm client +function plymeta:SetSettingOnServer(identifier, value) + self.playerSettings = self.playerSettings or {} + + if self.playerSettings[identifier] == value then + return + end + + local oldValue = self.playerSettings[identifier] + + self.playerSettings[identifier] = value + + net.Start("ttt2_set_player_setting") + net.WriteString(identifier) + net.WriteString(tostring(value)) + net.SendToServer() + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2PlayerSettingChanged", self, identifier, oldValue, self.playerSettings[identifier]) +end + +local airtime = 0 +local velocity = 0 +local position = 0 + +local frameCount = 10 + +local lastStrafeValue = 0 + +local cvHostTimescale = GetConVar("host_timescale") + +-- handles dynamic camera features such as view bobbing, stafe tilting and fov changes +-- parts of it are heavily inspired from V92's "Head Bobbing": https://steamcommunity.com/sharedfiles/filedetails/?id=572928034 +hook.Add("CalcView", "TTT2DynamicCamera", function(ply, origin, angles, fov) + local observerTarget = ply:GetObserverTarget() + + -- handle observing players + if not ply:IsTerror() and IsValid(obersverTarget) and observerTarget:IsPlayer() then + ply = observerTarget + end + + if not ply:IsTerror() or ply:GetMoveType() == MOVETYPE_NOCLIP then + return + end + + local view = { + ply = ply, + origin = origin, + angles = angles, + fov = fov, + } + + -- This variable is used to cache the last FOV falue on a frame by frame basis, it is used to + -- achieve smooth transitions. It is not to be confused with the synced LastFOVValue as this only + -- caches the FOV changes on a macro scale and has nothing to do with the animation on a frame + -- by frame base. + -- Also the lastFOVFrameValue variable is intentionally not always set to the current value to + -- control the animation in such a way that FOV jumps are prohibited. + ply.lastFOVFrameValue = ply.lastFOVFrameValue or fov + + local dynFOV = fov + local mul = ply:GetSpeedMultiplier() * SPRINT:HandleSpeedMultiplierCalculation(ply) + local desiredFOV = fov * mul ^ (1 / 6) + + -- fixed mode: when SetZoom is set to something different than 0, this value should be used + -- without any custom modification + if ply:GetFOVIsFixed() then + dynFOV = fov + + -- dynamic mode: transition between different FOV values + else + local time = math.max( + 0, + (CurTime() - ply:GetFOVTime()) * game.GetTimeScale() * cvHostTimescale:GetFloat() + ) + + local progressTransition = math.min(1.0, time / ply:GetFOVTransitionTime()) + + -- if the transition progress has reached 100%, we should enable the sprint + -- FOV smoothing algorithm; the value 40 was determined by trying different values + -- until it looked like a smooth transition + if progressTransition >= 1.0 then + if desiredFOV > ply.lastFOVFrameValue then + desiredFOV = math.min(desiredFOV, ply.lastFOVFrameValue + FrameTime() * 40) + elseif desiredFOV < ply.lastFOVFrameValue then + desiredFOV = math.max(desiredFOV, ply.lastFOVFrameValue - FrameTime() * 40) + end + + ply.lastFOVFrameValue = desiredFOV + end + + -- make sure that FOV values of 0 are mapped to the desired FOV value + -- which is based on the base FOV value set in the GMOD settings + local fovNext = ply:GetFOVValue() + local fovLast = ply:GetFOVLastValue() + + if fovNext == 0 then + fovNext = desiredFOV + end + + if fovLast == 0 then + fovLast = desiredFOV + end + + dynFOV = fovLast - (fovLast - fovNext) * progressTransition + end + + if cvEnableDynamicFOV:GetBool() then + view.fov = dynFOV + end + + if (not ply:IsOnGround() and ply:WaterLevel() == 0) or ply:InVehicle() then + airtime = math.Clamp(airtime + 1, 0, 300) + + return view + end + + local eyeAngles = ply:EyeAngles() + local strafeValue = 0 + + -- handle landing on ground + if airtime > 0 then + airtime = airtime / frameCount + + view.angles.p = view.angles.p + airtime * 0.01 -- pitch cam shake on land + view.angles.r = view.angles.r + airtime * 0.02 * math.Rand(-1, 1) -- roll cam shake on land + end + + -- handle crouching + if ply:Crouching() then + local velocityMultiplier = ply:GetVelocity() * 2 + + velocity = velocity * 0.9 + velocityMultiplier:Length() * 0.1 + position = position + velocity * FrameTime() * 0.1 + + strafeValue = eyeAngles:Right():Dot(velocityMultiplier) * 0.015 + + -- handle swimming + elseif ply:WaterLevel() > 0 then + local velocityMultiplier = ply:GetVelocity() * 1.5 + + velocity = velocity * 0.9 + velocityMultiplier:Length() * 0.1 + position = position + velocity * FrameTime() * 0.1 + + strafeValue = eyeAngles:Right():Dot(velocityMultiplier) * 0.005 + + -- handle walking + else + local velocityMultiplier = ply:GetVelocity() * 0.75 + + velocity = velocity * 0.9 + velocityMultiplier:Length() * 0.1 + position = position + velocity * FrameTime() * 0.1 + + strafeValue = eyeAngles:Right():Dot(velocityMultiplier) * 0.006 + end + + strafeValue = math.Round(strafeValue, 2) + lastStrafeValue = math.Round(lastStrafeValue, 2) + + if strafeValue > lastStrafeValue then + lastStrafeValue = math.min(strafeValue, lastStrafeValue + FrameTime() * 35.0) + elseif strafeValue < lastStrafeValue then + lastStrafeValue = math.max(strafeValue, lastStrafeValue - FrameTime() * 35.0) + else + lastStrafeValue = strafeValue + end + + if cvEnableBobbing:GetBool() then + view.angles.r = view.angles.r + math.sin(position * 0.5) * velocity * 0.001 + view.angles.p = view.angles.p + math.sin(position * 0.25) * velocity * 0.001 + end + + if cvEnableBobbingStrafe:GetBool() then + view.angles.r = view.angles.r + lastStrafeValue + end + + return view +end) diff --git a/gamemodes/terrortown/gamemode/client/cl_popups.lua b/gamemodes/terrortown/gamemode/client/cl_popups.lua index b128913f7..8776c2624 100644 --- a/gamemodes/terrortown/gamemode/client/cl_popups.lua +++ b/gamemodes/terrortown/gamemode/client/cl_popups.lua @@ -8,173 +8,191 @@ local timer = timer --- -- Round start local function GetTextForPlayer(ply) - local menukey = Key("+menu_context", "C") - local roleData = ply:GetSubRoleData() - - if ply:GetTeam() ~= TEAM_TRAITOR then - local fallback = GetGlobalString("ttt_" .. roleData.abbr .. "_shop_fallback") - - --- - -- @realm client - if fallback == SHOP_DISABLED or hook.Run("TTT2PreventAccessShop", ply) then - return GetTranslation("info_popup_" .. roleData.name) - else - return GetPTranslation("info_popup_" .. roleData.name, {menukey = menukey}) - end - else - local traitors = roles.GetTeamMembers(TEAM_TRAITOR) - - if #traitors > 1 then - local traitorlist = {} - - for i = 1, #traitors do - local p = traitors[i] - - if p == ply then continue end - - local index = #traitorlist - - traitorlist[index + 1] = string.rep(" ", 42) - traitorlist[index + 2] = p:Nick() - traitorlist[index + 3] = "\n" - end - - traitorlist = table.concat(traitorlist) - - local fallback = GetGlobalString("ttt_" .. roleData.abbr .. "_shop_fallback") - - --- - -- @realm client - if fallback == SHOP_DISABLED or hook.Run("TTT2PreventAccessShop", ply) then - return GetTranslation("info_popup_" .. roleData.name, {traitorlist = traitorlist}) - else - return GetPTranslation("info_popup_" .. roleData.name, {menukey = menukey, traitorlist = traitorlist}) - end - else - local fallback = GetGlobalString("ttt_" .. roleData.abbr .. "_shop_fallback") - - --- - -- @realm client - if fallback == SHOP_DISABLED or hook.Run("TTT2PreventAccessShop", ply) then - return GetTranslation("info_popup_" .. roleData.name .. "_alone") - else - return GetPTranslation("info_popup_" .. roleData.name .. "_alone", {menukey = menukey}) - end - end - end + local menukey = Key("+menu_context", "C") + local roleData = ply:GetSubRoleData() + + if ply:GetTeam() ~= TEAM_TRAITOR then + local fallback = GetGlobalString("ttt_" .. roleData.abbr .. "_shop_fallback") + + --- + -- @realm client + -- stylua: ignore + if fallback == SHOP_DISABLED or hook.Run("TTT2PreventAccessShop", ply) then + return GetTranslation("info_popup_" .. roleData.name) + else + return GetPTranslation("info_popup_" .. roleData.name, {menukey = menukey}) + end + else + local traitors = roles.GetTeamMembers(TEAM_TRAITOR) + + if traitors and #traitors > 1 then + local traitorlist = {} + + for i = 1, #traitors do + local p = traitors[i] + + if p == ply then + continue + end + + local index = #traitorlist + + traitorlist[index + 1] = string.rep(" ", 42) + traitorlist[index + 2] = p:Nick() + traitorlist[index + 3] = "\n" + end + + traitorlist = table.concat(traitorlist) + + local fallback = GetGlobalString("ttt_" .. roleData.abbr .. "_shop_fallback") + + --- + -- @realm client + -- stylua: ignore + if fallback == SHOP_DISABLED or hook.Run("TTT2PreventAccessShop", ply) then + return GetPTranslation("info_popup_" .. roleData.name, {traitorlist = traitorlist}) + else + return GetPTranslation("info_popup_" .. roleData.name, {menukey = menukey, traitorlist = traitorlist}) + end + else + local fallback = GetGlobalString("ttt_" .. roleData.abbr .. "_shop_fallback") + + --- + -- @realm client + -- stylua: ignore + if fallback == SHOP_DISABLED or hook.Run("TTT2PreventAccessShop", ply) then + return GetTranslation("info_popup_" .. roleData.name .. "_alone") + else + return GetPTranslation("info_popup_" .. roleData.name .. "_alone", {menukey = menukey}) + end + end + end end --- -- @realm client +-- stylua: ignore local startshowtime = CreateConVar("ttt_startpopup_duration", "17", FCVAR_ARCHIVE) local function drawFunc(s, w, h) - draw.RoundedBox(8, 0, 0, w, h, s.paintColor) + draw.RoundedBox(8, 0, 0, w, h, s.paintColor) end --- -- shows info about goal and fellow traitors (if any) local function RoundStartPopup() - -- based on Derma_Message - if startshowtime:GetInt() <= 0 then return end + -- based on Derma_Message + if startshowtime:GetInt() <= 0 then + return + end - local client = LocalPlayer() - if not client then return end + local client = LocalPlayer() + if not client then + return + end - local dframe = vgui.Create("Panel") - dframe:SetDrawOnTop(true) - dframe:SetMouseInputEnabled(false) - dframe:SetKeyboardInputEnabled(false) + local dframe = vgui.Create("Panel") + dframe:SetDrawOnTop(true) + dframe:SetMouseInputEnabled(false) + dframe:SetKeyboardInputEnabled(false) - dframe.paintColor = Color(0, 0, 0, 200) + dframe.paintColor = Color(0, 0, 0, 200) - local paintFn = drawFunc + local paintFn = drawFunc - if huds and HUDManager then - local hud = huds.GetStored(HUDManager.GetHUD()) - if hud then - paintFn = hud.PopupPaint or paintFn - end - end + if huds and HUDManager then + local hud = huds.GetStored(HUDManager.GetHUD()) + if hud then + paintFn = hud.PopupPaint or paintFn + end + end - dframe.Paint = paintFn + dframe.Paint = paintFn - local text = GetTextForPlayer(client) + local text = GetTextForPlayer(client) - local dtext = vgui.Create("DLabel", dframe) - dtext:SetFont("TabLarge") - dtext:SetText(text) - dtext:SizeToContents() - dtext:SetContentAlignment(5) - dtext:SetTextColor(COLOR_WHITE) + local dtext = vgui.Create("DLabel", dframe) + dtext:SetFont("TabLarge") + dtext:SetText(text) + dtext:SizeToContents() + dtext:SetContentAlignment(5) + dtext:SetTextColor(COLOR_WHITE) - local w, h = dtext:GetSize() - local m = 10 + local w, h = dtext:GetSize() + local m = 10 - dtext:SetPos(m, m) + dtext:SetPos(m, m) - dframe:SetSize(w + m * 2, h + m * 2) - dframe:Center() + dframe:SetSize(w + m * 2, h + m * 2) + dframe:Center() - dframe:AlignBottom(10) + dframe:AlignBottom(10) - timer.Simple(startshowtime:GetInt(), function() - if not IsValid(dframe) then return end + timer.Simple(startshowtime:GetInt(), function() + if not IsValid(dframe) then + return + end - dframe:Remove() - end) + dframe:Remove() + end) end concommand.Add("ttt_cl_startpopup", RoundStartPopup) --- -- Idle message local function IdlePopup() - local w, h = 300, 180 + local w, h = 300, 180 - local dframe = vgui.Create("DFrame") - dframe:SetSize(w, h) - dframe:Center() - dframe:SetTitle(GetTranslation("idle_popup_title")) - dframe:SetVisible(true) - dframe:SetMouseInputEnabled(true) + local dframe = vgui.Create("DFrame") + dframe:SetSize(w, h) + dframe:Center() + dframe:SetTitle(GetTranslation("idle_popup_title")) + dframe:SetVisible(true) + dframe:SetMouseInputEnabled(true) - local inner = vgui.Create("DPanel", dframe) - inner:StretchToParent(5, 25, 5, 45) + local inner = vgui.Create("DPanel", dframe) + inner:StretchToParent(5, 25, 5, 45) - local idle_limit = GetGlobalInt("ttt_idle_limit", 300) or 300 + local idle_limit = GetGlobalInt("ttt_idle_limit", 300) or 300 - local text = vgui.Create("DLabel", inner) - text:SetWrap(true) - text:SetText(GetPTranslation("idle_popup", {num = idle_limit, helpkey = Key("gm_showhelp", "F1")})) - text:SetDark(true) - text:StretchToParent(10, 5, 10, 5) + local text = vgui.Create("DLabel", inner) + text:SetWrap(true) + text:SetText( + GetPTranslation("idle_popup", { num = idle_limit, helpkey = Key("gm_showhelp", "F1") }) + ) + text:SetDark(true) + text:StretchToParent(10, 5, 10, 5) - local bw, bh = 75, 25 - local cancel = vgui.Create("DButton", dframe) + local bw, bh = 75, 25 + local cancel = vgui.Create("DButton", dframe) - cancel:SetPos(10, h - 40) - cancel:SetSize(bw, bh) - cancel:SetText(GetTranslation("idle_popup_close")) + cancel:SetPos(10, h - 40) + cancel:SetSize(bw, bh) + cancel:SetText(GetTranslation("idle_popup_close")) - cancel.DoClick = function() - if not IsValid(dframe) then return end + cancel.DoClick = function() + if not IsValid(dframe) then + return + end - dframe:Close() - end + dframe:Close() + end - local disable = vgui.Create("DButton", dframe) - disable:SetPos(w - 185, h - 40) - disable:SetSize(175, bh) - disable:SetText(GetTranslation("idle_popup_off")) + local disable = vgui.Create("DButton", dframe) + disable:SetPos(w - 185, h - 40) + disable:SetSize(175, bh) + disable:SetText(GetTranslation("idle_popup_off")) - disable.DoClick = function() - if not IsValid(dframe) then return end + disable.DoClick = function() + if not IsValid(dframe) then + return + end - RunConsoleCommand("ttt_spectator_mode", "0") + RunConsoleCommand("ttt_spectator_mode", "0") - dframe:Close() - end + dframe:Close() + end - dframe:MakePopup() + dframe:MakePopup() end concommand.Add("ttt_cl_idlepopup", IdlePopup) diff --git a/gamemodes/terrortown/gamemode/client/cl_radio.lua b/gamemodes/terrortown/gamemode/client/cl_radio.lua index 8fc71f26a..d99897104 100644 --- a/gamemodes/terrortown/gamemode/client/cl_radio.lua +++ b/gamemodes/terrortown/gamemode/client/cl_radio.lua @@ -13,71 +13,77 @@ local radioframe RADIO = {} RADIO.Show = false -RADIO.StoredTarget = {nick = "", t = 0} -RADIO.LastRadio = {msg = "", t = 0} +RADIO.StoredTarget = { nick = "", t = 0 } +RADIO.LastRadio = { msg = "", t = 0 } -- [key] -> command RADIO.Commands = { - {cmd = "yes", text = "quick_yes", format = false}, - {cmd = "no", text = "quick_no", format = false}, - {cmd = "help", text = "quick_help", format = false}, - {cmd = "imwith", text = "quick_imwith", format = true}, - {cmd = "see", text = "quick_see", format = true}, - {cmd = "suspect", text = "quick_suspect", format = true}, - {cmd = "traitor", text = "quick_traitor", format = true}, - {cmd = "innocent", text = "quick_inno", format = true}, - {cmd = "check", text = "quick_check", format = false} + { cmd = "yes", text = "quick_yes", format = false }, + { cmd = "no", text = "quick_no", format = false }, + { cmd = "help", text = "quick_help", format = false }, + { cmd = "imwith", text = "quick_imwith", format = true }, + { cmd = "see", text = "quick_see", format = true }, + { cmd = "suspect", text = "quick_suspect", format = true }, + { cmd = "traitor", text = "quick_traitor", format = true }, + { cmd = "innocent", text = "quick_inno", format = true }, + { cmd = "check", text = "quick_check", format = false }, } local cmdToTag = { - ["innocent"] = TTTScoreboard.Tags[1], - ["suspect"] = TTTScoreboard.Tags[2], - --[""] = TTTScoreboard.Tags[3], - ["traitor"] = TTTScoreboard.Tags[4] - --[""] = TTTScoreboard.Tags[5] + ["innocent"] = TTTScoreboard.Tags[1], + ["suspect"] = TTTScoreboard.Tags[2], + --[""] = TTTScoreboard.Tags[3], + ["traitor"] = TTTScoreboard.Tags[4], + --[""] = TTTScoreboard.Tags[5] } local function tagPlayer(ply, rCmd) - if not isstring(ply) and IsValid(ply) and ply:IsPlayer() and cmdToTag[rCmd] then - -- If the radio command is one of the ones I track, tag the player - ply.sb_tag = cmdToTag[rCmd] - end + if not isstring(ply) and IsValid(ply) and ply:IsPlayer() and cmdToTag[rCmd] then + -- If the radio command is one of the ones I track, tag the player + ply.sb_tag = cmdToTag[rCmd] + end end local function RadioThink(s) - local tgt, v = RADIO:GetTarget() + local tgt, v = RADIO:GetTarget() - if s.target == tgt then return end + if s.target == tgt then + return + end - s.target = tgt + s.target = tgt - tgt = string.Interp(s.txt, {player = RADIO.ToPrintable(tgt)}) + tgt = string.Interp(s.txt, { player = RADIO.ToPrintable(tgt) }) - if v then - tgt = util.Capitalize(tgt) - end + if v then + tgt = util.Capitalize(tgt) + end - s:SetText(s.id .. tgt) - s:SizeToContents() + s:SetText(s.id .. tgt) + s:SizeToContents() - if not IsValid(radioframe) then return end + if not IsValid(radioframe) then + return + end - radioframe:ForceResize() + radioframe:ForceResize() end local function RadioResize(s) - local w = 0 - local label + local w = 0 + local label - for _, v in pairs(s.Items) do - label = v:GetChild(0) + for _, v in pairs(s.Items) do + label = v:GetChild(0) - if not IsValid(label) or label:GetWide() <= w then continue end + if not IsValid(label) or label:GetWide() <= w then + continue + end - w = label:GetWide() - end + w = label:GetWide() + end - s:SetWide(w + 20) + s:SetWide(w + 20) end --- @@ -86,83 +92,89 @@ end -- @param boolean state -- @realm client function RADIO:ShowRadioCommands(state) - if not state then - if not IsValid(radioframe) then return end + if not state then + if not IsValid(radioframe) then + return + end - radioframe:Remove() + radioframe:Remove() - radioframe = nil + radioframe = nil - -- don't capture keys - self.Show = false + -- don't capture keys + self.Show = false - return - else - local client = LocalPlayer() - if not IsValid(client) then return end + return + else + local client = LocalPlayer() + if not IsValid(client) then + return + end - if not radioframe then - local w, h = 200, 300 + if not radioframe then + local w, h = 200, 300 - radioframe = vgui.Create("DForm") - radioframe:SetName(GetTranslation("quick_title")) - radioframe:SetSize(w, h) - radioframe:SetMouseInputEnabled(false) - radioframe:SetKeyboardInputEnabled(false) + radioframe = vgui.Create("DForm") + radioframe:SetName(GetTranslation("quick_title")) + radioframe:SetSize(w, h) + radioframe:SetMouseInputEnabled(false) + radioframe:SetKeyboardInputEnabled(false) - radioframe:CenterVertical() + radioframe:CenterVertical() - -- ASS - radioframe.ForceResize = RadioResize + -- ASS + radioframe.ForceResize = RadioResize - local commands = self.Commands - local text_nobody = GetTranslation("quick_nobody") + local commands = self.Commands + local text_nobody = GetTranslation("quick_nobody") - for key = 1, #commands do - local command = commands[key] - local dlabel = vgui.Create("DLabel", radioframe) - local id = key .. ": " - local txt = id + for key = 1, #commands do + local command = commands[key] + local dlabel = vgui.Create("DLabel", radioframe) + local id = key .. ": " + local txt = id - if command.format then - txt = txt .. GetPTranslation(command.text, {player = text_nobody}) - else - txt = txt .. GetTranslation(command.text) - end + if command.format then + txt = txt .. GetPTranslation(command.text, { player = text_nobody }) + else + txt = txt .. GetTranslation(command.text) + end - dlabel:SetText(txt) - dlabel:SetFont("TabLarge") - dlabel:SetTextColor(COLOR_WHITE) - dlabel:SizeToContents() + dlabel:SetText(txt) + dlabel:SetFont("TabLarge") + dlabel:SetTextColor(COLOR_WHITE) + dlabel:SizeToContents() - if command.format then - dlabel.target = nil - dlabel.id = id - dlabel.txt = GetTranslation(command.text) - dlabel.Think = RadioThink - end + if command.format then + dlabel.target = nil + dlabel.id = id + dlabel.txt = GetTranslation(command.text) + dlabel.Think = RadioThink + end - radioframe:AddItem(dlabel) - end + radioframe:AddItem(dlabel) + end - radioframe:ForceResize() - end + radioframe:ForceResize() + end - radioframe:MakePopup() + radioframe:MakePopup() - -- grabs input on init(), which happens in makepopup - radioframe:SetMouseInputEnabled(false) - radioframe:SetKeyboardInputEnabled(false) + -- grabs input on init(), which happens in makepopup + radioframe:SetMouseInputEnabled(false) + radioframe:SetKeyboardInputEnabled(false) - -- capture slot keys while we're open - self.Show = true + -- capture slot keys while we're open + self.Show = true - timer.Create("radiocmdshow", 3, 1, function() - if not RADIO then return end + timer.Create("radiocmdshow", 3, 1, function() + if not RADIO then + return + end - RADIO:ShowRadioCommands(false) - end) - end + RADIO:ShowRadioCommands(false) + end) + end end --- @@ -171,12 +183,14 @@ end -- @param number slotidx -- @realm client function RADIO:SendCommand(slotidx) - local c = self.Commands[slotidx] - if not c then return end + local c = self.Commands[slotidx] + if not c then + return + end - RunConsoleCommand("ttt_radio", c.cmd) + RunConsoleCommand("ttt_radio", c.cmd) - self:ShowRadioCommands(false) + self:ShowRadioCommands(false) end --- @@ -185,30 +199,35 @@ end -- @return nil|boolean Whether a custom cmd matches this situation, if true the first return argument is a command string -- @realm client function RADIO:GetTargetType() - local client = LocalPlayer() - if not IsValid(client) then return end - - local trace = client:GetEyeTrace(MASK_SHOT) - if not trace or not trace.Hit or not IsValid(trace.Entity) then return end - - --- - -- @hook - -- @realm client - local ent = hook.Run("TTT2ModifyRadioTarget", trace.Entity) or trace.Entity - - if ent:IsPlayer() and ent:IsTerror() then - if ent:GetNWBool("disguised", false) then - return "quick_disg", true - else - return ent, false - end - elseif ent:GetClass() == "prop_ragdoll" and CORPSE.GetPlayerNick(ent, "") ~= "" then - if DetectiveMode() and not CORPSE.GetFound(ent, false) then - return "quick_corpse", true - else - return ent, false - end - end + local client = LocalPlayer() + if not IsValid(client) then + return + end + + local trace = client:GetEyeTrace(MASK_SHOT) + if not trace or not trace.Hit or not IsValid(trace.Entity) then + return + end + + --- + -- @hook + -- @realm client + -- stylua: ignore + local ent = hook.Run("TTT2ModifyRadioTarget", trace.Entity) or trace.Entity + + if ent:IsPlayer() and ent:IsTerror() then + if ent:GetNWBool("disguised", false) then + return "quick_disg", true + else + return ent, false + end + elseif ent:GetClass() == "prop_ragdoll" and CORPSE.GetPlayerNick(ent, "") ~= "" then + if DetectiveMode() and not CORPSE.GetFound(ent, false) then + return "quick_corpse", true + else + return ent, false + end + end end --- @@ -217,15 +236,18 @@ end -- @return nil|string -- @realm client function RADIO.ToPrintable(target) - if isstring(target) then - return GetTranslation(target) - elseif IsValid(target) then - if target:IsPlayer() then - return target:Nick() - elseif target:GetClass() == "prop_ragdoll" then - return GetPTranslation("quick_corpse_id", {player = CORPSE.GetPlayerNick(target, "A Terrorist")}) - end - end + if isstring(target) then + return GetTranslation(target) + elseif IsValid(target) then + if target:IsPlayer() then + return target:Nick() + elseif target:GetClass() == "prop_ragdoll" then + return GetPTranslation( + "quick_corpse_id", + { player = CORPSE.GetPlayerNick(target, "A Terrorist") } + ) + end + end end --- @@ -235,23 +257,23 @@ end -- @see RADIO:GetTargetType -- @realm client function RADIO:GetTarget() - local client = LocalPlayer() + local client = LocalPlayer() - if IsValid(client) then - local current, vague = self:GetTargetType() + if IsValid(client) then + local current, vague = self:GetTargetType() - if current then - return current, vague - end + if current then + return current, vague + end - local stored = self.StoredTarget + local stored = self.StoredTarget - if stored.target and stored.t > CurTime() - 3 then - return stored.target, stored.vague - end - end + if stored.target and stored.t > CurTime() - 3 then + return stored.target, stored.vague + end + end - return "quick_nobody", true + return "quick_nobody", true end --- @@ -259,139 +281,146 @@ end -- @see RADIO:GetTargetType -- @realm client function RADIO:StoreTarget() - local current, vague = self:GetTargetType() + local current, vague = self:GetTargetType() - if current then - self.StoredTarget.target = current - self.StoredTarget.vague = vague - self.StoredTarget.t = CurTime() - end + if current then + self.StoredTarget.target = current + self.StoredTarget.vague = vague + self.StoredTarget.t = CurTime() + end end --- -- Radio commands are a console cmd instead of directly sent from RADIO, because -- this way players can bind keys to them local function RadioCommand(ply, cmd, arg) - if not IsValid(ply) then - print("ttt_radio failed, invalid player") + if not IsValid(ply) then + ErrorNoHaltWithStack("ttt_radio failed, invalid player") - return - end + return + end - --- - -- @realm client - if hook.Run("TTT2ClientRadioCommand", cmd) then - print("ttt_radio, execution prevented by a hook") + --- + -- @realm client + -- stylua: ignore + if hook.Run("TTT2ClientRadioCommand", cmd) then + Dev(1, "ttt_radio, execution prevented by a hook") - return - end + return + end - if #arg ~= 1 then - print("ttt_radio failed, too many arguments?") + if #arg ~= 1 then + ErrorNoHaltWithStack("ttt_radio failed, too many arguments?") - return - end + return + end - if RADIO.LastRadio.t > CurTime() - 0.5 then return end + if RADIO.LastRadio.t > CurTime() - 0.5 then + return + end - local msg_type = arg[1] - local target, vague = RADIO:GetTarget() - local msg_name + local msg_type = arg[1] + local target, vague = RADIO:GetTarget() + local msg_name - -- this will not be what is shown, but what is stored in case this message - -- has to be used as last words (which will always be english for now) - local text - local commands = RADIO.Commands + -- this will not be what is shown, but what is stored in case this message + -- has to be used as last words (which will always be english for now) + local text + local commands = RADIO.Commands - for i = 1, #commands do - local msg = commands[i] + for i = 1, #commands do + local msg = commands[i] - if msg.cmd ~= msg_type then continue end + if msg.cmd ~= msg_type then + continue + end - local eng = LANG.GetTranslationFromLanguage(msg.text, "en") - local _tmp = {player = RADIO.ToPrintable(target)} + local eng = LANG.GetTranslationFromLanguage(msg.text, "en") + local _tmp = { player = RADIO.ToPrintable(target) } - text = msg.format and string.Interp(eng, _tmp) or eng - msg_name = msg.text + text = msg.format and string.Interp(eng, _tmp) or eng + msg_name = msg.text - break - end + break + end - if not text then - print("ttt_radio failed, argument not valid radiocommand") + if not text then + ErrorNoHaltWithStack("ttt_radio failed, argument not valid radiocommand") - return - end + return + end - if vague then - text = util.Capitalize(text) - end + if vague then + text = util.Capitalize(text) + end - RADIO.LastRadio.t = CurTime() - RADIO.LastRadio.msg = text + RADIO.LastRadio.t = CurTime() + RADIO.LastRadio.msg = text - tagPlayer(target, msg_type) + tagPlayer(target, msg_type) - -- target is either a lang string or an entity - target = isstring(target) and target or tostring(target:EntIndex()) + -- target is either a lang string or an entity + target = isstring(target) and target or tostring(target:EntIndex()) - RunConsoleCommand("_ttt_radio_send", msg_name, target) + RunConsoleCommand("_ttt_radio_send", msg_name, target) end local function RadioComplete(cmd, arg) - local c = {} - local commands = RADIO.Commands + local c = {} + local commands = RADIO.Commands - for i = 1, #commands do - c[i] = "ttt_radio " .. commands[i].cmd - end + for i = 1, #commands do + c[i] = "ttt_radio " .. commands[i].cmd + end - return c + return c end concommand.Add("ttt_radio", RadioCommand, RadioComplete) local function RadioMsgRecv() - local sender = net.ReadEntity() - local msg = net.ReadString() - local param = net.ReadString() - - if not IsValid(sender) or not sender:IsPlayer() then return end - - GAMEMODE:PlayerSentRadioCommand(sender, msg, param) - - -- if param is a language string, translate it - -- else it's a nickname - local lang_param = LANG.GetNameParam(param) - if lang_param then - if lang_param == "quick_corpse_id" then - -- special case where nested translation is needed - param = GetPTranslation(lang_param, {player = net.ReadString()}) - else - param = GetTranslation(lang_param) - end - end - - local text = GetPTranslation(msg, {player = param}) - - -- don't want to capitalize nicks, but everything else is fair game - if lang_param then - text = util.Capitalize(text) - end - - if sender:IsDetective() then - AddDetectiveText(sender, text) - else - chat.AddText(sender, COLOR_WHITE, ": " .. text) - end + local sender = net.ReadEntity() + local msg = net.ReadString() + local param = net.ReadString() + + if not IsValid(sender) or not sender:IsPlayer() then + return + end + + GAMEMODE:PlayerSentRadioCommand(sender, msg, param) + + -- if param is a language string, translate it + -- else it's a nickname + local lang_param = LANG.GetNameParam(param) + if lang_param then + if lang_param == "quick_corpse_id" then + -- special case where nested translation is needed + param = GetPTranslation(lang_param, { player = net.ReadString() }) + else + param = GetTranslation(lang_param) + end + end + + local text = GetPTranslation(msg, { player = param }) + + -- don't want to capitalize nicks, but everything else is fair game + if lang_param then + text = util.Capitalize(text) + end + + if sender:IsDetective() then + AddDetectiveText(sender, text) + else + chat.AddText(sender, COLOR_WHITE, ": " .. text) + end end net.Receive("TTT_RadioMsg", RadioMsgRecv) local radio_gestures = { - quick_yes = ACT_GMOD_GESTURE_AGREE, - quick_no = ACT_GMOD_GESTURE_DISAGREE, - quick_see = ACT_GMOD_GESTURE_WAVE, - quick_check = ACT_SIGNAL_GROUP, - quick_suspect = ACT_SIGNAL_HALT + quick_yes = ACT_GMOD_GESTURE_AGREE, + quick_no = ACT_GMOD_GESTURE_DISAGREE, + quick_see = ACT_GMOD_GESTURE_WAVE, + quick_check = ACT_SIGNAL_GROUP, + quick_suspect = ACT_SIGNAL_HALT, } --- @@ -403,10 +432,12 @@ local radio_gestures = { -- @hook -- @realm client function GM:PlayerSentRadioCommand(ply, name, target) - local act = radio_gestures[name] - if not act then return end + local act = radio_gestures[name] + if not act then + return + end - ply:AnimPerformGesture(act) + ply:AnimPerformGesture(act) end --- @@ -416,9 +447,7 @@ end -- @return[default=nil] boolean -- @hook -- @realm client -function GM:TTT2ClientRadioCommand(cmd) - -end +function GM:TTT2ClientRadioCommand(cmd) end --- -- Hook that can be used to modify the targeted entity for the radio commands. @@ -426,6 +455,4 @@ end -- @return nil|Entity Return the entity that should be used, `nil` if kept unchanged -- @hook -- @realm client -function GM:TTT2ModifyRadioTarget(ent) - -end +function GM:TTT2ModifyRadioTarget(ent) end diff --git a/gamemodes/terrortown/gamemode/client/cl_reroll.lua b/gamemodes/terrortown/gamemode/client/cl_reroll.lua index 696fef439..b767f0ce6 100644 --- a/gamemodes/terrortown/gamemode/client/cl_reroll.lua +++ b/gamemodes/terrortown/gamemode/client/cl_reroll.lua @@ -13,45 +13,48 @@ local vgui = vgui -- @realm client -- @internal function CreateRerollMenu(parent) - local client = LocalPlayer() + local client = LocalPlayer() - local dform = vgui.Create("DForm", parent) - dform:SetName(GetTranslation("reroll_menutitle")) - dform:StretchToParent(0, 0, 0, 0) - dform:SetAutoSize(false) + local dform = vgui.Create("DForm", parent) + dform:SetName(GetTranslation("reroll_menutitle")) + dform:StretchToParent(0, 0, 0, 0) + dform:SetAutoSize(false) - local cost = GetGlobalInt("ttt2_random_shop_reroll_cost") + local cost = GetGlobalInt("ttt2_random_shop_reroll_cost") - if client:GetCredits() < cost then - dform:Help(GetParamTranslation("reroll_no_credits", {amount = cost})) + if client:GetCredits() < cost then + dform:Help(GetParamTranslation("reroll_no_credits", { amount = cost })) - return dform - end + return dform + end - local bw, bh = 100, 20 + local bw, bh = 100, 20 - local dsubmit = vgui.Create("DButton", dform) - dsubmit:SetSize(bw, bh) - dsubmit:SetDisabled(false) - dsubmit:SetText(GetTranslation("reroll_button")) + local dsubmit = vgui.Create("DButton", dform) + dsubmit:SetSize(bw, bh) + dsubmit:SetEnabled(true) + dsubmit:SetText(GetTranslation("reroll_button")) - dsubmit.DoClick = function(s) - RunConsoleCommand("ttt2_reroll_shop") - RunConsoleCommand("ttt_cl_traitorpopup") + dsubmit.DoClick = function(s) + if not shop.TryRerollShop(client) then + return + end - timer.Simple(0.1, function() - RunConsoleCommand("ttt_cl_traitorpopup") - end) - end + RunConsoleCommand("ttt_cl_traitorpopup") - dsubmit.Think = function(s) - if client:GetCredits() < cost or not GetGlobalBool("ttt2_random_shop_reroll") then - s:SetDisabled(true) - end - end + timer.Simple(0.1, function() + RunConsoleCommand("ttt_cl_traitorpopup") + end) + end - dform:AddItem(dsubmit) - dform:Help(GetParamTranslation("reroll_help", {amount = cost})) + dsubmit.Think = function(s) + if client:GetCredits() < cost or not GetGlobalBool("ttt2_random_shop_reroll") then + s:SetEnabled(false) + end + end - return dform + dform:AddItem(dsubmit) + dform:Help(GetParamTranslation("reroll_help", { amount = cost })) + + return dform end diff --git a/gamemodes/terrortown/gamemode/client/cl_scoreboard.lua b/gamemodes/terrortown/gamemode/client/cl_scoreboard.lua index 6208eb186..bcb5febd9 100644 --- a/gamemodes/terrortown/gamemode/client/cl_scoreboard.lua +++ b/gamemodes/terrortown/gamemode/client/cl_scoreboard.lua @@ -7,11 +7,13 @@ ttt_include("vgui__cl_sb_main") sboard_panel = nil local function ScoreboardRemove() - if not sboard_panel then return end + if not sboard_panel then + return + end - sboard_panel:Remove() + sboard_panel:Remove() - sboard_panel = nil + sboard_panel = nil end hook.Add("TTTLanguageChanged", "RebuildScoreboard", ScoreboardRemove) @@ -20,9 +22,9 @@ hook.Add("TTTLanguageChanged", "RebuildScoreboard", ScoreboardRemove) -- @hook -- @realm client function GM:ScoreboardCreate() - ScoreboardRemove() + ScoreboardRemove() - sboard_panel = vgui.Create("TTTScoreboard") + sboard_panel = vgui.Create("TTTScoreboard") end --- @@ -32,17 +34,17 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:ScoreboardShow -- @local function GM:ScoreboardShow() - self.ShowScoreboard = true + self.ShowScoreboard = true - if not sboard_panel then - self:ScoreboardCreate() - end + if not sboard_panel then + self:ScoreboardCreate() + end - gui.EnableScreenClicker(true) + gui.EnableScreenClicker(true) - sboard_panel:SetVisible(true) - sboard_panel:UpdateScoreboard(true) - sboard_panel:StartUpdateTimer() + sboard_panel:SetVisible(true) + sboard_panel:UpdateScoreboard(true) + sboard_panel:StartUpdateTimer() end --- @@ -52,13 +54,15 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:ScoreboardHide -- @local function GM:ScoreboardHide() - self.ShowScoreboard = false + self.ShowScoreboard = false - gui.EnableScreenClicker(false) + gui.EnableScreenClicker(false) - if not sboard_panel then return end + if not sboard_panel then + return + end - sboard_panel:SetVisible(false) + sboard_panel:SetVisible(false) end --- @@ -67,7 +71,7 @@ end -- @hook -- @realm client function GM:GetScoreboardPanel() - return sboard_panel + return sboard_panel end --- @@ -80,5 +84,5 @@ end -- @realm client -- @local function GM:HUDDrawScoreBoard() - -- replaced by panel version + -- replaced by panel version end diff --git a/gamemodes/terrortown/gamemode/client/cl_scoring.lua b/gamemodes/terrortown/gamemode/client/cl_scoring.lua index 148d1bfd6..1aee1f123 100644 --- a/gamemodes/terrortown/gamemode/client/cl_scoring.lua +++ b/gamemodes/terrortown/gamemode/client/cl_scoring.lua @@ -8,79 +8,81 @@ local vskin = vskin local TryT = LANG.TryTranslation local function CreateColumns(plys) - local teamsTbl = {} - local index = 1 - local direction = 1 + local teamsTbl = {} + local index = 1 + local direction = 1 - local colTbl = { - {}, - {}, - {} - } - local colTeamsTbl = { - {}, - {}, - {} - } + local colTbl = { + {}, + {}, + {}, + } + local colTeamsTbl = { + {}, + {}, + {}, + } - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - teamsTbl[ply.team] = teamsTbl[ply.team] or {} + teamsTbl[ply.team] = teamsTbl[ply.team] or {} - teamsTbl[ply.team][#teamsTbl[ply.team] + 1] = ply - end + teamsTbl[ply.team][#teamsTbl[ply.team] + 1] = ply + end - for i = 1, table.Count(teamsTbl) do - if index == 4 then - direction = -1 - index = 3 - end + for i = 1, table.Count(teamsTbl) do + if index == 4 then + direction = -1 + index = 3 + end - if index == 0 then - direction = 1 - index = 1 - end + if index == 0 then + direction = 1 + index = 1 + end - local plyTable, team = table.GetAndRemoveBiggestSubTable(teamsTbl) + local plyTable, team = table.GetAndRemoveBiggestSubTable(teamsTbl) - colTbl[index][#colTbl[index] + 1] = plyTable - colTeamsTbl[index][#colTeamsTbl[index] + 1] = team + colTbl[index][#colTbl[index] + 1] = plyTable + colTeamsTbl[index][#colTeamsTbl[index] + 1] = team - index = index + direction - end + index = index + direction + end - return colTbl, colTeamsTbl + return colTbl, colTeamsTbl end -- load score menu pages local function ShouldInherit(t, base) - return t.base ~= t.type + return t.base ~= t.type end local function OnInitialization(class, path, name) - class.type = name - class.base = class.base or "base_scoremenu" + class.type = name + class.base = class.base or "base_scoremenu" - MsgN("Added TTT2 score menu file: ", path, name) + Dev(1, "Added TTT2 score menu file: ", path, name) end local subMenus = classbuilder.BuildFromFolder( - "terrortown/menus/score/", - CLIENT_FILE, - "CLSCOREMENU", -- class scope - OnInitialization, -- on class loaded - true, -- should inherit - ShouldInherit -- special inheritance check + "terrortown/menus/score/", + CLIENT_FILE, + "CLSCOREMENU", -- class scope + OnInitialization, -- on class loaded + true, -- should inherit + ShouldInherit -- special inheritance check ) -- transfer subMenus into indexed table local subMenusIndexed = {} for _, subMenu in pairs(subMenus) do - if subMenu.type == "base_scoremenu" then continue end + if subMenu.type == "base_scoremenu" then + continue + end - subMenusIndexed[#subMenusIndexed + 1] = subMenu + subMenusIndexed[#subMenusIndexed + 1] = subMenu end table.SortByMember(subMenusIndexed, "priority") @@ -93,35 +95,43 @@ CLSCORE.sizes = {} -- @internal -- @realm client function CLSCORE:CalculateSizes() - self.sizes.width = 1200 - self.sizes.height = 700 - self.sizes.padding = 10 - self.sizes.paddingSmall = 0.5 * self.sizes.padding - - self.sizes.widthMenu = 50 + vskin.GetBorderSize() - - local doublePadding = 2 * self.sizes.padding - - self.sizes.heightMainArea = self.sizes.height - doublePadding - vskin.GetHeaderHeight() - vskin.GetBorderSize() - self.sizes.widthMainArea = self.sizes.width - self.sizes.widthMenu - doublePadding - - self.sizes.heightHeaderPanel = 120 - self.sizes.widthTopButton = 140 - self.sizes.heightTopButton = 30 - self.sizes.widthTopLabel = 0.5 * self.sizes.widthMainArea - self.sizes.widthTopButton - self.sizes.padding - self.sizes.heightTopButtonPanel = self.sizes.heightTopButton + doublePadding - self.sizes.heightRow = 25 - self.sizes.heightTitleRow = 30 - - self.sizes.heightButton = 45 - self.sizes.widthButton = 175 - self.sizes.heightBottomButtonPanel = self.sizes.heightButton + self.sizes.padding + 1 - self.sizes.heightContentLarge = self.sizes.heightMainArea - self.sizes.heightBottomButtonPanel - self.sizes.heightTopButtonPanel - 3 * self.sizes.padding - self.sizes.heightContent = self.sizes.heightContentLarge - self.sizes.heightHeaderPanel - self.sizes.heightMenuButton = 50 - - self.sizes.widthKarma = 50 - self.sizes.widthScore = 35 + self.sizes.width = 1200 + self.sizes.height = 700 + self.sizes.padding = 10 + self.sizes.paddingSmall = 0.5 * self.sizes.padding + + self.sizes.widthMenu = 50 + vskin.GetBorderSize() + + local doublePadding = 2 * self.sizes.padding + + self.sizes.heightMainArea = self.sizes.height + - doublePadding + - vskin.GetHeaderHeight() + - vskin.GetBorderSize() + self.sizes.widthMainArea = self.sizes.width - self.sizes.widthMenu - doublePadding + + self.sizes.heightHeaderPanel = 120 + self.sizes.widthTopButton = 140 + self.sizes.heightTopButton = 30 + self.sizes.widthTopLabel = 0.5 * self.sizes.widthMainArea + - self.sizes.widthTopButton + - self.sizes.padding + self.sizes.heightTopButtonPanel = self.sizes.heightTopButton + doublePadding + self.sizes.heightRow = 25 + self.sizes.heightTitleRow = 30 + + self.sizes.heightButton = 45 + self.sizes.widthButton = 175 + self.sizes.heightBottomButtonPanel = self.sizes.heightButton + self.sizes.padding + 1 + self.sizes.heightContentLarge = self.sizes.heightMainArea + - self.sizes.heightBottomButtonPanel + - self.sizes.heightTopButtonPanel + - 3 * self.sizes.padding + self.sizes.heightContent = self.sizes.heightContentLarge - self.sizes.heightHeaderPanel + self.sizes.heightMenuButton = 50 + + self.sizes.widthKarma = 50 + self.sizes.widthScore = 35 end --- @@ -129,94 +139,103 @@ end -- @internal -- @realm client function CLSCORE:CreatePanel() - self:CalculateSizes() - - local frame = vguihandler.GenerateFrame(self.sizes.width, self.sizes.height, "report_title", true) - - frame:SetPadding(0, 0, 0, 0) - frame:CloseButtonClickOverride(function() - self:HidePanel() - end) - - -- LEFT HAND MENU STRIP - local menuBox = vgui.Create("DPanelTTT2", frame) - menuBox:SetSize(self.sizes.widthMenu, self.sizes.heightMainArea) - menuBox:DockMargin(0, self.sizes.padding, 0, self.sizes.padding) - menuBox:Dock(LEFT) - menuBox.Paint = function(slf, w, h) - derma.SkinHook("Paint", "VerticalBorderedBoxTTT2", slf, w, h) - - return false - end - - local menuBoxGrid = vgui.Create("DIconLayout", menuBox) - menuBoxGrid:Dock(FILL) - menuBoxGrid:SetSpaceY(self.sizes.padding) - - -- RIGHT HAND MAIN AREA - local mainBox = vgui.Create("DPanelTTT2", frame) - mainBox:SetSize(self.sizes.widthMainArea, self.sizes.heightMainArea) - mainBox:DockMargin(self.sizes.padding, self.sizes.padding, self.sizes.padding, self.sizes.padding) - mainBox:Dock(RIGHT) - - local contentBox = vgui.Create("DPanelTTT2", mainBox) - contentBox:SetSize(self.sizes.widthMainArea, self.sizes.heightMainArea) - contentBox:Dock(TOP) - - local buttonArea = vgui.Create("DButtonPanelTTT2", mainBox) - buttonArea:SetSize(self.sizes.widthMainArea, self.sizes.heightBottomButtonPanel) - buttonArea:Dock(BOTTOM) - - local buttonSave = vgui.Create("DButtonTTT2", buttonArea) - buttonSave:SetText("report_save") - buttonSave:SetSize(self.sizes.widthButton, self.sizes.heightButton) - buttonSave:SetPos(0, self.sizes.padding + 1) - buttonSave.DoClick = function(btn) - self:SaveLog() - end - - local buttonClose = vgui.Create("DButtonTTT2", buttonArea) - buttonClose:SetText("close") - buttonClose:SetSize(self.sizes.widthButton, self.sizes.heightButton) - buttonClose:SetPos(self.sizes.widthMainArea - 175, self.sizes.padding + 1) - buttonClose.DoClick = function(btn) - self:HidePanel() - end - - -- POPULATE SIDEBAR PANEL - local lastActive - - for i = 1, #subMenusIndexed do - local data = subMenusIndexed[i] - - local menuButton = menuBoxGrid:Add("DSubmenuButtonTTT2") - menuButton:SetSize(self.sizes.widthMenu - 1, self.sizes.heightMenuButton) - menuButton:SetIcon(data.icon) - menuButton:SetTooltip(data.title) - menuButton.DoClick = function(slf) - contentBox:Clear() - - if isfunction(data.Populate) then - data:Populate(contentBox) - end - - slf:SetActive(true) - lastActive:SetActive(false) - lastActive = slf - end - - if i == 1 then - menuButton:SetActive(true) - lastActive = menuButton - end - end - - -- load initial menu - if isfunction(subMenusIndexed[1].Populate) then - subMenusIndexed[1]:Populate(contentBox) - end - - return frame + self:CalculateSizes() + + local frame = vguihandler.GenerateFrame(self.sizes.width, self.sizes.height, "report_title") + + frame:SetPadding(0, 0, 0, 0) + frame:CloseButtonClickOverride(function() + self:HidePanel() + end) + + frame.IsBlockingBindings = function(slf) + return self:IsBlockingBindings() + end + + -- LEFT HAND MENU STRIP + local menuBox = vgui.Create("DPanelTTT2", frame) + menuBox:SetSize(self.sizes.widthMenu, self.sizes.heightMainArea) + menuBox:DockMargin(0, self.sizes.padding, 0, self.sizes.padding) + menuBox:Dock(LEFT) + menuBox.Paint = function(slf, w, h) + derma.SkinHook("Paint", "VerticalBorderedBoxTTT2", slf, w, h) + + return false + end + + local menuBoxGrid = vgui.Create("DIconLayout", menuBox) + menuBoxGrid:Dock(FILL) + menuBoxGrid:SetSpaceY(self.sizes.padding) + + -- RIGHT HAND MAIN AREA + local mainBox = vgui.Create("DPanelTTT2", frame) + mainBox:SetSize(self.sizes.widthMainArea, self.sizes.heightMainArea) + mainBox:DockMargin( + self.sizes.padding, + self.sizes.padding, + self.sizes.padding, + self.sizes.padding + ) + mainBox:Dock(RIGHT) + + local contentBox = vgui.Create("DPanelTTT2", mainBox) + contentBox:SetSize(self.sizes.widthMainArea, self.sizes.heightMainArea) + contentBox:Dock(TOP) + + local buttonArea = vgui.Create("DButtonPanelTTT2", mainBox) + buttonArea:SetSize(self.sizes.widthMainArea, self.sizes.heightBottomButtonPanel) + buttonArea:Dock(BOTTOM) + + local buttonSave = vgui.Create("DButtonTTT2", buttonArea) + buttonSave:SetText("report_save") + buttonSave:SetSize(self.sizes.widthButton, self.sizes.heightButton) + buttonSave:SetPos(0, self.sizes.padding + 1) + buttonSave.DoClick = function(btn) + self:SaveLog() + end + + local buttonClose = vgui.Create("DButtonTTT2", buttonArea) + buttonClose:SetText("close") + buttonClose:SetSize(self.sizes.widthButton, self.sizes.heightButton) + buttonClose:SetPos(self.sizes.widthMainArea - self.sizes.widthButton, self.sizes.padding + 1) + buttonClose.DoClick = function(btn) + self:HidePanel() + end + + -- POPULATE SIDEBAR PANEL + local lastActive + + for i = 1, #subMenusIndexed do + local data = subMenusIndexed[i] + + local menuButton = menuBoxGrid:Add("DSubmenuButtonTTT2") + menuButton:SetSize(self.sizes.widthMenu - 1, self.sizes.heightMenuButton) + menuButton:SetIcon(data.icon) + menuButton:SetTooltip(data.title) + menuButton.DoClick = function(slf) + contentBox:Clear() + + if isfunction(data.Populate) then + data:Populate(contentBox) + end + + slf:SetActive(true) + lastActive:SetActive(false) + lastActive = slf + end + + if i == 1 then + menuButton:SetActive(true) + lastActive = menuButton + end + end + + -- load initial menu + if isfunction(subMenusIndexed[1].Populate) then + subMenusIndexed[1]:Populate(contentBox) + end + + return frame end --- @@ -224,20 +243,22 @@ end -- @realm client -- @internal function CLSCORE:ShowPanel() - if not IsValid(self.panel) then - self.panel = CLSCORE:CreatePanel() - end + if not IsValid(self.panel) then + self.panel = CLSCORE:CreatePanel() + end - self.panel:ShowFrame() + self.panel:ShowFrame() end --- -- Hides a @{Panel} without closing it. -- @realm client function CLSCORE:HidePanel() - if not IsValid(self.panel) then return end + if not IsValid(self.panel) then + return + end - self.panel:HideFrame() + self.panel:HideFrame() end --- @@ -245,11 +266,19 @@ end -- @return boolean Returns true if a @{Panel} is hidden -- @realm client function CLSCORE:IsPanelHidden() - if IsValid(self.panel) then - return self.panel:IsFrameHidden() - else - return true - end + if IsValid(self.panel) then + return self.panel:IsFrameHidden() + else + return true + end +end + +--- +-- Checks if this panel should block TTT2 Binds +-- @return boolean True if this frame blocks TTT2 Binds +-- @realm client +function CLSCORE:IsBlockingBindings() + return false end --- @@ -257,9 +286,9 @@ end -- @realm client -- @internal function CLSCORE:ClearPanel() - if IsValid(self.panel) then - self.panel:CloseFrame() - end + if IsValid(self.panel) then + self.panel:CloseFrame() + end end --- @@ -268,40 +297,57 @@ end -- @realm client -- @internal function CLSCORE:SaveLog() - local events = self.events + local events = self.events - if not events or #events == 0 then - chat.AddText(COLOR_WHITE, TryT("report_save_error")) + if not events or #events == 0 then + chat.AddText(COLOR_WHITE, TryT("report_save_error")) - return - end + return + end - local logdir = "terrortown/logs" + local logdir = "terrortown/logs" - if not file.IsDir(logdir, "DATA") then - file.CreateDir(logdir) - end + if not file.IsDir(logdir, "DATA") then + file.CreateDir(logdir) + end - local logname = logdir .. "/ttt_events_" .. os.time() .. ".txt" - local log = "Trouble in Terrorist Town 2 - Round Events Log\n" .. string.rep("-", 50) .. "\n" + local logname = logdir .. "/ttt_events_" .. os.time() .. ".txt" + local log = "Trouble in Terrorist Town 2 - Round Events Log\n" .. string.rep("-", 50) .. "\n" - log = log .. string.format("%s | %-15s | %s\n", " TIME ", "TYPE", "WHAT HAPPENED") .. string.rep("-", 50) .. "\n" + log = log + .. string.format("%s | %-15s | %s\n", " TIME ", "TYPE", "WHAT HAPPENED") + .. string.rep("-", 50) + .. "\n" - for i = 1, #events do - local event = events[i] + for i = 1, #events do + local event = events[i] - if event.event.roundState ~= ROUND_ACTIVE then continue end + if event.event.roundState ~= ROUND_ACTIVE then + continue + end - local time = event:GetTime() - local minutes = math.floor(time / 60) - local seconds = math.floor(time % 60) + local time = event:GetTime() + local minutes = math.floor(time / 60) + local seconds = math.floor(time % 60) - log = log .. string.format("[%02d:%02d] | %-15s | %s\n", minutes, seconds, event.type, event:Serialize() or "") - end + log = log + .. string.format( + "[%02d:%02d] | %-15s | %s\n", + minutes, + seconds, + event.type, + event:Serialize() or "" + ) + end - file.Write(logname, log) + file.Write(logname, log) - chat.AddText(COLOR_WHITE, TryT("report_save_result"), COLOR_GREEN, " /garrysmod/data/" .. logname) + chat.AddText( + COLOR_WHITE, + TryT("report_save_result"), + COLOR_GREEN, + " /garrysmod/data/" .. logname + ) end --- @@ -309,55 +355,61 @@ end -- @realm client -- @internal function CLSCORE:Init() - self.events = events.GetEventList() - self.eventsSorted = events.SortByPlayerAndEvent() - self.eventsInfoScores = eventdata.GetPlayerTotalScores() - self.eventsInfoKarma = eventdata.GetPlayerTotalKarma() - self.eventsPlayerRoles = eventdata.GetPlayerRoles() - self.eventsPlayerScores = eventdata.GetPlayerScores() - self.eventsPlayerKarma = eventdata.GetPlayerKarma() + self.events = events.GetEventList() + self.eventsSorted = events.SortByPlayerAndEvent() + self.eventsInfoScores = eventdata.GetPlayerTotalScores() + self.eventsInfoKarma = eventdata.GetPlayerTotalKarma() + self.eventsPlayerRoles = eventdata.GetPlayerRoles() + self.eventsPlayerScores = eventdata.GetPlayerScores() + self.eventsPlayerKarma = eventdata.GetPlayerKarma() - -- now iterate over the event table to get an instant access - -- to the important data - for i = 1, #self.events do - local event = self.events[i] + -- now iterate over the event table to get an instant access + -- to the important data + for i = 1, #self.events do + local event = self.events[i] - if event.type == EVENT_SELECTED then - self.eventsInfoPlayersStart = {} + if event.type == EVENT_SELECTED then + self.eventsInfoPlayersStart = {} - local plys = event.event.plys + local plys = event.event.plys - for k = 1, #plys do - local ply = plys[k] + for k = 1, #plys do + local ply = plys[k] - if ply.role == ROLE_NONE then continue end + if ply.role == ROLE_NONE then + continue + end - self.eventsInfoPlayersStart[#self.eventsInfoPlayersStart + 1] = ply - end - end + self.eventsInfoPlayersStart[#self.eventsInfoPlayersStart + 1] = ply + end + end - if event.type == EVENT_FINISH then - self.wintype = event.event.wintype - self.eventsInfoPlayersEnd = {} + if event.type == EVENT_FINISH then + self.wintype = event.event.wintype + self.eventsInfoPlayersEnd = {} - local plys = event.event.plys + local plys = event.event.plys - for k = 1, #plys do - local ply = plys[k] + for k = 1, #plys do + local ply = plys[k] - if ply.role == ROLE_NONE then continue end + if ply.role == ROLE_NONE then + continue + end - self.eventsInfoPlayersEnd[#self.eventsInfoPlayersEnd + 1] = ply - end - end - end + self.eventsInfoPlayersEnd[#self.eventsInfoPlayersEnd + 1] = ply + end + end + end - -- prepare info screen data into sorted groups - self.eventsInfoColumnDataStart, self.eventsInfoColumnTeamsStart = CreateColumns(self.eventsInfoPlayersStart) - self.eventsInfoColumnDataEnd, self.eventsInfoColumnTeamsEnd = CreateColumns(self.eventsInfoPlayersEnd) + -- prepare info screen data into sorted groups + self.eventsInfoColumnDataStart, self.eventsInfoColumnTeamsStart = + CreateColumns(self.eventsInfoPlayersStart) + self.eventsInfoColumnDataEnd, self.eventsInfoColumnTeamsEnd = + CreateColumns(self.eventsInfoPlayersEnd) - -- get data for info title - self.eventsInfoTitleText, self.eventsInfoTitleColor = self:GetWinData() + -- get data for info title + self.eventsInfoTitleText, self.eventsInfoTitleColor = self:GetWinData() end --- @@ -365,10 +417,10 @@ end -- @realm client -- @internal function CLSCORE:ReportEvents() - self:ClearPanel() + self:ClearPanel() - self:Init() - self:ShowPanel() + self:Init() + self:ShowPanel() end --- @@ -378,22 +430,22 @@ end -- @return Color The background color for the title box -- @realm client function CLSCORE:GetWinData() - local wintype = self.wintype - - -- convert default TTT win conditions - if wintype == WIN_TRAITOR then - wintype = TEAM_TRAITOR - elseif wintype == WIN_INNOCENT then - wintype = TEAM_INNOCENT - end - - if wintype == WIN_TIMELIMIT then - return "hilite_win_time", COLOR_LBROWN - elseif wintype == TEAM_NONE then -- make it a tie - return "hilite_win_tie", COLOR_LBROWN - else - return "hilite_win_" .. wintype, TEAMS[wintype].color - end + local wintype = self.wintype + + -- convert default TTT win conditions + if wintype == WIN_TRAITOR then + wintype = TEAM_TRAITOR + elseif wintype == WIN_INNOCENT then + wintype = TEAM_INNOCENT + end + + if wintype == WIN_TIMELIMIT then + return "hilite_win_time", COLOR_LBROWN + elseif wintype == TEAM_NONE then -- make it a tie + return "hilite_win_tie", COLOR_LBROWN + else + return "hilite_win_" .. wintype, TEAMS[wintype].color + end end --- @@ -401,18 +453,18 @@ end -- @realm client -- @internal function CLSCORE:Toggle() - -- if no round has happened yet, the menu can't be opened - if not self.events then return end - - if self:IsPanelHidden() then - self:ShowPanel() - else - self:HidePanel() - end + -- if no round has happened yet, the menu can't be opened + if not self.events then + return + end + + if self:IsPanelHidden() then + self:ShowPanel() + else + self:HidePanel() + end end bind.Register("toggle_clscore", function() - CLSCORE:ClearPanel() - CLSCORE:Toggle() -end, -nil, "header_bindings_ttt2", "label_bind_clscore") + CLSCORE:Toggle() +end, nil, "header_bindings_ttt2", "label_bind_clscore") diff --git a/gamemodes/terrortown/gamemode/client/cl_search.lua b/gamemodes/terrortown/gamemode/client/cl_search.lua index c1e229a1b..ef48644a9 100644 --- a/gamemodes/terrortown/gamemode/client/cl_search.lua +++ b/gamemodes/terrortown/gamemode/client/cl_search.lua @@ -2,723 +2,440 @@ -- Body search popup -- @section body_search_manager -local T = LANG.GetTranslation -local PT = LANG.GetParamTranslation -local RT = LANG.GetRawTranslation -local table = table +-- cache global functions local net = net local pairs = pairs local util = util local IsValid = IsValid local hook = hook -local is_dmg = util.BitSet - -local dtt = { - search_dmg_crush = DMG_CRUSH, - search_dmg_bullet = DMG_BULLET, - search_dmg_fall = DMG_FALL, - search_dmg_boom = DMG_BLAST, - search_dmg_club = DMG_CLUB, - search_dmg_drown = DMG_DROWN, - search_dmg_stab = DMG_SLASH, - search_dmg_burn = DMG_BURN, - search_dmg_tele = DMG_SONIC, - search_dmg_car = DMG_VEHICLE -} - --- "From his body you can tell XXX" -local function DmgToText(d) - for k, v in pairs(dtt) do - if is_dmg(d, v) then - return T(k) - end - end - - if is_dmg(d, DMG_DIRECT) then - return T("search_dmg_burn") - end - - return T("search_dmg_other") -end +-- cache materials +local materialCredits = Material("vgui/ttt/icon_credits_transparent") +local materialRoleUnknown = Material("vgui/ttt/tid/tid_big_role_not_known") +local materialPlayerIconUnknown = Material("vgui/ttt/b-draw/icon_avatar_default") --- Info type to icon mapping - --- Some icons have different appearances based on the data value. These have a --- separate table inside the TypeToMat table. - --- Those that have a lot of possible data values are defined separately, either --- as a function or a table. - -local dtm = { - bullet = DMG_BULLET, - rock = DMG_CRUSH, - splode = DMG_BLAST, - fall = DMG_FALL, - fire = DMG_BURN, - drown = DMG_DROWN -} - -local function DmgToMat(d) - for k, v in pairs(dtm) do - if is_dmg(d, v) then - return k - end - end - - if is_dmg(d, DMG_DIRECT) then - return "fire" - else - return "skull" - end -end +net.Receive("TTT2SendConfirmMsg", function() + local msgName = net.ReadString() + local sid64 = net.ReadString() + local clr = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8)) + local bool = net.ReadBool() -local function WeaponToIcon(d) - local wep = util.WeaponForClass(d) + local tbl = {} + tbl.finder = net.ReadString() + tbl.victim = net.ReadString() + tbl.role = LANG.GetTranslation(net.ReadString()) - return wep and wep.Icon or "vgui/ttt/icon_nades" -end + if bool then + tbl.team = LANG.GetTranslation(net.ReadString()) + end -local TypeToMat = { - nick = "id", - words = "halp", - c4 = "code", - dmg = DmgToMat, - wep = WeaponToIcon, - head = "head", - dtime = "time", - stime = "wtester", - lastid = "lastid", - kills = "list", - role = {} -} - --- Accessor for better fail handling -local function IconForInfoType(t, data) - local base = "vgui/ttt/icon_" - local mat = TypeToMat[t] - - if istable(mat) then - if t == "role" and not mat[data] then - TypeToMat[t][data] = roles.GetByIndex(data).icon - end - - mat = mat[data] - elseif isfunction(mat) then - mat = mat(data) - end - - if not mat then - mat = TypeToMat["nick"] - end - - -- ugly special casing for weapons, because they are more likely to be - -- customized and hence need more freedom in their icon filename - if t == "wep" or t == "role" then - return mat - else - return base .. mat - end -end + local searchUID = net.ReadUInt(16) + + -- update credits on victim table + local victimEnt = player.GetBySteamID64(tbl.victim) + if IsValid(victimEnt) and victimEnt.searchResultData then + victimEnt.searchResultData.credits = credits + end + + -- checking for bots + if sid64 == "" then + sid64 = nil + end -local pfmc_tbl = { - nick = function(search, d, raw) - search.nick.text = PT("search_nick", {player = d}) - search.nick.p = 1 - search.nick.nick = d - end, - role = function(search, d, raw) - local rd = roles.GetByIndex(d) - - search.role.text = T("search_role_" .. rd.abbr) - search.role.color = raw["role_color"] or rd.color - search.role.p = 2 - end, - team = function(search, d, raw) - search.team.text = "Team: " .. d .. "." -- will be merged with role later - end, - words = function(search, d, raw) - if d == "" then return end - - -- only append "--" if there's no ending interpunction - local final = string.match(d, "[\\.\\!\\?]$") ~= nil - - search.words.text = PT("search_words", {lastwords = d .. (final and "" or "--.")}) - end, - c4 = function(search, d, raw) - if d <= 0 then return end - - search.c4.text = PT("search_c4", {num = d}) - end, - dmg = function(search, d, raw) - search.dmg.text = DmgToText(d) - search.dmg.p = 12 - end, - wep = function(search, d, raw) - local wep = util.WeaponForClass(d) - local wname = wep and LANG.TryTranslation(wep.PrintName) - - if not wname then return end - - search.wep.text = PT("search_weapon", {weapon = wname}) - end, - head = function(search, d, raw) - search.head.p = 15 - - if not d then return end - - search.head.text = T("search_head") - end, - dtime = function(search, d, raw) - if d == 0 then return end - - local ftime = util.SimpleTime(d, "%02i:%02i") - - search.dtime.text = PT("search_time", {time = ftime}) - search.dtime.text_icon = ftime - search.dtime.p = 8 - end, - stime = function(search, d, raw) - if d <= 0 then return end - - local ftime = util.SimpleTime(d, "%02i:%02i") - - search.stime.text = PT("search_dna", {time = ftime}) - search.stime.text_icon = ftime - end, - kills = function(search, d, raw) - local num = table.Count(d) - - if num == 1 then - local vic = Entity(d[1]) - local dc = d[1] == -1 -- disconnected - - if dc or IsValid(vic) and vic:IsPlayer() then - search.kills.text = PT("search_kills1", {player = dc and "" or vic:Nick()}) - end - elseif num > 1 then - local txt = T("search_kills2") .. "\n" - local nicks = {} - - for k, idx in pairs(d) do - local vic = Entity(idx) - local dc = idx == -1 - - if dc or IsValid(vic) and vic:IsPlayer() then - nicks[#nicks + 1] = dc and "" or vic:Nick() - end - end - - local last = #nicks - - txt = txt .. table.concat(nicks, "\n", 1, last) - search.kills.text = txt - end - - search.kills.p = 30 - end, - lastid = function(search, d, raw) - if not d or d.idx == -1 then return end - - local ent = Entity(d.idx) - - if not IsValid(ent) or not ent:IsPlayer() then return end - - search.lastid.text = PT("search_eyes", {player = ent:Nick()}) - search.lastid.ply = ent - end, -} + local img = draw.GetAvatarMaterial(sid64, "medium") + + --- + -- @realm client + -- stylua: ignore + hook.Run("TTT2ConfirmedBody", tbl.finder, tbl.victim) + + MSTACK:AddColoredImagedMessage(LANG.GetParamTranslation(msgName, tbl), clr, img) + + if IsValid(SEARCHSCREEN.menuFrame) and SEARCHSCREEN.data.searchUID == searchUID then + SEARCHSCREEN:UpdateUIWhenPlayerWasConfirmed() + end +end) + +net.Receive("ttt2_credits_were_taken", function() + local searchUID = net.ReadUInt(16) + + if IsValid(SEARCHSCREEN.menuFrame) and SEARCHSCREEN.data.searchUID == searchUID then + SEARCHSCREEN:UpdateUIWhenCreditsWereTaken() + end +end) --- --- Creates a table with icons, text,... out of search_raw table --- @param table raw --- @return table a converted search data table --- @realm client -function PreprocSearch(raw) - local search = {} - - for t, d in pairs(raw) do - search[t] = { - img = nil, - text = "", - p = 10 -- sorting number - } - - if isfunction(pfmc_tbl[t]) then - pfmc_tbl[t](search, d, raw) - else - search[t] = nil - end - - -- anything matching a type but not given a text should be removed - if search[t] and search[t].text == "" then - search[t] = nil - end - - -- don't display an extra icon for the team. Merge with role desc - if search.role and search.team then - if GetGlobalBool("ttt2_confirm_team") then - search.role.text = search.role.text .. " " .. search.team.text - end - - search.team = nil - end - - -- if there's still something here, we'll be showing it, so find an icon - if search[t] then - search[t].img = IconForInfoType(t, d) - end - end - - local itms = items.GetList() - - for i = 1, #itms do - local item = itms[i] - - if not item.noCorpseSearch and raw["eq_" .. item.id] then - local highest = 0 - - for _, v in pairs(search) do - highest = math.max(highest, v.p) - end - - local text - - if item.corpseDesc then - text = RT(item.corpseDesc) or item.corpseDesc - else - if item.desc then - text = RT(item.desc) - end - - local tmpText - - if not text and item.EquipMenuData and item.EquipMenuData.desc then - tmpText = item.EquipMenuData.desc - text = RT(tmpText) - end - - if not text then - text = item.desc or tmpText or "" - end - end - - -- add item to body search if flag is set - if item.populateSearch then - search["eq_" .. item.id] = { - img = item.corpseIcon or item.material, - text = text, - p = highest + 1 - } - end - end - end - - --- - -- @realm client - hook.Run("TTTBodySearchPopulate", search, raw) - - return search -end +-- @class SEARCHSCREEN +SEARCHSCREEN = SEARCHSCREEN or {} +SEARCHSCREEN.sizes = SEARCHSCREEN.sizes or {} +SEARCHSCREEN.menuFrame = SEARCHSCREEN.menuFrame or nil +SEARCHSCREEN.data = SEARCHSCREEN.data or {} +SEARCHSCREEN.infoBoxes = {} --- --- This hook can be used to populate the body search panel. --- @param table search The search data table --- @param table raw The raw search data --- @hook +-- Calculates and caches the dimensions of the bodysearch UI. -- @realm client -function GM:TTTBodySearchPopulate(search, raw) - +function SEARCHSCREEN:CalculateSizes() + self.sizes.width = 600 + self.sizes.height = 500 + self.sizes.padding = 10 + + self.sizes.heightButton = 45 + self.sizes.widthButton = 160 + self.sizes.widthButtonCredits = 220 + self.sizes.widthButtonTakeCredits = 180 + self.sizes.widthButtonClose = 100 + self.sizes.heightBottomButtonPanel = self.sizes.heightButton + self.sizes.padding + 1 + + self.sizes.widthMainArea = self.sizes.width - 2 * self.sizes.padding + self.sizes.heightMainArea = self.sizes.height + - self.sizes.heightBottomButtonPanel + - 3 * self.sizes.padding + - vskin.GetHeaderHeight() + - vskin.GetBorderSize() + + self.sizes.widthProfileArea = 200 + + self.sizes.widthContentArea = self.sizes.width + - self.sizes.widthProfileArea + - 3 * self.sizes.padding + self.sizes.widthContentBox = self.sizes.widthContentArea - 2 * self.sizes.padding - 100 + + self.sizes.heightInfoItem = 78 end --- --- This hook can be used to modify the equipment info of a corpse. --- @param table search The search data table --- @param table equip The raw equipment table --- @hook +-- Updates the UI if the player, that is inspected right now, was confirmed. -- @realm client -function GM:TTTBodySearchEquipment(search, equip) - +function SEARCHSCREEN:UpdateUIWhenPlayerWasConfirmed() + self.buttonConfirm:SetEnabled(false) + self.buttonConfirm:SetText("search_confirmed") + self.buttonConfirm:SetIcon(nil) + + if not LocalPlayer():IsSpec() then + self.buttonReport:SetEnabled(true) + end + + -- remove hint about player needing to confirm corpse + local confirmBox = self.infoBoxes["policingrole_call_confirm"] + if IsValid(confirmBox) then + confirmBox:Remove() + end + + -- remove hint about player unable to confirm + local confirmDisabledBox = self.infoBoxes["policingrole_confirm_disabled"] + if IsValid(confirmDisabledBox) then + confirmDisabledBox:Remove() + end end --- --- This hook is called right before the killer found @{MSTACK} notification --- is added. --- @param string finder The nickname of the finder --- @param string victim The nickname of the victim --- @hook +-- Updates the UI if the player, that is inspected right now, lost their credits. -- @realm client -function GM:TTT2ConfirmedBody(finder, victim) - +function SEARCHSCREEN:UpdateUIWhenCreditsWereTaken() + -- if credits were taken, remove credit box + local creditBox = self.infoBoxes["credits"] + if IsValid(creditBox) then + creditBox:Remove() + end + + if not bodysearch.CanConfirmBody() then + self.buttonConfirm:SetText("search_confirm_forbidden") + self.buttonConfirm:SetEnabled(false) + self.buttonConfirm:SetIcon(nil) + end end --- --- Returns a function meant to override OnActivePanelChanged, which modifies --- dactive and dtext based on the search information that is associated with the --- newly selected panel -local function SearchInfoController(search, dactive, dtext) - return function(s, pold, pnew) - local t = pnew.info_type - - local data = search[t] - if not data then - ErrorNoHalt("Search: data not found", t, data, "\n") - - return - end - - -- If wrapping is on, the Label's SizeToContentsY misbehaves for - -- text that does not need wrapping. I long ago stopped wondering - -- "why" when it comes to VGUI. Apply hack, move on. - dtext:GetLabel():SetWrap(#data.text > 50) - dtext:SetText(data.text) - - local icon = data.img - - if t == "role" then - dactive:SetImage2("vgui/ttt/dynamic/icon_base_base") - dactive:SetImageOverlay("vgui/ttt/dynamic/icon_base_base_overlay") - dactive:SetRoleIconImage(icon) - - icon = "vgui/ttt/dynamic/icon_base" - else - dactive:UnloadImage2() - dactive:UnloadImageOverlay() - dactive:UnloadRoleIconImage() - end - - dactive:SetImage(icon) - dactive:SetImageColor(data.color or COLOR_WHITE) - end -end - -local function ShowSearchScreen(search_raw) - local client = LocalPlayer() - if not IsValid(client) then return end - - local m = 8 - local bw, bh = 100, 25 - local bw_large = 125 - local w, h = 425, 260 - - local rw, rh = (w - m * 2), (h - 25 - m * 2) - local rx, ry = 0, 0 - - local rows = 1 - local listw, listh = rw, (64 * rows + 6) - local listx, listy = rx, ry - - ry = ry + listh + m * 2 - rx = m - - local descw, desch = rw - m * 2, 80 - local descx, descy = rx, ry - - ry = ry + desch + m - - --local butx, buty = rx, ry - - local dframe = vgui.Create("DFrame") - dframe:SetSize(w, h) - dframe:Center() - dframe:SetTitle(T("search_title") .. " - " .. search_raw.nick or "???") - dframe:SetVisible(true) - dframe:ShowCloseButton(true) - dframe:SetMouseInputEnabled(true) - dframe:SetKeyboardInputEnabled(true) - dframe:SetDeleteOnClose(true) - - dframe.OnKeyCodePressed = util.BasicKeyHandler - - -- contents wrapper - local dcont = vgui.Create("DPanel", dframe) - dcont:SetPaintBackground(false) - dcont:SetSize(rw, rh) - dcont:SetPos(m, 25 + m) - - -- icon list - local dlist = vgui.Create("DPanelSelect", dcont) - dlist:SetPos(listx, listy) - dlist:SetSize(listw, listh) - dlist:EnableHorizontal(true) - dlist:SetSpacing(1) - dlist:SetPadding(2) - - if dlist.VBar then - dlist.VBar:Remove() - - dlist.VBar = nil - end - - -- description area - local dscroll = vgui.Create("DHorizontalScroller", dlist) - dscroll:StretchToParent(3, 3, 3, 3) - - local ddesc = vgui.Create("ColoredBox", dcont) - ddesc:SetColor(Color(50, 50, 50)) - ddesc:SetName(T("search_info")) - ddesc:SetPos(descx, descy) - ddesc:SetSize(descw, desch) - - local dactive = vgui.Create("DRoleImage", ddesc) - dactive:SetImage("vgui/ttt/icon_id") - dactive:SetPos(m, m) - dactive:SetSize(64, 64) - - local dtext = vgui.Create("ScrollLabel", ddesc) - dtext:SetSize(descw - 120, desch - m * 2) - dtext:MoveRightOf(dactive, m * 2) - dtext:AlignTop(m) - dtext:SetText("...") - - -- buttons - local by = rh - bh - m * 0.5 - - local dconfirm = vgui.Create("DButton", dcont) - dconfirm:SetPos(m, by) - dconfirm:SetSize(bw_large, bh) - dconfirm:SetText(T("search_confirm")) - - local id = search_raw.eidx + search_raw.dtime - - dconfirm.DoClick = function(s) - RunConsoleCommand("ttt_confirm_death", search_raw.eidx, id, search_raw.lrng) - end - - dconfirm:SetDisabled(client:IsSpec() or search_raw.owner and IsValid(search_raw.owner) and search_raw.owner:TTT2NETGetBool("body_found", false)) - - local dcall = vgui.Create("DButton", dcont) - dcall:SetPos(m * 2 + bw_large, by) - dcall:SetSize(bw_large, bh) - dcall:SetText(T("search_call")) - - dcall.DoClick = function(s) - client.called_corpses = client.called_corpses or {} - client.called_corpses[#client.called_corpses + 1] = search_raw.eidx - - s:SetDisabled(true) - - RunConsoleCommand("ttt_call_detective", search_raw.eidx) - end - - dcall:SetDisabled(client:IsSpec() or table.HasValue(client.called_corpses or {}, search_raw.eidx)) - - local dclose = vgui.Create("DButton", dcont) - dclose:SetPos(rw - m - bw, by) - dclose:SetSize(bw, bh) - dclose:SetText(T("close")) - - dclose.DoClick = function() - dframe:Close() - end - - -- Finalize search data, prune stuff that won't be shown etc - -- search is a table of tables that have an img and text key - local search = PreprocSearch(search_raw) - - -- Install info controller that will link up the icons to the text etc - dlist.OnActivePanelChanged = SearchInfoController(search, dactive, dtext) - - -- Create table of SimpleIcons, each standing for a piece of search - -- information. - local start_icon - - for t, info in SortedPairsByMemberValue(search, "p") do - local ic - local icon = info.img - - -- Certain items need a special icon conveying additional information - if t == "nick" then - local avply = IsValid(search_raw.owner) and search_raw.owner or nil - - ic = vgui.Create("SimpleIconAvatar", dlist) - ic:SetPlayer(avply) - - start_icon = ic - elseif t == "lastid" then - ic = vgui.Create("SimpleIconAvatar", dlist) - ic:SetPlayer(info.ply) - ic:SetAvatarSize(24) - elseif info.text_icon then - ic = vgui.Create("SimpleIconLabelled", dlist) - ic:SetIconText(info.text_icon) - elseif t == "role" then - ic = vgui.Create("SimpleRoleIcon", dlist) - - ic.Icon:SetImage2("vgui/ttt/dynamic/icon_base_base") - ic.Icon:SetImageOverlay("vgui/ttt/dynamic/icon_base_base_overlay") - ic.Icon:SetRoleIconImage(icon) - - icon = "vgui/ttt/dynamic/icon_base" - else - ic = vgui.Create("SimpleIcon", dlist) - end - - ic:SetIconSize(64) - ic:SetIcon(icon) - - if info.color then - ic:SetIconColor(info.color) - end - - ic.info_type = t - - dlist:AddPanel(ic) - dscroll:AddPanel(ic) - end - - dlist:SelectPanel(start_icon) - dframe:MakePopup() -end - -local function StoreSearchResult(search) - if not search.owner then return end - - -- if existing result was not ours, it was detective's, and should not - -- be overwritten - local ply = search.owner - - if ply.search_result and not ply.search_result.show then return end - - ply.search_result = search - - -- this is useful for targetid - local rag = Entity(search.eidx) - if not IsValid(rag) then return end - - rag.search_result = search -end - -local function bitsRequired(num) - local bits, max = 0, 1 +-- Helper function to create an info item for the search UI. +-- @param Panel parent The parent panel where it should be added to +-- @param string name A unique name to keep a reference to the item +-- @param table data The data that is added to the element +-- @param[opt] number height The height of the element +-- @return Panel The box element +-- @realm client +function SEARCHSCREEN:MakeInfoItem(parent, name, data, height) + local box = vgui.Create("DInfoItemTTT2", parent) + box:SetSize(self.sizes.widthContentBox, height or self.sizes.heightInfoItem) + box:Dock(TOP) + box:DockMargin(0, 0, 0, self.sizes.padding) + box:SetData(data) - while max <= num do - bits = bits + 1 - max = max + max - end + -- cache reference to box + self.infoBoxes[name] = box - return bits + return box end -local search = {} - -local function TTTRagdollSearch() - search = {} - - -- Basic info - search.eidx = net.ReadUInt(16) - - local owner = Entity(net.ReadUInt(8)) - - search.owner = owner - - if not IsValid(search.owner) or not search.owner:IsPlayer() or search.owner:IsTerror() then - search.owner = nil - end - - search.nick = net.ReadString() - search.role_color = net.ReadColor() - - -- Equipment - local eq = {} - - local eqAmount = net.ReadUInt(16) - - for i = 1, eqAmount do - local eqStr = net.ReadString() - - eq[i] = eqStr - search["eq_" .. eqStr] = true - end - - -- Traitor things - search.role = net.ReadUInt(ROLE_BITS) - search.team = net.ReadString() - search.c4 = net.ReadInt(bitsRequired(C4_WIRE_COUNT) + 1) - - -- Kill info - search.dmg = net.ReadUInt(30) - search.wep = net.ReadString() - search.head = net.ReadBit() == 1 - search.dtime = net.ReadInt(16) - search.stime = net.ReadInt(16) - - -- Players killed - local num_kills = net.ReadUInt(8) - if num_kills > 0 then - local t_kills = {} - - for i = 1, num_kills do - t_kills[i] = net.ReadUInt(8) - end - - search.kills = t_kills - else - search.kills = nil - end - - search.lastid = {idx = net.ReadUInt(8)} - - -- should we show a menu for this result? - search.finder = net.ReadUInt(8) - search.show = LocalPlayer():EntIndex() == search.finder - - -- - -- last words - -- - local words = net.ReadString() - search.words = (words ~= "") and words or nil - - -- long range - search.lrng = net.ReadBit() - - -- searched by detective? - search.detective_search = net.ReadBool() - - -- set search.show_sb based on detective_search or self search - search.show_sb = search.show or search.detective_search - - --- - -- @realm shared - hook.Run("TTTBodySearchEquipment", search, eq) - - if search.show then - ShowSearchScreen(search) - end - - -- cache search result in rag.search_result, e.g. useful for scoreboard - StoreSearchResult(search) - - search = nil +--- +-- Show the searchscreen with the provided data. Generates the whole UI. +-- @param table data The scene data that should be added +-- @realm client +function SEARCHSCREEN:Show(data) + local client = LocalPlayer() + + self:CalculateSizes() + + local frame = self.menuFrame + + -- IF MENU ELEMENT DOES NOT ALREADY EXIST, CREATE IT + if IsValid(frame) then + frame:ClearFrame( + nil, + nil, + { body = "search_title", params = { player = data.nick or "???" } } + ) + else + frame = vguihandler.GenerateFrame( + self.sizes.width, + self.sizes.height, + { body = "search_title", params = { player = data.nick or "???" } } + ) + end + + frame:SetPadding(self.sizes.padding, self.sizes.padding, self.sizes.padding, self.sizes.padding) + + -- any keypress closes the frame + frame:SetKeyboardInputEnabled(true) + frame.OnKeyCodePressed = util.BasicKeyHandler + + self.data = data + self.menuFrame = frame + + local playerDataKnown = data.nick ~= nil + local rd = roles.GetByIndex(data.subrole) + local clientRoleData = client:GetSubRoleData() + + local contentBox = vgui.Create("DPanelTTT2", frame) + contentBox:SetSize(self.sizes.widthMainArea, self.sizes.heightMainArea) + contentBox:Dock(TOP) + + local profileBox = vgui.Create("DProfilePanelTTT2", contentBox) + profileBox:SetSize(self.sizes.widthProfileArea, self.sizes.heightMainArea) + profileBox:Dock(LEFT) + profileBox:SetModel(data.playerModel) + profileBox:SetPlayerIcon( + playerDataKnown and draw.GetAvatarMaterial(data.sid64, "medium") + or materialPlayerIconUnknown + ) + profileBox:SetPlayerRoleColor(playerDataKnown and data.roleColor or COLOR_SLATEGRAY) + profileBox:SetPlayerRoleIcon(playerDataKnown and rd.iconMaterial or materialRoleUnknown) + profileBox:SetPlayerRoleString(playerDataKnown and rd.name or "search_team_role_unknown") + profileBox:SetPlayerTeamString(playerDataKnown and data.team or "search_team_role_unknown") + + -- ADD STATUS BOX AND ITS CONTENT + local contentAreaScroll = vgui.Create("DScrollPanelTTT2", contentBox) + contentAreaScroll:SetVerticalScrollbarEnabled(true) + contentAreaScroll:SetSize(self.sizes.widthContentArea, self.sizes.heightMainArea) + contentAreaScroll:Dock(RIGHT) + + local searchMode = bodysearch.GetInspectConfirmMode() + + -- POPULATE WITH SPECIAL INFORMATION + if client:IsSpec() then + -- a spectator can see all information, but not interact with the UI + self:MakeInfoItem(contentAreaScroll, "sepc_search_info", { + text = { + title = { + body = "search_title_spectator", + params = nil, + }, + text = { + { + body = "search_spec", + params = nil, + }, + }, + }, + colorBox = COLOR_SLATEGRAY, + }, 62) + elseif searchMode == 0 and not bodysearch.IsConfirmed(data.ragOwner) then + -- a detective can only be called AFTER a body was confirmed + self:MakeInfoItem(contentAreaScroll, "policingrole_call_confirm", { + text = { + title = { + body = "search_title_policingrole_report_confirm", + params = nil, + }, + text = { + { + body = "search_policingrole_report_confirm", + params = nil, + }, + }, + }, + colorBox = roles.DETECTIVE.ltcolor, + }, 62) + elseif + bodysearch.CanReportBody(data.ragOwner) + and not bodysearch.IsConfirmed(data.ragOwner) + and not (clientRoleData.isPolicingRole and clientRoleData.isPublicRole) + then + self:MakeInfoItem(contentAreaScroll, "policingrole_confirm_disabled", { + text = { + title = { + body = "search_title_policingrole_confirm_disabled", + params = nil, + }, + text = { + { + body = "search_policingrole_confirm_disabled_" .. tostring(searchMode), + params = nil, + }, + }, + }, + colorBox = roles.DETECTIVE.ltcolor, + }, (searchMode == 1) and 62 or 78) + end + + -- POPULATE WITH INFORMATION + for i = 1, #bodysearch.searchResultOrder do + local searchResultName = bodysearch.searchResultOrder[i] + local searchResultData = bodysearch.GetContentFromData(searchResultName, data) + + if not searchResultData then + continue + end + + self:MakeInfoItem(contentAreaScroll, searchResultName, searchResultData) + end + + if playerDataKnown then + -- additional information by other addons + local searchAdd = {} + --- + -- @realm client + -- stylua: ignore + hook.Run("TTTBodySearchPopulate", searchAdd, data) + for _, v in pairs(searchAdd) do + if istable(v.text) then + self:MakeInfoItem( + contentAreaScroll, + Material(v.img), + { title = v.title or "", text = v.text } + ) + else + self:MakeInfoItem( + contentAreaScroll, + Material(v.img), + { title = "", text = { { body = v.text } } } + ) + end + end + end + + -- BUTTONS + local buttonArea = vgui.Create("DButtonPanelTTT2", frame) + buttonArea:SetSize(self.sizes.width, self.sizes.heightBottomButtonPanel) + buttonArea:Dock(BOTTOM) + + local buttonReport = vgui.Create("DButtonTTT2", buttonArea) + buttonReport:SetText("search_call") + buttonReport:SetSize(self.sizes.widthButton, self.sizes.heightButton) + buttonReport:SetPos(0, self.sizes.padding + 1) + buttonReport.DoClick = function(btn) + bodysearch.ClientReportsCorpse(data.rag) + end + buttonReport:SetIcon(roles.DETECTIVE.iconMaterial, true, 16) + + if not bodysearch.CanReportBody(data.ragOwner) then + buttonReport:SetEnabled(false) + end + + local playerCanTakeCredits = bodysearch.CanTakeCredits(client, data.rag) + + local buttonConfirm = vgui.Create("DButtonTTT2", buttonArea) + + if client:IsSpec() then + local text = "search_confirm" + if bodysearch.IsConfirmed(data.ragOwner) then + text = "search_confirmed" + end + + buttonConfirm:SetEnabled(false) + buttonConfirm:SetText(text) + buttonConfirm:SetSize(self.sizes.widthButton, self.sizes.heightButton) + buttonConfirm:SetPos( + self.sizes.widthMainArea - self.sizes.widthButton, + self.sizes.padding + 1 + ) + elseif bodysearch.IsConfirmed(data.ragOwner) then + buttonConfirm:SetText("search_confirmed") + buttonConfirm:SetEnabled(false) + buttonConfirm:SetSize(self.sizes.widthButton, self.sizes.heightButton) + buttonConfirm:SetPos( + self.sizes.widthMainArea - self.sizes.widthButton, + self.sizes.padding + 1 + ) + elseif not bodysearch.CanConfirmBody() then + if playerCanTakeCredits then + buttonConfirm:SetText("search_take_credits") + buttonConfirm:SetTextParams({ credits = data.credits }) + buttonConfirm:SetIcon(materialCredits) + buttonConfirm:SetSize(self.sizes.widthButtonTakeCredits, self.sizes.heightButton) + buttonConfirm:SetPos( + self.sizes.widthMainArea - self.sizes.widthButtonTakeCredits, + self.sizes.padding + 1 + ) + else + buttonConfirm:SetText("search_confirm_forbidden") + buttonConfirm:SetEnabled(false) + buttonConfirm:SetSize(self.sizes.widthButton, self.sizes.heightButton) + buttonConfirm:SetPos( + self.sizes.widthMainArea - self.sizes.widthButton, + self.sizes.padding + 1 + ) + end + elseif playerCanTakeCredits then + buttonConfirm:SetText("search_confirm_credits") + buttonConfirm:SetTextParams({ credits = data.credits }) + buttonConfirm:SetSize(self.sizes.widthButtonCredits, self.sizes.heightButton) + buttonConfirm:SetPos( + self.sizes.widthMainArea - self.sizes.widthButtonCredits, + self.sizes.padding + 1 + ) + buttonConfirm:SetIcon(materialCredits) + else + buttonConfirm:SetText("search_confirm") + buttonConfirm:SetSize(self.sizes.widthButton, self.sizes.heightButton) + buttonConfirm:SetPos( + self.sizes.widthMainArea - self.sizes.widthButton, + self.sizes.padding + 1 + ) + end + buttonConfirm.DoClick = function(btn) + bodysearch.ClientConfirmsCorpse( + data.rag, + data.searchUID, + data.isLongRange, + playerCanTakeCredits + ) + + -- call these functions locally to give the user an instant feedback + -- the same function will be called again after the server processed + -- the confirmation and reported back to the clients + + if playerCanTakeCredits then + self:UpdateUIWhenCreditsWereTaken() + end + + if bodysearch.CanConfirmBody() then + self:UpdateUIWhenPlayerWasConfirmed() + end + end + + -- cache button references for external interaction + self.buttonReport = buttonReport + self.buttonConfirm = buttonConfirm end -net.Receive("TTT_RagdollSearch", TTTRagdollSearch) - -local function TTT2ConfirmMsg() - local msgName = net.ReadString() - local sid64 = net.ReadString() - local clr = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8)) - local bool = net.ReadBool() - - local tbl = {} - tbl.finder = net.ReadString() - tbl.victim = net.ReadString() - tbl.role = LANG.GetTranslation(net.ReadString()) - if bool then - tbl.team = LANG.GetTranslation(net.ReadString()) - end - - -- checking for bots - if sid64 == "" then - sid64 = nil - end - - local img = draw.GetAvatarMaterial(sid64, "medium") - - --- - -- @realm client - hook.Run("TTT2ConfirmedBody", tbl.finder, tbl.victim) +--- +-- Closes the curren searchscreen window if open +-- @realm client +function SEARCHSCREEN:Close() + if not IsValid(self.menuFrame) then + return + end - MSTACK:AddColoredImagedMessage(LANG.GetParamTranslation(msgName, tbl), clr, img) + self.menuFrame:CloseFrame() end -net.Receive("TTT2SendConfirmMsg", TTT2ConfirmMsg) diff --git a/gamemodes/terrortown/gamemode/client/cl_shop.lua b/gamemodes/terrortown/gamemode/client/cl_shop.lua new file mode 100644 index 000000000..322c1ac89 --- /dev/null +++ b/gamemodes/terrortown/gamemode/client/cl_shop.lua @@ -0,0 +1,27 @@ +--- +-- A class to handle all shop requirements +-- @author ZenBre4ker +-- @class Shop + +shop = shop or {} + +local function ResetTeambuyEquipment() + local team = net.ReadString() + + shop.ResetTeamBuy(LocalPlayer(), team) +end +net.Receive("TTT2ResetTBEq", ResetTeambuyEquipment) + +local function ReceiveTeambuyEquipment() + local equipmentName = net.ReadString() + + shop.SetEquipmentTeamBought(LocalPlayer(), equipmentName) +end +net.Receive("TTT2ReceiveTBEq", ReceiveTeambuyEquipment) + +local function ReceiveGlobalbuyEquipment() + local equipmentName = net.ReadString() + + shop.SetEquipmentGlobalBought(equipmentName) +end +net.Receive("TTT2ReceiveGBEq", ReceiveGlobalbuyEquipment) diff --git a/gamemodes/terrortown/gamemode/client/cl_shopeditor.lua b/gamemodes/terrortown/gamemode/client/cl_shopeditor.lua index 54def8eb6..ed8642885 100644 --- a/gamemodes/terrortown/gamemode/client/cl_shopeditor.lua +++ b/gamemodes/terrortown/gamemode/client/cl_shopeditor.lua @@ -1,7 +1,9 @@ --- -- @module ShopEditor -local Equipmentnew +local materialFallback = Material("vgui/ttt/missing_equip_icon") + +local equipmentCache local net = net @@ -12,185 +14,235 @@ ShopEditor.fallback = {} ShopEditor.customShopRefresh = false --- --- Returns a list of every available equipment --- @return table +-- Generates and returns a list of all equipment items and weapons on the server that can be loaded. +-- This means that they have an ID and are not a base for other weapons. This list also contains +-- disabled or ejected equipment. +-- @return table The table with all equipment -- @realm client -function ShopEditor.GetEquipmentForRoleAll() - -- need to build equipment cache? - if Equipmentnew ~= nil then - return Equipmentnew - end - - -- start with all the non-weapon goodies - local tbl = {} - - local eject = { - weapon_fists = true, - weapon_ttt_unarmed = true, - bobs_blacklisted = true - } - - --- - -- @realm client - hook.Run("TTT2ModifyShopEditorIgnoreEquip", eject) -- possibility to modify from externally - - local itms = items.GetList() - - -- find buyable items to load info from - for i = 1, #itms do - local eq = itms[i] - local name = WEPS.GetClass(eq) - - if name - and not eq.Doublicated - and not string.match(name, "base") - and not eject[name] - then - if eq.id then - tbl[#tbl + 1] = eq - else - ErrorNoHalt("[TTT2][SHOPEDITOR][ERROR] Item without id:\n") - PrintTable(eq) - end - end - end - - -- find buyable weapons to load info from - local weps = weapons.GetList() - - for i = 1, #weps do - local eq = weps[i] - local name = WEPS.GetClass(eq) - - if name - and not eq.Doublicated - and not string.match(name, "base") - and not string.match(name, "event") - and not eject[name] - then - -- @realm client - if hook.Run("TTT2RegisterWeaponID", eq) then - tbl[#tbl + 1] = eq - else - ErrorNoHalt("[TTT2][SHOPEDITOR][ERROR] Weapon without id.\n") - end - end - end - - Equipmentnew = tbl - - return Equipmentnew +function ShopEditor.GetInstalledEquipment() + local installedEquipment = {} + + local equipmentItems = items.GetList() + + -- find buyable items to load info from + for i = 1, #equipmentItems do + local equipment = equipmentItems[i] + local name = WEPS.GetClass(equipment) + + if name and not equipment.Duplicated and not string.match(name, "base") then + if equipment.id then + installedEquipment[#installedEquipment + 1] = equipment + else + ErrorNoHaltWithStack("[TTT2][SHOPEDITOR][ERROR] Item without id:\n") + + PrintTable(equipment) + end + end + end + + -- find buyable weapons to load info from + local equipmentWeapons = weapons.GetList() + + for i = 1, #equipmentWeapons do + local equipment = equipmentWeapons[i] + local name = WEPS.GetClass(equipment) + + if + name + and not equipment.Duplicated + and not string.match(name, "base") + and not string.match(name, "event") + then + -- @realm client + -- stylua: ignore + if hook.Run("TTT2RegisterWeaponID", equipment) then + installedEquipment[#installedEquipment + 1] = equipment + else + ErrorNoHaltWithStack("[TTT2][SHOPEDITOR][ERROR] Weapon without id.\n") + end + end + end + + return installedEquipment +end + +--- +-- Builts the clientside equipment cache with the cached icons; only caches weapons that are +-- deemed valid and not ejected. +-- @realm client +function ShopEditor.BuildValidEquipmentCache() + equipmentCache = {} + + local eject = { + weapon_fists = true, + weapon_ttt_unarmed = true, + bobs_blacklisted = true, + } + + --- + -- @realm client + -- stylua: ignore + hook.Run("TTT2ModifyShopEditorIgnoreEquip", eject) + + local installedEquipment = ShopEditor.GetInstalledEquipment() + + for i = 1, #installedEquipment do + local equipment = installedEquipment[i] + local name = WEPS.GetClass(equipment) + + -- caching should always happen, even if the weapon is not added to the list + if equipment.material then + equipment.iconMaterial = Material(equipment.material) + + if equipment.iconMaterial:IsError() then + -- setting fallback error material + equipment.iconMaterial = materialFallback + end + elseif equipment.model then + -- do not use fallback mat and use model instead + equipment.itemModel = equipment.model + end + + if eject[name] then + continue + end + + --if there is no sensible material and model, the equipment should probably not be + -- editable in the equipment editor or being added to the shop + if + equipment.material == "vgui/ttt/icon_id" + and equipment.model == "models/weapons/w_bugbait.mdl" + then + continue + end + + equipmentCache[#equipmentCache + 1] = equipment + end end -net.Receive("TTT2SESaveItem", function() - ShopEditor.ReadItemData() -end) +--- +-- Returns the cached equipment list that is built on every (re-)load of the game. +-- @return table The cached equipment table +-- @realm client +function ShopEditor.GetEquipmentForRoleAll() + return equipmentCache or {} +end local function shopFallbackAnsw(len) - local subrole = net.ReadUInt(ROLE_BITS) - local fallback = net.ReadString() - local roleData = roles.GetByIndex(subrole) + local subrole = net.ReadUInt(ROLE_BITS) + local fallback = net.ReadString() + local roleData = roles.GetByIndex(subrole) - ShopEditor.fallback[roleData.name] = fallback + ShopEditor.fallback[roleData.name] = fallback - -- reset everything - Equipment[subrole] = {} + -- reset everything + Equipment[subrole] = {} - local itms = items.GetList() + local equipmentItems = items.GetList() - for i = 1, #itms do - local v = itms[i] - local canBuy = v.CanBuy + for i = 1, #equipmentItems do + local v = equipmentItems[i] + local canBuy = v.CanBuy - if not canBuy then continue end + if not canBuy then + continue + end - canBuy[subrole] = nil - end + canBuy[subrole] = nil + end - local weps = weapons.GetList() + local equipmentWeapons = weapons.GetList() - for i = 1, #weps do - local v = weps[i] - local canBuy = v.CanBuy + for i = 1, #equipmentWeapons do + local v = equipmentWeapons[i] + local canBuy = v.CanBuy - if not canBuy then continue end + if not canBuy then + continue + end - canBuy[subrole] = nil - end + canBuy[subrole] = nil + end - if fallback == SHOP_UNSET then - local flbTbl = roleData.fallbackTable + if fallback == SHOP_UNSET then + local flbTbl = roleData.fallbackTable - if not flbTbl then return end + if not flbTbl then + return + end - -- set everything - for i = 1, #flbTbl do - local eq = flbTbl[i] + -- set everything + for i = 1, #flbTbl do + local eq = flbTbl[i] - local equip = not items.IsItem(eq.id) and weapons.GetStored(eq.id) or items.GetStored(eq.id) - if equip then - equip.CanBuy = equip.CanBuy or {} - equip.CanBuy[subrole] = subrole - end + local equip = not items.IsItem(eq.id) and weapons.GetStored(eq.id) + or items.GetStored(eq.id) + if equip then + equip.CanBuy = equip.CanBuy or {} + equip.CanBuy[subrole] = subrole + end - Equipment[subrole][#Equipment[subrole] + 1] = eq - end - end + Equipment[subrole][#Equipment[subrole] + 1] = eq + end + end end net.Receive("shopFallbackAnsw", shopFallbackAnsw) local function shopFallbackReset(len) - local itms = items.GetList() + local equipmentItems = items.GetList() - for i = 1, #itms do - itms[i].CanBuy = {} - end + for i = 1, #equipmentItems do + equipmentItems[i].CanBuy = {} + end - local weps = weapons.GetList() + local equipmentWeapons = weapons.GetList() - for i = 1, #weps do - weps[i].CanBuy = {} - end + for i = 1, #equipmentWeapons do + equipmentWeapons[i].CanBuy = {} + end - local rlsList = roles.GetList() + local rlsList = roles.GetList() - for i = 1, #rlsList do - local rd = rlsList[i] - local subrole = rd.index - local fb = GetGlobalString("ttt_" .. rd.abbr .. "_shop_fallback") + for i = 1, #rlsList do + local rd = rlsList[i] + local subrole = rd.index + local fb = GetGlobalString("ttt_" .. rd.abbr .. "_shop_fallback") - -- reset everything - Equipment[subrole] = {} + -- reset everything + Equipment[subrole] = {} - if fb == SHOP_UNSET then - local roleData = roles.GetByIndex(subrole) - local flbTbl = roleData.fallbackTable + if fb == SHOP_UNSET then + local roleData = roles.GetByIndex(subrole) + local flbTbl = roleData.fallbackTable - if not flbTbl then continue end + if not flbTbl then + continue + end - -- set everything - for k = 1, #flbTbl do - local eq = flbTbl[k] + -- set everything + for k = 1, #flbTbl do + local eq = flbTbl[k] - local equip = not items.IsItem(eq.id) and weapons.GetStored(eq.id) or items.GetStored(eq.id) - if equip then - equip.CanBuy = equip.CanBuy or {} - equip.CanBuy[subrole] = subrole - end + local equip = not items.IsItem(eq.id) and weapons.GetStored(eq.id) + or items.GetStored(eq.id) + if equip then + equip.CanBuy = equip.CanBuy or {} + equip.CanBuy[subrole] = subrole + end - Equipment[subrole][#Equipment[subrole] + 1] = eq - end - end - end + Equipment[subrole][#Equipment[subrole] + 1] = eq + end + end + end end net.Receive("shopFallbackReset", shopFallbackReset) local function shopFallbackRefresh() - -- Refresh F1 menu to show actual custom shop after ShopFallbackRefresh - if ShopEditor.customShopRefresh then - ShopEditor.customShopRefresh = false - vguihandler.Rebuild() - end + -- Refresh F1 menu to show actual custom shop after ShopFallbackRefresh + if ShopEditor.customShopRefresh then + ShopEditor.customShopRefresh = false + vguihandler.Rebuild() + end end net.Receive("shopFallbackRefresh", shopFallbackRefresh) @@ -199,6 +251,4 @@ net.Receive("shopFallbackRefresh", shopFallbackRefresh) -- @param table blockedWeapons A hashtable with the classnames of blocked weapons -- @hook -- @realm client -function GM:TTT2ModifyShopEditorIgnoreEquip(blockedWeapons) - -end +function GM:TTT2ModifyShopEditorIgnoreEquip(blockedWeapons) end diff --git a/gamemodes/terrortown/gamemode/client/cl_status.lua b/gamemodes/terrortown/gamemode/client/cl_status.lua index b9194c0cc..6898ca3e4 100644 --- a/gamemodes/terrortown/gamemode/client/cl_status.lua +++ b/gamemodes/terrortown/gamemode/client/cl_status.lua @@ -11,20 +11,23 @@ STATUS.active = {} -- @param string id The index of the new status -- @param table data -- @return boolean whether the creation was successfully +-- @note structure of data = {Material|table hud, string type, string|table name, string sidebarDescription, function DrawInfo()} +-- Elements with the additional option of a table can be used to switch between different states +-- of a given status @{STATUS:SetActiveIcon}. -- @realm client function STATUS:RegisterStatus(id, data) - if STATUS.registered[id] ~= nil then -- name is not unique - return false - end + if STATUS.registered[id] ~= nil then -- name is not unique + return false + end - -- support single and multible icons per status effect - if data.hud.GetTexture then - data.hud = {data.hud} - end + -- support single and multible icons per status effect + if data.hud.GetTexture then + data.hud = { data.hud } + end - STATUS.registered[id] = data + STATUS.registered[id] = data - return true + return true end --- @@ -33,11 +36,13 @@ end -- @param[default=1] number active_icon The numeric id of a specific status icon -- @realm client function STATUS:AddStatus(id, active_icon) - if STATUS.registered[id] == nil then return end + if STATUS.registered[id] == nil then + return + end - STATUS.active[id] = table.Copy(STATUS.registered[id]) + STATUS.active[id] = table.Copy(STATUS.registered[id]) - self:SetActiveIcon(id, active_icon or 1) + self:SetActiveIcon(id, active_icon or 1) end --- @@ -49,23 +54,27 @@ end -- @param[default=1] number active_icon The numeric id of a specific status icon -- @realm client function STATUS:AddTimedStatus(id, duration, showDuration, active_icon) - if self.registered[id] == nil or duration == 0 then return end + if self.registered[id] == nil or duration == 0 then + return + end - self:AddStatus(id, active_icon) + self:AddStatus(id, active_icon) - self.active[id].displaytime = CurTime() + duration + self.active[id].displaytime = CurTime() + duration - timer.Create(id, duration, 1, function() - if not self then return end + timer.Create(id, duration, 1, function() + if not self then + return + end - self:RemoveStatus(id) - end) + self:RemoveStatus(id) + end) - if showDuration then - self.active[id].DrawInfo = function(slf) - return tostring(math.ceil(math.max(0, slf.displaytime - CurTime()))) - end - end + if showDuration then + self.active[id].DrawInfo = function(slf) + return tostring(math.ceil(math.max(0, slf.displaytime - CurTime()))) + end + end end --- @@ -74,15 +83,17 @@ end -- @param[default=1] number active_icon The numeric id of a specific status icon -- @realm client function STATUS:SetActiveIcon(id, active_icon) - if STATUS.active[id] == nil then return end + if STATUS.active[id] == nil then + return + end - local max_amount = self.registered[id].hud.GetTexture and 1 or #STATUS.registered[id].hud + local max_amount = self.registered[id].hud.GetTexture and 1 or #STATUS.registered[id].hud - if not active_icon or active_icon < 1 or active_icon > max_amount then - active_icon = 1 - end + if not active_icon or active_icon < 1 or active_icon > max_amount then + active_icon = 1 + end - STATUS.active[id].active_icon = active_icon + STATUS.active[id].active_icon = active_icon end --- @@ -91,7 +102,7 @@ end -- @return boolean Whether the status is registered -- @realm client function STATUS:Registered(id) - return (STATUS.registered[id] ~= nil) and true or false + return (STATUS.registered[id] ~= nil) and true or false end --- @@ -100,7 +111,7 @@ end -- @return boolean Whether the status is active -- @realm client function STATUS:Active(id) - return (STATUS.active[id] ~= nil) and true or false + return (STATUS.active[id] ~= nil) and true or false end --- @@ -108,13 +119,15 @@ end -- @param string id The id of the registered @{STATUS} -- @realm client function STATUS:RemoveStatus(id) - if STATUS.active[id] == nil then return end + if STATUS.active[id] == nil then + return + end - STATUS.active[id] = nil + STATUS.active[id] = nil - if timer.Exists(id) then - timer.Remove(id) - end + if timer.Exists(id) then + timer.Remove(id) + end end --- @@ -122,27 +135,27 @@ end -- @see STATUS:RemoveStatus -- @realm client function STATUS:RemoveAll() - for i in pairs(STATUS.active) do - STATUS:RemoveStatus(i) - end + for i in pairs(STATUS.active) do + STATUS:RemoveStatus(i) + end end net.Receive("ttt2_status_effect_add", function() - STATUS:AddStatus(net.ReadString(), net.ReadUInt(8)) + STATUS:AddStatus(net.ReadString(), net.ReadUInt(8)) end) net.Receive("ttt2_status_effect_set_id", function() - STATUS:SetActiveIcon(net.ReadString(), net.ReadUInt(8)) + STATUS:SetActiveIcon(net.ReadString(), net.ReadUInt(8)) end) net.Receive("ttt2_status_effect_add_timed", function() - STATUS:AddTimedStatus(net.ReadString(), net.ReadUInt(32), net.ReadBool(), net.ReadUInt(8)) + STATUS:AddTimedStatus(net.ReadString(), net.ReadUInt(32), net.ReadBool(), net.ReadUInt(8)) end) net.Receive("ttt2_status_effect_remove", function() - STATUS:RemoveStatus(net.ReadString()) + STATUS:RemoveStatus(net.ReadString()) end) net.Receive("ttt2_status_effect_remove_all", function() - STATUS:RemoveAll() + STATUS:RemoveAll() end) diff --git a/gamemodes/terrortown/gamemode/client/cl_target_data.lua b/gamemodes/terrortown/gamemode/client/cl_target_data.lua index 164faf92b..a6e8c47f2 100644 --- a/gamemodes/terrortown/gamemode/client/cl_target_data.lua +++ b/gamemodes/terrortown/gamemode/client/cl_target_data.lua @@ -8,50 +8,49 @@ TARGET_DATA = {} --- -- Initializes the @{TARGET_DATA} object --- @param entity ent The focused Entity --- @param entity unchangedEnt The original focused Entity if focus was changed by the hook --- @param number distance The distance to the focused Entity --- @return @{TARGET_DATA} The object to be used in the hook +-- @param Entity ent The focused Entity +-- @param Entity unchangedEnt The original focused entity if focus was changed by the hook +-- @param number distance The distance to the focused entity +-- @return TARGET_DATA The object to be used in the hook -- @internal -- @realm client function TARGET_DATA:Initialize(ent, unchangedEnt, distance) - -- combine data into a table to read them inside a hook - local data = { - ent = ent, - unchangedEnt = unchangedEnt, - distance = distance - } - - -- preset a table of values that can be changed with a hook - local params = { - drawInfo = nil, - drawOutline = nil, - outlineColor = COLOR_WHITE, - displayInfo = { - key = nil, - icon = {}, - title = { - icons = {}, - text = "", - color = COLOR_WHITE - }, - subtitle = { - icons = {}, - text = "", - color = COLOR_LLGRAY - }, - desc = {} - }, - refPosition = { - x = math.Round(0.5 * ScrW() / appearance.GetGlobalScale(), 0), - y = math.Round(0.5 * ScrH() / appearance.GetGlobalScale(), 0) + 42 - } - } - - return TARGET_DATA:BindTarget(data, params) + -- combine data into a table to read them inside a hook + local data = { + ent = ent, + unchangedEnt = unchangedEnt, + distance = distance, + } + + -- preset a table of values that can be changed with a hook + local params = { + drawInfo = nil, + drawOutline = nil, + outlineColor = COLOR_WHITE, + displayInfo = { + key = nil, + icon = {}, + title = { + icons = {}, + text = "", + color = COLOR_WHITE, + }, + subtitle = { + icons = {}, + text = "", + color = COLOR_LLGRAY, + }, + desc = {}, + }, + refPosition = { + x = math.Round(0.5 * ScrW() / appearance.GetGlobalScale(), 0), + y = math.Round(0.5 * ScrH() / appearance.GetGlobalScale(), 0) + 42, + }, + } + + return TARGET_DATA:BindTarget(data, params) end - --- -- Binds two target data tables to the @{TARGET_DATA} object -- @param table data The data table about the focused entity @@ -60,10 +59,10 @@ end -- @internal -- @realm client function TARGET_DATA:BindTarget(data, params) - self.data = data - self.params = params + self.data = data + self.params = params - return self + return self end --- @@ -71,7 +70,7 @@ end -- @return Entity The focused entity -- @realm client function TARGET_DATA:GetEntity() - return self.data.ent + return self.data.ent end --- @@ -79,7 +78,7 @@ end -- @return nil|Entity The focused entity, nil if it wasn't modified -- @realm client function TARGET_DATA:GetUnchangedEntity() - return self.data.unchangedEnt + return self.data.unchangedEnt end --- @@ -87,7 +86,7 @@ end -- @return number The distance to the focused entity -- @realm client function TARGET_DATA:GetEntityDistance() - return self.data.distance + return self.data.distance end --- @@ -96,11 +95,13 @@ end -- @param[default=true] boolean enb_outline A boolean defining the outline state -- @realm client function TARGET_DATA:EnableOutline(enb_outline) - -- only set if not already set to false - if self.params.drawOutline == false then return end + -- only set if not already set to false + if self.params.drawOutline == false then + return + end - -- set to true if true or nil, but keep false - self.params.drawOutline = (enb_outline == nil) and true or enb_outline + -- set to true if true or nil, but keep false + self.params.drawOutline = (enb_outline == nil) and true or enb_outline end --- @@ -109,11 +110,13 @@ end -- @param[default=true] boolean enb_text A boolean defining the text state -- @realm client function TARGET_DATA:EnableText(enb_text) - -- only set if not already set to false - if self.params.drawInfo == false then return end + -- only set if not already set to false + if self.params.drawInfo == false then + return + end - -- set to true if true or nil, but keep false - self.params.drawInfo = (enb_text == nil) and true or enb_text + -- set to true if true or nil, but keep false + self.params.drawInfo = (enb_text == nil) and true or enb_text end --- @@ -121,16 +124,16 @@ end -- @param[default=Color(255, 255, 255, 255)] Color The outline color -- @realm client function TARGET_DATA:SetOutlineColor(color) - self.params.outlineColor = IsColor(color) and color or COLOR_WHITE + self.params.outlineColor = IsColor(color) and color or COLOR_WHITE end --- -- Returns the reference position of the targetID HUD element. The reference position -- is used to position the element on screen. --- @return number, number Reference position x, reference position y +-- @return number,number Reference position x, reference position y -- @realm client function TARGET_DATA:GetRefPosition() - return self.params.refPosition.x, self.params.refPosition.y + return self.params.refPosition.x, self.params.refPosition.y end --- @@ -141,10 +144,10 @@ end -- @param number ref_y The reference position on the y axis -- @realm client function TARGET_DATA:SetRefPosition(ref_x, ref_y) - self.params.refPosition = { - x = ref_x or self.params.refPosition.x, - y = ref_y or self.params.refPosition.y - } + self.params.refPosition = { + x = ref_x or self.params.refPosition.x, + y = ref_y or self.params.refPosition.y, + } end --- @@ -152,7 +155,7 @@ end -- @param string key The key to be displayed, this can be any string. E.g. "E". -- @realm client function TARGET_DATA:SetKey(key) - self.params.displayInfo.key = key + self.params.displayInfo.key = key end --- @@ -160,7 +163,7 @@ end -- @param string key_binding The key binding like "+use". -- @realm client function TARGET_DATA:SetKeyBinding(key_binding) - self.params.displayInfo.key = input.GetKeyCode(input.LookupBinding(key_binding)) + self.params.displayInfo.key = input.GetKeyCode(input.LookupBinding(key_binding)) end --- @@ -170,14 +173,14 @@ end -- @return number The amount of icons that are currently in the table -- @realm client function TARGET_DATA:AddIcon(material, color) - local amount = #self.params.displayInfo.icon + 1 + local amount = #self.params.displayInfo.icon + 1 - self.params.displayInfo.icon[amount] = { - material = material, - color = IsColor(color) and color or COLOR_WHITE - } + self.params.displayInfo.icon[amount] = { + material = material, + color = IsColor(color) and color or COLOR_WHITE, + } - return amount + return amount end --- @@ -187,11 +190,11 @@ end -- @param[opt] table inline_icons A table of materials that should be rendered in front of the text -- @realm client function TARGET_DATA:SetTitle(text, color, inline_icons) - self.params.displayInfo.title = { - text = text or "", - color = IsColor(color) and color or COLOR_WHITE, - icons = inline_icons or {} - } + self.params.displayInfo.title = { + text = text or "", + color = IsColor(color) and color or COLOR_WHITE, + icons = inline_icons or {}, + } end --- @@ -201,11 +204,11 @@ end -- @param[opt] table inline_icons A table of materials that should be rendered in front of the text -- @realm client function TARGET_DATA:SetSubtitle(text, color, inline_icons) - self.params.displayInfo.subtitle = { - text = text or "", - color = IsColor(color) and color or COLOR_LLGRAY, - icons = inline_icons or {} - } + self.params.displayInfo.subtitle = { + text = text or "", + color = IsColor(color) and color or COLOR_LLGRAY, + icons = inline_icons or {}, + } end --- @@ -216,15 +219,15 @@ end -- @return number The amount of description lines that are currently in the table -- @realm client function TARGET_DATA:AddDescriptionLine(text, color, inline_icons) - local amount = #self.params.displayInfo.desc + 1 + local amount = #self.params.displayInfo.desc + 1 - self.params.displayInfo.desc[amount] = { - text = text or "", - color = IsColor(color) and color or COLOR_WHITE, - icons = inline_icons or {} - } + self.params.displayInfo.desc[amount] = { + text = text or "", + color = IsColor(color) and color or COLOR_WHITE, + icons = inline_icons or {}, + } - return amount + return amount end --- @@ -232,7 +235,7 @@ end -- @return boolean True if a title is set -- @realm client function TARGET_DATA:HasTitle() - return self.params.displayInfo.title and self.params.displayInfo.title ~= "" + return self.params.displayInfo.title and self.params.displayInfo.title ~= "" end --- @@ -240,7 +243,7 @@ end -- @return boolean True if a subtitle is set -- @realm client function TARGET_DATA:HasSubtitle() - return self.params.displayInfo.subtitle and self.params.displayInfo.subtitle ~= "" + return self.params.displayInfo.subtitle and self.params.displayInfo.subtitle ~= "" end --- @@ -248,7 +251,7 @@ end -- @return number Amount of existing description lines -- @realm client function TARGET_DATA:GetAmountDescriptionLines() - return #self.params.displayInfo.desc + return #self.params.displayInfo.desc end --- @@ -256,7 +259,7 @@ end -- @return boolean True if a key is set -- @realm client function TARGET_DATA:HasKey() - return self.params.displayInfo.key ~= nil + return self.params.displayInfo.key ~= nil end --- @@ -264,13 +267,13 @@ end -- @return number Amount of existing icons -- @realm client function TARGET_DATA:GetAmountIcons() - return #self.params.displayInfo.icon + return #self.params.displayInfo.icon end --- -- Returns the raw data tables of the targetID element to me modified by experienced users --- @return table, table The table of the entity data, the table of the targetID element parameters +-- @return table,table The table of the entity data, the table of the targetID element parameters -- @realm client function TARGET_DATA:GetRaw() - return self.data, self.params + return self.data, self.params end diff --git a/gamemodes/terrortown/gamemode/client/cl_targetid.lua b/gamemodes/terrortown/gamemode/client/cl_targetid.lua index ebfc3ee95..7036f49bf 100644 --- a/gamemodes/terrortown/gamemode/client/cl_targetid.lua +++ b/gamemodes/terrortown/gamemode/client/cl_targetid.lua @@ -6,9 +6,7 @@ local util = util local render = render local surface = surface local draw = draw -local GetPlayers = player.GetAll local math = math -local table = table local IsValid = IsValid local hook = hook local targetid = targetid @@ -20,109 +18,112 @@ targetid.Initialize() --- -- @realm client +-- stylua: ignore local cvMinimalisticTid = CreateConVar("ttt_minimal_targetid", "0", FCVAR_ARCHIVE) --- -- @realm client +-- stylua: ignore local cvDrawHalo = CreateConVar("ttt_entity_draw_halo", "1", FCVAR_ARCHIVE) --- -- @realm client -local cvEnableSpectatorsoutline = CreateConVar("ttt2_cvEnableSpectatorsoutline", "1", {FCVAR_ARCHIVE, FCVAR_USERINFO}) +-- stylua: ignore +local cvEnableSpectatorsoutline = CreateConVar("ttt2_enable_spectatorsoutline", "1", { FCVAR_ARCHIVE, FCVAR_USERINFO }) --- -- @realm client -local cvEnableOverheadicons = CreateConVar("ttt2_cvEnableOverheadicons", "1", {FCVAR_ARCHIVE, FCVAR_USERINFO}) - -surface.CreateAdvancedFont("TargetID_Key", {font = "Trebuchet24", size = 26, weight = 900}) -surface.CreateAdvancedFont("TargetID_Title", {font = "Trebuchet24", size = 20, weight = 900}) -surface.CreateAdvancedFont("TargetID_Subtitle", {font = "Trebuchet24", size = 17, weight = 300}) -surface.CreateAdvancedFont("TargetID_Description", {font = "Trebuchet24", size = 15, weight = 300}) +-- stylua: ignore +local cvEnableOverheadicons = CreateConVar("ttt2_enable_overheadicons", "1", { FCVAR_ARCHIVE, FCVAR_USERINFO }) + +surface.CreateAdvancedFont( + "TargetID_Key", + { font = "Tahoma", size = 26, weight = 900, extended = true } +) +surface.CreateAdvancedFont( + "TargetID_Title", + { font = "Tahoma", size = 20, weight = 900, extended = true } +) +surface.CreateAdvancedFont( + "TargetID_Subtitle", + { font = "Tahoma", size = 17, weight = 300, extended = true } +) +surface.CreateAdvancedFont( + "TargetID_Description", + { font = "Tahoma", size = 15, weight = 300, extended = true } +) -- keep this font for compatibility reasons -surface.CreateFont("TargetIDSmall2", {font = "TargetID", size = 16, weight = 1000}) +surface.CreateFont("TargetIDSmall2", { font = "TargetID", size = 16, weight = 1000 }) -- cache colors -local colorBlacktrans = Color(0, 0, 0, 180) local colorKeyBack = Color(0, 0, 0, 150) +local colorPropSpecLabel = Color(220, 200, 0, 120) -- cached materials for overhead icons and outlines local materialPropspecOutline = Material("models/props_combine/portalball001_sheet") local materialBase = Material("vgui/ttt/dynamic/sprite_base") local materialBaseOverlay = Material("vgui/ttt/dynamic/sprite_base_overlay") ---- --- Returns the localized ClassHint table --- Access for servers to display hints using their own HUD/UI. --- @return table --- @hook --- @realm client -function GM:GetClassHints() - return ClassHint -end +local scaleFactorOverHeadIcon = 10 +local sizeBaseOverHeadIcon = 10 +local offsetOverHeadIcon = sizeBaseOverHeadIcon + 4 ---- --- Sets an index of the localized ClassHint table --- @note Basic access for servers to add/modify hints. They override hints stored on --- the entities themselves. --- @param string cls --- @param table hint --- @hook --- @realm client --- @local -function GM:AddClassHint(cls, hint) - ClassHint[cls] = table.Copy(hint) -end +local scaleOverHeadIcon = 1 / scaleFactorOverHeadIcon +local sizeOverHeadIcon = scaleFactorOverHeadIcon * sizeBaseOverHeadIcon +local shiftOverHeadIcon = 0.5 * sizeBaseOverHeadIcon +local xShiftIconOverHeadIcon = 0.15 * sizeOverHeadIcon +local yShiftIconOverHeadIcon = 0.1 * sizeOverHeadIcon +local sizeIconOverHeadIcon = 0.7 * sizeOverHeadIcon --- -- Function that handles the drawing of the overhead roleicons, it does not check whether -- the icon should be drawn or not, that has to be handled prior to calling this function +-- @param Player client The client entity -- @param Player ply The player to receive an overhead icon --- @param Material ricon --- @param Color rcolor +-- @param Material iconRole The role icon that will be drawn +-- @param Color colorRole The role color for the background -- @realm client -function DrawOverheadRoleIcon(ply, ricon, rcolor) - local client = LocalPlayer() - if ply == client then return end - - -- get position of player - local pos = ply:GetPos() - pos.z = pos.z + 80 - - -- get eye angle of player - local ea = ply:EyeAngles() - ea.pitch = 0 - - -- shift overheadicon in eyedirection of player - local shift = Vector(6, 0, 0) - shift:Rotate(ea) - pos:Add(shift) - - local dir = client:GetForward() * -1 - - -- start linear filter - render.PushFilterMag(TEXFILTER.LINEAR) - render.PushFilterMin(TEXFILTER.LINEAR) - - -- draw color - render.SetMaterial(materialBase) - render.DrawQuadEasy(pos, dir, 10, 10, rcolor, 180) - - -- draw border overlay - render.SetMaterial(materialBaseOverlay) - render.DrawQuadEasy(pos, dir, 10, 10, COLOR_WHITE, 180) - - -- draw shadow - render.SetMaterial(ricon) - render.DrawQuadEasy(Vector(pos.x, pos.y, pos.z + 0.2), dir, 8, 8, colorBlacktrans, 180) +function DrawOverheadRoleIcon(client, ply, iconRole, colorRole) + local ang = client:EyeAngles() + local pos = ply:GetPos() + ply:GetHeightVector() + pos.z = pos.z + offsetOverHeadIcon + + local shift = Vector(0, shiftOverHeadIcon, 0) + shift:Rotate(ang) + pos:Add(shift) + + ang.pitch = 90 + ang:RotateAroundAxis(ang:Up(), 90) + ang:RotateAroundAxis(ang:Right(), 180) + + cam.Start3D2D(pos, ang, scaleOverHeadIcon) + draw.FilteredTexture(0, 0, sizeOverHeadIcon, sizeOverHeadIcon, materialBase, 255, colorRole) + draw.FilteredTexture( + 0, + 0, + sizeOverHeadIcon, + sizeOverHeadIcon, + materialBaseOverlay, + 255, + COLOR_WHITE + ) + draw.FilteredShadowedTexture( + xShiftIconOverHeadIcon, + yShiftIconOverHeadIcon, + sizeIconOverHeadIcon, + sizeIconOverHeadIcon, + iconRole, + 255, + util.GetDefaultColor(colorRole) + ) + cam.End3D2D() +end - -- draw icon - render.SetMaterial(ricon) - render.DrawQuadEasy(Vector(pos.x, pos.y, pos.z + 0.5), dir, 8, 8, util.GetDefaultColor(rcolor), 180) +local function DistanceSorter(a, b) + local clientPos = LocalPlayer():GetPos() - -- stop linear filter - render.PopFilterMag() - render.PopFilterMin() + return clientPos:Distance(a.ply:GetPos()) > clientPos:Distance(b.ply:GetPos()) end --- @@ -138,99 +139,133 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PostDrawTranslucentRenderables -- @local function GM:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox) - local client = LocalPlayer() - local plys = GetPlayers() - - if client:Team() == TEAM_SPEC and cvEnableSpectatorsoutline:GetBool() then - cam.Start3D(EyePos(), EyeAngles()) - - for i = 1, #plys do - local ply = plys[i] - local tgt = ply:GetObserverTarget() - - if IsValid(tgt) and tgt:GetNWEntity("spec_owner", nil) == ply then - render.MaterialOverride(materialPropspecOutline) - render.SuppressEngineLighting(true) - render.SetColorModulation(1, 0.5, 0) - - tgt:SetModelScale(1.05, 0) - tgt:DrawModel() - - render.SetColorModulation(1, 1, 1) - render.SuppressEngineLighting(false) - render.MaterialOverride(nil) - end - end - - cam.End3D() - end - - -- OVERHEAD ICONS - if not cvEnableOverheadicons:GetBool() then return end - - for i = 1, #plys do - local ply = plys[i] - local rd = ply:GetSubRoleData() - - local shouldDrawDefault = ply:IsActive() - and ply:HasRole() - and (not client:IsActive() or ply:IsInTeam(client) or rd.isPublicRole) - and not rd.avoidTeamIcons - - --- - -- @realm client - local shouldDraw, material, color = hook.Run("TTT2ModifyOverheadIcon", ply, shouldDrawDefault) - - if shouldDraw == false - or not shouldDrawDefault and not (material and color) - then continue end - - DrawOverheadRoleIcon(ply, material or rd.iconMaterial, color or ply:GetRoleColor()) - end + local client = LocalPlayer() + local clientTarget = client:GetObserverTarget() + local clientObsMode = client:GetObserverMode() + local plys = player.GetAll() + + if client:Team() == TEAM_SPEC and cvEnableSpectatorsoutline:GetBool() then + cam.Start3D(EyePos(), EyeAngles()) + + for i = 1, #plys do + local ply = plys[i] + local tgt = ply:GetObserverTarget() + + if IsValid(tgt) and tgt:GetNWEntity("spec_owner", nil) == ply then + render.MaterialOverride(materialPropspecOutline) + render.SuppressEngineLighting(true) + render.SetColorModulation(1, 0.5, 0) + + tgt:SetModelScale(1.05, 0) + tgt:DrawModel() + + render.SetColorModulation(1, 1, 1) + render.SuppressEngineLighting(false) + render.MaterialOverride(nil) + end + end + + cam.End3D() + end + + -- OVERHEAD ICONS + if not cvEnableOverheadicons:GetBool() then + return + end + + local plysWithIcon = {} + + for i = 1, #plys do + local ply = plys[i] + local roleData = ply:GetSubRoleData() + + local shouldDrawDefault = ply:IsActive() + and ply:HasRole() + and (not client:IsActive() or ply:IsInTeam(client) or roleData.isPublicRole) + and not roleData.avoidTeamIcons + and ply ~= client + and not ( + clientTarget == ply + and IsPlayer(clientTarget) + and clientObsMode == OBS_MODE_IN_EYE + ) + + --- + -- @realm client + -- stylua: ignore + local shouldDraw, material, color = hook.Run("TTT2ModifyOverheadIcon", ply, shouldDrawDefault) + + if shouldDraw == false or not shouldDrawDefault then + continue + end + + plysWithIcon[#plysWithIcon + 1] = { + ply = ply, + material = material or roleData.iconMaterial, + color = color or ply:GetRoleColor(), + } + end + + table.sort(plysWithIcon, DistanceSorter) + + for i = 1, #plysWithIcon do + local plyWithIcon = plysWithIcon[i] + + DrawOverheadRoleIcon(client, plyWithIcon.ply, plyWithIcon.material, plyWithIcon.color) + end end --- -- Spectator labels local function DrawPropSpecLabels(client) - if not client:IsSpec() and GetRoundState() ~= ROUND_POST then return end - - surface.SetFont("TabLarge") - - local tgt, scrpos, text - local w = 0 - local plys = player.GetAll() - - for i = 1, #plys do - local ply = plys[i] - - if ply:IsSpec() then - surface.SetTextColor(220, 200, 0, 120) - - tgt = ply:GetObserverTarget() - - if IsValid(tgt) and tgt:GetNWEntity("spec_owner", nil) == ply then - scrpos = tgt:GetPos():ToScreen() - else - scrpos = nil - end - else - local _, healthcolor = util.HealthToString(ply:Health(), ply:GetMaxHealth()) - - surface.SetTextColor(clr(healthcolor)) - - scrpos = ply:EyePos() - scrpos.z = scrpos.z + 20 - scrpos = scrpos:ToScreen() - end - - if scrpos == nil or util.IsOffScreen(scrpos) then continue end - - text = ply:Nick() - w = surface.GetTextSize(text) - - surface.SetTextPos(scrpos.x - w * 0.5, scrpos.y) - surface.DrawText(text) - end + if not client:IsSpec() and GetRoundState() ~= ROUND_POST then + return + end + + local tgt, scrpos, color, _ + local plys = player.GetAll() + + for i = 1, #plys do + local ply = plys[i] + + if ply:IsSpec() then + color = colorPropSpecLabel + + tgt = ply:GetObserverTarget() + + if IsValid(tgt) and tgt:GetNWEntity("spec_owner", nil) == ply then + scrpos = tgt:GetPos():ToScreen() + else + scrpos = nil + end + else + local clientTarget = client:GetObserverTarget() + if ply == client or (clientTarget == ply and IsPlayer(clientTarget)) then + continue + end + _, color = util.HealthToString(ply:Health(), ply:GetMaxHealth()) + + scrpos = ply:EyePos() + scrpos.z = scrpos.z + 20 + scrpos = scrpos:ToScreen() + end + + if scrpos == nil or util.IsOffScreen(scrpos) then + continue + end + + draw.AdvancedText( + ply:Nick(), + "PureSkinMSTACKMsg", + scrpos.x, + scrpos.y, + color, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + true, + appearance.GetGlobalScale() + ) + end end --- @@ -240,177 +275,260 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:HUDDrawTargetID -- @local function GM:HUDDrawTargetID() - local client = LocalPlayer() - - --- - -- @realm client - if hook.Run("HUDShouldDraw", "TTTPropSpec") then - DrawPropSpecLabels(client) - end - - local ent, unchangedEnt, distance - local startpos = client:EyePos() - local direction = client:GetAimVector() - local filter = client:GetObserverMode() == OBS_MODE_IN_EYE and {client, client:GetObserverTarget()} or client - - ent, distance = targetid.FindEntityAlongView(startpos, direction, filter) - - --- - -- @realm client - local changedEnt = hook.Run("TTTModifyTargetedEntity", ent, distance) - - if changedEnt then - unchangedEnt = ent - ent = changedEnt - end - - -- make sure it is a valid entity - if not IsValid(ent) or ent.NoTarget then return end - - -- call internal targetID functions first so the data can be modified by addons - local tData = TARGET_DATA:Initialize(ent, unchangedEnt, distance) - - targetid.HUDDrawTargetIDSpawnEdit(tData) - targetid.HUDDrawTargetIDTButtons(tData) - targetid.HUDDrawTargetIDWeapons(tData) - targetid.HUDDrawTargetIDPlayers(tData) - targetid.HUDDrawTargetIDRagdolls(tData) - targetid.HUDDrawTargetIDDoors(tData) - targetid.HUDDrawTargetIDDNAScanner(tData) - - --- - -- now run a hook that can be used by addon devs that changes the appearance - -- of the targetid - -- @realm client - hook.Run("TTTRenderEntityInfo", tData) - - local data = tData.data - local params = tData.params - - -- draws an outline around the entity if defined - if params.drawOutline and cvDrawHalo:GetBool() then - outline.Add( - data.ent, - appearance.SelectFocusColor(params.outlineColor), - OUTLINE_MODE_VISIBLE - ) - end - - if not params.drawInfo then return end - - -- render on display text - local pad = 4 - local pad2 = pad * 2 - - -- draw key and keybox - -- the keyboxsize gets used as reference value since in most cases a key will be rendered - -- therefore the key size gets calculated every time, even if no key is set - local key_string = string.upper(params.displayInfo.key and input.GetKeyName(params.displayInfo.key) or "") - - local key_string_w, key_string_h = draw.GetTextSize(key_string, "TargetID_Key") - - local key_box_w = key_string_w + 5 * pad - local key_box_h = key_string_h + pad2 - local key_box_x = params.refPosition.x - key_box_w - pad2 - 2 -- -2 because of border width - local key_box_y = params.refPosition.y - - local key_string_x = key_box_x + math.Round(0.5 * key_box_w) - 1 - local key_string_y = key_box_y + math.Round(0.5 * key_box_h) - 1 - - if params.displayInfo.key then - drawsc.Box(key_box_x, key_box_y, key_box_w, key_box_h, colorKeyBack) - - drawsc.OutlinedShadowedBox(key_box_x, key_box_y, key_box_w, key_box_h, 1, COLOR_WHITE) - drawsc.AdvancedShadowedText(key_string, "TargetID_Key", key_string_x, key_string_y, COLOR_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) - end - - -- draw icon - local icon_amount = #params.displayInfo.icon - local icon_x, icon_y - - if icon_amount > 0 then - icon_x = params.refPosition.x - key_box_h - pad2 - icon_y = params.displayInfo.key and (key_box_y + key_box_h + pad2) or key_box_y + 1 - - for i = 1, icon_amount do - local icon = params.displayInfo.icon[i] - local color = icon.color or COLOR_WHITE - - drawsc.FilteredShadowedTexture(icon_x, icon_y, key_box_h, key_box_h, icon.material, color.a, color) - - icon_y = icon_y + key_box_h - end - end - - -- draw title - local title_string = params.displayInfo.title.text or "" - - local _, title_string_h = draw.GetTextSize(title_string, "TargetID_Title") - - local title_string_x = params.refPosition.x + pad2 - local title_string_y = key_box_y + title_string_h - 4 - - for i = 1, #params.displayInfo.title.icons do - drawsc.FilteredShadowedTexture(title_string_x, title_string_y - 16, 14, 14, params.displayInfo.title.icons[i], params.displayInfo.title.color.a, params.displayInfo.title.color) - - title_string_x = title_string_x + 18 - end - - drawsc.AdvancedShadowedText(title_string, "TargetID_Title", title_string_x, title_string_y, params.displayInfo.title.color, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) - - -- draw subtitle - local subtitle_string = params.displayInfo.subtitle.text or "" - - local subtitle_string_x = params.refPosition.x + pad2 - local subtitle_string_y = key_box_y + key_box_h + 2 - - for i = 1, #params.displayInfo.subtitle.icons do - drawsc.FilteredShadowedTexture(subtitle_string_x, subtitle_string_y - 14, 12, 12, params.displayInfo.subtitle.icons[i], params.displayInfo.subtitle.color.a, params.displayInfo.subtitle.color) - - subtitle_string_x = subtitle_string_x + 16 - end - - drawsc.AdvancedShadowedText(subtitle_string, "TargetID_Subtitle", subtitle_string_x, subtitle_string_y, params.displayInfo.subtitle.color, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) - - -- in cvMinimalisticTid mode, no descriptions should be shown - local desc_line_amount, desc_line_h = 0, 0 - - if not cvMinimalisticTid:GetBool() then - -- draw description text - local desc_lines = params.displayInfo.desc - - local desc_string_x = params.refPosition.x + pad2 - local desc_string_y = key_box_y + key_box_h + 8 * pad - desc_line_h = 17 - desc_line_amount = #desc_lines - - for i = 1, desc_line_amount do - local text = desc_lines[i].text - local icons = desc_lines[i].icons - local color = desc_lines[i].color - local desc_string_x_loop = desc_string_x - - for j = 1, #icons do - drawsc.FilteredShadowedTexture(desc_string_x_loop, desc_string_y - 13, 11, 11, icons[j], color.a, color) - - desc_string_x_loop = desc_string_x_loop + 14 - end - - drawsc.AdvancedShadowedText(text, "TargetID_Description", desc_string_x_loop, desc_string_y, color, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) - desc_string_y = desc_string_y + desc_line_h - end - end - - -- draw spacer line - local spacer_line_x = params.refPosition.x - 1 - local spacer_line_y = key_box_y - - local spacer_line_icon_l = (icon_y and icon_y or spacer_line_y) - spacer_line_y - local spacer_line_text_l = key_box_h + ((desc_line_amount > 0) and (4 * pad + desc_line_h * desc_line_amount - 3) or 0) - - local spacer_line_l = (spacer_line_icon_l > spacer_line_text_l) and spacer_line_icon_l or spacer_line_text_l - - drawsc.ShadowedBox(spacer_line_x, spacer_line_y, 1, spacer_line_l, Z) + local client = LocalPlayer() + + --- + -- @realm client + -- stylua: ignore + if hook.Run("HUDShouldDraw", "TTTPropSpec") then + DrawPropSpecLabels(client) + end + + local ent, unchangedEnt, distance + local startpos = client:EyePos() + local direction = client:GetAimVector() + local filter = client:GetObserverMode() == OBS_MODE_IN_EYE + and { client, client:GetObserverTarget() } + or client + + ent, distance = targetid.FindEntityAlongView(startpos, direction, filter) + + --- + -- @realm client + -- stylua: ignore + local changedEnt = hook.Run("TTTModifyTargetedEntity", ent, distance) + + if changedEnt then + unchangedEnt = ent + ent = changedEnt + end + + -- make sure it is a valid entity + if not IsValid(ent) or ent.NoTarget then + return + end + + -- call internal targetID functions first so the data can be modified by addons + local tData = TARGET_DATA:Initialize(ent, unchangedEnt, distance) + + targetid.HUDDrawTargetIDSpawnEdit(tData) + targetid.HUDDrawTargetIDTButtons(tData) + targetid.HUDDrawTargetIDWeapons(tData) + targetid.HUDDrawTargetIDPlayers(tData) + targetid.HUDDrawTargetIDRagdolls(tData) + targetid.HUDDrawTargetIDDoors(tData) + targetid.HUDDrawTargetIDDNAScanner(tData) + + -- add hints to the focused entity (deprecated method of adding stuff to targetID) + local hint = ent.TargetIDHint + + if hint and hint.hint then + tData:AddDescriptionLine(hint.fmt(ent, hint.hint), COLOR_LGRAY) + end + + --- + -- now run a hook that can be used by addon devs that changes the appearance + -- of the targetid + -- @realm client + -- stylua: ignore + hook.Run("TTTRenderEntityInfo", tData) + + local data = tData.data + local params = tData.params + + -- draws an outline around the entity if defined + if params.drawOutline and cvDrawHalo:GetBool() then + outline.Add( + data.ent, + appearance.SelectFocusColor(params.outlineColor), + OUTLINE_MODE_VISIBLE + ) + end + + if not params.drawInfo then + return + end + + -- render on display text + local pad = 4 + local pad2 = pad * 2 + + -- draw key and keybox + -- the keyboxsize gets used as reference value since in most cases a key will be rendered + -- therefore the key size gets calculated every time, even if no key is set + local key_string = + string.upper(params.displayInfo.key and input.GetKeyName(params.displayInfo.key) or "") + + local key_string_w, key_string_h = draw.GetTextSize(key_string, "TargetID_Key") + + local key_box_w = key_string_w + 5 * pad + local key_box_h = key_string_h + pad2 + local key_box_x = params.refPosition.x - key_box_w - pad2 - 2 -- -2 because of border width + local key_box_y = params.refPosition.y + + local key_string_x = key_box_x + math.Round(0.5 * key_box_w) - 1 + local key_string_y = key_box_y + math.Round(0.5 * key_box_h) - 1 + + if params.displayInfo.key then + drawsc.Box(key_box_x, key_box_y, key_box_w, key_box_h, colorKeyBack) + + drawsc.OutlinedShadowedBox(key_box_x, key_box_y, key_box_w, key_box_h, 1, COLOR_WHITE) + drawsc.AdvancedShadowedText( + key_string, + "TargetID_Key", + key_string_x, + key_string_y, + COLOR_WHITE, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) + end + + -- draw icon + local icon_amount = #params.displayInfo.icon + local icon_x, icon_y + + if icon_amount > 0 then + icon_x = params.refPosition.x - key_box_h - pad2 + icon_y = params.displayInfo.key and (key_box_y + key_box_h + pad2) or key_box_y + 1 + + for i = 1, icon_amount do + local icon = params.displayInfo.icon[i] + local color = icon.color or COLOR_WHITE + + drawsc.FilteredShadowedTexture( + icon_x, + icon_y, + key_box_h, + key_box_h, + icon.material, + color.a, + color + ) + + icon_y = icon_y + key_box_h + end + end + + -- draw title + local title_string = params.displayInfo.title.text or "" + + local _, title_string_h = draw.GetTextSize(title_string, "TargetID_Title") + + local title_string_x = params.refPosition.x + pad2 + local title_string_y = key_box_y + title_string_h - 4 + + for i = 1, #params.displayInfo.title.icons do + drawsc.FilteredShadowedTexture( + title_string_x, + title_string_y - 16, + 14, + 14, + params.displayInfo.title.icons[i], + params.displayInfo.title.color.a, + params.displayInfo.title.color + ) + + title_string_x = title_string_x + 18 + end + + drawsc.AdvancedShadowedText( + title_string, + "TargetID_Title", + title_string_x, + title_string_y, + params.displayInfo.title.color, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_BOTTOM + ) + + -- draw subtitle + local subtitle_string = params.displayInfo.subtitle.text or "" + + local subtitle_string_x = params.refPosition.x + pad2 + local subtitle_string_y = key_box_y + key_box_h + 2 + + for i = 1, #params.displayInfo.subtitle.icons do + drawsc.FilteredShadowedTexture( + subtitle_string_x, + subtitle_string_y - 14, + 12, + 12, + params.displayInfo.subtitle.icons[i], + params.displayInfo.subtitle.color.a, + params.displayInfo.subtitle.color + ) + + subtitle_string_x = subtitle_string_x + 16 + end + + drawsc.AdvancedShadowedText( + subtitle_string, + "TargetID_Subtitle", + subtitle_string_x, + subtitle_string_y, + params.displayInfo.subtitle.color, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_BOTTOM + ) + + -- in cvMinimalisticTid mode, no descriptions should be shown + local desc_line_amount, desc_line_h = 0, 0 + + if not cvMinimalisticTid:GetBool() then + -- draw description text + local desc_lines = params.displayInfo.desc + + local desc_string_x = params.refPosition.x + pad2 + local desc_string_y = key_box_y + key_box_h + 8 * pad + desc_line_h = 17 + desc_line_amount = #desc_lines + + for i = 1, desc_line_amount do + local text = desc_lines[i].text + local icons = desc_lines[i].icons + local color = desc_lines[i].color + local desc_string_x_loop = desc_string_x + + for j = 1, #icons do + drawsc.FilteredShadowedTexture( + desc_string_x_loop, + desc_string_y - 13, + 11, + 11, + icons[j], + color.a, + color + ) + + desc_string_x_loop = desc_string_x_loop + 14 + end + + drawsc.AdvancedShadowedText( + text, + "TargetID_Description", + desc_string_x_loop, + desc_string_y, + color, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_BOTTOM + ) + desc_string_y = desc_string_y + desc_line_h + end + end + + -- draw spacer line + local spacer_line_x = params.refPosition.x - 1 + local spacer_line_y = key_box_y + + local spacer_line_icon_l = (icon_y and icon_y or spacer_line_y) - spacer_line_y + local spacer_line_text_l = key_box_h + + ((desc_line_amount > 0) and (4 * pad + desc_line_h * desc_line_amount - 3) or 0) + + local spacer_line_l = (spacer_line_icon_l > spacer_line_text_l) and spacer_line_icon_l + or spacer_line_text_l + + drawsc.ShadowedBox(spacer_line_x, spacer_line_y, 1, spacer_line_l, COLOR_WHITE) end --- @@ -418,9 +536,7 @@ end -- @param TARGET_DATA tData The @{TARGET_DATA} data object which contains all information -- @hook -- @realm client -function GM:TTTRenderEntityInfo(tData) - -end +function GM:TTTRenderEntityInfo(tData) end --- -- Change the focused entity used for targetID. @@ -429,9 +545,7 @@ end -- @return Entity The new entity to replace the real one -- @hook -- @realm client -function GM:TTTModifyTargetedEntity(ent, distance) - -end +function GM:TTTModifyTargetedEntity(ent, distance) end --- -- Change the starting position for the trace that looks for entites. @@ -440,9 +554,7 @@ end -- @return Vector, Vector The new startpos for the trace, the new endpos for the trace -- @hook -- @realm client -function GM:TTTModifyTargetTracedata(startpos, endpos) - -end +function GM:TTTModifyTargetTracedata(startpos, endpos) end --- -- Can be used to modify, disable or add an overhead icon of a player. @@ -453,6 +565,4 @@ end -- @return Color The color of the overhead icon backdrop -- @hook -- @realm client -function GM:TTT2ModifyOverheadIcon(ply, shouldDrawDefault) - -end +function GM:TTT2ModifyOverheadIcon(ply, shouldDrawDefault) end diff --git a/gamemodes/terrortown/gamemode/client/cl_tbuttons.lua b/gamemodes/terrortown/gamemode/client/cl_tbuttons.lua index 3359b99a7..d9301b376 100644 --- a/gamemodes/terrortown/gamemode/client/cl_tbuttons.lua +++ b/gamemodes/terrortown/gamemode/client/cl_tbuttons.lua @@ -21,46 +21,49 @@ TBHUD.focus_stick = 0 -- Clears the list of stored traitor buttons -- @realm client function TBHUD:Clear() - self.buttons = {} - self.buttons_count = 0 + self.buttons = {} + self.buttons_count = 0 - self.focus_but = nil - self.focus_stick = 0 + self.focus_but = nil + self.focus_stick = 0 end --- -- Caches every available traitor button on the map for the local @{Player} -- @realm client function TBHUD:CacheEnts() - local ply = LocalPlayer() - self.buttons = {} - - if IsValid(ply) and ply:IsActive() then - local admin = ply:IsAdmin() - local team = ply:GetTeam() - local btns = ents.FindByClass("ttt_traitor_button") - - for i = 1, #btns do - local ent = btns[i] - local access, overrideRole, overrideTeam, roleIntend, teamIntend = ent:PlayerRoleCanUse(ply) - - if not admin and not access then continue end - - self.buttons[ent:EntIndex()] = { - ["ent"] = ent, - ["access"] = access, - ["overrideRole"] = overrideRole, - ["overrideTeam"] = overrideTeam, - ["roleIntend"] = roleIntend, - ["teamIntend"] = teamIntend, - ["admin"] = admin, - ["roleColor"] = ply:GetRoleColor(), - ["teamColor"] = TEAMS and TEAMS[team] and TEAMS[team].color or COLOR_BLACK - } - end - end - - self.buttons_count = table.Count(self.buttons) + local ply = LocalPlayer() + self.buttons = {} + + if IsValid(ply) and ply:IsActive() then + local admin = ply:IsAdmin() + local team = ply:GetTeam() + local btns = ents.FindByClass("ttt_traitor_button") + + for i = 1, #btns do + local ent = btns[i] + local access, overrideRole, overrideTeam, roleIntend, teamIntend = + ent:PlayerRoleCanUse(ply) + + if not admin and not access then + continue + end + + self.buttons[ent:EntIndex()] = { + ["ent"] = ent, + ["access"] = access, + ["overrideRole"] = overrideRole, + ["overrideTeam"] = overrideTeam, + ["roleIntend"] = roleIntend, + ["teamIntend"] = teamIntend, + ["admin"] = admin, + ["roleColor"] = ply:GetRoleColor(), + ["teamColor"] = TEAMS and TEAMS[team] and TEAMS[team].color or COLOR_BLACK, + } + end + end + + self.buttons_count = table.Count(self.buttons) end --- @@ -68,9 +71,13 @@ end -- @return boolean -- @realm client function TBHUD:PlayerIsFocused() - local ply = LocalPlayer() + local ply = LocalPlayer() - return IsValid(ply) and ply:IsActive() and self.focus_but and (self.focus_but.access or self.focus_but.admin) and IsValid(self.focus_but.ent) + return IsValid(ply) + and ply:IsActive() + and self.focus_but + and (self.focus_but.access or self.focus_but.admin) + and IsValid(self.focus_but.ent) end --- @@ -78,19 +85,22 @@ end -- @return boolean whether the activation was successful -- @realm client function TBHUD:UseFocused() - local buttonChecks = self.focus_but and IsValid(self.focus_but.ent) and self.focus_but.access and self.focus_stick >= CurTime() - - if buttonChecks then - net.Start("TTT2ActivateTButton") - net.WriteEntity(self.focus_but.ent) - net.SendToServer() - - self.focus_but = nil - - return true - else - return false - end + local buttonChecks = self.focus_but + and IsValid(self.focus_but.ent) + and self.focus_but.access + and self.focus_stick >= CurTime() + + if buttonChecks then + net.Start("TTT2ActivateTButton") + net.WriteEntity(self.focus_but.ent) + net.SendToServer() + + self.focus_but = nil + + return true + else + return false + end end --- @@ -99,20 +109,24 @@ end -- @return boolean whether the request was sent to server -- @realm client function TBHUD:ToggleFocused(teamMode) - local buttonChecks = self.focus_but and IsValid(self.focus_but.ent) and self.focus_but.admin and self.focus_stick >= CurTime() and GetGlobalBool("ttt2_tbutton_admin_show", false) - - if buttonChecks then - net.Start("TTT2ToggleTButton") - net.WriteEntity(self.focus_but.ent) - net.WriteBool(teamMode) - net.SendToServer() - - self.focus_but = nil - - return true - else - return false - end + local buttonChecks = self.focus_but + and IsValid(self.focus_but.ent) + and self.focus_but.admin + and self.focus_stick >= CurTime() + and GetGlobalBool("ttt2_tbutton_admin_show", false) + + if buttonChecks then + net.Start("TTT2ToggleTButton") + net.WriteEntity(self.focus_but.ent) + net.WriteBool(teamMode) + net.SendToServer() + + self.focus_but = nil + + return true + else + return false + end end local confirm_sound = Sound("buttons/button24.wav") @@ -121,9 +135,9 @@ local confirm_sound = Sound("buttons/button24.wav") -- Plays a sound and caches all traitor buttons -- @realm client function TBHUD.ReceiveUseConfirm() - surface.PlaySound(confirm_sound) + surface.PlaySound(confirm_sound) - TBHUD:CacheEnts() + TBHUD:CacheEnts() end net.Receive("TTT_ConfirmUseTButton", TBHUD.ReceiveUseConfirm) @@ -140,78 +154,131 @@ local focus_range = 25 -- @param Player client This should be the local @{Player} -- @realm client function TBHUD:Draw(client) - if self.buttons_count == 0 then return end - - -- we're doing slowish distance computation here, so lots of probably - -- ineffective micro-optimization - local plypos = client:GetPos() - local midscreen_x = ScrW() * 0.5 - local midscreen_y = ScrH() * 0.5 - local pos, scrpos, d - local focus_but - local focus_d, focus_scrpos_x, focus_scrpos_y = 0, midscreen_x, midscreen_y - local showToAdmins = GetGlobalBool("ttt2_tbutton_admin_show", false) - - -- draw icon on HUD for every button within range - for _, val in pairs(self.buttons) do - local ent = val.ent - local teamAccess = val.overrideTeam or val.access and val.teamIntend ~= TEAM_NONE and val.overrideRole == nil and val.overrideTeam == nil - local outlineColor = teamAccess and val.teamColor or val.roleColor or COLOR_BLACK - - if not IsValid(ent) or not ent.IsUsable then continue end - - pos = ent:GetPos() - scrpos = pos:ToScreen() - - if util.IsOffScreen(scrpos) or not ent:IsUsable() then continue end - - local usableRange = ent:GetUsableRange() - - if not val.access and not showToAdmins then continue end - - d = pos - plypos - d = d:Dot(d) / (usableRange * usableRange) - - -- draw if this button is within range, with alpha based on distance - if d >= 1 then continue end - - local scrPosXMid, scrPosYMid = scrpos.x - mid, scrpos.y - mid - - if val.access then - draw.FilteredTexture(scrPosXMid, scrPosYMid, size, size, tbut_normal, 200 * (1 - d), COLOR_WHITE) - end - - draw.FilteredTexture(scrPosXMid, scrPosYMid, size, size, tbut_outline, 200 * (1 - d), outlineColor) - - if d <= focus_d then continue end - - local x = abs(scrpos.x - midscreen_x) - local y = abs(scrpos.y - midscreen_y) - - if x >= focus_range - or y >= focus_range - or x >= focus_scrpos_x - or y >= focus_scrpos_y - or self.focus_stick >= CurTime() and (ent ~= (self.focus_but and self.focus_but.ent or nil)) then - continue - end - - -- avoid constantly switching focus every frame causing - -- 2+ buttons to appear in focus, instead "stick" to one - -- ent for a very short time to ensure consistency - focus_but = val - - -- draw extra graphics and information for button when it's in-focus - if not focus_but or not IsValid(focus_but.ent) then continue end - - self.focus_but = focus_but - self.focus_stick = CurTime() + 0.1 - - scrpos = focus_but.ent:GetPos():ToScreen() - scrPosXMid, scrPosYMid = scrpos.x - mid, scrpos.y - mid - - -- redraw in-focus version of icon - draw.FilteredTexture(scrPosXMid - 3 , scrPosYMid - 3, size + 6, size + 6, tbut_focus, 200, COLOR_WHITE) - draw.FilteredTexture(scrPosXMid - 3, scrPosYMid - 3, size + 6, size + 6, tbut_outline, 150, outlineColor) - end + if self.buttons_count == 0 then + return + end + + -- we're doing slowish distance computation here, so lots of probably + -- ineffective micro-optimization + local plypos = client:GetPos() + local midscreen_x = ScrW() * 0.5 + local midscreen_y = ScrH() * 0.5 + local pos, scrpos, d + local focus_but + local focus_d, focus_scrpos_x, focus_scrpos_y = 0, midscreen_x, midscreen_y + local showToAdmins = GetGlobalBool("ttt2_tbutton_admin_show", false) + + -- draw icon on HUD for every button within range + for _, val in pairs(self.buttons) do + local ent = val.ent + local teamAccess = val.overrideTeam + or val.access + and val.teamIntend ~= TEAM_NONE + and val.overrideRole == nil + and val.overrideTeam == nil + local outlineColor = teamAccess and val.teamColor or val.roleColor or COLOR_BLACK + + if not IsValid(ent) or not ent.IsUsable then + continue + end + + pos = ent:GetPos() + scrpos = pos:ToScreen() + + if util.IsOffScreen(scrpos) or not ent:IsUsable() then + continue + end + + local usableRange = ent:GetUsableRange() + + if not val.access and not showToAdmins then + continue + end + + d = pos - plypos + d = d:Dot(d) / (usableRange * usableRange) + + -- draw if this button is within range, with alpha based on distance + if d >= 1 then + continue + end + + local scrPosXMid, scrPosYMid = scrpos.x - mid, scrpos.y - mid + + if val.access then + draw.FilteredTexture( + scrPosXMid, + scrPosYMid, + size, + size, + tbut_normal, + 200 * (1 - d), + COLOR_WHITE + ) + end + + draw.FilteredTexture( + scrPosXMid, + scrPosYMid, + size, + size, + tbut_outline, + 200 * (1 - d), + outlineColor + ) + + if d <= focus_d then + continue + end + + local x = abs(scrpos.x - midscreen_x) + local y = abs(scrpos.y - midscreen_y) + + if + x >= focus_range + or y >= focus_range + or x >= focus_scrpos_x + or y >= focus_scrpos_y + or self.focus_stick >= CurTime() + and (ent ~= (self.focus_but and self.focus_but.ent or nil)) + then + continue + end + + -- avoid constantly switching focus every frame causing + -- 2+ buttons to appear in focus, instead "stick" to one + -- ent for a very short time to ensure consistency + focus_but = val + + -- draw extra graphics and information for button when it's in-focus + if not focus_but or not IsValid(focus_but.ent) then + continue + end + + self.focus_but = focus_but + self.focus_stick = CurTime() + 0.1 + + scrpos = focus_but.ent:GetPos():ToScreen() + scrPosXMid, scrPosYMid = scrpos.x - mid, scrpos.y - mid + + -- redraw in-focus version of icon + draw.FilteredTexture( + scrPosXMid - 3, + scrPosYMid - 3, + size + 6, + size + 6, + tbut_focus, + 200, + COLOR_WHITE + ) + draw.FilteredTexture( + scrPosXMid - 3, + scrPosYMid - 3, + size + 6, + size + 6, + tbut_outline, + 150, + outlineColor + ) + end end diff --git a/gamemodes/terrortown/gamemode/client/cl_tips.lua b/gamemodes/terrortown/gamemode/client/cl_tips.lua index b0f10af3c..309e91218 100644 --- a/gamemodes/terrortown/gamemode/client/cl_tips.lua +++ b/gamemodes/terrortown/gamemode/client/cl_tips.lua @@ -10,6 +10,7 @@ local IsValid = IsValid --- -- @realm client +-- stylua: ignore local cv_ttt_tips_enable = CreateConVar("ttt_tips_enable", "1", FCVAR_ARCHIVE) local draw = draw @@ -21,9 +22,9 @@ TIPS = {} -- Tip cycling button PANEL = {} PANEL.Colors = { - default = COLOR_LGRAY, - hover = COLOR_WHITE, - press = COLOR_RED + default = COLOR_LGRAY, + hover = COLOR_WHITE, + press = COLOR_RED, } --- @@ -33,42 +34,41 @@ PANEL.Colors = { --- -- @local function PANEL:Paint() - -- parent panel will deal with the normal bg, we only need to worry about - -- mouse effects + -- parent panel will deal with the normal bg, we only need to worry about + -- mouse effects - local c = self.Colors.default + local c = self.Colors.default - if self.Depressed then - c = self.Colors.press - elseif self.Hovered then - c = self.Colors.hover - end + if self.Depressed then + c = self.Colors.press + elseif self.Hovered then + c = self.Colors.hover + end - surface.SetDrawColor(c.r, c.g, c.b, c.a) + surface.SetDrawColor(c.r, c.g, c.b, c.a) - self:DrawOutlinedRect() + self:DrawOutlinedRect() end derma.DefineControl("TipsButton", "Tip cycling button", PANEL, "DButton") - -- Main tip panel local tips_bg = Color(0, 0, 0, 200) local tip_ids = {} for i = 1, 40 do - tip_ids[i] = i + tip_ids[i] = i end table.Shuffle(tip_ids) local tip_params = { - [1] = {walkkey = Key("+walk", "WALK"), usekey = Key("+use", "USE")}, - [24] = {helpkey = Key("+gm_showhelp", "F1")}, - [28] = {mutekey = Key("+gm_showteam", "F2")}, - [30] = {zoomkey = Key("+zoom", "the 'Suit Zoom' key")}, - [31] = {duckkey = Key("+duck", "DUCK")}, - [36] = {helpkey = Key("+gm_showhelp", "F1")}, + [1] = { walkkey = Key("+walk", "WALK"), usekey = Key("+use", "USE") }, + [24] = { helpkey = Key("+gm_showhelp", "F1") }, + [28] = { mutekey = Key("+gm_showteam", "F2") }, + [30] = { zoomkey = Key("+zoom", "the 'Suit Zoom' key") }, + [31] = { duckkey = Key("+duck", "DUCK") }, + [36] = { helpkey = Key("+gm_showhelp", "F1") }, } -- @section TTTTips @@ -78,47 +78,47 @@ PANEL = {} --- -- @local function PANEL:Init() - self.IdealWidth = 450 - self.IdealHeight = 45 - self.BgColor = tips_bg + self.IdealWidth = 450 + self.IdealHeight = 45 + self.BgColor = tips_bg - self.NextSwitch = 0 + self.NextSwitch = 0 - self.AutoDelay = 15 - self.ManualDelay = 25 + self.AutoDelay = 15 + self.ManualDelay = 25 - self.tiptext = vgui.Create("DLabel", self) - self.tiptext:SetContentAlignment(5) - self.tiptext:SetText(GetTranslation("tips_panel_title")) + self.tiptext = vgui.Create("DLabel", self) + self.tiptext:SetContentAlignment(5) + self.tiptext:SetText(GetTranslation("tips_panel_title")) - self.bwrap = vgui.Create("Panel", self) + self.bwrap = vgui.Create("Panel", self) - self.buttons = {} - self.buttons.left = vgui.Create("TipsButton", self.bwrap) - self.buttons.left:SetText("<") + self.buttons = {} + self.buttons.left = vgui.Create("TipsButton", self.bwrap) + self.buttons.left:SetText("<") - self.buttons.left.DoClick = function() - self:PrevTip() - end + self.buttons.left.DoClick = function() + self:PrevTip() + end - self.buttons.right = vgui.Create("TipsButton", self.bwrap) - self.buttons.right:SetText(">") + self.buttons.right = vgui.Create("TipsButton", self.bwrap) + self.buttons.right:SetText(">") - self.buttons.right.DoClick = function() - self:NextTip() - end + self.buttons.right.DoClick = function() + self:NextTip() + end - self.buttons.help = vgui.Create("TipsButton", self.bwrap) - self.buttons.help:SetText("?") - self.buttons.help:SetConsoleCommand("ttt_helpscreen") + self.buttons.help = vgui.Create("TipsButton", self.bwrap) + self.buttons.help:SetText("?") + self.buttons.help:SetConsoleCommand("ttt_helpscreen") - self.buttons.close = vgui.Create("TipsButton", self.bwrap) - self.buttons.close:SetText("X") - self.buttons.close:SetConsoleCommand("ttt_tips_hide") + self.buttons.close = vgui.Create("TipsButton", self.bwrap) + self.buttons.close:SetText("X") + self.buttons.close:SetConsoleCommand("ttt_tips_hide") - self.TipIndex = math.random(#tip_ids) or 0 + self.TipIndex = math.random(#tip_ids) or 0 - self:SetTip(self.TipIndex) + self:SetTip(self.TipIndex) end --- @@ -127,27 +127,27 @@ end -- @realm client -- @local function PANEL:SetTip(idx) - if not idx then - self:SetVisible(false) + if not idx then + self:SetVisible(false) - return - end + return + end - self.TipIndex = idx + self.TipIndex = idx - local tip_id = tip_ids[idx] + local tip_id = tip_ids[idx] - local text + local text - if tip_params[tip_id] then - text = GetPTranslation("tip" .. tip_id, tip_params[tip_id]) - else - text = GetTranslation("tip" .. tip_id) - end + if tip_params[tip_id] then + text = GetPTranslation("tip" .. tip_id, tip_params[tip_id]) + else + text = GetTranslation("tip" .. tip_id) + end - self.tiptext:SetText(GetTranslation("tips_panel_tip") .. " " .. text) + self.tiptext:SetText(GetTranslation("tips_panel_tip") .. " " .. text) - self:InvalidateLayout(true) + self:InvalidateLayout(true) end --- @@ -157,17 +157,19 @@ end -- @realm client -- @local function PANEL:NextTip(auto) - if not self.TipIndex then return end + if not self.TipIndex then + return + end - local idx = self.TipIndex + 1 + local idx = self.TipIndex + 1 - if idx > #tip_ids then - idx = 1 - end + if idx > #tip_ids then + idx = 1 + end - self:SetTip(idx) + self:SetTip(idx) - self.NextSwitch = CurTime() + (auto and self.AutoDelay or self.ManualDelay) + self.NextSwitch = CurTime() + (auto and self.AutoDelay or self.ManualDelay) end --- @@ -177,111 +179,113 @@ end -- @realm client -- @local function PANEL:PrevTip(auto) - if not self.TipIndex then return end + if not self.TipIndex then + return + end - local idx = self.TipIndex - 1 + local idx = self.TipIndex - 1 - if idx < 1 then - idx = #tip_ids - end + if idx < 1 then + idx = #tip_ids + end - self:SetTip(idx) + self:SetTip(idx) - self.NextSwitch = CurTime() + (auto and self.AutoDelay or self.ManualDelay) + self.NextSwitch = CurTime() + (auto and self.AutoDelay or self.ManualDelay) end --- -- @local function PANEL:PerformLayout() - local m = 8 - local off_bottom = 10 + local m = 8 + local off_bottom = 10 - -- need to account for voice stuff in the bottom right and the time in the - -- bottom left - local off_left = 260 - local off_right = 250 - local room = ScrW() - off_left - off_right - local width = math.min(room, self.IdealWidth) + -- need to account for voice stuff in the bottom right and the time in the + -- bottom left + local off_left = 260 + local off_right = 250 + local room = ScrW() - off_left - off_right + local width = math.min(room, self.IdealWidth) - if width < 200 then - -- people who run 640x480 do not deserve tips - self:SetVisible(false) + if width < 200 then + -- people who run 640x480 do not deserve tips + self:SetVisible(false) - return - end + return + end - local bsize = 14 + local bsize = 14 - -- position buttons - self.bwrap:SetSize(bsize * 2 + 2, bsize * 2 + 2) + -- position buttons + self.bwrap:SetSize(bsize * 2 + 2, bsize * 2 + 2) - self.buttons.left:SetSize(bsize, bsize) - self.buttons.left:SetPos(0, 0) + self.buttons.left:SetSize(bsize, bsize) + self.buttons.left:SetPos(0, 0) - self.buttons.right:SetSize(bsize, bsize) - self.buttons.right:SetPos(bsize + 2, 0) + self.buttons.right:SetSize(bsize, bsize) + self.buttons.right:SetPos(bsize + 2, 0) - self.buttons.help:SetSize(bsize, bsize) - self.buttons.help:SetPos(0, bsize + 2) + self.buttons.help:SetSize(bsize, bsize) + self.buttons.help:SetPos(0, bsize + 2) - self.buttons.close:SetSize(bsize, bsize) - self.buttons.close:SetPos(bsize + 2, bsize + 2) + self.buttons.close:SetSize(bsize, bsize) + self.buttons.close:SetPos(bsize + 2, bsize + 2) - -- position content - self.tiptext:SetPos(m, m) - self.tiptext:SetTall(self.IdealHeight) - self.tiptext:SetWide(width - m * 2 - self.bwrap:GetWide()) - self.tiptext:SizeToContentsY() + -- position content + self.tiptext:SetPos(m, m) + self.tiptext:SetTall(self.IdealHeight) + self.tiptext:SetWide(width - m * 2 - self.bwrap:GetWide()) + self.tiptext:SizeToContentsY() - local height = math.max(self.IdealHeight, self.tiptext:GetTall() + m * 2) + local height = math.max(self.IdealHeight, self.tiptext:GetTall() + m * 2) - local x = off_left + (room - width) * 0.5 - local y = ScrH() - off_bottom - height + local x = off_left + (room - width) * 0.5 + local y = ScrH() - off_bottom - height - self:SetPos(x, y) - self:SetSize(width, height) + self:SetPos(x, y) + self:SetSize(width, height) - self.bwrap:SetPos(width - self.bwrap:GetWide() - m, height - self.bwrap:GetTall() - m) + self.bwrap:SetPos(width - self.bwrap:GetWide() - m, height - self.bwrap:GetTall() - m) end --- -- @local function PANEL:ApplySchemeSettings() - for _, but in pairs(self.buttons) do - but:SetTextColor(COLOR_WHITE) - but:SetContentAlignment(5) - end + for _, but in pairs(self.buttons) do + but:SetTextColor(COLOR_WHITE) + but:SetContentAlignment(5) + end - self.bwrap:SetPaintBackgroundEnabled(false) + self.bwrap:SetPaintBackgroundEnabled(false) - self.tiptext:SetFont("DefaultBold") - self.tiptext:SetTextColor(COLOR_WHITE) - self.tiptext:SetWrap(true) + self.tiptext:SetFont("DefaultBold") + self.tiptext:SetTextColor(COLOR_WHITE) + self.tiptext:SetWrap(true) end --- -- @local function PANEL:Paint(w, h) - self.paintColor = self.BgColor + self.paintColor = self.BgColor - if huds and HUDManager then - local hud = huds.GetStored(HUDManager.GetHUD()) - if hud and isfunction(hud.PopupPaint) then - hud.PopupPaint(self, w, h) + if huds and HUDManager then + local hud = huds.GetStored(HUDManager.GetHUD()) + if hud and isfunction(hud.PopupPaint) then + hud.PopupPaint(self, w, h) - return - end - end + return + end + end - draw.RoundedBox(8, 0, 0, w, h, self.BgColor) + draw.RoundedBox(8, 0, 0, w, h, self.BgColor) end --- -- @local function PANEL:Think() - if self.NextSwitch < CurTime() then - self:NextTip(true) - end + if self.NextSwitch < CurTime() then + self:NextTip(true) + end end vgui.Register("TTTTips", PANEL, "Panel") @@ -297,47 +301,49 @@ local tips_panel -- Creates the @{TIPS} menu -- @realm client function TIPS.Create() - if IsValid(tips_panel) then - tips_panel:Remove() + if IsValid(tips_panel) then + tips_panel:Remove() - tips_panel = nil - end + tips_panel = nil + end - tips_panel = vgui.Create("TTTTips") + tips_panel = vgui.Create("TTTTips") - -- workaround for layout oddities, give it a poke next tick - timer.Simple(0.1, TIPS.Next) + -- workaround for layout oddities, give it a poke next tick + timer.Simple(0.1, TIPS.Next) end --- -- Displays the @{TIPS} menu -- @realm client function TIPS.Show() - if not cv_ttt_tips_enable:GetBool() then return end + if not cv_ttt_tips_enable:GetBool() then + return + end - if not tips_panel then - TIPS.Create() - end + if not tips_panel then + TIPS.Create() + end - tips_panel:SetVisible(true) + tips_panel:SetVisible(true) end --- -- Hides the @{TIPS} menu -- @realm client function TIPS.Hide() - if tips_panel then - tips_panel:SetVisible(false) - end - - if GAMEMODE.ForcedMouse then - -- currently the only use of unlocking the mouse is screwing around with - -- the hints, and it makes sense to lock the mouse again when closing the - -- tips - gui.EnableScreenClicker(false) - - GAMEMODE.ForcedMouse = false - end + if tips_panel then + tips_panel:SetVisible(false) + end + + if GAMEMODE.ForcedMouse then + -- currently the only use of unlocking the mouse is screwing around with + -- the hints, and it makes sense to lock the mouse again when closing the + -- tips + gui.EnableScreenClicker(false) + + GAMEMODE.ForcedMouse = false + end end concommand.Add("ttt_tips_hide", TIPS.Hide) @@ -345,27 +351,29 @@ concommand.Add("ttt_tips_hide", TIPS.Hide) -- Switches to the next tip -- @realm client function TIPS.Next() - if tips_panel then - tips_panel:NextTip() - end + if tips_panel then + tips_panel:NextTip() + end end --- -- Switches to the previous tip -- @realm client function TIPS.Prev() - if tips_panel then - tips_panel:PrevTip() - end + if tips_panel then + tips_panel:PrevTip() + end end local function TipsCallback(cv, prev, new) - if tobool(new) then - if not LocalPlayer():IsSpec() then return end - - TIPS.Show() - else - TIPS.Hide() - end + if tobool(new) then + if not LocalPlayer():IsSpec() then + return + end + + TIPS.Show() + else + TIPS.Hide() + end end cvars.AddChangeCallback("ttt_tips_enable", TipsCallback) diff --git a/gamemodes/terrortown/gamemode/client/cl_tradio.lua b/gamemodes/terrortown/gamemode/client/cl_tradio.lua index 0574e06bc..ceaa79f3a 100644 --- a/gamemodes/terrortown/gamemode/client/cl_tradio.lua +++ b/gamemodes/terrortown/gamemode/client/cl_tradio.lua @@ -7,75 +7,77 @@ TRADIO = {} local IsValid = IsValid local sound_names = { - scream = "radio_button_scream", - explosion = "radio_button_expl", - pistol = "radio_button_pistol", - m16 = "radio_button_m16", - deagle = "radio_button_deagle", - mac10 = "radio_button_mac10", - shotgun = "radio_button_shotgun", - rifle = "radio_button_rifle", - huge = "radio_button_huge", - beeps = "radio_button_c4", - burning = "radio_button_burn", - footsteps = "radio_button_steps" + scream = "radio_button_scream", + explosion = "radio_button_expl", + pistol = "radio_button_pistol", + m16 = "radio_button_m16", + deagle = "radio_button_deagle", + mac10 = "radio_button_mac10", + shotgun = "radio_button_shotgun", + rifle = "radio_button_rifle", + huge = "radio_button_huge", + beeps = "radio_button_c4", + burning = "radio_button_burn", + footsteps = "radio_button_steps", } local smatrix = { - {"scream", "burning", "explosion", "footsteps"}, - {"pistol", "shotgun", "mac10", "deagle"}, - {"m16", "rifle", "huge", "beeps"} + { "scream", "burning", "explosion", "footsteps" }, + { "pistol", "shotgun", "mac10", "deagle" }, + { "m16", "rifle", "huge", "beeps" }, } local function PlayRadioSound(snd) - local r = LocalPlayer().radio - if not IsValid(r) then return end + local r = LocalPlayer().radio + if not IsValid(r) then + return + end - RunConsoleCommand("ttt_radio_play", tostring(r:EntIndex()), snd) + RunConsoleCommand("ttt_radio_play", tostring(r:EntIndex()), snd) end local function ButtonClickPlay(s) - PlayRadioSound(s.snd) + PlayRadioSound(s.snd) end local function CreateSoundBoard(parent) - local b = vgui.Create("DPanel", parent) + local b = vgui.Create("DPanel", parent) - --b:SetPaintBackground(false) + --b:SetPaintBackground(false) - local bh, bw = 50, 100 - local m = 5 - local ver = #smatrix - local hor = #smatrix[1] + local bh, bw = 50, 100 + local m = 5 + local ver = #smatrix + local hor = #smatrix[1] - local x, y = 0, 0 + local x, y = 0, 0 - for ri = 1, ver do - local row = smatrix[ri] - local rj = ri - 1 -- easier for computing x, y + for ri = 1, ver do + local row = smatrix[ri] + local rj = ri - 1 -- easier for computing x, y - for rk = 1, #row do - local snd = row[rk] - local rl = rk - 1 + for rk = 1, #row do + local snd = row[rk] + local rl = rk - 1 - y = rj * m + rj * bh - x = rl * m + rl * bw + y = rj * m + rj * bh + x = rl * m + rl * bw - local but = vgui.Create("DButton", b) - but:SetPos(x, y) - but:SetSize(bw, bh) - but:SetText(LANG.GetTranslation(sound_names[snd])) + local but = vgui.Create("DButton", b) + but:SetPos(x, y) + but:SetSize(bw, bh) + but:SetText(LANG.GetTranslation(sound_names[snd])) - but.snd = snd - but.DoClick = ButtonClickPlay - end - end + but.snd = snd + but.DoClick = ButtonClickPlay + end + end - b:SetSize(bw * hor + m * (hor - 1), bh * ver + m * (ver - 1)) - b:SetPos(m, 25) - b:CenterHorizontal() + b:SetSize(bw * hor + m * (hor - 1), bh * ver + m * (ver - 1)) + b:SetPos(m, 25) + b:CenterHorizontal() - return b + return b end --- @@ -84,27 +86,27 @@ end -- @return Panel the created DPanel menu -- @realm client function TRADIO.CreateMenu(parent) - local w, h = parent:GetSize() - local client = LocalPlayer() + local w, h = parent:GetSize() + local client = LocalPlayer() - local wrap = vgui.Create("DPanel", parent) - wrap:SetSize(w, h) - wrap:SetPaintBackground(false) + local wrap = vgui.Create("DPanel", parent) + wrap:SetSize(w, h) + wrap:SetPaintBackground(false) - local dhelp = vgui.Create("DLabel", wrap) - dhelp:SetFont("TabLarge") - dhelp:SetText(LANG.GetTranslation("radio_help")) - dhelp:SetTextColor(COLOR_WHITE) + local dhelp = vgui.Create("DLabel", wrap) + dhelp:SetFont("TabLarge") + dhelp:SetText(LANG.GetTranslation("radio_help")) + dhelp:SetTextColor(COLOR_WHITE) - if IsValid(client.radio) then - CreateSoundBoard(wrap) -- local board - elseif client:HasWeapon("weapon_ttt_radio") then - dhelp:SetText(LANG.GetTranslation("radio_notplaced")) - end + if IsValid(client.radio) then + CreateSoundBoard(wrap) -- local board + elseif client:HasWeapon("weapon_ttt_radio") then + dhelp:SetText(LANG.GetTranslation("radio_notplaced")) + end - dhelp:SizeToContents() - dhelp:SetPos(10, 5) - dhelp:CenterHorizontal() + dhelp:SizeToContents() + dhelp:SetPos(10, 5) + dhelp:CenterHorizontal() - return wrap + return wrap end diff --git a/gamemodes/terrortown/gamemode/client/cl_transfer.lua b/gamemodes/terrortown/gamemode/client/cl_transfer.lua index 14fcb9b55..1d8a504a2 100644 --- a/gamemodes/terrortown/gamemode/client/cl_transfer.lua +++ b/gamemodes/terrortown/gamemode/client/cl_transfer.lua @@ -21,34 +21,35 @@ local selected_sid -- @return[default=nil] string for the client which offers info related to the transaction -- @hook -- @realm client -function GM:TTT2CanTransferCredits(sender, recipient, credits_per_xfer) - -end +function GM:TTT2CanTransferCredits(sender, recipient, credits_per_xfer) end local function UpdateTransferSubmitButton() - if not IsValid(dhelp) or not IsValid(dsubmit) then return end - - local client = LocalPlayer() - if client:GetCredits() <= 0 then - dhelp:SetText(GetTranslation("xfer_no_credits")) - dsubmit:SetDisabled(true) - elseif selected_sid then - local ply = player.GetBySteamID64(selected_sid) - - --- - -- @realm client - local allow, msg = hook.Run("TTT2CanTransferCredits", client, ply, CREDITS_PER_XFER) - - if allow == false then - dsubmit:SetDisabled(true) - else - dsubmit:SetDisabled(false) - end - - if isstring(msg) then - dhelp:SetText(msg) - end - end + if not IsValid(dhelp) or not IsValid(dsubmit) then + return + end + + local client = LocalPlayer() + if client:GetCredits() <= 0 then + dhelp:SetText(GetTranslation("xfer_no_credits")) + dsubmit:SetEnabled(false) + elseif selected_sid then + local ply = player.GetBySteamID64(selected_sid) + + --- + -- @realm client + -- stylua: ignore + local allow, msg = hook.Run("TTT2CanTransferCredits", client, ply, CREDITS_PER_XFER) + + if allow == false then + dsubmit:SetEnabled(false) + else + dsubmit:SetEnabled(true) + end + + if isstring(msg) then + dhelp:SetText(msg) + end + end end --Called after the server performs a successful transfer of credits. @@ -60,73 +61,73 @@ net.Receive("TTT2CreditTransferUpdate", UpdateTransferSubmitButton) -- @return Panel the created DForm menu -- @realm client function CreateTransferMenu(parent) - local client = LocalPlayer() + local client = LocalPlayer() - dform = vgui.Create("DForm", parent) - dform:SetName(GetTranslation("xfer_menutitle")) - dform:StretchToParent(0, 0, 0, 0) - dform:SetAutoSize(false) + dform = vgui.Create("DForm", parent) + dform:SetName(GetTranslation("xfer_menutitle")) + dform:StretchToParent(0, 0, 0, 0) + dform:SetAutoSize(false) - local bw, bh = 100, 20 + local bw, bh = 100, 20 - dsubmit = vgui.Create("DButton", dform) - dsubmit:SetSize(bw, bh) - dsubmit:SetDisabled(true) - dsubmit:SetText(GetTranslation("xfer_send")) + dsubmit = vgui.Create("DButton", dform) + dsubmit:SetSize(bw, bh) + dsubmit:SetEnabled(false) + dsubmit:SetText(GetTranslation("xfer_send")) - --Add the help button. Change its text dynamically to match the situation. - dhelp = dform:Help("") + --Add the help button. Change its text dynamically to match the situation. + dhelp = dform:Help("") - local dpick = vgui.Create("DComboBox", dform) - dpick.OnSelect = function(s, idx, val, data) - if data then - selected_sid = data + local dpick = vgui.Create("DComboBox", dform) + dpick.OnSelect = function(s, idx, val, data) + if data then + selected_sid = data - --Upon selecting the player, determine if a transfer can be made to them. - UpdateTransferSubmitButton() - end - end + --Upon selecting the player, determine if a transfer can be made to them. + UpdateTransferSubmitButton() + end + end - dpick:SetWide(250) - dpick:SetSortItems(false) + dpick:SetWide(250) + dpick:SetSortItems(false) - -- fill combobox - local plys = player.GetAll() + -- fill combobox + local plys = player.GetAll() - table.sort(plys, function (a, b) - return a:IsInTeam(client) and not b:IsInTeam(client) - end) + table.sort(plys, function(a, b) + return a:IsInTeam(client) and not b:IsInTeam(client) + end) - for i = 1, #plys do - local ply = plys[i] - local sid = ply:SteamID64() + for i = 1, #plys do + local ply = plys[i] + local sid = ply:SteamID64() - --SteamID64() returns nil for bots on the client, and so credits can't be transferred to them. - --Transfers can be made to players who have died (as the sender may not know if they're alive), but can't be made to spectators who joined in the middle of a match. - if ply ~= client and (ply:IsTerror() or ply:IsDeadTerror()) and sid then - local choiceText = ply:Nick() + --SteamID64() returns nil for bots on the client, and so credits can't be transferred to them. + --Transfers can be made to players who have died (as the sender may not know if they're alive), but can't be made to spectators who joined in the middle of a match. + if ply ~= client and (ply:IsTerror() or ply:IsDeadTerror()) and sid then + local choiceText = ply:Nick() - if ply:IsInTeam(client) then - choiceText = choiceText .. " (" .. GetTranslation("xfer_team_indicator") .. ")" - end + if ply:IsInTeam(client) then + choiceText = choiceText .. " (" .. GetTranslation("xfer_team_indicator") .. ")" + end - dpick:AddChoice(choiceText, sid) - end - end + dpick:AddChoice(choiceText, sid) + end + end - -- select first player by default - if dpick:GetOptionText(1) then - dpick:ChooseOptionID(1) - end + -- select first player by default + if dpick:GetOptionText(1) then + dpick:ChooseOptionID(1) + end - dsubmit.DoClick = function(s) - if selected_sid then - RunConsoleCommand("ttt_transfer_credits", selected_sid, CREDITS_PER_XFER) - end - end + dsubmit.DoClick = function(s) + if selected_sid then + shop.TransferCredits(client, selected_sid, CREDITS_PER_XFER) + end + end - dform:AddItem(dpick) - dform:AddItem(dsubmit) + dform:AddItem(dpick) + dform:AddItem(dsubmit) - return dform + return dform end diff --git a/gamemodes/terrortown/gamemode/client/cl_voice.lua b/gamemodes/terrortown/gamemode/client/cl_voice.lua index 208ca76f1..c4a7abfe5 100644 --- a/gamemodes/terrortown/gamemode/client/cl_voice.lua +++ b/gamemodes/terrortown/gamemode/client/cl_voice.lua @@ -26,100 +26,165 @@ local MutedState g_VoicePanelList = nil -local function VoiceTryEnable() - local client = LocalPlayer() +--- +-- @realm client +-- stylua: ignore +local duck_spectator = CreateConVar("ttt2_voice_duck_spectator", "0", {FCVAR_ARCHIVE}) - --- - -- @realm client - if hook.Run("TTT2CanUseVoiceChat", client, false) == false then - return false - end +--- +-- @realm client +-- stylua: ignore +local duck_spectator_amount = CreateConVar("ttt2_voice_duck_spectator_amount", "0", {FCVAR_ARCHIVE}) - if not VOICE.IsSpeaking() and VOICE.CanSpeak() then - VOICE.isTeam = false - permissions.EnableVoiceChat(true) +--- +-- @realm client +-- stylua: ignore +local scaling_mode = CreateConVar("ttt2_voice_scaling", "linear", {FCVAR_ARCHIVE}) + +local function CreateVoiceTable() + if not sql.TableExists("ttt2_voice") then + local query = + "CREATE TABLE ttt2_voice (guid TEXT PRIMARY KEY, mute INTEGER DEFAULT 0, volume REAL DEFAULT 1)" + sql.Query(query) + end +end + +CreateVoiceTable() + +local function VoiceTryEnable() + if not VOICE.IsSpeaking() and VOICE.CanSpeak() and VOICE.CanEnable() then + VOICE.isTeam = false + permissions.EnableVoiceChat(true) - return true - end + return true + end - return false + return false end local function VoiceTryDisable() - if VOICE.IsSpeaking() and not VOICE.isTeam then - permissions.EnableVoiceChat(false) + if VOICE.IsSpeaking() and not VOICE.isTeam then + permissions.EnableVoiceChat(false) - return true - end + return true + end - return false + return false end local function VoiceTeamTryEnable() - local client = LocalPlayer() + if not VOICE.IsSpeaking() and VOICE.CanSpeak() and VOICE.CanTeamEnable() then + VOICE.isTeam = true - --- - -- @realm client - if hook.Run("TTT2CanUseVoiceChat", client, true) == false then - return false - end + permissions.EnableVoiceChat(true) - if not IsValid(client) then return false end + return true + end - local clientrd = client:GetSubRoleData() - local tm = client:GetTeam() + return false +end - if not VOICE.IsSpeaking() - and VOICE.CanSpeak() - and client:IsActive() - and tm ~= TEAM_NONE - and not TEAMS[tm].alone - and not clientrd.unknownTeam - and not clientrd.disabledTeamVoice - then - VOICE.isTeam = true +local function VoiceTeamTryDisable() + if VOICE.IsSpeaking() and VOICE.isTeam then + permissions.EnableVoiceChat(false) - permissions.EnableVoiceChat(true) + return true + end - return true - end + return false +end - return false +--- +-- Checks if a player can enable the team voice chat. +-- @return boolean Returns if the player is able to use the team voice chat +-- @realm client +function VOICE.CanTeamEnable() + local client = LocalPlayer() + + --- + -- @realm client + -- stylua: ignore + if hook.Run("TTT2CanUseVoiceChat", client, true) == false then + return false + end + + if not IsValid(client) then + return false + end + + local clientrd = client:GetSubRoleData() + local tm = client:GetTeam() + + if + client:IsActive() + and tm ~= TEAM_NONE + and not TEAMS[tm].alone + and not clientrd.unknownTeam + and not clientrd.disabledTeamVoice + then + return true + end end -local function VoiceTeamTryDisable() - if VOICE.IsSpeaking() and VOICE.isTeam then - permissions.EnableVoiceChat(false) +--- +-- Checks if a player can enable the global voice chat. +-- @return boolean Returns if the player is able to use the global voice chat +-- @realm client +function VOICE.CanEnable() + local client = LocalPlayer() - return true - end + --- + -- @realm client + -- stylua: ignore + if hook.Run("TTT2CanUseVoiceChat", client, false) == false then + return false + end - return false + return true end -- register a binding for the general voicechat -bind.Register("ttt2_voice", VoiceTryEnable, VoiceTryDisable, "header_bindings_ttt2", "label_bind_voice", input.GetKeyCode(input.LookupBinding("+voicerecord") or KEY_X)) +bind.Register( + "ttt2_voice", + VoiceTryEnable, + VoiceTryDisable, + "header_bindings_ttt2", + "label_bind_voice", + input.GetKeyCode(input.LookupBinding("+voicerecord") or KEY_X) +) -- register a binding for the team voicechat -bind.Register("ttt2_voice_team", VoiceTeamTryEnable, VoiceTeamTryDisable, "header_bindings_ttt2", "label_bind_voice_team", input.GetKeyCode(input.LookupBinding("+speed") or KEY_LSHIFT)) +bind.Register( + "ttt2_voice_team", + VoiceTeamTryEnable, + VoiceTeamTryDisable, + "header_bindings_ttt2", + "label_bind_voice_team", + KEY_T +) -- 255 at 100 -- 5 at 5000 local function VoiceNotifyThink(pnl) - local client = LocalPlayer() - - if not IsValid(pnl) or not IsValid(client) or not IsValid(pnl.ply) - or not GetGlobalBool("ttt_locational_voice", false) or pnl.ply:IsSpec() or pnl.ply == client - or client:IsActive() and pnl.ply:IsActive() and ( - client:IsInTeam(pnl.ply) - and not pnl.ply:GetSubRoleData().unknownTeam - and not pnl.ply:GetSubRoleData().disabledTeamVoice - and not client:GetSubRoleData().disabledTeamVoiceRecv - ) then return end - - local d = client:GetPos():Distance(pnl.ply:GetPos()) - - pnl:SetAlpha(math.max(-0.1 * d + 255, 15)) + local client = LocalPlayer() + + if + not IsValid(pnl) + or not IsValid(client) + or not IsValid(pnl.ply) + or not GetGlobalBool("ttt_locational_voice", false) + or pnl.ply:IsSpec() + or pnl.ply == client + or client:IsActive() + and pnl.ply:IsActive() + and (client:IsInTeam(pnl.ply) and not pnl.ply:GetSubRoleData().unknownTeam and not pnl.ply:GetSubRoleData().disabledTeamVoice and not client:GetSubRoleData().disabledTeamVoiceRecv) + then + return + end + + local d = client:GetPos():Distance(pnl.ply:GetPos()) + + pnl:SetAlpha(math.max(-0.1 * d + 255, 15)) end local PlayerVoicePanels = {} @@ -132,134 +197,180 @@ local PlayerVoicePanels = {} -- @ref https://wiki.facepunch.com/gmod/GM:PlayerStartVoice -- @local function GM:PlayerStartVoice(ply) - if not IsValid(ply) then return end - - local client = LocalPlayer() - - if not IsValid(g_VoicePanelList) or not IsValid(client) then return end - - -- There'd be an extra one if voice_loopback is on, so remove it. - GAMEMODE:PlayerEndVoice(ply, true) - - -- Tell server this is global - if client == ply then - local tm = client:GetTeam() - - local isGlobal = not VOICE.isTeam - client[tm .. "_gvoice"] = isGlobal - - net.Start("TTT2RoleGlobalVoice") - net.WriteBool(isGlobal) - net.SendToServer() - - VOICE.SetSpeaking(true) - end - - local pnl = g_VoicePanelList:Add("VoiceNotify") - pnl:Setup(ply) - pnl:Dock(TOP) - - local oldThink = pnl.Think - - pnl.Think = function(s) - oldThink(s) - - VoiceNotifyThink(s) - end - - local shade = Color(0, 0, 0, 150) - - -- TODO recreate all voice panels on HUD switch - local paintFn = function(s, w, h) - if not IsValid(s.ply) then return end - - draw.RoundedBox(4, 0, 0, w, h, s.Color) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, shade) - end - - if huds and HUDManager then - local hud = huds.GetStored(HUDManager.GetHUD()) - if hud then - paintFn = hud.VoicePaint or paintFn - end - end - - pnl.Paint = paintFn - - -- roles things - local tm = client:GetTeam() - local clrd = client:GetSubRoleData() - - if client:IsActive() and tm ~= TEAM_NONE and not clrd.unknownTeam and not clrd.disabledTeamVoice and not TEAMS[tm].alone then - if ply == client then - if not client[tm .. "_gvoice"] then - pnl.Color = TEAMS[tm].color - end - elseif ply:IsInTeam(client) and not (ply:GetSubRoleData().disabledTeamVoice or clrd.disabledTeamVoiceRecv) then - if not ply[tm .. "_gvoice"] then - pnl.Color = TEAMS[tm].color - end - end - end - - -- since detective (sub-) roles don't have their own team, they have a manual role color - -- handling here - if ply:IsActive() and ply:GetBaseRole() == ROLE_DETECTIVE then - pnl.Color = roles.DETECTIVE.color - end - - --- - -- @realm client - pnl.Color = hook.Run("TTT2ModifyVoiceChatColor", ply, pnl.Color) or pnl.Color - - PlayerVoicePanels[ply] = pnl - - local plyrd = ply:GetSubRoleData() - - -- run ear gesture - if not (ply:IsActive() and not plyrd.unknownTeam and not plyrd.disabledTeamVoice and not clrd.disabledTeamVoiceRecv) or (tm ~= TEAM_NONE and not TEAMS[tm].alone) and ply[tm .. "_gvoice"] then - ply:AnimPerformGesture(ACT_GMOD_IN_CHAT) - end + if not IsValid(ply) then + return + end + + local client = LocalPlayer() + + VOICE.UpdatePlayerVoiceVolume(ply) + + if not IsValid(g_VoicePanelList) or not IsValid(client) then + return + end + + -- There'd be an extra one if voice_loopback is on, so remove it. + GAMEMODE:PlayerEndVoice(ply, true) + + -- Tell server this is global + if client == ply then + local tm = client:GetTeam() + + local isGlobal = not VOICE.isTeam + client[tm .. "_gvoice"] = isGlobal + + net.Start("TTT2RoleGlobalVoice") + net.WriteBool(isGlobal) + net.SendToServer() + + VOICE.SetSpeaking(true) + end + + local pnl = g_VoicePanelList:Add("VoiceNotify") + pnl:Setup(ply) + pnl:Dock(TOP) + + local oldThink = pnl.Think + + pnl.Think = function(s) + oldThink(s) + + VoiceNotifyThink(s) + end + + local shade = Color(0, 0, 0, 150) + + -- TODO recreate all voice panels on HUD switch + local paintFn = function(s, w, h) + if not IsValid(s.ply) then + return + end + + draw.RoundedBox(4, 0, 0, w, h, s.Color) + draw.RoundedBox(4, 1, 1, w - 2, h - 2, shade) + end + + if huds and HUDManager then + local hud = huds.GetStored(HUDManager.GetHUD()) + if hud then + paintFn = hud.VoicePaint or paintFn + end + end + + pnl.Paint = paintFn + + -- roles things + local tm = client:GetTeam() + local clrd = client:GetSubRoleData() + + if + client:IsActive() + and tm ~= TEAM_NONE + and not clrd.unknownTeam + and not clrd.disabledTeamVoice + and not TEAMS[tm].alone + then + if ply == client then + if not client[tm .. "_gvoice"] then + pnl.Color = TEAMS[tm].color + end + elseif + ply:IsInTeam(client) + and not (ply:GetSubRoleData().disabledTeamVoice or clrd.disabledTeamVoiceRecv) + then + if not ply[tm .. "_gvoice"] then + pnl.Color = TEAMS[tm].color + end + end + end + + -- since detective (sub-) roles don't have their own team, they have a manual role color + -- handling here + if ply:IsActive() and ply:GetBaseRole() == ROLE_DETECTIVE then + pnl.Color = roles.DETECTIVE.color + end + + --- + -- @realm client + -- stylua: ignore + pnl.Color = hook.Run("TTT2ModifyVoiceChatColor", ply, pnl.Color) or pnl.Color + + PlayerVoicePanels[ply] = pnl + + local plyrd = ply:GetSubRoleData() + + -- run ear gesture + if + not ( + ply:IsActive() + and not plyrd.unknownTeam + and not plyrd.disabledTeamVoice + and not clrd.disabledTeamVoiceRecv + ) or (tm ~= TEAM_NONE and not TEAMS[tm].alone) and ply[tm .. "_gvoice"] + then + ply:AnimPerformGesture(ACT_GMOD_IN_CHAT) + end end local function ReceiveVoiceState() - local idx = net.ReadUInt(7) + 1 -- we -1 serverside - local isGlobal = net.ReadBit() == 1 + local idx = net.ReadUInt(7) + 1 -- we -1 serverside + local isGlobal = net.ReadBit() == 1 - -- prevent glitching due to chat starting/ending across round boundary - if GAMEMODE.round_state ~= ROUND_ACTIVE then return end + -- prevent glitching due to chat starting/ending across round boundary + if GAMEMODE.round_state ~= ROUND_ACTIVE then + return + end - local lply = LocalPlayer() - if not IsValid(lply) then return end + local lply = LocalPlayer() + if not IsValid(lply) then + return + end - local ply = player.GetByID(idx) + local ply = player.GetByID(idx) - if not IsValid(ply) or not ply.GetSubRoleData then return end + if not IsValid(ply) or not ply.GetSubRoleData then + return + end - local plyrd = ply:GetSubRoleData() + local plyrd = ply:GetSubRoleData() - if not ply:IsActive() or plyrd.unknownTeam or plyrd.disabledTeamVoice or lply:GetSubRoleData().disabledTeamVoiceRecv then return end + if + not ply:IsActive() + or plyrd.unknownTeam + or plyrd.disabledTeamVoice + or lply:GetSubRoleData().disabledTeamVoiceRecv + then + return + end - local tm = ply:GetTeam() + local tm = ply:GetTeam() - if tm == TEAM_NONE or TEAMS[tm].alone then return end + if tm == TEAM_NONE or TEAMS[tm].alone then + return + end - ply[tm .. "_gvoice"] = isGlobal + ply[tm .. "_gvoice"] = isGlobal - if not IsValid(PlayerVoicePanels[ply]) then return end + if not IsValid(PlayerVoicePanels[ply]) then + return + end - PlayerVoicePanels[ply].Color = isGlobal and VP_GREEN or (ply:GetRoleColor() or VP_RED) + PlayerVoicePanels[ply].Color = isGlobal and VP_GREEN or (ply:GetRoleColor() or VP_RED) end net.Receive("TTT_RoleVoiceState", ReceiveVoiceState) local function VoiceClean() - if not PlayerVoicePanels then return end + if not PlayerVoicePanels then + return + end - for ply, pnl in pairs(PlayerVoicePanels) do - if IsValid(pnl) and IsValid(ply) then continue end + for ply, pnl in pairs(PlayerVoicePanels) do + if IsValid(pnl) and IsValid(ply) then + continue + end - GAMEMODE:PlayerEndVoice(ply) - end + GAMEMODE:PlayerEndVoice(ply) + end end timer.Create("VoiceClean", 10, 0, VoiceClean) @@ -272,56 +383,58 @@ timer.Create("VoiceClean", 10, 0, VoiceClean) -- @ref https://wiki.facepunch.com/gmod/GM:PlayerEndVoice -- @local function GM:PlayerEndVoice(ply, no_reset) - if IsValid(PlayerVoicePanels[ply]) then - PlayerVoicePanels[ply]:Remove() + if IsValid(PlayerVoicePanels[ply]) then + PlayerVoicePanels[ply]:Remove() - PlayerVoicePanels[ply] = nil - end + PlayerVoicePanels[ply] = nil + end - if IsValid(ply) and not no_reset then - local tm = ply:GetTeam() + if IsValid(ply) and not no_reset then + local tm = ply:GetTeam() - if tm ~= TEAM_NONE and not TEAMS[tm].alone then - ply[tm .. "_gvoice"] = false - end - end + if tm ~= TEAM_NONE and not TEAMS[tm].alone then + ply[tm .. "_gvoice"] = false + end + end - if ply == LocalPlayer() then - VOICE.SetSpeaking(false) - end + if ply == LocalPlayer() then + VOICE.SetSpeaking(false) + end end local function CreateVoiceVGUI() - g_VoicePanelList = vgui.Create("DPanel") - g_VoicePanelList:ParentToHUD() - g_VoicePanelList:SetPos(25, 25) - g_VoicePanelList:SetSize(200, ScrH() - 200) - g_VoicePanelList:SetPaintBackground(false) - - MutedState = vgui.Create("DLabel") - MutedState:SetPos(ScrW() - 200, ScrH() - 50) - MutedState:SetSize(200, 50) - MutedState:SetFont("Trebuchet18") - MutedState:SetText("") - MutedState:SetTextColor(Color(240, 240, 240, 250)) - MutedState:SetVisible(false) + g_VoicePanelList = vgui.Create("DPanel") + g_VoicePanelList:ParentToHUD() + g_VoicePanelList:SetPos(25, 25) + g_VoicePanelList:SetSize(200, ScrH() - 200) + g_VoicePanelList:SetPaintBackground(false) + + MutedState = vgui.Create("DLabel") + MutedState:SetPos(ScrW() - 200, ScrH() - 50) + MutedState:SetSize(200, 50) + MutedState:SetFont("Trebuchet18") + MutedState:SetText("") + MutedState:SetTextColor(Color(240, 240, 240, 250)) + MutedState:SetVisible(false) end hook.Add("InitPostEntity", "CreateVoiceVGUI", CreateVoiceVGUI) --local MuteStates = {MUTE_NONE, MUTE_TERROR, MUTE_ALL, MUTE_SPEC} local MuteText = { - [MUTE_NONE] = "", - [MUTE_TERROR] = "mute_living", - [MUTE_ALL] = "mute_all", - [MUTE_SPEC] = "mute_specs" + [MUTE_NONE] = "", + [MUTE_TERROR] = "mute_living", + [MUTE_ALL] = "mute_all", + [MUTE_SPEC] = "mute_specs", } local function SetMuteState(state) - if not MutedState then return end + if not MutedState then + return + end - MutedState:SetText(string.upper(GetTranslation(MuteText[state]))) - MutedState:SetVisible(state ~= MUTE_NONE) + MutedState:SetText(string.upper(GetTranslation(MuteText[state]))) + MutedState:SetVisible(state ~= MUTE_NONE) end local mute_state = MUTE_NONE @@ -332,63 +445,205 @@ local mute_state = MUTE_NONE -- @return number the new mute_state -- @realm client function VOICE.CycleMuteState(force_state) - mute_state = force_state or next(MuteText, mute_state) + mute_state = force_state or next(MuteText, mute_state) - if not mute_state then - mute_state = MUTE_NONE - end + if not mute_state then + mute_state = MUTE_NONE + end - SetMuteState(mute_state) + SetMuteState(mute_state) - return mute_state + return mute_state end VOICE.battery_max = 100 VOICE.battery_min = 10 +--- +-- Scales a linear volume into a Power 4 value. +-- @param number volume +-- @realm client +function VOICE.LinearToPower4(volume) + return math.Clamp(math.pow(volume, 4), 0, 1) +end + +--- +-- Scales a linear volume into a Log value. +-- @param number volume +-- @realm client +function VOICE.LinearToLog(volume) + local rolloff_cutoff = 0.1 + local log_a = math.pow(1 / 10, 60 / 20) + local log_b = math.log(1 / log_a) + + local vol = log_a * math.exp(log_b * volume) + if volume < rolloff_cutoff then + local log_rolloff = 10 * log_a * math.exp(log_b * rolloff_cutoff) + vol = volume * log_rolloff + end + + return math.Clamp(vol, 0, 1) +end + +--- +-- Passes along the input linear volume value. +-- @param number volume +-- @realm client +function VOICE.LinearToLinear(volume) + return volume +end + +VOICE.ScalingFunctions = { + power4 = VOICE.LinearToPower4, + log = VOICE.LinearToLog, + linear = VOICE.LinearToLinear, +} + +VOICE.GetScalingFunctions = function() + local opts = {} + for mode in pairs(VOICE.ScalingFunctions) do + opts[#opts + 1] = { + title = LANG.TryTranslation("label_voice_scaling_mode_" .. mode), + value = mode, + select = mode == scaling_mode:GetString(), + } + end + return opts +end + +--- +-- Gets the stored volume for the player's voice. +-- @param Player ply +-- @realm client +function VOICE.GetPreferredPlayerVoiceVolume(ply) + local val = sql.QueryValue( + "SELECT volume FROM ttt2_voice WHERE guid = " .. SQLStr(ply:SteamID64()) .. " LIMIT 1" + ) + if val == nil then + return 1 + end + return tonumber(val) +end + +--- +-- Sets the stored volume for the player's voice. +-- @param Player ply +-- @param number volume +-- @realm client +function VOICE.SetPreferredPlayerVoiceVolume(ply, volume) + return sql.Query( + "REPLACE INTO ttt2_voice ( guid, volume ) VALUES ( " + .. SQLStr(ply:SteamID64()) + .. ", " + .. SQLStr(volume) + .. " )" + ) +end + +--- +-- Gets the stored mute state for the player's voice. +-- @param Player ply +-- @realm client +function VOICE.GetPreferredPlayerVoiceMuted(ply) + local val = sql.QueryValue( + "SELECT mute FROM ttt2_voice WHERE guid = " .. SQLStr(ply:SteamID64()) .. " LIMIT 1" + ) + if val == nil then + return false + end + return tobool(val) +end + +--- +-- Sets the stored mute state for the player's voice. +-- @param Player ply +-- @param boolean is_muted +-- @realm client +function VOICE.SetPreferredPlayerVoiceMuted(ply, is_muted) + return sql.Query( + "REPLACE INTO ttt2_voice ( guid, mute ) VALUES ( " + .. SQLStr(ply:SteamID64()) + .. ", " + .. SQLStr(is_muted and 1 or 0) + .. " )" + ) +end + +--- +-- Refreshes and applies the preferred volume and mute state for a player's voice. +-- @param Player ply +-- @realm client +function VOICE.UpdatePlayerVoiceVolume(ply) + local mute = VOICE.GetPreferredPlayerVoiceMuted(ply) + if ply.SetMute then + ply:SetMute(mute) + end + + local vol = VOICE.GetPreferredPlayerVoiceVolume(ply) + if duck_spectator:GetBool() and ply:IsSpec() then + vol = vol * (1 - duck_spectator_amount:GetFloat()) + end + local out_vol = vol + + local func = VOICE.ScalingFunctions[scaling_mode:GetString()] + if isfunction(func) then + out_vol = func(vol) + end + + ply:SetVoiceVolumeScale(out_vol) + + return out_vol, mute +end + --- -- Initializes the voice battery -- @realm client function VOICE.InitBattery() - LocalPlayer().voice_battery = VOICE.battery_max + LocalPlayer().voice_battery = VOICE.battery_max end local function GetRechargeRate() - local r = GetGlobalFloat("ttt_voice_drain_recharge", 0.05) + local r = GetGlobalFloat("ttt_voice_drain_recharge", 0.05) - if LocalPlayer().voice_battery < VOICE.battery_min then - r = r * 0.5 - end + if LocalPlayer().voice_battery < VOICE.battery_min then + r = r * 0.5 + end - return r + return r end local function GetDrainRate() - local ply = LocalPlayer() - - if not IsValid(ply) or ply:IsSpec() or not GetGlobalBool("ttt_voice_drain", false) or GetRoundState() ~= ROUND_ACTIVE then - return 0 - end - - local plyRoleData = ply:GetSubRoleData() - - if ply:IsAdmin() or (plyRoleData.isPublicRole and plyRoleData.isPolicingRole) then - return GetGlobalFloat("ttt_voice_drain_admin", 0) - else - return GetGlobalFloat("ttt_voice_drain_normal", 0) - end + local ply = LocalPlayer() + + if + not IsValid(ply) + or ply:IsSpec() + or not GetGlobalBool("ttt_voice_drain", false) + or GetRoundState() ~= ROUND_ACTIVE + then + return 0 + end + + local plyRoleData = ply:GetSubRoleData() + + if ply:IsAdmin() or (plyRoleData.isPublicRole and plyRoleData.isPolicingRole) then + return GetGlobalFloat("ttt_voice_drain_admin", 0) + else + return GetGlobalFloat("ttt_voice_drain_normal", 0) + end end local function IsRoleChatting(ply) - local plyTeam = ply:GetTeam() - local plyRoleData = ply:GetSubRoleData() - - return ply:IsActive() - and not plyRoleData.unknownTeam - and not plyRoleData.disabledTeamVoice - and not LocalPlayer():GetSubRoleData().disabledTeamVoiceRecv - and plyTeam ~= TEAM_NONE and not TEAMS[plyTeam].alone - and not ply[plyTeam .. "_gvoice"] + local plyTeam = ply:GetTeam() + local plyRoleData = ply:GetSubRoleData() + + return ply:IsActive() + and not plyRoleData.unknownTeam + and not plyRoleData.disabledTeamVoice + and not LocalPlayer():GetSubRoleData().disabledTeamVoiceRecv + and plyTeam ~= TEAM_NONE + and not TEAMS[plyTeam].alone + and not ply[plyTeam .. "_gvoice"] end --- @@ -397,21 +652,23 @@ end -- @realm client -- @internal function VOICE.Tick() - if not GetGlobalBool("ttt_voice_drain", false) then return end + if not GetGlobalBool("ttt_voice_drain", false) then + return + end - local client = LocalPlayer() + local client = LocalPlayer() - if VOICE.IsSpeaking() and not IsRoleChatting(client) then - client.voice_battery = client.voice_battery - GetDrainRate() + if VOICE.IsSpeaking() and not IsRoleChatting(client) then + client.voice_battery = client.voice_battery - GetDrainRate() - if not VOICE.CanSpeak() then - client.voice_battery = 0 + if not VOICE.CanSpeak() then + client.voice_battery = 0 - permissions.EnableVoiceChat(false) - end - elseif client.voice_battery < VOICE.battery_max then - client.voice_battery = client.voice_battery + GetRechargeRate() - end + permissions.EnableVoiceChat(false) + end + elseif client.voice_battery < VOICE.battery_max then + client.voice_battery = client.voice_battery + GetRechargeRate() + end end --- @@ -420,7 +677,7 @@ end -- @return boolean -- @realm client function VOICE.IsSpeaking() - return LocalPlayer().speaking + return LocalPlayer().speaking end --- @@ -428,7 +685,7 @@ end -- @param boolean state -- @realm client function VOICE.SetSpeaking(state) - LocalPlayer().speaking = state + LocalPlayer().speaking = state end --- @@ -436,17 +693,17 @@ end -- @return boolean -- @realm client function VOICE.CanSpeak() - if not GetGlobalBool("sv_voiceenable", true) then - return false - end + if not GetGlobalBool("sv_voiceenable", true) then + return false + end - if not GetGlobalBool("ttt_voice_drain", false) then - return true - end + if not GetGlobalBool("ttt_voice_drain", false) then + return true + end - local client = LocalPlayer() + local client = LocalPlayer() - return client.voice_battery > VOICE.battery_min or IsRoleChatting(client) + return client.voice_battery > VOICE.battery_min or IsRoleChatting(client) end local speaker = surface.GetTextureID("voice/icntlk_sv") @@ -458,31 +715,33 @@ local speaker = surface.GetTextureID("voice/icntlk_sv") -- @realm client -- @internal function VOICE.Draw(client) - local b = client.voice_battery + local b = client.voice_battery - if not b or not VOICE.battery_max or b >= VOICE.battery_max then return end + if not b or not VOICE.battery_max or b >= VOICE.battery_max then + return + end - local x, y = 25, 10 - local w, h = 200, 6 + local x, y = 25, 10 + local w, h = 200, 6 - if b < VOICE.battery_min and CurTime() % 0.2 < 0.1 then - surface.SetDrawColor(200, 0, 0, 155) - else - surface.SetDrawColor(0, 200, 0, 255) - end + if b < VOICE.battery_min and CurTime() % 0.2 < 0.1 then + surface.SetDrawColor(200, 0, 0, 155) + else + surface.SetDrawColor(0, 200, 0, 255) + end - surface.DrawOutlinedRect(x, y, w, h) + surface.DrawOutlinedRect(x, y, w, h) - surface.SetTexture(speaker) - surface.DrawTexturedRect(5, 5, 16, 16) + surface.SetTexture(speaker) + surface.DrawTexturedRect(5, 5, 16, 16) - x = x + 1 - y = y + 1 - w = w - 2 - h = h - 2 + x = x + 1 + y = y + 1 + w = w - 2 + h = h - 2 - surface.SetDrawColor(0, 200, 0, 150) - surface.DrawRect(x, y, w * math.Clamp((client.voice_battery - 10) / 90, 0, 1), h) + surface.SetDrawColor(0, 200, 0, 150) + surface.DrawRect(x, y, w * math.Clamp((client.voice_battery - 10) / 90, 0, 1), h) end --- @@ -493,6 +752,4 @@ end -- @return Color The new and modified color -- @hook -- @realm client -function GM:TTT2ModifyVoiceChatColor(ply, clr) - -end +function GM:TTT2ModifyVoiceChatColor(ply, clr) end diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/default_skin.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/default_skin.lua index 3ce064a79..1481bc33a 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/default_skin.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/default_skin.lua @@ -18,11 +18,12 @@ local colorCardInheritAdded = Color(25, 190, 175) local colorCardInheritRemoved = Color(185, 45, 25) local SKIN = { - Name = "ttt2_default" + Name = "ttt2_default", } local TryT = LANG.TryTranslation local ParT = LANG.GetParamTranslation +local DynT = LANG.GetDynamicTranslation local mathRound = math.Round @@ -61,54 +62,108 @@ local colors = {} local sizes = {} -- register fonts -surface.CreateAdvancedFont("DermaTTT2Title", {font = "Trebuchet24", size = 26, weight = 300}) -surface.CreateAdvancedFont("DermaTTT2TitleSmall", {font = "Trebuchet24", size = 18, weight = 600}) -surface.CreateAdvancedFont("DermaTTT2MenuButtonTitle", {font = "Trebuchet24", size = 22, weight = 300}) -surface.CreateAdvancedFont("DermaTTT2MenuButtonDescription", {font = "Trebuchet24", size = 14, weight = 300}) -surface.CreateAdvancedFont("DermaTTT2SubMenuButtonTitle", {font = "Trebuchet24", size = 18, weight = 600}) -surface.CreateAdvancedFont("DermaTTT2Button", {font = "Trebuchet24", size = 14, weight = 600}) -surface.CreateAdvancedFont("DermaTTT2CatHeader", {font = "Trebuchet24", size = 16, weight = 900}) -surface.CreateAdvancedFont("DermaTTT2TextSmall", {font = "Trebuchet24", size = 12, weight = 300}) -surface.CreateAdvancedFont("DermaTTT2Text", {font = "Trebuchet24", size = 16, weight = 300}) -surface.CreateAdvancedFont("DermaTTT2TextLarge", {font = "Trebuchet24", size = 18, weight = 300}) -surface.CreateAdvancedFont("DermaTTT2TextLarger", {font = "Trebuchet24", size = 20, weight = 900}) -surface.CreateAdvancedFont("DermaTTT2TextLargest", {font = "Trebuchet24", size = 24, weight = 900}) -surface.CreateAdvancedFont("DermaTTT2TextHuge", {font = "Trebuchet24", size = 72, weight = 900}) +surface.CreateAdvancedFont( + "DermaTTT2Title", + { font = "Tahoma", size = 26, weight = 300, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2TitleSmall", + { font = "Tahoma", size = 18, weight = 600, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2MenuButtonTitle", + { font = "Tahoma", size = 22, weight = 300, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2MenuButtonDescription", + { font = "Tahoma", size = 14, weight = 300, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2SubMenuButtonTitle", + { font = "Tahoma", size = 18, weight = 600, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2MenuButtonDescription", + { font = "Tahoma", size = 14, weight = 300, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2SubMenuButtonTitle", + { font = "Tahoma", size = 18, weight = 600, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2Button", + { font = "Tahoma", size = 14, weight = 600, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2CatHeader", + { font = "Tahoma", size = 16, weight = 900, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2TextSmall", + { font = "Tahoma", size = 12, weight = 300, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2Text", + { font = "Tahoma", size = 16, weight = 300, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2TextLarge", + { font = "Tahoma", size = 18, weight = 300, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2TextLarger", + { font = "Tahoma", size = 20, weight = 900, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2TextLargest", + { font = "Tahoma", size = 24, weight = 900, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2TextHuge", + { font = "Tahoma", size = 72, weight = 900, extended = true } +) +surface.CreateAdvancedFont( + "DermaTTT2SmallBold", + { font = "Tahoma", size = 14, weight = 900, extended = true } +) --- -- Updates the @{SKIN} -- @realm client function SKIN:UpdatedVSkin() - colors = { - background = vskinGetBackgroundColor(), - accent = vskinGetAccentColor(), - accentHover = utilGetHoverColor(vskinGetAccentColor()), - accentActive = utilGetActiveColor(vskinGetAccentColor()), - accentText = utilGetDefaultColor(vskinGetAccentColor()), - accentDark = vskinGetDarkAccentColor(), - accentDarkHover = utilGetHoverColor(vskinGetDarkAccentColor()), - accentDarkActive = utilGetActiveColor(vskinGetDarkAccentColor()), - sliderInactive = utilGetChangedColor(vskinGetBackgroundColor(), 75), - shadow = vskinGetShadowColor(), - titleText = vskinGetTitleTextColor(), - default = utilGetDefaultColor(vskinGetBackgroundColor()), - content = utilGetChangedColor(vskinGetBackgroundColor(), 30), - handle = utilGetChangedColor(vskinGetBackgroundColor(), 15), - settingsBox = utilGetChangedColor(vskinGetBackgroundColor(), 150), - helpBox = utilGetChangedColor(vskinGetBackgroundColor(), 20), - helpBar = utilGetChangedColor(vskinGetBackgroundColor(), 80), - helpText = utilGetChangedColor(utilGetDefaultColor(utilGetChangedColor(vskinGetBackgroundColor(), 20)), 40), - settingsText = utilGetDefaultColor(utilGetChangedColor(vskinGetBackgroundColor(), 150)), - scrollBar = vskinGetScrollbarColor(), - scrollBarActive = utilColorDarken(vskinGetScrollbarColor(), 5) - } - - sizes = { - shadow = vskinGetShadowSize(), - header = vskinGetHeaderHeight(), - border = vskinGetBorderSize(), - cornerRadius = vskinGetCornerRadius() - } + colors = { + background = vskinGetBackgroundColor(), + accent = vskinGetAccentColor(), + accentHover = utilGetHoverColor(vskinGetAccentColor()), + accentActive = utilGetActiveColor(vskinGetAccentColor()), + accentText = utilGetDefaultColor(vskinGetAccentColor()), + accentDark = vskinGetDarkAccentColor(), + accentDarkHover = utilGetHoverColor(vskinGetDarkAccentColor()), + accentDarkActive = utilGetActiveColor(vskinGetDarkAccentColor()), + sliderInactive = utilGetChangedColor(vskinGetBackgroundColor(), 75), + shadow = vskinGetShadowColor(), + titleText = vskinGetTitleTextColor(), + default = utilGetDefaultColor(vskinGetBackgroundColor()), + content = utilGetChangedColor(vskinGetBackgroundColor(), 30), + handle = utilGetChangedColor(vskinGetBackgroundColor(), 15), + settingsBox = utilGetChangedColor(vskinGetBackgroundColor(), 150), + helpBox = utilGetChangedColor(vskinGetBackgroundColor(), 20), + helpBar = utilGetChangedColor(vskinGetBackgroundColor(), 80), + helpText = utilGetChangedColor( + utilGetDefaultColor(utilGetChangedColor(vskinGetBackgroundColor(), 20)), + 40 + ), + settingsText = utilGetDefaultColor(utilGetChangedColor(vskinGetBackgroundColor(), 150)), + scrollBar = vskinGetScrollbarColor(), + scrollBarActive = utilColorDarken(vskinGetScrollbarColor(), 5), + } + + sizes = { + shadow = vskinGetShadowSize(), + header = vskinGetHeaderHeight(), + border = vskinGetBorderSize(), + cornerRadius = vskinGetCornerRadius(), + } end --- @@ -118,20 +173,45 @@ end -- @param number h -- @realm client function SKIN:PaintFrameTTT2(panel, w, h) - if panel:GetPaintShadow() then - DisableClipping(true) - drawRoundedBox(sizes.shadow, -sizes.shadow, -sizes.shadow, w + 2 * sizes.shadow, h + 2 * sizes.shadow, colors.shadow) - DisableClipping(false) - end - - -- draw main panel box - drawBox(0, 0, w, h, colors.background) - - -- draw panel header area - drawBox(0, 0, w, sizes.header, colors.accent) - drawBox(0, sizes.header, w, sizes.border, colors.accentDark) - - drawShadowedText(TryT(panel:GetTitle()), panel:GetTitleFont(), 0.5 * w, 0.5 * sizes.header, colors.titleText, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1) + if panel:GetPaintShadow() then + DisableClipping(true) + drawRoundedBox( + sizes.shadow, + -sizes.shadow, + -sizes.shadow, + w + 2 * sizes.shadow, + h + 2 * sizes.shadow, + colors.shadow + ) + DisableClipping(false) + end + + -- draw main panel box + drawBox(0, 0, w, h, colors.background) + + -- draw panel header area + drawBox(0, 0, w, sizes.header, colors.accent) + drawBox(0, sizes.header, w, sizes.border, colors.accentDark) + + local title = panel:GetTitle() + local text = "" + + if istable(title) then + text = ParT(title.body, title.params) + else + text = TryT(title) + end + + drawShadowedText( + text, + panel:GetTitleFont(), + 0.5 * w, + 0.5 * sizes.header, + colors.titleText, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + 1 + ) end --- @@ -140,9 +220,7 @@ end -- @param number w -- @param number h -- @realm client -function SKIN:PaintPanel(panel, w, h) - -end +function SKIN:PaintPanel(panel, w, h) end --- -- @param Panel panel @@ -150,8 +228,8 @@ end -- @param number h -- @realm client function SKIN:PaintNavPanelTTT2(panel, w, h) - local _, _, rightPad = panel:GetDockPadding() - drawBox(w - rightPad, 0, rightPad, h, ColorAlpha(colors.default, 200)) + local _, _, rightPad = panel:GetDockPadding() + drawBox(w - rightPad, 0, rightPad, h, ColorAlpha(colors.default, 200)) end --- @@ -160,7 +238,7 @@ end -- @param number h -- @realm client function SKIN:PaintButtonPanelTTT2(panel, w, h) - drawBox(0, 0, w, 1, ColorAlpha(colors.default, 200)) + drawBox(0, 0, w, 1, ColorAlpha(colors.default, 200)) end --- @@ -169,7 +247,7 @@ end -- @param number h -- @realm client function SKIN:PaintContentPanelTTT2(panel, w, h) - drawBox(0, 0, w, h, colors.content) + drawBox(0, 0, w, h, colors.content) end --- @@ -178,26 +256,36 @@ end -- @param number h -- @realm client function SKIN:PaintWindowCloseButton(panel, w, h) - if not panel.m_bBackground then return end - - local colorBackground = colors.accent - local colorText = ColorAlpha(colors.accentText, 150) - local shift = 0 - local padding = 15 - - if not panel:IsEnabled() then - colorText = ColorAlpha(colors.accentText, 70) - elseif panel.Depressed or panel:IsSelected() then - colorBackground = colors.accentActive - colorText = ColorAlpha(colors.accentText, 200) - shift = 1 - elseif panel.Hovered then - colorBackground = colors.accentHover - colorText = colors.accentText - end - - drawBox(0, 0, w, h, colorBackground) - drawFilteredShadowedTexture(padding, padding + shift, w - 2 * padding, h - 2 * padding, materialClose, colorText.a, colorText) + if not panel.m_bBackground then + return + end + + local colorBackground = colors.accent + local colorText = ColorAlpha(colors.accentText, 150) + local shift = 0 + local padding = 15 + + if not panel:IsEnabled() then + colorText = ColorAlpha(colors.accentText, 70) + elseif panel.Depressed or panel:IsSelected() then + colorBackground = colors.accentActive + colorText = ColorAlpha(colors.accentText, 200) + shift = 1 + elseif panel.Hovered then + colorBackground = colors.accentHover + colorText = colors.accentText + end + + drawBox(0, 0, w, h, colorBackground) + drawFilteredShadowedTexture( + padding, + padding + shift, + w - 2 * padding, + h - 2 * padding, + materialClose, + colorText.a, + colorText + ) end --- @@ -206,37 +294,47 @@ end -- @param number h -- @realm client function SKIN:PaintWindowBackButton(panel, w, h) - if not panel.m_bBackground then return end - - local colorBackground = colors.accent - local colorText = ColorAlpha(colors.accentText, 150) - local shift = 0 - local padding_w = 10 - local padding_h = 15 - - if not panel:IsEnabled() then - colorText = ColorAlpha(colors.accentText, 70) - elseif panel.Depressed or panel:IsSelected() then - colorBackground = colors.accentActive - colorText = ColorAlpha(colors.accentText, 200) - shift = 1 - elseif panel.Hovered then - colorBackground = colors.accentHover - colorText = colors.accentText - end - - drawBox(0, 0, w, h, colorBackground) - drawFilteredShadowedTexture(padding_w, padding_h + shift, h - 2 * padding_h, h - 2 * padding_h, materialBack, colorText.a, colorText) - drawShadowedText( - TryT("button_menu_back"), - "DermaTTT2TitleSmall", - h - padding_w, - 0.5 * h + shift, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER, - 1 - ) + if not panel.m_bBackground then + return + end + + local colorBackground = colors.accent + local colorText = ColorAlpha(colors.accentText, 150) + local shift = 0 + local padding_w = 10 + local padding_h = 15 + + if not panel:IsEnabled() then + colorText = ColorAlpha(colors.accentText, 70) + elseif panel.Depressed or panel:IsSelected() then + colorBackground = colors.accentActive + colorText = ColorAlpha(colors.accentText, 200) + shift = 1 + elseif panel.Hovered then + colorBackground = colors.accentHover + colorText = colors.accentText + end + + drawBox(0, 0, w, h, colorBackground) + drawFilteredShadowedTexture( + padding_w, + padding_h + shift, + h - 2 * padding_h, + h - 2 * padding_h, + materialBack, + colorText.a, + colorText + ) + drawShadowedText( + TryT("button_menu_back"), + "DermaTTT2TitleSmall", + h - padding_w, + 0.5 * h + shift, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + 1 + ) end --- @@ -245,20 +343,20 @@ end -- @param number h -- @realm client function SKIN:PaintScrollBarGrip(panel, w, h) - local colorScrollbar = colors.scrollBar - local posX = 4 - local sizeX = w - 8 - - if panel.Depressed then - colorScrollbar = colors.scrollBarActive - posX = 0 - sizeX = w - elseif panel.Hovered then - posX = 0 - sizeX = w - end - - drawRoundedBox(sizes.cornerRadius, posX, 0, sizeX, h, colorScrollbar) + local colorScrollbar = colors.scrollBar + local posX = 4 + local sizeX = w - 8 + + if panel.Depressed then + colorScrollbar = colors.scrollBarActive + posX = 0 + sizeX = w + elseif panel.Hovered then + posX = 0 + sizeX = w + end + + drawRoundedBox(sizes.cornerRadius, posX, 0, sizeX, h, colorScrollbar) end --- @@ -267,57 +365,71 @@ end -- @param number h -- @realm client function SKIN:PaintMenuButtonTTT2(panel, w, h) - if not panel.m_bBackground then return end - - local colorOutline = utilGetChangedColor(colors.default, 170) - local colorDescription = utilGetChangedColor(colors.default, 145) - local colorText = utilGetChangedColor(colors.default, 65) - local colorIcon = utilGetChangedColor(colors.default, 170) - local shift = 0 - local paddingText = 10 - local paddingIcon = 25 - - if panel.Depressed or panel:IsSelected() or panel:GetToggle() then - colorOutline = utilGetChangedColor(colors.default, 135) - colorDescription = utilGetChangedColor(colors.default, 135) - colorIcon = utilGetChangedColor(colors.default, 160) - colorText = utilGetChangedColor(colors.default, 50) - shift = 1 - elseif panel.Hovered then - colorOutline = utilGetChangedColor(colors.default, 135) - colorDescription = utilGetChangedColor(colors.default, 135) - colorIcon = utilGetChangedColor(colors.default, 160) - colorText = utilGetChangedColor(colors.default, 50) - end - - drawOutlinedBox(0, 0, w, h, 1, colorOutline) - drawFilteredTexture(paddingIcon, paddingIcon + shift, h - 2 * paddingIcon, h - 2 * paddingIcon, panel:GetImage(), colorIcon.a, colorIcon) - drawSimpleText( - TryT(panel:GetTitle()), - panel:GetTitleFont(), - h, - paddingText + shift, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_TOP - ) - - local desc_wrapped = drawGetWrappedText(TryT(panel:GetDescription()), w - h - 2 * paddingText, panel:GetDescriptionFont()) - local line_pos = 35 - - for i = 1, #desc_wrapped do - drawSimpleText( - desc_wrapped[i], - panel:GetDescriptionFont(), - h, - line_pos + paddingText + shift, - colorDescription, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_TOP - ) - - line_pos = line_pos + 20 - end + if not panel.m_bBackground then + return + end + + local colorOutline = utilGetChangedColor(colors.default, 170) + local colorDescription = utilGetChangedColor(colors.default, 145) + local colorText = utilGetChangedColor(colors.default, 65) + local colorIcon = utilGetChangedColor(colors.default, 170) + local shift = 0 + local paddingText = 10 + local paddingIcon = 25 + + if panel.Depressed or panel:IsSelected() or panel:GetToggle() then + colorOutline = utilGetChangedColor(colors.default, 135) + colorDescription = utilGetChangedColor(colors.default, 135) + colorIcon = utilGetChangedColor(colors.default, 160) + colorText = utilGetChangedColor(colors.default, 50) + shift = 1 + elseif panel.Hovered then + colorOutline = utilGetChangedColor(colors.default, 135) + colorDescription = utilGetChangedColor(colors.default, 135) + colorIcon = utilGetChangedColor(colors.default, 160) + colorText = utilGetChangedColor(colors.default, 50) + end + + drawOutlinedBox(0, 0, w, h, 1, colorOutline) + drawFilteredTexture( + paddingIcon, + paddingIcon + shift, + h - 2 * paddingIcon, + h - 2 * paddingIcon, + panel:GetImage(), + colorIcon.a, + colorIcon + ) + drawSimpleText( + TryT(panel:GetTitle()), + panel:GetTitleFont(), + h, + paddingText + shift, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + + local desc_wrapped = drawGetWrappedText( + TryT(panel:GetDescription()), + w - h - 2 * paddingText, + panel:GetDescriptionFont() + ) + local line_pos = 35 + + for i = 1, #desc_wrapped do + drawSimpleText( + desc_wrapped[i], + panel:GetDescriptionFont(), + h, + line_pos + paddingText + shift, + colorDescription, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + + line_pos = line_pos + 20 + end end --- @@ -326,50 +438,77 @@ end -- @param number h -- @realm client function SKIN:PaintSubMenuButtonTTT2(panel, w, h) - if not panel.m_bBackground then return end - - local colorBackground = colors.background - local colorBar = colors.background - local colorText = utilGetChangedColor(colors.default, 75) - local shift = 0 - local pad = mathRound(0.3 * h) - local hasIcon = panel:HasIcon() - local isIconFullSize = panel:IsIconFullSize() - local padIcon = isIconFullSize and 0 or pad - local iconAlpha = isIconFullSize and 255 or colorText.a - local sizeIcon = h - 2 * padIcon - - if panel.Depressed or panel:IsSelected() or panel:GetToggle() then - colorBackground = utilGetActiveColor(ColorAlpha(colors.accent, 50)) - colorBar = colors.accentActive - colorText = utilGetActiveColor(utilGetChangedColor(colors.default, 25)) - shift = 1 - elseif panel.Hovered then - colorBackground = utilGetHoverColor(ColorAlpha(colors.accent, 50)) - colorBar = colors.accentHover - colorText = utilGetHoverColor(utilGetChangedColor(colors.default, 75)) - elseif panel:IsActive() then - colorBackground = utilGetHoverColor(ColorAlpha(colors.accent, 50)) - colorBar = colors.accentHover - colorText = utilGetHoverColor(utilGetChangedColor(colors.default, 75)) - end - - drawBox(0, 0, sizes.border, h, colorBar) - drawBox(sizes.border, 0, w - sizes.border, h, colorBackground) - - if hasIcon then - drawFilteredShadowedTexture(pad + sizes.border, padIcon + shift, sizeIcon, sizeIcon, panel:GetIcon(), iconAlpha, colorText) - end - - drawSimpleText( - TryT(panel:GetTitle()), - panel:GetTitleFont(), - sizes.border + pad + (hasIcon and (sizeIcon + pad) or pad), - 0.5 * h + shift, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + if not panel.m_bBackground then + return + end + + local colorBackground = colors.background + local colorBar = colors.background + local colorText = utilGetChangedColor(colors.default, 75) + local colorIcon = utilGetChangedColor(COLOR_WHITE, 32) + local shift = 0 + local pad = mathRound(0.3 * h) + local hasIcon = panel:HasIcon() + local isIconFullSize = panel:IsIconFullSize() + local padIcon = isIconFullSize and 0 or pad + local iconBadge = panel:GetIconBadge() + local iconBadgeSize = panel:GetIconBadgeSize() + local iconAlpha = isIconFullSize and 255 or colorText.a + local sizeIcon = h - 2 * padIcon + + if panel.Depressed or panel:IsSelected() or panel:GetToggle() then + colorBackground = utilGetActiveColor(ColorAlpha(colors.accent, 50)) + colorBar = colors.accentActive + colorText = utilGetActiveColor(utilGetChangedColor(colors.default, 25)) + colorIcon = utilGetActiveColor(utilGetChangedColor(COLOR_WHITE, 32)) + shift = 1 + elseif panel.Hovered then + colorBackground = utilGetHoverColor(ColorAlpha(colors.accent, 50)) + colorBar = colors.accentHover + colorText = utilGetHoverColor(utilGetChangedColor(colors.default, 75)) + colorIcon = utilGetHoverColor(utilGetChangedColor(COLOR_WHITE, 48)) + elseif panel:IsActive() then + colorBackground = utilGetHoverColor(ColorAlpha(colors.accent, 50)) + colorBar = colors.accentHover + colorText = utilGetHoverColor(utilGetChangedColor(colors.default, 75)) + colorIcon = utilGetHoverColor(utilGetChangedColor(COLOR_WHITE, 48)) + end + + drawBox(0, 0, sizes.border, h, colorBar) + drawBox(sizes.border, 0, w - sizes.border, h, colorBackground) + + if hasIcon then + drawFilteredShadowedTexture( + pad + sizes.border, + padIcon + shift, + sizeIcon, + sizeIcon, + panel:GetIcon(), + iconAlpha, + colorIcon + ) + if iconBadge then + drawFilteredShadowedTexture( + pad + sizes.border + sizeIcon - iconBadgeSize, + padIcon + shift + sizeIcon - iconBadgeSize, + iconBadgeSize, + iconBadgeSize, + iconBadge, + iconAlpha, + colors.accent + ) + end + end + + drawSimpleText( + TryT(panel:GetTitle()), + panel:GetTitleFont(), + sizes.border + pad + (hasIcon and (sizeIcon + pad) or pad), + 0.5 * h + shift, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) end --- @@ -378,40 +517,40 @@ end -- @param number h -- @realm client function SKIN:PaintCheckBox(panel, w, h) - local colorBox, colorCenter, offset - - if panel:GetChecked() then - if not panel:IsEnabled() then - colorBox = ColorAlpha(colors.handle, alphaDisabled) - colorCenter = ColorAlpha(colors.handle, alphaDisabled) - offset = w - h - elseif panel.Hovered then - colorBox = colors.handle - colorCenter = colors.accentHover - offset = w - h - else - colorBox = colors.handle - colorCenter = colors.accent - offset = w - h - end - else - if not panel:IsEnabled() then - colorBox = ColorAlpha(colors.handle, alphaDisabled) - colorCenter = ColorAlpha(colors.settingsBox, alphaDisabled) - offset = 0 - elseif panel.Hovered then - colorBox = colors.handle - colorCenter = colors.accentHover - offset = 0 - else - colorBox = colors.handle - colorCenter = colors.settingsBox - offset = 0 - end - end - - drawRoundedBox(4, 0, 0, w, h, colorBox) - drawRoundedBox(4, offset + 3, 3, h - 6, h - 6, colorCenter) + local colorBox, colorCenter, offset + + if panel:GetChecked() then + if not panel:IsEnabled() then + colorBox = ColorAlpha(colors.handle, alphaDisabled) + colorCenter = ColorAlpha(colors.handle, alphaDisabled) + offset = w - h + elseif panel.Hovered then + colorBox = colors.handle + colorCenter = colors.accentHover + offset = w - h + else + colorBox = colors.handle + colorCenter = colors.accent + offset = w - h + end + else + if not panel:IsEnabled() then + colorBox = ColorAlpha(colors.handle, alphaDisabled) + colorCenter = ColorAlpha(colors.settingsBox, alphaDisabled) + offset = 0 + elseif panel.Hovered then + colorBox = colors.handle + colorCenter = colors.accentHover + offset = 0 + else + colorBox = colors.handle + colorCenter = colors.settingsBox + offset = 0 + end + end + + drawRoundedBox(4, 0, 0, w, h, colorBox) + drawRoundedBox(4, offset + 3, 3, h - 6, h - 6, colorCenter) end --- @@ -420,27 +559,27 @@ end -- @param number h -- @realm client function SKIN:PaintCheckBoxLabel(panel, w, h) - local colorBox = colors.settingsBox - local colorText = colors.settingsText - - if not panel:IsEnabled() then - colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) - colorText = ColorAlpha(colors.settingsText, alphaDisabled) - end - - drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colorBox, true, false, true, false) - - local params = panel:GetParams() - - drawSimpleText( - params and ParT(panel:GetText(), params) or TryT(panel:GetText()), - panel:GetFont(), - panel:GetTextPosition(), - 0.5 * h, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + local colorBox = colors.settingsBox + local colorText = colors.settingsText + + if not panel:IsEnabled() then + colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) + colorText = ColorAlpha(colors.settingsText, alphaDisabled) + end + + drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colorBox, true, false, true, false) + + local params = panel:GetTextParams() + + drawSimpleText( + params and ParT(panel:GetText(), params) or TryT(panel:GetText()), + panel:GetFont(), + panel:GetTextPosition(), + 0.5 * h, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) end --- @@ -449,8 +588,8 @@ end -- @param number h -- @realm client function SKIN:PaintCollapsibleCategoryTTT2(panel, w, h) - drawBox(0, 0, w, h, colors.background) - drawBox(0, h - sizes.border, w, sizes.border, colors.accent) + drawBox(0, 0, w, h, colors.background) + drawBox(0, h - sizes.border, w, sizes.border, colors.accent) end --- @@ -459,30 +598,46 @@ end -- @param number h -- @realm client function SKIN:PaintCategoryHeaderTTT2(panel, w, h) - local colorLine = utilGetChangedColor(colors.background, 50) - local colorText = utilGetChangedColor(colors.default, 50) - local paddingX = 10 - local paddingY = 10 - - drawBox(0, 0, w, h, colors.background) - - if panel:GetParent():GetExpanded() then - drawLine(0, h - 1, w, h - 1, colorLine) - - drawFilteredShadowedTexture(paddingX, paddingY + 1, h - 2 * paddingY, h - 2 * paddingY, materialCollapseOpened, colorText.a * 0.5, colorText) - else - drawFilteredShadowedTexture(paddingX, paddingY, h - 2 * paddingY, h - 2 * paddingY, materialCollapseClosed, colorText.a * 0.5, colorText) - end - - drawSimpleText( - string.upper(TryT(panel.text)), - panel:GetFont(), - h, - 0.5 * h, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + local colorLine = utilGetChangedColor(colors.background, 50) + local colorText = utilGetChangedColor(colors.default, 50) + local paddingX = 10 + local paddingY = 10 + + drawBox(0, 0, w, h, colors.background) + + if panel:GetParent():GetExpanded() then + drawLine(0, h - 1, w, h - 1, colorLine) + + drawFilteredShadowedTexture( + paddingX, + paddingY + 1, + h - 2 * paddingY, + h - 2 * paddingY, + materialCollapseOpened, + colorText.a * 0.5, + colorText + ) + else + drawFilteredShadowedTexture( + paddingX, + paddingY, + h - 2 * paddingY, + h - 2 * paddingY, + materialCollapseClosed, + colorText.a * 0.5, + colorText + ) + end + + drawSimpleText( + string.upper(TryT(panel.text)), + panel:GetFont(), + h, + 0.5 * h, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) end --- @@ -491,40 +646,75 @@ end -- @param number h -- @realm client function SKIN:PaintButtonTTT2(panel, w, h) - local colorLine = colors.accentDark - local colorBox = colors.accent - local colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 220) - local shift = 0 - - if not panel:IsEnabled() then - local colorAccentDisabled = utilGetChangedColor(colors.default, 150) - - colorLine = utilColorDarken(colorAccentDisabled, 50) - colorBox = utilGetChangedColor(colors.default, 150) - colorText = ColorAlpha(utilGetDefaultColor(colorAccentDisabled), 220) - elseif panel.Depressed or panel:IsSelected() or panel:GetToggle() then - colorLine = colors.accentDarkActive - colorBox = colors.accentActive - colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 220) - shift = 1 - elseif panel.Hovered then - colorLine = colors.accentDarkHover - colorBox = colors.accentHover - colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 220) - end - - drawBox(0, 0, w, h, colorBox) - drawBox(0, h - sizes.border, w, sizes.border, colorLine) - - drawShadowedText( - string.upper(TryT(panel:GetText())), - panel:GetFont(), - 0.5 * w, - 0.5 * (h - sizes.border) + shift, - colorText, - TEXT_ALIGN_CENTER, - TEXT_ALIGN_CENTER - ) + local colorLine = colors.accentDark + local colorBox = colors.accent + local colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 220) + local shift = 0 + + if not panel:IsEnabled() then + local colorAccentDisabled = utilGetChangedColor(colors.default, 150) + + colorLine = utilColorDarken(colorAccentDisabled, 50) + colorBox = utilGetChangedColor(colors.default, 150) + colorText = ColorAlpha(utilGetDefaultColor(colorAccentDisabled), 220) + elseif panel.Depressed or panel:IsSelected() or panel:GetToggle() then + colorLine = colors.accentDarkActive + colorBox = colors.accentActive + colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 220) + shift = 1 + elseif panel.Hovered then + colorLine = colors.accentDarkHover + colorBox = colors.accentHover + colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 220) + end + + drawBox(0, 0, w, h, colorBox) + drawBox(0, h - sizes.border, w, sizes.border, colorLine) + + local translatedText = "" + if panel:HasTextParams() then + translatedText = string.upper(ParT(panel:GetText(), panel:GetTextParams())) + else + translatedText = string.upper(TryT(panel:GetText())) + end + + local font = panel:GetFont() + local xText = 0.5 * w + + if panel:HasIcon() then + local widthText = drawGetTextSize(translatedText, font) + local padding = 5 + local sizeIcon = panel:GetIconSize() + local yIcon = 0.5 * (h - sizeIcon) + + xText = 0.5 * (w + sizeIcon + padding) + + local xIcon = xText - sizeIcon - padding - 0.5 * widthText + + if panel:IsIconShadowed() then + drawFilteredShadowedTexture( + xIcon, + yIcon, + sizeIcon, + sizeIcon, + panel:GetIcon(), + 255, + colorText + ) + else + drawFilteredTexture(xIcon, yIcon, sizeIcon, sizeIcon, panel:GetIcon(), 255, COLOR_WHITE) + end + end + + drawShadowedText( + translatedText, + font, + xText, + 0.5 * (h - sizes.border) + shift, + colorText, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) end --- @@ -533,35 +723,43 @@ end -- @param number h -- @realm client function SKIN:PaintFormButtonIconTTT2(panel, w, h) - local colorBoxBack = colors.settingsBox - local colorBox = colors.accent - local colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) - local shift = 0 - local pad = 6 - - if not panel:IsEnabled() then - colorBoxBack = ColorAlpha(colors.settingsBox, alphaDisabled) - colorBox = ColorAlpha(colors.accent, alphaDisabled) - colorText = ColorAlpha(utilGetDefaultColor(colors.accent), alphaDisabled) - elseif panel.noDefault then - colorBoxBack = colors.settingsBox - colorBox = ColorAlpha(colors.accent, alphaDisabled) - colorText = ColorAlpha(utilGetDefaultColor(colors.accent), alphaDisabled) - elseif panel.Depressed or panel:IsSelected() or panel:GetToggle() then - colorBoxBack = colors.settingsBox - colorBox = colors.accentActive - colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) - shift = 1 - elseif panel.Hovered then - colorBoxBack = colors.settingsBox - colorBox = colors.accentHover - colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) - end - - drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colorBoxBack, false, true, false, true) - drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorBox) - - drawFilteredShadowedTexture(pad, pad + shift, w - 2 * pad, h - 2 * pad, panel.material, colorText.a, colorText) + local colorBoxBack = colors.settingsBox + local colorBox = colors.accent + local colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) + local shift = 0 + local pad = 6 + + if not panel:IsEnabled() then + colorBoxBack = ColorAlpha(colors.settingsBox, alphaDisabled) + colorBox = ColorAlpha(colors.accent, alphaDisabled) + colorText = ColorAlpha(utilGetDefaultColor(colors.accent), alphaDisabled) + elseif panel.noDefault then + colorBoxBack = colors.settingsBox + colorBox = ColorAlpha(colors.accent, alphaDisabled) + colorText = ColorAlpha(utilGetDefaultColor(colors.accent), alphaDisabled) + elseif panel.Depressed or panel:IsSelected() or panel:GetToggle() then + colorBoxBack = colors.settingsBox + colorBox = colors.accentActive + colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) + shift = 1 + elseif panel.Hovered then + colorBoxBack = colors.settingsBox + colorBox = colors.accentHover + colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) + end + + drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colorBoxBack, false, true, false, true) + drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorBox) + + drawFilteredShadowedTexture( + pad, + pad + shift, + w - 2 * pad, + h - 2 * pad, + panel.material, + colorText.a, + colorText + ) end --- @@ -570,42 +768,42 @@ end -- @param number h -- @realm client function SKIN:PaintBinderButtonTTT2(panel, w, h) - local colorBoxBack = colors.settingsBox - local colorBox = colors.accent - local colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) - local shift = 0 - - if not panel:IsEnabled() then - colorBoxBack = ColorAlpha(colors.settingsBox, alphaDisabled) - colorBox = ColorAlpha(colors.accent, alphaDisabled) - colorText = ColorAlpha(colors.settingsText, alphaDisabled) - end - - if panel.Depressed or panel:IsSelected() or panel:GetToggle() then - colorBoxBack = colors.settingsBox - colorBox = colors.accentActive - colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) - shift = 1 - end - - if panel.Hovered then - colorBoxBack = colors.settingsBox - colorBox = colors.accentActive - colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) - end - - drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colorBoxBack, false, true, false, true) - drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorBox) - - drawShadowedText( - string.upper(TryT(panel:GetText())), - panel:GetFont(), - 0.5 * w, - 0.5 * h + shift, - colorText, - TEXT_ALIGN_CENTER, - TEXT_ALIGN_CENTER - ) + local colorBoxBack = colors.settingsBox + local colorBox = colors.accent + local colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) + local shift = 0 + + if not panel:IsEnabled() then + colorBoxBack = ColorAlpha(colors.settingsBox, alphaDisabled) + colorBox = ColorAlpha(colors.accent, alphaDisabled) + colorText = ColorAlpha(colors.settingsText, alphaDisabled) + end + + if panel.Depressed or panel:IsSelected() or panel:GetToggle() then + colorBoxBack = colors.settingsBox + colorBox = colors.accentActive + colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) + shift = 1 + end + + if panel.Hovered then + colorBoxBack = colors.settingsBox + colorBox = colors.accentActive + colorText = ColorAlpha(utilGetDefaultColor(colors.accent), 150) + end + + drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colorBoxBack, false, true, false, true) + drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorBox) + + drawShadowedText( + string.upper(TryT(panel:GetText())), + panel:GetFont(), + 0.5 * w, + 0.5 * h + shift, + colorText, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) end --- @@ -614,33 +812,33 @@ end -- @param number h -- @realm client function SKIN:PaintLabelSpacerTTT2(panel, w, h) - local text = TryT(panel:GetText()) - local font = panel:GetFont() - - local padding = 10 - local heightBar = 5 - local barX1 = 0 - local barY1 = 0.5 * (h - heightBar) + 1 - local widthBar1 = 20 - local textX = barX1 + widthBar1 + padding - local widthText = drawGetTextSize(text, font) - local barX2 = textX + widthText + padding - local widthBar2 = w - barX2 - - local colorLine = utilGetChangedColor(colors.default, 170) - - drawBox(barX1, barY1, widthBar1, heightBar, colorLine) - drawBox(barX2, barY1, widthBar2, heightBar, colorLine) - - drawSimpleText( - text, - font, - textX, - 0.5 * h, - utilGetChangedColor(colors.default, 40), - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + local text = TryT(panel:GetText()) + local font = panel:GetFont() + + local padding = 10 + local heightBar = 5 + local barX1 = 0 + local barY1 = 0.5 * (h - heightBar) + 1 + local widthBar1 = 20 + local textX = barX1 + widthBar1 + padding + local widthText = drawGetTextSize(text, font) + local barX2 = textX + widthText + padding + local widthBar2 = w - barX2 + + local colorLine = utilGetChangedColor(colors.default, 170) + + drawBox(barX1, barY1, widthBar1, heightBar, colorLine) + drawBox(barX2, barY1, widthBar2, heightBar, colorLine) + + drawSimpleText( + text, + font, + textX, + 0.5 * h, + utilGetChangedColor(colors.default, 40), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) end --- @@ -649,15 +847,15 @@ end -- @param number h -- @realm client function SKIN:PaintLabelTTT2(panel, w, h) - drawSimpleText( - TryT(panel:GetText()), - panel:GetFont(), - 0, - 0.5 * h, - utilGetChangedColor(colors.default, 40), - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + drawSimpleText( + TryT(panel:GetText()), + panel:GetFont(), + 0, + 0.5 * h, + utilGetChangedColor(colors.default, 40), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) end --- @@ -666,15 +864,15 @@ end -- @param number h -- @realm client function SKIN:PaintLabelRightTTT2(panel, w, h) - drawSimpleText( - TryT(panel:GetText()), - panel:GetFont(), - w, - 0.5 * h, - utilGetChangedColor(colors.default, 40), - TEXT_ALIGN_RIGHT, - TEXT_ALIGN_CENTER - ) + drawSimpleText( + TryT(panel:GetText()), + panel:GetFont(), + w, + 0.5 * h, + utilGetChangedColor(colors.default, 40), + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_CENTER + ) end --- @@ -683,24 +881,24 @@ end -- @param number h -- @realm client function SKIN:PaintFormLabelTTT2(panel, w, h) - local colorText = colors.settingsText - local colorBox = colors.settingsBox - - if not panel:IsEnabled() then - colorText = ColorAlpha(colors.settingsText, alphaDisabled) - colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) - end - - drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colorBox, true, false, true, false) - drawSimpleText( - TryT(panel:GetText()), - panel:GetFont(), - 10, - 0.5 * h, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + local colorText = colors.settingsText + local colorBox = colors.settingsBox + + if not panel:IsEnabled() then + colorText = ColorAlpha(colors.settingsText, alphaDisabled) + colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) + end + + drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colorBox, true, false, true, false) + drawSimpleText( + TryT(panel:GetText()), + panel:GetFont(), + 10, + 0.5 * h, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) end --- @@ -709,16 +907,16 @@ end -- @param number h -- @realm client function SKIN:PaintFormBoxTTT2(panel, w, h) - local colorBox = colors.settingsBox - local colorHandle = colors.handle + local colorBox = colors.settingsBox + local colorHandle = colors.handle - if not panel:IsEnabled() then - colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) - colorHandle = ColorAlpha(colors.handle, alphaDisabled) - end + if not panel:IsEnabled() then + colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) + colorHandle = ColorAlpha(colors.handle, alphaDisabled) + end - drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colorBox, false, true, false, true) - drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorHandle) + drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colorBox, false, true, false, true) + drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorHandle) end --- @@ -727,15 +925,15 @@ end -- @param number h -- @realm client function SKIN:PaintMenuLabelTTT2(panel, w, h) - drawSimpleText( - TryT(panel:GetText()), - panel:GetFont(), - 0, - 0.5 * h, - colors.settingsText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + drawSimpleText( + TryT(panel:GetText()), + panel:GetFont(), + 0, + 0.5 * h, + colors.settingsText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) end --- @@ -744,32 +942,38 @@ end -- @param number h -- @realm client function SKIN:PaintHelpLabelTTT2(panel, w, h) - drawBox(0, 0, w, h, colors.helpBox) - drawBox(0, 0, 4, h, colors.helpBar) - - local textTranslated = ParT(panel:GetText(), TryT(panel:GetParams())) - local textWrapped = drawGetWrappedText( - textTranslated, - w - 2 * panel.paddingX, - panel:GetFont() - ) - - local _, heightText = drawGetTextSize("", panel:GetFont()) - local posY = panel.paddingY - - for i = 1, #textWrapped do - drawSimpleText( - textWrapped[i], - panel:GetFont(), - panel.paddingX, - posY, - colors.helpText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_TOP - ) - - posY = posY + heightText - end + local colorBox = colors.helpBox + local colorBar = colors.helpBar + local colorText = colors.helpText + + if not panel:IsEnabled() then + colorBox = ColorAlpha(colorBox, alphaDisabled) + colorBar = ColorAlpha(colorBar, alphaDisabled) + colorText = ColorAlpha(colorText, 0.5 * alphaDisabled) + end + + drawBox(0, 0, w, h, colorBox) + drawBox(0, 0, 4, h, colorBar) + + local textTranslated = ParT(panel:GetText(), TryT(panel:GetTextParams())) + local textWrapped = drawGetWrappedText(textTranslated, w - 2 * panel.paddingX, panel:GetFont()) + + local _, heightText = drawGetTextSize("", panel:GetFont()) + local posY = panel.paddingY + + for i = 1, #textWrapped do + drawSimpleText( + textWrapped[i], + panel:GetFont(), + panel.paddingX, + posY, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + + posY = posY + heightText + end end --- @@ -778,21 +982,21 @@ end -- @param number h -- @realm client function SKIN:PaintSliderKnob(panel, w, h) - local colorBox = colors.accent + local colorBox = colors.accent - if not panel:IsEnabled() then - colorBox = ColorAlpha(colors.accent, alphaDisabled) - end + if not panel:IsEnabled() then + colorBox = ColorAlpha(colors.accent, alphaDisabled) + end - if panel.Depressed then - colorBox = colors.accentActive - end + if panel.Depressed then + colorBox = colors.accentActive + end - if panel.Hovered then - colorBox = colors.accentHover - end + if panel.Hovered then + colorBox = colors.accentHover + end - drawRoundedBox(math.floor(w * 0.5), 0, 0, w, h, colorBox) + drawRoundedBox(math.floor(w * 0.5), 0, 0, w, h, colorBox) end --- @@ -801,25 +1005,25 @@ end -- @param number h -- @realm client function SKIN:PaintNumSliderTTT2(panel, w, h) - local colorBox = colors.settingsBox - local colorHandle = colors.handle - local colorLineActive = colors.accent - local colorLinePassive = colors.sliderInactive - local pad = 5 - - if not panel:IsEnabled() then - colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) - colorHandle = ColorAlpha(colors.handle, alphaDisabled) - colorLineActive = ColorAlpha(colors.accent, alphaDisabled) - colorLinePassive = ColorAlpha(colors.sliderInactive, alphaDisabled) - end - - drawBox(0, 0, w, h, colorBox) - drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorHandle) - - -- draw selection line - drawBox(5, 0.5 * h - 1, w - 2 * pad, 2, colorLinePassive) - drawBox(5, 0.5 * h - 1, (w - pad) * panel:GetFraction() - pad, 2, colorLineActive) + local colorBox = colors.settingsBox + local colorHandle = colors.handle + local colorLineActive = colors.accent + local colorLinePassive = colors.sliderInactive + local pad = 5 + + if not panel:IsEnabled() then + colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) + colorHandle = ColorAlpha(colors.handle, alphaDisabled) + colorLineActive = ColorAlpha(colors.accent, alphaDisabled) + colorLinePassive = ColorAlpha(colors.sliderInactive, alphaDisabled) + end + + drawBox(0, 0, w, h, colorBox) + drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorHandle) + + -- draw selection line + drawBox(5, 0.5 * h - 1, w - 2 * pad, 2, colorLinePassive) + drawBox(5, 0.5 * h - 1, (w - pad) * panel:GetFraction() - pad, 2, colorLineActive) end --- @@ -828,24 +1032,33 @@ end -- @param number h -- @realm client function SKIN:PaintSliderTextAreaTTT2(panel, w, h) - local colorBox = colors.settingsBox - local colorText = colors.settingsText - - if not panel:IsEnabled() then - colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) - colorText = ColorAlpha(colors.settingsText, alphaDisabled) - end - - drawBox(0, 0, w, h, colorBox) - drawSimpleText( - panel:GetText(), - panel:GetFont(), - 0.5 * w, - 0.5 * h, - colorText, - TEXT_ALIGN_CENTER, - TEXT_ALIGN_CENTER - ) + local colorBox = colors.settingsBox + local colorText = colors.settingsText + + if not panel:IsEnabled() then + colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) + colorText = ColorAlpha(colors.settingsText, alphaDisabled) + end + + -- Draw normal text if currently not in input mode, otherwise draw the TTT2 Text Entry + if not panel:GetParent():GetTextBoxEnabled() then + drawBox(0, 0, w, h, colorBox) + drawSimpleText( + panel:GetText(), + panel:GetFont(), + 0.5 * w, + 0.5 * h, + colorText, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) + else + self:PaintTextEntryTTT2(panel, w, h) + local vguiColor = utilGetActiveColor( + utilGetChangedColor(utilGetDefaultColor(vskinGetBackgroundColor()), 25) + ) + panel:DrawTextEntryText(vguiColor, vguiColor, vguiColor) + end end --- @@ -854,13 +1067,13 @@ end -- @param number h -- @realm client function SKIN:PaintBinderPanelTTT2(panel, w, h) - local colorBox = colors.settingsBox + local colorBox = colors.settingsBox - if not panel:IsEnabled() then - colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) - end + if not panel:IsEnabled() then + colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) + end - drawBox(0, 0, w, h, colorBox) + drawBox(0, 0, w, h, colorBox) end --- @@ -869,35 +1082,35 @@ end -- @param number h -- @realm client function SKIN:PaintComboBoxTTT2(panel, w, h) - local colorBox = colors.settingsBox - local colorHandle = colors.handle - local colorText = utilGetChangedColor(utilGetDefaultColor(colors.handle), 50) - - if not panel:IsEnabled() then - colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) - colorHandle = ColorAlpha(utilGetActiveColor(colors.handle), alphaDisabled) - colorText = ColorAlpha(colorText, alphaDisabled) - end - - if panel.Depressed or panel:IsMenuOpen() then - colorHandle = utilGetHoverColor(colors.handle) - end - - if panel.Hovered then - colorHandle = utilGetActiveColor(colors.handle) - end - - drawBox(0, 0, w, h, colorBox) - drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorHandle) - drawSimpleText( - TryT(panel:GetText()), - panel:GetFont(), - 10, - 0.5 * h, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + local colorBox = colors.settingsBox + local colorHandle = colors.handle + local colorText = utilGetChangedColor(utilGetDefaultColor(colors.handle), 50) + + if not panel:IsEnabled() then + colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) + colorHandle = ColorAlpha(utilGetActiveColor(colors.handle), alphaDisabled) + colorText = ColorAlpha(colorText, alphaDisabled) + end + + if panel.Depressed or panel:IsMenuOpen() then + colorHandle = utilGetHoverColor(colors.handle) + end + + if panel.Hovered then + colorHandle = utilGetActiveColor(colors.handle) + end + + drawBox(0, 0, w, h, colorBox) + drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorHandle) + drawSimpleText( + TryT(panel:GetText()), + panel:GetFont(), + 10, + 0.5 * h, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) end --- @@ -906,48 +1119,52 @@ end -- @param number h -- @realm client function SKIN:PaintColoredTextBoxTTT2(panel, w, h) - local colorBackground - - -- get the background color - if panel:HasDynamicColor() then - colorBackground = utilGetChangedColor(panel:GetDynamicParentColor() or colors.background, panel:GetDynamicParentColorShift()) - else - colorBackground = panel:GetColor() - end - - -- set the dynamic background color for the child elements - panel.dynBaseColor = colorBackground - - local colorText = utilGetDefaultColor(colorBackground) - local align = panel:GetTitleAlign() - local alpha = mathRound(colorText.a * panel:GetTitleOpacity()) - local hasIcon = panel:HasIcon() - local pad = mathRound(0.1 * h) - local sizeIcon = h - 2 * pad - - drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorBackground) - - if panel:HasFlashColor() then - local colorFlash = table.Copy(colorText) - colorFlash.a = math.Round(15 * (math.sin((CurTime() % 2 - 1) * math.pi) + 1.1)) - - drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorFlash) - end - - drawShadowedText( - TryT(panel:GetTitle()), - panel:GetTitleFont(), - (align == TEXT_ALIGN_CENTER) and (0.5 * w) or (hasIcon and (sizeIcon + 4 * pad) or (2 * pad)), - 0.5 * h, - ColorAlpha(colorText, alpha), - align, - TEXT_ALIGN_CENTER, - 1 - ) - - if hasIcon then - drawFilteredShadowedTexture(pad, pad, sizeIcon, sizeIcon, panel:GetIcon(), alpha, colorText) - end + local colorBackground + + -- get the background color + if panel:HasDynamicColor() then + colorBackground = utilGetChangedColor( + panel:GetDynamicParentColor() or colors.background, + panel:GetDynamicParentColorShift() + ) + else + colorBackground = panel:GetColor() + end + + -- set the dynamic background color for the child elements + panel.dynBaseColor = colorBackground + + local colorText = utilGetDefaultColor(colorBackground) + local align = panel:GetTitleAlign() + local alpha = mathRound(colorText.a * panel:GetTitleOpacity()) + local hasIcon = panel:HasIcon() + local pad = mathRound(0.1 * h) + local sizeIcon = h - 2 * pad + + drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorBackground) + + if panel:HasFlashColor() then + local colorFlash = table.Copy(colorText) + colorFlash.a = math.Round(15 * (math.sin((CurTime() % 2 - 1) * math.pi) + 1.1)) + + drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorFlash) + end + + drawShadowedText( + TryT(panel:GetTitle()), + panel:GetTitleFont(), + (align == TEXT_ALIGN_CENTER) and (0.5 * w) + or (hasIcon and (sizeIcon + 4 * pad) or (2 * pad)), + 0.5 * h, + ColorAlpha(colorText, alpha), + align, + TEXT_ALIGN_CENTER, + 1 + ) + + if hasIcon then + drawFilteredShadowedTexture(pad, pad, sizeIcon, sizeIcon, panel:GetIcon(), alpha, colorText) + end end --- @@ -956,14 +1173,15 @@ end -- @param number h -- @realm client function SKIN:PaintColoredBoxTTT2(panel, w, h) - -- get the background color - if panel:HasDynamicColor() then - panel.dynBaseColor = utilGetChangedColor(panel:GetDynamicParentColor() or colors.background, 30) - else - panel.dynBaseColor = panel:GetColor() - end - - drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, panel.dynBaseColor) + -- get the background color + if panel:HasDynamicColor() then + panel.dynBaseColor = + utilGetChangedColor(panel:GetDynamicParentColor() or colors.background, 30) + else + panel.dynBaseColor = panel:GetColor() + end + + drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, panel.dynBaseColor) end --- @@ -972,7 +1190,7 @@ end -- @param number h -- @realm client function SKIN:PaintVerticalBorderedBoxTTT2(panel, w, h) - drawBox(w - 1, 0, 1, h, ColorAlpha(colors.default, 200)) + drawBox(w - 1, 0, 1, h, ColorAlpha(colors.default, 200)) end --- @@ -981,31 +1199,31 @@ end -- @param number h -- @realm client function SKIN:PaintButtonRoundEndLeftTTT2(panel, w, h) - local colorForeground = colors.accent - local shift = 0 - - if panel.Depressed or panel:IsSelected() or panel:GetToggle() then - shift = 1 - elseif panel.Hovered then - colorForeground = colors.accentHover - elseif not panel.isActive then - colorForeground = colors.handle - end - - local colorText = ColorAlpha(utilGetDefaultColor(colorForeground), 220) - - drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colors.content, true, false, true, false) - drawRoundedBox(sizes.cornerRadius, 2, 2, w - 3, h - 4, colorForeground) - - drawSimpleText( - TryT(panel:GetText()), - panel:GetFont(), - 0.5 * w, - 0.5 * h + shift, - colorText, - TEXT_ALIGN_CENTER, - TEXT_ALIGN_CENTER - ) + local colorForeground = colors.accent + local shift = 0 + + if panel.Depressed or panel:IsSelected() or panel:GetToggle() then + shift = 1 + elseif panel.Hovered then + colorForeground = colors.accentHover + elseif not panel.isActive then + colorForeground = colors.handle + end + + local colorText = ColorAlpha(utilGetDefaultColor(colorForeground), 220) + + drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colors.content, true, false, true, false) + drawRoundedBox(sizes.cornerRadius, 2, 2, w - 3, h - 4, colorForeground) + + drawSimpleText( + TryT(panel:GetText()), + panel:GetFont(), + 0.5 * w, + 0.5 * h + shift, + colorText, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) end --- @@ -1014,31 +1232,31 @@ end -- @param number h -- @realm client function SKIN:PaintButtonRoundEndRightTTT2(panel, w, h) - local colorForeground = colors.accent - local shift = 0 - - if panel.Depressed or panel:IsSelected() or panel:GetToggle() then - shift = 1 - elseif panel.Hovered then - colorForeground = colors.accentHover - elseif not panel.isActive then - colorForeground = colors.handle - end - - local colorText = ColorAlpha(utilGetDefaultColor(colorForeground), 220) - - drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colors.content, false, true, false, true) - drawRoundedBox(sizes.cornerRadius, 1, 2, w - 3, h - 4, colorForeground) - - drawSimpleText( - TryT(panel:GetText()), - panel:GetFont(), - 0.5 * w, - 0.5 * h + shift, - colorText, - TEXT_ALIGN_CENTER, - TEXT_ALIGN_CENTER - ) + local colorForeground = colors.accent + local shift = 0 + + if panel.Depressed or panel:IsSelected() or panel:GetToggle() then + shift = 1 + elseif panel.Hovered then + colorForeground = colors.accentHover + elseif not panel.isActive then + colorForeground = colors.handle + end + + local colorText = ColorAlpha(utilGetDefaultColor(colorForeground), 220) + + drawRoundedBoxEx(sizes.cornerRadius, 0, 0, w, h, colors.content, false, true, false, true) + drawRoundedBox(sizes.cornerRadius, 1, 2, w - 3, h - 4, colorForeground) + + drawSimpleText( + TryT(panel:GetText()), + panel:GetFont(), + 0.5 * w, + 0.5 * h + shift, + colorText, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) end --- @@ -1047,28 +1265,44 @@ end -- @param number h -- @realm client function SKIN:PaintTooltipTTT2(panel, w, h) - local colorLine = ColorAlpha(colors.default, 100) - - local sizeArrow = panel:GetArrowSize() - local sizeRhombus = 2 * sizeArrow - - drawBox(0, sizeArrow, w, h - sizeArrow, colorLine) - drawFilteredTexture(sizeArrow, 0, sizeRhombus, sizeRhombus, materialRhombus, colorLine.a, colorLine) - - drawBox(1, sizeArrow + 1, w - 2, h - sizeArrow - 2, colors.background) - drawFilteredTexture(sizeArrow, 1, sizeRhombus, sizeRhombus, materialRhombus, colors.background.a, colors.background) - - if panel:HasText() then - drawSimpleText( - TryT(panel:GetText()), - panel:GetFont(), - 0.5 * w, - 0.5 * (h + sizeArrow), - utilGetDefaultColor(colors.background), - TEXT_ALIGN_CENTER, - TEXT_ALIGN_CENTER - ) - end + local colorLine = ColorAlpha(colors.default, 100) + + local sizeArrow = panel:GetArrowSize() + local sizeRhombus = 2 * sizeArrow + + drawBox(0, sizeArrow, w, h - sizeArrow, colorLine) + drawFilteredTexture( + sizeArrow, + 0, + sizeRhombus, + sizeRhombus, + materialRhombus, + colorLine.a, + colorLine + ) + + drawBox(1, sizeArrow + 1, w - 2, h - sizeArrow - 2, colors.background) + drawFilteredTexture( + sizeArrow, + 1, + sizeRhombus, + sizeRhombus, + materialRhombus, + colors.background.a, + colors.background + ) + + if panel:HasText() then + drawSimpleText( + TryT(panel:GetText()), + panel:GetFont(), + 0.5 * w, + 0.5 * (h + sizeArrow), + utilGetDefaultColor(colors.background), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) + end end --- @@ -1077,149 +1311,159 @@ end -- @param number h -- @realm client function SKIN:PaintEventBoxTTT2(panel, w, h) - local event = panel:GetEvent() - - local colorLine = ColorAlpha(colors.default, 25) - local colorText = ColorAlpha(colors.default, 200) - - local sizeIcon = 30 - local padding = 8 - local widthLine = 4 - local offsetXLine = 0.5 * sizeIcon + padding - local offsetXIcon = offsetXLine - 0.5 * (sizeIcon - widthLine) - local offsetYIcon = 20 + padding - local offsetYLine = offsetYIcon + padding + sizeIcon - local offsetXText = offsetXIcon + sizeIcon + padding - local offsetYTitle = offsetYIcon + 0.5 * sizeIcon - local widthScoreBox = w - offsetXText - 2 * padding - - drawBox(offsetXLine, 0, widthLine, offsetYIcon - padding, colorLine) - drawBox(offsetXLine, offsetYLine, widthLine, h - offsetYLine, colorLine) - - drawFilteredShadowedTexture(offsetXIcon, offsetYIcon, sizeIcon, sizeIcon, panel:GetIcon(), colorText.a, colorText) - - drawShadowedText( - TryT(panel:GetTitle()), - panel:GetTitleFont(), - offsetXText, - offsetYTitle, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER, - 1 - ) - - local time = event:GetTime() - local minutes = math.floor(time / 60) - local seconds = math.floor(time % 60) - - drawSimpleText( - string.format("[%02d:%02d]", minutes, seconds), - panel:GetFont(), - w - 20, - offsetYTitle, - colorLine, - TEXT_ALIGN_RIGHT, - TEXT_ALIGN_CENTER - ) - - local posY = offsetYIcon + sizeIcon + padding - local textTable = panel:GetText() - local _, heightText = drawGetTextSize("", panel:GetFont()) - - for i = 1, #textTable do - local text = textTable[i] - local params = {} - - if text.translateParams then - for key, value in pairs(text.params) do - params[key] = TryT(value) - end - else - params = text.params - end - - local textTranslated = ParT(text.string, params or {}) - - local textWrapped = drawGetWrappedText( - textTranslated, - w - offsetXText, - panel:GetFont() - ) - - for k = 1, #textWrapped do - drawSimpleText( - textWrapped[k], - panel:GetFont(), - offsetXText, - posY, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_TOP - ) - - posY = posY + heightText - end - - posY = posY + 15 - end - - if not event:HasScore() then return end - - local colorBox = ColorAlpha(colors.default, 10) - local scoredPlayers = event:GetScoredPlayers() - local sid64 = LocalPlayer():SteamID64() - - for i = 1, #scoredPlayers do - local ply64 = scoredPlayers[i] - - if event.onlyLocalPlayer and ply64 ~= sid64 then continue end - - local rawScoreTexts = event:GetRawScoreText(ply64) - local scoreRows = #rawScoreTexts - - if scoreRows == 0 then continue end - - local height = (scoreRows + 1) * heightText + 2 * padding - - drawRoundedBox(sizes.cornerRadius, offsetXText, posY, widthScoreBox, height, colorBox) - - drawSimpleText( - ParT("title_player_score", {player = event:GetNameFrom64(ply64)}), - panel:GetFont(), - offsetXText + padding, - posY + padding, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_TOP - ) - - for k = 1, scoreRows do - local rawScoreText = rawScoreTexts[k] - - drawSimpleText( - TryT(rawScoreText.name), - panel:GetFont(), - offsetXText + 2 * padding, - posY + padding + k * heightText, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_TOP - ) - - drawSimpleText( - rawScoreText.score, - panel:GetFont(), - offsetXText + 2 * padding + 175, - posY + padding + k * heightText, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_TOP - ) - end - - posY = posY + height + 15 - end + local event = panel:GetEvent() + + local colorLine = ColorAlpha(colors.default, 25) + local colorText = ColorAlpha(colors.default, 200) + + local sizeIcon = 30 + local padding = 8 + local widthLine = 4 + local offsetXLine = 0.5 * sizeIcon + padding + local offsetXIcon = offsetXLine - 0.5 * (sizeIcon - widthLine) + local offsetYIcon = 20 + padding + local offsetYLine = offsetYIcon + padding + sizeIcon + local offsetXText = offsetXIcon + sizeIcon + padding + local offsetYTitle = offsetYIcon + 0.5 * sizeIcon + local widthScoreBox = w - offsetXText - 2 * padding + + drawBox(offsetXLine, 0, widthLine, offsetYIcon - padding, colorLine) + drawBox(offsetXLine, offsetYLine, widthLine, h - offsetYLine, colorLine) + + drawFilteredShadowedTexture( + offsetXIcon, + offsetYIcon, + sizeIcon, + sizeIcon, + panel:GetIcon(), + colorText.a, + colorText + ) + + drawShadowedText( + TryT(panel:GetTitle()), + panel:GetTitleFont(), + offsetXText, + offsetYTitle, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + 1 + ) + + local time = event:GetTime() + local minutes = math.floor(time / 60) + local seconds = math.floor(time % 60) + + drawSimpleText( + string.format("[%02d:%02d]", minutes, seconds), + panel:GetFont(), + w - 20, + offsetYTitle, + colorLine, + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_CENTER + ) + + local posY = offsetYIcon + sizeIcon + padding + local textTable = panel:GetText() + local _, heightText = drawGetTextSize("", panel:GetFont()) + + for i = 1, #textTable do + local text = textTable[i] + local params = {} + + if text.translateParams then + for key, value in pairs(text.params) do + params[key] = TryT(value) + end + else + params = text.params + end + + local textTranslated = ParT(text.string, params or {}) + + local textWrapped = drawGetWrappedText(textTranslated, w - offsetXText, panel:GetFont()) + + for k = 1, #textWrapped do + drawSimpleText( + textWrapped[k], + panel:GetFont(), + offsetXText, + posY, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + + posY = posY + heightText + end + + posY = posY + 15 + end + + if not event:HasScore() then + return + end + + local colorBox = ColorAlpha(colors.default, 10) + local scoredPlayers = event:GetScoredPlayers() + local sid64 = LocalPlayer():SteamID64() + + for i = 1, #scoredPlayers do + local ply64 = scoredPlayers[i] + + if event.onlyLocalPlayer and ply64 ~= sid64 then + continue + end + + local rawScoreTexts = event:GetRawScoreText(ply64) + local scoreRows = #rawScoreTexts + + if scoreRows == 0 then + continue + end + + local height = (scoreRows + 1) * heightText + 2 * padding + + drawRoundedBox(sizes.cornerRadius, offsetXText, posY, widthScoreBox, height, colorBox) + + drawSimpleText( + ParT("title_player_score", { player = event:GetNameFrom64(ply64) }), + panel:GetFont(), + offsetXText + padding, + posY + padding, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + + for k = 1, scoreRows do + local rawScoreText = rawScoreTexts[k] + + drawSimpleText( + TryT(rawScoreText.name), + panel:GetFont(), + offsetXText + 2 * padding, + posY + padding + k * heightText, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + + drawSimpleText( + rawScoreText.score, + panel:GetFont(), + offsetXText + 2 * padding + 175, + posY + padding + k * heightText, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + end + + posY = posY + height + 15 + end end local MODE_ADDED = ShopEditor.MODE_ADDED @@ -1232,74 +1476,100 @@ local MODE_INHERIT_REMOVED = ShopEditor.MODE_INHERIT_REMOVED -- @param number h -- @realm client function SKIN:PaintCardTTT2(panel, w, h) - local widthBorder = 2 - local widthBorder2 = widthBorder * 2 - local sizeIcon = 64 - local padding = 5 - local posIcon = widthBorder + padding - local posText = posIcon + sizeIcon + 2 * padding - local heightMode = 35 - local widthMode = w - sizeIcon - 3 * padding - local posIconModeX = w - widthMode + 2 * padding - local posIconModeY = h - heightMode + 2 * padding - local sizeIconMode = heightMode - 4 * padding - local posTextModeX = posIconModeX + sizeIconMode + 2 * padding - local posTextModeY = posIconModeY + 0.5 * sizeIconMode - 1 - - local colorBackground = colors.settingsBox - local colorText = colors.settingsText - local colorMode = utilGetChangedColor(colors.background, 75) - - local materialMode = materialCardRemoved - local textMode = "equip_not_added" - - if panel:GetMode() == MODE_ADDED then - colorMode = colorCardAdded - materialMode = materialCardAdded - textMode = "equip_added" - elseif panel:GetMode() == MODE_INHERIT_ADDED then - colorMode = colorCardInheritAdded - materialMode = materialCardAdded - textMode = "equip_inherit_added" - elseif panel:GetMode() == MODE_INHERIT_REMOVED then - colorMode = colorCardInheritRemoved - textMode = "equip_inherit_removed" - end - - local colorTextMode = utilGetDefaultColor(colorMode) - - if panel.Hovered then - colorBackground = colors.accentHover - end - - drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorMode) - drawRoundedBox(sizes.cornerRadius, widthBorder, widthBorder, w - widthBorder2, h - widthBorder2, colorBackground) - - drawFilteredTexture(posIcon, posIcon, sizeIcon, sizeIcon, panel:GetIcon()) - - drawSimpleText( - TryT(panel:GetText()), - panel:GetFont(), - posText, - posIcon + padding, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_TOP - ) - - drawRoundedBoxEx(sizes.cornerRadius, w - widthMode, h - heightMode, widthMode, heightMode, colorMode, true, false, false, true) - - drawFilteredTexture(posIconModeX, posIconModeY, sizeIconMode, sizeIconMode, materialMode, 175, colorTextMode) - - drawSimpleText( - TryT(textMode), - "DermaTTT2TextSmall", - posTextModeX, - posTextModeY, - colorTextMode, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + local widthBorder = 2 + local widthBorder2 = widthBorder * 2 + local sizeIcon = 64 + local padding = 5 + local posIcon = widthBorder + padding + local posText = posIcon + sizeIcon + 2 * padding + local heightMode = 35 + local widthMode = w - sizeIcon - 3 * padding + local posIconModeX = w - widthMode + 2 * padding + local posIconModeY = h - heightMode + 2 * padding + local sizeIconMode = heightMode - 4 * padding + local posTextModeX = posIconModeX + sizeIconMode + 2 * padding + local posTextModeY = posIconModeY + 0.5 * sizeIconMode - 1 + + local colorBackground = colors.settingsBox + local colorText = colors.settingsText + local colorMode = utilGetChangedColor(colors.background, 75) + + local materialMode = materialCardRemoved + local textMode = "equip_not_added" + + if panel:GetMode() == MODE_ADDED then + colorMode = colorCardAdded + materialMode = materialCardAdded + textMode = "equip_added" + elseif panel:GetMode() == MODE_INHERIT_ADDED then + colorMode = colorCardInheritAdded + materialMode = materialCardAdded + textMode = "equip_inherit_added" + elseif panel:GetMode() == MODE_INHERIT_REMOVED then + colorMode = colorCardInheritRemoved + textMode = "equip_inherit_removed" + end + + local colorTextMode = utilGetDefaultColor(colorMode) + + if panel.Hovered then + colorBackground = colors.accentHover + end + + drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorMode) + drawRoundedBox( + sizes.cornerRadius, + widthBorder, + widthBorder, + w - widthBorder2, + h - widthBorder2, + colorBackground + ) + + drawFilteredTexture(posIcon, posIcon, sizeIcon, sizeIcon, panel:GetIcon()) + + drawSimpleText( + TryT(panel:GetText()), + panel:GetFont(), + posText, + posIcon + padding, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + + drawRoundedBoxEx( + sizes.cornerRadius, + w - widthMode, + h - heightMode, + widthMode, + heightMode, + colorMode, + true, + false, + false, + true + ) + + drawFilteredTexture( + posIconModeX, + posIconModeY, + sizeIconMode, + sizeIconMode, + materialMode, + 175, + colorTextMode + ) + + drawSimpleText( + TryT(textMode), + "DermaTTT2TextSmall", + posTextModeX, + posTextModeY, + colorTextMode, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) end --- @@ -1308,11 +1578,11 @@ end -- @param number h -- @realm client function SKIN:PaintRoleImageTTT2(panel, w, h) - local colorBackground = panel:GetColor() - local colorIcon = utilGetDefaultColor(colorBackground) + local colorBackground = panel:GetColor() + local colorIcon = utilGetDefaultColor(colorBackground) - drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorBackground) - drawFilteredShadowedTexture(0, 0, w, h, panel:GetMaterial(), colorIcon.a, colorIcon) + drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorBackground) + drawFilteredShadowedTexture(0, 0, w, h, panel:GetMaterial(), colorIcon.a, colorIcon) end --- @@ -1321,20 +1591,20 @@ end -- @param number h -- @realm client function SKIN:PaintRoleLayeringSenderTTT2(panel, w, h) - local colorBox = utilGetChangedColor(colors.background, 40) - local colorText = utilGetDefaultColor(colorBox) - - drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorBox) - - drawSimpleText( - TryT("layering_not_layered"), - "DermaTTT2Text", - 10, - 0.5 * h, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + local colorBox = utilGetChangedColor(colors.background, 40) + local colorText = utilGetDefaultColor(colorBox) + + drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorBox) + + drawSimpleText( + TryT("layering_not_layered"), + "DermaTTT2Text", + 10, + 0.5 * h, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) end --- @@ -1343,24 +1613,24 @@ end -- @param number h -- @realm client function SKIN:PaintRoleLayeringReceiverTTT2(panel, w, h) - local colorBox = utilGetChangedColor(colors.background, 20) - local colorText = utilGetDefaultColor(colorBox) - - for i = 1, #panel.layerBoxes do - local layerBox = panel.layerBoxes[i] - - drawRoundedBox(sizes.cornerRadius, 0, layerBox.y, w, layerBox.h, colorBox) - - drawSimpleText( - ParT("layering_layer", {layer = i}), - "DermaTTT2Text", - 10, - layerBox.label, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) - end + local colorBox = utilGetChangedColor(colors.background, 20) + local colorText = utilGetDefaultColor(colorBox) + + for i = 1, #panel.layerBoxes do + local layerBox = panel.layerBoxes[i] + + drawRoundedBox(sizes.cornerRadius, 0, layerBox.y, w, layerBox.h, colorBox) + + drawSimpleText( + ParT("layering_layer", { layer = i }), + "DermaTTT2Text", + 10, + layerBox.label, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) + end end --- @@ -1369,39 +1639,65 @@ end -- @param number h -- @realm client function SKIN:PaintSearchbar(panel, w, h) - local colorBox = colors.helpBox - local colorBar = colors.accentHover - local colorText = utilGetActiveColor(utilGetChangedColor(colors.default, 25)) - local heightMult = panel:GetHeightMult() - - local leftPad, topPad, rightPad, bottomPad = panel:GetDockPadding() - local widthPad = leftPad + rightPad - local heightPad = topPad + bottomPad - - if not panel:IsEnabled() then - colorBox = ColorAlpha(colorBox, alphaDisabled) - colorBar = ColorAlpha(colorBar, alphaDisabled) - colorText = ColorAlpha(colorText, alphaDisabled) - end - - -- Draw custom box background for the searchBar - drawBox(leftPad, h * (1 - heightMult) * 0.5 + topPad, w - widthPad, h * heightMult - heightPad, colorBox) - - -- Draw small blue bar on the bottom - drawBox(leftPad, h - sizes.border - bottomPad, w - widthPad, sizes.border, colorBar) - - -- If not focussed draw placeholder text - if panel:GetIsOnFocus() then return end - - drawSimpleText( - TryT(panel:GetCurrentPlaceholderText()), - panel:GetFont(), - leftPad + w * 0.02, - 0.5 * h, - colorText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) + local colorBox = colors.helpBox + local colorBar = colors.accentHover + local colorText = utilGetActiveColor(utilGetChangedColor(colors.default, 25)) + local heightMult = panel:GetHeightMult() + + local leftPad, topPad, rightPad, bottomPad = panel:GetDockPadding() + local widthPad = leftPad + rightPad + local heightPad = topPad + bottomPad + + if not panel:IsEnabled() then + colorBox = ColorAlpha(colorBox, alphaDisabled) + colorBar = ColorAlpha(colorBar, alphaDisabled) + colorText = ColorAlpha(colorText, alphaDisabled) + end + + -- Draw custom box background for the searchBar + drawBox( + leftPad, + h * (1 - heightMult) * 0.5 + topPad, + w - widthPad, + h * heightMult - heightPad, + colorBox + ) + + -- Draw small blue bar on the bottom + drawBox(leftPad, h - sizes.border - bottomPad, w - widthPad, sizes.border, colorBar) + + -- If not focussed draw placeholder text + if panel:GetIsOnFocus() then + return + end + + drawSimpleText( + TryT(panel:GetCurrentPlaceholderText()), + panel:GetFont(), + leftPad + w * 0.02, + 0.5 * h, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) +end + +--- +-- @param Panel panel +-- @param number w +-- @param number h +-- @realm client +function SKIN:PaintTextEntryTTT2(panel, w, h) + local colorBox = colors.settingsBox + local colorHandle = colors.handle + + if not panel:IsEnabled() then + colorBox = ColorAlpha(colors.settingsBox, alphaDisabled) + colorHandle = ColorAlpha(colors.handle, alphaDisabled) + end + + drawBox(0, 0, w, h, colorBox) + drawRoundedBox(sizes.cornerRadius, 1, 1, w - 2, h - 2, colorHandle) end --- @@ -1410,79 +1706,353 @@ end -- @param number h -- @realm client function SKIN:PaintImageCheckBoxTTT2(panel, w, h) - local widthBorder = 2 - local widthBorder2 = widthBorder * 2 - local padding = 5 - local heightMode = 35 - local widthMode = 175 - local posIconModeX = w - widthMode + 2 * padding - local posIconModeY = h - heightMode + 2 * padding - local sizeIconMode = heightMode - 4 * padding - local posTextModeX = posIconModeX + sizeIconMode + 2 * padding - local posTextModeY = posIconModeY + 0.5 * sizeIconMode - 1 - local posStatusIconBoxX = 8 - local sizeStatusIconBox = 32 - local sizeStatusIcon = sizeStatusIconBox - 2 * padding - local posStatusIconX = posStatusIconBoxX + padding - - local posHeadIconBoxY = 8 - local posHeadIconY = posHeadIconBoxY + padding - local posHattableIconBoxY = posHeadIconBoxY + sizeStatusIconBox + padding - local posHattableIconY = posHattableIconBoxY + padding - - local colorBackground = colors.settingsBox - local colorMode = utilGetChangedColor(colors.background, 75) - local colorHeadIcon = colorCardInheritRemoved - local colorHattableIcon = colorCardInheritRemoved - - local materialMode = materialCardRemoved - local materialHeadIcon = materialHeadboxNo - local materialHattableIcon = materialHattableNo - - if panel.Hovered then - colorBackground = colors.accentHover - end - - if panel:IsModelSelected() then - colorMode = colorCardAdded - materialMode = materialCardAdded - end - - if panel:HasHeadBox() then - colorHeadIcon = colorCardAdded - materialHeadIcon = materialHeadboxYes - end - - if panel:IsModelHattable() then - colorHattableIcon = colorCardAdded - materialHattableIcon = materialHattableYes - end - - drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorMode) - drawRoundedBox(sizes.cornerRadius, widthBorder, widthBorder, w - widthBorder2, h - widthBorder2, colorBackground) - - if panel:HasModel() then - panel:DrawModel() - end - - drawRoundedBoxEx(sizes.cornerRadius, w - widthMode, h - heightMode, widthMode, heightMode, colorMode, true, false, false, true) - drawFilteredTexture(posIconModeX, posIconModeY, sizeIconMode, sizeIconMode, materialMode, 175, colorTextMode) - - drawSimpleText( - TryT(panel:GetText()), - "DermaTTT2Text", - posTextModeX, - posTextModeY, - colorTextMode, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) - - drawRoundedBox(sizes.cornerRadius, posStatusIconBoxX, posHeadIconBoxY, sizeStatusIconBox, sizeStatusIconBox, colorHeadIcon) - drawFilteredTexture(posStatusIconX, posHeadIconY, sizeStatusIcon, sizeStatusIcon, materialHeadIcon, 200, COLOR_WHITE) - - drawRoundedBox(sizes.cornerRadius, posStatusIconBoxX, posHattableIconBoxY, sizeStatusIconBox, sizeStatusIconBox, colorHattableIcon) - drawFilteredTexture(posStatusIconX, posHattableIconY, sizeStatusIcon, sizeStatusIcon, materialHattableIcon, 200, COLOR_WHITE) + local widthBorder = 2 + local widthBorder2 = widthBorder * 2 + local padding = 5 + local heightMode = 35 + local widthMode = 175 + local posIconModeX = w - widthMode + 2 * padding + local posIconModeY = h - heightMode + 2 * padding + local sizeIconMode = heightMode - 4 * padding + local posTextModeX = posIconModeX + sizeIconMode + 2 * padding + local posTextModeY = posIconModeY + 0.5 * sizeIconMode - 1 + local posStatusIconBoxX = 8 + local sizeStatusIconBox = 32 + local sizeStatusIcon = sizeStatusIconBox - 2 * padding + local posStatusIconX = posStatusIconBoxX + padding + + local posHeadIconBoxY = 8 + local posHeadIconY = posHeadIconBoxY + padding + local posHattableIconBoxY = posHeadIconBoxY + sizeStatusIconBox + padding + local posHattableIconY = posHattableIconBoxY + padding + + local colorBackground = colors.settingsBox + local colorMode = utilGetChangedColor(colors.background, 75) + local colorTextMode = utilGetDefaultColor(colorMode) + local colorHeadIcon = colorCardInheritRemoved + local colorHattableIcon = colorCardInheritRemoved + + local materialMode = materialCardRemoved + local materialHeadIcon = materialHeadboxNo + local materialHattableIcon = materialHattableNo + + if panel.Hovered then + colorBackground = colors.accentHover + end + + if panel:IsModelSelected() then + colorMode = colorCardAdded + materialMode = materialCardAdded + end + + if panel:HasHeadBox() then + colorHeadIcon = colorCardAdded + materialHeadIcon = materialHeadboxYes + end + + if panel:IsModelHattable() then + colorHattableIcon = colorCardAdded + materialHattableIcon = materialHattableYes + end + + drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorMode) + drawRoundedBox( + sizes.cornerRadius, + widthBorder, + widthBorder, + w - widthBorder2, + h - widthBorder2, + colorBackground + ) + + if panel:HasModel() then + panel:DrawModel() + end + + drawRoundedBoxEx( + sizes.cornerRadius, + w - widthMode, + h - heightMode, + widthMode, + heightMode, + colorMode, + true, + false, + false, + true + ) + drawFilteredTexture( + posIconModeX, + posIconModeY, + sizeIconMode, + sizeIconMode, + materialMode, + 175, + colorTextMode + ) + + drawSimpleText( + TryT(panel:GetText()), + "DermaTTT2Text", + posTextModeX, + posTextModeY, + colorTextMode, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) + + drawRoundedBox( + sizes.cornerRadius, + posStatusIconBoxX, + posHeadIconBoxY, + sizeStatusIconBox, + sizeStatusIconBox, + colorHeadIcon + ) + drawFilteredTexture( + posStatusIconX, + posHeadIconY, + sizeStatusIcon, + sizeStatusIcon, + materialHeadIcon, + 200, + COLOR_WHITE + ) + + drawRoundedBox( + sizes.cornerRadius, + posStatusIconBoxX, + posHattableIconBoxY, + sizeStatusIconBox, + sizeStatusIconBox, + colorHattableIcon + ) + drawFilteredTexture( + posStatusIconX, + posHattableIconY, + sizeStatusIcon, + sizeStatusIcon, + materialHattableIcon, + 200, + COLOR_WHITE + ) +end + +--- +-- @param Panel panel +-- @param number w +-- @param number h +-- @realm client +function SKIN:PaintProfilePanelTTT2(panel, w, h) + local padding = 5 + + local heightBottom = 100 + + local widthRender = w - 2 * padding + local heightRender = h - padding - heightBottom + + local sizePlayerIcon = 64 + local xRoleIcon = 0.5 * (widthRender - sizePlayerIcon) + padding + local yRoleIcon = heightRender + padding - 0.5 * sizePlayerIcon + + local sizePlayerIconBox = sizePlayerIcon + 2 * padding + local xPlayerIconBox = xRoleIcon - padding + local yPlayerIconBox = yRoleIcon - padding + + local sizeRoleIcon = 32 + local posRoleIcon = 2 * padding + + local yTextTeam = h - 26 + local yTextRole = yTextTeam - 22 + local xText = 0.5 * w + + -- cache colors + local colorBackground = colors.background + local colorRole = panel:GetPlayerRoleColor() + local colorRoleText = utilGetDefaultColor(colorRole) + local colorRoleIcon = utilGetDefaultColor(colorBackground) + + -- cache materials + local materialPlayerIcon = panel:GetPlayerIcon() + local materialRole = panel:GetPlayerRoleIcon() + + drawBox(0, 0, w, h, colorRole) + drawBox(padding, padding, widthRender, heightRender, colorBackground) + + if panel:HasModel() then + panel:DrawModel(padding, padding, widthRender, heightRender) + end + + drawBox(xPlayerIconBox, yPlayerIconBox, sizePlayerIconBox, sizePlayerIconBox, colorRole) + drawFilteredTexture( + xRoleIcon, + yRoleIcon, + sizePlayerIcon, + sizePlayerIcon, + materialPlayerIcon, + 255, + COLOR_WHITE + ) + + drawFilteredShadowedTexture( + posRoleIcon, + posRoleIcon, + sizeRoleIcon, + sizeRoleIcon, + materialRole, + 255, + colorRoleIcon + ) + + drawShadowedText( + TryT(panel:GetPlayerRoleString()), + "DermaTTT2TextLargest", + xText, + yTextRole, + colorRoleText, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) + + drawShadowedText( + TryT(panel:GetPlayerTeamString()), + "DermaTTT2TextLarger", + xText, + yTextTeam, + colorRoleText, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) +end + +--- +-- @param Panel panel +-- @param number w +-- @param number h +-- @realm client +function SKIN:PaintInfoItemTTT2(panel, w, h) + local hasIcon = panel:HasIcon() + + local padding = 5 + + local widthBorder = 2 + local widthBorder2 = 2 * widthBorder + + local sizeIcon = 64 + local posIcon = widthBorder + padding + + local posText = hasIcon and (posIcon + sizeIcon + 2 * padding) or (posIcon + padding) + local heightText = 15 + + local colorBackground = panel:GetColor() or colors.settingsBox + local colorBorderDefault = utilGetChangedColor(colors.background, 75) + local colorText = utilGetDefaultColor(colorBackground) + + drawRoundedBox(sizes.cornerRadius, 0, 0, w, h, colorBorderDefault) + drawRoundedBox( + sizes.cornerRadius, + widthBorder, + widthBorder, + w - widthBorder2, + h - widthBorder2, + colorBackground + ) + + if hasIcon then + drawFilteredTexture(posIcon, posIcon, sizeIcon, sizeIcon, panel:GetIcon()) + end + + if hasIcon and panel:HasIconTextFunction() then + local textString = panel:GetIconText() + + local widthIconText = drawGetTextSize(textString, "DermaTTT2SmallBold") + local xIconText = posIcon + 0.5 * sizeIcon + local yIconText = posIcon + 0.75 * sizeIcon + local widthIconTextBox = widthIconText + 8 + local heightIconTextBox = 16 + + local colorLiveTimeBackground = colors.settingsBox + + drawRoundedBox( + sizes.cornerRadius, + xIconText - 0.5 * widthIconTextBox, + yIconText - 7, + widthIconTextBox, + heightIconTextBox, + colorLiveTimeBackground + ) + + drawShadowedText( + textString, + "DermaTTT2SmallBold", + xIconText, + yIconText, + COLOR_ORANGE, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) + end + + local title = panel:GetTitle() + local text_title = "" + + if title.params then + text_title = ParT(title.body, title.params) + else + text_title = TryT(title.body) + end + + drawSimpleText( + text_title, + "DermaTTT2TitleSmall", + posText, + posIcon, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + + local text = panel:GetText() + local text_translated = "" + for i = 1, #text do + local params = text[i].params + local body = text[i].body + + if not body then + continue + end + + text_translated = text_translated .. DynT(body, params, true) .. "" + end + + local text_wrapped = drawGetWrappedText(text_translated, w - posText - padding, "DermaDefault") + + local posY = posIcon + heightText + 4 + + for k = 1, #text_wrapped do + drawSimpleText( + text_wrapped[k], + "DermaDefault", + posText, + posY, + colorText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + + posY = posY + heightText + end +end + +--- +-- @param Panel panel +-- @param number w +-- @param number h +-- @realm client +function SKIN:PaintWeaponPreviewTTT2(panel, w, h) + if panel:HasModel() then + panel:DrawModel() + end end -- REGISTER DERMA SKIN diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbinder_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbinder_ttt2.lua index b7590066a..bc8ab6bb6 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbinder_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbinder_ttt2.lua @@ -14,90 +14,87 @@ Derma_Install_Convar_Functions(PANEL) --- -- @ignore function PANEL:Init() - self:SetSelectedNumber(0) - self:SetSize(60, 30) + self:SetSelectedNumber(0) + self:SetSize(60, 30) end --- -- @realm client function PANEL:UpdateText() - local str = input.GetKeyName(self:GetSelectedNumber()) + local str = input.GetKeyName(self:GetSelectedNumber()) - if not str then - str = "button_none" - end + if not str then + str = "button_none" + end - str = language.GetPhrase(str) + str = language.GetPhrase(str) - self:SetText(str) + self:SetText(str) end --- -- @realm client function PANEL:DoClick() - self:SetText("button_press_key") + self:SetText("button_press_key") - input.StartKeyTrapping() + input.StartKeyTrapping() - self.trapping = true + self.trapping = true end --- -- @realm client function PANEL:DoRightClick() - self:SetText("button_none") - self:SetValue(0) + self:SetText("button_none") + self:SetValue(0) end --- -- @param number iNum -- @realm client function PANEL:SetSelectedNumber(iNum) - self.m_iSelectedNumber = iNum - self:ConVarChanged(iNum) - self:UpdateText() - self:OnChange(iNum) + self.m_iSelectedNumber = iNum + self:ConVarChanged(iNum) + self:UpdateText() + self:OnChange(iNum) end --- -- @ignore function PANEL:Think() - if input.IsKeyTrapping() and self.trapping then - local code = input.CheckKeyTrapping() + if input.IsKeyTrapping() and self.trapping then + local code = input.CheckKeyTrapping() - if code then - if code == KEY_ESCAPE then - self:SetValue(self:GetSelectedNumber()) - else - self:SetValue(code) - end + if code then + if code == KEY_ESCAPE then + self:SetValue(self:GetSelectedNumber()) + else + self:SetValue(code) + end - self.trapping = false - end + self.trapping = false + end + end - end - - self:ConVarNumberThink() + self:ConVarNumberThink() end --- -- @param number iNumValue -- @realm client function PANEL:SetValue(iNumValue) - self:SetSelectedNumber(iNumValue) + self:SetSelectedNumber(iNumValue) end --- -- @return number -- @realm client function PANEL:GetValue() - return self:GetSelectedNumber() + return self:GetSelectedNumber() end --- -- @realm client -function PANEL:OnChange() - -end +function PANEL:OnChange() end derma.DefineControl("DBinderTTT2", "", PANEL, "DButtonTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbinderpanel_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbinderpanel_ttt2.lua index e577d2401..2e1937294 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbinderpanel_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbinderpanel_ttt2.lua @@ -7,38 +7,38 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - self.binder = vgui.Create("DBinderTTT2", self) - self.disable = vgui.Create("DButtonTTT2", self) + self.binder = vgui.Create("DBinderTTT2", self) + self.disable = vgui.Create("DButtonTTT2", self) - self.binder.Paint = function(slf, w, h) - derma.SkinHook("Paint", "BinderButtonTTT2", slf, w, h) + self.binder.Paint = function(slf, w, h) + derma.SkinHook("Paint", "BinderButtonTTT2", slf, w, h) - return false - end + return false + end - self.disable.Paint = function(slf, w, h) - derma.SkinHook("Paint", "FormButtonIconTTT2", slf, w, h) + self.disable.Paint = function(slf, w, h) + derma.SkinHook("Paint", "FormButtonIconTTT2", slf, w, h) - return true - end + return true + end end --- -- @ignore function PANEL:PerformLayout(w, h) - self.binder:SetSize(150, h) - self.binder:SetPos(0, 0) + self.binder:SetSize(150, h) + self.binder:SetPos(0, 0) - self.disable:SetSize(h, h) - self.disable:SetPos(w - h, 0) + self.disable:SetSize(h, h) + self.disable:SetPos(w - h, 0) end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "BinderPanelTTT2", self, w, h) + derma.SkinHook("Paint", "BinderPanelTTT2", self, w, h) - return false + return false end derma.DefineControl("DBinderPanelTTT2", "", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbutton_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbutton_ttt2.lua index 6d73bbe8d..4b818f9b4 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbutton_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbutton_ttt2.lua @@ -12,44 +12,64 @@ AccessorFunc(PANEL, "m_bBorder", "DrawBorder", FORCE_BOOL) --- -- @ignore function PANEL:Init() - self:SetContentAlignment(5) + self:SetContentAlignment(5) - self:SetTall(22) - self:SetMouseInputEnabled(true) - self:SetKeyboardInputEnabled(true) + self:SetTall(22) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) - self:SetCursor("hand") - self:SetFont("DermaTTT2Button") + self:SetCursor("hand") + self:SetFont("DermaTTT2Button") + -- remove label and overwrite function + self:SetText("") + self.SetText = function(slf, text) + slf.data.text = text + end - self.text = "" - - -- remove label and overwrite function - self:SetText("") - self.SetText = function(slf, text) - slf.text = text - end + self.data = {} end --- -- @return string -- @realm client function PANEL:GetText() - return self.text + return self.data.text +end + +--- +-- @param table params +-- @realm client +function PANEL:SetTextParams(params) + self.data.params = params +end + +--- +-- @return table +-- @realm client +function PANEL:GetTextParams() + return self.data.params +end + +--- +-- @return boolean +-- @realm client +function PANEL:HasTextParams() + return self.data.params ~= nil end --- -- @return boolean -- @realm client function PANEL:IsDown() - return self.Depressed + return self.Depressed end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "ButtonTTT2", self, w, h) + derma.SkinHook("Paint", "ButtonTTT2", self, w, h) - return false + return false end --- @@ -57,17 +77,70 @@ end -- @param string strArgs -- @realm client function PANEL:SetConsoleCommand(strName, strArgs) - self.DoClick = function(slf, val) - RunConsoleCommand(strName, strArgs) - end + self.DoClick = function(slf, val) + RunConsoleCommand(strName, strArgs) + end +end + +--- +-- @param Color color +-- @realm client +function PANEL:SetColor(color) + self.data.color = color +end + +--- +-- @return Color|nil +-- @realm client +function PANEL:GetColor() + return self.data.color +end + +--- +-- @param Material icon +-- @param[default=false] boolean is_shadowed +-- @param[default=32] number size +-- @realm client +function PANEL:SetIcon(icon, is_shadowed, size) + self.data.icon = icon + self.data.icon_shadow = is_shadowed or false + self.data.icon_size = size or 32 +end + +--- +-- @return Material|nil +-- @realm client +function PANEL:GetIcon() + return self.data.icon +end + +--- +-- @return boolean +-- @realm client +function PANEL:HasIcon() + return self.data.icon ~= nil +end + +--- +-- @return boolean|nil +-- @realm client +function PANEL:IsIconShadowed() + return self.data.icon_shadow +end + +--- +-- @return number|nil +-- @realm client +function PANEL:GetIconSize() + return self.data.icon_size end --- -- @ignore function PANEL:SizeToContents() - local w, h = self:GetContentSize() + local w, h = self:GetContentSize() - self:SetSize(w + 8, h + 4) + self:SetSize(w + 8, h + 4) end derma.DefineControl("DButtonTTT2", "A standard Button", PANEL, "DLabelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbuttonpanel_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbuttonpanel_ttt2.lua index 6a41888cf..61e9a4824 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbuttonpanel_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dbuttonpanel_ttt2.lua @@ -41,73 +41,73 @@ Derma_Hook(PANEL, "PerformLayout", "Layout", "Panel") --- -- @ignore function PANEL:Init() - self:SetPaintBackground(true) + self:SetPaintBackground(true) - -- This turns off the engine drawing - self:SetPaintBackgroundEnabled(false) - self:SetPaintBorderEnabled(false) + -- This turns off the engine drawing + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "ButtonPanelTTT2", self, w, h) + derma.SkinHook("Paint", "ButtonPanelTTT2", self, w, h) - return false + return false end --- -- @param boolean bEnabled -- @realm client function PANEL:SetEnabled(bEnabled) - self.m_bEnabled = not bEnabled - - if bEnabled then - self:SetAlpha(255) - self:SetMouseInputEnabled(true) - else - self:SetAlpha(75) - self:SetMouseInputEnabled(false) - end + self.m_bEnabled = not bEnabled + + if bEnabled then + self:SetAlpha(255) + self:SetMouseInputEnabled(true) + else + self:SetAlpha(75) + self:SetMouseInputEnabled(false) + end end --- -- @return boolean -- @realm client function PANEL:IsEnabled() - return self.m_bEnabled + return self.m_bEnabled end --- -- @param number mcode -- @realm client function PANEL:OnMousePressed(mcode) - if self:IsSelectionCanvas() and not dragndrop.IsDragging() then - self:StartBoxSelection() + if self:IsSelectionCanvas() and not dragndrop.IsDragging() then + self:StartBoxSelection() - return - end + return + end - if self:IsDraggable() then - self:MouseCapture(true) - self:DragMousePress(mcode) - end + if self:IsDraggable() then + self:MouseCapture(true) + self:DragMousePress(mcode) + end end --- -- @param number mcode -- @realm client function PANEL:OnMouseReleased(mcode) - if self:EndBoxSelection() then return end + if self:EndBoxSelection() then + return + end - self:MouseCapture(false) + self:MouseCapture(false) end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:UpdateColours() - -end +function PANEL:UpdateColours() end derma.DefineControl("DButtonPanelTTT2", "", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcard_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcard_ttt2.lua index 3f120d261..6bf74fb1c 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcard_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcard_ttt2.lua @@ -12,47 +12,47 @@ local MODE_INHERIT_REMOVED = ShopEditor.MODE_INHERIT_REMOVED --- -- @ignore function PANEL:Init() - self:SetContentAlignment(5) - - self:SetTall(22) - self:SetMouseInputEnabled(true) - self:SetKeyboardInputEnabled(true) - - self:SetCursor("hand") - self:SetFont("DermaTTT2TextLarge") - - -- remove label and overwrite function - self:SetText("") - self.SetText = function(slf, text) - slf.data.text = text - end - - self.data = { - title = "", - icon = nil, - mode = MODE_DEFAULT - } + self:SetContentAlignment(5) + + self:SetTall(22) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) + + self:SetCursor("hand") + self:SetFont("DermaTTT2TextLarge") + + -- remove label and overwrite function + self:SetText("") + self.SetText = function(slf, text) + slf.data.text = text + end + + self.data = { + title = "", + icon = nil, + mode = MODE_DEFAULT, + } end --- -- @return string -- @realm client function PANEL:GetText() - return self.data.text + return self.data.text end --- -- @param Material icon -- @realm client function PANEL:SetIcon(icon) - self.data.icon = icon + self.data.icon = icon end --- -- @return Matieral -- @realm client function PANEL:GetIcon() - return self.data.icon + return self.data.icon end --- @@ -60,60 +60,58 @@ end -- @param[default=false] boolean triggerFunction -- @realm client function PANEL:SetMode(mode, triggerFunction) - if triggerFunction then - self:OnModeChanged(self.data.mode or MODE_DEFAULT, mode or MODE_DEFAULT) - end + if triggerFunction then + self:OnModeChanged(self.data.mode or MODE_DEFAULT, mode or MODE_DEFAULT) + end - self.data.mode = mode or MODE_DEFAULT + self.data.mode = mode or MODE_DEFAULT end --- -- @return number -- @realm client function PANEL:GetMode() - return self.data.mode + return self.data.mode end --- -- @param number keyCode -- @realm client function PANEL:OnMouseReleased(keyCode) - if keyCode == MOUSE_LEFT then - if self:GetMode() == MODE_DEFAULT then - self:SetMode(MODE_ADDED, true) - elseif self:GetMode() == MODE_INHERIT_REMOVED then - self:SetMode(MODE_INHERIT_ADDED, true) - elseif self:GetMode() == MODE_ADDED then - self:SetMode(MODE_DEFAULT, true) - elseif self:GetMode() == MODE_INHERIT_ADDED then - self:SetMode(MODE_INHERIT_REMOVED, true) - end - end - - self.BaseClass.OnMouseReleased(self, keyCode) + if keyCode == MOUSE_LEFT then + if self:GetMode() == MODE_DEFAULT then + self:SetMode(MODE_ADDED, true) + elseif self:GetMode() == MODE_INHERIT_REMOVED then + self:SetMode(MODE_INHERIT_ADDED, true) + elseif self:GetMode() == MODE_ADDED then + self:SetMode(MODE_DEFAULT, true) + elseif self:GetMode() == MODE_INHERIT_ADDED then + self:SetMode(MODE_INHERIT_REMOVED, true) + end + end + + self.BaseClass.OnMouseReleased(self, keyCode) end --- -- @param number oldMode -- @param number newMode -- @realm client -function PANEL:OnModeChanged(oldMode, newMode) - -end +function PANEL:OnModeChanged(oldMode, newMode) end --- -- @return boolean -- @realm client function PANEL:IsDown() - return self.Depressed + return self.Depressed end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "CardTTT2", self, w, h) + derma.SkinHook("Paint", "CardTTT2", self, w, h) - return false + return false end derma.DefineControl("DCardTTT2", "A special button used for the shop editor", PANEL, "DLabelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcategorycollapse_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcategorycollapse_ttt2.lua index 9ec5609df..1e6a7ae56 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcategorycollapse_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcategorycollapse_ttt2.lua @@ -37,217 +37,221 @@ AccessorFunc(PANEL, "m_pList", "List", "Panel") --- -- @ignore function PANEL:Init() - self.Header = vgui.Create("DCategoryHeaderTTT2", self) - self.Header:Dock(TOP) - self.Header:SetSize(20, vskin.GetCollapsableHeight()) + self.Header = vgui.Create("DCategoryHeaderTTT2", self) + self.Header:Dock(TOP) + self.Header:SetSize(20, vskin.GetCollapsableHeight()) - self:SetSize(16, 16) - self:SetExpanded(true) - self:SetMouseInputEnabled(true) + self:SetSize(16, 16) + self:SetExpanded(true) + self:SetMouseInputEnabled(true) - self:SetPaintBackground(true) - self:DockMargin(10, 10, 10, 5) - self:DockPadding(0, 0, 0, 0) + self:SetPaintBackground(true) + self:DockMargin(10, 10, 10, 5) + self:DockPadding(0, 0, 0, 0) end --- -- @param string strName -- @realm client function PANEL:Add(strName) - local button = vgui.Create("DButton", self) + local button = vgui.Create("DButton", self) - button.Paint = function(panel, w, h) - derma.SkinHook("Paint", "CategoryButtonTTT2", panel, w, h) - end + button.Paint = function(panel, w, h) + derma.SkinHook("Paint", "CategoryButtonTTT2", panel, w, h) + end - button:SetHeight(17) - button:SetTextInset(4, 0) + button:SetHeight(17) + button:SetTextInset(4, 0) - button:SetContentAlignment(4) - button:DockMargin(1, 0, 1, 0) - button.DoClickInternal = function() - if self:GetList() then - self:GetList():UnselectAll() - else - self:UnselectAll() - end + button:SetContentAlignment(4) + button:DockMargin(1, 0, 1, 0) + button.DoClickInternal = function() + if self:GetList() then + self:GetList():UnselectAll() + else + self:UnselectAll() + end - button:SetSelected(true) - end + button:SetSelected(true) + end - button:Dock(TOP) - button:SetText(strName) + button:Dock(TOP) + button:SetText(strName) - self:InvalidateLayout(true) - self:UpdateAltLines() + self:InvalidateLayout(true) + self:UpdateAltLines() - return button + return button end --- -- @realm client function PANEL:UnselectAll() - local children = self:GetChildren() + local children = self:GetChildren() - for i = 1, #children do - local child = children[i] + for i = 1, #children do + local child = children[i] - if not isfunction(child.SetSelected) then continue end + if not isfunction(child.SetSelected) then + continue + end - child:SetSelected(false) - end + child:SetSelected(false) + end end --- -- @realm client function PANEL:UpdateAltLines() - local children = self:GetChildren() + local children = self:GetChildren() - for i = 1, #children do - children[i].AltLine = i % 2 ~= 1 - end + for i = 1, #children do + children[i].AltLine = i % 2 ~= 1 + end end --- -- @param string strLabel -- @realm client function PANEL:SetLabel(strLabel) - self.Header.text = strLabel + self.Header.text = strLabel end --- -- @return string label text -- @realm client function PANEL:GetLabel() - return self.Header.text + return self.Header.text end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "CollapsibleCategoryTTT2", self, w, h) + derma.SkinHook("Paint", "CollapsibleCategoryTTT2", self, w, h) - return false + return false end --- -- @param Panel pContens -- @realm client function PANEL:SetContents(pContents) - self.Contents = pContents - self.Contents:SetParent(self) - self.Contents:Dock(FILL) - - if not self:GetExpanded() then - self.OldHeight = self:GetTall() - elseif self:GetExpanded() and IsValid(self.Contents) and self.Contents:GetTall() < 1 then - self.Contents:SizeToChildren(false, true) - self.OldHeight = self.Contents:GetTall() - self:SetTall(self.OldHeight) - end - - self:InvalidateLayout(true) + self.Contents = pContents + self.Contents:SetParent(self) + self.Contents:Dock(FILL) + + if not self:GetExpanded() then + self.OldHeight = self:GetTall() + elseif self:GetExpanded() and IsValid(self.Contents) and self.Contents:GetTall() < 1 then + self.Contents:SizeToChildren(false, true) + self.OldHeight = self.Contents:GetTall() + self:SetTall(self.OldHeight) + end + + self:InvalidateLayout(true) end --- -- @param boolean expanded -- @realm client function PANEL:SetExpanded(expanded) - self.m_bSizeExpanded = tobool(expanded) + self.m_bSizeExpanded = tobool(expanded) - if not self:GetExpanded() then - self.OldHeight = self:GetTall() - end + if not self:GetExpanded() then + self.OldHeight = self:GetTall() + end end --- -- @realm client function PANEL:Toggle() - self:SetExpanded(not self:GetExpanded()) - self:InvalidateLayout(true) - self:OnToggle(self:GetExpanded()) + self:SetExpanded(not self:GetExpanded()) + self:InvalidateLayout(true) + self:OnToggle(self:GetExpanded()) end --- -- @param boolean expanded -- @realm client -function PANEL:OnToggle(expanded) - -end +function PANEL:OnToggle(expanded) end --- -- @param boolean b -- @realm client function PANEL:DoExpansion(b) - if self:GetExpanded() == b then return end + if self:GetExpanded() == b then + return + end - self:Toggle() + self:Toggle() end --- -- @ignore function PANEL:PerformLayout() - if IsValid(self.Contents) then - if self:GetExpanded() then - self.Contents:InvalidateLayout(true) - self.Contents:SetVisible(true) - else - self.Contents:SetVisible(false) - end - end - - if self:GetExpanded() then - if IsValid(self.Contents) and #self.Contents:GetChildren() > 0 then - self.Contents:SizeToChildren(false, true) - end - - self:SizeToChildren(false, true) - - -- hacky solution to make sure box is always big enough - -- I don't know why I have to do this though - local w, h = self:GetSize() - self:SetSize(w, h + 15) - else - if IsValid(self.Contents) and not self.OldHeight then - self.OldHeight = self.Contents:GetTall() - end - - self:SetTall(self.Header:GetTall() + vskin:GetBorderSize() + 2) - end - - -- Make sure the color of header text is set - self.Header:ApplySchemeSettings() - self.Header:SetSize(20, vskin.GetCollapsableHeight()) - - self:UpdateAltLines() + if IsValid(self.Contents) then + if self:GetExpanded() then + self.Contents:InvalidateLayout(true) + self.Contents:SetVisible(true) + else + self.Contents:SetVisible(false) + end + end + + if self:GetExpanded() then + if IsValid(self.Contents) and #self.Contents:GetChildren() > 0 then + self.Contents:SizeToChildren(false, true) + end + + self:SizeToChildren(false, true) + + -- hacky solution to make sure box is always big enough + -- I don't know why I have to do this though + local w, h = self:GetSize() + self:SetSize(w, h + 15) + else + if IsValid(self.Contents) and not self.OldHeight then + self.OldHeight = self.Contents:GetTall() + end + + self:SetTall(self.Header:GetTall() + vskin:GetBorderSize() + 2) + end + + -- Make sure the color of header text is set + self.Header:ApplySchemeSettings() + self.Header:SetSize(20, vskin.GetCollapsableHeight()) + + self:UpdateAltLines() end --- -- @param number mcode -- @realm client function PANEL:OnMousePressed(mcode) - if not self:GetParent().OnMousePressed then return end + if not self:GetParent().OnMousePressed then + return + end - return self:GetParent():OnMousePressed(mcode) + return self:GetParent():OnMousePressed(mcode) end --- -- @ignore function PANEL:GenerateExample(ClassName, PropertySheet, Width, Height) - local ctrl = vgui.Create(ClassName) + local ctrl = vgui.Create(ClassName) - ctrl:SetLabel("Category List Test Category") - ctrl:SetSize(300, 300) - ctrl:SetPadding(10) + ctrl:SetLabel("Category List Test Category") + ctrl:SetSize(300, 300) + ctrl:SetPadding(10) - -- The contents can be any panel, even a DPanelList - local Contents = vgui.Create("DButton") + -- The contents can be any panel, even a DPanelList + local Contents = vgui.Create("DButton") - Contents:SetText("This is the content of the control") - ctrl:SetContents(Contents) - ctrl:InvalidateLayout(true) + Contents:SetText("This is the content of the control") + ctrl:SetContents(Contents) + ctrl:InvalidateLayout(true) - PropertySheet:AddSheet(ClassName, ctrl, nil, true, true) + PropertySheet:AddSheet(ClassName, ctrl, nil, true, true) end derma.DefineControl("DCollapsibleCategoryTTT2", "Collapsable Category Panel", PANEL, "Panel") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcategoryheader_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcategoryheader_ttt2.lua index c117991c8..74dba4f7d 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcategoryheader_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcategoryheader_ttt2.lua @@ -7,27 +7,27 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - self:SetContentAlignment(4) - self:SetTextInset(5, 0) - self:SetFont("DermaTTT2CatHeader") + self:SetContentAlignment(4) + self:SetTextInset(5, 0) + self:SetFont("DermaTTT2CatHeader") - self.text = "" + self.text = "" - self:SetText("") + self:SetText("") end --- -- @ignore function PANEL:DoClick() - self:GetParent():Toggle() + self:GetParent():Toggle() end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "CategoryHeaderTTT2", self, w, h) + derma.SkinHook("Paint", "CategoryHeaderTTT2", self, w, h) - return false + return false end derma.DefineControl("DCategoryHeaderTTT2", "Category Header", PANEL, "DButton") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcheckboxlabel_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcheckboxlabel_ttt2.lua index bee3a0176..3da311dde 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcheckboxlabel_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcheckboxlabel_ttt2.lua @@ -12,268 +12,357 @@ AccessorFunc(PANEL, "m_iIndent", "Indent") --- -- @accessor bool -- @realm client -AccessorFunc(PANEL, "ignoreConVar", "IgnoreConVar", FORCE_BOOL) +AccessorFunc(PANEL, "ignoreCallbackEnabledVar", "IgnoreCallbackEnabledVar", FORCE_BOOL) --- -- @ignore function PANEL:Init() - self.Button = vgui.Create("DCheckBox", self) - self.Button.OnChange = function(_, val) - self:ValueChanged(val) + self.Button = vgui.Create("DCheckBox", self) + self.Button.OnChange = function(_, val) + self:ValueChanged(val) - -- enable / disable slaves on change - self:UpdateSlaves(val) - end + -- enable / disable slaves on change + self:UpdateSlaves(val) + end - local oldSetEnabled = self.SetEnabled + local oldSetEnabled = self.SetEnabled - self.SetEnabled = function(slf, enabled) - oldSetEnabled(slf, enabled) + self.SetEnabled = function(slf, enabled) + oldSetEnabled(slf, enabled) - slf.Button:SetEnabled(enabled) + slf.Button:SetEnabled(enabled) - -- make sure sub-slaves are updated as well - if not enabled then - slf:UpdateSlaves(false) - else - slf:UpdateSlaves(slf.Button:GetChecked()) - end - end + -- make sure sub-slaves are updated as well + if not enabled then + slf:UpdateSlaves(false) + else + slf:UpdateSlaves(slf.Button:GetChecked()) + end + end - self:SetFont("DermaTTT2Text") + self:SetFont("DermaTTT2Text") - -- store slaves in here to be updates on change of this value - self.slaves = {} + -- store slaves in here to be updates on change of this value + self.slaves = {} end --- -- @param boolean val -- @realm client function PANEL:UpdateSlaves(val) - for i = 1, #self.slaves do - self.slaves[i]:SetEnabled(val) - end + for i = 1, #self.slaves do + self.slaves[i]:SetEnabled(val) + end end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "CheckBoxLabel", self, w, h) + derma.SkinHook("Paint", "CheckBoxLabel", self, w, h) - return true + return true +end + +--- +-- This is only used temporarily to keep old variables without breaking the style of "no enable to disable" checkboxes +-- @param bool invert +-- @realm client +function PANEL:SetInverted(invert) + self.inverted = invert end --- -- @param string cvar -- @realm client function PANEL:SetConVar(cvar) - if not ConVarExists(cvar or "") then return end + if not ConVarExists(cvar or "") then + return + end - self.Button:SetConVar(cvar) - self:SetDefaultValue(tobool(GetConVar(cvar):GetDefault())) + self.Button:SetConVar(cvar) + self:SetDefaultValue(tobool(GetConVar(cvar):GetDefault())) end +local callbackEnabledVarTracker = 0 --- -- @param string cvar -- @realm client function PANEL:SetServerConVar(cvar) - if not cvar or cvar == "" then return end + if not cvar or cvar == "" then + return + end + + self.serverConVar = cvar + + -- Check if self is valid before calling SetValue. + cvars.ServerConVarGetValue(cvar, function(wasSuccess, value, default) + if wasSuccess and IsValid(self) then + self:SetValue(tobool(value), true) + self:SetDefaultValue(tobool(default)) + end + end) + + callbackEnabledVarTracker = callbackEnabledVarTracker + 1 + local myIdentifierString = "TTT2CheckBoxConVarChangeCallback" + .. tostring(callbackEnabledVarTracker) + + local callback = function(conVarName, oldValue, newValue) + if not IsValid(self) then + -- We need to remove the callback in a timer, because otherwise the ConVar change callback code + -- will throw an error while looping over the callbacks. + -- This happens, because the callback is removed from the same table that is iterated over. + -- Thus, the table size changes while iterating over it and leads to a nil callback as the last entry. + timer.Simple(0, function() + cvars.RemoveChangeCallback(conVarName, myIdentifierString) + end) + + return + end + + self:SetValue(tobool(newValue), true) + end + + cvars.AddChangeCallback(cvar, callback, myIdentifierString) +end + +--- +-- @param table databaseInfo containing {name, itemName, key} +-- @realm client +function PANEL:SetDatabase(databaseInfo) + if not istable(databaseInfo) then + return + end + + local name = databaseInfo.name + local itemName = databaseInfo.itemName + local key = databaseInfo.key - self.serverConVar = cvar + if not name or not itemName or not key then + return + end - cvars.ServerConVarGetValue(cvar, function(wasSuccess, value, default) - if wasSuccess then - self:SetValue(tobool(value), true) - self:SetDefaultValue(tobool(default)) - end - end) + self.databaseInfo = databaseInfo - local function OnServerConVarChangeCallback(conVarName, oldValue, newValue) - if not IsValid(self) then - cvars.RemoveChangeCallback(conVarName, "TTT2F1MenuServerConVarChangeCallback") + database.GetValue(name, itemName, key, function(databaseExists, value) + if databaseExists then + self:SetValue(value, true) + end + end) - return - end + self:SetDefaultValue(database.GetDefaultValue(name, itemName, key)) - self:SetValue(tobool(newValue), true) - end + callbackEnabledVarTracker = callbackEnabledVarTracker + 1 + local myIdentifierString = "TTT2CheckBoxDatabaseChangeCallback" + .. tostring(callbackEnabledVarTracker) - cvars.AddChangeCallback(cvar, OnServerConVarChangeCallback, "TTT2F1MenuServerConVarChangeCallback") + local function OnDatabaseChangeCallback(_name, _itemName, _key, oldValue, newValue) + if not IsValid(self) then + database.RemoveChangeCallback(name, itemName, key, myIdentifierString) + + return + end + + self:SetValue(newValue, true) + end + + database.AddChangeCallback(name, itemName, key, OnDatabaseChangeCallback, myIdentifierString) end --- -- @param any val --- @param bool ignoreConVar To avoid endless loops, separated setting of convars and UI values +-- @param boolean ignoreCallbackEnabledVar To avoid endless loops, separated setting of convars and UI values -- @realm client -function PANEL:SetValue(val, ignoreConVar) - self:SetIgnoreConVar(ignoreConVar) - self.Button:SetValue(val) +function PANEL:SetValue(val, ignoreCallbackEnabledVar) + self:SetIgnoreCallbackEnabledVar(ignoreCallbackEnabledVar) + + if self.inverted then + val = not val + end + + self.Button:SetValue(val) end --- --- @param bool value +-- @param boolean value -- @realm client function PANEL:SetDefaultValue(value) - local noDefault = true + local noDefault = true - if isbool(value) then - self.default = value - noDefault = false - else - self.default = nil - end + if isbool(value) then + if self.inverted then + value = not value + end - local reset = self:GetResetButton() + self.default = value + noDefault = false + else + self.default = nil + end - if ispanel(reset) then - reset.noDefault = noDefault - end + local reset = self:GetResetButton() + + if ispanel(reset) then + reset.noDefault = noDefault + end end --- --- @return bool defaultValue, if unset returns false +-- @return boolean defaultValue, if unset returns false -- @realm client function PANEL:GetDefaultValue() - return tobool(self.default) + return tobool(self.default) end --- -- @param any val -- @realm client function PANEL:SetChecked(val) - self.Button:SetChecked(val) + self.Button:SetChecked(val) end --- -- @return any -- @realm client function PANEL:GetChecked() - return self.Button:GetChecked() + return self.Button:GetChecked() end --- -- @realm client function PANEL:Toggle() - self.Button:Toggle() + self.Button:Toggle() end --- -- @param Panel reset -- @realm client function PANEL:SetResetButton(reset) - if not ispanel(reset) then return end + if not ispanel(reset) then + return + end - self.resetButton = reset + self.resetButton = reset - reset.DoClick = function(slf) - self:SetValue(self:GetDefaultValue()) - end + reset.DoClick = function(slf) + self:SetValue(self:GetDefaultValue()) + end - reset.noDefault = self.default == nil + reset.noDefault = self.default == nil end --- -- @return Panel reset -- @realm client function PANEL:GetResetButton() - return self.resetButton + return self.resetButton end --- -- @param Panel slave -- @realm client function PANEL:AddSlave(slave) - if not IsValid(slave) then return end + if not IsValid(slave) then + return + end - self.slaves[#self.slaves + 1] = slave + self.slaves[#self.slaves + 1] = slave - slave:SetEnabled(self.Button:GetChecked()) + slave:SetEnabled(self.Button:GetChecked()) end --- -- @ignore function PANEL:PerformLayout() - local x = self.m_iIndent or 0 + local x = self.m_iIndent or 0 - local height = self:GetTall() - local paddingButton = 4 - local heightButton = height - 2 * paddingButton - local widthButton = 1.5 * heightButton + local height = self:GetTall() + local paddingButton = 4 + local heightButton = height - 2 * paddingButton + local widthButton = 1.5 * heightButton - self.Button:SetSize(widthButton, heightButton) - self.Button:SetPos(x + paddingButton, paddingButton) + self.Button:SetSize(widthButton, heightButton) + self.Button:SetPos(x + paddingButton, paddingButton) - self.textPos = 2 * paddingButton + widthButton + x + 5 + self.textPos = 2 * paddingButton + widthButton + x + 5 end --- -- @return number -- @realm client function PANEL:GetTextPosition() - return self.textPos or 0 + return self.textPos or 0 end --- -- @param string text -- @realm client function PANEL:SetText(text) - self.text = text + self.text = text end --- -- @param table params -- @realm client -function PANEL:SetParams(params) - self.params = params +function PANEL:SetTextParams(params) + self.params = params end --- -- @param string font -- @realm client function PANEL:SetFont(font) - self.font = font + self.font = font end --- -- @return string -- @realm client function PANEL:GetFont() - return self.font or "" + return self.font or "" end --- -- @return string -- @realm client function PANEL:GetText() - return self.text or "" + return self.text or "" end --- -- @return table -- @realm client -function PANEL:GetParams() - return self.params +function PANEL:GetTextParams() + return self.params end --- -- @param any val -- @realm client function PANEL:ValueChanged(val) - if self.serverConVar and not self:GetIgnoreConVar() then - cvars.ChangeServerConVar(self.serverConVar, val and "1" or "0") - else - self:SetIgnoreConVar(false) - end - - self:OnValueChanged(val) + if self.inverted then + val = not val + end + + if self.serverConVar and not self:GetIgnoreCallbackEnabledVar() then + cvars.ChangeServerConVar(self.serverConVar, val and "1" or "0") + elseif self.databaseInfo and not self:GetIgnoreCallbackEnabledVar() then + database.SetValue( + self.databaseInfo.name, + self.databaseInfo.itemName, + self.databaseInfo.key, + val + ) + else + self:SetIgnoreCallbackEnabledVar(false) + end + + self:OnValueChanged(val) end --- -- overwrites the base function with an empty function -- @param any val -- @realm client -function PANEL:OnValueChanged(val) - -end +function PANEL:OnValueChanged(val) end derma.DefineControl("DCheckBoxLabelTTT2", "", PANEL, "DButtonTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcoloredbox_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcoloredbox_ttt2.lua index a860af99a..ac63e6345 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcoloredbox_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcoloredbox_ttt2.lua @@ -7,15 +7,15 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - self:SetText("") + self:SetText("") - self.contents = { - title = "", - title_font = "DermaTTT2MenuButtonTitle", - color = COLOR_WHITE, - parent = nil, - shift = 0 - } + self.contents = { + title = "", + title_font = "DermaTTT2MenuButtonTitle", + color = COLOR_WHITE, + parent = nil, + shift = 0, + } end --- @@ -23,43 +23,43 @@ end -- @param number shift -- @realm client function PANEL:SetDynamicColor(parent, shift) - self.contents.parent = parent - self.contents.shift = shift + self.contents.parent = parent + self.contents.shift = shift end --- -- @return boolean -- @realm client function PANEL:HasDynamicColor() - return self.contents.parent ~= nil + return self.contents.parent ~= nil end --- -- @return Color -- @realm client function PANEL:GetDynamicParentColor() - return self.contents.parent.dynBaseColor + return self.contents.parent.dynBaseColor end --- -- @return Color -- @realm client function PANEL:GetDynamicParentColorShift() - return self.contents.shift + return self.contents.shift end --- -- @param Color color -- @realm client function PANEL:SetColor(color) - self.contents.color = color or COLOR_WHITE + self.contents.color = color or COLOR_WHITE end --- -- @return Color -- @realm client function PANEL:GetColor() - return self.contents.color + return self.contents.color end --- @@ -67,9 +67,9 @@ end -- @param number h -- @realm client function PANEL:Paint(w, h) - derma.SkinHook("Paint", "ColoredBoxTTT2", self, w, h) + derma.SkinHook("Paint", "ColoredBoxTTT2", self, w, h) - return false + return false end derma.DefineControl("DColoredBoxTTT2", "", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcoloredtextbox_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcoloredtextbox_ttt2.lua index 2b527ffb2..ab67c74dd 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcoloredtextbox_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcoloredtextbox_ttt2.lua @@ -7,19 +7,19 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - self:SetText("") + self:SetText("") - self.contents = { - title = "", - title_font = "DermaTTT2Text", - opacity = 1.0, - align = TEXT_ALIGN_CENTER, - color = COLOR_WHITE, - flashcolor = false, - icon = nil, - parent = nil, - shift = 0 - } + self.contents = { + title = "", + title_font = "DermaTTT2Text", + opacity = 1.0, + align = TEXT_ALIGN_CENTER, + color = COLOR_WHITE, + flashcolor = false, + icon = nil, + parent = nil, + shift = 0, + } end --- @@ -27,134 +27,134 @@ end -- @param number shift -- @realm client function PANEL:SetDynamicColor(parent, shift) - self.contents.parent = parent - self.contents.shift = shift + self.contents.parent = parent + self.contents.shift = shift end --- -- @return boolean -- @realm client function PANEL:HasDynamicColor() - return self.contents.parent ~= nil + return self.contents.parent ~= nil end --- -- @return Color -- @realm client function PANEL:GetDynamicParentColor() - return self.contents.parent.dynBaseColor + return self.contents.parent.dynBaseColor end --- -- @return Color -- @realm client function PANEL:GetDynamicParentColorShift() - return self.contents.shift + return self.contents.shift end --- -- @param string title -- @realm client function PANEL:SetTitle(title) - self.contents.title = title or "" + self.contents.title = title or "" end --- -- @return string -- @realm client function PANEL:GetTitle() - return self.contents.title + return self.contents.title end --- -- @param string title_font -- @realm client function PANEL:SetTitleFont(title_font) - self.contents.title_font = title_font or "" + self.contents.title_font = title_font or "" end --- -- @return string -- @realm client function PANEL:GetTitleFont() - return self.contents.title_font + return self.contents.title_font end --- -- @param number opacity -- @realm client function PANEL:SetTitleOpacity(opacity) - self.contents.opacity = opacity or 1.0 + self.contents.opacity = opacity or 1.0 end --- -- @return number -- @realm client function PANEL:GetTitleOpacity() - return self.contents.opacity + return self.contents.opacity end --- -- @param number align -- @realm client function PANEL:SetTitleAlign(align) - self.contents.align = align or TEXT_ALIGN_CENTER + self.contents.align = align or TEXT_ALIGN_CENTER end --- -- @return number -- @realm client function PANEL:GetTitleAlign() - return self.contents.align + return self.contents.align end --- -- @param Color color -- @realm client function PANEL:SetColor(color) - self.contents.color = color or COLOR_WHITE + self.contents.color = color or COLOR_WHITE end --- -- @return Color -- @realm client function PANEL:GetColor() - return self.contents.color + return self.contents.color end --- -- @param Color color -- @realm client function PANEL:EnableFlashColor(enb) - self.contents.flashcolor = enb + self.contents.flashcolor = enb end --- -- @return boolean -- @realm client function PANEL:HasFlashColor() - return self.contents.flashcolor or false + return self.contents.flashcolor or false end --- -- @param Material icon -- @realm client function PANEL:SetIcon(icon) - self.contents.icon = icon + self.contents.icon = icon end --- -- @return Material -- @realm client function PANEL:GetIcon() - return self.contents.icon + return self.contents.icon end --- -- @return boolean -- @realm client function PANEL:HasIcon() - return self.contents.icon ~= nil + return self.contents.icon ~= nil end --- @@ -162,9 +162,9 @@ end -- @param number h -- @realm client function PANEL:Paint(w, h) - derma.SkinHook("Paint", "ColoredTextBoxTTT2", self, w, h) + derma.SkinHook("Paint", "ColoredTextBoxTTT2", self, w, h) - return false + return false end derma.DefineControl("DColoredTextBoxTTT2", "", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcombobox_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcombobox_ttt2.lua index 5eed77dce..142255d73 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcombobox_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcombobox_ttt2.lua @@ -16,48 +16,77 @@ AccessorFunc(PANEL, "m_bDoSort", "SortItems", FORCE_BOOL) --- -- @ignore function PANEL:Init() - self.DropButton = vgui.Create("DPanel", self) - self.DropButton:SetMouseInputEnabled(false) - self.DropButton.ComboBox = self + self.DropButton = vgui.Create("DPanel", self) + self.DropButton:SetMouseInputEnabled(false) + self.DropButton.ComboBox = self - self.DropButton.Paint = function(panel, w, h) - derma.SkinHook("Paint", "ComboDownArrow", panel, w, h) - end + self.DropButton.Paint = function(panel, w, h) + derma.SkinHook("Paint", "ComboDownArrow", panel, w, h) + end - self:SetTall(22) - self:Clear() + self:SetTall(22) + self:Clear() - self:SetContentAlignment(4) - self:SetTextInset(8, 0) - self:SetIsMenu(true) - self:SetSortItems(true) + self:SetContentAlignment(4) + self:SetTextInset(8, 0) + self:SetIsMenu(true) + self:SetSortItems(true) - self:SetFont("DermaTTT2Text") + self:SetFont("DermaTTT2Text") - self.choices = {} - self.titleIndices = {} - self.valueIndices = {} + self.choices = {} + self.titleIndices = {} + self.valueIndices = {} + + self.valueType = nil end --- -- @ignore function PANEL:PerformLayout() - self.DropButton:SetSize(15, 15) - self.DropButton:AlignRight(4) - self.DropButton:CenterVertical() + self.DropButton:SetSize(15, 15) + self.DropButton:AlignRight(4) + self.DropButton:CenterVertical() end --- -- @realm client function PANEL:Clear() - self:SetText("") + self:SetText("") + + self.choices = {} + self.titleIndices = {} + self.valueIndices = {} + self.selected = nil + self.valueType = nil - self.choices = {} - self.titleIndices = {} - self.valueIndices = {} - self.selected = nil + self:CloseMenu() +end - self:CloseMenu() +--- +-- Stores the type of value that is currently used +-- @param any value the value to store the type of +-- @realm client +function PANEL:StoreValueType(value) + -- Save value type of first entry for conversion + if self.valueType == nil then + self.valueType = type(value) + end +end + +--- +-- Converts the value to the current used type +-- @param any value the value to convert to a string or number +-- @return any +-- @realm client +function PANEL:ConvertValue(value) + if self.valueType == "string" then + return tostring(value) + elseif self.valueType == "number" then + return tonumber(value) + else + return value + end end --- @@ -67,19 +96,21 @@ end -- @return string -- @realm client function PANEL:GetOptionText(index) - local choice = self.choices[index] + local choice = self.choices[index] - if not choice then return end + if not choice then + return + end - return choice.title or choice.value + return choice.title or choice.value end --- -- @param number index the option id --- @return string/number a value that is indexable +-- @return string|number a value that is indexable -- @realm client function PANEL:GetOptionValue(index) - return self.choices[index].value + return self.choices[index].value end --- @@ -87,15 +118,15 @@ end -- @return any -- @realm client function PANEL:GetOptionData(index) - return self.choices[index].data + return self.choices[index].data end --- --- @param string/number value should be indexable +-- @param string|number value should be indexable -- @return number -- @realm client function PANEL:GetOptionID(value) - return self.valueIndices[value] or 1 + return self.valueIndices[self:ConvertValue(value)] or 1 end --- @@ -103,7 +134,7 @@ end -- @return number -- @realm client function PANEL:GetOptionTitleID(title) - return self.titleIndices[title] or 1 + return self.titleIndices[title] or 1 end --- @@ -111,118 +142,127 @@ end -- @return any -- @realm client function PANEL:GetOptionTextByData(data) - local choices = self.choices - local choicesSize = #choices + local choices = self.choices + local choicesSize = #choices - for index = 1, choicesSize do - local choiceData = choices[index].data + for index = 1, choicesSize do + local choiceData = choices[index].data - if choiceData ~= data and choiceData ~= tonumber(data) then continue end + if choiceData ~= data and choiceData ~= tonumber(data) then + continue + end - return self:GetOptionText(index) - end + return self:GetOptionText(index) + end - return + return end --- -- @param string title --- @param string/number value should be indexable --- @param bool select +-- @param string|number value should be indexable +-- @param boolean select -- @param string icon -- @param any data additional data that you might want to use -- @return number index -- @realm client function PANEL:AddChoice(title, value, select, icon, data) - if istable(value) then - ErrorNoHalt("[TTT2] dcombobox_ttt2 AddChoice format changed to PANEL:AddChoice(title, value, select, icon, data)\n For any table data use the last parameter.\n") + if istable(value) or isbool(value) then + ErrorNoHaltWithStack( + "[TTT2] dcombobox_ttt2 AddChoice format changed to PANEL:AddChoice(title, value, select, icon, data)\n For any table data use the last parameter.\n" + ) + + return + end - return - end + self:StoreValueType(value) + value = self:ConvertValue(value) - local index = #self.choices + 1 + local index = #self.choices + 1 - self.choices[index] = { - title = title, - value = value, - icon = icon, - data = data - } + self.choices[index] = { + title = title, + value = value, + icon = icon, + data = data, + } - -- Index tables for fast access - self.titleIndices[title] = index - self.valueIndices[value] = index + -- Index tables for fast access + self.titleIndices[title] = index + self.valueIndices[value] = index - if select then - self:ChooseOptionID(index, true) - end + if select then + self:ChooseOptionID(index, true) + end - return index + return index end --- -- @param number index the option id --- @param[default=false] bool ignoreConVar To avoid endless loops, separated setting of convars and UI values +-- @param[default=false] boolean ignoreCallbackEnabledVars To avoid endless loops, separated setting of convars and UI values -- @realm client -function PANEL:ChooseOptionID(index, ignoreConVar) - local choices = self.choices +function PANEL:ChooseOptionID(index, ignoreCallbackEnabledVars) + local choices = self.choices - if index > #choices then - ErrorNoHalt("[TTT2] PANEL:ChooseOptionID failed, exceeding index size of choices.") + if index > #choices then + ErrorNoHaltWithStack("[TTT2] PANEL:ChooseOptionID failed, exceeding index size of choices.") - return - end + return + end - local choice = choices[index] - local value = choice.value + local choice = choices[index] + local value = self:ConvertValue(choice.value) - self.selected = index + self.selected = index - self:SetText(choice.title) - self:OnSelect(index, value, choice.data) + self:SetText(choice.title) + self:OnSelect(index, value, choice.data) - self:CloseMenu() + self:CloseMenu() - if ignoreConVar then return end + if ignoreCallbackEnabledVars then + return + end - self:SetConVarValues(tostring(value)) + self:SetCallbackEnabledVarValues(value) end --- -- @note this chooses the the set value like in the original DComboBox --- @param string/number value should be indexable e.g. the value used to set conVars --- @param[default=false] bool ignoreConVar To avoid endless loops, separated setting of convars and UI values +-- @param string|number value should be indexable e.g. the value used to set conVars +-- @param[default=false] boolean ignoreConVar To avoid endless loops, separated setting of convars and UI values -- @realm client function PANEL:ChooseOptionValue(value, ignoreConVar) - self:ChooseOptionID(self:GetOptionID(value), ignoreConVar) + self:ChooseOptionID(self:GetOptionID(value), ignoreConVar) end --- -- @note this chooses the displayed text rather than the set value like in the original DComboBox -- So use `PANEL:ChooseOptionValue` for the old behaviour -- @param string name the displayed text --- @param[default=false] bool ignoreConVar To avoid endless loops, separated setting of convars and UI values +-- @param[default=false] boolean ignoreConVar To avoid endless loops, separated setting of convars and UI values -- @realm client function PANEL:ChooseOptionName(name, ignoreConVar) - self:ChooseOptionID(self:GetOptionTitleID(name), ignoreConVar) + self:ChooseOptionID(self:GetOptionTitleID(name), ignoreConVar) end --- --- Choose option by index, title is not settable +-- Choose option by index, title is not settable -- @param[opt] string title is unused as it cant be set anymore -- @param number index the option id --- @param[default=false] bool ignoreConVar To avoid endless loops, separated setting of convars and UI values +-- @param[default=false] boolean ignoreConVar To avoid endless loops, separated setting of convars and UI values -- @realm client -- @deprecated Giving titles is not possible anymore. Use `PANEL:ChooseOptionID` instead function PANEL:ChooseOption(title, index, ignoreConVar) - self:ChooseOptionID(index, ignoreConVar) + self:ChooseOptionID(index, ignoreConVar) end --- -- @return number -- @realm client function PANEL:GetSelectedID() - return self.selected + return self.selected end --- @@ -232,235 +272,306 @@ end -- @return any the additional data if given -- @realm client function PANEL:GetSelected() - if not self.selected then return end + if not self.selected then + return + end - return self:GetOptionValue(self.selected), self:GetOptionData(self.selected) + return self:GetOptionValue(self.selected), self:GetOptionData(self.selected) end --- -- @param number index --- @param string/number value is indexable +-- @param string|number value is indexable -- @param any data -- @realm client -function PANEL:OnSelect(index, value, data) - -end +function PANEL:OnSelect(index, value, data) end --- -- @return boolean -- @realm client function PANEL:DoClick() - if self:IsMenuOpen() then - self:CloseMenu() - end + if self:IsMenuOpen() then + self:CloseMenu() + end - self:OpenMenu() + self:OpenMenu() end --- -- @return boolean -- @realm client function PANEL:IsMenuOpen() - return IsValid(self.menu) and self.menu:IsVisible() + return IsValid(self.menu) and self.menu:IsVisible() end --- -- @param Panel pControlOpener -- @realm client function PANEL:OpenMenu(pControlOpener) - if pControlOpener and pControlOpener == self.TextEntry then return end - - -- Do a table Copy if you want to sort items - local sortItems = self:GetSortItems() - local choices = sortItems and table.Copy(self.choices) or self.choices - local choicesSize = #choices - - -- Don't do anything if there aren't any options.. - if choicesSize == 0 then return end - - -- If the menu still exists and hasn't been deleted - -- then just close it and open a new one. - self:CloseMenu() - - self.menu = DermaMenu(false, self) - - if sortItems then - -- Convert Gmod Strings - for i = 1, choicesSize do - local choice = choices[i] - local title = choice.title - - if string.len(title) > 1 and title:StartWith("#") then - title = language.GetPhrase(title:sub(2)) - end - - choice.title = title - end - - -- Sort by title - table.SortByMember(choices, "title", true) - end - - for index = 1, choicesSize do - local choice = choices[index] - local option = self.menu:AddOption(choice.title, function() - self:SetValue(choice.value, false) - end) - - if choice.icon then - option:SetIcon(choice.icon) - end - end - - local x, y = self:LocalToScreen(0, self:GetTall()) - - self.menu:SetMinimumWidth(self:GetWide()) - self.menu:Open(x, y, false, self) + if pControlOpener and pControlOpener == self.TextEntry then + return + end + + -- Do a table Copy if you want to sort items + local sortItems = self:GetSortItems() + local choices = sortItems and table.Copy(self.choices) or self.choices + local choicesSize = #choices + + -- Don't do anything if there aren't any options.. + if choicesSize == 0 then + return + end + + -- If the menu still exists and hasn't been deleted + -- then just close it and open a new one. + self:CloseMenu() + + self.menu = DermaMenu(false, self) + + if sortItems then + -- Convert Gmod Strings + for i = 1, choicesSize do + local choice = choices[i] + local title = choice.title + + if string.len(title) > 1 and title:StartWith("#") then + title = language.GetPhrase(title:sub(2)) + end + + choice.title = title + end + + -- Sort by title + table.SortByMember(choices, "title", true) + end + + for index = 1, choicesSize do + local choice = choices[index] + local option = self.menu:AddOption(choice.title, function() + self:SetValue(choice.value, false) + end) + + if choice.icon then + option:SetIcon(choice.icon) + end + end + + local x, y = self:LocalToScreen(0, self:GetTall()) + + self.menu:SetMinimumWidth(self:GetWide()) + self.menu:Open(x, y, false, self) end --- -- @realm client function PANEL:CloseMenu() - if IsValid(self.menu) then - self.menu:Remove() - self.menu = nil - end + if IsValid(self.menu) then + self.menu:Remove() + self.menu = nil + end end --- -- @warning this doesnt set the displayed text like before, but the value and selects an option --- @param string/number value should be indexable --- @param bool ignoreConVar To avoid endless loops, separated setting of convars and UI values +-- @param string|number value should be indexable +-- @param boolean ignoreConVar To avoid endless loops, separated setting of convars and UI values -- @realm client function PANEL:SetValue(value, ignoreConVar) - self:ChooseOptionValue(value, ignoreConVar) + self:ChooseOptionValue(value, ignoreConVar) end -local convarTracker = 0 +local callbackEnabledVarTracker = 0 --- --- @param Panel menu to set the value of +-- @param Panel panel to set the value of -- @param string conVar name of the convar -local function AddConVarChangeCallback(menu, conVar) - convarTracker = convarTracker % 1023 + 1 - local myIdentifierString = "TTT2F1MenuConVarChangeCallback" .. tostring(convarTracker) - - local function OnConVarChangeCallback(conVarName, oldValue, newValue) - if not IsValid(menu) then - cvars.RemoveChangeCallback(conVarName, myIdentifierString) - - return - end - - menu:SetValue(newValue, true) - end - - cvars.AddChangeCallback(conVar, OnConVarChangeCallback, myIdentifierString) +local function AddConVarChangeCallback(panel, conVar) + callbackEnabledVarTracker = callbackEnabledVarTracker + 1 + local myIdentifierString = "TTT2ComboboxConVarChangeCallback" + .. tostring(callbackEnabledVarTracker) + + local callback = function(conVarName, oldValue, newValue) + if not IsValid(panel) then + -- We need to remove the callback in a timer, because otherwise the ConVar change callback code + -- will throw an error while looping over the callbacks. + -- This happens, because the callback is removed from the same table that is iterated over. + -- Thus, the table size changes while iterating over it and leads to a nil callback as the last entry. + timer.Simple(0, function() + cvars.RemoveChangeCallback(conVarName, myIdentifierString) + end) + + return + end + + panel:SetValue(newValue, true) + end + + cvars.AddChangeCallback(conVar, callback, myIdentifierString) end --- -- @param string conVarName -- @realm client function PANEL:SetConVar(conVarName) - if not ConVarExists(conVarName or "") then return end + if not ConVarExists(conVarName or "") then + return + end - local conVar = GetConVar(conVarName) - self.conVar = conVar + local conVar = GetConVar(conVarName) + self.conVar = conVar - self:SetValue(conVar:GetString(), true) - self:SetDefaultValue(conVar:GetDefault()) + self:SetValue(conVar:GetString(), true) + self:SetDefaultValue(conVar:GetDefault()) - AddConVarChangeCallback(self, conVarName) + AddConVarChangeCallback(self, conVarName) end --- -- @param string conVarName -- @realm client function PANEL:SetServerConVar(conVarName) - if not conVarName or conVarName == "" then return end + if not conVarName or conVarName == "" then + return + end - self.serverConVar = conVarName + self.serverConVar = conVarName - cvars.ServerConVarGetValue(conVarName, function (wasSuccess, value, default) - if wasSuccess then - self:SetValue(value, true) - self:SetDefaultValue(default) - end - end) + cvars.ServerConVarGetValue(conVarName, function(wasSuccess, value, default) + if wasSuccess and IsValid(self) then + self:SetValue(value, true) + self:SetDefaultValue(default) + end + end) - AddConVarChangeCallback(self, conVarName) + AddConVarChangeCallback(self, conVarName) end --- --- @param string value +-- @param table databaseInfo containing {name, itemName, key} -- @realm client -function PANEL:SetConVarValues(value) - if self.conVar then - self.conVar:SetString(value) - end +function PANEL:SetDatabase(databaseInfo) + if not istable(databaseInfo) then + return + end + + local name = databaseInfo.name + local itemName = databaseInfo.itemName + local key = databaseInfo.key + + if not name or not itemName or not key then + return + end + + self.databaseInfo = databaseInfo - if self.serverConVar then - cvars.ChangeServerConVar(self.serverConVar, value) - end + database.GetValue(name, itemName, key, function(databaseExists, value) + if databaseExists then + self:SetValue(value, true) + end + end) + + self:SetDefaultValue(database.GetDefaultValue(name, itemName, key)) + + callbackEnabledVarTracker = callbackEnabledVarTracker + 1 + local myIdentifierString = "TTT2ComboboxDatabaseChangeCallback" + .. tostring(callbackEnabledVarTracker) + + local function OnDatabaseChangeCallback(_name, _itemName, _key, oldValue, newValue) + if not IsValid(self) then + database.RemoveChangeCallback(name, itemName, key, myIdentifierString) + + return + end + + self:SetValue(newValue, true) + end + + database.AddChangeCallback(name, itemName, key, OnDatabaseChangeCallback, myIdentifierString) end --- -- @param string value -- @realm client +function PANEL:SetCallbackEnabledVarValues(value) + if self.conVar then + self.conVar:SetString(tostring(value)) + end + + if self.serverConVar then + cvars.ChangeServerConVar(self.serverConVar, tostring(value)) + end + + if self.databaseInfo then + database.SetValue( + self.databaseInfo.name, + self.databaseInfo.itemName, + self.databaseInfo.key, + value + ) + end +end + +--- +-- @param string|number value +-- @realm client function PANEL:SetDefaultValue(value) - local noDefault = true + local hasDefault = false - if isstring(value) then - self.default = value - noDefault = false - else - self.default = nil - end + if isstring(value) or isnumber(value) then + self:StoreValueType(value) + self.default = self:ConvertValue(value) + hasDefault = true + else + self.default = nil + end - local reset = self:GetResetButton() + local reset = self:GetResetButton() - if ispanel(reset) then - reset.noDefault = noDefault - end + if ispanel(reset) then + reset.noDefault = not hasDefault + end end --- -- @return number defaultValue -- @realm client function PANEL:GetDefaultValue() - return self.default + return self.default end --- -- @realm client function PANEL:ResetToDefaultValue() - local default = self:GetDefaultValue() + local default = self:GetDefaultValue() - if not default then return end + if not default then + return + end - self:SetValue(default, false) + self:SetValue(default, false) end --- -- @param Panel reset -- @realm client function PANEL:SetResetButton(reset) - if not ispanel(reset) then return end + if not ispanel(reset) then + return + end - self.resetButton = reset + self.resetButton = reset - reset.DoClick = function(slf) - self:ResetToDefaultValue() - end + reset.DoClick = function(slf) + self:ResetToDefaultValue() + end - reset.noDefault = self.default == nil + reset.noDefault = self.default == nil end --- -- @return Panel reset -- @realm client function PANEL:GetResetButton() - return self.resetButton + return self.resetButton end - derma.DefineControl("DComboBoxTTT2", "", PANEL, "DButtonTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcontentpanel_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcontentpanel_ttt2.lua index c4b7a042d..a4b8189c0 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcontentpanel_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dcontentpanel_ttt2.lua @@ -12,7 +12,8 @@ AccessorFunc(PANEL, "m_bBackground", "PaintBackground", FORCE_BOOL) --- -- @accessor boolean -- @realm client -AccessorFunc(PANEL, "m_bBackground", "DrawBackground", FORCE_BOOL) -- deprecated +-- @deprecated +AccessorFunc(PANEL, "m_bBackground", "DrawBackground", FORCE_BOOL) --- -- @accessor boolean @@ -36,73 +37,73 @@ Derma_Hook(PANEL, "PerformLayout", "Layout", "Panel") --- -- @ignore function PANEL:Init() - self:SetPaintBackground(true) + self:SetPaintBackground(true) - -- This turns off the engine drawing - self:SetPaintBackgroundEnabled(false) - self:SetPaintBorderEnabled(false) + -- This turns off the engine drawing + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "ContentPanelTTT2", self, w, h) + derma.SkinHook("Paint", "ContentPanelTTT2", self, w, h) - return false + return false end --- -- @param boolean bEnabled -- @realm client function PANEL:SetEnabled(bEnabled) - self.m_bEnabled = not bEnabled - - if bEnabled then - self:SetAlpha(255) - self:SetMouseInputEnabled(true) - else - self:SetAlpha(75) - self:SetMouseInputEnabled(false) - end + self.m_bEnabled = not bEnabled + + if bEnabled then + self:SetAlpha(255) + self:SetMouseInputEnabled(true) + else + self:SetAlpha(75) + self:SetMouseInputEnabled(false) + end end --- -- @return boolean -- @realm client function PANEL:IsEnabled() - return self.m_bEnabled + return self.m_bEnabled end --- -- @param number mcode -- @realm client function PANEL:OnMousePressed(mcode) - if self:IsSelectionCanvas() and not dragndrop.IsDragging() then - self:StartBoxSelection() + if self:IsSelectionCanvas() and not dragndrop.IsDragging() then + self:StartBoxSelection() - return - end + return + end - if self:IsDraggable() then - self:MouseCapture(true) - self:DragMousePress(mcode) - end + if self:IsDraggable() then + self:MouseCapture(true) + self:DragMousePress(mcode) + end end --- -- @param number mcode -- @realm client function PANEL:OnMouseReleased(mcode) - if self:EndBoxSelection() then return end + if self:EndBoxSelection() then + return + end - self:MouseCapture(false) + self:MouseCapture(false) end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:UpdateColours() - -end +function PANEL:UpdateColours() end derma.DefineControl("DContentPanelTTT2", "", PANEL, "Panel") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/ddragbase_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/ddragbase_ttt2.lua index 049afbde5..d8cb926dc 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/ddragbase_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/ddragbase_ttt2.lua @@ -7,19 +7,19 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - DDragBase.Init(self) + DDragBase.Init(self) - self:SetDropPos("2468") + self:SetDropPos("2468") - self.m_iLeftMargin = 0 + self.m_iLeftMargin = 0 - local oldMakeDroppable = self.MakeDroppable + local oldMakeDroppable = self.MakeDroppable - self.MakeDroppable = function(slf, name, allowCopy) - slf.dropGroupName = name + self.MakeDroppable = function(slf, name, allowCopy) + slf.dropGroupName = name - oldMakeDroppable(slf, name, allowCopy) - end + oldMakeDroppable(slf, name, allowCopy) + end end --- @@ -28,8 +28,8 @@ end -- @param[default=64] number h The height -- @realm client function PANEL:SetChildSize(w, h) - self.childW = w - self.childH = h + self.childW = w + self.childH = h end --- @@ -37,21 +37,21 @@ end -- @return number The child height -- @realm client function PANEL:GetChildSize() - return self.childW or 64, self.childH or 64 + return self.childW or 64, self.childH or 64 end --- -- @return number The left margin -- @realm client function PANEL:GetLeftMargin() - return self.m_iLeftMargin + return self.m_iLeftMargin end --- -- @param[default=0] number leftMargin The left margin -- @realm client function PANEL:SetLeftMargin(leftMargin) - self.m_iLeftMargin = leftMargin + self.m_iLeftMargin = leftMargin end --- @@ -59,7 +59,7 @@ end -- @return table A table of valid children (@{PANEL}) -- @realm client function PANEL:GetDnDs() - return {} + return {} end --- @@ -68,37 +68,37 @@ end -- @return number -- @realm client function PANEL:GetDropMode(x, y) - local closest = self:GetClosestChild(x, y) + local closest = self:GetClosestChild(x, y) - local h = closest:GetTall() - local w = closest:GetWide() + local h = closest:GetTall() + local w = closest:GetWide() - local disty = y - (closest.y + h * 0.5) - local distx = x - (closest.x + w * 0.5) + local disty = y - (closest.y + h * 0.5) + local distx = x - (closest.x + w * 0.5) - local drop = 0 + local drop = 0 - if self.bDropCenter then - drop = 5 - end + if self.bDropCenter then + drop = 5 + end - if disty < 0 and self.bDropTop and (drop == 0 or math.abs(disty) > h * 0.1) then - drop = 8 - end + if disty < 0 and self.bDropTop and (drop == 0 or math.abs(disty) > h * 0.1) then + drop = 8 + end - if disty >= 0 and self.bDropBottom and (drop == 0 or math.abs(disty) > h * 0.1) then - drop = 2 - end + if disty >= 0 and self.bDropBottom and (drop == 0 or math.abs(disty) > h * 0.1) then + drop = 2 + end - if distx < 0 and self.bDropLeft and (drop == 0 or math.abs(distx) > w * 0.1) then - drop = 4 - end + if distx < 0 and self.bDropLeft and (drop == 0 or math.abs(distx) > w * 0.1) then + drop = 4 + end - if distx >= 0 and self.bDropRight and (drop == 0 or math.abs(distx) > w * 0.1) then - drop = 6 - end + if distx >= 0 and self.bDropRight and (drop == 0 or math.abs(distx) > w * 0.1) then + drop = 6 + end - return drop + return drop end --- @@ -109,61 +109,82 @@ end -- @param number y -- @realm client function PANEL:DropAction_Normal(drops, bDoDrop, command, x, y) - local closest = self:GetClosestChild(x, y) - - if not IsValid(closest) then - return self:DropAction_Simple(drops, bDoDrop, command, x, y) - end - - -- This panel is only meant to be copied from, not edited - if self:GetReadOnly() then return end - - local drop = self:GetDropMode(x, y) - - if not self:OnDropChildCheck(closest, drop) then return end - - self:UpdateDropTarget(drop, closest) - - if table.HasValue(drops, closest) or not bDoDrop and not self:GetUseLiveDrag() or #drops < 1 then return end - - -- This keeps the drop order the same, whether we add it before an object or after - if drop == 6 or drop == 2 then - drops = table.Reverse(drops) - end - - for i = 1, #drops do - local v = drops[i] - - -- Don't drop one of our parents onto us because we'll be sucked into a vortex - if v:IsOurChild(self) then continue end - - -- Copy the panel if we are told to from the DermaMenu(), or if we are moving from a read only panel to a not read only one. - if v.Copy and (command and command == "copy" - or (IsValid(v:GetParent()) and v:GetParent().GetReadOnly and v:GetParent():GetReadOnly() and v:GetParent():GetReadOnly() ~= self:GetReadOnly())) - then - v = v:Copy() - end - - v = v:OnDrop(self) - - if drop == 5 then - closest:DroppedOn(v) - end - - if drop == 8 or drop == 4 then - v:SetParent(self) - v:MoveToBefore(closest) - end - - if drop == 2 or drop == 6 then - v:SetParent(self) - v:MoveToAfter(closest) - end - - self:OnDropped(v, drop, closest) - end - - self:OnModified() + local closest = self:GetClosestChild(x, y) + + if not IsValid(closest) then + return self:DropAction_Simple(drops, bDoDrop, command, x, y) + end + + -- This panel is only meant to be copied from, not edited + if self:GetReadOnly() then + return + end + + local drop = self:GetDropMode(x, y) + + if not self:OnDropChildCheck(closest, drop) then + return + end + + self:UpdateDropTarget(drop, closest) + + if + table.HasValue(drops, closest) + or not bDoDrop and not self:GetUseLiveDrag() + or #drops < 1 + then + return + end + + -- This keeps the drop order the same, whether we add it before an object or after + if drop == 6 or drop == 2 then + drops = table.Reverse(drops) + end + + for i = 1, #drops do + local v = drops[i] + + -- Don't drop one of our parents onto us because we'll be sucked into a vortex + if v:IsOurChild(self) then + continue + end + + -- Copy the panel if we are told to from the DermaMenu(), or if we are moving from a read only panel to a not read only one. + if + v.Copy + and ( + command and command == "copy" + or ( + IsValid(v:GetParent()) + and v:GetParent().GetReadOnly + and v:GetParent():GetReadOnly() + and v:GetParent():GetReadOnly() ~= self:GetReadOnly() + ) + ) + then + v = v:Copy() + end + + v = v:OnDrop(self) + + if drop == 5 then + closest:DroppedOn(v) + end + + if drop == 8 or drop == 4 then + v:SetParent(self) + v:MoveToBefore(closest) + end + + if drop == 2 or drop == 6 then + v:SetParent(self) + v:MoveToAfter(closest) + end + + self:OnDropped(v, drop, closest) + end + + self:OnModified() end --- @@ -173,7 +194,7 @@ end -- @return boolean -- @realm client function PANEL:OnDropChildCheck(closestChild, direction) - return true + return true end --- @@ -182,8 +203,6 @@ end -- @param number pos -- @param PANEL closestPnl -- @realm client -function PANEL:OnDropped(droppedPnl, pos, closestPnl) - -end +function PANEL:OnDropped(droppedPnl, pos, closestPnl) end derma.DefineControl("DDragBaseTTT2", "", PANEL, "DIconLayout") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/deventbox_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/deventbox_ttt2.lua index 1d8cde758..510b87e2f 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/deventbox_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/deventbox_ttt2.lua @@ -13,76 +13,76 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - self:SetText("") + self:SetText("") - self.contents = { - title_font = "DermaTTT2TextLarger", - font = "DermaTTT2Text", - event = nil - } + self.contents = { + title_font = "DermaTTT2TextLarger", + font = "DermaTTT2Text", + event = nil, + } end --- -- @param EVENT event -- @realm client function PANEL:SetEvent(event) - self.contents.event = event + self.contents.event = event end --- -- @return EVENT -- @realm client function PANEL:GetEvent() - return self.contents.event + return self.contents.event end --- -- @return Material -- @realm client function PANEL:GetIcon() - return self.contents.event.icon + return self.contents.event.icon end --- -- @return string -- @realm client function PANEL:GetTitle() - return self.contents.event.title + return self.contents.event.title end --- -- @return string -- @realm client function PANEL:GetText() - return self.contents.event:GetText() + return self.contents.event:GetText() end --- -- @param string title_font -- @realm client function PANEL:SetTitleFont(title_font) - self.contents.title_font = title_font or "" + self.contents.title_font = title_font or "" end --- -- @return string -- @realm client function PANEL:GetTitleFont() - return self.contents.title_font + return self.contents.title_font end --- -- @param string font -- @realm client function PANEL:SetFont(font) - self.contents.font = font or "" + self.contents.font = font or "" end --- -- @return string -- @realm client function PANEL:GetFont() - return self.contents.font + return self.contents.font end --- @@ -91,54 +91,54 @@ end -- @return number The calculated height -- @realm client function PANEL:GetContentHeight(width) - local size = 50 -- title height + local size = 50 -- title height - local textTable = self:GetText() - local _, heightText = drawGetTextSize("", self:GetFont()) + local textTable = self:GetText() + local _, heightText = drawGetTextSize("", self:GetFont()) - for i = 1, #textTable do - local text = textTable[i] - local params = {} + for i = 1, #textTable do + local text = textTable[i] + local params = {} - if text.translateParams then - for key, value in pairs(text.params) do - params[key] = TryT(value) - end - else - params = text.params - end + if text.translateParams then + for key, value in pairs(text.params) do + params[key] = TryT(value) + end + else + params = text.params + end - local textTranslated = ParT(text.string, params or {}) + local textTranslated = ParT(text.string, params or {}) - local textWrapped = drawGetWrappedText( - textTranslated, - width - 50, - self:GetFont() - ) + local textWrapped = drawGetWrappedText(textTranslated, width - 50, self:GetFont()) - size = size + #textWrapped * heightText + 15 -- 15: paragraph end - end + size = size + #textWrapped * heightText + 15 -- 15: paragraph end + end - local event = self:GetEvent() + local event = self:GetEvent() - if event:HasScore() then - local scoredPlayers = event:GetScoredPlayers() - local sid64 = LocalPlayer():SteamID64() + if event:HasScore() then + local scoredPlayers = event:GetScoredPlayers() + local sid64 = LocalPlayer():SteamID64() - for i = 1, #scoredPlayers do - local ply64 = scoredPlayers[i] + for i = 1, #scoredPlayers do + local ply64 = scoredPlayers[i] - if event.onlyLocalPlayer and ply64 ~= sid64 then continue end + if event.onlyLocalPlayer and ply64 ~= sid64 then + continue + end - local scoreRows = #event:GetRawScoreText(ply64) + local scoreRows = #event:GetRawScoreText(ply64) - if scoreRows == 0 then continue end + if scoreRows == 0 then + continue + end - size = size + (scoreRows + 1) * heightText + 35 -- 35 spacing + padding - end - end + size = size + (scoreRows + 1) * heightText + 35 -- 35 spacing + padding + end + end - return mathmax(size, 75) + return mathmax(size, 75) end --- @@ -146,9 +146,9 @@ end -- @param number h -- @realm client function PANEL:Paint(w, h) - derma.SkinHook("Paint", "EventBoxTTT2", self, w, h) + derma.SkinHook("Paint", "EventBoxTTT2", self, w, h) - return false + return false end derma.DefineControl("DEventBoxTTT2", "", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dform_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dform_ttt2.lua index f6ffef64b..ddf100377 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dform_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dform_ttt2.lua @@ -27,36 +27,38 @@ local materialDisable = Material("vgui/ttt/vskin/icon_disable") --- -- @ignore function PANEL:Init() - self.items = {} + self.items = {} - self:SetSpacing(4) - self:SetPadding(10) + self:SetSpacing(4) + self:SetPadding(10) - self:SetPaintBackground(true) + self:SetPaintBackground(true) - self:SetMouseInputEnabled(true) - self:SetKeyboardInputEnabled(true) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) end --- -- @param string name -- @realm client function PANEL:SetName(name) - self:SetLabel(name) + self:SetLabel(name) end --- -- @realm client function PANEL:Clear() - for i = 1, #self.items do - local item = self.items[i] + for i = 1, #self.items do + local item = self.items[i] - if not IsValid(item) then continue end + if not IsValid(item) then + continue + end - item:Remove() - end + item:Remove() + end - self.items = {} + self.items = {} end --- @@ -65,61 +67,124 @@ end -- @param Panel reset -- @realm client function PANEL:AddItem(left, right, reset) - self:InvalidateLayout() - - local panel = vgui.Create("DSizeToContents", self) - - panel:SetSizeX(false) - panel:Dock(TOP) - panel:DockPadding(10, 10, 10, 0) - panel:InvalidateLayout() - - if IsValid(reset) then - reset:SetParent(panel) - reset:Dock(RIGHT) - end - - if IsValid(right) then - left:SetParent(panel) - left:Dock(LEFT) - left:InvalidateLayout(true) - left:SetSize(350, 20) - - right:SetParent(panel) - right:SetPos(350, 0) - right:InvalidateLayout(true) - elseif IsValid(left) then - left:SetParent(panel) - left:Dock(TOP) - end - - self.items[#self.items + 1] = panel + self:InvalidateLayout() + + local panel = vgui.Create("DSizeToContents", self) + + panel:SetSizeX(false) + panel:Dock(TOP) + panel:DockPadding(10, 10, 10, 0) + panel:InvalidateLayout() + + if IsValid(reset) then + reset:SetParent(panel) + reset:Dock(RIGHT) + end + + if IsValid(right) then + left:SetParent(panel) + left:Dock(LEFT) + left:InvalidateLayout(true) + left:SetSize(350, 20) + + right:SetParent(panel) + right:SetPos(350, 0) + right:InvalidateLayout(true) + elseif IsValid(left) then + left:SetParent(panel) + left:Dock(TOP) + end + + self.items[#self.items + 1] = panel end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:Rebuild() - -end +function PANEL:Rebuild() end -- FUNCTIONS TO POPULATE THE FORM local function MakeReset(parent) - local reset = vgui.Create("DButtonTTT2", parent) + local reset = vgui.Create("DButtonTTT2", parent) - reset:SetText("button_default") - reset:SetSize(32, 32) + reset:SetText("button_default") + reset:SetSize(32, 32) - reset.Paint = function(slf, w, h) - derma.SkinHook("Paint", "FormButtonIconTTT2", slf, w, h) + reset.Paint = function(slf, w, h) + derma.SkinHook("Paint", "FormButtonIconTTT2", slf, w, h) - return true - end + return true + end - reset.material = materialReset + reset.material = materialReset - return reset + return reset +end + +--- +-- Adds a textentry to the form +-- @param table data The data for the textentry +-- @note Structure of data = { +-- label, default, convar, serverConVar, initial, function OnChange(value), +-- master = { function AddSlave(self, slave) } +-- } +-- @return Panel The created textentry +-- @realm client +function PANEL:MakeTextEntry(data) + local left = vgui.Create("DLabelTTT2", self) + + left:SetText(data.label) + + left.Paint = function(slf, w, h) + derma.SkinHook("Paint", "FormLabelTTT2", slf, w, h) + + return true + end + + local right = vgui.Create("DTextEntryTTT2", self) + + local reset = MakeReset(self) + right:SetResetButton(reset) + + right:SetUpdateOnType(false) + right:SetHeightMult(1) + + right.OnGetFocus = function(slf) + util.getHighestPanelParent(self):SetKeyboardInputEnabled(true) + end + + right.OnLoseFocus = function(slf) + util.getHighestPanelParent(self):SetKeyboardInputEnabled(false) + end + + -- Set default if possible even if the convar could still overwrite it + right:SetDefaultValue(data.default) + right:SetConVar(data.convar) + right:SetServerConVar(data.serverConvar) + + if not data.convar and not data.serverConvar and data.initial then + right:SetValue(data.initial) + end + + right.OnValueChanged = function(slf, value) + if isfunction(data.OnChange) then + data.OnChange(slf, value) + end + end + + right:SetTall(32) + right:Dock(TOP) + + self:AddItem(left, right, reset) + + if IsValid(data.master) and isfunction(data.master.AddSlave) then + data.master:AddSlave(left) + data.master:AddSlave(right) + data.master:AddSlave(reset) + end + + return left, right end --- @@ -128,40 +193,46 @@ end -- @return Panel The created checkbox -- @realm client function PANEL:MakeCheckBox(data) - local left = vgui.Create("DCheckBoxLabelTTT2", self) + local left = vgui.Create("DCheckBoxLabelTTT2", self) + + local reset = MakeReset(self) + left:SetResetButton(reset) - local reset = MakeReset(self) - left:SetResetButton(reset) + left:SetText(data.label) + left:SetTextParams(data.params) + left:SetInverted(data.invert) - left:SetText(data.label) - left:SetParams(data.params) + -- Set default if possible even if the convar could still overwrite it + left:SetDefaultValue(data.default) + left:SetConVar(data.convar) + left:SetServerConVar(data.serverConvar) + left:SetDatabase(data.database) - -- Set default if possible even if the convar could still overwrite it - left:SetDefaultValue(data.default) - left:SetConVar(data.convar) - left:SetServerConVar(data.serverConvar) + left:SetTall(32) - left:SetTall(32) + if not data.convar and not data.serverConvar and not data.database and data.initial then + left:SetValue(data.initial) + end - if not data.convar and not data.serverConvar and data.initial then - left:SetValue(data.initial) - end + left.OnValueChanged = function(slf, value) + if isfunction(data.OnChange) then + data.OnChange(slf, value) + end + end - left.OnValueChanged = function(slf, value) - if isfunction(data.OnChange) then - data.OnChange(slf, value) - end - end + self:AddItem(left, nil, reset) + if IsValid(data.master) and isfunction(data.master.AddSlave) then + left:SetMaster(data.master) + reset:SetMaster(data.master) - self:AddItem(left, nil, reset) + data.master:AddSlave(left) + data.master:AddSlave(reset) - if IsValid(data.master) and isfunction(data.master.AddSlave) then - data.master:AddSlave(left) - data.master:AddSlave(reset) - end + left:DockMargin(left:GetIndentationMargin(), 0, 0, 0) + end - return left + return left end --- @@ -170,56 +241,62 @@ end -- @return Panel The created slider -- @realm client function PANEL:MakeSlider(data) - local left = vgui.Create("DLabelTTT2", self) + local left = vgui.Create("DLabelTTT2", self) - left:SetText(data.label) + left:SetText(data.label) - left.Paint = function(slf, w, h) - derma.SkinHook("Paint", "FormLabelTTT2", slf, w, h) + left.Paint = function(slf, w, h) + derma.SkinHook("Paint", "FormLabelTTT2", slf, w, h) - return true - end + return true + end - local right = vgui.Create("DNumSliderTTT2", self) + local right = vgui.Create("DNumSliderTTT2", self) - local reset = MakeReset(self) - right:SetResetButton(reset) + local reset = MakeReset(self) + right:SetResetButton(reset) - right:SetMinMax(data.min, data.max) + right:SetMinMax(data.min, data.max) - if data.decimal ~= nil then - right:SetDecimals(data.decimal) - end + if data.decimal ~= nil then + right:SetDecimals(data.decimal) + end - -- Set default if possible even if the convar could still overwrite it - right:SetDefaultValue(data.default) - right:SetConVar(data.convar) - right:SetServerConVar(data.serverConvar) - right:SizeToContents() + -- Set default if possible even if the convar could still overwrite it + right:SetDefaultValue(data.default) + right:SetConVar(data.convar) + right:SetServerConVar(data.serverConvar) + right:SetDatabase(data.database) + right:SizeToContents() - if not data.convar and not data.serverConvar and data.initial then - right:SetValue(data.initial) - end + if not data.convar and not data.serverConvar and not data.database and data.initial then + right:SetValue(data.initial) + end - right.OnValueChanged = function(slf, value) - if isfunction(data.OnChange) then - data.OnChange(slf, value) - end - end + right.OnValueChanged = function(slf, value) + if isfunction(data.OnChange) then + data.OnChange(slf, value) + end + end - right:SetTall(32) - right:Dock(TOP) + right:SetTall(32) + right:Dock(TOP) + self:AddItem(left, right, reset) - self:AddItem(left, right, reset) + if IsValid(data.master) and isfunction(data.master.AddSlave) then + data.master:AddSlave(left) + data.master:AddSlave(right) + data.master:AddSlave(reset) - if IsValid(data.master) and isfunction(data.master.AddSlave) then - data.master:AddSlave(left) - data.master:AddSlave(right) - data.master:AddSlave(reset) - end + left:SetMaster(data.master) + right:SetMaster(data.master) + reset:SetMaster(data.master) - return left + left:DockMargin(left:GetIndentationMargin(), 0, 0, 0) + end + + return left end --- @@ -235,70 +312,78 @@ end -- @return Panel The created label -- @realm client function PANEL:MakeComboBox(data) - local left = vgui.Create("DLabelTTT2", self) - - left:SetText(data.label) - - left.Paint = function(slf, w, h) - derma.SkinHook("Paint", "FormLabelTTT2", slf, w, h) - - return true - end - - local right = vgui.Create("DComboBoxTTT2", self) - - local reset = MakeReset(self) - right:SetResetButton(reset) - right:SetDefaultValue(data.default) -- Set default if possible even if the convar could still overwrite it - - if data.choices then - for i = 1, #data.choices do - local choice = data.choices[i] - - if istable(choice) then - right:AddChoice(choice.title, choice.value, choice.select, choice.icon, choice.data) - else - -- Support old simple structure - right:AddChoice(choice, choice) - end - end - end - - local conVar = data.convar or data.conVar - right:SetConVar(conVar) - - local serverConVar = data.serverConvar or data.serverConVar - right:SetServerConVar(serverConVar) - - -- Only choose an option, if no conVars are set - if not isstring(conVar) and not isstring(serverConVar) then - if data.selectId then - right:ChooseOptionId(data.selectId, true) - elseif data.selectName or data.selectTitle then - right:ChooseOptionName(data.selectName or data.selectTitle, true) - elseif data.selectValue then - right:ChooseOptionValue(data.selectValue, true) - end - end - - right.OnSelect = function(slf, index, value, additionalData) - if data and isfunction(data.OnChange) then - data.OnChange(value, additionalData, slf) - end - end - - right:SetTall(32) - right:Dock(TOP) - - self:AddItem(left, right, reset) - - if IsValid(data.master) and isfunction(data.master.AddSlave) then - data.master:AddSlave(left) - data.master:AddSlave(right) - data.master:AddSlave(reset) - end - - return right, left + local left = vgui.Create("DLabelTTT2", self) + + left:SetText(data.label) + + left.Paint = function(slf, w, h) + derma.SkinHook("Paint", "FormLabelTTT2", slf, w, h) + + return true + end + + local right = vgui.Create("DComboBoxTTT2", self) + + local reset = MakeReset(self) + right:SetResetButton(reset) + right:SetDefaultValue(data.default) -- Set default if possible even if the convar could still overwrite it + + if data.choices then + for i = 1, #data.choices do + local choice = data.choices[i] + + if istable(choice) then + right:AddChoice(choice.title, choice.value, choice.select, choice.icon, choice.data) + else + -- Support old simple structure + right:AddChoice(choice, choice) + end + end + end + + local conVar = data.convar or data.conVar + right:SetConVar(conVar) + + local serverConVar = data.serverConvar or data.serverConVar + right:SetServerConVar(serverConVar) + + right:SetDatabase(data.database) + + -- Only choose an option, if no conVars are set + if not isstring(conVar) and not isstring(serverConVar) and not istable(data.database) then + if data.selectId then + right:ChooseOptionId(data.selectId, true) + elseif data.selectName or data.selectTitle then + right:ChooseOptionName(data.selectName or data.selectTitle, true) + elseif data.selectValue then + right:ChooseOptionValue(data.selectValue, true) + end + end + + right.OnSelect = function(slf, index, value, additionalData) + if data and isfunction(data.OnChange) then + data.OnChange(value, additionalData, slf) + end + end + + right:SetTall(32) + right:Dock(TOP) + + self:AddItem(left, right, reset) + + if IsValid(data.master) and isfunction(data.master.AddSlave) then + data.master:AddSlave(left) + data.master:AddSlave(right) + data.master:AddSlave(reset) + + left:SetMaster(data.master) + right:SetMaster(data.master) + reset:SetMaster(data.master) + + left:DockMargin(left:GetIndentationMargin(), 0, 0, 0) + end + + return right, left end --- @@ -308,56 +393,62 @@ end -- @return Panel The created label -- @realm client function PANEL:MakeBinder(data) - local left = vgui.Create("DLabelTTT2", self) + local left = vgui.Create("DLabelTTT2", self) + + left:SetText(data.label) + + left.Paint = function(slf, w, h) + derma.SkinHook("Paint", "FormLabelTTT2", slf, w, h) - left:SetText(data.label) + return true + end - left.Paint = function(slf, w, h) - derma.SkinHook("Paint", "FormLabelTTT2", slf, w, h) + local right = vgui.Create("DBinderPanelTTT2", self) - return true - end + right.binder:SetValue(data.select) - local right = vgui.Create("DBinderPanelTTT2", self) + right.binder.OnChange = function(slf, keyNum) + if isfunction(data.OnChange) then + data.OnChange(slf, keyNum) + end + end - right.binder:SetValue(data.select) + right.disable.DoClick = function(slf) + if isfunction(data.OnDisable) then + data.OnDisable(slf, right.binder) + end + end - right.binder.OnChange = function(slf, keyNum) - if isfunction(data.OnChange) then - data.OnChange(slf, keyNum) - end - end + right.disable.material = materialDisable - right.disable.DoClick = function(slf) - if isfunction(data.OnDisable) then - data.OnDisable(slf, right.binder) - end - end + right:SetTall(32) + right:Dock(TOP) - right.disable.material = materialDisable + local reset = MakeReset(self) - right:SetTall(32) - right:Dock(TOP) + if data.default ~= nil then + reset.DoClick = function(slf) + right.binder:SetValue(data.default) + end + else + reset.noDefault = true + end - local reset = MakeReset(self) + self:AddItem(left, right, reset) - if data.default ~= nil then - reset.DoClick = function(slf) - right.binder:SetValue(data.default) - end - else - reset.noDefault = true - end + if IsValid(data.master) and isfunction(data.master.AddSlave) then + data.master:AddSlave(left) + data.master:AddSlave(right) + data.master:AddSlave(reset) - self:AddItem(left, right, reset) + left:SetMaster(data.master) + right:SetMaster(data.master) + reset:SetMaster(data.master) - if IsValid(data.master) and isfunction(data.master.AddSlave) then - data.master:AddSlave(left) - data.master:AddSlave(right) - data.master:AddSlave(reset) - end + left:DockMargin(left:GetIndentationMargin(), 0, 0, 0) + end - return right, left + return right, left end --- @@ -366,41 +457,46 @@ end -- @return Panel The created helpbox -- @realm client function PANEL:MakeHelp(data) - local left = vgui.Create("DLabelTTT2", self) + local left = vgui.Create("DLabelTTT2", self) + + left:SetText(data.label) + left:SetTextParams(data.params) + left:SetContentAlignment(7) + left:SetAutoStretchVertical(true) + + left.paddingX = 10 + left.paddingY = 5 + + left.Paint = function(slf, w, h) + derma.SkinHook("Paint", "HelpLabelTTT2", slf, w, h) - left:SetText(data.label) - left:SetParams(data.params) - left:SetContentAlignment(7) - left:SetAutoStretchVertical(true) + return true + end - left.paddingX = 10 - left.paddingY = 5 + -- make sure the height is based on the amount of text inside + left.PerformLayout = function(slf, w, h) + local textTranslated = + LANG.GetParamTranslation(slf:GetText(), LANG.TryTranslation(slf:GetTextParams())) - left.Paint = function(slf, w, h) - derma.SkinHook("Paint", "HelpLabelTTT2", slf, w, h) + local textWrapped = draw.GetWrappedText(textTranslated, w - 2 * slf.paddingX, slf:GetFont()) + local _, heightText = draw.GetTextSize("", slf:GetFont()) - return true - end + slf:SetSize(w, heightText * #textWrapped + 2 * slf.paddingY) + end - -- make sure the height is based on the amount of text inside - left.PerformLayout = function(slf, w, h) - local textTranslated = LANG.GetParamTranslation(slf:GetText(), LANG.TryTranslation(slf:GetParams())) + self:AddItem(left, nil) - local textWrapped = draw.GetWrappedText( - textTranslated, - w - 2 * slf.paddingX, - slf:GetFont() - ) - local _, heightText = draw.GetTextSize("", slf:GetFont()) + if IsValid(data.master) and isfunction(data.master.AddSlave) then + data.master:AddSlave(left) - slf:SetSize(w, heightText * #textWrapped + 2 * slf.paddingY) - end + left:SetMaster(data.master) - self:AddItem(left, nil) + left:DockMargin(left:GetIndentationMargin(), 0, 0, 0) + end - left:InvalidateLayout(true) + left:InvalidateLayout(true) - return left + return left end -- Adds a colormixer to the form @@ -409,74 +505,74 @@ end -- @return Panel The created label -- @realm client function PANEL:MakeColorMixer(data) - local left = vgui.Create("DLabelTTT2", self) - local right = vgui.Create("DPanel", self) + local left = vgui.Create("DLabelTTT2", self) + local right = vgui.Create("DPanel", self) - left:SetTall(data.height or 240) - right:SetTall(data.height or 240) + left:SetTall(data.height or 240) + right:SetTall(data.height or 240) - right:Dock(TOP) - right:DockPadding(10, 10, 10, 10) + right:Dock(TOP) + right:DockPadding(10, 10, 10, 10) - left:SetText(data.label) + left:SetText(data.label) - local colorMixer = vgui.Create("DColorMixer", right) + local colorMixer = vgui.Create("DColorMixer", right) - colorMixer:SetColor(data.initial or COLOR_WHITE) - colorMixer:SetAlphaBar(data.showAlphaBar or false) - colorMixer:SetPalette(data.showPalette or false) - colorMixer:Dock(FILL) + colorMixer:SetColor(data.initial or COLOR_WHITE) + colorMixer:SetAlphaBar(data.showAlphaBar or false) + colorMixer:SetPalette(data.showPalette or false) + colorMixer:Dock(FILL) - colorMixer.ValueChanged = function(slf, color) - if isfunction(data.OnChange) then - data.OnChange(slf, color) - end - end + colorMixer.ValueChanged = function(slf, color) + if isfunction(data.OnChange) then + data.OnChange(slf, color) + end + end - left.Paint = function(slf, w, h) - derma.SkinHook("Paint", "FormLabelTTT2", slf, w, h) + left.Paint = function(slf, w, h) + derma.SkinHook("Paint", "FormLabelTTT2", slf, w, h) - return true - end + return true + end - right.Paint = function(slf, w, h) - derma.SkinHook("Paint", "FormBoxTTT2", slf, w, h) + right.Paint = function(slf, w, h) + derma.SkinHook("Paint", "FormBoxTTT2", slf, w, h) - return true - end + return true + end - right.SetEnabled = function(slf, enabled) - slf.m_bDisabled = not enabled + right.SetEnabled = function(slf, enabled) + slf.m_bDisabled = not enabled - if enabled then - slf:SetMouseInputEnabled(true) - else - slf:SetMouseInputEnabled(false) - end + if enabled then + slf:SetMouseInputEnabled(true) + else + slf:SetMouseInputEnabled(false) + end - -- update colormixer as well - colorMixer:SetDisabled(not enabled) - end + -- update colormixer as well + colorMixer:SetEnabled(enabled) + end - self:AddItem(left, right) + self:AddItem(left, right) - if IsValid(data.master) and isfunction(data.master.AddSlave) then - data.master:AddSlave(left) - data.master:AddSlave(right) - end + if IsValid(data.master) and isfunction(data.master.AddSlave) then + data.master:AddSlave(left) + data.master:AddSlave(right) + end - return right, left + return right, left end -- Adds a panel to the form -- @return Panel The created panel -- @realm client function PANEL:MakePanel() - local panel = vgui.Create("DPanelTTT2", self) + local panel = vgui.Create("DPanelTTT2", self) - self:AddItem(panel) + self:AddItem(panel) - return panel + return panel end --- @@ -486,20 +582,20 @@ end -- @return Panel The created card -- @realm client function PANEL:MakeCard(data, base) - local card = base:Add("DCardTTT2") + local card = base:Add("DCardTTT2") - card:SetSize(238, 78) - card:SetIcon(data.icon) - card:SetText(data.label) - card:SetMode(data.initial) + card:SetSize(238, 78) + card:SetIcon(data.icon) + card:SetText(data.label) + card:SetMode(data.initial) - card.OnModeChanged = function(slf, oldMode, newMode) - if data and isfunction(data.OnChange) then - data.OnChange(slf, oldMode, newMode) - end - end + card.OnModeChanged = function(slf, oldMode, newMode) + if data and isfunction(data.OnChange) then + data.OnChange(slf, oldMode, newMode) + end + end - return card + return card end --- @@ -509,30 +605,34 @@ end -- @return Panel The created image check box -- @realm client function PANEL:MakeImageCheckBox(data, base) - local box = base:Add("DImageCheckBoxTTT2") - - box:SetSize(238, 175) - box:SetModel(data.model) - box:SetHeadBox(data.headbox or false) - box:SetText(data.label) - - if isfunction(data.OnModelSelected) then - box.OnModelSelected = function(slf, userTriggered, state) - if not userTriggered then return end - - data.OnModelSelected(slf, state) - end - end - - if isfunction(data.OnModelHattable) then - box.OnModelHattable = function(slf, userTriggered, state) - if not userTriggered then return end - - data.OnModelHattable(slf, state) - end - end - - return box + local box = base:Add("DImageCheckBoxTTT2") + + box:SetSize(238, 175) + box:SetModel(data.model) + box:SetHeadBox(data.headbox or false) + box:SetText(data.label) + + if isfunction(data.OnModelSelected) then + box.OnModelSelected = function(slf, userTriggered, state) + if not userTriggered then + return + end + + data.OnModelSelected(slf, state) + end + end + + if isfunction(data.OnModelHattable) then + box.OnModelHattable = function(slf, userTriggered, state) + if not userTriggered then + return + end + + data.OnModelHattable(slf, state) + end + end + + return box end -- Adds an icon layout to the form @@ -540,14 +640,14 @@ end -- @return Panel The created panel -- @realm client function PANEL:MakeIconLayout(spacing) - local panel = vgui.Create("DIconLayout", self) + local panel = vgui.Create("DIconLayout", self) - panel:SetSpaceY(spacing or 10) - panel:SetSpaceX(spacing or 10) + panel:SetSpaceY(spacing or 10) + panel:SetSpaceX(spacing or 10) - self:AddItem(panel) + self:AddItem(panel) - return panel + return panel end derma.DefineControl("DFormTTT2", "", PANEL, "DCollapsibleCategoryTTT2") @@ -562,10 +662,10 @@ derma.DefineControl("DFormTTT2", "", PANEL, "DCollapsibleCategoryTTT2") -- @return Panel The created collapsable form -- @realm client function vgui.CreateTTT2Form(parent, name) - local form = vgui.Create("DFormTTT2", parent) + local form = vgui.Create("DFormTTT2", parent, name) - form:SetName(name) - form:Dock(TOP) + form:SetName(name) + form:Dock(TOP) - return form + return form end diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dframe_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dframe_ttt2.lua index 6f5743f11..cc1f3d2ae 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dframe_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dframe_ttt2.lua @@ -52,77 +52,85 @@ AccessorFunc(PANEL, "m_bBackgroundBlur", "BackgroundBlur", FORCE_BOOL) --- -- @ignore function PANEL:Init() - self:SetFocusTopLevel(true) - self:SetPaintShadow(true) + self:SetFocusTopLevel(true) + self:SetPaintShadow(true) - self:InitButtons() + self:InitButtons() - self:SetDraggable(true) - self:SetSizable(false) - self:SetScreenLock(false) - self:SetDeleteOnClose(true) + self:SetDraggable(true) + self:SetSizable(false) + self:SetScreenLock(false) + self:SetDeleteOnClose(true) - self:SetMinWidth(50) - self:SetMinHeight(50) + self:SetMinWidth(50) + self:SetMinHeight(50) - -- This turns off the engine drawing - self:SetPaintBackgroundEnabled(false) - self:SetPaintBorderEnabled(false) + -- This turns off the engine drawing + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) - self:SetVisible(true) - self:SetDraggable(true) - self:ShowCloseButton(true) - self:ShowBackButton(false) - self:SetDeleteOnClose(true) - self:SetBackgroundBlur(false) - self:SetSkin("ttt2_default") + self:SetVisible(true) + self:SetDraggable(true) + self:ShowCloseButton(true) + self:ShowBackButton(false) + self:SetDeleteOnClose(true) + self:SetBackgroundBlur(false) + self:SetSkin("ttt2_default") - self:MakePopup() - self:SetKeyboardInputEnabled(false) + self:MakePopup() + self:SetKeyboardInputEnabled(false) - self.m_fCreateTime = SysTime() + self.m_fCreateTime = SysTime() - self.title = { - text = "Window", - font = "DermaTTT2Title" - } + self.title = { + text = "Window", + font = "DermaTTT2Title", + } - self:SetPadding(5, 5, 5, 5) + self:SetPadding(5, 5, 5, 5) - self.panelData = { - hidden = false, - deleteOnClose = true, - callbacks = {} - } + self.panelData = { + hidden = false, + deleteOnClose = true, + callbacks = {}, + } end --- -- Function to hide a frame without deleting the panel data. -- @realm client function PANEL:HideFrame() - if self:IsFrameHidden() then return end + if self:IsFrameHidden() then + return + end - if isfunction(self.OnHide) and self:OnHide() == false then return end + if isfunction(self.OnHide) and self:OnHide() == false then + return + end - self.panelData.hidden = true - self.panelData.deleteOnClose = self:GetDeleteOnClose() + self.panelData.hidden = true + self.panelData.deleteOnClose = self:GetDeleteOnClose() - self:SetDeleteOnClose(false) - self:Close() + self:SetDeleteOnClose(false) + self:Close() end --- -- Function to unhide a previously hidden frame. -- @realm client function PANEL:ShowFrame() - if not self:IsFrameHidden() then return end + if not self:IsFrameHidden() then + return + end - if isfunction(self.OnShow) and self:OnShow() == false then return end + if isfunction(self.OnShow) and self:OnShow() == false then + return + end - self.panelData.hidden = false + self.panelData.hidden = false - self:SetDeleteOnClose(self.panelData.deleteOnClose) - self:SetVisible(true) + self:SetDeleteOnClose(self.panelData.deleteOnClose) + self:SetVisible(true) end --- @@ -130,7 +138,15 @@ end -- @return boolean Returns if this frame is hidden -- @realm client function PANEL:IsFrameHidden() - return self.panelData.hidden + return self.panelData.hidden +end + +--- +-- Checks if a visible frame should block TTT2 Binds +-- @return boolean Returns if this frame blocks TTT2 Binds +-- @realm client +function PANEL:IsBlockingBindings() + return true end --- @@ -141,31 +157,33 @@ end -- @param[opt] string title The new title -- @realm client function PANEL:ClearFrame(w, h, title) - if isfunction(self.OnClear) and self:OnClear() == false then return end + if isfunction(self.OnClear) and self:OnClear() == false then + return + end - local oldW, oldH = self:GetSize() - local oldTitle = self:GetTitle() + local oldW, oldH = self:GetSize() + local oldTitle = self:GetTitle() - self:Clear() - self:InitButtons() - self:ShowBackButton(false) + self:Clear() + self:InitButtons() + self:ShowBackButton(false) - if (w and w ~= oldW) or (h and h ~= oldH) then - self:SetSize(w or oldW, h or oldH) - self:Center() - end + if (w and w ~= oldW) or (h and h ~= oldH) then + self:SetSize(w or oldW, h or oldH) + self:Center() + end - if title ~= oldTitle then - self:SetTitle(title or oldTitle) - end + if title ~= oldTitle then + self:SetTitle(title or oldTitle) + end end --- -- Closes the frame. -- @realm client function PANEL:CloseFrame() - self:SetDeleteOnClose(true) - self:Close() + self:SetDeleteOnClose(true) + self:Close() end --- @@ -173,93 +191,83 @@ end -- @return boolean Return false to cancel this event -- @hook -- @realm client -function PANEL:OnHide() - -end +function PANEL:OnHide() end --- -- Function that is called when the frame is unhidden, return false to cancel event. -- @return boolean Return false to cancel this event -- @hook -- @realm client -function PANEL:OnShow() - -end +function PANEL:OnShow() end --- -- Function that is called when the frame is rebuild -- @hook -- @realm client -function PANEL:OnRebuild() - -end +function PANEL:OnRebuild() end --- -- Function that is called when the frame is about to be cleared, return false to cancel event. -- @return boolean Return false to cancel this event -- @hook -- @realm client -function PANEL:OnClear() - -end +function PANEL:OnClear() end --- -- Initializes the buttons -- @realm client function PANEL:InitButtons() - -- add close button - self.btnClose = vgui.Create("DButton", self) - self.btnClose:SetText("") - self.btnClose:SetVisible(true) + -- add close button + self.btnClose = vgui.Create("DButton", self) + self.btnClose:SetText("") + self.btnClose:SetVisible(true) - self.btnClose.DoClick = function(button) - self:Close() - end + self.btnClose.DoClick = function(button) + self:Close() + end - self.btnClose.Paint = function(panel, w, h) - derma.SkinHook("Paint", "WindowCloseButton", panel, w, h) - end + self.btnClose.Paint = function(panel, w, h) + derma.SkinHook("Paint", "WindowCloseButton", panel, w, h) + end - -- add back button - self.btnBack = vgui.Create("DButton", self) - self.btnBack:SetText("") - self.btnBack:SetVisible(true) + -- add back button + self.btnBack = vgui.Create("DButton", self) + self.btnBack:SetText("") + self.btnBack:SetVisible(true) - self.btnBack.DoClick = function(button) + self.btnBack.DoClick = function(button) end - end - - self.btnBack.Paint = function(panel, w, h) - derma.SkinHook("Paint", "WindowBackButton", panel, w, h) - end + self.btnBack.Paint = function(panel, w, h) + derma.SkinHook("Paint", "WindowBackButton", panel, w, h) + end end --- -- @param string strTitle -- @realm client function PANEL:SetTitle(strTitle) - self.title.text = strTitle + self.title.text = strTitle end --- -- @return string -- @realm client function PANEL:GetTitle() - return self.title.text + return self.title.text end --- -- @param string fontName -- @realm client function PANEL:SetTitleFont(fontName) - self.title.font = fontName + self.title.font = fontName end --- -- @return string -- @realm client function PANEL:GetTitleFont() - return self.title.font + return self.title.font end --- @@ -269,45 +277,52 @@ end -- @param number bottom -- @realm client function PANEL:SetPadding(left, top, right, bottom) - self.padding = { - left = left, - top = top, - right = right, - bottom = bottom - } - - self:UpdatePadding() + self.padding = { + left = left, + top = top, + right = right, + bottom = bottom, + } + + self:UpdatePadding() end --- -- @realm client function PANEL:UpdatePadding() - self:DockPadding(self.padding.left, vskin.GetHeaderHeight() + vskin.GetBorderSize() + self.padding.top, self.padding.right, self.padding.bottom) + self:DockPadding( + self.padding.left, + vskin.GetHeaderHeight() + vskin.GetBorderSize() + self.padding.top, + self.padding.right, + self.padding.bottom + ) end --- -- @param boolean bShow -- @realm client function PANEL:ShowCloseButton(bShow) - self.btnClose:SetVisible(bShow) + self.btnClose:SetVisible(bShow) end --- -- @param function fn -- @realm client function PANEL:CloseButtonClickOverride(fn) - if not IsValid(self.btnClose) or not isfunction(fn) then return end + if not IsValid(self.btnClose) or not isfunction(fn) then + return + end - self.btnClose.DoClick = function(button) - fn(button) - end + self.btnClose.DoClick = function(button) + fn(button) + end end --- -- @param boolean bShow -- @realm client function PANEL:ShowBackButton(bShow) - self.btnBack:SetVisible(bShow) + self.btnBack:SetVisible(bShow) end --- @@ -316,172 +331,179 @@ end -- @param function fn The callback function -- @realm client function PANEL:RegisterBackFunction(fn) - self.btnBack.DoClick = fn + self.btnBack.DoClick = fn end --- -- @realm client function PANEL:Close() - self:SetVisible(false) + self:SetVisible(false) - if self:GetDeleteOnClose() then - self:Remove() - end + if self:GetDeleteOnClose() then + self:Remove() + end - self:OnClose() + self:OnClose() end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:OnClose() - -end +function PANEL:OnClose() end --- -- @realm client function PANEL:Center() - self:InvalidateLayout(true) - self:CenterVertical() - self:CenterHorizontal() + self:InvalidateLayout(true) + self:CenterVertical() + self:CenterHorizontal() end --- -- @return boolean -- @realm client function PANEL:IsActive() - if self:HasFocus() or vgui.FocusedHasParent(self) then - return true - end + if self:HasFocus() or vgui.FocusedHasParent(self) then + return true + end - return false + return false end --- -- @ignore function PANEL:Think() - local scrW = ScrW() - local scrH = ScrH() - local mousex = math.Clamp(gui.MouseX(), 1, scrW - 1) - local mousey = math.Clamp(gui.MouseY(), 1, scrH - 1) - - if self.dragging then - local x = mousex - self.dragging[1] - local y = mousey - self.dragging[2] - - -- Lock to screen bounds if screenlock is enabled - if self:GetScreenLock() then - x = math.Clamp(x, 0, scrW - self:GetWide()) - y = math.Clamp(y, 0, scrH - self:GetTall()) - end - - self:SetPos(x, y) - end - - if self.sizing then - local x = mousex - self.sizing[1] - local y = mousey - self.sizing[2] - local px, py = self:GetPos() - - if x < self.m_iMinWidth then - x = self.m_iMinWidth - elseif x > scrW - px and self:GetScreenLock() then - x = scrW - px - end - - if y < self.m_iMinHeight then - y = self.m_iMinHeight - elseif y > scrH - py and self:GetScreenLock() then - y = scrH - py - end - - self:SetSize(x, y) - self:SetCursor("sizenwse") - - return - end - - local screenX, screenY = self:LocalToScreen(0, 0) - - if self.Hovered and self.m_bSizable and mousex > (screenX + self:GetWide() - 20) and mousey > (screenY + self:GetTall() - vskin.GetHeaderHeight()) then - self:SetCursor("sizenwse") - - return - end - - if self.Hovered and self:GetDraggable() and mousey < (screenY + vskin.GetHeaderHeight()) then - self:SetCursor("sizeall") - - return - end - - self:SetCursor("arrow") - - -- Don't allow the frame to go higher than 0 - if self.y < 0 then - self:SetPos(self.x, 0) - end + local scrW = ScrW() + local scrH = ScrH() + local mousex = math.Clamp(gui.MouseX(), 1, scrW - 1) + local mousey = math.Clamp(gui.MouseY(), 1, scrH - 1) + + if self.dragging then + local x = mousex - self.dragging[1] + local y = mousey - self.dragging[2] + + -- Lock to screen bounds if screenlock is enabled + if self:GetScreenLock() then + x = math.Clamp(x, 0, scrW - self:GetWide()) + y = math.Clamp(y, 0, scrH - self:GetTall()) + end + + self:SetPos(x, y) + end + + if self.sizing then + local x = mousex - self.sizing[1] + local y = mousey - self.sizing[2] + local px, py = self:GetPos() + + if x < self.m_iMinWidth then + x = self.m_iMinWidth + elseif x > scrW - px and self:GetScreenLock() then + x = scrW - px + end + + if y < self.m_iMinHeight then + y = self.m_iMinHeight + elseif y > scrH - py and self:GetScreenLock() then + y = scrH - py + end + + self:SetSize(x, y) + self:SetCursor("sizenwse") + + return + end + + local screenX, screenY = self:LocalToScreen(0, 0) + + if + self.Hovered + and self.m_bSizable + and mousex > (screenX + self:GetWide() - 20) + and mousey > (screenY + self:GetTall() - vskin.GetHeaderHeight()) + then + self:SetCursor("sizenwse") + + return + end + + if self.Hovered and self:GetDraggable() and mousey < (screenY + vskin.GetHeaderHeight()) then + self:SetCursor("sizeall") + + return + end + + self:SetCursor("arrow") + + -- Don't allow the frame to go higher than 0 + if self.y < 0 then + self:SetPos(self.x, 0) + end end --- -- @ignore function PANEL:Paint(w, h) - if self.m_bBackgroundBlur then - Derma_DrawBackgroundBlur(self, self.m_fCreateTime) - end + if self.m_bBackgroundBlur then + Derma_DrawBackgroundBlur(self, self.m_fCreateTime) + end - derma.SkinHook("Paint", "FrameTTT2", self, w, h) + derma.SkinHook("Paint", "FrameTTT2", self, w, h) - return true + return true end --- -- @realm client function PANEL:OnMousePressed() - local screenX, screenY = self:LocalToScreen(0, 0) - - if self.m_bSizable and gui.MouseX() > (screenX + self:GetWide() - 20) and gui.MouseY() > (screenY + self:GetTall() - vskin.GetHeaderHeight()) then - self.sizing = { - gui.MouseX() - self:GetWide(), - gui.MouseY() - self:GetTall() - } - - self:MouseCapture(true) - - return - end - - if self:GetDraggable() and gui.MouseY() < (screenY + vskin.GetHeaderHeight()) then - self.dragging = { - gui.MouseX() - self.x, - gui.MouseY() - self.y - } - - self:MouseCapture(true) - end + local screenX, screenY = self:LocalToScreen(0, 0) + + if + self.m_bSizable + and gui.MouseX() > (screenX + self:GetWide() - 20) + and gui.MouseY() > (screenY + self:GetTall() - vskin.GetHeaderHeight()) + then + self.sizing = { + gui.MouseX() - self:GetWide(), + gui.MouseY() - self:GetTall(), + } + + self:MouseCapture(true) + + return + end + + if self:GetDraggable() and gui.MouseY() < (screenY + vskin.GetHeaderHeight()) then + self.dragging = { + gui.MouseX() - self.x, + gui.MouseY() - self.y, + } + + self:MouseCapture(true) + end end --- -- @realm client function PANEL:OnMouseReleased() - self.dragging = nil - self.sizing = nil + self.dragging = nil + self.sizing = nil - self:MouseCapture(false) + self:MouseCapture(false) end --- -- @ignore function PANEL:PerformLayout() - local size = vskin.GetHeaderHeight() + local size = vskin.GetHeaderHeight() - self.btnClose:SetPos(self:GetWide() - size, 0) - self.btnClose:SetSize(size, size) + self.btnClose:SetPos(self:GetWide() - size, 0) + self.btnClose:SetSize(size, size) - self.btnBack:SetPos(0, 0) - self.btnBack:SetSize(100, size) + self.btnBack:SetPos(0, 0) + self.btnBack:SetSize(100, size) - self:UpdatePadding() + self:UpdatePadding() end derma.DefineControl("DFrameTTT2", "A simple window", PANEL, "EditablePanel") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dimagecheckbox_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dimagecheckbox_ttt2.lua index 45e2b3ec0..cc69b29bf 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dimagecheckbox_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dimagecheckbox_ttt2.lua @@ -1,6 +1,6 @@ --- -- @class PANEL --- @section ImageCheckBoxTTT2 +-- @section DImageCheckBoxTTT2 local mathMax = math.max local mathMin = math.min @@ -60,49 +60,58 @@ AccessorFunc(PANEL, "bRotating", "Rotating") --- -- @ignore function PANEL:Init() - self.lastPaint = 0 - self.directionalLight = {} - self.farZ = 4096 + self.lastPaint = 0 + self.directionalLight = {} + self.farZ = 4096 - self:SetContentAlignment(5) + self:SetContentAlignment(5) - self:SetTall(22) - self:SetMouseInputEnabled(true) - self:SetKeyboardInputEnabled(true) + self:SetTall(22) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) - self:SetCursor("hand") - self:SetFont("DermaTTT2TextLarge") + self:SetCursor("hand") + self:SetFont("DermaTTT2TextLarge") - self:SetCamPos(Vector(55, 55, 55)) - self:SetLookAt(Vector(0, 0, 55)) - self:SetFOV(35) + self:SetCamPos(Vector(55, 55, 55)) + self:SetLookAt(Vector(0, 0, 55)) + self:SetFOV(35) - self:SetAnimSpeed(0.5) - self:SetAnimated(true) - self:SetRotating(false) + self:SetAnimSpeed(0.5) + self:SetAnimated(true) + self:SetRotating(false) - self:SetAmbientLight(Color(50, 50, 50)) + self:SetAmbientLight(Color(50, 50, 50)) - self:SetDirectionalLight(BOX_TOP, Color(255, 255, 255)) - self:SetDirectionalLight(BOX_FRONT, Color(255, 255, 255)) + self:SetDirectionalLight(BOX_TOP, Color(255, 255, 255)) + self:SetDirectionalLight(BOX_FRONT, Color(255, 255, 255)) - self:SetColor(COLOR_WHITE) + self:SetColor(COLOR_WHITE) - -- remove label and overwrite function - self:SetText("") - self.SetText = function(slf, text) - slf.data.text = text - end + -- remove label and overwrite function + self:SetText("") + self.SetText = function(slf, text) + slf.data.text = text + end - self.data = { - text = "", - img = nil, - mdl = nil, - ent = nil, - headbox = false, - hattable = false, - selected = false - } + self.data = { + text = "", + img = nil, + mdl = nil, + ent = nil, + headbox = false, + hattable = false, + selected = false, + } +end + +--- +-- @realm client +function PANEL:OnRemove() + -- old ent is removed because clientside models are not garbage collected + if IsValid(self.data.ent) then + self.data.ent:Remove() + end end --- @@ -110,34 +119,34 @@ end -- @param Color color -- @realm client function PANEL:SetDirectionalLight(iDirection, color) - self.directionalLight[iDirection] = color + self.directionalLight[iDirection] = color end --- -- @ignore function PANEL:GetText() - return self.data.text + return self.data.text end --- -- @param Material image -- @realm client function PANEL:SetImage(image) - self.data.img = image + self.data.img = image end --- -- @param boolean state -- @realm client function PANEL:SetHeadBox(state) - self.data.headbox = state + self.data.headbox = state end --- -- @return boolean -- @realm client function PANEL:HasHeadBox() - return self.data.headbox or false + return self.data.headbox or false end --- @@ -145,114 +154,123 @@ end -- @param boolean userTriggered -- @realm client function PANEL:SetModelHattable(state, userTriggered) - self.data.hattable = state + self.data.hattable = state - self:OnModelHattable(userTriggered or false, state) + self:OnModelHattable(userTriggered or false, state) end --- -- @return boolean -- @realm client function PANEL:IsModelHattable() - return self.data.hattable or false + return self.data.hattable or false end --- -- @param string model -- @realm client function PANEL:SetModel(model) - self.data.mdl = model + self.data.mdl = model - -- set the entity - local ent = ClientsideModel(model, RENDERGROUP_OTHER) + -- set the entity + local ent = ClientsideModel(model, RENDERGROUP_OTHER) - if not IsValid(ent) then return end + if not IsValid(ent) then + return + end - ent:SetNoDraw(true) - ent:SetIK(false) + ent:SetNoDraw(true) + ent:SetIK(false) - -- now try to find a nice sequence to play - local iSeq = ent:LookupSequence("walk_all") + -- now try to find a nice sequence to play + local iSeq = ent:LookupSequence("walk_all") - if iSeq <= 0 then - iSeq = ent:LookupSequence("WalkUnarmed_all") - end + if iSeq <= 0 then + iSeq = ent:LookupSequence("WalkUnarmed_all") + end - if iSeq <= 0 then - iSeq = ent:LookupSequence("walk_all_moderate") - end + if iSeq <= 0 then + iSeq = ent:LookupSequence("walk_all_moderate") + end - if iSeq > 0 then - ent:ResetSequence(iSeq) - end + if iSeq > 0 then + ent:ResetSequence(iSeq) + end - -- before storing the ent, make sure that a possible old ent - -- is removed because clientside models are not garbage collected - if IsValid(self.data.ent) then - self.data.ent:Remove() - self.data.ent = nil - end + -- before storing the ent, make sure that a possible old ent + -- is removed because clientside models are not garbage collected + if IsValid(self.data.ent) then + self.data.ent:Remove() + self.data.ent = nil + end - self.data.ent = ent + self.data.ent = ent end --- -- @realm client function PANEL:DrawModel() - local ent = self.data.ent + local ent = self.data.ent - if not IsValid(ent) then return end + if not IsValid(ent) then + return + end - local w, h = self:GetSize() - local xBaseStart, yBaseStart = self:LocalToScreen(0, 0) + local w, h = self:GetSize() + local xBaseStart, yBaseStart = self:LocalToScreen(0, 0) - local xLimitStart, yLimitStart = xBaseStart, yBaseStart - local xLimitEnd, yLimitEnd = self:LocalToScreen(self:GetWide(), self:GetTall()) + local xLimitStart, yLimitStart = xBaseStart, yBaseStart + local xLimitEnd, yLimitEnd = self:LocalToScreen(self:GetWide(), self:GetTall()) - local curparent = self + local currentParent = self - -- iterate till the top is found to make sure the image is not out of bounds - while curparent:GetParent() do - curparent = curparent:GetParent() + -- iterate till the top is found to make sure the image is not out of bounds + while currentParent:GetParent() do + currentParent = currentParent:GetParent() - local x1, y1 = curparent:LocalToScreen(0, 0) - local x2, y2 = curparent:LocalToScreen(curparent:GetWide(), curparent:GetTall()) + local x1, y1 = currentParent:LocalToScreen(0, 0) + local x2, y2 = currentParent:LocalToScreen(currentParent:GetWide(), currentParent:GetTall()) - xLimitStart = mathMax(xLimitStart, x1) - yLimitStart = mathMax(yLimitStart, y1) - xLimitEnd = mathMin(xLimitEnd, x2) - yLimitEnd = mathMin(yLimitEnd, y2) - end + xLimitStart = mathMax(xLimitStart, x1) + yLimitStart = mathMax(yLimitStart, y1) + xLimitEnd = mathMin(xLimitEnd, x2) + yLimitEnd = mathMin(yLimitEnd, y2) + end - self:LayoutEntity(ent) + self:LayoutEntity(ent) - local ang = self.aLookAngle or (self.vLookatPos - self.vCamPos):Angle() + local ang = self.aLookAngle or (self.vLookatPos - self.vCamPos):Angle() - cam.Start3D(self.vCamPos, ang, self.fFOV, xBaseStart, yBaseStart, w, h, 5, self.farZ) - render.SuppressEngineLighting(true) - render.SetLightingOrigin(ent:GetPos()) - render.ResetModelLighting(self.colAmbientLight.r / 255, self.colAmbientLight.g / 255, self.colAmbientLight.b / 255) - render.SetColorModulation(self.colColor.r / 255, self.colColor.g / 255, self.colColor.b / 255) - render.SetBlend((self:GetAlpha() / 255) * (self.colColor.a / 255)) + cam.Start3D(self.vCamPos, ang, self.fFOV, xBaseStart, yBaseStart, w, h, 5, self.farZ) + render.SuppressEngineLighting(true) + render.SetLightingOrigin(ent:GetPos()) + render.ResetModelLighting( + self.colAmbientLight.r / 255, + self.colAmbientLight.g / 255, + self.colAmbientLight.b / 255 + ) + render.SetColorModulation(self.colColor.r / 255, self.colColor.g / 255, self.colColor.b / 255) + render.SetBlend((self:GetAlpha() / 255) * (self.colColor.a / 255)) - for i = 0, 6 do - local col = self.directionalLight[i] + -- iterates over the model lighting enum: https://wiki.facepunch.com/gmod/Enums/BOX + for i = 0, 6 do + local col = self.directionalLight[i] - if col then - render.SetModelLighting(i, col.r / 255, col.g / 255, col.b / 255) - end - end + if col then + render.SetModelLighting(i, col.r / 255, col.g / 255, col.b / 255) + end + end - -- make a mask to make sure the graphic is limited - render.SetScissorRect(xLimitStart, yLimitStart, xLimitEnd, yLimitEnd, true) + -- make a mask to make sure the graphic is limited + render.SetScissorRect(xLimitStart, yLimitStart, xLimitEnd, yLimitEnd, true) - ent:DrawModel() + ent:DrawModel() - render.SetScissorRect(0, 0, 0, 0, false) - render.SuppressEngineLighting(false) - cam.End3D() + render.SetScissorRect(0, 0, 0, 0, false) + render.SuppressEngineLighting(false) + cam.End3D() - self.lastPaint = RealTime() + self.lastPaint = RealTime() end --- @@ -260,47 +278,47 @@ end -- @param Entity ent -- @realm client function PANEL:LayoutEntity(ent) - if self.bAnimated then - self:RunAnimation() - end + if self.bAnimated then + self:RunAnimation() + end - if self.bRotatin then - ent:SetAngles(Angle(0, RealTime() * 10 % 360, 0)) - end + if self.bRotatin then + ent:SetAngles(Angle(0, RealTime() * 10 % 360, 0)) + end end --- -- @realm client function PANEL:RunAnimation() - self.data.ent:FrameAdvance((RealTime() - self.lastPaint) * self.m_fAnimSpeed) + self.data.ent:FrameAdvance((RealTime() - self.lastPaint) * self.m_fAnimSpeed) end --- -- @return Material -- @realm client function PANEL:GetImage() - return self.data.img + return self.data.img end --- -- @return string -- @realm client function PANEL:GetModel() - return self.data.mdl + return self.data.mdl end --- -- @return boolean -- @realm client function PANEL:HasImage() - return self.data.img ~= nil + return self.data.img ~= nil end --- -- @return boolean -- @realm client function PANEL:HasModel() - return self.data.mdl ~= nil + return self.data.mdl ~= nil end --- @@ -308,32 +326,32 @@ end -- @param boolean userTriggered -- @realm client function PANEL:SetModelSelected(selected, userTriggered) - self.data.selected = selected + self.data.selected = selected - self:OnModelSelected(userTriggered or false, self.data.selected) + self:OnModelSelected(userTriggered or false, self.data.selected) end --- -- @return boolean -- @realm client function PANEL:IsModelSelected() - return self.data.selected or false + return self.data.selected or false end --- -- @ignore function PANEL:OnMouseReleased(keyCode) - if keyCode == MOUSE_LEFT then - local state = not self:IsModelSelected() + if keyCode == MOUSE_LEFT then + local state = not self:IsModelSelected() - self:SetModelSelected(state, true) - elseif keyCode == MOUSE_RIGHT then - local state = not self:IsModelHattable() + self:SetModelSelected(state, true) + elseif keyCode == MOUSE_RIGHT then + local state = not self:IsModelHattable() - self:SetModelHattable(state, true) - end + self:SetModelHattable(state, true) + end - self.BaseClass.OnMouseReleased(self, keyCode) + self.BaseClass.OnMouseReleased(self, keyCode) end --- @@ -341,31 +359,32 @@ end -- @param boolean userTriggered -- @param boolean state -- @realm client -function PANEL:OnModelSelected(userTriggered, state) - -end +function PANEL:OnModelSelected(userTriggered, state) end --- -- Is called when the hattable state is updated. Should be overwritten. -- @param boolean userTriggered -- @param boolean state -- @realm client -function PANEL:OnModelHattable(userTriggered, state) - -end +function PANEL:OnModelHattable(userTriggered, state) end --- -- @ignore function PANEL:IsDown() - return self.Depressed + return self.Depressed end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "ImageCheckBoxTTT2", self, w, h) + derma.SkinHook("Paint", "ImageCheckBoxTTT2", self, w, h) - return false + return false end -derma.DefineControl("DImageCheckBoxTTT2", "A special button with image or model that acts as a checkbox", PANEL, "DLabelTTT2") +derma.DefineControl( + "DImageCheckBoxTTT2", + "A special button with image or model that acts as a checkbox", + PANEL, + "DLabelTTT2" +) diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dinfoitem_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dinfoitem_ttt2.lua new file mode 100644 index 000000000..fdb6cc5b7 --- /dev/null +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dinfoitem_ttt2.lua @@ -0,0 +1,92 @@ +--- +-- @class PANEL +-- @section DInfoItemTTT2 + +local PANEL = {} + +--- +-- @ignore +function PANEL:Init() + self:SetContentAlignment(5) + + self:SetTall(22) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) + + self.data = {} +end + +--- +-- @param table data +-- @realm client +function PANEL:SetData(data) + self.data = data +end + +--- +-- @return string|nil +-- @realm client +function PANEL:GetText() + return self.data.text.text +end + +--- +-- @return string +-- @realm client +function PANEL:GetTitle() + return self.data.text.title +end + +--- +-- @return Material|nil +-- @realm client +function PANEL:GetIcon() + return self.data.iconMaterial +end + +--- +-- @return boolean +-- @realm client +function PANEL:HasIcon() + return self.data.iconMaterial ~= nil +end + +--- +-- @return Color|nil +-- @realm client +function PANEL:GetColor() + return self.data.colorBox +end + +--- +-- @return string +-- @realm client +function PANEL:GetIconText() + if not self:HasIconTextFunction() then + return "" + end + + return self.data.iconText() +end + +--- +-- @return boolean +-- @realm client +function PANEL:HasIconTextFunction() + return isfunction(self.data.iconText) +end + +--- +-- @ignore +function PANEL:Paint(w, h) + derma.SkinHook("Paint", "InfoItemTTT2", self, w, h) + + return false +end + +derma.DefineControl( + "DInfoItemTTT2", + "An info box that can contain an icon, a header text and a description area", + PANEL, + "DPanelTTT2" +) diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dlabel_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dlabel_ttt2.lua index 680dd44d6..79550cb39 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dlabel_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dlabel_ttt2.lua @@ -67,265 +67,281 @@ AccessorFunc(PANEL, "m_bHighlight", "Highlight", FORCE_BOOL) --- -- @ignore function PANEL:Init() - self:SetIsToggle(false) - self:SetToggle(false) - self:SetEnabled(true) - self:SetMouseInputEnabled(false) - self:SetKeyboardInputEnabled(false) - self:SetDoubleClickingEnabled(true) + self:SetIsToggle(false) + self:SetToggle(false) + self:SetEnabled(true) + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + self:SetDoubleClickingEnabled(true) - -- Nicer default height - self:SetTall(20) + -- Nicer default height + self:SetTall(20) - -- This turns off the engine drawing - self:SetPaintBackgroundEnabled(false) - self:SetPaintBorderEnabled(false) + -- This turns off the engine drawing + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) - self:SetFont("DermaTTT2Text") + self:SetFont("DermaTTT2Text") end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "LabelTTT2", self, w, h) + derma.SkinHook("Paint", "LabelTTT2", self, w, h) - return true + return true +end + +--- +-- @param Panel master +-- @realm client +function PANEL:SetMaster(master) + if not IsValid(master) then + return + end + + self.master = master +end + +--- +-- @return number +-- @realm client +function PANEL:GetIndentationMargin() + if not IsValid(self.master) then + return 0 + end + + return 10 + self.master:GetIndentationMargin() end --- -- @param string strFont -- @realm client function PANEL:SetFont(strFont) - self.m_FontName = strFont + self.m_FontName = strFont - self:SetFontInternal(self.m_FontName) - self:ApplySchemeSettings() + self:SetFontInternal(self.m_FontName) + self:ApplySchemeSettings() end --- -- Sets the param table used for the param translation. -- @param table params -- @realm client -function PANEL:SetParams(params) - self.params = params +function PANEL:SetTextParams(params) + self.params = params end --- -- @realm client -function PANEL:GetParams() - return self.params or {} +function PANEL:GetTextParams() + return self.params or {} end --- -- @realm client function PANEL:Toggle() - if not self:GetIsToggle() then return end + if not self:GetIsToggle() then + return + end - self:SetToggle(not self:GetToggle()) - self:OnToggled(self:GetToggle()) + self:SetToggle(not self:GetToggle()) + self:OnToggled(self:GetToggle()) end --- -- @param boolean bEnabled -- @realm client function PANEL:SetEnabled(bEnabled) - self.m_bEnabled = bEnabled + self.m_bEnabled = bEnabled - self:InvalidateLayout() + self:InvalidateLayout() end --- -- @return boolean -- @realm client function PANEL:IsEnabled() - return self.m_bEnabled + return self.m_bEnabled end --- -- @return boolean -- @realm client function PANEL:GetDisabled() - return not self:IsEnabled() + return not self:IsEnabled() end --- -- @realm client -function PANEL:ApplySchemeSettings() - -end +function PANEL:ApplySchemeSettings() end --- -- @ignore function PANEL:Think() - if self:GetAutoStretchVertical() then - self:SizeToContentsY() - end + if self:GetAutoStretchVertical() then + self:SizeToContentsY() + end end --- -- @ignore function PANEL:PerformLayout() - self:ApplySchemeSettings() + self:ApplySchemeSettings() end --- -- @realm client function PANEL:OnCursorEntered() - self:InvalidateLayout(true) + self:InvalidateLayout(true) end --- -- @realm client function PANEL:OnCursorExited() - self:InvalidateLayout(true) + self:InvalidateLayout(true) end --- -- @param number mcode -- @realm client function PANEL:OnMousePressed(mcode) - if self:GetDisabled() then return end - - if mcode == MOUSE_LEFT and not dragndrop.IsDragging() and self.m_bDoubleClicking then - if self.LastClickTime and SysTime() - self.LastClickTime < 0.2 then - self:DoDoubleClickInternal() - self:DoDoubleClick() - - return - end - - self.LastClickTime = SysTime() - end - - -- If we're selectable and have shift held down then go up - -- the parent until we find a selection canvas and start box selection - if self:IsSelectable() and mcode == MOUSE_LEFT and input.IsShiftDown() then - return self:StartBoxSelection() - end - - self:MouseCapture(true) - self.Depressed = true - self:OnDepressed() - self:InvalidateLayout(true) - - -- - -- Tell DragNDrop that we're down, and might start getting dragged! - -- - self:DragMousePress(mcode) + if self:GetDisabled() then + return + end + + if mcode == MOUSE_LEFT and not dragndrop.IsDragging() and self.m_bDoubleClicking then + if self.LastClickTime and SysTime() - self.LastClickTime < 0.2 then + self:DoDoubleClickInternal() + self:DoDoubleClick() + + return + end + + self.LastClickTime = SysTime() + end + + -- If we're selectable and have shift held down then go up + -- the parent until we find a selection canvas and start box selection + if self:IsSelectable() and mcode == MOUSE_LEFT and input.IsShiftDown() then + return self:StartBoxSelection() + end + + self:MouseCapture(true) + self.Depressed = true + self:OnDepressed() + self:InvalidateLayout(true) + + -- + -- Tell DragNDrop that we're down, and might start getting dragged! + -- + self:DragMousePress(mcode) end --- -- @param number mcode -- @realm client function PANEL:OnMouseReleased(mcode) - self:MouseCapture(false) - - if self:GetDisabled() then return end - - if not self.Depressed and dragndrop.m_DraggingMain ~= self then return end - - if self.Depressed then - self.Depressed = nil - self:OnReleased() - self:InvalidateLayout(true) - end - - -- If we were being dragged then don't do the default behaviour! - if self:DragMouseRelease(mcode) then return end - - if self:IsSelectable() and mcode == MOUSE_LEFT then - local canvas = self:GetSelectionCanvas() - - if canvas then - canvas:UnselectAll() - end - end - - if not self.Hovered then return end - - -- - -- For the purposes of these callbacks we want to - -- keep depressed true. This helps us out in controls - -- like the checkbox in the properties dialog. Because - -- the properties dialog will only manually change the value - -- if IsEditing() is true - and the only way to work out if - -- a label/button based control is editing is when it's depressed. - -- - self.Depressed = true - - if mcode == MOUSE_RIGHT then - self:DoRightClick() - elseif mcode == MOUSE_LEFT then - self:DoClickInternal() - self:DoClick() - elseif mcode == MOUSE_MIDDLE then - self:DoMiddleClick() - end - - self.Depressed = nil + self:MouseCapture(false) + + if self:GetDisabled() then + return + end + + if not self.Depressed and dragndrop.m_DraggingMain ~= self then + return + end + + if self.Depressed then + self.Depressed = nil + self:OnReleased() + self:InvalidateLayout(true) + end + + -- If we were being dragged then don't do the default behaviour! + if self:DragMouseRelease(mcode) then + return + end + + if self:IsSelectable() and mcode == MOUSE_LEFT then + local canvas = self:GetSelectionCanvas() + + if canvas then + canvas:UnselectAll() + end + end + + if not self.Hovered then + return + end + + -- + -- For the purposes of these callbacks we want to + -- keep depressed true. This helps us out in controls + -- like the checkbox in the properties dialog. Because + -- the properties dialog will only manually change the value + -- if IsEditing() is true - and the only way to work out if + -- a label/button based control is editing is when it's depressed. + -- + self.Depressed = true + + if mcode == MOUSE_RIGHT then + self:DoRightClick() + elseif mcode == MOUSE_LEFT then + self:DoClickInternal() + self:DoClick() + elseif mcode == MOUSE_MIDDLE then + self:DoMiddleClick() + end + + self.Depressed = nil end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:OnReleased() - -end +function PANEL:OnReleased() end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:OnDepressed() - -end +function PANEL:OnDepressed() end --- -- overwrites the base function with an empty function -- @param boolean bool -- @realm client -function PANEL:OnToggled(bool) - -end +function PANEL:OnToggled(bool) end --- -- @realm client function PANEL:DoClick() - self:Toggle() + self:Toggle() end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:DoRightClick() - -end +function PANEL:DoRightClick() end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:DoMiddleClick() - -end +function PANEL:DoMiddleClick() end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:DoClickInternal() - -end +function PANEL:DoClickInternal() end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:DoDoubleClick() - -end +function PANEL:DoDoubleClick() end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:DoDoubleClickInternal() - -end +function PANEL:DoDoubleClickInternal() end derma.DefineControl("DLabelTTT2", "A Label", PANEL, "DLabel") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dmenubutton_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dmenubutton_ttt2.lua index 376ff1864..1ed91a643 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dmenubutton_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dmenubutton_ttt2.lua @@ -12,116 +12,116 @@ AccessorFunc(PANEL, "m_bBorder", "DrawBorder", FORCE_BOOL) --- -- @ignore function PANEL:Init() - self:SetContentAlignment(5) + self:SetContentAlignment(5) - self:SetDrawBorder(true) - self:SetPaintBackground(true) + self:SetDrawBorder(true) + self:SetPaintBackground(true) - self:SetTall(22) - self:SetMouseInputEnabled(true) - self:SetKeyboardInputEnabled(true) + self:SetTall(22) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) - self:SetCursor("hand") - self:SetText("") + self:SetCursor("hand") + self:SetText("") - self.contents = { - title = "", - title_font = "DermaTTT2MenuButtonTitle", - description = "", - description_font = "DermaTTT2MenuButtonDescription", - icon = nil - } + self.contents = { + title = "", + title_font = "DermaTTT2MenuButtonTitle", + description = "", + description_font = "DermaTTT2MenuButtonDescription", + icon = nil, + } end --- -- @return boolean -- @realm client function PANEL:IsDown() - return self.Depressed + return self.Depressed end --- -- @param string mat Icon's material path -- @realm client function PANEL:SetImage(mat) - self.contents.icon = mat + self.contents.icon = mat end --- -- @return string Icon's material path -- @realm client function PANEL:GetImage() - return self.contents.icon + return self.contents.icon end --- -- @param string title -- @realm client function PANEL:SetTitle(title) - self.contents.title = title or "" + self.contents.title = title or "" end --- -- @return string The title -- @realm client function PANEL:GetTitle() - return self.contents.title + return self.contents.title end --- -- @param string title_font -- @realm client function PANEL:SetTitleFont(title_font) - self.contents.title_font = title_font or "" + self.contents.title_font = title_font or "" end --- -- @return string -- @realm client function PANEL:GetTitleFont() - return self.contents.title_font + return self.contents.title_font end --- -- @param string description -- @realm client function PANEL:SetDescription(description) - self.contents.description = description or "" + self.contents.description = description or "" end --- -- @return string -- @realm client function PANEL:GetDescription() - return self.contents.description + return self.contents.description end --- -- @param string description_font -- @realm client function PANEL:SetDescriptionFont(description_font) - self.contents.description_font = description_font or "" + self.contents.description_font = description_font or "" end --- -- @return string -- @realm client function PANEL:GetDescriptionFont() - return self.contents.description_font + return self.contents.description_font end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "MenuButtonTTT2", self, w, h) + derma.SkinHook("Paint", "MenuButtonTTT2", self, w, h) - return false + return false end --- -- @ignore function PANEL:PerformLayout() - DLabel.PerformLayout(self) + DLabel.PerformLayout(self) end --- @@ -129,17 +129,17 @@ end -- @param string strArgs -- @realm client function PANEL:SetConsoleCommand(strName, strArgs) - self.DoClick = function(slf, val) - RunConsoleCommand(strName, strArgs) - end + self.DoClick = function(slf, val) + RunConsoleCommand(strName, strArgs) + end end --- -- @ignore function PANEL:SizeToContents() - local w, h = self:GetContentSize() + local w, h = self:GetContentSize() - self:SetSize(w + 8, h + 4) + self:SetSize(w + 8, h + 4) end derma.DefineControl("DMenuButtonTTT2", "A standard Button", PANEL, "DLabel") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dnavpanel_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dnavpanel_ttt2.lua index 0537821eb..667f87e7f 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dnavpanel_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dnavpanel_ttt2.lua @@ -42,73 +42,73 @@ Derma_Hook(PANEL, "PerformLayout", "Layout", "Panel") --- -- @ignore function PANEL:Init() - self:SetPaintBackground(true) + self:SetPaintBackground(true) - -- This turns off the engine drawing - self:SetPaintBackgroundEnabled(false) - self:SetPaintBorderEnabled(false) + -- This turns off the engine drawing + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "NavPanelTTT2", self, w, h) + derma.SkinHook("Paint", "NavPanelTTT2", self, w, h) - return false + return false end --- -- @param boolean bEnabled -- @realm client function PANEL:SetEnabled(bEnabled) - self.m_bEnabled = not bEnabled - - if bEnabled then - self:SetAlpha(255) - self:SetMouseInputEnabled(true) - else - self:SetAlpha(75) - self:SetMouseInputEnabled(false) - end + self.m_bEnabled = not bEnabled + + if bEnabled then + self:SetAlpha(255) + self:SetMouseInputEnabled(true) + else + self:SetAlpha(75) + self:SetMouseInputEnabled(false) + end end --- -- @return boolean -- @realm client function PANEL:IsEnabled() - return self.m_bEnabled + return self.m_bEnabled end --- -- @param number mcode -- @realm client function PANEL:OnMousePressed(mcode) - if self:IsSelectionCanvas() and not dragndrop.IsDragging() then - self:StartBoxSelection() + if self:IsSelectionCanvas() and not dragndrop.IsDragging() then + self:StartBoxSelection() - return - end + return + end - if self:IsDraggable() then - self:MouseCapture(true) - self:DragMousePress(mcode) - end + if self:IsDraggable() then + self:MouseCapture(true) + self:DragMousePress(mcode) + end end --- -- @param number mcode -- @realm client function PANEL:OnMouseReleased(mcode) - if self:EndBoxSelection() then return end + if self:EndBoxSelection() then + return + end - self:MouseCapture(false) + self:MouseCapture(false) end --- -- overwrites the base function with an empty function -- @realm client -function PANEL:UpdateColours() - -end +function PANEL:UpdateColours() end derma.DefineControl("DNavPanelTTT2", "", PANEL, "Panel") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dnumslider_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dnumslider_ttt2.lua index 4346e8333..5eb0e26f5 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dnumslider_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dnumslider_ttt2.lua @@ -7,86 +7,100 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - self.TextArea = self:Add("DTextEntry") - self.TextArea:Dock(RIGHT) - self.TextArea:SetPaintBackground(false) - self.TextArea:SetWide(45) - self.TextArea:SetNumeric(true) - self.TextArea:SetFont("DermaTTT2Text") - - self.TextArea.OnChange = function(textarea, val) - self:SetValue(self.TextArea:GetText()) - end - - self.TextArea.Paint = function(slf, w, h) - derma.SkinHook("Paint", "SliderTextAreaTTT2", slf, w, h) - - return true - end - - -- Causes automatic clamp to min/max, disabled for now - self.Slider = self:Add("DSlider", self) - self.Slider:SetLockY(0.5) - - self.Slider.TranslateValues = function(slider, x, y) - return self:TranslateSliderValues(x, y) - end - - self.Slider.GetFraction = function(slf) - return self:GetFraction() - end - - self.Slider:SetTrapInside(true) - self.Slider:Dock(FILL) - self.Slider:SetHeight(16) - - self.Slider.Knob.OnMousePressed = function(panel, mcode) - if mcode == MOUSE_MIDDLE then - self:ResetToDefaultValue() - - return - end - - self.Slider:OnMousePressed(mcode) - end - - local sliderSetDragging = self.Slider.SetDragging - - self.Slider.SetDragging = function(panel, setDragging) - sliderSetDragging(self.Slider, setDragging) - self:OnChangeDragging(setDragging) - end - - -- make slider know a bit bigger - self.Slider.Knob.PerformLayout = function(slf) - local _, pH = self:GetSize() - - slf:SetSize(8, pH - 10) - end - - Derma_Hook(self.Slider, "Paint", "Paint", "NumSliderTTT2") - - self:SetTall(32) - self:SetMin(0) - self:SetMax(1) - self:SetDecimals(2) - self:SetText("") - self:SetValue(0.5) + self.TextArea = self:Add("DTextEntry") + self.TextArea:Dock(RIGHT) + self.TextArea:SetPaintBackground(false) + self.TextArea:SetWide(45) + self.TextArea:SetNumeric(true) + self.TextArea:SetFont("DermaTTT2Text") + self.TextArea:SetDrawLanguageID(false) + local vguiColor = util.GetActiveColor( + util.GetChangedColor(util.GetDefaultColor(vskin.GetBackgroundColor()), 25) + ) + self.TextArea:SetTextColor(vguiColor) + self.TextArea:SetCursorColor(vguiColor) + + -- On focus of textbox, enable input + self.TextArea.OnGetFocus = function(textarea) + util.getHighestPanelParent(self):SetKeyboardInputEnabled(true) + self:EnableTextBox(true) + end + + -- On focus loss, disable input and set new values from textbox + self.TextArea.OnLoseFocus = function(textarea) + util.getHighestPanelParent(self):SetKeyboardInputEnabled(false) + self:EnableTextBox(false) + self:SetValueFromTextBox() + end + + self.TextArea.Paint = function(slf, w, h) + derma.SkinHook("Paint", "SliderTextAreaTTT2", slf, w, h) + return true + end + + -- Causes automatic clamp to min/max, disabled for now + self.Slider = self:Add("DSlider", self) + self.Slider:SetLockY(0.5) + + self.Slider.TranslateValues = function(slider, x, y) + return self:TranslateSliderValues(x, y) + end + + self.Slider.GetFraction = function(slf) + return self:GetFraction() + end + + self.Slider:SetTrapInside(true) + self.Slider:Dock(FILL) + self.Slider:SetHeight(16) + + self.Slider.Knob.OnMousePressed = function(panel, mcode) + if mcode == MOUSE_MIDDLE then + self:ResetToDefaultValue() + + return + end + + self.Slider:OnMousePressed(mcode) + end + + local sliderSetDragging = self.Slider.SetDragging + + self.Slider.SetDragging = function(panel, setDragging) + sliderSetDragging(self.Slider, setDragging) + self:OnChangeDragging(setDragging) + end + + -- make slider know a bit bigger + self.Slider.Knob.PerformLayout = function(slf) + local _, pH = self:GetSize() + + slf:SetSize(8, pH - 10) + end + + Derma_Hook(self.Slider, "Paint", "Paint", "NumSliderTTT2") + + self:SetTall(32) + self:SetMin(0) + self:SetMax(1) + self:SetDecimals(2) + self:SetText("") + self:SetValue(0.5) end --- -- This function is called, when the slider starts and ends being dragged --- Calls SetConVarValue only after the dragging ends to not sync every change --- @param bool setDragging the state it is changed to +-- Calls SetCallbackEnabledVarValues only after the dragging ends to not sync every change +-- @param boolean setDragging the state it is changed to -- @realm client function PANEL:OnChangeDragging(setDragging) - local value = self:GetValue() + local value = self:GetValue() - if setDragging then - self.valueBeforeDragging = value - elseif value ~= self.valueBeforeDragging then - self:SetConVarValues(value) - end + if setDragging then + self.valueBeforeDragging = value + elseif value ~= self.valueBeforeDragging then + self:SetCallbackEnabledVarValues(value) + end end --- @@ -94,244 +108,342 @@ end -- @param number max -- @realm client function PANEL:SetMinMax(min, max) - self:SetMin(tonumber(min)) - self:SetMax(tonumber(max)) - self:UpdateNotches() + self:SetMin(tonumber(min)) + self:SetMax(tonumber(max)) + self:UpdateNotches() end --- -- @return[default=0] number -- @realm client function PANEL:GetMin() - return self.m_numMin or 0 + return self.m_numMin or 0 end --- -- @return[default=0] number -- @realm client function PANEL:GetMax() - return self.m_numMax or 0 + return self.m_numMax or 0 end --- -- @return[default=0] number -- @realm client function PANEL:GetRange() - return self:GetMax() - self:GetMin() + return self:GetMax() - self:GetMin() end --- -- @realm client function PANEL:ResetToDefaultValue() - if not self:GetDefaultValue() then return end + if not self:GetDefaultValue() then + return + end - self:SetValue(self:GetDefaultValue()) + self:SetValue(self:GetDefaultValue()) end --- -- @param number min -- @realm client function PANEL:SetMin(min) - self.m_numMin = tonumber(min) or 0 + self.m_numMin = tonumber(min) or 0 - self:UpdateNotches() + self:UpdateNotches() end --- -- @param number max -- @realm client function PANEL:SetMax(max) - self.m_numMax = tonumber(max) or 0 + self.m_numMax = tonumber(max) or 0 - self:UpdateNotches() + self:UpdateNotches() end --- -- @param any val --- @param bool ignoreConVar To avoid endless loops, separated setting of convars and UI values +-- @param boolean ignoreCallbackEnabledVars To avoid endless loops, separated setting of convars and UI values -- @realm client -function PANEL:SetValue(value, ignoreConVar) - if not value then return end +function PANEL:SetValue(value, ignoreCallbackEnabledVars) + if not value then + return + end - value = math.Clamp(tonumber(value) or 0, self:GetMin(), self:GetMax()) - value = math.Round(value, self:GetDecimals()) + value = math.Clamp(tonumber(value) or 0, self:GetMin(), self:GetMax()) + value = math.Round(value, self:GetDecimals()) - if value == self:GetValue() then return end + if value == self:GetValue() then + return + end - self.m_fValue = value + self.m_fValue = value - self:ValueChanged(value) + self:ValueChanged(value) + -- Set ConVars only when Mouse is released + if ignoreCallbackEnabledVars or self:IsEditing() then + return + end - -- Set ConVars only when Mouse is released - if ignoreConVar or self:IsEditing() then return end + self:SetCallbackEnabledVarValues(value) +end - self:SetConVarValues(value) +--- +-- @realm client +function PANEL:SetValueFromTextBox() + local val = self.TextArea:GetText() + val = val ~= "" and val or 0 + self:SetValue(val) + self.TextArea:SetText(val) end --- -- @param any val -- @realm client -function PANEL:SetConVarValues(value) - if self.conVar then - self.conVar:SetFloat(value) - end - - if self.serverConVar then - cvars.ChangeServerConVar(self.serverConVar, tostring(value)) - end +function PANEL:SetCallbackEnabledVarValues(value) + if self.conVar then + self.conVar:SetFloat(value) + end + + if self.serverConVar then + cvars.ChangeServerConVar(self.serverConVar, tostring(value)) + end + + if self.databaseInfo then + database.SetValue( + self.databaseInfo.name, + self.databaseInfo.itemName, + self.databaseInfo.key, + value + ) + end end --- -- @return any -- @realm client function PANEL:GetValue() - return self.m_fValue or 0 + return self.m_fValue or 0 end --- -- @param number value -- @realm client function PANEL:SetDefaultValue(value) - local noDefault = true + local noDefault = true - if isnumber(value) then - self.default = value - noDefault = false - else - self.default = nil - end + if isnumber(value) then + self.default = value + noDefault = false + else + self.default = nil + end - local reset = self:GetResetButton() + local reset = self:GetResetButton() - if ispanel(reset) then - reset.noDefault = noDefault - end + if ispanel(reset) then + reset.noDefault = noDefault + end end --- -- @return number defaultValue -- @realm client function PANEL:GetDefaultValue() - return self.default + return self.default end --- -- @param number d -- @realm client function PANEL:SetDecimals(d) - self.m_iDecimals = d + self.m_iDecimals = d - self:UpdateNotches() - self:ValueChanged(self:GetValue()) -- Update the text + self:UpdateNotches() + self:ValueChanged(self:GetValue()) -- Update the text end --- -- @return number -- @realm client function PANEL:GetDecimals() - return self.m_iDecimals or 0 + return self.m_iDecimals or 0 end --- -- @return boolean -- @realm client function PANEL:IsEditing() - return self.TextArea:IsEditing() or self.Slider:IsEditing() + return self.textBoxEnabled or self.Slider:IsEditing() end --- -- @return boolean -- @realm client function PANEL:IsHovered() - return self.TextArea:IsHovered() or self.Slider:IsHovered() or vgui.GetHoveredPanel() == self + return self.TextArea:IsHovered() or self.Slider:IsHovered() or vgui.GetHoveredPanel() == self +end + +--- +-- @param boolean b Enable or disable text input of text field +-- @realm client +function PANEL:EnableTextBox(b) + self.textBoxEnabled = b +end + +--- +-- @return boolean +-- @realm client +function PANEL:GetTextBoxEnabled() + return self.textBoxEnabled ~= nil and self.textBoxEnabled or false end --- -- @param string cvar -- @realm client function PANEL:SetConVar(cvar) - if not ConVarExists(cvar or "") then return end + if not ConVarExists(cvar or "") then + return + end - self.conVar = GetConVar(cvar) + self.conVar = GetConVar(cvar) - self:SetValue(self.conVar:GetFloat(), true) - self:SetDefaultValue(tonumber(GetConVar(cvar):GetDefault())) + self:SetValue(self.conVar:GetFloat(), true) + self:SetDefaultValue(tonumber(GetConVar(cvar):GetDefault())) end +local callbackEnabledVarTracker = 0 --- -- @param string cvar -- @realm client function PANEL:SetServerConVar(cvar) - if not cvar or cvar == "" then return end + if not cvar or cvar == "" then + return + end + + self.serverConVar = cvar + + -- Check if self is valid before calling SetValue. + cvars.ServerConVarGetValue(cvar, function(wasSuccess, value, default) + if wasSuccess and IsValid(self) then + self:SetValue(tonumber(value), true) + self:SetDefaultValue(tonumber(default)) + end + end) + + callbackEnabledVarTracker = callbackEnabledVarTracker + 1 + local myIdentifierString = "TTT2SliderConVarChangeCallback" + .. tostring(callbackEnabledVarTracker) + + local callback = function(conVarName, oldValue, newValue) + if not IsValid(self) then + -- We need to remove the callback in a timer, because otherwise the ConVar change callback code + -- will throw an error while looping over the callbacks. + -- This happens, because the callback is removed from the same table that is iterated over. + -- Thus, the table size changes while iterating over it and leads to a nil callback as the last entry. + timer.Simple(0, function() + cvars.RemoveChangeCallback(conVarName, myIdentifierString) + end) + + return + end + + self:SetValue(tonumber(newValue), true) + end + + cvars.AddChangeCallback(cvar, callback, myIdentifierString) +end - self.serverConVar = cvar +--- +-- @param table databaseInfo containing {name, itemName, key} +-- @realm client +function PANEL:SetDatabase(databaseInfo) + if not istable(databaseInfo) then + return + end + + local name = databaseInfo.name + local itemName = databaseInfo.itemName + local key = databaseInfo.key + + if not name or not itemName or not key then + return + end + + self.databaseInfo = databaseInfo - cvars.ServerConVarGetValue(cvar, function (wasSuccess, value, default) - if wasSuccess then - self:SetValue(tonumber(value), true) - self:SetDefaultValue(tonumber(default)) - end - end) + database.GetValue(name, itemName, key, function(databaseExists, value) + if databaseExists then + self:SetValue(value, true) + end + end) - local function OnServerConVarChangeCallback(conVarName, oldValue, newValue) - if not IsValid(self) then - cvars.RemoveChangeCallback(conVarName, "TTT2F1MenuServerConVarChangeCallback") + self:SetDefaultValue(database.GetDefaultValue(name, itemName, key)) - return - end + callbackEnabledVarTracker = callbackEnabledVarTracker + 1 + local myIdentifierString = "TTT2SliderDatabaseChangeCallback" + .. tostring(callbackEnabledVarTracker) - self:SetValue(tonumber(newValue), true) - end + local function OnDatabaseChangeCallback(_name, _itemName, _key, oldValue, newValue) + if not IsValid(self) then + database.RemoveChangeCallback(name, itemName, key, myIdentifierString) - cvars.AddChangeCallback(cvar, OnServerConVarChangeCallback, "TTT2F1MenuServerConVarChangeCallback") + return + end + + self:SetValue(newValue, true) + end + + database.AddChangeCallback(name, itemName, key, OnDatabaseChangeCallback, myIdentifierString) end --- -- @param Panel reset -- @realm client function PANEL:SetResetButton(reset) - if not ispanel(reset) then return end + if not ispanel(reset) then + return + end - self.resetButton = reset + self.resetButton = reset - reset.DoClick = function(slf) - self:ResetToDefaultValue() - end + reset.DoClick = function(slf) + self:ResetToDefaultValue() + end - reset.noDefault = self.default == nil + reset.noDefault = self.default == nil end --- -- @return Panel reset -- @realm client function PANEL:GetResetButton() - return self.resetButton + return self.resetButton end --- -- @param any val -- @realm client function PANEL:ValueChanged(val) - val = math.Clamp(tonumber(val) or 0, self:GetMin(), self:GetMax()) + val = math.Clamp(tonumber(val) or 0, self:GetMin(), self:GetMax()) - if self.TextArea ~= vgui.GetKeyboardFocus() then - self.TextArea:SetValue(self:GetTextValue()) - end + if self.TextArea ~= vgui.GetKeyboardFocus() then + self.TextArea:SetValue(self:GetTextValue()) + end - -- update knob position - self.Slider:SetSlideX(self:GetFraction()) - self.Slider:InvalidateLayout() + -- update knob position + self.Slider:SetSlideX(self:GetFraction()) + self.Slider:InvalidateLayout() - self:OnValueChanged(val) + self:OnValueChanged(val) end --- -- overwrites the base function with an empty function -- @param any val -- @realm client -function PANEL:OnValueChanged(val) - -end +function PANEL:OnValueChanged(val) end --- -- @param number x @@ -340,61 +452,61 @@ end -- @return number y The given y (second param) -- @realm client function PANEL:TranslateSliderValues(x, y) - self:SetValue(self:GetMin() + (x * self:GetRange())) + self:SetValue(self:GetMin() + (x * self:GetRange())) - return self:GetFraction(), y + return self:GetFraction(), y end --- -- @return Panel -- @realm client function PANEL:GetTextArea() - return self.TextArea + return self.TextArea end --- -- @return nil|boolean -- @realm client function PANEL:UpdateNotches() - local range = self:GetRange() + local range = self:GetRange() - self.Slider:SetNotches(nil) + self.Slider:SetNotches(nil) - if range < self:GetWide() * 0.25 then - return self.Slider:SetNotches(range) - else - self.Slider:SetNotches(self:GetWide() * 0.25) - end + if range < self:GetWide() * 0.25 then + return self.Slider:SetNotches(range) + else + self.Slider:SetNotches(self:GetWide() * 0.25) + end end --- -- @return number -- @realm client function PANEL:GetFraction() - return (self:GetValue() - self:GetMin()) / self:GetRange() + return (self:GetValue() - self:GetMin()) / self:GetRange() end --- -- @return string -- @realm client function PANEL:GetTextValue() - local iDecimals = self:GetDecimals() + local iDecimals = self:GetDecimals() - if iDecimals == 0 then - return Format("%i", self:GetValue()) - end + if iDecimals == 0 then + return Format("%i", self:GetValue()) + end - return Format("%." .. iDecimals .. "f", self:GetValue()) + return Format("%." .. iDecimals .. "f", self:GetValue()) end --- -- @param boolean b -- @realm client function PANEL:SetEnabled(b) - self.TextArea:SetEnabled(b) - self.Slider:SetEnabled(b) + self.TextArea:SetEnabled(b) + self.Slider:SetEnabled(b) - FindMetaTable("Panel").SetEnabled(self, b) + FindMetaTable("Panel").SetEnabled(self, b) end -derma.DefineControl("DNumSliderTTT2", "", PANEL, "Panel") +derma.DefineControl("DNumSliderTTT2", "", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dpanel_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dpanel_ttt2.lua index b58aeb14d..a10a1c2ee 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dpanel_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dpanel_ttt2.lua @@ -47,117 +47,117 @@ Derma_Hook(PANEL, "PerformLayout", "Layout", "Panel") --- -- @ignore function PANEL:Init() - self:SetPaintBackground(true) + self:SetPaintBackground(true) - -- This turns off the engine drawing - self:SetPaintBackgroundEnabled(false) - self:SetPaintBorderEnabled(false) + -- This turns off the engine drawing + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) - self.tooltip = { - fixedPosition = nil, - fixedSize = nil, - delay = 0, - text = "", - font = "DermaTTT2Text", - sizeArrow = 8 - } + self.tooltip = { + fixedPosition = nil, + fixedSize = nil, + delay = 0, + text = "", + font = "DermaTTT2Text", + sizeArrow = 8, + } - local oldSetTooltipPanel = self.SetTooltipPanel + local oldSetTooltipPanel = self.SetTooltipPanel - self.SetTooltipPanel = function(slf, panel) - slf:SetTooltipPanelOverride("DTooltipTTT2") + self.SetTooltipPanel = function(slf, panel) + slf:SetTooltipPanelOverride("DTooltipTTT2") - oldSetTooltipPanel(slf, panel) - end + oldSetTooltipPanel(slf, panel) + end end --- --- @param boolean +-- @param boolean bDisabled -- @realm client function PANEL:SetDisabled(bDisabled) - self.m_bDisabled = bDisabled - - if bDisabled then - self:SetAlpha(75) - self:SetMouseInputEnabled(false) - else - self:SetAlpha(255) - self:SetMouseInputEnabled(true) - end + self.m_bDisabled = bDisabled + + if bDisabled then + self:SetAlpha(75) + self:SetMouseInputEnabled(false) + else + self:SetAlpha(255) + self:SetMouseInputEnabled(true) + end end --- --- @param boolean +-- @param boolean bEnabled -- @realm client function PANEL:SetEnabled(bEnabled) - self:SetDisabled(not bEnabled) + self:SetDisabled(not bEnabled) end --- -- @return boolean -- @realm client function PANEL:IsEnabled() - return not self:GetDisabled() + return not self:GetDisabled() end --- --- @param number +-- @param number mousecode -- @realm client function PANEL:OnMousePressed(mousecode) - if self:IsSelectionCanvas() and not dragndrop.IsDragging() then - self:StartBoxSelection() + if self:IsSelectionCanvas() and not dragndrop.IsDragging() then + self:StartBoxSelection() - return - end + return + end - if self:IsDraggable() then - self:MouseCapture(true) - self:DragMousePress(mousecode) - end + if self:IsDraggable() then + self:MouseCapture(true) + self:DragMousePress(mousecode) + end end --- --- @param number +-- @param number mousecode -- @realm client function PANEL:OnMouseReleased(mousecode) - if self:EndBoxSelection() then return end + if self:EndBoxSelection() then + return + end - self:MouseCapture(false) + self:MouseCapture(false) - if self:DragMouseRelease(mousecode) then - return - end + if self:DragMouseRelease(mousecode) then + return + end end --- -- @realm client -function PANEL:UpdateColours() - -end +function PANEL:UpdateColours() end --- -- @param number x -- @param number y -- @realm client function PANEL:SetTooltipFixedPosition(x, y) - self.tooltip.fixedPosition = { - x = x, - y = y - } + self.tooltip.fixedPosition = { + x = x, + y = y, + } end --- -- @return number, number -- @realm client function PANEL:GetTooltipFixedPosition() - return self.tooltip.fixedPosition.x, self.tooltip.fixedPosition.y + return self.tooltip.fixedPosition.x, self.tooltip.fixedPosition.y end --- -- @return boolean -- @realm client function PANEL:HasTooltipFixedPosition() - return self.tooltip.fixedPosition ~= nil + return self.tooltip.fixedPosition ~= nil end --- @@ -165,75 +165,97 @@ end -- @param number h -- @realm client function PANEL:SetTooltipFixedSize(w, h) - -- +2 are the outline pixels - self.tooltip.fixedSize = { - w = w + 2, - h = h + self.tooltip.sizeArrow + 2 - } + -- +2 are the outline pixels + self.tooltip.fixedSize = { + w = w + 2, + h = h + self.tooltip.sizeArrow + 2, + } end --- -- @return number, number -- @realm client function PANEL:GetTooltipFixedSize() - return self.tooltip.fixedSize.w, self.tooltip.fixedSize.h + return self.tooltip.fixedSize.w, self.tooltip.fixedSize.h end --- -- @realm client function PANEL:HasTooltipFixedSize() - return self.tooltip.fixedSize ~= nil + return self.tooltip.fixedSize ~= nil end --- --- @param number +-- @param number delay -- @realm client function PANEL:SetTooltipOpeningDelay(delay) - self.tooltip.delay = delay + self.tooltip.delay = delay end --- -- @return number -- @realm client function PANEL:GetTooltipOpeningDelay() - return self.tooltip.delay + return self.tooltip.delay end --- -- @param string text -- @realm client function PANEL:SetTooltip(text) - self:SetTooltipPanelOverride("DTooltipTTT2") + self:SetTooltipPanelOverride("DTooltipTTT2") - self.tooltip.text = text + self.tooltip.text = text end --- -- @return string -- @realm client function PANEL:GetTooltipText() - return self.tooltip.text + return self.tooltip.text end --- -- @return boolean -- @realm client function PANEL:HasTooltipText() - return self.tooltip.text ~= nil and self.tooltip.text ~= "" + return self.tooltip.text ~= nil and self.tooltip.text ~= "" end --- -- @param string font -- @realm client function PANEL:SetTooltipFont(font) - self.tooltip.font = font + self.tooltip.font = font end --- -- @return string -- @realm client function PANEL:GetTooltipFont() - return self.tooltip.font + return self.tooltip.font +end + +--- +-- @param Panel master +-- @realm client +function PANEL:SetMaster(master) + if not IsValid(master) then + return + end + + self.master = master +end + +--- +-- @return number +-- @realm client +function PANEL:GetIndentationMargin() + if not IsValid(self.master) then + return 0 + end + + return 10 + self.master:GetIndentationMargin() end derma.DefineControl("DPanelTTT2", "", PANEL, "Panel") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dprofilepanel_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dprofilepanel_ttt2.lua new file mode 100644 index 000000000..7ff13f131 --- /dev/null +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dprofilepanel_ttt2.lua @@ -0,0 +1,318 @@ +--- +-- @class PANEL +-- @section DProfilePanelTTT2 + +local PANEL = {} + +--- +-- @accessor number +-- @realm client +AccessorFunc(PANEL, "m_fAnimSpeed", "AnimSpeed") + +--- +-- @accessor Entity +-- @realm client +AccessorFunc(PANEL, "Entity", "Entity") + +--- +-- @accessor Vector +-- @realm client +AccessorFunc(PANEL, "vCamPos", "CamPos") + +--- +-- @accessor number +-- @realm client +AccessorFunc(PANEL, "fFOV", "FOV") + +--- +-- @accessor Vector +-- @realm client +AccessorFunc(PANEL, "vLookatPos", "LookAt") + +--- +-- @accessor Angle +-- @realm client +AccessorFunc(PANEL, "aLookAngle", "LookAng") + +--- +-- @accessor Color +-- @realm client +AccessorFunc(PANEL, "colAmbientLight", "AmbientLight") + +--- +-- @accessor Color +-- @realm client +AccessorFunc(PANEL, "colColor", "Color") + +--- +-- @accessor boolean +-- @realm client +AccessorFunc(PANEL, "bAnimated", "Animated") + +--- +-- @accessor boolean +-- @realm client +AccessorFunc(PANEL, "bRotating", "Rotating") + +--- +-- @ignore +function PANEL:Init() + self.lastPaint = 0 + self.directionalLight = {} + self.farZ = 4096 + + self:SetContentAlignment(5) + + self:SetTall(22) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) + + self:SetFont("DermaTTT2TextLarge") + + self:SetCamPos(Vector(42, -1, 70)) + self:SetLookAt(Vector(0, -1, 58)) + self:SetFOV(35) + + self:SetAnimSpeed(0.5) + self:SetAnimated(true) + self:SetRotating(false) + + self:SetAmbientLight(Color(50, 50, 50)) + + self:SetDirectionalLight(BOX_TOP, Color(255, 255, 255)) + self:SetDirectionalLight(BOX_FRONT, Color(255, 255, 255)) + + self:SetColor(COLOR_WHITE) + + self:SetText("") + + self.data = {} +end + +--- +-- @realm client +function PANEL:OnRemove() + -- old ent is removed because clientside models are not garbage collected + if IsValid(self.data.ent) then + self.data.ent:Remove() + end +end + +--- +-- @param number iDirections +-- @param Color color +-- @realm client +function PANEL:SetDirectionalLight(iDirection, color) + self.directionalLight[iDirection] = color +end + +--- +-- @param string model +-- @realm client +function PANEL:SetModel(model) + self.data.mdl = Model(model) + + -- set the entity + local ent = ClientsideModel(model, RENDERGROUP_OTHER) + + if not IsValid(ent) then + return + end + + ent:SetNoDraw(true) + ent:SetIK(false) + + -- now try to find a nice sequence to play + local iSeq = ent:LookupSequence("walk_all") + + if iSeq <= 0 then + iSeq = ent:LookupSequence("WalkUnarmed_all") + end + + if iSeq <= 0 then + iSeq = ent:LookupSequence("walk_all_moderate") + end + + if iSeq > 0 then + ent:ResetSequence(iSeq) + end + + -- before storing the ent, make sure that a possible old ent + -- is removed because clientside models are not garbage collected + if IsValid(self.data.ent) then + self.data.ent:Remove() + self.data.ent = nil + end + + self.data.ent = ent +end + +--- +-- @param number x +-- @param number y +-- @param number w +-- @param number h +-- @realm client +function PANEL:DrawModel(x, y, w, h) + local ent = self.data.ent + + if not IsValid(ent) then + return + end + + local xBaseStart, yBaseStart = self:LocalToScreen(x, y) + + self:LayoutEntity(ent) + + local ang = self.aLookAngle or (self.vLookatPos - self.vCamPos):Angle() + + cam.Start3D(self.vCamPos, ang, self.fFOV, xBaseStart, yBaseStart, w, h, 5, self.farZ) + render.SuppressEngineLighting(true) + render.SetLightingOrigin(ent:GetPos()) + render.ResetModelLighting( + self.colAmbientLight.r / 255, + self.colAmbientLight.g / 255, + self.colAmbientLight.b / 255 + ) + render.SetColorModulation(self.colColor.r / 255, self.colColor.g / 255, self.colColor.b / 255) + render.SetBlend((self:GetAlpha() / 255) * (self.colColor.a / 255)) + + -- iterates over the model lighting enum: https://wiki.facepunch.com/gmod/Enums/BOX + for i = 0, 6 do + local col = self.directionalLight[i] + + if col then + render.SetModelLighting(i, col.r / 255, col.g / 255, col.b / 255) + end + end + + -- make a mask to make sure the graphic is limited + render.SetScissorRect(xBaseStart, yBaseStart, xBaseStart + w, yBaseStart + h, true) + + ent:DrawModel() + + render.SetScissorRect(0, 0, 0, 0, false) + render.SuppressEngineLighting(false) + cam.End3D() + + self.lastPaint = RealTime() +end + +--- +-- This function is to be overriden +-- @param Entity ent +-- @realm client +function PANEL:LayoutEntity(ent) + if self.bAnimated then + self:RunAnimation() + end + + if self.bRotatin then + ent:SetAngles(Angle(0, RealTime() * 10 % 360, 0)) + end +end + +--- +-- @realm client +function PANEL:RunAnimation() + self.data.ent:FrameAdvance((RealTime() - self.lastPaint) * self.m_fAnimSpeed) +end + +--- +-- @return string|nil +-- @realm client +function PANEL:GetModel() + return self.data.mdl +end + +--- +-- @return boolean +-- @realm client +function PANEL:HasModel() + return self.data.mdl ~= nil +end + +--- +-- @param string material +-- @realm client +function PANEL:SetPlayerIcon(material) + self.data.player_icon = material +end + +--- +-- @param string material +-- @realm client +function PANEL:SetPlayerRoleIcon(material) + self.data.player_role_icon = material +end + +--- +-- @param Color color +-- @realm client +function PANEL:SetPlayerRoleColor(color) + self.data.player_role_color = color +end + +--- +-- @param string role +-- @realm client +function PANEL:SetPlayerRoleString(role) + self.data.player_role_name = role +end + +--- +-- @param string team +-- @realm client +function PANEL:SetPlayerTeamString(team) + self.data.player_team_name = team +end + +--- +-- @return Material|nil +-- @realm client +function PANEL:GetPlayerIcon() + return self.data.player_icon +end + +--- +-- @return Material|nil +-- @realm client +function PANEL:GetPlayerRoleIcon() + return self.data.player_role_icon +end + +--- +-- @return Color|nil +-- @realm client +function PANEL:GetPlayerRoleColor() + return self.data.player_role_color +end + +--- +-- @return string|nil +-- @realm client +function PANEL:GetPlayerRoleString() + return self.data.player_role_name +end + +--- +-- @return string|nil +-- @realm client +function PANEL:GetPlayerTeamString() + return self.data.player_team_name +end + +--- +-- @ignore +function PANEL:Paint(w, h) + derma.SkinHook("Paint", "ProfilePanelTTT2", self, w, h) + + return false +end + +derma.DefineControl( + "DProfilePanelTTT2", + "A special box with a 3D model or model", + PANEL, + "DLabelTTT2" +) diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/droleimage_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/droleimage_ttt2.lua index 0ea860a45..e980d79de 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/droleimage_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/droleimage_ttt2.lua @@ -6,46 +6,46 @@ local PANEL = {} --- @ignore function PANEL:Init() - self.data = { - color = COLOR_WHITE, - icon = nil - } + self.data = { + color = COLOR_WHITE, + icon = nil, + } end --- -- @param Color color -- @realm client function PANEL:SetColor(color) - self.data.color = color + self.data.color = color end --- -- @return Color -- @realm client function PANEL:GetColor() - return self.data.color + return self.data.color end --- -- @param Material material -- @realm client function PANEL:SetMaterial(material) - self.data.material = material + self.data.material = material end --- -- @return Material -- @realm client function PANEL:GetMaterial() - return self.data.material + return self.data.material end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "RoleImageTTT2", self, w, h) + derma.SkinHook("Paint", "RoleImageTTT2", self, w, h) - return true + return true end derma.DefineControl("DRoleImageTTT2", "A simple role image", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/drolelayeringreceiver_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/drolelayeringreceiver_ttt2.lua index 32e4cfdc2..312c44af4 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/drolelayeringreceiver_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/drolelayeringreceiver_ttt2.lua @@ -7,19 +7,19 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - DDragBaseTTT2.Init(self) + DDragBaseTTT2.Init(self) - self.layerList = {} - self.layerBoxes = {} + self.layerList = {} + self.layerBoxes = {} - self.m_iPadding = 5 + self.m_iPadding = 5 end --- -- @param[default=5] number padding -- @realm client function PANEL:SetPadding(padding) - self.m_iPadding = padding + self.m_iPadding = padding end --- @@ -29,80 +29,80 @@ end -- @param PANEL closestPnl -- @realm client function PANEL:OnDropped(droppedPnl, pos, closestPnl) - local dropLayer, dropDepth = self:GetLayerAndDepthOfSubrole(droppedPnl.subrole) - - if dropLayer then - -- remove dropped panel from old position - table.remove(self.layerList[dropLayer], dropDepth) - - -- clear old layer if empty - if #self.layerList[dropLayer] < 1 then - table.remove(self.layerList, dropLayer) - end - end - - if pos == 6 or pos == 4 then -- right or left - local newLayer, newDepth = self:GetLayerAndDepthOfSubrole(closestPnl.subrole) - - -- insert dropped panel into the existing layer - table.insert( - self.layerList[newLayer], - pos == 4 and newDepth or newDepth + 1, - droppedPnl.subrole - ) - elseif pos == 8 or pos == 2 then -- top or bottom - local newLayer = self:GetLayerAndDepthOfSubrole(closestPnl.subrole) - - -- insert dropped panel into a new layer - table.insert( - self.layerList, - pos == 8 and newLayer or newLayer + 1, - {droppedPnl.subrole} - ) - end - - if not IsValid(self.senderPnl) then return end - - -- remove from sender's cached list - self.senderPnl.cachedTable[droppedPnl.subrole] = nil + local dropLayer, dropDepth = self:GetLayerAndDepthOfSubrole(droppedPnl.subrole) + + if dropLayer then + -- remove dropped panel from old position + table.remove(self.layerList[dropLayer], dropDepth) + + -- clear old layer if empty + if #self.layerList[dropLayer] < 1 then + table.remove(self.layerList, dropLayer) + end + end + + if pos == 6 or pos == 4 then -- right or left + local newLayer, newDepth = self:GetLayerAndDepthOfSubrole(closestPnl.subrole) + + -- insert dropped panel into the existing layer + table.insert( + self.layerList[newLayer], + pos == 4 and newDepth or newDepth + 1, + droppedPnl.subrole + ) + elseif pos == 8 or pos == 2 then -- top or bottom + local newLayer = self:GetLayerAndDepthOfSubrole(closestPnl.subrole) + + -- insert dropped panel into a new layer + table.insert(self.layerList, pos == 8 and newLayer or newLayer + 1, { droppedPnl.subrole }) + end + + if not IsValid(self.senderPnl) then + return + end + + -- remove from sender's cached list + self.senderPnl.cachedTable[droppedPnl.subrole] = nil end --- -- @realm client function PANEL:OnModified() - -- needed if the first element is dropped from sender's cached list - local children = self:GetDnDs() - local layerCount = #self:GetLayers() + -- needed if the first element is dropped from sender's cached list + local children = self:GetDnDs() + local layerCount = #self:GetLayers() - if #children == 1 and layerCount == 0 then - local droppedPnl = children[1] + if #children == 1 and layerCount == 0 then + local droppedPnl = children[1] - -- insert dropped panel into a new layer - self.layerList[1] = {droppedPnl.subrole} + -- insert dropped panel into a new layer + self.layerList[1] = { droppedPnl.subrole } - if not IsValid(self.senderPnl) then return end + if not IsValid(self.senderPnl) then + return + end - -- remove from sender's cached list - self.senderPnl.cachedTable[droppedPnl.subrole] = nil + -- remove from sender's cached list + self.senderPnl.cachedTable[droppedPnl.subrole] = nil - layerCount = 1 - end + layerCount = 1 + end - self:OnLayerUpdated() + self:OnLayerUpdated() end --- -- @param table tbl -- @realm client function PANEL:SetLayers(tbl) - self.layerList = tbl + self.layerList = tbl end --- -- @return table -- @realm client function PANEL:GetLayers() - return self.layerList + return self.layerList end --- @@ -111,15 +111,17 @@ end -- @return number -- @realm client function PANEL:GetLayerAndDepthOfSubrole(subrole) - for layer = 1, #self.layerList do - local currentLayerTable = self.layerList[layer] + for layer = 1, #self.layerList do + local currentLayerTable = self.layerList[layer] - for depth = 1, #currentLayerTable do - if currentLayerTable[depth] ~= subrole then continue end + for depth = 1, #currentLayerTable do + if currentLayerTable[depth] ~= subrole then + continue + end - return layer, depth - end - end + return layer, depth + end + end end --- @@ -127,130 +129,134 @@ end -- @return table A table of valid children (@{PANEL}) -- @realm client function PANEL:GetDnDs() - local children = self:GetChildren() - local validChildren = {} + local children = self:GetChildren() + local validChildren = {} - for i = 1, #children do - local child = children[i] + for i = 1, #children do + local child = children[i] - -- not a valid child with a subrole, skip - if not child.subrole then continue end + -- not a valid child with a subrole, skip + if not child.subrole then + continue + end - validChildren[#validChildren + 1] = child - end + validChildren[#validChildren + 1] = child + end - return validChildren + return validChildren end --- -- @ignore function PANEL:PerformLayout(width, height) - local w = self:GetWide() - local childW, childH = self:GetChildSize() + local w = self:GetWide() + local childW, childH = self:GetChildSize() - local children = self:GetDnDs() + local children = self:GetDnDs() - local xStart = self:GetLeftMargin() + self.m_iPadding - local x = xStart - local y = self.m_iPadding + local xStart = self:GetLeftMargin() + self.m_iPadding + local x = xStart + local y = self.m_iPadding - local sortedChildren = {} + local sortedChildren = {} - self.layerBoxes = {} + self.layerBoxes = {} - -- pre sort children so the rendering part is easier - for i = 1, #children do - local child = children[i] - local layer, depth = self:GetLayerAndDepthOfSubrole(child.subrole) + -- pre sort children so the rendering part is easier + for i = 1, #children do + local child = children[i] + local layer, depth = self:GetLayerAndDepthOfSubrole(child.subrole) - if not layer or not depth then continue end + if not layer or not depth then + continue + end - sortedChildren[layer] = sortedChildren[layer] or {} - sortedChildren[layer][depth] = child - end + sortedChildren[layer] = sortedChildren[layer] or {} + sortedChildren[layer][depth] = child + end - for i = 1, #sortedChildren do - local layerChildren = sortedChildren[i] + for i = 1, #sortedChildren do + local layerChildren = sortedChildren[i] - local yRow = y + local yRow = y - y = y + self.m_iPadding + y = y + self.m_iPadding - for k = 1, #layerChildren do - local child = layerChildren[k] + for k = 1, #layerChildren do + local child = layerChildren[k] - child:SetPos(x, y) + child:SetPos(x, y) - -- skip to next row if row is full - local xNext = x + child:GetWide() + self.m_iPadding + -- skip to next row if row is full + local xNext = x + child:GetWide() + self.m_iPadding - if xNext + childW > w and k < #layerChildren then - y = y + childH + self.m_iPadding - x = xStart - else - x = xNext - end - end + if xNext + childW > w and k < #layerChildren then + y = y + childH + self.m_iPadding + x = xStart + else + x = xNext + end + end - x = xStart - y = y + (childH + self.m_iPadding) + x = xStart + y = y + (childH + self.m_iPadding) - self.layerBoxes[i] = { - y = yRow, - h = y - yRow, - label = yRow + 0.5 * (y - yRow) - } + self.layerBoxes[i] = { + y = yRow, + h = y - yRow, + label = yRow + 0.5 * (y - yRow), + } - y = y + self.m_iPadding - end + y = y + self.m_iPadding + end - self:SetTall(y) + self:SetTall(y) end --- -- @param table layeredRoles -- @realm client function PANEL:InitRoles(layeredRoles) - self:SetLayers(layeredRoles) + self:SetLayers(layeredRoles) - local layerCount = #layeredRoles + local layerCount = #layeredRoles - for layer = 1, layerCount do - local currentLayerTable = layeredRoles[layer] + for layer = 1, layerCount do + local currentLayerTable = layeredRoles[layer] - for i = 1, #currentLayerTable do - local subrole = currentLayerTable[i] - local roleData = roles.GetByIndex(subrole) + for i = 1, #currentLayerTable do + local subrole = currentLayerTable[i] + local roleData = roles.GetByIndex(subrole) - -- create the role icon - local ic = vgui.Create("DRoleImageTTT2", self) - ic:SetSize(64, 64) - ic:SetMaterial(roleData.iconMaterial) - ic:SetColor(roleData.color) - ic:SetTooltip(roleData.name) - ic:SetTooltipFixedPosition(0, 64) + -- create the role icon + local ic = vgui.Create("DRoleImageTTT2", self) + ic:SetSize(64, 64) + ic:SetMaterial(roleData.iconMaterial) + ic:SetColor(roleData.color) + ic:SetTooltip(roleData.name) + ic:SetTooltipFixedPosition(0, 64) - ic.subrole = subrole + ic.subrole = subrole - ic:Droppable(self.dropGroupName) + ic:Droppable(self.dropGroupName) - self:Add(ic) - end - end + self:Add(ic) + end + end end --- -- @param PANEL senderPnl -- @realm client function PANEL:SetSender(senderPnl) - self.senderPnl = senderPnl + self.senderPnl = senderPnl end --- -- @return PANEL -- @realm client function PANEL:GetSender() - return self.senderPnl + return self.senderPnl end --- @@ -258,15 +264,15 @@ end -- @return boolean -- @realm client function PANEL:OnDropChildCheck(closestChild) - return closestChild.subrole ~= nil + return closestChild.subrole ~= nil end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "RoleLayeringReceiverTTT2", self, w, h) + derma.SkinHook("Paint", "RoleLayeringReceiverTTT2", self, w, h) - return true + return true end derma.DefineControl("DRoleLayeringReceiverTTT2", "", PANEL, "DDragBaseTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/drolelayeringsender_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/drolelayeringsender_ttt2.lua index b4ec242b8..de76a9875 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/drolelayeringsender_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/drolelayeringsender_ttt2.lua @@ -7,39 +7,39 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - DIconLayout.Init(self) + DIconLayout.Init(self) - self.cachedTable = {} - self.m_iPadding = 0 - self.m_iLeftMargin = 0 + self.cachedTable = {} + self.m_iPadding = 0 + self.m_iLeftMargin = 0 end --- -- @return number -- @realm client function PANEL:GetPadding() - return self.m_iPadding + return self.m_iPadding end --- -- @param[default=0] number padding -- @realm client function PANEL:SetPadding(padding) - self.m_iPadding = padding + self.m_iPadding = padding end --- -- @return number -- @realm client function PANEL:GetLeftMargin() - return self.m_iLeftMargin + return self.m_iLeftMargin end --- -- @param[default=0] number leftMargin -- @realm client function PANEL:SetLeftMargin(leftMargin) - self.m_iLeftMargin = leftMargin + self.m_iLeftMargin = leftMargin end --- @@ -47,114 +47,126 @@ end -- @return table A table of valid children (@{PANEL}) -- @realm client function PANEL:GetDnDs() - local children = self:GetChildren() - local validChildren = {} + local children = self:GetChildren() + local validChildren = {} - for i = 1, #children do - local child = children[i] + for i = 1, #children do + local child = children[i] - -- not a valid child with a subrole, skip - if not child.subrole then continue end + -- not a valid child with a subrole, skip + if not child.subrole then + continue + end - validChildren[#validChildren + 1] = child - end + validChildren[#validChildren + 1] = child + end - return validChildren + return validChildren end --- -- @param PANEL receiverPnl -- @realm client function PANEL:SetReceiver(receiverPnl) - self.receiverPnl = receiverPnl + self.receiverPnl = receiverPnl end --- -- @realm client function PANEL:OnModified() - if not IsValid(self.receiverPnl) then return end + if not IsValid(self.receiverPnl) then + return + end - local children = self:GetDnDs() + local children = self:GetDnDs() - for i = 1, #children do - local child = children[i] + for i = 1, #children do + local child = children[i] - -- already cached, skipping - if self.cachedTable[child.subrole] then continue end + -- already cached, skipping + if self.cachedTable[child.subrole] then + continue + end - -- remove from layer - local dropLayer, dropDepth = self.receiverPnl:GetLayerAndDepthOfSubrole(child.subrole) + -- remove from layer + local dropLayer, dropDepth = self.receiverPnl:GetLayerAndDepthOfSubrole(child.subrole) - -- not contained in layer - if dropLayer == nil then continue end + -- not contained in layer + if dropLayer == nil then + continue + end - local layerList = self.receiverPnl:GetLayers() + local layerList = self.receiverPnl:GetLayers() - -- remove dropped panel from old position - table.remove(layerList[dropLayer], dropDepth) + -- remove dropped panel from old position + table.remove(layerList[dropLayer], dropDepth) - -- clear old layer if empty - if #layerList[dropLayer] < 1 then - table.remove(layerList, dropLayer) - end - end + -- clear old layer if empty + if #layerList[dropLayer] < 1 then + table.remove(layerList, dropLayer) + end + end - -- update receiver - self.receiverPnl:OnModified() - self.receiverPnl:InvalidateLayout() + -- update receiver + self.receiverPnl:OnModified() + self.receiverPnl:InvalidateLayout() - -- update cache - self.cachedTable = {} + -- update cache + self.cachedTable = {} - for i = 1, #children do - local child = children[i] + for i = 1, #children do + local child = children[i] - -- not a valid child with a subrole, skip - if not child.subrole then continue end + -- not a valid child with a subrole, skip + if not child.subrole then + continue + end - if not self.cachedTable[child.subrole] then - self.cachedTable[child.subrole] = true - end - end + if not self.cachedTable[child.subrole] then + self.cachedTable[child.subrole] = true + end + end - self:InvalidateLayout() + self:InvalidateLayout() end --- -- @ignore function PANEL:PerformLayout(width, height) - local w = self:GetWide() - local childW, childH = self:GetChildSize() + local w = self:GetWide() + local childW, childH = self:GetChildSize() - local children = self:GetDnDs() + local children = self:GetDnDs() - local xStart = self:GetLeftMargin() + self.m_iPadding - local x = xStart - local row = 0 + local xStart = self:GetLeftMargin() + self.m_iPadding + local x = xStart + local row = 0 - for i = 1, #children do - local child = children[i] + for i = 1, #children do + local child = children[i] - if not IsValid(child) or not child:IsVisible() then continue end + if not IsValid(child) or not child:IsVisible() then + continue + end - child:SetPos(x, self.m_iPadding + row * (childH + self.m_iPadding)) + child:SetPos(x, self.m_iPadding + row * (childH + self.m_iPadding)) - if isfunction(child.ApplySchemeSettings) then - child:ApplySchemeSettings() - end + if isfunction(child.ApplySchemeSettings) then + child:ApplySchemeSettings() + end - -- skip to next row if row is full - local xNext = x + child:GetWide() + self.m_iPadding + -- skip to next row if row is full + local xNext = x + child:GetWide() + self.m_iPadding - if xNext + childW > w and i < #children then - row = row + 1 - x = xStart - else - x = xNext - end - end + if xNext + childW > w and i < #children then + row = row + 1 + x = xStart + else + x = xNext + end + end - self:SetTall((row + 1) * childH + (row + 2) * self.m_iPadding) + self:SetTall((row + 1) * childH + (row + 2) * self.m_iPadding) end --- @@ -164,19 +176,19 @@ end -- @return boolean -- @realm client function PANEL:OnDropChildCheck(closestChild, direction) - if #self:GetDnDs() == 0 and direction == 6 then - return true - else - return closestChild.subrole ~= nil - end + if #self:GetDnDs() == 0 and direction == 6 then + return true + else + return closestChild.subrole ~= nil + end end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "RoleLayeringSenderTTT2", self, w, h) + derma.SkinHook("Paint", "RoleLayeringSenderTTT2", self, w, h) - return true + return true end derma.DefineControl("DRoleLayeringSenderTTT2", "", PANEL, "DDragBaseTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dscrollpanel_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dscrollpanel_ttt2.lua index 7c3e1390e..59aa7a697 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dscrollpanel_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dscrollpanel_ttt2.lua @@ -17,74 +17,74 @@ AccessorFunc(PANEL, "pnlCanvas", "Canvas") --- -- @ignore function PANEL:Init() - self.pnlCanvas = vgui.Create("Panel", self) + self.pnlCanvas = vgui.Create("Panel", self) - self.pnlCanvas.OnMousePressed = function(slf, code) - slf:GetParent():OnMousePressed(code) - end + self.pnlCanvas.OnMousePressed = function(slf, code) + slf:GetParent():OnMousePressed(code) + end - self.pnlCanvas:SetMouseInputEnabled(true) + self.pnlCanvas:SetMouseInputEnabled(true) - self.pnlCanvas.PerformLayout = function(pnl) - self:PerformLayoutInternal() - self:InvalidateParent() - end + self.pnlCanvas.PerformLayout = function(pnl) + self:PerformLayoutInternal() + self:InvalidateParent() + end - -- Create the scroll bar - self.vBar = vgui.Create("DVScrollBarTTT2", self) - self.vBar:Dock(RIGHT) + -- Create the scroll bar + self.vBar = vgui.Create("DVScrollBarTTT2", self) + self.vBar:Dock(RIGHT) - self:SetPadding(0) - self:SetMouseInputEnabled(true) + self:SetPadding(0) + self:SetMouseInputEnabled(true) - -- This turns off the engine drawing - self:SetPaintBackgroundEnabled(false) - self:SetPaintBorderEnabled(false) - self:SetPaintBackground(false) + -- This turns off the engine drawing + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) + self:SetPaintBackground(false) end --- -- @param Panel pnl -- @realm client function PANEL:AddItem(pnl) - pnl:SetParent(self:GetCanvas()) + pnl:SetParent(self:GetCanvas()) end --- -- @param Panel child -- @realm client function PANEL:OnChildAdded(child) - self:AddItem(child) + self:AddItem(child) end --- -- @ignore function PANEL:SizeToContents() - self:SetSize(self.pnlCanvas:GetSize()) + self:SetSize(self.pnlCanvas:GetSize()) end --- -- @return Panel -- @realm client function PANEL:GetVBar() - return self.vBar + return self.vBar end --- -- @return number -- @realm client function PANEL:InnerWidth() - return self:GetCanvas():GetWide() + return self:GetCanvas():GetWide() end --- -- @realm client function PANEL:Rebuild() - self:GetCanvas():SizeToChildren(false, true) + self:GetCanvas():SizeToChildren(false, true) - if self.m_bNoSizing and self:GetCanvas():GetTall() < self:GetTall() then - self:GetCanvas():SetPos(0, (self:GetTall() - self:GetCanvas():GetTall()) * 0.5) - end + if self.m_bNoSizing and self:GetCanvas():GetTall() < self:GetTall() then + self:GetCanvas():SetPos(0, (self:GetTall() - self:GetCanvas():GetTall()) * 0.5) + end end --- @@ -92,69 +92,69 @@ end -- @return any -- @realm client function PANEL:OnMouseWheeled(dlta) - return self.vBar:OnMouseWheeled(dlta) + return self.vBar:OnMouseWheeled(dlta) end --- -- @param number iOffset -- @realm client function PANEL:OnVScroll(iOffset) - self.pnlCanvas:SetPos(0, iOffset) + self.pnlCanvas:SetPos(0, iOffset) end --- -- @param Panel panel -- @realm client function PANEL:ScrollToChild(panel) - self:InvalidateLayout(true) + self:InvalidateLayout(true) - local _, y = self.pnlCanvas:GetChildPosition(panel) - local _, h = panel:GetSize() + local _, y = self.pnlCanvas:GetChildPosition(panel) + local _, h = panel:GetSize() - y = y + h * 0.5 - y = y - self:GetTall() * 0.5 + y = y + h * 0.5 + y = y - self:GetTall() * 0.5 - self.vBar:AnimateTo(y, 0.5, 0, 0.5) + self.vBar:AnimateTo(y, 0.5, 0, 0.5) end --- -- @ignore function PANEL:PerformLayoutInternal() - local tall = self.pnlCanvas:GetTall() - local wide = self:GetWide() - local yPos = 0 + local tall = self.pnlCanvas:GetTall() + local wide = self:GetWide() + local yPos = 0 - self:Rebuild() + self:Rebuild() - self.vBar:SetUp(self:GetTall(), self.pnlCanvas:GetTall()) + self.vBar:SetUp(self:GetTall(), self.pnlCanvas:GetTall()) - yPos = self.vBar:GetOffset() + yPos = self.vBar:GetOffset() - if self.vBar.enabled then - wide = wide - self.vBar:GetWide() - end + if self.vBar.enabled then + wide = wide - self.vBar:GetWide() + end - self.pnlCanvas:SetPos(0, yPos) - self.pnlCanvas:SetWide(wide) + self.pnlCanvas:SetPos(0, yPos) + self.pnlCanvas:SetWide(wide) - self:Rebuild() + self:Rebuild() - if tall ~= self.pnlCanvas:GetTall() then - self.vBar:SetScroll(self.vBar:GetScroll()) - end + if tall ~= self.pnlCanvas:GetTall() then + self.vBar:SetScroll(self.vBar:GetScroll()) + end end --- -- @ignore function PANEL:PerformLayout() - self:PerformLayoutInternal() + self:PerformLayoutInternal() end --- -- @return boolean -- @realm client function PANEL:Clear() - return self.pnlCanvas:Clear() + return self.pnlCanvas:Clear() end derma.DefineControl("DScrollPanelTTT2", "", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsearchbar_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsearchbar_ttt2.lua index 0cb1ca5cb..45f5e00b2 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsearchbar_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsearchbar_ttt2.lua @@ -29,68 +29,70 @@ AccessorFunc(PANEL, "curPlaceholderText", "CurrentPlaceholderText") --- -- @ignore function PANEL:Init() - local textEntry = vgui.Create("DTextEntry", self) - local textColor = util.GetActiveColor(util.GetChangedColor(util.GetDefaultColor(vskin.GetBackgroundColor()), 25)) - - textEntry:SetFont(font) - textEntry:SetTextColor(textColor) - textEntry:SetCursorColor(textColor) - - textEntry.OnValueChange = function(slf,value) - self:OnValueChange(value) - end - - textEntry.OnGetFocus = function(slf) - self:SetIsOnFocus(true) - self:OnGetFocus() - end - - textEntry.OnLoseFocus = function(slf) - self:SetIsOnFocus(false) - self:OnLoseFocus() - - if slf:GetValue() == "" then - self:SetCurrentPlaceholderText(self:GetPlaceholderText()) - else - self:SetCurrentPlaceholderText("") - end - end - - -- This turns off the engine drawing for the text entry - textEntry:SetPaintBackgroundEnabled(false) - textEntry:SetPaintBorderEnabled(false) - textEntry:SetPaintBackground(false) - - self.textEntry = textEntry - - -- This turns off the engine drawing of the panel itself - self:SetPaintBackgroundEnabled(false) - self:SetPaintBorderEnabled(false) - self:SetPaintBackground(false) - - -- Sets default values - self:SetHeightMult(1) - self:SetIsOnFocus(false) - self:SetPlaceholderText("searchbar_default_placeholder") - self:SetCurrentPlaceholderText("searchbar_default_placeholder") - - self:PerformLayout() + local textEntry = vgui.Create("DTextEntry", self) + local textColor = util.GetActiveColor( + util.GetChangedColor(util.GetDefaultColor(vskin.GetBackgroundColor()), 25) + ) + + textEntry:SetFont(font) + textEntry:SetTextColor(textColor) + textEntry:SetCursorColor(textColor) + + textEntry.OnValueChange = function(slf, value) + self:OnValueChange(value) + end + + textEntry.OnGetFocus = function(slf) + self:SetIsOnFocus(true) + self:OnGetFocus() + end + + textEntry.OnLoseFocus = function(slf) + self:SetIsOnFocus(false) + self:OnLoseFocus() + + if slf:GetValue() == "" then + self:SetCurrentPlaceholderText(self:GetPlaceholderText()) + else + self:SetCurrentPlaceholderText("") + end + end + + -- This turns off the engine drawing for the text entry + textEntry:SetPaintBackgroundEnabled(false) + textEntry:SetPaintBorderEnabled(false) + textEntry:SetPaintBackground(false) + + self.textEntry = textEntry + + -- This turns off the engine drawing of the panel itself + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) + self:SetPaintBackground(false) + + -- Sets default values + self:SetHeightMult(1) + self:SetIsOnFocus(false) + self:SetPlaceholderText("searchbar_default_placeholder") + self:SetCurrentPlaceholderText("searchbar_default_placeholder") + + self:PerformLayout() end --- -- @param string newFont -- @realm client function PANEL:SetFont(newFont) - self.textEntry:SetFont(newFont) + self.textEntry:SetFont(newFont) - font = newFont + font = newFont end --- -- @return string -- @realm client function PANEL:GetFont() - return font + return font end --- @@ -98,70 +100,68 @@ end -- @return string -- @realm client function PANEL:GetValue() - return textEntry:GetValue() + return self.textEntry:GetValue() end --- -- This function determines if @{PANEL:OnValueChange()} is called on every typed letter or not. --- @param bool enabled +-- @param boolean enabled -- @realm client function PANEL:SetUpdateOnType(enabled) - self.textEntry:SetUpdateOnType(enabled) + self.textEntry:SetUpdateOnType(enabled) end --- -- This function is called when the searchbar is focussed. -- @note This function can be overwritten but not called. -- @realm client -function PANEL:OnGetFocus() - -end +function PANEL:OnGetFocus() end --- -- This function is called when the searchbar is not focussed anymore. -- @note This function can be overwritten but not called. -- @realm client -function PANEL:OnLoseFocus() - -end +function PANEL:OnLoseFocus() end --- -- This function is called by the searchbar when a text is entered/changed. -- @note This function should be overwritten but not called. -- @param string value -- @realm client -function PANEL:OnValueChange(value) - -end +function PANEL:OnValueChange(value) end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "Searchbar", self, w, h) + derma.SkinHook("Paint", "Searchbar", self, w, h) - return true + return true end --- -- @ignore function PANEL:PerformLayout() - local width, height = self:GetSize() - local heightMult = self:GetHeightMult() + local width, height = self:GetSize() + local heightMult = self:GetHeightMult() - self.textEntry:SetSize(width, height * heightMult) - self.textEntry:SetPos(0, height * (1 - heightMult) * 0.5) + self.textEntry:SetSize(width, height * heightMult) + self.textEntry:SetPos(0, height * (1 - heightMult) * 0.5) - -- React to skin changes in menu - self.textEntry:SetTextColor(util.GetActiveColor(util.GetChangedColor(util.GetDefaultColor(vskin.GetBackgroundColor()), 25))) + -- React to skin changes in menu + self.textEntry:SetTextColor( + util.GetActiveColor( + util.GetChangedColor(util.GetDefaultColor(vskin.GetBackgroundColor()), 25) + ) + ) - self.textEntry:InvalidateLayout(true) + self.textEntry:InvalidateLayout(true) end --- -- @return boolean -- @realm client function PANEL:Clear() - return self.textEntry:Clear() + return self.textEntry:Clear() end derma.DefineControl("DSearchBarTTT2", "", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsubmenubutton_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsubmenubutton_ttt2.lua index 51efac5d6..905305918 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsubmenubutton_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsubmenubutton_ttt2.lua @@ -12,60 +12,90 @@ AccessorFunc(PANEL, "m_bBorder", "DrawBorder", FORCE_BOOL) --- -- @ignore function PANEL:Init() - self:SetContentAlignment(5) + self:SetContentAlignment(5) - self:SetDrawBorder(true) - self:SetPaintBackground(true) + self:SetDrawBorder(true) + self:SetPaintBackground(true) - self:SetTall(22) - self:SetMouseInputEnabled(true) - self:SetKeyboardInputEnabled(true) + self:SetTall(22) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) - self:SetCursor("hand") - self:SetText("") + self:SetCursor("hand") + self:SetText("") - self.contents = { - title = "", - title_font = "DermaTTT2SubMenuButtonTitle", - icon = nil, - iconFullSize = false, - selected = false - } + self.contents = { + title = "", + title_font = "DermaTTT2SubMenuButtonTitle", + icon = nil, + iconFullSize = false, + iconBadge = nil, + iconBadgeSize = 0, + selected = false, + } end --- -- @return boolean -- @realm client function PANEL:IsDown() - return self.Depressed + return self.Depressed +end + +--- +-- @param Material badge +-- @realm client +function PANEL:SetIconBadge(badge) + self.contents.iconBadge = badge +end + +--- +-- @return Material|nil +-- @realm client +function PANEL:GetIconBadge() + return self.contents.iconBadge +end + +--- +-- @param number size +-- @realm client +function PANEL:SetIconBadgeSize(size) + self.contents.iconBadgeSize = size +end + +--- +-- @return number +-- @realm client +function PANEL:GetIconBadgeSize() + return self.contents.iconBadgeSize end --- -- @param string title -- @realm client function PANEL:SetTitle(title) - self.contents.title = title or "" + self.contents.title = title or "" end --- -- @return string -- @realm client function PANEL:GetTitle() - return self.contents.title + return self.contents.title end --- -- @param string title_font -- @realm client function PANEL:SetTitleFont(title_font) - self.contents.title_font = title_font or "" + self.contents.title_font = title_font or "" end --- -- @return string -- @realm client function PANEL:GetTitleFont() - return self.contents.title_font + return self.contents.title_font end --- @@ -73,57 +103,57 @@ end -- @param boolean iconFullSize -- @realm client function PANEL:SetIcon(iconMat, iconFullSize) - self.contents.icon = iconMat - self.contents.iconFullSize = tobool(iconFullSize) + self.contents.icon = iconMat + self.contents.iconFullSize = tobool(iconFullSize) end --- -- @return icon -- @realm client function PANEL:GetIcon() - return self.contents.icon + return self.contents.icon end --- -- @return boolean -- @realm client function PANEL:HasIcon() - return self.contents.icon ~= nil + return self.contents.icon ~= nil end --- -- @return boolean -- @realm client function PANEL:IsIconFullSize() - return self.contents.iconFullSize + return self.contents.iconFullSize end --- --- @param boolean +-- @param boolean active -- @realm client function PANEL:SetActive(active) - self.contents.active = active == nil and true or active + self.contents.active = active == nil and true or active end --- -- @return boolean -- @realm client function PANEL:IsActive() - return self.contents.active or false + return self.contents.active or false end --- -- @ignore function PANEL:Paint(w, h) - derma.SkinHook("Paint", "SubMenuButtonTTT2", self, w, h) + derma.SkinHook("Paint", "SubMenuButtonTTT2", self, w, h) - return false + return false end --- -- @ignore function PANEL:PerformLayout() - DLabel.PerformLayout(self) + DLabel.PerformLayout(self) end --- @@ -131,17 +161,17 @@ end -- @param string strArgs -- @realm client function PANEL:SetConsoleCommand(strName, strArgs) - self.DoClick = function(slf, val) - RunConsoleCommand(strName, strArgs) - end + self.DoClick = function(slf, val) + RunConsoleCommand(strName, strArgs) + end end --- -- @ignore function PANEL:SizeToContents() - local w, h = self:GetContentSize() + local w, h = self:GetContentSize() - self:SetSize(w + 8, h + 4) + self:SetSize(w + 8, h + 4) end derma.DefineControl("DSubmenuButtonTTT2", "A standard Button", PANEL, "DLabel") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsubmenulist_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsubmenulist_ttt2.lua index 3aded3d4b..07462a608 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsubmenulist_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dsubmenulist_ttt2.lua @@ -13,42 +13,27 @@ AccessorFunc(PANEL, "searchFunction", "SearchFunction") local heightNavHeader = 10 local heightNavButton = 50 ---- --- Checks recursively the parents until none is found and the highest parent is returned --- @ignore -local function getHighestParent(slf) - local parent = slf - local checkParent = slf:GetParent() - - while ispanel(checkParent) do - parent = checkParent - checkParent = parent:GetParent() - end - - return parent -end - --- -- @ignore function PANEL:Init() - -- Make navArea scrollable - local navAreaScroll = vgui.Create("DScrollPanelTTT2", self) - navAreaScroll:SetVerticalScrollbarEnabled(true) - navAreaScroll:Dock(BOTTOM) - self.navAreaScroll = navAreaScroll - - -- Split nav area into a grid layout - local navAreaScrollGrid = vgui.Create("DIconLayout", self.navAreaScroll) - navAreaScrollGrid:Dock(FILL) - self.navAreaScrollGrid = navAreaScrollGrid - - -- Get the frame to be able to enable keyboardinput on searchbar focus - self.frame = getHighestParent(self) - - -- This turns off the engine drawing - self:SetPaintBackgroundEnabled(false) - self:SetPaintBorderEnabled(false) - self:SetPaintBackground(false) + -- Make navArea scrollable + local navAreaScroll = vgui.Create("DScrollPanelTTT2", self) + navAreaScroll:SetVerticalScrollbarEnabled(true) + navAreaScroll:Dock(BOTTOM) + self.navAreaScroll = navAreaScroll + + -- Split nav area into a grid layout + local navAreaScrollGrid = vgui.Create("DIconLayout", self.navAreaScroll) + navAreaScrollGrid:Dock(FILL) + self.navAreaScrollGrid = navAreaScrollGrid + + -- Get the frame to be able to enable keyboardinput on searchbar focus + self.frame = util.getHighestPanelParent(self) + + -- This turns off the engine drawing + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) + self:SetPaintBackground(false) end --- @@ -57,43 +42,45 @@ end -- @param number heightBar -- @realm client function PANEL:SetSearchBarSize(widthBar, heightBar) - if not self.searchBar then return end + if not self.searchBar then + return + end - self.searchBar:SetSize(widthBar, heightBar) + self.searchBar:SetSize(widthBar, heightBar) end --- -- This function enables or disables the searchBar. --- @param bool active +-- @param boolean active -- @realm client function PANEL:EnableSearchBar(active) - if not active then - if self.searchBar then - self.searchBar:Clear() - end + if not active then + if self.searchBar then + self.searchBar:Clear() + end - return - end + return + end - -- Add searchbar on top - local searchBar = vgui.Create("DSearchBarTTT2", self) - searchBar:SetUpdateOnType(true) - searchBar:SetPos(0, heightNavHeader) - searchBar:SetHeightMult(1) + -- Add searchbar on top + local searchBar = vgui.Create("DSearchBarTTT2", self) + searchBar:SetUpdateOnType(true) + searchBar:SetPos(0, heightNavHeader) + searchBar:SetHeightMult(1) - searchBar.OnValueChange = function(slf, searchText) - self:GenerateSubmenuList(self.basemenuClass:GetMatchingSubmenus(searchText)) - end + searchBar.OnValueChange = function(slf, searchText) + self:GenerateSubmenuList(self.basemenuClass:GetMatchingSubmenus(searchText)) + end - searchBar.OnGetFocus = function(slf) - self.frame:SetKeyboardInputEnabled(true) - end + searchBar.OnGetFocus = function(slf) + self.frame:SetKeyboardInputEnabled(true) + end - searchBar.OnLoseFocus = function(slf) - self.frame:SetKeyboardInputEnabled(false) - end + searchBar.OnLoseFocus = function(slf) + self.frame:SetKeyboardInputEnabled(false) + end - self.searchBar = searchBar + self.searchBar = searchBar end --- @@ -102,29 +89,32 @@ end -- @return panel -- @realm client function PANEL:AddSubmenuButton(submenuClass) - local settingsButton = self.navAreaScrollGrid:Add("DSubmenuButtonTTT2") + local settingsButton = self.navAreaScrollGrid:Add("DSubmenuButtonTTT2") - settingsButton:SetTitle(submenuClass.title or submenuClass.type) - settingsButton:SetIcon(submenuClass.icon, submenuClass.iconFullSize) + settingsButton:SetTitle(submenuClass.title or submenuClass.type) + settingsButton:SetIcon(submenuClass.icon, submenuClass.iconFullSize) + settingsButton:SetIconBadge(submenuClass.iconBadge) + settingsButton:SetIconBadgeSize(submenuClass.iconBadgeSize) + settingsButton:SetTooltip(submenuClass.tooltip) - settingsButton.PerformLayout = function(panel) - panel:SetSize(panel:GetParent():GetWide(), heightNavButton) - end + settingsButton.PerformLayout = function(panel) + panel:SetSize(panel:GetParent():GetWide(), heightNavButton) + end - settingsButton.DoClick = function(slf) - HELPSCRN:SetupContentArea(self.contentArea, submenuClass) - HELPSCRN:BuildContentArea() + settingsButton.DoClick = function(slf) + HELPSCRN:SetupContentArea(self.contentArea, submenuClass) + HELPSCRN:BuildContentArea() - -- handle the set/unset of active buttons for the draw process - if self.lastActive and self.lastActive.SetActive then - self.lastActive:SetActive(false) - end + -- handle the set/unset of active buttons for the draw process + if self.lastActive and self.lastActive.SetActive then + self.lastActive:SetActive(false) + end - slf:SetActive() - self.lastActive = slf - end + slf:SetActive() + self.lastActive = slf + end - return settingsButton + return settingsButton end --- @@ -132,35 +122,35 @@ end -- @param menuClasses submenuClasses -- @realm client function PANEL:GenerateSubmenuList(submenuClasses) - self.navAreaScrollGrid:Clear() - self.contentArea:Clear() - - if #submenuClasses == 0 then - local labelNoContent = vgui.Create("DLabelTTT2", self.contentArea) - local widthContent = self.contentArea:GetSize() - - labelNoContent:SetText("label_menu_not_populated") - labelNoContent:SetSize(widthContent - 40, 50) - labelNoContent:SetFont("DermaTTT2Title") - labelNoContent:SetPos(20, 0) - else - for i = 1, #submenuClasses do - local submenuClass = submenuClasses[i] - local settingsButton = self:AddSubmenuButton(submenuClass) - - -- Handle the set of active buttons for the draw process - if i == 1 then - settingsButton:SetActive() - self.lastActive = settingsButton - end - end - - HELPSCRN:SetupContentArea(self.contentArea, submenuClasses[1]) - HELPSCRN:BuildContentArea() - end - - -- Last refresh sizes depending on number of submenus added - self:InvalidateLayout(true) + self.navAreaScrollGrid:Clear() + self.contentArea:Clear() + + if #submenuClasses == 0 then + local labelNoContent = vgui.Create("DLabelTTT2", self.contentArea) + local widthContent = self.contentArea:GetSize() + + labelNoContent:SetText("label_menu_not_populated") + labelNoContent:SetSize(widthContent - 40, 50) + labelNoContent:SetFont("DermaTTT2Title") + labelNoContent:SetPos(20, 0) + else + for i = 1, #submenuClasses do + local submenuClass = submenuClasses[i] + local settingsButton = self:AddSubmenuButton(submenuClass) + + -- Handle the set of active buttons for the draw process + if i == 1 then + settingsButton:SetActive() + self.lastActive = settingsButton + end + end + + HELPSCRN:SetupContentArea(self.contentArea, submenuClasses[1]) + HELPSCRN:BuildContentArea() + end + + -- Last refresh sizes depending on number of submenus added + self:InvalidateLayout(true) end --- @@ -169,52 +159,52 @@ end -- @param panel contenArea -- @realm client function PANEL:SetBasemenuClass(basemenuClass, contentArea) - self.basemenuClass = basemenuClass - self.contentArea = contentArea + self.basemenuClass = basemenuClass + self.contentArea = contentArea - self:GenerateSubmenuList(basemenuClass:GetVisibleSubmenus()) + self:GenerateSubmenuList(basemenuClass:GetVisibleSubmenus()) end --- -- @param number padding -- @realm client function PANEL:SetPadding(padding) - self.padding = padding + self.padding = padding - self.navAreaScrollGrid:SetSpaceY(padding) + self.navAreaScrollGrid:SetSpaceY(padding) end --- -- @return number -- @realm client function PANEL:GetPadding() - return self.padding + return self.padding end --- -- @ignore function PANEL:PerformLayout() - -- First invalidate Parent to get current correct docking size - self:InvalidateParent(true) + -- First invalidate Parent to get current correct docking size + self:InvalidateParent(true) - local widthNavContent, heightNavContent = self:GetSize() - local heightShift = heightNavHeader + (self.searchBar and heightNavButton + self.padding or 0) + local widthNavContent, heightNavContent = self:GetSize() + local heightShift = heightNavHeader + (self.searchBar and heightNavButton + self.padding or 0) - self:SetSearchBarSize(widthNavContent, heightNavButton) - self.navAreaScroll:SetSize(widthNavContent, heightNavContent - heightShift) + self:SetSearchBarSize(widthNavContent, heightNavButton) + self.navAreaScroll:SetSize(widthNavContent, heightNavContent - heightShift) - -- Last invalidate all buttons and then the scrolllist for correct size to contents - self.navAreaScrollGrid:InvalidateChildren(true) - self.navAreaScroll:InvalidateLayout(true) + -- Last invalidate all buttons and then the scrolllist for correct size to contents + self.navAreaScrollGrid:InvalidateChildren(true) + self.navAreaScroll:InvalidateLayout(true) end --- -- @return boolean -- @realm client function PANEL:Clear() - local cleared = self.searchBar:Clear() + local cleared = self.searchBar:Clear() - return tobool(cleared and self.navAreaScroll:Clear()) + return tobool(cleared and self.navAreaScroll:Clear()) end derma.DefineControl("DSubmenuListTTT2", "", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dtextentry_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dtextentry_ttt2.lua new file mode 100644 index 000000000..ae2ed99cc --- /dev/null +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dtextentry_ttt2.lua @@ -0,0 +1,346 @@ +--- +-- @class PANEL +-- @section DTextEntryTTT2 + +local PANEL = {} + +local font = "DermaTTT2Text" + +--- +-- @accessor number +-- @realm client +AccessorFunc(PANEL, "heightMult", "HeightMult") + +--- +-- @accessor bool +-- @realm client +AccessorFunc(PANEL, "isOnFocus", "IsOnFocus") + +--- +-- @ignore +function PANEL:Init() + self.TextArea = vgui.Create("DTextEntry", self) + self.TextColor = util.GetActiveColor( + util.GetChangedColor(util.GetDefaultColor(vskin.GetBackgroundColor()), 25) + ) + + self.TextArea:SetFont(font) + self.TextArea:SetTextColor(self.TextColor) + self.TextArea:SetCursorColor(self.TextColor) + + self.TextArea.OnValueChange = function(slf, value) + self:SetValue(value) + end + + self.TextArea.OnGetFocus = function(slf) + self:SetIsOnFocus(true) + self:OnGetFocus() + end + + self.TextArea.OnLoseFocus = function(slf) + self:SetIsOnFocus(false) + self:OnLoseFocus() + end + + -- This turns off the engine drawing for the text entry + self.TextArea:SetPaintBackgroundEnabled(false) + self.TextArea:SetPaintBorderEnabled(false) + self.TextArea:SetPaintBackground(false) + + -- This turns off the engine drawing of the panel itself + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) + self:SetPaintBackground(false) + + -- Sets default values + self:SetHeightMult(1) + self:SetIsOnFocus(false) + + self:PerformLayout() +end + +--- +-- @realm client +function PANEL:ResetToDefaultValue() + if not self:GetDefaultValue() then + return + end + + self:SetValue(self:GetDefaultValue()) +end + +--- +-- @param any val +-- @param boolean ignoreConVar To avoid endless loops, separated setting of convars and UI values +-- @realm client +function PANEL:SetValue(value, ignoreConVar) + if not value then + return + end + + if value == self:GetValue() then + return + end + + self.m_sValue = value + + self:ValueChanged(value) + + if ignoreConVar then + return + end + + self:SetConVarValues(value) +end + +--- +-- @param any val +-- @realm client +function PANEL:SetConVarValues(value) + if self.conVar then + self.conVar:SetString(value) + end + + if self.serverConVar then + cvars.ChangeServerConVar(self.serverConVar, tostring(value)) + end +end + +--- +-- @return any +-- @realm client +function PANEL:GetValue() + return self.m_sValue or "" +end + +--- +-- @param string value +-- @realm client +function PANEL:SetDefaultValue(value) + local noDefault = true + + if isstring(value) then + self.default = value + noDefault = false + else + self.default = nil + end + + local reset = self:GetResetButton() + + if ispanel(reset) then + reset.noDefault = noDefault + end +end + +--- +-- @return boolean +-- @realm client +function PANEL:IsEditing() + return self.TextArea:IsEditing() +end + +--- +-- @return string defaultValue +-- @realm client +function PANEL:GetDefaultValue() + return self.default +end + +--- +-- @return boolean +-- @realm client +function PANEL:IsHovered() + return self.TextArea:IsHovered() or vgui.GetHoveredPanel() == self +end + +--- +-- @param string cvar +-- @realm client +function PANEL:SetConVar(cvar) + if not ConVarExists(cvar or "") then + return + end + + self.conVar = GetConVar(cvar) + + self:SetValue(self.conVar:GetString(), true) + self:SetDefaultValue(tostring(GetConVar(cvar):GetDefault())) +end + +--- +-- @param string cvar +-- @realm client +function PANEL:SetServerConVar(cvar) + if not cvar or cvar == "" then + return + end + + self.serverConVar = cvar + + cvars.ServerConVarGetValue(cvar, function(wasSuccess, value, default) + if wasSuccess and IsValid(self) then + self:SetValue(tostring(value), true) + self:SetDefaultValue(tostring(default)) + end + end) + + local function OnServerConVarChangeCallback(conVarName, oldValue, newValue) + if not IsValid(self) then + cvars.RemoveChangeCallback(conVarName, "TTT2F1MenuServerConVarChangeCallback") + + return + end + + self:SetValue(tostring(newValue), true) + end + + cvars.AddChangeCallback( + cvar, + OnServerConVarChangeCallback, + "TTT2F1MenuServerConVarChangeCallback" + ) +end + +--- +-- @param Panel reset +-- @realm client +function PANEL:SetResetButton(reset) + if not ispanel(reset) then + return + end + + self.resetButton = reset + + reset.DoClick = function(slf) + self:ResetToDefaultValue() + end + + reset.noDefault = self.default == nil +end + +--- +-- @return Panel reset +-- @realm client +function PANEL:GetResetButton() + return self.resetButton +end + +--- +-- @param any val +-- @realm client +function PANEL:ValueChanged(val) + if self.TextArea ~= vgui.GetKeyboardFocus() then + self.TextArea:SetText(val) + end + + self:OnValueChanged(val) +end + +--- +-- overwrites the base function with an empty function +-- @param any val +-- @realm client +function PANEL:OnValueChanged(val) end + +--- +-- @return Panel +-- @realm client +function PANEL:GetTextArea() + return self.TextArea +end + +--- +-- @return string +-- @realm client +function PANEL:GetTextValue() + return self:GetValue() +end + +--- +-- @param string newFont +-- @realm client +function PANEL:SetFont(newFont) + self.TextArea:SetFont(newFont) + + font = newFont +end + +--- +-- @return string +-- @realm client +function PANEL:GetFont() + return font +end + +--- +-- This function determines if @{PANEL:OnValueChange()} is called on every typed letter or not. +-- @param boolean enabled +-- @realm client +function PANEL:SetUpdateOnType(enabled) + self.TextArea:SetUpdateOnType(enabled) +end + +--- +-- This function is called when the textentry is focussed. +-- @note This function can be overwritten but not called. +-- @realm client +function PANEL:OnGetFocus() end + +--- +-- This function is called when the textentry is not focussed anymore. +-- @note This function can be overwritten but not called. +-- @realm client +function PANEL:OnLoseFocus() end + +--- +-- This function is called by the textentry when a text is entered/changed. +-- @note This function should be overwritten but not called. +-- @param string value +-- @realm client +function PANEL:OnValueChange(value) end + +--- +-- @ignore +function PANEL:Paint(w, h) + derma.SkinHook("Paint", "TextEntryTTT2", self, w, h) + + return true +end + +--- +-- @ignore +function PANEL:PerformLayout() + local width, height = self:GetSize() + local heightMult = self:GetHeightMult() + + self.TextArea:SetSize(width, height * heightMult) + self.TextArea:SetPos(0, height * (1 - heightMult) * 0.5) + + -- React to skin changes in menu + self.TextArea:SetTextColor( + util.GetActiveColor( + util.GetChangedColor(util.GetDefaultColor(vskin.GetBackgroundColor()), 25) + ) + ) + + self.TextArea:InvalidateLayout(true) +end + +--- +-- @return boolean +-- @realm client +function PANEL:Clear() + return self.TextArea:Clear() +end + +--- +-- @param boolean b +-- @realm client +function PANEL:SetEnabled(b) + self.TextArea:SetEnabled(b) + + FindMetaTable("DPanelTTT2").SetEnabled(self, b) +end + +derma.DefineControl("DTextEntryTTT2", "", PANEL, "DPanelTTT2") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dtooltip_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dtooltip_ttt2.lua index 53b736617..ee5a51697 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dtooltip_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dtooltip_ttt2.lua @@ -7,10 +7,10 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - self:SetText("") - self:SetDrawOnTop(true) + self:SetText("") + self:SetDrawOnTop(true) - self.deleteContentsOnClose = false + self.deleteContentsOnClose = false end --- @@ -18,7 +18,7 @@ end -- @return Color -- @realm client function PANEL:UpdateColours(skin) - return self:SetTextStyleColor(skin.Colours.TooltipText) + return self:SetTextStyleColor(skin.Colours.TooltipText) end --- @@ -26,76 +26,78 @@ end -- @param boolean bDelete -- @realm client function PANEL:SetContents(panel, bDelete) - panel:SetParent(self) + panel:SetParent(self) - self.contents = panel - self.deleteContentsOnClose = bDelete or false - self.contents:SizeToContents() - self:InvalidateLayout(true) + self.contents = panel + self.deleteContentsOnClose = bDelete or false + self.contents:SizeToContents() + self:InvalidateLayout(true) - self.contents:SetVisible(false) + self.contents:SetVisible(false) end --- -- @ignore function PANEL:PerformLayout() - if not IsValid(self.targetPanel) then return end - - if self.targetPanel:HasTooltipFixedSize() then - self:SetSize(self.targetPanel:GetTooltipFixedSize()) - elseif IsValid(self.contents) then - self:SetWide(self.contents:GetWide() + 8) - self:SetTall(self.contents:GetTall() + 8) - else - local w, h = draw.GetTextSize(LANG.TryTranslation(self:GetText()), self:GetFont()) - - self:SetSize(w + 20, h + 8 + self.targetPanel.tooltip.sizeArrow) - end - - if IsValid(self.contents) then - self.contents:SetPos(1, self.targetPanel.tooltip.sizeArrow + 1) - self.contents:SetVisible(true) - end + if not IsValid(self.targetPanel) then + return + end + + if self.targetPanel:HasTooltipFixedSize() then + self:SetSize(self.targetPanel:GetTooltipFixedSize()) + elseif IsValid(self.contents) then + self:SetWide(self.contents:GetWide() + 8) + self:SetTall(self.contents:GetTall() + 8) + else + local w, h = draw.GetTextSize(LANG.TryTranslation(self:GetText()), self:GetFont()) + + self:SetSize(w + 20, h + 8 + self.targetPanel.tooltip.sizeArrow) + end + + if IsValid(self.contents) then + self.contents:SetPos(1, self.targetPanel.tooltip.sizeArrow + 1) + self.contents:SetVisible(true) + end end --- -- @return number -- @realm client function PANEL:GetArrowSize() - if not self.targetPanel.tooltip then - return 0 - end + if not self.targetPanel.tooltip then + return 0 + end - return self.targetPanel.tooltip.sizeArrow + return self.targetPanel.tooltip.sizeArrow end --- -- @realm client function PANEL:PositionTooltip() - if not IsValid(self.targetPanel) then - self:Close() + if not IsValid(self.targetPanel) then + self:Close() - return - end + return + end - self:InvalidateLayout(true) + self:InvalidateLayout(true) - local x, y + local x, y - if self.targetPanel:HasTooltipFixedPosition() then - x, y = self.targetPanel:GetTooltipFixedPosition() - parentX, parentY = self.targetPanel:GetParent():LocalToScreen(self.targetPanel:GetPos()) + if self.targetPanel:HasTooltipFixedPosition() then + x, y = self.targetPanel:GetTooltipFixedPosition() + parentX, parentY = self.targetPanel:GetParent():LocalToScreen(self.targetPanel:GetPos()) - x = x + parentX - y = y + parentY - else - x, y = input.GetCursorPos() + x = x + parentX + y = y + parentY + else + x, y = input.GetCursorPos() - x = x + 10 - y = y + 20 - end + x = x + 10 + y = y + 20 + end - self:SetPos(x, y) + self:SetPos(x, y) end --- @@ -103,58 +105,62 @@ end -- @param number h -- @realm client function PANEL:Paint(w, h) - self:PositionTooltip() + self:PositionTooltip() - derma.SkinHook("Paint", "TooltipTTT2", self, w, h) + derma.SkinHook("Paint", "TooltipTTT2", self, w, h) end --- -- @param Panel panel -- @realm client function PANEL:OpenForPanel(panel) - self.targetPanel = panel + self.targetPanel = panel - self:PositionTooltip() - self:SetSkin(panel:GetSkin().Name) - self:SetVisible(false) + self:PositionTooltip() + self:SetSkin(panel:GetSkin().Name) + self:SetVisible(false) - timer.Simple(self.targetPanel:GetTooltipOpeningDelay(), function() - if not IsValid(self) or not IsValid(self.targetPanel) then return end + timer.Simple(self.targetPanel:GetTooltipOpeningDelay(), function() + if not IsValid(self) or not IsValid(self.targetPanel) then + return + end - self:PositionTooltip() - self:SetVisible(true) - end) + self:PositionTooltip() + self:SetVisible(true) + end) end --- -- @realm client function PANEL:Close() - if not self.deleteContentsOnClose and IsValid(self.contents) then - self.contents:SetVisible(false) - self.contents:SetParent(nil) - end + if not self.deleteContentsOnClose and IsValid(self.contents) then + self.contents:SetVisible(false) + self.contents:SetParent(nil) + end - self:Remove() + self:Remove() end --- -- @realm client function PANEL:GetText() - return self.targetPanel:GetTooltipText() or "" + return self.targetPanel:GetTooltipText() or "" end --- -- @realm client function PANEL:HasText() - if not IsValid(self.targetPanel) then return end + if not IsValid(self.targetPanel) then + return + end - return self.targetPanel:HasTooltipText() + return self.targetPanel:HasTooltipText() end --- -- @realm client function PANEL:GetFont() - return self.targetPanel:GetTooltipFont() or "Default" + return self.targetPanel:GetTooltipFont() or "Default" end derma.DefineControl("DTooltipTTT2", "", PANEL, "DLabel") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dvscrollbar_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dvscrollbar_ttt2.lua index 7bfbd03d8..c1d60a1f6 100644 --- a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dvscrollbar_ttt2.lua +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dvscrollbar_ttt2.lua @@ -7,56 +7,56 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - self.offset = 0 - self.scroll = 0 - self.canvasSize = 1 - self.barSize = 1 + self.offset = 0 + self.scroll = 0 + self.canvasSize = 1 + self.barSize = 1 - self.btnGrip = vgui.Create("DScrollBarGrip", self) + self.btnGrip = vgui.Create("DScrollBarGrip", self) - self:SetSize(15, 15) + self:SetSize(15, 15) end --- -- @param boolean b -- @realm client function PANEL:SetEnabled(b) - if not b then - self.offset = 0 - self:SetScroll(0) - self.hasChanged = true - end + if not b then + self.offset = 0 + self:SetScroll(0) + self.hasChanged = true + end - self:SetMouseInputEnabled(b) - self:SetVisible(b) + self:SetMouseInputEnabled(b) + self:SetVisible(b) - if self.enabled ~= b then - self:GetParent():InvalidateLayout() + if self.enabled ~= b then + self:GetParent():InvalidateLayout() - if self:GetParent().OnScrollbarAppear then - self:GetParent():OnScrollbarAppear() - end - end + if self:GetParent().OnScrollbarAppear then + self:GetParent():OnScrollbarAppear() + end + end - self.enabled = b + self.enabled = b end --- -- @return number -- @realm client function PANEL:Value() - return self.Pos + return self.Pos end --- -- @return[default=1] number -- @realm client function PANEL:BarScale() - if self.barSize == 0 then - return 1 - end + if self.barSize == 0 then + return 1 + end - return self.barSize / (self.canvasSize + self.barSize) + return self.barSize / (self.canvasSize + self.barSize) end --- @@ -64,12 +64,12 @@ end -- @param number canvasSize -- @realm client function PANEL:SetUp(barSize, canvasSize) - self.barSize = barSize - self.canvasSize = math.max(canvasSize - barSize, 1) + self.barSize = barSize + self.canvasSize = math.max(canvasSize - barSize, 1) - self:SetEnabled(canvasSize > barSize) + self:SetEnabled(canvasSize > barSize) - self:InvalidateLayout() + self:InvalidateLayout() end --- @@ -77,11 +77,11 @@ end -- @return boolean -- @realm client function PANEL:OnMouseWheeled(dlta) - if not self:IsVisible() then - return false - end + if not self:IsVisible() then + return false + end - return self:AddScroll(dlta * -2) + return self:AddScroll(dlta * -2) end --- @@ -89,36 +89,36 @@ end -- @return boolean -- @realm client function PANEL:AddScroll(dlta) - local oldScroll = self:GetScroll() + local oldScroll = self:GetScroll() - dlta = dlta * 25 + dlta = dlta * 25 - self:SetScroll(self:GetScroll() + dlta) + self:SetScroll(self:GetScroll() + dlta) - return oldScroll ~= self:GetScroll() + return oldScroll ~= self:GetScroll() end --- -- @param number scrll -- @realm client function PANEL:SetScroll(scrll) - if not self.enabled then - self.scroll = 0 + if not self.enabled then + self.scroll = 0 - return - end + return + end - self.scroll = math.Clamp(scrll, 0, self.canvasSize) + self.scroll = math.Clamp(scrll, 0, self.canvasSize) - self:InvalidateLayout() + self:InvalidateLayout() - local parent = self:GetParent() + local parent = self:GetParent() - if isfunction(parent.OnVScroll) then - parent:OnVScroll(self:GetOffset()) - else - parent:InvalidateLayout() - end + if isfunction(parent.OnVScroll) then + parent:OnVScroll(self:GetOffset()) + else + parent:InvalidateLayout() + end end --- @@ -128,113 +128,113 @@ end -- @param number ease -- @realm client function PANEL:AnimateTo(scrll, length, delay, ease) - local anim = self:NewAnimation(length, delay, ease) + local anim = self:NewAnimation(length, delay, ease) - anim.startPos = self.scroll - anim.targetPos = scrll + anim.startPos = self.scroll + anim.targetPos = scrll - anim.Think = function(_, pnl, fraction) - pnl:SetScroll(Lerp(fraction, anim.startPos, anim.targetPos)) - end + anim.Think = function(_, pnl, fraction) + pnl:SetScroll(Lerp(fraction, anim.startPos, anim.targetPos)) + end end --- -- @return[default=0] number -- @realm client function PANEL:GetScroll() - if not self.enabled then - self.scroll = 0 - end + if not self.enabled then + self.scroll = 0 + end - return self.scroll + return self.scroll end --- -- @return[default=0] number -- @realm client function PANEL:GetOffset() - if not self.enabled then - return 0 - end + if not self.enabled then + return 0 + end - return self.scroll * -1 + return self.scroll * -1 end --- -- @ignore -function PANEL:Think() - -end +function PANEL:Think() end --- -- @ignore -function PANEL:Paint(w, h) - -end +function PANEL:Paint(w, h) end --- -- @realm client function PANEL:OnMousePressed() - local _, y = self:CursorPos() + local _, y = self:CursorPos() - local pageSize = self.barSize + local pageSize = self.barSize - if y > self.btnGrip.y then - self:SetScroll(self:GetScroll() + pageSize) - else - self:SetScroll(self:GetScroll() - pageSize) - end + if y > self.btnGrip.y then + self:SetScroll(self:GetScroll() + pageSize) + else + self:SetScroll(self:GetScroll() - pageSize) + end end --- -- @realm client function PANEL:OnMouseReleased() - self.dragging = false - self.draggingCanvas = nil - self:MouseCapture(false) + self.dragging = false + self.draggingCanvas = nil + self:MouseCapture(false) - self.btnGrip.Depressed = false - self.btnGrip.Hovered = false + self.btnGrip.Depressed = false + self.btnGrip.Hovered = false end --- -- @realm client function PANEL:OnCursorMoved() - if not self.enabled or not self.dragging then return end + if not self.enabled or not self.dragging then + return + end - local _, y = self:ScreenToLocal(0, gui.MouseY()) + local _, y = self:ScreenToLocal(0, gui.MouseY()) - y = (y - self.holdPos) / (self:GetTall() - self.btnGrip:GetTall()) + y = (y - self.holdPos) / (self:GetTall() - self.btnGrip:GetTall()) - self:SetScroll(y * self.canvasSize) + self:SetScroll(y * self.canvasSize) end --- -- @realm client function PANEL:Grip() - if not self.enabled or self.barSize == 0 then return end + if not self.enabled or self.barSize == 0 then + return + end - self:MouseCapture(true) - self.dragging = true + self:MouseCapture(true) + self.dragging = true - local _, y = self.btnGrip:ScreenToLocal(0, gui.MouseY()) + local _, y = self.btnGrip:ScreenToLocal(0, gui.MouseY()) - self.holdPos = y + self.holdPos = y - self.btnGrip.Depressed = true + self.btnGrip.Depressed = true end --- -- @ignore function PANEL:PerformLayout() - local wide = self:GetWide() + local wide = self:GetWide() - local barSize = math.max(self:BarScale() * self:GetTall(), 10) - local track = self:GetTall() - barSize + 1 - local scroll = self:GetScroll() / self.canvasSize * track + local barSize = math.max(self:BarScale() * self:GetTall(), 10) + local track = self:GetTall() - barSize + 1 + local scroll = self:GetScroll() / self.canvasSize * track - self.btnGrip:SetPos(0, scroll) - self.btnGrip:SetSize(wide, barSize) + self.btnGrip:SetPos(0, scroll) + self.btnGrip:SetSize(wide, barSize) end derma.DefineControl("DVScrollBarTTT2", "A Scrollbar", PANEL, "Panel") diff --git a/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dweaponpreview_ttt2.lua b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dweaponpreview_ttt2.lua new file mode 100644 index 000000000..354f6141f --- /dev/null +++ b/gamemodes/terrortown/gamemode/client/cl_vskin/vgui/dweaponpreview_ttt2.lua @@ -0,0 +1,334 @@ +--- +-- @class PANEL +-- @section DWeaponPreviewTTT2 + +local mathMax = math.max +local mathMin = math.min + +local PANEL = {} + +--- +-- @accessor number +-- @realm client +AccessorFunc(PANEL, "m_fAnimSpeed", "AnimSpeed") + +--- +-- @accessor Entity +-- @realm client +AccessorFunc(PANEL, "Entity", "Entity") + +--- +-- @accessor Vector +-- @realm client +AccessorFunc(PANEL, "vCamPos", "CamPos") + +--- +-- @accessor number +-- @realm client +AccessorFunc(PANEL, "fFOV", "FOV") + +--- +-- @accessor Vector +-- @realm client +AccessorFunc(PANEL, "vLookatPos", "LookAt") + +--- +-- @accessor Angle +-- @realm client +AccessorFunc(PANEL, "aLookAngle", "LookAng") + +--- +-- @accessor Color +-- @realm client +AccessorFunc(PANEL, "colAmbientLight", "AmbientLight") + +--- +-- @accessor Color +-- @realm client +AccessorFunc(PANEL, "colColor", "Color") + +--- +-- @accessor boolean +-- @realm client +AccessorFunc(PANEL, "bAnimated", "Animated") + +--- +-- @accessor boolean +-- @realm client +AccessorFunc(PANEL, "bHoverEffect", "HoverEffect") + +--- +-- @ignore +function PANEL:Init() + self.lastPaint = 0 + self.directionalLight = {} + self.farZ = 4096 + + self:SetContentAlignment(5) + + self:SetTall(22) + + self:SetFont("DermaTTT2TextLarge") + + self:SetCamPos(Vector(75, -65, 55)) + self:SetLookAt(Vector(5, 0, 35)) + self:SetFOV(35) + + self:SetAnimSpeed(0.5) + self:SetAnimated(true) + + -- the mouse input has to be enabled to detect mouse hovering + self:SetMouseInputEnabled(true) + self:SetHoverEffect(true) + + self:SetAmbientLight(Color(175, 175, 175)) + + self:SetDirectionalLight(BOX_TOP, Color(255, 255, 255)) + self:SetDirectionalLight(BOX_FRONT, Color(255, 255, 255)) + + self:SetColor(COLOR_WHITE) + + -- remove label and overwrite function + self:SetText("") + + self.data = { + ply = nil, + wep = nil, + HoldType = "normal", + } +end + +--- +-- @realm client +function PANEL:OnRemove() + -- old ent is removed because clientside models are not garbage collected + if IsValid(self.data.ply) then + self.data.ply:Remove() + end + if IsValid(self.data.wep) then + self.data.wep:Remove() + end +end + +--- +-- @param number iDirections The direction enum: https://wiki.facepunch.com/gmod/Enums/BOX +-- @param Color color +-- @realm client +function PANEL:SetDirectionalLight(iDirection, color) + self.directionalLight[iDirection] = color +end + +--- +-- @param string model +-- @realm client +function PANEL:SetPlayerModel(model) + -- set the entity + local clientsideEntity = ClientsideModel(model, RENDERGROUP_OTHER) + + if not IsValid(clientsideEntity) then + return + end + + clientsideEntity:SetNoDraw(true) + clientsideEntity:SetIK(false) + + -- before storing the ent, make sure that a possible old ent + -- is removed because clientside models are not garbage collected + if IsValid(self.data.ply) then + self.data.ply:Remove() + end + + self.data.ply = clientsideEntity +end + +--- +-- @param string cls +-- @realm client +function PANEL:SetWeaponClass(cls) + if not IsValid(self.data.ply) then + return + end + + -- weapons.Get returns a copy of the weapon table, it is therefore fine to + -- create modifications on that table + local wep = weapons.Get(cls) + + if not wep then + return + end + + wep:InitializeCustomModels() + + -- if the weapon is created with the SWEP Construction Kit, we have to import the + -- data into our system. To be on the safe side we also disable to default world + -- model for these weapons + if wep.WElements then + wep.ShowDefaultWorldModel = false + + for identifier, modelData in pairs(wep.WElements) do + wep:AddCustomWorldModel(identifier, modelData) + end + end + + -- before storing the ent, make sure that a possible old ent + -- is removed because clientside models are not garbage collected + if IsValid(self.data.wep) then + self.data.wep:Remove() + end + + local clientsideEntity = ClientsideModel(wep.WorldModel, RENDERGROUP_OTHER) + + if not IsValid(clientsideEntity) then + return + end + + clientsideEntity:SetNoDraw(true) + clientsideEntity:SetIK(false) + + clientsideEntity:SetParent(self.data.ply) + + -- Applies a bonemerge engine effect. This merges the bones of this entity with the + -- entity of its parent so that they always move together. + clientsideEntity:AddEffects(EF_BONEMERGE) + + self.data.wepModel = clientsideEntity + self.data.wep = wep + + self.data.HoldType = wep.HoldType + + if wep.HoldType == "normal" then + self.data.ply:SetSequence(self.data.ply:LookupSequence("idle_all_01")) + else + self.data.ply:SetSequence(self.data.ply:LookupSequence("idle_" .. wep.HoldType)) + end + + self.data.ply:SetupBones() +end + +--- +-- @realm client +function PANEL:DrawModel() + local ply = self.data.ply + local wep = self.data.wep + local wepModel = self.data.wepModel + + if not IsValid(ply) then + return + end + + local w, h = self:GetSize() + local xBaseStart, yBaseStart = self:LocalToScreen(0, 0) + + local xLimitStart, yLimitStart = xBaseStart, yBaseStart + local xLimitEnd, yLimitEnd = self:LocalToScreen(self:GetWide(), self:GetTall()) + + local currentParent = self + + -- iterate till the top is found to make sure the image is not out of bounds + while currentParent:GetParent() do + currentParent = currentParent:GetParent() + + local x1, y1 = currentParent:LocalToScreen(0, 0) + local x2, y2 = currentParent:LocalToScreen(currentParent:GetWide(), currentParent:GetTall()) + + xLimitStart = mathMax(xLimitStart, x1) + yLimitStart = mathMax(yLimitStart, y1) + xLimitEnd = mathMin(xLimitEnd, x2) + yLimitEnd = mathMin(yLimitEnd, y2) + end + + -- only the player has to be layouted (animated) because the weapon is tied to them + self:LayoutEntity(ply) + + local ang = self.aLookAngle or (self.vLookatPos - self.vCamPos):Angle() + + cam.Start3D(self.vCamPos, ang, self.fFOV, xBaseStart, yBaseStart, w, h, 5, self.farZ) + render.SuppressEngineLighting(true) + render.SetLightingOrigin(ply:GetPos()) + render.SetColorModulation(self.colColor.r / 255, self.colColor.g / 255, self.colColor.b / 255) + + -- iterates over the model lighting enum: https://wiki.facepunch.com/gmod/Enums/BOX + for i = 0, 6 do + local col = self.directionalLight[i] + + if col then + render.SetModelLighting(i, col.r / 255, col.g / 255, col.b / 255) + end + end + + -- make a mask to make sure the graphic is limited + render.SetScissorRect(xLimitStart, yLimitStart, xLimitEnd, yLimitEnd, true) + + if self.Hovered and self.bHoverEffect then + render.ResetModelLighting(1.0, 1.0, 1.0) + render.SetBlend(0.2) + else + render.ResetModelLighting( + self.colAmbientLight.r / 255, + self.colAmbientLight.g / 255, + self.colAmbientLight.b / 255 + ) + render.SetBlend((self:GetAlpha() / 255) * (self.colColor.a / 255)) + end + + ply:DrawModel() + + render.SetBlend((self:GetAlpha() / 255) * (self.colColor.a / 255)) + + weaponrenderer.RenderWorldModel( + wep, + wepModel, + wep.customWorldModelElements or wep.WElements, + ply + ) + + render.SetScissorRect(0, 0, 0, 0, false) + render.SuppressEngineLighting(false) + cam.End3D() + + self.lastPaint = RealTime() +end + +--- +-- This function is to be overriden +-- @param Entity ent +-- @realm client +function PANEL:LayoutEntity(ent) + if self.bAnimated then + self:RunAnimation() + end +end + +--- +-- @realm client +function PANEL:RunAnimation() + self.data.ply:FrameAdvance((RealTime() - self.lastPaint) * self.m_fAnimSpeed) +end + +--- +-- @return boolean +-- @realm client +function PANEL:HasModel() + return IsValid(self.data.ply) +end + +--- +-- @ignore +function PANEL:IsDown() + return self.Depressed +end + +--- +-- @ignore +function PANEL:Paint(w, h) + derma.SkinHook("Paint", "WeaponPreviewTTT2", self, w, h) + + return false +end + +derma.DefineControl( + "DWeaponPreviewTTT2", + "A box that renders a player with an equipped weapon", + PANEL, + "DLabelTTT2" +) diff --git a/gamemodes/terrortown/gamemode/client/cl_weapon_pickup.lua b/gamemodes/terrortown/gamemode/client/cl_weapon_pickup.lua index b7c12f142..b4afc95f6 100644 --- a/gamemodes/terrortown/gamemode/client/cl_weapon_pickup.lua +++ b/gamemodes/terrortown/gamemode/client/cl_weapon_pickup.lua @@ -1,41 +1,57 @@ local function GetPickableWeaponInFront() - local client = LocalPlayer() + local client = LocalPlayer() - if not IsValid(client) or not client:IsTerror() or not client:Alive() then return end + if not IsValid(client) or not client:IsTerror() or not client:Alive() then + return + end - local tracedWeapon = client:GetEyeTrace().Entity + local tracedWeapon = client:GetEyeTrace().Entity - if not IsValid(tracedWeapon) or not tracedWeapon:IsWeapon() - or client:GetPos():Distance(tracedWeapon:GetPos()) > 100 then - return - end + if + not IsValid(tracedWeapon) + or not tracedWeapon:IsWeapon() + or client:GetPos():Distance(tracedWeapon:GetPos()) > 100 + then + return + end - return tracedWeapon + return tracedWeapon end local lastRequest = 0 -- sends a request to the server that this client wants to pickup/switch a weapon local function AttemptWeaponSwitch() - if GetPickableWeaponInFront() == nil or lastRequest + 0.25 > CurTime() then return end + if GetPickableWeaponInFront() == nil or lastRequest + 0.25 > CurTime() then + return + end - net.Start("ttt2_switch_weapon") - net.SendToServer() + net.Start("ttt2_switch_weapon") + net.SendToServer() - lastRequest = CurTime() + lastRequest = CurTime() end -- picking up a weapon should update the client weapon cache net.Receive("ttt2_switch_weapon_update_cache", function() - -- this for now is a workaround to test if the timing of the refresh is the problem - timer.Simple(0.25, function() - local client = LocalPlayer() + -- this for now is a workaround to test if the timing of the refresh is the problem + timer.Simple(0.25, function() + local client = LocalPlayer() - if not IsValid(client) or not client:IsReady() then return end + if not IsValid(client) or not client:IsReady() then + return + end - WSWITCH:UpdateWeaponCache() - end) + WSWITCH:UpdateWeaponCache() + end) end) -- register a binding for the weapon switch, the default should be the use key -bind.Register("ttt2_weaponswitch", AttemptWeaponSwitch, nil, "header_bindings_ttt2", "label_bind_weaponswitch", input.GetKeyCode(input.LookupBinding("+use") or KEY_E)) +bind.Register( + "ttt2_weaponswitch", + AttemptWeaponSwitch, + nil, + "header_bindings_ttt2", + "label_bind_weaponswitch", + input.GetKeyCode(input.LookupBinding("+use") or KEY_E) +) diff --git a/gamemodes/terrortown/gamemode/client/cl_wepswitch.lua b/gamemodes/terrortown/gamemode/client/cl_wepswitch.lua index 78e3827dd..b8524cee9 100644 --- a/gamemodes/terrortown/gamemode/client/cl_wepswitch.lua +++ b/gamemodes/terrortown/gamemode/client/cl_wepswitch.lua @@ -6,52 +6,60 @@ local pairs = pairs local IsValid = IsValid WSWITCH = { - Show = false, - Selected = -1, - NextSwitch = -1, - WeaponCache = {}, - - cv = { - --- - -- @realm client - hide = CreateConVar("ttt_weaponswitcher_hide", "1", FCVAR_ARCHIVE), - - --- - -- @realm client - fast = CreateConVar("ttt_weaponswitcher_fast", "0", FCVAR_ARCHIVE), - - --- - -- @realm client - display = CreateConVar("ttt_weaponswitcher_displayfast", "0", FCVAR_ARCHIVE) - } + Show = false, + Selected = -1, + NextSwitch = -1, + WeaponCache = {}, + + cv = { + --- + -- @realm client + -- stylua: ignore + hide = CreateConVar("ttt_weaponswitcher_hide", "1", FCVAR_ARCHIVE), + + --- + -- @realm client + -- stylua: ignore + fast = CreateConVar("ttt_weaponswitcher_fast", "0", FCVAR_ARCHIVE), + + --- + -- @realm client + -- stylua: ignore + display = CreateConVar("ttt_weaponswitcher_displayfast", "0", FCVAR_ARCHIVE) +, + }, } local delay = 0.03 local showtime = 3 local function InsertIfValid(dest, wep) - if not IsValid(wep) then return end + if not IsValid(wep) then + return + end - dest[#dest + 1] = wep + dest[#dest + 1] = wep end --- -- Updates the weapon cache -- @realm client function WSWITCH:UpdateWeaponCache() - local client = LocalPlayer() + local client = LocalPlayer() - if not IsValid(client) then return end + if not IsValid(client) then + return + end - self.WeaponCache = {} + self.WeaponCache = {} - local inventory = client:GetInventory() + local inventory = client:GetInventory() - for kind = 1, #ORDERED_SLOT_TABLE do - for _, wep in pairs(inventory[kind]) do - InsertIfValid(self.WeaponCache, wep) - end - end + for kind = 1, #ORDERED_SLOT_TABLE do + for _, wep in pairs(inventory[kind]) do + InsertIfValid(self.WeaponCache, wep) + end + end end --- @@ -62,46 +70,50 @@ end -- @realm client -- @see WSWITCH:DoSelect function WSWITCH:SetSelected(idx) - self.Selected = idx + self.Selected = idx - self:UpdateWeaponCache() + self:UpdateWeaponCache() end --- -- Increases the current index of the weapon switch -- @realm client function WSWITCH:SelectNext() - if self.NextSwitch > CurTime() then return end + if self.NextSwitch > CurTime() then + return + end - self:Enable() + self:Enable() - local s = self.Selected + 1 + local s = self.Selected + 1 - if s > #self.WeaponCache then - s = 1 - end + if s > #self.WeaponCache then + s = 1 + end - self:DoSelect(s) + self:DoSelect(s) - self.NextSwitch = CurTime() + delay + self.NextSwitch = CurTime() + delay end --- -- Decreases the current index of the weapon switch -- @realm client function WSWITCH:SelectPrev() - if self.NextSwitch > CurTime() then return end + if self.NextSwitch > CurTime() then + return + end - self:Enable() + self:Enable() - local s = self.Selected - 1 - if s < 1 then - s = #self.WeaponCache - end + local s = self.Selected - 1 + if s < 1 then + s = #self.WeaponCache + end - self:DoSelect(s) + self:DoSelect(s) - self.NextSwitch = CurTime() + delay + self.NextSwitch = CurTime() + delay end --- @@ -112,12 +124,12 @@ end -- @realm client -- @see WSWITCH:SetSelected function WSWITCH:DoSelect(idx) - self:SetSelected(idx) + self:SetSelected(idx) - if self.cv.fast:GetBool() then - -- immediately confirm if fastswitch is on - self:ConfirmSelection(self.cv.display:GetBool()) - end + if self.cv.fast:GetBool() then + -- immediately confirm if fastswitch is on + self:ConfirmSelection(self.cv.display:GetBool()) + end end --- @@ -125,78 +137,80 @@ end -- @param number slot -- @realm client function WSWITCH:SelectSlot(slot) - if not slot then return end - - self:Enable() - self:UpdateWeaponCache() - - -- find which idx in the weapon table has the slot we want - local toselect = self.Selected - local activeWeapon = LocalPlayer():GetActiveWeapon() - local cache = self.WeaponCache - local cacheCount = #cache - local activeSlot = 1 - - -- if the current weapon is active - -- and the current weapon is in the same slot as the requested slot - if IsValid(activeWeapon) and MakeKindValid(activeWeapon.Kind) == slot then - activeSlot = toselect + 1 -- start with index of the next weapon - - -- reset index if it's bigger than available weapons or the weapon at this index isn't at the same slot - if activeSlot > cacheCount or MakeKindValid(cache[activeSlot].Kind) ~= slot then - activeSlot = 1 - end - end - - -- do the weapon switch to the selected slot - for i = activeSlot, cacheCount do - if MakeKindValid(cache[i].Kind) == slot then - toselect = i - - break - end - end - - self:DoSelect(toselect) - - self.NextSwitch = CurTime() + delay + if not slot then + return + end + + self:Enable() + self:UpdateWeaponCache() + + -- find which idx in the weapon table has the slot we want + local toselect = self.Selected + local activeWeapon = LocalPlayer():GetActiveWeapon() + local cache = self.WeaponCache + local cacheCount = #cache + local activeSlot = 1 + + -- if the current weapon is active + -- and the current weapon is in the same slot as the requested slot + if IsValid(activeWeapon) and MakeKindValid(activeWeapon.Kind) == slot then + activeSlot = toselect + 1 -- start with index of the next weapon + + -- reset index if it's bigger than available weapons or the weapon at this index isn't at the same slot + if activeSlot > cacheCount or MakeKindValid(cache[activeSlot].Kind) ~= slot then + activeSlot = 1 + end + end + + -- do the weapon switch to the selected slot + for i = activeSlot, cacheCount do + if MakeKindValid(cache[i].Kind) == slot then + toselect = i + + break + end + end + + self:DoSelect(toselect) + + self.NextSwitch = CurTime() + delay end --- -- Show the weapon switcher -- @realm client function WSWITCH:Enable() - if self.Show == false then - self.Show = true + if self.Show == false then + self.Show = true - local wep_active = LocalPlayer():GetActiveWeapon() + local wep_active = LocalPlayer():GetActiveWeapon() - self:UpdateWeaponCache() + self:UpdateWeaponCache() - -- make our active weapon the initial selection - local toselect = 1 - local cachedWeapons = self.WeaponCache + -- make our active weapon the initial selection + local toselect = 1 + local cachedWeapons = self.WeaponCache - for k = 1, #cachedWeapons do - if cachedWeapons[k] == wep_active then - toselect = k + for k = 1, #cachedWeapons do + if cachedWeapons[k] == wep_active then + toselect = k - break - end - end + break + end + end - self:SetSelected(toselect) - end + self:SetSelected(toselect) + end - -- cache for speed, checked every Think - self.Stay = not self.cv.hide:GetBool() + -- cache for speed, checked every Think + self.Stay = not self.cv.hide:GetBool() end --- -- Hide the weapon switcher -- @realm client function WSWITCH:Disable() - self.Show = false + self.Show = false end --- @@ -204,28 +218,28 @@ end -- @param boolean noHide -- @realm client function WSWITCH:ConfirmSelection(noHide) - if not noHide then - self:Disable() - end + if not noHide then + self:Disable() + end - local cachedWeapons = self.WeaponCache + local cachedWeapons = self.WeaponCache - for k = 1, #cachedWeapons do - local w = cachedWeapons[k] + for k = 1, #cachedWeapons do + local w = cachedWeapons[k] - if k == self.Selected and IsValid(w) then - input.SelectWeapon(w) + if k == self.Selected and IsValid(w) then + input.SelectWeapon(w) - return - end - end + return + end + end end --- -- Allow for suppression of the attack command -- @realm client function WSWITCH:PreventAttack() - return self.Show and not self.cv.fast:GetBool() + return self.Show and not self.cv.fast:GetBool() end --- @@ -234,12 +248,14 @@ end -- @realm client -- @internal function WSWITCH:Think() - if not self.Show or self.Stay then return end - - -- hide after period of inaction - if self.NextSwitch < (CurTime() - showtime) then - self:Disable() - end + if not self.Show or self.Stay then + return + end + + -- hide after period of inaction + if self.NextSwitch < (CurTime() - showtime) then + self:Disable() + end end --- @@ -247,30 +263,38 @@ end -- @param number slot -- @realm client function WSWITCH:SelectAndConfirm(slot) - if not slot then return end + if not slot then + return + end - self:SelectSlot(slot) - self:ConfirmSelection() + self:SelectSlot(slot) + self:ConfirmSelection() end local function QuickSlot(ply, cmd, args) - if not IsValid(ply) or not args or #args ~= 1 then return end - - local slot = tonumber(args[1]) - if not slot then return end - - local wep = ply:GetActiveWeapon() - if not IsValid(wep) then return end - - if MakeKindValid(wep.Kind) == slot then - RunConsoleCommand("lastinv") - else - WSWITCH:SelectAndConfirm(slot) - end + if not IsValid(ply) or not args or #args ~= 1 then + return + end + + local slot = tonumber(args[1]) + if not slot then + return + end + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) then + return + end + + if MakeKindValid(wep.Kind) == slot then + RunConsoleCommand("lastinv") + else + WSWITCH:SelectAndConfirm(slot) + end end concommand.Add("ttt_quickslot", QuickSlot) local function SwitchToEquipment(ply, cmd, args) - RunConsoleCommand("ttt_quickslot", "7") + RunConsoleCommand("ttt_quickslot", "7") end concommand.Add("ttt_equipswitch", SwitchToEquipment) diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_coloredbox.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_coloredbox.lua index d669e3a0f..68fabba1c 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_coloredbox.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_coloredbox.lua @@ -18,24 +18,26 @@ AccessorFunc(PANEL, "m_Color", "Color") --- @ignore function PANEL:Init() - self:SetBorder(true) - self:SetColor(Color(0, 255, 0, 255)) + self:SetBorder(true) + self:SetColor(Color(0, 255, 0, 255)) end --- @ignore function PANEL:Paint() - surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, 255) + surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, 255) - self:DrawFilledRect() + self:DrawFilledRect() end --- @ignore function PANEL:PaintOver() - if not self.m_bBorder then return end + if not self.m_bBorder then + return + end - surface.SetDrawColor(0, 0, 0, 255) + surface.SetDrawColor(0, 0, 0, 255) - self:DrawOutlinedRect() + self:DrawOutlinedRect() end derma.DefineControl("ColoredBox", "", PANEL, "DPanel") diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_droleimage.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_droleimage.lua index 4a0b77b06..016eca677 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_droleimage.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_droleimage.lua @@ -59,18 +59,18 @@ AccessorFunc(PANEL, "m_strRoleIconName", "RoleIconName") --- @ignore function PANEL:Init() - self:SetImageColor(Color(255, 255, 255, 255)) - self:SetMouseInputEnabled(false) - self:SetKeyboardInputEnabled(false) - self:SetKeepAspect(false) - - self.ImageName = "" - self.ImageName2 = "" - self.ImageOverlayName = "" - self.RoleIconImageName = "" - - self.ActualWidth = 10 - self.ActualHeight = 10 + self:SetImageColor(Color(255, 255, 255, 255)) + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + self:SetKeepAspect(false) + + self.ImageName = "" + self.ImageName2 = "" + self.ImageOverlayName = "" + self.RoleIconImageName = "" + + self.ActualWidth = 10 + self.ActualHeight = 10 end --- @@ -80,359 +80,371 @@ end -- @param string matOverName -- @realm client function PANEL:SetOnViewMaterial(matName, matName2, roleIconName, matOverName) - self:SetMatName(matName) - self:SetMatName2(matName2) - self:SetMatOverName(matOverName) - self:SetRoleIconName(roleIconName) - - self.ImageName = matName - self.ImageName2 = matName2 - self.ImageOverlayName = matOverName - self.RoleIconImageName = roleIconName + self:SetMatName(matName) + self:SetMatName2(matName2) + self:SetMatOverName(matOverName) + self:SetRoleIconName(roleIconName) + + self.ImageName = matName + self.ImageName2 = matName2 + self.ImageOverlayName = matOverName + self.RoleIconImageName = roleIconName end --- -- @return boolean returns whether the current @{PANEL} is unloaded -- @realm client function PANEL:Unloaded() - return self.m_strMatName ~= nil + return self.m_strMatName ~= nil end --- -- Loads the @{Material} -- @realm client function PANEL:LoadMaterial() - if not self:Unloaded() then return end + if not self:Unloaded() then + return + end - self:DoLoadMaterial() + self:DoLoadMaterial() - self:SetMatName(nil) - self:SetMatName2(nil) - self:SetRoleIconName(nil) - self:SetMatOverName(nil) + self:SetMatName(nil) + self:SetMatName2(nil) + self:SetRoleIconName(nil) + self:SetMatOverName(nil) end --- -- Post loads the @{Material} -- @realm client function PANEL:DoLoadMaterial() - local mat = Material(self:GetMatName()) - local mat2 = Material(self:GetMatName2()) - local matover = Material(self:GetMatOverName()) - local roleIcon = Material(self:GetRoleIconName()) - - self:SetMaterial(mat) - self:FixVertexLitMaterial() - - if mat2 then - self:SetMaterial2(mat2) - self:FixVertexLitMaterial2() - end - - if matover then - self:MaterialOverlay(matover) - self:FixVertexLitMaterialOverlay() - end - - if roleIcon then - self:SetRoleIcon(roleIcon) - self:FixVertexLitRoleIcon() - end - - -- - -- This isn't ideal, but it will probably help you out of a jam - -- in cases where you position the image according to the texture - -- size and you want to load on view - instead of on load. - -- - self:InvalidateParent() + local mat = Material(self:GetMatName()) + local mat2 = Material(self:GetMatName2()) + local matover = Material(self:GetMatOverName()) + local roleIcon = Material(self:GetRoleIconName()) + + self:SetMaterial(mat) + self:FixVertexLitMaterial() + + if mat2 then + self:SetMaterial2(mat2) + self:FixVertexLitMaterial2() + end + + if matover then + self:MaterialOverlay(matover) + self:FixVertexLitMaterialOverlay() + end + + if roleIcon then + self:SetRoleIcon(roleIcon) + self:FixVertexLitRoleIcon() + end + + -- + -- This isn't ideal, but it will probably help you out of a jam + -- in cases where you position the image according to the texture + -- size and you want to load on view - instead of on load. + -- + self:InvalidateParent() end --- -- @param Material|string mat -- @realm client function PANEL:SetMaterial(mat) - -- Everybody makes mistakes, - -- that's why they put erasers on pencils. - if isstring(mat) then - self:SetImage(mat) - - return - end - - self.m_Material = mat - - if not self.m_Material then return end - - local Texture = self.m_Material:GetTexture("$basetexture") - if Texture then - self.ActualWidth = Texture:Width() - self.ActualHeight = Texture:Height() - else - self.ActualWidth = self.m_Material:Width() - self.ActualHeight = self.m_Material:Height() - end + -- Everybody makes mistakes, + -- that's why they put erasers on pencils. + if isstring(mat) then + self:SetImage(mat) + + return + end + + self.m_Material = mat + + if not self.m_Material then + return + end + + local Texture = self.m_Material:GetTexture("$basetexture") + if Texture then + self.ActualWidth = Texture:Width() + self.ActualHeight = Texture:Height() + else + self.ActualWidth = self.m_Material:Width() + self.ActualHeight = self.m_Material:Height() + end end --- -- @param Material|string mat -- @realm client function PANEL:SetMaterial2(mat) - -- Everybody makes mistakes, - -- that's why they put erasers on pencils. - if isstring(mat) then - self:SetImage2(mat) + -- Everybody makes mistakes, + -- that's why they put erasers on pencils. + if isstring(mat) then + self:SetImage2(mat) - return - end + return + end - self.m_Material2 = mat + self.m_Material2 = mat end --- -- @param Material|string mat -- @realm client function PANEL:SetMaterialOverlay(mat) - -- Everybody makes mistakes, - -- that's why they put erasers on pencils. - if isstring(mat) then - self:SetImageOverlay(mat) + -- Everybody makes mistakes, + -- that's why they put erasers on pencils. + if isstring(mat) then + self:SetImageOverlay(mat) - return - end + return + end - self.m_MaterialOverlay = mat + self.m_MaterialOverlay = mat end --- -- @param Material|string mat -- @realm client function PANEL:SetRoleIcon(mat) - -- Everybody makes mistakes, - -- that's why they put erasers on pencils. - if isstring(mat) then - self:SetRoleIconImage(mat) + -- Everybody makes mistakes, + -- that's why they put erasers on pencils. + if isstring(mat) then + self:SetRoleIconImage(mat) - return - end + return + end - self.m_RoleIcon = mat + self.m_RoleIcon = mat end --- -- @param string strImage The image name -- @realm client function PANEL:SetImage(strImage) - self.ImageName = strImage + self.ImageName = strImage - local mat = Material(strImage) + local mat = Material(strImage) - self:SetMaterial(mat) - self:FixVertexLitMaterial() + self:SetMaterial(mat) + self:FixVertexLitMaterial() end --- -- @param string strImage2 The image name -- @realm client function PANEL:SetImage2(strImage2) - self.ImageName2 = strImage2 + self.ImageName2 = strImage2 - local mat = Material(strImage2) + local mat = Material(strImage2) - self:SetMaterial2(mat) - self:FixVertexLitMaterial2() + self:SetMaterial2(mat) + self:FixVertexLitMaterial2() end --- -- @param string strImageOverlay The image overlay name -- @realm client function PANEL:SetImageOverlay(strImageOverlay) - self.ImageOverlayName = strImageOverlay + self.ImageOverlayName = strImageOverlay - local mat = Material(strImageOverlay) + local mat = Material(strImageOverlay) - self:SetMaterialOverlay(mat) - self:FixVertexLitMaterialOverlay() + self:SetMaterialOverlay(mat) + self:FixVertexLitMaterialOverlay() end --- -- @param string strImage The role icon image name -- @realm client function PANEL:SetRoleIconImage(strImage) - self.RoleIconImageName = strImage + self.RoleIconImageName = strImage - local mat = Material(strImage) + local mat = Material(strImage) - self:SetRoleIcon(mat) - self:FixVertexLitRoleIcon() + self:SetRoleIcon(mat) + self:FixVertexLitRoleIcon() end --- -- @realm client function PANEL:UnloadImage2() - self.ImageName2 = nil - self.m_Material2 = nil + self.ImageName2 = nil + self.m_Material2 = nil end --- -- @realm client function PANEL:UnloadImageOverlay() - self.ImageOverlayName = nil - self.m_MaterialOverlay = nil + self.ImageOverlayName = nil + self.m_MaterialOverlay = nil end --- -- @realm client function PANEL:UnloadRoleIconImage() - self.RoleIconImageName = nil - self.m_RoleIcon = nil + self.RoleIconImageName = nil + self.m_RoleIcon = nil end --- -- @return string -- @realm client function PANEL:GetImage() - return self.ImageName + return self.ImageName end --- -- @return string -- @realm client function PANEL:GetImage2() - return self.ImageName2 + return self.ImageName2 end --- -- @return string -- @realm client function PANEL:GetImageOverlay() - return self.ImageOverlayName + return self.ImageOverlayName end --- -- @return string -- @realm client function PANEL:GetRoleIconImage() - return self.RoleIconImageName + return self.RoleIconImageName end --- -- @local function PANEL:FixVertexLitMaterial() - -- - -- If it's a vertexlitgeneric material we need to change it to be - -- UnlitGeneric so it doesn't go dark when we enter a dark room - -- and flicker all about - -- - - local mat = self:GetMaterial() - local strImage = mat:GetName() - - if string.find(mat:GetShader(), "VertexLitGeneric") or string.find(mat:GetShader(), "Cable") then - local t = mat:GetString("$basetexture") - if t then - local params = {} - params["$basetexture"] = t - params["$vertexcolor"] = 1 - params["$vertexalpha"] = 1 - - mat = CreateMaterial(strImage .. "_DImage", "UnlitGeneric", params) - end - end - - self:SetMaterial(mat) + -- + -- If it's a vertexlitgeneric material we need to change it to be + -- UnlitGeneric so it doesn't go dark when we enter a dark room + -- and flicker all about + -- + + local mat = self:GetMaterial() + local strImage = mat:GetName() + + if + string.find(mat:GetShader(), "VertexLitGeneric") or string.find(mat:GetShader(), "Cable") + then + local t = mat:GetString("$basetexture") + if t then + local params = {} + params["$basetexture"] = t + params["$vertexcolor"] = 1 + params["$vertexalpha"] = 1 + + mat = CreateMaterial(strImage .. "_DImage", "UnlitGeneric", params) + end + end + + self:SetMaterial(mat) end --- -- @local function PANEL:FixVertexLitMaterial2() - -- - -- If it's a vertexlitgeneric material we need to change it to be - -- UnlitGeneric so it doesn't go dark when we enter a dark room - -- and flicker all about - -- - - local mat = self:GetMaterial2() - local strImage = mat:GetName() - - if string.find(mat:GetShader(), "VertexLitGeneric") or string.find(mat:GetShader(), "Cable") then - local t = mat:GetString("$basetexture") - if t then - local params = {} - params["$basetexture"] = t - params["$vertexcolor"] = 1 - params["$vertexalpha"] = 1 - - mat = CreateMaterial(strImage .. "_DImage", "UnlitGeneric", params) - end - end - - self:SetMaterial2(mat) + -- + -- If it's a vertexlitgeneric material we need to change it to be + -- UnlitGeneric so it doesn't go dark when we enter a dark room + -- and flicker all about + -- + + local mat = self:GetMaterial2() + local strImage = mat:GetName() + + if + string.find(mat:GetShader(), "VertexLitGeneric") or string.find(mat:GetShader(), "Cable") + then + local t = mat:GetString("$basetexture") + if t then + local params = {} + params["$basetexture"] = t + params["$vertexcolor"] = 1 + params["$vertexalpha"] = 1 + + mat = CreateMaterial(strImage .. "_DImage", "UnlitGeneric", params) + end + end + + self:SetMaterial2(mat) end --- -- @local function PANEL:FixVertexLitMaterialOverlay() - -- - -- If it's a vertexlitgeneric material we need to change it to be - -- UnlitGeneric so it doesn't go dark when we enter a dark room - -- and flicker all about - -- - - local mat = self:GetMaterialOverlay() - local strImage = mat:GetName() - - if string.find(mat:GetShader(), "VertexLitGeneric") or string.find(mat:GetShader(), "Cable") then - local t = mat:GetString("$basetexture") - if t then - local params = {} - params["$basetexture"] = t - params["$vertexcolor"] = 1 - params["$vertexalpha"] = 1 - - mat = CreateMaterial(strImage .. "_DImage", "UnlitGeneric", params) - end - end - - self:SetMaterialOverlay(mat) + -- + -- If it's a vertexlitgeneric material we need to change it to be + -- UnlitGeneric so it doesn't go dark when we enter a dark room + -- and flicker all about + -- + + local mat = self:GetMaterialOverlay() + local strImage = mat:GetName() + + if + string.find(mat:GetShader(), "VertexLitGeneric") or string.find(mat:GetShader(), "Cable") + then + local t = mat:GetString("$basetexture") + if t then + local params = {} + params["$basetexture"] = t + params["$vertexcolor"] = 1 + params["$vertexalpha"] = 1 + + mat = CreateMaterial(strImage .. "_DImage", "UnlitGeneric", params) + end + end + + self:SetMaterialOverlay(mat) end --- -- @local function PANEL:FixVertexLitRoleIcon() - -- - -- If it's a vertexlitgeneric material we need to change it to be - -- UnlitGeneric so it doesn't go dark when we enter a dark room - -- and flicker all about - -- - - local mat = self:GetRoleIcon() - local strImage = mat:GetName() - - if string.find(mat:GetShader(), "VertexLitGeneric") or string.find(mat:GetShader(), "Cable") then - local t = mat:GetString("$basetexture") - if t then - local params = {} - params["$basetexture"] = t - params["$vertexcolor"] = 1 - params["$vertexalpha"] = 1 - - mat = CreateMaterial(strImage .. "_DImage", "UnlitGeneric", params) - end - end - - self:SetRoleIcon(mat) + -- + -- If it's a vertexlitgeneric material we need to change it to be + -- UnlitGeneric so it doesn't go dark when we enter a dark room + -- and flicker all about + -- + + local mat = self:GetRoleIcon() + local strImage = mat:GetName() + + if + string.find(mat:GetShader(), "VertexLitGeneric") or string.find(mat:GetShader(), "Cable") + then + local t = mat:GetString("$basetexture") + if t then + local params = {} + params["$basetexture"] = t + params["$vertexcolor"] = 1 + params["$vertexalpha"] = 1 + + mat = CreateMaterial(strImage .. "_DImage", "UnlitGeneric", params) + end + end + + self:SetRoleIcon(mat) end --- -- @ignore function PANEL:SizeToContents() - self:SetSize(self.ActualWidth, self.ActualHeight) + self:SetSize(self.ActualWidth, self.ActualHeight) end --- -- @ignore function PANEL:Paint() - self:PaintAt(0, 0, self:GetWide(), self:GetTall()) + self:PaintAt(0, 0, self:GetWide(), self:GetTall()) end --- @@ -443,102 +455,102 @@ end -- @return[default=true] boolean -- @realm client function PANEL:PaintAt(x, y, dw, dh) - dw, dh = dw or self:GetWide(), dh or self:GetTall() + dw, dh = dw or self:GetWide(), dh or self:GetTall() - self:LoadMaterial() + self:LoadMaterial() - if not self.m_Material then - return true - end + if not self.m_Material then + return true + end - surface.SetMaterial(self.m_Material) - surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(self.m_Material) + surface.SetDrawColor(255, 255, 255, 255) - if self:GetKeepAspect() then - local w = self.ActualWidth - local h = self.ActualHeight + if self:GetKeepAspect() then + local w = self.ActualWidth + local h = self.ActualHeight - -- Image is bigger than panel, shrink to suitable size - if w > dw and h > dh then - if w > dw then - local diff = dw / w + -- Image is bigger than panel, shrink to suitable size + if w > dw and h > dh then + if w > dw then + local diff = dw / w - w = w * diff - h = h * diff - end + w = w * diff + h = h * diff + end - if h > dh then - local diff = dh / h + if h > dh then + local diff = dh / h - w = w * diff - h = h * diff - end - end + w = w * diff + h = h * diff + end + end - if w < dw then - local diff = dw / w + if w < dw then + local diff = dw / w - w = w * diff - h = h * diff - end + w = w * diff + h = h * diff + end - if h < dh then - local diff = dh / h + if h < dh then + local diff = dh / h - w = w * diff - h = h * diff - end + w = w * diff + h = h * diff + end - local OffX = (dw - w) * 0.5 - local OffY = (dh - h) * 0.5 + local OffX = (dw - w) * 0.5 + local OffY = (dh - h) * 0.5 - local tx = OffX + x - local ty = OffY + y + local tx = OffX + x + local ty = OffY + y - surface.DrawTexturedRect(tx, ty, w, h) + surface.DrawTexturedRect(tx, ty, w, h) - if self.m_Material2 then - surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a) - surface.SetMaterial(self.m_Material2) - surface.DrawTexturedRect(tx, ty, w, h) - end + if self.m_Material2 then + surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a) + surface.SetMaterial(self.m_Material2) + surface.DrawTexturedRect(tx, ty, w, h) + end - if self.m_MaterialOverlay then - surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a) - surface.SetMaterial(self.m_MaterialOverlay) - surface.DrawTexturedRect(tx, ty, w, h) - end + if self.m_MaterialOverlay then + surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a) + surface.SetMaterial(self.m_MaterialOverlay) + surface.DrawTexturedRect(tx, ty, w, h) + end - if self.m_RoleIcon then - surface.SetDrawColor(255, 255, 255, 255) - surface.SetMaterial(self.m_RoleIcon) - surface.DrawTexturedRect(tx + 8, ty + 8, w - 16, h - 16) - end + if self.m_RoleIcon then + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(self.m_RoleIcon) + surface.DrawTexturedRect(tx + 8, ty + 8, w - 16, h - 16) + end - return true - end + return true + end - surface.DrawTexturedRect(x, y, dw, dh) + surface.DrawTexturedRect(x, y, dw, dh) - if self.m_Material2 then - surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a) - surface.SetMaterial(self.m_Material2) - surface.DrawTexturedRect(x, y, dw, dh) - end + if self.m_Material2 then + surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a) + surface.SetMaterial(self.m_Material2) + surface.DrawTexturedRect(x, y, dw, dh) + end - if self.m_MaterialOverlay then - surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a) - surface.SetMaterial(self.m_MaterialOverlay) - surface.DrawTexturedRect(x, y, dw, dh) - end + if self.m_MaterialOverlay then + surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a) + surface.SetMaterial(self.m_MaterialOverlay) + surface.DrawTexturedRect(x, y, dw, dh) + end - if self.m_RoleIcon then - surface.SetDrawColor(255, 255, 255, 255) - surface.SetMaterial(self.m_RoleIcon) - surface.DrawTexturedRect(x + 8, y + 8, dw - 16, dh - 16) - end + if self.m_RoleIcon then + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(self.m_RoleIcon) + surface.DrawTexturedRect(x + 8, y + 8, dw - 16, dh - 16) + end - return true + return true end --- @@ -548,11 +560,11 @@ end -- @param number height -- @realm client function PANEL:GenerateExample(className, propertySheet, width, height) - local ctrl = vgui.Create(className) - ctrl:SetImage("brick/brick_model") - ctrl:SetSize(200, 200) + local ctrl = vgui.Create(className) + ctrl:SetImage("brick/brick_model") + ctrl:SetSize(200, 200) - propertySheet:AddSheet(className, ctrl, nil, true, true) + propertySheet:AddSheet(className, ctrl, nil, true, true) end derma.DefineControl("DRoleImage", "A simple role image", PANEL, "DPanel") diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_progressbar.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_progressbar.lua index b1ff1ee31..d090b275e 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_progressbar.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_progressbar.lua @@ -30,99 +30,101 @@ AccessorFunc(PANEL, "m_Color", "Color") --- -- @ignore function PANEL:Init() - self.Label = vgui.Create("DLabel", self) - self.Label:SetFont("DefaultSmall") - self.Label:SetColor(Color(0, 0, 0)) - - self:SetMin(0) - self:SetMax(1000) - self:SetValue(253) - self:SetColor(Color(50, 205, 255, 255)) + self.Label = vgui.Create("DLabel", self) + self.Label:SetFont("DefaultSmall") + self.Label:SetColor(Color(0, 0, 0)) + + self:SetMin(0) + self:SetMax(1000) + self:SetValue(253) + self:SetColor(Color(50, 205, 255, 255)) end --- -- @realm client function PANEL:LabelAsPercentage() - self.m_bLabelAsPercentage = true + self.m_bLabelAsPercentage = true - self:UpdateText() + self:UpdateText() end --- -- @param number i -- @realm client function PANEL:SetMin(i) - self.m_iMin = i + self.m_iMin = i - self:UpdateText() + self:UpdateText() end --- -- @param number i -- @realm client function PANEL:SetMax(i) - self.m_iMax = i + self.m_iMax = i - self:UpdateText() + self:UpdateText() end --- -- @param number i -- @realm client function PANEL:SetValue(i) - self.m_iValue = i + self.m_iValue = i - self:UpdateText() + self:UpdateText() end --- -- @realm client function PANEL:UpdateText() - if not self.m_iMax or not self.m_iMin or not self.m_iValue then return end + if not self.m_iMax or not self.m_iMin or not self.m_iValue then + return + end - local fDelta = 0 + local fDelta = 0 - if self.m_iMax - self.m_iMin ~= 0 then - fDelta = (self.m_iValue - self.m_iMin) / (self.m_iMax - self.m_iMin) - end + if self.m_iMax - self.m_iMin ~= 0 then + fDelta = (self.m_iValue - self.m_iMin) / (self.m_iMax - self.m_iMin) + end - if self.m_bLabelAsPercentage then - self.Label:SetText(Format("%.2f%%", fDelta * 100)) + if self.m_bLabelAsPercentage then + self.Label:SetText(Format("%.2f%%", fDelta * 100)) - return - end + return + end - if self.m_iMin == 0 then - self.Label:SetText(Format("%i / %i", self.m_iValue, self.m_iMax)) - end + if self.m_iMin == 0 then + self.Label:SetText(Format("%i / %i", self.m_iValue, self.m_iMax)) + end end --- -- @ignore function PANEL:PerformLayout() - self.Label:SizeToContents() - self.Label:AlignRight(5) - self.Label:CenterVertical() + self.Label:SizeToContents() + self.Label:AlignRight(5) + self.Label:CenterVertical() end --- -- @ignore function PANEL:Paint() - local fDelta = 0 + local fDelta = 0 - if self.m_iMax - self.m_iMin ~= 0 then - fDelta = (self.m_iValue - self.m_iMin) / (self.m_iMax - self.m_iMin) - end + if self.m_iMax - self.m_iMin ~= 0 then + fDelta = (self.m_iValue - self.m_iMin) / (self.m_iMax - self.m_iMin) + end - local Width = self:GetWide() + local Width = self:GetWide() - surface.SetDrawColor(0, 0, 0, 170) - surface.DrawRect(0, 0, Width, self:GetTall()) + surface.SetDrawColor(0, 0, 0, 170) + surface.DrawRect(0, 0, Width, self:GetTall()) - surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a * 0.5) - surface.DrawRect(2, 2, Width - 4, self:GetTall() - 4) - surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a) - surface.DrawRect(2, 2, Width * fDelta - 4, self:GetTall() - 4) + surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a * 0.5) + surface.DrawRect(2, 2, Width - 4, self:GetTall() - 4) + surface.SetDrawColor(self.m_Color.r, self.m_Color.g, self.m_Color.b, self.m_Color.a) + surface.DrawRect(2, 2, Width * fDelta - 4, self:GetTall() - 4) end vgui.Register("TTTProgressBar", PANEL, "DPanel") diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_sb_info.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_sb_info.lua index 3fa0ad85b..6517a4cca 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_sb_info.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_sb_info.lua @@ -17,31 +17,31 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - self.Player = nil + self.Player = nil - --self:SetMouseInputEnabled(false) + --self:SetMouseInputEnabled(false) end --- -- @param Player ply -- @realm client function PANEL:SetPlayer(ply) - self.Player = ply + self.Player = ply - self:UpdatePlayerData() + self:UpdatePlayerData() end --- -- @realm client function PANEL:UpdatePlayerData() - -- override me + -- override me end --- -- @return[default=true] boolean -- @realm client function PANEL:Paint() - return true + return true end vgui.Register("TTTScorePlayerInfoBase", PANEL, "Panel") @@ -56,113 +56,117 @@ PANEL = {} --- -- @ignore function PANEL:Init() - self.List = vgui.Create("DPanelSelect", self) - self.List:EnableHorizontal(true) + self.List = vgui.Create("DPanelSelect", self) + self.List:EnableHorizontal(true) - if self.List.VBar then - self.List.VBar:Remove() - self.List.VBar = nil - end + if self.List.VBar then + self.List.VBar:Remove() + self.List.VBar = nil + end - self.Scroll = vgui.Create("DHorizontalScroller", self.List) + self.Scroll = vgui.Create("DHorizontalScroller", self.List) - self.Help = vgui.Create("DLabel", self) - self.Help:SetText(GetTranslation("sb_info_help")) - self.Help:SetFont("treb_small") - self.Help:SetVisible(false) + self.Help = vgui.Create("DLabel", self) + self.Help:SetText(GetTranslation("sb_info_help")) + self.Help:SetFont("treb_small") + self.Help:SetVisible(false) end --- -- @ignore function PANEL:PerformLayout() - self:SetSize(self:GetWide(), 75) + self:SetSize(self:GetWide(), 75) - self.List:SetPos(0, 0) - self.List:SetSize(self:GetWide(), 70) - self.List:SetSpacing(1) - self.List:SetPadding(2) - self.List:SetPaintBackground(false) + self.List:SetPos(0, 0) + self.List:SetSize(self:GetWide(), 70) + self.List:SetSpacing(1) + self.List:SetPadding(2) + self.List:SetPaintBackground(false) - self.Scroll:StretchToParent(3, 3, 3, 3) + self.Scroll:StretchToParent(3, 3, 3, 3) - self.Help:SizeToContents() - self.Help:SetPos(5, 5) + self.Help:SizeToContents() + self.Help:SetPos(5, 5) end --- -- @realm client function PANEL:UpdatePlayerData() - if not IsValid(self.Player) then return end + if not IsValid(self.Player) then + return + end - if not self.Player.search_result or not self.Player.search_result.show_sb then - self.Help:SetVisible(true) + if not self.Player.bodySearchResult or not self.Player.bodySearchResult.show_sb then + self.Help:SetVisible(true) - return - end + return + end - self.Help:SetVisible(false) + self.Help:SetVisible(false) - if self.Search == self.Player.search_result then return end + if self.Search == self.Player.bodySearchResult then + return + end - self.List:Clear(true) + self.List:Clear(true) - self.Scroll.Panels = {} + self.Scroll.Panels = {} - local search_raw = self.Player.search_result + local search_raw = self.Player.bodySearchResult - -- standard search result preproc - local search = PreprocSearch(search_raw) + -- standard search result preproc + local search = bodysearch.PreprocSearch(search_raw) - -- wipe some stuff we don't need, like id - search.nick = nil + -- wipe some stuff we don't need, like id + search.nick = nil - -- Create table of SimpleIcons, each standing for a piece of search - -- information. - for t, info in SortedPairsByMemberValue(search, "p") do - local ic - local icon = info.img + -- Create table of SimpleIcons, each standing for a piece of search + -- information. + for t, info in SortedPairsByMemberValue(search, "p") do + local ic + local icon = info.img - -- Certain items need a special icon conveying additional information - if t == "lastid" then - ic = vgui.Create("SimpleIconAvatar", self.List) - ic:SetPlayer(info.ply) - ic:SetAvatarSize(24) - elseif t == "dtime" then - ic = vgui.Create("SimpleIconLabelled", self.List) - ic:SetIconText(info.text_icon) - elseif t == "role" then - ic = vgui.Create("SimpleRoleIcon", self.List) + -- Certain items need a special icon conveying additional information + if t == "lastid" then + ic = vgui.Create("SimpleIconAvatar", self.List) + ic:SetPlayer(info.ply) + ic:SetAvatarSize(24) + elseif t == "dtime" then + ic = vgui.Create("SimpleIconLabelled", self.List) + ic:SetIconText(info.text_icon) + elseif t == "role" then + ic = vgui.Create("SimpleRoleIcon", self.List) - ic.Icon:SetImage2("vgui/ttt/dynamic/icon_base_base") - ic.Icon:SetImageOverlay("vgui/ttt/dynamic/icon_base_base_overlay") - ic.Icon:SetRoleIconImage(icon) + ic.Icon:SetImage2("vgui/ttt/dynamic/icon_base_base") + ic.Icon:SetImageOverlay("vgui/ttt/dynamic/icon_base_base_overlay") + ic.Icon:SetRoleIconImage(icon) - icon = "vgui/ttt/dynamic/icon_base" - else - ic = vgui.Create("SimpleIcon", self.List) - end + icon = "vgui/ttt/dynamic/icon_base" + else + ic = vgui.Create("SimpleIcon", self.List) + end - ic:SetIconSize(64) - ic:SetIcon(icon) + ic:SetIconSize(64) + ic:SetIcon(icon) - if info.color then - ic:SetIconColor(info.color) - end + if info.color then + ic:SetIconColor(info.color) + end - ic:SetTooltip(info.text) + ic:SetTooltip(info.text) - ic.info_type = t + ic.info_type = t - self.List:AddPanel(ic) - self.Scroll:AddPanel(ic) - end + self.List:AddPanel(ic) + self.Scroll:AddPanel(ic) + end - self.Search = search_raw + self.Search = search_raw - self.List:InvalidateLayout() - self.Scroll:InvalidateLayout() + self.List:InvalidateLayout() + self.Scroll:InvalidateLayout() - self:PerformLayout() + self:PerformLayout() end vgui.Register("TTTScorePlayerInfoSearch", PANEL, "TTTScorePlayerInfoBase") @@ -173,11 +177,11 @@ vgui.Register("TTTScorePlayerInfoSearch", PANEL, "TTTScorePlayerInfoBase") --- TTTScoreboard.Tags = { - {txt = "sb_tag_friend", color = COLOR_GREEN}, - {txt = "sb_tag_susp", color = COLOR_YELLOW}, - {txt = "sb_tag_avoid", color = Color(255, 150, 0, 255)}, - {txt = "sb_tag_kill", color = COLOR_RED}, - {txt = "sb_tag_miss", color = Color(130, 190, 130, 255)} + { txt = "sb_tag_friend", color = COLOR_GREEN }, + { txt = "sb_tag_susp", color = COLOR_YELLOW }, + { txt = "sb_tag_avoid", color = Color(255, 150, 0, 255) }, + { txt = "sb_tag_kill", color = COLOR_RED }, + { txt = "sb_tag_miss", color = Color(130, 190, 130, 255) }, } PANEL = {} @@ -185,59 +189,57 @@ PANEL = {} --- -- @ignore function PANEL:Init() - self.TagButtons = {} + self.TagButtons = {} - for k, tag in ipairs(TTTScoreboard.Tags) do - self.TagButtons[k] = vgui.Create("TagButton", self) - self.TagButtons[k]:SetupTag(tag) - end + for k, tag in ipairs(TTTScoreboard.Tags) do + self.TagButtons[k] = vgui.Create("TagButton", self) + self.TagButtons[k]:SetupTag(tag) + end - --self:SetMouseInputEnabled(false) + --self:SetMouseInputEnabled(false) end --- -- @param Player ply -- @realm client function PANEL:SetPlayer(ply) - self.Player = ply + self.Player = ply - for _, btn in ipairs(self.TagButtons) do - btn:SetPlayer(ply) - end + for _, btn in ipairs(self.TagButtons) do + btn:SetPlayer(ply) + end - self:InvalidateLayout() + self:InvalidateLayout() end --- -- @ignore -function PANEL:ApplySchemeSettings() - -end +function PANEL:ApplySchemeSettings() end --- -- @realm client function PANEL:UpdateTag() - self:GetParent():UpdatePlayerData() - self:GetParent():SetOpen(false) + self:GetParent():UpdatePlayerData() + self:GetParent():SetOpen(false) end --- -- @ignore function PANEL:PerformLayout() - self:SetSize(self:GetWide(), 30) + self:SetSize(self:GetWide(), 30) - local margin = 10 - local x = 250 -- 29 - local y = 0 + local margin = 10 + local x = 250 -- 29 + local y = 0 - for _, btn in ipairs(self.TagButtons) do - btn:SetPos(x, y) - btn:SetCursor("hand") - btn:SizeToContents() - btn:PerformLayout() + for _, btn in ipairs(self.TagButtons) do + btn:SetPos(x, y) + btn:SetCursor("hand") + btn:SizeToContents() + btn:PerformLayout() - x = x + btn:GetWide() + margin - end + x = x + btn:GetWide() + margin + end end vgui.Register("TTTScorePlayerInfoTags", PANEL, "TTTScorePlayerInfoBase") @@ -252,83 +254,83 @@ PANEL = {} --- -- @ignore function PANEL:Init() - self.Player = nil + self.Player = nil - self:SetText("") - self:SetMouseInputEnabled(true) - self:SetKeyboardInputEnabled(false) + self:SetText("") + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(false) - self:SetTall(20) + self:SetTall(20) - self:SetPaintBackgroundEnabled(false) - self:SetPaintBorderEnabled(false) + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) - self:SetPaintBackground(false) - self:SetDrawBorder(false) + self:SetPaintBackground(false) + self:SetDrawBorder(false) - self:SetFont("treb_small") - self:SetTextColor(self.Tag and self.Tag.color or COLOR_WHITE) + self:SetFont("treb_small") + self:SetTextColor(self.Tag and self.Tag.color or COLOR_WHITE) end --- -- @param Player ply -- @realm client function PANEL:SetPlayer(ply) - self.Player = ply + self.Player = ply end --- -- @param table tag -- @realm client function PANEL:SetupTag(tag) - self.Tag = tag + self.Tag = tag - self.Color = tag.color - self.Text = tag.txt + self.Color = tag.color + self.Text = tag.txt - self:SetTextColor(self.Tag and self.Tag.color or COLOR_WHITE) + self:SetTextColor(self.Tag and self.Tag.color or COLOR_WHITE) end --- -- @ignore function PANEL:PerformLayout() - self:SetText(self.Tag and GetTranslation(self.Tag.txt) or "") - self:SizeToContents() - self:SetContentAlignment(5) - self:SetSize(self:GetWide() + 10, self:GetTall() + 3) + self:SetText(self.Tag and GetTranslation(self.Tag.txt) or "") + self:SizeToContents() + self:SetContentAlignment(5) + self:SetSize(self:GetWide() + 10, self:GetTall() + 3) end --- -- @realm client function PANEL:DoRightClick() - if IsValid(self.Player) then - self.Player.sb_tag = nil + if IsValid(self.Player) then + self.Player.sb_tag = nil - self:GetParent():UpdateTag() - end + self:GetParent():UpdateTag() + end end --- -- @realm client function PANEL:DoClick() - if IsValid(self.Player) then - if self.Player.sb_tag == self.Tag then - self.Player.sb_tag = nil - else - self.Player.sb_tag = self.Tag - end - - self:GetParent():UpdateTag() - end + if IsValid(self.Player) then + if self.Player.sb_tag == self.Tag then + self.Player.sb_tag = nil + else + self.Player.sb_tag = self.Tag + end + + self:GetParent():UpdateTag() + end end --- -- @realm client function PANEL:PaintOver() - if self.Player and self.Player.sb_tag == self.Tag then - surface.SetDrawColor(255, 200, 0, 255) - surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall()) - end + if self.Player and self.Player.sb_tag == self.Tag then + surface.SetDrawColor(255, 200, 0, 255) + surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall()) + end end vgui.Register("TagButton", PANEL, "DButton") diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_sb_main.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_sb_main.lua index b4104532d..01d97468a 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_sb_main.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_sb_main.lua @@ -24,10 +24,12 @@ ttt_include("vgui__cl_sb_team") --- -- @realm client -- @todo add Team! +-- stylua: ignore local cv_ttt_scoreboard_sorting = CreateConVar("ttt_scoreboard_sorting", "name", FCVAR_ARCHIVE, "name | role | karma | score | deaths | ping") --- -- @realm client +-- stylua: ignore local cv_ttt_scoreboard_ascending = CreateConVar("ttt_scoreboard_ascending", "1", FCVAR_ARCHIVE, "Should scoreboard ordering be in ascending order") GROUP_TERROR = 1 @@ -42,15 +44,15 @@ GROUP_COUNT = 4 -- @param string name -- @realm client function AddScoreGroup(name) - if _G["GROUP_" .. name] then - error("Group of name '" .. name .. "' already exists!") + if _G["GROUP_" .. name] then + error("Group of name '" .. name .. "' already exists!") - return - end + return + end - GROUP_COUNT = GROUP_COUNT + 1 + GROUP_COUNT = GROUP_COUNT + 1 - _G["GROUP_" .. name] = GROUP_COUNT + _G["GROUP_" .. name] = GROUP_COUNT end --- @@ -59,102 +61,104 @@ end -- @return number|string -- @realm client function ScoreGroup(ply) - if not IsValid(ply) then -- will not match any group panel - return -1 - end - - --- - -- @realm client - local group = hook.Run("TTTScoreGroup", ply) - - if group then -- If that hook gave us a group, use it - return group - end - - if DetectiveMode() and ply:IsSpec() and not ply:Alive() then - if ply:TTT2NETGetBool("body_found", false) then - return GROUP_FOUND - else - local client = LocalPlayer() - - -- To terrorists, missing players show as alive - if client:IsSpec() - or client:IsActive() and client:GetSubRoleData().isOmniscientRole - or GetRoundState() ~= ROUND_ACTIVE and client:IsTerror() - then - return GROUP_NOTFOUND - else - return GROUP_TERROR - end - end - end - - return ply:IsTerror() and GROUP_TERROR or GROUP_SPEC + if not IsValid(ply) then -- will not match any group panel + return -1 + end + + --- + -- @realm client + -- stylua: ignore + local group = hook.Run("TTTScoreGroup", ply) + + if group then -- If that hook gave us a group, use it + return group + end + + if DetectiveMode() and ply:IsSpec() and not ply:Alive() then + if ply:TTT2NETGetBool("body_found", false) then + return GROUP_FOUND + else + local client = LocalPlayer() + + -- To terrorists, missing players show as alive + if + client:IsSpec() + or client:IsActive() and client:GetSubRoleData().isOmniscientRole + or GetRoundState() ~= ROUND_ACTIVE and client:IsTerror() + then + return GROUP_NOTFOUND + else + return GROUP_TERROR + end + end + end + + return ply:IsTerror() and GROUP_TERROR or GROUP_SPEC end TTTScoreboard = TTTScoreboard or {} TTTScoreboard.Logo = surface.GetTextureID("vgui/ttt/score_logo_2") surface.CreateFont("cool_small", { - font = "coolvetica", - size = 20, - weight = 400 + font = "coolvetica", + size = 20, + weight = 400, }) surface.CreateFont("cool_large", { - font = "coolvetica", - size = 24, - weight = 400 + font = "coolvetica", + size = 24, + weight = 400, }) surface.CreateFont("treb_small", { - font = "Trebuchet18", - size = 14, - weight = 700 + font = "Trebuchet18", + size = 14, + weight = 700, }) local function UntilMapChange() - local rounds_left = max(0, GetGlobalInt("ttt_rounds_left", 6)) - local time_left = floor(max(0, (GetGlobalInt("ttt_time_limit_minutes") or 60) * 60 - CurTime())) - local h = floor(time_left / 3600) + local rounds_left = max(0, GetGlobalInt("ttt_rounds_left", 6)) + local time_left = floor(max(0, (GetGlobalInt("ttt_time_limit_minutes") or 60) * 60 - CurTime())) + local h = floor(time_left / 3600) - time_left = time_left - floor(h * 3600) + time_left = time_left - floor(h * 3600) - local m = floor(time_left / 60) + local m = floor(time_left / 60) - time_left = time_left - floor(m * 60) + time_left = time_left - floor(m * 60) - local s = floor(time_left) + local s = floor(time_left) - return rounds_left, string.format("%02i:%02i:%02i", h, m, s) + return rounds_left, string.format("%02i:%02i:%02i", h, m, s) end --- -- Comparison functions used to sort scoreboard sboard_sort = { - name = function(plya, plyb) - return 0 -- Automatically sorts by name if this returns 0 - end, - ping = function(plya, plyb) - return plya:Ping() - plyb:Ping() - end, - deaths = function(plya, plyb) - return plya:Deaths() - plyb:Deaths() - end, - score = function(plya, plyb) - return plya:Frags() - plyb:Frags() - end, - role = function(plya, plyb) - local comp = (plya:GetSubRole() or 0) - (plyb:GetSubRole() or 0) - -- Reverse on purpose - -- otherwise the default ascending order puts boring innocents first - comp = 0 - comp - - return comp - end, - karma = function(plya, plyb) - return (plya:GetBaseKarma() or 0) - (plyb:GetBaseKarma() or 0) - end + name = function(plya, plyb) + return 0 -- Automatically sorts by name if this returns 0 + end, + ping = function(plya, plyb) + return plya:Ping() - plyb:Ping() + end, + deaths = function(plya, plyb) + return plya:Deaths() - plyb:Deaths() + end, + score = function(plya, plyb) + return plya:Frags() - plyb:Frags() + end, + role = function(plya, plyb) + local comp = (plya:GetSubRole() or 0) - (plyb:GetSubRole() or 0) + -- Reverse on purpose + -- otherwise the default ascending order puts boring innocents first + comp = 0 - comp + + return comp + end, + karma = function(plya, plyb) + return (plya:GetBaseKarma() or 0) - (plyb:GetBaseKarma() or 0) + end, } --- @@ -166,139 +170,146 @@ local PANEL = {} --- -- @ignore function PANEL:Init() - self.hostdesc = vgui.Create("DLabel", self) - self.hostdesc:SetText(GetTranslation("sb_playing")) - self.hostdesc:SetContentAlignment(9) - - self.hostname = vgui.Create("DLabel", self) - self.hostname:SetText(GetHostName()) - self.hostname:SetContentAlignment(6) - - self.mapchange = vgui.Create("DLabel", self) - self.mapchange:SetText("Map changes in 00 rounds or in 00:00:00") - self.mapchange:SetContentAlignment(9) - - self.mapchange.Think = function (sf) - if GetGlobalBool("ttt_session_limits_enabled") then - local r, t = UntilMapChange() - sf:SetText(GetPTranslation("sb_mapchange", {num = r, time = t})) - else - sf:SetText(GetTranslation("sb_mapchange_disabled")) - end - - sf:SizeToContents() - end - - self.ply_frame = vgui.Create("TTTPlayerFrame", self) - self.ply_groups = {} - - local t = vgui.Create("TTTScoreGroup", self.ply_frame:GetCanvas()) - t:SetGroupInfo(GetTranslation("terrorists"), Color(0, 200, 0, 100), GROUP_TERROR) - self.ply_groups[GROUP_TERROR] = t - - t = vgui.Create("TTTScoreGroup", self.ply_frame:GetCanvas()) - t:SetGroupInfo(GetTranslation("spectators"), Color(200, 200, 0, 100), GROUP_SPEC) - self.ply_groups[GROUP_SPEC] = t - - if DetectiveMode() then - t = vgui.Create("TTTScoreGroup", self.ply_frame:GetCanvas()) - t:SetGroupInfo(GetTranslation("sb_mia"), Color(130, 190, 130, 100), GROUP_NOTFOUND) - self.ply_groups[GROUP_NOTFOUND] = t - - t = vgui.Create("TTTScoreGroup", self.ply_frame:GetCanvas()) - t:SetGroupInfo(GetTranslation("sb_confirmed"), Color(130, 170, 10, 100), GROUP_FOUND) - self.ply_groups[GROUP_FOUND] = t - end - - --- - -- @realm client - hook.Run("TTTScoreGroups", self.ply_frame:GetCanvas(), self.ply_groups) - - -- the various score column headers - self.cols = {} - - self:AddColumn(GetTranslation("sb_ping"), nil, nil, "ping") - self:AddColumn(GetTranslation("sb_deaths"), nil, nil, "deaths") - self:AddColumn(GetTranslation("sb_score"), nil, nil, "score") - - if KARMA.IsEnabled() then - self:AddColumn(GetTranslation("sb_karma"), nil, nil, "karma") - end - - self.sort_headers = {} - - -- Reuse some translations - self:AddFakeColumn(GetTranslation("sb_sortby"), nil, nil, nil) -- "Sort by:" - self:AddFakeColumn(GetTranslation("equip_spec_name"), nil, nil, "name") - self:AddFakeColumn(GetTranslation("col_roles"), nil, nil, "role") - - --- - -- Let hooks add their column headers (via AddColumn() or AddFakeColumn()) - -- @realm client - hook.Run("TTTScoreboardColumns", self) - - self:UpdateScoreboard() - self:StartUpdateTimer() + self.hostdesc = vgui.Create("DLabel", self) + self.hostdesc:SetText(GetTranslation("sb_playing")) + self.hostdesc:SetContentAlignment(9) + + self.hostname = vgui.Create("DLabel", self) + self.hostname:SetText(GetHostName()) + self.hostname:SetContentAlignment(6) + + self.mapchange = vgui.Create("DLabel", self) + self.mapchange:SetText("Map changes in 00 rounds or in 00:00:00") + self.mapchange:SetContentAlignment(9) + + self.mapchange.Think = function(sf) + if GetGlobalBool("ttt_session_limits_enabled") then + local r, t = UntilMapChange() + sf:SetText(GetPTranslation("sb_mapchange", { num = r, time = t })) + else + sf:SetText(GetTranslation("sb_mapchange_disabled")) + end + + sf:SizeToContents() + end + + self.ply_frame = vgui.Create("TTTPlayerFrame", self) + self.ply_groups = {} + + local t = vgui.Create("TTTScoreGroup", self.ply_frame:GetCanvas()) + t:SetGroupInfo(GetTranslation("terrorists"), Color(0, 200, 0, 100), GROUP_TERROR) + self.ply_groups[GROUP_TERROR] = t + + t = vgui.Create("TTTScoreGroup", self.ply_frame:GetCanvas()) + t:SetGroupInfo(GetTranslation("spectators"), Color(200, 200, 0, 100), GROUP_SPEC) + self.ply_groups[GROUP_SPEC] = t + + if DetectiveMode() then + t = vgui.Create("TTTScoreGroup", self.ply_frame:GetCanvas()) + t:SetGroupInfo(GetTranslation("sb_mia"), Color(130, 190, 130, 100), GROUP_NOTFOUND) + self.ply_groups[GROUP_NOTFOUND] = t + + t = vgui.Create("TTTScoreGroup", self.ply_frame:GetCanvas()) + t:SetGroupInfo(GetTranslation("sb_confirmed"), Color(130, 170, 10, 100), GROUP_FOUND) + self.ply_groups[GROUP_FOUND] = t + end + + --- + -- @realm client + -- stylua: ignore + hook.Run("TTTScoreGroups", self.ply_frame:GetCanvas(), self.ply_groups) + + -- the various score column headers + self.cols = {} + + self:AddColumn(GetTranslation("sb_ping"), nil, nil, "ping") + self:AddColumn(GetTranslation("sb_deaths"), nil, nil, "deaths") + self:AddColumn(GetTranslation("sb_score"), nil, nil, "score") + + if KARMA.IsEnabled() then + self:AddColumn(GetTranslation("sb_karma"), nil, nil, "karma") + end + + self.sort_headers = {} + + -- Reuse some translations + self:AddFakeColumn(GetTranslation("sb_sortby"), nil, nil, nil) -- "Sort by:" + self:AddFakeColumn(GetTranslation("equip_spec_name"), nil, nil, "name") + self:AddFakeColumn(GetTranslation("col_roles"), nil, nil, "role") + + --- + -- Let hooks add their column headers (via AddColumn() or AddFakeColumn()) + -- @realm client + -- stylua: ignore + hook.Run("TTTScoreboardColumns", self) + + self:UpdateScoreboard() + self:StartUpdateTimer() end local function sort_header_handler(self_, lbl) - return function() - surface.PlaySound("ui/buttonclick.wav") - - if lbl.HeadingIdentifier == cv_ttt_scoreboard_sorting:GetString() then - cv_ttt_scoreboard_ascending:SetBool(not cv_ttt_scoreboard_ascending:GetBool()) - else - cv_ttt_scoreboard_sorting:SetString(lbl.HeadingIdentifier) - cv_ttt_scoreboard_ascending:SetBool(true) - end - - for _, scoregroup in pairs(self_.ply_groups) do - scoregroup:UpdateSortCache() - scoregroup:InvalidateLayout() - end - - self_:ApplySchemeSettings() - end + return function() + surface.PlaySound("ui/buttonclick.wav") + + if lbl.HeadingIdentifier == cv_ttt_scoreboard_sorting:GetString() then + cv_ttt_scoreboard_ascending:SetBool(not cv_ttt_scoreboard_ascending:GetBool()) + else + cv_ttt_scoreboard_sorting:SetString(lbl.HeadingIdentifier) + cv_ttt_scoreboard_ascending:SetBool(true) + end + + for _, scoregroup in pairs(self_.ply_groups) do + scoregroup:UpdateSortCache() + scoregroup:InvalidateLayout() + end + + self_:ApplySchemeSettings() + end end --- -- For headings only the label parameter is relevant, second param is included for -- parity with sb_row local function column_label_work(self_, table_to_add, label, width, sort_identifier, sort_func) - local lbl = vgui.Create("DLabel", self_) - lbl:SetText(label) - - local can_sort = false - - lbl.IsHeading = true - lbl.Width = width or 50 -- Retain compatibility with existing code - - if sort_identifier ~= nil then - can_sort = true - -- If we have an identifier and an existing sort function then it was a built-in - -- Otherwise... - if _G.sboard_sort[sort_identifier] == nil then - if sort_func == nil then - ErrorNoHalt("Sort ID provided without a sorting function, Label = ", label, " ; ID = ", sort_identifier) - - can_sort = false - else - _G.sboard_sort[sort_identifier] = sort_func - end - end - end - - if can_sort then - lbl:SetMouseInputEnabled(true) - lbl:SetCursor("hand") - - lbl.HeadingIdentifier = sort_identifier - lbl.DoClick = sort_header_handler(self_, lbl) - end - - table.insert(table_to_add, lbl) - - return lbl + local lbl = vgui.Create("DLabel", self_) + lbl:SetText(label) + + local can_sort = false + + lbl.IsHeading = true + lbl.Width = width or 50 -- Retain compatibility with existing code + + if sort_identifier ~= nil then + can_sort = true + -- If we have an identifier and an existing sort function then it was a built-in + -- Otherwise... + if _G.sboard_sort[sort_identifier] == nil then + if sort_func == nil then + ErrorNoHaltWithStack( + "Sort ID provided without a sorting function, Label = ", + label, + " ; ID = ", + sort_identifier + ) + + can_sort = false + else + _G.sboard_sort[sort_identifier] = sort_func + end + end + end + + if can_sort then + lbl:SetMouseInputEnabled(true) + lbl:SetCursor("hand") + + lbl.HeadingIdentifier = sort_identifier + lbl.DoClick = sort_header_handler(self_, lbl) + end + + table.insert(table_to_add, lbl) + + return lbl end --- @@ -312,7 +323,7 @@ end -- @see PANEL:AddFakeColumn -- @realm client function PANEL:AddColumn(label, _, width, sort_id, sort_func) - return column_label_work(self, self.cols, label, width, sort_id, sort_func) + return column_label_work(self, self.cols, label, width, sort_id, sort_func) end --- @@ -320,7 +331,7 @@ end -- @return table -- @realm client function PANEL:GetColumns() - return self.cols + return self.cols end --- @@ -335,29 +346,29 @@ end -- @see PANEL:AddColumn -- @realm client function PANEL:AddFakeColumn(label, _, width, sort_id, sort_func) - return column_label_work(self, self.sort_headers, label, width, sort_id, sort_func) + return column_label_work(self, self.sort_headers, label, width, sort_id, sort_func) end local function _sbfunc() - local pnl = GAMEMODE:GetScoreboardPanel() + local pnl = GAMEMODE:GetScoreboardPanel() - if IsValid(pnl) then - pnl:UpdateScoreboard() - end + if IsValid(pnl) then + pnl:UpdateScoreboard() + end end --- -- Starts the update timer (if not already started) -- @realm client function PANEL:StartUpdateTimer() - if not timer.Exists("TTTScoreboardUpdater") then - timer.Create("TTTScoreboardUpdater", 0.3, 0, _sbfunc) - end + if not timer.Exists("TTTScoreboardUpdater") then + timer.Create("TTTScoreboardUpdater", 0.3, 0, _sbfunc) + end end local colors = { - bg = Color(30, 30, 30, 235), - bar = Color(220, 180, 0, 255) + bg = Color(30, 30, 30, 235), + bar = Color(220, 180, 0, 255), } local y_logo_off = 89 @@ -365,181 +376,183 @@ local y_logo_off = 89 --- -- @ignore function PANEL:Paint() - -- Logo sticks out, so always offset bg - draw.RoundedBox(8, 0, y_logo_off, self:GetWide(), self:GetTall() - y_logo_off, colors.bg) + -- Logo sticks out, so always offset bg + draw.RoundedBox(8, 0, y_logo_off, self:GetWide(), self:GetTall() - y_logo_off, colors.bg) - -- Server name is outlined by orange/gold area - draw.RoundedBox(8, 0, y_logo_off + 25, self:GetWide(), 32, colors.bar) + -- Server name is outlined by orange/gold area + draw.RoundedBox(8, 0, y_logo_off + 25, self:GetWide(), 32, colors.bar) - -- TTT Logo - surface.SetTexture(TTTScoreboard.Logo) - surface.SetDrawColor(255, 255, 255, 255) - surface.DrawTexturedRect(5, 0, 256, 256) + -- TTT Logo + surface.SetTexture(TTTScoreboard.Logo) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(5, 0, 256, 256) end --- -- @ignore function PANEL:PerformLayout() - -- position groups and find their total size - local gy = 0 + -- position groups and find their total size + local gy = 0 - -- can't just use pairs (undefined ordering) or ipairs (group 2 and 3 might not exist) - for i = 1, GROUP_COUNT do - local group = self.ply_groups[i] + -- can't just use pairs (undefined ordering) or ipairs (group 2 and 3 might not exist) + for i = 1, GROUP_COUNT do + local group = self.ply_groups[i] - if IsValid(group) then - if group:HasRows() then - group:SetVisible(true) - group:SetPos(0, gy) - group:SetSize(self.ply_frame:GetWide(), group:GetTall()) - group:InvalidateLayout() + if IsValid(group) then + if group:HasRows() then + group:SetVisible(true) + group:SetPos(0, gy) + group:SetSize(self.ply_frame:GetWide(), group:GetTall()) + group:InvalidateLayout() - gy = gy + group:GetTall() + 5 - else - group:SetVisible(false) - end - end - end + gy = gy + group:GetTall() + 5 + else + group:SetVisible(false) + end + end + end - self.ply_frame:GetCanvas():SetSize(self.ply_frame:GetCanvas():GetWide(), gy) + self.ply_frame:GetCanvas():SetSize(self.ply_frame:GetCanvas():GetWide(), gy) - local h = y_logo_off + 110 + self.ply_frame:GetCanvas():GetTall() + local h = y_logo_off + 110 + self.ply_frame:GetCanvas():GetTall() - -- if we will have to clamp our height, enable the mouse so player can scroll - local scrolling = h > ScrH() * 0.95 - -- gui.EnableScreenClicker(scrolling) - self.ply_frame:SetScroll(scrolling) + -- if we will have to clamp our height, enable the mouse so player can scroll + local scrolling = h > ScrH() * 0.95 + -- gui.EnableScreenClicker(scrolling) + self.ply_frame:SetScroll(scrolling) - h = math.Clamp(h, 110 + y_logo_off, ScrH() * 0.95) + h = math.Clamp(h, 110 + y_logo_off, ScrH() * 0.95) - local w = math.max(ScrW() * 0.6, 640) + local w = math.max(ScrW() * 0.6, 640) - self:SetSize(w, h) - self:SetPos((ScrW() - w) * 0.5, math.min(72, (ScrH() - h) * 0.25)) + self:SetSize(w, h) + self:SetPos((ScrW() - w) * 0.5, math.min(72, (ScrH() - h) * 0.25)) - self.ply_frame:SetPos(8, y_logo_off + 109) - self.ply_frame:SetSize(self:GetWide() - 16, self:GetTall() - 109 - y_logo_off - 5) + self.ply_frame:SetPos(8, y_logo_off + 109) + self.ply_frame:SetSize(self:GetWide() - 16, self:GetTall() - 109 - y_logo_off - 5) - -- server stuff - self.hostdesc:SizeToContents() - self.hostdesc:SetPos(w - self.hostdesc:GetWide() - 8, y_logo_off + 5) + -- server stuff + self.hostdesc:SizeToContents() + self.hostdesc:SetPos(w - self.hostdesc:GetWide() - 8, y_logo_off + 5) - local hw = w - 180 - 8 + local hw = w - 180 - 8 - self.hostname:SetSize(hw, 32) - self.hostname:SetPos(w - self.hostname:GetWide() - 8, y_logo_off + 27) + self.hostname:SetSize(hw, 32) + self.hostname:SetPos(w - self.hostname:GetWide() - 8, y_logo_off + 27) - surface.SetFont("cool_large") + surface.SetFont("cool_large") - local hname = self.hostname:GetValue() - local tw = surface.GetTextSize(hname) + local hname = self.hostname:GetValue() + local tw = surface.GetTextSize(hname) - while tw > hw do - hname = string.sub(hname, 1, - 6) .. "..." - tw, th = surface.GetTextSize(hname) - end + while tw > hw do + hname = string.sub(hname, 1, -6) .. "..." + tw, th = surface.GetTextSize(hname) + end - self.hostname:SetText(hname) + self.hostname:SetText(hname) - self.mapchange:SizeToContents() - self.mapchange:SetPos(w - self.mapchange:GetWide() - 8, y_logo_off + 60) + self.mapchange:SizeToContents() + self.mapchange:SetPos(w - self.mapchange:GetWide() - 8, y_logo_off + 60) - -- score columns - local cy = y_logo_off + 90 - local cx = w - 8 - (scrolling and 16 or 0) + -- score columns + local cy = y_logo_off + 90 + local cx = w - 8 - (scrolling and 16 or 0) - for _, v in ipairs(self.cols) do - v:SizeToContents() + for _, v in ipairs(self.cols) do + v:SizeToContents() - cx = cx - v.Width + cx = cx - v.Width - v:SetPos(cx - v:GetWide() * 0.5, cy) - end + v:SetPos(cx - v:GetWide() * 0.5, cy) + end - -- sort headers - -- reuse cy - -- cx = logo width + buffer space - cx = 256 + 8 + -- sort headers + -- reuse cy + -- cx = logo width + buffer space + cx = 256 + 8 - for _, v in ipairs(self.sort_headers) do - v:SizeToContents() + for _, v in ipairs(self.sort_headers) do + v:SizeToContents() - cx = cx + v.Width + cx = cx + v.Width - v:SetPos(cx - v:GetWide() * 0.5, cy) - end + v:SetPos(cx - v:GetWide() * 0.5, cy) + end end --- -- @ignore function PANEL:ApplySchemeSettings() - self.hostdesc:SetFont("cool_small") - self.hostname:SetFont("cool_large") - self.mapchange:SetFont("treb_small") - - self.hostdesc:SetTextColor(COLOR_WHITE) - self.hostname:SetTextColor(COLOR_BLACK) - self.mapchange:SetTextColor(COLOR_WHITE) - - local sorting = cv_ttt_scoreboard_sorting:GetString() - local highlight_color = Color(175, 175, 175, 255) - local default_color = COLOR_WHITE - - for _, v in pairs(self.cols) do - v:SetFont("treb_small") - - if sorting == v.HeadingIdentifier then - v:SetTextColor(highlight_color) - else - v:SetTextColor(default_color) - end - end - - for _, v in pairs(self.sort_headers) do - v:SetFont("treb_small") - - if sorting == v.HeadingIdentifier then - v:SetTextColor(highlight_color) - else - v:SetTextColor(default_color) - end - end + self.hostdesc:SetFont("cool_small") + self.hostname:SetFont("cool_large") + self.mapchange:SetFont("treb_small") + + self.hostdesc:SetTextColor(COLOR_WHITE) + self.hostname:SetTextColor(COLOR_BLACK) + self.mapchange:SetTextColor(COLOR_WHITE) + + local sorting = cv_ttt_scoreboard_sorting:GetString() + local highlight_color = Color(175, 175, 175, 255) + local default_color = COLOR_WHITE + + for _, v in pairs(self.cols) do + v:SetFont("treb_small") + + if sorting == v.HeadingIdentifier then + v:SetTextColor(highlight_color) + else + v:SetTextColor(default_color) + end + end + + for _, v in pairs(self.sort_headers) do + v:SetFont("treb_small") + + if sorting == v.HeadingIdentifier then + v:SetTextColor(highlight_color) + else + v:SetTextColor(default_color) + end + end end --- -- @param boolean force -- @realm client function PANEL:UpdateScoreboard(force) - if not force and not self:IsVisible() then return end - - local layout = false - - -- Put players where they belong. Groups will dump them as soon as they don't - -- anymore. - for _, p in ipairs(player.GetAll()) do - if IsValid(p) then - local group = ScoreGroup(p) - - if self.ply_groups[group] and not self.ply_groups[group]:HasPlayerRow(p) then - self.ply_groups[group]:AddPlayerRow(p) - - layout = true - end - end - end - - for _, group in pairs(self.ply_groups) do - if IsValid(group) then - group:SetVisible(group:HasRows()) - group:UpdatePlayerData() - end - end - - if layout then - self:PerformLayout() - else - self:InvalidateLayout() - end + if not force and not self:IsVisible() then + return + end + + local layout = false + + -- Put players where they belong. Groups will dump them as soon as they don't + -- anymore. + for _, p in ipairs(player.GetAll()) do + if IsValid(p) then + local group = ScoreGroup(p) + + if self.ply_groups[group] and not self.ply_groups[group]:HasPlayerRow(p) then + self.ply_groups[group]:AddPlayerRow(p) + + layout = true + end + end + end + + for _, group in pairs(self.ply_groups) do + if IsValid(group) then + group:SetVisible(group:HasRows()) + group:UpdatePlayerData() + end + end + + if layout then + self:PerformLayout() + else + self:InvalidateLayout() + end end vgui.Register("TTTScoreboard", PANEL, "Panel") @@ -554,26 +567,26 @@ PANEL = {} --- -- @ignore function PANEL:Init() - self.pnlCanvas = vgui.Create("Panel", self) - self.YOffset = 0 + self.pnlCanvas = vgui.Create("Panel", self) + self.YOffset = 0 - self.scroll = vgui.Create("DVScrollBar", self) + self.scroll = vgui.Create("DVScrollBar", self) end --- -- @return Panel -- @realm client function PANEL:GetCanvas() - return self.pnlCanvas + return self.pnlCanvas end --- -- @param number dlta -- @realm client function PANEL:OnMouseWheeled(dlta) - self.scroll:AddScroll(dlta * -2) + self.scroll:AddScroll(dlta * -2) - self:InvalidateLayout() + self:InvalidateLayout() end --- @@ -581,27 +594,30 @@ end -- @param boolean st -- @realm client function PANEL:SetScroll(st) - self.scroll:SetEnabled(st) + self.scroll:SetEnabled(st) end --- -- @ignore function PANEL:PerformLayout() - self.pnlCanvas:SetVisible(self:IsVisible()) + self.pnlCanvas:SetVisible(self:IsVisible()) - -- scrollbar - self.scroll:SetPos(self:GetWide() - 16, 0) - self.scroll:SetSize(16, self:GetTall()) + -- scrollbar + self.scroll:SetPos(self:GetWide() - 16, 0) + self.scroll:SetSize(16, self:GetTall()) - local was_on = self.scroll.Enabled + local was_on = self.scroll.Enabled - self.scroll:SetUp(self:GetTall(), self.pnlCanvas:GetTall()) - self.scroll:SetEnabled(was_on) -- setup mangles enabled state + self.scroll:SetUp(self:GetTall(), self.pnlCanvas:GetTall()) + self.scroll:SetEnabled(was_on) -- setup mangles enabled state - self.YOffset = self.scroll:GetOffset() + self.YOffset = self.scroll:GetOffset() - self.pnlCanvas:SetPos(0, self.YOffset) - self.pnlCanvas:SetSize(self:GetWide() - (self.scroll.Enabled and 16 or 0), self.pnlCanvas:GetTall()) + self.pnlCanvas:SetPos(0, self.YOffset) + self.pnlCanvas:SetSize( + self:GetWide() - (self.scroll.Enabled and 16 or 0), + self.pnlCanvas:GetTall() + ) end vgui.Register("TTTPlayerFrame", PANEL, "Panel") @@ -613,9 +629,7 @@ vgui.Register("TTTPlayerFrame", PANEL, "Panel") -- @return nil|number The scoregroup, it must be one of: GROUP_TERROR, GROUP_NOTFOUND, GROUP_FOUND, or GROUP_SPEC -- @hook -- @realm client -function GM:TTTScoreGroup(ply) - -end +function GM:TTTScoreGroup(ply) end --- -- Called when initializing the scoreboard. In this hook you could add additional panels for new player groups, @@ -624,9 +638,7 @@ end -- @param table playerGroup A table of the player group panels -- @hook -- @realm client -function GM:TTTScoreGroups(parent, playerGroup) - -end +function GM:TTTScoreGroups(parent, playerGroup) end --- -- Use this hook to add a new column to the scoreboard. Use @{TTTPlayerFrame:AddColumn} @@ -634,6 +646,4 @@ end -- @param TTTPlayerFrame panel The player frame panel where a new column can be added -- @hook -- @realm client -function GM:TTTScoreboardColumns(panel) - -end +function GM:TTTScoreboardColumns(panel) end diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_sb_row.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_sb_row.lua index 56922eca1..5c258ae14 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_sb_row.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_sb_row.lua @@ -6,6 +6,7 @@ ttt_include("vgui__cl_sb_info") +local TryT = LANG.TryTranslation local GetTranslation = LANG.GetTranslation local math = math local table = table @@ -26,42 +27,44 @@ local materialIndicatorHeroes = "vgui/ttt/ttt2_indicator_heroes" local materialNoTeam = "vgui/ttt/dynamic/roles/icon_no_team" local dev_tbl = { - ["76561197964193008"] = true, -- Bad King Urgrain - ["76561198049831089"] = true, -- Alf21 - ["76561198058039701"] = true, -- saibotk - ["76561198047819379"] = true, -- Mineotopia - ["76561198052323988"] = true, -- LeBroomer - ["76561198076404571"] = true, -- Histalek - ["76561198296468397"] = true -- ZenBre4ker + ["76561197964193008"] = true, -- Bad King Urgrain + ["76561198049831089"] = true, -- Alf21 + ["76561198058039701"] = true, -- saibotk + ["76561198047819379"] = true, -- Mineotopia + ["76561198052323988"] = true, -- LeBroomer + ["76561198076404571"] = true, -- Histalek + ["76561198296468397"] = true, -- ZenBre4ker } local vip_tbl = { - ["76561198378608300"] = true, -- SirKadeeka / $r㣠- ["76561198102780049"] = true, -- Nick - ["76561198020955880"] = true, -- V3kta - ["76561198033468770"] = true, -- dok441 - ["76561198208729341"] = true, -- eBBIS - ["76561198152558244"] = true, -- Lost TheSuspect - ["76561198162764168"] = true, -- DerJohn - ["76561198132229662"] = true, -- Satton(RU) - ["76561198007725535"] = true, -- Skatcat - ["76561197989909602"] = true, -- Tobiti - ["76561198150260014"] = true, -- Lunex - ["76561198076404571"] = true, -- Histalek - ["76561198042086461"] = true, -- James - ["76561193814529882"] = true, -- Trystan - ["76561198114719750"] = true, -- Reispfannenfresser - ["76561198082931319"] = true, -- Henk - ["76561198049910438"] = true, -- Zzzaaaccc13 - ["76561198296468397"] = true, -- ZenBre4ker - ["76561198041170748"] = true -- Pytho | Paul + ["76561198378608300"] = true, -- SirKadeeka / $r㣠+ ["76561198102780049"] = true, -- Nick + ["76561198020955880"] = true, -- V3kta + ["76561198033468770"] = true, -- dok441 + ["76561198208729341"] = true, -- eBBIS + ["76561198152558244"] = true, -- Lost TheSuspect + ["76561198162764168"] = true, -- DerJohn + ["76561198132229662"] = true, -- Satton(RU) + ["76561198007725535"] = true, -- Skatcat + ["76561197989909602"] = true, -- Tobiti + ["76561198150260014"] = true, -- Lunex + ["76561198076404571"] = true, -- Histalek + ["76561198042086461"] = true, -- James + ["76561193814529882"] = true, -- Trystan + ["76561198114719750"] = true, -- Reispfannenfresser + ["76561198082931319"] = true, -- Henk + ["76561198049910438"] = true, -- Zzzaaaccc13 + ["76561198296468397"] = true, -- ZenBre4ker + ["76561198041170748"] = true, -- Pytho | Paul + ["76561197999466165"] = true, -- EntranceJew + ["76561198279816989"] = true, -- mexikoedi } local addondev_tbl = { - ["76561198102780049"] = true, -- Nick - ["76561197989909602"] = true, -- Tobiti - ["76561198076404571"] = true, -- Histalek - ["76561198027025876"] = true -- 8Z + ["76561198102780049"] = true, -- Nick + ["76561197989909602"] = true, -- Tobiti + ["76561198076404571"] = true, -- Histalek + ["76561198027025876"] = true, -- 8Z } --- @@ -69,31 +72,33 @@ local addondev_tbl = { -- @param string steamid64 -- @realm client function AddTTT2AddonDev(steamid64) - if not steamid64 then return end + if not steamid64 then + return + end - addondev_tbl[tostring(steamid64)] = true + addondev_tbl[tostring(steamid64)] = true end local streamer_tbl = { - ["76561198049831089"] = true, - ["76561198058039701"] = true, - ["76561198047819379"] = true, -- Mineotopia - ["76561198052323988"] = true + ["76561198049831089"] = true, + ["76561198058039701"] = true, + ["76561198047819379"] = true, -- Mineotopia + ["76561198052323988"] = true, } --TODO: move into heroes local heroes_tbl = { - ["76561198000950884"] = true -- Dhalucard + ["76561198000950884"] = true, -- Dhalucard } local namecolor = { - default = COLOR_WHITE, - dev = Color(100, 240, 105, 255), - vip = Color(220, 55, 55, 255), - addondev = Color(30, 105, 30, 255), - admin = Color(255, 210, 35, 255), - streamer = Color(100, 70, 140, 255), - heroes = Color(70, 125, 110, 255) + default = COLOR_WHITE, + dev = Color(100, 240, 105, 255), + vip = Color(220, 55, 55, 255), + addondev = Color(30, 105, 30, 255), + admin = Color(255, 210, 35, 255), + streamer = Color(100, 70, 140, 255), + heroes = Color(70, 125, 110, 255), } SB_ROW_HEIGHT = 24 --16 @@ -103,120 +108,119 @@ local iconSizes = 16 local PANEL = {} local function _func1(ply) - return ply:Ping() + return ply:Ping() end local function _func2(ply) - return ply:Deaths() + return ply:Deaths() end local function _func3(ply) - return ply:Frags() + return ply:Frags() end local function _func4(ply) - return math.Round(ply:GetBaseKarma()) + return math.Round(ply:GetBaseKarma()) end --- -- @ignore function PANEL:Init() - local TryT = LANG.TryTranslation - - -- cannot create info card until player state is known - self.info = nil - self.open = false - self.cols = {} - - self:AddColumn(GetTranslation("sb_ping"), _func1) - self:AddColumn(GetTranslation("sb_deaths"), _func2) - self:AddColumn(GetTranslation("sb_score"), _func3) - - if KARMA.IsEnabled() then - self:AddColumn(GetTranslation("sb_karma"), _func4) - end - - --- - -- Let hooks add their custom columns - -- @realm client - hook.Run("TTTScoreboardColumns", self) - - for i = 1, #self.cols do - self.cols[i]:SetMouseInputEnabled(false) - end - - self.tag = vgui.Create("DLabel", self) - self.tag:SetText("") - self.tag:SetMouseInputEnabled(false) - - self.sresult = vgui.Create("DImage", self) - self.sresult:SetSize(iconSizes, iconSizes) - self.sresult:SetMouseInputEnabled(false) - - self.dev = vgui.Create("DImage", self) - self.dev:SetSize(iconSizes, iconSizes) - self.dev:SetMouseInputEnabled(true) - self.dev:SetKeepAspect(true) - self.dev:SetTooltip(TryT("sb_rank_tooltip_developer")) - - self.vip = vgui.Create("DImage", self) - self.vip:SetSize(iconSizes, iconSizes) - self.vip:SetMouseInputEnabled(true) - self.vip:SetKeepAspect(true) - self.vip:SetTooltip(TryT("sb_rank_tooltip_vip")) - - self.addondev = vgui.Create("DImage", self) - self.addondev:SetSize(iconSizes, iconSizes) - self.addondev:SetMouseInputEnabled(true) - self.addondev:SetKeepAspect(true) - self.addondev:SetTooltip(TryT("sb_rank_tooltip_addondev")) - - self.admin = vgui.Create("DImage", self) - self.admin:SetSize(iconSizes, iconSizes) - self.admin:SetMouseInputEnabled(true) - self.admin:SetKeepAspect(true) - self.admin:SetTooltip(TryT("sb_rank_tooltip_admin")) - - self.streamer = vgui.Create("DImage", self) - self.streamer:SetSize(iconSizes, iconSizes) - self.streamer:SetMouseInputEnabled(true) - self.streamer:SetKeepAspect(true) - self.streamer:SetTooltip(TryT("sb_rank_tooltip_streamer")) - - self.heroes = vgui.Create("DImage", self) - self.heroes:SetSize(iconSizes, iconSizes) - self.heroes:SetMouseInputEnabled(true) - self.heroes:SetKeepAspect(true) - self.heroes:SetTooltip(TryT("sb_rank_tooltip_heroes")) - - self.avatar = vgui.Create("AvatarImage", self) - self.avatar:SetSize(SB_ROW_HEIGHT, SB_ROW_HEIGHT) - self.avatar:SetMouseInputEnabled(false) - - self.nick2 = vgui.Create("DLabel", self) - self.nick2:SetMouseInputEnabled(false) - - self.nick3 = vgui.Create("DLabel", self) - self.nick3:SetMouseInputEnabled(false) - - self.nick = vgui.Create("DLabel", self) - self.nick:SetMouseInputEnabled(false) - - self.team2 = vgui.Create("DImage", self) - self.team2:SetSize(iconSizes, iconSizes) - self.team2:SetMouseInputEnabled(false) - self.team2:SetKeepAspect(true) - - self.team = vgui.Create("DImage", self) - self.team:SetSize(iconSizes, iconSizes) - self.team:SetMouseInputEnabled(true) - self.team:SetKeepAspect(true) - self.team:SetTooltip(TryT("sb_rank_tooltip_team")) - - self.voice = vgui.Create("DImageButton", self) - self.voice:SetSize(iconSizes, iconSizes) - - self:SetCursor("hand") + -- cannot create info card until player state is known + self.info = nil + self.open = false + self.cols = {} + + self:AddColumn(GetTranslation("sb_ping"), _func1) + self:AddColumn(GetTranslation("sb_deaths"), _func2) + self:AddColumn(GetTranslation("sb_score"), _func3) + + if KARMA.IsEnabled() then + self:AddColumn(GetTranslation("sb_karma"), _func4) + end + + --- + -- Let hooks add their custom columns + -- @realm client + -- stylua: ignore + hook.Run("TTTScoreboardColumns", self) + + for i = 1, #self.cols do + self.cols[i]:SetMouseInputEnabled(false) + end + + self.tag = vgui.Create("DLabel", self) + self.tag:SetText("") + self.tag:SetMouseInputEnabled(false) + + self.sresult = vgui.Create("DImage", self) + self.sresult:SetSize(iconSizes, iconSizes) + self.sresult:SetMouseInputEnabled(true) + + self.dev = vgui.Create("DImage", self) + self.dev:SetSize(iconSizes, iconSizes) + self.dev:SetMouseInputEnabled(true) + self.dev:SetKeepAspect(true) + self.dev:SetTooltip(TryT("sb_rank_tooltip_developer")) + + self.vip = vgui.Create("DImage", self) + self.vip:SetSize(iconSizes, iconSizes) + self.vip:SetMouseInputEnabled(true) + self.vip:SetKeepAspect(true) + self.vip:SetTooltip(TryT("sb_rank_tooltip_vip")) + + self.addondev = vgui.Create("DImage", self) + self.addondev:SetSize(iconSizes, iconSizes) + self.addondev:SetMouseInputEnabled(true) + self.addondev:SetKeepAspect(true) + self.addondev:SetTooltip(TryT("sb_rank_tooltip_addondev")) + + self.admin = vgui.Create("DImage", self) + self.admin:SetSize(iconSizes, iconSizes) + self.admin:SetMouseInputEnabled(true) + self.admin:SetKeepAspect(true) + self.admin:SetTooltip(TryT("sb_rank_tooltip_admin")) + + self.streamer = vgui.Create("DImage", self) + self.streamer:SetSize(iconSizes, iconSizes) + self.streamer:SetMouseInputEnabled(true) + self.streamer:SetKeepAspect(true) + self.streamer:SetTooltip(TryT("sb_rank_tooltip_streamer")) + + self.heroes = vgui.Create("DImage", self) + self.heroes:SetSize(iconSizes, iconSizes) + self.heroes:SetMouseInputEnabled(true) + self.heroes:SetKeepAspect(true) + self.heroes:SetTooltip(TryT("sb_rank_tooltip_heroes")) + + self.avatar = vgui.Create("AvatarImage", self) + self.avatar:SetSize(SB_ROW_HEIGHT, SB_ROW_HEIGHT) + self.avatar:SetMouseInputEnabled(false) + + self.nick2 = vgui.Create("DLabel", self) + self.nick2:SetMouseInputEnabled(false) + + self.nick3 = vgui.Create("DLabel", self) + self.nick3:SetMouseInputEnabled(false) + + self.nick = vgui.Create("DLabel", self) + self.nick:SetMouseInputEnabled(false) + + self.team2 = vgui.Create("DImage", self) + self.team2:SetSize(iconSizes, iconSizes) + self.team2:SetMouseInputEnabled(false) + self.team2:SetKeepAspect(true) + + self.team = vgui.Create("DImage", self) + self.team:SetSize(iconSizes, iconSizes) + self.team:SetMouseInputEnabled(true) + self.team:SetKeepAspect(true) + self.team:SetTooltip(TryT("sb_rank_tooltip_team")) + + self.voice = vgui.Create("DImageButton", self) + self.voice:SetSize(iconSizes, iconSizes) + + self:SetCursor("hand") end --- @@ -227,14 +231,14 @@ end -- @return Panel DLabel -- @realm client function PANEL:AddColumn(label, func, width) - local lbl = vgui.Create("DLabel", self) - lbl.GetPlayerText = func - lbl.IsHeading = false - lbl.Width = width or 50 -- Retain compatibility with existing code + local lbl = vgui.Create("DLabel", self) + lbl.GetPlayerText = func + lbl.IsHeading = false + lbl.Width = width or 50 -- Retain compatibility with existing code - self.cols[#self.cols + 1] = lbl + self.cols[#self.cols + 1] = lbl - return lbl + return lbl end --- @@ -244,25 +248,26 @@ end -- use the AddFakeColumn function of `sb_main`, which would -- cause this file to raise a `function not found` error or others -- @realm client -function PANEL:AddFakeColumn() - -end +function PANEL:AddFakeColumn() end local function ColorForPlayer(ply) - if IsValid(ply) then - --- - -- @realm client - local c = hook.Run("TTTScoreboardColorForPlayer", ply) - - -- verify that we got a proper color - if c and istable(c) and c.r and c.b and c.g and c.a then - return c - else - ErrorNoHalt("TTTScoreboardColorForPlayer hook returned something that isn't a color!\n") - end - end - - return namecolor.default + if IsValid(ply) then + --- + -- @realm client + -- stylua: ignore + local c = hook.Run("TTTScoreboardColorForPlayer", ply) + + -- verify that we got a proper color + if c and istable(c) and c.r and c.b and c.g and c.a then + return c + else + ErrorNoHaltWithStack( + "TTTScoreboardColorForPlayer hook returned something that isn't a color!\n" + ) + end + end + + return namecolor.default end --- @@ -271,339 +276,358 @@ end -- @realm client -- @ignore function PANEL:Paint(width, height) - if not IsValid(self.Player) then - return false - end + if not IsValid(self.Player) then + return false + end - -- if (self.Player:GetFriendStatus() == "friend") then - -- color = Color(236, 181, 113, 255) - -- end + -- if (self.Player:GetFriendStatus() == "friend") then + -- color = Color(236, 181, 113, 255) + -- end - local ply = self.Player + local ply = self.Player - --- - -- @realm client - local c = hook.Run("TTTScoreboardRowColorForPlayer", ply) + --- + -- @realm client + -- stylua: ignore + local c = hook.Run("TTTScoreboardRowColorForPlayer", ply) - surface.SetDrawColor(clr(c)) - surface.DrawRect(0, 0, width, SB_ROW_HEIGHT) + surface.SetDrawColor(clr(c)) + surface.DrawRect(0, 0, width, SB_ROW_HEIGHT) - if ply == LocalPlayer() then - surface.SetDrawColor(200, 200, 200, math.Clamp(math.sin(RealTime() * 2) * 50, 0, 100)) - surface.DrawRect(0, 0, width, SB_ROW_HEIGHT) - end + if ply == LocalPlayer() then + surface.SetDrawColor(200, 200, 200, math.Clamp(math.sin(RealTime() * 2) * 50, 0, 100)) + surface.DrawRect(0, 0, width, SB_ROW_HEIGHT) + end - return true + return true end --- -- @param Player ply -- @realm client function PANEL:SetPlayer(ply) - self.Player = ply - self.avatar:SetPlayer(ply) - - local client = LocalPlayer() - - if ply ~= client then - self.voice:SetTooltip(GetTranslation("scoreboard_voice_tooltip")) - end - - if not self.info then - local g = ScoreGroup(ply) - - if g == GROUP_TERROR and ply ~= LocalPlayer() then - self.info = vgui.Create("TTTScorePlayerInfoTags", self) - self.info:SetPlayer(ply) - - self:InvalidateLayout() - elseif g == GROUP_FOUND or g == GROUP_NOTFOUND then - self.info = vgui.Create("TTTScorePlayerInfoSearch", self) - self.info:SetPlayer(ply) - - self:InvalidateLayout() - end - else - self.info:SetPlayer(ply) - - self:InvalidateLayout() - end - - self.voice.DoClick = function() - if IsValid(ply) and ply ~= client then - ply:SetMuted(not ply:IsMuted()) - end - end - - self.voice.OnMouseWheeled = function(label, delta) - if IsValid(ply) and ply ~= client then - self:ScrollPlayerVolume(delta) - end - end - - self:UpdatePlayerData() + self.Player = ply + self.avatar:SetPlayer(ply) + + local client = LocalPlayer() + + if ply ~= client then + self.voice:SetTooltip(GetTranslation("scoreboard_voice_tooltip")) + end + + if not self.info then + local g = ScoreGroup(ply) + + if g == GROUP_TERROR and ply ~= LocalPlayer() then + self.info = vgui.Create("TTTScorePlayerInfoTags", self) + self.info:SetPlayer(ply) + + self:InvalidateLayout() + elseif g == GROUP_FOUND or g == GROUP_NOTFOUND then + self.info = vgui.Create("TTTScorePlayerInfoSearch", self) + self.info:SetPlayer(ply) + + self:InvalidateLayout() + end + else + self.info:SetPlayer(ply) + + self:InvalidateLayout() + end + + self.voice.DoClick = function() + if IsValid(ply) and ply ~= client then + local muted = VOICE.GetPreferredPlayerVoiceMuted(ply) + VOICE.SetPreferredPlayerVoiceMuted(ply, not muted) + VOICE.UpdatePlayerVoiceVolume(ply) + end + end + + self.voice.OnMouseWheeled = function(label, delta) + if IsValid(ply) and ply ~= client then + self:ScrollPlayerVolume(delta) + end + end + + self:UpdatePlayerData() end --- -- @return Player -- @realm client function PANEL:GetPlayer() - return self.Player + return self.Player end --- -- @realm client function PANEL:UpdatePlayerData() - if not IsValid(self.Player) then return end - - local ply = self.Player - - for i = 1, #self.cols do - -- Set text from function, passing the label along so stuff like text - -- color can be changed - self.cols[i]:SetText(self.cols[i].GetPlayerText(ply, self.cols[i])) - end - - self.nick2:SetText(ply:Nick()) - self.nick2:SizeToContents() - self.nick2:SetTextColor(COLOR_BLACK) - - self.nick3:SetText(ply:Nick()) - self.nick3:SizeToContents() - self.nick3:SetTextColor(COLOR_BLACK) - - self.nick:SetText(ply:Nick()) - self.nick:SizeToContents() - self.nick:SetTextColor(ColorForPlayer(ply)) - - local tm = ply:GetTeam() or nil - if tm then - local tmData = TEAMS[tm] - if tm == TEAM_NONE or not tmData or tmData.alone then - self.team2:SetImage(materialNoTeam) - self.team:SetImage(materialNoTeam) - else - local teamImageName = tmData.iconMaterial:GetName() - - self.team2:SetImage(teamImageName) - self.team:SetImage(teamImageName) - end - - self.team2:SetImageColor(COLOR_BLACK) - self.team:SetImageColor(COLOR_WHITE) - - self.team:SetTooltip(LANG.GetTranslation(tm)) - end - - local showTeam = ply:HasRole() - - self.team2:SetVisible(showTeam) - self.team:SetVisible(showTeam) - - local steamid64 = ply:SteamID64() - if steamid64 then - steamid64 = tostring(steamid64) - end - - local isdev = steamid64 and dev_tbl[steamid64] == true - - self.dev:SetVisible(isdev and GetGlobalBool("ttt_highlight_dev", true)) - - if not isdev or not GetGlobalBool("ttt_highlight_dev", true) then - self.vip:SetVisible(steamid64 and vip_tbl[steamid64] and GetGlobalBool("ttt_highlight_vip", true)) - self.addondev:SetVisible(steamid64 and addondev_tbl[steamid64] and GetGlobalBool("ttt_highlight_addondev", true)) - else - self.vip:SetVisible(false) - self.addondev:SetVisible(false) - end - - self.admin:SetVisible(ply:IsAdmin() and GetGlobalBool("ttt_highlight_admins", true)) - self.streamer:SetVisible(steamid64 and streamer_tbl[steamid64] and GetGlobalBool("ttt_highlight_supporter", true)) - self.heroes:SetVisible(steamid64 and heroes_tbl[steamid64] and GetGlobalBool("ttt_highlight_supporter", true)) - - local ptag = ply.sb_tag - - if ScoreGroup(ply) ~= GROUP_TERROR then - ptag = nil - end - - self.tag:SetText(ptag and GetTranslation(ptag.txt) or "") - self.tag:SetTextColor(ptag and ptag.color or COLOR_WHITE) - - self.sresult:SetVisible(ply.search_result and ply.search_result.detective_search) - - -- more blue if a detective searched them - if ply.search_result and (LocalPlayer():GetSubRoleData().isPolicingRole or not ply.search_result.show) then - self.sresult:SetImageColor(Color(200, 200, 255)) - end - - -- cols are likely to need re-centering - self:LayoutColumns() - - if self.info then - self.info:UpdatePlayerData() - end - - if self.Player ~= LocalPlayer() then - local muted = self.Player:IsMuted() - - self.voice:SetImage(muted and "icon16/sound_mute.png" or "icon16/sound.png") - else - self.voice:Hide() - end + if not IsValid(self.Player) then + return + end + + local ply = self.Player + + for i = 1, #self.cols do + -- Set text from function, passing the label along so stuff like text + -- color can be changed + self.cols[i]:SetText(self.cols[i].GetPlayerText(ply, self.cols[i])) + end + + self.nick2:SetText(ply:Nick()) + self.nick2:SizeToContents() + self.nick2:SetTextColor(COLOR_BLACK) + + self.nick3:SetText(ply:Nick()) + self.nick3:SizeToContents() + self.nick3:SetTextColor(COLOR_BLACK) + + self.nick:SetText(ply:Nick()) + self.nick:SizeToContents() + self.nick:SetTextColor(ColorForPlayer(ply)) + + local tm = ply:GetTeam() or nil + if tm then + local tmData = TEAMS[tm] + if tm == TEAM_NONE or not tmData or tmData.alone then + self.team2:SetImage(materialNoTeam) + self.team:SetImage(materialNoTeam) + else + local teamImageName = tmData.iconMaterial:GetName() + + self.team2:SetImage(teamImageName) + self.team:SetImage(teamImageName) + end + + self.team2:SetImageColor(COLOR_BLACK) + self.team:SetImageColor(COLOR_WHITE) + + self.team:SetTooltip(LANG.GetTranslation(tm)) + end + + local showTeam = ply:HasRole() + + self.team2:SetVisible(showTeam) + self.team:SetVisible(showTeam) + + local steamid64 = ply:SteamID64() + if steamid64 then + steamid64 = tostring(steamid64) + end + + local isdev = steamid64 and dev_tbl[steamid64] == true + + self.dev:SetVisible(isdev and GetGlobalBool("ttt_highlight_dev", true)) + + if not isdev or not GetGlobalBool("ttt_highlight_dev", true) then + self.vip:SetVisible( + steamid64 and vip_tbl[steamid64] and GetGlobalBool("ttt_highlight_vip", true) + ) + self.addondev:SetVisible( + steamid64 and addondev_tbl[steamid64] and GetGlobalBool("ttt_highlight_addondev", true) + ) + else + self.vip:SetVisible(false) + self.addondev:SetVisible(false) + end + + self.admin:SetVisible(ply:IsAdmin() and GetGlobalBool("ttt_highlight_admins", true)) + self.streamer:SetVisible( + steamid64 and streamer_tbl[steamid64] and GetGlobalBool("ttt_highlight_supporter", true) + ) + self.heroes:SetVisible( + steamid64 and heroes_tbl[steamid64] and GetGlobalBool("ttt_highlight_supporter", true) + ) + + local ptag = ply.sb_tag + + if ScoreGroup(ply) ~= GROUP_TERROR then + ptag = nil + end + + self.tag:SetText(ptag and GetTranslation(ptag.txt) or "") + self.tag:SetTextColor(ptag and ptag.color or COLOR_WHITE) + + local show_sresult = true + -- we have body search results + if ply.bodySearchResult and ply.bodySearchResult.base.isPublicPolicingSearch then + self.sresult:SetImage("icon16/magnifier.png") + self.sresult:SetTooltip(GetTranslation("cl_sb_row_sresult_pub_police")) + elseif ply.bodySearchResult and ply.bodySearchResult.show_sb then + self.sresult:SetImage("icon16/information.png") + self.sresult:SetTooltip(GetTranslation("cl_sb_row_sresult_direct_conf")) + else + show_sresult = false + end + + self.sresult:SetVisible(show_sresult) + + -- cols are likely to need re-centering + self:LayoutColumns() + + if self.info then + self.info:UpdatePlayerData() + end + + if self.Player ~= LocalPlayer() then + local muted = VOICE.GetPreferredPlayerVoiceMuted(self.Player) + + self.voice:SetImage(muted and "icon16/sound_mute.png" or "icon16/sound.png") + else + self.voice:Hide() + end end --- -- @ignore function PANEL:ApplySchemeSettings() - local ply = self.Player + local ply = self.Player - for i = 1, #self.cols do - local v = self.cols[i] + for i = 1, #self.cols do + local v = self.cols[i] - v:SetFont("treb_small") - v:SetTextColor(COLOR_WHITE) - end + v:SetFont("treb_small") + v:SetTextColor(COLOR_WHITE) + end - self.nick2:SetFont("treb_small") - self.nick2:SetTextColor(COLOR_BLACK) + self.nick2:SetFont("treb_small") + self.nick2:SetTextColor(COLOR_BLACK) - self.nick3:SetFont("treb_small") - self.nick3:SetTextColor(COLOR_BLACK) + self.nick3:SetFont("treb_small") + self.nick3:SetTextColor(COLOR_BLACK) - self.nick:SetFont("treb_small") + self.nick:SetFont("treb_small") - if IsValid(ply) then - self.nick:SetTextColor(ColorForPlayer(ply)) - end + if IsValid(ply) then + self.nick:SetTextColor(ColorForPlayer(ply)) + end - local ptag = IsValid(ply) and ply.sb_tag or nil - self.tag:SetTextColor(ptag and ptag.color or COLOR_WHITE) - self.tag:SetFont("treb_small") + local ptag = IsValid(ply) and ply.sb_tag or nil + self.tag:SetTextColor(ptag and ptag.color or COLOR_WHITE) + self.tag:SetFont("treb_small") - self.sresult:SetImage("icon16/magnifier.png") - self.sresult:SetImageColor(Color(170, 170, 170, 150)) + self.sresult:SetImageColor(COLOR_WHITE) - self.dev:SetImage(materialIndicatorDev) - self.dev:SetImageColor(namecolor.dev) + self.dev:SetImage(materialIndicatorDev) + self.dev:SetImageColor(namecolor.dev) - self.vip:SetImage(materialIndicatorVIP) - self.vip:SetImageColor(namecolor.vip) + self.vip:SetImage(materialIndicatorVIP) + self.vip:SetImageColor(namecolor.vip) - self.addondev:SetImage(materialIndicatorAddonDev) - self.addondev:SetImageColor(namecolor.addondev) + self.addondev:SetImage(materialIndicatorAddonDev) + self.addondev:SetImageColor(namecolor.addondev) - self.admin:SetImage(materialIndicatorAdmin) - self.admin:SetImageColor(namecolor.admin) + self.admin:SetImage(materialIndicatorAdmin) + self.admin:SetImageColor(namecolor.admin) - self.streamer:SetImage(materialIndicatorStreamer) - self.streamer:SetImageColor(namecolor.streamer) + self.streamer:SetImage(materialIndicatorStreamer) + self.streamer:SetImageColor(namecolor.streamer) - self.heroes:SetImage(materialIndicatorHeroes) - self.heroes:SetImageColor(namecolor.heroes) + self.heroes:SetImage(materialIndicatorHeroes) + self.heroes:SetImageColor(namecolor.heroes) end --- -- @realm client function PANEL:LayoutColumns() - local cx = self:GetWide() + local cx = self:GetWide() - for i = 1, #self.cols do - local v = self.cols[i] + for i = 1, #self.cols do + local v = self.cols[i] - v:SizeToContents() + v:SizeToContents() - cx = cx - v.Width + cx = cx - v.Width - v:SetPos(cx - v:GetWide() * 0.5, (SB_ROW_HEIGHT - v:GetTall()) * 0.5) - end + v:SetPos(cx - v:GetWide() * 0.5, (SB_ROW_HEIGHT - v:GetTall()) * 0.5) + end - self.tag:SizeToContents() + self.tag:SizeToContents() - cx = cx - 90 + cx = cx - 90 - self.tag:SetPos(cx - self.tag:GetWide() * 0.5, (SB_ROW_HEIGHT - self.tag:GetTall()) * 0.5) + self.tag:SetPos(cx - self.tag:GetWide() * 0.5, (SB_ROW_HEIGHT - self.tag:GetTall()) * 0.5) - self.sresult:SetPos(cx - 8, (SB_ROW_HEIGHT - iconSizes) * 0.5) + self.sresult:SetPos(cx - 8, (SB_ROW_HEIGHT - iconSizes) * 0.5) - local x = self.nick:GetPos() - local w = self.nick:GetSize() + local x = self.nick:GetPos() + local w = self.nick:GetSize() - local count = 0 - local mgn = 10 + local count = 0 + local mgn = 10 - local tx = x + w + mgn - local ty = (SB_ROW_HEIGHT - iconSizes) * 0.5 + local tx = x + w + mgn + local ty = (SB_ROW_HEIGHT - iconSizes) * 0.5 - local iconTbl = { - "dev", - "vip", - "addondev", - "admin", - "streamer", - "heroes" - } + local iconTbl = { + "dev", + "vip", + "addondev", + "admin", + "streamer", + "heroes", + } - for i = 1, #iconTbl do - local entry = iconTbl[i] - local iconData = self[entry] + for i = 1, #iconTbl do + local entry = iconTbl[i] + local iconData = self[entry] - if iconData:IsVisible() then - iconData:SetPos(tx + (iconSizes + mgn) * count, ty) + if iconData:IsVisible() then + iconData:SetPos(tx + (iconSizes + mgn) * count, ty) - count = count + 1 - end - end + count = count + 1 + end + end end --- -- @ignore function PANEL:PerformLayout() - self.avatar:SetPos(0, 0) - self.avatar:SetSize(SB_ROW_HEIGHT, SB_ROW_HEIGHT) + self.avatar:SetPos(0, 0) + self.avatar:SetSize(SB_ROW_HEIGHT, SB_ROW_HEIGHT) - local fw = sboard_panel.ply_frame:GetWide() + local fw = sboard_panel.ply_frame:GetWide() - self:SetWide(sboard_panel.ply_frame.scroll.Enabled and (fw - iconSizes) or fw) + self:SetWide(sboard_panel.ply_frame.scroll.Enabled and (fw - iconSizes) or fw) - if not self.open then - self:SetSize(self:GetWide(), SB_ROW_HEIGHT) + if not self.open then + self:SetSize(self:GetWide(), SB_ROW_HEIGHT) - if self.info then - self.info:SetVisible(false) - end - elseif self.info then - self:SetSize(self:GetWide(), 100 + SB_ROW_HEIGHT) + if self.info then + self.info:SetVisible(false) + end + elseif self.info then + self:SetSize(self:GetWide(), 100 + SB_ROW_HEIGHT) - self.info:SetVisible(true) - self.info:SetPos(5, SB_ROW_HEIGHT + 5) - self.info:SetSize(self:GetWide(), 100) - self.info:PerformLayout() + self.info:SetVisible(true) + self.info:SetPos(5, SB_ROW_HEIGHT + 5) + self.info:SetSize(self:GetWide(), 100) + self.info:PerformLayout() - self:SetSize(self:GetWide(), SB_ROW_HEIGHT + self.info:GetTall()) - end + self:SetSize(self:GetWide(), SB_ROW_HEIGHT + self.info:GetTall()) + end - local tx = SB_ROW_HEIGHT + 10 + iconSizes - local ty = (SB_ROW_HEIGHT - self.nick:GetTall()) * 0.5 + local tx = SB_ROW_HEIGHT + 10 + iconSizes + local ty = (SB_ROW_HEIGHT - self.nick:GetTall()) * 0.5 - self.nick2:SizeToContents() - self.nick2:SetPos(tx + 1, ty + 1) + self.nick2:SizeToContents() + self.nick2:SetPos(tx + 1, ty + 1) - self.nick3:SizeToContents() - self.nick3:SetPos(tx, ty) + self.nick3:SizeToContents() + self.nick3:SetPos(tx, ty) - self.nick:SizeToContents() - self.nick:SetPos(tx, ty) + self.nick:SizeToContents() + self.nick:SetPos(tx, ty) - self.team2:SetPos(tx - iconSizes - 4, (SB_ROW_HEIGHT - iconSizes) * 0.5 + 1) - self.team:SetPos(tx - iconSizes - 5, (SB_ROW_HEIGHT - iconSizes) * 0.5) + self.team2:SetPos(tx - iconSizes - 4, (SB_ROW_HEIGHT - iconSizes) * 0.5 + 1) + self.team:SetPos(tx - iconSizes - 5, (SB_ROW_HEIGHT - iconSizes) * 0.5) - self:LayoutColumns() + self:LayoutColumns() - self.voice:SetVisible(not self.open) - self.voice:SetSize(iconSizes, iconSizes) - self.voice:DockMargin(4, 4, 4, 4) - self.voice:Dock(RIGHT) + self.voice:SetVisible(not self.open) + self.voice:SetSize(iconSizes, iconSizes) + self.voice:DockMargin(4, 4, 4, 4) + self.voice:Dock(RIGHT) end --- @@ -611,123 +635,140 @@ end -- @param number y -- @realm client function PANEL:DoClick(x, y) - self:SetOpen(not self.open) + self:SetOpen(not self.open) end --- -- @param boolean o -- @realm client function PANEL:SetOpen(o) - if self.open then - surface.PlaySound("ui/buttonclickrelease.wav") - else - surface.PlaySound("ui/buttonclick.wav") - end + if self.open then + surface.PlaySound("ui/buttonclickrelease.wav") + else + surface.PlaySound("ui/buttonclick.wav") + end - self.open = o + self.open = o - self:PerformLayout() - self:GetParent():PerformLayout() + self:PerformLayout() + self:GetParent():PerformLayout() - sboard_panel:PerformLayout() + sboard_panel:PerformLayout() end --- -- @realm client function PANEL:DoRightClick() - local menu = DermaMenu() - menu.Player = self:GetPlayer() + local menu = DermaMenu() + menu.Player = self:GetPlayer() - --- - -- @realm client - local close = hook.Run("TTTScoreboardMenu", menu) - if close then - menu:Remove() + --- + -- @realm client + -- stylua: ignore + local close = hook.Run("TTTScoreboardMenu", menu) + if close then + menu:Remove() - return - end + return + end - menu:Open() + menu:Open() end --- -- @param number delta -- @realm client function PANEL:ScrollPlayerVolume(delta) - local ply = self:GetPlayer() - - -- Bots return nil for the steamid64 on the client, so we need to improvise a bit - local identifier = ply:IsBot() and ply:Nick() or ply:SteamID64() - - local cur_volume = ply:GetVoiceVolumeScale() - cur_volume = cur_volume ~= nil and cur_volume or 1 - - local new_volume = delta == -1 and math.max(0, cur_volume - 0.01) or math.min(1, cur_volume + 0.01) - new_volume = math.Round(new_volume, 2) - - ply:SetVoiceVolumeScale(new_volume) - - if self.voice.percentage_frame ~= nil and not self.voice.percentage_frame:IsVisible() then - self.voice.percentage_frame:Show() - end - - local x, y = input.GetCursorPos() - local width = 60 - local height = 40 - local padding = 10 - - -- Frame - local frame = self.voice.percentage_frame ~= nil and self.voice.percentage_frame or vgui.Create("DFrame") - frame:SetPos(x - 10, y + 25) - frame:SetSize(width, height) - frame:SetTitle("") - frame:ShowCloseButton(false) - frame:SetDraggable(false) - frame:SetSizable(false) - frame.Paint = function(_, w, h) - drawRoundedBox(8, 0, 0, w, h, Color(24, 25, 28, 180)) - end - - self.voice.percentage_frame = frame - - local label = self.voice.percentage_frame_label ~= nil and self.voice.percentage_frame_label or vgui.Create("DLabel", frame) - label:SetPos(padding, padding) - label:SetFont("treb_small") - label:SetSize(width - padding * 2, 20) - label:SetColor(COLOR_WHITE) - label:SetText(tostring(math.Round(new_volume * 100)) .. "%") - - self.voice.percentage_frame_label = label - - timer.Remove("ttt_score_close_perc_frame_" .. identifier) - - timer.Create("ttt_score_close_perc_frame_" .. identifier, 1.5, 1, function() - if not self.voice and frame and frame:IsVisible() then - frame:Close() - frame = nil - - return - end - - if not self.voice or not self.voice.percentage_frame or not self.voice.percentage_frame:IsVisible() then return end - - self.voice.percentage_frame:Hide() - end) - - hook.Add("ScoreboardHide", "TTTCloseVolumeFrame_" .. identifier, function() - if not self.voice and frame and frame:IsVisible() then - frame:Close() - frame = nil - - return - end - - if not self.voice or not self.voice.percentage_frame or not self.voice.percentage_frame:IsVisible() then return end - - self.voice.percentage_frame:Hide() - - timer.Remove("ttt_score_close_perc_frame_" .. identifier) - end) + local ply = self:GetPlayer() + + -- Bots return nil for the steamid64 on the client, so we need to improvise a bit + local identifier = ply:IsBot() and ply:Nick() or ply:SteamID64() + + local cur_volume = VOICE.GetPreferredPlayerVoiceVolume(ply) + cur_volume = cur_volume ~= nil and cur_volume or 1 + + local new_volume = delta == -1 and math.max(0, cur_volume - 0.01) + or math.min(1, cur_volume + 0.01) + new_volume = math.Round(new_volume, 2) + + VOICE.SetPreferredPlayerVoiceVolume(ply, new_volume) + VOICE.UpdatePlayerVoiceVolume(ply) + + if self.voice.percentage_frame ~= nil and not self.voice.percentage_frame:IsVisible() then + self.voice.percentage_frame:Show() + end + + local x, y = input.GetCursorPos() + local width = 60 + local height = 40 + local padding = 10 + + -- Frame + local frame = self.voice.percentage_frame ~= nil and self.voice.percentage_frame + or vgui.Create("DFrame") + frame:SetPos(x - 10, y + 25) + frame:SetSize(width, height) + frame:SetTitle("") + frame:ShowCloseButton(false) + frame:SetDraggable(false) + frame:SetSizable(false) + frame.Paint = function(_, w, h) + drawRoundedBox(8, 0, 0, w, h, Color(24, 25, 28, 180)) + end + + self.voice.percentage_frame = frame + + local label = self.voice.percentage_frame_label ~= nil and self.voice.percentage_frame_label + or vgui.Create("DLabel", frame) + label:SetPos(padding, padding) + label:SetFont("treb_small") + label:SetSize(width - padding * 2, 20) + label:SetColor(COLOR_WHITE) + label:SetText(tostring(math.Round(new_volume * 100)) .. "%") + + self.voice.percentage_frame_label = label + + timer.Remove("ttt_score_close_perc_frame_" .. identifier) + + timer.Create("ttt_score_close_perc_frame_" .. identifier, 1.5, 1, function() + if not self.voice and frame and frame:IsVisible() then + frame:Close() + frame = nil + + return + end + + if + not self.voice + or not self.voice.percentage_frame + or not self.voice.percentage_frame:IsVisible() + then + return + end + + self.voice.percentage_frame:Hide() + end) + + hook.Add("ScoreboardHide", "TTTCloseVolumeFrame_" .. identifier, function() + if not self.voice and frame and frame:IsVisible() then + frame:Close() + frame = nil + + return + end + + if + not self.voice + or not self.voice.percentage_frame + or not self.voice.percentage_frame:IsVisible() + then + return + end + + self.voice.percentage_frame:Hide() + + timer.Remove("ttt_score_close_perc_frame_" .. identifier) + end) end vgui.Register("TTTScorePlayerRow", PANEL, "DButton") @@ -741,14 +782,14 @@ vgui.Register("TTTScorePlayerRow", PANEL, "DButton") -- @hook -- @realm client function GM:TTTScoreboardRowColorForPlayer(ply) - local col = colorTransparent + local col = colorTransparent - if IsValid(ply) and ply.HasRole and ply:HasRole() then - col = table.Copy(ply:GetRoleColor()) - col.a = 255 -- old value: 30 - end + if IsValid(ply) and ply.HasRole and ply:HasRole() then + col = table.Copy(ply:GetRoleColor()) + col.a = 255 -- old value: 30 + end - return col + return col end --- @@ -760,27 +801,27 @@ end -- @hook -- @realm client function GM:TTTScoreboardColorForPlayer(ply) - if IsValid(ply) then - local steamid64 = ply:SteamID64() - - if steamid64 then - steamid64 = tostring(steamid64) - - if dev_tbl[steamid64] and GetGlobalBool("ttt_highlight_dev", true) then - return namecolor.dev - elseif vip_tbl[steamid64] and GetGlobalBool("ttt_highlight_vip", true) then - return namecolor.vip - elseif addondev_tbl[steamid64] and GetGlobalBool("ttt_highlight_addondev", true) then - return namecolor.addondev - end - end - - if ply:IsAdmin() and GetGlobalBool("ttt_highlight_admins", true) then - return namecolor.admin - end - end - - return namecolor.default + if IsValid(ply) then + local steamid64 = ply:SteamID64() + + if steamid64 then + steamid64 = tostring(steamid64) + + if dev_tbl[steamid64] and GetGlobalBool("ttt_highlight_dev", true) then + return namecolor.dev + elseif vip_tbl[steamid64] and GetGlobalBool("ttt_highlight_vip", true) then + return namecolor.vip + elseif addondev_tbl[steamid64] and GetGlobalBool("ttt_highlight_addondev", true) then + return namecolor.addondev + end + end + + if ply:IsAdmin() and GetGlobalBool("ttt_highlight_admins", true) then + return namecolor.admin + end + end + + return namecolor.default end --- @@ -790,6 +831,4 @@ end -- @param DermaMenu menu A @{DermaMenu} that you can add options to that the player can click -- @hook -- @realm client -function GM:TTTScoreboardMenu(menu) - -end +function GM:TTTScoreboardMenu(menu) end diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_sb_team.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_sb_team.lua index 124694a27..4f70cad70 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_sb_team.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_sb_team.lua @@ -23,15 +23,15 @@ local cv_ttt_scoreboard_ascending --- -- @ignore function PANEL:Init() - self.name = "Unnamed" - self.color = COLOR_WHITE - self.rows = {} - self.rowcount = 0 - self.rows_sorted = {} - self.group = "spec" - - cv_ttt_scoreboard_sorting = GetConVar("ttt_scoreboard_sorting") - cv_ttt_scoreboard_ascending = GetConVar("ttt_scoreboard_ascending") + self.name = "Unnamed" + self.color = COLOR_WHITE + self.rows = {} + self.rowcount = 0 + self.rows_sorted = {} + self.group = "spec" + + cv_ttt_scoreboard_sorting = GetConVar("ttt_scoreboard_sorting") + cv_ttt_scoreboard_ascending = GetConVar("ttt_scoreboard_ascending") end --- @@ -40,9 +40,9 @@ end -- @param string group -- @realm client function PANEL:SetGroupInfo(name, color, group) - self.name = name - self.color = color - self.group = group + self.name = name + self.color = color + self.group = group end local bgcolor = Color(20, 20, 20, 150) @@ -50,231 +50,238 @@ local bgcolor = Color(20, 20, 20, 150) --- -- @ignore function PANEL:Paint() - -- Darkened background - draw.RoundedBox(8, 0, 0, self:GetWide(), self:GetTall(), bgcolor) + -- Darkened background + draw.RoundedBox(8, 0, 0, self:GetWide(), self:GetTall(), bgcolor) - surface.SetFont("treb_small") + surface.SetFont("treb_small") - -- Header bg - local txt = self.name .. " (" .. self.rowcount .. ")" - local w, h = surface.GetTextSize(txt) + -- Header bg + local txt = self.name .. " (" .. self.rowcount .. ")" + local w, h = surface.GetTextSize(txt) - draw.RoundedBox(8, 0, 0, w + 24, 20, self.color) + draw.RoundedBox(8, 0, 0, w + 24, 20, self.color) - -- Shadow - surface.SetTextPos(11, 11 - h * 0.5) - surface.SetTextColor(0, 0, 0, 200) - surface.DrawText(txt) + -- Shadow + surface.SetTextPos(11, 11 - h * 0.5) + surface.SetTextColor(0, 0, 0, 200) + surface.DrawText(txt) - -- Text - surface.SetTextPos(10, 10 - h * 0.5) - surface.SetTextColor(255, 255, 255, 255) - surface.DrawText(txt) + -- Text + surface.SetTextPos(10, 10 - h * 0.5) + surface.SetTextColor(255, 255, 255, 255) + surface.DrawText(txt) - -- Alternating row background - local y = 24 + -- Alternating row background + local y = 24 - for i = 1, #self.rows_sorted do - local row = self.rows_sorted[i] + for i = 1, #self.rows_sorted do + local row = self.rows_sorted[i] - if i % 2 ~= 0 then - surface.SetDrawColor(75, 75, 75, 100) - surface.DrawRect(0, y, self:GetWide(), row:GetTall()) - end + if i % 2 ~= 0 then + surface.SetDrawColor(75, 75, 75, 100) + surface.DrawRect(0, y, self:GetWide(), row:GetTall()) + end - y = y + row:GetTall() + 1 - end + y = y + row:GetTall() + 1 + end - -- Column darkening - local scr = sboard_panel.ply_frame.scroll.Enabled and 16 or 0 + -- Column darkening + local scr = sboard_panel.ply_frame.scroll.Enabled and 16 or 0 - surface.SetDrawColor(0, 0, 0, 80) + surface.SetDrawColor(0, 0, 0, 80) - if sboard_panel.cols then - local cx = self:GetWide() - scr + if sboard_panel.cols then + local cx = self:GetWide() - scr - for k, v in ipairs(sboard_panel.cols) do - cx = cx - v.Width + for k, v in ipairs(sboard_panel.cols) do + cx = cx - v.Width - if k % 2 == 1 then -- Draw for odd numbered columns - surface.DrawRect(cx - v.Width * 0.5, 0, v.Width, self:GetTall()) - end - end - else - -- If columns are not setup yet, fall back to darkening the areas for the - -- default columns - surface.DrawRect(self:GetWide() - 200 - scr, 0, 50, self:GetTall()) - surface.DrawRect(self:GetWide() - 100 - scr, 0, 50, self:GetTall()) - end + if k % 2 == 1 then -- Draw for odd numbered columns + surface.DrawRect(cx - v.Width * 0.5, 0, v.Width, self:GetTall()) + end + end + else + -- If columns are not setup yet, fall back to darkening the areas for the + -- default columns + surface.DrawRect(self:GetWide() - 200 - scr, 0, 50, self:GetTall()) + surface.DrawRect(self:GetWide() - 100 - scr, 0, 50, self:GetTall()) + end end --- -- @param Player ply -- @realm client function PANEL:AddPlayerRow(ply) - if ScoreGroup(ply) ~= self.group or self.rows[ply] then return end + if ScoreGroup(ply) ~= self.group or self.rows[ply] then + return + end - --- - -- @realm client - hook.Run("TTT2ScoreboardAddPlayerRow", ply) + --- + -- @realm client + -- stylua: ignore + hook.Run("TTT2ScoreboardAddPlayerRow", ply) - local row = vgui.Create("TTTScorePlayerRow", self) - row:SetPlayer(ply) + local row = vgui.Create("TTTScorePlayerRow", self) + row:SetPlayer(ply) - self.rows[ply] = row - self.rowcount = table.Count(self.rows) + self.rows[ply] = row + self.rowcount = table.Count(self.rows) - -- must force layout immediately or it takes its sweet time to do so - self:PerformLayout() + -- must force layout immediately or it takes its sweet time to do so + self:PerformLayout() end --- -- @param Player ply -- @realm client function PANEL:HasPlayerRow(ply) - return self.rows[ply] ~= nil + return self.rows[ply] ~= nil end --- -- @realm client function PANEL:HasRows() - return self.rowcount > 0 + return self.rowcount > 0 end local function FallbackSort(rowa, rowb) - return tostring(rowa) < tostring(rowb) + return tostring(rowa) < tostring(rowb) end local function SortFunc(rowa, rowb) - if not IsValid(rowa) then - if IsValid(rowb) then - return false - end - return FallbackSort(rowa, rowb) - end - - if not IsValid(rowb) then - return true - end - - local plya = rowa:GetPlayer() - local plyb = rowb:GetPlayer() - - if not IsValid(plya) then - if IsValid(plyb) then - return false - end - return FallbackSort(rowa, rowb) - end - - if not IsValid(plyb) then - return true - end - - local sort_mode = cv_ttt_scoreboard_sorting:GetString() - local sort_func = _G.sboard_sort[sort_mode] - - local comp - - if isfunction(sort_func) then - comp = sort_func(plya, plyb) - end - - if comp == nil then - comp = 0 - end - - if comp ~= 0 then - if cv_ttt_scoreboard_ascending:GetBool() then - return comp < 0 - else - return comp > 0 - end - else - if cv_ttt_scoreboard_ascending:GetBool() then - return strlower(plya:Nick()) < strlower(plyb:Nick()) - else - return strlower(plya:Nick()) > strlower(plyb:Nick()) - end - end + if not IsValid(rowa) then + if IsValid(rowb) then + return false + end + return FallbackSort(rowa, rowb) + end + + if not IsValid(rowb) then + return true + end + + local plya = rowa:GetPlayer() + local plyb = rowb:GetPlayer() + + if not IsValid(plya) then + if IsValid(plyb) then + return false + end + return FallbackSort(rowa, rowb) + end + + if not IsValid(plyb) then + return true + end + + local sort_mode = cv_ttt_scoreboard_sorting:GetString() + local sort_func = _G.sboard_sort[sort_mode] + + local comp + + if isfunction(sort_func) then + comp = sort_func(plya, plyb) + end + + if comp == nil then + comp = 0 + end + + if comp ~= 0 then + if cv_ttt_scoreboard_ascending:GetBool() then + return comp < 0 + else + return comp > 0 + end + else + if cv_ttt_scoreboard_ascending:GetBool() then + return strlower(plya:Nick()) < strlower(plyb:Nick()) + else + return strlower(plya:Nick()) > strlower(plyb:Nick()) + end + end end --- -- @realm client function PANEL:UpdateSortCache() - self.rows_sorted = {} + self.rows_sorted = {} - for _, row in pairs(self.rows) do - self.rows_sorted[#self.rows_sorted + 1] = row - end + for _, row in pairs(self.rows) do + self.rows_sorted[#self.rows_sorted + 1] = row + end - if #self.rows_sorted < 2 then return end + if #self.rows_sorted < 2 then + return + end - table.sort(self.rows_sorted, SortFunc) + table.sort(self.rows_sorted, SortFunc) end --- -- @realm client function PANEL:UpdatePlayerData() - local to_remove = {} + local to_remove = {} - for k, v in pairs(self.rows) do - -- Player still belongs in this group? - if IsValid(v) and IsValid(v:GetPlayer()) and ScoreGroup(v:GetPlayer()) == self.group then - v:UpdatePlayerData() - else - -- can't remove now, will break pairs - to_remove[#to_remove + 1] = k - end - end + for k, v in pairs(self.rows) do + -- Player still belongs in this group? + if IsValid(v) and IsValid(v:GetPlayer()) and ScoreGroup(v:GetPlayer()) == self.group then + v:UpdatePlayerData() + else + -- can't remove now, will break pairs + to_remove[#to_remove + 1] = k + end + end - local remCount = #to_remove + local remCount = #to_remove - if remCount == 0 then return end + if remCount == 0 then + return + end - for i = 1, remCount do - local ply = to_remove[i] - local pnl = self.rows[ply] + for i = 1, remCount do + local ply = to_remove[i] + local pnl = self.rows[ply] - if IsValid(pnl) then - pnl:Remove() - end + if IsValid(pnl) then + pnl:Remove() + end - self.rows[ply] = nil - end + self.rows[ply] = nil + end - self.rowcount = table.Count(self.rows) + self.rowcount = table.Count(self.rows) - self:UpdateSortCache() - self:InvalidateLayout() + self:UpdateSortCache() + self:InvalidateLayout() end --- -- @ignore function PANEL:PerformLayout() - if self.rowcount < 1 then - self:SetVisible(false) + if self.rowcount < 1 then + self:SetVisible(false) - return - end + return + end - self:SetSize(self:GetWide(), 30 + self.rowcount + self.rowcount * SB_ROW_HEIGHT) + self:SetSize(self:GetWide(), 30 + self.rowcount + self.rowcount * SB_ROW_HEIGHT) - -- Sort and layout player rows - self:UpdateSortCache() + -- Sort and layout player rows + self:UpdateSortCache() - local y = 24 + local y = 24 - for i = 1, #self.rows_sorted do - local v = self.rows_sorted[i] + for i = 1, #self.rows_sorted do + local v = self.rows_sorted[i] - v:SetPos(0, y) - v:SetSize(self:GetWide(), v:GetTall()) + v:SetPos(0, y) + v:SetSize(self:GetWide(), v:GetTall()) - y = y + v:GetTall() + 1 - end + y = y + v:GetTall() + 1 + end - self:SetSize(self:GetWide(), 30 + (y - 24)) + self:SetSize(self:GetWide(), 30 + (y - 24)) end vgui.Register("TTTScoreGroup", PANEL, "Panel") @@ -285,6 +292,4 @@ vgui.Register("TTTScoreGroup", PANEL, "Panel") -- @param Player ply The player whose row is created -- @hook -- @realm client -function GM:TTT2ScoreboardAddPlayerRow(ply) - -end +function GM:TTT2ScoreboardAddPlayerRow(ply) end diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_scrolllabel.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_scrolllabel.lua index 80f4cd20c..46eb16998 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_scrolllabel.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_scrolllabel.lua @@ -12,96 +12,104 @@ local vgui = vgui --- -- @ignore function PANEL:Init() - self.Label = vgui.Create("DLabel", self) - self.Label:SetPos(0, 0) + self.Label = vgui.Create("DLabel", self) + self.Label:SetPos(0, 0) - self.Scroll = vgui.Create("DVScrollBar", self) + self.Scroll = vgui.Create("DVScrollBar", self) end --- -- @return string -- @realm client function PANEL:GetLabel() - return self.Label + return self.Label end --- -- @param number dlta -- @realm client function PANEL:OnMouseWheeled(dlta) - if not self.Scroll then return end + if not self.Scroll then + return + end - self.Scroll:AddScroll(dlta * -2) + self.Scroll:AddScroll(dlta * -2) - self:InvalidateLayout() + self:InvalidateLayout() end --- -- @param boolean st -- @realm client function PANEL:SetScrollEnabled(st) - self.Scroll:SetEnabled(st) + self.Scroll:SetEnabled(st) end --- -- enable/disable scrollbar depending on content size -- @realm client function PANEL:UpdateScrollState() - if not self.Scroll then return end + if not self.Scroll then + return + end - self.Scroll:SetScroll(0) - self:SetScrollEnabled(false) + self.Scroll:SetScroll(0) + self:SetScrollEnabled(false) - self.Label:SetSize(self:GetWide(), self:GetTall()) - self.Label:SizeToContentsY() + self.Label:SetSize(self:GetWide(), self:GetTall()) + self.Label:SizeToContentsY() - self:SetScrollEnabled(self.Label:GetTall() > self:GetTall()) + self:SetScrollEnabled(self.Label:GetTall() > self:GetTall()) - self.Label:InvalidateLayout(true) - self:InvalidateLayout(true) + self.Label:InvalidateLayout(true) + self:InvalidateLayout(true) end --- -- @param string txt -- @realm client function PANEL:SetText(txt) - if not self.Label then return end - - self.Label:SetText(txt) - self:UpdateScrollState() - - -- I give up. VGUI, you have won. Here is your ugly hack to make the label - -- resize to the proper height, after you have completely mangled it the - -- first time I call SizeToContents. I don't know how or what happens to the - -- Label's internal state that makes it work when resizing a second time a - -- tick later (it certainly isn't any variant of PerformLayout I can find), - -- but it does. - local pnl = self.Panel - - timer.Simple(0, function() - if IsValid(pnl) then - pnl:UpdateScrollState() - end - end) + if not self.Label then + return + end + + self.Label:SetText(txt) + self:UpdateScrollState() + + -- I give up. VGUI, you have won. Here is your ugly hack to make the label + -- resize to the proper height, after you have completely mangled it the + -- first time I call SizeToContents. I don't know how or what happens to the + -- Label's internal state that makes it work when resizing a second time a + -- tick later (it certainly isn't any variant of PerformLayout I can find), + -- but it does. + local pnl = self.Panel + + timer.Simple(0, function() + if IsValid(pnl) then + pnl:UpdateScrollState() + end + end) end --- -- @ignore function PANEL:PerformLayout() - if not self.Scroll then return end + if not self.Scroll then + return + end - self.Label:SetVisible(self:IsVisible()) + self.Label:SetVisible(self:IsVisible()) - self.Scroll:SetPos(self:GetWide() - 16, 0) - self.Scroll:SetSize(16, self:GetTall()) + self.Scroll:SetPos(self:GetWide() - 16, 0) + self.Scroll:SetSize(16, self:GetTall()) - local was_on = self.Scroll.Enabled + local was_on = self.Scroll.Enabled - self.Scroll:SetUp(self:GetTall(), self.Label:GetTall()) - self.Scroll:SetEnabled(was_on) -- setup mangles enabled state + self.Scroll:SetUp(self:GetTall(), self.Label:GetTall()) + self.Scroll:SetEnabled(was_on) -- setup mangles enabled state - self.Label:SetPos(0, self.Scroll:GetOffset()) - self.Label:SetSize(self:GetWide() - (self.Scroll.Enabled and 16 or 0), self.Label:GetTall()) + self.Label:SetPos(0, self.Scroll:GetOffset()) + self.Label:SetSize(self:GetWide() - (self.Scroll.Enabled and 16 or 0), self.Label:GetTall()) end vgui.Register("ScrollLabel", PANEL, "Panel") diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_simpleclickicon.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_simpleclickicon.lua index fbc8d26b8..d0e5a4e7e 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_simpleclickicon.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_simpleclickicon.lua @@ -23,119 +23,113 @@ AccessorFunc(PANEL, "m_iIconSize", "IconSize") --- -- @ignore function PANEL:Init() - self.Icon = vgui.Create("DImage", self) - self.Icon:SetMouseInputEnabled(false) - self.Icon:SetKeyboardInputEnabled(false) + self.Icon = vgui.Create("DImage", self) + self.Icon:SetMouseInputEnabled(false) + self.Icon:SetKeyboardInputEnabled(false) - self.animPress = Derma_Anim("Press", self, self.PressedAnim) + self.animPress = Derma_Anim("Press", self, self.PressedAnim) - self:SetIconSize(64) + self:SetIconSize(64) end --- -- @param number mcode mouse key / code -- @realm client function PANEL:OnMousePressed(mcode) - if mcode == MOUSE_LEFT then - if self.OnClick then - self:OnClick() - end + if mcode == MOUSE_LEFT then + if self.OnClick then + self:OnClick() + end - self.animPress:Start(0.1) - end + self.animPress:Start(0.1) + end end --- -- @realm client -function PANEL:OnMouseReleased() - -end +function PANEL:OnMouseReleased() end --- -- @param boolean b -- @realm client function PANEL:Toggle(b) - self.toggled = b + self.toggled = b end --- -- @realm client -function PANEL:OpenMenu() - -end +function PANEL:OpenMenu() end --- -- @realm client -function PANEL:OnCursorEntered() - -end +function PANEL:OnCursorEntered() end --- -- @realm client -function PANEL:OnCursorExited() - -end +function PANEL:OnCursorExited() end --- -- @ignore -function PANEL:ApplySchemeSettings() - -end +function PANEL:ApplySchemeSettings() end --- -- @ignore function PANEL:PaintOver() - if self.animPress:Active() then return end + if self.animPress:Active() then + return + end - if self.toggled then - surface.SetDrawColor(0, 200, 0, 255) - surface.SetMaterial(matHover) + if self.toggled then + surface.SetDrawColor(0, 200, 0, 255) + surface.SetMaterial(matHover) - self:DrawTexturedRect() - end + self:DrawTexturedRect() + end end --- -- @ignore function PANEL:PerformLayout() - if self.animPress:Active() then return end + if self.animPress:Active() then + return + end - self:SetSize(self.m_iIconSize, self.m_iIconSize) + self:SetSize(self.m_iIconSize, self.m_iIconSize) - self.Icon:StretchToParent(0, 0, 0, 0) + self.Icon:StretchToParent(0, 0, 0, 0) end --- -- @param Material icon -- @realm client function PANEL:SetIcon(icon) - self.Icon:SetImage(icon) + self.Icon:SetImage(icon) end -- @param Material icon -- @realm client function PANEL:SetMaterial(material) - self.Icon:SetMaterial(material) + self.Icon:SetMaterial(material) end --- -- @return Material -- @realm client function PANEL:GetIcon() - return self.Icon:GetImage() + return self.Icon:GetImage() end --- -- @param Color c -- @realm client function PANEL:SetIconColor(c) - self.Icon:SetImageColor(c) + self.Icon:SetImageColor(c) end --- -- @ignore function PANEL:Think() - self.animPress:Run() + self.animPress:Run() end --- @@ -144,17 +138,19 @@ end -- @param table data -- @realm client function PANEL:PressedAnim(anim, delta, data) - if anim.Started then return end + if anim.Started then + return + end - if anim.Finished then - self.Icon:StretchToParent(0, 0, 0, 0) + if anim.Finished then + self.Icon:StretchToParent(0, 0, 0, 0) - return - end + return + end - local border = math.sin(delta * math.pi) * self.m_iIconSize * 0.05 + local border = math.sin(delta * math.pi) * self.m_iIconSize * 0.05 - self.Icon:StretchToParent(border, border, border, border) + self.Icon:StretchToParent(border, border, border, border) end vgui.Register("SimpleClickIcon", PANEL, "Panel") @@ -167,7 +163,7 @@ PANEL = {} -- reset --- -- @ignore function PANEL:Init() - self.Layers = {} + self.Layers = {} end --- @@ -175,65 +171,69 @@ end -- @param Panel pnl -- @realm client function PANEL:AddLayer(pnl) - if not IsValid(pnl) then return end + if not IsValid(pnl) then + return + end - pnl:SetParent(self) + pnl:SetParent(self) - pnl:SetMouseInputEnabled(false) - pnl:SetKeyboardInputEnabled(false) + pnl:SetMouseInputEnabled(false) + pnl:SetKeyboardInputEnabled(false) - self.Layers[#self.Layers + 1] = pnl + self.Layers[#self.Layers + 1] = pnl end --- -- @ignore function PANEL:PerformLayout() - if self.animPress:Active() then return end + if self.animPress:Active() then + return + end - self:SetSize(self.m_iIconSize, self.m_iIconSize) + self:SetSize(self.m_iIconSize, self.m_iIconSize) - self.Icon:StretchToParent(0, 0, 0, 0) + self.Icon:StretchToParent(0, 0, 0, 0) - for _, p in ipairs(self.Layers) do - p:SetPos(0, 0) - p:InvalidateLayout() - end + for _, p in ipairs(self.Layers) do + p:SetPos(0, 0) + p:InvalidateLayout() + end end --- -- @param Panel pnl -- @realm client function PANEL:EnableMousePassthrough(pnl) - for _, p in ipairs(self.Layers) do - if p == pnl then - p.OnMousePressed = function(s, mc) - s:GetParent():OnMousePressed(mc) - end - - p.OnCursorEntered = function(s) - s:GetParent():OnCursorEntered() - end - - p.OnCursorExited = function(s) - s:GetParent():OnCursorExited() - end - - p:SetMouseInputEnabled(true) - end - end + for _, p in ipairs(self.Layers) do + if p == pnl then + p.OnMousePressed = function(s, mc) + s:GetParent():OnMousePressed(mc) + end + + p.OnCursorEntered = function(s) + s:GetParent():OnCursorEntered() + end + + p.OnCursorExited = function(s) + s:GetParent():OnCursorExited() + end + + p:SetMouseInputEnabled(true) + end + end end --- -- @param number mcode mouse key / code -- @realm client function PANEL:OnMousePressed(mcode) - if mcode == MOUSE_LEFT then - if self.OnClick then - self:OnClick() - end + if mcode == MOUSE_LEFT then + if self.OnClick then + self:OnClick() + end - self.animPress:Start(0.1) - end + self.animPress:Start(0.1) + end end vgui.Register("LayeredClickIcon", PANEL, "SimpleClickIcon") @@ -247,43 +247,43 @@ PANEL = {} --- -- @ignore function PANEL:Init() - self.imgAvatar = vgui.Create("AvatarImage", self) - self.imgAvatar:SetMouseInputEnabled(false) - self.imgAvatar:SetKeyboardInputEnabled(false) + self.imgAvatar = vgui.Create("AvatarImage", self) + self.imgAvatar:SetMouseInputEnabled(false) + self.imgAvatar:SetKeyboardInputEnabled(false) - self.imgAvatar.PerformLayout = function(s) - s:Center() - end + self.imgAvatar.PerformLayout = function(s) + s:Center() + end - self:SetAvatarSize(32) - self:AddLayer(self.imgAvatar) + self:SetAvatarSize(32) + self:AddLayer(self.imgAvatar) end --- -- @param number s -- @realm client function PANEL:SetAvatarSize(s) - self.imgAvatar:SetSize(s, s) + self.imgAvatar:SetSize(s, s) end --- -- @param Player ply -- @realm client function PANEL:SetPlayer(ply) - self.imgAvatar:SetPlayer(ply) + self.imgAvatar:SetPlayer(ply) end --- -- @param number mcode mouse key / code -- @realm client function PANEL:OnMousePressed(mcode) - if mcode == MOUSE_LEFT then - if self.OnClick then - self:OnClick() - end + if mcode == MOUSE_LEFT then + if self.OnClick then + self:OnClick() + end - self.animPress:Start(0.1) - end + self.animPress:Start(0.1) + end end vgui.Register("SimpleClickIconAvatar", PANEL, "LayeredClickIcon") @@ -322,30 +322,35 @@ AccessorFunc(PANEL, "IconTextPos", "IconTextPos") --- -- @ignore function PANEL:Init() - self:SetIconText("") - self:SetIconTextColor(Color(255, 200, 0)) - self:SetIconFont("TargetID") - self:SetIconTextShadow({opacity = 255, offset = 2}) - self:SetIconTextPos({32, 32}) - - -- DPanelSelect loves to overwrite its children's PaintOver hooks and such, - -- so have to use a dummy panel to do some custom painting. - self.FakeLabel = vgui.Create("Panel", self) - self.FakeLabel.PerformLayout = function(s) - s:StretchToParent(0, 0, 0, 0) - end - - self:AddLayer(self.FakeLabel) - - return self.BaseClass.Init(self) + self:SetIconText("") + self:SetIconTextColor(Color(255, 200, 0)) + self:SetIconFont("TargetID") + self:SetIconTextShadow({ opacity = 255, offset = 2 }) + self:SetIconTextPos({ 32, 32 }) + + -- DPanelSelect loves to overwrite its children's PaintOver hooks and such, + -- so have to use a dummy panel to do some custom painting. + self.FakeLabel = vgui.Create("Panel", self) + self.FakeLabel.PerformLayout = function(s) + s:StretchToParent(0, 0, 0, 0) + end + + self:AddLayer(self.FakeLabel) + + return self.BaseClass.Init(self) end --- -- @ignore function PANEL:PerformLayout() - self:SetLabelText(self:GetIconText(), self:GetIconTextColor(), self:GetIconFont(), self:GetIconTextPos()) - - return self.BaseClass.PerformLayout(self) + self:SetLabelText( + self:GetIconText(), + self:GetIconTextColor(), + self:GetIconFont(), + self:GetIconTextPos() + ) + + return self.BaseClass.PerformLayout(self) end --- @@ -355,10 +360,10 @@ end -- @param table pos -- @realm client function PANEL:SetIconProperties(color, font, shadow, pos) - self:SetIconTextColor(color or self:GetIconTextColor()) - self:SetIconFont(font or self:GetIconFont()) - self:SetIconTextShadow(shadow or self:GetIconShadow()) - self:SetIconTextPos(pos or self:GetIconTextPos()) + self:SetIconTextColor(color or self:GetIconTextColor()) + self:SetIconFont(font or self:GetIconFont()) + self:SetIconTextShadow(shadow or self:GetIconShadow()) + self:SetIconTextPos(pos or self:GetIconTextPos()) end --- @@ -368,38 +373,38 @@ end -- @param table pos -- @realm client function PANEL:SetLabelText(text, color, font, pos) - if self.FakeLabel then - local spec = { - pos = pos, - color = color, - text = text, - font = font, - xalign = TEXT_ALIGN_CENTER, - yalign = TEXT_ALIGN_CENTER - } - - local shadow = self:GetIconTextShadow() - local opacity = shadow and shadow.opacity or 0 - local offset = shadow and shadow.offset or 0 - local drawfn = shadow and draw.TextShadow or draw.Text - - self.FakeLabel.Paint = function() - drawfn(spec, offset, opacity) - end - end + if self.FakeLabel then + local spec = { + pos = pos, + color = color, + text = text, + font = font, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + + local shadow = self:GetIconTextShadow() + local opacity = shadow and shadow.opacity or 0 + local offset = shadow and shadow.offset or 0 + local drawfn = shadow and draw.TextShadow or draw.Text + + self.FakeLabel.Paint = function() + drawfn(spec, offset, opacity) + end + end end --- -- @param number mcode mouse key / code -- @realm client function PANEL:OnMousePressed(mcode) - if mcode == MOUSE_LEFT then - if self.OnClick then - self:OnClick() - end + if mcode == MOUSE_LEFT then + if self.OnClick then + self:OnClick() + end - self.animPress:Start(0.1) - end + self.animPress:Start(0.1) + end end vgui.Register("SimpleClickIconLabelled", PANEL, "LayeredClickIcon") diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_simpleicon.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_simpleicon.lua index 468213b37..019b32bb0 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_simpleicon.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_simpleicon.lua @@ -24,129 +24,123 @@ AccessorFunc(PANEL, "m_iIconSize", "IconSize") --- -- @ignore function PANEL:Init() - self.Icon = vgui.Create("DImage", self) - self.Icon:SetMouseInputEnabled(false) - self.Icon:SetKeyboardInputEnabled(false) + self.Icon = vgui.Create("DImage", self) + self.Icon:SetMouseInputEnabled(false) + self.Icon:SetKeyboardInputEnabled(false) - self.lastLeftClick = 0 + self.lastLeftClick = 0 - self.animPress = Derma_Anim("Press", self, self.PressedAnim) + self.animPress = Derma_Anim("Press", self, self.PressedAnim) - self:SetIconSize(64) + self:SetIconSize(64) end --- -- @param number mcode mouse key / code -- @realm client function PANEL:OnMousePressed(mcode) - if mcode == MOUSE_LEFT then - self:DoClick() - self:PressedLeftMouse(SysTime() <= self.lastLeftClick + doubleClickTime) + if mcode == MOUSE_LEFT then + self:DoClick() + self:PressedLeftMouse(SysTime() <= self.lastLeftClick + doubleClickTime) - self.lastLeftClick = SysTime() + self.lastLeftClick = SysTime() - self.animPress:Start(0.1) - end + self.animPress:Start(0.1) + end end --- -- @realm client -function PANEL:OnMouseReleased() - -end +function PANEL:OnMouseReleased() end --- -- @param boolean doubleClick doubleClick happened in 0.8s -- @realm client -function PANEL:PressedLeftMouse(doubleClick) - -end +function PANEL:PressedLeftMouse(doubleClick) end --- -- @realm client -function PANEL:DoClick() - -end +function PANEL:DoClick() end --- -- @realm client -function PANEL:OpenMenu() - -end +function PANEL:OpenMenu() end -- -- @ignore -function PANEL:ApplySchemeSettings() - -end +function PANEL:ApplySchemeSettings() end --- -- @realm client function PANEL:OnCursorEntered() - self.PaintOverOld = self.PaintOver - self.PaintOver = self.PaintOverHovered + self.PaintOverOld = self.PaintOver + self.PaintOver = self.PaintOverHovered end --- -- @realm client function PANEL:OnCursorExited() - if self.PaintOver == self.PaintOverHovered then - self.PaintOver = self.PaintOverOld - end + if self.PaintOver == self.PaintOverHovered then + self.PaintOver = self.PaintOverOld + end end --- -- @realm client function PANEL:PaintOverHovered() - if self.animPress:Active() then return end + if self.animPress:Active() then + return + end - surface.SetDrawColor(255, 255, 255, 80) - surface.SetMaterial(matHover) + surface.SetDrawColor(255, 255, 255, 80) + surface.SetMaterial(matHover) - self:DrawTexturedRect() + self:DrawTexturedRect() end --- -- @ignore function PANEL:PerformLayout() - if self.animPress:Active() then return end + if self.animPress:Active() then + return + end - self:SetSize(self.m_iIconSize, self.m_iIconSize) + self:SetSize(self.m_iIconSize, self.m_iIconSize) - self.Icon:StretchToParent(0, 0, 0, 0) + self.Icon:StretchToParent(0, 0, 0, 0) end --- -- @param Material icon -- @realm client function PANEL:SetIcon(icon) - self.Icon:SetImage(icon) + self.Icon:SetImage(icon) end -- @param Material icon -- @realm client function PANEL:SetMaterial(material) - self.Icon:SetMaterial(material) + self.Icon:SetMaterial(material) end --- -- @return Material -- @realm client function PANEL:GetIcon() - return self.Icon:GetImage() + return self.Icon:GetImage() end --- -- @param Color c -- @realm client function PANEL:SetIconColor(c) - self.Icon:SetImageColor(c) + self.Icon:SetImageColor(c) end --- -- @ignore function PANEL:Think() - self.animPress:Run() + self.animPress:Run() end --- @@ -155,17 +149,19 @@ end -- @param table data -- @realm client function PANEL:PressedAnim(anim, delta, data) - if anim.Started then return end + if anim.Started then + return + end - if anim.Finished then - self.Icon:StretchToParent(0, 0, 0, 0) + if anim.Finished then + self.Icon:StretchToParent(0, 0, 0, 0) - return - end + return + end - local border = math.sin(delta * math.pi) * (self.m_iIconSize * 0.05) + local border = math.sin(delta * math.pi) * (self.m_iIconSize * 0.05) - self.Icon:StretchToParent(border, border, border, border) + self.Icon:StretchToParent(border, border, border, border) end vgui.Register("SimpleIcon", PANEL, "Panel") @@ -178,7 +174,7 @@ PANEL = {} --- -- @ignore function PANEL:Init() - self.Layers = {} + self.Layers = {} end --- @@ -186,51 +182,55 @@ end -- @param Panel pnl -- @realm client function PANEL:AddLayer(pnl) - if not IsValid(pnl) then return end + if not IsValid(pnl) then + return + end - pnl:SetParent(self) - pnl:SetMouseInputEnabled(false) - pnl:SetKeyboardInputEnabled(false) + pnl:SetParent(self) + pnl:SetMouseInputEnabled(false) + pnl:SetKeyboardInputEnabled(false) - self.Layers[#self.Layers + 1] = pnl + self.Layers[#self.Layers + 1] = pnl end --- -- @ignore function PANEL:PerformLayout() - if self.animPress:Active() then return end + if self.animPress:Active() then + return + end - self:SetSize(self.m_iIconSize, self.m_iIconSize) + self:SetSize(self.m_iIconSize, self.m_iIconSize) - self.Icon:StretchToParent(0, 0, 0, 0) + self.Icon:StretchToParent(0, 0, 0, 0) - for _, p in ipairs(self.Layers) do - p:SetPos(0, 0) - p:InvalidateLayout() - end + for _, p in ipairs(self.Layers) do + p:SetPos(0, 0) + p:InvalidateLayout() + end end --- -- @param Panel pnl -- @realm client function PANEL:EnableMousePassthrough(pnl) - for _, p in ipairs(self.Layers) do - if p == pnl then - p.OnMousePressed = function(s, mc) - s:GetParent():OnMousePressed(mc) - end - - p.OnCursorEntered = function(s) - s:GetParent():OnCursorEntered() - end - - p.OnCursorExited = function(s) - s:GetParent():OnCursorExited() - end - - p:SetMouseInputEnabled(true) - end - end + for _, p in ipairs(self.Layers) do + if p == pnl then + p.OnMousePressed = function(s, mc) + s:GetParent():OnMousePressed(mc) + end + + p.OnCursorEntered = function(s) + s:GetParent():OnCursorEntered() + end + + p.OnCursorExited = function(s) + s:GetParent():OnCursorExited() + end + + p:SetMouseInputEnabled(true) + end + end end vgui.Register("LayeredIcon", PANEL, "SimpleIcon") @@ -244,30 +244,32 @@ PANEL = {} --- -- @ignore function PANEL:Init() - self.imgAvatar = vgui.Create("AvatarImage", self) - self.imgAvatar:SetMouseInputEnabled(false) - self.imgAvatar:SetKeyboardInputEnabled(false) - self.imgAvatar.PerformLayout = function(s) s:Center() end + self.imgAvatar = vgui.Create("AvatarImage", self) + self.imgAvatar:SetMouseInputEnabled(false) + self.imgAvatar:SetKeyboardInputEnabled(false) + self.imgAvatar.PerformLayout = function(s) + s:Center() + end - self:SetAvatarSize(32) + self:SetAvatarSize(32) - self:AddLayer(self.imgAvatar) + self:AddLayer(self.imgAvatar) - --return self.BaseClass.Init(self) + --return self.BaseClass.Init(self) end --- -- @param number s -- @realm client function PANEL:SetAvatarSize(s) - self.imgAvatar:SetSize(s, s) + self.imgAvatar:SetSize(s, s) end --- -- @param Player ply -- @realm client function PANEL:SetPlayer(ply) - self.imgAvatar:SetPlayer(ply) + self.imgAvatar:SetPlayer(ply) end vgui.Register("SimpleIconAvatar", PANEL, "LayeredIcon") @@ -306,30 +308,35 @@ AccessorFunc(PANEL, "IconTextPos", "IconTextPos") --- -- @ignore function PANEL:Init() - self:SetIconText("") - self:SetIconTextColor(Color(255, 200, 0)) - self:SetIconFont("TargetID") - self:SetIconTextShadow({opacity = 255, offset = 2}) - self:SetIconTextPos({32, 32}) - - -- DPanelSelect loves to overwrite its children's PaintOver hooks and such, - -- so have to use a dummy panel to do some custom painting. - self.FakeLabel = vgui.Create("Panel", self) - self.FakeLabel.PerformLayout = function(s) - s:StretchToParent(0, 0, 0, 0) - end - - self:AddLayer(self.FakeLabel) - - return self.BaseClass.Init(self) + self:SetIconText("") + self:SetIconTextColor(Color(255, 200, 0)) + self:SetIconFont("TargetID") + self:SetIconTextShadow({ opacity = 255, offset = 2 }) + self:SetIconTextPos({ 32, 32 }) + + -- DPanelSelect loves to overwrite its children's PaintOver hooks and such, + -- so have to use a dummy panel to do some custom painting. + self.FakeLabel = vgui.Create("Panel", self) + self.FakeLabel.PerformLayout = function(s) + s:StretchToParent(0, 0, 0, 0) + end + + self:AddLayer(self.FakeLabel) + + return self.BaseClass.Init(self) end --- -- @ignore function PANEL:PerformLayout() - self:SetLabelText(self:GetIconText(), self:GetIconTextColor(), self:GetIconFont(), self:GetIconTextPos()) - - return self.BaseClass.PerformLayout(self) + self:SetLabelText( + self:GetIconText(), + self:GetIconTextColor(), + self:GetIconFont(), + self:GetIconTextPos() + ) + + return self.BaseClass.PerformLayout(self) end --- @@ -339,10 +346,10 @@ end -- @param table pos -- @realm client function PANEL:SetIconProperties(color, font, shadow, pos) - self:SetIconTextColor(color or self:GetIconTextColor()) - self:SetIconFont(font or self:GetIconFont()) - self:SetIconTextShadow(shadow or self:GetIconShadow()) - self:SetIconTextPos(pos or self:GetIconTextPos()) + self:SetIconTextColor(color or self:GetIconTextColor()) + self:SetIconFont(font or self:GetIconFont()) + self:SetIconTextShadow(shadow or self:GetIconShadow()) + self:SetIconTextPos(pos or self:GetIconTextPos()) end --- @@ -352,17 +359,24 @@ end -- @param table pos -- @realm client function PANEL:SetLabelText(text, color, font, pos) - if self.FakeLabel then - local spec = {pos = pos, color = color, text = text, font = font, xalign = TEXT_ALIGN_CENTER, yalign = TEXT_ALIGN_CENTER} - local shadow = self:GetIconTextShadow() - local opacity = shadow and shadow.opacity or 0 - local offset = shadow and shadow.offset or 0 - local drawfn = shadow and draw.TextShadow or draw.Text - - self.FakeLabel.Paint = function() - drawfn(spec, offset, opacity) - end - end + if self.FakeLabel then + local spec = { + pos = pos, + color = color, + text = text, + font = font, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + local shadow = self:GetIconTextShadow() + local opacity = shadow and shadow.opacity or 0 + local offset = shadow and shadow.offset or 0 + local drawfn = shadow and draw.TextShadow or draw.Text + + self.FakeLabel.Paint = function() + drawfn(spec, offset, opacity) + end + end end vgui.Register("SimpleIconLabelled", PANEL, "LayeredIcon") diff --git a/gamemodes/terrortown/gamemode/client/vgui/cl_simpleroleicon.lua b/gamemodes/terrortown/gamemode/client/vgui/cl_simpleroleicon.lua index 165396f87..de17450f2 100644 --- a/gamemodes/terrortown/gamemode/client/vgui/cl_simpleroleicon.lua +++ b/gamemodes/terrortown/gamemode/client/vgui/cl_simpleroleicon.lua @@ -18,49 +18,41 @@ AccessorFunc(PANEL, "m_iIconSize", "IconSize") --- -- @ignore function PANEL:Init() - self.Icon = vgui.Create("DRoleImage", self) - self.Icon:SetMouseInputEnabled(false) - self.Icon:SetKeyboardInputEnabled(false) + self.Icon = vgui.Create("DRoleImage", self) + self.Icon:SetMouseInputEnabled(false) + self.Icon:SetKeyboardInputEnabled(false) - self.animPress = Derma_Anim("Press", self, self.PressedAnim) + self.animPress = Derma_Anim("Press", self, self.PressedAnim) - self:SetIconSize(64) + self:SetIconSize(64) end --- -- @param number mcode mouse key / code -- @realm client function PANEL:OnMousePressed(mcode) - if mcode == MOUSE_LEFT then - self:DoClick() + if mcode == MOUSE_LEFT then + self:DoClick() - self.animPress:Start(0.1) - end + self.animPress:Start(0.1) + end end --- -- @realm client -function PANEL:OnMouseReleased() - -end +function PANEL:OnMouseReleased() end --- -- @realm client -function PANEL:DoClick() - -end +function PANEL:DoClick() end --- -- @realm client -function PANEL:OpenMenu() - -end +function PANEL:OpenMenu() end --- -- @ignore -function PANEL:ApplySchemeSettings() - -end +function PANEL:ApplySchemeSettings() end local oldPaintOver = PANEL.PaintOver @@ -69,79 +61,83 @@ local oldPaintOver = PANEL.PaintOver -- @param number h height -- @realm client function PANEL:PaintOver(w, h) - if self.toggled then - surface.SetDrawColor(0, 200, 0, 255) - surface.SetMaterial(matHover) + if self.toggled then + surface.SetDrawColor(0, 200, 0, 255) + surface.SetMaterial(matHover) - self:DrawTexturedRect() - end + self:DrawTexturedRect() + end - if isfunction(oldPaintOver) then - oldPaintOver(self, w, h) - end + if isfunction(oldPaintOver) then + oldPaintOver(self, w, h) + end end --- -- @realm client function PANEL:OnCursorEntered() - self.PaintOverOld = self.PaintOver - self.PaintOver = self.PaintOverHovered + self.PaintOverOld = self.PaintOver + self.PaintOver = self.PaintOverHovered end --- -- @realm client function PANEL:OnCursorExited() - if self.PaintOver == self.PaintOverHovered then - self.PaintOver = self.PaintOverOld - end + if self.PaintOver == self.PaintOverHovered then + self.PaintOver = self.PaintOverOld + end end --- -- @realm client function PANEL:PaintOverHovered() - if self.animPress:Active() or self.toggled then return end + if self.animPress:Active() or self.toggled then + return + end - surface.SetDrawColor(255, 255, 255, 80) - surface.SetMaterial(matHover) + surface.SetDrawColor(255, 255, 255, 80) + surface.SetMaterial(matHover) - self:DrawTexturedRect() + self:DrawTexturedRect() end --- -- @ignore function PANEL:PerformLayout() - if self.animPress:Active() then return end + if self.animPress:Active() then + return + end - self:SetSize(self.m_iIconSize, self.m_iIconSize) + self:SetSize(self.m_iIconSize, self.m_iIconSize) - self.Icon:StretchToParent(0, 0, 0, 0) + self.Icon:StretchToParent(0, 0, 0, 0) end --- -- @param Material icon -- @realm client function PANEL:SetIcon(icon) - self.Icon:SetImage(icon) + self.Icon:SetImage(icon) end --- -- @return Material -- @realm client function PANEL:GetIcon() - return self.Icon:GetImage() + return self.Icon:GetImage() end --- -- @param Color c -- @realm client function PANEL:SetIconColor(c) - self.Icon:SetImageColor(c) + self.Icon:SetImageColor(c) end --- -- @ignore function PANEL:Think() - self.animPress:Run() + self.animPress:Run() end --- @@ -150,24 +146,26 @@ end -- @param table data -- @realm client function PANEL:PressedAnim(anim, delta, data) - if anim.Started then return end + if anim.Started then + return + end - if anim.Finished then - self.Icon:StretchToParent(0, 0, 0, 0) + if anim.Finished then + self.Icon:StretchToParent(0, 0, 0, 0) - return - end + return + end - local border = math.sin(delta * math.pi) * (self.m_iIconSize * 0.05) + local border = math.sin(delta * math.pi) * (self.m_iIconSize * 0.05) - self.Icon:StretchToParent(border, border, border, border) + self.Icon:StretchToParent(border, border, border, border) end --- -- @param boolean b -- @realm client function PANEL:Toggle(b) - self.toggled = b + self.toggled = b end vgui.Register("SimpleRoleIcon", PANEL, "Panel") diff --git a/gamemodes/terrortown/gamemode/server/sv_addonchecker.lua b/gamemodes/terrortown/gamemode/server/sv_addonchecker.lua index 471d3ce35..41160a162 100644 --- a/gamemodes/terrortown/gamemode/server/sv_addonchecker.lua +++ b/gamemodes/terrortown/gamemode/server/sv_addonchecker.lua @@ -10,472 +10,634 @@ ADDON_OUTDATED = 1 addonChecker = addonChecker or {} addonChecker.curatedList = { - ["656662924"] = { -- Killer Notifier by nerzlakai96 - alternative = "1367128301", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["167547072"] = { -- Killer Notifier by StarFox - alternative = "1367128301", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["649273679"] = { -- Role Counter by Zaratusa - alternative = "1367128301", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["1531794562"] = { -- Killer Info by wurffl - alternative = "1367128301", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["257624318"] = { -- Killer info by DerRene - alternative = "1367128301", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["308966836"] = { -- Death Faker by Exho - alternative = "1473581448", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["922007616"] = { -- Death Faker by BocciardoLight - alternative = "1473581448", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["785423990"] = { -- Death Faker by markusmarkusz - alternative = "1473581448", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["236217898"] = { -- Death Faker by jayjayjay1 - alternative = "1473581448", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["912181642"] = { -- Death Faker by w4rum - alternative = "1473581448", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["127865722"] = { -- TTT ULX by Bender180 - alternative = "1362430347", - reason = "All TTT2 features are missing from this version.", - type = ADDON_INCOMPATIBLE - }, - ["305101059"] = { -- ID Bomb by MolagA - type = ADDON_INCOMPATIBLE - }, - ["663328966"] = { -- damagelogs by Hundreth - reason = "Breaks the TTT2 gamemode, alternative available here: https://github.com/Alf21/tttdamagelogs", - type = ADDON_INCOMPATIBLE - }, - ["404599106"] = { -- SpectatorDeathmatch by P4sca1 [EGM] - alternative = "1997666028", - reason = "Breaks the TTT2 gamemode.", - type = ADDON_INCOMPATIBLE - }, - ["1376434172"] = { -- Golden Deagle by DaniX_Chile - alternative = "1398388611", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["1434026961"] = { -- Golden Deagle by Mangonaut - alternative = "1398388611", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["303535203"] = { -- Golden Deagle by Navusaur - alternative = "1398388611", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["1325415810"] = { -- Golden Deagle by Faedon | Max - alternative = "1398388611", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["648505481"] = { -- Golden Deagle by TypicalRookie - alternative = "1398388611", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["659643589"] = { -- Golden Deagle by 中國是同性戀 - alternative = "1398388611", - reason = "Does not work with our role system.", - type = ADDON_INCOMPATIBLE - }, - ["828347015"] = { -- TTT Totem by Gamefreak - alternative = "1566390281", - reason = "Has its own role selection system.", - type = ADDON_INCOMPATIBLE - }, - ["644532564"] = { -- TTT Totem Content by Gamefreak - type = ADDON_INCOMPATIBLE - }, - ["1092556189"] = { -- Town of Terror by Jenssons - reason = "Inferior role selection system that breaks TTT2.", - type = ADDON_INCOMPATIBLE - }, - ["1215502383"] = { -- Custom Roles by Noxx - reason = "Inferior role selection system that breaks TTT2.", - type = ADDON_INCOMPATIBLE - }, - ["886346394"] = { -- Identity Swapper by Lesh - reason = "Overwrites the targetID gamemode function and therefore removes the TTT2 targetID.", - type = ADDON_INCOMPATIBLE - }, - ["844284735"] = { -- Identity Swapper by Saty - reason = "Overwrites the targetID gamemode function and therefore removes the TTT2 targetID.", - type = ADDON_INCOMPATIBLE - }, - ["1382102057"] = { -- Thanos's Infinity Gauntlet SWEP and Model - reason = "Sets everybodies sprintspeed quite high.", - type = ADDON_INCOMPATIBLE - }, - ["1757151213"] = { -- Random Shop by Lupus - reason = "Random shop is already included in TTT2.", - type = ADDON_INCOMPATIBLE - }, - ["481440358"] = { -- Drowning Indicator by Moe - reason = "A drowning indicator is already included in TTT2.", - type = ADDON_INCOMPATIBLE - }, - ["933056549"] = { -- Sprint by Fresh Garry - reason = "A sprinting system is already included in TTT2.", - type = ADDON_INCOMPATIBLE - }, - ["1729301513"] = { -- Sprint by Lesh - reason = "A sprinting system is already included in TTT2.", - type = ADDON_INCOMPATIBLE - }, - ["367945571"] = { -- Advanced Body Search by Mr Trung - reason = "This addon breaks the whole body confirmation system.", - type = ADDON_INCOMPATIBLE - }, - ["878772496"] = { -- BEM by Long Long Longson - reason = "A reworked shop is already part of TTT2.", - type = ADDON_INCOMPATIBLE - }, - ["1107420703"] = { -- BEM by Fresh Garry - reason = "A reworked shop is already part of TTT2.", - type = ADDON_INCOMPATIBLE - }, - ["290961117"] = { -- Destructible Doors by Exho - reason = "Breakable doors are already a part of TTT2 that can be enabled with a convar. This addon has also problems with area portals in some doors.", - type = ADDON_INCOMPATIBLE - }, - ["606792331"] = { -- Advanced disguiser by Gamefreak - alternative = "2144375749", - reason = "Does not work with TTT2 targetID.", - type = ADDON_INCOMPATIBLE - }, - ["610632051"] = { -- Advanced disguiser by Killberty - alternative = "2144375749", - reason = "Does not work with TTT2 targetID.", - type = ADDON_INCOMPATIBLE - }, - ["654341247"] = { -- Clairvoyancy by Liberty - alternative = "1637001449", - reason = "Does not use the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["130051756"] = { -- Spartan Kick by uacnix - alternative = "1584675927", - reason = "Broken area portals in some doors and improved use of TTT2 systems.", - type = ADDON_OUTDATED - }, - ["122277196"] = { -- Spartan Kick by Chowder908 - alternative = "1584675927", - reason = "Broken area portals in some doors and improved use of TTT2 systems.", - type = ADDON_OUTDATED - }, - ["922510848"] = { -- Spartan Kick by BocciardoLight - alternative = "1584675927", - reason = "Broken area portals in some doors and improved use of TTT2 systems.", - type = ADDON_OUTDATED - }, - ["282584080"] = { -- Spartan Kick by Porter - alternative = "1584675927", - reason = "Broken area portals in some doors and improved use of TTT2 systems.", - type = ADDON_OUTDATED - }, - ["1092632443"] = { -- Spartan Kick by Jenssons - alternative = "1584675927", - reason = "Broken area portals in some doors and improved use of TTT2 systems.", - type = ADDON_OUTDATED - }, - ["1523332573"] = { -- Thomas by Tubner - alternative = "2024902834", - reason = "Broken models, sounds and damage inflictor info.", - type = ADDON_OUTDATED - }, - ["962093923"] = { -- Thomas by ThunfischArnold - alternative = "2024902834", - reason = "Broken models, sounds and damage inflictor info.", - type = ADDON_OUTDATED - }, - ["1479128258"] = { -- Thomas by JollyJelly1001 - alternative = "2024902834", - reason = "Broken models, sounds and damage inflictor info.", - type = ADDON_OUTDATED - }, - ["1390915062"] = { -- Thomas by Doc Snyder - alternative = "2024902834", - reason = "Broken models, sounds and damage inflictor info.", - type = ADDON_OUTDATED - }, - ["1171584841"] = { -- Thomas by Maxdome - alternative = "2024902834", - reason = "Broken models, sounds and damage inflictor info.", - type = ADDON_OUTDATED - }, - ["811718553"] = { -- Thomas by Mr C Funk - alternative = "2024902834", - reason = "Broken models, sounds and damage inflictor info.", - type = ADDON_OUTDATED - }, - ["922355426"] = { -- Melon Mine by Phoenixf129 - alternative = "1629914760", - reason = "Doesn't really work with the TTT2 role system.", - type = ADDON_OUTDATED - }, - ["960077088"] = { -- Melon Mine by TheSoulrester - alternative = "1629914760", - reason = "Doesn't really work with the TTT2 role system.", - type = ADDON_OUTDATED - }, - ["309299668"] = { -- Martyrdom by Exho - alternative = "1630269736", - reason = "Does not use the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["899206223"] = { -- Martyrdom by Koksgesicht - alternative = "1630269736", - reason = "Does not use the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["652046425"] = { -- Juggernaut Suit by Zaratusa - alternative = "2157829981", - reason = "Does not use the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["650523807"] = { -- Lucky Horseshoe by Zaratusa - alternative = "2157888469", - reason = "Does not use the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["650523765"] = { -- Hermes Boots by Zaratusa - alternative = "2157850255", - reason = "Does not use the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["1470823315"] = { -- Beartrap by Milkwater - alternative = "1641605106", - reason = "Has some problems with damage after roundend and doesn't use the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["407751746"] = { -- Beartrap by Nerdhive - alternative = "1641605106", - reason = "Has some problems with damage after roundend and doesn't use the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["273623128"] = { -- zombie perk bottles by Hoff - alternative = "842302491", - type = ADDON_OUTDATED - }, - ["1387914296"] = { -- zombie perk bottles by Schmitler - alternative = "842302491", - type = ADDON_OUTDATED - }, - ["1371596971"] = { -- zombie perk bottles by Amenius - alternative = "842302491", - type = ADDON_OUTDATED - }, - ["860794236"] = { -- zombie perk bottles by Menzek - alternative = "842302491", - type = ADDON_OUTDATED - }, - ["1198504029"] = { -- zombie perk bottles by RedocPlays - alternative = "842302491", - type = ADDON_OUTDATED - }, - ["911658617"] = { -- zombie perk bottles by Luchix - alternative = "842302491", - type = ADDON_OUTDATED - }, - ["869353740"] = { -- zombie perk bottles by Railroad Engineer 111 - alternative = "842302491", - type = ADDON_OUTDATED - }, - ["310403937"] = { -- Prop Disguiser by Exho - alternative = "1662844145", - type = ADDON_OUTDATED - }, - ["843092697"] = { -- Prop Disguiser by Soren - alternative = "1662844145", - type = ADDON_OUTDATED - }, - ["937535488"] = { -- Prop Disguiser by St Addi - alternative = "1662844145", - type = ADDON_OUTDATED - }, - ["1168304202"] = { -- Prop Disguiser by Izellix - alternative = "1662844145", - type = ADDON_OUTDATED - }, - ["1361103159"] = { -- Prop Disguiser by Derp altamas - alternative = "1662844145", - type = ADDON_OUTDATED - }, - ["1301826793"] = { -- Prop Disguiser by Akechi - alternative = "1662844145", - type = ADDON_OUTDATED - }, - ["254779132"] = { -- Dead Ringer by Porter - alternative = "810154456", - type = ADDON_OUTDATED - }, - ["1315377462"] = { -- Dead Ringer by MuratYilderimTM - alternative = "810154456", - type = ADDON_OUTDATED - }, - ["240281783"] = { -- Dead Ringer by Niandra - alternative = "810154456", - type = ADDON_OUTDATED - }, - ["284419411"] = { -- Minifier by Lykrast - alternative = "1896918348", - reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["1338887971"] = { -- Minifier by Evan - alternative = "1896918348", - reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["1551396306"] = { -- Minifier by FaBe2 - alternative = "1896918348", - reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["1354031183"] = { -- Minifier by SnowSoulAnget - alternative = "1896918348", - reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["1670942051"] = { -- Minifier by Not Jesus - alternative = "1896918348", - reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["1376141849"] = { -- Minifier by --- - alternative = "1896918348", - reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["1599819393"] = { -- Minifier by Coe - alternative = "1896918348", - reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", - type = ADDON_OUTDATED - }, - ["863963592"] = { -- Super Soda by --- - alternative = "1815518231", - reason = "Less bottles, no integration in TTT2 systems, generally buggy.", - type = ADDON_OUTDATED - }, - ["801433502"] = { -- Defibrillator by Minty - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["162581348"] = { -- Defibrillator by Willox - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["921953443"] = { -- Defibrillator by BocciardoLight - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["785796753"] = { -- Defibrillator by Shiratori - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["299479443"] = { -- Defibrillator by PixL - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["1553970612"] = { -- Defibrillator by DasNedwork - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["821963023"] = { -- Defibrillator by DarkIce - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["1695163471"] = { -- Defibrillator by Mr. KobraX - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["1563553647"] = { -- Defibrillator by _BLU - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["1445820970"] = { -- Defibrillator by Mangonaut - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["805980529"] = { -- Defibrillator by Musiker15 - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["422006754"] = { -- Defibrillator by Toxic_Terrorists - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["840688276"] = { -- Defibrillator by Saty - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["1198501844"] = { -- Defibrillator by Brannium - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["1199113632"] = { -- Defibrillator by Brannium - alternative = "2115944312", - reason = "Improved integration into TTT2 systems.", - type = ADDON_OUTDATED - }, - ["290945941"] = { -- Door Locker by Exho - alternative = "2115945573", - reason = "Improved integration into TTT2 systems and fixed area portal problems for some doors.", - type = ADDON_OUTDATED - }, - ["1132650862"] = { -- Door Locker by Saiyajin ByTayro - alternative = "2115945573", - reason = "Improved integration into TTT2 systems and fixed area portal problems for some doors.", - type = ADDON_OUTDATED - }, - ["1361100842"] = { -- Door Locker by Altamas - alternative = "2115945573", - reason = "Improved integration into TTT2 systems and fixed area portal problems for some doors.", - type = ADDON_OUTDATED - } + ["656662924"] = { -- Killer Notifier by nerzlakai96 + alternative = "1367128301", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["167547072"] = { -- Killer Notifier by StarFox + alternative = "1367128301", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["649273679"] = { -- Role Counter by Zaratusa + alternative = "1367128301", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["1531794562"] = { -- Killer Info by wurffl + alternative = "1367128301", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["257624318"] = { -- Killer info by DerRene + alternative = "1367128301", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["308966836"] = { -- Death Faker by Exho + alternative = "1473581448", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["922007616"] = { -- Death Faker by BocciardoLight + alternative = "1473581448", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["785423990"] = { -- Death Faker by markusmarkusz + alternative = "1473581448", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["236217898"] = { -- Death Faker by jayjayjay1 + alternative = "1473581448", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["912181642"] = { -- Death Faker by w4rum + alternative = "1473581448", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["127865722"] = { -- TTT ULX by Bender180 + alternative = "1362430347", + reason = "All TTT2 features are missing from this version.", + type = ADDON_INCOMPATIBLE, + }, + ["305101059"] = { -- ID Bomb by MolagA + type = ADDON_INCOMPATIBLE, + }, + ["663328966"] = { -- damagelogs by Hundreth + reason = "Breaks the TTT2 gamemode, alternative available here: https://github.com/Alf21/tttdamagelogs", + type = ADDON_INCOMPATIBLE, + }, + ["404599106"] = { -- SpectatorDeathmatch by P4sca1 [EGM] + alternative = "1997666028", + reason = "Breaks the TTT2 gamemode.", + type = ADDON_INCOMPATIBLE, + }, + ["1376434172"] = { -- Golden Deagle by DaniX_Chile + alternative = "1398388611", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["1434026961"] = { -- Golden Deagle by Mangonaut + alternative = "1398388611", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["303535203"] = { -- Golden Deagle by Navusaur + alternative = "1398388611", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["1325415810"] = { -- Golden Deagle by Faedon | Max + alternative = "1398388611", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["648505481"] = { -- Golden Deagle by TypicalRookie + alternative = "1398388611", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["659643589"] = { -- Golden Deagle by 中國是同性戀 + alternative = "1398388611", + reason = "Does not work with our role system.", + type = ADDON_INCOMPATIBLE, + }, + ["828347015"] = { -- TTT Totem by Gamefreak + alternative = "1566390281", + reason = "Has its own role selection system.", + type = ADDON_INCOMPATIBLE, + }, + ["644532564"] = { -- TTT Totem Content by Gamefreak + type = ADDON_INCOMPATIBLE, + }, + ["1092556189"] = { -- Town of Terror by Jenssons + reason = "Inferior role selection system that breaks TTT2.", + type = ADDON_INCOMPATIBLE, + }, + ["1215502383"] = { -- Custom Roles by Noxx + reason = "Inferior role selection system that breaks TTT2.", + type = ADDON_INCOMPATIBLE, + }, + ["886346394"] = { -- Identity Swapper by Lesh + reason = "Overwrites the targetID gamemode function and therefore removes the TTT2 targetID.", + type = ADDON_INCOMPATIBLE, + }, + ["844284735"] = { -- Identity Swapper by Saty + reason = "Overwrites the targetID gamemode function and therefore removes the TTT2 targetID.", + type = ADDON_INCOMPATIBLE, + }, + ["1382102057"] = { -- Thanos's Infinity Gauntlet SWEP and Model + reason = "Sets everybodies sprintspeed quite high.", + type = ADDON_INCOMPATIBLE, + }, + ["1757151213"] = { -- Random Shop by Lupus + reason = "Random shop is already included in TTT2.", + type = ADDON_INCOMPATIBLE, + }, + ["481440358"] = { -- Drowning Indicator by Moe + reason = "A drowning indicator is already included in TTT2.", + type = ADDON_INCOMPATIBLE, + }, + ["933056549"] = { -- Sprint by Fresh Garry + reason = "A sprinting system is already included in TTT2.", + type = ADDON_INCOMPATIBLE, + }, + ["1729301513"] = { -- Sprint by Lesh + reason = "A sprinting system is already included in TTT2.", + type = ADDON_INCOMPATIBLE, + }, + ["367945571"] = { -- Advanced Body Search by Mr Trung + reason = "This addon breaks the whole body confirmation system.", + type = ADDON_INCOMPATIBLE, + }, + ["878772496"] = { -- BEM by Long Long Longson + reason = "A reworked shop is already part of TTT2.", + type = ADDON_INCOMPATIBLE, + }, + ["1107420703"] = { -- BEM by Fresh Garry + reason = "A reworked shop is already part of TTT2.", + type = ADDON_INCOMPATIBLE, + }, + ["290961117"] = { -- Destructible Doors by Exho + reason = "Breakable doors are already a part of TTT2 that can be enabled with a convar. This addon has also problems with area portals in some doors.", + type = ADDON_INCOMPATIBLE, + }, + ["606792331"] = { -- Advanced disguiser by Gamefreak + alternative = "2144375749", + reason = "Does not work with TTT2 targetID.", + type = ADDON_INCOMPATIBLE, + }, + ["610632051"] = { -- Advanced disguiser by Killberty + alternative = "2144375749", + reason = "Does not work with TTT2 targetID.", + type = ADDON_INCOMPATIBLE, + }, + ["375989005"] = { -- Jihad bomb by Roy301 + alternative = "254177214", + reason = "Breaks the equipment shop.", + type = ADDON_INCOMPATIBLE, + }, + ["1729451064"] = { -- TTT - More Melee Weapons by Solrob + reason = "Overwrites stock TTT2 crowbar which causes problems with doors and ttt_map_settings entity.", + type = ADDON_INCOMPATIBLE, + }, + ["2912756384"] = { -- TTT ProofOfConcept InnocentTasks by Emzatin. + reason = "Overwrites stock TTT2 crowbar which causes problems with doors and ttt_map_settings entity.", + type = ADDON_INCOMPATIBLE, + }, + ["1961869471"] = { -- [TTT/2] Crowbar to throwable Crowbar by GengarDC + reason = "Overwrites stock TTT2 crowbar which causes problems with doors and ttt_map_settings entity.", + type = ADDON_INCOMPATIBLE, + }, + ["278185787"] = { -- Death Note by Blue-Pentagram + alternative = "3118796974", + reason = "Prints only Detectives in TTT2. Roles are hard coded.", + type = ADDON_INCOMPATIBLE, + }, + ["2758610950"] = { -- Death Note TTT2 Fixed Edition by pat201290 + alternative = "3118796974", + reason = "Prints only Detectives in TTT2. Roles are hard coded.", + type = ADDON_INCOMPATIBLE, + }, + ["110148946"] = { -- Death Note by SmokeTheBanana + alternative = "3118796974", + reason = "Prints only Detectives in TTT2. Roles are hard coded.", + type = ADDON_INCOMPATIBLE, + }, + ["110148946"] = { -- ttt_broken_hand_fix by Jolez + reason = "Already built in into TTT2", + type = ADDON_INCOMPATIBLE, + }, + ["2990353959"] = { -- Weapon Spawn Ratio Mod by Corvatile + reason = "Breaks random weapon spawns by overwriting the random weapon entity.", + type = ADDON_INCOMPATIBLE, + }, + ["456247192"] = { -- TTT Coffee-Cup Hunt by Niandra! + alternative = "2150924507", + reason = "Addon is broken and doesn't do anything.", + type = ADDON_OUTDATED, + }, + ["1125892999"] = { -- TTT Coffee-Cup Hunt by Niandra! + alternative = "2150924507", + reason = "Addon is broken and doesn't do anything.", + type = ADDON_OUTDATED, + }, + ["654341247"] = { -- Clairvoyancy by Liberty + alternative = "1637001449", + reason = "Does not use the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["130051756"] = { -- Spartan Kick by uacnix + alternative = "1584675927", + reason = "Broken area portals in some doors and improved use of TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["122277196"] = { -- Spartan Kick by Chowder908 + alternative = "1584675927", + reason = "Broken area portals in some doors and improved use of TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["922510848"] = { -- Spartan Kick by BocciardoLight + alternative = "1584675927", + reason = "Broken area portals in some doors and improved use of TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["282584080"] = { -- Spartan Kick by Porter + alternative = "1584675927", + reason = "Broken area portals in some doors and improved use of TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["1092632443"] = { -- Spartan Kick by Jenssons + alternative = "1584675927", + reason = "Broken area portals in some doors and improved use of TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["1523332573"] = { -- Thomas by Tubner + alternative = "2024902834", + reason = "Broken models, sounds and damage inflictor info.", + type = ADDON_OUTDATED, + }, + ["962093923"] = { -- Thomas by ThunfischArnold + alternative = "2024902834", + reason = "Broken models, sounds and damage inflictor info.", + type = ADDON_OUTDATED, + }, + ["1479128258"] = { -- Thomas by JollyJelly1001 + alternative = "2024902834", + reason = "Broken models, sounds and damage inflictor info.", + type = ADDON_OUTDATED, + }, + ["1390915062"] = { -- Thomas by Doc Snyder + alternative = "2024902834", + reason = "Broken models, sounds and damage inflictor info.", + type = ADDON_OUTDATED, + }, + ["1171584841"] = { -- Thomas by Maxdome + alternative = "2024902834", + reason = "Broken models, sounds and damage inflictor info.", + type = ADDON_OUTDATED, + }, + ["811718553"] = { -- Thomas by Mr C Funk + alternative = "2024902834", + reason = "Broken models, sounds and damage inflictor info.", + type = ADDON_OUTDATED, + }, + ["922355426"] = { -- Melon Mine by Phoenixf129 + alternative = "1629914760", + reason = "Doesn't really work with the TTT2 role system.", + type = ADDON_OUTDATED, + }, + ["960077088"] = { -- Melon Mine by TheSoulrester + alternative = "1629914760", + reason = "Doesn't really work with the TTT2 role system.", + type = ADDON_OUTDATED, + }, + ["309299668"] = { -- Martyrdom by Exho + alternative = "1630269736", + reason = "Does not use the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["899206223"] = { -- Martyrdom by Koksgesicht + alternative = "1630269736", + reason = "Does not use the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["652046425"] = { -- Juggernaut Suit by Zaratusa + alternative = "2157829981", + reason = "Does not use the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["650523807"] = { -- Lucky Horseshoe by Zaratusa + alternative = "2157888469", + reason = "Does not use the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["650523765"] = { -- Hermes Boots by Zaratusa + alternative = "2157850255", + reason = "Does not use the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["1470823315"] = { -- Beartrap by Milkwater + alternative = "1641605106", + reason = "Has some problems with damage after roundend and doesn't use the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["407751746"] = { -- Beartrap by Nerdhive + alternative = "1641605106", + reason = "Has some problems with damage after roundend and doesn't use the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["273623128"] = { -- zombie perk bottles by Hoff + alternative = "2243578658", + type = ADDON_OUTDATED, + }, + ["1387914296"] = { -- zombie perk bottles by Schmitler + alternative = "2243578658", + type = ADDON_OUTDATED, + }, + ["1371596971"] = { -- zombie perk bottles by Amenius + alternative = "2243578658", + type = ADDON_OUTDATED, + }, + ["860794236"] = { -- zombie perk bottles by Menzek + alternative = "2243578658", + type = ADDON_OUTDATED, + }, + ["1198504029"] = { -- zombie perk bottles by RedocPlays + alternative = "2243578658", + type = ADDON_OUTDATED, + }, + ["911658617"] = { -- zombie perk bottles by Luchix + alternative = "2243578658", + type = ADDON_OUTDATED, + }, + ["869353740"] = { -- zombie perk bottles by Railroad Engineer 111 + alternative = "2243578658", + type = ADDON_OUTDATED, + }, + ["842302491"] = { -- zombie perk bottles by Hagen + alternative = "2243578658", + reason = "Rebalancing, fixing stamin-up, UI integration.", + type = ADDON_OUTDATED, + }, + ["662342819"] = { -- randomat by hagen + alternative = "2266894222", + reason = "TTT2 Minigames randomat is more powerful, streamlined and cleaned up. Use TTT2 minigames, minigame packs and the TTT2 Randomat.", + type = ADDON_OUTDATED, + }, + ["1398629839"] = { -- randomat by GhostPhanom + alternative = "2266894222", + reason = "TTT2 Minigames randomat is more powerful, streamlined and cleaned up. Use TTT2 minigames, minigame packs and the TTT2 Randomat.", + type = ADDON_OUTDATED, + }, + ["1244828603"] = { -- randomat by SnowSoulAngel + alternative = "2266894222", + reason = "TTT2 Minigames randomat is more powerful, streamlined and cleaned up. Use TTT2 minigames, minigame packs and the TTT2 Randomat.", + type = ADDON_OUTDATED, + }, + ["2037019426"] = { -- randomat by HyruleKrieger + alternative = "2266894222", + reason = "TTT2 Minigames randomat is more powerful, streamlined and cleaned up. Use TTT2 minigames, minigame packs and the TTT2 Randomat.", + type = ADDON_OUTDATED, + }, + ["1406495040"] = { -- randomat 2.0 by Abi + alternative = "2266894222", + reason = "TTT2 Minigames randomat is more powerful, streamlined and cleaned up. Use TTT2 minigames, minigame packs and the TTT2 Randomat.", + type = ADDON_OUTDATED, + }, + ["2055805086"] = { -- randomat 2.0 for CR by Malivil + alternative = "2266894222", + reason = "TTT2 Minigames randomat is more powerful, streamlined and cleaned up. Use TTT2 minigames, minigame packs and the TTT2 Randomat.", + type = ADDON_OUTDATED, + }, + ["671603913"] = { -- Space and Time manipulator by Hagen + alternative = "2237612513", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["1558020463"] = { -- TTT - Thanos Snap by Pocable + alternative = "1837434311", + reason = "Improved integration into TTT2 systems and viewmodels.", + type = ADDON_OUTDATED, + }, + ["1466843055"] = { -- [TTT] - Teleport Gun - Fixed version by cookie + alternative = "931856840", + reason = "Jazz fixed their original addon.", + type = ADDON_OUTDATED, + }, + ["590909626"] = { -- Handcuffs by porter + alternative = "2401563697", + reason = "Broken handcuffs that don't do anything or make the game unplayable.", + type = ADDON_OUTDATED, + }, + ["2249861635"] = { -- Handcuffs by Malivil + alternative = "2401563697", + reason = "Doesn't use the targetID system of TTT2", + type = ADDON_OUTDATED, + }, + ["2124909686"] = { -- Handcuffs by DJ Bat + alternative = "2401563697", + reason = "Doesn't use the targetID system of TTT2", + type = ADDON_OUTDATED, + }, + ["1667876426"] = { -- Handcuffs by Wokki + alternative = "2401563697", + reason = "Doesn't use the targetID system of TTT2", + type = ADDON_OUTDATED, + }, + ["1190286764"] = { -- Handcuffs 2 by porter + alternative = "2401563697", + reason = "Doesn't use the targetID system of TTT2", + type = ADDON_OUTDATED, + }, + ["310403937"] = { -- Prop Disguiser by Exho + alternative = "2127939503", + type = ADDON_OUTDATED, + }, + ["843092697"] = { -- Prop Disguiser by Soren + alternative = "2127939503", + type = ADDON_OUTDATED, + }, + ["937535488"] = { -- Prop Disguiser by St Addi + alternative = "2127939503", + type = ADDON_OUTDATED, + }, + ["1168304202"] = { -- Prop Disguiser by Izellix + alternative = "2127939503", + type = ADDON_OUTDATED, + }, + ["1361103159"] = { -- Prop Disguiser by Derp altamas + alternative = "2127939503", + type = ADDON_OUTDATED, + }, + ["1301826793"] = { -- Prop Disguiser by Akechi + alternative = "2127939503", + type = ADDON_OUTDATED, + }, + ["1662844145"] = { -- Prop Disguiser by TeamAlgee + alternative = "2127939503", + type = ADDON_OUTDATED, + }, + ["254779132"] = { -- Dead Ringer by Porter + alternative = "3074845055", + reason = "HUD elements clash with TTT2 HUD on certain scales, doesn't integrate with TTT2 features such as corpses, UI, bindings.", + type = ADDON_OUTDATED, + }, + ["922459145"] = { -- Explosive Corpse by Bocciardo Light + alternative = "2664879356", + reason = "Corpse integration with TargetID, correct key input detection, teammates see where boombodies are located, etc.", + type = ADDON_OUTDATED, + }, + ["359372950"] = { -- Explosive Corpse by Daywalker + alternative = "2664879356", + reason = "Corpse integration with TargetID, correct key input detection, teammates see where boombodies are located, etc.", + type = ADDON_OUTDATED, + }, + ["1553970745"] = { -- Explosive Corpse by DasNerdwork + alternative = "2664879356", + reason = "Corpse integration with TargetID, correct key input detection, teammates see where boombodies are located, etc.", + type = ADDON_OUTDATED, + }, + ["1315377462"] = { -- Dead Ringer by MuratYilderimTM + alternative = "3074845055", + reason = "HUD elements clash with TTT2 HUD on certain scales, doesn't integrate with TTT2 features such as corpses, UI, bindings.", + type = ADDON_OUTDATED, + }, + ["240281783"] = { -- Dead Ringer by Niandra + alternative = "3074845055", + reason = "HUD elements clash with TTT2 HUD on certain scales, doesn't integrate with TTT2 features such as corpses, UI, bindings.", + type = ADDON_OUTDATED, + }, + ["810154456"] = { -- Dead Ringer by Hagen + alternative = "3074845055", + reason = "HUD elements clash with TTT2 HUD on certain scales, doesn't integrate with TTT2 features such as corpses, UI, bindings.", + type = ADDON_OUTDATED, + }, + ["284419411"] = { -- Minifier by Lykrast + alternative = "1896918348", + reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["1338887971"] = { -- Minifier by Evan + alternative = "1896918348", + reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["1551396306"] = { -- Minifier by FaBe2 + alternative = "1896918348", + reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["1354031183"] = { -- Minifier by SnowSoulAnget + alternative = "1896918348", + reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["1670942051"] = { -- Minifier by Not Jesus + alternative = "1896918348", + reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["1376141849"] = { -- Minifier by --- + alternative = "1896918348", + reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["1599819393"] = { -- Minifier by Coe + alternative = "1896918348", + reason = "Broken hitboxes and other problems. Also no integration in the TTT2 sidebar system.", + type = ADDON_OUTDATED, + }, + ["863963592"] = { -- Super Soda by --- + alternative = "1815518231", + reason = "Less bottles, no integration in TTT2 systems, generally buggy.", + type = ADDON_OUTDATED, + }, + ["2672799157"] = { -- Minifier by The Stig + alternative = "1896918348", + reason = "TTT2 integration.", + type = ADDON_OUTDATED, + }, + ["801433502"] = { -- Defibrillator by Minty + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["162581348"] = { -- Defibrillator by Willox + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["921953443"] = { -- Defibrillator by BocciardoLight + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["785796753"] = { -- Defibrillator by Shiratori + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["299479443"] = { -- Defibrillator by PixL + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["1553970612"] = { -- Defibrillator by DasNedwork + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["821963023"] = { -- Defibrillator by DarkIce + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["1695163471"] = { -- Defibrillator by Mr. KobraX + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["1563553647"] = { -- Defibrillator by _BLU + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["1445820970"] = { -- Defibrillator by Mangonaut + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["805980529"] = { -- Defibrillator by Musiker15 + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["422006754"] = { -- Defibrillator by Toxic_Terrorists + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["840688276"] = { -- Defibrillator by Saty + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["1198501844"] = { -- Defibrillator by Brannium + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["1199113632"] = { -- Defibrillator by Brannium + alternative = "2115944312", + reason = "Improved integration into TTT2 systems.", + type = ADDON_OUTDATED, + }, + ["290945941"] = { -- Door Locker by Exho + alternative = "2115945573", + reason = "Improved integration into TTT2 systems and fixed area portal problems for some doors.", + type = ADDON_OUTDATED, + }, + ["1132650862"] = { -- Door Locker by Saiyajin ByTayro + alternative = "2115945573", + reason = "Improved integration into TTT2 systems and fixed area portal problems for some doors.", + type = ADDON_OUTDATED, + }, + ["672173225"] = { -- Second Chance by Hagen + alternative = "2143268505", + reason = "Doesn't use the TTT2 spawn and UI system.", + type = ADDON_OUTDATED, + }, + ["1361100842"] = { -- Door Locker by Altamas + alternative = "2115945573", + reason = "Improved integration into TTT2 systems and fixed area portal problems for some doors.", + type = ADDON_OUTDATED, + }, } --- @@ -483,38 +645,57 @@ addonChecker.curatedList = { -- incompatible / outdated addons and about a working alternative, if one exists. -- @realm server function addonChecker.Check() - local addonTable = engine.GetAddons() + local addonTable = engine.GetAddons() - print("") - print("TTT2 ADDON CHECKER") - print("=============================================================") - print("") + print("") + print("TTT2 ADDON CHECKER") + print("=============================================================") + print("") - for i = 1, #addonTable do - local addon = addonTable[i] - if not addon.mounted then continue end + for i = 1, #addonTable do + local addon = addonTable[i] + if not addon.mounted then + continue + end - local detectedAddon = addonChecker.curatedList[tostring(addon.wsid)] - if not detectedAddon then continue end + local detectedAddon = addonChecker.curatedList[tostring(addon.wsid)] + if not detectedAddon then + continue + end - ErrorNoHalt(((detectedAddon.type == ADDON_OUTDATED) and "Outdated add-on detected: " or "Incompatible add-on detected: ") .. addon.title .. "\n") + ErrorNoHalt( + ( + (detectedAddon.type == ADDON_OUTDATED) and "Outdated add-on detected: " + or "Incompatible add-on detected: " + ) + .. addon.title + .. "\n" + ) - if detectedAddon.reason then - ErrorNoHalt("Reason: " .. detectedAddon.reason .. "\n") - end + if detectedAddon.reason then + print("Reason: " .. detectedAddon.reason .. "\n") + end - ErrorNoHalt("--> Detected add-on: https://steamcommunity.com/sharedfiles/filedetails/?id=" .. addon.wsid .. "\n") + print( + "--> Detected add-on: https://steamcommunity.com/sharedfiles/filedetails/?id=" + .. addon.wsid + .. "\n" + ) - if detectedAddon.alternative then - ErrorNoHalt("--> Alternative add-on: https://steamcommunity.com/sharedfiles/filedetails/?id=" .. detectedAddon.alternative .. "\n") - end + if detectedAddon.alternative then + print( + "--> Alternative add-on: https://steamcommunity.com/sharedfiles/filedetails/?id=" + .. detectedAddon.alternative + .. "\n" + ) + end - print("") - end + print("") + end - print("=============================================================") - print("This is the end of the addon checker output.") - print("") + print("=============================================================") + print("This is the end of the addon checker output.") + print("") end concommand.Add("addonchecker", addonChecker.Check) diff --git a/gamemodes/terrortown/gamemode/server/sv_admin.lua b/gamemodes/terrortown/gamemode/server/sv_admin.lua index f64c52cf7..2553c01ae 100644 --- a/gamemodes/terrortown/gamemode/server/sv_admin.lua +++ b/gamemodes/terrortown/gamemode/server/sv_admin.lua @@ -11,35 +11,35 @@ local util = util local IsValid = IsValid local function GetPrintFn(ply) - if IsValid(ply) then - return function(...) - local t = "" - - for _, a in ipairs({...}) do - t = t .. "\t" .. a - end - - ply:PrintMessage(HUD_PRINTCONSOLE, t) - end - else - return print - end + if IsValid(ply) then + return function(...) + local t = "" + + for _, a in ipairs({ ... }) do + t = t .. "\t" .. a + end + + ply:PrintMessage(HUD_PRINTCONSOLE, t) + end + else + return print + end end local function TraitorSort(a, b) - if not IsValid(a) then - return true - end + if not IsValid(a) then + return true + end - if not IsValid(b) then - return false - end + if not IsValid(b) then + return false + end - if a:GetTeam() == TEAM_TRAITOR and b:GetTeam() ~= TEAM_TRAITOR then - return true - end + if a:GetTeam() == TEAM_TRAITOR and b:GetTeam() ~= TEAM_TRAITOR then + return true + end - return false + return false end --- @@ -47,20 +47,21 @@ end -- @param Player ply -- @realm server function PrintTraitors(ply) - --- - -- @realm server - if not IsValid(ply) or hook.Run("TTT2AdminCheck", ply) then - ServerLog(Format("%s used ttt_print_traitors\n", IsValid(ply) and ply:Nick() or "console")) + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or hook.Run("TTT2AdminCheck", ply) then + ServerLog(Format("%s used ttt_print_traitors\n", IsValid(ply) and ply:Nick() or "console")) - local pr = GetPrintFn(ply) - local ps = player.GetAll() + local pr = GetPrintFn(ply) + local ps = player.GetAll() - table.sort(ps, TraitorSort) + table.sort(ps, TraitorSort) - for _, p in ipairs(ps) do - pr(string.upper(p:GetTeam()), ": ", p:Nick()) - end - end + for _, p in ipairs(ps) do + pr(string.upper(p:GetTeam()), ": ", p:Nick()) + end + end end concommand.Add("ttt_print_traitors", PrintTraitors) @@ -69,13 +70,13 @@ concommand.Add("ttt_print_traitors", PrintTraitors) -- @param Player ply -- @realm server function PrintGroups(ply) - local pr = GetPrintFn(ply) + local pr = GetPrintFn(ply) - pr("User", "-", "Group") + pr("User", "-", "Group") - for _, p in ipairs(player.GetAll()) do - pr(p:Nick(), "-", p:GetNWString("UserGroup")) - end + for _, p in ipairs(player.GetAll()) do + pr(p:Nick(), "-", p:GetNWString("UserGroup")) + end end concommand.Add("ttt_print_usergroups", PrintGroups) @@ -84,145 +85,157 @@ concommand.Add("ttt_print_usergroups", PrintGroups) -- @param Player ply -- @realm server function PrintReport(ply) - local pr = GetPrintFn(ply) - - --- - -- @realm server - if not IsValid(ply) or hook.Run("TTT2AdminCheck", ply) then - ServerLog(Format("%s used ttt_print_adminreport\n", IsValid(ply) and ply:Nick() or "console")) - - for _, e in pairs(SCORE.Events) do - if e.id == EVENT_KILL then - if e.att.sid64 == -1 then - pr(" killed " .. e.vic.ni .. "[" .. string.upper(e.vic.t) .. "]") - else - local as = "[" .. string.upper(e.att.t) .. "]" - local vs = "[" .. string.upper(e.vic.t) .. "]" - - pr(as .. e.att.ni .. " killed " .. vs .. e.vic.ni) - end - end - end - else - if IsValid(ply) then - pr("You do not appear to be RCON or a superadmin!") - end - end + local pr = GetPrintFn(ply) + + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or hook.Run("TTT2AdminCheck", ply) then + ServerLog(Format("%s used ttt_print_adminreport\n", IsValid(ply) and ply:Nick() or "console")) + + for _, e in pairs(SCORE.Events) do + if e.id == EVENT_KILL then + if e.att.sid64 == -1 then + pr(" killed " .. e.vic.ni .. "[" .. string.upper(e.vic.t) .. "]") + else + local as = "[" .. string.upper(e.att.t) .. "]" + local vs = "[" .. string.upper(e.vic.t) .. "]" + + pr(as .. e.att.ni .. " killed " .. vs .. e.vic.ni) + end + end + end + else + if IsValid(ply) then + pr("You do not appear to be RCON or a superadmin!") + end + end end concommand.Add("ttt_print_adminreport", PrintReport) local function PrintKarma(ply) - local pr = GetPrintFn(ply) + local pr = GetPrintFn(ply) - --- - -- @realm server - if not IsValid(ply) or hook.Run("TTT2AdminCheck", ply) then - ServerLog(Format("%s used ttt_print_karma\n", IsValid(ply) and ply:Nick() or "console")) + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or hook.Run("TTT2AdminCheck", ply) then + ServerLog(Format("%s used ttt_print_karma\n", IsValid(ply) and ply:Nick() or "console")) - KARMA.PrintAll(pr) + KARMA.PrintAll(pr) - else - if IsValid(ply) then - pr("You do not appear to be RCON or a superadmin!") - end - end + else + if IsValid(ply) then + pr("You do not appear to be RCON or a superadmin!") + end + end end concommand.Add("ttt_print_karma", PrintKarma) --- -- @realm server +-- stylua: ignore local cv_ttt_highlight_admins = CreateConVar("ttt_highlight_admins", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local cv_ttt_highlight_dev = CreateConVar("ttt_highlight_dev", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local cv_ttt_highlight_vip = CreateConVar("ttt_highlight_vip", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local cv_ttt_highlight_addondev = CreateConVar("ttt_highlight_addondev", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local cv_ttt_highlight_supporter = CreateConVar("ttt_highlight_supporter", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) hook.Add("TTT2SyncGlobals", "AddScoreboardGlobals", function() - SetGlobalBool(cv_ttt_highlight_admins:GetName(), cv_ttt_highlight_admins:GetBool()) - SetGlobalBool(cv_ttt_highlight_dev:GetName(), cv_ttt_highlight_dev:GetBool()) - SetGlobalBool(cv_ttt_highlight_vip:GetName(), cv_ttt_highlight_vip:GetBool()) - SetGlobalBool(cv_ttt_highlight_addondev:GetName(), cv_ttt_highlight_addondev:GetBool()) - SetGlobalBool(cv_ttt_highlight_supporter:GetName(), cv_ttt_highlight_supporter:GetBool()) + SetGlobalBool(cv_ttt_highlight_admins:GetName(), cv_ttt_highlight_admins:GetBool()) + SetGlobalBool(cv_ttt_highlight_dev:GetName(), cv_ttt_highlight_dev:GetBool()) + SetGlobalBool(cv_ttt_highlight_vip:GetName(), cv_ttt_highlight_vip:GetBool()) + SetGlobalBool(cv_ttt_highlight_addondev:GetName(), cv_ttt_highlight_addondev:GetBool()) + SetGlobalBool(cv_ttt_highlight_supporter:GetName(), cv_ttt_highlight_supporter:GetBool()) end) cvars.AddChangeCallback(cv_ttt_highlight_admins:GetName(), function(cv, old, new) - SetGlobalBool(cv_ttt_highlight_admins:GetName(), tobool(tonumber(new))) + SetGlobalBool(cv_ttt_highlight_admins:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(cv_ttt_highlight_dev:GetName(), function(cv, old, new) - SetGlobalBool(cv_ttt_highlight_dev:GetName(), tobool(tonumber(new))) + SetGlobalBool(cv_ttt_highlight_dev:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(cv_ttt_highlight_vip:GetName(), function(cv, old, new) - SetGlobalBool(cv_ttt_highlight_vip:GetName(), tobool(tonumber(new))) + SetGlobalBool(cv_ttt_highlight_vip:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(cv_ttt_highlight_addondev:GetName(), function(cv, old, new) - SetGlobalBool(cv_ttt_highlight_addondev:GetName(), tobool(tonumber(new))) + SetGlobalBool(cv_ttt_highlight_addondev:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(cv_ttt_highlight_supporter:GetName(), function(cv, old, new) - SetGlobalBool(cv_ttt_highlight_supporter:GetName(), tobool(tonumber(new))) + SetGlobalBool(cv_ttt_highlight_supporter:GetName(), tobool(tonumber(new))) end) --- -- @realm server +-- stylua: ignore local dmglog_console = CreateConVar("ttt_log_damage_for_console", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local dmglog_save = CreateConVar("ttt_damagelog_save", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) local function PrintDamageLog(ply) - local pr = GetPrintFn(ply) - - --- - -- @realm server - if not IsValid(ply) or hook.Run("TTT2AdminCheck", ply) or GetRoundState() ~= ROUND_ACTIVE then - ServerLog(Format("%s used ttt_print_damagelog\n", IsValid(ply) and ply:Nick() or "console")) - pr("*** Damage log:\n") - - if not dmglog_console:GetBool() then - pr("Damage logging for console disabled. Enable with ttt_log_damage_for_console 1.") - end - - for _, txt in ipairs(GAMEMODE.DamageLog) do - pr(txt) - end - - pr("*** Damage log end.") - else - if IsValid(ply) then - pr("You do not appear to be RCON or a superadmin, nor are we in the post-round phase!") - end - end + local pr = GetPrintFn(ply) + + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or hook.Run("TTT2AdminCheck", ply) or GetRoundState() ~= ROUND_ACTIVE then + ServerLog(Format("%s used ttt_print_damagelog\n", IsValid(ply) and ply:Nick() or "console")) + pr("*** Damage log:\n") + + if not dmglog_console:GetBool() then + pr("Damage logging for console disabled. Enable with ttt_log_damage_for_console 1.") + end + + for _, txt in ipairs(GAMEMODE.DamageLog) do + pr(txt) + end + + pr("*** Damage log end.") + else + if IsValid(ply) then + pr("You do not appear to be RCON or a superadmin, nor are we in the post-round phase!") + end + end end concommand.Add("ttt_print_damagelog", PrintDamageLog) local function SaveDamageLog() - if not dmglog_save:GetBool() then return end + if not dmglog_save:GetBool() then + return + end - local text = "" + local text = "" - if #GAMEMODE.DamageLog == 0 then - text = "Damage log is empty." - else - for _, txt in ipairs(GAMEMODE.DamageLog) do - text = text .. txt .. "\n" - end - end + if #GAMEMODE.DamageLog == 0 then + text = "Damage log is empty." + else + for _, txt in ipairs(GAMEMODE.DamageLog) do + text = text .. txt .. "\n" + end + end - local fname = Format("terrortown/logs/dmglog_%s_%d.txt", os.date("%d%b%Y_%H%M"), os.time()) + local fname = Format("terrortown/logs/dmglog_%s_%d.txt", os.date("%d%b%Y_%H%M"), os.time()) - file.Write(fname, text) + file.Write(fname, text) end hook.Add("TTTEndRound", "ttt_damagelog_save_hook", SaveDamageLog) @@ -231,67 +244,68 @@ hook.Add("TTTEndRound", "ttt_damagelog_save_hook", SaveDamageLog) -- @param string txt -- @realm server function DamageLog(txt) - local t = math.max(0, CurTime() - GAMEMODE.RoundStartTime) + local t = math.max(0, CurTime() - GAMEMODE.RoundStartTime) - txt = util.SimpleTime(t, "%02i:%02i.%02i - ") .. txt + txt = util.SimpleTime(t, "%02i:%02i.%02i - ") .. txt - ServerLog(txt .. "\n") + ServerLog(txt .. "\n") - if dmglog_console:GetBool() or dmglog_save:GetBool() then - table.insert(GAMEMODE.DamageLog, txt) - end + if dmglog_console:GetBool() or dmglog_save:GetBool() then + table.insert(GAMEMODE.DamageLog, txt) + end end --- -- @realm server +-- stylua: ignore local ttt_bantype = CreateConVar("ttt_ban_type", "autodetect", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) local function DetectServerPlugin() - if ULib and ULib.kickban then - return "ulx" - elseif evolve and evolve.Ban then - return "evolve" - elseif exsto and exsto.GetPlugin("administration") then - return "exsto" - else - return "gmod" - end + if ULib and ULib.kickban then + return "ulx" + elseif evolve and evolve.Ban then + return "evolve" + elseif exsto and exsto.GetPlugin("administration") then + return "exsto" + else + return "gmod" + end end local function StandardBan(ply, length, reason) - RunConsoleCommand("banid", length, ply:UserID()) + RunConsoleCommand("banid", length, ply:UserID()) - ply:Kick(reason) + ply:Kick(reason) end local ban_functions = { - ulx = ULib and ULib.kickban, -- has (ply, length, reason) signature - evolve = function(p, l, r) - evolve:Ban(p:UniqueID(), l * 60, r) -- time in seconds - end, - sm = function(p, l, r) - game.ConsoleCommand(Format("sm_ban \"#%s\" %d \"%s\"\n", p:SteamID64(), l, r)) - end, - exsto = function(p, l, r) - local adm = exsto.GetPlugin("administration") - - if adm and adm.Ban then - adm:Ban(nil, p, l, r) - end - end, - gmod = StandardBan + ulx = ULib and ULib.kickban, -- has (ply, length, reason) signature + evolve = function(p, l, r) + evolve:Ban(p:UniqueID(), l * 60, r) -- time in seconds + end, + sm = function(p, l, r) + game.ConsoleCommand(Format("sm_ban \"#%s\" %d \"%s\"\n", p:SteamID64(), l, r)) + end, + exsto = function(p, l, r) + local adm = exsto.GetPlugin("administration") + + if adm and adm.Ban then + adm:Ban(nil, p, l, r) + end + end, + gmod = StandardBan, } local function BanningFunction() - local bantype = string.lower(ttt_bantype:GetString()) + local bantype = string.lower(ttt_bantype:GetString()) - if bantype == "autodetect" then - bantype = DetectServerPlugin() - end + if bantype == "autodetect" then + bantype = DetectServerPlugin() + end - print("Banning using " .. bantype .. " method.") + Dev(2, "Banning using " .. bantype .. " method.") - return ban_functions[bantype] or ban_functions["gmod"] + return ban_functions[bantype] or ban_functions["gmod"] end --- @@ -301,7 +315,7 @@ end -- @param string reason -- @realm server function PerformKickBan(ply, length, reason) - local banfn = BanningFunction() + local banfn = BanningFunction() - banfn(ply, length, reason) + banfn(ply, length, reason) end diff --git a/gamemodes/terrortown/gamemode/server/sv_armor.lua b/gamemodes/terrortown/gamemode/server/sv_armor.lua index 17fbbb6ea..aba1942e9 100644 --- a/gamemodes/terrortown/gamemode/server/sv_armor.lua +++ b/gamemodes/terrortown/gamemode/server/sv_armor.lua @@ -3,9 +3,9 @@ local plymeta = FindMetaTable("Player") if not plymeta then - Error("FAILED TO FIND PLAYER TABLE") + ErrorNoHaltWithStack("FAILED TO FIND PLAYER TABLE") - return + return end ARMOR = {} @@ -16,59 +16,75 @@ util.AddNetworkString("ttt2_sync_armor_max") -- SET UP CONVARS ARMOR.cv = { - --- - -- @realm server - armor_on_spawn = CreateConVar("ttt_armor_on_spawn", 0, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - armor_enable_reinforced = CreateConVar("ttt_armor_enable_reinforced", 1, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - armor_threshold_for_reinforced = CreateConVar("ttt_armor_threshold_for_reinforced", 50, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - armor_damage_block_pct = CreateConVar("ttt_armor_damage_block_pct", 0.2, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - armor_damage_health_pct = CreateConVar("ttt_armor_damage_health_pct", 0.7, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - armor_dynamic = CreateConVar("ttt_armor_dynamic", 1, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - item_armor_value = CreateConVar("ttt_item_armor_value", 30, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - item_armor_block_headshots = CreateConVar("ttt_item_armor_block_headshots", 0, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - item_armor_block_blastdmg = CreateConVar("ttt_item_armor_block_blastdmg", 0, {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + --- + -- @realm server + -- stylua: ignore + armor_on_spawn = CreateConVar("ttt_armor_on_spawn", 0, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + armor_enable_reinforced = CreateConVar("ttt_armor_enable_reinforced", 1, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + armor_threshold_for_reinforced = CreateConVar("ttt_armor_threshold_for_reinforced", 50, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + armor_damage_block_pct = CreateConVar("ttt_armor_damage_block_pct", 0.2, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + armor_damage_health_pct = CreateConVar("ttt_armor_damage_health_pct", 0.7, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + armor_dynamic = CreateConVar("ttt_armor_dynamic", 1, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + item_armor_value = CreateConVar("ttt_item_armor_value", 30, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + item_armor_block_headshots = CreateConVar("ttt_item_armor_block_headshots", 0, {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + item_armor_block_blastdmg = CreateConVar("ttt_item_armor_block_blastdmg", 0, {FCVAR_NOTIFY, FCVAR_ARCHIVE}) +, } hook.Add("TTT2SyncGlobals", "AddArmorGlobals", function() - SetGlobalBool(ARMOR.cv.armor_dynamic:GetName(), ARMOR.cv.armor_dynamic:GetBool()) - SetGlobalBool(ARMOR.cv.armor_enable_reinforced:GetName(), ARMOR.cv.armor_enable_reinforced:GetBool()) - SetGlobalBool(ARMOR.cv.armor_threshold_for_reinforced:GetName(), ARMOR.cv.armor_threshold_for_reinforced:GetInt()) + SetGlobalBool(ARMOR.cv.armor_dynamic:GetName(), ARMOR.cv.armor_dynamic:GetBool()) + SetGlobalBool( + ARMOR.cv.armor_enable_reinforced:GetName(), + ARMOR.cv.armor_enable_reinforced:GetBool() + ) + SetGlobalBool( + ARMOR.cv.armor_threshold_for_reinforced:GetName(), + ARMOR.cv.armor_threshold_for_reinforced:GetInt() + ) end) cvars.AddChangeCallback(ARMOR.cv.armor_dynamic:GetName(), function(cv, old, new) - SetGlobalBool(ARMOR.cv.armor_dynamic:GetName(), tobool(tonumber(new))) + SetGlobalBool(ARMOR.cv.armor_dynamic:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(ARMOR.cv.armor_enable_reinforced:GetName(), function(cv, old, new) - SetGlobalBool(ARMOR.cv.armor_enable_reinforced:GetName(), tobool(tonumber(new))) + SetGlobalBool(ARMOR.cv.armor_enable_reinforced:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(ARMOR.cv.armor_threshold_for_reinforced:GetName(), function(cv, old, new) - SetGlobalInt(ARMOR.cv.armor_threshold_for_reinforced:GetName(), tonumber(new)) + SetGlobalInt(ARMOR.cv.armor_threshold_for_reinforced:GetName(), tonumber(new)) end) -- SERVERSIDE ARMOR FUNCTIONS @@ -80,15 +96,17 @@ end) -- @realm server -- @internal function ARMOR:InitPlayerArmor() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - if not ply:IsTerror() then continue end + if not ply:IsTerror() then + continue + end - ply:GiveArmor(self.cv.armor_on_spawn:GetInt()) - end + ply:GiveArmor(self.cv.armor_on_spawn:GetInt()) + end end --- @@ -97,54 +115,66 @@ end -- @param Entity infl The inflictor -- @param Player|Entity att The attacker -- @param number amount Amount of damage --- @param DamageInfo dmginfo Damage info +-- @param CTakeDamageInfo dmginfo Damage info -- @realm server -- @internal function ARMOR:HandlePlayerTakeDamage(ply, infl, att, amount, dmginfo) - local armor = ply:GetArmor() - - -- normal damage handling when no armor is available - if armor == 0 then return end - - -- handle if headshots should be ignored by the armor - if ply:LastHitGroup() == HITGROUP_HEAD and not self.cv.item_armor_block_headshots:GetBool() then return end - - -- handle different damage type factors, only these four damage types are valid - if not dmginfo:IsDamageType(DMG_BULLET) and not dmginfo:IsDamageType(DMG_CLUB) - and not dmginfo:IsDamageType(DMG_BURN) and not dmginfo:IsDamageType(DMG_BLAST) - then return end - - -- handle if blast damage should be ignored by the armor - if dmginfo:IsDamageType(DMG_BLAST) and not self.cv.item_armor_block_blastdmg:GetBool() then return end - - -- fallback for players who prefer the vanilla armor - if self.cv.armor_dynamic:GetBool() then - -- classic armor only shields from bullet/crowbar damage - if dmginfo:IsDamageType(DMG_BULLET) or dmginfo:IsDamageType(DMG_CLUB) then - dmginfo:ScaleDamage(0.7) - end - - return - end - - -- calculate damage - local damage = dmginfo:GetDamage() - - self.cv.armor_factor = self.cv.armor_damage_block_pct:GetFloat() - self.cv.health_factor = self.cv.armor_damage_health_pct:GetFloat() - - if ply:ArmorIsReinforced() then - self.cv.health_factor = self.cv.health_factor - 0.15 - end - - local armorDamage = self.cv.armor_factor * damage - - ply:DecreaseArmorValue(armorDamage) - - -- The armor offset is used to catch the damage that should be taken, - -- if the armor is not strong enough to take that many hitpoints. - -- It is zero as long as the armor is able to take the damage. - dmginfo:SetDamage(self.cv.health_factor * damage - math.min(armor - armorDamage, 0)) + local armor = ply:GetArmor() + + -- normal damage handling when no armor is available + if armor == 0 then + return + end + + -- handle if headshots should be ignored by the armor + if ply:LastHitGroup() == HITGROUP_HEAD and not self.cv.item_armor_block_headshots:GetBool() then + return + end + + -- handle different damage type factors, only these four damage types are valid + if + not dmginfo:IsDamageType(DMG_BULLET) + and not dmginfo:IsDamageType(DMG_CLUB) + and not dmginfo:IsDamageType(DMG_BURN) + and not dmginfo:IsDamageType(DMG_BLAST) + then + return + end + + -- handle if blast damage should be ignored by the armor + if dmginfo:IsDamageType(DMG_BLAST) and not self.cv.item_armor_block_blastdmg:GetBool() then + return + end + + -- fallback for players who prefer the vanilla armor + if not self.cv.armor_dynamic:GetBool() then + -- classic armor only shields from bullet/crowbar damage + if dmginfo:IsDamageType(DMG_BULLET) or dmginfo:IsDamageType(DMG_CLUB) then + dmginfo:ScaleDamage(0.7) + end + + return + end + + -- calculate damage + local damage = dmginfo:GetDamage() + + self.cv.armor_factor = self.cv.armor_damage_block_pct:GetFloat() + self.cv.health_factor = self.cv.armor_damage_health_pct:GetFloat() + + if ply:ArmorIsReinforced() then + self.cv.health_factor = self.cv.health_factor - 0.15 + end + + local armorDamage = self.cv.armor_factor * damage + + ply:DecreaseArmorValue(armorDamage) + + -- Describes the maximum amount of damage that our current armor can endure. + -- This might exceed the actual damage, so we need to limit this. + local damageReduced = math.min(damage, armor / self.cv.armor_factor) + + dmginfo:SetDamage(damageReduced * self.cv.health_factor + damage - damageReduced) end --- @@ -155,11 +185,11 @@ end -- @param number armor The new armor to be set -- @realm server function plymeta:SetArmor(armor) - self.armor = math.Clamp(math.Round(armor), 0, self:GetMaxArmor()) + self.armor = math.Clamp(math.Round(armor), 0, self:GetMaxArmor()) - net.Start("ttt2_sync_armor") - net.WriteUInt(self.armor, 16) - net.Send(self) + net.Start("ttt2_sync_armor") + net.WriteUInt(self.armor, 16) + net.Send(self) end --- @@ -167,14 +197,14 @@ end -- @param number armor_max The new max armor to be set -- @realm server function plymeta:SetMaxArmor(armor_max) - self.armor_max = math.max(math.Round(armor_max), 0) + self.armor_max = math.max(math.Round(armor_max), 0) - net.Start("ttt2_sync_armor_max") - net.WriteUInt(self.armor_max, 16) - net.Send(self) + net.Start("ttt2_sync_armor_max") + net.WriteUInt(self.armor_max, 16) + net.Send(self) - -- make sure armor is always smaller than the max armor - self:SetArmor(math.min(self.armor_max, self:GetArmor())) + -- make sure armor is always smaller than the max armor + self:SetArmor(math.min(self.armor_max, self:GetArmor())) end --- @@ -183,8 +213,8 @@ end -- @param number armor The amount to be increased -- @realm server function plymeta:GiveArmor(armor) - self:IncreaseMaxArmorValue(armor) - self:IncreaseArmorValue(armor) + self:IncreaseMaxArmorValue(armor) + self:IncreaseArmorValue(armor) end --- @@ -193,7 +223,7 @@ end -- @param number remove The amount to be decreased -- @realm server function plymeta:RemoveArmor(armor) - self:DecreaseMaxArmorValue(armor) + self:DecreaseMaxArmorValue(armor) end --- @@ -202,7 +232,7 @@ end -- @param number armor The amount to be decreased -- @realm server function plymeta:DecreaseArmorValue(armor) - self:SetArmor(self:GetArmor() - math.max(armor, 0)) + self:SetArmor(self:GetArmor() - math.max(armor, 0)) end --- @@ -211,7 +241,7 @@ end -- @param number armor The amount to be increased -- @realm server function plymeta:IncreaseArmorValue(armor) - self:SetArmor(self:GetArmor() + math.max(armor, 0)) + self:SetArmor(self:GetArmor() + math.max(armor, 0)) end --- @@ -219,7 +249,7 @@ end -- @param number armor The amount to be decreased -- @realm server function plymeta:DecreaseMaxArmorValue(armor) - self:SetMaxArmor(self:GetMaxArmor() - math.max(armor, 0)) + self:SetMaxArmor(self:GetMaxArmor() - math.max(armor, 0)) end --- @@ -227,7 +257,7 @@ end -- @param number armor The amount to be increased -- @realm server function plymeta:IncreaseMaxArmorValue(armor) - self:SetMaxArmor(self:GetMaxArmor() + math.max(armor, 0)) + self:SetMaxArmor(self:GetMaxArmor() + math.max(armor, 0)) end --- @@ -235,6 +265,6 @@ end -- @realm server -- @internal function plymeta:ResetArmor() - self:SetArmor(0) - self:SetMaxArmor(0) + self:SetArmor(0) + self:SetMaxArmor(0) end diff --git a/gamemodes/terrortown/gamemode/server/sv_corpse.lua b/gamemodes/terrortown/gamemode/server/sv_corpse.lua index 6d71b85b0..6f3faaf53 100644 --- a/gamemodes/terrortown/gamemode/server/sv_corpse.lua +++ b/gamemodes/terrortown/gamemode/server/sv_corpse.lua @@ -2,10 +2,25 @@ -- Corpse functions -- @module CORPSE +---@class DamageInfoData +---@field ammoType number The ammo type which was used, referring to game.GetAmmoTypes() +---@field attacker Entity The attacker (character who originated the attack), for example a player or an NPC that shot the weapon. Or an Entity. +---@field baseDamage number The initial unmodified by skill level ( game.GetSkillLevel() ) damage +---@field damage number The total damage. +---@field damageBonus number The amount of bonus damage. +---@field damageCustom number Custom damage type. This is used by Day of Defeat: Source and Team Fortress 2 for extended damage info, but isn't used in Garry's Mod by default. +---@field damageForce Vector The force taken from the damage, sometimes used for knockback. +---@field damagePosition Vector The position where the damage was or is going to be applied to. +---@field damageType number A bitflag which indicates the damage type(s) of the damage. +---@field inflictor Entity The inflictor of the damage, a projectile, a weapon, or an ordinariy Entity. +---@field reportedPosition Vector The initial, unmodified position where the damage occured +---@field isBulletDamage boolean Whether the DamageInfo contained DMG_BULLET +---@field isExplosionDamage boolean Whether the DamageInfo contained DMG_BLAST +---@field isFallDamage boolean Whether the DamageInfo contained DMG_FALL + -- namespaced because we have no ragdoll metatable CORPSE = {} -local math = math local table = table local net = net local player = player @@ -17,15 +32,14 @@ local hook = hook --- -- @realm server +-- stylua: ignore local cvBodyfound = CreateConVar("ttt_announce_body_found", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "If detective mode, announce when someone's body is found") --- -- @realm server +-- stylua: ignore local cvRagCollide = CreateConVar("ttt_ragdoll_collide", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) -local cvDeteOnlyConfirm = GetConVar("ttt2_confirm_detective_only") -local cvDeteOnlyInspect = GetConVar("ttt2_inspect_detective_only") - ttt_include("sh_corpse") util.AddNetworkString("TTT2SendConfirmMsg") @@ -39,8 +53,8 @@ local dti = CORPSE.dti -- @param boolean state -- @realm server function CORPSE.SetFound(rag, state) - --rag:SetNWBool("found", state) - rag:SetDTBool(dti.BOOL_FOUND, state) + --rag:SetNWBool("found", state) + rag:SetDTBool(dti.BOOL_FOUND, state) end --- @@ -49,17 +63,17 @@ end -- @param Player|string ply_or_name -- @realm server function CORPSE.SetPlayerNick(rag, ply_or_name) - -- don't have datatable strings, so use a dt entity for common case of - -- still-connected player, and if the player is gone, fall back to nw string - local name = ply_or_name + -- don't have datatable strings, so use a dt entity for common case of + -- still-connected player, and if the player is gone, fall back to nw string + local name = ply_or_name - if IsValid(ply_or_name) then - name = ply_or_name:Nick() + if IsValid(ply_or_name) then + name = ply_or_name:Nick() - rag:SetDTEntity(dti.ENT_PLAYER, ply_or_name) - end + rag:SetDTEntity(dti.ENT_PLAYER, ply_or_name) + end - rag:SetNWString("nick", name) + rag:SetNWString("nick", name) end --- @@ -68,369 +82,206 @@ end -- @param number credits -- @realm server function CORPSE.SetCredits(rag, credits) - --rag:SetNWInt("credits", credits) - rag:SetDTInt(dti.INT_CREDITS, credits) + --rag:SetNWInt("credits", credits) + rag:SetDTInt(dti.INT_CREDITS, credits) end -local function IdentifyBody(ply, rag) - if not ply:IsTerror() or not ply:Alive() then return end - - -- simplified case for those who die and get found during prep - if GetRoundState() == ROUND_PREP then - CORPSE.SetFound(rag, true) - - return - end - - local roleData = ply:GetSubRoleData() - - if cvDeteOnlyInspect:GetBool() and not roleData.isPolicingRole then - LANG.Msg(ply, "inspect_detective_only", nil, MSG_MSTACK_WARN) - - return false - end - - --- - -- @realm server - if not hook.Run("TTTCanIdentifyCorpse", ply, rag) then return end - - local finder = ply:Nick() - local nick = CORPSE.GetPlayerNick(rag, "") - local notConfirmed = not CORPSE.GetFound(rag, false) - - -- Register find - if notConfirmed then -- will return either false or a valid ply - local deadply = player.GetBySteamID64(rag.sid64) - - if cvDeteOnlyConfirm:GetBool() and not roleData.isPolicingRole then - LANG.Msg(ply, "confirm_detective_only", nil, MSG_MSTACK_WARN) - - return - end - - --- - -- @realm server - if deadply and not deadply:Alive() and hook.Run("TTT2ConfirmPlayer", deadply, ply, rag) ~= false then - deadply:ConfirmPlayer(true) - - SendPlayerToEveryone(deadply) - end - - events.Trigger(EVENT_BODYFOUND, ply, rag) - - --- - -- @realm server - hook.Run("TTTBodyFound", ply, deadply, rag) - - --- - -- @realm server - if hook.Run("TTT2SetCorpseFound", deadply, ply, rag) ~= false then - CORPSE.SetFound(rag, true) - end - end - - if GetConVar("ttt2_confirm_killlist"):GetBool() then - -- Handle kill list - local ragKills = rag.kills - - for i = 1, #ragKills do - local vicsid = ragKills[i] - - -- filter out disconnected (and bots !) - local vic = player.GetBySteamID64(vicsid) - - -- is this an unconfirmed dead? - if not IsValid(vic) or vic:TTT2NETGetBool("body_found", false) then continue end - - LANG.Msg("body_confirm", {finder = finder, victim = vic:Nick()}) - - vic:ConfirmPlayer(false) - - -- however, do not mark body as found. This lets players find the - -- body later and get the benefits of that - --local vicrag = vic.server_ragdoll - --CORPSE.SetFound(vicrag, true) - end - end - - -- Announce body - if cvBodyfound:GetBool() and notConfirmed then - local subrole = rag.was_role - local team = rag.was_team - local rd = roles.GetByIndex(subrole) - local roletext = "body_found_" .. rd.abbr - local clr = rag.role_color - local bool = GetGlobalBool("ttt2_confirm_team") - - net.Start("TTT2SendConfirmMsg") - - if bool then - net.WriteString("body_found_team") - else - net.WriteString("body_found") - end - - net.WriteString(rag.sid == "BOT" and "" or rag.sid64) - - -- color - net.WriteUInt(clr.r, 8) - net.WriteUInt(clr.g, 8) - net.WriteUInt(clr.b, 8) - net.WriteUInt(clr.a, 8) - - net.WriteBool(bool) - - net.WriteString(finder) - net.WriteString(nick) - net.WriteString(roletext) - - if bool then - net.WriteString(team) - end - - net.Broadcast() - end -end - -local function ttt_confirm_death(ply, cmd, args) - if not IsValid(ply) then return end - - if #args < 2 then return end - - local eidx = tonumber(args[1]) - local id = tonumber(args[2]) - local isLongRange = (args[3] and tonumber(args[3]) == 1) and true or false - - if not eidx or not id then return end - - if not ply.search_id or ply.search_id.id ~= id or ply.search_id.eidx ~= eidx then - ply.search_id = nil - - return - end - - ply.search_id = nil - - local rag = Entity(eidx) - - if IsValid(rag) and (rag:GetPos():Distance(ply:GetPos()) < 128 or isLongRange) and not CORPSE.GetFound(rag, false) then - IdentifyBody(ply, rag) - end -end -concommand.Add("ttt_confirm_death", ttt_confirm_death) - --- Call detectives to a corpse -local function ttt_call_detective(ply, cmd, args) - if not IsValid(ply) then return end - - if #args ~= 1 then return end - - if not ply:IsActive() then return end - - local eidx = tonumber(args[1]) - if not eidx then return end - - local rag = Entity(eidx) - - if IsValid(rag) and rag:GetPos():Distance(ply:GetPos()) < 128 then - if CORPSE.GetFound(rag, false) then - local plyTable = util.GetFilteredPlayers(function(p) - return p:GetSubRoleData().isPolicingRole and p:IsTerror() - end) - - --- - -- @realm server - hook.Run("TTT2ModifyCorpseCallRadarRecipients", plyTable, rag, ply) - - -- show indicator in radar to detectives - net.Start("TTT_CorpseCall") - net.WriteVector(rag:GetPos()) - net.Send(plyTable) - - LANG.MsgAll("body_call", {player = ply:Nick(), victim = CORPSE.GetPlayerNick(rag, "someone")}, MSG_CHAT_PLAIN) - - --- - -- @realm server - hook.Run("TTT2CalledPolicingRole", plyTable, ply, rag, CORPSE.GetPlayer(rag)) - else - LANG.Msg(ply, "body_call_error") - end - end -end -concommand.Add("ttt_call_detective", ttt_call_detective) - -local function bitsRequired(num) - local bits, max = 0, 1 - - while max <= num do - bits = bits + 1 -- increase - max = max + max -- double - end - - return bits -end - -local function GiveFoundCredits(ply, rag, isLongRange) - local corpseNick = CORPSE.GetPlayerNick(rag) - local credits = CORPSE.GetCredits(rag, 0) - - if not ply:IsActiveShopper() or ply:GetSubRoleData().preventFindCredits - or credits == 0 or isLongRange - then return end - - LANG.Msg(ply, "body_credits", {num = credits}) - - ply:AddCredits(credits) - - CORPSE.SetCredits(rag, 0) - - ServerLog(ply:Nick() .. " took " .. credits .. " credits from the body of " .. corpseNick .. "\n") - - events.Trigger(EVENT_CREDITFOUND, ply, rag, credits) - - hook.Run("TTT2OnGiveFoundCredits", ply, rag, credits) +--- +-- Identifies the corpse, registers it and announces it to the players, if possible. +-- @param Player ply The player that tries to identify the body +-- @param Entity rag The ragdoll entity that is searched +-- @param[default=0] number searchUID The unique search ID that is used to keep track of the search for the UI +-- @realm server +function CORPSE.IdentifyBody(ply, rag, searchUID) + if not ply:IsTerror() or not ply:Alive() then + return + end + + -- simplified case for those who die and get found during prep + if GetRoundState() == ROUND_PREP then + CORPSE.SetFound(rag, true) + + return + end + + --- + -- @realm server + -- stylua: ignore + if not hook.Run("TTTCanIdentifyCorpse", ply, rag) then return end + + local finder = ply:Nick() + local nick = CORPSE.GetPlayerNick(rag, "") + local notConfirmed = not CORPSE.GetFound(rag, false) + + -- Register find + if notConfirmed then -- will return either false or a valid ply + local deadply = player.GetBySteamID64(rag.sid64) + + --- + -- @realm server + -- stylua: ignore + if deadply and not deadply:Alive() and hook.Run("TTT2ConfirmPlayer", deadply, ply, rag) ~= false then + deadply:ConfirmPlayer(true) + + SendPlayerToEveryone(deadply) + end + + events.Trigger(EVENT_BODYFOUND, ply, rag) + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTTBodyFound", ply, deadply, rag) + + --- + -- @realm server + -- stylua: ignore + if hook.Run("TTT2SetCorpseFound", deadply, ply, rag) ~= false then + CORPSE.SetFound(rag, true) + end + end + + -- Announce body + if cvBodyfound:GetBool() and notConfirmed then + local subrole = rag.was_role + local team = rag.was_team + local rd = roles.GetByIndex(subrole) + local roletext = "body_found_" .. rd.abbr + local clr = rag.role_color + local bool = GetGlobalBool("ttt2_confirm_team") + + net.Start("TTT2SendConfirmMsg") + + if bool then + net.WriteString("body_found_team") + else + net.WriteString("body_found") + end + + net.WriteString(rag.sid64) + + -- color + net.WriteUInt(clr.r, 8) + net.WriteUInt(clr.g, 8) + net.WriteUInt(clr.b, 8) + net.WriteUInt(clr.a, 8) + + net.WriteBool(bool) + + net.WriteString(finder) + net.WriteString(nick) + net.WriteString(roletext) + + if bool then + net.WriteString(team) + end + + -- send searchUID to update UI buttons on client + net.WriteUInt(searchUID or 0, 16) + + net.Broadcast() + end + + if GetConVar("ttt2_confirm_killlist"):GetBool() then + -- Handle kill list + local ragKills = rag.kills + + local killnicks = {} + for i = 1, #ragKills do + local victimSIDs = ragKills[i] + + -- filter out disconnected (and bots !) + local vic = player.GetBySteamID64(victimSIDs) + + -- is this an unconfirmed dead? + if not IsValid(vic) or vic:TTT2NETGetBool("body_found", false) then + continue + end + + killnicks[#killnicks + 1] = vic:Nick() + + vic:ConfirmPlayer(false) + + -- however, do not mark body as found. This lets players find the + -- body later and get the benefits of that + --local vicrag = vic.server_ragdoll + --CORPSE.SetFound(vicrag, true) + end + + if #killnicks == 1 then + LANG.Msg("body_confirm_one", { finder = finder, victim = killnicks[1] }) + elseif #killnicks > 1 then + table.sort(killnicks, function(a, b) + return a and b and a:upper() < b:upper() + end) + + local names = killnicks[1] + for k = 2, #killnicks do + names = names .. ", " .. killnicks[k] + end + + LANG.Msg("body_confirm_more", { finder = finder, victims = names, count = #killnicks }) + end + end end --- -- Send a usermessage to client containing search results. --- @param Player ply The player that is inspection the ragdoll +-- @param Player ply The player that is inspecting the ragdoll -- @param Entity rag The ragdoll that is inspected -- @param boolean isCovert Is the search hidden -- @param boolean isLongRange Is the search performed from a long range -- @realm server function CORPSE.ShowSearch(ply, rag, isCovert, isLongRange) - if not IsValid(ply) or not IsValid(rag) then return end - - local roleData = ply:GetSubRoleData() - - if cvDeteOnlyInspect:GetBool() and not roleData.isPolicingRole then - LANG.Msg(ply, "inspect_detective_only", nil, MSG_MSTACK_WARN) - - GiveFoundCredits(ply, rag, isLongRange) - - return - end - - if rag:IsOnFire() then - LANG.Msg(ply, "body_burning", nil, MSG_CHAT_WARN) - - return - end - - --- - -- @realm server - if not hook.Run("TTTCanSearchCorpse", ply, rag, isCovert, isLongRange) then return end - - -- init a heap of data we'll be sending - local nick = CORPSE.GetPlayerNick(rag) - local subrole = rag.was_role - local role_color = rag.role_color - local team = rag.was_team - local eq = rag.equipment or {} - local c4 = rag.bomb_wire or - 1 - local dmg = rag.dmgtype or DMG_GENERIC - local wep = rag.dmgwep or "" - local words = rag.last_words or "" - local hshot = rag.was_headshot or false - local dtime = rag.time or 0 - - local owner = player.GetBySteamID64(rag.sid64) - owner = IsValid(owner) and owner:EntIndex() or -1 - - -- basic sanity check - if not nick or not eq or not subrole or not team then return end - - if GetConVar("ttt_identify_body_woconfirm"):GetBool() and DetectiveMode() and not isCovert then - IdentifyBody(ply, rag) - end - - GiveFoundCredits(ply, rag, isLongRange) - - -- time of death relative to current time (saves bits) - if dtime ~= 0 then - dtime = math.Round(CurTime() - dtime) - end - - -- identifier so we know whether a ttt_confirm_death was legit - ply.search_id = {eidx = rag:EntIndex(), id = rag:EntIndex() + dtime} - - -- time of dna sample decay relative to current time - local stime = 0 - - if rag.killer_sample then - stime = math.max(0, rag.killer_sample.t - CurTime()) - end - - -- build list of people this player killed - local kill_entids = {} - local ragKills = rag.kills - - for i = 1, #ragKills do - local vicsid = ragKills[i] - - -- also send disconnected players as a marker - local vic = player.GetBySteamID64(vicsid) - - kill_entids[#kill_entids + 1] = IsValid(vic) and vic:EntIndex() or -1 - end - - local lastid = -1 - - if rag.lastid and ply:IsActive() and roleData.isPolicingRole then - -- if the person this victim last id'd has since disconnected, send -1 to - -- indicate this - lastid = IsValid(rag.lastid.ent) and rag.lastid.ent:EntIndex() or - 1 - end - - -- Send a message with basic info - net.Start("TTT_RagdollSearch") - net.WriteUInt(rag:EntIndex(), 16) -- 16 bits - net.WriteUInt(owner, 8) -- 128 max players. (8 bits) - net.WriteString(nick) - net.WriteColor(role_color) - - net.WriteUInt(#eq, 16) -- Equipment (16 = max.) - - for i = 1, #eq do - net.WriteString(eq[i]) - end - - net.WriteUInt(subrole, ROLE_BITS) -- (... bits) - net.WriteString(team) - net.WriteInt(c4, bitsRequired(C4_WIRE_COUNT) + 1) -- -1 -> 2^bits (default c4: 4 bits) - net.WriteUInt(dmg, 30) -- DMG_BUCKSHOT is the highest. (30 bits) - net.WriteString(wep) - net.WriteBit(hshot) -- (1 bit) - net.WriteInt(dtime, 16) - net.WriteInt(stime, 16) - - net.WriteUInt(#kill_entids, 8) - - for i = 1, #kill_entids do - net.WriteUInt(kill_entids[i], 8) -- first game.MaxPlayers() of entities are for players. - end - - net.WriteUInt(lastid, 8) - - -- Who found this, so if we get this from a detective we can decide not to - -- show a window - net.WriteUInt(ply:EntIndex(), 8) - net.WriteString(words) - - net.WriteBit(isLongRange) - - -- 133 + string data + #kill_entids * 8 + team + 1 - -- 200 + ? - - -- workaround to make sure only detective searches are added to the scoreboard - net.WriteBool(ply:IsActive() and roleData.isPolicingRole and not isCovert) - - -- If searched publicly, send to all, else just the finder - if not isCovert then - net.Broadcast() - else - net.Send(ply) - end + if not IsValid(ply) or not IsValid(rag) then + return + end + + -- prevent search for anyone if the body is burning + if rag:IsOnFire() then + LANG.Msg(ply, "body_burning", nil, MSG_CHAT_WARN) + + return + end + + --- + -- @realm server + -- stylua: ignore + if not hook.Run("TTTCanSearchCorpse", ply, rag, isCovert, isLongRange) then return end + + local sceneData = bodysearch.AssimilateSceneData(ply, rag, isCovert, isLongRange) + + -- only in mode 0 everyone can confirm by pressing E + if bodysearch.GetInspectConfirmMode() == 0 or sceneData.base.isPublicPolicingSearch then + -- only give credits if body is also confirmed + if not isCovert then + bodysearch.GiveFoundCredits(ply, rag, isLongRange, sceneData.searchUID) + end + + if + GetConVar("ttt_identify_body_woconfirm"):GetBool() + and DetectiveMode() + and not isCovert + then + CORPSE.IdentifyBody(ply, rag, sceneData.searchUID) + end + elseif not isCovert then + -- in mode 1 and 2 every active shopping role can take credits + bodysearch.GiveFoundCredits(ply, rag, isLongRange, sceneData.searchUID) + end + + -- cache credits of corpse here, AFTER one might has taken them + sceneData.credits = CORPSE.GetCredits(rag, 0) + + -- identifier so we know whether a ttt_confirm_death was legit + ply.searchID = sceneData.searchUID + + local roleData = ply:GetSubRoleData() + if ply:IsActive() and roleData.isPolicingRole and roleData.isPublicRole and not isCovert then + bodysearch.StreamSceneData(sceneData) + else + bodysearch.StreamSceneData(sceneData, ply) + end end --- @@ -438,95 +289,162 @@ end -- else returns nil -- @param Player victim -- @param Player attacker --- @param DamageInfo dmg +-- @param CTakeDamageInfo dmg -- @return table sample -- @realm server local function GetKillerSample(victim, attacker, dmg) - -- only guns and melee damage, not explosions - if not dmg:IsBulletDamage() and not dmg:IsDamageType(DMG_SLASH) and not dmg:IsDamageType(DMG_CLUB) then return end - - if not IsValid(victim) or not IsValid(attacker) or not attacker:IsPlayer() then return end - - -- NPCs for which a player is damage owner (meaning despite the NPC dealing - -- the damage, the attacker is a player) should not cause the player's DNA to - -- end up on the corpse. - local infl = dmg:GetInflictor() - - if IsValid(infl) and infl:IsNPC() then return end - - local dist = victim:GetPos():Distance(attacker:GetPos()) - - if not ConVarExists("ttt_killer_dna_range") or dist > GetConVar("ttt_killer_dna_range"):GetInt() then return end - - local sample = {} - sample.killer = attacker - sample.killer_sid = attacker:SteamID64() - sample.killer_sid64 = attacker:SteamID64() - sample.victim = victim - sample.t = CurTime() + (-1 * (0.019 * dist) ^ 2 + (ConVarExists("ttt_killer_dna_basetime") and GetConVar("ttt_killer_dna_basetime"):GetInt() or 0)) - - return sample + -- only guns and melee damage, not explosions + if + not dmg:IsBulletDamage() + and not dmg:IsDamageType(DMG_SLASH) + and not dmg:IsDamageType(DMG_CLUB) + then + return + end + + if not IsValid(victim) or not IsValid(attacker) or not attacker:IsPlayer() then + return + end + + -- NPCs for which a player is damage owner (meaning despite the NPC dealing + -- the damage, the attacker is a player) should not cause the player's DNA to + -- end up on the corpse. + local infl = dmg:GetInflictor() + + if IsValid(infl) and infl:IsNPC() then + return + end + + local dist = victim:GetPos():Distance(attacker:GetPos()) + + if + not ConVarExists("ttt_killer_dna_range") + or dist > GetConVar("ttt_killer_dna_range"):GetInt() + then + return + end + + local sample = {} + sample.killer = attacker + sample.killer_sid = attacker:SteamID64() + sample.killer_sid64 = attacker:SteamID64() + sample.victim = victim + sample.t = CurTime() + + ( + -1 * (0.019 * dist) ^ 2 + + ( + ConVarExists("ttt_killer_dna_basetime") + and GetConVar("ttt_killer_dna_basetime"):GetInt() + or 0 + ) + ) + + return sample end local crimescene_keys = { - "Fraction", - "HitBox", - "Normal", - "HitPos", - "StartPos" + "Fraction", + "HitBox", + "Normal", + "HitPos", + "StartPos", } local poseparams = { - "aim_yaw", - "move_yaw", - "aim_pitch" + "aim_yaw", + "move_yaw", + "aim_pitch", } local function GetSceneDataFromPlayer(ply) - local data = { - pos = ply:GetPos(), - ang = ply:GetAngles(), - sequence = ply:GetSequence(), - cycle = ply:GetCycle() - } + local data = { + pos = ply:GetPos(), + ang = ply:GetAngles(), + sequence = ply:GetSequence(), + cycle = ply:GetCycle(), + } - for i = 1, #poseparams do - local param = poseparams[i] + for i = 1, #poseparams do + local param = poseparams[i] - data[param] = ply:GetPoseParameter(param) - end + data[param] = ply:GetPoseParameter(param) + end - return data + return data end -local function GetSceneData(victim, attacker, dmginfo) - -- only for guns for now, hull traces don't work well etc - if not dmginfo:IsBulletDamage() then return end - - local scene = {} - - if victim.hit_trace then - scene.hit_trace = table.CopyKeys(victim.hit_trace, crimescene_keys) - else - return scene - end - - scene.victim = GetSceneDataFromPlayer(victim) - - if IsValid(attacker) and attacker:IsPlayer() then - scene.killer = GetSceneDataFromPlayer(attacker) - - local att = attacker:LookupAttachment("anim_attachment_RH") - local angpos = attacker:GetAttachment(att) +--- - if not angpos then - scene.hit_trace.StartPos = attacker:GetShootPos() - else - scene.hit_trace.StartPos = angpos.Pos - end - end +--- +-- Clones a CTakeDamageInfo into a table called DamageInfoData +-- @param CTakeDamageInfo dmginfo +-- @return DamageInfoData The damage info data table +-- @realm server +function CreateDamageInfoData(dmginfo) + return { + ammoType = dmginfo:GetAmmoType(), + attacker = dmginfo:GetAttacker(), + baseDamage = dmginfo:GetBaseDamage(), + damage = dmginfo:GetDamage(), + damageBonus = dmginfo:GetDamageBonus(), + damageCustom = dmginfo:GetDamageCustom(), + damageForce = dmginfo:GetDamageForce(), + damagePosition = dmginfo:GetDamagePosition(), + damageType = dmginfo:GetDamageType(), + inflictor = dmginfo:GetInflictor(), + maxDamage = dmginfo:GetMaxDamage(), + reportedPosition = dmginfo:GetReportedPosition(), + isBulletDamage = dmginfo:IsBulletDamage(), + isExplosionDamage = dmginfo:IsExplosionDamage(), + isFallDamage = dmginfo:IsFallDamage(), + } +end - return scene +local function GetSceneData(victim, attacker, dmginfo) + local scene = {} + + scene.damageInfoData = CreateDamageInfoData(dmginfo) + + if victim.hit_trace then + scene.hit_trace = table.CopyKeys(victim.hit_trace, crimescene_keys) + end + + scene.waterLevel = victim:WaterLevel() + scene.hitGroup = victim:LastHitGroup() + scene.floorSurface = 0 + local groundTrace = util.TraceLine({ + start = victim:GetPos(), + endpos = victim:GetPos() + Vector(0, 0, -100), + }) + if groundTrace.Hit then + scene.floorSurface = groundTrace.MatType + end + scene.plyModel = victim:GetModel() + scene.plySID64 = victim:SteamID64() + scene.lastDamage = dmginfo:GetDamage() + + scene.victim = GetSceneDataFromPlayer(victim) + + if IsValid(attacker) and attacker:IsPlayer() then + scene.killer = GetSceneDataFromPlayer(attacker) + + if not scene.hit_trace then + return scene + end + + local att = attacker:LookupAttachment("anim_attachment_RH") + local angpos = attacker:GetAttachment(att) + + if not angpos then + scene.hit_trace.StartPos = attacker:GetShootPos() + scene.hit_trace.StartAng = attacker:EyeAngles() + else + scene.hit_trace.StartPos = angpos.Pos + scene.hit_trace.StartAng = angpos.Ang + end + end + + return scene end realdamageinfo = 0 @@ -535,107 +453,117 @@ realdamageinfo = 0 -- Creates client or server ragdoll depending on settings -- @param Player ply -- @param Player attacker --- @param DamageInfo dmginfo +-- @param CTakeDamageInfo dmginfo -- @return Entity the CORPSE -- @realm server function CORPSE.Create(ply, attacker, dmginfo) - if not IsValid(ply) then return end - - local rag = ents.Create("prop_ragdoll") - if not IsValid(rag) then return end - - rag:SetPos(ply:GetPos()) - rag:SetModel(ply:GetModel()) - rag:SetSkin(ply:GetSkin()) - rag:SetAngles(ply:GetAngles()) - rag:SetColor(ply:GetColor()) - - rag:Spawn() - rag:Activate() - - -- nonsolid to players, but can be picked up and shot - rag:SetCollisionGroup(COLLISION_GROUP_WEAPON) - rag:SetCustomCollisionCheck(true) - - -- flag this ragdoll as being a player's - rag.player_ragdoll = true - rag.sid = ply:SteamID() - rag.sid64 = ply:SteamID64() - rag.uqid = ply:UniqueID() -- backwards compatibility use rag.sid64 instead - - -- network data - CORPSE.SetPlayerNick(rag, ply) - CORPSE.SetFound(rag, false) - CORPSE.SetCredits(rag, ply:GetCredits()) - - -- if someone searches this body they can find info on the victim and the - -- death circumstances - rag.equipment = table.Copy(ply:GetEquipmentItems()) - rag.was_role = ply:GetSubRole() - rag.role_color = ply:GetRoleColor() - - rag.was_team = ply:GetTeam() - rag.bomb_wire = ply.bomb_wire - rag.dmgtype = dmginfo:GetDamageType() - - local wep = util.WeaponFromDamage(dmginfo) - rag.dmgwep = IsValid(wep) and wep:GetClass() or "" - - rag.was_headshot = ply.was_headshot and dmginfo:IsBulletDamage() - rag.time = CurTime() - rag.kills = table.Copy(ply.kills) - rag.killer_sample = GetKillerSample(ply, attacker, dmginfo) - - -- crime scene data - rag.scene = GetSceneData(ply, attacker, dmginfo) - - -- position the bones - local num = (rag:GetPhysicsObjectCount() - 1) - local v = ply:GetVelocity() - - -- bullets have a lot of force, which feels better when shooting props, - -- but makes bodies fly, so dampen that here - if dmginfo:IsDamageType(DMG_BULLET) or dmginfo:IsDamageType(DMG_SLASH) then - v:Mul(0.2) - end - - --- - -- @realm server - hook.Run("TTT2ModifyRagdollVelocity", ply, rag, v) - - for i = 0, num do - local bone = rag:GetPhysicsObjectNum(i) - - if IsValid(bone) then - local bp, ba = ply:GetBonePosition(rag:TranslatePhysBoneToBone(i)) - - if bp and ba then - bone:SetPos(bp) - bone:SetAngles(ba) - end - - -- not sure if this will work: - bone:SetVelocity(v) - end - end - - -- create advanced death effects (knives) - if ply.effect_fn then - -- next frame, after physics is happy for this ragdoll - local efn = ply.effect_fn - - timer.Simple(0, function() - if not IsValid(rag) then return end - - efn(rag) - end) - end - - --- - -- @realm server - hook.Run("TTTOnCorpseCreated", rag, ply) - - return rag -- we'll be speccing this + if not IsValid(ply) then + return + end + + local efn = ply.effect_fn + ply.effect_fn = nil + + local rag = ents.Create("prop_ragdoll") + if not IsValid(rag) then + return + end + + rag:SetPos(ply:GetPos()) + rag:SetModel(ply:GetModel()) + rag:SetSkin(ply:GetSkin()) + rag:SetAngles(ply:GetAngles()) + rag:SetColor(ply:GetColor()) + + rag:Spawn() + rag:Activate() + + -- nonsolid to players, but can be picked up and shot + rag:SetCollisionGroup(COLLISION_GROUP_WEAPON) + rag:SetCustomCollisionCheck(true) + + -- flag this ragdoll as being a player's + rag.player_ragdoll = true + rag.sid = ply:SteamID() + rag.sid64 = ply:SteamID64() + rag.uqid = ply:UniqueID() -- backwards compatibility use rag.sid64 instead + + -- network data + CORPSE.SetPlayerNick(rag, ply) + CORPSE.SetFound(rag, false) + CORPSE.SetCredits(rag, ply:GetCredits()) + + -- if someone searches this body they can find info on the victim and the + -- death circumstances + rag.equipment = table.Copy(ply:GetEquipmentItems()) + rag.was_role = ply:GetSubRole() + rag.role_color = ply:GetRoleColor() + + rag.was_team = ply:GetTeam() + rag.bomb_wire = ply.bomb_wire + rag.dmgtype = dmginfo:GetDamageType() + + local wep = util.WeaponFromDamage(dmginfo) + ---@cast wep -nil + rag.dmgwep = IsValid(wep) and wep:GetClass() or "" + + rag.was_headshot = ply.was_headshot and dmginfo:IsBulletDamage() + rag.time = CurTime() + rag.kills = table.Copy(ply.kills) + rag.killer_sample = GetKillerSample(ply, attacker, dmginfo) + + -- crime scene data + rag.scene = GetSceneData(ply, attacker, dmginfo) + + -- position the bones + local num = (rag:GetPhysicsObjectCount() - 1) + local v = ply:GetVelocity() + + -- bullets have a lot of force, which feels better when shooting props, + -- but makes bodies fly, so dampen that here + if dmginfo:IsDamageType(DMG_BULLET) or dmginfo:IsDamageType(DMG_SLASH) then + v:Mul(0.2) + end + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2ModifyRagdollVelocity", ply, rag, v) + + for i = 0, num do + local bone = rag:GetPhysicsObjectNum(i) + + if IsValid(bone) then + local bp, ba = ply:GetBonePosition(rag:TranslatePhysBoneToBone(i)) + + if bp and ba then + bone:SetPos(bp) + bone:SetAngles(ba) + end + + -- not sure if this will work: + bone:SetVelocity(v) + end + end + + -- create advanced death effects (knives) + if efn then + -- next frame, after physics is happy for this ragdoll + timer.Simple(0, function() + if not IsValid(rag) then + return + end + + efn(rag) + end) + end + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTTOnCorpseCreated", rag, ply) + + return rag -- we'll be speccing this end --- @@ -644,7 +572,7 @@ end -- @return boolean Returns if the player was headshot -- @realm server function CORPSE.WasHeadshot(rag) - return IsValid(rag) and rag.was_headshot + return IsValid(rag) and rag.was_headshot end --- @@ -653,7 +581,7 @@ end -- @return number The death time, 0 if the ragdol is not valid -- @realm server function CORPSE.GetPlayerDeathTime(rag) - return rag.time or 0 + return rag.time or 0 end --- @@ -662,7 +590,7 @@ end -- @return string The SteamID64, "" if the ragdol is not valid -- @realm server function CORPSE.GetPlayerSID64(rag) - return rag.sid64 or "" + return rag.sid64 or "" end --- @@ -671,7 +599,7 @@ end -- @return number The role, @{ROLE_INNOCENT} if the ragdol is not valid -- @realm server function CORPSE.GetPlayerRole(rag) - return rag.was_role or ROLE_INNOCENT + return rag.was_role or ROLE_INNOCENT end --- @@ -680,19 +608,26 @@ end -- @return string The team, @{TEAM_INNOCENT} if the ragdol is not valid -- @realm server function CORPSE.GetPlayerTeam(rag) - return rag.was_team or TEAM_INNOCENT + return rag.was_team or TEAM_INNOCENT end hook.Add("ShouldCollide", "TTT2RagdollCollide", function(ent1, ent2) - if cvRagCollide:GetBool() then return end - - if IsValid(ent1) and IsValid(ent2) - and ent1:IsRagdoll() and ent2:IsRagdoll() - and ent1.GetCollisionGroup and ent1:GetCollisionGroup() == COLLISION_GROUP_WEAPON - and ent2.GetCollisionGroup and ent2:GetCollisionGroup() == COLLISION_GROUP_WEAPON - then - return false - end + if cvRagCollide:GetBool() then + return + end + + if + IsValid(ent1) + and IsValid(ent2) + and ent1:IsRagdoll() + and ent2:IsRagdoll() + and ent1.GetCollisionGroup + and ent1:GetCollisionGroup() == COLLISION_GROUP_WEAPON + and ent2.GetCollisionGroup + and ent2:GetCollisionGroup() == COLLISION_GROUP_WEAPON + then + return false + end end) --- @@ -705,9 +640,7 @@ end) -- @param Player deadply The dead player -- @hook -- @realm server -function GM:TTT2CalledPolicingRole(policingPlys, finder, ragdoll, deadply) - -end +function GM:TTT2CalledPolicingRole(policingPlys, finder, ragdoll, deadply) end --- -- Checks whether a @{Player} is able to identify a @{CORPSE}. @@ -718,7 +651,7 @@ end -- @hook -- @realm server function GM:TTTCanIdentifyCorpse(ply, rag) - return true + return true end --- @@ -729,9 +662,7 @@ end -- @param[default=nil] boolean Return false to block confirmation -- @hook -- @realm server -function GM:TTT2ConfirmPlayer(ply, rag) - -end +function GM:TTT2ConfirmPlayer(ply, rag) end --- -- Called when a player finds a ragdoll. They must be able to inspect the body @@ -742,9 +673,7 @@ end -- @param Entity rag The ragdoll that was found -- @hook -- @realm server -function GM:TTTBodyFound(ply, deadply, rag) - -end +function GM:TTTBodyFound(ply, deadply, rag) end --- -- Used to block updating the state of the corpse. Normally a confirmed @@ -758,9 +687,7 @@ end -- corpse found variable -- @hook -- @realm server -function GM:TTT2SetCorpseFound(deadply, ply, rag) - -end +function GM:TTT2SetCorpseFound(deadply, ply, rag) end --- -- This hook is called after a players pressed the "call detective" button and @@ -771,9 +698,7 @@ end -- @param Player ply The player that pressed the "call detective" button -- @hook -- @realm server -function GM:TTT2ModifyCorpseCallRadarRecipients(notifiedPlayers, rag, ply) - -end +function GM:TTT2ModifyCorpseCallRadarRecipients(notifiedPlayers, rag, ply) end --- -- Checks whether a @{Player} is able to search a @{CORPSE} based on their position. @@ -787,7 +712,7 @@ end -- @hook -- @realm server function GM:TTTCanSearchCorpse(ply, rag, isCovert, isLongRange) - return true + return true end --- @@ -797,9 +722,7 @@ end -- @param Vector velocity The velocity vector of the corpse -- @hook -- @realm server -function GM:TTT2ModifyRagdollVelocity(deadply, rag, velocity) - -end +function GM:TTT2ModifyRagdollVelocity(deadply, rag, velocity) end --- -- Called after a dead player's corpse has been created and initialized. Modify the corpse table @@ -808,9 +731,7 @@ end -- @param Player deadply The dead player whose ragdoll was created -- @hook -- @realm server -function GM:TTTOnCorpseCreated(rag, deadply) - -end +function GM:TTTOnCorpseCreated(rag, deadply) end --- -- Called after a player has been given credits for searching a corpse. @@ -819,6 +740,4 @@ end -- @param number credits The amount of credits that were given -- @hook -- @realm server -function GM:TTT2OnGiveFoundCredits(ply, rag, credits) - -end \ No newline at end of file +function GM:TTT2OnGiveFoundCredits(ply, rag, credits) end diff --git a/gamemodes/terrortown/gamemode/server/sv_ent_replace.lua b/gamemodes/terrortown/gamemode/server/sv_ent_replace.lua index d6d2d9def..d54ba4fd1 100644 --- a/gamemodes/terrortown/gamemode/server/sv_ent_replace.lua +++ b/gamemodes/terrortown/gamemode/server/sv_ent_replace.lua @@ -18,18 +18,18 @@ local stringMatch = string.match -- @param boolean player_only -- @realm server function ents.TTT.RemoveRagdolls(player_only) - local ragdolls = ents.FindByClass("prop_ragdoll") - local stringfind = string.find - - for i = 1, #ragdolls do - local ent = ragdolls[i] - - if not player_only and stringfind(ent:GetModel(), "zm_", 6, true) then - ent:Remove() - elseif ent.player_ragdoll then - ent:Remove() -- cleanup ought to catch these but you know - end - end + local ragdolls = ents.FindByClass("prop_ragdoll") + local stringfind = string.find + + for i = 1, #ragdolls do + local ent = ragdolls[i] + + if not player_only and stringfind(ent:GetModel(), "zm_", 6, true) then + ent:Remove() + elseif ent.player_ragdoll then + ent:Remove() -- cleanup ought to catch these but you know + end + end end -- GMod's game.CleanUpMap destroys rope entities that are parented. This is an @@ -37,10 +37,10 @@ end -- rope reparented. -- Same happens for func_brush. local broken_parenting_ents = { - "move_rope", - "keyframe_rope", - "info_target", - "func_brush" + "move_rope", + "keyframe_rope", + "info_target", + "func_brush", } --- @@ -48,23 +48,25 @@ local broken_parenting_ents = { -- @realm server -- @internal function ents.TTT.FixParentedPreCleanup() - local entsFindByClass = ents.FindByClass + local entsFindByClass = ents.FindByClass - for i = 1, #broken_parenting_ents do - local entits = entsFindByClass(broken_parenting_ents[i]) + for i = 1, #broken_parenting_ents do + local entits = entsFindByClass(broken_parenting_ents[i]) - for k = 1, #entits do - local v = entits[k] + for k = 1, #entits do + local v = entits[k] - if v.GetParent == nil or not IsValid(v:GetParent()) then continue end + if v.GetParent == nil or not IsValid(v:GetParent()) then + continue + end - v.CachedParentName = v:GetParent():GetName() + v.CachedParentName = v:GetParent():GetName() - v:SetParent(nil) + v:SetParent(nil) - v.OrigPos = v.OrigPos or v:GetPos() - end - end + v.OrigPos = v.OrigPos or v:GetPos() + end + end end --- @@ -72,29 +74,31 @@ end -- @realm server -- @internal function ents.TTT.FixParentedPostCleanup() - local entsFindByClass = ents.FindByClass - local entsFindByName = ents.FindByName + local entsFindByClass = ents.FindByClass + local entsFindByName = ents.FindByName - for i = 1, #broken_parenting_ents do - local entits = entsFindByClass(broken_parenting_ents[i]) + for i = 1, #broken_parenting_ents do + local entits = entsFindByClass(broken_parenting_ents[i]) - for k = 1, #entits do - local v = entits[k] + for k = 1, #entits do + local v = entits[k] - if v.CachedParentName == nil then continue end + if v.CachedParentName == nil then + continue + end - if v.OrigPos then - v:SetPos(v.OrigPos) - end + if v.OrigPos then + v:SetPos(v.OrigPos) + end - local parents = entsFindByName(v.CachedParentName) - if #parents == 1 then - local parent = parents[1] + local parents = entsFindByName(v.CachedParentName) + if #parents == 1 then + local parent = parents[1] - v:SetParent(parent) - end - end - end + v:SetParent(parent) + end + end + end end --- @@ -104,16 +108,15 @@ end -- @realm server -- @internal function ents.TTT.TriggerRoundStateOutputs(r, param) - r = r or GetRoundState() + r = r or GetRoundState() - local entMapSettings = ents.FindByClass("ttt_map_settings") + local entMapSettings = ents.FindByClass("ttt_map_settings") - for i = 1, #entMapSettings do - entMapSettings[i]:RoundStateTrigger(r, param) - end + for i = 1, #entMapSettings do + entMapSettings[i]:RoundStateTrigger(r, param) + end end - --- -- Checks whether the given map is able to import @{Entity} based on the map's data -- @param string map @@ -121,13 +124,13 @@ end -- @realm server -- @internal function ents.TTT.CanImportEntities(map) - local fname = "maps/" .. map .. "_ttt.txt" + local fname = "maps/" .. map .. "_ttt.txt" - return file.Exists(fname, "GAME") + return file.Exists(fname, "GAME") end local classremap = { - ttt_playerspawn = "info_player_deathmatch" + ttt_playerspawn = "info_player_deathmatch", } --- @@ -139,59 +142,61 @@ local classremap = { -- @internal -- @realm server function ents.TTT.ImportEntities(map) - local fname = "maps/" .. map .. "_ttt.txt" - local buf = file.Read(fname, "GAME") - local lines = string.Explode("\n", buf) + local fname = "maps/" .. map .. "_ttt.txt" + local buf = file.Read(fname, "GAME") + local lines = string.Explode("\n", buf) - local settings = {} - local spawns = {} + local settings = {} + local spawns = {} - for k = 1, #lines do - local line = lines[k] + for k = 1, #lines do + local line = lines[k] - -- ignore the line if empty or comment - if stringMatch(line, "^#") or line == "" or string.byte(line) == 0 then continue end + -- ignore the line if empty or comment + if stringMatch(line, "^#") or line == "" or string.byte(line) == 0 then + continue + end - -- attempt to find settings in the file - local key, val = stringMatch(line, "^setting:\t(%w*) ([0-9]*)") + -- attempt to find settings in the file + local key, val = stringMatch(line, "^setting:\t(%w*) ([0-9]*)") - if key and val then - val = tonumber(val) + if key and val then + val = tonumber(val) - settings[key] = val + settings[key] = val - continue - end + continue + end - -- if it is no settings line, it probably is a spawn line - local data = string.Explode("\t", line) + -- if it is no settings line, it probably is a spawn line + local data = string.Explode("\t", line) - local newSpawn = {} + local newSpawn = {} - if data[2] and data[3] then - -- some dummy ents remap to different, real entity names - newSpawn.class = classremap[data[1]] or data[1] + if data[2] and data[3] then + -- some dummy ents remap to different, real entity names + newSpawn.class = classremap[data[1]] or data[1] - local posraw = string.Explode(" ", data[2]) - newSpawn.pos = Vector(tonum(posraw[1]), tonum(posraw[2]), tonum(posraw[3])) + local posraw = string.Explode(" ", data[2]) + newSpawn.pos = Vector(tonum(posraw[1]), tonum(posraw[2]), tonum(posraw[3])) - local angraw = string.Explode(" ", data[3]) - newSpawn.ang = Angle(tonum(angraw[1]), tonum(angraw[2]), tonum(angraw[3])) + local angraw = string.Explode(" ", data[3]) + newSpawn.ang = Angle(tonum(angraw[1]), tonum(angraw[2]), tonum(angraw[3])) - -- random weapons have a useful keyval - if data[4] then - local kvraw = string.Explode(" ", data[4]) - local kvKey = kvraw[1] - local kvVal = tonum(kvraw[2]) + -- random weapons have a useful keyval + if data[4] then + local kvraw = string.Explode(" ", data[4]) + local kvKey = kvraw[1] + local kvVal = tonum(kvraw[2]) - if kvKey and kvVal and kvKey == "auto_ammo" then - newSpawn.ammo = kvVal - end - end - end + if kvKey and kvVal and kvKey == "auto_ammo" then + newSpawn.ammo = kvVal + end + end + end - spawns[#spawns + 1] = newSpawn - end + spawns[#spawns + 1] = newSpawn + end - return spawns, settings + return spawns, settings end diff --git a/gamemodes/terrortown/gamemode/server/sv_entity.lua b/gamemodes/terrortown/gamemode/server/sv_entity.lua index 864065cf9..db305f22f 100644 --- a/gamemodes/terrortown/gamemode/server/sv_entity.lua +++ b/gamemodes/terrortown/gamemode/server/sv_entity.lua @@ -1,20 +1,22 @@ --- --- @ref https://wiki.garrysmod.com/page/Category:Entity +-- @ref https://wiki.facepunch.com/gmod/Entity -- @class Entity local safeCollisionGroups = { - [COLLISION_GROUP_WEAPON] = true + [COLLISION_GROUP_WEAPON] = true, } local entmeta = FindMetaTable("Entity") -if not entmeta then return end +if not entmeta then + return +end --- -- Sets the @{Entity}'s damage owner and the current time -- @param Player ply -- @realm server function entmeta:SetDamageOwner(ply) - self.dmg_owner = {ply = ply, t = CurTime()} + self.dmg_owner = { ply = ply, t = CurTime() } end --- @@ -23,9 +25,11 @@ end -- @return number the time the damage owner was set -- @realm server function entmeta:GetDamageOwner() - if self.dmg_owner == nil then return end + if self.dmg_owner == nil then + return + end - return self.dmg_owner.ply, self.dmg_owner.t + return self.dmg_owner.ply, self.dmg_owner.t end --- @@ -33,9 +37,9 @@ end -- @return boolean -- @realm server function entmeta:IsExplosive() - local kv = self:GetKeyValues()["ExplodeDamage"] + local kv = self:GetKeyValues()["ExplodeDamage"] - return self:Health() > 0 and kv and kv > 0 + return self:Health() > 0 and kv and kv > 0 end --- @@ -43,7 +47,7 @@ end -- @return boolean Returns if the entity is passable -- @realm server function entmeta:HasPassableCollisionGrup() - return safeCollisionGroups[self:GetCollisionGroup()] + return safeCollisionGroups[self:GetCollisionGroup()] end local oldSpawn = entmeta.Spawn @@ -54,14 +58,14 @@ local oldSpawn = entmeta.Spawn -- or @{Player:SpawnForRound} if you want to (re-)spawn the player. -- @realm server function entmeta:Spawn() - if self:IsPlayer() then - local spawnPoint = plyspawn.GetRandomSafePlayerSpawnPoint(self) + if self:IsPlayer() then + local spawnPoint = plyspawn.GetRandomSafePlayerSpawnPoint(self) - if spawnPoint then - self:SetPos(spawnPoint.pos) - self:SetAngles(spawnPoint.ang) - end - end + if spawnPoint then + self:SetPos(spawnPoint.pos) + self:SetAngles(spawnPoint.ang) + end + end - oldSpawn(self) + oldSpawn(self) end diff --git a/gamemodes/terrortown/gamemode/server/sv_eventpopup.lua b/gamemodes/terrortown/gamemode/server/sv_eventpopup.lua index f53409bcb..1e7d398fe 100644 --- a/gamemodes/terrortown/gamemode/server/sv_eventpopup.lua +++ b/gamemodes/terrortown/gamemode/server/sv_eventpopup.lua @@ -14,48 +14,50 @@ EPOP = EPOP or {} -- @param[default=false] boolean blocking If this is false, this message gets instantly replaced if a new message is added -- @realm server function EPOP:AddMessage(plys, title, subtitle, displayTime, blocking) - if not title and not subtitle then return end - - net.Start("ttt2_eventpopup") - - if title then - title = istable(title) and title or {text = title} - - net.WriteBool(true) - net.WriteString(title.text) - - if IsColor(title.color) then - net.WriteBool(true) - net.WriteColor(title.color) - else - net.WriteBool(false) - end - else - net.WriteBool(false) - end - - if subtitle then - subtitle = istable(subtitle) and subtitle or {text = subtitle} - - net.WriteBool(true) - net.WriteString(subtitle.text) - - if IsColor(subtitle.color) then - net.WriteBool(true) - net.WriteColor(subtitle.color) - else - net.WriteBool(false) - end - else - net.WriteBool(false) - end - - net.WriteUInt(displayTime or 4, 16) - net.WriteBool(blocking == true) - - if plys then - net.Send(plys) - else - net.Broadcast() - end + if not title and not subtitle then + return + end + + net.Start("ttt2_eventpopup") + + if title then + title = istable(title) and title or { text = title } + + net.WriteBool(true) + net.WriteString(title.text) + + if IsColor(title.color) then + net.WriteBool(true) + net.WriteColor(title.color) + else + net.WriteBool(false) + end + else + net.WriteBool(false) + end + + if subtitle then + subtitle = istable(subtitle) and subtitle or { text = subtitle } + + net.WriteBool(true) + net.WriteString(subtitle.text) + + if IsColor(subtitle.color) then + net.WriteBool(true) + net.WriteColor(subtitle.color) + else + net.WriteBool(false) + end + else + net.WriteBool(false) + end + + net.WriteUInt(displayTime or 4, 16) + net.WriteBool(blocking == true) + + if plys then + net.Send(plys) + else + net.Broadcast() + end end diff --git a/gamemodes/terrortown/gamemode/server/sv_gamemsg.lua b/gamemodes/terrortown/gamemode/server/sv_gamemsg.lua index 25964f116..319f4ab66 100644 --- a/gamemodes/terrortown/gamemode/server/sv_gamemsg.lua +++ b/gamemodes/terrortown/gamemode/server/sv_gamemsg.lua @@ -16,10 +16,10 @@ local IsValid = IsValid -- @realm server -- @deprecated function GameMsg(msg) - net.Start("TTT_GameMsg") - net.WriteString(msg) - net.WriteBit(false) - net.Broadcast() + net.Start("TTT_GameMsg") + net.WriteString(msg) + net.WriteBit(false) + net.Broadcast() end --- @@ -30,19 +30,19 @@ end -- @realm server -- @deprecated function CustomMsg(ply_or_rf, msg, c) - c = c or COLOR_WHITE - - net.Start("TTT_GameMsgColor") - net.WriteString(msg) - net.WriteUInt(c.r, 8) - net.WriteUInt(c.g, 8) - net.WriteUInt(c.b, 8) - - if ply_or_rf then - net.Send(ply_or_rf) - else - net.Broadcast() - end + c = c or COLOR_WHITE + + net.Start("TTT_GameMsgColor") + net.WriteString(msg) + net.WriteUInt(c.r, 8) + net.WriteUInt(c.g, 8) + net.WriteUInt(c.b, 8) + + if ply_or_rf then + net.Send(ply_or_rf) + else + net.Broadcast() + end end --- @@ -53,15 +53,15 @@ end -- @realm server -- @deprecated function PlayerMsg(ply_or_rf, msg, traitor_only) - net.Start("TTT_GameMsg") - net.WriteString(msg) - net.WriteBit(traitor_only) - - if ply_or_rf then - net.Send(ply_or_rf) - else - net.Broadcast() - end + net.Start("TTT_GameMsg") + net.WriteString(msg) + net.WriteBit(traitor_only) + + if ply_or_rf then + net.Send(ply_or_rf) + else + net.Broadcast() + end end --- @@ -71,27 +71,31 @@ end -- @realm server -- @deprecated function TraitorMsg(ply_or_rfilter, msg) - PlayerMsg(ply_or_rfilter, msg, true) + PlayerMsg(ply_or_rfilter, msg, true) end -- Teamchat local function RoleChatMsg(sender, msg) - local senderTeam = sender:GetTeam() - local senderRoleData = sender:GetSubRoleData() - - if senderTeam == TEAM_NONE - or senderRoleData.unknownTeam - or senderRoleData.disabledTeamChat - or TEAMS[senderTeam].alone - --- - -- @realm server - or hook.Run("TTT2AvoidTeamChat", sender, senderTeam, msg) == false - then return end - - net.Start("TTT_RoleChat") - net.WriteEntity(sender) - net.WriteString(msg) - net.Send(GetTeamChatFilter(senderTeam)) + local senderTeam = sender:GetTeam() + local senderRoleData = sender:GetSubRoleData() + + if + senderTeam == TEAM_NONE + or senderRoleData.unknownTeam + or senderRoleData.disabledTeamChat + or TEAMS[senderTeam].alone + --- + -- @realm server + -- stylua: ignore + or hook.Run("TTT2AvoidTeamChat", sender, senderTeam, msg) == false + then + return + end + + net.Start("TTT_RoleChat") + net.WriteEntity(sender) + net.WriteString(msg) + net.Send(GetTeamChatFilter(senderTeam)) end --- @@ -99,15 +103,17 @@ end -- @realm server -- @internal function ShowRoundStartPopup() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - if not IsValid(ply) or not ply:IsTerror() then continue end + if not IsValid(ply) or not ply:IsTerror() then + continue + end - ply:ConCommand("ttt_cl_startpopup") - end + ply:ConCommand("ttt_cl_startpopup") + end end --- @@ -116,30 +122,37 @@ end -- @return table -- @realm server function GetPlayerFilter(pred) - local filter = {} - local plys = player.GetAll() + local filter = {} + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - if not pred(ply) then continue end + if not pred(ply) then + continue + end - filter[#filter + 1] = ply - end + filter[#filter + 1] = ply + end - return filter + return filter end --- -- Returns a list of filtered @{Player}s by the team. -- @param string team -- @param boolean aliveOnly +-- @param boolean ignoreUnknownTeam -- @return table -- @realm server -function GetTeamFilter(team, aliveOnly) - return GetPlayerFilter(function(p) - return team ~= TEAM_NONE and not TEAMS[team].alone and p:GetTeam() == team and not p:GetSubRoleData().unknownTeam and (not aliveOnly or p:IsTerror()) - end) +function GetTeamFilter(team, aliveOnly, ignoreUnknownTeam) + return GetPlayerFilter(function(p) + return team ~= TEAM_NONE + and not TEAMS[team].alone + and p:GetTeam() == team + and (ignoreUnknownTeam or not p:GetSubRoleData().unknownTeam) + and (not aliveOnly or p:IsTerror()) + end) end --- @@ -149,7 +162,7 @@ end -- @realm server -- @see GetTeamFilter function GetInnocentFilter(aliveOnly) - return GetTeamFilter(TEAM_INNOCENT, aliveOnly) + return GetTeamFilter(TEAM_INNOCENT, aliveOnly) end --- @@ -159,7 +172,7 @@ end -- @realm server -- @see GetTeamFilter function GetTraitorFilter(aliveOnly) - return GetTeamFilter(TEAM_TRAITOR, aliveOnly) + return GetTeamFilter(TEAM_TRAITOR, aliveOnly) end --- @@ -172,9 +185,9 @@ end -- @realm server -- @see Player:IsRole function GetRoleFilter(subrole, aliveOnly) - return GetPlayerFilter(function(p) - return p:IsRole(subrole) and (not aliveOnly or p:IsTerror()) - end) + return GetPlayerFilter(function(p) + return p:IsRole(subrole) and (not aliveOnly or p:IsTerror()) + end) end --- @@ -184,9 +197,9 @@ end -- @return table -- @realm server function GetSubRoleFilter(subrole, aliveOnly) - return GetPlayerFilter(function(p) - return p:GetSubRole() == subrole and (not aliveOnly or p:IsTerror()) - end) + return GetPlayerFilter(function(p) + return p:GetSubRole() == subrole and (not aliveOnly or p:IsTerror()) + end) end --- @@ -196,7 +209,7 @@ end -- @realm server -- @see GetRoleFilter function GetDetectiveFilter(aliveOnly) - return GetRoleFilter(ROLE_DETECTIVE, aliveOnly) + return GetRoleFilter(ROLE_DETECTIVE, aliveOnly) end --- @@ -206,15 +219,15 @@ end -- @return table -- @realm server function GetRoleChatFilter(subrole, aliveOnly) - if roles.GetByIndex(subrole).disabledTeamChat then - return {} - end - - return GetPlayerFilter(function(p) - return p:IsRole(subrole) - and not p:GetSubRoleData().disabledTeamChatRecv - and (not aliveOnly or p:IsTerror()) - end) + if roles.GetByIndex(subrole).disabledTeamChat then + return {} + end + + return GetPlayerFilter(function(p) + return p:IsRole(subrole) + and not p:GetSubRoleData().disabledTeamChatRecv + and (not aliveOnly or p:IsTerror()) + end) end --- @@ -224,16 +237,16 @@ end -- @return table -- @realm server function GetTeamChatFilter(team, aliveOnly) - return GetPlayerFilter(function(ply) - local plyRoleData = ply:GetSubRoleData() - - return team ~= TEAM_NONE - and not TEAMS[team].alone - and ply:GetTeam() == team - and not plyRoleData.unknownTeam - and not plyRoleData.disabledTeamChatRecv - and (not aliveOnly or ply:IsTerror()) - end) + return GetPlayerFilter(function(ply) + local plyRoleData = ply:GetSubRoleData() + + return team ~= TEAM_NONE + and not TEAMS[team].alone + and ply:GetTeam() == team + and not plyRoleData.unknownTeam + and not plyRoleData.disabledTeamChatRecv + and (not aliveOnly or ply:IsTerror()) + end) end --- @@ -244,15 +257,16 @@ end -- @return table -- @realm server function GetTeamMemberFilter(ply, aliveOnly) - return GetPlayerFilter(function(p) - return p:IsInTeam(ply) and (not aliveOnly or p:IsTerror()) - end) + return GetPlayerFilter(function(p) + return p:IsInTeam(ply) and (not aliveOnly or p:IsTerror()) + end) end -- Communication control --- -- @realm server +-- stylua: ignore local cv_ttt_spectators_chat_globally = CreateConVar("ttt_spectators_chat_globally", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) --- @@ -266,68 +280,73 @@ local cv_ttt_spectators_chat_globally = CreateConVar("ttt_spectators_chat_global -- @realm server -- @internal function GM:PlayerCanSeePlayersChat(text, teamOnly, listener, sender) - if not IsValid(listener) then - return false - end - - if not IsValid(sender) then - if IsEntity(sender) then - return true - end - - return false - end - - local senderIsSpectator = sender:Team() == TEAM_SPEC - local listenerIsSpectator = listener:Team() == TEAM_SPEC - local senderRoleData = sender:GetSubRoleData() - - if GetRoundState() ~= ROUND_ACTIVE -- Round isn't active - or cv_ttt_spectators_chat_globally:GetBool() -- Spectators can chat freely - or not DetectiveMode() -- Mumbling - or not senderIsSpectator and not teamOnly -- General Chat - or not senderIsSpectator and teamOnly and ( -- Team Chat - sender:IsInTeam(listener) - and not senderRoleData.unknownTeam - and not senderRoleData.disabledTeamChat - and not listener:GetSubRoleData().disabledTeamChatRecv - --- - -- @realm server - and hook.Run("TTT2CanSeeChat", listener, sender, teamOnly) ~= true - ) or senderIsSpectator and listenerIsSpectator -- If the sender and listener are spectators - then - return true - end - - return false + if not IsValid(listener) then + return false + end + + if not IsValid(sender) then + if IsEntity(sender) then + return true + end + + return false + end + + local senderIsSpectator = sender:Team() == TEAM_SPEC + local listenerIsSpectator = listener:Team() == TEAM_SPEC + local senderRoleData = sender:GetSubRoleData() + + if + GetRoundState() ~= ROUND_ACTIVE -- Round isn't active + or cv_ttt_spectators_chat_globally:GetBool() -- Spectators can chat freely + or not DetectiveMode() -- Mumbling + or not senderIsSpectator and not teamOnly -- General Chat + or not senderIsSpectator + and teamOnly + and ( -- Team Chat + sender:IsInTeam(listener) + and not senderRoleData.unknownTeam + and not senderRoleData.disabledTeamChat + and not listener:GetSubRoleData().disabledTeamChatRecv + --- + -- @realm server + -- stylua: ignore + and hook.Run("TTT2CanSeeChat", listener, sender, teamOnly) ~= true + ) + or senderIsSpectator and listenerIsSpectator -- If the sender and listener are spectators + then + return true + end + + return false end local mumbles = { - "mumble", - "mm", - "hmm", - "hum", - "mum", - "mbm", - "mble", - "ham", - "mammaries", - "political situation", - "mrmm", - "hrm", - "uzbekistan", - "mumu", - "cheese export", - "hmhm", - "mmh", - "mumble", - "mphrrt", - "mrh", - "hmm", - "mumble", - "mbmm", - "hmml", - "mfrrm" + "mumble", + "mm", + "hmm", + "hum", + "mum", + "mbm", + "mble", + "ham", + "mammaries", + "political situation", + "mrmm", + "hrm", + "uzbekistan", + "mumu", + "cheese export", + "hmhm", + "mmh", + "mumble", + "mphrrt", + "mrh", + "hmm", + "mumble", + "mbmm", + "hmml", + "mfrrm", } --- @@ -342,170 +361,181 @@ local mumbles = { -- @realm server -- @internal function GM:PlayerSay(ply, text, teamOnly) - if not IsValid(ply) then - return text or "" - end - - if GetRoundState() == ROUND_ACTIVE then - local team_spec = ply:Team() == TEAM_SPEC - - if team_spec and not DetectiveMode() then - local filtered = {} - local parts = string.Explode(" ", text) - - for i = 1, #parts do - -- grab word characters and whitelisted interpunction - -- necessary or leetspeek will be used (by trolls especially) - local word, interp = string.match(parts[i], "(%a*)([%.,!%?]*)") - - if word ~= "" then - filtered[#filtered + 1] = mumbles[math.random(#mumbles)] .. interp - end - end - - -- make sure we have something to say - if #filtered < 1 then - filtered[#filtered + 1] = mumbles[math.random(#mumbles)] - end - - table.insert(filtered, 1, "[MUMBLED]") - - return table.concat(filtered, " ") - elseif teamOnly and not team_spec then -- Team Chat handling - RoleChatMsg(ply, text) - - return "" - elseif not team_spec then -- General Chat handling - --- - -- @realm server - if ply:GetSubRoleData().disabledGeneralChat or hook.Run("TTT2AvoidGeneralChat", ply, text) == false then - return "" - end - end - end - - return text or "" + if not IsValid(ply) then + return text or "" + end + + if GetRoundState() == ROUND_ACTIVE then + local team_spec = ply:Team() == TEAM_SPEC + + if team_spec and not DetectiveMode() then + local filtered = {} + local parts = string.Explode(" ", text) + + for i = 1, #parts do + -- grab word characters and whitelisted interpunction + -- necessary or leetspeek will be used (by trolls especially) + local word, interp = string.match(parts[i], "(%a*)([%.,!%?]*)") + + if word ~= "" then + filtered[#filtered + 1] = mumbles[math.random(#mumbles)] .. interp + end + end + + -- make sure we have something to say + if #filtered < 1 then + filtered[#filtered + 1] = mumbles[math.random(#mumbles)] + end + + table.insert(filtered, 1, "[MUMBLED]") + + return table.concat(filtered, " ") + elseif teamOnly and not team_spec then -- Team Chat handling + RoleChatMsg(ply, text) + + return "" + elseif not team_spec then -- General Chat handling + --- + -- @realm server + -- stylua: ignore + if ply:GetSubRoleData().disabledGeneralChat or hook.Run("TTT2AvoidGeneralChat", ply, text) == false then + return "" + end + end + end + + return text or "" end --- -- @realm server +-- stylua: ignore local ttt_lastwords = CreateConVar("ttt_lastwords_chatprint", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) local LastWordContext = { - [KILL_NORMAL] = "", - [KILL_SUICIDE] = " *kills self*", - [KILL_FALL] = " *SPLUT*", - [KILL_BURN] = " *crackle*" + [KILL_NORMAL] = "", + [KILL_SUICIDE] = " *kills self*", + [KILL_FALL] = " *SPLUT*", + [KILL_BURN] = " *crackle*", } local function LastWordsMsg(ply, words) - -- only append "--" if there's no ending interpunction - local final = string.match(words, "[\\.\\!\\?]$") ~= nil + -- only append "--" if there's no ending interpunction + local final = string.match(words, "[\\.\\!\\?]$") ~= nil - -- add optional context relating to death type - local context = LastWordContext[ply.death_type] or "" + -- add optional context relating to death type + local context = LastWordContext[ply.death_type] or "" - net.Start("TTT_LastWordsMsg") - net.WriteEntity(ply) - net.WriteString(words .. (final and "" or "--") .. context) - net.Broadcast() + net.Start("TTT_LastWordsMsg") + net.WriteEntity(ply) + net.WriteString(words .. (final and "" or "--") .. context) + net.Broadcast() end local function deathrec(ply, cmd, args) - if not IsValid(ply) or ply:Alive() or #args <= 1 then return end + if not IsValid(ply) or ply:Alive() or #args <= 1 then + return + end - local id = tonumber(args[1]) + local id = tonumber(args[1]) - if not id or not ply.last_words_id or id ~= ply.last_words_id then - ply.last_words_id = nil + if not id or not ply.last_words_id or id ~= ply.last_words_id then + ply.last_words_id = nil - return - end + return + end - -- never allow multiple last word stuff - ply.last_words_id = nil + -- never allow multiple last word stuff + ply.last_words_id = nil - -- we will be storing this on the ragdoll - local rag = ply.server_ragdoll + -- we will be storing this on the ragdoll + local rag = ply.server_ragdoll - if not (IsValid(rag) and rag.player_ragdoll) then - rag = nil - end + if not (IsValid(rag) and rag.player_ragdoll) then + rag = nil + end - -- last id'd person - local last_seen = tonumber(args[2]) + -- last id'd person + local last_seen = tonumber(args[2]) - if last_seen then - local ent = Entity(last_seen) + if last_seen then + local ent = Entity(last_seen) - if IsValid(ent) and ent:IsPlayer() and rag and not rag.lastid then - rag.lastid = {ent = ent, t = CurTime()} - end - end + if IsValid(ent) and ent:IsPlayer() and rag and not rag.lastid then + rag.lastid = { ent = ent, t = CurTime() } + end + end - -- last words - local words = string.Trim(args[3]) + -- last words + local words = string.Trim(args[3]) - -- nothing of interest - if string.len(words) < 2 then return end + -- nothing of interest + if string.len(words) < 2 then + return + end - -- ignore admin commands - local firstchar = string.sub(words, 1, 1) + -- ignore admin commands + local firstchar = string.sub(words, 1, 1) - if firstchar == "!" or firstchar == "@" or firstchar == "/" then return end + if firstchar == "!" or firstchar == "@" or firstchar == "/" then + return + end - if ttt_lastwords:GetBool() or ply.death_type == KILL_FALL then - LastWordsMsg(ply, words) - end + if ttt_lastwords:GetBool() or ply.death_type == KILL_FALL then + LastWordsMsg(ply, words) + end - if rag and not rag.last_words then - rag.last_words = words - end + if rag and not rag.last_words then + rag.last_words = words + end end concommand.Add("_deathrec", deathrec) local function ttt_radio_send(ply, cmd, args) - if not IsValid(ply) or not ply:IsTerror() or #args ~= 2 then return end - - local msgName = args[1] - local msgTarget = args[2] - - local name = "" - local ragPlayerNick = nil - - if tonumber(msgTarget) then - -- player or corpse ent idx - local ent = Entity(tonumber(msgTarget)) - - if IsValid(ent) then - if ent:IsPlayer() then - name = ent:Nick() - elseif ent:GetClass() == "prop_ragdoll" then - name = LANG.NameParam("quick_corpse_id") - ragPlayerNick = CORPSE.GetPlayerNick(ent, "A Terrorist") - end - end - - msgTarget = ent - else - -- lang string - name = LANG.NameParam(msgTarget) - end - - --- - -- @realm server - if hook.Run("TTTPlayerRadioCommand", ply, msgName, msgTarget) then return end - - net.Start("TTT_RadioMsg") - net.WriteEntity(ply) - net.WriteString(msgName) - net.WriteString(name) - - if ragPlayerNick then - net.WriteString(ragPlayerNick) - end - - net.Broadcast() + if not IsValid(ply) or not ply:IsTerror() or #args ~= 2 then + return + end + + local msgName = args[1] + local msgTarget = args[2] + + local name = "" + local ragPlayerNick = nil + + if tonumber(msgTarget) then + -- player or corpse ent idx + local ent = Entity(tonumber(msgTarget)) + + if IsValid(ent) then + if ent:IsPlayer() then + name = ent:Nick() + elseif ent:GetClass() == "prop_ragdoll" then + name = LANG.NameParam("quick_corpse_id") + ragPlayerNick = CORPSE.GetPlayerNick(ent, "A Terrorist") + end + end + + msgTarget = ent + else + -- lang string + name = LANG.NameParam(msgTarget) + end + + --- + -- @realm server + -- stylua: ignore + if hook.Run("TTTPlayerRadioCommand", ply, msgName, msgTarget) then return end + + net.Start("TTT_RadioMsg") + net.WriteEntity(ply) + net.WriteString(msgName) + net.WriteString(name) + + if ragPlayerNick then + net.WriteString(ragPlayerNick) + end + + net.Broadcast() end concommand.Add("_ttt_radio_send", ttt_radio_send) @@ -519,9 +549,7 @@ concommand.Add("_ttt_radio_send", ttt_radio_send) -- @return[default=nil] boolean Return true to not send this message -- @hook -- @realm server -function GM:TTTPlayerRadioCommand(ply, msgName, msgTarget) - -end +function GM:TTTPlayerRadioCommand(ply, msgName, msgTarget) end --- -- Whether or not the @{Player} can receive the chat message. @@ -532,7 +560,7 @@ end -- @hook -- @realm server function GM:TTT2CanSeeChat(reader, sender, isTeam) - return true + return true end --- @@ -543,9 +571,7 @@ end -- @return nil|boolean Return false to block message -- @hook -- @realm server -function GM:TTT2AvoidTeamChat(sender, team, msg) - -end +function GM:TTT2AvoidTeamChat(sender, team, msg) end --- -- Cancelable hook to block a general chat message. @@ -554,6 +580,4 @@ end -- @return nil|boolean Return false to block message -- @hook -- @realm server -function GM:TTT2AvoidGeneralChat(sender, msg) - -end +function GM:TTT2AvoidGeneralChat(sender, msg) end diff --git a/gamemodes/terrortown/gamemode/server/sv_hud_manager.lua b/gamemodes/terrortown/gamemode/server/sv_hud_manager.lua index 9c033469d..71e6864b0 100644 --- a/gamemodes/terrortown/gamemode/server/sv_hud_manager.lua +++ b/gamemodes/terrortown/gamemode/server/sv_hud_manager.lua @@ -17,48 +17,52 @@ HUDManager = {} -- local function DB_EnsureTableExists(tablename, tablecolumns) - if not tablename or not tablecolumns then - return false - end - - if not sql.TableExists(tablename) then - local result = sql.Query("CREATE TABLE " .. sql.SQLStr(tablename, false) .. " (" .. tablecolumns .. ")") - if result == false then - return false - end - end - - return true + if not tablename or not tablecolumns then + return false + end + + if not sql.TableExists(tablename) then + local result = sql.Query( + "CREATE TABLE " .. sql.SQLStr(tablename, false) .. " (" .. tablecolumns .. ")" + ) + if result == false then + return false + end + end + + return true end local function DB_GetStringValue(key) - if DB_EnsureTableExists(HUD_MANAGER_SQL_TABLE, "key TEXT PRIMARY KEY, value TEXT") then - local result = sql.Query("SELECT * FROM " .. HUD_MANAGER_SQL_TABLE .. " WHERE key = " .. sql.SQLStr(key, false)) - - if istable(result) and #result > 0 and result[1].value ~= "nil" then - return result[1].value - else - return nil - end - end + if DB_EnsureTableExists(HUD_MANAGER_SQL_TABLE, "key TEXT PRIMARY KEY, value TEXT") then + local result = sql.Query( + "SELECT * FROM " .. HUD_MANAGER_SQL_TABLE .. " WHERE key = " .. sql.SQLStr(key, false) + ) + + if istable(result) and #result > 0 and result[1].value ~= "nil" then + return result[1].value + else + return nil + end + end end local function DB_GetStringTable(db_table) - if DB_EnsureTableExists(db_table, "name TEXT PRIMARY KEY") then - local res = sql.Query("SELECT * FROM " .. sql.SQLStr(db_table, false)) + if DB_EnsureTableExists(db_table, "name TEXT PRIMARY KEY") then + local res = sql.Query("SELECT * FROM " .. sql.SQLStr(db_table, false)) - if istable(res) then - local tab = {} + if istable(res) then + local tab = {} - for i = 1, #res do - tab[i] = res[i].name - end + for i = 1, #res do + tab[i] = res[i].name + end - return tab - end - end + return tab + end + end - return nil + return nil end -- @@ -70,23 +74,41 @@ end -- @realm server -- @internal function HUDManager.StoreData() - MsgN("[TTT2][HUDManager] Storing data in database...") - - if DB_EnsureTableExists(HUD_MANAGER_SQL_TABLE, "key TEXT PRIMARY KEY, value TEXT") then - sql.Query("INSERT OR REPLACE INTO " .. HUD_MANAGER_SQL_TABLE .. " VALUES('forcedHUD', " .. sql.SQLStr(ttt2net.GetGlobal({"hud_manager", "forcedHUD"})) .. ")") - sql.Query("INSERT OR REPLACE INTO " .. HUD_MANAGER_SQL_TABLE .. " VALUES('defaultHUD', " .. sql.SQLStr(ttt2net.GetGlobal({"hud_manager", "defaultHUD"})) .. ")") - end - - -- delete the table to recreate it again, to remove all values that might have been removed from the table - sql.DropTable(HUD_MANAGER_SQL_RESTRICTEDHUDS_TABLE) - - if DB_EnsureTableExists(HUD_MANAGER_SQL_RESTRICTEDHUDS_TABLE, "name TEXT PRIMARY KEY") then - local restrictedHuds = ttt2net.GetGlobal({"hud_manager", "restrictedHUDs"}) - - for i = 1, #restrictedHuds do - sql.Query("INSERT INTO " .. HUD_MANAGER_SQL_RESTRICTEDHUDS_TABLE .. " VALUES(" .. sql.SQLStr(restrictedHuds[i]) .. ")") - end - end + Dev(1, "[TTT2][HUDManager] Storing data in database...") + + if DB_EnsureTableExists(HUD_MANAGER_SQL_TABLE, "key TEXT PRIMARY KEY, value TEXT") then + sql.Query( + "INSERT OR REPLACE INTO " + .. HUD_MANAGER_SQL_TABLE + .. " VALUES('forcedHUD', " + .. sql.SQLStr(ttt2net.GetGlobal({ "hud_manager", "forcedHUD" })) + .. ")" + ) + sql.Query( + "INSERT OR REPLACE INTO " + .. HUD_MANAGER_SQL_TABLE + .. " VALUES('defaultHUD', " + .. sql.SQLStr(ttt2net.GetGlobal({ "hud_manager", "defaultHUD" })) + .. ")" + ) + end + + -- delete the table to recreate it again, to remove all values that might have been removed from the table + sql.DropTable(HUD_MANAGER_SQL_RESTRICTEDHUDS_TABLE) + + if DB_EnsureTableExists(HUD_MANAGER_SQL_RESTRICTEDHUDS_TABLE, "name TEXT PRIMARY KEY") then + local restrictedHuds = ttt2net.GetGlobal({ "hud_manager", "restrictedHUDs" }) + + for i = 1, #restrictedHuds do + sql.Query( + "INSERT INTO " + .. HUD_MANAGER_SQL_RESTRICTEDHUDS_TABLE + .. " VALUES(" + .. sql.SQLStr(restrictedHuds[i]) + .. ")" + ) + end + end end --- @@ -94,11 +116,23 @@ end -- @realm server -- @internal function HUDManager.LoadData() - MsgN("[TTT2][HUDManager] Loading data from database...") - - ttt2net.SetGlobal({"hud_manager", "forcedHUD"}, {type = "string"}, DB_GetStringValue("forcedHUD")) - ttt2net.SetGlobal({"hud_manager", "defaultHUD"}, {type = "string"}, DB_GetStringValue("defaultHUD") or "pure_skin") - ttt2net.SetGlobal({"hud_manager", "restrictedHUDs"}, {type = "table"}, DB_GetStringTable(HUD_MANAGER_SQL_RESTRICTEDHUDS_TABLE) or {}) + Dev(1, "[TTT2][HUDManager] Loading data from database...") + + ttt2net.SetGlobal( + { "hud_manager", "forcedHUD" }, + { type = "string" }, + DB_GetStringValue("forcedHUD") + ) + ttt2net.SetGlobal( + { "hud_manager", "defaultHUD" }, + { type = "string" }, + DB_GetStringValue("defaultHUD") or "pure_skin" + ) + ttt2net.SetGlobal( + { "hud_manager", "restrictedHUDs" }, + { type = "table" }, + DB_GetStringTable(HUD_MANAGER_SQL_RESTRICTEDHUDS_TABLE) or {} + ) end -- load values from the database when this file is executed @@ -110,135 +144,139 @@ HUDManager.LoadData() -- User wants to change / use a HUD net.Receive("TTT2RequestHUD", function(_, ply) - local hudname = net.ReadString() -- new requested HUD - local oldHUD = net.ReadString() -- current HUD as fallback - local forced = ttt2net.GetGlobal({"hud_manager", "forcedHUD"}) - - if not forced then - local restrictions = ttt2net.GetGlobal({"hud_manager", "restrictedHUDs"}) or {} - local restricted = false - - for i = 1, #restrictions do - if restrictions[i] == hudname then - restricted = true - - break - end - end - - -- is the HUD restricted? Then check the second HUD - if restricted then - restricted = false - hudname = oldHUD - - for i = 1, #restrictions do - if restrictions[i] == hudname then - restricted = true - - break - end - end - end - - -- still restricted? Then take the default - if restricted then - hudname = ttt2net.GetGlobal({"hud_manager", "defaultHUD"}) - end - end - - local hudToSend = forced or hudname - local hudToSendTbl = huds.GetStored(hudToSend) - - if not hudToSendTbl or hudToSendTbl.isAbstract then - hudToSend = ttt2net.GetGlobal({"hud_manager", "defaultHUD"}) or "pure_skin" - end - - net.Start("TTT2ReceiveHUD") - net.WriteString(hudToSend) - net.Send(ply) + local hudname = net.ReadString() -- new requested HUD + local oldHUD = net.ReadString() -- current HUD as fallback + local forced = ttt2net.GetGlobal({ "hud_manager", "forcedHUD" }) + + if not forced then + local restrictions = ttt2net.GetGlobal({ "hud_manager", "restrictedHUDs" }) or {} + local restricted = false + + for i = 1, #restrictions do + if restrictions[i] == hudname then + restricted = true + + break + end + end + + -- is the HUD restricted? Then check the second HUD + if restricted then + restricted = false + hudname = oldHUD + + for i = 1, #restrictions do + if restrictions[i] == hudname then + restricted = true + + break + end + end + end + + -- still restricted? Then take the default + if restricted then + hudname = ttt2net.GetGlobal({ "hud_manager", "defaultHUD" }) + end + end + + local hudToSend = forced or hudname + local hudToSendTbl = huds.GetStored(hudToSend) + + if not hudToSendTbl or hudToSendTbl.isAbstract then + hudToSend = ttt2net.GetGlobal({ "hud_manager", "defaultHUD" }) or "pure_skin" + end + + net.Start("TTT2ReceiveHUD") + net.WriteString(hudToSend) + net.Send(ply) end) -- An admin wants to set the default HUD value net.Receive("TTT2DefaultHUDRequest", function(_, ply) - local HUDToSet = net.ReadString() - local acceptedRequest = false - - if ply:IsAdmin() then - if HUDToSet == "" then -- Reset the forcedHUD value, to allow users to have a different HUD - ttt2net.SetGlobal({"hud_manager", "defaultHUD"}, {type = "string"}, "pure_skin") - - acceptedRequest = true - else - local hudtbl = huds.GetStored(HUDToSet) - if hudtbl ~= nil then - ttt2net.SetGlobal({"hud_manager", "defaultHUD"}, {type = "string"}, HUDToSet) - - acceptedRequest = true - end - end - - HUDManager.StoreData() - HUDManager.LoadData() - end - - if not acceptedRequest then - LANG.Msg(ply, "hud_default_failed", {hudname = HUDToSet}, MSG_CHAT_PLAIN) - end + local HUDToSet = net.ReadString() + local acceptedRequest = false + + if ply:IsAdmin() then + if HUDToSet == "" then -- Reset the forcedHUD value, to allow users to have a different HUD + ttt2net.SetGlobal({ "hud_manager", "defaultHUD" }, { type = "string" }, "pure_skin") + + acceptedRequest = true + else + local hudtbl = huds.GetStored(HUDToSet) + if hudtbl ~= nil then + ttt2net.SetGlobal({ "hud_manager", "defaultHUD" }, { type = "string" }, HUDToSet) + + acceptedRequest = true + end + end + + HUDManager.StoreData() + HUDManager.LoadData() + end + + if not acceptedRequest then + LANG.Msg(ply, "hud_default_failed", { hudname = HUDToSet }, MSG_CHAT_PLAIN) + end end) -- An admin wants to set the forceHUD value net.Receive("TTT2ForceHUDRequest", function(_, ply) - local HUDToForce = net.ReadString() - local acceptedRequest = false + local HUDToForce = net.ReadString() + local acceptedRequest = false - if ply:IsAdmin() then - if HUDToForce == "" then -- Reset the forcedHUD value, to allow users to have a different HUD - ttt2net.SetGlobal({"hud_manager", "forcedHUD"}, {type = "string"}, nil) + if ply:IsAdmin() then + if HUDToForce == "" then -- Reset the forcedHUD value, to allow users to have a different HUD + ttt2net.SetGlobal({ "hud_manager", "forcedHUD" }, { type = "string" }, nil) - acceptedRequest = true - else - local hudtbl = huds.GetStored(HUDToForce) - if hudtbl ~= nil then - ttt2net.SetGlobal({"hud_manager", "forcedHUD"}, {type = "string"}, HUDToForce) + acceptedRequest = true + else + local hudtbl = huds.GetStored(HUDToForce) + if hudtbl ~= nil then + ttt2net.SetGlobal({ "hud_manager", "forcedHUD" }, { type = "string" }, HUDToForce) - acceptedRequest = true - end - end + acceptedRequest = true + end + end - HUDManager.StoreData() - end + HUDManager.StoreData() + end - if not acceptedRequest then - LANG.Msg(ply, "hud_forced_failed", {hudname = HUDToForce}, MSG_CHAT_PLAIN) - end + if not acceptedRequest then + LANG.Msg(ply, "hud_forced_failed", { hudname = HUDToForce }, MSG_CHAT_PLAIN) + end end) -- An admin wants to change the restricted status for an HUD net.Receive("TTT2RestrictHUDRequest", function(_, ply) - local HUDToRestrict = net.ReadString() - local shouldBeRestricted = net.ReadBool() - local acceptedRequest = false - - if ply:IsAdmin() then - local hudtbl = huds.GetStored(HUDToRestrict) - if hudtbl ~= nil then - local restrictedHUDs = ttt2net.GetGlobal({"hud_manager", "restrictedHUDs"}) or {} - - if shouldBeRestricted and not table.HasValue(restrictedHUDs, HUDToRestrict) then - restrictedHUDs[#restrictedHUDs + 1] = HUDToRestrict - elseif not shouldBeRestricted then - table.RemoveByValue(restrictedHUDs, HUDToRestrict) - end - - ttt2net.SetGlobal({"hud_manager", "restrictedHUDs"}, {type = "table"}, table.Copy(restrictedHUDs)) - - HUDManager.StoreData() - - acceptedRequest = true - end - end - - if not acceptedRequest then - LANG.Msg(ply, "hud_restricted_failed", {hudname = HUDToRestrict}, MSG_CHAT_PLAIN) - end + local HUDToRestrict = net.ReadString() + local shouldBeRestricted = net.ReadBool() + local acceptedRequest = false + + if ply:IsAdmin() then + local hudtbl = huds.GetStored(HUDToRestrict) + if hudtbl ~= nil then + local restrictedHUDs = ttt2net.GetGlobal({ "hud_manager", "restrictedHUDs" }) or {} + + if shouldBeRestricted and not table.HasValue(restrictedHUDs, HUDToRestrict) then + restrictedHUDs[#restrictedHUDs + 1] = HUDToRestrict + elseif not shouldBeRestricted then + table.RemoveByValue(restrictedHUDs, HUDToRestrict) + end + + ttt2net.SetGlobal( + { "hud_manager", "restrictedHUDs" }, + { type = "table" }, + table.Copy(restrictedHUDs) + ) + + HUDManager.StoreData() + + acceptedRequest = true + end + end + + if not acceptedRequest then + LANG.Msg(ply, "hud_restricted_failed", { hudname = HUDToRestrict }, MSG_CHAT_PLAIN) + end end) diff --git a/gamemodes/terrortown/gamemode/server/sv_inventory.lua b/gamemodes/terrortown/gamemode/server/sv_inventory.lua index 47459f603..fc0de8bde 100644 --- a/gamemodes/terrortown/gamemode/server/sv_inventory.lua +++ b/gamemodes/terrortown/gamemode/server/sv_inventory.lua @@ -8,10 +8,10 @@ ttt_include("sh_inventory") -- @param Player ply -- @realm server function CleanupInventoryAndNotifyClient(ply) - CleanupInventory(ply) + CleanupInventory(ply) - net.Start("TTT2CleanupInventory") - net.Send(ply) + net.Start("TTT2CleanupInventory") + net.Send(ply) end --- @@ -20,10 +20,10 @@ end -- @param Weapon wep -- @realm server function AddWeaponToInventoryAndNotifyClient(ply, wep) - AddWeaponToInventory(ply, wep) + AddWeaponToInventory(ply, wep) - net.Start("TTT2AddWeaponToInventory") - net.Send(ply) + net.Start("TTT2AddWeaponToInventory") + net.Send(ply) end -- Removes a @{Weapon} from the Inventory of a @{Player} and sends a message @@ -31,8 +31,8 @@ end -- @param Weapon wep -- @realm server function RemoveWeaponFromInventoryAndNotifyClient(ply, wep) - RemoveWeaponFromInventory(ply, wep) + RemoveWeaponFromInventory(ply, wep) - net.Start("TTT2RemoveWeaponFromInventory") - net.Send(ply) + net.Start("TTT2RemoveWeaponFromInventory") + net.Send(ply) end diff --git a/gamemodes/terrortown/gamemode/server/sv_karma.lua b/gamemodes/terrortown/gamemode/server/sv_karma.lua index 6d640dd5d..9b28e4300 100644 --- a/gamemodes/terrortown/gamemode/server/sv_karma.lua +++ b/gamemodes/terrortown/gamemode/server/sv_karma.lua @@ -3,80 +3,98 @@ -- @module KARMA KARMA = { - rememberedPlayers = {}, -- ply steamid -> karma table for disconnected players who might reconnect - karmaChanges = {}, -- ply steamid -> karma table for karma changes that will get applied at roundend - karmaChangesOld = {} -- ply steamid -> karma table for old karma changes after roundend + rememberedPlayers = {}, -- ply steamid -> karma table for disconnected players who might reconnect + karmaChanges = {}, -- ply steamid -> karma table for karma changes that will get applied at roundend + karmaChangesOld = {}, -- ply steamid -> karma table for old karma changes after roundend } -- Convars, more convenient access than GetConVar KARMA.cv = { - --- - -- @realm server - enabled = CreateConVar("ttt_karma", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - strict = CreateConVar("ttt_karma_strict", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - starting = CreateConVar("ttt_karma_starting", "1000", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - max = CreateConVar("ttt_karma_max", "1000", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - ratio = CreateConVar("ttt_karma_ratio", "0.001", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - killpenalty = CreateConVar("ttt_karma_kill_penalty", "15", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - roundheal = CreateConVar("ttt_karma_round_increment", "5", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - clean = CreateConVar("ttt_karma_clean_bonus", "30", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - tbonus = CreateConVar("ttt_karma_traitorkill_bonus", "40", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - tratio = CreateConVar("ttt_karma_traitordmg_ratio", "0.0003", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - debug = CreateConVar("ttt_karma_debugspam", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - persist = CreateConVar("ttt_karma_persist", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - falloff = CreateConVar("ttt_karma_clean_half", "0.25", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - autokick = CreateConVar("ttt_karma_low_autokick", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - kicklevel = CreateConVar("ttt_karma_low_amount", "450", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - autoban = CreateConVar("ttt_karma_low_ban", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), - - --- - -- @realm server - bantime = CreateConVar("ttt_karma_low_ban_minutes", "60", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + --- + -- @realm server + -- stylua: ignore + enabled = CreateConVar("ttt_karma", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + strict = CreateConVar("ttt_karma_strict", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + starting = CreateConVar("ttt_karma_starting", "1000", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + max = CreateConVar("ttt_karma_max", "1000", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + ratio = CreateConVar("ttt_karma_ratio", "0.001", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + killpenalty = CreateConVar("ttt_karma_kill_penalty", "15", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + roundheal = CreateConVar("ttt_karma_round_increment", "5", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + clean = CreateConVar("ttt_karma_clean_bonus", "30", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + tbonus = CreateConVar("ttt_karma_traitorkill_bonus", "40", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + tratio = CreateConVar("ttt_karma_traitordmg_ratio", "0.0003", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + debug = CreateConVar("ttt_karma_debugspam", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + persist = CreateConVar("ttt_karma_persist", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + falloff = CreateConVar("ttt_karma_clean_half", "0.25", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + autokick = CreateConVar("ttt_karma_low_autokick", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + kicklevel = CreateConVar("ttt_karma_low_amount", "450", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + autoban = CreateConVar("ttt_karma_low_ban", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}), + + --- + -- @realm server + -- stylua: ignore + bantime = CreateConVar("ttt_karma_low_ban_minutes", "60", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) +, } local config = KARMA.cv @@ -90,31 +108,31 @@ local KARMA_ROUND = 6 local KARMA_UNKNOWN = 7 KARMA.reason = { - [KARMA_TEAMKILL] = "karma_teamkill_tooltip", - [KARMA_TEAMHURT] = "karma_teamhurt_tooltip", - [KARMA_ENEMYKILL] = "karma_enemykill_tooltip", - [KARMA_ENEMYHURT] = "karma_enemyhurt_tooltip", - [KARMA_CLEAN] = "karma_cleanround_tooltip", - [KARMA_ROUND] = "karma_roundheal_tooltip", - [KARMA_UNKNOWN] = "karma_unknown_tooltip" + [KARMA_TEAMKILL] = "karma_teamkill_tooltip", + [KARMA_TEAMHURT] = "karma_teamhurt_tooltip", + [KARMA_ENEMYKILL] = "karma_enemykill_tooltip", + [KARMA_ENEMYHURT] = "karma_enemyhurt_tooltip", + [KARMA_CLEAN] = "karma_cleanround_tooltip", + [KARMA_ROUND] = "karma_roundheal_tooltip", + [KARMA_UNKNOWN] = "karma_unknown_tooltip", } local IsValid = IsValid local hook = hook local function IsDebug() - return config.debug:GetBool() + return config.debug:GetBool() end local math = math local function ttt_karma_max(cvar, old, new) - SetGlobalInt("ttt_karma_max", new) + SetGlobalInt("ttt_karma_max", new) end cvars.AddChangeCallback("ttt_karma_max", ttt_karma_max) local function ttt_karma(cvar, old, new) - SetGlobalBool("ttt_karma", tobool(new)) + SetGlobalBool("ttt_karma", tobool(new)) end cvars.AddChangeCallback("ttt_karma", ttt_karma) @@ -123,8 +141,8 @@ cvars.AddChangeCallback("ttt_karma", ttt_karma) -- @realm server -- @internal function KARMA.InitState() - SetGlobalBool("ttt_karma", config.enabled:GetBool()) - SetGlobalInt("ttt_karma_max", config.max:GetFloat()) + SetGlobalBool("ttt_karma", config.enabled:GetBool()) + SetGlobalInt("ttt_karma_max", config.max:GetFloat()) end --- @@ -132,7 +150,7 @@ end -- @return boolean -- @realm server function KARMA.IsEnabled() - return GetGlobalBool("ttt_karma", false) + return GetGlobalBool("ttt_karma", false) end --- @@ -144,9 +162,9 @@ end -- @param string reason -- @realm server function KARMA.DoKarmaChange(ply, amount, reason) - ply:SetLiveKarma(math.Clamp(ply:GetLiveKarma() + amount, 0, config.max:GetFloat())) + ply:SetLiveKarma(math.Clamp(ply:GetLiveKarma() + amount, 0, config.max:GetFloat())) - KARMA.SaveKarmaChange(ply, amount, isstring(reason) and reason or KARMA.reason[KARMA_UNKNOWN]) + KARMA.SaveKarmaChange(ply, amount, isstring(reason) and reason or KARMA.reason[KARMA_UNKNOWN]) end --- @@ -157,14 +175,16 @@ end -- @realm server -- @internal function KARMA.SaveKarmaChange(ply, amount, reason) - amount = math.Round(amount) + amount = math.Round(amount) - if amount == 0 then return end + if amount == 0 then + return + end - local sid64 = ply:SteamID64() + local sid64 = ply:SteamID64() - KARMA.karmaChanges[sid64] = KARMA.karmaChanges[sid64] or {} - KARMA.karmaChanges[sid64][reason] = (KARMA.karmaChanges[sid64][reason] or 0) + amount + KARMA.karmaChanges[sid64] = KARMA.karmaChanges[sid64] or {} + KARMA.karmaChanges[sid64][reason] = (KARMA.karmaChanges[sid64][reason] or 0) + amount end --- @@ -172,22 +192,26 @@ end -- @realm server -- @internal function KARMA.ResetRoundChanges() - KARMA.karmaChangesOld = table.Copy(KARMA.karmaChanges) - KARMA.karmaChanges = {} + KARMA.karmaChangesOld = table.Copy(KARMA.karmaChanges) + KARMA.karmaChanges = {} - if not IsDebug() then return end + if not IsDebug() then + return + end - for sid64, reasonList in pairs(KARMA.karmaChangesOld) do - local ply = player.GetBySteamID64(sid64) + for sid64, reasonList in pairs(KARMA.karmaChangesOld) do + local ply = player.GetBySteamID64(sid64) - if not IsValid(ply) then continue end + if not IsValid(ply) then + continue + end - print("\nFor Player " .. ply:GetName()) + Dev(1, "\nFor Player " .. ply:GetName()) - for reason, karma in pairs(reasonList) do - print("An amount of " .. karma .. " was changed for the reason of " .. reason) - end - end + for reason, karma in pairs(reasonList) do + Dev(1, "An amount of " .. karma .. " was changed for the reason of " .. reason) + end + end end --- @@ -196,7 +220,7 @@ end -- @return table containing karmachange per reason -- @realm server function KARMA.GetKarmaChangesBySteamID64(sid64) - return KARMA.karmaChanges[sid64] + return KARMA.karmaChanges[sid64] end --- @@ -205,7 +229,7 @@ end -- @return table containing karmachange per reason -- @realm server function KARMA.GetOldKarmaChangesBySteamID64(sid64) - return KARMA.karmaChangesOld[sid64] + return KARMA.karmaChangesOld[sid64] end --- @@ -214,16 +238,18 @@ end -- @return number containing absolute karmachange per player -- @realm server function KARMA.GetAbsoluteKarmaChangeBySteamID64(sid64) - local amount = 0 - local reasonList = KARMA.karmaChanges[sid64] + local amount = 0 + local reasonList = KARMA.karmaChanges[sid64] - if not reasonList then return end + if not reasonList then + return + end - for _, karma in pairs(reasonList) do - amount = amount + karma - end + for _, karma in pairs(reasonList) do + amount = amount + karma + end - return amount + return amount end --- @@ -232,16 +258,18 @@ end -- @return number containing absolute karmachange per player -- @realm server function KARMA.GetAbsoluteOldKarmaChangeBySteamID64(sid64) - local amount = 0 - local reasonList = KARMA.karmaChangesOld[sid64] + local amount = 0 + local reasonList = KARMA.karmaChangesOld[sid64] - if not reasonList then return end + if not reasonList then + return + end - for _, karma in pairs(reasonList) do - amount = amount + karma - end + for _, karma in pairs(reasonList) do + amount = amount + karma + end - return amount + return amount end --- @@ -251,7 +279,7 @@ end -- @return number the amount of KARMA penalty -- @realm server function KARMA.GetHurtPenalty(victim_karma, dmg) - return victim_karma * math.Clamp(dmg * config.ratio:GetFloat(), 0, 1) + return victim_karma * math.Clamp(dmg * config.ratio:GetFloat(), 0, 1) end --- @@ -260,8 +288,8 @@ end -- @return number the amount of KARMA penalty -- @realm server function KARMA.GetKillPenalty(victim_karma) - -- the kill penalty handled like dealing a bit of damage - return KARMA.GetHurtPenalty(victim_karma, config.killpenalty:GetFloat()) + -- the kill penalty handled like dealing a bit of damage + return KARMA.GetHurtPenalty(victim_karma, config.killpenalty:GetFloat()) end --- @@ -270,7 +298,7 @@ end -- @return number the amount of KARMA reward -- @realm server function KARMA.GetHurtReward(dmg) - return config.max:GetFloat() * math.Clamp(dmg * config.tratio:GetFloat(), 0, 1) + return config.max:GetFloat() * math.Clamp(dmg * config.tratio:GetFloat(), 0, 1) end --- @@ -278,7 +306,7 @@ end -- @return number the amount of KARMA kill reward -- @realm server function KARMA.GetKillReward() - return KARMA.GetHurtReward(config.tbonus:GetFloat()) + return KARMA.GetHurtReward(config.tbonus:GetFloat()) end --- @@ -289,11 +317,12 @@ end -- @param string reason -- @realm server function KARMA.GivePenalty(ply, penalty, victim, reason) - --- - -- @realm server - if not hook.Run("TTTKarmaGivePenalty", ply, penalty, victim) then - KARMA.DoKarmaChange(ply, -penalty, reason) - end + --- + -- @realm server + -- stylua: ignore + if not hook.Run("TTTKarmaGivePenalty", ply, penalty, victim) then + KARMA.DoKarmaChange(ply, -penalty, reason) + end end --- @@ -304,11 +333,11 @@ end -- @return number reward modified / reward -- @realm server function KARMA.GiveReward(ply, reward, reason) - reward = KARMA.DecayedMultiplier(ply) * reward + reward = KARMA.DecayedMultiplier(ply) * reward - KARMA.DoKarmaChange(ply, reward, reason) + KARMA.DoKarmaChange(ply, reward, reason) - return reward + return reward end --- @@ -316,53 +345,56 @@ end -- @param Player ply -- @realm server function KARMA.ApplyKarma(ply) - if not KARMA.IsEnabled() then return end + if not KARMA.IsEnabled() then + return + end - local df = 1 + local df = 1 - -- any karma at 1000 or over guarantees a df of 1, only when it's lower do we - -- need the penalty curve - if ply:GetBaseKarma() < 1000 then - local k = ply:GetBaseKarma() - 1000 + -- any karma at 1000 or over guarantees a df of 1, only when it's lower do we + -- need the penalty curve + if ply:GetBaseKarma() < 1000 then + local k = ply:GetBaseKarma() - 1000 - if config.strict:GetBool() then - -- this penalty curve sinks more quickly, less parabolic - df = 1 + 0.0007 * k + -0.000002 * (k ^ 2) - else - df = 1 + -0.0000025 * (k ^ 2) - end - end + if config.strict:GetBool() then + -- this penalty curve sinks more quickly, less parabolic + df = 1 + 0.0007 * k + -0.000002 * (k ^ 2) + else + df = 1 + -0.0000025 * (k ^ 2) + end + end - ply:SetDamageFactor(math.Clamp(df, 0.1, 1.0)) + ply:SetDamageFactor(math.Clamp(df, 0.1, 1.0)) - if IsDebug() then - print(Format("%s has karma %f and gets df %f", ply:Nick(), ply:GetBaseKarma(), df)) - end + if IsDebug() then + Dev(1, Format("%s has karma %f and gets df %f", ply:Nick(), ply:GetBaseKarma(), df)) + end end -- Return true if a traitor could have easily avoided the damage/death local function WasAvoidable(attacker, victim, dmginfo) - local infl = dmginfo:GetInflictor() + local infl = dmginfo:GetInflictor() - if attacker:IsInTeam(victim) and IsValid(infl) and infl.Avoidable ~= false then - local victimRoleData = victim:GetSubRoleData() + if attacker:IsInTeam(victim) and IsValid(infl) and infl.Avoidable ~= false then + local victimRoleData = victim:GetSubRoleData() - --- - -- @realm server - local mutiplier = hook.Run("TTT2KarmaPenaltyMultiplier", attacker, victim, dmginfo) + --- + -- @realm server + -- stylua: ignore + local mutiplier = hook.Run("TTT2KarmaPenaltyMultiplier", attacker, victim, dmginfo) - if mutiplier then - return mutiplier - elseif victimRoleData.isPublicRole and victimRoleData.isPolicingRole then - return 2 - elseif not attacker:GetSubRoleData().unknownTeam then - return 1 - else - return 0.5 - end - end + if mutiplier then + return mutiplier + elseif victimRoleData.isPublicRole and victimRoleData.isPolicingRole then + return 2 + elseif not attacker:GetSubRoleData().unknownTeam then + return 1 + else + return 0.5 + end + end - return 0 + return 0 end --- @@ -371,82 +403,135 @@ end -- damage factor of the attacker. -- @param Player attacker -- @param Player victim --- @param DamageInfo dmginfo +-- @param CTakeDamageInfo dmginfo -- @realm server function KARMA.Hurt(attacker, victim, dmginfo) - if attacker == victim - or not IsValid(attacker) - or not IsValid(victim) - or not attacker:IsPlayer() - or not victim:IsPlayer() - or dmginfo:GetDamage() <= 0 - then return end - - -- Ignore excess damage - local hurt_amount = math.min(victim:Health(), dmginfo:GetDamage()) - - local attackerRoleData = attacker:GetSubRoleData() - - -- team hurts another team - if not attacker:IsInTeam(victim) then - if attackerRoleData.unknownTeam then - local reward = KARMA.GetHurtReward(hurt_amount) * attackerRoleData.karma.enemyHurtBonusMultiplier - - reward = KARMA.GiveReward(attacker, reward, KARMA.reason[KARMA_ENEMYHURT]) - - print(Format("%s (%f) hurt %s (%f) and gets REWARDED %f", attacker:Nick(), attacker:GetLiveKarma(), victim:Nick(), victim:GetLiveKarma(), reward)) - end - else -- team hurts own team - if not victim:GetCleanRound() then return end - - local multiplicator = WasAvoidable(attacker, victim, dmginfo) * attackerRoleData.karma.teamHurtPenaltyMultiplier - local penalty = KARMA.GetHurtPenalty(victim:GetLiveKarma(), hurt_amount) * multiplicator - - KARMA.GivePenalty(attacker, penalty, victim, KARMA.reason[KARMA_TEAMHURT]) - - attacker:SetCleanRound(false) - - print(Format("%s (%f) hurt %s (%f) and gets penalised for %f", attacker:Nick(), attacker:GetLiveKarma(), victim:Nick(), victim:GetLiveKarma(), penalty)) - end + if + attacker == victim + or not IsValid(attacker) + or not IsValid(victim) + or not attacker:IsPlayer() + or not victim:IsPlayer() + or dmginfo:GetDamage() <= 0 + then + return + end + + -- Ignore excess damage + local hurt_amount = math.min(victim:Health(), dmginfo:GetDamage()) + + local attackerRoleData = attacker:GetSubRoleData() + + -- team hurts another team + if not attacker:IsInTeam(victim) then + if attackerRoleData.unknownTeam then + local reward = KARMA.GetHurtReward(hurt_amount) + * attackerRoleData.karma.enemyHurtBonusMultiplier + + reward = KARMA.GiveReward(attacker, reward, KARMA.reason[KARMA_ENEMYHURT]) + + Dev( + 1, + Format( + "%s (%f) hurt %s (%f) and gets REWARDED %f", + attacker:Nick(), + attacker:GetLiveKarma(), + victim:Nick(), + victim:GetLiveKarma(), + reward + ) + ) + end + else -- team hurts own team + if not victim:GetCleanRound() then + return + end + + local multiplicator = WasAvoidable(attacker, victim, dmginfo) + * attackerRoleData.karma.teamHurtPenaltyMultiplier + local penalty = KARMA.GetHurtPenalty(victim:GetLiveKarma(), hurt_amount) * multiplicator + + KARMA.GivePenalty(attacker, penalty, victim, KARMA.reason[KARMA_TEAMHURT]) + + attacker:SetCleanRound(false) + + Dev( + 1, + Format( + "%s (%f) hurt %s (%f) and gets penalised for %f", + attacker:Nick(), + attacker:GetLiveKarma(), + victim:Nick(), + victim:GetLiveKarma(), + penalty + ) + ) + end end --- -- Handle karma change due to one player killing another. -- @param Player attacker -- @param Player victim --- @param DamageInfo dmginfo +-- @param CTakeDamageInfo dmginfo -- @realm server function KARMA.Killed(attacker, victim, dmginfo) - if attacker == victim - or not IsValid(attacker) - or not IsValid(victim) - or not victim:IsPlayer() - or not attacker:IsPlayer() - then return end - - local attackerRoleData = attacker:GetSubRoleData() - - -- team kills another team - if not attacker:IsInTeam(victim) then - if attackerRoleData.unknownTeam then - local reward = KARMA.GetKillReward() * attackerRoleData.karma.enemyKillBonusMultiplier - - reward = KARMA.GiveReward(attacker, reward, KARMA.reason[KARMA_ENEMYKILL]) - - print(Format("%s (%f) killed %s (%f) and gets REWARDED %f", attacker:Nick(), attacker:GetLiveKarma(), victim:Nick(), victim:GetLiveKarma(), reward)) - end - else -- team kills own team - if not victim:GetCleanRound() then return end - - local multiplicator = WasAvoidable(attacker, victim, dmginfo) * attackerRoleData.karma.teamKillPenaltyMultiplier - local penalty = KARMA.GetKillPenalty(victim:GetLiveKarma()) * multiplicator - - KARMA.GivePenalty(attacker, penalty, victim, KARMA.reason[KARMA_TEAMKILL]) - - attacker:SetCleanRound(false) - - print(Format("%s (%f) killed %s (%f) and gets penalised for %f", attacker:Nick(), attacker:GetLiveKarma(), victim:Nick(), victim:GetLiveKarma(), penalty)) - end + if + attacker == victim + or not IsValid(attacker) + or not IsValid(victim) + or not victim:IsPlayer() + or not attacker:IsPlayer() + then + return + end + + local attackerRoleData = attacker:GetSubRoleData() + + -- team kills another team + if not attacker:IsInTeam(victim) then + if attackerRoleData.unknownTeam then + local reward = KARMA.GetKillReward() * attackerRoleData.karma.enemyKillBonusMultiplier + + reward = KARMA.GiveReward(attacker, reward, KARMA.reason[KARMA_ENEMYKILL]) + + Dev( + 1, + Format( + "%s (%f) killed %s (%f) and gets REWARDED %f", + attacker:Nick(), + attacker:GetLiveKarma(), + victim:Nick(), + victim:GetLiveKarma(), + reward + ) + ) + end + else -- team kills own team + if not victim:GetCleanRound() then + return + end + + local multiplicator = WasAvoidable(attacker, victim, dmginfo) + * attackerRoleData.karma.teamKillPenaltyMultiplier + local penalty = KARMA.GetKillPenalty(victim:GetLiveKarma()) * multiplicator + + KARMA.GivePenalty(attacker, penalty, victim, KARMA.reason[KARMA_TEAMKILL]) + + attacker:SetCleanRound(false) + + Dev( + 1, + Format( + "%s (%f) killed %s (%f) and gets penalised for %f", + attacker:Nick(), + attacker:GetLiveKarma(), + victim:Nick(), + victim:GetLiveKarma(), + penalty + ) + ) + end end local expdecay = math.ExponentialDecay @@ -457,81 +542,77 @@ local expdecay = math.ExponentialDecay -- @return number -- @realm server function KARMA.DecayedMultiplier(ply) - local max = config.max:GetFloat() - local start = config.starting:GetFloat() - local k = ply:GetLiveKarma() + local max = config.max:GetFloat() + local start = config.starting:GetFloat() + local k = ply:GetLiveKarma() - if config.falloff:GetFloat() <= 0 or k < start then - return 1 - elseif k < max then - -- if falloff is enabled, then if our karma is above the starting value, - -- our round bonus is going to start decreasing as our karma increases - local basediff = max - start - local plydiff = k - start - local half = math.Clamp(config.falloff:GetFloat(), 0.01, 0.99) + if config.falloff:GetFloat() <= 0 or k < start then + return 1 + elseif k < max then + -- if falloff is enabled, then if our karma is above the starting value, + -- our round bonus is going to start decreasing as our karma increases + local basediff = max - start + local plydiff = k - start + local half = math.Clamp(config.falloff:GetFloat(), 0.01, 0.99) - -- exponentially decay the bonus such that when the player's excess karma - -- is at (basediff * half) the bonus is half of the original value - return expdecay(basediff * half, plydiff) - end + -- exponentially decay the bonus such that when the player's excess karma + -- is at (basediff * half) the bonus is half of the original value + return expdecay(basediff * half, plydiff) + end - return 1 + return 1 end --- -- Handle karma regeneration upon the start of a new round -- @realm server function KARMA.RoundIncrement() - local healbonus = config.roundheal:GetFloat() - local cleanbonus = config.clean:GetFloat() + local healbonus = config.roundheal:GetFloat() + local cleanbonus = config.clean:GetFloat() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - if ply:IsDeadTerror() and ply.death_type ~= KILL_SUICIDE or not ply:IsSpec() then - KARMA.GiveReward(ply, healbonus, KARMA.reason[KARMA_ROUND]) + if ply:IsDeadTerror() and ply.death_type ~= KILL_SUICIDE or not ply:IsSpec() then + KARMA.GiveReward(ply, healbonus, KARMA.reason[KARMA_ROUND]) - if ply:GetCleanRound() then - KARMA.GiveReward(ply, cleanbonus, KARMA.reason[KARMA_CLEAN]) - end + if ply:GetCleanRound() then + KARMA.GiveReward(ply, cleanbonus, KARMA.reason[KARMA_CLEAN]) + end + end + end - if IsDebug() then - print(ply, "gets roundincr ", incr) - end - end - end - - -- player's CleanRound state will be reset by the ply class + -- player's CleanRound state will be reset by the ply class end --- -- When a new round starts, Live karma becomes Base karma -- @realm server function KARMA.Rebase() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - if IsDebug() then - print(ply, "rebased from ", ply:GetBaseKarma(), " to ", ply:GetLiveKarma()) - end + if IsDebug() then + Dev(1, ply, "rebased from ", ply:GetBaseKarma(), " to ", ply:GetLiveKarma()) + end - ply:SetBaseKarma(ply:GetLiveKarma()) - end + ply:SetBaseKarma(ply:GetLiveKarma()) + end end --- -- Apply karma to damage factor for all players -- @realm server function KARMA.ApplyKarmaAll() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - KARMA.ApplyKarma(plys[i]) - end + for i = 1, #plys do + KARMA.ApplyKarma(plys[i]) + end end --- @@ -539,61 +620,67 @@ end -- @param Player ply -- @realm server function KARMA.NotifyPlayer(ply) - local df = ply:GetDamageFactor() or 1 - local k = math.Round(ply:GetBaseKarma()) + local df = ply:GetDamageFactor() or 1 + local k = math.Round(ply:GetBaseKarma()) - if df > 0.99 then - LANG.Msg(ply, "karma_dmg_full", {amount = k}) - else - LANG.Msg(ply, "karma_dmg_other", {amount = k, num = math.ceil((1 - df) * 100)}) - end + if df > 0.99 then + LANG.Msg(ply, "karma_dmg_full", { amount = k }) + else + LANG.Msg(ply, "karma_dmg_other", { amount = k, num = math.ceil((1 - df) * 100) }) + end end --- -- Runs the karma related functions on round end. -- @realm server function KARMA.RoundEnd() - if not KARMA.IsEnabled() then return end + if not KARMA.IsEnabled() then + return + end - KARMA.RoundIncrement() + KARMA.RoundIncrement() - -- if karma trend needs to be shown in round report, may want to delay - -- rebase until start of next round - KARMA.Rebase() - KARMA.RememberAll() + -- if karma trend needs to be shown in round report, may want to delay + -- rebase until start of next round + KARMA.Rebase() + KARMA.RememberAll() - -- check if players should be kicked due to low karma - KARMA.CheckAutoKickAll() + -- check if players should be kicked due to low karma + KARMA.CheckAutoKickAll() end --- -- Runs the karma related functions on round begin. -- @realm server function KARMA.RoundBegin() - if not KARMA.IsEnabled() then return end + if not KARMA.IsEnabled() then + return + end - -- Check for low-karma players that weren't banned on round end - -- because they disconnected before the round ended. - KARMA.CheckAutoKickAll() + -- Check for low-karma players that weren't banned on round end + -- because they disconnected before the round ended. + KARMA.CheckAutoKickAll() end --- -- Update / Reset the KARMA System after the previous round ended in prepare round. -- @realm server function KARMA.RoundPrepare() - KARMA.InitState() - KARMA.ResetRoundChanges() + KARMA.InitState() + KARMA.ResetRoundChanges() - if not KARMA.IsEnabled() then return end + if not KARMA.IsEnabled() then + return + end - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - KARMA.ApplyKarma(ply) - KARMA.NotifyPlayer(ply) - end + KARMA.ApplyKarma(ply) + KARMA.NotifyPlayer(ply) + end end --- @@ -601,13 +688,15 @@ end -- Usually called in @{GM:TTTBeginRound} and @{GM:TTTEndRound}. -- @realm server function KARMA.CheckAutoKickAll() - if not config.autokick:GetBool() then return end + if not config.autokick:GetBool() then + return + end - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - KARMA.CheckAutoKick(plys[i]) - end + for i = 1, #plys do + KARMA.CheckAutoKick(plys[i]) + end end --- @@ -615,15 +704,15 @@ end -- @param Player ply -- @realm server function KARMA.InitPlayer(ply) - local k = math.Clamp(KARMA.Recall(ply) or config.starting:GetFloat(), 0, config.max:GetFloat()) + local k = math.Clamp(KARMA.Recall(ply) or config.starting:GetFloat(), 0, config.max:GetFloat()) - ply:SetBaseKarma(k) - ply:SetLiveKarma(k) - ply:SetCleanRound(true) - ply:SetDamageFactor(1.0) + ply:SetBaseKarma(k) + ply:SetLiveKarma(k) + ply:SetCleanRound(true) + ply:SetDamageFactor(1.0) - -- compute the damagefactor based on actual (possibly loaded) karma - KARMA.ApplyKarma(ply) + -- compute the damagefactor based on actual (possibly loaded) karma + KARMA.ApplyKarma(ply) end --- @@ -631,21 +720,23 @@ end -- @param Player ply -- @realm server function KARMA.Remember(ply) - if ply.karma_kicked or not ply:IsFullyAuthenticated() then return end + if ply.karma_kicked or not ply:IsFullyAuthenticated() then + return + end - -- use sql if persistence is on - if config.persist:GetBool() then - ply:SetPData("karma_stored", ply:GetLiveKarma()) - end + -- use sql if persistence is on + if config.persist:GetBool() then + ply:SetPData("karma_stored", ply:GetLiveKarma()) + end - if ply:SteamID64() == nil then - print("[TTT2] ERROR: Player has no steamID64") + if ply:SteamID64() == nil then + ErrorNoHaltWithStack("[TTT2] ERROR: Player has no steamID64:", ply) - return - end + return + end - -- if persist is on, this is purely a backup method - KARMA.rememberedPlayers[ply:SteamID64()] = ply:GetLiveKarma() + -- if persist is on, this is purely a backup method + KARMA.rememberedPlayers[ply:SteamID64()] = ply:GetLiveKarma() end --- @@ -654,18 +745,18 @@ end -- @return number -- @realm server function KARMA.Recall(ply) - if config.persist:GetBool() then - ply.delay_karma_recall = not ply:IsFullyAuthenticated() + if config.persist:GetBool() then + ply.delay_karma_recall = not ply:IsFullyAuthenticated() - if ply:IsFullyAuthenticated() then - local k = tonumber(ply:GetPData("karma_stored", nil)) - if k then - return k - end - end - end + if ply:IsFullyAuthenticated() then + local k = tonumber(ply:GetPData("karma_stored", nil)) + if k then + return k + end + end + end - return KARMA.rememberedPlayers[ply:SteamID64()] + return KARMA.rememberedPlayers[ply:SteamID64()] end --- @@ -673,11 +764,11 @@ end -- @param Player ply -- @realm server function KARMA.LateRecallAndSet(ply) - local k = tonumber(ply:GetPData("karma_stored", KARMA.rememberedPlayers[ply:SteamID64()])) - if k and k < ply:GetLiveKarma() then - ply:SetBaseKarma(k) - ply:SetLiveKarma(k) - end + local k = tonumber(ply:GetPData("karma_stored", KARMA.rememberedPlayers[ply:SteamID64()])) + if k and k < ply:GetLiveKarma() then + ply:SetBaseKarma(k) + ply:SetLiveKarma(k) + end end --- @@ -685,11 +776,11 @@ end -- @realm server -- @see KARMA.Remember function KARMA.RememberAll() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - KARMA.Remember(plys[i]) - end + for i = 1, #plys do + KARMA.Remember(plys[i]) + end end local reason = "Karma too low" @@ -699,29 +790,34 @@ local reason = "Karma too low" -- @param Player ply -- @realm server function KARMA.CheckAutoKick(ply) - --- - -- @realm server - if ply:GetBaseKarma() > config.kicklevel:GetInt() or hook.Run("TTTKarmaLow", ply) == false then return end + --- + -- @realm server + -- stylua: ignore + if ply:GetBaseKarma() > config.kicklevel:GetInt() or hook.Run("TTTKarmaLow", ply) == false then return end - ServerLog(ply:Nick() .. " autokicked/banned for low karma.\n") + ServerLog(ply:Nick() .. " autokicked/banned for low karma.\n") - -- flag player as autokicked so we don't perform the normal player - -- disconnect logic - ply.karma_kicked = true + -- flag player as autokicked so we don't perform the normal player + -- disconnect logic + ply.karma_kicked = true - if config.persist:GetBool() then - local k = math.Clamp(config.starting:GetFloat() * 0.8, config.kicklevel:GetFloat() * 1.1, config.max:GetFloat()) + if config.persist:GetBool() then + local k = math.Clamp( + config.starting:GetFloat() * 0.8, + config.kicklevel:GetFloat() * 1.1, + config.max:GetFloat() + ) - ply:SetPData("karma_stored", k) + ply:SetPData("karma_stored", k) - KARMA.rememberedPlayers[ply:SteamID64()] = k - end + KARMA.rememberedPlayers[ply:SteamID64()] = k + end - if config.autoban:GetBool() then - ply:KickBan(config.bantime:GetInt(), reason) - else - ply:Kick(reason) - end + if config.autoban:GetBool() then + ply:KickBan(config.bantime:GetInt(), reason) + else + ply:Kick(reason) + end end --- @@ -729,18 +825,21 @@ end -- @param function printfn -- @realm server function KARMA.PrintAll(printfn) - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - printfn(Format("%s : Live = %f -- Base = %f -- Dmg = %f\n", - ply:Nick(), - ply:GetLiveKarma(), - ply:GetBaseKarma(), - ply:GetDamageFactor() * 100 - )) - end + printfn( + Format( + "%s : Live = %f -- Base = %f -- Dmg = %f\n", + ply:Nick(), + ply:GetLiveKarma(), + ply:GetBaseKarma(), + ply:GetDamageFactor() * 100 + ) + ) + end end --- @@ -752,21 +851,17 @@ end -- @return nil|boolean Return true to block the given penalty -- @hook -- @realm server -function GM:TTTKarmaGivePenalty(attacker, penalty, victim) - -end +function GM:TTTKarmaGivePenalty(attacker, penalty, victim) end --- -- Modify the karma penalty multiplier. -- @param Player attacker The player who attacked someone and should receive a penalty -- @param Player victim The player that was attacked --- @param DamageInfo dmginfo The damage info from the attack +-- @param CTakeDamageInfo dmginfo The damage info from the attack -- @return nil|number Return the karma multiplier -- @hook -- @realm server -function GM:TTT2KarmaPenaltyMultiplier(attacker, victim, dmginfo) - -end +function GM:TTT2KarmaPenaltyMultiplier(attacker, victim, dmginfo) end --- -- Called when a player is about to be kicked/banned because their karma has gone below @@ -777,6 +872,4 @@ end -- @return nil|boolean Return false to prevent the player from being kicked -- @hook -- @realm server -function GM:TTTKarmaLow(ply) - -end +function GM:TTTKarmaLow(ply) end diff --git a/gamemodes/terrortown/gamemode/server/sv_main.lua b/gamemodes/terrortown/gamemode/server/sv_main.lua index 79cd473f3..8409d7d75 100644 --- a/gamemodes/terrortown/gamemode/server/sv_main.lua +++ b/gamemodes/terrortown/gamemode/server/sv_main.lua @@ -7,12 +7,14 @@ ttt_include("sh_cvar_handler") ttt_include("sh_sprint") ttt_include("sh_main") +ttt_include("sh_shop") ttt_include("sh_shopeditor") ttt_include("sh_network_sync") ttt_include("sh_door") ttt_include("sh_voice") ttt_include("sh_printmessage_override") ttt_include("sh_speed") +ttt_include("sh_marker_vision_element") ttt_include("sv_network_sync") ttt_include("sv_hud_manager") @@ -60,7 +62,6 @@ local math = math local table = table local net = net local player = player -local pairs = pairs local timer = timer local util = util local IsValid = IsValid @@ -68,145 +69,178 @@ local hook = hook --- -- @realm server +-- stylua: ignore local roundtime = CreateConVar("ttt_roundtime_minutes", "10", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local preptime = CreateConVar("ttt_preptime_seconds", "30", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local posttime = CreateConVar("ttt_posttime_seconds", "30", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local firstpreptime = CreateConVar("ttt_firstpreptime", "60", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local ttt_haste = CreateConVar("ttt_haste", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local haste_starting = CreateConVar("ttt_haste_starting_minutes", "5", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore CreateConVar("ttt_haste_minutes_per_death", "0.5", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) -- Credits --- -- @realm server +-- stylua: ignore CreateConVar("ttt_credits_award_pct", "0.35", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore CreateConVar("ttt_credits_award_size", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore CreateConVar("ttt_credits_award_repeat", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore CreateConVar("ttt_credits_award_kill", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local ttt_session_limits_enabled = CreateConVar("ttt_session_limits_enabled", "1", SERVER and {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED} or FCVAR_REPLICATED) --- -- @realm server +-- stylua: ignore local round_limit = CreateConVar("ttt_round_limit", "6", SERVER and {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED} or FCVAR_REPLICATED) --- -- @realm server +-- stylua: ignore local time_limit = CreateConVar("ttt_time_limit_minutes", "75", SERVER and {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED} or FCVAR_REPLICATED) --- -- @realm server +-- stylua: ignore local idle_enabled = CreateConVar("ttt_idle", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local idle_time = CreateConVar("ttt_idle_limit", "180", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local voice_drain = CreateConVar("ttt_voice_drain", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local voice_drain_normal = CreateConVar("ttt_voice_drain_normal", "0.2", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local voice_drain_admin = CreateConVar("ttt_voice_drain_admin", "0.05", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local voice_drain_recharge = CreateConVar("ttt_voice_drain_recharge", "0.05", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local namechangekick = CreateConVar("ttt_namechange_kick", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local namechangebtime = CreateConVar("ttt_namechange_bantime", "10", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local ttt_detective = CreateConVar("ttt_sherlock_mode", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local ttt_minply = CreateConVar("ttt_minimum_players", "2", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local cvPreferMapModels = CreateConVar("ttt2_prefer_map_models", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local cvSelectModelPerRound = CreateConVar("ttt2_select_model_per_round", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore CreateConVar("ttt2_prep_respawn", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Respawn if dead in preparing time") --- -- @realm server +-- stylua: ignore local map_switch_delay = CreateConVar("ttt2_map_switch_delay", "15", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Time that passes before the map is changed after the last round ends or the timer runs out", 0) --- -- @realm server +-- stylua: ignore CreateConVar("ttt_identify_body_woconfirm", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Toggles whether ragdolls should be confirmed in DetectiveMode() without clicking on confirm espacially") --- -- @realm server +-- stylua: ignore local confirm_team = CreateConVar("ttt2_confirm_team", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Show team of confirmed player") --- -- @realm server +-- stylua: ignore CreateConVar("ttt2_confirm_killlist", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Confirm players in kill list") --- -- @realm server +-- stylua: ignore CreateConVar("ttt_enforce_playermodel", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Whether or not to enforce terrorist playermodels. Set to 0 for compatibility with Enhanced Playermodel Selector") --- -- @realm server +-- stylua: ignore local ttt_dbgwin = CreateConVar("ttt_debug_preventwin", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local ttt_newroles_enabled = CreateConVar("ttt_newroles_enabled", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) -- Pool some network names. util.AddNetworkString("TTT_RoundState") -util.AddNetworkString("TTT_RagdollSearch") util.AddNetworkString("TTT_GameMsg") util.AddNetworkString("TTT_GameMsgColor") util.AddNetworkString("TTT_RoleChat") @@ -232,7 +266,6 @@ util.AddNetworkString("TTT_RoleReset") util.AddNetworkString("TTT_ConfirmUseTButton") util.AddNetworkString("TTT_C4Config") util.AddNetworkString("TTT_C4DisarmResult") -util.AddNetworkString("TTT_C4Warn") util.AddNetworkString("TTT_ScanResult") util.AddNetworkString("TTT_FlareScorch") util.AddNetworkString("TTT_Radar") @@ -241,7 +274,6 @@ util.AddNetworkString("TTT_Spectate") util.AddNetworkString("TTT2TestRole") util.AddNetworkString("TTT2SyncShopsWithServer") util.AddNetworkString("TTT2DevChanges") -util.AddNetworkString("TTT2SyncDBItems") util.AddNetworkString("TTT2ReceiveTBEq") util.AddNetworkString("TTT2ReceiveGBEq") util.AddNetworkString("TTT2ResetTBEq") @@ -262,19 +294,17 @@ fileloader.LoadFolder("terrortown/menus/gamemode/", true, CLIENT_FILE) -- provide and add autorun files fileloader.LoadFolder("terrortown/autorun/client/", false, CLIENT_FILE, function(path) - MsgN("Marked TTT2 client autorun file for distribution: ", path) + Dev(1, "Marked TTT2 client autorun file for distribution: ", path) end) fileloader.LoadFolder("terrortown/autorun/shared/", false, SHARED_FILE, function(path) - MsgN("Marked and added TTT2 shared autorun file for distribution: ", path) + Dev(1, "Marked and added TTT2 shared autorun file for distribution: ", path) end) fileloader.LoadFolder("terrortown/autorun/server/", false, SERVER_FILE, function(path) - MsgN("Added TTT2 server autorun file: ", path) + Dev(1, "Added TTT2 server autorun file: ", path) end) -CHANGED_EQUIPMENT = CHANGED_EQUIPMENT or {} - --- -- Called after the gamemode loads and starts. -- @hook @@ -282,90 +312,109 @@ CHANGED_EQUIPMENT = CHANGED_EQUIPMENT or {} -- @ref https://wiki.facepunch.com/gmod/GM:Initialize -- @local function GM:Initialize() - MsgN("Trouble In Terrorist Town 2 gamemode initializing...") - ShowVersion() - - --- - -- @realm shared - hook.Run("TTT2Initialize") - - --- - -- @realm shared - hook.Run("TTT2FinishedLoading") - - -- load default TTT2 language files or mark them as downloadable on the server - -- load addon language files in a second pass, the core language files are loaded earlier - fileloader.LoadFolder("terrortown/lang/", true, CLIENT_FILE, function(path) - MsgN("Added TTT2 language file: ", path) - end) - - fileloader.LoadFolder("lang/", true, CLIENT_FILE, function(path) - MsgN("[DEPRECATION WARNING]: Loaded language file from 'lang/', this folder is deprecated. Please switch to 'terrortown/lang/'") - MsgN("Added TTT2 language file: ", path) - end) - - -- load vskin files - fileloader.LoadFolder("terrortown/vskin/", false, CLIENT_FILE, function(path) - MsgN("Added TTT2 vskin file: ", path) - end) - - roleselection.LoadLayers() - - ShopEditor.SetupShopEditorCVars() - ShopEditor.CreateShopDBs() - - -- Force friendly fire to be enabled. If it is off, we do not get lag compensation. - RunConsoleCommand("mp_friendlyfire", "1") - - -- Default crowbar unlocking settings, may be overridden by config entity - self.crowbar_unlocks = { - [OPEN_DOOR] = true, - [OPEN_ROT] = true, - [OPEN_BUT] = true, - [OPEN_NOTOGGLE] = true - } - - -- More map config ent defaults - self.force_plymodel = "" - self.propspec_allow_named = true - - self.MapWin = WIN_NONE - self.AwardedCredits = false - self.AwardedCreditsDead = 0 - - self.round_state = ROUND_WAIT - self.FirstRound = true - self.RoundStartTime = 0 - self.roundCount = 0 - - self.DamageLog = {} - self.LastRole = {} - - -- Delay reading of cvars until config has definitely loaded - self.cvar_init = false - - SetGlobalFloat("ttt_round_end", -1) - SetGlobalFloat("ttt_haste_end", -1) - - -- For the paranoid - math.randomseed(os.time()) - math.random(); math.random(); math.random() -- warming up - - WaitForPlayers() - - if cvars.Number("sv_alltalk", 0) > 0 then - ErrorNoHalt("TTT2 WARNING: sv_alltalk is enabled. Dead players will be able to talk to living players. TTT2 will now attempt to set sv_alltalk 0.\n") - - RunConsoleCommand("sv_alltalk", "0") - end - - if not IsMounted("cstrike") then - ErrorNoHalt("TTT2 WARNING: CS:S does not appear to be mounted by GMod. Things may break in strange ways. Server admin? Check the TTT readme for help.\n") - end - - --- - -- @realm shared - hook.Run("PostInitialize") + Dev(1, "Trouble In Terrorist Town 2 gamemode initializing...") + ShowVersion() + + -- Migrate all changes of TTT2 + migrations.Apply() + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2Initialize") + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2FinishedLoading") + + -- load default TTT2 language files or mark them as downloadable on the server + -- load addon language files in a second pass, the core language files are loaded earlier + fileloader.LoadFolder("terrortown/lang/", true, CLIENT_FILE, function(path) + Dev(1, "Added TTT2 language file: ", path) + end) + + fileloader.LoadFolder("lang/", true, CLIENT_FILE, function(path) + ErrorNoHaltWithStack( + "[DEPRECATION WARNING]: Loaded language file from 'lang/', this folder is deprecated. Please switch to 'terrortown/lang/'. Source: \"" + .. path + .. "\"" + ) + Dev(1, "Added TTT2 language file: ", path) + end) + + -- load vskin files + fileloader.LoadFolder("terrortown/vskin/", false, CLIENT_FILE, function(path) + Dev(1, "Added TTT2 vskin file: ", path) + end) + + roleselection.LoadLayers() + + ShopEditor.SetupShopEditorCVars() + ShopEditor.CreateShopDBs() + + -- register synced player variables + player.RegisterSettingOnServer("enable_dynamic_fov", "bool") + + -- Force friendly fire to be enabled. If it is off, we do not get lag compensation. + RunConsoleCommand("mp_friendlyfire", "1") + + -- Default crowbar unlocking settings, may be overridden by config entity + self.crowbar_unlocks = { + [OPEN_DOOR] = true, + [OPEN_ROT] = true, + [OPEN_BUT] = true, + [OPEN_NOTOGGLE] = true, + } + + -- More map config ent defaults + self.force_plymodel = "" + self.propspec_allow_named = true + + self.MapWin = WIN_NONE + self.AwardedCredits = false + self.AwardedCreditsDead = 0 + + self.round_state = ROUND_WAIT + self.FirstRound = true + self.RoundStartTime = 0 + self.roundCount = 0 + + self.DamageLog = {} + self.LastRole = {} + + -- Delay reading of cvars until config has definitely loaded + self.cvar_init = false + + SetGlobalFloat("ttt_round_end", -1) + SetGlobalFloat("ttt_haste_end", -1) + + -- For the paranoid + math.randomseed(os.time()) + math.random() + math.random() + math.random() + + WaitForPlayers() + + if cvars.Number("sv_alltalk", 0) > 0 then + ErrorNoHalt( + "TTT2 WARNING: sv_alltalk is enabled. Dead players will be able to talk to living players. TTT2 will now attempt to set sv_alltalk 0.\n" + ) + + RunConsoleCommand("sv_alltalk", "0") + end + + if not IsMounted("cstrike") then + ErrorNoHalt( + "TTT2 WARNING: CS:S does not appear to be mounted by GMod. Things may break in strange ways. Server admin? Check the TTT readme for help.\n" + ) + end + + --- + -- @realm shared + -- stylua: ignore + hook.Run("PostInitialize") end --- @@ -374,16 +423,16 @@ end -- @hook -- @realm server function GM:InitCvars() - MsgN("TTT2 initializing ConVar settings...") + Dev(1, "TTT2 initializing ConVar settings...") - -- Initialize game state that is synced with client - SetGlobalInt("ttt_rounds_left", round_limit:GetInt()) + -- Initialize game state that is synced with client + SetGlobalInt("ttt_rounds_left", round_limit:GetInt()) - self:SyncGlobals() + self:SyncGlobals() - KARMA.InitState() + KARMA.InitState() - self.cvar_init = true + self.cvar_init = true end --- @@ -394,7 +443,7 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:GetGameDescription -- @local function GM:GetGameDescription() - return self.Name + return self.Name end --- @@ -409,99 +458,107 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:InitPostEntity -- @local function GM:InitPostEntity() - self:InitCvars() + self:InitCvars() - MsgN("[TTT2][INFO] Client post-init...") + Dev(1, "[TTT2][INFO] Client post-init...") - --- - -- @realm shared - hook.Run("TTTInitPostEntity") + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTTInitPostEntity") - -- load entity spawns from file / map - entspawnscript.OnLoaded() + -- load entity spawns from file / map + entspawnscript.OnLoaded() - items.MigrateLegacyItems() - items.OnLoaded() + items.MigrateLegacyItems() + items.OnLoaded() - -- load all HUDs - huds.OnLoaded() + -- load all HUDs + huds.OnLoaded() - -- load all HUD elements - hudelements.OnLoaded() + -- load all HUD elements + hudelements.OnLoaded() - local sweps = weapons.GetList() + local sweps = weapons.GetList() - for i = 1, #sweps do - local eq = sweps[i] + for i = 1, #sweps do + local eq = sweps[i] - -- Check if an equipment has an id or ignore it - -- @realm server - if not hook.Run("TTT2RegisterWeaponID", eq) then continue end + -- Check if an equipment has an id or ignore it + -- @realm server + -- stylua: ignore + if not hook.Run("TTT2RegisterWeaponID", eq) then continue end - -- Insert data into role fallback tables - InitDefaultEquipment(eq) + -- Insert data into role fallback tables + InitDefaultEquipment(eq) - eq.CanBuy = {} -- reset normal weapons equipment - end + eq.CanBuy = {} -- reset normal weapons equipment + end - -- init hudelements fns - local hudElems = hudelements.GetList() + -- init hudelements fns + local hudElems = hudelements.GetList() - for i = 1, #hudElems do - local hudelem = hudElems[i] + for i = 1, #hudElems do + local hudelem = hudElems[i] - if not hudelem.togglable then continue end + if not hudelem.togglable then + continue + end - local nm = "ttt2_elem_toggled_" .. hudelem.id + local nm = "ttt2_elem_toggled_" .. hudelem.id - --- - -- @name ttt2_elem_toggled_[HUDELEMENT_NAME] - -- @realm server - local ret = CreateConVar(nm, "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + --- + -- @name ttt2_elem_toggled_[HUDELEMENT_NAME] + -- @realm server + -- stylua: ignore + local ret = CreateConVar(nm, "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - SetGlobalBool(nm, ret:GetBool()) + SetGlobalBool(nm, ret:GetBool()) - cvars.AddChangeCallback(nm, function(cvarName, old, new) - SetGlobalBool(cvarName, tobool(new)) - end, "CVAR_" .. nm) - end + cvars.AddChangeCallback(nm, function(cvarName, old, new) + SetGlobalBool(cvarName, tobool(new)) + end, "CVAR_" .. nm) + end - -- initialize fallback shops - InitFallbackShops() + -- initialize fallback shops + InitFallbackShops() - --- - -- @realm server - hook.Run("PostInitPostEntity") + --- + -- @realm server + -- stylua: ignore + hook.Run("PostInitPostEntity") - --- - -- @realm server - hook.Run("InitFallbackShops") + --- + -- @realm server + -- stylua: ignore + hook.Run("InitFallbackShops") - --- - -- @realm server - hook.Run("LoadedFallbackShops") + --- + -- @realm server + -- stylua: ignore + hook.Run("LoadedFallbackShops") - -- initialize the equipment - LoadShopsEquipment() + -- initialize the equipment + LoadShopsEquipment() - MsgN("[TTT2][INFO] Shops initialized...") - TTT2ShopFallbackInitialized = true + Dev(1, "[TTT2][INFO] Shops initialized...") + TTT2ShopFallbackInitialized = true - WEPS.ForcePrecache() + WEPS.ForcePrecache() - -- precache player models - playermodels.PrecacheModels() + -- precache player models + playermodels.PrecacheModels() - -- initialize playermodel database - playermodels.Initialize() + -- initialize playermodel database + playermodels.Initialize() - -- set the default random playermodel - self.playermodel = playermodels.GetRandomPlayerModel() - self.playercolor = COLOR_WHITE + -- set the default random playermodel + self.playermodel = playermodels.GetRandomPlayerModel() + self.playercolor = COLOR_WHITE - timer.Simple(0, function() - addonChecker.Check() - end) + timer.Simple(0, function() + addonChecker.Check() + end) end --- @@ -516,7 +573,7 @@ end -- @hook -- @realm server function GM:AcceptInput(ent, name, activator, caller, data) - return door.AcceptInput(ent, name, activator, caller, data) + return door.AcceptInput(ent, name, activator, caller, data) end --- @@ -526,71 +583,75 @@ end -- @hook -- @realm server function GM:SyncGlobals() - SetGlobalBool("ttt_detective", ttt_detective:GetBool()) - SetGlobalBool(ttt_haste:GetName(), ttt_haste:GetBool()) - SetGlobalBool(ttt_session_limits_enabled:GetName(), ttt_session_limits_enabled:GetBool()) - SetGlobalInt(time_limit:GetName(), time_limit:GetInt()) - SetGlobalInt(idle_time:GetName(), idle_time:GetInt()) - SetGlobalBool(idle_enabled:GetName(), idle_enabled:GetBool()) - - SetGlobalBool(voice_drain:GetName(), voice_drain:GetBool()) - SetGlobalFloat(voice_drain_normal:GetName(), voice_drain_normal:GetFloat()) - SetGlobalFloat(voice_drain_admin:GetName(), voice_drain_admin:GetFloat()) - SetGlobalFloat(voice_drain_recharge:GetName(), voice_drain_recharge:GetFloat()) - - local rlsList = roles.GetList() - - for i = 1, #rlsList do - local abbr = rlsList[i].abbr - - SetGlobalString("ttt_" .. abbr .. "_shop_fallback", GetConVar("ttt_" .. abbr .. "_shop_fallback"):GetString()) - end - - SetGlobalBool("ttt2_confirm_team", confirm_team:GetBool()) - - --- - -- @realm server - hook.Run("TTT2SyncGlobals") + SetGlobalBool("ttt_detective", ttt_detective:GetBool()) + SetGlobalBool(ttt_haste:GetName(), ttt_haste:GetBool()) + SetGlobalBool(ttt_session_limits_enabled:GetName(), ttt_session_limits_enabled:GetBool()) + SetGlobalInt(time_limit:GetName(), time_limit:GetInt()) + SetGlobalInt(idle_time:GetName(), idle_time:GetInt()) + SetGlobalBool(idle_enabled:GetName(), idle_enabled:GetBool()) + + SetGlobalBool(voice_drain:GetName(), voice_drain:GetBool()) + SetGlobalFloat(voice_drain_normal:GetName(), voice_drain_normal:GetFloat()) + SetGlobalFloat(voice_drain_admin:GetName(), voice_drain_admin:GetFloat()) + SetGlobalFloat(voice_drain_recharge:GetName(), voice_drain_recharge:GetFloat()) + + local rlsList = roles.GetList() + + for i = 1, #rlsList do + local abbr = rlsList[i].abbr + + SetGlobalString( + "ttt_" .. abbr .. "_shop_fallback", + GetConVar("ttt_" .. abbr .. "_shop_fallback"):GetString() + ) + end + + SetGlobalBool("ttt2_confirm_team", confirm_team:GetBool()) + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2SyncGlobals") end cvars.AddChangeCallback(ttt_detective:GetName(), function(cv, old, new) - SetGlobalBool("ttt_detective", tobool(tonumber(new))) + SetGlobalBool("ttt_detective", tobool(tonumber(new))) end) cvars.AddChangeCallback(ttt_haste:GetName(), function(cv, old, new) - SetGlobalBool(ttt_haste:GetName(), tobool(tonumber(new))) + SetGlobalBool(ttt_haste:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(ttt_session_limits_enabled:GetName(), function(cv, old, new) - SetGlobalBool(ttt_session_limits_enabled:GetName(), tobool(tonumber(new))) + SetGlobalBool(ttt_session_limits_enabled:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(time_limit:GetName(), function(cv, old, new) - SetGlobalInt(time_limit:GetName(), tonumber(new)) + SetGlobalInt(time_limit:GetName(), tonumber(new)) end) cvars.AddChangeCallback(idle_time:GetName(), function(cv, old, new) - SetGlobalInt(idle_time:GetName(), tonumber(new)) + SetGlobalInt(idle_time:GetName(), tonumber(new)) end) cvars.AddChangeCallback(idle_enabled:GetName(), function(cv, old, new) - SetGlobalBool(idle_enabled:GetName(), tobool(tonumber(new))) + SetGlobalBool(idle_enabled:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(voice_drain:GetName(), function(cv, old, new) - SetGlobalBool(voice_drain:GetName(), tobool(tonumber(new))) + SetGlobalBool(voice_drain:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(voice_drain_normal:GetName(), function(cv, old, new) - SetGlobalFloat(voice_drain_normal:GetName(), tonumber(new)) + SetGlobalFloat(voice_drain_normal:GetName(), tonumber(new)) end) cvars.AddChangeCallback(voice_drain_admin:GetName(), function(cv, old, new) - SetGlobalFloat(voice_drain_admin:GetName(), tonumber(new)) + SetGlobalFloat(voice_drain_admin:GetName(), tonumber(new)) end) cvars.AddChangeCallback(voice_drain_recharge:GetName(), function(cv, old, new) - SetGlobalFloat(voice_drain_recharge:GetName(), tonumber(new)) + SetGlobalFloat(voice_drain_recharge:GetName(), tonumber(new)) end) --- @@ -598,72 +659,35 @@ end) -- @realm server -- @internal function LoadShopsEquipment() - -- initialize shop equipment - local rlsList = roles.GetList() + -- initialize shop equipment + local rlsList = roles.GetList() - for i = 1, #rlsList do - local roleData = rlsList[i] + for i = 1, #rlsList do + local roleData = rlsList[i] - local shopFallback = GetConVar("ttt_" .. roleData.abbr .. "_shop_fallback"):GetString() - if shopFallback ~= roleData.name then continue end + local shopFallback = GetConVar("ttt_" .. roleData.abbr .. "_shop_fallback"):GetString() + if shopFallback ~= roleData.name then + continue + end - LoadSingleShopEquipment(roleData) - end + LoadSingleShopEquipment(roleData) + end end -local function TTT2SyncShopsWithServer(len, ply) - -- at first, sync items - for i = 1, #CHANGED_EQUIPMENT do - local tbl = CHANGED_EQUIPMENT[i] - - ShopEditor.WriteItemData("TTT2SyncDBItems", tbl[1], tbl[2]) - end - - -- reset and set if it's a fallback - net.Start("shopFallbackReset") - net.Send(ply) - - SyncEquipment(ply) - - -- sync bought sweps - if BUYTABLE then - for id in pairs(BUYTABLE) do - net.Start("TTT2ReceiveGBEq") - net.WriteString(id) - net.Send(ply) - end - end - - if TEAMBUYTABLE then - local team = ply:GetTeam() - - if team and team ~= TEAM_NONE and not TEAMS[team].alone and TEAMBUYTABLE[team] then - local filter = GetTeamFilter(team) - - for id in pairs(TEAMBUYTABLE[team]) do - net.Start("TTT2ReceiveTBEq") - net.WriteString(id) - net.Send(filter) - end - end - end -end -net.Receive("TTT2SyncShopsWithServer", TTT2SyncShopsWithServer) - --- -- This @{function} is used to trigger the round syncing -- @param number state the round state -- @param[opt] Player ply if nil, this will broadcast to every connected @{PLayer} -- @realm server function SendRoundState(state, ply) - net.Start("TTT_RoundState") - net.WriteUInt(state, 3) - - if IsValid(ply) then - net.Send(ply) - else - net.Broadcast() - end + net.Start("TTT_RoundState") + net.WriteUInt(state, 3) + + if IsValid(ply) then + net.Send(ply) + else + net.Broadcast() + end end --- @@ -673,11 +697,11 @@ end -- @param number state -- @realm server function SetRoundState(state) - GAMEMODE.round_state = state + GAMEMODE.round_state = state - events.Trigger(EVENT_GAME, state) + events.Trigger(EVENT_GAME, state) - SendRoundState(state) + SendRoundState(state) end --- @@ -687,24 +711,26 @@ end -- @return number -- @realm server function GetRoundState() - return GAMEMODE.round_state + return GAMEMODE.round_state end local function EnoughPlayers() - local ready = 0 + local ready = 0 - -- only count truly available players, i.e. no forced specs - local plys = player.GetAll() + -- only count truly available players, i.e. no forced specs + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - if not IsValid(ply) or not ply:ShouldSpawn() then continue end + if not IsValid(ply) or not ply:ShouldSpawn() then + continue + end - ready = ready + 1 - end + ready = ready + 1 + end - return ready >= ttt_minply:GetInt() + return ready >= ttt_minply:GetInt() end --- @@ -716,10 +742,12 @@ end -- @see WaitForPlayers -- @internal function WaitingForPlayersChecker() - if GetRoundState() ~= ROUND_WAIT or not EnoughPlayers() then return end + if GetRoundState() ~= ROUND_WAIT or not EnoughPlayers() then + return + end - timer.Create("wait2prep", 1, 1, PrepareRound) - timer.Stop("waitingforply") + timer.Create("wait2prep", 1, 1, PrepareRound) + timer.Stop("waitingforply") end --- @@ -728,11 +756,13 @@ end -- @see WaitingForPlayersChecker -- @internal function WaitForPlayers() - SetRoundState(ROUND_WAIT) + SetRoundState(ROUND_WAIT) - if timer.Start("waitingforply") then return end + if timer.Start("waitingforply") then + return + end - timer.Create("waitingforply", 2, 0, WaitingForPlayersChecker) + timer.Create("waitingforply", 2, 0, WaitingForPlayersChecker) end --- @@ -744,15 +774,17 @@ end -- @realm server -- @internal function FixSpectators() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - if not ply:IsSpec() or ply:GetRagdollSpec() or ply:GetMoveType() >= MOVETYPE_NOCLIP then continue end + if not ply:IsSpec() or ply:GetRagdollSpec() or ply:GetMoveType() >= MOVETYPE_NOCLIP then + continue + end - ply:Spectate(OBS_MODE_ROAMING) - end + ply:Spectate(OBS_MODE_ROAMING) + end end --- @@ -761,58 +793,67 @@ end -- @realm server -- @internal local function WinChecker() - if GetRoundState() ~= ROUND_ACTIVE then return end - - if CurTime() > GetGlobalFloat("ttt_round_end", 0) then - EndRound(WIN_TIMELIMIT) - elseif not ttt_dbgwin:GetBool() then - --- - -- @realm server - win = hook.Run("TTT2PreWinChecker") - - --- - -- @realm server - win = win or hook.Run("TTTCheckForWin") - - if win == WIN_NONE then return end - - EndRound(win) - end + if GetRoundState() ~= ROUND_ACTIVE then + return + end + + if CurTime() > GetGlobalFloat("ttt_round_end", 0) then + EndRound(WIN_TIMELIMIT) + elseif not ttt_dbgwin:GetBool() then + --- + -- @realm server + -- stylua: ignore + win = hook.Run("TTT2PreWinChecker") + + --- + -- @realm server + -- stylua: ignore + win = win or hook.Run("TTTCheckForWin") + + if win == WIN_NONE then + return + end + + EndRound(win) + end end local function NameChangeKick() - if not namechangekick:GetBool() then - timer.Remove("namecheck") + if not namechangekick:GetBool() then + timer.Remove("namecheck") - return - end + return + end - if GetRoundState() ~= ROUND_ACTIVE then return end + if GetRoundState() ~= ROUND_ACTIVE then + return + end - local hmns = player.GetHumans() + local hmns = player.GetHumans() - for i = 1, #hmns do - local ply = hmns[i] + for i = 1, #hmns do + local ply = hmns[i] - if not ply.spawn_nick then - ply.spawn_nick = ply:Nick() + if not ply.spawn_nick then + ply.spawn_nick = ply:Nick() - continue - end + continue + end - --- - -- @realm server - if not ply.has_spawned or ply.spawn_nick == ply:Nick() or hook.Run("TTTNameChangeKick", ply) then continue end + --- + -- @realm server + -- stylua: ignore + if not ply.has_spawned or ply.spawn_nick == ply:Nick() or hook.Run("TTTNameChangeKick", ply) then continue end - local t = namechangebtime:GetInt() - local msg = "Changed name during a round" + local t = namechangebtime:GetInt() + local msg = "Changed name during a round" - if t > 0 then - ply:KickBan(t, msg) - else - ply:Kick(msg) - end - end + if t > 0 then + ply:KickBan(t, msg) + else + ply:Kick(msg) + end + end end --- @@ -821,20 +862,22 @@ end -- @realm server -- @internal function StartNameChangeChecks() - if not namechangekick:GetBool() then return end + if not namechangekick:GetBool() then + return + end - -- bring nicks up to date, may have been changed during prep/post - local plys = player.GetAll() + -- bring nicks up to date, may have been changed during prep/post + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - ply.spawn_nick = ply:Nick() - end + ply.spawn_nick = ply:Nick() + end - if not timer.Exists("namecheck") then - timer.Create("namecheck", 3, 0, NameChangeKick) - end + if not timer.Exists("namecheck") then + timer.Create("namecheck", 3, 0, NameChangeKick) + end end --- @@ -843,9 +886,9 @@ end -- @internal -- @see StopWinChecks function StartWinChecks() - if not timer.Start("winchecker") then - timer.Create("winchecker", 1, 0, WinChecker) - end + if not timer.Start("winchecker") then + timer.Create("winchecker", 1, 0, WinChecker) + end end --- @@ -854,7 +897,7 @@ end -- @internal -- @see StartWinChecks function StopWinChecks() - timer.Stop("winchecker") + timer.Stop("winchecker") end --- @@ -864,12 +907,12 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PreCleanupMap -- @local function GM:PreCleanupMap() - ents.TTT.FixParentedPreCleanup() + ents.TTT.FixParentedPreCleanup() - entityOutputs.CleanUp() + entityOutputs.CleanUp() - -- While cleaning up the map, disable random weapons directly spawning - entspawn.SetForcedRandomSpawn(false) + -- While cleaning up the map, disable random weapons directly spawning + entspawn.SetForcedRandomSpawn(false) end --- @@ -879,61 +922,62 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PostCleanupMap -- @local function GM:PostCleanupMap() - ents.TTT.FixParentedPostCleanup() + ents.TTT.FixParentedPostCleanup() - entityOutputs.SetUp() + entityOutputs.SetUp() - entspawn.HandleSpawns() + entspawn.HandleSpawns() - -- After map cleanup enable 'env_entity_maker'-entities to force spawn random weapons and ammo - -- This is necessary for maps like 'ttt_lttp_kakariko_a5', that only initialize 'ttt_random_weapon'-entities - -- after destroying vases and were therefore not affected by our entspawn-system - entspawn.SetForcedRandomSpawn(true) - --- - -- @realm server - hook.Run("TTT2PostCleanupMap") + -- After map cleanup enable 'env_entity_maker'-entities to force spawn random weapons and ammo + -- This is necessary for maps like 'ttt_lttp_kakariko_a5', that only initialize 'ttt_random_weapon'-entities + -- after destroying vases and were therefore not affected by our entspawn-system + entspawn.SetForcedRandomSpawn(true) + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2PostCleanupMap") - door.SetUp() + door.SetUp() end local function CleanUp() - game.CleanUpMap() + game.CleanUpMap() - -- Strip players now, so that their weapons are not seen by ReplaceEntities - local plys = player.GetAll() + -- Strip players now, so that their weapons are not seen by ReplaceEntities + local plys = player.GetAll() - for i = 1, #plys do - local v = plys[i] + for i = 1, #plys do + local v = plys[i] - v:StripWeapons() - v:SetRole(ROLE_NONE) -- will reset team automatically - end + v:StripWeapons() + v:SetRole(ROLE_NONE) -- will reset team automatically + end - -- a different kind of cleanup - hook.Remove("PlayerSay", "ULXMeCheck") + -- a different kind of cleanup + hook.Remove("PlayerSay", "ULXMeCheck") end local function StopRoundTimers() - -- remove all timers - timer.Stop("wait2prep") - timer.Stop("prep2begin") - timer.Stop("end2prep") - timer.Stop("winchecker") + -- remove all timers + timer.Stop("wait2prep") + timer.Stop("prep2begin") + timer.Stop("end2prep") + timer.Stop("winchecker") end -- Make sure we have the players to do a round, people can leave during our -- preparations so we'll call this numerous times local function CheckForAbort() - if not EnoughPlayers() then - LANG.Msg("round_minplayers") + if not EnoughPlayers() then + LANG.Msg("round_minplayers") - StopRoundTimers() - WaitForPlayers() + StopRoundTimers() + WaitForPlayers() - return true - end + return true + end - return false + return false end --- @@ -942,7 +986,7 @@ end -- @realm server -- @internal function SetRoundEnd(endtime) - SetGlobalFloat("ttt_round_end", endtime) + SetGlobalFloat("ttt_round_end", endtime) end --- @@ -951,7 +995,7 @@ end -- @realm server -- @internal function IncRoundEnd(incr) - SetRoundEnd(GetGlobalFloat("ttt_round_end", 0) + incr) + SetRoundEnd(GetGlobalFloat("ttt_round_end", 0) + incr) end --- @@ -959,183 +1003,205 @@ end -- @realm server -- @internal function PrepareRound() - -- Check playercount - if CheckForAbort() then return end + -- Check playercount + if CheckForAbort() then + return + end - --- - -- @realm server - local delay_round, delay_length = hook.Run("TTTDelayRoundStartForVote") + --- + -- @realm server + -- stylua: ignore + local delay_round, delay_length = hook.Run("TTTDelayRoundStartForVote") - if delay_round then - delay_length = delay_length or 30 + if delay_round then + delay_length = delay_length or 30 - LANG.Msg("round_voting", {num = delay_length}) + LANG.Msg("round_voting", { num = delay_length }) - timer.Create("delayedprep", delay_length, 1, PrepareRound) + timer.Create("delayedprep", delay_length, 1, PrepareRound) - return - end + return + end - CleanUp() + CleanUp() - GAMEMODE.roundCount = GAMEMODE.roundCount + 1 + GAMEMODE.roundCount = GAMEMODE.roundCount + 1 - GAMEMODE.MapWin = WIN_NONE - GAMEMODE.AwardedCredits = false - GAMEMODE.AwardedCreditsDead = 0 + GAMEMODE.MapWin = WIN_NONE + GAMEMODE.AwardedCredits = false + GAMEMODE.AwardedCreditsDead = 0 - events.Reset() + events.Reset() - -- Update damage scaling - KARMA.RoundPrepare() + -- Update damage scaling + KARMA.RoundPrepare() - -- New look. Random if no forced model set - if cvPreferMapModels:GetBool() and GAMEMODE.force_plymodel and GAMEMODE.force_plymodel ~= "" then - GAMEMODE.playermodel = GAMEMODE.force_plymodel - elseif cvSelectModelPerRound:GetBool() then - GAMEMODE.playermodel = playermodels.GetRandomPlayerModel() - end + -- New look. Random if no forced model set + if + cvPreferMapModels:GetBool() + and GAMEMODE.force_plymodel + and GAMEMODE.force_plymodel ~= "" + then + GAMEMODE.playermodel = GAMEMODE.force_plymodel + elseif cvSelectModelPerRound:GetBool() then + GAMEMODE.playermodel = playermodels.GetRandomPlayerModel() + end - --- - -- @realm server - GAMEMODE.playercolor = hook.Run("TTTPlayerColor", GAMEMODE.playermodel) + --- + -- @realm server + -- stylua: ignore + GAMEMODE.playercolor = hook.Run("TTTPlayerColor", GAMEMODE.playermodel) - if CheckForAbort() then return end + if CheckForAbort() then + return + end - -- Schedule round start - local ptime = preptime:GetInt() + -- Schedule round start + local ptime = preptime:GetInt() - if GAMEMODE.FirstRound then - ptime = firstpreptime:GetInt() + if GAMEMODE.FirstRound then + ptime = firstpreptime:GetInt() - GAMEMODE.FirstRound = false - end + GAMEMODE.FirstRound = false + end - -- remove decals - util.ClearDecals() + -- remove decals + util.ClearDecals() - -- Piggyback on "round end" time global var to show end of phase timer - SetRoundEnd(CurTime() + ptime) + -- Piggyback on "round end" time global var to show end of phase timer + SetRoundEnd(CurTime() + ptime) - timer.Simple(1, function() - SetRoundEnd(CurTime() + ptime - 1) - end) + timer.Simple(1, function() + SetRoundEnd(CurTime() + ptime - 1) + end) - timer.Create("prep2begin", ptime, 1, BeginRound) + timer.Create("prep2begin", ptime, 1, BeginRound) - -- Mute for a second around traitor selection, to counter a dumb exploit - -- related to traitor's mics cutting off for a second when they're selected. - timer.Create("selectmute", ptime - 1, 1, function() - MuteForRestart(true) - end) + -- Mute for a second around traitor selection, to counter a dumb exploit + -- related to traitor's mics cutting off for a second when they're selected. + timer.Create("selectmute", ptime - 1, 1, function() + MuteForRestart(true) + end) - LANG.Msg("round_begintime", {num = ptime}) + LANG.Msg("round_begintime", { num = ptime }) - SetRoundState(ROUND_PREP) + SetRoundState(ROUND_PREP) - -- Undo the roundrestart mute, though they will once again be muted for the - -- selectmute timer. - timer.Create("restartmute", 1, 1, function() - MuteForRestart(false) - end) + -- Undo the roundrestart mute, though they will once again be muted for the + -- selectmute timer. + timer.Create("restartmute", 1, 1, function() + MuteForRestart(false) + end) - net.Start("TTT_ClearClientState") - net.Broadcast() + net.Start("TTT_ClearClientState") + net.Broadcast() - -- In case client's cleanup fails, make client set all players to innocent role - timer.Simple(1, SendRoleReset) + -- In case client's cleanup fails, make client set all players to innocent role + timer.Simple(1, SendRoleReset) - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - ply:SetTargetPlayer(nil) - ply:ResetRoundDeathCounter() - ply:SetActiveInRound(false) + ply:SetTargetPlayer(nil) + ply:ResetRoundDeathCounter() + ply:SetActiveInRound(false) - ply:CancelRevival(nil, true) - ply:SendRevivalReason(nil) - end + ply:CancelRevival(nil, true) + ply:SendRevivalReason(nil) - --- - -- Tell hooks and map we started prep - -- @realm server - hook.Run("TTTPrepareRound") + ply:ResetItemAndWeaponCache() + end - ents.TTT.TriggerRoundStateOutputs(ROUND_PREP) + --- + -- Tell hooks and map we started prep + -- @realm server + -- stylua: ignore + hook.Run("TTTPrepareRound") + + ents.TTT.TriggerRoundStateOutputs(ROUND_PREP) end local function TraitorSorting(a, b) - return a and b and a:upper() < b:upper() + return a and b and a:upper() < b:upper() end --- -- Tells the Traitors about their team mates -- @realm server function TellTraitorsAboutTraitors() - local traitornicks = {} - local plys = player.GetAll() + local traitornicks = {} + local plys = player.GetAll() - for i = 1, #plys do - local v = plys[i] + for i = 1, #plys do + local v = plys[i] - if v:GetTeam() ~= TEAM_TRAITOR then continue end + if v:GetTeam() ~= TEAM_TRAITOR then + continue + end - traitornicks[#traitornicks + 1] = v:Nick() - end + traitornicks[#traitornicks + 1] = v:Nick() + end - for i = 1, #plys do - local v = plys[i] + for i = 1, #plys do + local v = plys[i] - if v:GetTeam() ~= TEAM_TRAITOR then continue end + if v:GetTeam() ~= TEAM_TRAITOR then + continue + end - local tmp = table.Copy(traitornicks) + local tmp = table.Copy(traitornicks) - --- - -- @realm server - local shouldShow = hook.Run("TTT2TellTraitors", tmp, v) + --- + -- @realm server + -- stylua: ignore + local shouldShow = hook.Run("TTT2TellTraitors", tmp, v) - if shouldShow == false or tmp == nil or #tmp == 0 then continue end + if shouldShow == false or tmp == nil or #tmp == 0 then + continue + end - if #tmp == 1 then - LANG.Msg(v, "round_traitors_one", nil, MSG_MSTACK_ROLE) + if #tmp == 1 then + LANG.Msg(v, "round_traitors_one", nil, MSG_MSTACK_ROLE) - return - end + return + end - if #tmp >= 3 then - table.sort(tmp, TraitorSorting) - end + if #tmp >= 3 then + table.sort(tmp, TraitorSorting) + end - local names = "" + local names = "" - for k = 1, #tmp do - local name = tmp[k] - if name == v:Nick() then continue end + for k = 1, #tmp do + local name = tmp[k] + if name == v:Nick() then + continue + end - names = names .. name .. ", " - end + names = names .. name .. ", " + end - names = string.sub(names, 1, -3) + names = string.sub(names, 1, -3) - LANG.Msg(v, "round_traitors_more", {names = names}, MSG_MSTACK_ROLE) - end + LANG.Msg(v, "round_traitors_more", { names = names }, MSG_MSTACK_ROLE) + end end local function InitRoundEndTime() - -- Init round values - local endtime = CurTime() + roundtime:GetInt() * 60 + -- Init round values + local endtime = CurTime() + roundtime:GetInt() * 60 - if HasteMode() then - endtime = CurTime() + haste_starting:GetInt() * 60 + if HasteMode() then + endtime = CurTime() + haste_starting:GetInt() * 60 - -- this is a "fake" time shown to innocents, showing the end time if no - -- one would have been killed, it has no gameplay effect - SetGlobalFloat("ttt_haste_end", endtime) - end + -- this is a "fake" time shown to innocents, showing the end time if no + -- one would have been killed, it has no gameplay effect + SetGlobalFloat("ttt_haste_end", endtime) + end - SetRoundEnd(endtime) + SetRoundEnd(endtime) end --- @@ -1143,85 +1209,92 @@ end -- @realm server -- @internal function BeginRound() - GAMEMODE:SyncGlobals() + GAMEMODE:SyncGlobals() - if CheckForAbort() then return end + if CheckForAbort() then + return + end - InitRoundEndTime() + InitRoundEndTime() - if CheckForAbort() then return end + if CheckForAbort() then + return + end - -- Respawn dumb people who died during prep - entspawn.SpawnPlayers(true) + -- Respawn dumb people who died during prep + entspawn.SpawnPlayers(true) - -- Remove their ragdolls - ents.TTT.RemoveRagdolls(true) + -- Remove their ragdolls + ents.TTT.RemoveRagdolls(true) - -- remove decals - util.ClearDecals() + -- remove decals + util.ClearDecals() - -- Check for low-karma players that weren't banned on round end - KARMA.RoundBegin() + -- Check for low-karma players that weren't banned on round end + KARMA.RoundBegin() - if CheckForAbort() then return end + if CheckForAbort() then + return + end - -- Select traitors & co. This is where things really start so we can't abort - -- anymore. - roleselection.SelectRoles() + -- Select traitors & co. This is where things really start so we can't abort + -- anymore. + roleselection.SelectRoles() - LANG.Msg("round_selected") + LANG.Msg("round_selected") - -- Edge case where a player joins just as the round starts and is picked as - -- traitor, but for whatever reason does not get the traitor state msg. So - -- re-send after a second just to make sure everyone is getting it. - -- TODO improve - timer.Simple(1, SendFullStateUpdate) - timer.Simple(10, SendFullStateUpdate) + -- Edge case where a player joins just as the round starts and is picked as + -- traitor, but for whatever reason does not get the traitor state msg. So + -- re-send after a second just to make sure everyone is getting it. + -- TODO improve + timer.Simple(1, SendFullStateUpdate) + timer.Simple(10, SendFullStateUpdate) - -- Give the StateUpdate messages ample time to arrive - timer.Simple(1.5, TellTraitorsAboutTraitors) - timer.Simple(2.5, ShowRoundStartPopup) + -- Give the StateUpdate messages ample time to arrive + timer.Simple(1.5, TellTraitorsAboutTraitors) + timer.Simple(2.5, ShowRoundStartPopup) - -- Start the win condition check timer - StartWinChecks() - StartNameChangeChecks() + -- Start the win condition check timer + StartWinChecks() + StartNameChangeChecks() - timer.Create("selectmute", 1, 1, function() - MuteForRestart(false) - end) + timer.Create("selectmute", 1, 1, function() + MuteForRestart(false) + end) - GAMEMODE.DamageLog = {} - GAMEMODE.RoundStartTime = CurTime() + GAMEMODE.DamageLog = {} + GAMEMODE.RoundStartTime = CurTime() - -- Sound start alarm - SetRoundState(ROUND_ACTIVE) - LANG.Msg("round_started") - ServerLog("Round proper has begun...\n") + -- Sound start alarm + SetRoundState(ROUND_ACTIVE) + LANG.Msg("round_started") + ServerLog("Round proper has begun...\n") - events.Trigger(EVENT_SELECTED) + events.Trigger(EVENT_SELECTED) - GAMEMODE:UpdatePlayerLoadouts() -- needs to happen when round_active + GAMEMODE:UpdatePlayerLoadouts() -- needs to happen when round_active - ARMOR:InitPlayerArmor() + ARMOR:InitPlayerArmor() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - ply:ResetRoundDeathCounter() + ply:ResetRoundDeathCounter() - -- a player should be considered "was active in round" if they received a role - ply:SetActiveInRound(ply:Alive() and ply:IsTerror()) - end + -- a player should be considered "was active in round" if they received a role + ply:SetActiveInRound(ply:Alive() and ply:IsTerror()) + end - credits.ResetTeamStates() + credits.ResetTeamStates() - --- - -- @realm server - hook.Run("TTTBeginRound") + --- + -- @realm server + -- stylua: ignore + hook.Run("TTTBeginRound") - ents.TTT.TriggerRoundStateOutputs(ROUND_BEGIN) + ents.TTT.TriggerRoundStateOutputs(ROUND_ACTIVE) end --- @@ -1230,34 +1303,32 @@ end -- @realm server -- @internal function PrintResultMessage(result) - ServerLog("Round ended.\n") - - if result == WIN_TIMELIMIT then - LANG.Msg("win_time") - ServerLog("Result: timelimit reached, traitors lose.\n") - - return - elseif result == WIN_NONE or result == TEAM_NONE then - LANG.Msg("win_nones") - ServerLog("Result: No-one wins.\n") - - return - else - if isnumber(result) then - if result == WIN_TRAITOR then - result = TEAM_TRAITOR - elseif result == WIN_INNOCENT then - result = TEAM_INNOCENT - end - end - - LANG.Msg("win_" .. result) -- TODO translation - ServerLog("Result: " .. result .. " wins.\n") -- TODO translation - - return - end - - ServerLog("Result: unknown victory condition!\n") + ServerLog("Round ended.\n") + + if result == WIN_TIMELIMIT then + LANG.Msg("win_time") + ServerLog("Result: timelimit reached, traitors lose.\n") + + return + elseif result == WIN_NONE or result == TEAM_NONE then + LANG.Msg("win_nones") + ServerLog("Result: No-one wins.\n") + + return + else + if isnumber(result) then + if result == WIN_TRAITOR then + result = TEAM_TRAITOR + elseif result == WIN_INNOCENT then + result = TEAM_INNOCENT + end + end + + LANG.Msg("win_" .. result) -- TODO translation + ServerLog("Result: " .. result .. " wins.\n") -- TODO translation + + return + end end --- @@ -1265,24 +1336,25 @@ end -- @realm server -- @internal function CheckForMapSwitch() - -- Check for mapswitch - local roundsLeft = math.max(0, GetGlobalInt("ttt_rounds_left", 6) - 1) + -- Check for mapswitch + local roundsLeft = math.max(0, GetGlobalInt("ttt_rounds_left", 6) - 1) - SetGlobalInt("ttt_rounds_left", roundsLeft) + SetGlobalInt("ttt_rounds_left", roundsLeft) - local timeLeft = math.max(0, time_limit:GetInt() * 60 - CurTime()) - local nextmap = string.upper(game.GetMapNext()) + local timeLeft = math.max(0, time_limit:GetInt() * 60 - CurTime()) + local nextmap = string.upper(game.GetMapNext()) - if roundsLeft <= 0 or timeLeft <= 0 then - timer.Stop("end2prep") - SetRoundEnd(CurTime()) + if roundsLeft <= 0 or timeLeft <= 0 then + timer.Stop("end2prep") + SetRoundEnd(CurTime()) - --- - -- @realm server - hook.Run("TTT2LoadNextMap", nextmap, roundsLeft, timeLeft) - else - LANG.Msg("limit_left", {num = roundsLeft, time = math.ceil(timeLeft / 60)}) - end + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2LoadNextMap", nextmap, roundsLeft, timeLeft) + else + LANG.Msg("limit_left", { num = roundsLeft, time = math.ceil(timeLeft / 60) }) + end end --- @@ -1291,54 +1363,55 @@ end -- @realm server -- @internal function EndRound(result) - PrintResultMessage(result) + PrintResultMessage(result) - KARMA.RoundEnd() + KARMA.RoundEnd() - events.Trigger(EVENT_FINISH, result) + events.Trigger(EVENT_FINISH, result) - SetRoundState(ROUND_POST) + SetRoundState(ROUND_POST) - local ptime = math.max(5, posttime:GetInt()) + local ptime = math.max(5, posttime:GetInt()) - LANG.Msg("win_showreport", {num = ptime}) - timer.Create("end2prep", ptime, 1, PrepareRound) + LANG.Msg("win_showreport", { num = ptime }) + timer.Create("end2prep", ptime, 1, PrepareRound) - -- Piggyback on "round end" time global var to show end of phase timer - SetRoundEnd(CurTime() + ptime) + -- Piggyback on "round end" time global var to show end of phase timer + SetRoundEnd(CurTime() + ptime) - timer.Create("restartmute", ptime - 1, 1, function() - MuteForRestart(true) - end) + timer.Create("restartmute", ptime - 1, 1, function() + MuteForRestart(true) + end) - -- Stop checking for wins - StopWinChecks() + -- Stop checking for wins + StopWinChecks() - -- send each client the role setup, reveal every player - local rlsList = roles.GetList() + -- send each client the role setup, reveal every player + local rlsList = roles.GetList() - for i = 1, #rlsList do - SendSubRoleList(rlsList[i].index) - end + for i = 1, #rlsList do + SendSubRoleList(rlsList[i].index) + end - -- We may need to start a timer for a mapswitch, or start a vote - if GetGlobalBool("ttt_session_limits_enabled") then - CheckForMapSwitch() - end + -- We may need to start a timer for a mapswitch, or start a vote + if GetGlobalBool("ttt_session_limits_enabled") then + CheckForMapSwitch() + end - events.UpdateScoreboard() + events.UpdateScoreboard() - -- send the clients the round log, players will be shown the report - events.StreamToClients() + -- send the clients the round log, players will be shown the report + events.StreamToClients() - --- - -- server plugins might want to start a map vote here or something - -- these hooks are not used by TTT internally - -- possible incompatibility for other addons - -- @realm server - hook.Run("TTTEndRound", result) + --- + -- server plugins might want to start a map vote here or something + -- these hooks are not used by TTT internally + -- possible incompatibility for other addons + -- @realm server + -- stylua: ignore + hook.Run("TTTEndRound", result) - ents.TTT.TriggerRoundStateOutputs(ROUND_POST, result) + ents.TTT.TriggerRoundStateOutputs(ROUND_POST, result) end --- @@ -1347,35 +1420,45 @@ end -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:OnReloaded function GM:OnReloaded() - -- load all roles - roles.OnLoaded() + -- load all roles + roles.OnLoaded() + + -- reload entity spawns from file + entspawnscript.OnLoaded() - -- reload entity spawns from file - entspawnscript.OnLoaded() + -- load all items + items.OnLoaded() - -- load all items - items.OnLoaded() + -- load all HUDs + huds.OnLoaded() - -- load all HUDs - huds.OnLoaded() + -- load all HUD elements + hudelements.OnLoaded() - -- load all HUD elements - hudelements.OnLoaded() + -- reload everything from the playermodels + playermodels.Initialize() - -- reload everything from the playermodels - playermodels.Initialize() + -- set the default random playermodel + self.playermodel = playermodels.GetRandomPlayerModel() + self.playercolor = COLOR_WHITE - -- set the default random playermodel - self.playermodel = playermodels.GetRandomPlayerModel() - self.playercolor = COLOR_WHITE + -- register synced player variables + player.RegisterSettingOnServer("enable_dynamic_fov", "bool") - --- - -- @realm shared - hook.Run("TTT2RolesLoaded") + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2RolesLoaded") - --- - -- @realm shared - hook.Run("TTT2BaseRoleInit") + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2BaseRoleInit") + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2FinishedLoading") end --- @@ -1384,35 +1467,36 @@ end -- @hook -- @realm server function GM:MapTriggeredEnd(wintype) - if wintype ~= WIN_NONE then - self.MapWin = wintype - else - -- print alert and hint for contact - print("\n\nCalled hook 'GM:MapTriggeredEnd' with incorrect wintype\n\n") - end + if wintype ~= WIN_NONE then + self.MapWin = wintype + else + -- print alert and hint for contact + ErrorNoHaltWithStack("\n\nCalled hook 'GM:MapTriggeredEnd' with incorrect wintype\n\n") + end end hook.Add("PlayerAuthed", "TTT2PlayerAuthedSharedHook", function(ply, steamid, uniqueid) - net.Start("TTT2PlayerAuthedShared") - net.WriteString(not ply:IsBot() and util.SteamIDTo64(steamid) or "") - net.WriteString((ply and ply:Nick()) or "UNKNOWN") - net.Broadcast() + net.Start("TTT2PlayerAuthedShared") + net.WriteString(not ply:IsBot() and util.SteamIDTo64(steamid) or "") + net.WriteString((ply and ply:Nick()) or "UNKNOWN") + net.Broadcast() end) local function ttt_roundrestart(ply, command, args) - --- - -- ply is nil on dedicated server console - -- @realm server - if not IsValid(ply) or ply:IsAdmin() or hook.Run("TTT2AdminCheck", ply) or cvars.Bool("sv_cheats", 0) then - LANG.Msg("round_restart") - - StopRoundTimers() - - -- do prep - PrepareRound() - else - ply:PrintMessage(HUD_PRINTCONSOLE, "You must be a GMod Admin or SuperAdmin on the server to use this command, or sv_cheats must be enabled.") - end + --- + -- ply is nil on dedicated server console + -- @realm server + -- stylua: ignore + if not IsValid(ply) or ply:IsAdmin() or hook.Run("TTT2AdminCheck", ply) or cvars.Bool("sv_cheats", 0) then + LANG.Msg("round_restart") + + StopRoundTimers() + + -- do prep + PrepareRound() + else + ply:PrintMessage(HUD_PRINTCONSOLE, "You must be a GMod Admin or SuperAdmin on the server to use this command, or sv_cheats must be enabled.") + end end concommand.Add("ttt_roundrestart", ttt_roundrestart) @@ -1421,26 +1505,31 @@ concommand.Add("ttt_roundrestart", ttt_roundrestart) -- @param Player ply -- @realm server function ShowVersion(ply) - local text = Format("This is [TTT2] Trouble in Terrorist Town 2 (Advanced Update) - by the TTT2 Dev Team (v%s)\n", GAMEMODE.Version) - - if IsValid(ply) then - ply:PrintMessage(HUD_PRINTNOTIFY, text) - else - Msg(text) - end + local text = Format( + "This is [TTT2] Trouble in Terrorist Town 2 (Advanced Update) - by the TTT2 Dev Team (v%s)\n", + GAMEMODE.Version + ) + + if IsValid(ply) then + ply:PrintMessage(HUD_PRINTNOTIFY, text) + else + Msg(text) + end end concommand.Add("ttt_version", ShowVersion) local function ttt_toggle_newroles(ply) - if not ply:IsAdmin() then return end + if not ply:IsAdmin() then + return + end - local b = not ttt_newroles_enabled:GetBool() + local b = not ttt_newroles_enabled:GetBool() - ttt_newroles_enabled:SetBool(b) + ttt_newroles_enabled:SetBool(b) - local word = b and "enabled" or "disabled" + local word = b and "enabled" or "disabled" - ply:PrintMessage(HUD_PRINTNOTIFY, "You " .. word .. " the new roles for TTT!") + ply:PrintMessage(HUD_PRINTNOTIFY, "You " .. word .. " the new roles for TTT!") end concommand.Add("ttt_toggle_newroles", ttt_toggle_newroles) @@ -1449,9 +1538,7 @@ concommand.Add("ttt_toggle_newroles", ttt_toggle_newroles) -- TTT2 globals are synced. -- @hook -- @realm server -function GM:TTT2SyncGlobals() - -end +function GM:TTT2SyncGlobals() end --- -- This hook is run before @{GM:TTTCheckForWin} and should be used for custom winconditions in @@ -1459,9 +1546,7 @@ end -- @return nil|string The team identifier of the winning team -- @hook -- @realm server -function GM:TTT2PreWinChecker() - -end +function GM:TTT2PreWinChecker() end --- -- Called after a player changed their nickname. @@ -1469,9 +1554,7 @@ end -- @return nil|boolean Return true to prevent the kick of the player -- @hook -- @realm server -function GM:TTTNameChangeKick(ply) - -end +function GM:TTTNameChangeKick(ply) end --- -- A hook to prevent a traitor from receiving the list of their mates. @@ -1481,9 +1564,7 @@ end -- name of their mates -- @hook -- @realm server -function GM:TTT2TellTraitors(traitorNicks, ply) - -end +function GM:TTT2TellTraitors(traitorNicks, ply) end --- -- Can be used to modify the table of teams with alive players. This hook is @@ -1493,9 +1574,7 @@ end -- @param table alives The table of teams which have at least one player still alive -- @hook -- @realm server -function GM:TTT2ModifyWinningAlives(alives) - -end +function GM:TTT2ModifyWinningAlives(alives) end --- -- Called if CheckForMapSwitch has determined that a map change should happen. @@ -1506,13 +1585,13 @@ end -- @hook -- @realm server function GM:TTT2LoadNextMap(nextmap, roundsLeft, timeLeft) - if roundsLeft <= 0 then - LANG.Msg("limit_round", {mapname = nextmap}) - elseif timeLeft <= 0 then - LANG.Msg("limit_time", {mapname = nextmap}) - end + if roundsLeft <= 0 then + LANG.Msg("limit_round", { mapname = nextmap }) + elseif timeLeft <= 0 then + LANG.Msg("limit_time", { mapname = nextmap }) + end - timer.Simple(map_switch_delay:GetFloat(), game.LoadNextMap) + timer.Simple(map_switch_delay:GetFloat(), game.LoadNextMap) end --- @@ -1523,7 +1602,7 @@ end -- @hook -- @realm server function GM:TTTDelayRoundStartForVote() - return false + return false end --- @@ -1533,64 +1612,73 @@ end -- @hook -- @realm server function GM:TTTCheckForWin() - if not ttt_dbgwin or ttt_dbgwin:GetBool() then - return WIN_NONE - end - - if self.MapWin ~= WIN_NONE then -- a role wins - local mapWin = self.MapWin - - self.MapWin = WIN_NONE - - return mapWin - end - - local aliveTeams = {} - local plys = player.GetAll() - - for i = 1, #plys do - local ply = plys[i] - local team = ply:GetTeam() - - if (ply:IsTerror() or ply:IsBlockingRevival()) and not ply:GetSubRoleData().preventWin and team ~= TEAM_NONE then - aliveTeams[#aliveTeams + 1] = team - end - - -- special case: The revival blocks the round end - if ply:GetRevivalBlockMode() == REVIVAL_BLOCK_ALL then - return WIN_NONE - end - end - - --- - -- @realm server - hook.Run("TTT2ModifyWinningAlives", aliveTeams) - - local checkedTeams = {} - local b = 0 - - for i = 1, #aliveTeams do - local team = aliveTeams[i] - - if team == TEAM_NONE then continue end - - if not checkedTeams[team] or TEAMS[team].alone then - -- prevent win of custom role -> maybe own win conditions - b = b + 1 - - -- check - checkedTeams[team] = true - end - - -- if 2 teams alive - if b == 2 then break end - end - - if b > 1 then -- if >= 2 teams alive: no one wins - return WIN_NONE -- early out - elseif b == 1 then -- just 1 team is alive - return aliveTeams[1] - else -- rare case: nobody is alive, e.g. because of an explosion - return TEAM_NONE -- none_win - end + if not ttt_dbgwin or ttt_dbgwin:GetBool() then + return WIN_NONE + end + + if self.MapWin ~= WIN_NONE then -- a role wins + local mapWin = self.MapWin + + self.MapWin = WIN_NONE + + return mapWin + end + + local aliveTeams = {} + local plys = player.GetAll() + + for i = 1, #plys do + local ply = plys[i] + local team = ply:GetTeam() + + if + (ply:IsTerror() or ply:IsBlockingRevival()) + and not ply:GetSubRoleData().preventWin + and team ~= TEAM_NONE + then + aliveTeams[#aliveTeams + 1] = team + end + + -- special case: The revival blocks the round end + if ply:GetRevivalBlockMode() == REVIVAL_BLOCK_ALL then + return WIN_NONE + end + end + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2ModifyWinningAlives", aliveTeams) + + local checkedTeams = {} + local b = 0 + + for i = 1, #aliveTeams do + local team = aliveTeams[i] + + if team == TEAM_NONE then + continue + end + + if not checkedTeams[team] or TEAMS[team].alone then + -- prevent win of custom role -> maybe own win conditions + b = b + 1 + + -- check + checkedTeams[team] = true + end + + -- if 2 teams alive + if b == 2 then + break + end + end + + if b > 1 then -- if >= 2 teams alive: no one wins + return WIN_NONE -- early out + elseif b == 1 then -- just 1 team is alive + return aliveTeams[1] + else -- rare case: nobody is alive, e.g. because of an explosion + return TEAM_NONE -- none_win + end end diff --git a/gamemodes/terrortown/gamemode/server/sv_network_sync.lua b/gamemodes/terrortown/gamemode/server/sv_network_sync.lua index d1ffffa30..227942ff5 100644 --- a/gamemodes/terrortown/gamemode/server/sv_network_sync.lua +++ b/gamemodes/terrortown/gamemode/server/sv_network_sync.lua @@ -47,29 +47,32 @@ util.AddNetworkString(ttt2net.NETMSG_REQUEST_FULL_STATE_UPDATE) -- @realm server -- @internal local function SyncNWVarWithPath(path, meta, nwent, nwkey) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - local metadata = meta or table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) - local value = ttt2net.Get(tmpPath) + local metadata = meta or table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) + local value = ttt2net.Get(tmpPath) - if metadata.type == "int" then - assert(not metadata.unsigned, "[TTT2NET] Unsigned numbers are not supported by NWVars! Change the metadata information!") + if metadata.type == "int" then + assert( + not metadata.unsigned, + "[TTT2NET] Unsigned numbers are not supported by NWVars! Change the metadata information!" + ) - nwent:SetNWInt(nwkey, value) - elseif metadata.type == "bool" then - nwent:SetNWBool(nwkey, value) - elseif metadata.type == "float" then - nwent:SetNWFloat(nwkey, value) - else - nwent:SetNWString(nwkey, value) - end + nwent:SetNWInt(nwkey, value) + elseif metadata.type == "bool" then + nwent:SetNWBool(nwkey, value) + elseif metadata.type == "float" then + nwent:SetNWFloat(nwkey, value) + else + nwent:SetNWString(nwkey, value) + end end --- @@ -80,11 +83,15 @@ end -- @return boolean Returns true if they are equal false if not -- @realm server function ttt2net.NetworkMetaDataTableEqual(meta1, meta2) - if meta1 == nil and meta2 == nil then - return true - end + if meta1 == nil and meta2 == nil then + return true + end - return istable(meta1) and istable(meta2) and meta1.type == meta2.type and meta1.bits == meta2.bits and meta1.unsigned == meta2.unsigned + return istable(meta1) + and istable(meta2) + and meta1.type == meta2.type + and meta1.bits == meta2.bits + and meta1.unsigned == meta2.unsigned end --- @@ -103,29 +110,31 @@ end -- @param[opt] table metadata The metadata table -- @realm server function ttt2net.SetMetaData(path, metadata) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Retrieve the stored metadata - local storedMetaData = table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) + -- Retrieve the stored metadata + local storedMetaData = table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) - -- If the metadata is already stored, do nothing - if ttt2net.NetworkMetaDataTableEqual(storedMetaData, metadata) then return end + -- If the metadata is already stored, do nothing + if ttt2net.NetworkMetaDataTableEqual(storedMetaData, metadata) then + return + end - -- Insert data to metadata table - table.SetWithPath(ttt2net.dataStoreMetadata, tmpPath, metadata) + -- Insert data to metadata table + table.SetWithPath(ttt2net.dataStoreMetadata, tmpPath, metadata) - -- Set value of the data field to nil - table.SetWithPath(ttt2net.dataStore, tmpPath, nil) + -- Set value of the data field to nil + table.SetWithPath(ttt2net.dataStore, tmpPath, nil) - -- Sync new meta information to all clients that are already connected and received a full state! - ttt2net.SendMetaDataUpdate(tmpPath) + -- Sync new meta information to all clients that are already connected and received a full state! + ttt2net.SendMetaDataUpdate(tmpPath) end --- @@ -136,19 +145,19 @@ end -- @param[opt] table metadata The metadata table -- @realm server function ttt2net.SetMetaDataGlobal(path, metadata) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table - table.insert(tmpPath, 1, "global") + -- Add the prefix for the correct table + table.insert(tmpPath, 1, "global") - ttt2net.SetMetaData(tmpPath, metadata) + ttt2net.SetMetaData(tmpPath, metadata) end --- @@ -160,20 +169,20 @@ end -- @param Player|Entity ply The player/entity to set the value on -- @realm server function ttt2net.SetMetaDataOnPlayer(path, metadata, ply) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table with the specific player - table.insert(tmpPath, 1, "players") - table.insert(tmpPath, 2, ply:EntIndex()) + -- Add the prefix for the correct table with the specific player + table.insert(tmpPath, 1, "players") + table.insert(tmpPath, 2, ply:EntIndex()) - ttt2net.SetMetaData(tmpPath, metadata) + ttt2net.SetMetaData(tmpPath, metadata) end --- @@ -193,53 +202,57 @@ end -- @param[opt] Entity client The client/entity to set this value for (overrides the default value) -- @realm server function ttt2net.Set(path, meta, value, client) - local tmpPath - local clientId = client and client:EntIndex() or nil + local tmpPath + local clientId = client and client:EntIndex() or nil - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - local metadata = meta or table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) + local metadata = meta or table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) - assert(metadata, "[TTT2NET] Set() called but no metadata entry or metadata parameter found!") + assert(metadata, "[TTT2NET] Set() called but no metadata entry or metadata parameter found!") - -- Set the meta data, this will only send / update if the meta data has changed - ttt2net.SetMetaData(tmpPath, metadata) + -- Set the meta data, this will only send / update if the meta data has changed + ttt2net.SetMetaData(tmpPath, metadata) - -- Add the identifier for the default value for all clients or the clientId if specified - tmpPath[#tmpPath + 1] = clientId or "default" + -- Add the identifier for the default value for all clients or the clientId if specified + tmpPath[#tmpPath + 1] = clientId or "default" - -- Skip if the value is already present or nil - if table.GetWithPath(ttt2net.dataStore, tmpPath) == value then return end + -- Skip if the value is already present or nil + if table.GetWithPath(ttt2net.dataStore, tmpPath) == value then + return + end - -- Save the value - table.SetWithPath(ttt2net.dataStore, tmpPath, value) + -- Save the value + table.SetWithPath(ttt2net.dataStore, tmpPath, value) - -- Remove last key eg. "default" or the player id, as this does not belong to the base path - tmpPath[#tmpPath] = nil + -- Remove last key eg. "default" or the player id, as this does not belong to the base path + tmpPath[#tmpPath] = nil - -- Sync the new value - ttt2net.SendDataUpdate(tmpPath, client) + -- Sync the new value + ttt2net.SendDataUpdate(tmpPath, client) - -- Sync all registered nwvars to the new value - -- Only attempt to sync, if the path leads to a player, which NWVars can be synced to. - if #path < 2 or path[1] ~= "players" then return end + -- Sync all registered nwvars to the new value + -- Only attempt to sync, if the path leads to a player, which NWVars can be synced to. + if #path < 2 or path[1] ~= "players" then + return + end - -- Get the entity associated to the path - local ent = Entity(path[2]) + -- Get the entity associated to the path + local ent = Entity(path[2]) - assert(IsEntity(ent), "[TTT2NET] Set() used on a path with an invalid entity!") + assert(IsEntity(ent), "[TTT2NET] Set() used on a path with an invalid entity!") - -- Get all registered nwvars - local nwvars = table.GetWithPath(ttt2net.dataSyncedNWVars, tmpPath) or {} + -- Get all registered nwvars + local nwvars = table.GetWithPath(ttt2net.dataSyncedNWVars, tmpPath) or {} - for i = 1, #nwvars do - SyncNWVarWithPath(tmpPath, metadata, ent, nwvars[i]) - end + for i = 1, #nwvars do + SyncNWVarWithPath(tmpPath, metadata, ent, nwvars[i]) + end end --- @@ -250,53 +263,59 @@ end -- @param table path The path to clear out all overrides on. -- @realm server function ttt2net.RemoveOverrides(path) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - local currentDataTable = ttt2net.dataStore + local currentDataTable = ttt2net.dataStore - if currentDataTable == nil then return end + if currentDataTable == nil then + return + end - -- Traverse the path in the ttt2net.dataStore table. - -- This will be done until the second last table is reached, so we still have a reference to the parent table. - for i = 1, (#tmpPath - 1) do - currentDataTable = currentDataTable[tmpPath[i]] + -- Traverse the path in the ttt2net.dataStore table. + -- This will be done until the second last table is reached, so we still have a reference to the parent table. + for i = 1, (#tmpPath - 1) do + currentDataTable = currentDataTable[tmpPath[i]] - if currentDataTable == nil then return end - end + if currentDataTable == nil then + return + end + end - local lastKey = tmpPath[#tmpPath] + local lastKey = tmpPath[#tmpPath] - -- If the table does not have the last key, then exit - if currentDataTable[lastKey] == nil then return end + -- If the table does not have the last key, then exit + if currentDataTable[lastKey] == nil then + return + end - local defaultValue = currentDataTable[lastKey].default + local defaultValue = currentDataTable[lastKey].default - -- Collect all players that had an override value set - local playerIds = table.GetKeys(currentDataTable[lastKey]) - local receivers = {} + -- Collect all players that had an override value set + local playerIds = table.GetKeys(currentDataTable[lastKey]) + local receivers = {} - -- Check all keys if they are a valid EntIndex and resolve them to the player instance - for i = 1, #playerIds do - local ply = Entity(playerIds[i]) - -- ply will be nil if no entity is found + -- Check all keys if they are a valid EntIndex and resolve them to the player instance + for i = 1, #playerIds do + local ply = Entity(playerIds[i]) + -- ply will be nil if no entity is found - if IsEntity(ply) and ply:IsPlayer() then - receivers[#receivers + 1] = ply - end - end + if IsEntity(ply) and ply:IsPlayer() then + receivers[#receivers + 1] = ply + end + end - -- Replace the existing table with a new table that just contains the default value - currentDataTable[lastKey] = { default = defaultValue } + -- Replace the existing table with a new table that just contains the default value + currentDataTable[lastKey] = { default = defaultValue } - -- Send update to all players that previously had an override - ttt2net.SendDataUpdate(tmpPath, receivers) + -- Send update to all players that previously had an override + ttt2net.SendDataUpdate(tmpPath, receivers) end --- @@ -307,19 +326,19 @@ end -- @param table path The path to clear out all overrides on. -- @realm server function ttt2net.RemoveOverridesGlobal(path) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table - table.insert(tmpPath, 1, "global") + -- Add the prefix for the correct table + table.insert(tmpPath, 1, "global") - ttt2net.RemoveOverrides(tmpPath) + ttt2net.RemoveOverrides(tmpPath) end --- @@ -331,20 +350,20 @@ end -- @param Player|Entity ply The player/entity object that will be cleared -- @realm server function ttt2net.RemoveOverridesOnPlayer(path, ply) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table with the specific player - table.insert(tmpPath, 1, "players") - table.insert(tmpPath, 2, ply:EntIndex()) + -- Add the prefix for the correct table with the specific player + table.insert(tmpPath, 1, "players") + table.insert(tmpPath, 2, ply:EntIndex()) - ttt2net.RemoveOverrides(tmpPath) + ttt2net.RemoveOverrides(tmpPath) end --- @@ -357,20 +376,20 @@ end -- @return any|nil The value found at the given path -- @realm server function ttt2net.Get(path, client) - local clientId = client and client:EntIndex() or nil - local tmpPath + local clientId = client and client:EntIndex() or nil + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the identifier for the default value for all clients or the clientId if specified - tmpPath[#tmpPath + 1] = clientId or "default" + -- Add the identifier for the default value for all clients or the clientId if specified + tmpPath[#tmpPath + 1] = clientId or "default" - return table.GetWithPath(ttt2net.dataStore, tmpPath) + return table.GetWithPath(ttt2net.dataStore, tmpPath) end --- @@ -383,31 +402,31 @@ end -- @return any|nil The value that a client knows -- @realm server function ttt2net.GetWithOverride(path, client) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - local clientId = client:EntIndex() + local clientId = client:EntIndex() - local overridePath = table.Copy(tmpPath) - overridePath[#overridePath + 1] = clientId + local overridePath = table.Copy(tmpPath) + overridePath[#overridePath + 1] = clientId - local defaultPath = table.Copy(tmpPath) - defaultPath[#defaultPath + 1] = "default" + local defaultPath = table.Copy(tmpPath) + defaultPath[#defaultPath + 1] = "default" - -- Get the overwrite value if available - local overrideValue = table.GetWithPath(ttt2net.dataStore, overridePath) + -- Get the overwrite value if available + local overrideValue = table.GetWithPath(ttt2net.dataStore, overridePath) - if overrideValue ~= nil then - return overrideValue - else - return table.GetWithPath(ttt2net.dataStore, defaultPath) - end + if overrideValue ~= nil then + return overrideValue + else + return table.GetWithPath(ttt2net.dataStore, defaultPath) + end end --- @@ -419,19 +438,19 @@ end -- @return any|nil The value for the given path -- @realm server function ttt2net.GetGlobal(path, client) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table - table.insert(tmpPath, 1, "global") + -- Add the prefix for the correct table + table.insert(tmpPath, 1, "global") - return ttt2net.Get(tmpPath, client) + return ttt2net.Get(tmpPath, client) end --- @@ -444,19 +463,19 @@ end -- @param[opt] Entity client The client/entity to set this value for (overrides the default value) -- @realm server function ttt2net.SetGlobal(path, meta, value, client) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table - table.insert(tmpPath, 1, "global") + -- Add the prefix for the correct table + table.insert(tmpPath, 1, "global") - ttt2net.Set(tmpPath, meta, value, client) + ttt2net.Set(tmpPath, meta, value, client) end --- @@ -470,20 +489,20 @@ end -- @param[opt] Entity client The client/entity to set this value for (as an override for the default value) -- @realm server function ttt2net.SetOnPlayer(path, meta, value, ply, client) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table with the specific player - table.insert(tmpPath, 1, "players") - table.insert(tmpPath, 2, ply:EntIndex()) + -- Add the prefix for the correct table with the specific player + table.insert(tmpPath, 1, "players") + table.insert(tmpPath, 2, ply:EntIndex()) - ttt2net.Set(tmpPath, meta, value, client) + ttt2net.Set(tmpPath, meta, value, client) end --- @@ -496,35 +515,35 @@ end -- @return any|nil The value for the given path -- @realm server function ttt2net.GetOnPlayer(path, ply, client) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table with the specific player - table.insert(tmpPath, 1, "players") - table.insert(tmpPath, 2, ply:EntIndex()) + -- Add the prefix for the correct table with the specific player + table.insert(tmpPath, 1, "players") + table.insert(tmpPath, 2, ply:EntIndex()) - return ttt2net.Get(tmpPath, client) + return ttt2net.Get(tmpPath, client) end --- -- Prints out all ttt2net related tables, for debugging purposes. -- @realm server function ttt2net.Debug() - print("[TTT2NET] Debug:") - print("Initialized clients:") - PrintTable(ttt2net.initializedClients) - print("Synced NWVars:") - PrintTable(ttt2net.dataSyncedNWVars) - print("Meta data table:") - PrintTable(ttt2net.dataStoreMetadata) - print("Data store table:") - PrintTable(ttt2net.dataStore) + Dev(2, "[TTT2NET] Debug:") + Dev(2, "Initialized clients:") + PrintTable(ttt2net.initializedClients) + Dev(2, "Synced NWVars:") + PrintTable(ttt2net.dataSyncedNWVars) + Dev(2, "Meta data table:") + PrintTable(ttt2net.dataStoreMetadata) + Dev(2, "Data store table:") + PrintTable(ttt2net.dataStore) end --- @@ -534,25 +553,25 @@ end -- @param any path The path to send a metadata update for -- @realm server function ttt2net.SendMetaDataUpdate(path) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Check for a metadata entry - local metadata = table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) + -- Check for a metadata entry + local metadata = table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) - -- Write meta data - net.Start(ttt2net.NETMSG_META_UPDATE) + -- Write meta data + net.Start(ttt2net.NETMSG_META_UPDATE) - ttt2net.NetWritePath(tmpPath) - ttt2net.NetWriteMetaData(metadata) + ttt2net.NetWritePath(tmpPath) + ttt2net.NetWriteMetaData(metadata) - net.Send(ttt2net.initializedClients) + net.Send(ttt2net.initializedClients) end --- @@ -567,23 +586,23 @@ end -- @param[opt] table path The current path to the curTable based on the root of the data table -- @realm server function ttt2net.RemoveOverridesForClient(client, curTable, path) - local tmpPath = not istable(path) and { path } or path and table.Copy(path) or {} + local tmpPath = not istable(path) and { path } or path and table.Copy(path) or {} - -- Visit all keys in the current tree - for key in pairs(curTable) do - local nextNode = curTable[key] - local nextPath = table.Copy(tmpPath) - nextPath[#nextPath + 1] = key + -- Visit all keys in the current tree + for key in pairs(curTable) do + local nextNode = curTable[key] + local nextPath = table.Copy(tmpPath) + nextPath[#nextPath + 1] = key - -- Check if we reached a leaf node (a node with metadata) - if nextNode.type then - -- Remove the override from this key - ttt2net.Set(nextPath, nil, nil, client) - else - -- Descend a level deeper - ttt2net.RemoveOverridesForClient(client, curTable[key], nextPath) - end - end + -- Check if we reached a leaf node (a node with metadata) + if nextNode.type then + -- Remove the override from this key + ttt2net.Set(nextPath, nil, nil, client) + else + -- Descend a level deeper + ttt2net.RemoveOverridesForClient(client, curTable[key], nextPath) + end + end end --- @@ -592,15 +611,15 @@ end -- @param Entity client The client to remove -- @realm server function ttt2net.ResetClient(client) - assert(IsEntity(client), "[TTT2NET] ResetClient() client is not a valid Entity!") + assert(IsEntity(client), "[TTT2NET] ResetClient() client is not a valid Entity!") - table.RemoveByValue(ttt2net.initializedClients, client) + table.RemoveByValue(ttt2net.initializedClients, client) - -- Clear up the player specific data table - ttt2net.SetMetaDataOnPlayer(nil, nil, client) + -- Clear up the player specific data table + ttt2net.SetMetaDataOnPlayer(nil, nil, client) - -- Clear up all left over overrides - ttt2net.RemoveOverridesForClient(client, ttt2net.dataStoreMetadata) + -- Clear up all left over overrides + ttt2net.RemoveOverridesForClient(client, ttt2net.dataStoreMetadata) end --- @@ -610,37 +629,39 @@ end -- @param[opt] Player|table client The client/list of clients or nil to send this to all known clients -- @realm server function ttt2net.SendDataUpdate(path, client) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Check for a valid metadata entry - local metadata = table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) + -- Check for a valid metadata entry + local metadata = table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) - assert(metadata, "[TTT2NET] SendDataUpdate(): Metadata table is not initialized for this path.") + assert(metadata, "[TTT2NET] SendDataUpdate(): Metadata table is not initialized for this path.") - -- Only send to the client (or table of clients) that was specified or the already initialized clients - -- This will check if client is a table or a single client and format it to a table and otherwise use - -- the ttt2net.initializedClients list. - local receivers = istable(client) and client or client and { client } or ttt2net.initializedClients + -- Only send to the client (or table of clients) that was specified or the already initialized clients + -- This will check if client is a table or a single client and format it to a table and otherwise use + -- the ttt2net.initializedClients list. + local receivers = istable(client) and client + or client and { client } + or ttt2net.initializedClients - -- For each receiver send the data - for i = 1, #receivers do - local receiver = receivers[i] - local value = ttt2net.GetWithOverride(tmpPath, receiver) + -- For each receiver send the data + for i = 1, #receivers do + local receiver = receivers[i] + local value = ttt2net.GetWithOverride(tmpPath, receiver) - net.Start(ttt2net.NETMSG_DATA_UPDATE) + net.Start(ttt2net.NETMSG_DATA_UPDATE) - ttt2net.NetWritePath(tmpPath) - ttt2net.NetWriteData(metadata, value) + ttt2net.NetWritePath(tmpPath) + ttt2net.NetWriteData(metadata, value) - net.Send(receiver) - end + net.Send(receiver) + end end --- @@ -655,27 +676,27 @@ end -- @param[opt] table path The current path to the curTable based on the root of the data table -- @realm server function ttt2net.DataTableWithOverrides(client, curTable, path) - local tmpPath = not istable(path) and { path } or path and table.Copy(path) or {} - local newTable = table.Copy(curTable) + local tmpPath = not istable(path) and { path } or path and table.Copy(path) or {} + local newTable = table.Copy(curTable) - -- Visit all keys in the current tree - for key in pairs(curTable) do - local nextNode = curTable[key] - local nextPath = table.Copy(tmpPath) + -- Visit all keys in the current tree + for key in pairs(curTable) do + local nextNode = curTable[key] + local nextPath = table.Copy(tmpPath) - nextPath[#nextPath + 1] = key + nextPath[#nextPath + 1] = key - -- Check if we reached a leaf node (a node with metadata) - if nextNode.type then - -- Replace the metadata with the data - newTable[key] = ttt2net.GetWithOverride(nextPath, client) - else - -- Descend a level deeper - newTable[key] = ttt2net.DataTableWithOverrides(client, curTable[key], nextPath) - end - end + -- Check if we reached a leaf node (a node with metadata) + if nextNode.type then + -- Replace the metadata with the data + newTable[key] = ttt2net.GetWithOverride(nextPath, client) + else + -- Descend a level deeper + newTable[key] = ttt2net.DataTableWithOverrides(client, curTable[key], nextPath) + end + end - return newTable + return newTable end --- @@ -686,24 +707,26 @@ end -- @param[opt] Player|table client The client/list of clients or nil for all known clients, to send the update to -- @realm server function ttt2net.SendFullStateUpdate(client) - -- Wrap the given receivers to a table - local receivers = istable(client) and client or client and { client } or ttt2net.initializedClients + -- Wrap the given receivers to a table + local receivers = istable(client) and client + or client and { client } + or ttt2net.initializedClients - -- For each receiver create the custom data table with respect to the override values and send it - for i = 1, #receivers do - local receiver = receivers[i] - local data = { - meta = ttt2net.dataStoreMetadata, - data = ttt2net.DataTableWithOverrides(receiver, ttt2net.dataStoreMetadata) - } + -- For each receiver create the custom data table with respect to the override values and send it + for i = 1, #receivers do + local receiver = receivers[i] + local data = { + meta = ttt2net.dataStoreMetadata, + data = ttt2net.DataTableWithOverrides(receiver, ttt2net.dataStoreMetadata), + } - net.SendStream(ttt2net.NET_STREAM_FULL_STATE_UPDATE, data, receiver) + net.SendStream(ttt2net.NET_STREAM_FULL_STATE_UPDATE, data, receiver) - -- Set the client as initialized (so it will receive future data updates) - if not table.HasValue(ttt2net.initializedClients, receiver) then - ttt2net.initializedClients[#ttt2net.initializedClients + 1] = receiver - end - end + -- Set the client as initialized (so it will receive future data updates) + if not table.HasValue(ttt2net.initializedClients, receiver) then + ttt2net.initializedClients[#ttt2net.initializedClients + 1] = receiver + end + end end --- @@ -715,9 +738,9 @@ end -- @realm server -- @internal local function ClientRequestFullStateUpdate(len, client) - print("[TTT2NET] Client " .. (client:Nick() or "unknown") .. " requested a full state update.") + Dev(1, "[TTT2NET] Client " .. (client:Nick() or "unknown") .. " requested a full state update.") - ttt2net.SendFullStateUpdate(client) + ttt2net.SendFullStateUpdate(client) end net.Receive(ttt2net.NETMSG_REQUEST_FULL_STATE_UPDATE, ClientRequestFullStateUpdate) @@ -727,7 +750,7 @@ net.Receive(ttt2net.NETMSG_REQUEST_FULL_STATE_UPDATE, ClientRequestFullStateUpda -- @param table path The path table to send -- @realm server function ttt2net.NetWritePath(path) - net.WriteString(pon.encode(path)) + net.WriteString(pon.encode(path)) end --- @@ -736,18 +759,20 @@ end -- @param table metadata The metadata to send -- @realm server function ttt2net.NetWriteMetaData(metadata) - local isMetadataNil = metadata == nil + local isMetadataNil = metadata == nil - net.WriteBool(isMetadataNil) + net.WriteBool(isMetadataNil) - if isMetadataNil then return end + if isMetadataNil then + return + end - net.WriteString(metadata.type) + net.WriteString(metadata.type) - if metadata.type == "int" then - net.WriteUInt(metadata.bits, 6) -- 6 bits, so we can safely send the maximum bit count of 32 bits - net.WriteBool(metadata.unsigned) - end + if metadata.type == "int" then + net.WriteUInt(metadata.bits, 6) -- 6 bits, so we can safely send the maximum bit count of 32 bits + net.WriteBool(metadata.unsigned) + end end --- @@ -760,33 +785,35 @@ end -- @param[opt] any val The value to send, can also be nil -- @realm server function ttt2net.NetWriteData(metadata, val) - -- Check if the value is nil, to also allow setting a value to nil - local isValNil = val == nil - - net.WriteBool(isValNil) - - if isValNil then return end - - -- Decide on how to send the data, or fallback to string - if metadata.type == "int" then - if metadata.unsigned then - net.WriteUInt(val, metadata.bits or 32) - else - net.WriteInt(val, metadata.bits or 32) - end - elseif metadata.type == "bool" then - net.WriteBool(val) - elseif metadata.type == "float" then - net.WriteFloat(val) - elseif metadata.type == "table" then - net.WriteString(pon.encode(val)) - else - net.WriteString(val) - end + -- Check if the value is nil, to also allow setting a value to nil + local isValNil = val == nil + + net.WriteBool(isValNil) + + if isValNil then + return + end + + -- Decide on how to send the data, or fallback to string + if metadata.type == "int" then + if metadata.unsigned then + net.WriteUInt(val, metadata.bits or 32) + else + net.WriteInt(val, metadata.bits or 32) + end + elseif metadata.type == "bool" then + net.WriteBool(val) + elseif metadata.type == "float" then + net.WriteFloat(val) + elseif metadata.type == "table" then + net.WriteString(pon.encode(val)) + else + net.WriteString(val) + end end local function NWVarProxyCallback(ent, name, oldval, newval, path, meta) - ttt2net.Set(path, meta, newval) + ttt2net.Set(path, meta, newval) end --- @@ -805,55 +832,61 @@ end -- @param string nwkey The key of the NWVar -- @realm server function ttt2net.SyncWithNWVar(path, meta, nwent, nwkey) - local tmpPath + local tmpPath - -- Convert path with single key to table - if not istable(path) then - tmpPath = { path } - else - tmpPath = table.Copy(path) - end + -- Convert path with single key to table + if not istable(path) then + tmpPath = { path } + else + tmpPath = table.Copy(path) + end - -- Add the prefix for the correct table with the specific player - table.insert(tmpPath, 1, "players") - table.insert(tmpPath, 2, nwent:EntIndex()) + -- Add the prefix for the correct table with the specific player + table.insert(tmpPath, 1, "players") + table.insert(tmpPath, 2, nwent:EntIndex()) - local metadata = meta or table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) + local metadata = meta or table.GetWithPath(ttt2net.dataStoreMetadata, tmpPath) - assert(metadata, "[TTT2NET] SyncWithNWVar() called but no metadata entry or metadata parameter found!") + assert( + metadata, + "[TTT2NET] SyncWithNWVar() called but no metadata entry or metadata parameter found!" + ) - -- Set the meta data, this will only send / update if the meta data has changed - ttt2net.SetMetaData(tmpPath, metadata) + -- Set the meta data, this will only send / update if the meta data has changed + ttt2net.SetMetaData(tmpPath, metadata) - nwent:SetNWVarProxy(nwkey, function (ent, name, oldval, newval) - NWVarProxyCallback(ent, name, oldval, newval, tmpPath, metadata) - end) + nwent:SetNWVarProxy(nwkey, function(ent, name, oldval, newval) + NWVarProxyCallback(ent, name, oldval, newval, tmpPath, metadata) + end) - local curval = nil + local curval = nil - if metadata.type == "int" then - assert(not metadata.unsigned, "[TTT2NET] Unsigned numbers are not supported by NWVars! Change the metadata information!") + if metadata.type == "int" then + assert( + not metadata.unsigned, + "[TTT2NET] Unsigned numbers are not supported by NWVars! Change the metadata information!" + ) - curval = nwent:GetNWInt(nwkey) - elseif metadata.type == "bool" then - curval = nwent:GetNWBool(nwkey) - elseif metadata.type == "float" then - curval = nwent:GetNWFloat(nwkey) - else - curval = nwent:GetNWString(nwkey) - end + curval = nwent:GetNWInt(nwkey) + elseif metadata.type == "bool" then + curval = nwent:GetNWBool(nwkey) + elseif metadata.type == "float" then + curval = nwent:GetNWFloat(nwkey) + else + curval = nwent:GetNWString(nwkey) + end - ttt2net.Set(tmpPath, metadata, curval) + ttt2net.Set(tmpPath, metadata, curval) - -- Get all registered nwvars - local nwvars = table.GetWithPath(ttt2net.dataSyncedNWVars, tmpPath) or {} + -- Get all registered nwvars + local nwvars = table.GetWithPath(ttt2net.dataSyncedNWVars, tmpPath) or {} - -- Add the nwkey to the registered nwvars on this path - if not table.HasValue(nwvars, nwkey) then - nwvars[#nwvars + 1] = nwkey - end + -- Add the nwkey to the registered nwvars on this path + if not table.HasValue(nwvars, nwkey) then + nwvars[#nwvars + 1] = nwkey + end - table.SetWithPath(ttt2net.dataSyncedNWVars, tmpPath, nwvars) + table.SetWithPath(ttt2net.dataSyncedNWVars, tmpPath, nwvars) end --- @@ -870,11 +903,11 @@ local plymeta = assert(FindMetaTable("Player"), "[TTT2NET] FAILED TO FIND PLAYER -- Sets a bool value at the given path on the player. -- -- @param any|table path The path to set the value for --- @param[opt] bool value The value to set +-- @param[opt] boolean value The value to set -- @param[optchain] Entity client The client/entity to set this value for (as an override for the default value) -- @realm server function plymeta:TTT2NETSetBool(path, value, client) - ttt2net.SetOnPlayer(path, { type = "bool" }, value, self, client) + ttt2net.SetOnPlayer(path, { type = "bool" }, value, self, client) end --- @@ -886,10 +919,10 @@ end -- @param[optchain] Entity client The client/entity to set this value for (as an override for the default value) -- @realm server function plymeta:TTT2NETSetInt(path, value, bits, client) - ttt2net.SetOnPlayer(path, { - type = "int", - bits = bits - }, value, self, client) + ttt2net.SetOnPlayer(path, { + type = "int", + bits = bits, + }, value, self, client) end --- @@ -901,22 +934,22 @@ end -- @param[optchain] Entity client The client/entity to set this value for (as an override for the default value) -- @realm server function plymeta:TTT2NETSetUInt(path, value, bits, client) - ttt2net.SetOnPlayer(path, { - type = "int", - unsigned = true, - bits = bits - }, value, self, client) + ttt2net.SetOnPlayer(path, { + type = "int", + unsigned = true, + bits = bits, + }, value, self, client) end --- -- Sets a float value at the given path on the player. -- -- @param any|table path The path to set the value for --- @param[opt] float value The value to set +-- @param[opt] number value The value to set -- @param[optchain] Entity client The client/entity to set this value for (as an override for the default value) -- @realm server function plymeta:TTT2NETSetFloat(path, value, client) - ttt2net.SetOnPlayer(path, { type = "float" }, value, self, client) + ttt2net.SetOnPlayer(path, { type = "float" }, value, self, client) end --- @@ -927,5 +960,5 @@ end -- @param[optchain] Entity client The client/entity to set this value for (as an override for the default value) -- @realm server function plymeta:TTT2NETSetString(path, value, client) - ttt2net.SetOnPlayer(path, { type = "string" }, value, self, client) + ttt2net.SetOnPlayer(path, { type = "string" }, value, self, client) end diff --git a/gamemodes/terrortown/gamemode/server/sv_networking.lua b/gamemodes/terrortown/gamemode/server/sv_networking.lua index f63696970..2a04b61a1 100644 --- a/gamemodes/terrortown/gamemode/server/sv_networking.lua +++ b/gamemodes/terrortown/gamemode/server/sv_networking.lua @@ -21,70 +21,80 @@ local hook = hook -- @todo add role hacking -- @realm server function SendRoleListMessage(subrole, team, sids, ply_or_rf) - local tmp = ply_or_rf or player.GetAll() - - if not istable(tmp) then - tmp = {ply_or_rf} - end - - for _i = 1, #tmp do - local ply = tmp[_i] - local adds = {} - local localPly = false - local num_ids = #sids - - TTT2NETTABLE[ply] = TTT2NETTABLE[ply] or {} - - for i = 1, num_ids do - local eidx = sids[i] - local p = Entity(eidx) - - if TTT2NETTABLE[ply][p] and TTT2NETTABLE[ply][p][1] == subrole and TTT2NETTABLE[ply][p][2] == team then continue end - - TTT2NETTABLE[ply][p] = {subrole, team} - - if p ~= ply then - local rs = GetRoundState() - - if p:GetSubRoleData().disableSync - and rs == ROUND_ACTIVE - -- TODO this has to be reworked - and not p:RoleKnown() - --- - -- @realm server - and not hook.Run("TTT2OverrideDisabledSync", ply, p) - then continue end - - adds[#adds + 1] = eidx - else - localPly = true - end - end - - num_ids = #adds - - if num_ids > 0 then - net.Start("TTT_RoleList") - net.WriteUInt(subrole, ROLE_BITS) - net.WriteString(team) - - -- list contents - net.WriteUInt(num_ids, 8) - - for i = 1, num_ids do - net.WriteUInt(adds[i] - 1, 7) - end - - net.Send(ply) - end - - if localPly then - net.Start("TTT_Role") - net.WriteUInt(subrole, ROLE_BITS) - net.WriteString(team) - net.Send(ply) - end - end + local tmp = ply_or_rf or player.GetAll() + + if not istable(tmp) then + tmp = { ply_or_rf } + end + + for _i = 1, #tmp do + local ply = tmp[_i] + local adds = {} + local localPly = false + local num_ids = #sids + + TTT2NETTABLE[ply] = TTT2NETTABLE[ply] or {} + + for i = 1, num_ids do + local eidx = sids[i] + local p = Entity(eidx) + + if + TTT2NETTABLE[ply][p] + and TTT2NETTABLE[ply][p][1] == subrole + and TTT2NETTABLE[ply][p][2] == team + then + continue + end + + TTT2NETTABLE[ply][p] = { subrole, team } + + if p ~= ply then + local rs = GetRoundState() + + if + p:GetSubRoleData().disableSync + and rs == ROUND_ACTIVE + -- TODO this has to be reworked + and not p:RoleKnown() + --- + -- @realm server + -- stylua: ignore + and not hook.Run("TTT2OverrideDisabledSync", ply, p) + then + continue + end + + adds[#adds + 1] = eidx + else + localPly = true + end + end + + num_ids = #adds + + if num_ids > 0 then + net.Start("TTT_RoleList") + net.WriteUInt(subrole, ROLE_BITS) + net.WriteString(team) + + -- list contents + net.WriteUInt(num_ids, 8) + + for i = 1, num_ids do + net.WriteUInt(adds[i] - 1, 7) + end + + net.Send(ply) + end + + if localPly then + net.Start("TTT_Role") + net.WriteUInt(subrole, ROLE_BITS) + net.WriteString(team) + net.Send(ply) + end + end end --- @@ -96,24 +106,24 @@ end -- @realm server -- @see SendRoleList function SendSubRoleList(subrole, ply_or_rf, pred) - local team_ids = {} - local plys = player.GetAll() - - for i = 1, #plys do - local v = plys[i] - - if v:GetSubRole() == subrole and (not pred or (pred and pred(v))) then - local team = v:GetTeam() - if team ~= TEAM_NONE and not TEAMS[team].alone then - team_ids[team] = team_ids[team] or {} -- create table if it does not exists - team_ids[team][#team_ids[team] + 1] = v:EntIndex() - end - end - end - - for team, ids in pairs(team_ids) do - SendRoleListMessage(subrole, team, ids, ply_or_rf) - end + local team_ids = {} + local plys = player.GetAll() + + for i = 1, #plys do + local v = plys[i] + + if v:GetSubRole() == subrole and (not pred or (pred and pred(v))) then + local team = v:GetTeam() + if team ~= TEAM_NONE and not TEAMS[team].alone then + team_ids[team] = team_ids[team] or {} -- create table if it does not exists + team_ids[team][#team_ids[team] + 1] = v:EntIndex() + end + end + end + + for team, ids in pairs(team_ids) do + SendRoleListMessage(subrole, team, ids, ply_or_rf) + end end --- @@ -125,24 +135,24 @@ end -- @realm server -- @see SendSubRoleList function SendRoleList(subrole, ply_or_rf, pred) - local team_ids = {} - local plys = player.GetAll() - - for i = 1, #plys do - local v = plys[i] - - if v:IsRole(subrole) and (not pred or (pred and pred(v))) then - local team = v:GetTeam() - if team ~= TEAM_NONE and not TEAMS[team].alone then - team_ids[team] = team_ids[team] or {} -- create table if it does not exists - team_ids[team][#team_ids[team] + 1] = v:EntIndex() - end - end - end - - for team, ids in pairs(team_ids) do - SendRoleListMessage(subrole, team, ids, ply_or_rf) - end + local team_ids = {} + local plys = player.GetAll() + + for i = 1, #plys do + local v = plys[i] + + if v:IsRole(subrole) and (not pred or (pred and pred(v))) then + local team = v:GetTeam() + if team ~= TEAM_NONE and not TEAMS[team].alone then + team_ids[team] = team_ids[team] or {} -- create table if it does not exists + team_ids[team][#team_ids[team] + 1] = v:EntIndex() + end + end + end + + for team, ids in pairs(team_ids) do + SendRoleListMessage(subrole, team, ids, ply_or_rf) + end end --- @@ -152,25 +162,27 @@ end -- @param function pred the filter @{function} -- @realm server function SendTeamList(team, ply_or_rf, pred) - if team == TEAM_NONE or TEAMS[team].alone then return end + if team == TEAM_NONE or TEAMS[team].alone then + return + end - local team_ids = {} - local plys = player.GetAll() + local team_ids = {} + local plys = player.GetAll() - for i = 1, #plys do - local v = plys[i] + for i = 1, #plys do + local v = plys[i] - if v:GetTeam() == team and (not pred or (pred and pred(v))) then - local subrole = v:GetSubRole() + if v:GetTeam() == team and (not pred or (pred and pred(v))) then + local subrole = v:GetSubRole() - team_ids[subrole] = team_ids[subrole] or {} -- create table if it does not exists - team_ids[subrole][#team_ids[subrole] + 1] = v:EntIndex() - end - end + team_ids[subrole] = team_ids[subrole] or {} -- create table if it does not exists + team_ids[subrole][#team_ids[subrole] + 1] = v:EntIndex() + end + end - for subrole, ids in pairs(team_ids) do - SendRoleListMessage(subrole, team, ids, ply_or_rf) - end + for subrole, ids in pairs(team_ids) do + SendRoleListMessage(subrole, team, ids, ply_or_rf) + end end --- @@ -179,13 +191,15 @@ end -- @param nil|Player|table ply_or_rf -- @realm server function SendConfirmedTeam(team, ply_or_rf) - if team == TEAM_NONE or TEAMS[team].alone then return end + if team == TEAM_NONE or TEAMS[team].alone then + return + end - local _func = function(p) - return p:RoleKnown() -- TODO rework - end + local _func = function(p) + return p:RoleKnown() -- TODO rework + end - SendTeamList(team, ply_or_rf, _func) + SendTeamList(team, ply_or_rf, _func) end --- @@ -194,7 +208,7 @@ end -- @param nil|Player|table ply_or_rf -- @realm server function SendPlayerToEveryone(ply, ply_or_rf) - return SendRoleListMessage(ply:GetSubRole(), ply:GetTeam(), {ply:EntIndex()}, ply_or_rf) + return SendRoleListMessage(ply:GetSubRole(), ply:GetTeam(), { ply:EntIndex() }, ply_or_rf) end --- @@ -204,283 +218,315 @@ end -- @todo Improve, merging data to send netmsg for players that will receive same data -- @realm server function SendFullStateUpdate() - local syncTbl = {} - local localPly = false - local players = player.GetAll() - local plyCount = #players - - for i = 1, plyCount do - local plySyncTo = players[i] - local tmp = {} - local teamSyncTo = plySyncTo:GetTeam() - local roleDataSyncTo = plySyncTo:GetSubRoleData() - - for k = 1, plyCount do - local plySyncFrom = players[k] - local roleDataSyncFrom = plySyncFrom:GetSubRoleData() - local teamSyncFrom = plySyncFrom:GetTeam() - - if not roleDataSyncTo.unknownTeam and teamSyncFrom == teamSyncTo - or plySyncFrom:RoleKnown() -- TODO rework - or table.HasValue(roleDataSyncFrom.visibleForTeam, teamSyncTo) - or table.HasValue(roleDataSyncTo.networkRoles, roleDataSyncFrom) - or roleDataSyncFrom.isPublicRole - or plySyncTo == plySyncFrom - then - tmp[plySyncFrom] = {plySyncFrom:GetSubRole() or ROLE_NONE, teamSyncFrom or TEAM_NONE} - else - tmp[plySyncFrom] = {ROLE_NONE, TEAM_NONE} - end - end - - --- - -- maybe some networking for custom roles or role hacking - -- @realm server - hook.Run("TTT2SpecialRoleSyncing", plySyncTo, tmp) - - syncTbl[plySyncTo] = {} - - TTT2NETTABLE[plySyncTo] = TTT2NETTABLE[plySyncTo] or {} - - for p, t in pairs(tmp) do - if p ~= plySyncTo then - syncTbl[plySyncTo][t[2]] = syncTbl[plySyncTo][t[2]] or {} - syncTbl[plySyncTo][t[2]][t[1]] = syncTbl[plySyncTo][t[2]][t[1]] or {} - syncTbl[plySyncTo][t[2]][t[1]][#syncTbl[plySyncTo][t[2]][t[1]] + 1] = p:EntIndex() - elseif not TTT2NETTABLE[plySyncTo][p] or TTT2NETTABLE[plySyncTo][p][1] ~= t[1] or TTT2NETTABLE[plySyncTo][p][2] ~= t[2] then - localPly = true - end - end - - for tm, t in pairs(syncTbl[plySyncTo]) do - for sr, sids in pairs(t) do - SendRoleListMessage(sr, tm, sids, plySyncTo) - end - end - - -- update own subrole for ply - if localPly then - TTT2NETTABLE[plySyncTo][plySyncTo] = {tmp[plySyncTo][1], tmp[plySyncTo][2]} - - net.Start("TTT_Role") - net.WriteUInt(tmp[plySyncTo][1], ROLE_BITS) - net.WriteString(tmp[plySyncTo][2]) - net.Send(plySyncTo) - end - end + local syncTbl = {} + local localPly = false + local players = player.GetAll() + local plyCount = #players + + for i = 1, plyCount do + local plySyncTo = players[i] + local tmp = {} + local teamSyncTo = plySyncTo:GetTeam() + local roleDataSyncTo = plySyncTo:GetSubRoleData() + + for k = 1, plyCount do + local plySyncFrom = players[k] + local roleDataSyncFrom = plySyncFrom:GetSubRoleData() + local teamSyncFrom = plySyncFrom:GetTeam() + + if + not roleDataSyncTo.unknownTeam and teamSyncFrom == teamSyncTo + or plySyncFrom:RoleKnown() -- TODO rework + or table.HasValue(roleDataSyncFrom.visibleForTeam, teamSyncTo) + or table.HasValue(roleDataSyncTo.networkRoles, roleDataSyncFrom) + or roleDataSyncFrom.isPublicRole + or plySyncTo == plySyncFrom + then + tmp[plySyncFrom] = + { plySyncFrom:GetSubRole() or ROLE_NONE, teamSyncFrom or TEAM_NONE } + else + tmp[plySyncFrom] = { ROLE_NONE, TEAM_NONE } + end + end + + --- + -- maybe some networking for custom roles or role hacking + -- @realm server + -- stylua: ignore + hook.Run("TTT2SpecialRoleSyncing", plySyncTo, tmp) + + syncTbl[plySyncTo] = {} + + TTT2NETTABLE[plySyncTo] = TTT2NETTABLE[plySyncTo] or {} + + for p, t in pairs(tmp) do + if p ~= plySyncTo then + syncTbl[plySyncTo][t[2]] = syncTbl[plySyncTo][t[2]] or {} + syncTbl[plySyncTo][t[2]][t[1]] = syncTbl[plySyncTo][t[2]][t[1]] or {} + syncTbl[plySyncTo][t[2]][t[1]][#syncTbl[plySyncTo][t[2]][t[1]] + 1] = p:EntIndex() + elseif + not TTT2NETTABLE[plySyncTo][p] + or TTT2NETTABLE[plySyncTo][p][1] ~= t[1] + or TTT2NETTABLE[plySyncTo][p][2] ~= t[2] + then + localPly = true + end + end + + for tm, t in pairs(syncTbl[plySyncTo]) do + for sr, sids in pairs(t) do + SendRoleListMessage(sr, tm, sids, plySyncTo) + end + end + + -- update own subrole for ply + if localPly then + TTT2NETTABLE[plySyncTo][plySyncTo] = { tmp[plySyncTo][1], tmp[plySyncTo][2] } + + net.Start("TTT_Role") + net.WriteUInt(tmp[plySyncTo][1], ROLE_BITS) + net.WriteString(tmp[plySyncTo][2]) + net.Send(plySyncTo) + end + end end --- -- Resynces the list of @{Player}s for a given list of @{Player}s --- @param nil|Player|table +-- @param nil|Player|table ply_or_rf -- @realm server function SendRoleReset(ply_or_rf) - local players = player.GetAll() - local plyCount = #players + local players = player.GetAll() + local plyCount = #players - for i = 1, plyCount do - local ply = players[i] + for i = 1, plyCount do + local ply = players[i] - TTT2NETTABLE[ply] = TTT2NETTABLE[ply] or {} + TTT2NETTABLE[ply] = TTT2NETTABLE[ply] or {} - for k = 1, plyCount do - local p = players[k] + for k = 1, plyCount do + local p = players[k] - TTT2NETTABLE[ply][p] = {ROLE_NONE, TEAM_NONE} - end - end + TTT2NETTABLE[ply][p] = { ROLE_NONE, TEAM_NONE } + end + end - net.Start("TTT_RoleReset") + net.Start("TTT_RoleReset") - if ply_or_rf then - net.Send(ply_or_rf) - else - net.Broadcast() - end + if ply_or_rf then + net.Send(ply_or_rf) + else + net.Broadcast() + end end -- Console commands local function ttt_request_rolelist(plySyncTo) - -- Client requested a state update. Note that the client can only use this - -- information after entities have been initialized (e.g. in InitPostEntity). - if GetRoundState() ~= ROUND_WAIT then - local localPly = false - local tmp = {} - local team = plySyncTo:GetTeam() - local roleDataSyncTo = plySyncTo:GetSubRoleData() - local plys = player.GetAll() - - for i = 1, #plys do - local plySyncFrom = plys[i] - local roleDataSyncFrom = plySyncFrom:GetSubRoleData() - - if not roleDataSyncTo.unknownTeam and plySyncFrom:GetTeam() == team - or plySyncFrom:RoleKnown() -- TODO rework - or table.HasValue(roleDataSyncFrom.visibleForTeam, plySyncTo:GetTeam()) - or table.HasValue(roleDataSyncTo.networkRoles, roleDataSyncFrom) - or roleDataSyncFrom.isPublicRole - or plySyncTo == plySyncFrom - then - tmp[plySyncFrom] = {plySyncFrom:GetSubRole() or ROLE_NONE, plySyncFrom:GetTeam() or TEAM_NONE} - else - tmp[plySyncFrom] = {ROLE_NONE, TEAM_NONE} - end - end - - --- - -- maybe some networking for custom roles or role hacking - -- @realm server - hook.Run("TTT2SpecialRoleSyncing", plySyncTo, tmp) - - local tbl = {} - - TTT2NETTABLE[plySyncTo] = TTT2NETTABLE[plySyncTo] or {} - - for p, t in pairs(tmp) do - if p ~= plySyncTo then - tbl[t[2]] = tbl[t[2]] or {} - tbl[t[2]][t[1]] = tbl[t[2]][t[1]] or {} - tbl[t[2]][t[1]][#tbl[t[2]][t[1]] + 1] = p:EntIndex() - elseif not TTT2NETTABLE[plySyncTo][p] or TTT2NETTABLE[plySyncTo][p][1] ~= t[1] or TTT2NETTABLE[plySyncTo][p][2] ~= t[2] then - localPly = true - end - end - - for tm, t in pairs(tbl) do - for sr, sids in pairs(t) do - SendRoleListMessage(sr, tm, sids, plySyncTo) - end - end - - -- update own subrole for ply - if localPly then - TTT2NETTABLE[plySyncTo][plySyncTo] = {tmp[plySyncTo][1], tmp[plySyncTo][2]} - - net.Start("TTT_Role") - net.WriteUInt(tmp[plySyncTo][1], ROLE_BITS) - net.WriteString(tmp[plySyncTo][2]) - net.Send(plySyncTo) - end - end + -- Client requested a state update. Note that the client can only use this + -- information after entities have been initialized (e.g. in InitPostEntity). + if GetRoundState() ~= ROUND_WAIT then + local localPly = false + local tmp = {} + local team = plySyncTo:GetTeam() + local roleDataSyncTo = plySyncTo:GetSubRoleData() + local plys = player.GetAll() + + for i = 1, #plys do + local plySyncFrom = plys[i] + local roleDataSyncFrom = plySyncFrom:GetSubRoleData() + + if + not roleDataSyncTo.unknownTeam and plySyncFrom:GetTeam() == team + or plySyncFrom:RoleKnown() -- TODO rework + or table.HasValue(roleDataSyncFrom.visibleForTeam, plySyncTo:GetTeam()) + or table.HasValue(roleDataSyncTo.networkRoles, roleDataSyncFrom) + or roleDataSyncFrom.isPublicRole + or plySyncTo == plySyncFrom + then + tmp[plySyncFrom] = + { plySyncFrom:GetSubRole() or ROLE_NONE, plySyncFrom:GetTeam() or TEAM_NONE } + else + tmp[plySyncFrom] = { ROLE_NONE, TEAM_NONE } + end + end + + --- + -- maybe some networking for custom roles or role hacking + -- @realm server + -- stylua: ignore + hook.Run("TTT2SpecialRoleSyncing", plySyncTo, tmp) + + local tbl = {} + + TTT2NETTABLE[plySyncTo] = TTT2NETTABLE[plySyncTo] or {} + + for p, t in pairs(tmp) do + if p ~= plySyncTo then + tbl[t[2]] = tbl[t[2]] or {} + tbl[t[2]][t[1]] = tbl[t[2]][t[1]] or {} + tbl[t[2]][t[1]][#tbl[t[2]][t[1]] + 1] = p:EntIndex() + elseif + not TTT2NETTABLE[plySyncTo][p] + or TTT2NETTABLE[plySyncTo][p][1] ~= t[1] + or TTT2NETTABLE[plySyncTo][p][2] ~= t[2] + then + localPly = true + end + end + + for tm, t in pairs(tbl) do + for sr, sids in pairs(t) do + SendRoleListMessage(sr, tm, sids, plySyncTo) + end + end + + -- update own subrole for ply + if localPly then + TTT2NETTABLE[plySyncTo][plySyncTo] = { tmp[plySyncTo][1], tmp[plySyncTo][2] } + + net.Start("TTT_Role") + net.WriteUInt(tmp[plySyncTo][1], ROLE_BITS) + net.WriteString(tmp[plySyncTo][2]) + net.Send(plySyncTo) + end + end end concommand.Add("_ttt_request_rolelist", ttt_request_rolelist) local function ttt_force_terror(ply) - ply:SetRole(ROLE_INNOCENT) - ply:UnSpectate() - ply:SetTeam(TEAM_TERROR) + ply:SetRole(ROLE_INNOCENT) + ply:UnSpectate() + ply:SetTeam(TEAM_TERROR) - ply:StripAll() + ply:StripAll() - ply:Spawn() - ply:PrintMessage(HUD_PRINTTALK, "You are now on the terrorist team.") + ply:Spawn() + ply:PrintMessage(HUD_PRINTTALK, "You are now on the terrorist team.") - SendFullStateUpdate() + SendFullStateUpdate() end concommand.Add("ttt_force_terror", ttt_force_terror, nil, nil, FCVAR_CHEAT) local function ttt_force_traitor(ply) - ply:SetRole(ROLE_TRAITOR) + ply:SetRole(ROLE_TRAITOR) - SendFullStateUpdate() + SendFullStateUpdate() end concommand.Add("ttt_force_traitor", ttt_force_traitor, nil, nil, FCVAR_CHEAT) local function ttt_force_detective(ply) - ply:SetRole(ROLE_DETECTIVE) + ply:SetRole(ROLE_DETECTIVE) - SendFullStateUpdate() + SendFullStateUpdate() end concommand.Add("ttt_force_detective", ttt_force_detective, nil, nil, FCVAR_CHEAT) local function ttt_force_role(ply, cmd, args, argStr) - local role = tonumber(args[1]) - if not role then return end + local role = tonumber(args[1]) + if not role then + return + end - local rd - local rlsList = roles.GetList() + local rd + local rlsList = roles.GetList() - for i = 1, #rlsList do - if rlsList[i].index == role then - rd = rlsList[i] + for i = 1, #rlsList do + if rlsList[i].index == role then + rd = rlsList[i] - break - end - end + break + end + end - if not rd or rd.notSelectable then return end + if not rd or rd.notSelectable then + return + end - ply:SetRole(role) + ply:SetRole(role) - SendFullStateUpdate() + SendFullStateUpdate() - ply:ChatPrint("You changed to '" .. rd.name .. "' (index: " .. role .. ")") + ply:ChatPrint("You changed to '" .. rd.name .. "' (index: " .. role .. ")") end concommand.Add("ttt_force_role", ttt_force_role, nil, nil, FCVAR_CHEAT) local function get_role(ply) - net.Start("TTT2TestRole") - net.Send(ply) + net.Start("TTT2TestRole") + net.Send(ply) end concommand.Add("get_role", get_role) local function ttt_toggle_role(ply, cmd, args, argStr) - if not ply:IsAdmin() then return end - - local role = tonumber(args[1]) - local roleData = roles.GetByIndex(role) - local currentState = not GetConVar("ttt_" .. roleData.name .. "_enabled"):GetBool() - - local word = currentState and "disabled" or "enabled" - - RunConsoleCommand("ttt_" .. roleData.name .. "_enabled", currentState and "1" or "0") - - ply:ChatPrint("You " .. word .. " role with index '" .. role .. "(" .. roleData.name .. ")'. This will take effect in the next role selection!") + if not ply:IsAdmin() then + return + end + + local role = tonumber(args[1]) + local roleData = roles.GetByIndex(role) + local currentState = not GetConVar("ttt_" .. roleData.name .. "_enabled"):GetBool() + + local word = currentState and "disabled" or "enabled" + + RunConsoleCommand("ttt_" .. roleData.name .. "_enabled", currentState and "1" or "0") + + ply:ChatPrint( + "You " + .. word + .. " role with index '" + .. role + .. "(" + .. roleData.name + .. ")'. This will take effect in the next role selection!" + ) end concommand.Add("ttt_toggle_role", ttt_toggle_role) local function force_spectate(ply, cmd, arg) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - if #arg == 1 and tonumber(arg[1]) == 0 then - ply:SetForceSpec(false) - else - if not ply:IsSpec() then - ply:Kill() - end + if #arg == 1 and tonumber(arg[1]) == 0 then + ply:SetForceSpec(false) + else + if not ply:IsSpec() then + ply:Kill() + end - GAMEMODE:PlayerSpawnAsSpectator(ply) + GAMEMODE:PlayerSpawnAsSpectator(ply) - ply:SetTeam(TEAM_SPEC) - ply:SetForceSpec(true) - ply:Spawn() + ply:SetTeam(TEAM_SPEC) + ply:SetForceSpec(true) + ply:Spawn() - ply:SetRagdollSpec(false) -- dying will enable this, we don't want it here - end + ply:SetRagdollSpec(false) -- dying will enable this, we don't want it here + end end concommand.Add("ttt_spectate", force_spectate) local function TTT_Spectate(l, pl) - force_spectate(pl, nil, {net.ReadBool() and 1 or 0}) + force_spectate(pl, nil, { net.ReadBool() and 1 or 0 }) end net.Receive("TTT_Spectate", TTT_Spectate) local function ttt_roles_index(ply) - if not ply:IsAdmin() then return end + if not ply:IsAdmin() then + return + end - ply:ChatPrint("[TTT2] roles_index...") - ply:ChatPrint("----------------") - ply:ChatPrint("[Role] | [Index]") + ply:ChatPrint("[TTT2] roles_index...") + ply:ChatPrint("----------------") + ply:ChatPrint("[Role] | [Index]") - local rlsList = roles.GetSortedRoles() + local rlsList = roles.GetSortedRoles() - for i = 1, #rlsList do - local v = rlsList[i] + for i = 1, #rlsList do + local v = rlsList[i] - ply:ChatPrint(v.name .. " | " .. v.index) - end + ply:ChatPrint(v.name .. " | " .. v.index) + end - ply:ChatPrint("----------------") + ply:ChatPrint("----------------") end concommand.Add("ttt_roles_index", ttt_roles_index) @@ -489,9 +535,7 @@ concommand.Add("ttt_roles_index", ttt_roles_index) -- @param Player p -- @hook -- @realm server -function GM:TTT2OverrideDisabledSync(ply, p) - -end +function GM:TTT2OverrideDisabledSync(ply, p) end --- -- Hook that is called in @{SendFullStateUpdate} that can be used @@ -501,6 +545,4 @@ end -- of their role info; can be modified -- @hook -- @realm server -function GM:TTT2SpecialRoleSyncing(ply, syncTeamTbl) - -end +function GM:TTT2SpecialRoleSyncing(ply, syncTeamTbl) end diff --git a/gamemodes/terrortown/gamemode/server/sv_player.lua b/gamemodes/terrortown/gamemode/server/sv_player.lua index 02c7ab516..1839b39df 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player.lua @@ -9,23 +9,42 @@ local net = net local IsValid = IsValid local hook = hook +---@class Player +local plymeta = FindMetaTable("Player") +if not plymeta then + ErrorNoHaltWithStack("FAILED TO FIND PLAYER TABLE") + + return +end + + --- -- @realm server +-- stylua: ignore local ttt_bots_are_spectators = CreateConVar("ttt_bots_are_spectators", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore +local ttt2_bots_lock_on_death = CreateConVar("ttt2_bots_lock_on_death", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + +--- +-- @realm server +-- stylua: ignore local ttt_dyingshot = CreateConVar("ttt_dyingshot", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore CreateConVar("ttt_killer_dna_range", "550", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore CreateConVar("ttt_killer_dna_basetime", "100", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) util.AddNetworkString("ttt2_damage_received") +util.AddNetworkString("ttt2_set_player_setting") --- -- First spawn on the server. @@ -39,36 +58,36 @@ util.AddNetworkString("ttt2_damage_received") -- @ref https://wiki.facepunch.com/gmod/GM:PlayerInitialSpawn -- @local function GM:PlayerInitialSpawn(ply) - ply:InitialSpawn() + ply:InitialSpawn() - local rstate = GetRoundState() or ROUND_WAIT + local rstate = GetRoundState() or ROUND_WAIT - -- We should update the traitor list, if we are not about to send it - -- sending roles for spectators - if rstate <= ROUND_PREP then - SendFullStateUpdate() -- TODO needed? - end + -- We should update the traitor list, if we are not about to send it + -- sending roles for spectators + if rstate <= ROUND_PREP then + SendFullStateUpdate() -- TODO needed? + end - -- Game has started, tell this guy (spec) where the round is at - if rstate ~= ROUND_WAIT then - SendRoundState(rstate, ply) + -- Game has started, tell this guy (spec) where the round is at + if rstate ~= ROUND_WAIT then + SendRoundState(rstate, ply) - timer.Simple(1, SendFullStateUpdate) - end + timer.Simple(1, SendFullStateUpdate) + end - -- Handle spec bots - if ply:IsBot() and ttt_bots_are_spectators:GetBool() then - ply:SetTeam(TEAM_SPEC) - ply:SetForceSpec(true) - end + -- Handle spec bots + if ply:IsBot() and ttt_bots_are_spectators:GetBool() then + ply:SetTeam(TEAM_SPEC) + ply:SetForceSpec(true) + end - -- Sync NWVars - -- Needs to be done here, to include bots (also this wont send any net messages to the initialized player) - ttt2net.SyncWithNWVar("body_found", { type = "bool" }, ply, "body_found") + -- Sync NWVars + -- Needs to be done here, to include bots (also this wont send any net messages to the initialized player) + ttt2net.SyncWithNWVar("body_found", { type = "bool" }, ply, "body_found") - -- maybe show credits - net.Start("TTT2DevChanges") - net.Send(ply) + -- maybe show credits + net.Start("TTT2DevChanges") + net.Send(ply) end --- @@ -80,18 +99,20 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:NetworkIDValidated -- @local function GM:NetworkIDValidated(name, steamid) - -- edge case where player authed after initspawn - local plys = player.GetAll() + -- edge case where player authed after initspawn + local plys = player.GetAll() - for i = 1, #plys do - local p = plys[i] + for i = 1, #plys do + local p = plys[i] - if not IsValid(p) or p:SteamID64() ~= steamid or not p.delay_karma_recall then continue end + if not IsValid(p) or p:SteamID64() ~= steamid or not p.delay_karma_recall then + continue + end - KARMA.LateRecallAndSet(p) + KARMA.LateRecallAndSet(p) - return - end + return + end end --- @@ -102,68 +123,78 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerSpawn -- @local function GM:PlayerSpawn(ply) - -- reset any cached weapons - ply:ResetCachedWeapons() + player_manager.SetPlayerClass(ply, "player_ttt") + + -- reset any cached weapons + ply:ResetCachedWeapons() - -- stop bleeding - util.StopBleeding(ply) + -- Allow bots to wander again. + if ply:IsBot() then + ply:UnLock() + end - -- Some spawns may be tilted - ply:ResetViewRoll() + -- stop bleeding + util.StopBleeding(ply) - -- Clear out stuff like whether we ordered guns or what bomb code we used - ply:ResetRoundFlags() + -- Some spawns may be tilted + ply:ResetViewRoll() - -- latejoiner, send him some info - if GetRoundState() == ROUND_ACTIVE then - SendRoundState(GetRoundState(), ply) - end + -- Clear out stuff like whether we ordered guns or what bomb code we used + ply:ResetRoundFlags() - ply.spawn_nick = ply:Nick() - ply.has_spawned = true + -- latejoiner, send him some info + if GetRoundState() == ROUND_ACTIVE then + SendRoundState(GetRoundState(), ply) + end - -- let the client do things on spawn - net.Start("TTT_PlayerSpawned") - net.WriteBit(ply:IsSpec()) - net.Send(ply) + ply.spawn_nick = ply:Nick() + ply.has_spawned = true - if ply:IsSpec() then - ply:StripAll() - ply:Spectate(OBS_MODE_ROAMING) + -- let the client do things on spawn + net.Start("TTT_PlayerSpawned") + net.WriteBit(ply:IsSpec()) + net.Send(ply) - return - end + if ply:IsSpec() then + ply:StripAll() + ply:Spectate(OBS_MODE_ROAMING) - ply:UnSpectate() + return + end - -- ye olde hooks + ply:UnSpectate() - --- - -- @realm server - hook.Run("PlayerLoadout", ply, false) + -- ye olde hooks - --- - -- @realm server - hook.Run("PlayerSetModel", ply) + --- + -- @realm server + -- stylua: ignore + hook.Run("PlayerLoadout", ply, false) - --- - -- @realm server - hook.Run("TTTPlayerSetColor", ply) + --- + -- @realm server + -- stylua: ignore + hook.Run("PlayerSetModel", ply) - ply:SetupHands() + --- + -- @realm server + -- stylua: ignore + hook.Run("TTTPlayerSetColor", ply) - ply:SetLastSpawnPosition(ply:GetPos()) - ply:SetLastDeathPosition(nil) + ply:SetupHands() - if ply:IsActive() then - -- a function to handle the rolespecific stuff that should be done on - -- rolechange and respawn (while a round is active) - ply:GetSubRoleData():GiveRoleLoadout(ply, false) + ply:SetLastSpawnPosition(ply:GetPos()) + ply:SetLastDeathPosition(nil) - events.Trigger(EVENT_RESPAWN, ply) - else - events.Trigger(EVENT_SPAWN, ply) - end + if ply:IsActive() then + -- a function to handle the rolespecific stuff that should be done on + -- rolechange and respawn (while a round is active) + ply:GetSubRoleData():GiveRoleLoadout(ply, false) + + events.Trigger(EVENT_RESPAWN, ply) + else + events.Trigger(EVENT_SPAWN, ply) + end end --- @@ -177,14 +208,14 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerSetHandsModel -- @local function GM:PlayerSetHandsModel(ply, ent) - local simplemodel = player_manager.TranslateToPlayerModelName(ply:GetModel()) - local info = player_manager.TranslatePlayerHands(simplemodel) - - if info then - ent:SetModel(info.model) - ent:SetSkin(info.skin) - ent:SetBodyGroups(info.body) - end + local simplemodel = player_manager.TranslateToPlayerModelName(ply:GetModel()) + local info = player_manager.TranslatePlayerHands(simplemodel) + + if info then + ent:SetModel(info.model) + ent:SetSkin(info.skin) + ent:SetBodyGroups(info.body) + end end --- @@ -198,11 +229,11 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:IsSpawnpointSuitable -- @local function GM:IsSpawnpointSuitable(ply, spawnEntity, force) - if not IsValid(ply) or not ply:IsTerror() then - return true - end + if not IsValid(ply) or not ply:IsTerror() then + return true + end - return plyspawn.IsSpawnPointSafe(ply, spawnEntity:GetPos(), force) + return plyspawn.IsSpawnPointSafe(ply, spawnEntity:GetPos(), force) end --- @@ -213,14 +244,14 @@ end -- @deprecated Use @{plyspawn.GetPlayerSpawnPoints} instead -- @realm server function GetSpawnEnts(shouldShuffle, forceAll) - -- forceAll is ignored because the new system doesn't use it anymore - local spawnEntities = plyspawn.GetPlayerSpawnPoints() + -- forceAll is ignored because the new system doesn't use it anymore + local spawnEntities = plyspawn.GetPlayerSpawnPoints() - if shouldShuffle then - table.Shuffle(spawnEntities) - end + if shouldShuffle then + table.Shuffle(spawnEntities) + end - return spawnEntities + return spawnEntities end --- @@ -235,14 +266,15 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerSelectSpawn -- @local function GM:PlayerSelectSpawn(ply, transition) - -- this overwrite is needed to suppress the GMod warning if no spawn entities were found - -- "[PlayerSelectSpawn] Error! No spawn points!" + -- this overwrite is needed to suppress the GMod warning if no spawn entities were found + -- "[PlayerSelectSpawn] Error! No spawn points!" end --- -- Called whenever a @{Player} spawns and must choose a model. -- A good place to assign a model to a @{Player}. -- @note This function may not work in your custom gamemode if you have overridden +-- stylua: ignore -- your @{GM:PlayerSpawn} and you do not use self.BaseClass.PlayerSpawn or @{hook.Run}. -- @param Player ply The @{Player} being chosen -- @hook @@ -250,16 +282,16 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerSetModel -- @local function GM:PlayerSetModel(ply) - -- The player modes has to be applied here since some player model selectors overwrite - -- this hook to suppress the TTT2 player models. If the model is assigned elsewhere, it - -- breaks with external model selectors. - if not IsValid(ply) then return end + -- The player modes has to be applied here since some player model selectors overwrite + -- this hook to suppress the TTT2 player models. If the model is assigned elsewhere, it + -- breaks with external model selectors. + if not IsValid(ply) then return end - -- this will call the overwritten internal function to modify the model - ply:SetModel(ply.defaultModel or GAMEMODE.playermodel) + -- this will call the overwritten internal function to modify the model + ply:SetModel(ply.defaultModel or GAMEMODE.playermodel) - -- Always clear color state, may later be changed in TTTPlayerSetColor - ply:SetColor(COLOR_WHITE) + -- Always clear color state, may later be changed in TTTPlayerSetColor + ply:SetColor(COLOR_WHITE) end --- @@ -268,16 +300,16 @@ end -- @hook -- @realm server function GM:TTTPlayerSetColor(ply) - local c = COLOR_WHITE + local c = COLOR_WHITE - if GAMEMODE.playercolor then - -- If this player has a colorable model, always use the same color as all - -- other colorable players, so color will never be the factor that lets - -- you tell players apart. - c = GAMEMODE.playercolor - end + if GAMEMODE.playercolor then + -- If this player has a colorable model, always use the same color as all + -- other colorable players, so color will never be the factor that lets + -- you tell players apart. + c = GAMEMODE.playercolor + end - ply:SetPlayerColor(Vector(c.r / 255.0, c.g / 255.0, c.b / 255.0)) + ply:SetPlayerColor(Vector(c.r / 255.0, c.g / 255.0, c.b / 255.0)) end --- @@ -290,7 +322,7 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:CanPlayerSuicide -- @local function GM:CanPlayerSuicide(ply) - return ply:IsTerror() + return ply:IsTerror() end --- @@ -305,20 +337,20 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerSwitchFlashlight -- @local function GM:PlayerSwitchFlashlight(ply, on) - if not IsValid(ply) then - return false - end - - -- add the flashlight "effect" here, and then deny the switch - -- this prevents the sound from playing, fixing the exploit - -- where weapon sound could be silenced using the flashlight sound - if on and ply:IsTerror() then - ply:AddEffects(EF_DIMLIGHT) - else - ply:RemoveEffects(EF_DIMLIGHT) - end - - return false + if not IsValid(ply) then + return false + end + + -- add the flashlight "effect" here, and then deny the switch + -- this prevents the sound from playing, fixing the exploit + -- where weapon sound could be silenced using the flashlight sound + if on and ply:IsTerror() then + ply:AddEffects(EF_DIMLIGHT) + else + ply:RemoveEffects(EF_DIMLIGHT) + end + + return false end --- @@ -331,9 +363,9 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerSpray -- @local function GM:PlayerSpray(ply) - if not IsValid(ply) or not ply:IsTerror() then - return true -- block - end + if not IsValid(ply) or not ply:IsTerror() then + return true -- block + end end --- @@ -350,7 +382,7 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerUse -- @local function GM:PlayerUse(ply, ent) - return ply:IsTerror() + return ply:IsTerror() end --- @@ -360,97 +392,122 @@ end -- from this hook will not be networked to the client, so make sure to do that on both realms -- @predicted -- @param Player ply The @{Player} pressing the key. If running client-side, this will always be @{LocalPlayer} --- @param number key The key that the @{Player} pressed using IN_Enums. +-- @param number key The key that the @{Player} pressed using IN_Enums. -- @hook -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:KeyPress -- @local function GM:KeyPress(ply, key) - if not IsValid(ply) then return end - - -- Spectator keys - if not ply:IsSpec() or ply:GetRagdollSpec() then return end - - -- Do not allow the spectator to gather information if they're about to revive. - if ply:IsReviving() then - LANG.Msg(ply, "spec_about_to_revive", nil, MSG_MSTACK_WARN) - - return - end - - if ply.propspec then - return PROPSPEC.Key(ply, key) - end - - ply:ResetViewRoll() - - if key == IN_ATTACK then - -- snap to random guy - ply:Spectate(OBS_MODE_ROAMING) - ply:SetEyeAngles(angle_zero) -- After exiting propspec, this could be set to awkward values - ply:SpectateEntity(nil) - - local alive = util.GetAlivePlayers() - - local alive_count = #alive - if alive_count < 1 then return end - - local target = alive[math.random(alive_count)] - - if IsValid(target) then - --ply:SetPos(target:EyePos()) - --ply:SetEyeAngles(target:EyeAngles()) - ply:Spectate(OBS_MODE_IN_EYE) - ply:SpectateEntity(target) - end - elseif key == IN_ATTACK2 then - -- spectate either the next guy or a random guy in chase - local target = util.GetNextAlivePlayer(ply:GetObserverTarget()) - - if IsValid(target) then - ply:Spectate(ply.spec_mode or OBS_MODE_IN_EYE) - ply:SpectateEntity(target) - end - elseif key == IN_DUCK then - local pos = ply:GetPos() - local ang = ply:EyeAngles() - - local target = ply:GetObserverTarget() - - -- Only set the spectator's position to the player they are spectating if they are in chase or eye mode. - -- They can use the reload key if they want to return to the person they're spectating - if IsValid(target) and target:IsPlayer() and ply:GetObserverMode() ~= OBS_MODE_ROAMING then - pos = target:EyePos() - ang = target:EyeAngles() - end - - -- reset - ply:Spectate(OBS_MODE_ROAMING) - ply:SpectateEntity(nil) - - ply:SetPos(pos) - ply:SetEyeAngles(ang) - - return true - elseif key == IN_JUMP then - -- unfuck if you're on a ladder etc - if ply:GetMoveType() ~= MOVETYPE_NOCLIP then - ply:SetMoveType(MOVETYPE_NOCLIP) - end - elseif key == IN_RELOAD then - local tgt = ply:GetObserverTarget() - - if not IsValid(tgt) or not tgt:IsPlayer() then return end - - if not ply.spec_mode or ply.spec_mode == OBS_MODE_IN_EYE then - ply.spec_mode = OBS_MODE_CHASE - elseif ply.spec_mode == OBS_MODE_CHASE then - ply.spec_mode = OBS_MODE_IN_EYE - end - -- roam stays roam - - ply:Spectate(ply.spec_mode) - end + if not IsValid(ply) or util.EditingModeActive(ply) then + return + end + + -- Spectator keys + if not ply:IsSpec() or ply:GetRagdollSpec() then + return + end + + if entspawnscript.IsEditing(ply) then + return + end + + -- Do not allow the spectator to gather information if they're about to revive. + if ply:IsReviving() then + LANG.Msg(ply, "spec_about_to_revive", nil, MSG_MSTACK_WARN) + + return + end + + if ply.propspec then + return PROPSPEC.Key(ply, key) + end + + ply:ResetViewRoll() + + if key == IN_ATTACK then + local tgt = ply:GetObserverTarget() + + if IsValid(tgt) and tgt:IsPlayer() then + local target = util.GetPreviousAlivePlayer(tgt) + + if IsValid(target) then + ply:Spectate(ply.spec_mode or OBS_MODE_IN_EYE) + ply:SpectateEntity(target) + end + end + elseif key == IN_ATTACK2 then + local tgt = ply:GetObserverTarget() + + if IsValid(tgt) and tgt:IsPlayer() then + local target = util.GetNextAlivePlayer(tgt) + + if IsValid(target) then + ply:Spectate(ply.spec_mode or OBS_MODE_IN_EYE) + ply:SpectateEntity(target) + end + else + -- when not focused yet, snap to random guy + ply:UnSpectate() + ply:Spectate(OBS_MODE_ROAMING) + ply:SetEyeAngles(angle_zero) -- After exiting propspec, this could be set to awkward values + + local alive = util.GetAlivePlayers() + + local alive_count = #alive + if alive_count < 1 then + return + end + + ---@cast alive -nil + local target = alive[math.random(alive_count)] + + if IsValid(target) then + ply:Spectate(OBS_MODE_IN_EYE) + ply:SpectateEntity(target) + end + end + elseif key == IN_DUCK then + local pos = ply:GetPos() + local ang = ply:EyeAngles() + + local target = ply:GetObserverTarget() + + -- Only set the spectator's position to the player they are spectating if they are in chase or eye mode. + -- They can use the reload key if they want to return to the person they're spectating + if IsValid(target) and target:IsPlayer() and ply:GetObserverMode() ~= OBS_MODE_ROAMING then + pos = target:EyePos() + ang = target:EyeAngles() + end + + -- reset + ply:UnSpectate() + ply:Spectate(OBS_MODE_ROAMING) + + ply:SetPos(pos) + ply:SetEyeAngles(ang) + + return true + elseif key == IN_JUMP then + -- unfuck if you're on a ladder etc + if ply:GetMoveType() ~= MOVETYPE_NOCLIP then + ply:SetMoveType(MOVETYPE_NOCLIP) + end + elseif key == IN_RELOAD then + local tgt = ply:GetObserverTarget() + + if not IsValid(tgt) or not tgt:IsPlayer() then + return + end + + if not ply.spec_mode or ply.spec_mode == OBS_MODE_IN_EYE then + ply.spec_mode = OBS_MODE_CHASE + elseif ply.spec_mode == OBS_MODE_CHASE then + ply.spec_mode = OBS_MODE_IN_EYE + end + -- roam stays roam + + ply:Spectate(ply.spec_mode) + end end --- @@ -458,40 +515,44 @@ end -- For a more general purpose @{function} that handles all kinds of input, see @{GM:PlayerButtonUp} -- @predicted -- @param Player ply The @{Player} pressing the key. If running client-side, this will always be @{LocalPlayer} --- @param number key The key that the @{Player} pressed using IN_Enums. +-- @param number key The key that the @{Player} pressed using IN_Enums. -- @hook -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:KeyRelease -- @local function GM:KeyRelease(ply, key) - if key ~= IN_USE or not IsValid(ply) or not ply:IsTerror() then return end - - -- see if we need to do some custom usekey overriding - local tr = util.TraceLine({ - start = ply:GetShootPos(), - endpos = ply:GetShootPos() + ply:GetAimVector() * 100, - filter = ply, - mask = MASK_SHOT - }) - - if not tr.Hit or not IsValid(tr.Entity) then return end - - if tr.Entity.CanUseKey and tr.Entity.UseOverride then - local phys = tr.Entity:GetPhysicsObject() - - if IsValid(phys) and not phys:HasGameFlag(FVPHYSICS_PLAYER_HELD) then - tr.Entity:UseOverride(ply) - - return true - else - -- do nothing, can't +use held objects - return true - end - elseif tr.Entity.player_ragdoll then - CORPSE.ShowSearch(ply, tr.Entity, ply:KeyDown(IN_WALK) or ply:KeyDownLast(IN_WALK)) -- Body Corpse Search Identify TODO - - return true - end + if key ~= IN_USE or not IsValid(ply) or not ply:IsTerror() then + return + end + + -- see if we need to do some custom usekey overriding + local tr = util.TraceLine({ + start = ply:GetShootPos(), + endpos = ply:GetShootPos() + ply:GetAimVector() * 100, + filter = ply, + mask = MASK_SHOT, + }) + + if not tr.Hit or not IsValid(tr.Entity) then + return + end + + if tr.Entity.CanUseKey and tr.Entity.UseOverride then + local phys = tr.Entity:GetPhysicsObject() + + if IsValid(phys) and not phys:HasGameFlag(FVPHYSICS_PLAYER_HELD) then + tr.Entity:UseOverride(ply) + + return true + else + -- do nothing, can't +use held objects + return true + end + elseif tr.Entity.player_ragdoll then + CORPSE.ShowSearch(ply, tr.Entity, ply:KeyDown(IN_WALK) or ply:KeyDownLast(IN_WALK)) -- Body Corpse Search Identify TODO + + return true + end end --- @@ -499,112 +560,118 @@ end -- can't let them search bodies. This sucks because searching bodies is -- fun. Hence on the client we override +use for specs and use this instead. local function SpecUseKey(ply, cmd, arg) - if not IsValid(ply) or not ply:IsSpec() then return end - - -- Do not allow the spectator to gather information if they're about to revive. - if ply:IsReviving() then - LANG.Msg(ply, "spec_about_to_revive", nil, MSG_MSTACK_WARN) - - return - end - - -- longer range than normal use - local tr = util.QuickTrace(ply:GetShootPos(), ply:GetAimVector() * 128, ply) - - if not tr.Hit or not IsValid(tr.Entity) then return end - - if tr.Entity.player_ragdoll then - if not ply:KeyDown(IN_WALK) then - CORPSE.ShowSearch(ply, tr.Entity) - else - ply:Spectate(OBS_MODE_IN_EYE) - ply:SpectateEntity(tr.Entity) - end - elseif tr.Entity:IsPlayer() and tr.Entity:IsActive() then - ply:Spectate(ply.spec_mode or OBS_MODE_IN_EYE) - ply:SpectateEntity(tr.Entity) - else - PROPSPEC.Target(ply, tr.Entity) - end + if not IsValid(ply) or not ply:IsSpec() then + return + end + + -- Do not allow the spectator to gather information if they're about to revive. + if ply:IsReviving() then + LANG.Msg(ply, "spec_about_to_revive", nil, MSG_MSTACK_WARN) + + return + end + + -- longer range than normal use + local tr = util.QuickTrace(ply:GetShootPos(), ply:GetAimVector() * 128, ply) + + if not tr.Hit or not IsValid(tr.Entity) then + return + end + + if tr.Entity.player_ragdoll then + if not ply:KeyDown(IN_WALK) then + CORPSE.ShowSearch(ply, tr.Entity) + else + ply:Spectate(OBS_MODE_IN_EYE) + ply:SpectateEntity(tr.Entity) + end + elseif tr.Entity:IsPlayer() and tr.Entity:IsActive() then + ply:Spectate(ply.spec_mode or OBS_MODE_IN_EYE) + ply:SpectateEntity(tr.Entity) + else + PROPSPEC.Target(ply, tr.Entity) + end end concommand.Add("ttt_spec_use", SpecUseKey) --- --- Called when a @{Player} leaves the server. See the player_disconnect gameevent for a shared version of this hook. +-- Called when a @{Player} leaves the server. See the player_disconnect gameevent for a shared version of this hook. -- @param Player ply -- @hook -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:PlayerDisconnected -- @local function GM:PlayerDisconnected(ply) - if IsValid(ply) then - -- Kill the player when necessary - if ply:IsTerror() and ply:Alive() then - ply:Kill() - end + if IsValid(ply) then + -- Kill the player when necessary + if ply:IsTerror() and ply:Alive() then + ply:Kill() + end - -- TODO ? - -- Prevent the disconnected player from being in the resends - ply:SetRole(ROLE_NONE) - end + -- TODO ? + -- Prevent the disconnected player from being in the resends + ply:SetRole(ROLE_NONE) + end - if GetRoundState() ~= ROUND_PREP then - TTT2NETTABLE[ply] = nil + if GetRoundState() ~= ROUND_PREP then + TTT2NETTABLE[ply] = nil - SendFullStateUpdate() - end + SendFullStateUpdate() + end - if KARMA.IsEnabled() then - KARMA.Remember(ply) - end + if KARMA.IsEnabled() then + KARMA.Remember(ply) + end - ttt2net.ResetClient(ply) + ttt2net.ResetClient(ply) end --- -- Death affairs local function CreateDeathEffect(ent, marked) - local pos = ent:GetPos() + Vector(0, 0, 20) - local jit = 35.0 - local jitter = Vector(math.Rand(-jit, jit), math.Rand(-jit, jit), 0) + local pos = ent:GetPos() + Vector(0, 0, 20) + local jit = 35.0 + local jitter = Vector(math.Rand(-jit, jit), math.Rand(-jit, jit), 0) - util.PaintDown(pos + jitter, "Blood", ent) + util.PaintDown(pos + jitter, "Blood", ent) - if marked then - util.PaintDown(pos, "Cross", ent) - end + if marked then + util.PaintDown(pos, "Cross", ent) + end end local deathsounds = { - Sound("player/death1.wav"), - Sound("player/death2.wav"), - Sound("player/death3.wav"), - Sound("player/death4.wav"), - Sound("player/death5.wav"), - Sound("player/death6.wav"), - Sound("vo/npc/male01/pain07.wav"), - Sound("vo/npc/male01/pain08.wav"), - Sound("vo/npc/male01/pain09.wav"), - Sound("vo/npc/male01/pain04.wav"), - Sound("vo/npc/Barney/ba_pain06.wav"), - Sound("vo/npc/Barney/ba_pain07.wav"), - Sound("vo/npc/Barney/ba_pain09.wav"), - Sound("vo/npc/Barney/ba_ohshit03.wav"), --heh - Sound("vo/npc/Barney/ba_no01.wav"), - Sound("vo/npc/male01/no02.wav"), - Sound("hostage/hpain/hpain1.wav"), - Sound("hostage/hpain/hpain2.wav"), - Sound("hostage/hpain/hpain3.wav"), - Sound("hostage/hpain/hpain4.wav"), - Sound("hostage/hpain/hpain5.wav"), - Sound("hostage/hpain/hpain6.wav") + Sound("player/death1.wav"), + Sound("player/death2.wav"), + Sound("player/death3.wav"), + Sound("player/death4.wav"), + Sound("player/death5.wav"), + Sound("player/death6.wav"), + Sound("vo/npc/male01/pain07.wav"), + Sound("vo/npc/male01/pain08.wav"), + Sound("vo/npc/male01/pain09.wav"), + Sound("vo/npc/male01/pain04.wav"), + Sound("vo/npc/Barney/ba_pain06.wav"), + Sound("vo/npc/Barney/ba_pain07.wav"), + Sound("vo/npc/Barney/ba_pain09.wav"), + Sound("vo/npc/Barney/ba_ohshit03.wav"), --heh + Sound("vo/npc/Barney/ba_no01.wav"), + Sound("vo/npc/male01/no02.wav"), + Sound("hostage/hpain/hpain1.wav"), + Sound("hostage/hpain/hpain2.wav"), + Sound("hostage/hpain/hpain3.wav"), + Sound("hostage/hpain/hpain4.wav"), + Sound("hostage/hpain/hpain5.wav"), + Sound("hostage/hpain/hpain6.wav"), } local deathsounds_count = #deathsounds local function PlayDeathSound(victim) - if not IsValid(victim) then return end + if not IsValid(victim) then + return + end - sound.Play(deathsounds[math.random(deathsounds_count)], victim:GetShootPos(), 90, 100) + sound.Play(deathsounds[math.random(deathsounds_count)], victim:GetShootPos(), 90, 100) end --- @@ -618,85 +685,113 @@ end -- @note @{Player:Alive} returns true when this is called -- @param Player ply -- @param Player|Entity attacker @{Player} or @{Entity} that killed the @{Player} --- @param DamageInfo dmginfo +-- @param CTakeDamageInfo dmginfo -- @hook -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:DoPlayerDeath -- @local function GM:DoPlayerDeath(ply, attacker, dmginfo) - if ply:IsSpec() then return end - - -- Experimental: Fire a last shot if ironsighting and not headshot - if ttt_dyingshot:GetBool() then - local wep = ply:GetActiveWeapon() - - if IsValid(wep) and wep.DyingShot and not ply.was_headshot and dmginfo:IsBulletDamage() then - local fired = wep:DyingShot() - if fired then return end - end - - -- Note that funny things can happen here because we fire a gun while the - -- player is dead. Specifically, this DoPlayerDeath is run twice for - -- him. This is ugly, and we have to return the first one to prevent crazy - -- shit. - end - - -- Drop all weapons - local weps = ply:GetWeapons() - - for i = 1, #weps do - local wep = weps[i] - - WEPS.DropNotifiedWeapon(ply, wep, true) -- with ammo in them - - if isfunction(wep.DampenDrop) then - wep:DampenDrop() - end - end - - if IsValid(ply.hat) then - ply.hat:Drop() - end - - -- Create ragdoll and hook up marking effects - local rag = CORPSE.Create(ply, attacker, dmginfo) - - ply.server_ragdoll = rag -- nil if clientside - - CreateDeathEffect(ply, false) - - util.StartBleeding(rag, dmginfo:GetDamage(), 15) - - -- Score only when there is a round active. - if GetRoundState() == ROUND_ACTIVE then - events.Trigger(EVENT_KILL, ply, attacker, dmginfo) - - if IsValid(attacker) and attacker:IsPlayer() then - attacker:RecordKill(ply) - - DamageLog(Format("KILL:\t %s [%s] killed %s [%s]", attacker:Nick(), attacker:GetRoleString(), ply:Nick(), ply:GetRoleString())) - else - DamageLog(Format("KILL:\t killed %s [%s]", ply:Nick(), ply:GetRoleString())) - end - - KARMA.Killed(attacker, ply, dmginfo) - end - - -- Clear out any weapon or equipment we still have - ply:StripAll() - - -- Tell the client to send their chat contents - ply:SendLastWords(dmginfo) - - local killwep = util.WeaponFromDamage(dmginfo) - - -- headshots, knife damage, and weapons tagged as silent all prevent death - -- sound from occurring - if not ply.was_headshot and not dmginfo:IsDamageType(DMG_SLASH) and not (IsValid(killwep) and killwep.IsSilent) then - PlayDeathSound(ply) - end - - credits.HandleKillCreditsAward(ply, attacker) + if entspawnscript.IsEditing(ply) then + entspawnscript.StopEditing(ply) + end + + -- Prevent bots from wandering and creating logspam. + if ply:IsBot() and ttt2_bots_lock_on_death:GetBool() then + ply:Lock() + end + + if ply:IsSpec() then + return + end + + -- Experimental: Fire a last shot if ironsighting and not headshot + if ttt_dyingshot:GetBool() then + local wep = ply:GetActiveWeapon() + + if IsValid(wep) and wep.DyingShot and not ply.was_headshot and dmginfo:IsBulletDamage() then + local fired = wep:DyingShot() + if fired then + return + end + end + + -- Note that funny things can happen here because we fire a gun while the + -- player is dead. Specifically, this DoPlayerDeath is run twice for + -- him. This is ugly, and we have to return the first one to prevent crazy + -- shit. + end + + -- Drop all weapons + local weps = ply:GetWeapons() + + for i = 1, #weps do + local wep = weps[i] + + WEPS.DropNotifiedWeapon(ply, wep, true) -- with ammo in them + + if isfunction(wep.DampenDrop) then + wep:DampenDrop() + end + end + + if IsValid(ply.hat) then + ply.hat:Drop() + end + + -- Create ragdoll and hook up marking effects + local rag = CORPSE.Create(ply, attacker, dmginfo) + + ply.server_ragdoll = rag + + CreateDeathEffect(ply, false) + + util.StartBleeding(rag, dmginfo:GetDamage(), 15) + + -- Score only when there is a round active. + if GetRoundState() == ROUND_ACTIVE then + events.Trigger(EVENT_KILL, ply, attacker, dmginfo) + + if IsValid(attacker) and attacker:IsPlayer() then + attacker:RecordKill(ply) + + DamageLog( + Format( + "KILL:\t %s [%s] killed %s [%s]", + attacker:Nick(), + attacker:GetRoleString(), + ply:Nick(), + ply:GetRoleString() + ) + ) + else + DamageLog( + Format("KILL:\t killed %s [%s]", ply:Nick(), ply:GetRoleString()) + ) + end + + KARMA.Killed(attacker, ply, dmginfo) + end + + -- Clear out any weapon or equipment we still have + ply:StripAll() + + -- Tell the client to send their chat contents + ply:SendLastWords(dmginfo) + + local killwep = util.WeaponFromDamage(dmginfo) + + -- headshots, knife damage, and weapons tagged as silent all prevent death + -- sound from occurring + ---@cast killwep -nil + if + not ply.was_headshot + and not dmginfo:IsDamageType(DMG_SLASH) + and not (IsValid(killwep) and killwep.IsSilent) + then + PlayDeathSound(ply) + end + + credits.HandleKillCreditsAward(ply, attacker) end --- @@ -716,48 +811,50 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerDeath -- @local function GM:PlayerDeath(victim, infl, attacker) - victim:SetLastDeathPosition(victim:GetPos()) - victim:IncreaseRoundDeathCounter() - - -- stop bleeding - util.StopBleeding(victim) - - -- tell no one - self:PlayerSilentDeath(victim) - - -- a function to handle the rolespecific stuff that should be done on - -- rolechange and respawn (while a round is active) - if victim:IsActive() then - victim:GetSubRoleData():RemoveRoleLoadout(victim, false) - end - - victim:SetTeam(TEAM_SPEC) - victim:Freeze(false) - victim:SetRagdollSpec(true) - victim:Spectate(OBS_MODE_IN_EYE) - - local rag_ent = victim.server_ragdoll or victim:GetRagdollEntity() - - victim:SpectateEntity(rag_ent) - victim:Flashlight(false) - victim:Extinguish() - - net.Start("TTT_PlayerDied") - net.Send(victim) - - --- - -- @realm server - if HasteMode() and GetRoundState() == ROUND_ACTIVE and not hook.Run("TTT2ShouldSkipHaste", victim, attacker) then - IncRoundEnd(GetConVar("ttt_haste_minutes_per_death"):GetFloat() * 60) - end - - if IsValid(attacker) and attacker:IsPlayer() and attacker ~= victim and attacker:IsActive() then - victim.killerSpec = attacker - end - - --- - -- @realm server - hook.Run("TTT2PostPlayerDeath", victim, infl, attacker) + victim:SetLastDeathPosition(victim:GetPos()) + victim:IncreaseRoundDeathCounter() + + -- stop bleeding + util.StopBleeding(victim) + + -- tell no one + self:PlayerSilentDeath(victim) + + -- a function to handle the rolespecific stuff that should be done on + -- rolechange and respawn (while a round is active) + if victim:IsActive() then + victim:GetSubRoleData():RemoveRoleLoadout(victim, false) + end + + victim:SetTeam(TEAM_SPEC) + victim:Freeze(false) + victim:SetRagdollSpec(true) + victim:Spectate(OBS_MODE_IN_EYE) + + local rag_ent = victim.server_ragdoll or victim:GetRagdollEntity() + + victim:SpectateEntity(rag_ent) + victim:Flashlight(false) + victim:Extinguish() + + net.Start("TTT_PlayerDied") + net.Send(victim) + + --- + -- @realm server + -- stylua: ignore + if HasteMode() and GetRoundState() == ROUND_ACTIVE and not hook.Run("TTT2ShouldSkipHaste", victim, attacker) then + IncRoundEnd(GetConVar("ttt_haste_minutes_per_death"):GetFloat() * 60) + end + + if IsValid(attacker) and attacker:IsPlayer() and attacker ~= victim and attacker:IsActive() then + victim.killerSpec = attacker + end + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2PostPlayerDeath", victim, infl, attacker) end --- @@ -768,8 +865,8 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerSilentDeath -- @realm server function GM:PlayerSilentDeath(victim) - victim:SetLastDeathPosition(victim:GetPos()) - victim:IncreaseRoundDeathCounter() + victim:SetLastDeathPosition(victim:GetPos()) + victim:IncreaseRoundDeathCounter() end --- @@ -781,7 +878,7 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerDeathSound -- @local function GM:PlayerDeathSound() - return true + return true end --- @@ -794,18 +891,23 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PostPlayerDeath -- @local function GM:PostPlayerDeath(ply) - -- endless respawn player if he dies in preparing time - if GetRoundState() ~= ROUND_PREP or not GetConVar("ttt2_prep_respawn"):GetBool() then return end - - timer.Simple(1, function() - if not IsValid(ply) then return end - - ply:SpawnForRound(true) - - --- - -- @realm server - hook.Run("PlayerLoadout", ply, false) - end) + -- endless respawn player if he dies in preparing time + if GetRoundState() ~= ROUND_PREP or not GetConVar("ttt2_prep_respawn"):GetBool() then + return + end + + timer.Simple(1, function() + if not IsValid(ply) then + return + end + + ply:SpawnForRound(true) + + --- + -- @realm server + -- stylua: ignore + hook.Run("PlayerLoadout", ply, false) + end) end --- @@ -815,72 +917,83 @@ end -- @realm server -- @see GM:PlayerDeathThink function GM:SpectatorThink(ply) - -- when spectating a ragdoll after death - if ply:GetRagdollSpec() then - local to_switch, to_chase, to_roam = 2, 5, 8 - local elapsed = CurTime() - ply.spec_ragdoll_start - local clicked = ply:KeyPressed(IN_ATTACK) - - -- After first click, go into chase cam, then after another click, to into - -- eye mode. If no clicks made, go into chase after X secs, and eye mode after Y. - -- Don't switch for a second in case the player was shooting when he died, - -- this would make him accidentally switch out of ragdoll cam. - - local m = ply:GetObserverMode() - - if m == OBS_MODE_CHASE and clicked or elapsed > to_roam then - - -- free roam mode - ply:SetRagdollSpec(false) - - if ply.killerSpec and IsValid(ply.killerSpec) and ply.killerSpec:IsPlayer() and ply.killerSpec:IsActive() then - ply:Spectate(OBS_MODE_IN_EYE) - ply:SpectateEntity(ply.killerSpec) - else - ply:Spectate(OBS_MODE_ROAMING) - - -- move to spectator spawn if mapper defined any - local spec_spawns = ents.FindByClass("ttt_spectator_spawn") - local spec_spawns_count = #spec_spawns - - if spec_spawns_count > 0 then - local spawnPoint = spec_spawns[math.random(spec_spawns_count)] - - ply:SetPos(spawnPoint:GetPos()) - ply:SetEyeAngles(spawnPoint:GetAngles()) - end - end - elseif m == OBS_MODE_IN_EYE and clicked and elapsed > to_switch or elapsed > to_chase then - -- start following ragdoll - ply:Spectate(OBS_MODE_CHASE) - end - - if not IsValid(ply.server_ragdoll) then - ply:SetRagdollSpec(false) - end - - -- when roaming and messing with ladders - elseif ply:GetMoveType() < MOVETYPE_NOCLIP and ply:GetMoveType() > 0 or ply:GetMoveType() == MOVETYPE_LADDER then - ply:Spectate(OBS_MODE_ROAMING) - end - - -- when spectating a player - if ply:GetObserverMode() ~= OBS_MODE_ROAMING and not ply.propspec and not ply:GetRagdollSpec() then - local tgt = ply:GetObserverTarget() - - if IsValid(tgt) and tgt:IsPlayer() then - if not tgt:IsTerror() or not tgt:Alive() then - -- stop speccing as soon as target dies - ply:Spectate(OBS_MODE_ROAMING) - ply:SpectateEntity(nil) - elseif GetRoundState() == ROUND_ACTIVE then - -- Sync position to target. Uglier than parenting, but unlike - -- parenting this is less sensitive to breakage: if we are - -- no longer spectating, we will never sync to their position. - ply:SetPos(tgt:GetPos()) - end - end - end + -- when spectating a ragdoll after death + if ply:GetRagdollSpec() then + local to_switch, to_chase, to_roam = 2, 5, 8 + local elapsed = CurTime() - ply.spec_ragdoll_start + local clicked = ply:KeyPressed(IN_ATTACK) + + -- After first click, go into chase cam, then after another click, to into + -- eye mode. If no clicks made, go into chase after X secs, and eye mode after Y. + -- Don't switch for a second in case the player was shooting when he died, + -- this would make him accidentally switch out of ragdoll cam. + + local m = ply:GetObserverMode() + + if m == OBS_MODE_CHASE and clicked or elapsed > to_roam then + -- free roam mode + ply:SetRagdollSpec(false) + + if + ply.killerSpec + and IsValid(ply.killerSpec) + and ply.killerSpec:IsPlayer() + and ply.killerSpec:IsActive() + then + ply:Spectate(OBS_MODE_IN_EYE) + ply:SpectateEntity(ply.killerSpec) + else + ply:Spectate(OBS_MODE_ROAMING) + + -- move to spectator spawn if mapper defined any + local spec_spawns = ents.FindByClass("ttt_spectator_spawn") + local spec_spawns_count = #spec_spawns + + if spec_spawns_count > 0 then + local spawnPoint = spec_spawns[math.random(spec_spawns_count)] + + ply:SetPos(spawnPoint:GetPos()) + ply:SetEyeAngles(spawnPoint:GetAngles()) + end + end + elseif m == OBS_MODE_IN_EYE and clicked and elapsed > to_switch or elapsed > to_chase then + -- start following ragdoll + ply:Spectate(OBS_MODE_CHASE) + end + + if not IsValid(ply.server_ragdoll) then + ply:SetRagdollSpec(false) + end + + -- when roaming and messing with ladders + elseif + ply:GetMoveType() < MOVETYPE_NOCLIP and ply:GetMoveType() > 0 + or ply:GetMoveType() == MOVETYPE_LADDER + then + ply:Spectate(OBS_MODE_ROAMING) + end + + -- when spectating a player + if + ply:GetObserverMode() ~= OBS_MODE_ROAMING + and not ply.propspec + and not ply:GetRagdollSpec() + then + local tgt = ply:GetObserverTarget() + + if IsValid(tgt) and tgt:IsPlayer() then + if not tgt:IsTerror() or not tgt:Alive() then + -- stop speccing as soon as target dies + ply:UnSpectate() + ply:Spectate(OBS_MODE_ROAMING) + elseif GetRoundState() == ROUND_ACTIVE then + -- Sync position to target. Uglier than parenting, but unlike + -- parenting this is less sensitive to breakage: if we are + -- no longer spectating, we will never sync to their position. + ply:SetPos(tgt:GetPos()) + end + end + end end --- @@ -896,36 +1009,34 @@ GM.PlayerDeathThink = GM.SpectatorThink -- Called when a @{Player} has been hit by a trace and damaged (such as from a bullet). -- Returning true overrides the damage handling and prevents @{GM:ScalePlayerDamage} from being called. -- @param Player ply The @{Player} that has been hit --- @param DamageInfo dmginfo The damage info of the bullet +-- @param CTakeDamageInfo dmginfo The damage info of the bullet -- @param Vector dir Normalized vector direction of the bullet's path -- @param table trace The trace of the bullet's path, see --- TraceResult structure +-- TraceResult structure -- @return boolean Override engine handling -- @hook -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:PlayerTraceAttack -- @local function GM:PlayerTraceAttack(ply, dmginfo, dir, trace) - if IsValid(ply.hat) and trace.HitGroup == HITGROUP_HEAD then - ply.hat:Drop(dir) - end + if IsValid(ply.hat) and trace.HitGroup == HITGROUP_HEAD then + ply.hat:Drop(dir) + end - ply.hit_trace = trace + ply.hit_trace = trace - return false + return false end --- -- Called when a @{Player} has been hurt by an explosion. Override to disable default sound effect. -- @param Player ply @{Player} who has been hurt --- @param DamageInfo dmginfo Damage info from explsion +-- @param CTakeDamageInfo dmginfo Damage info from explsion -- @hook -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:OnDamagedByExplosion -- @local -function GM:OnDamagedByExplosion(ply, dmginfo) - -end +function GM:OnDamagedByExplosion(ply, dmginfo) end --- -- This hook allows you to change how much damage a @{Player} receives when one takes damage to a specific body part. @@ -933,8 +1044,8 @@ end -- so you should use @{GM:EntityTakeDamage} instead if you need to detect ALL damage. -- @param Player ply The @{Player} taking damage -- @param number hitgroup The hitgroup where the @{Player} took damage. See --- HITGROUP_Enums --- @param DamageInfo dmginfo The damage info +-- HITGROUP_Enums +-- @param CTakeDamageInfo dmginfo The damage info -- @return boolean Return true to prevent damage that this hook is called for, stop blood particle effects and blood decals.
        -- It is possible to return true only on client ( This will work only in multiplayer ) to stop the effects but still take damage. -- @hook @@ -942,41 +1053,44 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:ScalePlayerDamage -- @local function GM:ScalePlayerDamage(ply, hitgroup, dmginfo) - if ply:IsPlayer() and dmginfo:GetAttacker():IsPlayer() and GetRoundState() == 2 then - dmginfo:ScaleDamage(0) - end - - ply.was_headshot = false - - -- actual damage scaling - if hitgroup == HITGROUP_HEAD then - -- headshot if it was dealt by a bullet - ply.was_headshot = dmginfo:IsBulletDamage() - - local wep = util.WeaponFromDamage(dmginfo) - - if IsValid(wep) then - local s = wep:GetHeadshotMultiplier(ply, dmginfo) or 2 - - dmginfo:ScaleDamage(s) - end - elseif hitgroup == HITGROUP_LEFTARM - or hitgroup == HITGROUP_RIGHTARM - or hitgroup == HITGROUP_LEFTLEG - or hitgroup == HITGROUP_RIGHTLEG - or hitgroup == HITGROUP_GEAR - then - dmginfo:ScaleDamage(0.55) - end - - -- Keep ignite-burn damage etc on old levels - if dmginfo:IsDamageType(DMG_DIRECT) - or dmginfo:IsExplosionDamage() - or dmginfo:IsDamageType(DMG_FALL) - or dmginfo:IsDamageType(DMG_PHYSGUN) - then - dmginfo:ScaleDamage(2) - end + if ply:IsPlayer() and dmginfo:GetAttacker():IsPlayer() and GetRoundState() == 2 then + dmginfo:ScaleDamage(0) + end + + ply.was_headshot = false + + -- actual damage scaling + if hitgroup == HITGROUP_HEAD then + -- headshot if it was dealt by a bullet + ply.was_headshot = dmginfo:IsBulletDamage() + + local wep = util.WeaponFromDamage(dmginfo) + + if IsValid(wep) then + ---@cast wep -nil + local s = wep:GetHeadshotMultiplier(ply, dmginfo) or 2 + + dmginfo:ScaleDamage(s) + end + elseif + hitgroup == HITGROUP_LEFTARM + or hitgroup == HITGROUP_RIGHTARM + or hitgroup == HITGROUP_LEFTLEG + or hitgroup == HITGROUP_RIGHTLEG + or hitgroup == HITGROUP_GEAR + then + dmginfo:ScaleDamage(0.55) + end + + -- Keep ignite-burn damage etc on old levels + if + dmginfo:IsDamageType(DMG_DIRECT) + or dmginfo:IsExplosionDamage() + or dmginfo:IsDamageType(DMG_FALL) + or dmginfo:IsDamageType(DMG_PHYSGUN) + then + dmginfo:ScaleDamage(2) + end end --- @@ -992,26 +1106,29 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:GetFallDamage -- @local function GM:GetFallDamage(ply, speed) - return 0 + return 0 end local fallsounds = { - Sound("player/damage1.wav"), - Sound("player/damage2.wav"), - Sound("player/damage3.wav") + Sound("player/damage1.wav"), + Sound("player/damage2.wav"), + Sound("player/damage3.wav"), } local fallsounds_count = #fallsounds --- -- @realm server +-- stylua: ignore local falldmg_enable = CreateConVar("ttt2_falldmg_enable", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local falldmg_min_vel = CreateConVar("ttt2_falldmg_min_velocity", "450", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local falldmg_expo = CreateConVar("ttt2_falldmg_exponent", "1.75", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- @@ -1027,78 +1144,92 @@ local falldmg_expo = CreateConVar("ttt2_falldmg_exponent", "1.75", {FCVAR_NOTIFY -- @ref https://wiki.facepunch.com/gmod/GM:OnPlayerHitGround -- @local function GM:OnPlayerHitGround(ply, in_water, on_floater, speed) - if not falldmg_enable:GetBool() or not IsValid(ply) or in_water or speed < falldmg_min_vel:GetInt() then return end - - -- Everything over a threshold hurts you, rising exponentially with speed - local damage = math.pow(0.05 * (speed - (falldmg_min_vel:GetInt() - 30)), falldmg_expo:GetFloat()) - - -- Halve damage dealt if the impacted object is floating on water - if on_floater then - damage = damage * 0.5 - end - - -- if we fell on a dude, that hurts (him) - local ground = ply:GetGroundEntity() - - if IsValid(ground) and ground:IsPlayer() then - if math.floor(damage) > 0 then - local att = ply - - -- if the faller was pushed, that person should get attrib - local push = ply.was_pushed - - if push and math.max(push.t or 0, push.hurt or 0) > CurTime() - 4 then - att = push.att - end - - local dmg = DamageInfo() - - if att == ply then - -- hijack physgun damage as a marker of this type of kill - dmg:SetDamageType(DMG_CRUSH + DMG_PHYSGUN) - else - -- if attributing to pusher, show more generic crush msg for now - dmg:SetDamageType(DMG_CRUSH) - end - - dmg:SetAttacker(att) - dmg:SetInflictor(att) - dmg:SetDamageForce(Vector(0, 0, -1)) - dmg:SetDamage(damage) - - ground:TakeDamageInfo(dmg) - end - - -- our own falling damage is cushioned - damage = damage / 3 - end - - if math.floor(damage) > 0 then - local dmg = DamageInfo() - - dmg:SetDamageType(DMG_FALL) - dmg:SetAttacker(game.GetWorld()) - dmg:SetInflictor(game.GetWorld()) - dmg:SetDamageForce(Vector(0, 0, 1)) - dmg:SetDamage(damage) - - ply:TakeDamageInfo(dmg) - - -- play CS:S fall sound if we got somewhat significant damage - if damage > 5 then - sound.Play(fallsounds[math.random(fallsounds_count)], ply:GetShootPos(), 55 + math.Clamp(damage, 0, 50), 100) - end - end + if + not falldmg_enable:GetBool() + or not IsValid(ply) + or in_water + or speed < falldmg_min_vel:GetInt() + then + return + end + + -- Everything over a threshold hurts you, rising exponentially with speed + local damage = + math.pow(0.05 * (speed - (falldmg_min_vel:GetInt() - 30)), falldmg_expo:GetFloat()) + + -- Halve damage dealt if the impacted object is floating on water + if on_floater then + damage = damage * 0.5 + end + + -- if we fell on a dude, that hurts (him) + local ground = ply:GetGroundEntity() + + if IsValid(ground) and ground:IsPlayer() then + if math.floor(damage) > 0 then + local att = ply + + -- if the faller was pushed, that person should get attrib + local push = ply.was_pushed + + if push and math.max(push.t or 0, push.hurt or 0) > CurTime() - 4 then + att = push.att + end + + local dmg = DamageInfo() + + if att == ply then + -- hijack physgun damage as a marker of this type of kill + dmg:SetDamageType(DMG_CRUSH + DMG_PHYSGUN) + else + -- if attributing to pusher, show more generic crush msg for now + dmg:SetDamageType(DMG_CRUSH) + end + + dmg:SetAttacker(att) + dmg:SetInflictor(att) + dmg:SetDamageForce(Vector(0, 0, -1)) + dmg:SetDamage(damage) + + ground:TakeDamageInfo(dmg) + end + + -- our own falling damage is cushioned + damage = damage / 3 + end + + if math.floor(damage) > 0 then + local dmg = DamageInfo() + + dmg:SetDamageType(DMG_FALL) + dmg:SetAttacker(game.GetWorld()) + dmg:SetInflictor(game.GetWorld()) + dmg:SetDamageForce(Vector(0, 0, 1)) + dmg:SetDamage(damage) + + ply:TakeDamageInfo(dmg) + + -- play CS:S fall sound if we got somewhat significant damage + if damage > 5 then + sound.Play( + fallsounds[math.random(fallsounds_count)], + ply:GetShootPos(), + 55 + math.Clamp(damage, 0, 50), + 100 + ) + end + end end --- -- @realm server +-- stylua: ignore local ttt_postdm = CreateConVar("ttt_postround_dm", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- Called when an entity takes damage. You can modify all parts of the damage info in this hook. -- @param Entity ent The @{Entity} taking damage --- @param DamageInfo dmginfo Damage info +-- @param CTakeDamageInfo dmginfo Damage info -- @return boolean Return true to completely block the damage event -- @note e.g. no damage during prep, etc -- @hook @@ -1106,43 +1237,47 @@ local ttt_postdm = CreateConVar("ttt_postround_dm", "0", {FCVAR_NOTIFY, FCVAR_AR -- @ref https://wiki.facepunch.com/gmod/GM:EntityTakeDamage -- @local function GM:EntityTakeDamage(ent, dmginfo) - if not IsValid(ent) then return end - - door.HandleDamage(ent, dmginfo) - door.HandlePropDamage(ent, dmginfo) - - local att = dmginfo:GetAttacker() - - --- - -- @realm server - if not hook.Run("AllowPVP") then - -- if player vs player damage, or if damage versus a prop, then zero - if ent:IsExplosive() or ent:IsPlayer() and IsValid(att) and att:IsPlayer() then - dmginfo:ScaleDamage(0) - dmginfo:SetDamage(0) - end - elseif ent:IsPlayer() then - --- - -- @realm server - hook.Run("PlayerTakeDamage", ent, dmginfo:GetInflictor(), att, dmginfo:GetDamage(), dmginfo) - elseif ent:IsExplosive() then - -- When a barrel hits a player, that player damages the barrel because - -- Source physics. This gives stupid results like a player who gets hit - -- with a barrel being blamed for killing himself or even his attacker. - if IsValid(att) - and att:IsPlayer() - and dmginfo:IsDamageType(DMG_CRUSH) - and IsValid(ent:GetPhysicsAttacker()) - then - dmginfo:SetAttacker(ent:GetPhysicsAttacker()) - dmginfo:ScaleDamage(0) - dmginfo:SetDamage(0) - end - elseif ent.is_pinned and ent.OnPinnedDamage then - ent:OnPinnedDamage(dmginfo) - - dmginfo:SetDamage(0) - end + if not IsValid(ent) then + return + end + + door.HandleDamage(ent, dmginfo) + door.HandlePropDamage(ent, dmginfo) + + local att = dmginfo:GetAttacker() + + --- + -- @realm server + -- stylua: ignore + if not hook.Run("AllowPVP") then + -- if player vs player damage, or if damage versus a prop, then zero + if ent:IsExplosive() or ent:IsPlayer() and IsValid(att) and att:IsPlayer() then + dmginfo:ScaleDamage(0) + dmginfo:SetDamage(0) + end + elseif ent:IsPlayer() then + --- + -- @realm server + -- stylua: ignore + hook.Run("PlayerTakeDamage", ent, dmginfo:GetInflictor(), att, dmginfo:GetDamage(), dmginfo) + elseif ent:IsExplosive() then + -- When a barrel hits a player, that player damages the barrel because + -- Source physics. This gives stupid results like a player who gets hit + -- with a barrel being blamed for killing himself or even his attacker. + if IsValid(att) + and att:IsPlayer() + and dmginfo:IsDamageType(DMG_CRUSH) + and IsValid(ent:GetPhysicsAttacker()) + then + dmginfo:SetAttacker(ent:GetPhysicsAttacker()) + dmginfo:ScaleDamage(0) + dmginfo:SetDamage(0) + end + elseif ent.is_pinned and ent.OnPinnedDamage then + ent:OnPinnedDamage(dmginfo) + + dmginfo:SetDamage(0) + end end --- @@ -1151,166 +1286,220 @@ end -- @param Entity infl the inflictor -- @param Player|Entity att the attacker -- @param number amount amount of damage --- @param DamageInfo dmginfo Damage info +-- @param CTakeDamageInfo dmginfo Damage info -- @hook -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:EntityTakeDamage function GM:PlayerTakeDamage(ent, infl, att, amount, dmginfo) - -- Change damage attribution if necessary - if infl or att then - local hurter, owner, owner_time - - -- fall back to the attacker if there is no inflictor - if IsValid(infl) then - hurter = infl - elseif IsValid(att) then - hurter = att - end - - -- have a damage owner? - if hurter and IsValid(hurter:GetDamageOwner()) then - owner, owner_time = hurter:GetDamageOwner() - - -- barrel bangs can hurt us even if we threw them, but that's our fault - elseif hurter and ent == hurter:GetPhysicsAttacker() and dmginfo:IsDamageType(DMG_BLAST) then - owner = ent - elseif hurter and hurter:IsVehicle() and IsValid(hurter:GetDriver()) then - owner = hurter:GetDriver() - end - - -- if we were hurt by a trap OR by a non-ply ent, and we were pushed - -- recently, then our pusher is the attacker - if owner_time or not IsValid(att) or not att:IsPlayer() then - local push = ent.was_pushed - - if push and IsValid(push.att) and push.t then - -- push must be within the last 5 seconds, and must be done - -- after the trap was enabled (if any) - owner_time = owner_time or 0 - - local t = math.max(push.t or 0, push.hurt or 0) - - if t > owner_time and t > CurTime() - 4 then - owner = push.att - - -- pushed by a trap? - if IsValid(push.infl) then - dmginfo:SetInflictor(push.infl) - end - - -- for slow-hurting traps we do leech-like damage timing - push.hurt = CurTime() - end - end - end - - -- if we are being hurt by a physics object, we will take damage from - -- the world entity as well, which screws with damage attribution so we - -- need to detect and work around that - if IsValid(owner) and dmginfo:IsDamageType(DMG_CRUSH) then - -- we should be able to use the push system for this, as the cases are - -- similar: event causes future damage but should still be attributed - -- physics traps can also push you to your death, for example - local push = ent.was_pushed or {} - - -- if we already blamed this on a pusher, no need to do more - -- else we override whatever was in was_pushed with info pointing - -- at our damage owner - if push.att ~= owner then - owner_time = owner_time or CurTime() - - push.att = owner - push.t = owner_time - push.hurt = CurTime() - - -- store the current inflictor so that we can attribute it as the - -- trap used by the player in the event - if IsValid(infl) then - push.infl = infl - end - - -- make sure this is set, for if we created a new table - ent.was_pushed = push - end - end - - -- make the owner of the damage the attacker - att = IsValid(owner) and owner or att - - dmginfo:SetAttacker(att) - end - - -- scale phys damage caused by props - if dmginfo:IsDamageType(DMG_CRUSH) and IsValid(att) and not dmginfo:IsDamageType(DMG_PHYSGUN) then - -- player falling on player, or player hurt by prop? - -- this is prop-based physics damage - dmginfo:ScaleDamage(0.25) - - -- if the prop is held, no damage - if IsValid(infl) and IsValid(infl:GetOwner()) and infl:GetOwner():IsPlayer() then - dmginfo:ScaleDamage(0) - dmginfo:SetDamage(0) - end - end - - -- handle fire attacker - if ent.ignite_info and dmginfo:IsDamageType(DMG_DIRECT) then - local datt = dmginfo:GetAttacker() - - if not IsValid(datt) or not datt:IsPlayer() then - local ignite = ent.ignite_info - - if IsValid(ignite.att) and IsValid(ignite.infl) then - dmginfo:SetAttacker(ignite.att) - dmginfo:SetInflictor(ignite.infl) - end - end - end - - -- try to work out if this was push-induced leech-water damage (common on - -- some popular maps like dm_island17) - if ent.was_pushed - and ent == att - and dmginfo:GetDamageType() == DMG_GENERIC - and util.BitSet(util.PointContents(dmginfo:GetDamagePosition()), CONTENTS_WATER) - then - local t = math.max(ent.was_pushed.t or 0, ent.was_pushed.hurt or 0) - - if t > CurTime() - 3 then - dmginfo:SetAttacker(ent.was_pushed.att) - - ent.was_pushed.hurt = CurTime() - end - end - - -- start painting blood decals - util.StartBleeding(ent, dmginfo:GetDamage(), 5) - - -- handle damage scaling by karma - if IsValid(att) and att:IsPlayer() and GetRoundState() == ROUND_ACTIVE and math.floor(dmginfo:GetDamage()) > 0 then - if KARMA.IsEnabled() and ent ~= att and not dmginfo:IsDamageType(DMG_SLASH) then - -- scale everything to karma damage factor except the knife, because it assumes a kill - dmginfo:ScaleDamage(att:GetDamageFactor()) - end - - -- before the karma is calculated, but after all other damage hooks / damage change is processed, - -- the armor system should come into place (GM functions are called last) - ARMOR:HandlePlayerTakeDamage(ent, infl, att, amount, dmginfo) - - if ent ~= att then - -- process the effects of the damage on karma - KARMA.Hurt(att, ent, dmginfo) - - DamageLog(Format("DMG: \t %s [%s] damaged %s [%s] for %d dmg", att:Nick(), att:GetRoleString(), ent:Nick(), ent:GetRoleString(), math.Round(dmginfo:GetDamage()))) - end - end - - -- send damage information to client - net.Start("ttt2_damage_received") - net.WriteFloat(dmginfo:GetDamage()) - net.Send(ent) + -- Change damage attribution if necessary + if infl or att then + local hurter, owner, owner_time + + -- fall back to the attacker if there is no inflictor + if IsValid(infl) then + hurter = infl + elseif IsValid(att) then + hurter = att + end + + -- have a damage owner? + if hurter and IsValid(hurter:GetDamageOwner()) then + owner, owner_time = hurter:GetDamageOwner() + + -- barrel bangs can hurt us even if we threw them, but that's our fault + elseif + hurter + and ent == hurter:GetPhysicsAttacker() + and dmginfo:IsDamageType(DMG_BLAST) + then + owner = ent + elseif hurter and hurter:IsVehicle() and IsValid(hurter:GetDriver()) then + owner = hurter:GetDriver() + end + + -- if we were hurt by a trap OR by a non-ply ent, and we were pushed + -- recently, then our pusher is the attacker + if owner_time or not IsValid(att) or not att:IsPlayer() then + local push = ent.was_pushed + + if push and IsValid(push.att) and push.t then + -- push must be within the last 5 seconds, and must be done + -- after the trap was enabled (if any) + owner_time = owner_time or 0 + + local t = math.max(push.t or 0, push.hurt or 0) + + if t > owner_time and t > CurTime() - 4 then + owner = push.att + + -- pushed by a trap? + if IsValid(push.infl) then + dmginfo:SetInflictor(push.infl) + end + + -- for slow-hurting traps we do leech-like damage timing + push.hurt = CurTime() + end + end + end + + -- if we are being hurt by a physics object, we will take damage from + -- the world entity as well, which screws with damage attribution so we + -- need to detect and work around that + if IsValid(owner) and dmginfo:IsDamageType(DMG_CRUSH) then + -- we should be able to use the push system for this, as the cases are + -- similar: event causes future damage but should still be attributed + -- physics traps can also push you to your death, for example + local push = ent.was_pushed or {} + + -- if we already blamed this on a pusher, no need to do more + -- else we override whatever was in was_pushed with info pointing + -- at our damage owner + if push.att ~= owner then + owner_time = owner_time or CurTime() + + push.att = owner + push.t = owner_time + push.hurt = CurTime() + + -- store the current inflictor so that we can attribute it as the + -- trap used by the player in the event + if IsValid(infl) then + push.infl = infl + end + + -- make sure this is set, for if we created a new table + ent.was_pushed = push + end + end + + -- make the owner of the damage the attacker + att = IsValid(owner) and owner or att + + dmginfo:SetAttacker(att) + end + + -- scale phys damage caused by props + if + dmginfo:IsDamageType(DMG_CRUSH) + and IsValid(att) + and not dmginfo:IsDamageType(DMG_PHYSGUN) + then + -- player falling on player, or player hurt by prop? + -- this is prop-based physics damage + dmginfo:ScaleDamage(0.25) + + -- if the prop is held, no damage + if IsValid(infl) and IsValid(infl:GetOwner()) and infl:GetOwner():IsPlayer() then + dmginfo:ScaleDamage(0) + dmginfo:SetDamage(0) + end + end + + -- handle fire attacker + if ent.ignite_info and dmginfo:IsDamageType(DMG_DIRECT) then + local datt = dmginfo:GetAttacker() + + if not IsValid(datt) or not datt:IsPlayer() then + local ignite = ent.ignite_info + + if IsValid(ignite.att) and IsValid(ignite.infl) then + dmginfo:SetAttacker(ignite.att) + dmginfo:SetInflictor(ignite.infl) + end + end + end + + -- try to work out if this was push-induced leech-water damage (common on + -- some popular maps like dm_island17) + if + ent.was_pushed + and ent == att + and dmginfo:GetDamageType() == DMG_GENERIC + and util.BitSet(util.PointContents(dmginfo:GetDamagePosition()), CONTENTS_WATER) + then + local t = math.max(ent.was_pushed.t or 0, ent.was_pushed.hurt or 0) + + if t > CurTime() - 3 then + dmginfo:SetAttacker(ent.was_pushed.att) + + ent.was_pushed.hurt = CurTime() + end + end + + -- start painting blood decals + util.StartBleeding(ent, dmginfo:GetDamage(), 5) + + -- handle damage scaling by karma + if + IsValid(att) + and att:IsPlayer() + and GetRoundState() == ROUND_ACTIVE + and math.floor(dmginfo:GetDamage()) > 0 + then + if KARMA.IsEnabled() and ent ~= att and not dmginfo:IsDamageType(DMG_SLASH) then + -- scale everything to karma damage factor except the knife, because it assumes a kill + dmginfo:ScaleDamage(att:GetDamageFactor()) + end + + -- before the karma is calculated, but after all other damage hooks / damage change is processed, + -- the armor system should come into place (GM functions are called last) + ARMOR:HandlePlayerTakeDamage(ent, infl, att, amount, dmginfo) + + if ent ~= att then + -- process the effects of the damage on karma + KARMA.Hurt(att, ent, dmginfo) + + DamageLog( + Format( + "DMG: \t %s [%s] damaged %s [%s] for %d dmg", + att:Nick(), + att:GetRoleString(), + ent:Nick(), + ent:GetRoleString(), + math.Round(dmginfo:GetDamage()) + ) + ) + end + end + + -- send damage information to client + net.Start("ttt2_damage_received") + net.WriteFloat(dmginfo:GetDamage()) + net.Send(ent) end +net.Receive("ttt2_set_player_setting", function(_, ply) + if not IsValid(ply) then + return + end + + ply.playerSettings = ply.playerSettings or {} + + local identifier = net.ReadString() + local data = net.ReadString() + + -- make sure that the setting is registered on the server + if not player.playerSettingRegistry[identifier] then + return + end + + local oldValue = ply.playerSettings[identifier] + + if player.playerSettingRegistry[identifier] == "number" then + ply.playerSettings[identifier] = tonumber(data) + elseif player.playerSettingRegistry[identifier] == "bool" then + ply.playerSettings[identifier] = tobool(data) + else + ply.playerSettings[identifier] = data + end + + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2PlayerSettingChanged", ply, identifier, oldValue, ply.playerSettings[identifier]) +end) + --- -- Called whenever an @{NPC} is killed -- @param NPC npc The killed @{NPC} @@ -1320,9 +1509,7 @@ end -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:OnNPCKilled -- @local -function GM:OnNPCKilled(npc, attacker, inflictor) - -end +function GM:OnNPCKilled(npc, attacker, inflictor) end --- -- Called when a @{Player} executes gm_showhelp console command. ( Default bind is F1 ) @@ -1332,9 +1519,11 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:ShowHelp -- @local function GM:ShowHelp(ply) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply:ConCommand("ttt_helpscreen") + ply:ConCommand("ttt_helpscreen") end --- @@ -1347,9 +1536,7 @@ end -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:PlayerRequestTeam -- @local -function GM:PlayerRequestTeam(ply, teamid) - -end +function GM:PlayerRequestTeam(ply, teamid) end --- -- Called when a @{Player} enters a vehicle.
        @@ -1364,9 +1551,11 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerEnteredVehicle -- @local function GM:PlayerEnteredVehicle(ply, vehicle, role) - if not IsValid(vehicle) then return end + if not IsValid(vehicle) then + return + end - vehicle:SetNWEntity("ttt_driver", ply) + vehicle:SetNWEntity("ttt_driver", ply) end --- @@ -1379,10 +1568,12 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerLeaveVehicle -- @local function GM:PlayerLeaveVehicle(ply, vehicle) - if not IsValid(vehicle) then return end + if not IsValid(vehicle) then + return + end - -- setting nil will not do anything, so bogusify - vehicle:SetNWEntity("ttt_driver", vehicle) + -- setting nil will not do anything, so bogusify + vehicle:SetNWEntity("ttt_driver", vehicle) end --- @@ -1396,7 +1587,7 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:AllowPlayerPickup -- @local function GM:AllowPlayerPickup(ply, ent) - return false + return false end --- @@ -1404,14 +1595,14 @@ end -- @note Disable taunts, we don't have a system for them (camera freezing etc).
        -- Mods/plugins that add such a system should override this. -- @param Player ply @{Player} who tried to taunt --- @param number act Act ID of the taunt player tries to do, see ACT_Enums +-- @param number act Act ID of the taunt player tries to do, see ACT_Enums -- @return[default=false] boolean Return false to disallow player taunting -- @hook -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:PlayerShouldTaunt -- @local function GM:PlayerShouldTaunt(ply, act) - return false + return false end --- @@ -1421,9 +1612,16 @@ end -- @return nil|boolean Return false to prevent check -- @hook -- @realm server -function GM:TTT2CheckCreditAward(victim, attacker) +function GM:TTT2CheckCreditAward(victim, attacker) end -end +--- +-- Use this hook to prevent the transfer of credits from a body to a player. +-- @param Entity rag The ragdoll that is inspected +-- @param Player ply The @{Player} attempting to find credits from ragdoll +-- @return nil|boolean Return false to prevent transfer +-- @hook +-- @realm server +function GM:TTT2GiveFoundCredits(ply, rag) end --- -- Use this hook to prevent the addition of time to the hastemode. @@ -1432,9 +1630,7 @@ end -- @return nil|boolean Return true to prevent haste addition -- @hook -- @realm server -function GM:TTT2ShouldSkipHaste(victim, attacker) - -end +function GM:TTT2ShouldSkipHaste(victim, attacker) end --- -- Called in @{GM:PlayeDeath} at the very end. @@ -1444,9 +1640,7 @@ end -- @param Player|Entity attacker @{Player} or @{Entity} that killed the victim -- @hook -- @realm server -function GM:TTT2PostPlayerDeath(victim, inflictor, attacker) - -end +function GM:TTT2PostPlayerDeath(victim, inflictor, attacker) end --- -- Returns whether PVP is allowed @@ -1454,7 +1648,7 @@ end -- @hook -- @realm server function GM:AllowPVP() - local rs = GetRoundState() + local rs = GetRoundState() - return rs ~= ROUND_PREP and (rs ~= ROUND_POST or ttt_postdm:GetBool()) + return rs ~= ROUND_PREP and (rs ~= ROUND_POST or ttt_postdm:GetBool()) end diff --git a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua index 0bf802910..c0c5a9ea8 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua @@ -11,11 +11,13 @@ local hook = hook local plymeta = FindMetaTable("Player") if not plymeta then - Error("FAILED TO FIND PLAYER TABLE") + ErrorNoHaltWithStack("FAILED TO FIND PLAYER TABLE") - return + return end +local soundWeaponPickup = Sound("items/ammo_pickup.wav") + util.AddNetworkString("StartDrowning") util.AddNetworkString("TTT2TargetPlayer") util.AddNetworkString("TTT2SetPlayerReady") @@ -31,11 +33,11 @@ util.AddNetworkString("TTT2RevivalUpdate_RevivalDuration") -- @param boolean s -- @realm server function plymeta:SetRagdollSpec(s) - if s then - self.spec_ragdoll_start = CurTime() - end + if s then + self.spec_ragdoll_start = CurTime() + end - self.spec_ragdoll = s + self.spec_ragdoll = s end --- @@ -43,7 +45,7 @@ end -- @return boolean -- @realm server function plymeta:GetRagdollSpec() - return self.spec_ragdoll + return self.spec_ragdoll end --- @@ -51,9 +53,9 @@ end -- @param boolean state -- @realm server function plymeta:SetForceSpec(state) - self.force_spec = state -- compatibility with other addons + self.force_spec = state -- compatibility with other addons - self:SetNWBool("force_spec", state) + self:SetNWBool("force_spec", state) end -- Karma @@ -64,7 +66,7 @@ end -- @param number k -- @realm server function plymeta:SetBaseKarma(k) - self:SetNWFloat("karma", k) + self:SetNWFloat("karma", k) end --- @@ -90,7 +92,7 @@ AccessorFunc(plymeta, "clean_round", "CleanRound", FORCE_BOOL) -- Initializes the KARMA for the given @{Player} -- @realm server function plymeta:InitKarma() - KARMA.InitPlayer(self) + KARMA.InitPlayer(self) end -- Equipment credits @@ -100,9 +102,9 @@ end -- @param number amt -- @realm server function plymeta:SetCredits(amt) - self.equipment_credits = amt + self.equipment_credits = amt - self:SendCredits() + self:SendCredits() end --- @@ -110,7 +112,7 @@ end -- @param number amt -- @realm server function plymeta:AddCredits(amt) - self:SetCredits(self:GetCredits() + amt) + self:SetCredits(self:GetCredits() + amt) end --- @@ -118,100 +120,106 @@ end -- @param number amt -- @realm server function plymeta:SubtractCredits(amt) - local tmp = self:GetCredits() - amt - if tmp < 0 then - tmp = 0 - end + local tmp = self:GetCredits() - amt + if tmp < 0 then + tmp = 0 + end - self:SetCredits(tmp) + self:SetCredits(tmp) end --- -- Sets the default amount of credits -- @realm server function plymeta:SetDefaultCredits() - --- - -- @realm server - if hook.Run("TTT2SetDefaultCredits", self) then return end + --- + -- @realm server + -- stylua: ignore + if hook.Run("TTT2SetDefaultCredits", self) then return end - if not self:IsShopper() then - self:SetCredits(0) + if not self:IsShopper() then + self:SetCredits(0) - return - end + return + end - local rd = self:GetSubRoleData() - local name = "ttt_" .. rd.abbr .. "_credits_starting" + local rd = self:GetSubRoleData() + local name = "ttt_" .. rd.abbr .. "_credits_starting" - if self:GetTeam() ~= TEAM_TRAITOR then - self:SetCredits(math.ceil(ConVarExists(name) and GetConVar(name):GetFloat() or 0)) + if self:GetTeam() ~= TEAM_TRAITOR then + self:SetCredits(math.ceil(ConVarExists(name) and GetConVar(name):GetFloat() or 0)) - return - end + return + end - local c = ConVarExists(name) and GetConVar(name):GetFloat() or 0 + local c = ConVarExists(name) and GetConVar(name):GetFloat() or 0 - --- - -- @realm server - self:SetCredits(math.ceil(hook.Run("TTT2ModifyDefaultTraitorCredits", self, c) or c)) + --- + -- @realm server + -- stylua: ignore + self:SetCredits(math.ceil(hook.Run("TTT2ModifyDefaultTraitorCredits", self, c) or c)) end --- --- Synces the amount of credits with the @{Player} +-- Syncs the amount of credits with the @{Player} -- @realm server function plymeta:SendCredits() - net.Start("TTT_Credits") - net.WriteUInt(self:GetCredits(), 8) - net.Send(self) + net.Start("TTT_Credits") + net.WriteUInt(self:GetCredits(), 8) + net.Send(self) end -- Equipment items --- -- Gives a specific @{ITEM} (if possible) --- @param string cls +-- @param string className -- @return ITEM|nil -- @realm server -- @internal -function plymeta:AddEquipmentItem(cls) - local item = items.GetStored(cls) +function plymeta:AddEquipmentItem(className) + local item = items.GetStored(className) - if not item or item.limited and self:HasEquipmentItem(cls) then return end + if not item or item.limited and self:HasEquipmentItem(className) then + return + end - self.equipmentItems = self.equipmentItems or {} - self.equipmentItems[#self.equipmentItems + 1] = cls + self.equipmentItems = self.equipmentItems or {} + self.equipmentItems[#self.equipmentItems + 1] = className - item:Equip(self) + item:Equip(self) - self:SendEquipment() + self:SendEquipment(EQUIPITEMS_ADD, className) - return item + return item end --- -- Removes a specific @{ITEM} --- @param string cls +-- @param string className -- @realm server -function plymeta:RemoveEquipmentItem(cls) - if not self:HasEquipmentItem(cls) then return end +function plymeta:RemoveEquipmentItem(className) + if not self:HasEquipmentItem(className) then + return + end - local item = items.GetStored(cls) + local item = items.GetStored(className) - if item and isfunction(item.Reset) then - item:Reset(self) - end + if item and isfunction(item.Reset) then + item:Reset(self) + end - local equipItems = self:GetEquipmentItems() + local equipItems = self:GetEquipmentItems() - for k = 1, #equipItems do - if equipItems[k] == cls then - table.remove(self.equipmentItems, k) + for k = 1, #equipItems do + if equipItems[k] == className then + table.remove(self.equipmentItems, k) - break - end - end + break + end + end - self:SendEquipment() + self:SendEquipment(EQUIPITEMS_REMOVE, className) end --- @@ -221,62 +229,72 @@ end plymeta.RemoveEquipmentWeapon = plymeta.StripWeapon --- --- Synces the server stored equipment with the @{Player} +-- Syncs the server stored equipment with the @{Player} -- @note We do this instead of an NW var in order to limit the info to just this ply +-- @param number mode The mode to determine how the Equipment is handled on the client +-- @param string itemName The name of the item to send, can be 'nil' if mode is EQUIPITEMS_RESET -- @realm server -function plymeta:SendEquipment() - local arr = self:GetEquipmentItems() +function plymeta:SendEquipment(mode, itemName) + if not mode then + ErrorNoHaltWithStackWithStack( + "[TTT2] Define an EQUIPITEMS_mode for plymeta:SendEquipment(mode, itemName) to work.\n" + ) + + return + end - net.Start("TTT_Equipment") - net.WriteUInt(#arr, 16) + net.Start("TTT_Equipment") + net.WriteUInt(mode, 2) - for i = 1, #arr do - net.WriteString(arr[i]) - end + if mode ~= EQUIPITEMS_RESET then + net.WriteString(itemName) + end - net.Send(self) + net.Send(self) end --- -- Resets the equipment of a @{Player} -- @realm server function plymeta:ResetEquipment() - local equipItems = self:GetEquipmentItems() + local equipItems = self:GetEquipmentItems() - for i = 1, #equipItems do - local item = items.GetStored(equipItems[i]) + for i = 1, #equipItems do + local item = items.GetStored(equipItems[i]) - if item and isfunction(item.Reset) then - item:Reset(self) - end - end + if item and isfunction(item.Reset) then + item:Reset(self) + end + end - self.equipmentItems = {} + self.equipmentItems = {} - self:SendEquipment() + self:SendEquipment(EQUIPITEMS_RESET) end --- -- Sends the list of bought @{ITEM}s and @{Weapon}s to the @{Player} -- @realm server function plymeta:SendBought() - local bought = self.bought + local bought = self.bought - -- Send all as string, even though equipment are numbers, for simplicity - net.Start("TTT_Bought") - net.WriteUInt(#bought, 8) + -- Send all as string, even though equipment are numbers, for simplicity + net.Start("TTT_Bought") + net.WriteUInt(#bought, 8) - for i = 1, #bought do - net.WriteString(bought[i]) - end + for i = 1, #bought do + net.WriteString(bought[i]) + end - net.Send(self) + net.Send(self) end local function ttt_resend_bought(ply) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply:SendBought() + ply:SendBought() end concommand.Add("ttt_resend_bought", ttt_resend_bought) @@ -284,133 +302,120 @@ concommand.Add("ttt_resend_bought", ttt_resend_bought) -- Resets the bought list of a @{Player} -- @realm server function plymeta:ResetBought() - self.bought = {} + self.bought = {} - self:SendBought() + self:SendBought() end --- -- Adds an @{ITEM} or a @{Weapon} into the bought list of a @{Player} -- @note This will disable another purchase of the same equipment -- if this equipment is limited --- @param string cls +-- @param string equipmentName -- @realm server -- @see Player:RemoveBought -function plymeta:AddBought(cls) - self.bought = self.bought or {} - self.bought[#self.bought + 1] = tostring(cls) - - BUYTABLE[cls] = true - - net.Start("TTT2ReceiveGBEq") - net.WriteString(cls) - net.Broadcast() - - local team = self:GetTeam() +function plymeta:AddBought(equipmentName) + self.bought = self.bought or {} + self.bought[#self.bought + 1] = tostring(equipmentName) - if team and team ~= TEAM_NONE and not TEAMS[team].alone then - TEAMBUYTABLE[team] = TEAMBUYTABLE[team] or {} - TEAMBUYTABLE[team][cls] = true + shop.SetEquipmentBought(self, equipmentName) + shop.SetEquipmentGlobalBought(equipmentName) + shop.SetEquipmentTeamBought(self, equipmentName) - if SERVER then - net.Start("TTT2ReceiveTBEq") - net.WriteString(cls) - net.Send(GetTeamFilter(team)) - end - end - - self:SendBought() + self:SendBought() end --- -- Removes an @{ITEM} or a @{Weapon} from the bought list of a @{Player} -- @note This will enable another purchase of the same equipment -- if this equipment is limited --- @param string cls +-- @param string equipmentName -- @realm server -- @see Player:AddBought -function plymeta:RemoveBought(cls) - local key +function plymeta:RemoveBought(equipmentName) + local key - self.bought = self.bought or {} + self.bought = self.bought or {} - for k = 1, #self.bought do - if self.bought[k] ~= tostring(cls) then continue end + for k = 1, #self.bought do + if self.bought[k] ~= tostring(equipmentName) then + continue + end - key = k + key = k - break - end + break + end - if key then - table.remove(self.bought, key) + if key then + table.remove(self.bought, key) - self:SendBought() - end + self:SendBought() + end end --- -- Strips player of all equipment -- @realm server function plymeta:StripAll() - -- standard stuff - self:StripAmmo() - self:StripWeapons() + -- standard stuff + self:StripAmmo() + self:StripWeapons() - -- our stuff - self:ResetEquipment() - self:SetCredits(0) + -- our stuff + self:ResetEquipment() + self:SetCredits(0) end --- -- Sets all flags (force_spec, etc) to their default -- @realm server function plymeta:ResetStatus() - self:SetRole(ROLE_NONE) -- this will update the team automatically - self:SetRagdollSpec(false) - self:SetForceSpec(false) - self:ResetRoundFlags() + self:SetRole(ROLE_NONE) -- this will update the team automatically + self:SetRagdollSpec(false) + self:SetForceSpec(false) + self:ResetRoundFlags() end --- -- Sets round-based misc flags to default position. Called at PlayerSpawn. -- @realm server function plymeta:ResetRoundFlags() - self:ResetEquipment() - self:SetCredits(0) - self:ResetBought() + self:ResetEquipment() + self:SetCredits(0) + self:ResetBought() - -- equipment stuff - self.bomb_wire = nil - self.radar_charge = 0 - self.decoy = nil + -- equipment stuff + self.bomb_wire = nil + self.radar_charge = 0 + self.decoy = nil - timer.Remove("give_equipment" .. self:UniqueID()) + timer.Remove("give_equipment" .. self:UniqueID()) - -- corpse - self:TTT2NETSetBool("body_found", false) + -- corpse + self:TTT2NETSetBool("body_found", false) - self.kills = {} - self.dying_wep = nil - self.was_headshot = false + self.kills = {} + self.dying_wep = nil + self.was_headshot = false - -- communication - self.mute_team = -1 + -- communication + self.mute_team = -1 - local winTms = roles.GetWinTeams() + local winTms = roles.GetWinTeams() - for i = 1, #winTms do - self[winTms[i] .. "_gvoice"] = false - end + for i = 1, #winTms do + self[winTms[i] .. "_gvoice"] = false + end - self:SetNWBool("disguised", false) + self:SetNWBool("disguised", false) - -- karma - self:SetCleanRound(true) - self:Freeze(false) + -- karma + self:SetCleanRound(true) + self:Freeze(false) - -- armor - self:ResetArmor() + -- armor + self:ResetArmor() end --- @@ -423,46 +428,52 @@ end -- takes the @{Player}, cls and created @{Weapon} as parameters, can be nil -- @realm server function plymeta:GiveEquipmentWeapon(cls, callback) - if not cls then return end + if not cls then + return + end - if not self:CanCarryWeapon(weapons.GetStored(cls)) then return end + if not self:CanCarryWeapon(weapons.GetStored(cls)) then + return + end - -- Referring to players by SteamID64 because a player may disconnect while his - -- unique timer still runs, in which case we want to be able to stop it. For - -- that we need its name, and hence his SteamID64. - local tmr = "give_equipment" .. self:UniqueID() + -- Referring to players by SteamID64 because a player may disconnect while his + -- unique timer still runs, in which case we want to be able to stop it. For + -- that we need its name, and hence his SteamID64. + local tmr = "give_equipment" .. self:UniqueID() - if not IsValid(self) or not self:Alive() then - timer.Remove(tmr) + if not IsValid(self) or not self:Alive() then + timer.Remove(tmr) - return - end + return + end - -- giving attempt, will fail if we're in a crazy spot in the map or perhaps - -- other glitchy cases - local w = self:Give(cls) + -- giving attempt, will fail if we're in a crazy spot in the map or perhaps + -- other glitchy cases + local w = self:Give(cls) - if not IsValid(w) or not self:HasWeapon(cls) then - if not timer.Exists(tmr) then - local slf = self + if not IsValid(w) or not self:HasWeapon(cls) then + if not timer.Exists(tmr) then + local slf = self - timer.Create(tmr, 1, 0, function() - if not IsValid(slf) then return end + timer.Create(tmr, 1, 0, function() + if not IsValid(slf) then + return + end - slf:GiveEquipmentWeapon(cls, callback) - end) - end + slf:GiveEquipmentWeapon(cls, callback) + end) + end - -- we will be retrying - else - -- can stop retrying, if we were - timer.Remove(tmr) + -- we will be retrying + else + -- can stop retrying, if we were + timer.Remove(tmr) - if isfunction(callback) then - -- basically a delayed/asynchronous return, necessary due to the timers - callback(self, cls, w) - end - end + if isfunction(callback) then + -- basically a delayed/asynchronous return, necessary due to the timers + callback(self, cls, w) + end + end end --- @@ -471,15 +482,17 @@ end -- @return ITEM|nil -- @realm server function plymeta:GiveEquipmentItem(cls) - if not cls then return end + if not cls then + return + end - local item = items.GetStored(cls) + local item = items.GetStored(cls) - if not item or item.limited and self:HasEquipmentItem(cls) then - return - end + if not item or item.limited and self:HasEquipmentItem(cls) then + return + end - return self:AddEquipmentItem(cls) + return self:AddEquipmentItem(cls) end --- @@ -487,13 +500,13 @@ end -- @return boolean -- @realm server function plymeta:ShouldScore() - if self:GetForceSpec() then - return false - elseif self:IsSpec() and self:Alive() then - return false - else - return true - end + if self:GetForceSpec() then + return false + elseif self:IsSpec() and self:Alive() then + return false + else + return true + end end --- @@ -501,10 +514,12 @@ end -- @param Player victim -- @realm server function plymeta:RecordKill(victim) - if not IsValid(victim) then return end + if not IsValid(victim) then + return + end - self.kills = self.kills or {} - self.kills[#self.kills + 1] = victim:SteamID64() + self.kills = self.kills or {} + self.kills[#self.kills + 1] = victim:SteamID64() end --- @@ -514,64 +529,70 @@ end -- @deprecated -- @realm server function plymeta:SetSpeed(slowed) - error "Player:SetSpeed(slowed) is deprecated - please remove this call and use the TTTPlayerSpeedModifier hook in both CLIENT and SERVER states" + ErrorNoHaltWithStack( + "Player:SetSpeed(slowed) is deprecated - please remove this call and use the TTTPlayerSpeedModifier hook in both CLIENT and SERVER states" + ) end --- -- Resets the last words -- @realm server function plymeta:ResetLastWords() - --if not IsValid(self) then return end -- timers are dangerous things + --if not IsValid(self) then return end -- timers are dangerous things - self.last_words_id = nil + self.last_words_id = nil end --- -- Sends the last words based on the DamageInfo --- @param DamageInfo dmginfo +-- @param CTakeDamageInfo dmginfo -- @realm server function plymeta:SendLastWords(dmginfo) - -- Use a pseudo unique id to prevent people from abusing the concmd - self.last_words_id = math.floor(CurTime() + math.random(500)) + -- Use a pseudo unique id to prevent people from abusing the concmd + self.last_words_id = math.floor(CurTime() + math.random(500)) - -- See if the damage was interesting - local dtype = KILL_NORMAL + -- See if the damage was interesting + local dtype = KILL_NORMAL - if dmginfo:GetAttacker() == self or dmginfo:GetInflictor() == self then - dtype = KILL_SUICIDE - elseif dmginfo:IsDamageType(DMG_BURN) then - dtype = KILL_BURN - elseif dmginfo:IsFallDamage() then - dtype = KILL_FALL - end + if dmginfo:GetAttacker() == self or dmginfo:GetInflictor() == self then + dtype = KILL_SUICIDE + elseif dmginfo:IsDamageType(DMG_BURN) then + dtype = KILL_BURN + elseif dmginfo:IsFallDamage() then + dtype = KILL_FALL + end - self.death_type = dtype + self.death_type = dtype - net.Start("TTT_InterruptChat") - net.WriteUInt(self.last_words_id, 32) - net.Send(self) + net.Start("TTT_InterruptChat") + net.WriteUInt(self.last_words_id, 32) + net.Send(self) - -- any longer than this and you're out of luck - local ply = self + -- any longer than this and you're out of luck + local ply = self - timer.Simple(2, function() - if not IsValid(ply) then return end + timer.Simple(2, function() + if not IsValid(ply) then + return + end - ply:ResetLastWords() - end) + ply:ResetLastWords() + end) end --- -- Resets the view -- @realm server function plymeta:ResetViewRoll() - local ang = self:EyeAngles() + local ang = self:EyeAngles() - if ang.r == 0 then return end + if ang.r == 0 then + return + end - ang.r = 0 + ang.r = 0 - self:SetEyeAngles(ang) + self:SetEyeAngles(ang) end --- @@ -579,17 +600,17 @@ end -- @return boolean -- @realm server function plymeta:ShouldSpawn() - -- do not spawn players who have not been through initspawn - if not self:IsSpec() and not self:IsTerror() then - return false - end + -- do not spawn players who have not been through initspawn + if not self:IsSpec() and not self:IsTerror() then + return false + end - -- do not spawn forced specs - if self:IsSpec() and self:GetForceSpec() then - return false - end + -- do not spawn forced specs + if self:IsSpec() and self:GetForceSpec() then + return false + end - return true + return true end --- @@ -598,88 +619,89 @@ end -- @return boolean Returns true if player is spawned -- @realm server function plymeta:SpawnForRound(deadOnly) - --- - -- @realm server - hook.Run("PlayerSetModel", self) + --- + -- @realm server + -- stylua: ignore + hook.Run("PlayerSetModel", self) - --- - -- @realm server - hook.Run("TTTPlayerSetColor", self) + --- + -- @realm server + -- stylua: ignore + hook.Run("TTTPlayerSetColor", self) - -- wrong alive status and not a willing spec who unforced after prep started - -- (and will therefore be "alive") - if deadOnly and self:Alive() and not self:IsSpec() then - -- if the player does not need respawn, make sure he has full health - self:SetHealth(self:GetMaxHealth()) + -- wrong alive status and not a willing spec who unforced after prep started + -- (and will therefore be "alive") + if deadOnly and self:Alive() and not self:IsSpec() then + -- if the player does not need respawn, make sure he has full health + self:SetHealth(self:GetMaxHealth()) - return false - end + return false + end - if not self:ShouldSpawn() then - return false - end + if not self:ShouldSpawn() then + return false + end - -- reset propspec state that they may have gotten during prep - PROPSPEC.Clear(self) + -- reset propspec state that they may have gotten during prep + PROPSPEC.Clear(self) - -- respawn anyone else - if self:Team() == TEAM_SPEC then - self:UnSpectate() - end + -- respawn anyone else + if self:Team() == TEAM_SPEC then + self:UnSpectate() + end - self:StripAll() - self:SetTeam(TEAM_TERROR) - self:Spawn() + self:StripAll() + self:SetTeam(TEAM_TERROR) + self:Spawn() - -- set spawn position - local spawnPoint = plyspawn.GetRandomSafePlayerSpawnPoint(self) + -- set spawn position + local spawnPoint = plyspawn.GetRandomSafePlayerSpawnPoint(self) - if not spawnPoint then - return false - end + if not spawnPoint then + return false + end - self:SetPos(spawnPoint.pos) - self:SetAngles(spawnPoint.ang) + self:SetPos(spawnPoint.pos) + self:SetAngles(spawnPoint.ang) - -- tell caller that we spawned - return true + -- tell caller that we spawned + return true end --- -- This is called on the first spawn to set the default vars -- @realm server function plymeta:InitialSpawn() - self.has_spawned = false + self.has_spawned = false - -- The team the player spawns on depends on the round state - self:SetTeam(GetRoundState() == ROUND_PREP and TEAM_TERROR or TEAM_SPEC) + -- The team the player spawns on depends on the round state + self:SetTeam(GetRoundState() == ROUND_PREP and TEAM_TERROR or TEAM_SPEC) - -- Change some gmod defaults - self:SetCanZoom(false) - self:SetJumpPower(160) - self:SetCrouchedWalkSpeed(0.3) - self:SetRunSpeed(220) - self:SetWalkSpeed(220) - self:SetMaxSpeed(220) + -- Change some gmod defaults + self:SetCanZoom(false) + self:SetJumpPower(160) + self:SetCrouchedWalkSpeed(0.3) + self:SetRunSpeed(220) + self:SetWalkSpeed(220) + self:SetMaxSpeed(220) - self:ResetStatus() + self:ResetStatus() - -- Always reset sprint - self.sprintProgress = 1 + -- Start off with clean, full karma (unless it can and should be loaded) + self:InitKarma() - -- Start off with clean, full karma (unless it can and should be loaded) - self:InitKarma() + -- We never have weapons here, but this inits our equipment state + self:StripAll() - -- We never have weapons here, but this inits our equipment state - self:StripAll() + -- set spawn position + local spawnPoint = plyspawn.GetRandomSafePlayerSpawnPoint(self) - -- set spawn position - local spawnPoint = plyspawn.GetRandomSafePlayerSpawnPoint(self) + if not spawnPoint then + return + end - if not spawnPoint then return end - - self:SetPos(spawnPoint.pos) - self:SetAngles(spawnPoint.ang) + self:SetPos(spawnPoint.pos) + self:SetAngles(spawnPoint.ang) end --- @@ -688,8 +710,8 @@ end -- @param string reason -- @realm server function plymeta:KickBan(length, reason) - -- see admin.lua - PerformKickBan(self, length, reason) + -- see admin.lua + PerformKickBan(self, length, reason) end local oldSpectate = plymeta.Spectate @@ -699,15 +721,15 @@ local oldSpectate = plymeta.Spectate -- @param number type -- @realm server function plymeta:Spectate(type) - oldSpectate(self, type) + oldSpectate(self, type) - -- NPCs should never see spectators. A workaround for the fact that gmod NPCs - -- do not ignore them by default. - self:SetNoTarget(true) + -- NPCs should never see spectators. A workaround for the fact that gmod NPCs + -- do not ignore them by default. + self:SetNoTarget(true) - if type == OBS_MODE_ROAMING then - self:SetMoveType(MOVETYPE_NOCLIP) - end + if type == OBS_MODE_ROAMING then + self:SetMoveType(MOVETYPE_NOCLIP) + end end local oldSpectateEntity = plymeta.SpectateEntity @@ -717,11 +739,11 @@ local oldSpectateEntity = plymeta.SpectateEntity -- @param Entity ent -- @realm server function plymeta:SpectateEntity(ent) - oldSpectateEntity(self, ent) + oldSpectateEntity(self, ent) - if IsValid(ent) and ent:IsPlayer() then - self:SetupHands(ent) - end + if IsValid(ent) and ent:IsPlayer() then + self:SetupHands(ent) + end end local oldUnSpectate = plymeta.UnSpectate @@ -730,28 +752,9 @@ local oldUnSpectate = plymeta.UnSpectate -- Unspectates a @{Player} -- @realm server function plymeta:UnSpectate() - oldUnSpectate(self) - - self:SetNoTarget(false) -end - ---- --- Returns whether a @{Player} has disabled the selection of a given @{ROLE} --- @param number role subrole id of a @{ROLE} --- @return boolean --- @realm server -function plymeta:GetAvoidRole(role) - return self:GetInfoNum("ttt_avoid_" .. roles.GetByIndex(role).name, 0) > 0 -end + oldUnSpectate(self) ---- --- Returns whether a @{Player} has disabled the selection of the detective role --- @note This gives compatibility for some legacy ttt addons --- @return boolean --- @realm server --- @deprecated -function plymeta:GetAvoidDetective() - return self:GetAvoidRole(ROLE_DETECTIVE) + self:SetNoTarget(false) end --- @@ -762,42 +765,53 @@ end -- @return boolean -- @realm server function plymeta:CanSelectRole(roleData, choice_count, role_count) - local min_karmas = ConVarExists("ttt_" .. roleData.name .. "_karma_min") and GetConVar("ttt_" .. roleData.name .. "_karma_min"):GetInt() or 0 + -- if there aren't enough players anymore to have a greater role variety + if choice_count <= role_count then + return true + end + + -- or the player has enough karma + local minKarmaCVar = GetConVar("ttt_" .. roleData.name .. "_karma_min") + local minKarma = minKarmaCVar and minKarmaCVar:GetInt() or 0 + if KARMA.cv.enabled:GetBool() and self:GetBaseKarma() > minKarma then + return true + end + + -- or if the randomness decides + if math.random(3) == 2 then + return true + end - return ( - choice_count <= role_count - or self:GetBaseKarma() > min_karmas and GAMEMODE.LastRole[self:SteamID64()] == ROLE_INNOCENT - or math.random(3) == 2 - ) and (choice_count <= role_count or not self:GetAvoidRole(roleData.index)) + return false end --- -- Function taken from Trouble in Terrorist Town Commands (https://github.com/bender180/Trouble-in-Terrorist-Town-ULX-Commands) -- @realm server function plymeta:FindCorpse() - local ragdolls = ents.FindByClass("prop_ragdoll") + local ragdolls = ents.FindByClass("prop_ragdoll") - for i = 1, #ragdolls do - local ent = ragdolls[i] + for i = 1, #ragdolls do + local ent = ragdolls[i] - if ent.uqid == self:UniqueID() and IsValid(ent) then - return ent or false - end - end + if ent.uqid == self:UniqueID() and IsValid(ent) then + return ent or false + end + end end -- Handles all stuff needed if the revival failed local function OnReviveFailed(ply, failMessage) - if isfunction(ply.OnReviveFailedCallback) then - ply.OnReviveFailedCallback(ply, failMessage) + if isfunction(ply.OnReviveFailedCallback) then + ply.OnReviveFailedCallback(ply, failMessage) - ply.OnReviveFailedCallback = nil - else - LANG.Msg(ply, failMessage, nil, MSG_MSTACK_WARN) - end + ply.OnReviveFailedCallback = nil + else + LANG.Msg(ply, failMessage, nil, MSG_MSTACK_WARN) + end - net.Start("TTT2RevivalStopped") - net.Send(ply) + net.Start("TTT2RevivalStopped") + net.Send(ply) end --- @@ -811,99 +825,112 @@ end -- @param[opt] Vector spawnPos The position where the player should be spawned, accounts for minor obstacles -- @param[opt] Angle spawnEyeAngle The eye angles of the revived players -- @realm server -function plymeta:Revive(delay, OnRevive, DoCheck, needsCorpse, blockRound, OnFail, spawnPos, spawnEyeAngle) - if self:IsReviving() then return end +function plymeta:Revive( + delay, + OnRevive, + DoCheck, + needsCorpse, + blockRound, + OnFail, + spawnPos, + spawnEyeAngle +) + if self:IsReviving() then + return + end - local name = "TTT2RevivePlayer" .. self:EntIndex() + local name = "TTT2RevivePlayer" .. self:EntIndex() - delay = delay or 3 + delay = delay or 3 - -- compatible mode for block round - if isbool(blockRound) then - MsgN("[DEPRECATION WARNING]: You should use the REVIVAL_BLOCK enum here.") - debug.Trace() - end + -- compatible mode for block round + if isbool(blockRound) then + ErrorNoHaltWithStack("[DEPRECATION WARNING]: You should use the REVIVAL_BLOCK enum here.") + end - if blockRound == nil or blockRound == false then - blockRound = REVIVAL_BLOCK_NONE - elseif blockRound == true then - blockRound = REVIVAL_BLOCK_AS_ALIVE - end + if blockRound == nil or blockRound == false then + blockRound = REVIVAL_BLOCK_NONE + elseif blockRound == true then + blockRound = REVIVAL_BLOCK_AS_ALIVE + end - self:SetReviving(true) - self:SetRevivalBlockMode(blockRound) - self:SetRevivalStartTime(CurTime()) - self:SetRevivalDuration(delay) + self:SetReviving(true) + self:SetRevivalBlockMode(blockRound) + self:SetRevivalStartTime(CurTime()) + self:SetRevivalDuration(delay) - self.OnReviveFailedCallback = OnFail + self.OnReviveFailedCallback = OnFail - timer.Create(name, delay, 1, function() - if not IsValid(self) then return end + timer.Create(name, delay, 1, function() + if not IsValid(self) then + return + end - self:SetReviving(false) - self:SetRevivalBlockMode(REVIVAL_BLOCK_NONE) - self:SendRevivalReason(nil) + self:SetReviving(false) + self:SetRevivalBlockMode(REVIVAL_BLOCK_NONE) + self:SendRevivalReason(nil) - if not isfunction(DoCheck) or DoCheck(self) then - local corpse = self:FindCorpse() + if not isfunction(DoCheck) or DoCheck(self) then + local corpse = self:FindCorpse() - if needsCorpse and (not IsValid(corpse) or corpse:IsOnFire()) then - OnReviveFailed(self, "message_revival_failed_missing_body") + if needsCorpse and (not IsValid(corpse) or corpse:IsOnFire()) then + OnReviveFailed(self, "message_revival_failed_missing_body") - return - end + return + end - self:SetMaxHealth(100) - self:SetHealth(100) + self:SetMaxHealth(100) + self:SetHealth(100) - self:SpawnForRound(true) + self:SpawnForRound(true) - if not spawnPos and IsValid(corpse) then - spawnPos = corpse:GetPos() - spawnEyeAngle = Angle(0, corpse:GetAngles().y, 0) - end + if not spawnPos and IsValid(corpse) then + spawnPos = corpse:GetPos() + spawnEyeAngle = Angle(0, corpse:GetAngles().y, 0) + end - spawnPos = spawnPos or self:GetDeathPosition() - spawnPos = plyspawn.MakeSpawnPointSafe(self, spawnPos) + spawnPos = spawnPos or self:GetDeathPosition() + spawnPos = plyspawn.MakeSpawnPointSafe(self, spawnPos) - if not spawnPos then - local spawnPoint = plyspawn.GetRandomSafePlayerSpawnPoint(self) + if not spawnPos then + local spawnPoint = plyspawn.GetRandomSafePlayerSpawnPoint(self) - if not spawnPoint then - OnReviveFailed(self, "message_revival_failed") + if not spawnPoint then + OnReviveFailed(self, "message_revival_failed") - return - end + return + end - spawnPos = spawnPoint.pos - spawnEyeAngle = spawnPoint.ang - end + spawnPos = spawnPoint.pos + spawnEyeAngle = spawnPoint.ang + end - self:SetPos(spawnPos) - self:SetEyeAngles(spawnEyeAngle or Angle(0, 0, 0)) + self:SetPos(spawnPos) + self:SetEyeAngles(spawnEyeAngle or Angle(0, 0, 0)) - --- - -- @realm server - hook.Run("PlayerLoadout", self, true) + --- + -- @realm server + -- stylua: ignore + hook.Run("PlayerLoadout", self, true) - self:SetCredits(CORPSE.GetCredits(corpse, 0)) - self:SelectWeapon("weapon_zm_improvised") + self:SetCredits(CORPSE.GetCredits(corpse, 0)) + self:SelectWeapon("weapon_zm_improvised") - if IsValid(corpse) then - corpse:Remove() - end + if IsValid(corpse) then + corpse:Remove() + end - DamageLog("TTT2Revive: " .. self:Nick() .. " has been respawned.") + DamageLog("TTT2Revive: " .. self:Nick() .. " has been respawned.") - if isfunction(OnRevive) then - OnRevive(self) - end - else - OnReviveFailed(self, "message_revival_failed") - end + if isfunction(OnRevive) then + OnRevive(self) + end + else + OnReviveFailed(self, "message_revival_failed") + end - self.OnReviveFailedCallback = nil - end) + self.OnReviveFailedCallback = nil + end) end --- @@ -912,35 +939,40 @@ end -- @param[opt] boolean silent If silent is true, no sound and text will be displayed -- @realm server function plymeta:CancelRevival(failMessage, silent) - if not self:IsReviving() then return end + if not self:IsReviving() then + return + end - self:SetReviving(false) - self:SetRevivalBlockMode(REVIVAL_BLOCK_NONE) - self:SendRevivalReason(nil) + self:SetReviving(false) + self:SetRevivalBlockMode(REVIVAL_BLOCK_NONE) + self:SendRevivalReason(nil) - timer.Remove("TTT2RevivePlayer" .. self:EntIndex()) + timer.Remove("TTT2RevivePlayer" .. self:EntIndex()) - if silent then return end + if silent then + return + end - OnReviveFailed(self, failMessage or "message_revival_canceled") + OnReviveFailed(self, failMessage or "message_revival_canceled") end - --- -- Sets the revival state. -- @param[default=false] boolean isReviving The reviving state -- @internal -- @realm server function plymeta:SetReviving(isReviving) - isReviving = isReviving or false + isReviving = isReviving or false - if self.isReviving == isReviving then return end + if self.isReviving == isReviving then + return + end - self.isReviving = isReviving + self.isReviving = isReviving - net.Start("TTT2RevivalUpdate_IsReviving") - net.WriteBool(self.isReviving) - net.Send(self) + net.Start("TTT2RevivalUpdate_IsReviving") + net.WriteBool(self.isReviving) + net.Send(self) end --- @@ -949,15 +981,17 @@ end -- @internal -- @realm server function plymeta:SetRevivalBlockMode(revivalBlockMode) - revivalBlockMode = revivalBlockMode or REVIVAL_BLOCK_NONE + revivalBlockMode = revivalBlockMode or REVIVAL_BLOCK_NONE - if self.revivalBlockMode == revivalBlockMode then return end + if self.revivalBlockMode == revivalBlockMode then + return + end - self.revivalBlockMode = revivalBlockMode + self.revivalBlockMode = revivalBlockMode - net.Start("TTT2RevivalUpdate_RevivalBlockMode") - net.WriteUInt(self.revivalBlockMode, REVIVAL_BITS) - net.Send(self) + net.Start("TTT2RevivalUpdate_RevivalBlockMode") + net.WriteUInt(self.revivalBlockMode, REVIVAL_BITS) + net.Send(self) end --- @@ -966,15 +1000,17 @@ end -- @internal -- @realm server function plymeta:SetRevivalStartTime(startTime) - startTime = startTime or CurTime() + startTime = startTime or CurTime() - if self.revivalStartTime == startTime then return end + if self.revivalStartTime == startTime then + return + end - self.revivalStartTime = startTime + self.revivalStartTime = startTime - net.Start("TTT2RevivalUpdate_RevivalStartTime") - net.WriteFloat(self.revivalStartTime) - net.Send(self) + net.Start("TTT2RevivalUpdate_RevivalStartTime") + net.WriteFloat(self.revivalStartTime) + net.Send(self) end --- @@ -983,55 +1019,56 @@ end -- @internal -- @realm server function plymeta:SetRevivalDuration(duration) - duration = duration or 0.0 + duration = duration or 0.0 - if self.revivalDurarion == duration then return end + if self.revivalDurarion == duration then + return + end - self.revivalDurarion = duration + self.revivalDurarion = duration - net.Start("TTT2RevivalUpdate_RevivalDuration") - net.WriteFloat(self.revivalDurarion) - net.Send(self) + net.Start("TTT2RevivalUpdate_RevivalDuration") + net.WriteFloat(self.revivalDurarion) + net.Send(self) end --- -- Sends a revival reason that is displayed in the clients revival HUD element. -- It supports a language identifier for translated strings. --- @param[default=nil] string name The text or the language identifer, nil to reset +-- @param[default=nil] string name The text or the language identifier, nil to reset -- @param[opt] table params The params table used for @{LANG.GetParamTranslation} -- @realm server function plymeta:SendRevivalReason(name, params) - net.Start("TTT2SetRevivalReason") + net.Start("TTT2SetRevivalReason") - if name then - net.WriteBool(false) - net.WriteString(name) + if name then + net.WriteBool(false) + net.WriteString(name) - local paramsAmount = params and table.Count(params) or 0 + local paramsAmount = params and table.Count(params) or 0 - net.WriteUInt(paramsAmount, 8) + net.WriteUInt(paramsAmount, 8) - if paramsAmount > 0 then - for k, v in pairs(params) do - net.WriteString(k) - net.WriteString(tostring(v)) - end - end - else - net.WriteBool(true) - end + if paramsAmount > 0 then + for k, v in pairs(params) do + net.WriteString(k) + net.WriteString(tostring(v)) + end + end + else + net.WriteBool(true) + end - net.Send(self) + net.Send(self) end - --- -- Sets the last death position. -- @param Vector pos The death position -- @internal -- @realm server function plymeta:SetLastDeathPosition(pos) - self.lastDeathPosition = pos + self.lastDeathPosition = pos end --- @@ -1040,7 +1077,7 @@ end -- @internal -- @realm server function plymeta:SetLastSpawnPosition(pos) - self.lastSpawnPosition = pos + self.lastSpawnPosition = pos end --- @@ -1048,7 +1085,7 @@ end -- @return Vector The last death position -- @realm server function plymeta:GetDeathPosition() - return self.lastDeathPosition + return self.lastDeathPosition end --- @@ -1056,7 +1093,7 @@ end -- @return Vector The last spawn position -- @realm server function plymeta:GetSpawnPosition() - return self.lastSpawnPosition + return self.lastSpawnPosition end --- @@ -1065,7 +1102,7 @@ end -- @internal -- @realm server function plymeta:SetActiveInRound(state) - self:TTT2NETSetBool("player_was_active_in_round", state or false) + self:TTT2NETSetBool("player_was_active_in_round", state or false) end --- @@ -1073,7 +1110,7 @@ end -- @internal -- @realm server function plymeta:IncreaseRoundDeathCounter() - self:TTT2NETSetUInt("player_round_deaths", self:GetDeathsInRound() + 1, 8) + self:TTT2NETSetUInt("player_round_deaths", self:GetDeathsInRound() + 1, 8) end --- @@ -1081,7 +1118,7 @@ end -- @internal -- @realm server function plymeta:ResetRoundDeathCounter() - self:TTT2NETSetUInt("player_round_deaths", 0, 8) + self:TTT2NETSetUInt("player_round_deaths", 0, 8) end --- @@ -1089,30 +1126,35 @@ end -- @param table avoidRoles list of @{ROLE}s that should be avoided -- @realm server function plymeta:SelectRandomRole(avoidRoles) - local availablePlayers = roleselection.GetSelectablePlayers(player.GetAll()) - local allAvailableRoles = roleselection.GetAllSelectableRolesList(#availablePlayers) - local selectableRoles = roleselection.GetSelectableRoles(#availablePlayers, allAvailableRoles) + local availablePlayers = roleselection.GetSelectablePlayers(player.GetAll()) + local allAvailableRoles = roleselection.GetAllSelectableRolesList(#availablePlayers) + local selectableRoles = roleselection.GetSelectableRoles(#availablePlayers, allAvailableRoles) - local availableRoles = {} - local roleCount = {} + local availableRoles = {} + local roleCount = {} - for i = 1, #availablePlayers do - local rd = availablePlayers[i]:GetSubRoleData() + for i = 1, #availablePlayers do + local rd = availablePlayers[i]:GetSubRoleData() - roleCount[rd] = (roleCount[rd] or 0) + 1 - end + roleCount[rd] = (roleCount[rd] or 0) + 1 + end - for roleData, roleAmount in pairs(selectableRoles) do - if (not avoidRoles or not avoidRoles[roleData]) and (not roleCount[roleData] or roleCount[roleData] < roleAmount) then - availableRoles[#availableRoles + 1] = roleData.index - end - end + for roleData, roleAmount in pairs(selectableRoles) do + if + (not avoidRoles or not avoidRoles[roleData]) + and (not roleCount[roleData] or roleCount[roleData] < roleAmount) + then + availableRoles[#availableRoles + 1] = roleData.index + end + end - if #availableRoles < 1 then return end + if #availableRoles < 1 then + return + end - self:SetRole(availableRoles[math.random(#availableRoles)]) + self:SetRole(availableRoles[math.random(#availableRoles)]) - SendFullStateUpdate() + SendFullStateUpdate() end local pendingItems = {} @@ -1122,30 +1164,32 @@ local pendingItems = {} -- @param string cls -- @realm server function plymeta:GiveItem(cls) - if GetRoundState() == ROUND_PREP then - pendingItems[self] = pendingItems[self] or {} - pendingItems[self][#pendingItems[self] + 1] = cls + if GetRoundState() == ROUND_PREP then + pendingItems[self] = pendingItems[self] or {} + pendingItems[self][#pendingItems[self] + 1] = cls - return - end + return + end - self:GiveEquipmentItem(cls) - self:AddBought(cls) + self:GiveEquipmentItem(cls) + self:AddBought(cls) - local item = items.GetStored(cls) - if item and isfunction(item.Bought) then - item:Bought(self) - end + local item = items.GetStored(cls) + if item and isfunction(item.Bought) then + item:Bought(self) + end - local ply = self + local ply = self - timer.Simple(0.5, function() - if not IsValid(ply) then return end + timer.Simple(0.5, function() + if not IsValid(ply) then + return + end - net.Start("TTT_BoughtItem") - net.WriteString(cls) - net.Send(ply) - end) + net.Start("TTT_BoughtItem") + net.WriteString(cls) + net.Send(ply) + end) end --- @@ -1153,8 +1197,8 @@ end -- @param string cls -- @realm server function plymeta:RemoveItem(cls) - self:RemoveEquipmentItem(cls) - self:RemoveBought(cls) + self:RemoveEquipmentItem(cls) + self:RemoveBought(cls) end --- @@ -1162,8 +1206,8 @@ end -- @param string cls -- @realm server function plymeta:RemoveWeapon(cls) - self:RemoveEquipmentWeapon(cls) - self:RemoveBought(cls) + self:RemoveEquipmentWeapon(cls) + self:RemoveBought(cls) end --- @@ -1171,27 +1215,27 @@ end -- @param[opt] boolean announceRole -- @realm server function plymeta:ConfirmPlayer(announceRole) - if self:GetNWFloat("t_first_found", -1) < 0 then - self:TTT2NETSetFloat("t_first_found", CurTime()) - end + if self:GetNWFloat("t_first_found", -1) < 0 then + self:TTT2NETSetFloat("t_first_found", CurTime()) + end - self:TTT2NETSetFloat("t_last_found", CurTime()) + self:TTT2NETSetFloat("t_last_found", CurTime()) - if announceRole then - self:TTT2NETSetBool("role_found", true) - end + if announceRole then + self:TTT2NETSetBool("role_found", true) + end - self:TTT2NETSetBool("body_found", true) + self:TTT2NETSetBool("body_found", true) end --- -- Resets the confirmation of a @{Player} -- @realm server function plymeta:ResetConfirmPlayer() - -- body_found is reset on the player reset - self:TTT2NETSetBool("role_found", false) - self:TTT2NETSetFloat("t_first_found", -1) - self:TTT2NETSetFloat("t_last_found", -1) + -- body_found is reset on the player reset + self:TTT2NETSetBool("role_found", false) + self:TTT2NETSetFloat("t_first_found", -1) + self:TTT2NETSetFloat("t_last_found", -1) end --- @@ -1201,46 +1245,50 @@ end -- @param ACT act The @{ACT} or sequence that should be played -- @realm server function plymeta:AnimPerformGesture(act) - if not act then return end + if not act then + return + end - net.Start("TTT_PerformGesture") - net.WriteEntity(self) - net.WriteUInt(act, 16) - net.Broadcast() + net.Start("TTT_PerformGesture") + net.WriteEntity(self) + net.WriteUInt(act, 16) + net.Broadcast() end -- TODO REMOVE THIS hook.Add("TTTBeginRound", "TTT2GivePendingItems", function() - for ply, tbl in pairs(pendingItems) do - if not IsValid(ply) then continue end + for ply, tbl in pairs(pendingItems) do + if not IsValid(ply) then + continue + end - local plyGiveItem = ply.GiveItem + local plyGiveItem = ply.GiveItem - for i = 1, #tbl do - plyGiveItem(ply, tbl[i]) - end - end + for i = 1, #tbl do + plyGiveItem(ply, tbl[i]) + end + end - pendingItems = {} + pendingItems = {} end) -- reset confirm state only on round begin, not on revive hook.Add("TTTBeginRound", "TTT2ResetRoleState_Begin", function() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - plys[i]:ResetConfirmPlayer() - end + for i = 1, #plys do + plys[i]:ResetConfirmPlayer() + end end) -- additionally reset confirm state on round prepare to prevent short blinking of confirmed roles on round start hook.Add("TTTPrepareRound", "TTT2ResetRoleState_End", function() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - plys[i]:ResetConfirmPlayer() - end + for i = 1, #plys do + plys[i]:ResetConfirmPlayer() + end end) local plymeta_old_Give = plymeta.Give @@ -1254,18 +1302,32 @@ local plymeta_old_Give = plymeta.Give -- @return Weapon -- @realm server function plymeta:Give(weaponClassName, bNoAmmo) - -- ForcedPickup needs to be used to be able to ignore the cv_auto_pickup cvar - self.forcedPickup = true + -- ForcedPickup needs to be used to be able to ignore the cv_auto_pickup cvar + self.forcedPickup = true + + -- ForcedGive needs to be used to give weapons when there are cached ones, e.g. in use with the spawneditor + self.forcedGive = true + + local wep = plymeta_old_Give(self, weaponClassName, bNoAmmo or false) - -- ForcedGive needs to be used to give weapons when there are cached ones, e.g. in use with the spawneditor - self.forcedGive = true + self.forcedPickup = false + self.forcedGive = false - local wep = plymeta_old_Give(self, weaponClassName, bNoAmmo or false) + return wep +end - self.forcedPickup = false - self.forcedGive = false +--- +-- Checks if the player has space in front of them to drop a weapon. +-- @param Vector pos The position from where the drop should start +-- @param Vector aim The aim vector or the general drop vector +-- @param[opt] Weapon wep The weapon that should be dropped; add it as a parameter +-- to have it on the trace ignore list +-- @return boolean Returns if there is space for a weapon to be dropped +-- @realm server +function plymeta:HasDropSpace(pos, aim, wep) + local tr = util.QuickTrace(pos, aim * 18, { self, wep }) - return wep + return not tr.Hit end --- @@ -1274,23 +1336,21 @@ end -- @return boolean Returns if this weapon can be dropped -- @realm server function plymeta:CanSafeDropWeapon(wep) - if not wep then - return true - end - - if not IsValid(wep) or not wep.AllowDrop then - return false - end + if not wep then + return true + end - local tr = util.QuickTrace(self:GetShootPos(), self:GetAimVector() * 32, self) + if not IsValid(wep) or not wep.AllowDrop then + return false + end - if tr.Hit then - LANG.Msg(self, "drop_no_room", nil, MSG_MSTACK_WARN) + if not self:HasDropSpace(self:GetShootPos(), self:GetAimVector(), wep) then + LANG.Msg(self, "drop_no_room", nil, MSG_MSTACK_WARN) - return false - end + return false + end - return true + return true end --- @@ -1300,15 +1360,148 @@ end -- @return boolean Returns if this weapon is dropped -- @realm server function plymeta:SafeDropWeapon(wep, keepSelection) - if not self:CanSafeDropWeapon(wep) then - return false - end + if not self:CanSafeDropWeapon(wep) then + return false + end + + self:AnimPerformGesture(ACT_GMOD_GESTURE_ITEM_PLACE) + + WEPS.DropNotifiedWeapon(self, wep, false, keepSelection) + + return true +end + +local function TraceAmmoDrop(ply) + local pos, ang = ply:GetShootPos(), ply:EyeAngles() + local fwd, rgt, up = ang:Forward(), ang:Right(), ang:Up() + + local dir = fwd * 32 + rgt:Mul(6) + up:Mul(-5) + dir:Add(rgt) + dir:Add(up) + + local tr = util.QuickTrace(pos, dir, ply) + + return tr, pos, dir, fwd +end + +--- +-- Checks if the ammo can be dropped in a safe manner. +-- @param Weapon wep The weapon's ammo that should be dropped +-- @return boolean Returns if this weapon's ammo can be dropped +-- @realm server +function plymeta:CanSafeDropAmmo(wep) + if not IsValid(self) or not (IsValid(wep) and wep.AmmoEnt) then + return false + end + + local tr = TraceAmmoDrop(self) + + if tr.HitWorld then + LANG.Msg(self, "drop_no_room_ammo", nil, MSG_MSTACK_WARN) + + return false + end + + return true +end + +--- +-- Called to drop ammo in a safe manner (e.g. preparing and space-check). +-- @param Weapon wep The weapon that should be referenced when dropping ammo. +-- @param[default=false] boolean useClip If set to true the ammo is dropped from the clip, otherwise, from reserve ammo. +-- @param[default=0] number amt The quantity of ammo to drop. +-- @return boolean Returns if ammo is dropped +-- @realm server +function plymeta:SafeDropAmmo(wep, useClip, amt) + if not self:CanSafeDropAmmo(wep) then + return false + end + + self:AnimPerformGesture(ACT_GMOD_GESTURE_ITEM_GIVE) + + self:DropAmmo(wep, useClip, amt) + + return true +end + +--- +-- Called to drop a weapon's ammo. Does no safety checks. +-- @param Weapon wep The weapon's ammo that should be dropped +-- @param[default=false] boolean useClip If set to true the ammo is dropped from the clip, otherwise, from reserve ammo. +-- @param[default=0] number amt The quantity of ammo to drop. +-- @return boolean Returns if this weapon's ammo is dropped. +-- @realm server +function plymeta:DropAmmo(wep, useClip, amt) + amt = amt or 0 + + local box = ents.Create(wep.AmmoEnt) + + if not IsValid(box) then + return + end + + -- most of this goes into computing ammo amount + if amt <= 0 then + if useClip then + amt = wep:Clip1() + else + amt = math.min(wep.Primary.ClipSize, self:GetAmmoCount(wep.Primary.Ammo)) + end + end + local hook_data = { amt } + + --- + -- @realm server + -- stylua: ignore + if hook.Run("TTT2DropAmmo", self, hook_data) == false then + LANG.Msg(self, useClip and "drop_ammo_prevented" or "drop_reserve_prevented", nil, MSG_MSTACK_WARN) - self:AnimPerformGesture(ACT_GMOD_GESTURE_ITEM_PLACE) + return false + end + amt = hook_data[1] + if amt < 1 or amt <= wep.Primary.ClipSize * 0.25 then + LANG.Msg(self, useClip and "drop_no_ammo" or "drop_no_reserve", nil, MSG_MSTACK_WARN) - WEPS.DropNotifiedWeapon(self, wep, false, keepSelection) + return false + end - return true + local _, pos, dir, fwd = TraceAmmoDrop(self) + + pos:Add(dir) + + box:SetPos(pos) + box:SetOwner(self) + box:Spawn() + box:PhysWake() + + local phys = box:GetPhysicsObject() + + if IsValid(phys) then + fwd:Mul(1000) + + phys:ApplyForceCenter(fwd) + phys:ApplyForceOffset(VectorRand(), vector_origin) + end + + box.AmmoAmount = amt + + timer.Simple(2, function() + if not IsValid(box) then + return + end + + box:SetOwner(nil) + end) + + if useClip then + wep:SetClip1(math.max(wep:Clip1() - amt, 0)) + else + self:RemoveAmmo(amt, wep.Primary.Ammo) + end + + return true end --- @@ -1320,15 +1513,16 @@ end -- @return number errorCode that appeared. For the error, give a look into the specific hook -- @realm server function plymeta:CanPickupWeapon(wep, forcePickup, dropBlockingWeapon) - self.forcedPickup = forcePickup + self.forcedPickup = forcePickup - --- - -- @realm server - local ret, errCode = hook.Run("PlayerCanPickupWeapon", self, wep, dropBlockingWeapon, true) + --- + -- @realm server + -- stylua: ignore + local ret, errCode = hook.Run("PlayerCanPickupWeapon", self, wep, dropBlockingWeapon, true) - self.forcedPickup = false + self.forcedPickup = false - return ret, errCode + return ret, errCode end --- @@ -1339,9 +1533,9 @@ end -- @return boolean -- @realm server function plymeta:CanPickupWeaponClass(wepCls, forcePickup, dropBlockingWeapon) - local wep = ents.Create(wepCls) + local wep = ents.Create(wepCls) - return self:CanPickupWeapon(wep, forcePickup, dropBlockingWeapon) + return self:CanPickupWeapon(wep, forcePickup, dropBlockingWeapon) end --- @@ -1355,68 +1549,80 @@ end -- @return Weapon if successful, nil if not -- @realm server function plymeta:SafePickupWeapon(wep, ammoOnly, forcePickup, dropBlockingWeapon, shouldAutoSelect) - if not IsValid(wep) then - ErrorNoHalt(tostring(self) .. " tried to pickup an invalid weapon " .. tostring(wep) .. "\n") + if not IsValid(wep) then + ErrorNoHaltWithStack( + tostring(self) .. " tried to pickup an invalid weapon " .. tostring(wep) .. "\n" + ) - LANG.Msg(self, "pickup_fail") + LANG.Msg(self, "pickup_fail") - return - end + return + end - -- block weapon switch if slot is occupied and there is no room to - -- drop the weapon safely - if not InventorySlotFree(self, wep.Kind) and not self:CanSafeDropWeapon(wep) then return end + -- block weapon switch if slot is occupied and there is no room to + -- drop the weapon safely + if not InventorySlotFree(self, wep.Kind) and not self:CanSafeDropWeapon(wep) then + return + end - local ret, errCode = self:CanPickupWeapon(wep, forcePickup, dropBlockingWeapon) + local ret, errCode = self:CanPickupWeapon(wep, forcePickup, dropBlockingWeapon) - if not ret then - if errCode == 1 then - LANG.Msg(self, "pickup_error_spec") - elseif errCode == 2 then - LANG.Msg(self, "pickup_error_owns") - elseif errCode == 3 then - LANG.Msg(self, "pickup_error_noslot") - elseif errCode == 6 then - LANG.Msg(self, "pickup_error_inv_cached") - end + if not ret then + if errCode == 1 then + LANG.Msg(self, "pickup_error_spec") + elseif errCode == 2 then + LANG.Msg(self, "pickup_error_owns") + elseif errCode == 3 then + LANG.Msg(self, "pickup_error_noslot") + elseif errCode == 6 then + LANG.Msg(self, "pickup_error_inv_cached") + end - return - end + return + end - -- if the variable is not set, set it fitting to the keypress - if shouldAutoSelect == nil then - shouldAutoSelect = not self:KeyDown(IN_WALK) and not self:KeyDownLast(IN_WALK) - end + -- if the variable is not set, set it fitting to the keypress + if shouldAutoSelect == nil then + shouldAutoSelect = not self:KeyDown(IN_WALK) and not self:KeyDownLast(IN_WALK) + end - -- if parameter is set the currently blocking weapon should be dropped - if dropBlockingWeapon ~= false then - local dropWeapon, isActiveWeapon, switchMode = GetBlockingWeapon(self, wep) + -- if parameter is set the currently blocking weapon should be dropped + if dropBlockingWeapon ~= false then + local dropWeapon, isActiveWeapon, switchMode = GetBlockingWeapon(self, wep) - if switchMode == SWITCHMODE_FULLINV then - LANG.Msg(self, "pickup_no_room") + if switchMode == SWITCHMODE_FULLINV then + LANG.Msg(self, "pickup_no_room") - return - end + return + end - -- Very very rarely happens but definitely breaks the weapon and should be avoided at all costs - if dropWeapon == wep then return end + -- Very very rarely happens but definitely breaks the weapon and should be avoided at all costs + if dropWeapon == wep then + return + end - if not self:SafeDropWeapon(dropWeapon, true) then return end + if not self:SafeDropWeapon(dropWeapon, true) then + return + end - -- set flag to new weapon that is used to autoselect it later on - shouldAutoSelect = shouldAutoSelect or isActiveWeapon + -- set flag to new weapon that is used to autoselect it later on + shouldAutoSelect = shouldAutoSelect or isActiveWeapon - -- set to holstered if current weapon is dropped to prevent short crowbar selection - if isActiveWeapon then - self:SelectWeapon("weapon_ttt_unarmed") - end - end + -- set to holstered if current weapon is dropped to prevent short crowbar selection + if isActiveWeapon then + self:SelectWeapon("weapon_ttt_unarmed") + end + end - if not self:PickupWeapon(wep, ammoOnly or false) then return end + if not self:PickupWeapon(wep, ammoOnly or false) then + return + end - wep.wpickup_autoSelect = shouldAutoSelect + wep.wpickup_autoSelect = shouldAutoSelect - return wep + self:EmitSound(soundWeaponPickup) + + return wep end --- @@ -1428,47 +1634,54 @@ end -- @return Weapon if successful, nil if not -- @realm server function plymeta:SafePickupWeaponClass(wepCls, dropBlockingWeapon, shouldAutoSelect) - -- if the variable is not set, set it fitting to the keypress - if shouldAutoSelect == nil then - shouldAutoSelect = not self:KeyDown(IN_WALK) and not self:KeyDownLast(IN_WALK) - end + -- if the variable is not set, set it fitting to the keypress + if shouldAutoSelect == nil then + shouldAutoSelect = not self:KeyDown(IN_WALK) and not self:KeyDownLast(IN_WALK) + end + + local wep = weapons.GetStored(wepCls) + local pWep - local wep = weapons.GetStored(wepCls) - local pWep + -- if parameter is set the currently blocking weapon should be dropped + if dropBlockingWeapon then + local dropWeapon, isActiveWeapon, switchMode = GetBlockingWeapon(self, wep) - -- if parameter is set the currently blocking weapon should be dropped - if dropBlockingWeapon then - local dropWeapon, isActiveWeapon, switchMode = GetBlockingWeapon(self, wep) + if switchMode == SWITCHMODE_FULLINV or switchMode == SWITCHMODE_NOSPACE then + return + end - if switchMode == SWITCHMODE_FULLINV or switchMode == SWITCHMODE_NOSPACE then return end + self:SafeDropWeapon(dropWeapon, true) - self:SafeDropWeapon(dropWeapon, true) + pWep = self:Give(wepCls) - pWep = self:Give(wepCls) + if IsValid(pWep) then + -- set flag to new weapon that is used to autoselect it later on + pWep.wpickup_autoSelect = shouldAutoSelect or isActiveWeapon + end + end - if IsValid(pWep) then - -- set flag to new weapon that is used to autoselect it later on - pWep.wpickup_autoSelect = shouldAutoSelect or isActiveWeapon - end - end + self:EmitSound(soundWeaponPickup) - return pWep + return pWep end -- receives the PlayerReady flag from the client and calls the serverwide hook local function SetPlayerReady(_, ply) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply.isReady = true + ply.isReady = true - -- Send full state update to client - ttt2net.SendFullStateUpdate(ply) + -- Send full state update to client + ttt2net.SendFullStateUpdate(ply) - entspawnscript.TransmitToPlayer(ply) + entspawnscript.TransmitToPlayer(ply) - --- - -- @realm server - hook.Run("TTT2PlayerReady", ply) + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2PlayerReady", ply) end net.Receive("TTT2SetPlayerReady", SetPlayerReady) @@ -1477,8 +1690,8 @@ net.Receive("TTT2SetPlayerReady", SetPlayerReady) -- without triggering the restore function, e.g. @{GM:PlayerSpawn}. -- @realm server function plymeta:ResetCachedWeapons() - self.cachedWeaponInventory = nil - self.cachedWeaponSelected = nil + self.cachedWeaponInventory = nil + self.cachedWeaponSelected = nil end --- @@ -1486,33 +1699,43 @@ end -- @return boolean Returns wether the player has a cached inventory -- @realm server function plymeta:HasCachedWeapons() - return self.cachedWeaponInventory ~= nil + return self.cachedWeaponInventory ~= nil end --- -- Caches the weapons currently in the player inventory and removes them. -- These weapons can be restored at any time. +-- @param boolean removeUnarmed force all weapons to be removed, including weapon_ttt_unarmed -- @note As long as a player has cached weapons, they are unable to pick up any weapon. -- @realm server -function plymeta:CacheAndStripWeapons() - local cachedWeaponInventory = {} +function plymeta:CacheAndStripWeapons(removeUnarmed) + self.cachedWeaponInventory = {} + self.cachedWeaponSelected = WEPS.GetClass(self:GetActiveWeapon()) - local weps = self:GetWeapons() + local weps = self:GetWeapons() - for i = 1, #weps do - local wep = weps[i] + for i = 1, #weps do + local wep = weps[i] + local wepClass = WEPS.GetClass(wep) - cachedWeaponInventory[#cachedWeaponInventory + 1] = { - cls = WEPS.GetClass(wep), - clip1 = wep:Clip1(), - clip2 = wep:Clip2() - } - end + if not removeUnarmed and wepClass == "weapon_ttt_unarmed" then + continue + end - self.cachedWeaponInventory = cachedWeaponInventory - self.cachedWeaponSelected = WEPS.GetClass(self:GetActiveWeapon()) + self.cachedWeaponInventory[#self.cachedWeaponInventory + 1] = { + cls = wepClass, + clip1 = wep:Clip1(), + clip2 = wep:Clip2(), + } + end - self:StripWeapons() + -- we have to use this hack here instead of StripWeapon because StripWeapon calls + -- OnDrop which is not intended for the weapon caching + self:StripWeapons() + + if not removeUnarmed then + self:Give("weapon_ttt_unarmed") + end end --- @@ -1520,24 +1743,135 @@ end -- no weapons are cached. -- @realm server function plymeta:RestoreCachedWeapons() - if not self:HasCachedWeapons() then return end + if not self:HasCachedWeapons() then + return + end + + for i = 1, #self.cachedWeaponInventory do + local wep = self.cachedWeaponInventory[i] + + local givenWep = self:Give(wep.cls) + + if not IsValid(givenWep) then + continue + end - for i = 1, #self.cachedWeaponInventory do - local wep = self.cachedWeaponInventory[i] + givenWep:SetClip1(wep.clip1 or 0) + givenWep:SetClip2(wep.clip2 or 0) + end - local givenWep = self:Give(wep.cls) + if self.cachedWeaponSelected then + local cachedWeaponSelected = self.cachedWeaponSelected - if not IsValid(givenWep) then continue end + -- delay selection by .1 seconds to actually select the weapon + timer.Simple(0.1, function() + if not IsValid(self) then + return + end - givenWep:SetClip1(wep.clip1 or 0) - givenWep:SetClip2(wep.clip2 or 0) - end + self:SelectWeapon(cachedWeaponSelected) + end) + end - if self.cachedWeaponSelected then - self:SelectWeapon(self.cachedWeaponSelected) - end + self:ResetCachedWeapons() +end + +--- +-- Removes a cached weapon from the cache list. +-- @param string wep The weapon class +-- @realm server +function plymeta:RemoveCachedWeapon(wep) + if not self:HasCachedWeapons() then + return + end + + for i = 1, #self.cachedWeaponInventory do + local cachedWeapon = self.cachedWeaponInventory[i] + + if cachedWeapon.cls ~= wep then + continue + end + + table.remove(self.cachedWeaponInventory, i) + + return + end +end - self:ResetCachedWeapons() +--- +-- Checks wether a player has cached items that can be restored. +-- @return boolean Returns wether the player has a cached inventory +-- @realm server +function plymeta:HasCachedItems() + return self.cachedItemInventory ~= nil +end + +--- +-- Caches the items currently in the player inventory and removes them. +-- These items can be restored at any time. +-- @realm server +function plymeta:CacheAndStripItems() + if self:HasCachedItems() then + return + end + + self.cachedItemInventory = self:GetEquipmentItems() + + self:SetEquipmentItems(nil) +end + +--- +-- Restores the cached items if there are any cached items. Does nothing if +-- no items are cached. +-- @realm server +function plymeta:RestoreCachedItems() + if not self:HasCachedItems() then + return + end + + -- make sure the player keeps any items received during this period + table.Merge(self.cachedItemInventory, self:GetEquipmentItems()) + + self:SetEquipmentItems(self.cachedItemInventory) + + self.cachedItemInventory = nil +end + +--- +-- Removes a cached item from the cache list. +-- @param string item The item class +-- @realm server +function plymeta:RemoveCachedItem(item) + if not self:HasCachedItems() then + return + end + + for i = 1, #self.cachedItemInventory do + local cachedItem = self.cachedItemInventory[i] + + if cachedItem ~= item then + continue + end + + table.remove(self.cachedItemInventory, i) + + -- make sure equipment remove functions are called + items.GetStored(item):Reset(self) + self:SendEquipment(EQUIPITEMS_REMOVE, item) + + return + end +end + +--- +-- Used to reset the weapon cache at round restart. +-- @internal +-- @realm server +function plymeta:ResetItemAndWeaponCache() + self.cachedWeaponInventory = nil + self.cachedWeaponSelected = nil + + self.cachedItemInventory = nil end --- @@ -1547,9 +1881,7 @@ end -- receiving their credits -- @hook -- @realm server -function GM:TTT2SetDefaultCredits(ply) - -end +function GM:TTT2SetDefaultCredits(ply) end --- -- Hook that is used to modify the default credits of a traitor. @@ -1558,9 +1890,7 @@ end -- @return nil|number The amound of credits the player should receive -- @hook -- @realm server -function GM:TTT2ModifyDefaultTraitorCredits(ply, credits) - -end +function GM:TTT2ModifyDefaultTraitorCredits(ply, credits) end --- -- Hook that is called when a player recieves credits for a kill. @@ -1569,9 +1899,7 @@ end -- @param number credits The amount of credits the player received -- @hook -- @realm server -function GM:TTT2ReceivedKillCredits(ply, victim, credits) - -end +function GM:TTT2ReceivedKillCredits(ply, victim, credits) end --- -- Hook that is called when a player recieves credits as a team award. @@ -1579,6 +1907,4 @@ end -- @param number credits The amount of credits the player received -- @hook -- @realm server -function GM:TTT2ReceivedTeamAwardCredits(ply, credits) - -end \ No newline at end of file +function GM:TTT2ReceivedTeamAwardCredits(ply, credits) end \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/server/sv_propspec.lua b/gamemodes/terrortown/gamemode/server/sv_propspec.lua index 880c28497..ddc134830 100644 --- a/gamemodes/terrortown/gamemode/server/sv_propspec.lua +++ b/gamemodes/terrortown/gamemode/server/sv_propspec.lua @@ -11,19 +11,28 @@ PROPSPEC = {} --- -- @realm server -local propspec_toggle = CreateConVar("ttt_spec_prop_control", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) +-- stylua: ignore +local cvPropspecToggle = CreateConVar("ttt_spec_prop_control", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server -local propspec_base = CreateConVar("ttt_spec_prop_base", "8", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) +-- stylua: ignore +local cvPropspecBase = CreateConVar("ttt_spec_prop_base", "8", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server -local propspec_min = CreateConVar("ttt_spec_prop_maxpenalty", "-6", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) +-- stylua: ignore +local cvPropspecMin = CreateConVar("ttt_spec_prop_maxpenalty", "-6", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server -local propspec_max = CreateConVar("ttt_spec_prop_maxbonus", "16", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) +-- stylua: ignore +local cvPropspecMax = CreateConVar("ttt_spec_prop_maxbonus", "16", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + +--- +-- @realm server +-- stylua: ignore +local cvPropspecDashMulitplier = CreateConVar("ttt_spec_prop_dash", "2", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- Forces a @{Player} to spectate an @{Entity} @@ -31,25 +40,26 @@ local propspec_max = CreateConVar("ttt_spec_prop_maxbonus", "16", {FCVAR_NOTIFY, -- @param Entity ent -- @realm server function PROPSPEC.Start(ply, ent) - ply:Spectate(OBS_MODE_CHASE) - ply:SpectateEntity(ent, true) - - local bonus = math.Clamp(math.ceil(ply:Frags() * 0.5), propspec_min:GetInt(), propspec_max:GetInt()) - - ply.propspec = { - ent = ent, - t = 0, - retime = 0, - punches = 0, - max = propspec_base:GetInt() + bonus - } - - ent:SetNWEntity("spec_owner", ply) - ply:SetNWInt("bonuspunches", bonus) + ply:Spectate(OBS_MODE_CHASE) + ply:SpectateEntity(ent, true) + + local bonus = + math.Clamp(math.ceil(ply:Frags() * 0.5), cvPropspecMin:GetInt(), cvPropspecMax:GetInt()) + + ply.propspec = { + ent = ent, + t = 0, + retime = 0, + punches = 0, + max = cvPropspecBase:GetInt() + bonus, + } + + ent:SetNWEntity("spec_owner", ply) + ply:SetNWInt("bonuspunches", bonus) end local function IsWhitelistedClass(cls) - return string.match(cls, "prop_physics*") or string.match(cls, "func_physbox*") + return string.match(cls, "prop_physics*") or string.match(cls, "func_physbox*") end --- @@ -58,17 +68,33 @@ end -- @param Entity ent -- @realm server function PROPSPEC.Target(ply, ent) - if not propspec_toggle:GetBool() or not IsValid(ply) or not ply:IsSpec() or not IsValid(ent) or IsValid(ent:GetNWEntity("spec_owner", nil)) then return end - - local phys = ent:GetPhysicsObject() - - if ent:GetName() ~= "" and not GAMEMODE.propspec_allow_named or not IsValid(phys) or not phys:IsMoveable() then return end - - -- normally only specific whitelisted ent classes can be possessed, but - -- custom ents can mark themselves possessable as well - if not ent.AllowPropspec and not IsWhitelistedClass(ent:GetClass()) then return end - - PROPSPEC.Start(ply, ent) + if + not cvPropspecToggle:GetBool() + or not IsValid(ply) + or not ply:IsSpec() + or not IsValid(ent) + or IsValid(ent:GetNWEntity("spec_owner", nil)) + then + return + end + + local phys = ent:GetPhysicsObject() + + if + ent:GetName() ~= "" and not GAMEMODE.propspec_allow_named + or not IsValid(phys) + or not phys:IsMoveable() + then + return + end + + -- normally only specific whitelisted ent classes can be possessed, but + -- custom ents can mark themselves possessable as well + if not ent.AllowPropspec and not IsWhitelistedClass(ent:GetClass()) then + return + end + + PROPSPEC.Start(ply, ent) end --- @@ -77,15 +103,15 @@ end -- @param Player ply -- @realm server function PROPSPEC.Clear(ply) - local ent = (ply.propspec and ply.propspec.ent) or ply:GetObserverTarget() + local ent = (ply.propspec and ply.propspec.ent) or ply:GetObserverTarget() - if IsValid(ent) then - ent:SetNWEntity("spec_owner", nil) - end + if IsValid(ent) then + ent:SetNWEntity("spec_owner", nil) + end - ply.propspec = nil + ply.propspec = nil - ply:SpectateEntity(nil) + ply:UnSpectate() end --- @@ -93,102 +119,114 @@ end -- @param Player ply -- @realm server function PROPSPEC.End(ply) - PROPSPEC.Clear(ply) + PROPSPEC.Clear(ply) - ply:Spectate(OBS_MODE_ROAMING) - ply:ResetViewRoll() + ply:Spectate(OBS_MODE_ROAMING) + ply:ResetViewRoll() - timer.Simple(0.1, function() - if not IsValid(ply) then return end + timer.Simple(0.1, function() + if not IsValid(ply) then + return + end - ply:ResetViewRoll() - end) + ply:ResetViewRoll() + end) end --- -- @realm server +-- stylua: ignore local propspec_force = CreateConVar("ttt_spec_prop_force", "110", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- Triggers an event based on the pressed key -- @param Player ply The @{Player} pressing the key. If running client-side, this will always be @{LocalPlayer} --- @param number key The key that the @{Player} pressed using IN_Enums. +-- @param number key The key that the @{Player} pressed using IN_Enums. -- @return boolean -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:KeyPress function PROPSPEC.Key(ply, key) - local ent = ply.propspec.ent - local validEnt = IsValid(ent) - local phys = validEnt and ent:GetPhysicsObject() - - if not validEnt or not IsValid(phys) then - PROPSPEC.End(ply) - - return false - end - - if not phys:IsMoveable() then - PROPSPEC.End(ply) - - return true - elseif phys:HasGameFlag(FVPHYSICS_PLAYER_HELD) then - -- we can stay with the prop while it's held, but not affect it - if key == IN_DUCK then - PROPSPEC.End(ply) - end - - return true - end - - -- always allow leaving - if key == IN_DUCK then - PROPSPEC.End(ply) - - return true - end - - local pr = ply.propspec - - if pr.t > CurTime() or pr.punches < 1 then - return true - end - - local m = math.min(150, phys:GetMass()) - local force = propspec_force:GetInt() - local aim = ply:GetAimVector() - local mf = m * force - - pr.t = CurTime() + 0.15 - - if key == IN_JUMP then - -- upwards bump - phys:ApplyForceCenter(Vector(0, 0, mf)) - - pr.t = CurTime() + 0.05 - elseif key == IN_FORWARD then - -- bump away from player - phys:ApplyForceCenter(aim * mf) - elseif key == IN_BACK then - phys:ApplyForceCenter(aim * (mf * -1)) - elseif key == IN_MOVELEFT then - phys:AddAngleVelocity(Vector(0, 0, 200)) - phys:ApplyForceCenter(Vector(0, 0, mf / 3)) - elseif key == IN_MOVERIGHT then - phys:AddAngleVelocity(Vector(0, 0, -200)) - phys:ApplyForceCenter(Vector(0, 0, mf / 3)) - else - return true -- eat other keys, and do not decrement punches - end - - pr.punches = math.max(pr.punches - 1, 0) - - ply:SetNWFloat("specpunches", pr.punches / pr.max) - - return true + local ent = ply.propspec.ent + local validEnt = IsValid(ent) + local phys = validEnt and ent:GetPhysicsObject() + + if not validEnt or not IsValid(phys) then + PROPSPEC.End(ply) + + return false + end + + if not phys:IsMoveable() then + PROPSPEC.End(ply) + + return true + elseif phys:HasGameFlag(FVPHYSICS_PLAYER_HELD) then + -- we can stay with the prop while it's held, but not affect it + if key == IN_DUCK then + PROPSPEC.End(ply) + end + + return true + end + + -- always allow leaving + if key == IN_DUCK then + PROPSPEC.End(ply) + + return true + end + + local pSpec = ply.propspec + + if pSpec.t > CurTime() or pSpec.punches < 1 then + return true + end + + local mass = math.min(150, phys:GetMass()) + local force = propspec_force:GetInt() + local mf = mass * force + local vectorAim = ply:GetAimVector() + + local vectorAimPlanar = Vector(vectorAim.x, vectorAim.y, 0) + vectorAimPlanar:Normalize() + + local vectorPerpendicularLeft = Vector(-vectorAimPlanar.y, vectorAimPlanar.x, 0) + local vectorPerpendicularRight = Vector(vectorAimPlanar.y, -vectorAimPlanar.x, 0) + + local multiplierPunches = 1 + + pSpec.t = CurTime() + 0.15 + + if key == IN_JUMP then + phys:ApplyForceCenter(Vector(0, 0, mf)) + + pSpec.t = CurTime() + 0.05 + elseif key == IN_SPEED then + multiplierPunches = cvPropspecDashMulitplier:GetInt() + + phys:ApplyForceCenter(vectorAim:GetNormalized() * mf * cvPropspecDashMulitplier:GetInt()) + elseif key == IN_FORWARD then + phys:ApplyForceCenter(vectorAimPlanar * mf) + elseif key == IN_BACK then + phys:ApplyForceCenter(-vectorAimPlanar * mf) + elseif key == IN_MOVELEFT then + phys:ApplyForceCenter(vectorPerpendicularLeft * mf * 0.33) + elseif key == IN_MOVERIGHT then + phys:ApplyForceCenter(vectorPerpendicularRight * mf * 0.33) + else + return true -- eat other keys, and do not decrement punches + end + + pSpec.punches = math.max(pSpec.punches - multiplierPunches, 0) + + ply:SetNWFloat("specpunches", pSpec.punches / pSpec.max) + + return true end --- -- @realm server +-- stylua: ignore local propspec_retime = CreateConVar("ttt_spec_prop_rechargetime", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- @@ -197,13 +235,15 @@ local propspec_retime = CreateConVar("ttt_spec_prop_rechargetime", "1", {FCVAR_N -- @param Player ply -- @realm server function PROPSPEC.Recharge(ply) - local pr = ply.propspec + local pr = ply.propspec - if pr.retime >= CurTime() then return end + if pr.retime >= CurTime() then + return + end - pr.punches = math.min(pr.punches + 1, pr.max) + pr.punches = math.min(pr.punches + 1, pr.max) - ply:SetNWFloat("specpunches", pr.punches / pr.max) + ply:SetNWFloat("specpunches", pr.punches / pr.max) - pr.retime = CurTime() + propspec_retime:GetFloat() + pr.retime = CurTime() + propspec_retime:GetFloat() end diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index ef2bc75b8..c8ea2a36f 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -19,155 +19,173 @@ roleselection.subroleLayers = {} -- Convars roleselection.cv = { - --- - -- @realm server - ttt_max_roles = CreateConVar("ttt_max_roles", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different roles"), - - --- - -- @realm server - ttt_max_roles_pct = CreateConVar("ttt_max_roles_pct", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different roles based on player amount. ttt_max_roles needs to be 0"), - - --- - -- @realm server - ttt_max_baseroles = CreateConVar("ttt_max_baseroles", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different baseroles"), - - --- - -- @realm server - ttt_max_baseroles_pct = CreateConVar("ttt_max_baseroles_pct", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different baseroles based on player amount. ttt_max_baseroles needs to be 0") + --- + -- @realm server + -- stylua: ignore + ttt_max_roles = CreateConVar("ttt_max_roles", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different roles"), + + --- + -- @realm server + -- stylua: ignore + ttt_max_roles_pct = CreateConVar("ttt_max_roles_pct", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different roles based on player amount. ttt_max_roles needs to be 0"), + + --- + -- @realm server + -- stylua: ignore + ttt_max_baseroles = CreateConVar("ttt_max_baseroles", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different baseroles"), + + --- + -- @realm server + -- stylua: ignore + ttt_max_baseroles_pct = CreateConVar("ttt_max_baseroles_pct", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different baseroles based on player amount. ttt_max_baseroles needs to be 0") +, } -- saving and loading roleselection.sqltable = "ttt2_roleselection" roleselection.savingKeys = { - layer = {typ = "number", bits = ROLE_BITS, default = 0}, - depth = {typ = "number", bits = ROLE_BITS, default = 0} + layer = { typ = "number", bits = ROLE_BITS, default = 0 }, + depth = { typ = "number", bits = ROLE_BITS, default = 0 }, } --- -- Loads every layer from the SQL database -- @realm server function roleselection.LoadLayers() - if not sql.CreateSqlTable(roleselection.sqltable, roleselection.savingKeys) then return end - - local roleList = roles.GetList() - - for i = 1, #roleList do - local roleData = roleList[i] - local dataTable = { - layer = 0, - depth = 0 - } - - local loaded, changed = sql.Load(roleselection.sqltable, roleData.name, dataTable, roleselection.savingKeys) - - if not loaded then - -- automatically put the Detective into the first layer if the layering system is initialized the first time - -- for that role (and there isn't any already existing layer) to keep the default TTT behavior - if roleData.index == ROLE_DETECTIVE and roleselection.baseroleLayers[1] == nil then - dataTable.layer = 1 - dataTable.depth = 1 - - roleselection.baseroleLayers[1] = {} - roleselection.baseroleLayers[1][1] = roleData.index - end - - sql.Init(roleselection.sqltable, roleData.name, dataTable, roleselection.savingKeys) - elseif changed then - if dataTable.layer == 0 or dataTable.depth == 0 then continue end -- if (0, 0), exclude from layering - - if roleData:IsBaseRole() then - roleselection.baseroleLayers[dataTable.layer] = roleselection.baseroleLayers[dataTable.layer] or {} - roleselection.baseroleLayers[dataTable.layer][dataTable.depth] = roleData.index - else - local baserole = roleData:GetBaseRole() - - roleselection.subroleLayers[baserole] = roleselection.subroleLayers[baserole] or {} - roleselection.subroleLayers[baserole][dataTable.layer] = roleselection.subroleLayers[baserole][dataTable.layer] or {} - roleselection.subroleLayers[baserole][dataTable.layer][dataTable.depth] = roleData.index - end - end - end - - -- validate layers, could be invalid if there are already uninstalled roles - local layerCount = 0 - local depthCount = 0 - local validTbl = {} - - -- baseroles - for _, currentLayerTbl in pairs(roleselection.baseroleLayers) do -- layer - layerCount = layerCount + 1 - depthCount = 0 - validTbl[layerCount] = {} - - for _, entry in pairs(currentLayerTbl) do -- depth - depthCount = depthCount + 1 - validTbl[layerCount][depthCount] = entry - end - end - - roleselection.baseroleLayers = validTbl - - validTbl = {} - -- subroles - for baserole, layerTbl in pairs(roleselection.subroleLayers) do -- baserole connection - validTbl[baserole] = {} - layerCount = 0 - - for _, currentLayerTbl in pairs(layerTbl) do -- layer - layerCount = layerCount + 1 - depthCount = 0 - validTbl[baserole][layerCount] = {} - - for _, entry in pairs(currentLayerTbl) do -- depth - depthCount = depthCount + 1 - validTbl[baserole][layerCount][depthCount] = entry - end - end - end - - roleselection.subroleLayers = validTbl + if not sql.CreateSqlTable(roleselection.sqltable, roleselection.savingKeys) then + return + end + + local roleList = roles.GetList() + + for i = 1, #roleList do + local roleData = roleList[i] + local dataTable = { + layer = 0, + depth = 0, + } + + local loaded, changed = + sql.Load(roleselection.sqltable, roleData.name, dataTable, roleselection.savingKeys) + + if not loaded then + -- automatically put the Detective into the first layer if the layering system is initialized the first time + -- for that role (and there isn't any already existing layer) to keep the default TTT behavior + if roleData.index == ROLE_DETECTIVE and roleselection.baseroleLayers[1] == nil then + dataTable.layer = 1 + dataTable.depth = 1 + + roleselection.baseroleLayers[1] = {} + roleselection.baseroleLayers[1][1] = roleData.index + end + + sql.Init(roleselection.sqltable, roleData.name, dataTable, roleselection.savingKeys) + elseif changed then + if dataTable.layer == 0 or dataTable.depth == 0 then + continue + end -- if (0, 0), exclude from layering + + if roleData:IsBaseRole() then + roleselection.baseroleLayers[dataTable.layer] = roleselection.baseroleLayers[dataTable.layer] + or {} + roleselection.baseroleLayers[dataTable.layer][dataTable.depth] = roleData.index + else + local baserole = roleData:GetBaseRole() + + roleselection.subroleLayers[baserole] = roleselection.subroleLayers[baserole] or {} + roleselection.subroleLayers[baserole][dataTable.layer] = roleselection.subroleLayers[baserole][dataTable.layer] + or {} + roleselection.subroleLayers[baserole][dataTable.layer][dataTable.depth] = + roleData.index + end + end + end + + -- validate layers, could be invalid if there are already uninstalled roles + local layerCount = 0 + local depthCount = 0 + local validTbl = {} + + -- baseroles + for _, currentLayerTbl in pairs(roleselection.baseroleLayers) do -- layer + layerCount = layerCount + 1 + depthCount = 0 + validTbl[layerCount] = {} + + for _, entry in pairs(currentLayerTbl) do -- depth + depthCount = depthCount + 1 + validTbl[layerCount][depthCount] = entry + end + end + + roleselection.baseroleLayers = validTbl + + validTbl = {} + -- subroles + for baserole, layerTbl in pairs(roleselection.subroleLayers) do -- baserole connection + validTbl[baserole] = {} + layerCount = 0 + + for _, currentLayerTbl in pairs(layerTbl) do -- layer + layerCount = layerCount + 1 + depthCount = 0 + validTbl[baserole][layerCount] = {} + + for _, entry in pairs(currentLayerTbl) do -- depth + depthCount = depthCount + 1 + validTbl[baserole][layerCount][depthCount] = entry + end + end + end + + roleselection.subroleLayers = validTbl end --- -- Saves every layer into the SQL database -- @realm server function roleselection.SaveLayers() - local dataTable = {} - - -- baseroles - for cLayer = 1, #roleselection.baseroleLayers do - local currentLayerTbl = roleselection.baseroleLayers[cLayer] - - for cDepth = 1, #currentLayerTbl do - dataTable[currentLayerTbl[cDepth]] = { -- role index - ["layer"] = cLayer, - ["depth"] = cDepth - } - end - end - - -- subroles - for baserole, layerTbl in pairs(roleselection.subroleLayers) do - for cLayer = 1, #layerTbl do - local currentLayerTbl = layerTbl[cLayer] - - for cDepth = 1, #currentLayerTbl do - dataTable[currentLayerTbl[cDepth]] = { -- role index - ["layer"] = cLayer, - ["depth"] = cDepth - } - end - end - end - - -- if not in layer, set (0, 0) as default values - local roleList = roles.GetList() - - for i = 1, #roleList do - local roleData = roleList[i] - - sql.Save(roleselection.sqltable, roleData.name, dataTable[roleData.index] or {}, roleselection.savingKeys) - end + local dataTable = {} + + -- baseroles + for cLayer = 1, #roleselection.baseroleLayers do + local currentLayerTbl = roleselection.baseroleLayers[cLayer] + + for cDepth = 1, #currentLayerTbl do + dataTable[currentLayerTbl[cDepth]] = { -- role index + ["layer"] = cLayer, + ["depth"] = cDepth, + } + end + end + + -- subroles + for baserole, layerTbl in pairs(roleselection.subroleLayers) do + for cLayer = 1, #layerTbl do + local currentLayerTbl = layerTbl[cLayer] + + for cDepth = 1, #currentLayerTbl do + dataTable[currentLayerTbl[cDepth]] = { -- role index + ["layer"] = cLayer, + ["depth"] = cDepth, + } + end + end + end + + -- if not in layer, set (0, 0) as default values + local roleList = roles.GetList() + + for i = 1, #roleList do + local roleData = roleList[i] + + sql.Save( + roleselection.sqltable, + roleData.name, + dataTable[roleData.index] or {}, + roleselection.savingKeys + ) + end end --- @@ -177,27 +195,29 @@ end -- @return number amount -- @realm server function roleselection.GetCurrentRoleAmount(subrole) - local tmp = 0 + local tmp = 0 - if GetRoundState() == ROUND_ACTIVE then - local plys = player.GetAll() + if GetRoundState() == ROUND_ACTIVE then + local plys = player.GetAll() - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - if not IsValid(ply) or ply:GetForceSpec() or ply:GetSubRole() ~= subrole then continue end + if not IsValid(ply) or ply:GetForceSpec() or ply:GetSubRole() ~= subrole then + continue + end - tmp = tmp + 1 - end - elseif roleselection.finalRoles then - for ply, sr in pairs(roleselection.finalRoles) do - if sr == subrole then - tmp = tmp + 1 - end - end - end + tmp = tmp + 1 + end + elseif roleselection.finalRoles then + for ply, sr in pairs(roleselection.finalRoles) do + if sr == subrole then + tmp = tmp + 1 + end + end + end - return tmp + return tmp end --- @@ -211,19 +231,20 @@ end -- @realm server -- @internal local function GetAvailableRoleAmount(roleData, forced, maxPlys) - local bool = true + local bool = true - if not forced then - local randomCVar = GetConVar("ttt_" .. roleData.name .. "_random") + if not forced then + local randomCVar = GetConVar("ttt_" .. roleData.name .. "_random") - bool = math.random(100) <= (randomCVar and randomCVar:GetInt() or 100) - end + bool = math.random(100) <= (randomCVar and randomCVar:GetInt() or 100) + end - if bool then - return roleData:GetAvailableRoleCount(maxPlys) - roleselection.GetCurrentRoleAmount(roleData.index) - end + if bool then + return roleData:GetAvailableRoleCount(maxPlys) + - roleselection.GetCurrentRoleAmount(roleData.index) + end - return 0 + return 0 end --- @@ -233,20 +254,21 @@ end -- @return table list of filtered players -- @realm server function roleselection.GetSelectablePlayers(plys) - local tmp = {} + local tmp = {} - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - --- - -- Everyone on the spec team is in specmode - -- @realm server - if not ply:GetForceSpec() and not hook.Run("TTT2DisableRoleSelection", ply) then - tmp[#tmp + 1] = ply - end - end + --- + -- Everyone on the spec team is in specmode + -- @realm server + -- stylua: ignore + if not ply:GetForceSpec() and not hook.Run("TTT2DisableRoleSelection", ply) then + tmp[#tmp + 1] = ply + end + end - return tmp + return tmp end --- @@ -256,81 +278,101 @@ end -- @return table a list of all selectable @{ROLE}s -- @realm server function roleselection.GetAllSelectableRolesList(maxPlys) - if maxPlys < 2 then return end - - local forcedRolesTbl = {} - - for _, subrole in pairs(roleselection.forcedRoles) do - forcedRolesTbl[subrole] = true - end - - local rolesCountTbl = { - [ROLE_INNOCENT] = GetAvailableRoleAmount(INNOCENT, true, maxPlys), - [ROLE_TRAITOR] = GetAvailableRoleAmount(TRAITOR, true, maxPlys) - } - - local checked = {} - local rlsList = roles.GetList() - - -- add all roles if possible - for i = 1, #rlsList do - local roleData = rlsList[i] - - if checked[roleData.index] then continue end - - checked[roleData.index] = true - - -- INNOCENT and TRAITOR are all the time selectable - if roleData == INNOCENT or roleData == TRAITOR or not roleData:IsSelectable() then continue end - - -- if this is a subrole (a role that has a baserole is a subrole), check if the base role is available first - if roleData.baserole and roleData.baserole ~= ROLE_INNOCENT and roleData.baserole ~= ROLE_TRAITOR then - local baseRoleData = roles.GetByIndex(roleData.baserole) - - if not checked[baseRoleData.index] then - checked[baseRoleData.index] = true - - local rolesCount = GetAvailableRoleAmount(baseRoleData, forcedRolesTbl[baseRoleData.index], maxPlys) - - if rolesCount > 0 then - rolesCountTbl[baseRoleData.index] = rolesCount - end - end - - -- continue if baserole is not available - if not rolesCountTbl[baseRoleData.index] then continue end - end - - -- now check for subrole availability - local rolesCount = GetAvailableRoleAmount(roleData, forcedRolesTbl[roleData.index], maxPlys) - - if rolesCount > 0 then - rolesCountTbl[roleData.index] = rolesCount - end - end - - return rolesCountTbl + if maxPlys < 2 then + return + end + + local forcedRolesTbl = {} + + for _, subrole in pairs(roleselection.forcedRoles) do + forcedRolesTbl[subrole] = true + end + + local rolesCountTbl = { + [ROLE_INNOCENT] = GetAvailableRoleAmount(roles.INNOCENT, true, maxPlys), + [ROLE_TRAITOR] = GetAvailableRoleAmount(roles.TRAITOR, true, maxPlys), + } + + local checked = {} + local rlsList = roles.GetList() + + -- add all roles if possible + for i = 1, #rlsList do + local roleData = rlsList[i] + + if checked[roleData.index] then + continue + end + + checked[roleData.index] = true + + -- INNOCENT and TRAITOR are all the time selectable + if + roleData == roles.INNOCENT + or roleData == roles.TRAITOR + or not roleData:IsSelectable() + then + continue + end + + -- if this is a subrole (a role that has a baserole is a subrole), check if the base role is available first + if + roleData.baserole + and roleData.baserole ~= ROLE_INNOCENT + and roleData.baserole ~= ROLE_TRAITOR + then + local baseRoleData = roles.GetByIndex(roleData.baserole) + + if not checked[baseRoleData.index] then + checked[baseRoleData.index] = true + + local rolesCount = GetAvailableRoleAmount( + baseRoleData, + forcedRolesTbl[baseRoleData.index], + maxPlys + ) + + if rolesCount > 0 then + rolesCountTbl[baseRoleData.index] = rolesCount + end + end + + -- continue if baserole is not available + if not rolesCountTbl[baseRoleData.index] then + continue + end + end + + -- now check for subrole availability + local rolesCount = GetAvailableRoleAmount(roleData, forcedRolesTbl[roleData.index], maxPlys) + + if rolesCount > 0 then + rolesCountTbl[roleData.index] = rolesCount + end + end + + return rolesCountTbl end local function CleanupAvailableRolesLayerTbl(availableRolesTbl, currentIndexedLayer) - local cleanedLayerTbl = {} + local cleanedLayerTbl = {} - for cDepth = 1, #currentIndexedLayer do - local layerEntry = currentIndexedLayer[cDepth] + for cDepth = 1, #currentIndexedLayer do + local layerEntry = currentIndexedLayer[cDepth] - for i = 1, #availableRolesTbl do - if availableRolesTbl[i] == layerEntry then -- the role is still available / selectable - cleanedLayerTbl[#cleanedLayerTbl + 1] = layerEntry + for i = 1, #availableRolesTbl do + if availableRolesTbl[i] == layerEntry then -- the role is still available / selectable + cleanedLayerTbl[#cleanedLayerTbl + 1] = layerEntry - -- remove the role from the available roles table. This also means, that even if a layered "or"-table is given and not every role in there was already selected, every role in the layered "or"-table will become unavailable - table.remove(availableRolesTbl, i) + -- remove the role from the available roles table. This also means, that even if a layered "or"-table is given and not every role in there was already selected, every role in the layered "or"-table will become unavailable + table.remove(availableRolesTbl, i) - break - end - end - end + break + end + end + end - return cleanedLayerTbl + return cleanedLayerTbl end --- @@ -342,221 +384,239 @@ end -- @return table a list of filtered selectable @{ROLE}s -- @realm server function roleselection.GetSelectableRolesList(maxPlys, rolesAmountList) - if roleselection.selectableRoles then - return roleselection.selectableRoles - end - - -- yea it begins - local maxRoles = roleselection.cv.ttt_max_roles:GetInt() - if maxRoles == 0 then - maxRoles = math.floor(roleselection.cv.ttt_max_roles_pct:GetFloat() * maxPlys) - if maxRoles == 0 then - maxRoles = nil - end - end - - -- damn, not again - local maxBaseroles = roleselection.cv.ttt_max_baseroles:GetInt() - if maxBaseroles == 0 then - maxBaseroles = math.floor(roleselection.cv.ttt_max_baseroles_pct:GetFloat() * maxPlys) - if maxBaseroles == 0 then - maxBaseroles = nil - end - end - - -- we have to create a enumerable table to get random results easily - local availableBaseRolesTbl = {} - local availableSubRolesTbl = {} - local availableBaseRolesAmount = 0 - - for subrole in pairs(rolesAmountList) do - local roleData = roles.GetByIndex(subrole) - - -- exclude innocents and traitors, as they are already included, see below def. of selectableRoles. - if subrole == ROLE_INNOCENT or subrole == ROLE_TRAITOR then continue end - - if not roleData:IsBaseRole() then - local baserole = roleData:GetBaseRole() - - availableSubRolesTbl[baserole] = availableSubRolesTbl[baserole] or {} - availableSubRolesTbl[baserole][#availableSubRolesTbl[baserole] + 1] = subrole - else - availableBaseRolesAmount = availableBaseRolesAmount + 1 - - availableBaseRolesTbl[availableBaseRolesAmount] = subrole - end - end - - local selectableRoles = { - [ROLE_TRAITOR] = rolesAmountList[ROLE_TRAITOR], - [ROLE_INNOCENT] = rolesAmountList[ROLE_INNOCENT] - } - local curRoles = 2 -- amount of roles, start with 2 because INNOCENT and TRAITOR are all the time available - local curBaseroles = 2 -- amount of base roles, ... - - local layeredBaseRolesTbl = table.Copy(roleselection.baseroleLayers) -- layered roles list, the order defines the pick order. Just one role per layer is picked. Before a role is picked, the given layer is cleared (checked if the given roles are still selectable). Insert a table as a "or" list - - --- - -- @realm server - hook.Run("TTT2ModifyLayeredBaseRoles", layeredBaseRolesTbl, availableBaseRolesTbl) - - local baseroleLoopTbl = { -- just contains available / selectable baseroles - ROLE_TRAITOR, - ROLE_INNOCENT - } - - local currentlyAvailableRolesAmount = rolesAmountList[ROLE_TRAITOR] + rolesAmountList[ROLE_INNOCENT] - - -- first of all, we need to select the baseroles. Otherwise, we would select subroles that never gonna be choosen because if the missing baserole - for i = 1, availableBaseRolesAmount do - -- if the limit is reached or no available roles left (could happen if removing available roles that weren't already selected in layered "or"-tables), stop selection - if currentlyAvailableRolesAmount >= maxPlys - or maxRoles and maxRoles <= curRoles - or maxBaseroles and maxBaseroles <= curBaseroles - or #availableBaseRolesTbl < 1 - then - break - end - - -- the selected role - local subrole = nil - - -- if there are still defined layer - if #layeredBaseRolesTbl >= i then - for j = i, #layeredBaseRolesTbl do - local cleanedLayerTbl = CleanupAvailableRolesLayerTbl(availableBaseRolesTbl, layeredBaseRolesTbl[i]) -- clean the currently indexed layer (so that it just includes selectable roles), because we working with predefined layers that probably includes roles that aren't selectable with the current amount of players, etc. - - -- if there is no selectable role left in the current layer - if #cleanedLayerTbl < 1 then - table.remove(layeredBaseRolesTbl, i) -- remove the current layer + if roleselection.selectableRoles then + return roleselection.selectableRoles + end + + -- yea it begins + local maxRoles = roleselection.cv.ttt_max_roles:GetInt() + if maxRoles == 0 then + maxRoles = math.floor(roleselection.cv.ttt_max_roles_pct:GetFloat() * maxPlys) + if maxRoles == 0 then + maxRoles = nil + end + end + + -- damn, not again + local maxBaseroles = roleselection.cv.ttt_max_baseroles:GetInt() + if maxBaseroles == 0 then + maxBaseroles = math.floor(roleselection.cv.ttt_max_baseroles_pct:GetFloat() * maxPlys) + if maxBaseroles == 0 then + maxBaseroles = nil + end + end + + -- we have to create a enumerable table to get random results easily + local availableBaseRolesTbl = {} + local availableSubRolesTbl = {} + local availableBaseRolesAmount = 0 + + for subrole in pairs(rolesAmountList) do + local roleData = roles.GetByIndex(subrole) + + -- exclude innocents and traitors, as they are already included, see below def. of selectableRoles. + if subrole == ROLE_INNOCENT or subrole == ROLE_TRAITOR then + continue + end + + if not roleData:IsBaseRole() then + local baserole = roleData:GetBaseRole() + + availableSubRolesTbl[baserole] = availableSubRolesTbl[baserole] or {} + availableSubRolesTbl[baserole][#availableSubRolesTbl[baserole] + 1] = subrole + else + availableBaseRolesAmount = availableBaseRolesAmount + 1 + + availableBaseRolesTbl[availableBaseRolesAmount] = subrole + end + end + + local selectableRoles = { + [ROLE_TRAITOR] = rolesAmountList[ROLE_TRAITOR], + [ROLE_INNOCENT] = rolesAmountList[ROLE_INNOCENT], + } + local curRoles = 2 -- amount of roles, start with 2 because INNOCENT and TRAITOR are all the time available + local curBaseroles = 2 -- amount of base roles, ... + + local layeredBaseRolesTbl = table.Copy(roleselection.baseroleLayers) -- layered roles list, the order defines the pick order. Just one role per layer is picked. Before a role is picked, the given layer is cleared (checked if the given roles are still selectable). Insert a table as a "or" list + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2ModifyLayeredBaseRoles", layeredBaseRolesTbl, availableBaseRolesTbl) + + local baseroleLoopTbl = { -- just contains available / selectable baseroles + ROLE_TRAITOR, + ROLE_INNOCENT, + } + + local currentlyAvailableRolesAmount = rolesAmountList[ROLE_TRAITOR] + + rolesAmountList[ROLE_INNOCENT] + + -- first of all, we need to select the baseroles. Otherwise, we would select subroles that never gonna be choosen because if the missing baserole + for i = 1, availableBaseRolesAmount do + -- if the limit is reached or no available roles left (could happen if removing available roles that weren't already selected in layered "or"-tables), stop selection + if + currentlyAvailableRolesAmount >= maxPlys + or maxRoles and maxRoles <= curRoles + or maxBaseroles and maxBaseroles <= curBaseroles + or #availableBaseRolesTbl < 1 + then + break + end + + -- the selected role + local subrole = nil + + -- if there are still defined layer + if #layeredBaseRolesTbl >= i then + for j = i, #layeredBaseRolesTbl do + local cleanedLayerTbl = + CleanupAvailableRolesLayerTbl(availableBaseRolesTbl, layeredBaseRolesTbl[i]) -- clean the currently indexed layer (so that it just includes selectable roles), because we working with predefined layers that probably includes roles that aren't selectable with the current amount of players, etc. + + -- if there is no selectable role left in the current layer + if #cleanedLayerTbl < 1 then + table.remove(layeredBaseRolesTbl, i) -- remove the current layer + + -- redo the inner loop with the same index i + continue + end + + subrole = cleanedLayerTbl[math.random(#cleanedLayerTbl)] + + break + end + end + + -- if no subrole was selected (no layer left or no layer defined) + if not subrole then + local rnd = math.random(#availableBaseRolesTbl) + subrole = availableBaseRolesTbl[rnd] + + table.remove(availableBaseRolesTbl, rnd) -- selected subrole shouldn't get selected multiple times + end + + local amountForThisRole = + math.min(rolesAmountList[subrole], maxPlys - currentlyAvailableRolesAmount) - -- redo the inner loop with the same index i - continue - end + selectableRoles[subrole] = amountForThisRole + baseroleLoopTbl[#baseroleLoopTbl + 1] = subrole - subrole = cleanedLayerTbl[math.random(#cleanedLayerTbl)] + curRoles = curRoles + 1 + curBaseroles = curBaseroles + 1 + currentlyAvailableRolesAmount = currentlyAvailableRolesAmount + amountForThisRole + end - break - end - end + local layeredSubRolesTbl = table.Copy(roleselection.subroleLayers) -- layered roles list, the order defines the pick order. Just one role per layer is picked. Before a role is picked, the given layer is cleared (checked if the given roles are still selectable). Insert a table as a "or" list - -- if no subrole was selected (no layer left or no layer defined) - if not subrole then - local rnd = math.random(#availableBaseRolesTbl) - subrole = availableBaseRolesTbl[rnd] + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2ModifyLayeredSubRoles", layeredSubRolesTbl, availableSubRolesTbl) - table.remove(availableBaseRolesTbl, rnd) -- selected subrole shouldn't get selected multiple times - end + -- Counts max distributable subroles after cleanup + local maxSubroleCount = 0 - local amountForThisRole = math.min(rolesAmountList[subrole], maxPlys - currentlyAvailableRolesAmount) + -- Cleanup baserole and subrole layers for last distribution + for i = #baseroleLoopTbl, 1, -1 do + local baseRole = baseroleLoopTbl[i] + local subRoles = availableSubRolesTbl[baseRole] - selectableRoles[subrole] = amountForThisRole - baseroleLoopTbl[#baseroleLoopTbl + 1] = subrole + -- Cleanup baseroles, that have no subroles + if not subRoles or #subRoles < 1 then + table.remove(baseroleLoopTbl, i) - curRoles = curRoles + 1 - curBaseroles = curBaseroles + 1 - currentlyAvailableRolesAmount = currentlyAvailableRolesAmount + amountForThisRole - end + continue + end - local layeredSubRolesTbl = table.Copy(roleselection.subroleLayers) -- layered roles list, the order defines the pick order. Just one role per layer is picked. Before a role is picked, the given layer is cleared (checked if the given roles are still selectable). Insert a table as a "or" list + -- Separate available subroles from already layered subroles, so that availableSubRolesTbl only contains unlayered ones + -- Cleanup layers from unavailable subroles + local subroleLayers = layeredSubRolesTbl[baseRole] - --- - -- @realm server - hook.Run("TTT2ModifyLayeredSubRoles", layeredSubRolesTbl, availableSubRolesTbl) + if not subroleLayers then + maxSubroleCount = maxSubroleCount + #subRoles - -- Counts max distributable subroles after cleanup - local maxSubroleCount = 0 + continue + end - -- Cleanup baserole and subrole layers for last distribution - for i = #baseroleLoopTbl, 1, -1 do - local baseRole = baseroleLoopTbl[i] - local subRoles = availableSubRolesTbl[baseRole] + lastSubroleCount = 0 - -- Cleanup baseroles, that have no subroles - if not subRoles or #subRoles < 1 then - table.remove(baseroleLoopTbl, i) + for j = #subroleLayers, 1, -1 do + -- Clean the currently indexed layer (so that it just includes selectable roles), + -- because we working with predefined layers that probably includes roles + -- that aren't selectable with the current amount of players, etc. + subroleLayers[j] = CleanupAvailableRolesLayerTbl(subRoles, subroleLayers[j]) - continue - end + if #subroleLayers[j] > 0 then + -- One subrole per layer + maxSubroleCount = maxSubroleCount + 1 - -- Separate available subroles from already layered subroles, so that availableSubRolesTbl only contains unlayered ones - -- Cleanup layers from unavailable subroles - local subroleLayers = layeredSubRolesTbl[baseRole] + continue + end - if not subroleLayers then - maxSubroleCount = maxSubroleCount + #subRoles + table.remove(subroleLayers, j) + end - continue - end + -- Count unlayered subroles + maxSubroleCount = maxSubroleCount + #subRoles - lastSubroleCount = 0 + if #subroleLayers > 0 then + continue + end - for j = #subroleLayers, 1, -1 do - -- Clean the currently indexed layer (so that it just includes selectable roles), - -- because we working with predefined layers that probably includes roles - -- that aren't selectable with the current amount of players, etc. - subroleLayers[j] = CleanupAvailableRolesLayerTbl(subRoles, subroleLayers[j]) + layeredSubRolesTbl[baseRole] = nil + end - if #subroleLayers[j] > 0 then - -- One subrole per layer - maxSubroleCount = maxSubroleCount + 1 + -- Now we need to select the subroles randomly and respect layers + for i = 1, maxSubroleCount do + if maxRoles and maxRoles <= curRoles or #baseroleLoopTbl < 1 then + break + end -- if the limit is reached, stop selection - continue - end + local cBase = math.random(#baseroleLoopTbl) + local baserole = baseroleLoopTbl[cBase] - table.remove(subroleLayers, j) - end + local subroleLayers = layeredSubRolesTbl[baserole] + local subrolesUnlayered = availableSubRolesTbl[baserole] - -- Count unlayered subroles - maxSubroleCount = maxSubroleCount + #subRoles + -- the selected role + local subrole = nil - if #subroleLayers > 0 then continue end + -- If there are still defined layers check them first + if subroleLayers and #subroleLayers >= 1 then + subrole = subroleLayers[1][math.random(#subroleLayers[1])] - layeredSubRolesTbl[baseRole] = nil - end + table.remove(subroleLayers, 1) + else + -- If no layers left, choose random subrole + local rnd = math.random(#subrolesUnlayered) + subrole = subrolesUnlayered[rnd] - -- Now we need to select the subroles randomly and respect layers - for i = 1, maxSubroleCount do - if maxRoles and maxRoles <= curRoles or #baseroleLoopTbl < 1 then break end -- if the limit is reached, stop selection + table.remove(subrolesUnlayered, rnd) + end - local cBase = math.random(#baseroleLoopTbl) - local baserole = baseroleLoopTbl[cBase] + selectableRoles[subrole] = rolesAmountList[subrole] - local subroleLayers = layeredSubRolesTbl[baserole] - local subrolesUnlayered = availableSubRolesTbl[baserole] + curRoles = curRoles + 1 - -- the selected role - local subrole = nil + if + subroleLayers and #subroleLayers >= 1 + or subrolesUnlayered and #subrolesUnlayered >= 1 + then + continue + end - -- If there are still defined layers check them first - if subroleLayers and #subroleLayers >= 1 then - subrole = subroleLayers[1][math.random(#subroleLayers[1])] + table.remove(baseroleLoopTbl, cBase) + end - table.remove(subroleLayers, 1) - else - -- If no layers left, choose random subrole - local rnd = math.random(#subrolesUnlayered) - subrole = subrolesUnlayered[rnd] + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2ModifySelectableRoles", selectableRoles) - table.remove(subrolesUnlayered, rnd) - end + roleselection.selectableRoles = selectableRoles - selectableRoles[subrole] = rolesAmountList[subrole] - - curRoles = curRoles + 1 - - if subroleLayers and #subroleLayers >= 1 or subrolesUnlayered and #subrolesUnlayered >= 1 then continue end - - table.remove(baseroleLoopTbl, cBase) - end - - --- - -- @realm server - hook.Run("TTT2ModifySelectableRoles", selectableRoles) - - roleselection.selectableRoles = selectableRoles - - return selectableRoles + return selectableRoles end --- @@ -569,48 +629,40 @@ end -- @realm server -- @internal local function SetSubRoles(plys, availableRoles, selectableRoles, selectedForcedRoles) - local plysAmount = #plys - local availableRolesAmount = #availableRoles - local tmpSelectableRoles = table.Copy(selectableRoles) - - while plysAmount > 0 and availableRolesAmount > 0 do - local pick = math.random(plysAmount) - local ply = plys[pick] + local plysAmount = #plys + local availableRolesAmount = #availableRoles + local tmpSelectableRoles = table.Copy(selectableRoles) - local rolePick = math.random(availableRolesAmount) - local subrole = availableRoles[rolePick] - local roleData = roles.GetByIndex(subrole) - local roleCount = tmpSelectableRoles[subrole] + while plysAmount > 0 and availableRolesAmount > 0 do + local pick = math.random(plysAmount) + local ply = plys[pick] - if selectedForcedRoles[subrole] then - roleCount = roleCount - selectedForcedRoles[subrole] - end + local rolePick = math.random(availableRolesAmount) + local subrole = availableRoles[rolePick] + local roleData = roles.GetByIndex(subrole) + local roleCount = tmpSelectableRoles[subrole] - local minKarmaCVar = GetConVar("ttt_" .. roleData.name .. "_karma_min") - local minKarma = minKarmaCVar and minKarmaCVar:GetInt() or 0 + if selectedForcedRoles[subrole] then + roleCount = roleCount - selectedForcedRoles[subrole] + end - -- give this player the role if - if plysAmount <= roleCount -- or there aren't enough players anymore to have a greater role variety - or ply:GetBaseKarma() > minKarma -- or the player has enough karma - and not ply:GetAvoidRole(subrole) -- and the player doesn't avoid this role - or math.random(3) == 2 -- or if the randomness decides - then - table.remove(plys, pick) + if ply:CanSelectRole(roleData, plysAmount, availableRolesAmount) then + table.remove(plys, pick) - roleselection.finalRoles[ply] = subrole + roleselection.finalRoles[ply] = subrole - plysAmount = plysAmount - 1 - roleCount = roleCount - 1 + plysAmount = plysAmount - 1 + roleCount = roleCount - 1 - tmpSelectableRoles[subrole] = roleCount -- update the available roles + tmpSelectableRoles[subrole] = roleCount -- update the available roles - if roleCount < 1 then - table.remove(availableRoles, rolePick) + if roleCount < 1 then + table.remove(availableRoles, rolePick) - availableRolesAmount = availableRolesAmount - 1 - end - end - end + availableRolesAmount = availableRolesAmount - 1 + end + end + end end --- @@ -620,20 +672,20 @@ end -- @realm server -- @internal local function GetHardForcedBaseRoles() - local selectedForcedRoles = {} + local selectedForcedRoles = {} - for ply, subrole in pairs(roleselection.finalRoles) do - local curRole = roles.GetByIndex(subrole) - local isBaseRole = curRole:IsBaseRole() + for ply, subrole in pairs(roleselection.finalRoles) do + local curRole = roles.GetByIndex(subrole) + local isBaseRole = curRole:IsBaseRole() - -- assign amount of forced players per baserole if this is only a subrole - if not isBaseRole then - local baserole = curRole.baserole - selectedForcedRoles[baserole] = (selectedForcedRoles[baserole] or 0) + 1 - end - end + -- assign amount of forced players per baserole if this is only a subrole + if not isBaseRole then + local baserole = curRole.baserole + selectedForcedRoles[baserole] = (selectedForcedRoles[baserole] or 0) + 1 + end + end - return selectedForcedRoles + return selectedForcedRoles end --- @@ -645,79 +697,88 @@ end -- @realm server -- @internal local function SelectForcedRoles(plys, selectableRoles) - local transformed = {} + local transformed = {} - -- filter and restructure the forcedRoles table - for id, subrole in pairs(roleselection.forcedRoles) do - local ply = player.GetBySteamID64(id) + -- filter and restructure the forcedRoles table + for id, subrole in pairs(roleselection.forcedRoles) do + local ply = player.GetBySteamID64(id) - if not IsValid(ply) then - roleselection.forcedRoles[id] = nil - elseif table.HasValue(plys, ply) then - transformed[subrole] = transformed[subrole] or {} - transformed[subrole][#transformed[subrole] + 1] = ply - end - end + if not IsValid(ply) then + roleselection.forcedRoles[id] = nil + elseif table.HasValue(plys, ply) then + transformed[subrole] = transformed[subrole] or {} + transformed[subrole][#transformed[subrole] + 1] = ply + end + end - local selectedForcedRoles = GetHardForcedBaseRoles() -- this gets the hardforced amount of baseroles that are taken by subroles + local selectedForcedRoles = GetHardForcedBaseRoles() -- this gets the hardforced amount of baseroles that are taken by subroles - for subrole, forcedPlys in pairs(transformed) do - local roleCount = selectableRoles[subrole] + for subrole, forcedPlys in pairs(transformed) do + local roleCount = selectableRoles[subrole] - -- if it's not a selectable role, continue - if not roleCount then continue end + -- if it's not a selectable role, continue + if not roleCount then + continue + end - local curRole = roles.GetByIndex(subrole) - local isBaseRole = curRole:IsBaseRole() - local baserole = nil + local curRole = roles.GetByIndex(subrole) + local isBaseRole = curRole:IsBaseRole() + local baserole = nil - -- Consider maximum number of roles, that are available to the corresponding baserole - if not isBaseRole then - baserole = curRole.baserole + -- Consider maximum number of roles, that are available to the corresponding baserole + if not isBaseRole then + baserole = curRole.baserole - local baseroleCount = selectableRoles[baserole] - (selectedForcedRoles[baserole] or 0) + local baseroleCount = selectableRoles[baserole] - (selectedForcedRoles[baserole] or 0) - -- take the minimum of baseroles or subroles, if baseroles are available, else continue - if baseroleCount < 1 then continue end + -- take the minimum of baseroles or subroles, if baseroles are available, else continue + if baseroleCount < 1 then + continue + end - roleCount = math.min(baseroleCount, roleCount) - end + roleCount = math.min(baseroleCount, roleCount) + end - -- Consider number of roles, that were forced by other subroles - local curCount = selectedForcedRoles[subrole] or 0 - local amount = #forcedPlys + -- Consider number of roles, that were forced by other subroles + local curCount = selectedForcedRoles[subrole] or 0 + local amount = #forcedPlys - for i = 1, amount do - local pick = math.random(#forcedPlys) - local ply = forcedPlys[pick] + for i = 1, amount do + local pick = math.random(#forcedPlys) + local ply = forcedPlys[pick] - if roleCount <= curCount then break end -- if the limit is reached, stop selection for this role + if roleCount <= curCount then + break + end -- if the limit is reached, stop selection for this role - table.remove(forcedPlys, pick) -- remove selected player to avoid multiple selection + table.remove(forcedPlys, pick) -- remove selected player to avoid multiple selection - roleselection.forcedRoles[tostring(ply:SteamID64())] = nil + roleselection.forcedRoles[tostring(ply:SteamID64())] = nil - if roleselection.finalRoles[ply] then continue end -- we don't need to set a final role if this player already has a final role + if roleselection.finalRoles[ply] then + continue + end -- we don't need to set a final role if this player already has a final role - roleselection.finalRoles[ply] = subrole - curCount = curCount + 1 + roleselection.finalRoles[ply] = subrole + curCount = curCount + 1 - --- - -- @realm server - hook.Run("TTT2ReceivedForcedRole", ply, subrole) - end + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2ReceivedForcedRole", ply, subrole) + end - -- assigning the amount of forced players per role - selectedForcedRoles[subrole] = curCount + -- assigning the amount of forced players per role + selectedForcedRoles[subrole] = curCount - -- now assign amount of forced players per baserole if this is only a subrole - if not isBaseRole then - selectedForcedRoles[baserole] = (selectedForcedRoles[baserole] or 0) + curCount - end - end - roleselection.forcedRoles = {} + -- now assign amount of forced players per baserole if this is only a subrole + if not isBaseRole and baserole then + selectedForcedRoles[baserole] = (selectedForcedRoles[baserole] or 0) + curCount + end + end + roleselection.forcedRoles = {} - return selectedForcedRoles + return selectedForcedRoles end --- @@ -731,25 +792,27 @@ end -- @realm server -- @internal local function UpgradeRoles(plys, subrole, selectableRoles, selectedForcedRoles) - local availableRoles = {} - local roleAmount = 0 + local availableRoles = {} + local roleAmount = 0 - -- now upgrade this role if there are other subroles - for sub in pairs(selectableRoles) do - if roles.GetByIndex(sub).baserole ~= subrole then continue end + -- now upgrade this role if there are other subroles + for sub in pairs(selectableRoles) do + if roles.GetByIndex(sub).baserole ~= subrole then + continue + end - roleAmount = selectableRoles[sub] + roleAmount = selectableRoles[sub] - if selectedForcedRoles[sub] then - roleAmount = roleAmount - selectedForcedRoles[sub] - end + if selectedForcedRoles[sub] then + roleAmount = roleAmount - selectedForcedRoles[sub] + end - if roleAmount > 0 then - availableRoles[#availableRoles + 1] = sub - end - end + if roleAmount > 0 then + availableRoles[#availableRoles + 1] = sub + end + end - SetSubRoles(plys, availableRoles, selectableRoles, selectedForcedRoles) + SetSubRoles(plys, availableRoles, selectableRoles, selectedForcedRoles) end --- @@ -758,15 +821,15 @@ end -- @realm server -- @internal local function UpdateFinalRoles() - local tbl = {} + local tbl = {} - for ply, subrole in pairs(roleselection.finalRoles) do - if IsValid(ply) then - tbl[ply] = subrole - end - end + for ply, subrole in pairs(roleselection.finalRoles) do + if IsValid(ply) then + tbl[ply] = subrole + end + end - roleselection.finalRoles = tbl + roleselection.finalRoles = tbl end --- @@ -780,36 +843,28 @@ end -- @realm server -- @internal local function SelectBaseRolePlayers(plys, subrole, roleAmount) - local curRoles = 0 - local plysList = {} - - local minKarmaCVar = GetConVar("ttt_" .. roles.GetByIndex(subrole).name .. "_karma_min") - local min_karmas = minKarmaCVar and minKarmaCVar:GetInt() or 0 + local curRoles = 0 + local plysList = {} + local roleData = roles.GetByIndex(subrole) - while curRoles < roleAmount and #plys > 0 do - -- select random index in plys table - local pick = math.random(#plys) + while curRoles < roleAmount and #plys > 0 do + -- select random index in plys table + local pick = math.random(#plys) - -- the player we consider - local ply = plys[pick] + -- the player we consider + local ply = plys[pick] - -- give this player the role if - if subrole == ROLE_INNOCENT -- this role is an innocent role - or #plys <= roleAmount -- or there aren't enough players anymore to have a greater role variety - or ply:GetBaseKarma() > min_karmas -- or the player has enough karma - and not ply:GetAvoidRole(subrole) -- and the player doesn't avoid this role - or math.random(3) == 2 -- or if the randomness decides - then - table.remove(plys, pick) + if subrole == ROLE_INNOCENT or ply:CanSelectRole(roleData, #plys, roleAmount) then + table.remove(plys, pick) - curRoles = curRoles + 1 - plysList[curRoles] = ply + curRoles = curRoles + 1 + plysList[curRoles] = ply - roleselection.finalRoles[ply] = subrole -- give the player the final baserole (maybe he will receive his subrole later) - end - end + roleselection.finalRoles[ply] = subrole -- give the player the final baserole (maybe he will receive his subrole later) + end + end - return plysList + return plysList end --- @@ -820,111 +875,129 @@ end -- @param[optchain] number maxPlys amount of maximum @{Player}s. `nil` to calculate automatically -- @realm server function roleselection.SelectRoles(plys, maxPlys) - roleselection.selectableRoles = nil -- reset to enable recalculation + roleselection.selectableRoles = nil -- reset to enable recalculation + + GAMEMODE.LastRole = GAMEMODE.LastRole or {} + + plys = roleselection.GetSelectablePlayers(plys or player.GetAll()) - GAMEMODE.LastRole = GAMEMODE.LastRole or {} + -- Randomize role assignment by shuffling the list early. + table.Shuffle(plys) - plys = roleselection.GetSelectablePlayers(plys or player.GetAll()) + maxPlys = maxPlys or #plys - maxPlys = maxPlys or #plys + if maxPlys < 2 then + return + end -- we don't need to select anything if there is just one player - if maxPlys < 2 then return end -- we don't need to select anything if there is just one player + local allAvailableRoles = roleselection.GetAllSelectableRolesList(maxPlys) + local selectableRoles = roleselection.GetSelectableRolesList(maxPlys, allAvailableRoles) -- update roleselection.selectableRoles table - local allAvailableRoles = roleselection.GetAllSelectableRolesList(maxPlys) - local selectableRoles = roleselection.GetSelectableRolesList(maxPlys, allAvailableRoles) -- update roleselection.selectableRoles table + UpdateFinalRoles() -- Update the roleselection.finalRoles table by removing all invalid players - UpdateFinalRoles() -- Update the roleselection.finalRoles table by removing all invalid players + -- Select forced roles at first + local selectedForcedRoles = SelectForcedRoles(plys, selectableRoles) -- this updates roleselection.finalRoles table with forced players - -- Select forced roles at first - local selectedForcedRoles = SelectForcedRoles(plys, selectableRoles) -- this updates roleselection.finalRoles table with forced players + -- We need to remove already selected players + local plysFirstPass, plysSecondPass = {}, {} -- modified player table - -- We need to remove already selected players - local plysFirstPass, plysSecondPass = {}, {} -- modified player table + for i = 1, #plys do + local ply = plys[i] - for i = 1, #plys do - local ply = plys[i] + if roleselection.finalRoles[ply] then + continue + end - if roleselection.finalRoles[ply] then continue end + local pos = #plysFirstPass + 1 - local pos = #plysFirstPass + 1 + plysFirstPass[pos] = ply + plysSecondPass[pos] = ply + end - plysFirstPass[pos] = ply - plysSecondPass[pos] = ply - end + -- if there are still available players + if #plysFirstPass > 0 then + -- first select traitors, then innos, then the other roles + local list = { + [1] = ROLE_TRAITOR, + [2] = ROLE_INNOCENT, + } - -- if there are still available players - if #plysFirstPass > 0 then - -- first select traitors, then innos, then the other roles - local list = { - [1] = ROLE_TRAITOR, - [2] = ROLE_INNOCENT - } + -- insert selectable roles into the list. The order doesn't matter, players are chosen randomly and the roles are already filtered and limited + for subrole in pairs(selectableRoles) do + if subrole == ROLE_TRAITOR or subrole == ROLE_INNOCENT then + continue + end - -- insert selectable roles into the list. The order doesn't matter, players are chosen randomly and the roles are already filtered and limited - for subrole in pairs(selectableRoles) do - if subrole == ROLE_TRAITOR or subrole == ROLE_INNOCENT then continue end + list[#list + 1] = subrole + end - list[#list + 1] = subrole - end + -- Check all base roles, and assign players where possible. + -- After that, this will also try to upgrade the selected players, to any applicable subrole, that might replace the baserole. + -- But this will not upgrade Innocent subroles, as Innocents and players without any role are upgraded in the end. + for i = 1, #list do + local subrole = list[i] + local roleData = roles.GetByIndex(subrole) - -- Check all base roles, and assign players where possible. - -- After that, this will also try to upgrade the selected players, to any applicable subrole, that might replace the baserole. - -- But this will not upgrade Innocent subroles, as Innocents and players without any role are upgraded in the end. - for i = 1, #list do - local subrole = list[i] - local roleData = roles.GetByIndex(subrole) + if not roleData:IsBaseRole() or not selectableRoles[subrole] then + continue + end - if not roleData:IsBaseRole() or not selectableRoles[subrole] then continue end + local amount = selectableRoles[subrole] - local amount = selectableRoles[subrole] + if selectedForcedRoles[subrole] then + amount = amount - selectedForcedRoles[subrole] + end - if selectedForcedRoles[subrole] then - amount = amount - selectedForcedRoles[subrole] - end + local baseRolePlys = SelectBaseRolePlayers(plysFirstPass, subrole, amount) - local baseRolePlys = SelectBaseRolePlayers(plysFirstPass, subrole, amount) + -- upgrade innos and players without any role later + if subrole ~= ROLE_INNOCENT then + UpgradeRoles(baseRolePlys, subrole, selectableRoles, selectedForcedRoles) + end + end - -- upgrade innos and players without any role later - if subrole ~= ROLE_INNOCENT then - UpgradeRoles(baseRolePlys, subrole, selectableRoles, selectedForcedRoles) - end - end + -- last but not least, upgrade the innos and players without any role to special/normal innos + local innos = {} + for i = 1, #plysSecondPass do + local ply = plysSecondPass[i] - -- last but not least, upgrade the innos and players without any role to special/normal innos - local innos = {} - for i = 1, #plysSecondPass do - local ply = plysSecondPass[i] + roleselection.finalRoles[ply] = roleselection.finalRoles[ply] or ROLE_INNOCENT - roleselection.finalRoles[ply] = roleselection.finalRoles[ply] or ROLE_INNOCENT + if roleselection.finalRoles[ply] ~= ROLE_INNOCENT then + continue + end - if roleselection.finalRoles[ply] ~= ROLE_INNOCENT then continue end + innos[#innos + 1] = ply + end - innos[#innos + 1] = ply - end + UpgradeRoles(innos, ROLE_INNOCENT, selectableRoles, selectedForcedRoles) + end - UpgradeRoles(innos, ROLE_INNOCENT, selectableRoles, selectedForcedRoles) - end + GAMEMODE.LastRole = {} - GAMEMODE.LastRole = {} + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2ModifyFinalRoles", roleselection.finalRoles) - for i = 1, #plys do - local ply = plys[i] - local subrole = roleselection.finalRoles[ply] or ROLE_INNOCENT + for i = 1, #plys do + local ply = plys[i] + local subrole = roleselection.finalRoles[ply] or ROLE_INNOCENT - ply:SetRole(subrole, nil, true) + ply:SetRole(subrole, nil, true) - -- store a steamid -> role map - GAMEMODE.LastRole[ply:SteamID64()] = subrole - end + -- store a steamid -> role map + GAMEMODE.LastRole[ply:SteamID64()] = subrole + end - -- just set the credits after all roles were selected (to fix alone traitor bug) - for i = 1, #plys do - plys[i]:SetDefaultCredits() - end + -- just set the credits after all roles were selected (to fix alone traitor bug) + for i = 1, #plys do + plys[i]:SetDefaultCredits() + end - roleselection.finalRoles = {} + roleselection.finalRoles = {} - SendFullStateUpdate() + SendFullStateUpdate() end --- @@ -932,32 +1005,30 @@ end -- @param table availableBaseRolesTbl -- @hook -- @realm server -function GM:TTT2ModifyLayeredBaseRoles(layeredBaseRolesTbl, availableBaseRolesTbl) - -end +function GM:TTT2ModifyLayeredBaseRoles(layeredBaseRolesTbl, availableBaseRolesTbl) end --- -- @param table layeredSubRolesTbl -- @param table availableSubRolesTbl -- @hook -- @realm server -function GM:TTT2ModifyLayeredSubRoles(layeredSubRolesTbl, availableSubRolesTbl) - -end +function GM:TTT2ModifyLayeredSubRoles(layeredSubRolesTbl, availableSubRolesTbl) end --- -- @param table selectableRoles -- @hook -- @realm server -function GM:TTT2ModifySelectableRoles(selectableRoles) +function GM:TTT2ModifySelectableRoles(selectableRoles) end -end +--- +-- @param table finalRoles +-- @hook +-- @realm server +function GM:TTT2ModifyFinalRoles(finalRoles) end --- -- @param Player ply -- @param number subrole -- @hook -- @realm server -function GM:TTT2ReceivedForcedRole(ply, subrole) - -end +function GM:TTT2ReceivedForcedRole(ply, subrole) end diff --git a/gamemodes/terrortown/gamemode/server/sv_scoring.lua b/gamemodes/terrortown/gamemode/server/sv_scoring.lua index b02b50704..03ae3984c 100644 --- a/gamemodes/terrortown/gamemode/server/sv_scoring.lua +++ b/gamemodes/terrortown/gamemode/server/sv_scoring.lua @@ -13,7 +13,7 @@ SCORE.Events = SCORE.Events or {} -- @realm server -- @deprecated function SCORE:AddEvent(entry, t_override) - entry.t = t_override or CurTime() + entry.t = t_override or CurTime() - self.Events[#self.Events + 1] = entry + self.Events[#self.Events + 1] = entry end diff --git a/gamemodes/terrortown/gamemode/server/sv_shop.lua b/gamemodes/terrortown/gamemode/server/sv_shop.lua index cc3d4f189..3571f99bc 100644 --- a/gamemodes/terrortown/gamemode/server/sv_shop.lua +++ b/gamemodes/terrortown/gamemode/server/sv_shop.lua @@ -1,19 +1,11 @@ --- --- @section shop +-- A class to handle all shop requirements +-- @author ZenBre4ker +-- @class Shop -util.AddNetworkString("TTT2CreditTransferUpdate") - -local function RerollShop(ply) - if GetGlobalBool("ttt2_random_team_shops") then - ResetRandomShopsForRole(ply:GetSubRole(), GetGlobalInt("ttt2_random_shop_items"), true) - else - UpdateRandomShops({ply}, GetGlobalInt("ttt2_random_shop_items"), false) - end -end +shop = shop or {} -local function HasPendingOrder(ply) - return timer.Exists("give_equipment" .. ply:UniqueID()) -end +util.AddNetworkString("TTT2CreditTransferUpdate") --- -- Called whenever a @{Player} tries to order an @{ITEM} or @{Weapon}. @@ -24,7 +16,7 @@ end -- @hook -- @realm server function GM:TTTCanOrderEquipment(ply, id, isItem) - return true + return true end --- @@ -35,11 +27,10 @@ end -- @param number credits The purchase price of the @{ITEM} or @{Weapon} -- @return boolean Return false to block buying of an equipment item -- @return boolean Return true to make the purchase free --- @return string Return a string that is shown in a @{MSTACK} message -- @hook -- @realm server function GM:TTT2CanOrderEquipment(ply, cls, isItem, credits) - + return true, false end --- @@ -49,174 +40,137 @@ end -- @param boolean isItem True if item, false if weapon -- @hook -- @realm server -function GM:TTTOrderedEquipment(ply, id, isItem) - -end +function GM:TTTOrderedEquipment(ply, id, isItem) end --- -- Called whenever a @{Player} ordered an @{ITEM} or @{Weapon}. -- @param Player ply The player that bought something --- @param string cls The class of the @{ITEM} or @{Weapon} +-- @param string equipmentName The name of the @{ITEM} or @{Weapon} -- @param boolean isItem True if item, false if weapon -- @param number credits The purchase price of the @{ITEM} or @{Weapon} -- @param boolean ignoreCost True if the cost was ignored and received for free -- @hook -- @realm server -function GM:TTT2OrderedEquipment(ply, cls, isItem, credits, ignoreCost) - -end - -local function IsPartOfShop(ply, cls) - if not GetGlobalBool("ttt2_random_shops") or not RANDOMSHOP[ply] or #RANDOMSHOP[ply] == 0 then - return true - end - - for i = 1, #RANDOMSHOP[ply] do - if RANDOMSHOP[ply][i].id ~= cls then continue end - - return true - end - - print(ply, "tried to buy a prohibited item/weapon!") - - return false -end +function GM:TTT2OrderedEquipment(ply, equipmentName, isItem, credits, ignoreCost) end -- Equipment buying -local function OrderEquipment(ply, cls) - if not IsValid(ply) or not cls or not ply:IsActive() then return end - - -- if we have a pending order because we are in a confined space, don't - -- start a new one - if HasPendingOrder(ply) then - LANG.Msg(ply, "buy_pending", nil, MSG_MSTACK_ROLE) - - return - end - - local subrole = GetShopFallback(ply:GetSubRole()) - local rd = roles.GetByIndex(subrole) - - local shopFallback = GetGlobalString("ttt_" .. rd.abbr .. "_shop_fallback") - if shopFallback == SHOP_DISABLED then return end - - local isItem = items.IsItem(cls) - - -- The item/weapon might not even be part of the current shop - if not IsPartOfShop(ply, cls) then return end - - -- we use weapons.GetStored to save time on an unnecessary copy, we will not be modifying it - local equip_table = not isItem and weapons.GetStored(cls) or items.GetStored(cls) - - if not equip_table then - print(ply, "tried to buy equip that doesn't exists", cls) - - return - end - - -- Check for minimum players, global limit, team limit and player limit - local buyable, _, reason = EquipmentIsBuyable(equip_table, ply) - if not buyable then - if reason then - LANG.Msg(ply, reason, nil, MSG_MSTACK_ROLE) - end - - return - end - - -- still support old items - local idOrCls = (isItem and equip_table.oldId or nil) or cls - - local credits = equip_table.credits or 1 - - if credits > ply:GetCredits() then - print(ply, "tried to buy item/weapon, but didn't have enough credits") - - return - end - - --- - -- @note Keep compatibility with old addons - -- @realm server - if not hook.Run("TTTCanOrderEquipment", ply, idOrCls, isItem) then return end - - --- - -- @note Add our own hook with more consistent class parameter and some more information - -- @realm server - local allow, ignoreCost, message = hook.Run("TTT2CanOrderEquipment", ply, cls, isItem, credits) - if message then - LANG.Msg(ply, message, nil, MSG_MSTACK_ROLE) - end - - if allow == false then return end - - if not ignoreCost then - ply:SubtractCredits(credits) - end - - ply:AddBought(cls) - - -- no longer restricted to only WEAPON_EQUIP weapons, just anything that - -- is whitelisted and carryable - if isItem then - local item = ply:GiveEquipmentItem(cls) +local function HandleErrorMessage(ply, equipmentName, statusCode) + if statusCode == shop.statusCode.SUCCESS then + return + elseif statusCode == shop.statusCode.PENDINGORDER then + LANG.Msg(ply, "buy_pending", nil, MSG_MSTACK_ROLE) + elseif statusCode == shop.statusCode.NOTEXISTING then + Dev(1, ply .. " tried to buy equip that doesn't exist: " .. equipmentName) + elseif statusCode == shop.statusCode.NOTENOUGHCREDITS then + Dev(1, ply .. " tried to buy item/weapon, but didn't have enough credits.") + elseif statusCode == shop.statusCode.INVALIDID then + ErrorNoHaltWithStack("[TTT2][ERROR] No ID was requested by:", ply) + elseif statusCode == shop.statusCode.NOTBUYABLE then + LANG.Msg(ply, "This equipment cannot be bought.", nil, MSG_MSTACK_ROLE) + elseif statusCode == shop.statusCode.NOTENOUGHPLAYERS then + LANG.Msg(ply, "Minimum amount of active players needed.", nil, MSG_MSTACK_ROLE) + elseif statusCode == shop.statusCode.LIMITEDBOUGHT then + LANG.Msg(ply, "This equipment is limited and is already bought.", nil, MSG_MSTACK_ROLE) + elseif statusCode == shop.statusCode.NOTBUYABLEFORROLE then + LANG.Msg(ply, "Your role can't buy this equipment.", nil, MSG_MSTACK_ROLE) + end +end - if isfunction(item.Bought) then - item:Bought(ply) - end - else - ply:GiveEquipmentWeapon(cls, function(p, c, w) - if isfunction(w.WasBought) then - -- some weapons give extra ammo after being bought, etc - w:WasBought(p) - end - end) - end +--- +-- This skips restrictions and forces a reroll of the shop +-- @param Player ply The player to reroll the shop for +-- @realm server +function shop.ForceRerollShop(ply) + if GetGlobalBool("ttt2_random_team_shops") then + ResetRandomShopsForRole(ply:GetSubRole(), GetGlobalInt("ttt2_random_shop_items"), true) + else + UpdateRandomShops({ ply }, GetGlobalInt("ttt2_random_shop_items"), false) + end +end - LANG.Msg(ply, "buy_received", nil, MSG_MSTACK_ROLE) +local function NetOrderEquipment(len, ply) + local equipmentName = net.ReadString() - timer.Simple(0.5, function() - if not IsValid(ply) then return end + local isSuccess, statusCode = shop.BuyEquipment(ply, equipmentName) - net.Start("TTT_BoughtItem") - net.WriteString(cls) - net.Send(ply) - end) + if not isSuccess then + HandleErrorMessage(ply, equipmentName, statusCode) + end +end +net.Receive("TTT2OrderEquipment", NetOrderEquipment) - if GetGlobalBool("ttt2_random_shop_reroll_per_buy") then - RerollShop(ply) - end +--- +-- Broadcast a globally bought equipment +-- @param string equipmentName The bought equipment +-- @realm server +function shop.BroadcastEquipmentGlobalBought(equipmentName) + net.Start("TTT2ReceiveGBEq") + net.WriteString(equipmentName) + net.Broadcast() +end - --- - -- @note Keep compatibility with old addons - -- @realm server - hook.Run("TTTOrderedEquipment", ply, idOrCls, isItem) +--- +-- Sends all bought equipments to players as globally bought +-- @param Player ply The player to send it to +-- @realm shared +function shop.SendAllEquipmentGlobalBought(ply) + for equipmentName in pairs(shop.globalBuyTable) do + net.Start("TTT2ReceiveGBEq") + net.WriteString(equipmentName) + net.Send(ply) + end +end - --- - -- @note Add our own hook with more consistent class parameter - -- @realm server - hook.Run("TTT2OrderedEquipment", ply, cls, isItem, credits, ignoreCost or false) +--- +-- Sends all bought equipments to players of the same team +-- @param Player ply The player to get the team of +-- @realm shared +function shop.SendEquipmentTeamBought(ply) + local team = ply:GetTeam() + + if team and shop.teamBuyTable[team] then + local filter = GetTeamFilter(team) + + for equipmentName in pairs(shop.teamBuyTable[team]) do + net.Start("TTT2ReceiveTBEq") + net.WriteString(equipmentName) + net.Send(filter) + end + end end -local function NetOrderEquipment(len, ply) - local cls = net.ReadString() +local function TTT2SyncShopsWithServer(len, ply) + -- reset and set if it's a fallback + net.Start("shopFallbackReset") + net.Send(ply) + + SyncEquipment(ply) - OrderEquipment(ply, cls) + shop.SendAllEquipmentGlobalBought(ply) + shop.SendEquipmentTeamBought(ply) end -net.Receive("TTT2OrderEquipment", NetOrderEquipment) +net.Receive("TTT2SyncShopsWithServer", TTT2SyncShopsWithServer) local function ConCommandOrderEquipment(ply, cmd, args) - if #args ~= 1 then return end + if #args ~= 1 then + return + end + + local isSuccess, statusCode = shop.BuyEquipment(ply, args[1]) - OrderEquipment(ply, args[1]) + if not isSuccess then + HandleErrorMessage(ply, statusCode) + end end concommand.Add("ttt_order_equipment", ConCommandOrderEquipment) local function CheatCredits(ply) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply:AddCredits(10) + ply:AddCredits(10) end concommand.Add("ttt_cheat_credits", CheatCredits, nil, nil, FCVAR_CHEAT) @@ -246,66 +200,18 @@ function GM:TTT2TransferedCredits(sender, recipient, credits, isRecipientDead) end local function TransferCredits(ply, cmd, args) - if not IsValid(ply) then return end - - if #args ~= 2 then return end - - local sid64 = tostring(args[1]) - local credits = tonumber(args[2]) - - if sid64 == nil or not credits then return end - - local target = player.GetBySteamID64(sid64) + if #args ~= 2 then + return + end - if not IsValid(target) - or target == ply - then - LANG.Msg(ply, "xfer_no_recip", nil, MSG_MSTACK_ROLE) + local plyId64 = tostring(args[1]) + local credits = tonumber(args[2]) - return - end - - if ply:GetCredits() < credits then - LANG.Msg(ply, "xfer_no_credits", nil, MSG_MSTACK_ROLE) - - return - end - - credits = math.Clamp(credits, 0, ply:GetCredits()) - - if credits == 0 then return end - - --- - -- @realm server - local allow, _ = hook.Run("TTT2CanTransferCredits", ply, target, credits) - if allow == false then return end - - ply:SubtractCredits(credits) - if target:IsTerror() and target:Alive() then - target:AddCredits(credits) - hook.Run("TTT2TransferedCredits", ply, target, credits, false) - else - -- The would be recipient is dead, which the sender may not know. - -- Instead attempt to send the credits to the target's corpse, where they can be picked up. - local rag = target:FindCorpse() - if IsValid(rag) then - CORPSE.SetCredits(rag, CORPSE.GetCredits(rag, 0) + credits) - hook.Run("TTT2TransferedCredits", ply, target, credits, true) - end - end - - net.Start("TTT2CreditTransferUpdate") - net.Send(ply) - LANG.Msg(ply, "xfer_success", {player = target:Nick()}, MSG_MSTACK_ROLE) - LANG.Msg(target, "xfer_received", {player = ply:Nick(), num = credits}, MSG_MSTACK_ROLE) + shop.TransferCredits(ply, plyId64, credits) end concommand.Add("ttt_transfer_credits", TransferCredits) local function RerollShopForCredit(ply, cmd, args) - if not IsValid(ply) or not ply:IsActiveShopper() or ply:GetCredits() < GetGlobalInt("ttt2_random_shop_reroll_cost") or not GetGlobalBool("ttt2_random_shop_reroll") then return end - - ply:SubtractCredits(GetGlobalInt("ttt2_random_shop_reroll_cost")) - RerollShop(ply) - hook.Run("TTT2OrderedEquipment", ply, "reroll_shop", false, GetGlobalInt("ttt2_random_shop_reroll_cost"), false) + shop.TryRerollShop(ply) end concommand.Add("ttt2_reroll_shop", RerollShopForCredit) diff --git a/gamemodes/terrortown/gamemode/server/sv_shopeditor.lua b/gamemodes/terrortown/gamemode/server/sv_shopeditor.lua index 683a41146..b081711ed 100644 --- a/gamemodes/terrortown/gamemode/server/sv_shopeditor.lua +++ b/gamemodes/terrortown/gamemode/server/sv_shopeditor.lua @@ -8,13 +8,17 @@ local util = util util.AddNetworkString("TTT2UpdateCVar") net.Receive("TTT2UpdateCVar", function(_, ply) - if not IsValid(ply) or not ply:IsAdmin() then return end + if not IsValid(ply) or not ply:IsAdmin() then + return + end - local cvar = net.ReadString() + local cvar = net.ReadString() - if not ShopEditor.cvars[cvar] then return end + if not ShopEditor.cvars[cvar] then + return + end - RunConsoleCommand(cvar, net.ReadString()) + RunConsoleCommand(cvar, net.ReadString()) end) ShopEditor.ShopTablePre = "ttt2_shop_" @@ -26,13 +30,15 @@ ShopEditor.ShopTablePre = "ttt2_shop_" -- @realm server -- @internal function ShopEditor.CreateShopDB(name) - local result + local result - if not sql.TableExists(ShopEditor.ShopTablePre .. name) then - result = sql.Query("CREATE TABLE " .. ShopEditor.ShopTablePre .. name .. " (name TEXT PRIMARY KEY)") - end + if not sql.TableExists(ShopEditor.ShopTablePre .. name) then + result = sql.Query( + "CREATE TABLE " .. ShopEditor.ShopTablePre .. name .. " (name TEXT PRIMARY KEY)" + ) + end - return result ~= false + return result ~= false end --- @@ -40,15 +46,17 @@ end -- @realm server -- @internal function ShopEditor.CreateShopDBs() - local rlsList = roles.GetList() + local rlsList = roles.GetList() - for i = 1, #rlsList do - local rd = rlsList[i] + for i = 1, #rlsList do + local rd = rlsList[i] - if rd.index == ROLE_NONE then continue end + if rd.index == ROLE_NONE then + continue + end - ShopEditor.CreateShopDB(rd.name) - end + ShopEditor.CreateShopDB(rd.name) + end end --- @@ -57,17 +65,17 @@ end -- @return table -- @realm server function ShopEditor.GetShopEquipments(roleData) - if roleData.index == ROLE_NONE then - return {} - end + if roleData.index == ROLE_NONE then + return {} + end - local result = sql.Query("SELECT * FROM " .. ShopEditor.ShopTablePre .. roleData.name) + local result = sql.Query("SELECT * FROM " .. ShopEditor.ShopTablePre .. roleData.name) - if not result or not istable(result) then - result = {} - end + if not result or not istable(result) then + result = {} + end - return result + return result end --- @@ -77,20 +85,24 @@ end -- @param ITEM|Weapon|table equip -- @realm server function ShopEditor.AddToShopEditor(ply, roleData, equip) - sql.Query("INSERT INTO " .. ShopEditor.ShopTablePre .. roleData.name .. " VALUES ('" .. equip .. "')") + sql.Query( + "INSERT INTO " .. ShopEditor.ShopTablePre .. roleData.name .. " VALUES ('" .. equip .. "')" + ) - local eq = GetEquipmentByName(equip) + local eq = GetEquipmentByName(equip) - AddEquipmentToRole(roleData.index, eq) + AddEquipmentToRole(roleData.index, eq) - -- last but not least, notify each player - local plys = player.GetAll() - local nick = ply:Nick() - local rdName = roleData.name + -- last but not least, notify each player + local plys = player.GetAll() + local nick = ply:Nick() + local rdName = roleData.name - for i = 1, #plys do - plys[i]:ChatPrint("[TTT2][SHOP] " .. nick .. " added '" .. equip .. "' into the shop of the " .. rdName) - end + for i = 1, #plys do + plys[i]:ChatPrint( + "[TTT2][SHOP] " .. nick .. " added '" .. equip .. "' into the shop of the " .. rdName + ) + end end --- @@ -100,70 +112,69 @@ end -- @param ITEM|Weapon|table equip -- @realm server function ShopEditor.RemoveFromShopEditor(ply, roleData, equip) - sql.Query("DELETE FROM " .. ShopEditor.ShopTablePre .. roleData.name .. " WHERE name='" .. equip .. "'") - - local eq = GetEquipmentByName(equip) - - RemoveEquipmentFromRole(roleData.index, eq) - - -- last but not least, notify each player - local plys = player.GetAll() - local nick = ply:Nick() - local rdName = roleData.name - - for i = 1, #plys do - plys[i]:ChatPrint("[TTT2][SHOP] " .. nick .. " removed '" .. equip .. "' from the shop of the " .. rdName) - end + sql.Query( + "DELETE FROM " + .. ShopEditor.ShopTablePre + .. roleData.name + .. " WHERE name='" + .. equip + .. "'" + ) + + local eq = GetEquipmentByName(equip) + + RemoveEquipmentFromRole(roleData.index, eq) + + -- last but not least, notify each player + local plys = player.GetAll() + local nick = ply:Nick() + local rdName = roleData.name + + for i = 1, #plys do + plys[i]:ChatPrint( + "[TTT2][SHOP] " .. nick .. " removed '" .. equip .. "' from the shop of the " .. rdName + ) + end end util.AddNetworkString("shop") local function shop(len, ply) - if not IsValid(ply) or not ply:IsAdmin() then return end - - local add = net.ReadBool() - local subrole = net.ReadUInt(ROLE_BITS) - local eq = net.ReadString() - - local equip = GetEquipmentFileName(eq) - local rd = roles.GetByIndex(subrole) - - if add then - ShopEditor.AddToShopEditor(ply, rd, equip) - else - ShopEditor.RemoveFromShopEditor(ply, rd, equip) - end + if not IsValid(ply) or not ply:IsAdmin() then + return + end + + local add = net.ReadBool() + local subrole = net.ReadUInt(ROLE_BITS) + local eq = net.ReadString() + + local equip = GetEquipmentFileName(eq) + local rd = roles.GetByIndex(subrole) + + if add then + ShopEditor.AddToShopEditor(ply, rd, equip) + else + ShopEditor.RemoveFromShopEditor(ply, rd, equip) + end end net.Receive("shop", shop) -util.AddNetworkString("TTT2SESaveItem") - -local function TTT2SESaveItem(len, ply) - if not IsValid(ply) or not ply:IsAdmin() then return end - - local name, item = ShopEditor.ReadItemData() - - if not item then return end - - ShopEditor.WriteItemData("TTT2SESaveItem", name, item) - sql.Save("ttt2_items", name, item, ShopEditor.savingKeys) -end -net.Receive("TTT2SESaveItem", TTT2SESaveItem) - util.AddNetworkString("shopFallback") util.AddNetworkString("shopFallbackAnsw") util.AddNetworkString("shopFallbackReset") util.AddNetworkString("shopFallbackRefresh") local function shopFallback(len, ply) - if not IsValid(ply) or not ply:IsAdmin() then return end + if not IsValid(ply) or not ply:IsAdmin() then + return + end - local subrole = net.ReadUInt(ROLE_BITS) - local fallback = net.ReadString() + local subrole = net.ReadUInt(ROLE_BITS) + local fallback = net.ReadString() - local rd = roles.GetByIndex(subrole) + local rd = roles.GetByIndex(subrole) - RunConsoleCommand("ttt_" .. rd.abbr .. "_shop_fallback", fallback) + RunConsoleCommand("ttt_" .. rd.abbr .. "_shop_fallback", fallback) end net.Receive("shopFallback", shopFallback) @@ -172,85 +183,96 @@ net.Receive("shopFallback", shopFallback) -- the shops if needed -- @param number subrole subrole id of a @{ROLE} -- @param string fallback the fallback @{ROLE}'s name --- @param nil|Player|table if this is nil, it will be broadcasted to every available @{Player} +-- @param nil|Player|table ply_or_rf if this is nil, it will be broadcasted to every available @{Player} -- @realm server -- @internal function ShopEditor.OnChangeWSCVar(subrole, fallback, ply_or_rf) - local rd = roles.GetByIndex(subrole) + local rd = roles.GetByIndex(subrole) - SYNC_EQUIP[subrole] = {} + SYNC_EQUIP[subrole] = {} - -- reset equipment - local itms = items.GetList() + -- reset equipment + local itms = items.GetList() - for i = 1, #itms do - local v = itms[i] - local canBuy = v.CanBuy + for i = 1, #itms do + local v = itms[i] + local canBuy = v.CanBuy - if not canBuy then continue end + if not canBuy then + continue + end - canBuy[subrole] = nil - end + canBuy[subrole] = nil + end - local weps = weapons.GetList() + local weps = weapons.GetList() - for i = 1, #weps do - local v = weps[i] - local canBuy = v.CanBuy + for i = 1, #weps do + local v = weps[i] + local canBuy = v.CanBuy - if not canBuy then continue end + if not canBuy then + continue + end - canBuy[subrole] = nil - end + canBuy[subrole] = nil + end - -- reset and set if it's a fallback - net.Start("shopFallbackAnsw") - net.WriteUInt(subrole, ROLE_BITS) - net.WriteString(fallback) + -- reset and set if it's a fallback + net.Start("shopFallbackAnsw") + net.WriteUInt(subrole, ROLE_BITS) + net.WriteString(fallback) - if ply_or_rf then - net.Send(ply_or_rf) - else - net.Broadcast() - end + if ply_or_rf then + net.Send(ply_or_rf) + else + net.Broadcast() + end - if fallback == SHOP_DISABLED then return end + if fallback == SHOP_DISABLED then + return + end - if fallback ~= SHOP_UNSET and fallback == rd.name then - LoadSingleShopEquipment(rd) + if fallback ~= SHOP_UNSET and fallback == rd.name then + LoadSingleShopEquipment(rd) - ply_or_rf = ply_or_rf or player.GetAll() + ply_or_rf = ply_or_rf or player.GetAll() - if not istable(ply_or_rf) then - ply_or_rf = {ply_or_rf} - end + if not istable(ply_or_rf) then + ply_or_rf = { ply_or_rf } + end - for i = 1, #ply_or_rf do - SyncEquipment(ply_or_rf[i]) - end + for i = 1, #ply_or_rf do + SyncEquipment(ply_or_rf[i]) + end - net.Start("shopFallbackRefresh") + net.Start("shopFallbackRefresh") - if ply_or_rf then - net.Send(ply_or_rf) - else - net.Broadcast() - end - elseif fallback == SHOP_UNSET then - local flbTbl = rd.fallbackTable - if not flbTbl then return end + if ply_or_rf then + net.Send(ply_or_rf) + else + net.Broadcast() + end + elseif fallback == SHOP_UNSET then + local flbTbl = rd.fallbackTable + if not flbTbl then + return + end - -- set everything - for i = 1, #flbTbl do - local eq = flbTbl[i] + -- set everything + for i = 1, #flbTbl do + local eq = flbTbl[i] - local eqTbl = not items.IsItem(eq.id) and weapons.GetStored(eq.id) or items.GetStored(eq.id) - if not eqTbl then continue end + local eqTbl = not items.IsItem(eq.id) and weapons.GetStored(eq.id) + or items.GetStored(eq.id) + if not eqTbl then + continue + end - eqTbl.CanBuy = eqTbl.CanBuy or {} - eqTbl.CanBuy[subrole] = subrole - end - end + eqTbl.CanBuy = eqTbl.CanBuy or {} + eqTbl.CanBuy[subrole] = subrole + end + end end --- @@ -258,20 +280,22 @@ end -- @realm server -- @internal function ShopEditor.SetupShopEditorCVars() - local rlsList = roles.GetList() + local rlsList = roles.GetList() - for i = 1, #rlsList do - local v = rlsList[i] + for i = 1, #rlsList do + local v = rlsList[i] - local _func = function(convar_name, value_old, value_new) - if value_old == value_new then return end + local _func = function(convar_name, value_old, value_new) + if value_old == value_new then + return + end - print(convar_name .. ": Changing fallback from " .. value_old .. " to " .. value_new) + Dev(1, convar_name .. ": Changing fallback from " .. value_old .. " to " .. value_new) - SetGlobalString("ttt_" .. v.abbr .. "_shop_fallback", value_new) - ShopEditor.OnChangeWSCVar(v.index, value_new) - end + SetGlobalString("ttt_" .. v.abbr .. "_shop_fallback", value_new) + ShopEditor.OnChangeWSCVar(v.index, value_new) + end - cvars.AddChangeCallback("ttt_" .. v.abbr .. "_shop_fallback", _func) - end + cvars.AddChangeCallback("ttt_" .. v.abbr .. "_shop_fallback", _func) + end end diff --git a/gamemodes/terrortown/gamemode/server/sv_status.lua b/gamemodes/terrortown/gamemode/server/sv_status.lua index d5d645c6d..c0531155a 100644 --- a/gamemodes/terrortown/gamemode/server/sv_status.lua +++ b/gamemodes/terrortown/gamemode/server/sv_status.lua @@ -17,10 +17,10 @@ util.AddNetworkString("ttt2_status_effect_remove_all") -- @param[default=1] number active_icon The numeric id of a specific status icon -- @realm server function STATUS:AddStatus(ply, id, active_icon) - net.Start("ttt2_status_effect_add") - net.WriteString(id) - net.WriteUInt(active_icon or 1, 8) - net.Send(ply) + net.Start("ttt2_status_effect_add") + net.WriteString(id) + net.WriteUInt(active_icon or 1, 8) + net.Send(ply) end --- @@ -30,10 +30,10 @@ end -- @param[default=1] number active_icon The numeric id of a specific status icon -- @realm server function STATUS:SetActiveIcon(ply, id, active_icon) - net.Start("ttt2_status_effect_set_id") - net.WriteString(id) - net.WriteUInt(active_icon or 1, 8) - net.Send(ply) + net.Start("ttt2_status_effect_set_id") + net.WriteString(id) + net.WriteUInt(active_icon or 1, 8) + net.Send(ply) end --- @@ -46,12 +46,12 @@ end -- @param[default=1] number active_icon The numeric id of a specific status icon -- @realm server function STATUS:AddTimedStatus(ply, id, duration, showDuration, active_icon) - net.Start("ttt2_status_effect_add_timed") - net.WriteString(id) - net.WriteUInt(duration, 32) - net.WriteBool(showDuration or false) - net.WriteUInt(active_icon or 1, 8) - net.Send(ply) + net.Start("ttt2_status_effect_add_timed") + net.WriteString(id) + net.WriteUInt(duration, 32) + net.WriteBool(showDuration or false) + net.WriteUInt(active_icon or 1, 8) + net.Send(ply) end --- @@ -60,9 +60,9 @@ end -- @param string id The id of the registered @{STATUS} -- @realm server function STATUS:RemoveStatus(ply, id) - net.Start("ttt2_status_effect_remove") - net.WriteString(id) - net.Send(ply) + net.Start("ttt2_status_effect_remove") + net.WriteString(id) + net.Send(ply) end --- @@ -70,10 +70,10 @@ end -- @param Player ply The @{Player} that should receive this status update -- @realm server function STATUS:RemoveAll(ply) - net.Start("ttt2_status_effect_remove_all") - net.Send(ply) + net.Start("ttt2_status_effect_remove_all") + net.Send(ply) end hook.Add("PlayerSpawn", "status_removed", function(ply) - STATUS:RemoveAll(ply) + STATUS:RemoveAll(ply) end) diff --git a/gamemodes/terrortown/gamemode/server/sv_voice.lua b/gamemodes/terrortown/gamemode/server/sv_voice.lua index 7c13fe670..f66994a81 100644 --- a/gamemodes/terrortown/gamemode/server/sv_voice.lua +++ b/gamemodes/terrortown/gamemode/server/sv_voice.lua @@ -14,7 +14,7 @@ local net = net -- @param number state -- @realm server function MuteForRestart(state) - mute_all = state + mute_all = state end -- Communication control @@ -22,59 +22,78 @@ local sv_voiceenable = GetConVar("sv_voiceenable") --- -- @realm server +-- stylua: ignore local cv_ttt_limit_spectator_voice = CreateConVar("ttt_limit_spectator_voice", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm server +-- stylua: ignore local loc_voice = CreateConVar("ttt_locational_voice", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) hook.Add("TTT2SyncGlobals", "AddVoiceGlobals", function() - SetGlobalBool(sv_voiceenable:GetName(), sv_voiceenable:GetBool()) - SetGlobalBool(loc_voice:GetName(), loc_voice:GetBool()) + SetGlobalBool(sv_voiceenable:GetName(), sv_voiceenable:GetBool()) + SetGlobalBool(loc_voice:GetName(), loc_voice:GetBool()) +end) + +cvars.AddChangeCallback(sv_voiceenable:GetName(), function(cv, old, new) + SetGlobalBool(sv_voiceenable:GetName(), tobool(tonumber(new))) end) cvars.AddChangeCallback(loc_voice:GetName(), function(cv, old, new) - SetGlobalBool(loc_voice:GetName(), tobool(tonumber(new))) + SetGlobalBool(loc_voice:GetName(), tobool(tonumber(new))) end) local function PlayerCanHearSpectator(listener, speaker, roundState) - local isSpec = listener:IsSpec() + local isSpec = listener:IsSpec() - -- limited if specific convar is on, or we're in detective mode - local limit = DetectiveMode() or cv_ttt_limit_spectator_voice:GetBool() + -- limited if specific convar is on, or we're in detective mode + local limit = DetectiveMode() or cv_ttt_limit_spectator_voice:GetBool() - return isSpec or not limit or roundState ~= ROUND_ACTIVE, not isSpec and loc_voice:GetBool() and roundState ~= ROUND_POST + return isSpec or not limit or roundState ~= ROUND_ACTIVE, + not isSpec and loc_voice:GetBool() and roundState ~= ROUND_POST end local function PlayerCanHearTeam(listener, speaker, speakerTeam) - local speakerSubRoleData = speaker:GetSubRoleData() - - -- Speaker checks - if speakerTeam == TEAM_NONE or speakerSubRoleData.unknownTeam or speakerSubRoleData.disabledTeamVoice then - return false, false - end - - -- Listener checks - if listener:GetSubRoleData().disabledTeamVoiceRecv or not listener:IsActive() or not listener:IsInTeam(speaker) then - return false, false - end - - if TEAMS[speakerTeam].alone then - return false, false - end - - return true, loc_voice:GetBool() + local speakerSubRoleData = speaker:GetSubRoleData() + + -- Speaker checks + if + speakerTeam == TEAM_NONE + or speakerSubRoleData.unknownTeam + or speakerSubRoleData.disabledTeamVoice + then + return false, false + end + + -- Listener checks + if + listener:GetSubRoleData().disabledTeamVoiceRecv + or not listener:IsActive() + or not listener:IsInTeam(speaker) + then + return false, false + end + + if TEAMS[speakerTeam].alone then + return false, false + end + + return true, loc_voice:GetBool() end local function PlayerIsMuted(listener, speaker) - -- Enforced silence and specific mute - if mute_all or listener:IsSpec() and listener.mute_team == speaker:Team() or listener.mute_team == MUTE_ALL then - return true - end + -- Enforced silence and specific mute + if + mute_all + or listener:IsSpec() and listener.mute_team == speaker:Team() + or listener.mute_team == MUTE_ALL + then + return true + end end local function PlayerCanHearGlobal(roundState) - return true, loc_voice:GetBool() and roundState ~= ROUND_POST + return true, loc_voice:GetBool() and roundState ~= ROUND_POST end --- @@ -90,39 +109,40 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerCanHearPlayersVoice -- @local function GM:PlayerCanHearPlayersVoice(listener, speaker) - if speaker.blockVoice then - return false, false - end - - if not IsValid(speaker) or not IsValid(listener) or listener == speaker then - return false, false - end - - local speakerTeam = speaker:GetTeam() - local roundState = GetRoundState() - local isGlobalVoice = speaker[speakerTeam .. "_gvoice"] - - if PlayerIsMuted(listener, speaker) then - return false, false - end - - --- - -- custom post-settings - -- @realm server - local can_hear, is_locational = hook.Run("TTT2CanHearVoiceChat", listener, speaker, not isGlobalVoice) - - if can_hear ~= nil then - return can_hear, is_locational or false - end - - if speaker:IsSpec() and isGlobalVoice then - -- Check that the speaker was not previously sending voice on the team chat - return PlayerCanHearSpectator(listener, speaker, roundState) - elseif isGlobalVoice then - return PlayerCanHearGlobal(roundState) - else - return PlayerCanHearTeam(listener, speaker, speakerTeam) - end + if speaker.blockVoice then + return false, false + end + + if not IsValid(speaker) or not IsValid(listener) or listener == speaker then + return false, false + end + + local speakerTeam = speaker:GetTeam() + local roundState = GetRoundState() + local isGlobalVoice = speaker[speakerTeam .. "_gvoice"] + + if PlayerIsMuted(listener, speaker) then + return false, false + end + + --- + -- custom post-settings + -- @realm server + -- stylua: ignore + local can_hear, is_locational = hook.Run("TTT2CanHearVoiceChat", listener, speaker, not isGlobalVoice) + + if can_hear ~= nil then + return can_hear, is_locational or false + end + + if speaker:IsSpec() and isGlobalVoice then + -- Check that the speaker was not previously sending voice on the team chat + return PlayerCanHearSpectator(listener, speaker, roundState) + elseif isGlobalVoice then + return PlayerCanHearGlobal(roundState) + else + return PlayerCanHearTeam(listener, speaker, speakerTeam) + end end --- @@ -134,87 +154,94 @@ end -- @return boolean 3D sound. If set to true, will fade out the sound the further away listener is from the talker, the voice will also be in stereo, and not mono -- @hook -- @realm server -function GM:TTT2CanHearVoiceChat(listener, speaker, isTeam) - -end +function GM:TTT2CanHearVoiceChat(listener, speaker, isTeam) end local function SendRoleVoiceState(speaker) - -- send umsg to living traitors that this is traitor-only talk - local rf = GetTeamMemberFilter(speaker, true) - - -- make it as small as possible, to get there as fast as possible - -- we can fit it into a mere byte by being cheeky. - net.Start("TTT_RoleVoiceState") - net.WriteUInt(speaker:EntIndex() - 1, 7) -- player ids can only be 1-128 - net.WriteBit(speaker[speaker:GetTeam() .. "_gvoice"]) - - if rf then - net.Send(rf) - else - net.Broadcast() - end + -- send umsg to living traitors that this is traitor-only talk + local rf = GetTeamMemberFilter(speaker, true) + + -- make it as small as possible, to get there as fast as possible + -- we can fit it into a mere byte by being cheeky. + net.Start("TTT_RoleVoiceState") + net.WriteUInt(speaker:EntIndex() - 1, 7) -- player ids can only be 1-128 + net.WriteBit(speaker[speaker:GetTeam() .. "_gvoice"]) + + if rf then + net.Send(rf) + else + net.Broadcast() + end end local function RoleGlobalVoice(ply, isGlobal) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply[ply:GetTeam() .. "_gvoice"] = isGlobal + ply[ply:GetTeam() .. "_gvoice"] = isGlobal - --- - -- @realm server - ply.blockVoice = hook.Run("TTT2CanUseVoiceChat", ply, not isGlobal) == false + --- + -- @realm server + -- stylua: ignore + ply.blockVoice = hook.Run("TTT2CanUseVoiceChat", ply, not isGlobal) == false - SendRoleVoiceState(ply) + SendRoleVoiceState(ply) end local function NetRoleGlobalVoice(len, ply) - local isGlobal = net.ReadBool() + local isGlobal = net.ReadBool() - RoleGlobalVoice(ply, isGlobal) + RoleGlobalVoice(ply, isGlobal) end net.Receive("TTT2RoleGlobalVoice", NetRoleGlobalVoice) local function ConCommandRoleGlobalVoice(ply, cmd, args) - if #args ~= 1 then return end + if #args ~= 1 then + return + end - local isGlobal = tobool(args[1]) + local isGlobal = tobool(args[1]) - RoleGlobalVoice(ply, isGlobal) + RoleGlobalVoice(ply, isGlobal) end concommand.Add("tvog", ConCommandRoleGlobalVoice) local function MuteTeam(ply, state) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - if not ply:IsSpec() then - ply.mute_team = -1 + if not ply:IsSpec() then + ply.mute_team = -1 - return - end + return + end - ply.mute_team = state + ply.mute_team = state - if state == MUTE_ALL then - LANG.Msg(ply, "mute_all", nil, MSG_CHAT_PLAIN) - elseif state == MUTE_NONE or state == TEAM_UNASSIGNED or not team.Valid(state) then - LANG.Msg(ply, "mute_off", nil, MSG_CHAT_PLAIN) - else - LANG.Msg(ply, "mute_team", {team = team.GetName(state)}, MSG_CHAT_PLAIN) - end + if state == MUTE_ALL then + LANG.Msg(ply, "mute_all", nil, MSG_CHAT_PLAIN) + elseif state == MUTE_NONE or state == TEAM_UNASSIGNED or not team.Valid(state) then + LANG.Msg(ply, "mute_off", nil, MSG_CHAT_PLAIN) + else + LANG.Msg(ply, "mute_team", { team = team.GetName(state) }, MSG_CHAT_PLAIN) + end end local function NetMuteTeam(len, ply) - local state = net.ReadBool() + local state = net.ReadBool() - MuteTeam(ply, state) + MuteTeam(ply, state) end net.Receive("TTT2MuteTeam", NetMuteTeam) local function ConCommandMuteTeam(ply, cmd, args) - if #args ~= 1 and tonumber(args[1]) then return end + if #args ~= 1 and tonumber(args[1]) then + return + end - local state = tonumber(args[1]) + local state = tonumber(args[1]) - MuteTeam(ply, state) + MuteTeam(ply, state) end concommand.Add("ttt_mute_team", ConCommandMuteTeam) diff --git a/gamemodes/terrortown/gamemode/server/sv_weapon_pickup.lua b/gamemodes/terrortown/gamemode/server/sv_weapon_pickup.lua index 6d2165993..9cbab1000 100644 --- a/gamemodes/terrortown/gamemode/server/sv_weapon_pickup.lua +++ b/gamemodes/terrortown/gamemode/server/sv_weapon_pickup.lua @@ -3,22 +3,28 @@ util.AddNetworkString("ttt2_switch_weapon_update_cache") local plymeta = FindMetaTable("Player") if not plymeta then - Error("FAILED TO FIND PLAYER TABLE") + ErrorNoHaltWithStack("FAILED TO FIND PLAYER TABLE") - return + return end net.Receive("ttt2_switch_weapon", function(_, ply) - -- player and wepaon must be valid - if not IsValid(ply) or not ply:IsTerror() or not ply:Alive() then return end + -- player and wepaon must be valid + if not IsValid(ply) or not ply:IsTerror() or not ply:Alive() then + return + end - -- handle weapon switch - local tracedWeapon = ply:GetEyeTrace().Entity + -- handle weapon switch + local tracedWeapon = ply:GetEyeTrace().Entity - if not IsValid(tracedWeapon) or not tracedWeapon:IsWeapon() then return end + if not IsValid(tracedWeapon) or not tracedWeapon:IsWeapon() then + return + end - -- do not pickup weapon if too far away - if ply:GetPos():Distance(tracedWeapon:GetPos()) > 100 then return end + -- do not pickup weapon if too far away + if ply:GetPos():Distance(tracedWeapon:GetPos()) > 100 then + return + end - ply:SafePickupWeapon(tracedWeapon, false, true, true, nil) -- force pickup; drop blocking weapon, autoselect is set automatically + ply:SafePickupWeapon(tracedWeapon, false, true, true, nil) -- force pickup; drop blocking weapon, autoselect is set automatically end) diff --git a/gamemodes/terrortown/gamemode/server/sv_weaponry.lua b/gamemodes/terrortown/gamemode/server/sv_weaponry.lua index 03b10c066..c4b630b7c 100644 --- a/gamemodes/terrortown/gamemode/server/sv_weaponry.lua +++ b/gamemodes/terrortown/gamemode/server/sv_weaponry.lua @@ -17,10 +17,12 @@ local IsEquipment = WEPS.IsEquipment --- -- @realm server +-- stylua: ignore local cv_auto_pickup = CreateConVar("ttt_weapon_autopickup", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY}) --- -- @realm server +-- stylua: ignore local cv_ttt_detective_hats = CreateConVar("ttt_detective_hats", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- @@ -42,195 +44,208 @@ local cv_ttt_detective_hats = CreateConVar("ttt_detective_hats", "0", {FCVAR_NOT -- @ref https://wiki.facepunch.com/gmod/GM:PlayerCanPickupWeapon -- @local function GM:PlayerCanPickupWeapon(ply, wep, dropBlockingWeapon, isPickupProbe) - if not IsValid(wep) or not IsValid(ply) then return end - - -- spectators are not allowed to pickup weapons - if ply:IsSpec() then - return false, 1 - end - - -- prevent picking up weapons of the same class a player already has (for ammo if auto-pickup is enabled) - -- exception: this hook is called to check if a player can pick up weapon while dropping - -- the current weapon - if not dropBlockingWeapon and ply:HasWeapon(WEPS.GetClass(wep)) then - return false, 2 - end - - -- block pickup when there is no slot free - -- exception: this hook is called to check if a player can pick up weapon while dropping - -- the current weapon - if not dropBlockingWeapon and not InventorySlotFree(ply, wep.Kind) and not ply.forcedPickup then - return false, 3 - end - - -- if the auto pickup convar is set to false, no weapons should be picked up automatically - if not cv_auto_pickup:GetBool() and not ply.forcedPickup then - return false, 4 - end - - -- if it is a dropped equipment item, it shouldn't be picked up automatically - if IsEquipment(wep) and wep.IsDropped and not ply.forcedPickup then - return false, 5 - end - - -- if the player has cached their inventory, weapons should not be picked up with the - -- exception of weapons given by the ply:Give function - if ply:HasCachedWeapons() and not ply.forcedGive then - return false, 6 - end - - -- make sure that the weapon is moved to the player if it should be automatically picked - -- up; this however should not happen for manual pickup and/or hook probing - if cv_auto_pickup:GetBool() and not ply.forcedPickup and not isPickupProbe then - local tr = util.TraceEntity({ - start = wep:GetPos(), - endpos = ply:GetShootPos(), - mask = MASK_SOLID - }, wep) - - if tr.Fraction == 1.0 or tr.Entity == ply then - wep:SetPos(ply:GetShootPos()) - end - end - - return true + if not IsValid(wep) or not IsValid(ply) then + return + end + + -- spectators are not allowed to pickup weapons + if ply:IsSpec() and WEPS.GetClass(wep) ~= "weapon_ttt_spawneditor" then + return false, 1 + end + + -- prevent picking up weapons of the same class a player already has (for ammo if auto-pickup is enabled) + -- exception: this hook is called to check if a player can pick up weapon while dropping + -- the current weapon + if not dropBlockingWeapon and ply:HasWeapon(WEPS.GetClass(wep)) then + return false, 2 + end + + -- block pickup when there is no slot free + -- exception: this hook is called to check if a player can pick up weapon while dropping + -- the current weapon + if not dropBlockingWeapon and not InventorySlotFree(ply, wep.Kind) and not ply.forcedPickup then + return false, 3 + end + + -- if the auto pickup convar is set to false, no weapons should be picked up automatically + if not cv_auto_pickup:GetBool() and not ply.forcedPickup then + return false, 4 + end + + -- if it is a dropped equipment item, it shouldn't be picked up automatically + if IsEquipment(wep) and wep.IsDropped and not ply.forcedPickup then + return false, 5 + end + + -- if the player has cached their inventory, weapons should not be picked up with the + -- exception of weapons given by the ply:Give function + if ply:HasCachedWeapons() and not ply.forcedGive then + return false, 6 + end + + -- make sure that the weapon is moved to the player if it should be automatically picked + -- up; this however should not happen for manual pickup and/or hook probing + if cv_auto_pickup:GetBool() and not ply.forcedPickup and not isPickupProbe then + local tr = util.TraceEntity({ + start = wep:GetPos(), + endpos = ply:GetShootPos(), + mask = MASK_SOLID, + }, wep) + + if tr.Fraction == 1.0 or tr.Entity == ply then + wep:SetPos(ply:GetShootPos()) + end + end + + return true end -- Cache subrole -> default-weapons table local loadout_weapons = {} local function GetLoadoutWeapons(subrole) - local tmpLoadoutWeps = loadout_weapons[subrole] + local tmpLoadoutWeps = loadout_weapons[subrole] - if tmpLoadoutWeps then - return tmpLoadoutWeps - end + if tmpLoadoutWeps then + return tmpLoadoutWeps + end - tmpLoadoutWeps = {} + tmpLoadoutWeps = {} - local weps = weapons.GetList() + local weps = weapons.GetList() - for i = 1, #weps do - local w = weps[i] + for i = 1, #weps do + local w = weps[i] - if not istable(w.InLoadoutFor) or w.Doublicated then continue end + if not istable(w.InLoadoutFor) or w.Duplicated then + continue + end - local cls = WEPS.GetClass(w) + local cls = WEPS.GetClass(w) - if table.HasValue(w.InLoadoutFor, subrole) then - if not table.HasValue(tmpLoadoutWeps, cls) then - tmpLoadoutWeps[#tmpLoadoutWeps + 1] = cls - end - elseif table.HasValue(w.InLoadoutFor, ROLE_INNOCENT) then -- setup for new roles - w.InLoadoutFor[#w.InLoadoutFor + 1] = subrole + if table.HasValue(w.InLoadoutFor, subrole) then + if not table.HasValue(tmpLoadoutWeps, cls) then + tmpLoadoutWeps[#tmpLoadoutWeps + 1] = cls + end + elseif table.HasValue(w.InLoadoutFor, ROLE_INNOCENT) then -- setup for new roles + w.InLoadoutFor[#w.InLoadoutFor + 1] = subrole - if not table.HasValue(tmpLoadoutWeps, cls) then - tmpLoadoutWeps[#tmpLoadoutWeps + 1] = cls - end - end - end + if not table.HasValue(tmpLoadoutWeps, cls) then + tmpLoadoutWeps[#tmpLoadoutWeps + 1] = cls + end + end + end - -- default loadout, insert it at the end - local default = { - "weapon_zm_carry", - "weapon_ttt_unarmed", - "weapon_zm_improvised" - } + -- default loadout, insert it at the end + local default = { + "weapon_zm_carry", + "weapon_ttt_unarmed", + "weapon_zm_improvised", + } - for i = 1, #default do - local def = default[i] + for i = 1, #default do + local def = default[i] - if table.HasValue(tmpLoadoutWeps, def) then continue end + if table.HasValue(tmpLoadoutWeps, def) then + continue + end - tmpLoadoutWeps[#tmpLoadoutWeps + 1] = def - end + tmpLoadoutWeps[#tmpLoadoutWeps + 1] = def + end - loadout_weapons[subrole] = tmpLoadoutWeps + loadout_weapons[subrole] = tmpLoadoutWeps - --- - -- @realm server - hook.Run("TTT2ModifyDefaultLoadout", loadout_weapons, subrole) + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2ModifyDefaultLoadout", loadout_weapons, subrole) - return loadout_weapons[subrole] + return loadout_weapons[subrole] end -- Give player loadout weapons he should have for his subrole that he does not have -- yet local function GiveLoadoutWeapon(ply, cls) - if ply:HasWeapon(cls) or not ply:CanCarryType(WEPS.TypeForWeapon(cls)) then return end + if ply:HasWeapon(cls) or not ply:CanCarryType(WEPS.TypeForWeapon(cls)) then + return + end - local wep = ply:Give(cls) + local wep = ply:Give(cls) - ply.loadoutWeps = ply.loadoutWeps or {} + ply.loadoutWeps = ply.loadoutWeps or {} - if not table.HasValue(ply.loadoutWeps, cls) then - ply.loadoutWeps[#ply.loadoutWeps + 1] = cls - end + if not table.HasValue(ply.loadoutWeps, cls) then + ply.loadoutWeps[#ply.loadoutWeps + 1] = cls + end - return wep + return wep end local function GiveLoadoutWeapons(ply) - local subrole = GetRoundState() == ROUND_PREP and ROLE_INNOCENT or ply:GetSubRole() - local weps = GetLoadoutWeapons(subrole) + local subrole = GetRoundState() == ROUND_PREP and ROLE_INNOCENT or ply:GetSubRole() + local weps = GetLoadoutWeapons(subrole) - if not weps then return end + if not weps then + return + end - for i = 1, #weps do - local cls = weps[i] + for i = 1, #weps do + local cls = weps[i] - if ply:HasWeapon(cls) or not ply:CanCarryType(WEPS.TypeForWeapon(cls)) then continue end + if ply:HasWeapon(cls) or not ply:CanCarryType(WEPS.TypeForWeapon(cls)) then + continue + end - GiveLoadoutWeapon(ply, cls) - end + GiveLoadoutWeapon(ply, cls) + end end local function GetGiveLoadoutWeapons(ply) - local subrole = GetRoundState() == ROUND_PREP and ROLE_INNOCENT or ply:GetSubRole() - local weps = GetLoadoutWeapons(subrole) + local subrole = GetRoundState() == ROUND_PREP and ROLE_INNOCENT or ply:GetSubRole() + local weps = GetLoadoutWeapons(subrole) - return table.Copy(weps) + return table.Copy(weps) end local function GetResetLoadoutWeapons(ply) - local tmp = {} + local tmp = {} - ply.loadoutWeps = ply.loadoutWeps or {} + ply.loadoutWeps = ply.loadoutWeps or {} - local weps = ply:GetWeapons() + local weps = ply:GetWeapons() - for i = 1, #weps do - local cls = WEPS.GetClass(weps[i]) + for i = 1, #weps do + local cls = WEPS.GetClass(weps[i]) - if table.HasValue(ply.loadoutWeps, cls) and cls ~= "weapon_ttt_unarmed" then - tmp[#tmp + 1] = cls - end - end + if table.HasValue(ply.loadoutWeps, cls) and cls ~= "weapon_ttt_unarmed" then + tmp[#tmp + 1] = cls + end + end - return tmp + return tmp end local function HasLoadoutWeapons(ply) - if ply:IsSpec() then - return true - end + if ply:IsSpec() then + return true + end - local subrole = GetRoundState() == ROUND_PREP and ROLE_INNOCENT or ply:GetSubRole() - local weps = GetLoadoutWeapons(subrole) + local subrole = GetRoundState() == ROUND_PREP and ROLE_INNOCENT or ply:GetSubRole() + local weps = GetLoadoutWeapons(subrole) - if not weps then - return true - end + if not weps then + return true + end - for i = 1, #weps do - local cls = weps[i] + for i = 1, #weps do + local cls = weps[i] - if not ply:HasWeapon(cls) and ply:CanCarryType(WEPS.TypeForWeapon(cls)) then - return false - end - end + if not ply:HasWeapon(cls) and ply:CanCarryType(WEPS.TypeForWeapon(cls)) then + return false + end + end - return true + return true end -- Cache subrole -> default-items table @@ -238,85 +253,98 @@ local loadout_items = {} -- Get loadout items. local function GetLoadoutItems(subrole) - local tmpLoadoutItems = loadout_items[subrole] + local tmpLoadoutItems = loadout_items[subrole] - if tmpLoadoutItems then - return tmpLoadoutItems - end + if tmpLoadoutItems then + return tmpLoadoutItems + end - tmpLoadoutItems = {} + tmpLoadoutItems = {} - local itms = items.GetList() + local itms = items.GetList() - for i = 1, #itms do - local w = itms[i] + for i = 1, #itms do + local w = itms[i] - if not istable(w.InLoadoutFor) or w.Doublicated then continue end + if not istable(w.InLoadoutFor) or w.Duplicated then + continue + end - local cls = w.id + local cls = w.id - if table.HasValue(w.InLoadoutFor, subrole) then - if not table.HasValue(tmpLoadoutItems, cls) then - tmpLoadoutItems[#tmpLoadoutItems + 1] = cls - end - elseif table.HasValue(w.InLoadoutFor, ROLE_INNOCENT) then -- setup for new roles - w.InLoadoutFor[#w.InLoadoutFor + 1] = subrole + if table.HasValue(w.InLoadoutFor, subrole) then + if not table.HasValue(tmpLoadoutItems, cls) then + tmpLoadoutItems[#tmpLoadoutItems + 1] = cls + end + elseif table.HasValue(w.InLoadoutFor, ROLE_INNOCENT) then -- setup for new roles + w.InLoadoutFor[#w.InLoadoutFor + 1] = subrole - if not table.HasValue(tmpLoadoutItems, cls) then - tmpLoadoutItems[#tmpLoadoutItems + 1] = cls - end - end - end + if not table.HasValue(tmpLoadoutItems, cls) then + tmpLoadoutItems[#tmpLoadoutItems + 1] = cls + end + end + end - loadout_items[subrole] = tmpLoadoutItems + loadout_items[subrole] = tmpLoadoutItems - --- - -- @realm server - hook.Run("TTT2ModifyDefaultLoadout", loadout_items, subrole) + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2ModifyDefaultLoadout", loadout_items, subrole) - return loadout_items[subrole] + return loadout_items[subrole] end -- Give player loadout items he should have for his subrole that he does not have -- yet local function GiveLoadoutItem(ply, cls) - if ply:HasEquipmentItem(cls) then return end + if ply:HasEquipmentItem(cls) then + return + end - local item = ply:GiveItem(cls) + local item = ply:GiveItem(cls) - ply.loadoutItems = ply.loadoutItems or {} + ply.loadoutItems = ply.loadoutItems or {} - if not table.HasValue(ply.loadoutItems, cls) then - ply.loadoutItems[#ply.loadoutItems + 1] = cls - end + if not table.HasValue(ply.loadoutItems, cls) then + ply.loadoutItems[#ply.loadoutItems + 1] = cls + end - return item + return item end local function GiveLoadoutItems(ply) - local subrole = GetRoundState() == ROUND_PREP and ROLE_INNOCENT or ply:GetSubRole() - local itms = GetLoadoutItems(subrole) + local subrole = GetRoundState() == ROUND_PREP and ROLE_INNOCENT or ply:GetSubRole() + local itms = GetLoadoutItems(subrole) - if not itms then return end + if not itms then + return + end - for i = 1, #itms do - local cls = itms[i] + for i = 1, #itms do + local cls = itms[i] - if ply:HasEquipmentItem(cls) then continue end + if ply:HasEquipmentItem(cls) then + continue + end - GiveLoadoutItem(ply, cls) - end + GiveLoadoutItem(ply, cls) + end end local function ResetLoadoutItems(ply) - local itms = GetModifiedEquipment(ply, items.GetRoleItems(ply:GetSubRole())) - if itms == nil then return end - - for i = 1, #itms do - if not itms[i].loadout then continue end - - ply:RemoveItem(itms[i].id) - end + local itms = GetModifiedEquipment(ply, items.GetRoleItems(ply:GetSubRole())) + if itms == nil then + return + end + + for i = 1, #itms do + if not itms[i].loadout then + continue + end + + ply:RemoveItem(itms[i].id) + end end --- @@ -324,28 +352,29 @@ end -- calling this function is used to get them the weapons anyway as soon as -- possible. local function LateLoadout(id) - local ply = Entity(id) + local ply = Entity(id) - if not IsValid(ply) or not ply:IsPlayer() then - timer.Remove("lateloadout" .. id) + if not IsValid(ply) or not ply:IsPlayer() then + timer.Remove("lateloadout" .. id) - return - end + return + end - if not HasLoadoutWeapons(ply) then - GiveLoadoutWeapons(ply) + if not HasLoadoutWeapons(ply) then + GiveLoadoutWeapons(ply) - if HasLoadoutWeapons(ply) then - timer.Remove("lateloadout" .. id) - end - else - timer.Remove("lateloadout" .. id) - end + if HasLoadoutWeapons(ply) then + timer.Remove("lateloadout" .. id) + end + else + timer.Remove("lateloadout" .. id) + end end --- -- Called to give @{Player}s the default set of @{Weapon}s. -- @note This function may not work in your custom gamemode if you have overridden your +-- stylua: ignore -- @{GM:PlayerSpawn} and you do not use self.BaseClass.PlayerSpawn or @{hook.Run}. -- @param Player ply @{Player} to give @{Weapon}s to -- @note Note that this is called both when a @{Player} spawns and when a round starts @@ -354,83 +383,83 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerLoadout -- @local function GM:PlayerLoadout(ply, isRespawn) - if not IsValid(ply) or ply:IsSpec() then return end + if not IsValid(ply) or ply:IsSpec() then return end - CleanupInventoryAndNotifyClient(ply) + CleanupInventoryAndNotifyClient(ply) - ResetLoadoutItems(ply) + ResetLoadoutItems(ply) - -- give default items - GiveLoadoutItems(ply) + -- give default items + GiveLoadoutItems(ply) - -- reset default loadout - local reset = GetResetLoadoutWeapons(ply) + -- reset default loadout + local reset = GetResetLoadoutWeapons(ply) - -- hand out weaponry - local give = GetGiveLoadoutWeapons(ply) + -- hand out weaponry + local give = GetGiveLoadoutWeapons(ply) - local tmp = {} + local tmp = {} - -- check which weapon the player should get/loose - for k = 1, #reset do - local cls = reset[k] - local has = false + -- check which weapon the player should get/loose + for k = 1, #reset do + local cls = reset[k] + local has = false - for k2 = 1, #give do - if cls ~= give[k2] then continue end + for k2 = 1, #give do + if cls ~= give[k2] then continue end - has = true + has = true - table.remove(give, k2) + table.remove(give, k2) - break - end + break + end - if not has then - tmp[#tmp + 1] = cls - end - end + if not has then + tmp[#tmp + 1] = cls + end + end - local loudoutWeps = ply.loadoutWeps + local loudoutWeps = ply.loadoutWeps - for i = 1, #tmp do - local cls = tmp[i] + for i = 1, #tmp do + local cls = tmp[i] - ply:StripWeapon(cls) + ply:StripWeapon(cls) - for k = 1, #loudoutWeps do - if cls ~= loudoutWeps[k] then continue end + for k = 1, #loudoutWeps do + if cls ~= loudoutWeps[k] then continue end - table.remove(loudoutWeps, k) + table.remove(loudoutWeps, k) - break - end - end + break + end + end - for i = 1, #give do - GiveLoadoutWeapon(ply, give[i]) - end + for i = 1, #give do + GiveLoadoutWeapon(ply, give[i]) + end - playermodels.RemovePlayerHat(ply) - playermodels.ApplyPlayerHat(ply, function(p) - local plyRoleData = ply:GetSubRoleData() + playermodels.RemovePlayerHat(ply) + playermodels.ApplyPlayerHat(ply, function(p) + local plyRoleData = ply:GetSubRoleData() - return ply:IsActive() - and plyRoleData.isPolicingRole - and plyRoleData.isPublicRole - and cv_ttt_detective_hats:GetBool() - and playermodels.PlayerCanHaveHat(ply) - end) + return ply:IsActive() + and plyRoleData.isPolicingRole + and plyRoleData.isPublicRole + and cv_ttt_detective_hats:GetBool() + and playermodels.PlayerCanHaveHat(ply) + end) - if not HasLoadoutWeapons(ply) then - MsgN("Could not spawn all loadout weapons for " .. ply:Nick() .. ", will retry.") + if not HasLoadoutWeapons(ply) then + Dev(1, "Could not spawn all loadout weapons for " .. ply:Nick() .. ", will retry.") - local timerId = ply:EntIndex() + local timerId = ply:EntIndex() - timer.Create("lateloadout" .. timerId, 1, 0, function() - LateLoadout(timerId) - end) - end + timer.Create("lateloadout" .. timerId, 1, 0, function() + LateLoadout(timerId) + end) + end end --- @@ -440,82 +469,38 @@ end -- @realm server -- @ref https://wiki.facepunch.com/gmod/GM:PlayerLoadout function GM:UpdatePlayerLoadouts() - local plys = player.GetAll() - - for i = 1, #plys do - --- - -- @realm server - hook.Run("PlayerLoadout", plys[i], false) - end + local plys = player.GetAll() + + for i = 1, #plys do + --- + -- @realm server + -- stylua: ignore + hook.Run("PlayerLoadout", plys[i], false) + end end local function DropActiveWeapon(ply) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply:SafeDropWeapon(ply:GetActiveWeapon(), false) + ply:SafeDropWeapon(ply:GetActiveWeapon(), false) end concommand.Add("ttt_dropweapon", DropActiveWeapon) - -local function DropActiveAmmo(ply) - if not IsValid(ply) then return end - - local wep = ply:GetActiveWeapon() - - if not IsValid(wep) or not wep.AmmoEnt then return end - - local hook_data = {wep:Clip1()} - - --- - -- @realm server - if hook.Run("TTT2DropAmmo", ply, hook_data) == false then - LANG.Msg(ply, "drop_ammo_prevented", nil, MSG_CHAT_WARN) - - return - end - - local amt = hook_data[1] - - if amt < 1 or amt <= wep.Primary.ClipSize * 0.25 then - LANG.Msg(ply, "drop_no_ammo", nil, MSG_CHAT_WARN) - - return - end - - local pos, ang = ply:GetShootPos(), ply:EyeAngles() - local dir = ang:Forward() * 32 + ang:Right() * 6 + ang:Up() * -5 - local tr = util.QuickTrace(pos, dir, ply) - - if tr.HitWorld then return end - - wep:SetClip1(0) - - ply:AnimPerformGesture(ACT_GMOD_GESTURE_ITEM_GIVE) - - local box = ents.Create(wep.AmmoEnt) - - if not IsValid(box) then return end - - box:SetPos(pos + dir) - box:SetOwner(ply) - box:Spawn() - box:PhysWake() - - local phys = box:GetPhysicsObject() - - if IsValid(phys) then - phys:ApplyForceCenter(ang:Forward() * 1000) - phys:ApplyForceOffset(VectorRand(), vector_origin) - end - - box.AmmoAmount = amt - - timer.Simple(2, function() - if not IsValid(box) then return end - - box:SetOwner(nil) - end) -end -concommand.Add("ttt_dropammo", DropActiveAmmo) +concommand.Add("ttt_dropammo", function(ply) + if not IsValid(ply) then + return + end + local wep = ply:GetActiveWeapon() + ply:SafeDropAmmo(wep, false) +end) +concommand.Add("ttt_dropclip", function(ply) + if not IsValid(ply) then + return + end + + ply:SafeDropAmmo(ply:GetActiveWeapon(), true) +end) --- -- Called as a @{Weapon} entity is picked up by a @{Player}.
        @@ -532,37 +517,43 @@ concommand.Add("ttt_dropammo", DropActiveAmmo) -- @ref https://wiki.facepunch.com/gmod/GM:WeaponEquip -- @local function GM:WeaponEquip(wep, ply) - if not IsValid(ply) or not IsValid(wep) then return end + if not IsValid(ply) or not IsValid(wep) then + return + end - if not wep.Kind then - wep:Remove() -- only remove if they lack critical stuff + if not wep.Kind then + wep:Remove() -- only remove if they lack critical stuff - ErrorNoHalt("Equipped weapon " .. wep:GetClass() .. " is not compatible with TTT\n") + ErrorNoHaltWithStack( + "Equipped weapon " .. wep:GetClass() .. " is not compatible with TTT\n" + ) - return - end + return + end - AddWeaponToInventoryAndNotifyClient(ply, wep) + AddWeaponToInventoryAndNotifyClient(ply, wep) - local function WeaponEquipNextFrame() - if not IsValid(ply) or not IsValid(wep) then return end + local function WeaponEquipNextFrame() + if not IsValid(ply) or not IsValid(wep) then + return + end - -- autoselect weapon when the new weapon has the same slot than the old one - -- do not autoselect when ALT is pressed - if wep.wpickup_autoSelect then - wep.wpickup_autoSelect = nil + -- autoselect weapon when the new weapon has the same slot than the old one + -- do not autoselect when ALT is pressed + if wep.wpickup_autoSelect then + wep.wpickup_autoSelect = nil - ply:SelectWeapon(WEPS.GetClass(wep)) - end + ply:SelectWeapon(WEPS.GetClass(wep)) + end - -- there is a glitch that picking up a weapon does not refresh the weapon cache on - -- the client. Therefore the client has to be notified to updated its cache - net.Start("ttt2_switch_weapon_update_cache") - net.Send(ply) - end + -- there is a glitch that picking up a weapon does not refresh the weapon cache on + -- the client. Therefore the client has to be notified to updated its cache + net.Start("ttt2_switch_weapon_update_cache") + net.Send(ply) + end - -- handle all this stuff in the next frame since the owner is not yet valid - timer.Simple(0, WeaponEquipNextFrame) + -- handle all this stuff in the next frame since the owner is not yet valid + timer.Simple(0, WeaponEquipNextFrame) end --- @@ -577,22 +568,24 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:PlayerDroppedWeapon -- @local function GM:PlayerDroppedWeapon(ply, wep) - if not IsValid(wep) or not IsValid(ply) or not wep.Kind then return end + if not IsValid(wep) or not IsValid(ply) or not wep.Kind then + return + end - if wep.name_timer_pos then - timer.Remove(wep.name_timer_pos) - end + if wep.name_timer_pos then + timer.Remove(wep.name_timer_pos) + end - if wep.name_timer_cancel then - timer.Remove(wep.name_timer_cancel) - end + if wep.name_timer_cancel then + timer.Remove(wep.name_timer_cancel) + end - RemoveWeaponFromInventoryAndNotifyClient(ply, wep) + RemoveWeaponFromInventoryAndNotifyClient(ply, wep) - -- there is a glitch that picking up a weapon does not refresh the weapon cache on - -- the client. Therefore the client has to be notified to update its cache - net.Start("ttt2_switch_weapon_update_cache") - net.Send(ply) + -- there is a glitch that picking up a weapon does not refresh the weapon cache on + -- the client. Therefore the client has to be notified to update its cache + net.Start("ttt2_switch_weapon_update_cache") + net.Send(ply) end --- @@ -603,9 +596,9 @@ end -- @ref https://wiki.facepunch.com/gmod/GM:EntityRemoved -- @local function GM:EntityRemoved(ent) - if IsValid(ent) and IsValid(ent:GetOwner()) and ent:IsWeapon() and ent.Kind then - RemoveWeaponFromInventoryAndNotifyClient(ent:GetOwner(), ent) - end + if IsValid(ent) and IsValid(ent:GetOwner()) and ent:IsWeapon() and ent.Kind then + RemoveWeaponFromInventoryAndNotifyClient(ent:GetOwner(), ent) + end end --- @@ -619,39 +612,55 @@ end -- @param boolean keepSelection If set to true the current selection is kept if not dropped -- @realm server function WEPS.DropNotifiedWeapon(ply, wep, deathDrop, keepSelection) - if not IsValid(ply) or not IsValid(wep) then return end - - -- Hack to tell the weapon it's about to be dropped and should do what it - -- must right now - if wep.PreDrop then - wep:PreDrop(deathDrop) - end - - -- PreDrop might destroy weapon - if not IsValid(wep) then return end - - -- Tag this weapon as dropped, so that if it's a special weapon we do not - -- auto-pickup when nearby. - wep.IsDropped = true - - -- After dropping a weapon, always switch to holstered, so that traitors - -- will never accidentally pull out a traitor weapon. - -- - -- Perform this *before* the drop in order to abuse the fact that this - -- holsters the weapon, which in turn aborts any reload that's in - -- progress. We don't want a dropped weapon to be in a reloading state - -- because the relevant timer is reset when picking it up, making the - -- reload happen instantly. This allows one to dodge the delay by dropping - -- during reload. All of this is a workaround for not having access to - -- CBaseWeapon::AbortReload() (and that not being handled in - -- CBaseWeapon::Drop in the first place). - if not keepSelection then - ply:SelectWeapon("weapon_ttt_unarmed") - end - - ply:DropWeapon(wep) - - wep:PhysWake() + if not IsValid(ply) or not IsValid(wep) then + return + end + + -- Tag the weapon as having been dropped due to a player death so that + -- we can prevent it from dropping if we want. + wep.IsDroppedBecauseDeath = deathDrop + + -- Hack to tell the weapon it's about to be dropped and should do what it + -- must right now + if wep.PreDrop then + wep:PreDrop(deathDrop) + end + + -- PreDrop might destroy weapon + if not IsValid(wep) then + return + end + + -- Tag this weapon as dropped, so that if it's a special weapon we do not + -- auto-pickup when nearby. + wep.IsDropped = true + + -- After dropping a weapon, always switch to holstered, so that traitors + -- will never accidentally pull out a traitor weapon. + -- + -- Perform this *before* the drop in order to abuse the fact that this + -- holsters the weapon, which in turn aborts any reload that's in + -- progress. We don't want a dropped weapon to be in a reloading state + -- because the relevant timer is reset when picking it up, making the + -- reload happen instantly. This allows one to dodge the delay by dropping + -- during reload. All of this is a workaround for not having access to + -- CBaseWeapon::AbortReload() (and that not being handled in + -- CBaseWeapon::Drop in the first place). + if not keepSelection then + ply:SelectWeapon("weapon_ttt_unarmed") + end + + if deathDrop and wep.overrideDropOnDeath == DROP_ON_DEATH_TYPE_DENY then + wep:Remove() + return + end + + ply:DropWeapon(wep) + + wep:PhysWake() + + -- Unset this, because we can't use it after this point. + wep.IsDroppedBecauseDeath = nil end --- @@ -660,19 +669,19 @@ end -- is bought, so trigger it at the start of a round instead -- @realm server function WEPS.ForcePrecache() - local weps = weapons.GetList() + local weps = weapons.GetList() - for i = 1, #weps do - local w = weps[i] + for i = 1, #weps do + local w = weps[i] - if w.WorldModel then - util.PrecacheModel(w.WorldModel) - end + if w.WorldModel then + util.PrecacheModel(w.WorldModel) + end - if w.ViewModel then - util.PrecacheModel(w.ViewModel) - end - end + if w.ViewModel then + util.PrecacheModel(w.ViewModel) + end + end end --- @@ -680,15 +689,15 @@ end -- @return boolean -- @realm server function WEPS.IsInstalled(cls) - local weps = weapons.GetList() + local weps = weapons.GetList() - for i = 1, #weps do - if weps[i].ClassName == cls then - return true - end - end + for i = 1, #weps do + if weps[i].ClassName == cls then + return true + end + end - return false + return false end --- @@ -699,9 +708,7 @@ end -- @param number role The role indentifier -- @hook -- @realm server -function GM:TTT2ModifyDefaultLoadout(loadout, role) - -end +function GM:TTT2ModifyDefaultLoadout(loadout, role) end --- -- Used to modifiy or block the amount of ammo dropped. @@ -710,6 +717,4 @@ end -- @return nil|boolean Return false to prevent the drop of the ammo -- @hook -- @realm server -function GM:TTT2DropAmmo(ply, amountTbl) - -end +function GM:TTT2DropAmmo(ply, amountTbl) end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/base_stacking_element.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/base_stacking_element.lua index bfb1ebd0e..af623f228 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/base_stacking_element.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/base_stacking_element.lua @@ -17,35 +17,35 @@ HUDELEMENT.lastCount = 0 -- @internal -- @realm client function HUDELEMENT:Draw() - local elems = self.elements + local elems = self.elements - -- set size beforehand if #elements was reduced to remove flickering - if #elems < self.lastCount then - local height = 0 + -- set size beforehand if #elements was reduced to remove flickering + if #elems < self.lastCount then + local height = 0 - for i = 1, #elems do - height = height + elems[i].h + self.element_margin - end + for i = 1, #elems do + height = height + elems[i].h + self.element_margin + end - self:SetSize(self.size.w, -height) - end + self:SetSize(self.size.w, -height) + end - -- draw all elements - local running_y = self.pos.y + -- draw all elements + local running_y = self.pos.y - for i = 1, #elems do - local el = elems[i] + for i = 1, #elems do + local el = elems[i] - self:DrawElement(i, self.pos.x, running_y, self.size.w, el.h) + self:DrawElement(i, self.pos.x, running_y, self.size.w, el.h) - running_y = running_y + el.h + self.element_margin - end + running_y = running_y + el.h + self.element_margin + end - local totalHeight = running_y - self.pos.y + local totalHeight = running_y - self.pos.y - self.lastCount = #elems + self.lastCount = #elems - self:SetSize(self.size.w, - math.max(totalHeight, self.minsize.h)) + self:SetSize(self.size.w, -math.max(totalHeight, self.minsize.h)) end --- @@ -57,16 +57,14 @@ end -- @param number h -- @hook -- @realm client -function HUDELEMENT:DrawElement(i, x, y, w, h) - -end +function HUDELEMENT:DrawElement(i, x, y, w, h) end --- -- Pass a list of elements, which should be drawn. Each element needs a height h. -- @param table elements list of @{HUDELEMENT} -- @realm client function HUDELEMENT:SetElements(elements) - self.elements = elements + self.elements = elements end --- @@ -74,5 +72,5 @@ end -- @param number margin -- @realm client function HUDELEMENT:SetElementMargin(margin) - self.element_margin = margin + self.element_margin = margin end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/dynamic_hud_element.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/dynamic_hud_element.lua index 13f1aef83..c06e516e3 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/dynamic_hud_element.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/dynamic_hud_element.lua @@ -12,55 +12,55 @@ local draw = draw local huds = huds if CLIENT then - local defaultColor = Color(49, 71, 94) + local defaultColor = Color(49, 71, 94) - --- - -- Returns the current @{HUD} scale (for this element) - -- @return[default=1.0] number - -- @realm client - -- @deprecated - function HUDELEMENT:GetHUDScale() - return appearance.GetGlobalScale() - end + --- + -- Returns the current @{HUD} scale (for this element) + -- @return[default=1.0] number + -- @realm client + -- @deprecated + function HUDELEMENT:GetHUDScale() + return appearance.GetGlobalScale() + end - --- - -- Returns the current @{HUD} base @{Color} - -- @return[default=Color(49, 71, 94)] Color - -- @realm client - function HUDELEMENT:GetHUDBasecolor() - local hud = huds.GetStored(HUDManager.GetHUD()) + --- + -- Returns the current @{HUD} base @{Color} + -- @return[default=Color(49, 71, 94)] Color + -- @realm client + function HUDELEMENT:GetHUDBasecolor() + local hud = huds.GetStored(HUDManager.GetHUD()) - return (hud and hud.basecolor) or defaultColor - end + return (hud and hud.basecolor) or defaultColor + end - --- - -- @param string text - -- @param string font - -- @param number x - -- @param number y - -- @param Color color - -- @param number xalign - -- @param number yalign - -- @param boolean dark - -- @deprecated - -- @realm client - function HUDELEMENT:ShadowedText(text, font, x, y, color, xalign, yalign, dark) - draw.ShadowedText(text, font, x, y, color, xalign, yalign, dark) - end + --- + -- @param string text + -- @param string font + -- @param number x + -- @param number y + -- @param Color color + -- @param number xalign + -- @param number yalign + -- @param boolean dark + -- @deprecated + -- @realm client + function HUDELEMENT:ShadowedText(text, font, x, y, color, xalign, yalign, dark) + draw.ShadowedText(text, font, x, y, color, xalign, yalign, dark) + end - --- - -- @param string text - -- @param string font - -- @param number x - -- @param number y - -- @param Color color - -- @param number xalign - -- @param number yalign - -- @param boolean shadow - -- @param number scale - -- @deprecated - -- @realm client - function HUDELEMENT:AdvancedText(text, font, x, y, color, xalign, yalign, shadow, scale) - draw.AdvancedText(text, font, x, y, color, xalign, yalign, shadow, scale) - end + --- + -- @param string text + -- @param string font + -- @param number x + -- @param number y + -- @param Color color + -- @param number xalign + -- @param number yalign + -- @param boolean shadow + -- @param number scale + -- @deprecated + -- @realm client + function HUDELEMENT:AdvancedText(text, font, x, y, color, xalign, yalign, shadow, scale) + draw.AdvancedText(text, font, x, y, color, xalign, yalign, shadow, scale) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/hud_element_base/cl_init.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/hud_element_base/cl_init.lua index 983d14eaf..509d713d9 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/hud_element_base/cl_init.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/hud_element_base/cl_init.lua @@ -12,18 +12,18 @@ local hudelements = hudelements local math = math local zero_tbl_pos = { - x = 0, - y = 0 + x = 0, + y = 0, } local zero_tbl_size = { - w = 0, - h = 0 + w = 0, + h = 0, } local zero_tbl_min_size = { - w = 0, - h = 0 + w = 0, + h = 0, } -- Basic position / dimension variables @@ -41,15 +41,15 @@ HUDELEMENT.parent_is_type = nil HUDELEMENT.children = {} HUDELEMENT.defaults = { - -- resize area parameters - click_area = 20, - click_padding = 0 + -- resize area parameters + click_area = 20, + click_padding = 0, } HUDELEMENT.edit_live_data = { - calc_new_click_area = false, - old_row = nil, - old_col = nil + calc_new_click_area = false, + old_row = nil, + old_col = nil, } --- @@ -60,26 +60,44 @@ HUDELEMENT.edit_live_data = { -- @param[opt] any ... parameters to call the given function with -- @realm client function HUDELEMENT:ApplyToChildren(funcName, ...) - if not funcName then return end - - local children = self:GetChildren() - - for i = 1, #children do - local elem = children[i] - - local elemtbl = hudelements.GetStored(elem) - if not elemtbl then - Msg("ERROR: HUDElement " .. (self.id or "?") .. " has unknown child element named " .. elem .. " when applying a function to all children: " .. funcName .. " \n") - - continue - end - - if isfunction(elemtbl[funcName]) then - elemtbl[funcName](elemtbl, ...) - else - MsgN("ERROR: HUDElement " .. (self.id or "?") .. " has child named " .. elem .. " with unknown function " .. funcName .. " \n") - end - end + if not funcName then + return + end + + local children = self:GetChildren() + + for i = 1, #children do + local elem = children[i] + + local elemtbl = hudelements.GetStored(elem) + if not elemtbl then + ErrorNoHaltWithStack( + "ERROR: HUDElement " + .. (self.id or "?") + .. " has unknown child element named " + .. elem + .. " when applying a function to all children: " + .. funcName + .. " \n" + ) + + continue + end + + if isfunction(elemtbl[funcName]) then + elemtbl[funcName](elemtbl, ...) + else + ErrorNoHaltWithStack( + "ERROR: HUDElement " + .. (self.id or "?") + .. " has child named " + .. elem + .. " with unknown function " + .. funcName + .. " \n" + ) + end + end end --- @@ -94,10 +112,7 @@ end -- @note Use this to set child<->parent relations and also to force your element to a specific HUD if needed etc, -- this is called before Initialized and other objects can still be uninitialized! -- @realm client -function HUDELEMENT:PreInitialize() - -end - +function HUDELEMENT:PreInitialize() end --- -- This function will be called each time the HUD is loaded eg. when @@ -111,29 +126,26 @@ end -- @{HUDELEMENT:Initialize} on all children. -- @realm client function HUDELEMENT:Initialize() - local defaults = self:GetDefaults() + local defaults = self:GetDefaults() - self:SetSize(defaults.size.w, defaults.size.h) - self:SetMinSize(defaults.minsize.w, defaults.minsize.h) + self:SetSize(defaults.size.w, defaults.size.h) + self:SetMinSize(defaults.minsize.w, defaults.minsize.h) - defaults = self:GetDefaults() + defaults = self:GetDefaults() - self:SetBasePos(defaults.basepos.x, defaults.basepos.y) + self:SetBasePos(defaults.basepos.x, defaults.basepos.y) - -- use this to set default values and dont forget to call BaseClass.Initialze(self)!! - self:ApplyToChildren("Initialize") + -- use this to set default values and dont forget to call BaseClass.Initialze(self)!! + self:ApplyToChildren("Initialize") end - --- -- This function is called when an element should draw its content. -- Please use this function only to draw your element and dont calculate -- any values if not explicitly needed. -- @hook -- @realm client -function HUDELEMENT:Draw() - -end +function HUDELEMENT:Draw() end --- -- This function is called to decide whether or not an element should be drawn. @@ -142,7 +154,7 @@ end -- @hook -- @realm client function HUDELEMENT:ShouldDraw() - return true + return true end --- @@ -153,7 +165,7 @@ end -- @hook -- @realm client function HUDELEMENT:IsResizable() - return true, true + return true, true end --- @@ -163,7 +175,7 @@ end -- @hook -- @realm client function HUDELEMENT:AspectRatioIsLocked() - return false + return false end --- @@ -173,7 +185,7 @@ end -- @hook -- @realm client function HUDELEMENT:InheritParentBorder() - return false + return false end -- parameter overwrites end @@ -183,7 +195,7 @@ end -- @hook -- @realm client function HUDELEMENT:PerformLayout() - self:ApplyToChildren("PerformLayout") + self:ApplyToChildren("PerformLayout") end --- @@ -192,7 +204,7 @@ end -- @return table x and y value -- @realm client function HUDELEMENT:GetBasePos() - return table.Copy(self.basepos) + return table.Copy(self.basepos) end --- @@ -203,13 +215,13 @@ end -- @param number y -- @realm client function HUDELEMENT:SetBasePos(x, y) - local pos_difference_x = self.pos.x - self.basepos.x - local pos_difference_y = self.pos.y - self.basepos.y + local pos_difference_x = self.pos.x - self.basepos.x + local pos_difference_y = self.pos.y - self.basepos.y - self.basepos.x = x - self.basepos.y = y + self.basepos.x = x + self.basepos.y = y - self:SetPos(x + pos_difference_x, y + pos_difference_y) + self:SetPos(x + pos_difference_x, y + pos_difference_y) end --- @@ -218,7 +230,7 @@ end -- @return table with x and y value -- @realm client function HUDELEMENT:GetPos() - return table.Copy(self.pos) + return table.Copy(self.pos) end --- @@ -228,8 +240,8 @@ end -- @param number y -- @realm client function HUDELEMENT:SetPos(x, y) - self.pos.x = x - self.pos.y = y + self.pos.x = x + self.pos.y = y end --- @@ -239,7 +251,7 @@ end -- @return table width and height value -- @realm client function HUDELEMENT:GetMinSize() - return table.Copy(self.minsize) + return table.Copy(self.minsize) end --- @@ -250,8 +262,8 @@ end -- @param number h -- @realm client function HUDELEMENT:SetMinSize(w, h) - self.minsize.w = w - self.minsize.h = h + self.minsize.w = w + self.minsize.h = h end --- @@ -259,7 +271,7 @@ end -- @return table with width and height value -- @realm client function HUDELEMENT:GetSize() - return table.Copy(self.size) + return table.Copy(self.size) end --- @@ -271,31 +283,31 @@ end -- @param number h -- @realm client function HUDELEMENT:SetSize(w, h) - w = math.Round(w) - h = math.Round(h) + w = math.Round(w) + h = math.Round(h) - local nw, nh = w < 0, h < 0 + local nw, nh = w < 0, h < 0 - if nw then - w = -w - end + if nw then + w = -w + end - if nh then - h = -h - end + if nh then + h = -h + end - if nw or nh then - if nw then - self:SetPos(self:GetBasePos().x - w, self:GetPos().y) - end + if nw or nh then + if nw then + self:SetPos(self:GetBasePos().x - w, self:GetPos().y) + end - if nh then - self:SetPos(self:GetPos().x, self:GetBasePos().y - h) - end - end + if nh then + self:SetPos(self:GetPos().x, self:GetBasePos().y - h) + end + end - self.size.w = w - self.size.h = h + self.size.w = w + self.size.h = h end --- @@ -305,7 +317,7 @@ end -- @internal -- @realm client function HUDELEMENT:GetParentRelation() - return self.parent, self.parent_is_type + return self.parent, self.parent_is_type end --- @@ -316,8 +328,8 @@ end -- @internal -- @realm client function HUDELEMENT:SetParentRelation(parent, is_type) - self.parent = parent - self.parent_is_type = is_type + self.parent = parent + self.parent_is_type = is_type end --- @@ -327,9 +339,9 @@ end -- @param number elementid -- @realm client function HUDELEMENT:AddChild(elementid) - if not table.HasValue(self.children, elementid) then - self.children[#self.children + 1] = elementid - end + if not table.HasValue(self.children, elementid) then + self.children[#self.children + 1] = elementid + end end --- @@ -337,7 +349,7 @@ end -- @return boolean -- @realm client function HUDELEMENT:IsChild() - return self.parent ~= nil + return self.parent ~= nil end --- @@ -345,7 +357,7 @@ end -- @return boolean -- @realm client function HUDELEMENT:IsParent() - return #self.children > 0 + return #self.children > 0 end --- @@ -353,7 +365,7 @@ end -- @return table a copy of all your child elements -- @realm client function HUDELEMENT:GetChildren() - return table.Copy(self.children) + return table.Copy(self.children) end --- @@ -362,35 +374,40 @@ end -- @return table size ({@{number} w, @{number} h}) -- @realm client function HUDELEMENT:GetBorderParams() - if not self:IsParent() then - return self:GetPos(), self:GetSize() - end + if not self:IsParent() then + return self:GetPos(), self:GetSize() + end - local pos = self:GetPos() - local size = self:GetSize() - local children = self:GetChildren() + local pos = self:GetPos() + local size = self:GetSize() + local children = self:GetChildren() - local x_min, y_min, x_max, y_max = pos.x, pos.y, pos.x + size.w, pos.y + size.h + local x_min, y_min, x_max, y_max = pos.x, pos.y, pos.x + size.w, pos.y + size.h - -- iterate over children - for i = 1, #children do - local elem_str = children[i] - local elem = hudelements.GetStored(elem_str) + -- iterate over children + for i = 1, #children do + local elem_str = children[i] + local elem = hudelements.GetStored(elem_str) - local hud = huds.GetStored(HUDManager.GetHUD()) + local hud = huds.GetStored(HUDManager.GetHUD()) - if elem and elem:InheritParentBorder() and hud:ShouldShow(elem.type) and elem:ShouldDraw() then - local c_pos = elem:GetPos() - local c_size = elem:GetSize() + if + elem + and elem:InheritParentBorder() + and hud:ShouldShow(elem.type) + and elem:ShouldDraw() + then + local c_pos = elem:GetPos() + local c_size = elem:GetSize() - x_min = math.min(x_min, c_pos.x) - y_min = math.min(y_min, c_pos.y) - x_max = math.max(x_max, c_pos.x + c_size.w) - y_max = math.max(y_max, c_pos.y + c_size.h) - end - end + x_min = math.min(x_min, c_pos.x) + y_min = math.min(y_min, c_pos.y) + x_max = math.max(x_max, c_pos.x + c_size.w) + y_max = math.max(y_max, c_pos.y + c_size.h) + end + end - return {x = x_min, y = y_min}, {w = x_max - x_min, h = y_max - y_min} + return { x = x_min, y = y_min }, { w = x_max - x_min, h = y_max - y_min } end --- @@ -401,12 +418,12 @@ end -- @return boolean -- @realm client function HUDELEMENT:IsInRange(x, y, range) - range = range or 0 + range = range or 0 - local minX, minY = self.pos.x, self.pos.y - local maxX, maxY = minX + self.size.w, minY + self.size.h + local minX, minY = self.pos.x, self.pos.y + local maxX, maxY = minX + self.size.w, minY + self.size.h - return x - range <= maxX and x + range >= minX and y - range <= maxY and y + range >= minY + return x - range <= maxX and x + range >= minX and y - range <= maxY and y + range >= minY end --- @@ -416,7 +433,7 @@ end -- @return boolean -- @realm client function HUDELEMENT:IsInPos(x, y) - return self:IsInRange(x, y, 0) + return self:IsInRange(x, y, 0) end --- @@ -428,56 +445,56 @@ end -- @local -- @realm client function HUDELEMENT:OnHovered(x, y) - if self:IsChild() then -- children are not resizeable - return {false, false, false}, {false, false, false} - end - - local minX, minY = self.pos.x, self.pos.y - local maxX, maxY = minX + self.size.w, minY + self.size.h - - local c_pad, c_area = self.defaults.click_padding, self.defaults.click_area - local res_x, res_y = self:IsResizable() - - local row, col - - -- ROWS - if res_y then - row = { - y > minY + c_pad and y < minY + c_pad + c_area, -- top row - y > minY + 2 * c_pad + c_area and y < maxY - 2 * c_pad - c_area, -- center column - y > maxY - c_pad - c_area and y < maxY - c_pad -- right column - } - else - row = { - false, -- top row - y > minY + c_pad and y < maxY - c_pad, -- center column - false -- right column - } - end - - -- COLUMS - if res_x then - col = { - x > minX + c_pad and x < minX + c_pad + c_area, -- left column - x > minX + 2 * c_pad + c_area and x < maxX - 2 * c_pad - c_area, -- center column - x > maxX - c_pad - c_area and x < maxX - c_pad -- right column - } - else - col = { - false, -- left column - x > minX + c_pad and x < maxX - c_pad, -- center column - false -- right column - } - end - - -- locked aspect ratio has to be a special case to not break movement - -- ignore if mouse is on center - if self:AspectRatioIsLocked() and not (row[2] and col[2]) then - row[2] = false - col[2] = false - end - - return row, col + if self:IsChild() then -- children are not resizeable + return { false, false, false }, { false, false, false } + end + + local minX, minY = self.pos.x, self.pos.y + local maxX, maxY = minX + self.size.w, minY + self.size.h + + local c_pad, c_area = self.defaults.click_padding, self.defaults.click_area + local res_x, res_y = self:IsResizable() + + local row, col + + -- ROWS + if res_y then + row = { + y > minY + c_pad and y < minY + c_pad + c_area, -- top row + y > minY + 2 * c_pad + c_area and y < maxY - 2 * c_pad - c_area, -- center column + y > maxY - c_pad - c_area and y < maxY - c_pad, -- right column + } + else + row = { + false, -- top row + y > minY + c_pad and y < maxY - c_pad, -- center column + false, -- right column + } + end + + -- COLUMS + if res_x then + col = { + x > minX + c_pad and x < minX + c_pad + c_area, -- left column + x > minX + 2 * c_pad + c_area and x < maxX - 2 * c_pad - c_area, -- center column + x > maxX - c_pad - c_area and x < maxX - c_pad, -- right column + } + else + col = { + false, -- left column + x > minX + c_pad and x < maxX - c_pad, -- center column + false, -- right column + } + end + + -- locked aspect ratio has to be a special case to not break movement + -- ignore if mouse is on center + if self:AspectRatioIsLocked() and not (row[2] and col[2]) then + row[2] = false + col[2] = false + end + + return row, col end --- @@ -487,61 +504,61 @@ end -- @local -- @realm client function HUDELEMENT:DrawHovered(x, y) - if not self:IsInPos(x, y) then - return false - end - - local minX, minY = self.pos.x, self.pos.y - local maxX, maxY = minX + self.size.w, minY + self.size.h - local c_pad, c_area = self.defaults.click_padding, self.defaults.click_area - local res_x, res_y = self:IsResizable() - - local row, col = self:OnHovered(x, y) - local x1, x2, y1, y2 = 0, 0, 0, 0 - - if row[1] then -- resizeable in all directions - y1 = minY + c_pad - y2 = minY + c_pad + c_area - elseif row[2] and (col[1] or col[3]) and not res_y then -- only resizeable in X - y1 = minY + c_pad - y2 = maxY - c_pad - elseif row[2] and not res_y then -- only resizeable in X / show center area - y1 = minY + c_pad - y2 = maxY - c_pad - elseif row[2] then -- resizeable in all directions / show center area - y1 = minY + 2 * c_pad + c_area - y2 = maxY - 2 * c_pad - c_area - elseif row[3] then -- resizeable in all directions - y1 = maxY - c_pad - c_area - y2 = maxY - c_pad - end - - if col[1] then -- resizeable in all directions - x1 = minX + c_pad - x2 = minX + c_pad + c_area - elseif col[2] and (row[1] or row[3]) and not res_x then -- only resizeable in Y - x1 = minX + c_pad - x2 = maxX - c_pad - elseif col[2] and not res_x then -- only resizeable in Y / show center area - x1 = minX + c_pad - x2 = maxX - c_pad - elseif col[2] then -- resizeable in all directions / show center area - x1 = minX + 2 * c_pad + c_area - x2 = maxX - 2 * c_pad - c_area - elseif col[3] then -- resizeable in all directions - x1 = maxX - c_pad - c_area - x2 = maxX - c_pad - end - - -- set color - if row[2] and col[2] then - surface.SetDrawColor(20, 150, 245, 155) - else - surface.SetDrawColor(245, 30, 80, 155) - end - - -- draw rect - surface.DrawRect(x1, y1, x2 - x1, y2 - y1) + if not self:IsInPos(x, y) then + return false + end + + local minX, minY = self.pos.x, self.pos.y + local maxX, maxY = minX + self.size.w, minY + self.size.h + local c_pad, c_area = self.defaults.click_padding, self.defaults.click_area + local res_x, res_y = self:IsResizable() + + local row, col = self:OnHovered(x, y) + local x1, x2, y1, y2 = 0, 0, 0, 0 + + if row[1] then -- resizeable in all directions + y1 = minY + c_pad + y2 = minY + c_pad + c_area + elseif row[2] and (col[1] or col[3]) and not res_y then -- only resizeable in X + y1 = minY + c_pad + y2 = maxY - c_pad + elseif row[2] and not res_y then -- only resizeable in X / show center area + y1 = minY + c_pad + y2 = maxY - c_pad + elseif row[2] then -- resizeable in all directions / show center area + y1 = minY + 2 * c_pad + c_area + y2 = maxY - 2 * c_pad - c_area + elseif row[3] then -- resizeable in all directions + y1 = maxY - c_pad - c_area + y2 = maxY - c_pad + end + + if col[1] then -- resizeable in all directions + x1 = minX + c_pad + x2 = minX + c_pad + c_area + elseif col[2] and (row[1] or row[3]) and not res_x then -- only resizeable in Y + x1 = minX + c_pad + x2 = maxX - c_pad + elseif col[2] and not res_x then -- only resizeable in Y / show center area + x1 = minX + c_pad + x2 = maxX - c_pad + elseif col[2] then -- resizeable in all directions / show center area + x1 = minX + 2 * c_pad + c_area + x2 = maxX - 2 * c_pad - c_area + elseif col[3] then -- resizeable in all directions + x1 = maxX - c_pad - c_area + x2 = maxX - c_pad + end + + -- set color + if row[2] and col[2] then + surface.SetDrawColor(20, 150, 245, 155) + else + surface.SetDrawColor(245, 30, 80, 155) + end + + -- draw rect + surface.DrawRect(x1, y1, x2 - x1, y2 - y1) end --- @@ -554,47 +571,47 @@ end -- @local -- @realm client function HUDELEMENT:GetClickedArea(x, y, alt_pressed) - alt_pressed = alt_pressed or false + alt_pressed = alt_pressed or false - local row, col + local row, col - if self.edit_live_data.calc_new_click_area then - if not self:IsInPos(x, y) then - return false - end + if self.edit_live_data.calc_new_click_area then + if not self:IsInPos(x, y) then + return false + end - row, col = self:OnHovered(x, y) - self.edit_live_data.old_row = row - self.edit_live_data.old_col = col + row, col = self:OnHovered(x, y) + self.edit_live_data.old_row = row + self.edit_live_data.old_col = col - self.edit_live_data.calc_new_click_area = false - else - row = self.edit_live_data.old_row - col = self.edit_live_data.old_col - end + self.edit_live_data.calc_new_click_area = false + else + row = self.edit_live_data.old_row + col = self.edit_live_data.old_col + end - if row == nil or col == nil then - return false - end + if row == nil or col == nil then + return false + end - -- cache for shorter access - local x_p = col[3] and (row[1] or row[2] or row[3]) - local x_m = col[1] and (row[1] or row[2] or row[3]) - local y_p = row[3] and (col[1] or col[2] or col[3]) - local y_m = row[1] and (col[1] or col[2] or col[3]) + -- cache for shorter access + local x_p = col[3] and (row[1] or row[2] or row[3]) + local x_m = col[1] and (row[1] or row[2] or row[3]) + local y_p = row[3] and (col[1] or col[2] or col[3]) + local y_m = row[1] and (col[1] or col[2] or col[3]) - local ret_transform_axis = { - x_p = x_p or (alt_pressed and x_m) or false, - x_m = x_m or (alt_pressed and x_p) or false, - y_p = y_p or (alt_pressed and y_m) or false, - y_m = y_m or (alt_pressed and y_p) or false, - edge = (col[1] or col[3]) and (row[1] or row[3]), - direction_x = x_p and 1 or - 1, - direction_y = y_p and 1 or - 1, - move = row[2] and col[2] - } + local ret_transform_axis = { + x_p = x_p or (alt_pressed and x_m) or false, + x_m = x_m or (alt_pressed and x_p) or false, + y_p = y_p or (alt_pressed and y_m) or false, + y_m = y_m or (alt_pressed and y_p) or false, + edge = (col[1] or col[3]) and (row[1] or row[3]), + direction_x = x_p and 1 or -1, + direction_y = y_p and 1 or -1, + move = row[2] and col[2], + } - return ret_transform_axis + return ret_transform_axis end -- @todo the active area should only be changed on mouse click @@ -607,9 +624,10 @@ end -- @local -- @realm client function HUDELEMENT:SetMouseClicked(mouse_clicked, x, y) - if self:IsInPos(x, y) then - self.edit_live_data.calc_new_click_area = mouse_clicked or self.edit_live_data.calc_new_click_area - end + if self:IsInPos(x, y) then + self.edit_live_data.calc_new_click_area = mouse_clicked + or self.edit_live_data.calc_new_click_area + end end --- @@ -617,22 +635,30 @@ end -- @local -- @realm client function HUDELEMENT:DrawSize() - local x, y, w, h = self.pos.x, self.pos.y, self.size.w, self.size.h + local x, y, w, h = self.pos.x, self.pos.y, self.size.w, self.size.h - surface.SetDrawColor(255, 0, 0, 255) - surface.DrawLine(x, y, x + w, y) -- top - surface.DrawLine(x + 1, y + 1, x + w - 1, y + 1) -- top + surface.SetDrawColor(255, 0, 0, 255) + surface.DrawLine(x, y, x + w, y) -- top + surface.DrawLine(x + 1, y + 1, x + w - 1, y + 1) -- top - surface.DrawLine(x + w, y, x + w, y + h) -- right - surface.DrawLine(x + w - 1, y + 1, x + w - 1, y + h - 1) -- right + surface.DrawLine(x + w, y, x + w, y + h) -- right + surface.DrawLine(x + w - 1, y + 1, x + w - 1, y + h - 1) -- right - surface.DrawLine(x, y + h, x + w, y + h) -- bottom - surface.DrawLine(x + 1, y + h - 1, x + w - 1, y + h - 1) -- bottom + surface.DrawLine(x, y + h, x + w, y + h) -- bottom + surface.DrawLine(x + 1, y + h - 1, x + w - 1, y + h - 1) -- bottom - surface.DrawLine(x, y, x, y + h) -- left - surface.DrawLine(x + 1, y + 1, x + 1, y + h - 1) -- left + surface.DrawLine(x, y, x, y + h) -- left + surface.DrawLine(x + 1, y + 1, x + 1, y + h - 1) -- left - draw.DrawText(self.id, "DermaDefault", x + w * 0.5, y + h * 0.5 - 7, COLOR_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.DrawText( + self.id, + "DermaDefault", + x + w * 0.5, + y + h * 0.5 - 7, + COLOR_WHITE, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) end --- @@ -642,11 +668,11 @@ end -- @return table basepos, size and minsize fields -- @realm client function HUDELEMENT:GetDefaults() - return { - basepos = table.Copy(self.basepos), - size = table.Copy(self.size), - minsize = table.Copy(self.minsize) - } + return { + basepos = table.Copy(self.basepos), + size = table.Copy(self.size), + minsize = table.Copy(self.minsize), + } end --- @@ -654,31 +680,31 @@ end -- original position. -- @realm client function HUDELEMENT:Reset() - local defaults = self:GetDefaults() - local defaultPos = defaults.basepos - local defaultSize = defaults.size - local defaultMinSize = defaults.minsize + local defaults = self:GetDefaults() + local defaultPos = defaults.basepos + local defaultSize = defaults.size + local defaultMinSize = defaults.minsize - if defaultPos then - self:SetBasePos(defaultPos.x, defaultPos.y) - end + if defaultPos then + self:SetBasePos(defaultPos.x, defaultPos.y) + end - if defaultMinSize then - self:SetMinSize(defaultMinSize.w, defaultMinSize.h) - end + if defaultMinSize then + self:SetMinSize(defaultMinSize.w, defaultMinSize.h) + end - if defaultSize then - self:SetSize(defaultSize.w, defaultSize.h) - end + if defaultSize then + self:SetSize(defaultSize.w, defaultSize.h) + end - self:ApplyToChildren("Reset") + self:ApplyToChildren("Reset") - self:PerformLayout() + self:PerformLayout() end local savingKeys = { - basepos = {typ = "pos"}, - size = {typ = "size"} + basepos = { typ = "pos" }, + size = { typ = "size" }, } --- @@ -686,50 +712,52 @@ local savingKeys = { -- @return table with savable keys -- @realm client function HUDELEMENT:GetSavingKeys() - return table.Copy(savingKeys) + return table.Copy(savingKeys) end --- -- Saves the current savingkey values (position, size) -- @realm client function HUDELEMENT:SaveData() - sql.Save("ttt2_hudelements", self.id, self, self:GetSavingKeys()) + sql.Save("ttt2_hudelements", self.id, self, self:GetSavingKeys()) end --- -- Loads the saved keys and applies them to the element -- @realm client function HUDELEMENT:LoadData() - local skeys = self:GetSavingKeys() - local loadedData = {} + local skeys = self:GetSavingKeys() + local loadedData = {} - -- load and initialize the elements data from database - if sql.CreateSqlTable("ttt2_hudelements", skeys) then - local loaded = sql.Load("ttt2_hudelements", self.id, loadedData, skeys) - if not loaded then - sql.Init("ttt2_hudelements", self.id, self, skeys) - end - end + -- load and initialize the elements data from database + if sql.CreateSqlTable("ttt2_hudelements", skeys) then + local loaded = sql.Load("ttt2_hudelements", self.id, loadedData, skeys) + if not loaded then + sql.Init("ttt2_hudelements", self.id, self, skeys) + end + end - if loadedData.pos then - self:SetPos(loadedData.pos.x, loadedData.pos.y) + if loadedData.pos then + self:SetPos(loadedData.pos.x, loadedData.pos.y) - loadedData.pos = nil - end + loadedData.pos = nil + end - if loadedData.basepos then - self:SetBasePos(loadedData.basepos.x, loadedData.basepos.y) + if loadedData.basepos then + self:SetBasePos(loadedData.basepos.x, loadedData.basepos.y) - loadedData.basepos = nil - end + loadedData.basepos = nil + end - if loadedData.size then - self:SetSize(loadedData.size.w, loadedData.size.h) + if loadedData.size then + self:SetSize(loadedData.size.w, loadedData.size.h) - loadedData.size = nil - end + loadedData.size = nil + end - for k, v in pairs(loadedData) do - self[k] = v or self[k] - end + for k, v in pairs(loadedData) do + if v ~= nil then + self[k] = v + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/old_ttt_element.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/old_ttt_element.lua index aa72c8d7f..8c20eee73 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/old_ttt_element.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/old_ttt_element.lua @@ -13,162 +13,171 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - --- - -- @realm client - local hudWidth = CreateConVar("ttt2_base_hud_width", "0", FCVAR_ARCHIVE) - - -- Color presets - HUDELEMENT.bg_colors = { - background_main = Color(0, 0, 10, 200), - noround = Color(100, 100, 100, 200) - } - - HUDELEMENT.health_colors = { - border = COLOR_WHITE, - background = Color(100, 25, 25, 222), - fill = Color(200, 50, 50, 250) - } - - HUDELEMENT.ammo_colors = { - border = COLOR_WHITE, - background = Color(20, 20, 5, 222), - fill = Color(205, 155, 0, 255) - } - - HUDELEMENT.sprint_colors = { - border = COLOR_WHITE, - background = Color(10, 50, 73, 222), - fill = Color(36, 154, 198, 255) - } - - -- Modified RoundedBox - HUDELEMENT.Tex_Corner8 = surface.GetTextureID("gui/corner8") - - --- - -- Draws a rounded bar - -- The bar painting is loosely based on: http://wiki.garrysmod.com/?title=Creating_a_HUD - -- @param number bs - -- @param number x - -- @param number y - -- @param number w - -- @param number h - -- @param Color color - -- @realm client - function HUDELEMENT:RoundedMeter(bs, x, y, w, h, color) - surface.SetDrawColor(clr(color)) - - surface.DrawRect(x + bs, y, w - bs * 2, h) - surface.DrawRect(x, y + bs, bs, h - bs * 2) - - surface.SetTexture(self.Tex_Corner8) - surface.DrawTexturedRectRotated(x + bs * 0.5, y + bs * 0.5, bs, bs, 0) - surface.DrawTexturedRectRotated(x + bs * 0.5, y + h - bs * 0.5, bs, bs, 90) - - if w > 14 then - surface.DrawRect(x + w - bs, y + bs, bs, h - bs * 2) - surface.DrawTexturedRectRotated(x + w - bs * 0.5, y + bs * 0.5, bs, bs, 270) - surface.DrawTexturedRectRotated(x + w - bs * 0.5, y + h - bs * 0.5, bs, bs, 180) - else - surface.DrawRect(x + math.max(w - bs, bs), y, bs * 0.5, h) - end - end - - --- - -- Paints the main bar area - -- @param number x - -- @param number y - -- @param number w - -- @param number h - -- @param table colors Table of @{Color}. There need to be a .background and a .fill attribute - -- @param number value - -- @realm client - function HUDELEMENT:PaintBar(x, y, w, h, colors, value) - value = value or 1 - - -- Background - -- slightly enlarged to make a subtle border - draw.RoundedBox(8, x - 1, y - 1, w + 2, h + 2, colors.background) - - -- Fill - local width = w * math.Clamp(value, 0, 1) - if width <= 0 then return end - - self:RoundedMeter(8, x, y, width, h, colors.fill) - end - - HUDELEMENT.roundstate_string = { - [ROUND_WAIT] = "round_wait", - [ROUND_PREP] = "round_prep", - [ROUND_ACTIVE] = "round_active", - [ROUND_POST] = "round_post" - } - - HUDELEMENT.margin = 10 - HUDELEMENT.dmargin = HUDELEMENT.margin * 2 - HUDELEMENT.smargin = 2 - HUDELEMENT.maxheight = 90 - HUDELEMENT.maxwidth = hudWidth:GetInt() + HUDELEMENT.maxheight + HUDELEMENT.margin + 170 - HUDELEMENT.hastewidth = 80 - HUDELEMENT.bgheight = 30 - - --- - -- Returns player's ammo information - -- @param Player ply - -- @return number ammo in the current clip - -- @return number maximum ammo of the current clip - -- @return number ammo in the inventory - -- @realm client - function HUDELEMENT:GetAmmo(ply) - local weap = ply:GetActiveWeapon() - - if not weap or not ply:Alive() then - return -1 - end - - local ammo_inv = weap.Ammo1 and weap:Ammo1() or 0 - local ammo_clip = weap:Clip1() or 0 - local ammo_max = weap.Primary.ClipSize or 0 - - return ammo_clip, ammo_max, ammo_inv - end - - --- - -- Draws the main background - -- @param number x - -- @param number y - -- @param number width - -- @param number height - -- @param Player client should be the LocalPlayer() - -- @realm client - function HUDELEMENT:DrawBg(x, y, width, height, client) - -- Traitor area sizes - local th = self.bgheight - local tw = width - self.hastewidth - self.bgheight - self.smargin * 2 -- bgheight = team icon - - -- Adjust for these - y = y - th - height = height + th - - -- main bg area, invariant - -- encompasses entire area - draw.RoundedBox(8, x, y, width, height, self.bg_colors.background_main) - - -- main border, role based - draw.RoundedBox(8, x, y, tw, th, GAMEMODE.round_state ~= ROUND_ACTIVE and self.bg_colors.noround or client:GetRoleColor()) - end - - --- - -- Draws a shadowed text - -- @param string text - -- @param string font - -- @param number x - -- @param number y - -- @param Color color - -- @param number xalign - -- @param number yalign - -- @realm client - function HUDELEMENT:ShadowedText(text, font, x, y, color, xalign, yalign) - draw.SimpleText(text, font, x + 2, y + 2, COLOR_BLACK, xalign, yalign) - draw.SimpleText(text, font, x, y, color, xalign, yalign) - end + --- + -- @realm client + -- stylua: ignore + local hudWidth = CreateConVar("ttt2_base_hud_width", "0", FCVAR_ARCHIVE) + + -- Color presets + HUDELEMENT.bg_colors = { + background_main = Color(0, 0, 10, 200), + noround = Color(100, 100, 100, 200), + } + + HUDELEMENT.health_colors = { + border = COLOR_WHITE, + background = Color(100, 25, 25, 222), + fill = Color(200, 50, 50, 250), + } + + HUDELEMENT.ammo_colors = { + border = COLOR_WHITE, + background = Color(20, 20, 5, 222), + fill = Color(205, 155, 0, 255), + } + + HUDELEMENT.sprint_colors = { + border = COLOR_WHITE, + background = Color(10, 50, 73, 222), + fill = Color(36, 154, 198, 255), + } + + -- Modified RoundedBox + HUDELEMENT.Tex_Corner8 = surface.GetTextureID("gui/corner8") + + --- + -- Draws a rounded bar + -- @param number bs + -- @param number x + -- @param number y + -- @param number w + -- @param number h + -- @param Color color + -- @realm client + function HUDELEMENT:RoundedMeter(bs, x, y, w, h, color) + surface.SetDrawColor(clr(color)) + + surface.DrawRect(x + bs, y, w - bs * 2, h) + surface.DrawRect(x, y + bs, bs, h - bs * 2) + + surface.SetTexture(self.Tex_Corner8) + surface.DrawTexturedRectRotated(x + bs * 0.5, y + bs * 0.5, bs, bs, 0) + surface.DrawTexturedRectRotated(x + bs * 0.5, y + h - bs * 0.5, bs, bs, 90) + + if w > 14 then + surface.DrawRect(x + w - bs, y + bs, bs, h - bs * 2) + surface.DrawTexturedRectRotated(x + w - bs * 0.5, y + bs * 0.5, bs, bs, 270) + surface.DrawTexturedRectRotated(x + w - bs * 0.5, y + h - bs * 0.5, bs, bs, 180) + else + surface.DrawRect(x + math.max(w - bs, bs), y, bs * 0.5, h) + end + end + + --- + -- Paints the main bar area + -- @param number x + -- @param number y + -- @param number w + -- @param number h + -- @param table colors Table of @{Color}. There need to be a .background and a .fill attribute + -- @param number value + -- @realm client + function HUDELEMENT:PaintBar(x, y, w, h, colors, value) + value = value or 1 + + -- Background + -- slightly enlarged to make a subtle border + draw.RoundedBox(8, x - 1, y - 1, w + 2, h + 2, colors.background) + + -- Fill + local width = w * math.Clamp(value, 0, 1) + if width <= 0 then + return + end + + self:RoundedMeter(8, x, y, width, h, colors.fill) + end + + HUDELEMENT.roundstate_string = { + [ROUND_WAIT] = "round_wait", + [ROUND_PREP] = "round_prep", + [ROUND_ACTIVE] = "round_active", + [ROUND_POST] = "round_post", + } + + HUDELEMENT.margin = 10 + HUDELEMENT.dmargin = HUDELEMENT.margin * 2 + HUDELEMENT.smargin = 2 + HUDELEMENT.maxheight = 90 + HUDELEMENT.maxwidth = hudWidth:GetInt() + HUDELEMENT.maxheight + HUDELEMENT.margin + 170 + HUDELEMENT.hastewidth = 80 + HUDELEMENT.bgheight = 30 + + --- + -- Returns player's ammo information + -- @param Player ply + -- @return number ammo in the current clip + -- @return number maximum ammo of the current clip + -- @return number ammo in the inventory + -- @realm client + function HUDELEMENT:GetAmmo(ply) + local weap = ply:GetActiveWeapon() + + if not weap or not ply:Alive() then + return -1 + end + + local ammo_inv = weap.Ammo1 and weap:Ammo1() or 0 + local ammo_clip = weap:Clip1() or 0 + local ammo_max = weap.Primary.ClipSize or 0 + + return ammo_clip, ammo_max, ammo_inv + end + + --- + -- Draws the main background + -- @param number x + -- @param number y + -- @param number width + -- @param number height + -- @param Player client should be the LocalPlayer() + -- @realm client + function HUDELEMENT:DrawBg(x, y, width, height, client) + -- Traitor area sizes + local th = self.bgheight + local tw = width - self.hastewidth - self.bgheight - self.smargin * 2 -- bgheight = team icon + + -- Adjust for these + y = y - th + height = height + th + + -- main bg area, invariant + -- encompasses entire area + draw.RoundedBox(8, x, y, width, height, self.bg_colors.background_main) + + -- main border, role based + draw.RoundedBox( + 8, + x, + y, + tw, + th, + GAMEMODE.round_state ~= ROUND_ACTIVE and self.bg_colors.noround or client:GetRoleColor() + ) + end + + --- + -- Draws a shadowed text + -- @param string text + -- @param string font + -- @param number x + -- @param number y + -- @param Color color + -- @param number xalign + -- @param number yalign + -- @realm client + function HUDELEMENT:ShadowedText(text, font, x, y, color, xalign, yalign) + draw.SimpleText(text, font, x + 2, y + 2, COLOR_BLACK, xalign, yalign) + draw.SimpleText(text, font, x, y, color, xalign, yalign) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/pure_skin_element.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/pure_skin_element.lua index 88d3bbe39..c2f1e3a2e 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/pure_skin_element.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/base_elements/pure_skin_element.lua @@ -11,75 +11,113 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - --- - -- Draws the main background - -- @param number x - -- @param number y - -- @param number w width - -- @param number h height - -- @param Color c color - -- @realm client - function HUDELEMENT:DrawBg(x, y, w, h, c) - DrawHUDElementBg(x, y, w, h, c) - end - - --- - -- Draws the shadow effect - -- @param number x - -- @param number y - -- @param number w width - -- @param number h height - -- @param number a alpha of line's color - -- @realm client - function HUDELEMENT:DrawLines(x, y, w, h, a) - DrawHUDElementLines(x, y, w, h, a or 255) - end - - --- - -- Draws the main background - -- @param number x - -- @param number y - -- @param number w width - -- @param number h height - -- @param Color c color - -- @param number p progress - -- @param number s scale - -- @param string t text - -- @realm client - function HUDELEMENT:DrawBar(x, y, w, h, c, p, s, t) - s = s or 1 - - surface.SetDrawColor(clr(c)) - surface.DrawRect(x, y, w, h) - - local w2 = math.Round(w * (p or 1)) - - surface.SetDrawColor(0, 0, 0, 165) - surface.DrawRect(x + w2, y, w - w2, h) - - -- draw lines around this bar - self:DrawLines(x, y, w, h, c.a) - - -- draw text - if t then - draw.AdvancedText(t, "PureSkinBar", x + 14, y + h * 0.5 - 1, util.GetDefaultColor(c), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, true, s) - end - end - - --- - -- Returns @{Color} white OR black based on the bgcolor - -- @param Color bgcolor background color - -- @realm client - -- @deprecated - -- @see util.GetDefaultColor - function HUDELEMENT:GetDefaultFontColor(bgcolor) - return util.GetDefaultColor(bgcolor) - end - - HUDELEMENT.roundstate_string = { - [ROUND_WAIT] = "round_wait", - [ROUND_PREP] = "round_prep", - [ROUND_ACTIVE] = "round_active", - [ROUND_POST] = "round_post" - } + --- + -- Draws the main background + -- @param number x + -- @param number y + -- @param number w width + -- @param number h height + -- @param Color c color + -- @realm client + function HUDELEMENT:DrawBg(x, y, w, h, c) + DrawHUDElementBg(x, y, w, h, c) + end + + --- + -- Draws the shadow effect + -- @param number x + -- @param number y + -- @param number w width + -- @param number h height + -- @param number a alpha of line's color + -- @realm client + function HUDELEMENT:DrawLines(x, y, w, h, a) + DrawHUDElementLines(x, y, w, h, a or 255) + end + + --- + -- Draws the main background + -- @param number x + -- @param number y + -- @param number w width + -- @param number h height + -- @param Color c color + -- @param number p progress + -- @param number s scale + -- @param string t text + -- @realm client + function HUDELEMENT:DrawBar(x, y, w, h, c, p, s, t) + s = s or 1 + + surface.SetDrawColor(clr(c)) + surface.DrawRect(x, y, w, h) + + local w2 = math.Round(w * (p or 1)) + + surface.SetDrawColor(0, 0, 0, 165) + surface.DrawRect(x + w2, y, w - w2, h) + + -- draw lines around this bar + self:DrawLines(x, y, w, h, c.a) + + -- draw text + if t then + draw.AdvancedText( + t, + "PureSkinBar", + x + 14, + y + h * 0.5 - 1, + util.GetDefaultColor(c), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + true, + s + ) + end + end + + --- + -- Returns @{Color} white OR black based on the bgcolor + -- @param Color bgcolor background color + -- @realm client + -- @deprecated + -- @see util.GetDefaultColor + function HUDELEMENT:GetDefaultFontColor(bgcolor) + return util.GetDefaultColor(bgcolor) + end + + HUDELEMENT.roundstate_string = { + [ROUND_WAIT] = "round_wait", + [ROUND_PREP] = "round_prep", + [ROUND_ACTIVE] = "round_active", + [ROUND_POST] = "round_post", + } + + HUDELEMENT.SlotIcons = { + [WEAPON_MELEE] = Material("vgui/ttt/slot/slot_weapon_melee"), + [WEAPON_PISTOL] = Material("vgui/ttt/slot/slot_weapon_pistol"), + [WEAPON_HEAVY] = Material("vgui/ttt/slot/slot_weapon_heavy"), + [WEAPON_NADE] = Material("vgui/ttt/slot/slot_weapon_nade"), + [WEAPON_CARRY] = Material("vgui/ttt/slot/slot_weapon_carry"), + [WEAPON_UNARMED] = Material("vgui/ttt/slot/slot_weapon_unarmed"), + [WEAPON_SPECIAL] = Material("vgui/ttt/slot/slot_weapon_special"), + [WEAPON_EXTRA] = Material("vgui/ttt/slot/slot_weapon_extra"), + [WEAPON_CLASS] = Material("vgui/ttt/slot/slot_weapon_class"), + } + + HUDELEMENT.BulletIcons = { + ["357"] = Material("vgui/ttt/ammo/bullet_357"), + ["buckshot"] = Material("vgui/ttt/ammo/bullet_buckshot"), + ["smg1"] = Material("vgui/ttt/ammo/bullet_smg1"), + ["pistol"] = Material("vgui/ttt/ammo/bullet_pistol"), + ["alyxgun"] = Material("vgui/ttt/ammo/bullet_alyxgun"), + } + + HUDELEMENT.AmmoIcons = { + ["357"] = Material("vgui/ttt/ammo/box_357"), + ["buckshot"] = Material("vgui/ttt/ammo/box_buckshot"), + ["smg1"] = Material("vgui/ttt/ammo/box_smg1"), + ["pistol"] = Material("vgui/ttt/ammo/box_pistol"), + ["alyxgun"] = Material("vgui/ttt/ammo/box_alyxgun"), + } end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttdnascanner/pure_skin_dnascanner.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttdnascanner/pure_skin_dnascanner.lua index 422c5d043..cb7ce2444 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttdnascanner/pure_skin_dnascanner.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttdnascanner/pure_skin_dnascanner.lua @@ -7,146 +7,185 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local dna = Material("vgui/ttt/dnascanner/dna_hud") - - local pad = 14 - local iconSize = 64 - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 326, h = 92}, - minsize = {w = 326, h = 92} - } - - function HUDELEMENT:Initialize() - self.scale = 1 - self.pad = pad - self.iconSize = iconSize - self.basecolor = self:GetHUDBasecolor() - self.slotCount = 4 - - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return false, false - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = math.Round(ScrW() * 0.5 - self.size.w * 0.5), y = ScrH() - self.size.h - 105} - - return const_defaults - end - - function HUDELEMENT:ShouldDraw() - local client = LocalPlayer() - local scanner = client:GetWeapon("weapon_ttt_wtester") - - return HUDEditor.IsEditing or IsValid(scanner) and client:GetActiveWeapon() == scanner and client:Alive() - end - - function HUDELEMENT:PerformLayout() - self.scale = self:GetHUDScale() - self.basecolor = self:GetHUDBasecolor() - self.pad = pad * self.scale - self.iconSize = iconSize * self.scale - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:DrawMarker(x, y, size, color) - local thickness = 2 * self.scale - local margin = 3 * self.scale - local marker_x = x - margin - thickness - local marker_y = y - margin - thickness - local marker_size = size + margin * 2 + thickness * 2 - - surface.SetDrawColor(color) - - for i = 0, thickness - 1 do - surface.DrawOutlinedRect( marker_x + i, marker_y + i, marker_size - i * 2, marker_size - i * 2 ) - end - end - - function HUDELEMENT:GetAlpha(selectTime) - local time_left = CurTime() - selectTime - - if time_left < 2 then - local num = 0.5 * math.pi + (-2.0 * time_left + 7) * math.pi - local factor = 0.5 * (math.sin(num) + 1) - - return 20 + 235 * ( 1 - factor) - end - - return 255 - end - - function HUDELEMENT:Draw() - local client = LocalPlayer() - local pos = self:GetPos() - local size = self:GetSize() - local x, y = pos.x, pos.y - local w, h = size.w, size.h - local scanner = client:GetWeapon("weapon_ttt_wtester") - - --fake scanner for HUD editing - if not IsValid(scanner) then - scanner = {ItemSamples = {true}, ScanSuccess = 0, NewSample = 0, ScanTime = 0, ActiveSample = 3} - end - - local slotCount = GetGlobalBool("ttt2_dna_scanner_slots") - local newWidth = self.pad + slotCount * (self.pad + self.iconSize) - - if newWidth ~= size.w then - local newX = pos.x + size.w * 0.5 - newWidth * 0.5 - w = newWidth - x = newX - end - - -- draw bg and shadow - self:DrawBg(x, y, w, h, self.basecolor) - - local tmp_x = x + self.pad - local tmp_y = y + self.pad - local icon_size = 64 * self.scale - local label_offset = 8 * self.scale - - for i = 1, GetGlobalBool("ttt2_dna_scanner_slots") do - local identifier = string.char(64 + i) - - --draw background - surface.SetDrawColor(50, 50, 50, 255) - surface.DrawRect(tmp_x, tmp_y, icon_size, icon_size) - - if scanner.ItemSamples[i] then - local alpha = 255 - - if scanner.ScanSuccess > 0 and scanner.NewSample == i then - alpha = self:GetAlpha(scanner.ScanTime) - end - - surface.SetDrawColor(40, 120, 40, alpha) - surface.DrawRect(tmp_x, tmp_y, icon_size, icon_size) - - draw.FilteredShadowedTexture(tmp_x + 3, tmp_y + 3, icon_size - 6, icon_size - 6, dna, 190, COLOR_WHITE) - else - draw.FilteredTexture(tmp_x + 3, tmp_y + 3, icon_size - 6, icon_size - 6, dna, 150, COLOR_BLACK) - end - - if scanner.ActiveSample == i then - self:DrawMarker(tmp_x, tmp_y, icon_size, COLOR_WHITE) - end - - draw.AdvancedText(identifier, "PureSkinMSTACKMsg", tmp_x + label_offset, tmp_y + label_offset, COLOR_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, true, self.scale) - - self:DrawLines(tmp_x, tmp_y, icon_size, icon_size, 255) - - tmp_x = tmp_x + self.pad + icon_size - end - - -- draw lines around the element - self:DrawLines(x, y, w, h, self.basecolor.a) - end + local dna = Material("vgui/ttt/dnascanner/dna_hud") + + local pad = 14 + local iconSize = 64 + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 326, h = 92 }, + minsize = { w = 326, h = 92 }, + } + + function HUDELEMENT:Initialize() + self.scale = 1 + self.pad = pad + self.iconSize = iconSize + self.basecolor = self:GetHUDBasecolor() + self.slotCount = 4 + + BaseClass.Initialize(self) + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return false, false + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = + { x = math.Round(ScrW() * 0.5 - self.size.w * 0.5), y = ScrH() - self.size.h - 105 } + + return const_defaults + end + + function HUDELEMENT:ShouldDraw() + local client = LocalPlayer() + local scanner = client:GetWeapon("weapon_ttt_wtester") + + return HUDEditor.IsEditing + or IsValid(scanner) and client:GetActiveWeapon() == scanner and client:Alive() + end + + function HUDELEMENT:PerformLayout() + self.scale = self:GetHUDScale() + self.basecolor = self:GetHUDBasecolor() + self.pad = pad * self.scale + self.iconSize = iconSize * self.scale + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:DrawMarker(x, y, size, color) + local thickness = 2 * self.scale + local margin = 3 * self.scale + local marker_x = x - margin - thickness + local marker_y = y - margin - thickness + local marker_size = size + margin * 2 + thickness * 2 + + surface.SetDrawColor(color) + + for i = 0, thickness - 1 do + surface.DrawOutlinedRect( + marker_x + i, + marker_y + i, + marker_size - i * 2, + marker_size - i * 2 + ) + end + end + + function HUDELEMENT:GetAlpha(selectTime) + local time_left = CurTime() - selectTime + + if time_left < 2 then + local num = 0.5 * math.pi + (-2.0 * time_left + 7) * math.pi + local factor = 0.5 * (math.sin(num) + 1) + + return 20 + 235 * (1 - factor) + end + + return 255 + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + local pos = self:GetPos() + local size = self:GetSize() + local x, y = pos.x, pos.y + local w, h = size.w, size.h + local scanner = client:GetWeapon("weapon_ttt_wtester") + + --fake scanner for HUD editing + if not IsValid(scanner) then + scanner = { + ItemSamples = { true }, + ScanSuccess = 0, + NewSample = 0, + ScanTime = 0, + ActiveSample = 3, + } + end + + local slotCount = GetGlobalBool("ttt2_dna_scanner_slots") + local newWidth = self.pad + slotCount * (self.pad + self.iconSize) + + if newWidth ~= size.w then + local newX = pos.x + size.w * 0.5 - newWidth * 0.5 + w = newWidth + x = newX + end + + -- draw bg and shadow + self:DrawBg(x, y, w, h, self.basecolor) + + local tmp_x = x + self.pad + local tmp_y = y + self.pad + local icon_size = 64 * self.scale + local label_offset = 8 * self.scale + + for i = 1, GetGlobalBool("ttt2_dna_scanner_slots") do + local identifier = string.char(64 + i) + + --draw background + surface.SetDrawColor(50, 50, 50, 255) + surface.DrawRect(tmp_x, tmp_y, icon_size, icon_size) + + if scanner.ItemSamples[i] then + local alpha = 255 + + if scanner.ScanSuccess > 0 and scanner.NewSample == i then + alpha = self:GetAlpha(scanner.ScanTime) + end + + surface.SetDrawColor(40, 120, 40, alpha) + surface.DrawRect(tmp_x, tmp_y, icon_size, icon_size) + + draw.FilteredShadowedTexture( + tmp_x + 3, + tmp_y + 3, + icon_size - 6, + icon_size - 6, + dna, + 190, + COLOR_WHITE + ) + else + draw.FilteredTexture( + tmp_x + 3, + tmp_y + 3, + icon_size - 6, + icon_size - 6, + dna, + 150, + COLOR_BLACK + ) + end + + if scanner.ActiveSample == i then + self:DrawMarker(tmp_x, tmp_y, icon_size, COLOR_WHITE) + end + + draw.AdvancedText( + identifier, + "PureSkinMSTACKMsg", + tmp_x + label_offset, + tmp_y + label_offset, + COLOR_WHITE, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + true, + self.scale + ) + + self:DrawLines(tmp_x, tmp_y, icon_size, icon_size, 255) + + tmp_x = tmp_x + self.pad + icon_size + end + + -- draw lines around the element + self:DrawLines(x, y, w, h, self.basecolor.a) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttdrowning/pure_skin_drowning.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttdrowning/pure_skin_drowning.lua index 1c642ea1f..3415de7d1 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttdrowning/pure_skin_drowning.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttdrowning/pure_skin_drowning.lua @@ -7,63 +7,75 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local pad = 14 - - local drowning_color = Color(36, 154, 198) - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 321, h = 36}, - minsize = {w = 75, h = 36} - } - - function HUDELEMENT:Initialize() - self.pad = pad - self.basecolor = self:GetHUDBasecolor() - - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return true, false - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = math.Round(ScrW() * 0.5 - self.size.w * 0.5), y = ScrH() - self.pad - self.size.h} - - return const_defaults - end - - function HUDELEMENT:ShouldDraw() - local client = LocalPlayer() - - return HUDEditor.IsEditing or client.drowningProgress and client:Alive() and client.drowningProgress ~= -1 - end - - function HUDELEMENT:PerformLayout() - local scale = appearance.GetGlobalScale() - - self.basecolor = self:GetHUDBasecolor() - self.pad = pad * scale - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:Draw() - local client = LocalPlayer() - local pos = self:GetPos() - local size = self:GetSize() - local x, y = pos.x, pos.y - local w, h = size.w, size.h - - -- draw bg and shadow - self:DrawBg(x, y, w, h, self.basecolor) - - self:DrawBar(x + self.pad, y + self.pad, w - self.pad * 2, h - self.pad * 2, drowning_color, HUDEditor.IsEditing and 1 or (client.drowningProgress or 1), 1) - - -- draw lines around the element - self:DrawLines(x, y, w, h, self.basecolor.a) - end + local pad = 14 + + local drowning_color = Color(36, 154, 198) + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 321, h = 36 }, + minsize = { w = 75, h = 36 }, + } + + function HUDELEMENT:Initialize() + self.pad = pad + self.basecolor = self:GetHUDBasecolor() + + BaseClass.Initialize(self) + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return true, false + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = { + x = math.Round(ScrW() * 0.5 - self.size.w * 0.5), + y = ScrH() - self.pad - self.size.h, + } + + return const_defaults + end + + function HUDELEMENT:ShouldDraw() + local client = LocalPlayer() + + return HUDEditor.IsEditing + or client.drowningProgress and client:Alive() and client.drowningProgress ~= -1 + end + + function HUDELEMENT:PerformLayout() + local scale = appearance.GetGlobalScale() + + self.basecolor = self:GetHUDBasecolor() + self.pad = pad * scale + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + local pos = self:GetPos() + local size = self:GetSize() + local x, y = pos.x, pos.y + local w, h = size.w, size.h + + -- draw bg and shadow + self:DrawBg(x, y, w, h, self.basecolor) + + self:DrawBar( + x + self.pad, + y + self.pad, + w - self.pad * 2, + h - self.pad * 2, + drowning_color, + HUDEditor.IsEditing and 1 or (client.drowningProgress or 1), + 1 + ) + + -- draw lines around the element + self:DrawLines(x, y, w, h, self.basecolor.a) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/ttteventpopup/pure_skin_eventpopup.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/ttteventpopup/pure_skin_eventpopup.lua index 71a6c320a..c0cb7fb38 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/ttteventpopup/pure_skin_eventpopup.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/ttteventpopup/pure_skin_eventpopup.lua @@ -7,150 +7,183 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 1000, h = 86}, - minsize = {w = 500, h = 86} - } + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 1000, h = 86 }, + minsize = { w = 500, h = 86 }, + } - local pad = 14 - local icon_size = 64 + local pad = 14 + local icon_size = 64 + + local titlefont = "PureSkinPopupTitle" + local textfont = "PureSkinPopupText" + + function HUDELEMENT:Initialize() + self.pad = pad + self.basecolor = self:GetHUDBasecolor() + + BaseClass.Initialize(self) + end - local titlefont = "PureSkinPopupTitle" - local textfont = "PureSkinPopupText" + -- parameter overwrites + function HUDELEMENT:IsResizable() + return true, false + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = { + x = math.Round((ScrW() - self.size.w) * 0.5), + y = math.Round((ScrH() - self.size.h) * 0.5) - 250, + } - function HUDELEMENT:Initialize() - self.pad = pad - self.basecolor = self:GetHUDBasecolor() + return const_defaults + end - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return true, false - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = { - x = math.Round((ScrW() - self.size.w) * 0.5), - y = math.Round((ScrH() - self.size.h) * 0.5) - 250 - } - - return const_defaults - end - - function HUDELEMENT:ShouldDraw() - return HUDEditor.IsEditing or EPOP:ShouldRender() - end - - function HUDELEMENT:PerformLayout() - self.scale = appearance.GetGlobalScale() - self.pad = pad * self.scale - self.icon_size = icon_size * self.scale - - self.basecolor = self:GetHUDBasecolor() - - -- reset item ready - if EPOP.msg then - EPOP.msg.ready = false - end - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:PrepareItem(item) - local size = self:GetSize() - local pos = self:GetPos() - local width_title, width_text, height_title, height_text = 0, 0, 0, 0 - local pad2 = 2 * self.pad - - -- wrap title if needed - item.wrappedTitle, width_title, height_title = draw.GetWrappedText(item.title.text, size.w - pad2, titlefont, self.scale) - - -- wrap text if needed - item.wrappedSubtitle, width_text, height_text = draw.GetWrappedText(item.subtitle.text, size.w - pad2, textfont, self.scale) - - item.size = {} - item.size.w = ((width_title > width_text) and width_title or width_text) + pad2 - item.size.h = height_title + pad2 + height_text + ((height_text > 0) and self.pad or 0) - - item.pos = {} - item.pos.y = pos.y - item.pos.x = pos.x + math.Round(0.5 * (size.w - item.size.w)) - item.pos.center_x = pos.x + math.Round(0.5 * size.w) - - local wrappedItems = #item.wrappedTitle - local wrappedTexts = #item.wrappedSubtitle - - -- precalculate text positions - local height_title_line = height_title / wrappedItems - local height_text_line = height_text / wrappedTexts - - item.pos.title_y = {} - - for i = 1, wrappedItems do - item.pos.title_y[i] = pos.y + self.pad + (i - 1) * height_title_line - end - - item.pos.text_y = {} - - for i = 1, wrappedTexts do - item.pos.text_y[i] = item.pos.title_y[wrappedItems] + height_title_line + self.pad + (i - 1) * height_text_line - end - - -- add item positions - local icon_amt = #item.iconTable - local icon_start_x = item.pos.center_x - math.Round(0.5 * (icon_amt * self.icon_size + math.max(0, icon_amt - 1) * self.pad)) - - item.pos.icon_y = item.pos.y + item.size.h + self.pad - item.pos.icon_x = {icon_start_x} - - for i = 1, icon_amt do - item.pos.icon_x[i] = icon_start_x + (i * 2 - 1) * (self.icon_size + self.pad) - end - - -- mark as ready - item.ready = true - end - - function HUDELEMENT:Draw() - local size = self:GetSize() - local msg = HUDEditor.IsEditing and EPOP:GetDefaultMessage() or EPOP:GetMessage() - - -- prepare item, caches the data of the element to improve performance - if not msg.ready then - self:PrepareItem(msg) - end - - -- calculate fadeout - local fadetime = 1.5 - local timediff = HUDEditor.IsEditing and 5 or (msg.time - CurTime()) - local opacity = (timediff > fadetime) and 255 or math.Round(255 * timediff / fadetime) - - local colorTitle = msg.title.color or COLOR_WHITE - local colorSubtitle = msg.subtitle.color or COLOR_WHITE - - -- draw content - local wrappedTitles = msg.wrappedTitle - - for i = 1, #wrappedTitles do - draw.AdvancedText(wrappedTitles[i], titlefont, msg.pos.center_x, msg.pos.title_y[i], Color(colorTitle.r, colorTitle.g, colorTitle.b, opacity), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, true, self.scale) - end - - local wrappedTexts = msg.wrappedSubtitle - - for i = 1, #wrappedTexts do - draw.AdvancedText(wrappedTexts[i], textfont, msg.pos.center_x, msg.pos.text_y[i], Color(colorSubtitle.r, colorSubtitle.g, colorSubtitle.b, opacity), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, true, self.scale) - end - - local wrappedIcons = msg.iconTable - - for i = 1, #wrappedIcons do - draw.FilteredTexture(msg.pos.icon_x[i], msg.pos.icon_y, self.icon_size, self.icon_size, wrappedIcons[i], opacity) - end - - self:SetSize(size.w, msg.size.h) - end + function HUDELEMENT:ShouldDraw() + return HUDEditor.IsEditing or EPOP:ShouldRender() + end + + function HUDELEMENT:PerformLayout() + self.scale = appearance.GetGlobalScale() + self.pad = pad * self.scale + self.icon_size = icon_size * self.scale + + self.basecolor = self:GetHUDBasecolor() + + -- reset item ready + if EPOP.msg then + EPOP.msg.ready = false + end + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:PrepareItem(item) + local size = self:GetSize() + local pos = self:GetPos() + local width_title, width_text, height_title, height_text = 0, 0, 0, 0 + local pad2 = 2 * self.pad + + -- wrap title if needed + item.wrappedTitle, width_title, height_title = + draw.GetWrappedText(item.title.text, size.w - pad2, titlefont, self.scale) + + -- wrap text if needed + item.wrappedSubtitle, width_text, height_text = + draw.GetWrappedText(item.subtitle.text, size.w - pad2, textfont, self.scale) + + item.size = {} + item.size.w = ((width_title > width_text) and width_title or width_text) + pad2 + item.size.h = height_title + pad2 + height_text + ((height_text > 0) and self.pad or 0) + + item.pos = {} + item.pos.y = pos.y + item.pos.x = pos.x + math.Round(0.5 * (size.w - item.size.w)) + item.pos.center_x = pos.x + math.Round(0.5 * size.w) + + local wrappedItems = #item.wrappedTitle + local wrappedTexts = #item.wrappedSubtitle + + -- precalculate text positions + local height_title_line = height_title / wrappedItems + local height_text_line = height_text / wrappedTexts + + item.pos.title_y = {} + + for i = 1, wrappedItems do + item.pos.title_y[i] = pos.y + self.pad + (i - 1) * height_title_line + end + + item.pos.text_y = {} + + for i = 1, wrappedTexts do + item.pos.text_y[i] = item.pos.title_y[wrappedItems] + + height_title_line + + self.pad + + (i - 1) * height_text_line + end + + -- add item positions + local icon_amt = #item.iconTable + local icon_start_x = item.pos.center_x + - math.Round(0.5 * (icon_amt * self.icon_size + math.max(0, icon_amt - 1) * self.pad)) + + item.pos.icon_y = item.pos.y + item.size.h + self.pad + item.pos.icon_x = { icon_start_x } + + for i = 1, icon_amt do + item.pos.icon_x[i] = icon_start_x + (i * 2 - 1) * (self.icon_size + self.pad) + end + + -- mark as ready + item.ready = true + end + + function HUDELEMENT:Draw() + local size = self:GetSize() + local msg = HUDEditor.IsEditing and EPOP:GetDefaultMessage() or EPOP:GetMessage() + + -- prepare item, caches the data of the element to improve performance + if not msg.ready then + self:PrepareItem(msg) + end + + -- calculate fadeout + local fadetime = 1.5 + local timediff = HUDEditor.IsEditing and 5 or (msg.time - CurTime()) + local opacity = (timediff > fadetime) and 255 or math.Round(255 * timediff / fadetime) + + local colorTitle = msg.title.color or COLOR_WHITE + local colorSubtitle = msg.subtitle.color or COLOR_WHITE + + -- draw content + local wrappedTitles = msg.wrappedTitle + + for i = 1, #wrappedTitles do + draw.AdvancedText( + wrappedTitles[i], + titlefont, + msg.pos.center_x, + msg.pos.title_y[i], + Color(colorTitle.r, colorTitle.g, colorTitle.b, opacity), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_TOP, + true, + self.scale + ) + end + + local wrappedTexts = msg.wrappedSubtitle + + for i = 1, #wrappedTexts do + draw.AdvancedText( + wrappedTexts[i], + textfont, + msg.pos.center_x, + msg.pos.text_y[i], + Color(colorSubtitle.r, colorSubtitle.g, colorSubtitle.b, opacity), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_TOP, + true, + self.scale + ) + end + + local wrappedIcons = msg.iconTable + + for i = 1, #wrappedIcons do + draw.FilteredTexture( + msg.pos.icon_x[i], + msg.pos.icon_y, + self.icon_size, + self.icon_size, + wrappedIcons[i], + opacity + ) + end + + self:SetSize(size.w, msg.size.h) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttinfopanel/old_ttt_info.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttinfopanel/old_ttt_info.lua index 379e05550..4d0bec005 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttinfopanel/old_ttt_info.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttinfopanel/old_ttt_info.lua @@ -11,228 +11,301 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local x = 0 - local y = 0 - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = HUDELEMENT.maxwidth, h = HUDELEMENT.maxheight}, - minsize = {w = HUDELEMENT.maxwidth, h = HUDELEMENT.maxheight} - } - - --- - -- @ignore - function HUDELEMENT:Initialize() - BaseClass.Initialize(self) - end - - --- - -- @ignore - function HUDELEMENT:GetDefaults() - const_defaults["size"] = {w = self.maxwidth, h = self.maxheight} - const_defaults["minsize"] = {w = self.maxwidth, h = self.maxheight} - const_defaults["basepos"] = {x = self.margin, y = ScrH() - (self.margin + self.maxheight)} - - return const_defaults - end - - --- - -- @ignore - function HUDELEMENT:PerformLayout() - local pos = self:GetPos() - - x = pos.x - y = pos.y - - BaseClass.PerformLayout(self) - end - - --- - -- Draws the old role icon - -- @param number xPos - -- @param number yPos - -- @param number wSize width - -- @param number hSize height - -- @param Material iconMat the role icon - -- @param Color col - -- @realm client - local function DrawOldRoleIcon(xPos, yPos, wSize, hSize, iconMat, col) - local base_mat = Material("vgui/ttt/dynamic/base") - local base_overlay = Material("vgui/ttt/dynamic/base_overlay") - - surface.SetDrawColor(col.r, col.g, col.b, col.a) - surface.SetMaterial(base_mat) - surface.DrawTexturedRect(xPos, yPos, wSize, hSize) - - surface.SetDrawColor(col.r, col.g, col.b, col.a) - surface.SetMaterial(base_overlay) - surface.DrawTexturedRect(xPos, yPos, wSize, hSize) - - surface.SetDrawColor(255, 255, 255, 255) - surface.SetMaterial(iconMat) - surface.DrawTexturedRect(xPos, yPos, wSize, hSize) - end - - --- - -- @ignore - function HUDELEMENT:Draw() - local client = LocalPlayer() - local L = GetLang() - - local maxwidth = self.maxwidth - local width = maxwidth - local height = self.maxheight - local margin = self.margin - - if client:Alive() and client:Team() == TEAM_TERROR then - self:DrawBg(x, y, width, height, client) - - local bar_height = 25 - local bar_width = width - self.dmargin - - -- Draw health - local health = math.max(0, client:Health()) - local health_y = y + margin - local health_string = health - - if GetGlobalBool("ttt_armor_dynamic", false) and client:GetArmor() > 0 then - health_string = health_string .. " + " .. client:GetArmor() - end - - self:PaintBar(x + margin, health_y, bar_width, bar_height, self.health_colors, health / client:GetMaxHealth()) - self:ShadowedText(health_string, "HealthAmmo", x + bar_width, health_y, COLOR_WHITE, TEXT_ALIGN_RIGHT, TEXT_ALIGN_RIGHT) - - -- Draw ammo - local ammo_y = health_y + bar_height + margin - if client:GetActiveWeapon().Primary then - local ammo_clip, ammo_max, ammo_inv = self:GetAmmo(client) - - if ammo_clip ~= -1 then - - local text = string.format("%i + %02i", ammo_clip, ammo_inv) - - self:PaintBar(x + margin, ammo_y, bar_width, bar_height, self.ammo_colors, ammo_clip / ammo_max) - self:ShadowedText(text, "HealthAmmo", x + bar_width, ammo_y, COLOR_WHITE, TEXT_ALIGN_RIGHT, TEXT_ALIGN_RIGHT) - end - end - - -- sprint bar - local sbh = 8 -- spring bar height - - if GetGlobalBool("ttt2_sprint_enabled", true) then - self:PaintBar(x + margin, ammo_y + bar_height + 5, bar_width, sbh, self.sprint_colors, client.sprintProgress) - end - - local hastewidth = self.hastewidth - local bgheight = self.bgheight - local smargin = self.smargin - local tmp = width - hastewidth - bgheight - smargin * 2 - - -- Draw the current role - local round_state = GAMEMODE.round_state - local traitor_y = y - 30 - local text - - if round_state == ROUND_ACTIVE then - text = L[client:GetSubRoleData().name] - else - text = L[self.roundstate_string[round_state]] - end - - self:ShadowedText(text, "TraitorState", x + tmp * 0.5, traitor_y, COLOR_WHITE, TEXT_ALIGN_CENTER) - - -- Draw team icon - local team = client:GetTeam() - - if team ~= TEAM_NONE and round_state == ROUND_ACTIVE and not TEAMS[team].alone then - local t = TEAMS[team] - - if t.iconMaterial then - DrawOldRoleIcon(x + tmp + smargin, traitor_y, bgheight, bgheight, t.iconMaterial, t.color or COLOR_BLACK) - end - end - - -- Draw round time - local isHaste = HasteMode() and round_state == ROUND_ACTIVE - local isOmniscient = client:IsActive() and client:GetSubRoleData().isOmniscientRole - local endtime = GetGlobalFloat("ttt_round_end", 0) - CurTime() - local font = "TimeLeft" - local color = COLOR_WHITE - - tmp = width + x - hastewidth + smargin + hastewidth * 0.5 - - local rx = tmp - local ry = traitor_y + 3 - - -- Time displays differently depending on whether haste mode is on, - -- whether the player is traitor or not, and whether it is overtime. - if isHaste then - local hastetime = GetGlobalFloat("ttt_haste_end", 0) - CurTime() - if hastetime < 0 then - if not isOmniscient or math.ceil(CurTime()) % 7 <= 2 then - - -- innocent or blinking "overtime" - text = L.overtime - font = "Trebuchet18" - - -- need to hack the position a little because of the font switch - ry = ry + 5 - rx = rx - 3 - else - -- traitor and not blinking "overtime" right now, so standard endtime display - text = util.SimpleTime(math.max(0, endtime), "%02i:%02i") - color = COLOR_RED - end - else - -- still in starting period - local t = hastetime - - if isOmniscient and math.ceil(CurTime()) % 6 < 2 then - t = endtime - color = COLOR_RED - end - - text = util.SimpleTime(math.max(0, t), "%02i:%02i") - end - else - -- bog standard time when haste mode is off (or round not active) - text = util.SimpleTime(math.max(0, endtime), "%02i:%02i") - end - - self:ShadowedText(text, font, rx, ry, color, TEXT_ALIGN_CENTER) - - if isHaste then - draw.SimpleText(L.hastemode, "TabLarge", tmp, traitor_y - 8, COLOR_WHITE, TEXT_ALIGN_CENTER) - end - else - -- Draw round state - local smargin = self.smargin - local hastewidth = self.hastewidth - local bg_colors = self.bg_colors - local round_y = y + height - self.bgheight - - height = self.bgheight - - -- move up a little on low resolutions to allow space for spectator hints - if ScrW() < 1000 then - round_y = round_y - 15 - end - - local time_x = width - hastewidth - local time_y = round_y + 4 - - draw.RoundedBox(8, x, round_y, width, height, bg_colors.background_main) - draw.RoundedBox(8, x, round_y, time_x, height, bg_colors.noround) - - -- Draw current round state - local text = L[self.roundstate_string[GAMEMODE.round_state]] - - self:ShadowedText(text, "TraitorState", x + (width - hastewidth) * 0.5, round_y, COLOR_WHITE, TEXT_ALIGN_CENTER) - - -- Draw round/prep/post time remaining - text = util.SimpleTime(math.max(0, GetGlobalFloat("ttt_round_end", 0) - CurTime()), "%02i:%02i") - - self:ShadowedText(text, "TimeLeft", x + time_x + smargin + hastewidth * 0.5, time_y, COLOR_WHITE, TEXT_ALIGN_CENTER) - end - end + local x = 0 + local y = 0 + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = HUDELEMENT.maxwidth, h = HUDELEMENT.maxheight }, + minsize = { w = HUDELEMENT.maxwidth, h = HUDELEMENT.maxheight }, + } + + --- + -- @ignore + function HUDELEMENT:Initialize() + BaseClass.Initialize(self) + end + + --- + -- @ignore + function HUDELEMENT:GetDefaults() + const_defaults["size"] = { w = self.maxwidth, h = self.maxheight } + const_defaults["minsize"] = { w = self.maxwidth, h = self.maxheight } + const_defaults["basepos"] = { x = self.margin, y = ScrH() - (self.margin + self.maxheight) } + + return const_defaults + end + + --- + -- @ignore + function HUDELEMENT:PerformLayout() + local pos = self:GetPos() + + x = pos.x + y = pos.y + + BaseClass.PerformLayout(self) + end + + --- + -- Draws the old role icon + -- @param number xPos + -- @param number yPos + -- @param number wSize width + -- @param number hSize height + -- @param Material iconMat the role icon + -- @param Color col + -- @realm client + local function DrawOldRoleIcon(xPos, yPos, wSize, hSize, iconMat, col) + local base_mat = Material("vgui/ttt/dynamic/base") + local base_overlay = Material("vgui/ttt/dynamic/base_overlay") + + surface.SetDrawColor(col.r, col.g, col.b, col.a) + surface.SetMaterial(base_mat) + surface.DrawTexturedRect(xPos, yPos, wSize, hSize) + + surface.SetDrawColor(col.r, col.g, col.b, col.a) + surface.SetMaterial(base_overlay) + surface.DrawTexturedRect(xPos, yPos, wSize, hSize) + + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(iconMat) + surface.DrawTexturedRect(xPos, yPos, wSize, hSize) + end + + --- + -- @ignore + function HUDELEMENT:Draw() + local client = LocalPlayer() + local L = GetLang() + + local maxwidth = self.maxwidth + local width = maxwidth + local height = self.maxheight + local margin = self.margin + + if client:Alive() and client:Team() == TEAM_TERROR then + self:DrawBg(x, y, width, height, client) + + local bar_height = 25 + local bar_width = width - self.dmargin + + -- Draw health + local health = math.max(0, client:Health()) + local health_y = y + margin + local health_string = health + + if GetGlobalBool("ttt_armor_dynamic", false) and client:GetArmor() > 0 then + health_string = health_string .. " + " .. client:GetArmor() + end + + self:PaintBar( + x + margin, + health_y, + bar_width, + bar_height, + self.health_colors, + health / client:GetMaxHealth() + ) + self:ShadowedText( + health_string, + "HealthAmmo", + x + bar_width, + health_y, + COLOR_WHITE, + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_RIGHT + ) + + -- Draw ammo + local ammo_y = health_y + bar_height + margin + if client:GetActiveWeapon().Primary then + local ammo_clip, ammo_max, ammo_inv = self:GetAmmo(client) + + if ammo_clip ~= -1 then + local text = string.format("%i + %02i", ammo_clip, ammo_inv) + + self:PaintBar( + x + margin, + ammo_y, + bar_width, + bar_height, + self.ammo_colors, + ammo_clip / ammo_max + ) + self:ShadowedText( + text, + "HealthAmmo", + x + bar_width, + ammo_y, + COLOR_WHITE, + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_RIGHT + ) + end + end + + -- sprint bar + local sbh = 8 -- spring bar height + + if SPRINT.convars.enabled:GetBool() then + self:PaintBar( + x + margin, + ammo_y + bar_height + 5, + bar_width, + sbh, + self.sprint_colors, + client:GetSprintStamina() + ) + end + + local hastewidth = self.hastewidth + local bgheight = self.bgheight + local smargin = self.smargin + local tmp = width - hastewidth - bgheight - smargin * 2 + + -- Draw the current role + local round_state = GAMEMODE.round_state + local traitor_y = y - 30 + local text + + if round_state == ROUND_ACTIVE then + text = L[client:GetSubRoleData().name] + else + text = L[self.roundstate_string[round_state]] + end + + self:ShadowedText( + text, + "TraitorState", + x + tmp * 0.5, + traitor_y, + COLOR_WHITE, + TEXT_ALIGN_CENTER + ) + + -- Draw team icon + local team = client:GetTeam() + + if team ~= TEAM_NONE and round_state == ROUND_ACTIVE and not TEAMS[team].alone then + local t = TEAMS[team] + + if t.iconMaterial then + DrawOldRoleIcon( + x + tmp + smargin, + traitor_y, + bgheight, + bgheight, + t.iconMaterial, + t.color or COLOR_BLACK + ) + end + end + + -- Draw round time + local isHaste = HasteMode() and round_state == ROUND_ACTIVE + local isOmniscient = client:IsActive() and client:GetSubRoleData().isOmniscientRole + local endtime = GetGlobalFloat("ttt_round_end", 0) - CurTime() + local font = "TimeLeft" + local color = COLOR_WHITE + + tmp = width + x - hastewidth + smargin + hastewidth * 0.5 + + local rx = tmp + local ry = traitor_y + 3 + + -- Time displays differently depending on whether haste mode is on, + -- whether the player is traitor or not, and whether it is overtime. + if isHaste then + local hastetime = GetGlobalFloat("ttt_haste_end", 0) - CurTime() + if hastetime < 0 then + if not isOmniscient or math.ceil(CurTime()) % 7 <= 2 then + -- innocent or blinking "overtime" + text = L.overtime + font = "Trebuchet18" + + -- need to hack the position a little because of the font switch + ry = ry + 5 + rx = rx - 3 + else + -- traitor and not blinking "overtime" right now, so standard endtime display + text = util.SimpleTime(math.max(0, endtime), "%02i:%02i") + color = COLOR_RED + end + else + -- still in starting period + local t = hastetime + + if isOmniscient and math.ceil(CurTime()) % 6 < 2 then + t = endtime + color = COLOR_RED + end + + text = util.SimpleTime(math.max(0, t), "%02i:%02i") + end + else + -- bog standard time when haste mode is off (or round not active) + text = util.SimpleTime(math.max(0, endtime), "%02i:%02i") + end + + self:ShadowedText(text, font, rx, ry, color, TEXT_ALIGN_CENTER) + + if isHaste then + draw.SimpleText( + L.hastemode, + "TabLarge", + tmp, + traitor_y - 8, + COLOR_WHITE, + TEXT_ALIGN_CENTER + ) + end + else + -- Draw round state + local smargin = self.smargin + local hastewidth = self.hastewidth + local bg_colors = self.bg_colors + local round_y = y + height - self.bgheight + + height = self.bgheight + + -- move up a little on low resolutions to allow space for spectator hints + if ScrW() < 1000 then + round_y = round_y - 15 + end + + local time_x = width - hastewidth + local time_y = round_y + 4 + + draw.RoundedBox(8, x, round_y, width, height, bg_colors.background_main) + draw.RoundedBox(8, x, round_y, time_x, height, bg_colors.noround) + + -- Draw current round state + local text = L[self.roundstate_string[GAMEMODE.round_state]] + + self:ShadowedText( + text, + "TraitorState", + x + (width - hastewidth) * 0.5, + round_y, + COLOR_WHITE, + TEXT_ALIGN_CENTER + ) + + -- Draw round/prep/post time remaining + text = util.SimpleTime( + math.max(0, GetGlobalFloat("ttt_round_end", 0) - CurTime()), + "%02i:%02i" + ) + + self:ShadowedText( + text, + "TimeLeft", + x + time_x + smargin + hastewidth * 0.5, + time_y, + COLOR_WHITE, + TEXT_ALIGN_CENTER + ) + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttinfopanel/pure_skin_playerinfo.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttinfopanel/pure_skin_playerinfo.lua index 610dcb1b5..0f4b2e467 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttinfopanel/pure_skin_playerinfo.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttinfopanel/pure_skin_playerinfo.lua @@ -7,279 +7,453 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local GetLang = LANG.GetUnsafeLanguageTable - - local pad = 14 -- padding - local lpw = 44 -- left panel width - local sri_text_width_padding = 8 -- secondary role information padding (needed for size calculations) - - local watching_icon = Material("vgui/ttt/watching_icon") - local credits_default = Material("vgui/ttt/equip/credits_default") - local credits_zero = Material("vgui/ttt/equip/credits_zero") - - local icon_armor = Material("vgui/ttt/hud_armor.vmt") - local icon_armor_rei = Material("vgui/ttt/hud_armor_reinforced.vmt") - - local color_sprint = Color(36, 154, 198) - local color_defaultgrey = Color(100, 100, 100, 200) - local color_health = Color(234, 41, 41) - local color_ammoBar = Color(238, 151, 0) - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 365, h = 146}, - minsize = {w = 240, h = 146} - } - - function HUDELEMENT:Initialize() - self.scale = 1.0 - self.basecolor = self:GetHUDBasecolor() - self.pad = pad - self.lpw = lpw - self.sri_text_width_padding = sri_text_width_padding - --self.secondaryRoleInformationFunc = nil - - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return true, true - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = { - x = 10 * self.scale, - y = ScrH() - (10 * self.scale + self.size.h) - } - - return const_defaults - end - - function HUDELEMENT:PerformLayout() - local defaults = self:GetDefaults() - - self.basecolor = self:GetHUDBasecolor() - self.scale = math.min(self.size.w / defaults.minsize.w, self.size.h / defaults.minsize.h) - self.lpw = lpw * self.scale - self.pad = pad * self.scale - self.sri_text_width_padding = sri_text_width_padding * self.scale - - BaseClass.PerformLayout(self) - end - - -- Returns player's ammo information - function HUDELEMENT:GetAmmo(ply) - local weap = ply:GetActiveWeapon() - - if not weap or not ply:Alive() then - return -1 - end - - local ammo_inv = weap.Ammo1 and weap:Ammo1() or 0 - local ammo_clip = weap:Clip1() or 0 - local ammo_max = weap.Primary.ClipSize or 0 - - return ammo_clip, ammo_max, ammo_inv - end - - --[[ - This function expects to receive a function as a parameter which later returns a table with the following keys: { text: "", color: Color } - The function should also take care of managing the visibility by returning nil to tell the UI that nothing should be displayed - ]]-- - function HUDELEMENT:SetSecondaryRoleInfoFunction(func) - if not isfunction(func) then return end - - self.secondaryRoleInformationFunc = func - end - - function HUDELEMENT:Draw() - local client = LocalPlayer() - local calive = client:Alive() and client:IsTerror() - local cactive = client:IsActive() - local L = GetLang() - - local x2, y2, w2, h2 = self.pos.x, self.pos.y, self.size.w, self.size.h - local t_scale = self.scale - local t_lpw = self.lpw - local t_pad = self.pad - local t_basecolor = self.basecolor - local t_sri_text_width_padding = self.sri_text_width_padding - - if not calive then - y2 = y2 + h2 - t_lpw - h2 = t_lpw - end - - -- draw bg and shadow - self:DrawBg(x2, y2, w2, h2, t_basecolor) - - -- draw left panel - local c - - if cactive then - c = client:GetRoleColor() - else - c = color_defaultgrey - end - - surface.SetDrawColor(clr(c)) - surface.DrawRect(x2, y2, t_lpw, h2) - - local ry = y2 + t_lpw * 0.5 - local ty = y2 + t_lpw + t_pad -- new y - local nx = x2 + t_lpw + t_pad -- new x - - -- draw role icon - local rd = client:GetSubRoleData() - if rd then - local tgt = client:GetObserverTarget() - - if cactive then - if rd.iconMaterial then - draw.FilteredShadowedTexture(x2 + 4, y2 + 4, t_lpw - 8, t_lpw - 8, rd.iconMaterial, 255, util.GetDefaultColor(c), t_scale) - end - elseif IsValid(tgt) and tgt:IsPlayer() then - draw.FilteredShadowedTexture(x2 + 4, y2 + 4, t_lpw - 8, t_lpw - 8, watching_icon, 255, util.GetDefaultColor(c), t_scale) - end - - -- draw role string name - local text - local round_state = GAMEMODE.round_state - - if cactive then - text = L[rd.name] - else - if IsValid(tgt) and tgt:IsPlayer() then - text = tgt:Nick() - else - text = L[self.roundstate_string[round_state]] - end - end - - --calculate the scale multplier for role text - surface.SetFont("PureSkinRole") - - local role_text_width = surface.GetTextSize(string.upper(text)) * t_scale - local role_scale_multiplier = (w2 - t_lpw - 2 * t_pad) / role_text_width - - if calive and cactive and isfunction(self.secondaryRoleInformationFunc) then - local secInfoTbl = self.secondaryRoleInformationFunc() - - if secInfoTbl and secInfoTbl.text then - surface.SetFont("PureSkinBar") - - local sri_text_width = surface.GetTextSize(string.upper(secInfoTbl.text)) * t_scale - - role_scale_multiplier = (w2 - sri_text_width - t_lpw - 2 * t_pad - 3 * t_sri_text_width_padding) / role_text_width - end - end - - role_scale_multiplier = math.Clamp(role_scale_multiplier, 0.55, 0.85) * t_scale - - draw.AdvancedText(string.upper(text), "PureSkinRole", nx, ry, util.GetDefaultColor(t_basecolor), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, true, Vector(role_scale_multiplier * 0.9, role_scale_multiplier, role_scale_multiplier)) - end - - -- player informations - if calive then - - -- draw secondary role information - if cactive and isfunction(self.secondaryRoleInformationFunc) then - local secInfoTbl = self.secondaryRoleInformationFunc() - - if secInfoTbl and secInfoTbl.color and secInfoTbl.text then - surface.SetFont("PureSkinBar") - - local sri_text_caps = string.upper(secInfoTbl.text) - local sri_text_width = surface.GetTextSize(sri_text_caps) * t_scale - local sri_margin_top_bottom = 8 * t_scale - local sri_width = sri_text_width + t_sri_text_width_padding * 2 - local sri_xoffset = w2 - sri_width - t_pad - - local nx2 = x2 + sri_xoffset - local ny = y2 + sri_margin_top_bottom - local nh = t_lpw - sri_margin_top_bottom * 2 - - surface.SetDrawColor(clr(secInfoTbl.color)) - surface.DrawRect(nx2, ny, sri_width, nh) - - draw.AdvancedText(sri_text_caps, "PureSkinBar", nx2 + sri_width * 0.5, ry, util.GetDefaultColor(secInfoTbl.color), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, true, t_scale) - - -- draw lines around the element - self:DrawLines(nx2, ny, sri_width, nh, secInfoTbl.color.a) - end - end - - -- draw dark bottom overlay - surface.SetDrawColor(0, 0, 0, 90) - surface.DrawRect(x2, y2 + t_lpw, w2, h2 - t_lpw) - - -- draw bars - local bw = w2 - t_lpw - t_pad * 2 -- bar width - local bh = 26 * t_scale -- bar height - local sbh = 8 * t_scale -- spring bar height - local spc = 7 * t_scale -- space between bars - - -- health bar - local health = math.max(0, client:Health()) - local armor = client:GetArmor() - - self:DrawBar(nx, ty, bw, bh, color_health, health / client:GetMaxHealth(), t_scale, string.upper(L["hud_health"]) .. ": " .. health) - - -- draw armor information - if GetGlobalBool("ttt_armor_dynamic", false) and armor > 0 then - local icon_mat = client:ArmorIsReinforced() and icon_armor_rei or icon_armor - - local a_size = bh - math.Round(11 * t_scale) - local a_pad = math.Round(5 * t_scale) - - local a_pos_y = ty + a_pad - local a_pos_x = nx + bw - math.Round(65 * t_scale) - - local at_pos_y = ty + 1 - local at_pos_x = a_pos_x + a_size + a_pad - - draw.FilteredShadowedTexture(a_pos_x, a_pos_y, a_size, a_size, icon_mat, 255, COLOR_WHITE, t_scale) - - draw.AdvancedText(armor, "PureSkinBar", at_pos_x, at_pos_y, util.GetDefaultColor(color_health), TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT, true, t_scale) - end - - -- ammo bar - ty = ty + bh + spc - - -- Draw ammo - if client:GetActiveWeapon().Primary then - local ammo_clip, ammo_max, ammo_inv = self:GetAmmo(client) - - if ammo_clip ~= -1 then - local text = string.format("%i + %02i", ammo_clip, ammo_inv) - - self:DrawBar(nx, ty, bw, bh, color_ammoBar, ammo_clip / ammo_max, t_scale, text) - end - end - - -- sprint bar - ty = ty + bh + spc - - if GetGlobalBool("ttt2_sprint_enabled", true) then - self:DrawBar(nx, ty, bw, sbh, color_sprint, client.sprintProgress, t_scale, "") - end - - -- coin info - if cactive and client:IsShopper() then - local coinSize = 24 * t_scale - local x2_pad = math.Round((t_lpw - coinSize) * 0.5) - - if client:GetCredits() > 0 then - draw.FilteredTexture(x2 + x2_pad, y2 + h2 - coinSize - x2_pad, coinSize, coinSize, credits_default, 200) - else - draw.FilteredTexture(x2 + x2_pad, y2 + h2 - coinSize - x2_pad, coinSize, coinSize, credits_zero, 100) - end - end - end - - -- draw lines around the element - self:DrawLines(x2, y2, w2, h2, t_basecolor.a) - end + local GetLang = LANG.GetUnsafeLanguageTable + + local pad = 14 -- padding + local lpw = 44 -- left panel width + local sri_text_width_padding = 8 -- secondary role information padding (needed for size calculations) + + local watching_icon = Material("vgui/ttt/watching_icon") + local credits_default = Material("vgui/ttt/equip/credits_default") + local credits_zero = Material("vgui/ttt/equip/credits_zero") + + local icon_health = Material("vgui/ttt/hud_health.vmt") + local icon_health_low = Material("vgui/ttt/hud_health_low.vmt") + + local icon_armor = Material("vgui/ttt/hud_armor.vmt") + local icon_armor_rei = Material("vgui/ttt/hud_armor_reinforced.vmt") + + local mat_tid_ammo = Material("vgui/ttt/tid/tid_ammo") + + local color_sprint = Color(36, 154, 198) + local color_defaultgrey = Color(100, 100, 100, 200) + local color_health = Color(234, 41, 41) + local color_ammoBar = Color(238, 151, 0) + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 365, h = 146 }, + minsize = { w = 240, h = 146 }, + } + + function HUDELEMENT:Initialize() + self.scale = 1.0 + self.basecolor = self:GetHUDBasecolor() + self.pad = pad + self.lpw = lpw + self.sri_text_width_padding = sri_text_width_padding + --self.secondaryRoleInformationFunc = nil + + BaseClass.Initialize(self) + end + + --- + -- This function will return a table containing all keys that will be stored by + -- the @{HUDELEMENT:SaveData} function. + -- @return table + -- @realm client + function HUDELEMENT:GetSavingKeys() + local savingKeys = BaseClass.GetSavingKeys(self) or {} + savingKeys.healthPulsate = { + typ = "bool", + desc = "label_hud_pulsate_health_enable", + default = true, + OnChange = function(slf, bool) + slf:PerformLayout() + slf:SaveData() + end, + } + + return table.Copy(savingKeys) + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return true, true + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = { + x = 10 * self.scale, + y = ScrH() - (10 * self.scale + self.size.h), + } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + local defaults = self:GetDefaults() + + self.basecolor = self:GetHUDBasecolor() + self.scale = math.min(self.size.w / defaults.minsize.w, self.size.h / defaults.minsize.h) + self.lpw = lpw * self.scale + self.pad = pad * self.scale + self.sri_text_width_padding = sri_text_width_padding * self.scale + + BaseClass.PerformLayout(self) + end + + -- Returns player's ammo information + function HUDELEMENT:GetAmmo(ply) + local weap = ply:GetActiveWeapon() + + if not weap or not ply:Alive() then + return -1 + end + + local ammo_inv = weap.Ammo1 and weap:Ammo1() or 0 + local ammo_clip = weap:Clip1() or 0 + local ammo_max = weap.Primary.ClipSize or 0 + local ammo_type = string.lower(weap.Primary.Ammo) + + return ammo_clip, ammo_max, ammo_inv, ammo_type + end + + --[[ + This function expects to receive a function as a parameter which later returns a table with the following keys: { text: "", color: Color } + The function should also take care of managing the visibility by returning nil to tell the UI that nothing should be displayed + ]] + -- + function HUDELEMENT:SetSecondaryRoleInfoFunction(func) + if not isfunction(func) then + return + end + + self.secondaryRoleInformationFunc = func + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + local calive = client:Alive() and client:IsTerror() + local cactive = client:IsActive() + local L = GetLang() + + local x2, y2, w2, h2 = self.pos.x, self.pos.y, self.size.w, self.size.h + local t_scale = self.scale + local t_lpw = self.lpw + local t_pad = self.pad + local t_basecolor = self.basecolor + local t_sri_text_width_padding = self.sri_text_width_padding + + if not calive then + y2 = y2 + h2 - t_lpw + h2 = t_lpw + end + + -- draw bg and shadow + self:DrawBg(x2, y2, w2, h2, t_basecolor) + + -- draw left panel + local c + + if cactive then + c = client:GetRoleColor() + else + c = color_defaultgrey + end + + surface.SetDrawColor(clr(c)) + surface.DrawRect(x2, y2, t_lpw, h2) + + local ry = y2 + t_lpw * 0.5 + local ty = y2 + t_lpw + t_pad -- new y + local nx = x2 + t_lpw + t_pad -- new x + + -- draw role icon + local rd = client:GetSubRoleData() + if rd then + local tgt = client:GetObserverTarget() + + if cactive then + if rd.iconMaterial then + draw.FilteredShadowedTexture( + x2 + 4, + y2 + 4, + t_lpw - 8, + t_lpw - 8, + rd.iconMaterial, + 255, + util.GetDefaultColor(c), + t_scale + ) + end + elseif IsValid(tgt) and tgt:IsPlayer() then + draw.FilteredShadowedTexture( + x2 + 4, + y2 + 4, + t_lpw - 8, + t_lpw - 8, + watching_icon, + 255, + util.GetDefaultColor(c), + t_scale + ) + end + + -- draw role string name + local text + local round_state = GAMEMODE.round_state + + if cactive then + text = L[rd.name] + else + if IsValid(tgt) and tgt:IsPlayer() then + text = tgt:Nick() + else + text = L[self.roundstate_string[round_state]] + end + end + + --calculate the scale multplier for role text + surface.SetFont("PureSkinRole") + + local role_text_width = surface.GetTextSize(string.upper(text)) * t_scale + local role_scale_multiplier = (w2 - t_lpw - 2 * t_pad) / role_text_width + + if calive and cactive and isfunction(self.secondaryRoleInformationFunc) then + local secInfoTbl = self.secondaryRoleInformationFunc() + + if secInfoTbl and secInfoTbl.text then + surface.SetFont("PureSkinBar") + + local sri_text_width = surface.GetTextSize(string.upper(secInfoTbl.text)) + * t_scale + + role_scale_multiplier = ( + w2 + - sri_text_width + - t_lpw + - 2 * t_pad + - 3 * t_sri_text_width_padding + ) / role_text_width + end + end + + role_scale_multiplier = math.Clamp(role_scale_multiplier, 0.55, 0.85) * t_scale + + draw.AdvancedText( + string.upper(text), + "PureSkinRole", + nx, + ry, + util.GetDefaultColor(t_basecolor), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + true, + Vector(role_scale_multiplier * 0.9, role_scale_multiplier, role_scale_multiplier) + ) + end + + -- player informations + if calive then + -- draw secondary role information + if cactive and isfunction(self.secondaryRoleInformationFunc) then + local secInfoTbl = self.secondaryRoleInformationFunc() + + if secInfoTbl and secInfoTbl.color and secInfoTbl.text then + surface.SetFont("PureSkinBar") + + local sri_text_caps = string.upper(secInfoTbl.text) + local sri_text_width = surface.GetTextSize(sri_text_caps) * t_scale + local sri_margin_top_bottom = 8 * t_scale + local sri_width = sri_text_width + t_sri_text_width_padding * 2 + local sri_xoffset = w2 - sri_width - t_pad + + local nx2 = x2 + sri_xoffset + local ny = y2 + sri_margin_top_bottom + local nh = t_lpw - sri_margin_top_bottom * 2 + + surface.SetDrawColor(clr(secInfoTbl.color)) + surface.DrawRect(nx2, ny, sri_width, nh) + + draw.AdvancedText( + sri_text_caps, + "PureSkinBar", + nx2 + sri_width * 0.5, + ry, + util.GetDefaultColor(secInfoTbl.color), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + true, + t_scale + ) + + -- draw lines around the element + self:DrawLines(nx2, ny, sri_width, nh, secInfoTbl.color.a) + end + end + + -- draw dark bottom overlay + surface.SetDrawColor(0, 0, 0, 90) + surface.DrawRect(x2, y2 + t_lpw, w2, h2 - t_lpw) + + -- draw bars + local bw = w2 - t_lpw - t_pad * 2 -- bar width + local bh = 26 * t_scale -- bar height + local sbh = 8 * t_scale -- spring bar height + local spc = 7 * t_scale -- space between bars + + -- health bar + local health = math.max(0, client:Health()) + local armor = client:GetArmor() + local alpha = 255 + local health_icon = icon_health + + if health <= client:GetMaxHealth() * 0.25 and self.healthPulsate then + local frequency = + util.TransformToRange(health, 1, client:GetMaxHealth() * 0.25 + 1, 1, 6) + health_icon = icon_health_low + + local factor = math.abs(math.sin(CurTime() * (7 - frequency))) + + alpha = math.Round(factor * 255) + end + + color_health = ColorAlpha(color_health, alpha) + + self:DrawBar(nx, ty, bw, bh, color_health, health / client:GetMaxHealth(), t_scale) + + local a_size = bh - math.Round(11 * t_scale) + local a_pad = math.Round(5 * t_scale) + + local a_pos_y = ty + a_pad + local a_pos_x = nx + (a_size / 2) + + local at_pos_y = ty + 1 + local at_pos_x = a_pos_x + a_size + a_pad + + draw.FilteredShadowedTexture( + a_pos_x, + a_pos_y, + a_size, + a_size, + health_icon, + 255, + COLOR_WHITE, + t_scale + ) + draw.AdvancedText( + health, + "PureSkinBar", + at_pos_x, + at_pos_y, + util.GetDefaultColor(color_health), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_LEFT, + true, + t_scale + ) + + -- draw armor information + if GetGlobalBool("ttt_armor_dynamic", false) and armor > 0 then + local icon_mat = client:ArmorIsReinforced() and icon_armor_rei or icon_armor + + a_pos_x = nx + bw - math.Round(65 * t_scale) + at_pos_x = a_pos_x + a_size + a_pad + + draw.FilteredShadowedTexture( + a_pos_x, + a_pos_y, + a_size, + a_size, + icon_mat, + 255, + COLOR_WHITE, + t_scale + ) + + draw.AdvancedText( + armor, + "PureSkinBar", + at_pos_x, + at_pos_y, + util.GetDefaultColor(color_health), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_LEFT, + true, + t_scale + ) + end + + -- ammo bar + ty = ty + bh + spc + a_pos_y = ty + a_pad + + -- Draw ammo + if client:GetActiveWeapon().Primary then + local ammo_clip, ammo_max, ammo_inv, ammo_type = self:GetAmmo(client) + + if ammo_clip ~= -1 then + local text = string.format("%i + %02i", ammo_clip, ammo_inv) + + self:DrawBar(nx, ty, bw, bh, color_ammoBar, ammo_clip / ammo_max, t_scale) + + local icon_mat = BaseClass.BulletIcons[ammo_type] or mat_tid_ammo + + a_pos_x = nx + (a_size / 2) + at_pos_y = ty + 1 + at_pos_x = a_pos_x + a_size + a_pad + + draw.FilteredShadowedTexture( + a_pos_x, + a_pos_y, + a_size, + a_size, + icon_mat, + 255, + COLOR_WHITE, + t_scale + ) + draw.AdvancedText( + text, + "PureSkinBar", + at_pos_x, + at_pos_y, + util.GetDefaultColor(color_ammoBar), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_LEFT, + true, + t_scale + ) + end + end + + -- sprint bar + ty = ty + bh + spc + + if SPRINT.convars.enabled:GetBool() then + self:DrawBar(nx, ty, bw, sbh, color_sprint, client:GetSprintStamina(), t_scale, "") + end + + -- coin info + if cactive and client:IsShopper() then + local coinSize = 24 * t_scale + local x2_pad = math.Round((t_lpw - coinSize) * 0.5) + + if client:GetCredits() > 0 then + draw.FilteredTexture( + x2 + x2_pad, + y2 + h2 - coinSize - x2_pad, + coinSize, + coinSize, + credits_default, + 200 + ) + else + draw.FilteredTexture( + x2 + x2_pad, + y2 + h2 - coinSize - x2_pad, + coinSize, + coinSize, + credits_zero, + 100 + ) + end + end + end + + -- draw lines around the element + self:DrawLines(x2, y2, w2, h2, t_basecolor.a) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttminiscoreboard/pure_skin_miniscoreboard.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttminiscoreboard/pure_skin_miniscoreboard.lua index 5baf38dba..b04b01fb6 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttminiscoreboard/pure_skin_miniscoreboard.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttminiscoreboard/pure_skin_miniscoreboard.lua @@ -8,7 +8,9 @@ DEFINE_BASECLASS(base) HUDELEMENT.togglable = true -if SERVER then return end -- clientside-only +if SERVER then + return +end -- clientside-only local margin = 14 local element_margin = 6 @@ -18,181 +20,208 @@ local color_blacktrans = Color(0, 0, 0, 130) local color_indirconfirm = Color(215, 215, 215, 155) local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 72, h = 72}, - minsize = {w = 0, h = 0} + basepos = { x = 0, y = 0 }, + size = { w = 72, h = 72 }, + minsize = { w = 0, h = 0 }, } local plysList = {} local function SortMiniscoreboardFunc(a, b) - if not a:OnceFound() then - return false - end + if not a:OnceFound() then + return false + end - -- bodies were confirmed and body a was confirmed prior to body b - if b:OnceFound() and a:GetFirstFound() >= b:GetFirstFound() then - return false - end + -- bodies were confirmed and body a was confirmed prior to body b + if b:OnceFound() and a:GetFirstFound() >= b:GetFirstFound() then + return false + end - return true + return true end local refreshPaths = { - ["t_first_found"] = true, - ["t_last_found"] = true, - ["role_found"] = true, - ["body_found"] = true + ["t_first_found"] = true, + ["t_last_found"] = true, + ["role_found"] = true, + ["body_found"] = true, } function HUDELEMENT:PreInitialize() - hudelements.RegisterChildRelation(self.id, "pure_skin_roundinfo", false) - - -- resort miniscoreboard if body_found is changed - ttt2net.OnUpdate("players", function(oldval, newval, reversePath) - -- check if path of changed value is one of our releavant paths - if not refreshPaths[reversePath[2]] then return end - - -- sort playerlist: confirmed players should be in the first position - table.sort(plysList, SortMiniscoreboardFunc) - end) + hudelements.RegisterChildRelation(self.id, "pure_skin_roundinfo", false) + + -- resort miniscoreboard if body_found is changed + ttt2net.OnUpdate("players", function(oldval, newval, reversePath) + -- check if path of changed value is one of our releavant paths + if not refreshPaths[reversePath[2]] then + return + end + + -- sort playerlist: confirmed players should be in the first position + table.sort(plysList, SortMiniscoreboardFunc) + end) end HUDELEMENT.icon_in_conf = Material("vgui/ttt/indirect_confirmed") HUDELEMENT.icon_revived = Material("vgui/ttt/revived") function HUDELEMENT:Initialize() - self.margin = margin - self.element_margin = element_margin - self.column_count = 0 - self.parentInstance = hudelements.GetStored(self.parent) - self.curPlayerCount = 0 - self.ply_ind_size = 0 - self.scale = 1.0 - self.basecolor = self:GetHUDBasecolor() + self.margin = margin + self.element_margin = element_margin + self.column_count = 0 + self.parentInstance = hudelements.GetStored(self.parent) + self.curPlayerCount = 0 + self.ply_ind_size = 0 + self.scale = 1.0 + self.basecolor = self:GetHUDBasecolor() - plysList = util.GetFilteredPlayers(function (ply) - return ply:WasActiveInRound() - end) + plysList = util.GetFilteredPlayers(function(ply) + return ply:WasActiveInRound() + end) - self.curPlayerCount = #plysList + self.curPlayerCount = #plysList - -- sort playerlist: confirmed players should be in the first position - table.sort(plysList, SortMiniscoreboardFunc) + -- sort playerlist: confirmed players should be in the first position + table.sort(plysList, SortMiniscoreboardFunc) - self.lastUpdate = CurTime() + self.lastUpdate = CurTime() - BaseClass.Initialize(self) + BaseClass.Initialize(self) end -- parameter overwrites function HUDELEMENT:ShouldDraw() - return GAMEMODE.round_state == ROUND_ACTIVE + return GAMEMODE.round_state == ROUND_ACTIVE end function HUDELEMENT:InheritParentBorder() - return true + return true end -- parameter overwrites end function HUDELEMENT:GetDefaults() - return const_defaults + return const_defaults end function HUDELEMENT:PerformLayout() - local parent_pos = self.parentInstance:GetPos() - local parent_size = self.parentInstance:GetSize() - local parent_defaults = self.parentInstance:GetDefaults() - local h = parent_size.h - - self.basecolor = self:GetHUDBasecolor() - self.scale = h / parent_defaults.size.h - self.margin = margin * self.scale - self.element_margin = element_margin * self.scale - self.ply_ind_size = math.Round((h - self.element_margin - self.margin * 2) * 0.5) - self.column_count = math.Round(self.curPlayerCount * 0.5) - - local w = self.element_margin * (self.column_count - 1) + self.ply_ind_size * self.column_count + 2 * self.margin - - self:SetPos(parent_pos.x + parent_size.w, parent_pos.y) - self:SetSize(w, h) - - BaseClass.PerformLayout(self) + local parent_pos = self.parentInstance:GetPos() + local parent_size = self.parentInstance:GetSize() + local parent_defaults = self.parentInstance:GetDefaults() + local h = parent_size.h + + self.basecolor = self:GetHUDBasecolor() + self.scale = h / parent_defaults.size.h + self.margin = margin * self.scale + self.element_margin = element_margin * self.scale + self.ply_ind_size = math.Round((h - self.element_margin - self.margin * 2) * 0.5) + self.column_count = math.Round(self.curPlayerCount * 0.5) + + local w = self.element_margin * (self.column_count - 1) + + self.ply_ind_size * self.column_count + + 2 * self.margin + + self:SetPos(parent_pos.x + parent_size.w, parent_pos.y) + self:SetSize(w, h) + + BaseClass.PerformLayout(self) end local function GetMSBColorForPlayer(ply) - local color = color_blacktrans -- not yet confirmed - - if ply:OnceFound() then - if ply:HasRole() then - local roleColor = ply:GetRoleColor() - - color = Color(roleColor.r, roleColor.g, roleColor.b, 155) -- role known - else - color = color_indirconfirm -- indirect confirmed - end - end - - --- - -- @realm client - return hook.Run("TTT2ModifyMiniscoreboardColor", ply, color) or color + local color = color_blacktrans -- not yet confirmed + + if ply:OnceFound() then + if ply:HasRole() then + local roleColor = ply:GetRoleColor() + + color = Color(roleColor.r, roleColor.g, roleColor.b, 155) -- role known + else + color = color_indirconfirm -- indirect confirmed + end + end + + --- + -- @realm client + -- stylua: ignore + return hook.Run("TTT2ModifyMiniscoreboardColor", ply, color) or color end function HUDELEMENT:Draw() - -- just update every 0.1 seconds; TODO maybe add a client ConVar - if self.lastUpdate + 0.1 < CurTime() then - local plys = util.GetFilteredPlayers(function(ply) - return ply:WasActiveInRound() - end) - - if #plys ~= self.curPlayerCount then - plysList = plys - self.curPlayerCount = #plys - - self:PerformLayout() - - -- sort playerlist: confirmed players should be in the first position - table.sort(plysList, SortMiniscoreboardFunc) - end - - self.lastUpdate = CurTime() - end - - -- draw bg and shadow - self:DrawBg(self.pos.x, self.pos.y, self.size.w, self.size.h, self.basecolor) - - -- draw dark bottom overlay - surface.SetDrawColor(0, 0, 0, 90) - surface.DrawRect(self.pos.x, self.pos.y, self.size.w, self.size.h) - - -- draw squares - local tmp_x, tmp_y = self.pos.x, self.pos.y - - for i = 1, self.curPlayerCount do - local ply = plysList[i] - - tmp_x = self.pos.x + self.margin + (self.element_margin + self.ply_ind_size) * math.floor((i - 1) * 0.5) - tmp_y = self.pos.y + self.margin + (self.element_margin + self.ply_ind_size) * ((i - 1) % row_count) - - local ply_color = GetMSBColorForPlayer(ply) - - surface.SetDrawColor(clr(ply_color)) - surface.DrawRect(tmp_x, tmp_y, self.ply_ind_size, self.ply_ind_size) - - if ply:WasRevivedAndConfirmed() then - draw.FilteredTexture(tmp_x + 3, tmp_y + 3, self.ply_ind_size - 6, self.ply_ind_size - 6, self.icon_revived, 180, COLOR_BLACK) - elseif ply:OnceFound() and not ply:RoleKnown() then -- draw marker on indirect confirmed bodies - draw.FilteredTexture(tmp_x + 3, tmp_y + 3, self.ply_ind_size - 6, self.ply_ind_size - 6, self.icon_in_conf, 120, COLOR_BLACK) - end - - -- draw lines around the element - self:DrawLines(tmp_x, tmp_y, self.ply_ind_size, self.ply_ind_size, ply_color.a) - end - - -- draw lines around the element - if not self:InheritParentBorder() then - self:DrawLines(self.pos.x, self.pos.y, self.size.w, self.size.h, self.basecolor.a) - end + -- just update every 0.1 seconds; TODO maybe add a client ConVar + if self.lastUpdate + 0.1 < CurTime() then + local plys = util.GetFilteredPlayers(function(ply) + return ply:WasActiveInRound() + end) + + if #plys ~= self.curPlayerCount then + plysList = plys + self.curPlayerCount = #plys + + self:PerformLayout() + + -- sort playerlist: confirmed players should be in the first position + table.sort(plysList, SortMiniscoreboardFunc) + end + + self.lastUpdate = CurTime() + end + + -- draw bg and shadow + self:DrawBg(self.pos.x, self.pos.y, self.size.w, self.size.h, self.basecolor) + + -- draw dark bottom overlay + surface.SetDrawColor(0, 0, 0, 90) + surface.DrawRect(self.pos.x, self.pos.y, self.size.w, self.size.h) + + -- draw squares + local tmp_x, tmp_y = self.pos.x, self.pos.y + + for i = 1, self.curPlayerCount do + local ply = plysList[i] + + tmp_x = self.pos.x + + self.margin + + (self.element_margin + self.ply_ind_size) * math.floor((i - 1) * 0.5) + tmp_y = self.pos.y + + self.margin + + (self.element_margin + self.ply_ind_size) * ((i - 1) % row_count) + + local ply_color = GetMSBColorForPlayer(ply) + + surface.SetDrawColor(clr(ply_color)) + surface.DrawRect(tmp_x, tmp_y, self.ply_ind_size, self.ply_ind_size) + + if IsValid(ply) then + if ply:WasRevivedAndConfirmed() then + draw.FilteredTexture( + tmp_x + 3, + tmp_y + 3, + self.ply_ind_size - 6, + self.ply_ind_size - 6, + self.icon_revived, + 180, + COLOR_BLACK + ) + elseif ply:OnceFound() and not ply:RoleKnown() then -- draw marker on indirect confirmed bodies + draw.FilteredTexture( + tmp_x + 3, + tmp_y + 3, + self.ply_ind_size - 6, + self.ply_ind_size - 6, + self.icon_in_conf, + 120, + COLOR_BLACK + ) + end + end + + -- draw lines around the element + self:DrawLines(tmp_x, tmp_y, self.ply_ind_size, self.ply_ind_size, ply_color.a) + end + + -- draw lines around the element + if not self:InheritParentBorder() then + self:DrawLines(self.pos.x, self.pos.y, self.size.w, self.size.h, self.basecolor.a) + end end --- @@ -202,6 +231,4 @@ end -- @return nil|Color The new color for the dot -- @hook -- @realm client -function GM:TTT2ModifyMiniscoreboardColor(ply, col) - -end +function GM:TTT2ModifyMiniscoreboardColor(ply, col) end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttmstack/old_ttt_mstack.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttmstack/old_ttt_mstack.lua index ef7334118..aebf34b3d 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttmstack/old_ttt_mstack.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttmstack/old_ttt_mstack.lua @@ -7,244 +7,253 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local surface = surface - local draw = draw - local math = math - - -- Constants for configuration - local msg_sound = Sound("Hud.Hint") - local base_text_display_options = { - font = "DefaultBold", - xalign = TEXT_ALIGN_CENTER, - yalign = TEXT_ALIGN_TOP - } - - local top_y = 0 - local top_x = 0 - - local staytime = 12 - local max_items = 8 - - local fadein = 0.1 - local fadeout = 0.6 - local movespeed = 2 - - local margin = 6 - local line_margin = 6 - local top_margin = 8 - local title_bottom_margin = 8 - local msg_width = 400 - local text_width = msg_width - line_margin * 3 - local pad = 7 - local leftImagePad = 10 - local msgfont = "DefaultBold" - local imagedmsgfont = "DefaultBold" - local text_color = COLOR_WHITE - local bg_color = Color(0, 0, 0, 200) - local imageSize = 64 - local imageMinHeight = imageSize + 2 * pad - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = msg_width, h = 80}, - minsize = {w = msg_width, h = 80} - } + local surface = surface + local draw = draw + local math = math + + -- Constants for configuration + local msg_sound = Sound("Hud.Hint") + local base_text_display_options = { + font = "DefaultBold", + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_TOP, + } + + local top_y = 0 + local top_x = 0 + + local staytime = 12 + local max_items = 8 + + local fadein = 0.1 + local fadeout = 0.6 + local movespeed = 2 + + local margin = 6 + local line_margin = 6 + local top_margin = 8 + local title_bottom_margin = 8 + local msg_width = 400 + local text_width = msg_width - line_margin * 3 + local pad = 7 + local leftImagePad = 10 + local msgfont = "DefaultBold" + local imagedmsgfont = "DefaultBold" + local text_color = COLOR_WHITE + local bg_color = Color(0, 0, 0, 200) + local imageSize = 64 + local imageMinHeight = imageSize + 2 * pad + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = msg_width, h = 80 }, + minsize = { w = msg_width, h = 80 }, + } + + function HUDELEMENT:Initialize() + base_text_display_options = { + font = msgfont, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_TOP, + } + + BaseClass.Initialize(self) + end + + function HUDELEMENT:GetDefaults() + top_y = margin + top_x = ScrW() - margin - msg_width + + const_defaults["basepos"] = { x = top_x, y = top_y } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + top_x = self.pos.x + top_y = self.pos.y + + msg_width = self.size.w + text_width = msg_width - pad * 2 + + -- invalidate previous item size calculations + for _, v in pairs(MSTACK.msgs) do + v.ready = false + end + + BaseClass.PerformLayout(self) + end + + local function PrepareItem(item, bg_col) + local max_text_width = text_width + local item_height = pad * 2 + + item.text_spec = table.Copy(base_text_display_options) - function HUDELEMENT:Initialize() - base_text_display_options = { - font = msgfont, - xalign = TEXT_ALIGN_CENTER, - yalign = TEXT_ALIGN_TOP - } + item.bg = item.bg and table.Copy(item.bg) or table.Copy(bg_col) + item.bg.a_max = item.bg.a - BaseClass.Initialize(self) - end - - function HUDELEMENT:GetDefaults() - top_y = margin - top_x = ScrW() - margin - msg_width - - const_defaults["basepos"] = {x = top_x, y = top_y} + item.col = item.col and table.Copy(item.col) or table.Copy(text_color) + item.col.a_max = item.col.a + + if item.image then + max_text_width = max_text_width - imageSize - leftImagePad + item.text_spec.font = imagedmsgfont + item.title_spec = table.Copy(base_text_display_options) + item.title_spec.font = imagedmsgfont + item.title_spec.font_height = draw.GetFontHeight(item.title_spec.font) + + item.title_wrapped = + draw.GetWrappedText(item.title, max_text_width, item.title_spec.font) - return const_defaults - end + -- calculate the new height + item_height = item_height + + top_margin + + title_bottom_margin + + #item.title_wrapped * (item.title_spec.font_height + line_margin) + - line_margin + end - function HUDELEMENT:PerformLayout() - top_x = self.pos.x - top_y = self.pos.y + item.text_spec.font_height = draw.GetFontHeight(item.text_spec.font) - msg_width = self.size.w - text_width = msg_width - pad * 2 + item.text_wrapped = draw.GetWrappedText(item.text, max_text_width, item.text_spec.font) - -- invalidate previous item size calculations - for _, v in pairs(MSTACK.msgs) do - v.ready = false - end + -- Height depends on number of lines, which is equal to number of table + -- elements of the wrapped item.text + item_height = item_height + + #item.text_wrapped * (item.text_spec.font_height + line_margin) + - line_margin - BaseClass.PerformLayout(self) - end + if item.image then + item_height = math.max(item_height, imageMinHeight) + end - local function PrepareItem(item, bg_col) - local max_text_width = text_width - local item_height = pad * 2 + item.move_y = -item_height + item.height = item_height - item.text_spec = table.Copy(base_text_display_options) + item.ready = true + end - item.bg = item.bg and table.Copy(item.bg) or table.Copy(bg_col) - item.bg.a_max = item.bg.a + function HUDELEMENT:DrawSmallMessage(item, pos_y, alpha) + -- Background box + draw.RoundedBox(8, top_x, pos_y, msg_width, item.height, item.bg) - item.col = item.col and table.Copy(item.col) or table.Copy(text_color) - item.col.a_max = item.col.a + -- Text + local tx = top_x + msg_width * 0.5 + local ty = pos_y + pad - if item.image then - max_text_width = max_text_width - imageSize - leftImagePad - item.text_spec.font = imagedmsgfont - item.title_spec = table.Copy(base_text_display_options) - item.title_spec.font = imagedmsgfont - item.title_spec.font_height = draw.GetFontHeight(item.title_spec.font) + -- draw the normal text + local text_spec = item.text_spec + text_spec.color = item.col - item.title_wrapped = draw.GetWrappedText(item.title, max_text_width, item.title_spec.font) + for i = 1, #item.text_wrapped do + text_spec.text = item.text_wrapped[i] + text_spec.pos = { tx, ty } - -- calculate the new height - item_height = item_height + top_margin + title_bottom_margin + #item.title_wrapped * (item.title_spec.font_height + line_margin) - line_margin - end + draw.TextShadow(text_spec, 1, alpha) - item.text_spec.font_height = draw.GetFontHeight(item.text_spec.font) + ty = ty + text_spec.font_height + line_margin + end + end - item.text_wrapped = draw.GetWrappedText(item.text, max_text_width, item.text_spec.font) + function HUDELEMENT:DrawMessageWithImage(item, pos_y, alpha) + -- Background box + draw.RoundedBox(8, top_x, pos_y, msg_width, item.height, item.bg) - -- Height depends on number of lines, which is equal to number of table - -- elements of the wrapped item.text - item_height = item_height + #item.text_wrapped * (item.text_spec.font_height + line_margin) - line_margin + -- Text + local tx = top_x + (msg_width + leftImagePad + pad + imageSize) * 0.5 + local ty = pos_y + pad + top_margin - if item.image then - item_height = math.max(item_height, imageMinHeight) - end + -- draw the title text + local title_spec = item.title_spec + title_spec.color = item.col - item.move_y = -item_height - item.height = item_height + for i = 1, #item.title_wrapped do + title_spec.text = item.title_wrapped[i] + title_spec.pos = { tx, ty } - item.ready = true - end + draw.TextShadow(title_spec, 1, alpha) - function HUDELEMENT:DrawSmallMessage(item, pos_y, alpha) - -- Background box - draw.RoundedBox(8, top_x, pos_y, msg_width, item.height, item.bg) + ty = ty + title_spec.font_height + line_margin + end - -- Text - local tx = top_x + msg_width * 0.5 - local ty = pos_y + pad + ty = ty + title_bottom_margin - line_margin -- remove old margin used for new line set in for loop above - -- draw the normal text - local text_spec = item.text_spec - text_spec.color = item.col + -- draw the normal text + local text_spec = item.text_spec + text_spec.color = item.col - for i = 1, #item.text_wrapped do - text_spec.text = item.text_wrapped[i] - text_spec.pos = {tx, ty} + for i = 1, #item.text_wrapped do + text_spec.text = item.text_wrapped[i] + text_spec.pos = { tx, ty } - draw.TextShadow(text_spec, 1, alpha) + draw.TextShadow(text_spec, 1, alpha) - ty = ty + text_spec.font_height + line_margin - end - end + ty = ty + text_spec.font_height + line_margin + end - function HUDELEMENT:DrawMessageWithImage(item, pos_y, alpha) - -- Background box - draw.RoundedBox(8, top_x, pos_y, msg_width, item.height, item.bg) + -- image + surface.SetMaterial(item.image) + surface.SetDrawColor(255, 255, 255, item.bg.a) + surface.DrawTexturedRect(top_x + pad, pos_y + pad, imageSize, imageSize) + end - -- Text - local tx = top_x + (msg_width + leftImagePad + pad + imageSize) * 0.5 - local ty = pos_y + pad + top_margin + function HUDELEMENT:Draw() + if next(MSTACK.msgs) == nil then + return + end -- fast empty check - -- draw the title text - local title_spec = item.title_spec - title_spec.color = item.col + local running_y = top_y - for i = 1, #item.title_wrapped do - title_spec.text = item.title_wrapped[i] - title_spec.pos = {tx, ty} + for k, item in pairs(MSTACK.msgs) do + if item.time < CurTime() then + if not item.ready then + PrepareItem(item, bg_color) + end - draw.TextShadow(title_spec, 1, alpha) + if item.sounded == false then + LocalPlayer():EmitSound(msg_sound, 80, 250) - ty = ty + title_spec.font_height + line_margin - end + item.sounded = true + end - ty = ty + title_bottom_margin - line_margin -- remove old margin used for new line set in for loop above + -- Apply move effects to y + local y = running_y + margin + item.move_y - -- draw the normal text - local text_spec = item.text_spec - text_spec.color = item.col + item.move_y = (item.move_y < 0) and item.move_y + movespeed or 0 - for i = 1, #item.text_wrapped do - text_spec.text = item.text_wrapped[i] - text_spec.pos = {tx, ty} + local delta = item.time + staytime - CurTime() + delta = delta / staytime -- pct of staytime left - draw.TextShadow(text_spec, 1, alpha) + -- Hurry up if we have too many + if k >= max_items then + delta = delta * 0.5 + end - ty = ty + text_spec.font_height + line_margin - end + local alpha = 255 + -- These somewhat arcane delta and alpha equations are from gmod's + -- HUDPickup stuff + if delta > 1 - fadein then + alpha = math.Clamp((1.0 - delta) * (255 / fadein), 0, 255) + elseif delta < fadeout then + alpha = math.Clamp(delta * (255 / fadeout), 0, 255) + end - -- image - surface.SetMaterial(item.image) - surface.SetDrawColor(255, 255, 255, item.bg.a) - surface.DrawTexturedRect(top_x + pad, pos_y + pad, imageSize, imageSize) - end + item.bg.a = math.Clamp(alpha, 0, item.bg.a_max) + item.col.a = math.Clamp(alpha, 0, item.col.a_max) - function HUDELEMENT:Draw() - if next(MSTACK.msgs) == nil then return end -- fast empty check + if item.image then + self:DrawMessageWithImage(item, y, alpha) + else + self:DrawSmallMessage(item, y, alpha) + end - local running_y = top_y + if alpha == 0 then + MSTACK.msgs[k] = nil + end - for k, item in pairs(MSTACK.msgs) do - if item.time < CurTime() then - if not item.ready then - PrepareItem(item, bg_color) - end - - if item.sounded == false then - LocalPlayer():EmitSound(msg_sound, 80, 250) - - item.sounded = true - end - - -- Apply move effects to y - local y = running_y + margin + item.move_y - - item.move_y = (item.move_y < 0) and item.move_y + movespeed or 0 - - local delta = item.time + staytime - CurTime() - delta = delta / staytime -- pct of staytime left - - -- Hurry up if we have too many - if k >= max_items then - delta = delta * 0.5 - end - - local alpha = 255 - -- These somewhat arcane delta and alpha equations are from gmod's - -- HUDPickup stuff - if delta > 1 - fadein then - alpha = math.Clamp((1.0 - delta) * (255 / fadein), 0, 255) - elseif delta < fadeout then - alpha = math.Clamp(delta * (255 / fadeout), 0, 255) - end - - item.bg.a = math.Clamp(alpha, 0, item.bg.a_max) - item.col.a = math.Clamp(alpha, 0, item.col.a_max) - - if item.image then - self:DrawMessageWithImage(item, y, alpha) - else - self:DrawSmallMessage(item, y, alpha) - end - - if alpha == 0 then - MSTACK.msgs[k] = nil - end - - running_y = y + item.height - end - end - end + running_y = y + item.height + end + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttmstack/pure_skin_mstack.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttmstack/pure_skin_mstack.lua index a1dc5a509..6f0791d9f 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttmstack/pure_skin_mstack.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttmstack/pure_skin_mstack.lua @@ -7,283 +7,321 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local draw = draw - local math = math - - -- Constants for configuration - local msg_sound = Sound("Hud.Hint") - local base_text_display_options = { - font = "DefaultBold", - xalign = TEXT_ALIGN_LEFT, - yalign = TEXT_ALIGN_TOP - } - - local leftPad = 10 - local margin = 5 - local line_margin = 6 - local top_margin = 4 - local title_bottom_margin = 8 - local pad = 6 - local leftImagePad = 10 - local image_size = 64 - - local staytime = 12 - local max_items = 8 - - local fadein = 0.1 - local fadeout = 0.6 - local movespeed = 2 - - local msgfont = "PureSkinMSTACKMsg" - local imagedmsgfont = "PureSkinMSTACKImageMsg" - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 400, h = 80}, - minsize = {w = 250, h = 80} - } - - function HUDELEMENT:Initialize() - self.margin = margin - - local defaults = self:GetDefaults() - - self.scale = 1.0 - self.basecolor = self:GetHUDBasecolor() - - self.leftPad = leftPad - self.line_margin = line_margin - self.top_margin = top_margin - self.title_bottom_margin = title_bottom_margin - self.pad = pad - self.leftImagePad = leftImagePad - self.text_width = defaults.size.w - self.pad * 2 - self.leftPad - self.image_size = image_size - self.imageMinHeight = self.image_size + 2 * self.pad - - base_text_display_options = { - font = msgfont, - xalign = TEXT_ALIGN_LEFT, - yalign = TEXT_ALIGN_TOP - } - - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return true, false - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = ScrW() - self.margin - self.size.w, y = self.margin} - - return const_defaults - end - - function HUDELEMENT:PerformLayout() - self.scale = appearance.GetGlobalScale() - self.basecolor = self:GetHUDBasecolor() - - self.leftPad = leftPad * self.scale - self.margin = margin * self.scale - self.line_margin = line_margin * self.scale - self.top_margin = top_margin * self.scale - self.title_bottom_margin = title_bottom_margin * self.scale - self.pad = pad * self.scale - self.leftImagePad = leftImagePad * self.scale - self.image_size = image_size * self.scale - self.imageMinHeight = self.image_size + 2 * self.pad - self.text_width = self.size.w - self.pad * 2 - self.leftPad - - -- invalidate previous item size calculations - for _, v in pairs(MSTACK.msgs) do - v.ready = false - end - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:PrepareItem(item, bg_color) - local max_text_width = self.size.w - self.pad * 2 - self.leftPad - - item.text_spec = table.Copy(base_text_display_options) - - item.bg = item.bg and table.Copy(item.bg) or table.Copy(bg_color) - item.bg.a_max = item.bg.a - - item.col = item.col and table.Copy(item.col) or table.Copy(util.GetDefaultColor(item.bg)) - item.col.a_max = item.col.a - - if item.image then - max_text_width = self.text_width - self.leftImagePad - self.image_size - item.text_spec.font = imagedmsgfont - item.title_spec = table.Copy(base_text_display_options) - item.title_spec.font = imagedmsgfont - item.title_spec.font_height = draw.GetFontHeight(item.title_spec.font) * self.scale - - item.title_wrapped = draw.GetWrappedText(item.title, max_text_width, item.title_spec.font, self.scale) - end - - item.text_spec.font_height = draw.GetFontHeight(item.text_spec.font) * self.scale - - item.text_wrapped = draw.GetWrappedText(item.text, max_text_width, item.text_spec.font, self.scale) - - local title_lines = item.title_wrapped and #item.title_wrapped or 0 - local text_lines = item.text_wrapped and #item.text_wrapped or 0 - - local text_height = ((title_lines > 0) and (title_lines * item.title_spec.font_height) or 0) + ((text_lines > 0) and (text_lines * item.text_spec.font_height) or 0) - text_height = text_height + math.max(title_lines + text_lines - 1, 0) * self.line_margin - - if not item.image and text_height < self.image_size then - item.init_y = 0 - elseif item.image and text_height > self.image_size then -- display text next to image (higher than image) - item.init_y = 0.5 * self.pad - else -- display text next to image (centered) - item.init_y = 0.5 * (self.imageMinHeight - text_height) + self.pad - item.text_spec.yalign = TEXT_ALIGN_CENTER - end - - -- Height depends on number of lines, which is equal to number of table - -- elements of the wrapped item.text - local item_height = text_height + 2 * self.pad - - if item.image then - item_height = math.max(item_height, self.imageMinHeight) - end - - item.move_y = -item_height - item.height = item_height - - item.ready = true - end - - function HUDELEMENT:DrawSmallMessage(item, pos_y, alpha) - -- Background box - self:DrawBg(self.pos.x, pos_y, self.size.w, item.height, item.bg) - - -- Text - local tx = self.pos.x + self.pad + self.leftPad - local ty = pos_y + self.pad - - -- draw the normal text - local text_spec = item.text_spec - text_spec.color = item.col - - for i = 1, #item.text_wrapped do - text_spec.text = item.text_wrapped[i] - text_spec.pos = {tx, ty} - - --draw.TextShadow(text_spec, 1, alpha) - draw.AdvancedText(text_spec.text, text_spec.font, text_spec.pos[1], text_spec.pos[2], text_spec.color, text_spec.xalign, text_spec.yalign, true, self.scale) - - ty = ty + text_spec.font_height + self.line_margin - end - - self:DrawLines(self.pos.x, pos_y, self.size.w, item.height, item.bg.a) - end - - function HUDELEMENT:DrawMessageWithImage(item, pos_y, alpha) - -- Background box - self:DrawBg(self.pos.x, pos_y, self.size.w, item.height, item.bg) - - -- Text - local tx = self.pos.x + self.image_size + self.pad + self.leftImagePad - local ty = pos_y + item.init_y - - -- draw the title text - local title_spec = item.title_spec - title_spec.color = item.col - - for i = 1, #item.title_wrapped do - title_spec.text = item.title_wrapped[i] - title_spec.pos = {tx, ty} - - draw.AdvancedText(title_spec.text, title_spec.font, title_spec.pos[1], title_spec.pos[2], title_spec.color, title_spec.xalign, title_spec.yalign, true, self.scale) - - ty = ty + title_spec.font_height + self.line_margin - end - - ty = ty + self.title_bottom_margin - self.line_margin -- remove old margin used for new line set in for loop above - - -- draw the normal text - local text_spec = item.text_spec - text_spec.color = item.col - - for i = 1, #item.text_wrapped do - text_spec.text = item.text_wrapped[i] - text_spec.pos = {tx, ty} - - draw.AdvancedText(text_spec.text, text_spec.font, text_spec.pos[1], text_spec.pos[2], text_spec.color, text_spec.xalign, text_spec.yalign, true, self.scale) - - ty = ty + text_spec.font_height + self.line_margin - end - - -- image - surface.SetMaterial(item.image) - surface.SetDrawColor(255, 255, 255, item.bg.a) - surface.DrawTexturedRect(self.pos.x + self.pad, pos_y + self.pad, self.image_size, self.image_size) - - self:DrawLines(self.pos.x, pos_y, self.size.w, item.height, item.bg.a) - end - - function HUDELEMENT:ShouldDraw() - return next(MSTACK.msgs) ~= nil or HUDEditor.IsEditing - end - - function HUDELEMENT:Draw() - local running_y = self.pos.y - - for k, item in pairs(MSTACK.msgs) do - if item.time < CurTime() then - if not item.ready then - self:PrepareItem(item, self.basecolor) - end - - if item.sounded == false then - LocalPlayer():EmitSound(msg_sound, 80, 250) - - item.sounded = true - end - - -- Apply move effects to y - local y = running_y + self.line_margin + item.move_y - - item.move_y = (item.move_y < 0) and item.move_y + movespeed or 0 - - local delta = item.time + staytime - CurTime() - delta = delta / staytime -- pct of staytime left - - -- Hurry up if we have too many - if k >= max_items then - delta = delta * 0.5 - end - - local alpha = 255 - -- These somewhat arcane delta and alpha equations are from gmod's - -- HUDPickup stuff - if delta > 1 - fadein then - alpha = math.Clamp((1.0 - delta) * (255 / fadein), 0, 255) - elseif delta < fadeout then - alpha = math.Clamp(delta * (255 / fadeout), 0, 255) - end - - item.bg.a = math.Clamp(alpha, 0, item.bg.a_max) - item.col.a = math.Clamp(alpha, 0, item.col.a_max) - - if item.image then - self:DrawMessageWithImage(item, y, alpha) - else - self:DrawSmallMessage(item, y, alpha) - end - - if alpha == 0 then - MSTACK.msgs[k] = nil - end - - running_y = y + item.height - end - end - end + local draw = draw + local math = math + + -- Constants for configuration + local msg_sound = Sound("Hud.Hint") + local base_text_display_options = { + font = "DefaultBold", + xalign = TEXT_ALIGN_LEFT, + yalign = TEXT_ALIGN_TOP, + } + + local leftPad = 10 + local margin = 5 + local line_margin = 6 + local top_margin = 4 + local title_bottom_margin = 8 + local pad = 6 + local leftImagePad = 10 + local image_size = 64 + + local staytime = 12 + local max_items = 8 + + local fadein = 0.1 + local fadeout = 0.6 + local movespeed = 2 + + local msgfont = "PureSkinMSTACKMsg" + local imagedmsgfont = "PureSkinMSTACKImageMsg" + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 400, h = 80 }, + minsize = { w = 250, h = 80 }, + } + + function HUDELEMENT:Initialize() + self.margin = margin + + local defaults = self:GetDefaults() + + self.scale = 1.0 + self.basecolor = self:GetHUDBasecolor() + + self.leftPad = leftPad + self.line_margin = line_margin + self.top_margin = top_margin + self.title_bottom_margin = title_bottom_margin + self.pad = pad + self.leftImagePad = leftImagePad + self.text_width = defaults.size.w - self.pad * 2 - self.leftPad + self.image_size = image_size + self.imageMinHeight = self.image_size + 2 * self.pad + + base_text_display_options = { + font = msgfont, + xalign = TEXT_ALIGN_LEFT, + yalign = TEXT_ALIGN_TOP, + } + + BaseClass.Initialize(self) + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return true, false + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = { x = ScrW() - self.margin - self.size.w, y = self.margin } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + self.scale = appearance.GetGlobalScale() + self.basecolor = self:GetHUDBasecolor() + + self.leftPad = leftPad * self.scale + self.margin = margin * self.scale + self.line_margin = line_margin * self.scale + self.top_margin = top_margin * self.scale + self.title_bottom_margin = title_bottom_margin * self.scale + self.pad = pad * self.scale + self.leftImagePad = leftImagePad * self.scale + self.image_size = image_size * self.scale + self.imageMinHeight = self.image_size + 2 * self.pad + self.text_width = self.size.w - self.pad * 2 - self.leftPad + + -- invalidate previous item size calculations + for _, v in pairs(MSTACK.msgs) do + v.ready = false + end + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:PrepareItem(item, bg_color) + local max_text_width = self.size.w - self.pad * 2 - self.leftPad + + item.text_spec = table.Copy(base_text_display_options) + + item.bg = item.bg and table.Copy(item.bg) or table.Copy(bg_color) + item.bg.a_max = item.bg.a + + item.col = item.col and table.Copy(item.col) or table.Copy(util.GetDefaultColor(item.bg)) + item.col.a_max = item.col.a + + if item.image then + max_text_width = self.text_width - self.leftImagePad - self.image_size + item.text_spec.font = imagedmsgfont + item.title_spec = table.Copy(base_text_display_options) + item.title_spec.font = imagedmsgfont + item.title_spec.font_height = draw.GetFontHeight(item.title_spec.font) * self.scale + + item.title_wrapped = + draw.GetWrappedText(item.title, max_text_width, item.title_spec.font, self.scale) + end + + item.text_spec.font_height = draw.GetFontHeight(item.text_spec.font) * self.scale + + item.text_wrapped = + draw.GetWrappedText(item.text, max_text_width, item.text_spec.font, self.scale) + + local title_lines = item.title_wrapped and #item.title_wrapped or 0 + local text_lines = item.text_wrapped and #item.text_wrapped or 0 + + local text_height = ((title_lines > 0) and (title_lines * item.title_spec.font_height) or 0) + + ((text_lines > 0) and (text_lines * item.text_spec.font_height) or 0) + text_height = text_height + math.max(title_lines + text_lines - 1, 0) * self.line_margin + + if not item.image and text_height < self.image_size then + item.init_y = 0 + elseif item.image and text_height > self.image_size then -- display text next to image (higher than image) + item.init_y = 0.5 * self.pad + else -- display text next to image (centered) + item.init_y = 0.5 * (self.imageMinHeight - text_height) + self.pad + item.text_spec.yalign = TEXT_ALIGN_CENTER + end + + -- Height depends on number of lines, which is equal to number of table + -- elements of the wrapped item.text + local item_height = text_height + 2 * self.pad + + if item.image then + item_height = math.max(item_height, self.imageMinHeight) + end + + item.move_y = -item_height + item.height = item_height + + item.ready = true + end + + function HUDELEMENT:DrawSmallMessage(item, pos_y, alpha) + -- Background box + self:DrawBg(self.pos.x, pos_y, self.size.w, item.height, item.bg) + + -- Text + local tx = self.pos.x + self.pad + self.leftPad + local ty = pos_y + self.pad + + -- draw the normal text + local text_spec = item.text_spec + text_spec.color = item.col + + for i = 1, #item.text_wrapped do + text_spec.text = item.text_wrapped[i] + text_spec.pos = { tx, ty } + + --draw.TextShadow(text_spec, 1, alpha) + draw.AdvancedText( + text_spec.text, + text_spec.font, + text_spec.pos[1], + text_spec.pos[2], + text_spec.color, + text_spec.xalign, + text_spec.yalign, + true, + self.scale + ) + + ty = ty + text_spec.font_height + self.line_margin + end + + self:DrawLines(self.pos.x, pos_y, self.size.w, item.height, item.bg.a) + end + + function HUDELEMENT:DrawMessageWithImage(item, pos_y, alpha) + -- Background box + self:DrawBg(self.pos.x, pos_y, self.size.w, item.height, item.bg) + + -- Text + local tx = self.pos.x + self.image_size + self.pad + self.leftImagePad + local ty = pos_y + item.init_y + + -- draw the title text + local title_spec = item.title_spec + title_spec.color = item.col + + for i = 1, #item.title_wrapped do + title_spec.text = item.title_wrapped[i] + title_spec.pos = { tx, ty } + + draw.AdvancedText( + title_spec.text, + title_spec.font, + title_spec.pos[1], + title_spec.pos[2], + title_spec.color, + title_spec.xalign, + title_spec.yalign, + true, + self.scale + ) + + ty = ty + title_spec.font_height + self.line_margin + end + + ty = ty + self.title_bottom_margin - self.line_margin -- remove old margin used for new line set in for loop above + + -- draw the normal text + local text_spec = item.text_spec + text_spec.color = item.col + + for i = 1, #item.text_wrapped do + text_spec.text = item.text_wrapped[i] + text_spec.pos = { tx, ty } + + draw.AdvancedText( + text_spec.text, + text_spec.font, + text_spec.pos[1], + text_spec.pos[2], + text_spec.color, + text_spec.xalign, + text_spec.yalign, + true, + self.scale + ) + + ty = ty + text_spec.font_height + self.line_margin + end + + -- image + surface.SetMaterial(item.image) + surface.SetDrawColor(255, 255, 255, item.bg.a) + surface.DrawTexturedRect( + self.pos.x + self.pad, + pos_y + self.pad, + self.image_size, + self.image_size + ) + + self:DrawLines(self.pos.x, pos_y, self.size.w, item.height, item.bg.a) + end + + function HUDELEMENT:ShouldDraw() + return next(MSTACK.msgs) ~= nil or HUDEditor.IsEditing + end + + function HUDELEMENT:Draw() + local running_y = self.pos.y + + for k, item in pairs(MSTACK.msgs) do + if item.time < CurTime() then + if not item.ready then + self:PrepareItem(item, self.basecolor) + end + + if item.sounded == false then + LocalPlayer():EmitSound(msg_sound, 80, 250) + + item.sounded = true + end + + -- Apply move effects to y + local y = running_y + self.line_margin + item.move_y + + item.move_y = (item.move_y < 0) and item.move_y + movespeed or 0 + + local delta = item.time + staytime - CurTime() + delta = delta / staytime -- pct of staytime left + + -- Hurry up if we have too many + if k >= max_items then + delta = delta * 0.5 + end + + local alpha = 255 + -- These somewhat arcane delta and alpha equations are from gmod's + -- HUDPickup stuff + if delta > 1 - fadein then + alpha = math.Clamp((1.0 - delta) * (255 / fadein), 0, 255) + elseif delta < fadeout then + alpha = math.Clamp(delta * (255 / fadeout), 0, 255) + end + + item.bg.a = math.Clamp(alpha, 0, item.bg.a_max) + item.col.a = math.Clamp(alpha, 0, item.col.a_max) + + if item.image then + self:DrawMessageWithImage(item, y, alpha) + else + self:DrawSmallMessage(item, y, alpha) + end + + if alpha == 0 then + MSTACK.msgs[k] = nil + end + + running_y = y + item.height + end + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttpickup/old_ttt_pickup.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttpickup/old_ttt_pickup.lua index e2312a407..ede9b55d1 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttpickup/old_ttt_pickup.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttpickup/old_ttt_pickup.lua @@ -11,110 +11,156 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local width = 200 - local height = 20 - local font = "DefaultBold" - local bordersize = 8 - local colorTipAmmo = Color(205, 155, 0, 255) - - HUDELEMENT.margin = 10 - HUDELEMENT.barcorner = surface.GetTextureID("gui/corner8") - HUDELEMENT.PickupHistoryTop = ScrH() * 0.5 - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 200, h = -height}, - minsize = {w = 200, h = height} - } - - function HUDELEMENT:Initialize() - BaseClass.Initialize(self) - end - - function HUDELEMENT:IsResizable() - return true, false - end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = ScrW() - width - 20, y = ScrH() * 0.5} - - return const_defaults - end - - function HUDELEMENT:PerformLayout() - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:DrawBar(x, y, w, h, tipSize, alpha, item) - local pad = 10 - - draw.RoundedBox(8, x, y, w, h, Color(20, 20, 20, math.Clamp(alpha, 0, 200))) - surface.SetTexture(self.barcorner) - - local tipColor = LocalPlayer():GetRoleDkColor() - - if item.type == PICKUP_ITEM then - tipColor = COLOR_WHITE - elseif item.type == PICKUP_AMMO then - tipColor = colorTipAmmo - end - - surface.SetDrawColor(tipColor.r, tipColor.g, tipColor.b, alpha) - surface.DrawTexturedRectRotated(x + bordersize * 0.5, y + bordersize * 0.5, bordersize, bordersize, 0) - surface.DrawTexturedRectRotated(x + bordersize * 0.5, y + h - bordersize * 0.5, bordersize, bordersize, 90) - surface.DrawRect(x, y + bordersize, bordersize, h - bordersize * 2) - surface.DrawRect(x + bordersize, y, tipSize, h) - - draw.SimpleText(item.name, font, x + tipSize + bordersize + pad + 2, y + h * 0.5 + 2, Color(0, 0, 0, alpha * 0.75), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - draw.SimpleText(item.name, font, x + tipSize + bordersize + pad, y + h * 0.5, Color(255, 255, 255, alpha), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - - if item.amount then - draw.SimpleText(item.amount, font, x + w - pad, y + h * 0.5 + 2, Color(0, 0, 0, alpha * 0.75), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) - draw.SimpleText(item.amount, font, x + w - pad, y + h * 0.5, Color(255, 255, 255, alpha), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) - end - end - - function HUDELEMENT:ShouldDraw() - return PICKUP.items ~= nil or HUDEditor.IsEditing - end - - function HUDELEMENT:Draw() - local pickupList = {} - - for k, v in pairs(PICKUP.items) do - if v.time >= CurTime() then continue end - - pickupList[#pickupList + 1] = {h = 20} - end - - self:SetElements(pickupList) - self:SetElementMargin(self.margin) - - BaseClass.Draw(self) - - PICKUP.RemoveOutdatedValues() - end - - function HUDELEMENT:DrawElement(i, x, y, w, h) - local item = PICKUP.items[i] - - local alpha = 255 - local delta = (item.time + item.holdtime - CurTime()) / item.holdtime - - if delta > 1 - item.fadein then - alpha = math.Clamp((1.0 - delta) * (255 / item.fadein), 1, 255) - elseif delta < item.fadeout then - alpha = math.Clamp(delta * (255 / item.fadeout), 0, 255) - end - - local shiftX = x + w - self.size.w * (alpha / 255) - local tipSize = h - 10 - - self:DrawBar(shiftX, y, w, h, tipSize, alpha, item) - - --mark item for removal - if alpha == 0 then - item.remove = true - end - end + local width = 200 + local height = 20 + local font = "DefaultBold" + local bordersize = 8 + local colorTipAmmo = Color(205, 155, 0, 255) + + HUDELEMENT.margin = 10 + HUDELEMENT.barcorner = surface.GetTextureID("gui/corner8") + HUDELEMENT.PickupHistoryTop = ScrH() * 0.5 + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 200, h = -height }, + minsize = { w = 200, h = height }, + } + + function HUDELEMENT:Initialize() + BaseClass.Initialize(self) + end + + function HUDELEMENT:IsResizable() + return true, false + end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = { x = ScrW() - width - 20, y = ScrH() * 0.5 } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:DrawBar(x, y, w, h, tipSize, alpha, item) + local pad = 10 + + draw.RoundedBox(8, x, y, w, h, Color(20, 20, 20, math.Clamp(alpha, 0, 200))) + surface.SetTexture(self.barcorner) + + local tipColor = LocalPlayer():GetRoleDkColor() + + if item.type == PICKUP_ITEM then + tipColor = COLOR_WHITE + elseif item.type == PICKUP_AMMO then + tipColor = colorTipAmmo + end + + surface.SetDrawColor(tipColor.r, tipColor.g, tipColor.b, alpha) + surface.DrawTexturedRectRotated( + x + bordersize * 0.5, + y + bordersize * 0.5, + bordersize, + bordersize, + 0 + ) + surface.DrawTexturedRectRotated( + x + bordersize * 0.5, + y + h - bordersize * 0.5, + bordersize, + bordersize, + 90 + ) + surface.DrawRect(x, y + bordersize, bordersize, h - bordersize * 2) + surface.DrawRect(x + bordersize, y, tipSize, h) + + draw.SimpleText( + item.name, + font, + x + tipSize + bordersize + pad + 2, + y + h * 0.5 + 2, + Color(0, 0, 0, alpha * 0.75), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) + draw.SimpleText( + item.name, + font, + x + tipSize + bordersize + pad, + y + h * 0.5, + Color(255, 255, 255, alpha), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) + + if item.amount then + draw.SimpleText( + item.amount, + font, + x + w - pad, + y + h * 0.5 + 2, + Color(0, 0, 0, alpha * 0.75), + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_CENTER + ) + draw.SimpleText( + item.amount, + font, + x + w - pad, + y + h * 0.5, + Color(255, 255, 255, alpha), + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_CENTER + ) + end + end + + function HUDELEMENT:ShouldDraw() + return PICKUP.items ~= nil or HUDEditor.IsEditing + end + + function HUDELEMENT:Draw() + local pickupList = {} + + for k, v in pairs(PICKUP.items) do + if v.time >= CurTime() then + continue + end + + pickupList[#pickupList + 1] = { h = 20 } + end + + self:SetElements(pickupList) + self:SetElementMargin(self.margin) + + BaseClass.Draw(self) + + PICKUP.RemoveOutdatedValues() + end + + function HUDELEMENT:DrawElement(i, x, y, w, h) + local item = PICKUP.items[i] + + local alpha = 255 + local delta = (item.time + item.holdtime - CurTime()) / item.holdtime + + if delta > 1 - item.fadein then + alpha = math.Clamp((1.0 - delta) * (255 / item.fadein), 1, 255) + elseif delta < item.fadeout then + alpha = math.Clamp(delta * (255 / item.fadeout), 0, 255) + end + + local shiftX = x + w - self.size.w * (alpha / 255) + local tipSize = h - 10 + + self:DrawBar(shiftX, y, w, h, tipSize, alpha, item) + + --mark item for removal + if alpha == 0 then + item.remove = true + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttpickup/pure_skin_pickup.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttpickup/pure_skin_pickup.lua index 52d869b1c..77414b0b7 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttpickup/pure_skin_pickup.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttpickup/pure_skin_pickup.lua @@ -11,150 +11,170 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local width = 200 - local element_height = 27 - local font = "PureSkinMSTACKMsg" - local tipsize = element_height - local margin = 5 - local pad = 8 - local color_tip = Color(205, 155, 0, 255) - - HUDELEMENT.SlotIcons = {[WEAPON_HEAVY] = Material("vgui/ttt/pickup/icon_heavy.png"), - [WEAPON_PISTOL] = Material("vgui/ttt/pickup/icon_pistol.png"), - [WEAPON_NADE] = Material("vgui/ttt/pickup/icon_nades.png"), - [WEAPON_SPECIAL] = Material("vgui/ttt/pickup/icon_special.png"), - [WEAPON_EXTRA] = Material("vgui/ttt/pickup/icon_extra.png"), - [WEAPON_CLASS] = Material("vgui/ttt/pickup/icon_class.png") - } - - HUDELEMENT.icon_item = Material("vgui/ttt/pickup/icon_special.png") - HUDELEMENT.icon_ammo = Material("vgui/ttt/pickup/icon_ammo.png") - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = width, h = -element_height}, - minsize = {w = width, h = element_height} - } - - function HUDELEMENT:PreInitialize() - self.drawer = hudelements.GetStored("pure_skin_element") - end - - function HUDELEMENT:Initialize() - self.scale = 1.0 - self.basecolor = self:GetHUDBasecolor() - self.element_height = element_height * self.scale - self.margin = margin * self.scale - self.pad = pad * self.scale - self.tipsize = tipsize * self.scale - - BaseClass.Initialize(self) - end - - function HUDELEMENT:IsResizable() - return true, false - end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = ScrW() - self.size.w - self.margin * 2, y = ScrH() / 2} - - return const_defaults - end - - function HUDELEMENT:PerformLayout() - self.scale = appearance.GetGlobalScale() - self.basecolor = self:GetHUDBasecolor() - self.element_height = element_height * self.scale - self.margin = margin * self.scale - self.pad = pad * self.scale - self.tipsize = tipsize * self.scale - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:DrawBar(x, y, w, h, alpha, item) - - -- draw bg and shadow - local barColor = Color(self.basecolor.r, self.basecolor.g, self.basecolor.b, alpha) - self.drawer:DrawBg(x, y, w, h, barColor) - - --draw tip - local tipColor = COLOR_WHITE - local icon = self.icon_item - - if item.type == PICKUP_WEAPON then - tipColor = LocalPlayer():GetRoleColor() - icon = self.SlotIcons[item.kind] or self.icon_item - elseif item.type == PICKUP_AMMO then - tipColor = color_tip - icon = self.icon_ammo - end - - -- Draw the colour tip - surface.SetDrawColor(tipColor.r, tipColor.g, tipColor.b, alpha) - surface.DrawRect(x, y, self.tipsize, h) - - --draw icon - draw.FilteredShadowedTexture(x, y, h, h, icon, math.Round(alpha * 0.75), util.GetDefaultColor(tipColor), self.scale) - - -- draw lines around the element - self.drawer:DrawLines(x, y, w, h, alpha) - - --draw name text - local fontColor = util.GetDefaultColor(self.basecolor) - fontColor = Color(fontColor.r, fontColor.g, fontColor.b, alpha) - - draw.AdvancedText(item.name, font, x + self.tipsize + self.pad, y + h * 0.5, fontColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, true, self.scale) - - --draw amount text - if item.amount then - draw.AdvancedText(item.amount, font, x + w - self.pad, y + h * 0.5, fontColor, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER, true, self.scale) - end - end - - function HUDELEMENT:ShouldDraw() - return PICKUP.items ~= nil or HUDEditor.IsEditing - end - - function HUDELEMENT:Draw() - local pickupList = {} - local i = 0 - - for k, v in pairs(PICKUP.items) do - if v.time < CurTime() then - i = i + 1 - - pickupList[i] = {h = self.element_height} - end - end - - self:SetElements(pickupList) - self:SetElementMargin(self.margin) - - BaseClass.Draw(self) - - PICKUP.RemoveOutdatedValues() - end - - function HUDELEMENT:DrawElement(i, x, y, w, h) - local item = PICKUP.items[i] - - local alpha = 255 - local delta = (item.time + item.holdtime - CurTime()) / item.holdtime - - if delta > 1 - item.fadein then - alpha = math.Clamp((1.0 - delta) * (255 / item.fadein), 1, 255) - elseif delta < item.fadeout then - alpha = math.Clamp(delta * (255 / item.fadeout), 0, 255) - end - - local shiftX = x + w - self.size.w * (alpha / 255) - - self:DrawBar(shiftX, y, w, h, alpha, item) - - --mark item for removal - if alpha == 0 then - item.remove = true - end - end + local width = 200 + local element_height = 27 + local font = "PureSkinMSTACKMsg" + local tipsize = element_height + local margin = 5 + local pad = 8 + local color_tip = Color(205, 155, 0, 255) + + HUDELEMENT.icon_item = Material("vgui/ttt/pickup/icon_special.png") + HUDELEMENT.icon_ammo = Material("vgui/ttt/pickup/icon_ammo.png") + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = width, h = -element_height }, + minsize = { w = width, h = element_height }, + } + + function HUDELEMENT:PreInitialize() + self.drawer = hudelements.GetStored("pure_skin_element") + end + + function HUDELEMENT:Initialize() + self.scale = 1.0 + self.basecolor = self:GetHUDBasecolor() + self.element_height = element_height * self.scale + self.margin = margin * self.scale + self.pad = pad * self.scale + self.tipsize = tipsize * self.scale + + BaseClass.Initialize(self) + end + + function HUDELEMENT:IsResizable() + return true, false + end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = { x = ScrW() - self.size.w - self.margin * 2, y = ScrH() / 2 } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + self.scale = appearance.GetGlobalScale() + self.basecolor = self:GetHUDBasecolor() + self.element_height = element_height * self.scale + self.margin = margin * self.scale + self.pad = pad * self.scale + self.tipsize = tipsize * self.scale + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:DrawBar(x, y, w, h, alpha, item) + -- draw bg and shadow + local barColor = Color(self.basecolor.r, self.basecolor.g, self.basecolor.b, alpha) + self.drawer:DrawBg(x, y, w, h, barColor) + + --draw tip + local tipColor = COLOR_WHITE + local icon = self.icon_item + + if item.type == PICKUP_WEAPON then + tipColor = LocalPlayer():GetRoleColor() + icon = self.drawer.SlotIcons[item.kind] or self.icon_item + elseif item.type == PICKUP_AMMO then + tipColor = color_tip + icon = self.icon_ammo + end + + -- Draw the colour tip + surface.SetDrawColor(tipColor.r, tipColor.g, tipColor.b, alpha) + surface.DrawRect(x, y, self.tipsize, h) + + --draw icon + draw.FilteredShadowedTexture( + x, + y, + h, + h, + icon, + math.Round(alpha * 0.75), + util.GetDefaultColor(tipColor), + self.scale + ) + + -- draw lines around the element + self.drawer:DrawLines(x, y, w, h, alpha) + + --draw name text + local fontColor = util.GetDefaultColor(self.basecolor) + fontColor = Color(fontColor.r, fontColor.g, fontColor.b, alpha) + + draw.AdvancedText( + item.name, + font, + x + self.tipsize + self.pad, + y + h * 0.5, + fontColor, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + true, + self.scale + ) + + --draw amount text + if item.amount then + draw.AdvancedText( + item.amount, + font, + x + w - self.pad, + y + h * 0.5, + fontColor, + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_CENTER, + true, + self.scale + ) + end + end + + function HUDELEMENT:ShouldDraw() + return PICKUP.items ~= nil or HUDEditor.IsEditing + end + + function HUDELEMENT:Draw() + local pickupList = {} + local i = 0 + + for k, v in pairs(PICKUP.items) do + if v.time < CurTime() then + i = i + 1 + + pickupList[i] = { h = self.element_height } + end + end + + self:SetElements(pickupList) + self:SetElementMargin(self.margin) + + BaseClass.Draw(self) + + PICKUP.RemoveOutdatedValues() + end + + function HUDELEMENT:DrawElement(i, x, y, w, h) + local item = PICKUP.items[i] + + local alpha = 255 + local delta = (item.time + item.holdtime - CurTime()) / item.holdtime + + if delta > 1 - item.fadein then + alpha = math.Clamp((1.0 - delta) * (255 / item.fadein), 1, 255) + elseif delta < item.fadeout then + alpha = math.Clamp(delta * (255 / item.fadeout), 0, 255) + end + + local shiftX = x + w - self.size.w * (alpha / 255) + + self:DrawBar(shiftX, y, w, h, alpha, item) + + --mark item for removal + if alpha == 0 then + item.remove = true + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttpunchometer/old_ttt_punchometer.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttpunchometer/old_ttt_punchometer.lua index 34deb9d09..4ae55d731 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttpunchometer/old_ttt_punchometer.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttpunchometer/old_ttt_punchometer.lua @@ -13,74 +13,63 @@ HUDELEMENT.Base = base DEFINE_BASECLASS(base) if CLIENT then - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 0, h = 0}, - minsize = {w = 0, h = 0} - } - - function HUDELEMENT:Initialize() - self.cv_ttt_spectator_mode = GetConVar("ttt_spectator_mode"); - - BaseClass.Initialize(self) - end - - function HUDELEMENT:GetDefaults() - return const_defaults - end - - -- Paint punch-o-meter - local function PunchPaint(el, client) - local L = GetLang() - local punch = client:GetNWFloat("specpunches", 0) - local width, height = 200, 25 - local x = ScrW() * 0.5 - width * 0.5 - local y = el.margin * 0.5 + height - - el:PaintBar(x, y, width, height, el.ammo_colors, punch) - - local color = el.bg_colors.background_main - - draw.SimpleText(L.punch_title, "HealthAmmo", ScrW() * 0.5, y, color, TEXT_ALIGN_CENTER) - draw.SimpleText(L.punch_help, "TabLarge", ScrW() * 0.5, el.margin, COLOR_WHITE, TEXT_ALIGN_CENTER) - - local bonus = client:GetNWInt("bonuspunches", 0) - if bonus ~= 0 then - local text - - if bonus < 0 then - text = interp(L.punch_bonus, {num = bonus}) - else - text = interp(L.punch_malus, {num = bonus}) - end - - draw.SimpleText(text, "TabLarge", ScrW() * 0.5, y * 2, COLOR_WHITE, TEXT_ALIGN_CENTER) - end - end - - local key_params = {usekey = Key("+use", "USE"), helpkey = Key("gm_showhelp", "F1")} - - function HUDELEMENT:Draw() - local client = LocalPlayer() - - if client:Alive() and client:Team() ~= TEAM_SPEC then return end - - local L = GetLang() -- for fast direct table lookups - - -- Draw round state - local margin = self.margin - local tgt = client:GetObserverTarget() - - if IsValid(tgt) and tgt:IsPlayer() then - self:ShadowedText(tgt:Nick(), "TimeLeft", ScrW() * 0.5, margin, COLOR_WHITE, TEXT_ALIGN_CENTER) -- draw name of the spectators target - elseif IsValid(tgt) and tgt:GetNWEntity("spec_owner", nil) == client then - PunchPaint(self, client) -- punch bar if you are spectator and inside of an entity - else - self:ShadowedText(interp(L.spec_help, key_params), "TabLarge", ScrW() * 0.5, margin, COLOR_WHITE, TEXT_ALIGN_CENTER) - - if self.cv_ttt_spectator_mode:GetBool() then - self:ShadowedText(interp(L.spec_help2, key_params), "TabLarge",ScrW() * 0.5, margin + 20, COLOR_WHITE, TEXT_ALIGN_CENTER) - end - end - end + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 0, h = 0 }, + minsize = { w = 0, h = 0 }, + } + + function HUDELEMENT:Initialize() + self.cv_ttt_spectator_mode = GetConVar("ttt_spectator_mode") + + BaseClass.Initialize(self) + end + + function HUDELEMENT:GetDefaults() + return const_defaults + end + + -- Paint punch-o-meter + local function PunchPaint(el, client) + local L = GetLang() + local punch = client:GetNWFloat("specpunches", 0) + local width, height = 200, 25 + local x = ScrW() * 0.5 - width * 0.5 + local y = ScrH() - 120 + + el:PaintBar(x, y, width, height, el.ammo_colors, punch) + + local color = el.bg_colors.background_main + + draw.SimpleText(L.punch_title, "HealthAmmo", ScrW() * 0.5, y, color, TEXT_ALIGN_CENTER) + + local bonus = client:GetNWInt("bonuspunches", 0) + if bonus ~= 0 then + local text + + if bonus < 0 then + text = interp(L.punch_bonus, { num = bonus }) + else + text = interp(L.punch_malus, { num = bonus }) + end + + draw.SimpleText(text, "TabLarge", ScrW() * 0.5, y * 2, COLOR_WHITE, TEXT_ALIGN_CENTER) + end + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + local tgt = client:GetObserverTarget() + + if + not client:IsSpec() + or not IsValid(tgt) + or tgt:IsPlayer() + or tgt:GetNWEntity("spec_owner", nil) ~= client + then + return + end + + PunchPaint(self, client) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttpunchometer/pure_skin_punchometer.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttpunchometer/pure_skin_punchometer.lua index 36139565b..6b81d99a1 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttpunchometer/pure_skin_punchometer.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttpunchometer/pure_skin_punchometer.lua @@ -13,106 +13,104 @@ HUDELEMENT.Base = base DEFINE_BASECLASS(base) if CLIENT then - local draw_col = Color(205, 155, 0, 255) - - local pad = 7 - local margin = 14 - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 200, h = 40}, - minsize = {w = 100, h = 40} - } - - function HUDELEMENT:Initialize() - self.scale = 1.0 - self.basecolor = self:GetHUDBasecolor() - - self.pad = pad - self.margin = margin - - self.cv_ttt_spectator_mode = GetConVar("ttt_spectator_mode"); - - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return true, false - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = ScrW() * 0.5 - self.size.w * 0.5, y = self.margin + 72 * self.scale} - - return const_defaults - end - - -- Paint punch-o-meter - function HUDELEMENT:PunchPaint() - local client = LocalPlayer() - local L = GetLang() - local punch = client:GetNWFloat("specpunches", 0) - local pos = self:GetPos() - local size = self:GetSize() - local x, y = pos.x, pos.y - local w, h = size.w, size.h - - self:DrawBg(x, y, w, h, self.basecolor) - self:DrawBar(x + self.pad, y + self.pad, w - self.pad * 2, h - self.pad * 2, draw_col, punch, self.scale, L.punch_title) - self:DrawLines(x, y, w, h, self.basecolor.a) - - draw.AdvancedText(L.punch_help, "TabLarge", x + w * 0.5, y, util.GetDefaultColor(self.basecolor), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, true, self.scale) - - local bonus = client:GetNWInt("bonuspunches", 0) - if bonus ~= 0 then - local text - - if bonus < 0 then - text = interp(L.punch_bonus, {num = bonus}) - else - text = interp(L.punch_malus, {num = bonus}) - end - - draw.AdvancedText(text, "TabLarge", x + w * 0.5, y + self.margin * 2 + 20, util.GetDefaultColor(self.basecolor), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, true, self.scale) - end - end - - function HUDELEMENT:PerformLayout() - self.scale = appearance.GetGlobalScale() - self.basecolor = self:GetHUDBasecolor() - self.pad = pad * self.scale - self.margin = margin * self.scale - - BaseClass.PerformLayout(self) - end - - local key_params = {usekey = Key("+use", "USE"), helpkey = Key("gm_showhelp", "F1")} - - function HUDELEMENT:ShouldDraw() - local client = LocalPlayer() - - return not client:Alive() or client:Team() == TEAM_SPEC - end - - function HUDELEMENT:Draw() - local client = LocalPlayer() - - local L = GetLang() -- for fast direct table lookups - - -- Draw round state - local tgt = client:GetObserverTarget() - - local pos = self:GetPos() - local x, y = pos.x, pos.y - - if IsValid(tgt) and not tgt:IsPlayer() and tgt:GetNWEntity("spec_owner", nil) == client then - self:PunchPaint() -- punch bar if you are spectator and inside of an entity - else - draw.AdvancedText(interp(L.spec_help, key_params), "TabLarge", x + self.size.w * 0.5, y, COLOR_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, true, self.scale) - if self.cv_ttt_spectator_mode:GetBool() then - draw.AdvancedText(interp(L.spec_help2, key_params), "TabLarge", x + self.size.w * 0.5, y + 20, COLOR_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, true, self.scale) - end - end - end + local draw_col = Color(205, 155, 0, 255) + + local pad = 7 + local margin = 14 + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 200, h = 40 }, + minsize = { w = 100, h = 40 }, + } + + function HUDELEMENT:Initialize() + self.scale = 1.0 + self.basecolor = self:GetHUDBasecolor() + + self.pad = pad + self.margin = margin + + self.cv_ttt_spectator_mode = GetConVar("ttt_spectator_mode") + + BaseClass.Initialize(self) + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return true, false + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = + { x = ScrW() * 0.5 - self.size.w * 0.5, y = ScrH() - 120 * self.scale } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + self.scale = appearance.GetGlobalScale() + self.basecolor = self:GetHUDBasecolor() + self.pad = pad * self.scale + self.margin = margin * self.scale + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:ShouldDraw() + local client = LocalPlayer() + local tgt = client:GetObserverTarget() + + return client:IsSpec() + and IsValid(tgt) + and not tgt:IsPlayer() + and tgt:GetNWEntity("spec_owner", nil) == client + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + local L = GetLang() + local punch = client:GetNWFloat("specpunches", 0) + local pos = self:GetPos() + local size = self:GetSize() + local x, y = pos.x, pos.y + local w, h = size.w, size.h + + self:DrawBg(x, y, w, h, self.basecolor) + self:DrawBar( + x + self.pad, + y + self.pad, + w - self.pad * 2, + h - self.pad * 2, + draw_col, + punch, + self.scale, + L.punch_title + ) + self:DrawLines(x, y, w, h, self.basecolor.a) + + local bonus = client:GetNWInt("bonuspunches", 0) + if bonus ~= 0 then + local text + + if bonus < 0 then + text = interp(L.punch_bonus, { num = bonus }) + else + text = interp(L.punch_malus, { num = bonus }) + end + + draw.AdvancedText( + text, + "TabLarge", + x + w * 0.5, + y + self.margin * 2 + 20, + util.GetDefaultColor(self.basecolor), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + true, + self.scale + ) + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttrevivalinfo/old_ttt_revival.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttrevivalinfo/old_ttt_revival.lua index df019009d..21e7d3f1e 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttrevivalinfo/old_ttt_revival.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttrevivalinfo/old_ttt_revival.lua @@ -7,139 +7,144 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local ParT = LANG.GetParamTranslation - local TryT = LANG.TryTranslation - - local materialBlockingRevival = Material("vgui/ttt/hud_blocking_revival") - - local pad = 14 - local defaultHeight = 74 - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 321, h = defaultHeight}, - minsize = {w = 250, h = defaultHeight} - } - - function HUDELEMENT:Initialize() - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return true, false - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = math.Round(ScrW() * 0.5 - self.size.w * 0.5), y = 0.5 * ScrH() + 100} - - return const_defaults - end - - function HUDELEMENT:ShouldDraw() - local client = LocalPlayer() - - return HUDEditor.IsEditing or client:IsReviving() - end - - function HUDELEMENT:PerformLayout() - defaultHeight = defaultHeight - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:Draw() - local client = LocalPlayer() - local pos = self:GetPos() - local size = self:GetSize() - local x, y = pos.x, pos.y - local w, h = size.w, size.h - - local timeLeft = HUDEditor.IsEditing and 1 or math.ceil(math.max(0, client:GetRevivalDuration() - (CurTime() - client:GetRevivalStartTime()))) - local progress = HUDEditor.IsEditing and 1 or ((CurTime() - client:GetRevivalStartTime()) / client:GetRevivalDuration()) - - local posHeaderY = y + pad - local posBarY = posHeaderY + 20 - local barHeight = 26 - local iconPad = 5 - local iconSize = barHeight - 2 * iconPad - local posReasonY = posBarY + barHeight + pad - - local revivalReasonLines = {} - local lineHeight = 0 - - if client:HasRevivalReason() then - local rawRevivalReason = client:GetRevivalReason() - - local translatedText - if rawRevivalReason.params then - translatedText = ParT(rawRevivalReason.name, rawRevivalReason.params) - else - translatedText = TryT(rawRevivalReason.name) - end - - local lines, _, textHeight = draw.GetWrappedText( - translatedText, - w - 2 * pad, - "HealthAmmo" - ) - - revivalReasonLines = lines - lineHeight = textHeight / #revivalReasonLines - - h = defaultHeight + textHeight + pad - else - h = defaultHeight - end - - -- draw bg and shadow - draw.RoundedBox(8, x, y, w, h, self.bg_colors.background_main) - - self:ShadowedText( - TryT("hud_revival_title"), - "HealthAmmo", - x + pad, - posHeaderY, - COLOR_WHITE, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER - ) - - self:ShadowedText( - ParT("hud_revival_time", {time = timeLeft}), - "HealthAmmo", - x + w - pad, - posHeaderY, - COLOR_WHITE, - TEXT_ALIGN_RIGHT, - TEXT_ALIGN_CENTER - ) - - self:PaintBar(x + pad, posBarY, w - pad * 2, barHeight, self.sprint_colors, progress, 1) - - if client:IsBlockingRevival() then - draw.FilteredShadowedTexture( - x + w - pad - iconSize - 4 * iconPad, - posBarY + iconPad, - iconSize, - iconSize, - materialBlockingRevival, - 255, - COLOR_WHITE - ) - end - - for i = 1, #revivalReasonLines do - self:ShadowedText( - revivalReasonLines[i], - "HealthAmmo", - x + pad, - posReasonY + (i - 1) * lineHeight, - COLOR_WHITE, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_TOP - ) - end - end + local ParT = LANG.GetParamTranslation + local TryT = LANG.TryTranslation + + local materialBlockingRevival = Material("vgui/ttt/hud_blocking_revival") + + local pad = 14 + local defaultHeight = 74 + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 321, h = defaultHeight }, + minsize = { w = 250, h = defaultHeight }, + } + + function HUDELEMENT:Initialize() + BaseClass.Initialize(self) + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return true, false + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = + { x = math.Round(ScrW() * 0.5 - self.size.w * 0.5), y = 0.5 * ScrH() + 100 } + + return const_defaults + end + + function HUDELEMENT:ShouldDraw() + local client = LocalPlayer() + + return HUDEditor.IsEditing or client:IsReviving() + end + + function HUDELEMENT:PerformLayout() + defaultHeight = defaultHeight + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + local pos = self:GetPos() + local size = self:GetSize() + local x, y = pos.x, pos.y + local w, h = size.w, size.h + + local timeLeft = HUDEditor.IsEditing and 1 + or math.ceil( + math.max( + 0, + client:GetRevivalDuration() - (CurTime() - client:GetRevivalStartTime()) + ) + ) + local progress = HUDEditor.IsEditing and 1 + or ((CurTime() - client:GetRevivalStartTime()) / client:GetRevivalDuration()) + + local posHeaderY = y + pad + local posBarY = posHeaderY + 20 + local barHeight = 26 + local iconPad = 5 + local iconSize = barHeight - 2 * iconPad + local posReasonY = posBarY + barHeight + pad + + local revivalReasonLines = {} + local lineHeight = 0 + + if client:HasRevivalReason() then + local rawRevivalReason = client:GetRevivalReason() + + local translatedText + if rawRevivalReason.params then + translatedText = ParT(rawRevivalReason.name, rawRevivalReason.params) + else + translatedText = TryT(rawRevivalReason.name) + end + + local lines, _, textHeight = + draw.GetWrappedText(translatedText, w - 2 * pad, "HealthAmmo") + + revivalReasonLines = lines + lineHeight = textHeight / #revivalReasonLines + + h = defaultHeight + textHeight + pad + else + h = defaultHeight + end + + -- draw bg and shadow + draw.RoundedBox(8, x, y, w, h, self.bg_colors.background_main) + + self:ShadowedText( + TryT("hud_revival_title"), + "HealthAmmo", + x + pad, + posHeaderY, + COLOR_WHITE, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) + + self:ShadowedText( + ParT("hud_revival_time", { time = timeLeft }), + "HealthAmmo", + x + w - pad, + posHeaderY, + COLOR_WHITE, + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_CENTER + ) + + self:PaintBar(x + pad, posBarY, w - pad * 2, barHeight, self.sprint_colors, progress) + + if client:IsBlockingRevival() then + draw.FilteredShadowedTexture( + x + w - pad - iconSize - 4 * iconPad, + posBarY + iconPad, + iconSize, + iconSize, + materialBlockingRevival, + 255, + COLOR_WHITE + ) + end + + for i = 1, #revivalReasonLines do + self:ShadowedText( + revivalReasonLines[i], + "HealthAmmo", + x + pad, + posReasonY + (i - 1) * lineHeight, + COLOR_WHITE, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP + ) + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttrevivalinfo/pure_skin_revival.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttrevivalinfo/pure_skin_revival.lua index 7d915bbdc..6287e2c7a 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttrevivalinfo/pure_skin_revival.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttrevivalinfo/pure_skin_revival.lua @@ -7,163 +7,175 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local ParT = LANG.GetParamTranslation - local TryT = LANG.TryTranslation - - local materialBlockingRevival = Material("vgui/ttt/hud_blocking_revival") - - local pad = 14 - local defaultHeight = 74 - - local colorRevivingBar = Color(36, 154, 198) - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 321, h = defaultHeight}, - minsize = {w = 250, h = defaultHeight} - } - - function HUDELEMENT:Initialize() - self.pad = pad - self.defaultHeight = defaultHeight - self.basecolor = self:GetHUDBasecolor() - - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return true, false - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = math.Round(ScrW() * 0.5 - self.size.w * 0.5), y = 0.5 * ScrH() + 100} - - return const_defaults - end - - function HUDELEMENT:ShouldDraw() - local client = LocalPlayer() - - return HUDEditor.IsEditing or client:IsReviving() - end - - function HUDELEMENT:PerformLayout() - local scale = self:GetHUDScale() - - self.scale = scale - self.basecolor = self:GetHUDBasecolor() - self.pad = pad * scale - self.defaultHeight = defaultHeight * scale - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:Draw() - local client = LocalPlayer() - local pos = self:GetPos() - local size = self:GetSize() - local x, y = pos.x, pos.y - local w, h = size.w, size.h - - local timeLeft = HUDEditor.IsEditing and 1 or math.ceil(math.max(0, client:GetRevivalDuration() - (CurTime() - client:GetRevivalStartTime()))) - local progress = HUDEditor.IsEditing and 1 or ((CurTime() - client:GetRevivalStartTime()) / client:GetRevivalDuration()) - - local posHeaderY = y + self.pad - local posBarY = posHeaderY + 20 * self.scale - local barHeight = 26 * self.scale - local iconPad = 5 * self.scale - local iconSize = barHeight - 2 * iconPad - local posReasonY = posBarY + barHeight + self.pad - - local revivalReasonLines = {} - local lineHeight = 0 - - if client:HasRevivalReason() then - local rawRevivalReason = client:GetRevivalReason() - - local translatedText - if rawRevivalReason.params then - translatedText = ParT(rawRevivalReason.name, rawRevivalReason.params) - else - translatedText = TryT(rawRevivalReason.name) - end - - local lines, _, textHeight = draw.GetWrappedText( - translatedText, - w - 2 * self.pad, - "PureSkinBar", - self.scale - ) - - revivalReasonLines = lines - lineHeight = textHeight / #revivalReasonLines - - h = self.defaultHeight + textHeight + self.pad - else - h = self.defaultHeight - end - - self:SetSize(w, h) - - -- draw bg and shadow - self:DrawBg(x, y, w, h, self.basecolor) - - draw.AdvancedText( - TryT("hud_revival_title"), - "PureSkinBar", - x + self.pad, - posHeaderY, - util.GetDefaultColor(self.basecolor), - TEXT_ALIGN_LEFT, - TEXT_ALIGN_CENTER, - true, - self.scale - ) - - draw.AdvancedText( - ParT("hud_revival_time", {time = timeLeft}), - "PureSkinBar", - x + w - self.pad, - posHeaderY, - util.GetDefaultColor(self.basecolor), - TEXT_ALIGN_RIGHT, - TEXT_ALIGN_CENTER, - true, - self.scale - ) - - self:DrawBar(x + self.pad, posBarY, w - self.pad * 2, barHeight, colorRevivingBar, progress, 1) - - if client:IsBlockingRevival() then - draw.FilteredShadowedTexture( - x + w - self.pad - iconSize - 4 * iconPad, - posBarY + iconPad, - iconSize, - iconSize, - materialBlockingRevival, - 255, - COLOR_WHITE, - self.scale - ) - end - - for i = 1, #revivalReasonLines do - draw.AdvancedText( - revivalReasonLines[i], - "PureSkinBar", - x + self.pad, - posReasonY + (i - 1) * lineHeight, - util.GetDefaultColor(self.basecolor), - TEXT_ALIGN_LEFT, - TEXT_ALIGN_TOP, - true, - self.scale - ) - end - - -- draw lines around the element - self:DrawLines(x, y, w, h, self.basecolor.a) - end + local ParT = LANG.GetParamTranslation + local TryT = LANG.TryTranslation + + local materialBlockingRevival = Material("vgui/ttt/hud_blocking_revival") + + local pad = 14 + local defaultHeight = 74 + + local colorRevivingBar = Color(36, 154, 198) + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 321, h = defaultHeight }, + minsize = { w = 250, h = defaultHeight }, + } + + function HUDELEMENT:Initialize() + self.pad = pad + self.defaultHeight = defaultHeight + self.basecolor = self:GetHUDBasecolor() + + BaseClass.Initialize(self) + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return true, false + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = + { x = math.Round(ScrW() * 0.5 - self.size.w * 0.5), y = 0.5 * ScrH() + 100 } + + return const_defaults + end + + function HUDELEMENT:ShouldDraw() + local client = LocalPlayer() + + return HUDEditor.IsEditing or client:IsReviving() + end + + function HUDELEMENT:PerformLayout() + local scale = self:GetHUDScale() + + self.scale = scale + self.basecolor = self:GetHUDBasecolor() + self.pad = pad * scale + self.defaultHeight = defaultHeight * scale + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + local pos = self:GetPos() + local size = self:GetSize() + local x, y = pos.x, pos.y + local w, h = size.w, size.h + + local timeLeft = HUDEditor.IsEditing and 1 + or math.ceil( + math.max( + 0, + client:GetRevivalDuration() - (CurTime() - client:GetRevivalStartTime()) + ) + ) + local progress = HUDEditor.IsEditing and 1 + or ((CurTime() - client:GetRevivalStartTime()) / client:GetRevivalDuration()) + + local posHeaderY = y + self.pad + local posBarY = posHeaderY + 20 * self.scale + local barHeight = 26 * self.scale + local iconPad = 5 * self.scale + local iconSize = barHeight - 2 * iconPad + local posReasonY = posBarY + barHeight + self.pad + + local revivalReasonLines = {} + local lineHeight = 0 + + if client:HasRevivalReason() then + local rawRevivalReason = client:GetRevivalReason() + + local translatedText + if rawRevivalReason.params then + translatedText = ParT(rawRevivalReason.name, rawRevivalReason.params) + else + translatedText = TryT(rawRevivalReason.name) + end + + local lines, _, textHeight = + draw.GetWrappedText(translatedText, w - 2 * self.pad, "PureSkinBar", self.scale) + + revivalReasonLines = lines + lineHeight = textHeight / #revivalReasonLines + + h = self.defaultHeight + textHeight + self.pad + else + h = self.defaultHeight + end + + self:SetSize(w, h) + + -- draw bg and shadow + self:DrawBg(x, y, w, h, self.basecolor) + + draw.AdvancedText( + TryT("hud_revival_title"), + "PureSkinBar", + x + self.pad, + posHeaderY, + util.GetDefaultColor(self.basecolor), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + true, + self.scale + ) + + draw.AdvancedText( + ParT("hud_revival_time", { time = timeLeft }), + "PureSkinBar", + x + w - self.pad, + posHeaderY, + util.GetDefaultColor(self.basecolor), + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_CENTER, + true, + self.scale + ) + + self:DrawBar( + x + self.pad, + posBarY, + w - self.pad * 2, + barHeight, + colorRevivingBar, + progress, + 1 + ) + + if client:IsBlockingRevival() then + draw.FilteredShadowedTexture( + x + w - self.pad - iconSize - 4 * iconPad, + posBarY + iconPad, + iconSize, + iconSize, + materialBlockingRevival, + 255, + COLOR_WHITE, + self.scale + ) + end + + for i = 1, #revivalReasonLines do + draw.AdvancedText( + revivalReasonLines[i], + "PureSkinBar", + x + self.pad, + posReasonY + (i - 1) * lineHeight, + util.GetDefaultColor(self.basecolor), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP, + true, + self.scale + ) + end + + -- draw lines around the element + self:DrawLines(x, y, w, h, self.basecolor.a) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttroundinfo/pure_skin_roundinfo.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttroundinfo/pure_skin_roundinfo.lua index b05a84eae..52ee7fd5f 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttroundinfo/pure_skin_roundinfo.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttroundinfo/pure_skin_roundinfo.lua @@ -10,115 +10,136 @@ HUDELEMENT.togglable = true HUDELEMENT.disabledUnlessForced = true if CLIENT then - local GetLang = LANG.GetUnsafeLanguageTable - - local pad = 14 - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 96, h = 72}, - minsize = {w = 96, h = 72} - } - - function HUDELEMENT:Initialize() - self.scale = 1.0 - self.basecolor = self:GetHUDBasecolor() - self.pad = pad - - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return true, true - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = math.Round(ScrW() * 0.5 - self.size.w * 0.5), y = 4 * self.scale} - - return const_defaults - end - - function HUDELEMENT:PerformLayout() - local defaults = self:GetDefaults() - - self.scale = math.min(self.size.w / defaults.minsize.w, self.size.h / defaults.minsize.h) - self.basecolor = self:GetHUDBasecolor() - self.pad = pad * self.scale - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:Draw() - local client = LocalPlayer() - local L = GetLang() - local round_state = GAMEMODE.round_state - - -- draw bg and shadow - self:DrawBg(self.pos.x, self.pos.y, self.size.w, self.size.h, self.basecolor) - - -- draw haste / time - -- Draw round time - - local isHaste = HasteMode() and round_state == ROUND_ACTIVE - local isOmniscient = not client:IsActive() or client:GetSubRoleData().isOmniscientRole - local endtime = GetGlobalFloat("ttt_round_end", 0) - CurTime() - local font = "PureSkinTimeLeft" - local color = util.GetDefaultColor(self.basecolor) - - local tmpx = self.pos.x + self.size.w * 0.5 - local tmpy = self.pos.y + self.size.h * 0.5 - - local rx = tmpx - local ry = tmpy - - local vert_align_clock = TEXT_ALIGN_TOP - - -- Time displays differently depending on whether haste mode is on, - -- whether the player is traitor or not, and whether it is overtime. - if isHaste then - local hastetime = GetGlobalFloat("ttt_haste_end", 0) - CurTime() - if hastetime < 0 then - if not isOmniscient or math.ceil(CurTime()) % 7 <= 2 then - -- innocent or blinking "overtime" - text = L.overtime - font = "PureSkinMSTACKMsg" - - -- need to hack the position a little because of the font switch - ry = ry + 5 - rx = rx - 3 - else - -- traitor and not blinking "overtime" right now, so standard endtime display - text = util.SimpleTime(math.max(0, endtime), "%02i:%02i") - color = COLOR_RED - end - else - -- still in starting period - local t = hastetime - - if isOmniscient and math.ceil(CurTime()) % 6 < 2 then - t = endtime - color = COLOR_RED - end - - text = util.SimpleTime(math.max(0, t), "%02i:%02i") - end - else - vert_align_clock = TEXT_ALIGN_CENTER - - -- bog standard time when haste mode is off (or round not active) - text = util.SimpleTime(math.max(0, endtime), "%02i:%02i") - end - - draw.AdvancedText(text, font, rx, ry, color, TEXT_ALIGN_CENTER, vert_align_clock, true, self.scale) - - if isHaste then - draw.AdvancedText(L.hastemode, "PureSkinMSTACKMsg", tmpx, self.pos.y + self.pad, util.GetDefaultColor(self.basecolor), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, true, self.scale) - end - - -- draw lines around the element - local border_pos, border_size = self:GetBorderParams() - self:DrawLines(border_pos.x, border_pos.y, border_size.w, border_size.h, self.basecolor.a) - end + local GetLang = LANG.GetUnsafeLanguageTable + + local pad = 14 + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 96, h = 72 }, + minsize = { w = 96, h = 72 }, + } + + function HUDELEMENT:Initialize() + self.scale = 1.0 + self.basecolor = self:GetHUDBasecolor() + self.pad = pad + + BaseClass.Initialize(self) + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return true, true + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = + { x = math.Round(ScrW() * 0.5 - self.size.w * 0.5), y = 4 * self.scale } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + local defaults = self:GetDefaults() + + self.scale = math.min(self.size.w / defaults.minsize.w, self.size.h / defaults.minsize.h) + self.basecolor = self:GetHUDBasecolor() + self.pad = pad * self.scale + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + local L = GetLang() + local round_state = GAMEMODE.round_state + + -- draw bg and shadow + self:DrawBg(self.pos.x, self.pos.y, self.size.w, self.size.h, self.basecolor) + + -- draw haste / time + -- Draw round time + + local isHaste = HasteMode() and round_state == ROUND_ACTIVE + local isOmniscient = not client:IsActive() or client:GetSubRoleData().isOmniscientRole + local endtime = GetGlobalFloat("ttt_round_end", 0) - CurTime() + local font = "PureSkinTimeLeft" + local color = util.GetDefaultColor(self.basecolor) + + local tmpx = self.pos.x + self.size.w * 0.5 + local tmpy = self.pos.y + self.size.h * 0.5 + + local rx = tmpx + local ry = tmpy + + local vert_align_clock = TEXT_ALIGN_TOP + + -- Time displays differently depending on whether haste mode is on, + -- whether the player is traitor or not, and whether it is overtime. + if isHaste then + local hastetime = GetGlobalFloat("ttt_haste_end", 0) - CurTime() + if hastetime < 0 then + if not isOmniscient or math.ceil(CurTime()) % 7 <= 2 then + -- innocent or blinking "overtime" + text = L.overtime + font = "PureSkinMSTACKMsg" + + -- need to hack the position a little because of the font switch + ry = ry + 5 + rx = rx - 3 + else + -- traitor and not blinking "overtime" right now, so standard endtime display + text = util.SimpleTime(math.max(0, endtime), "%02i:%02i") + color = COLOR_RED + end + else + -- still in starting period + local t = hastetime + + if isOmniscient and math.ceil(CurTime()) % 6 < 2 then + t = endtime + color = COLOR_RED + end + + text = util.SimpleTime(math.max(0, t), "%02i:%02i") + end + else + vert_align_clock = TEXT_ALIGN_CENTER + + -- bog standard time when haste mode is off (or round not active) + text = util.SimpleTime(math.max(0, endtime), "%02i:%02i") + end + + draw.AdvancedText( + text, + font, + rx, + ry, + color, + TEXT_ALIGN_CENTER, + vert_align_clock, + true, + self.scale + ) + + if isHaste then + draw.AdvancedText( + L.hastemode, + "PureSkinMSTACKMsg", + tmpx, + self.pos.y + self.pad, + util.GetDefaultColor(self.basecolor), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_TOP, + true, + self.scale + ) + end + + -- draw lines around the element + local border_pos, border_size = self:GetBorderParams() + self:DrawLines(border_pos.x, border_pos.y, border_size.w, border_size.h, self.basecolor.a) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttsidebar/old_ttt_sidebar.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttsidebar/old_ttt_sidebar.lua index 32a4d9e38..615003005 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttsidebar/old_ttt_sidebar.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttsidebar/old_ttt_sidebar.lua @@ -10,92 +10,120 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local size = 64 - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = size, h = -size}, - minsize = {w = size, h = size} - } - - function HUDELEMENT:Initialize() - BaseClass.Initialize(self) - - self.defaults.minWidth = size - self.defaults.minHeight = size - self.defaults.resizeableX = false - self.defaults.resizeableY = false - end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = 20, y = ScrH() * 0.5} - - return const_defaults - end - - function HUDELEMENT:PerformLayout() - local basepos = self:GetBasePos() - - self:SetPos(basepos.x, basepos.y) - self:SetSize(size, -size) - - BaseClass.PerformLayout(self) - end - - local old_ttt_bg = Material("vgui/ttt/perks/old_ttt_bg.png") - - function HUDELEMENT:Draw() - local client = LocalPlayer() - - if not client:Alive() or client:Team() ~= TEAM_TERROR then return end - - local basepos = self:GetBasePos() - - self:SetPos(basepos.x, basepos.y) - - local itms = client:GetEquipmentItems() - local pos = self:GetPos() - local curY = pos.y - - -- at first, calculate old items because they don't take care of the new ones - for i = 1, #itms do - local item = items.GetStored(itms[i]) - if not item or not item.oldHud then continue end - - curY = curY - 80 - end - - -- now draw our new items automatically - for i = 1, #itms do - local item = items.GetStored(itms[i]) - if not item or not item.hud then continue end - - surface.SetMaterial(old_ttt_bg) - surface.SetDrawColor(255, 255, 255, 255) - surface.DrawTexturedRect(pos.x-1, curY, size, size) - - draw.FilteredTexture(pos.x + 0.1 * size, curY + 0.1 * size, size - 0.2 * size, size - 0.2 * size, item.hud, 175) - - if isfunction(item.DrawInfo) then - local info = item:DrawInfo() - if info then - -- right bottom corner - local tx = pos.x + size - local ty = curY + size - local pad = 5 - - surface.SetFont("ItemInfo") - - local infoW, infoH = surface.GetTextSize(info) - - draw.RoundedBox(4, tx - infoW * 0.5 - pad, ty - infoH * 0.5, infoW + pad * 2, infoH, COLOR_DARKGREY) - draw.DrawText(info, "ItemInfo", tx, ty - infoH * 0.5, COLOR_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) - end - end - - curY = curY - (size + size * 0.25) - end - - self:SetSize(size, curY - basepos.y) - end + local size = 64 + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = size, h = -size }, + minsize = { w = size, h = size }, + } + + function HUDELEMENT:Initialize() + BaseClass.Initialize(self) + + self.defaults.minWidth = size + self.defaults.minHeight = size + self.defaults.resizeableX = false + self.defaults.resizeableY = false + end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = { x = 20, y = ScrH() * 0.5 } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + local basepos = self:GetBasePos() + + self:SetPos(basepos.x, basepos.y) + self:SetSize(size, -size) + + BaseClass.PerformLayout(self) + end + + local old_ttt_bg = Material("vgui/ttt/perks/old_ttt_bg.png") + + function HUDELEMENT:Draw() + local client = LocalPlayer() + + if not client:Alive() or client:Team() ~= TEAM_TERROR then + return + end + + local basepos = self:GetBasePos() + + self:SetPos(basepos.x, basepos.y) + + local itms = client:GetEquipmentItems() + local pos = self:GetPos() + local curY = pos.y + + -- at first, calculate old items because they don't take care of the new ones + for i = 1, #itms do + local item = items.GetStored(itms[i]) + if not item or not item.oldHud then + continue + end + + curY = curY - 80 + end + + -- now draw our new items automatically + for i = 1, #itms do + local item = items.GetStored(itms[i]) + if not item or not item.hud then + continue + end + + surface.SetMaterial(old_ttt_bg) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(pos.x - 1, curY, size, size) + + draw.FilteredTexture( + pos.x + 0.1 * size, + curY + 0.1 * size, + size - 0.2 * size, + size - 0.2 * size, + item.hud, + 175 + ) + + if isfunction(item.DrawInfo) then + local info = item:DrawInfo() + if info then + -- right bottom corner + local tx = pos.x + size + local ty = curY + size + local pad = 5 + + surface.SetFont("ItemInfo") + + local infoW, infoH = surface.GetTextSize(info) + + draw.RoundedBox( + 4, + tx - infoW * 0.5 - pad, + ty - infoH * 0.5, + infoW + pad * 2, + infoH, + COLOR_DARKGREY + ) + draw.DrawText( + info, + "ItemInfo", + tx, + ty - infoH * 0.5, + COLOR_WHITE, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) + end + end + + curY = curY - (size + size * 0.25) + end + + self:SetSize(size, curY - basepos.y) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttsidebar/pure_skin_sidebar.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttsidebar/pure_skin_sidebar.lua index 8a558d17e..d5de0f8e5 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttsidebar/pure_skin_sidebar.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttsidebar/pure_skin_sidebar.lua @@ -10,192 +10,275 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local padding = 10 - local color_badstatus = Color(183, 54, 47) - local color_goodstatus = Color(36, 115, 51) - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 48, h = 48}, - minsize = {w = 48, h = 48} - } - - function HUDELEMENT:Initialize() - self.scale = 1.0 - self.basecolor = self:GetHUDBasecolor() - self.padding = padding - - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return false, false - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = { - x = self.padding, - y = ScrH() * 0.5 - } - - return const_defaults - end - - function HUDELEMENT:PerformLayout() - self.scale = appearance.GetGlobalScale() - self.basecolor = self:GetHUDBasecolor() - self.padding = padding * self.scale - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:ShouldDraw() - local client = LocalPlayer() - - return client:Alive() or client:Team() == TEAM_TERROR - end - - function HUDELEMENT:DrawIcon(curY, item) - local pos = self:GetPos() - local size = self:GetSize() - - if not item.hud_color then - item.hud_color = self.basecolor - end - - local fontColor = util.GetDefaultColor(item.hud_color) - local iconAlpha = fontColor.r > 60 and 175 or 250 - - curY = curY - size.w - - local factor = 1 - - if item.displaytime then -- start blinking in last 5 seconds - local time_left = item.displaytime - CurTime() - - if time_left < 5 then - local num = 0.5 * math.pi + (-1.4 * time_left + 7) * math.pi - - factor = 0.5 * (math.sin(num) + 1) - end - end - - surface.SetDrawColor(item.hud_color.r, item.hud_color.g, item.hud_color.b, math.Round(factor * 255)) - surface.DrawRect(pos.x, curY, size.w, size.w) - - local hud_icon = item.hud.GetTexture and item.hud or item.hud[item.active_icon] - - draw.FilteredShadowedTexture(pos.x, curY, size.w, size.w, hud_icon, iconAlpha, fontColor, self.scale) - - self:DrawLines(pos.x, curY, size.w, size.w, item.hud_color.a * factor) - - if isfunction(item.DrawInfo) then - local info = item:DrawInfo() - if info then - -- right bottom corner - local tx = pos.x + size.w - 5 - local ty = curY + size.w - 1 - local pad = 5 * self.scale - - surface.SetFont("PureSkinItemInfo") - - local infoW, infoH = surface.GetTextSize(info) - infoW = infoW * self.scale - infoH = (infoH + 2) * self.scale - - local bx = tx - infoW * 0.5 - pad - local by = ty - infoH * 0.5 - local bw = infoW + pad * 2 - - self:DrawBg(bx, by, bw, infoH, item.hud_color) - - draw.AdvancedText(info, "PureSkinItemInfo", tx, ty, fontColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, false, self.scale) - - self:DrawLines(bx, by, bw, infoH, item.hud_color.a) - end - end - - return curY - self.padding - end - - function HUDELEMENT:Draw() - local client = LocalPlayer() - - local basepos = self:GetBasePos() - local itms = client:GetEquipmentItems() - - -- get number of new icons - local num_icons = 0 - local num_items = 0 - - for i = 1, #itms do - local item = items.GetStored(itms[i]) - - if item and item.hud then - num_items = num_items + 1 - end - end - - num_icons = num_icons + num_items - - local num_status = 0 - - for _, status in pairs(STATUS.active) do - num_status = num_status + 1 - end - - num_icons = num_icons + num_status - - local linespace = ((num_status > 0) and (num_items > 0)) and 25 or 0 - local height = math.max(num_icons, 1) * self.size.w + math.max(num_icons -1, 0) * ((num_icons > 1) and self.padding or 0) + linespace - local startY = basepos.y + 0.5 * self.size.w + 0.5 * height - local curY = startY - - -- draw status - for _, status in pairs(STATUS.active) do - if status.type == "bad" then - status.hud_color = color_badstatus - end - - if status.type == "good" then - status.hud_color = color_goodstatus - end - - if status.type == "default" then - status.hud_color = Color(self.basecolor.r, self.basecolor.g, self.basecolor.b) - end - - -- fallback - if status.type == nil and status.hud_color == nil then - status.hud_color = Color(self.basecolor.r, self.basecolor.g, self.basecolor.b) - end - - curY = self:DrawIcon(curY, status) - end - - -- draw spacer - if num_status > 0 and num_items > 0 then - curY = curY - 16 - - local pos = self:GetPos() - local size = self:GetSize() - - surface.SetDrawColor(self.basecolor) - surface.DrawRect(pos.x, curY + self.padding * 0.5 + 8, size.w, 2) - end - - -- draw items - for i = 1, #itms do - local item = items.GetStored(itms[i]) - if not item or not item.hud then continue end - - item.hud_color = Color(self.basecolor.r, self.basecolor.g, self.basecolor.b) - curY = self:DrawIcon(curY, item) - end - - self:SetSize(self.size.w, - math.max(height, self.minsize.h)) -- adjust the size - self:SetPos(basepos.x, startY - height) - end + local TryT = LANG.TryTranslation + + local padding = 10 + local color_badstatus = Color(183, 54, 47) + local color_goodstatus = Color(36, 115, 51) + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 48, h = 48 }, + minsize = { w = 48, h = 48 }, + } + + function HUDELEMENT:Initialize() + self.scale = 1.0 + self.basecolor = self:GetHUDBasecolor() + self.padding = padding + + BaseClass.Initialize(self) + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return false, false + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = { + x = self.padding, + y = ScrH() * 0.5, + } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + self.scale = appearance.GetGlobalScale() + self.basecolor = self:GetHUDBasecolor() + self.padding = padding * self.scale + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:ShouldDraw() + local client = LocalPlayer() + + return client:Alive() or client:Team() == TEAM_TERROR + end + + function HUDELEMENT:DrawIcon(curY, item) + local pos = self:GetPos() + local size = self:GetSize() + + if not item.hud_color then + item.hud_color = self.basecolor + end + + local fontColor = util.GetDefaultColor(item.hud_color) + local iconAlpha = fontColor.r > 60 and 175 or 250 + + curY = curY - size.w + + local factor = 1 + + if item.displaytime then -- start blinking in last 5 seconds + local time_left = item.displaytime - CurTime() + + if time_left < 5 then + local num = 0.5 * math.pi + (-1.4 * time_left + 7) * math.pi + + factor = 0.5 * (math.sin(num) + 1) + end + end + + surface.SetDrawColor( + item.hud_color.r, + item.hud_color.g, + item.hud_color.b, + math.Round(factor * 255) + ) + surface.DrawRect(pos.x, curY, size.w, size.w) + + local hud_icon = item.hud.GetTexture and item.hud or item.hud[item.active_icon] + + draw.FilteredShadowedTexture( + pos.x, + curY, + size.w, + size.w, + hud_icon, + iconAlpha, + fontColor, + self.scale + ) + + self:DrawLines(pos.x, curY, size.w, size.w, item.hud_color.a * factor) + + if isfunction(item.DrawInfo) then + local info = TryT(item:DrawInfo()) + if info then + local infoW, infoH = draw.GetTextSize(info, "PureSkinItemInfo") + infoW = infoW * self.scale + infoH = (infoH + 3) * self.scale + + -- right bottom corner + local pad = 4 * self.scale + local tx = pos.x + size.w - 1 * self.scale - 0.5 * infoW + local ty = curY + size.w + 4 * self.scale - 0.5 * infoH + + local bx = tx - 0.5 * infoW - pad + local by = ty - infoH * 0.5 + local bw = infoW + pad * 2 + + self:DrawBg(bx, by, bw, infoH, item.hud_color) + + draw.AdvancedText( + info, + "PureSkinItemInfo", + tx, + ty, + fontColor, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + true, + self.scale + ) + + self:DrawLines(bx, by, bw, infoH, item.hud_color.a) + end + end + + if GAMEMODE.ShowScoreboard and GetConVar("ttt2_hud_enable_description"):GetBool() then + local xText = pos.x + size.w + self.padding + local offsetTitle = -2 * self.scale + local offsetLines = { 19 * self.scale, 33 * self.scale } + + local name = item.EquipMenuData and item.EquipMenuData.name or (item.name or "") + + -- allow dynamic status names + if istable(name) then + name = name[item.active_icon] + end + + draw.AdvancedText( + TryT(name), + "PureSkinPopupText", + xText, + curY + offsetTitle, + COLOR_WHITE, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP, + true, + self.scale + ) + + local translatedText = TryT( + (item.sidebarDescription or item.EquipMenuData and item.EquipMenuData.desc) or "" + ) + + local wrappedText = + draw.GetWrappedText(translatedText, 285 * self.scale, "PureSkinItemInfo") + + local lineCount = #wrappedText + + for i = 1, math.min(2, lineCount) do + local line = wrappedText[i] + + -- if text is shortned, then this should be indicated + if i == 2 and lineCount > 2 then + line = line .. " [...]" + end + + draw.AdvancedText( + line, + "PureSkinItemInfo", + xText, + curY + offsetLines[i], + COLOR_WHITE, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_TOP, + true, + self.scale + ) + end + end + + return curY - self.padding + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + + local basepos = self:GetBasePos() + local itms = client:GetEquipmentItems() + + -- get number of new icons + local num_icons = 0 + local num_items = 0 + + for i = 1, #itms do + local item = items.GetStored(itms[i]) + + if item and item.hud then + num_items = num_items + 1 + end + end + + num_icons = num_icons + num_items + + local num_status = 0 + + for _, status in pairs(STATUS.active) do + num_status = num_status + 1 + end + + num_icons = num_icons + num_status + + local linespace = ((num_status > 0) and (num_items > 0)) and 25 or 0 + local height = math.max(num_icons, 1) * self.size.w + + math.max(num_icons - 1, 0) * ((num_icons > 1) and self.padding or 0) + + linespace + local startY = basepos.y + 0.5 * self.size.w + 0.5 * height + local curY = startY + + -- draw status + for _, status in pairs(STATUS.active) do + if status.type == "bad" then + status.hud_color = color_badstatus + end + + if status.type == "good" then + status.hud_color = color_goodstatus + end + + if status.type == "default" then + status.hud_color = Color(self.basecolor.r, self.basecolor.g, self.basecolor.b) + end + + -- fallback + if status.type == nil and status.hud_color == nil then + status.hud_color = Color(self.basecolor.r, self.basecolor.g, self.basecolor.b) + end + + curY = self:DrawIcon(curY, status) + end + + -- draw spacer + if num_status > 0 and num_items > 0 then + curY = curY - 16 + + local pos = self:GetPos() + local size = self:GetSize() + + surface.SetDrawColor(self.basecolor) + surface.DrawRect(pos.x, curY + self.padding * 0.5 + 8, size.w, 2) + end + + -- draw items + for i = 1, #itms do + local item = items.GetStored(itms[i]) + if not item or not item.hud then + continue + end + + item.hud_color = Color(self.basecolor.r, self.basecolor.g, self.basecolor.b) + curY = self:DrawIcon(curY, item) + end + + self:SetSize(self.size.w, -math.max(height, self.minsize.h)) -- adjust the size + self:SetPos(basepos.x, startY - height) + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/ttttarget/old_ttt_target.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/ttttarget/old_ttt_target.lua index 013e669b0..7f8929e98 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/ttttarget/old_ttt_target.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/ttttarget/old_ttt_target.lua @@ -7,84 +7,101 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then -- CLIENT - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 0, h = 45}, - minsize = {w = 0, h = 45} - } - - function HUDELEMENT:PreInitialize() - BaseClass.PreInitialize(self) - - hudelements.RegisterChildRelation(self.id, "old_ttt_info", false) - end - - function HUDELEMENT:Initialize() - BaseClass.Initialize(self) - end - - function HUDELEMENT:GetDefaults() - local _, height = self.maxwidth, 45 - local parent = self:GetParentRelation() - local parentEl = hudelements.GetStored(parent) - local x, y = 15, ScrH() - height - self.maxheight - self.margin - - if parentEl then - x = parentEl.pos.x - y = parentEl.pos.y - self.margin - height - 30 - end - - const_defaults["basepos"] = {x = x, y = y} - const_defaults["size"] = {w = self.maxwidth, h = 45} - const_defaults["minsize"] = {w = self.maxwidth, h = 45} - - return const_defaults - end - - function HUDELEMENT:DrawComponent(name, col, val) - local pos = self:GetPos() - local size = self:GetSize() - local x, y = pos.x, pos.y - local width, height = size.w, size.h - - draw.RoundedBox(8, x, y, width, height, self.bg_colors.background_main) - - local bar_width = width - self.dmargin - local bar_height = height - self.dmargin - - local tx = x + self.margin - local ty = y + self.margin - - self:PaintBar(tx, ty, bar_width, bar_height, col) - self:ShadowedText(val, "HealthAmmo", tx + bar_width * 0.5, ty + bar_height * 0.5, COLOR_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) - - draw.SimpleText(name, "TabLarge", x + self.margin * 2, y, COLOR_WHITE, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - end - - local edit_colors = { - border = COLOR_WHITE, - background = Color(0, 0, 10, 200), - fill = Color(100, 100, 100, 255) - } - - function HUDELEMENT:Draw() - local ply = LocalPlayer() - - if not IsValid(ply) then return end - - local tgt = ply:GetTargetPlayer() - - if HUDEditor.IsEditing then - self:DrawComponent("TARGET", edit_colors, "- TARGET -") - elseif IsValid(tgt) and ply:IsActive() then - local col_tbl = { - border = COLOR_WHITE, - background = tgt:GetRoleDkColor(), - fill = tgt:GetRoleColor() - } - - self:DrawComponent("TARGET", col_tbl, tgt:Nick()) - end - end + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 0, h = 45 }, + minsize = { w = 0, h = 45 }, + } + + function HUDELEMENT:PreInitialize() + BaseClass.PreInitialize(self) + + hudelements.RegisterChildRelation(self.id, "old_ttt_info", false) + end + + function HUDELEMENT:Initialize() + BaseClass.Initialize(self) + end + + function HUDELEMENT:GetDefaults() + local _, height = self.maxwidth, 45 + local parent = self:GetParentRelation() + local parentEl = hudelements.GetStored(parent) + local x, y = 15, ScrH() - height - self.maxheight - self.margin + + if parentEl then + x = parentEl.pos.x + y = parentEl.pos.y - self.margin - height - 30 + end + + const_defaults["basepos"] = { x = x, y = y } + const_defaults["size"] = { w = self.maxwidth, h = 45 } + const_defaults["minsize"] = { w = self.maxwidth, h = 45 } + + return const_defaults + end + + function HUDELEMENT:DrawComponent(name, col, val) + local pos = self:GetPos() + local size = self:GetSize() + local x, y = pos.x, pos.y + local width, height = size.w, size.h + + draw.RoundedBox(8, x, y, width, height, self.bg_colors.background_main) + + local bar_width = width - self.dmargin + local bar_height = height - self.dmargin + + local tx = x + self.margin + local ty = y + self.margin + + self:PaintBar(tx, ty, bar_width, bar_height, col) + self:ShadowedText( + val, + "HealthAmmo", + tx + bar_width * 0.5, + ty + bar_height * 0.5, + COLOR_WHITE, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) + + draw.SimpleText( + name, + "TabLarge", + x + self.margin * 2, + y, + COLOR_WHITE, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) + end + + local edit_colors = { + border = COLOR_WHITE, + background = Color(0, 0, 10, 200), + fill = Color(100, 100, 100, 255), + } + + function HUDELEMENT:Draw() + local ply = LocalPlayer() + + if not IsValid(ply) then + return + end + + local tgt = ply:GetTargetPlayer() + + if HUDEditor.IsEditing then + self:DrawComponent("TARGET", edit_colors, "- TARGET -") + elseif IsValid(tgt) and ply:IsActive() then + local col_tbl = { + border = COLOR_WHITE, + background = tgt:GetRoleDkColor(), + fill = tgt:GetRoleColor(), + } + + self:DrawComponent("TARGET", col_tbl, tgt:Nick()) + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/ttttarget/pure_skin_target.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/ttttarget/pure_skin_target.lua index f76464fb9..8a9b0d368 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/ttttarget/pure_skin_target.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/ttttarget/pure_skin_target.lua @@ -7,77 +7,99 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then -- CLIENT - local pad = 14 -- padding - local iconSize = 64 - - HUDELEMENT.icon = Material("vgui/ttt/target_icon") - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 365, h = 32}, - minsize = {w = 225, h = 32} - } - - function HUDELEMENT:Initialize() - self.scale = 1.0 - self.basecolor = self:GetHUDBasecolor() - self.pad = pad - self.iconSize = iconSize - - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return true, false - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = 10 * self.scale, y = ScrH() - self.size.h - 146 * self.scale - self.pad - 10 * self.scale} - - return const_defaults - end - - function HUDELEMENT:PerformLayout() - self.scale = appearance.GetGlobalScale() - self.basecolor = self:GetHUDBasecolor() - self.iconSize = iconSize * self.scale - self.pad = pad * self.scale - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:DrawComponent(name) - local pos = self:GetPos() - local size = self:GetSize() - local x, y = pos.x, pos.y - local w, h = size.w, size.h - - self:DrawBg(x, y, w, h, self.basecolor) - draw.AdvancedText(name, "PureSkinBar", x + self.iconSize + self.pad, y + h * 0.5, util.GetDefaultColor(self.basecolor), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, true, self.scale) - self:DrawLines(x, y, w, h, self.basecolor.a) - - local nSize = self.iconSize - 16 - - draw.FilteredShadowedTexture(x, y - 2 - (nSize - h), nSize, nSize, self.icon, 255, util.GetDefaultColor(self.basecolor), self.scale) - end - - function HUDELEMENT:ShouldDraw() - local client = LocalPlayer() - - return IsValid(client) - end - - function HUDELEMENT:Draw() - local client = LocalPlayer() - - local tgt = client:GetTargetPlayer() - - if HUDEditor.IsEditing then - self:DrawComponent("- TARGET -") - elseif IsValid(tgt) and client:IsActive() then - self:DrawComponent(tgt:Nick()) - end - end + local pad = 14 -- padding + local iconSize = 64 + + HUDELEMENT.icon = Material("vgui/ttt/target_icon") + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 365, h = 32 }, + minsize = { w = 225, h = 32 }, + } + + function HUDELEMENT:Initialize() + self.scale = 1.0 + self.basecolor = self:GetHUDBasecolor() + self.pad = pad + self.iconSize = iconSize + + BaseClass.Initialize(self) + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return true, false + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = { + x = 10 * self.scale, + y = ScrH() - self.size.h - 146 * self.scale - self.pad - 10 * self.scale, + } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + self.scale = appearance.GetGlobalScale() + self.basecolor = self:GetHUDBasecolor() + self.iconSize = iconSize * self.scale + self.pad = pad * self.scale + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:DrawComponent(name) + local pos = self:GetPos() + local size = self:GetSize() + local x, y = pos.x, pos.y + local w, h = size.w, size.h + + self:DrawBg(x, y, w, h, self.basecolor) + draw.AdvancedText( + name, + "PureSkinBar", + x + self.iconSize + self.pad, + y + h * 0.5, + util.GetDefaultColor(self.basecolor), + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + true, + self.scale + ) + self:DrawLines(x, y, w, h, self.basecolor.a) + + local nSize = self.iconSize - 16 + + draw.FilteredShadowedTexture( + x, + y - 2 - (nSize - h), + nSize, + nSize, + self.icon, + 255, + util.GetDefaultColor(self.basecolor), + self.scale + ) + end + + function HUDELEMENT:ShouldDraw() + local client = LocalPlayer() + + return IsValid(client) + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + + local tgt = client:GetTargetPlayer() + + if HUDEditor.IsEditing then + self:DrawComponent("- TARGET -") + elseif IsValid(tgt) and client:IsActive() then + self:DrawComponent(tgt:Nick()) + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttteamindicator/pure_skin_teamindicator.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttteamindicator/pure_skin_teamindicator.lua index c39301af5..803b8aa82 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttteamindicator/pure_skin_teamindicator.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttteamindicator/pure_skin_teamindicator.lua @@ -9,104 +9,113 @@ HUDELEMENT.togglable = true DEFINE_BASECLASS(base) if CLIENT then - local pad = 14 - local element_margin = 6 - - local material_watching = Material("vgui/ttt/watching_icon") - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 72, h = 72}, - minsize = {w = 0, h = 0} - } - - function HUDELEMENT:PreInitialize() - hudelements.RegisterChildRelation(self.id, "pure_skin_roundinfo", false) - end - - function HUDELEMENT:Initialize() - self.parentInstance = hudelements.GetStored(self.parent) - self.pad = pad - self.element_margin = element_margin - self.scale = 1.0 - self.basecolor = self:GetHUDBasecolor() - - BaseClass.Initialize(self) - end - - -- parameter overwrites - function HUDELEMENT:ShouldDraw() - return GAMEMODE.round_state == ROUND_ACTIVE - end - - function HUDELEMENT:InheritParentBorder() - return true - end - -- parameter overwrites end - - function HUDELEMENT:GetDefaults() - return const_defaults - end - - function HUDELEMENT:PerformLayout() - local parent_pos = self.parentInstance:GetPos() - local parent_size = self.parentInstance:GetSize() - local parent_defaults = self.parentInstance:GetDefaults() - local size = parent_size.h - - self.basecolor = self:GetHUDBasecolor() - self.scale = size / parent_defaults.size.h - self.pad = pad * self.scale - self.element_margin = element_margin * self.scale - - self:SetPos(parent_pos.x - size, parent_pos.y) - self:SetSize(size, size) - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:Draw() - local client = LocalPlayer() - local pos = self:GetPos() - local size = self:GetSize() - local x, y = pos.x, pos.y - local w, h = size.w, size.h - - -- draw team icon - local team = client:GetTeam() - local tm = TEAMS[team] - - -- draw bg and shadow - self:DrawBg(x, y, w, h, self.basecolor) - - local iconSize = h - self.pad * 2 - local icon, c - - if client:Alive() and client:IsTerror() then - icon = tm.iconMaterial - c = tm.color or COLOR_BLACK - else -- player is dead and spectator - icon = material_watching - c = COLOR_WARMGRAY - end - - -- draw dark bottom overlay - surface.SetDrawColor(0, 0, 0, 90) - surface.DrawRect(x, y, h, h) - - surface.SetDrawColor(clr(c)) - surface.DrawRect(x + self.pad, y + self.pad, iconSize, iconSize) - - if icon then - draw.FilteredShadowedTexture(x + self.pad, y + self.pad, iconSize, iconSize, icon, 255, util.GetDefaultColor(c), self.scale) - end - - -- draw lines around the element - self:DrawLines(x + self.pad, y + self.pad, iconSize, iconSize, 255) - - -- draw lines around the element - if not self:InheritParentBorder() then - self:DrawLines(x, y, w, h, self.basecolor.a) - end - end + local pad = 14 + local element_margin = 6 + + local material_watching = Material("vgui/ttt/watching_icon") + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 72, h = 72 }, + minsize = { w = 0, h = 0 }, + } + + function HUDELEMENT:PreInitialize() + hudelements.RegisterChildRelation(self.id, "pure_skin_roundinfo", false) + end + + function HUDELEMENT:Initialize() + self.parentInstance = hudelements.GetStored(self.parent) + self.pad = pad + self.element_margin = element_margin + self.scale = 1.0 + self.basecolor = self:GetHUDBasecolor() + + BaseClass.Initialize(self) + end + + -- parameter overwrites + function HUDELEMENT:ShouldDraw() + return GAMEMODE.round_state == ROUND_ACTIVE + end + + function HUDELEMENT:InheritParentBorder() + return true + end + -- parameter overwrites end + + function HUDELEMENT:GetDefaults() + return const_defaults + end + + function HUDELEMENT:PerformLayout() + local parent_pos = self.parentInstance:GetPos() + local parent_size = self.parentInstance:GetSize() + local parent_defaults = self.parentInstance:GetDefaults() + local size = parent_size.h + + self.basecolor = self:GetHUDBasecolor() + self.scale = size / parent_defaults.size.h + self.pad = pad * self.scale + self.element_margin = element_margin * self.scale + + self:SetPos(parent_pos.x - size, parent_pos.y) + self:SetSize(size, size) + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:Draw() + local client = LocalPlayer() + local pos = self:GetPos() + local size = self:GetSize() + local x, y = pos.x, pos.y + local w, h = size.w, size.h + + -- draw team icon + local team = client:GetTeam() + local tm = TEAMS[team] + + -- draw bg and shadow + self:DrawBg(x, y, w, h, self.basecolor) + + local iconSize = h - self.pad * 2 + local icon, c + + if client:Alive() and client:IsTerror() then + icon = tm.iconMaterial + c = tm.color or COLOR_BLACK + else -- player is dead and spectator + icon = material_watching + c = COLOR_WARMGRAY + end + + -- draw dark bottom overlay + surface.SetDrawColor(0, 0, 0, 90) + surface.DrawRect(x, y, h, h) + + surface.SetDrawColor(clr(c)) + surface.DrawRect(x + self.pad, y + self.pad, iconSize, iconSize) + + if icon then + draw.FilteredShadowedTexture( + x + self.pad, + y + self.pad, + iconSize, + iconSize, + icon, + 255, + util.GetDefaultColor(c), + self.scale + ) + end + + -- draw lines around the element + self:DrawLines(x + self.pad, y + self.pad, iconSize, iconSize, 255) + + -- draw lines around the element + if not self:InheritParentBorder() then + self:DrawLines(x, y, w, h, self.basecolor.a) + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttwswitch/old_ttt_wswitch.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttwswitch/old_ttt_wswitch.lua index 9b1940d6e..91ed80512 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttwswitch/old_ttt_wswitch.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttwswitch/old_ttt_wswitch.lua @@ -13,169 +13,170 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - local width = 300 - local height = 20 - - HUDELEMENT.margin = 10 - HUDELEMENT.barcorner = surface.GetTextureID("gui/corner8") - - -- Draw a bar in the style of the the weapon pickup ones - local round = math.Round - - -- color defines - col_active = { - bg = Color(20, 20, 20, 250), - text_empty = Color(200, 20, 20, 255), - text = COLOR_WHITE, - shadow = 255 - } - - col_dark = { - bg = Color(20, 20, 20, 200), - text_empty = Color(200, 20, 20, 100), - text = Color(255, 255, 255, 100), - shadow = 100 - } - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = width, h = -height}, - minsize = {w = width, h = height} - } - - function HUDELEMENT:Initialize() - WSWITCH:UpdateWeaponCache() - - BaseClass.Initialize(self) - - self.defaults.resizeableY = false - self.defaults.minHeight = height - end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = ScrW() - (width + self.margin * 2), y = ScrH() - self.margin} - - return const_defaults - end - - function HUDELEMENT:PerformLayout() - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:DrawBarBg(x, y, w, h, col) - local rx = round(x - 4) - local ry = round(y - h * 0.5 - 4) - local rw = round(w + 9) - local rh = round(h + 8) - - local b = 8 -- bordersize - local bh = b * 0.5 - - local ply = LocalPlayer() - local c = col == col_active and ply:GetRoleColor() or ply:GetRoleDkColor() - - -- Draw the colour tip - surface.SetTexture(self.barcorner) - - surface.SetDrawColor(c.r, c.g, c.b, c.a) - surface.DrawTexturedRectRotated(rx + bh, ry + bh, b, b, 0) - surface.DrawTexturedRectRotated(rx + bh, ry + rh - bh, b, b, 90) - surface.DrawRect(rx, ry + b, b, rh - b * 2) - surface.DrawRect(rx + b, ry, h - 4, rh) - - -- Draw the remainder - -- Could just draw a full roundedrect bg and overdraw it with the tip, but - -- I don't have to do the hard work here anymore anyway - c = col.bg - - surface.SetDrawColor(c.r, c.g, c.b, c.a) - surface.DrawRect(rx + b + h - 4, ry, rw - (h - 4) - b * 2, rh) - surface.DrawTexturedRectRotated(rx + rw - bh, ry + rh - bh, b, b, 180) - surface.DrawTexturedRectRotated(rx + rw - bh, ry + bh, b, b, 270) - surface.DrawRect(rx + rw - b, ry + b, b, rh - b * 2) - end - - function HUDELEMENT:DrawWeapon(x, y, c, wep) - if not IsValid(wep) or not IsValid(wep.Owner) or not isfunction(wep.Owner.GetAmmoCount) then - return false - end - - local name = TryTranslation(wep:GetPrintName() or wep.PrintName or "...") - local cl1, am1 = wep:Clip1(), (wep.Ammo1 and wep:Ammo1() or false) - local ammo = false - - -- Clip1 will be -1 if a melee weapon - -- Ammo1 will be false if weapon has no owner (was just dropped) - if cl1 ~= -1 and am1 ~= false then - ammo = Format("%i + %02i", cl1, am1) - end - - -- Slot - local _tmp = {x + 4, y} - local spec = { - text = MakeKindValid(wep.Kind), - font = "Trebuchet22", - pos = _tmp, - yalign = TEXT_ALIGN_CENTER, - color = c.text - } - - draw.TextShadow(spec, 1, c.shadow) - - -- Name - spec.text = name - spec.font = "TimeLeft" - spec.pos[1] = x + 10 + height - - draw.Text(spec) - - if ammo then - local col = (wep:Clip1() == 0 and wep:Ammo1() == 0) and c.text_empty or c.text - - -- Ammo - spec.text = ammo - spec.pos[1] = x + width - self.margin * 3 - spec.xalign = TEXT_ALIGN_RIGHT - spec.color = col - - draw.Text(spec) - end + local width = 300 + local height = 20 + + HUDELEMENT.margin = 10 + HUDELEMENT.barcorner = surface.GetTextureID("gui/corner8") + + -- Draw a bar in the style of the the weapon pickup ones + local round = math.Round + + -- color defines + col_active = { + bg = Color(20, 20, 20, 250), + text_empty = Color(200, 20, 20, 255), + text = COLOR_WHITE, + shadow = 255, + } + + col_dark = { + bg = Color(20, 20, 20, 200), + text_empty = Color(200, 20, 20, 100), + text = Color(255, 255, 255, 100), + shadow = 100, + } + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = width, h = -height }, + minsize = { w = width, h = height }, + } + + function HUDELEMENT:Initialize() + WSWITCH:UpdateWeaponCache() + + BaseClass.Initialize(self) + + self.defaults.resizeableY = false + self.defaults.minHeight = height + end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = + { x = ScrW() - (width + self.margin * 2), y = ScrH() - self.margin } + + return const_defaults + end + + function HUDELEMENT:PerformLayout() + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:DrawBarBg(x, y, w, h, col) + local rx = round(x - 4) + local ry = round(y - h * 0.5 - 4) + local rw = round(w + 9) + local rh = round(h + 8) + + local b = 8 -- bordersize + local bh = b * 0.5 + + local ply = LocalPlayer() + local c = col == col_active and ply:GetRoleColor() or ply:GetRoleDkColor() + + -- Draw the colour tip + surface.SetTexture(self.barcorner) + + surface.SetDrawColor(c.r, c.g, c.b, c.a) + surface.DrawTexturedRectRotated(rx + bh, ry + bh, b, b, 0) + surface.DrawTexturedRectRotated(rx + bh, ry + rh - bh, b, b, 90) + surface.DrawRect(rx, ry + b, b, rh - b * 2) + surface.DrawRect(rx + b, ry, h - 4, rh) + + -- Draw the remainder + -- Could just draw a full roundedrect bg and overdraw it with the tip, but + -- I don't have to do the hard work here anymore anyway + c = col.bg + + surface.SetDrawColor(c.r, c.g, c.b, c.a) + surface.DrawRect(rx + b + h - 4, ry, rw - (h - 4) - b * 2, rh) + surface.DrawTexturedRectRotated(rx + rw - bh, ry + rh - bh, b, b, 180) + surface.DrawTexturedRectRotated(rx + rw - bh, ry + bh, b, b, 270) + surface.DrawRect(rx + rw - b, ry + b, b, rh - b * 2) + end + + function HUDELEMENT:DrawWeapon(x, y, c, wep) + if not IsValid(wep) or not IsValid(wep.Owner) or not isfunction(wep.Owner.GetAmmoCount) then + return false + end + + local name = TryTranslation(wep:GetPrintName() or wep.PrintName or "...") + local cl1, am1 = wep:Clip1(), (wep.Ammo1 and wep:Ammo1() or false) + local ammo = false + + -- Clip1 will be -1 if a melee weapon + -- Ammo1 will be false if weapon has no owner (was just dropped) + if cl1 ~= -1 and am1 ~= false then + ammo = Format("%i + %02i", cl1, am1) + end + + -- Slot + local _tmp = { x + 4, y } + local spec = { + text = MakeKindValid(wep.Kind), + font = "Trebuchet22", + pos = _tmp, + yalign = TEXT_ALIGN_CENTER, + color = c.text, + } + + draw.TextShadow(spec, 1, c.shadow) + + -- Name + spec.text = name + spec.font = "TimeLeft" + spec.pos[1] = x + 10 + height + + draw.Text(spec) + + if ammo then + local col = (wep:Clip1() == 0 and wep:Ammo1() == 0) and c.text_empty or c.text + + -- Ammo + spec.text = ammo + spec.pos[1] = x + width - self.margin * 3 + spec.xalign = TEXT_ALIGN_RIGHT + spec.color = col + + draw.Text(spec) + end - return true - end + return true + end - function HUDELEMENT:ShouldDraw() - return HUDEditor.IsEditing or WSWITCH.Show - end - - function HUDELEMENT:Draw() - local weaponList = {} - local weps = WSWITCH.WeaponCache - local count = table.Count(weps) - - for i = 1, count do - weaponList[i] = {h = height} - end - - self:SetElements(weaponList) - self:SetElementMargin(self.margin) - - BaseClass.Draw(self) - end - - function HUDELEMENT:DrawElement(i, x, y, w, h) - local col = col_dark - - if WSWITCH.Selected == i then - col = col_active - end - - self:DrawBarBg(x, y, w, h, col) - - if not self:DrawWeapon(x, y, col, WSWITCH.WeaponCache[i]) then - WSWITCH:UpdateWeaponCache() - - return - end - end + function HUDELEMENT:ShouldDraw() + return HUDEditor.IsEditing or WSWITCH.Show + end + + function HUDELEMENT:Draw() + local weaponList = {} + local weps = WSWITCH.WeaponCache + local count = table.Count(weps) + + for i = 1, count do + weaponList[i] = { h = height } + end + + self:SetElements(weaponList) + self:SetElementMargin(self.margin) + + BaseClass.Draw(self) + end + + function HUDELEMENT:DrawElement(i, x, y, w, h) + local col = col_dark + + if WSWITCH.Selected == i then + col = col_active + end + + self:DrawBarBg(x, y, w, h, col) + + if not self:DrawWeapon(x, y, col, WSWITCH.WeaponCache[i]) then + WSWITCH:UpdateWeaponCache() + + return + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttwswitch/pure_skin_wswitch.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttwswitch/pure_skin_wswitch.lua index 32c09c5f8..c8ac8bbc2 100644 --- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttwswitch/pure_skin_wswitch.lua +++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttwswitch/pure_skin_wswitch.lua @@ -11,149 +11,186 @@ DEFINE_BASECLASS(base) HUDELEMENT.Base = base if CLIENT then - - -- color defines - local color_empty = Color(200, 20, 20, 255) - local color_empty_dark = Color(200, 20, 20, 100) - - local element_height = 28 - local margin = 5 - local lpw = 22 -- left panel width - - local const_defaults = { - basepos = {x = 0, y = 0}, - size = {w = 365, h = 28}, - minsize = {w = 240, h = 28} - } - - function HUDELEMENT:PreInitialize() - self.drawer = hudelements.GetStored("pure_skin_element") - end - - function HUDELEMENT:Initialize() - self.scale = 1.0 - self.basecolor = self:GetHUDBasecolor() - self.element_height = element_height - self.margin = margin - self.lpw = lpw - - WSWITCH:UpdateWeaponCache() - - BaseClass.Initialize(self) - end - - function HUDELEMENT:GetDefaults() - const_defaults["basepos"] = {x = ScrW() - (self.size.w + self.margin * 2), y = ScrH() - self.margin} - - return const_defaults - end - - -- parameter overwrites - function HUDELEMENT:IsResizable() - return true, false - end - -- parameter overwrites end - - function HUDELEMENT:PerformLayout() - self.scale = appearance.GetGlobalScale() - self.basecolor = self:GetHUDBasecolor() - self.element_height = element_height * self.scale - self.margin = margin * self.scale - self.lpw = lpw * self.scale - - BaseClass.PerformLayout(self) - end - - function HUDELEMENT:DrawBarBg(x, y, w, h, active) - local ply = LocalPlayer() - local c = active and ply:GetRoleColor() or ply:GetRoleDkColor() - - -- draw bg and shadow - self.drawer:DrawBg(x, y, w, h, self.basecolor) - - if active then - surface.SetDrawColor(0, 0, 0, 90) - surface.DrawRect(x, y, w, h) - end - - -- Draw the colour tip - surface.SetDrawColor(c.r, c.g, c.b, c.a) - surface.DrawRect(x, y, self.lpw, h) - - -- draw lines around the element - self.drawer:DrawLines(x, y, w, h, self.basecolor.a) - - return c - end - - function HUDELEMENT:DrawWeapon(x, y, active, wep, tip_color) - if not IsValid(wep) or not IsValid(wep.Owner) or not isfunction(wep.Owner.GetAmmoCount) then - return false - end - - --define colors - local text_color = util.GetDefaultColor(self.basecolor) - text_color = active and text_color or Color(text_color.r, text_color.g, text_color.b, text_color.r > 128 and 100 or 180) - - local number_color = util.GetDefaultColor(tip_color) - number_color = active and number_color or Color(number_color.r, number_color.g, number_color.b, number_color.r > 128 and 100 or 180) - - local empty_color = active and color_empty or color_empty_dark - - local name = TryTranslation(wep:GetPrintName() or wep.PrintName or "...") - local cl1, am1 = wep:Clip1(), (wep.Ammo1 and wep:Ammo1() or false) - local ammo = false - - -- Clip1 will be -1 if a melee weapon - -- Ammo1 will be false if weapon has no owner (was just dropped) - if cl1 ~= -1 and am1 ~= false then - ammo = Format("%i + %02i", cl1, am1) - end - - -- Slot - draw.AdvancedText(MakeKindValid(wep.Kind), "PureSkinWepNum", x + self.lpw * 0.5, y + self.element_height * 0.5, number_color, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, true, self.scale) - - -- Name - draw.AdvancedText(string.upper(name), "PureSkinWep", x + 10 + self.element_height, y + self.element_height * 0.5, text_color, nil, TEXT_ALIGN_CENTER, true, self.scale) - - if ammo then - local col = (wep:Clip1() == 0 and wep:Ammo1() == 0) and empty_color or text_color - - -- Ammo - draw.AdvancedText(tostring(ammo), "PureSkinWep", x + self.size.w - self.margin * 3, y + self.element_height * 0.5, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER, false, self.scale) - end - - return true - end - - function HUDELEMENT:ShouldDraw() - return HUDEditor.IsEditing or WSWITCH.Show - end - - function HUDELEMENT:Draw() - local weaponList = {} - local weps = WSWITCH.WeaponCache - local count = table.Count(weps) - - for i = 1, count do - weaponList[i] = {h = self.element_height} - end - - self:SetElements(weaponList) - self:SetElementMargin(self.margin) - - BaseClass.Draw(self) - end - - function HUDELEMENT:DrawElement(i, x, y, w, h) - local active = WSWITCH.Selected == i - - local tipCol = self:DrawBarBg(x, y, w, h, active) - - if not self:DrawWeapon(x, y, active, WSWITCH.WeaponCache[i], tipCol) then - WSWITCH:UpdateWeaponCache() - - return - end - end + -- color defines + local color_empty = Color(200, 20, 20, 255) + local color_empty_dark = Color(200, 20, 20, 100) + + local element_height = 28 + local margin = 5 + local lpw = 22 -- left panel width + + local const_defaults = { + basepos = { x = 0, y = 0 }, + size = { w = 365, h = 28 }, + minsize = { w = 240, h = 28 }, + } + + function HUDELEMENT:PreInitialize() + self.drawer = hudelements.GetStored("pure_skin_element") + end + + function HUDELEMENT:Initialize() + self.scale = 1.0 + self.basecolor = self:GetHUDBasecolor() + self.element_height = element_height + self.margin = margin + self.lpw = lpw + + WSWITCH:UpdateWeaponCache() + + BaseClass.Initialize(self) + end + + function HUDELEMENT:GetDefaults() + const_defaults["basepos"] = + { x = ScrW() - (self.size.w + self.margin * 2), y = ScrH() - self.margin } + + return const_defaults + end + + -- parameter overwrites + function HUDELEMENT:IsResizable() + return true, false + end + -- parameter overwrites end + + function HUDELEMENT:PerformLayout() + self.scale = appearance.GetGlobalScale() + self.basecolor = self:GetHUDBasecolor() + self.element_height = element_height * self.scale + self.margin = margin * self.scale + self.lpw = lpw * self.scale + + BaseClass.PerformLayout(self) + end + + function HUDELEMENT:DrawBarBg(x, y, w, h, active) + local ply = LocalPlayer() + local c = active and ply:GetRoleColor() or ply:GetRoleDkColor() + + -- draw bg and shadow + self.drawer:DrawBg(x, y, w, h, self.basecolor) + + if active then + surface.SetDrawColor(0, 0, 0, 90) + surface.DrawRect(x, y, w, h) + end + + -- Draw the colour tip + surface.SetDrawColor(c.r, c.g, c.b, c.a) + surface.DrawRect(x, y, self.lpw, h) + + -- draw lines around the element + self.drawer:DrawLines(x, y, w, h, self.basecolor.a) + + return c + end + + function HUDELEMENT:DrawWeapon(x, y, active, wep, tip_color) + if not IsValid(wep) or not IsValid(wep.Owner) or not isfunction(wep.Owner.GetAmmoCount) then + return false + end + + --define colors + local text_color = util.GetDefaultColor(self.basecolor) + text_color = active and text_color + or Color(text_color.r, text_color.g, text_color.b, text_color.r > 128 and 100 or 180) + + local number_color = util.GetDefaultColor(tip_color) + number_color = active and number_color + or Color( + number_color.r, + number_color.g, + number_color.b, + number_color.r > 128 and 100 or 180 + ) + + local empty_color = active and color_empty or color_empty_dark + + local name = TryTranslation(wep:GetPrintName() or wep.PrintName or "...") + local cl1, am1 = wep:Clip1(), (wep.Ammo1 and wep:Ammo1() or false) + local ammo = false + + -- Clip1 will be -1 if a melee weapon + -- Ammo1 will be false if weapon has no owner (was just dropped) + if cl1 ~= -1 and am1 ~= false then + ammo = Format("%i + %02i", cl1, am1) + end + + -- Slot + draw.AdvancedText( + MakeKindValid(wep.Kind), + "PureSkinWepNum", + x + self.lpw * 0.5, + y + self.element_height * 0.5, + number_color, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + true, + self.scale + ) + + -- Name + draw.AdvancedText( + string.upper(name), + "PureSkinWep", + x + 10 + self.element_height, + y + self.element_height * 0.5, + text_color, + nil, + TEXT_ALIGN_CENTER, + true, + self.scale + ) + + if ammo then + local col = (wep:Clip1() == 0 and wep:Ammo1() == 0) and empty_color or text_color + + -- Ammo + draw.AdvancedText( + tostring(ammo), + "PureSkinWep", + x + self.size.w - self.margin * 3, + y + self.element_height * 0.5, + col, + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_CENTER, + false, + self.scale + ) + end + + return true + end + + function HUDELEMENT:ShouldDraw() + return HUDEditor.IsEditing or WSWITCH.Show + end + + function HUDELEMENT:Draw() + local weaponList = {} + local weps = WSWITCH.WeaponCache + local count = table.Count(weps) + + for i = 1, count do + weaponList[i] = { h = self.element_height } + end + + self:SetElements(weaponList) + self:SetElementMargin(self.margin) + + BaseClass.Draw(self) + end + + function HUDELEMENT:DrawElement(i, x, y, w, h) + local active = WSWITCH.Selected == i + + local tipCol = self:DrawBarBg(x, y, w, h, active) + + if not self:DrawWeapon(x, y, active, WSWITCH.WeaponCache[i], tipCol) then + WSWITCH:UpdateWeaponCache() + + return + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/huds/base_huds/hud_base/cl_init.lua b/gamemodes/terrortown/gamemode/shared/huds/base_huds/hud_base/cl_init.lua index 5566c7bc4..8f52c5337 100644 --- a/gamemodes/terrortown/gamemode/shared/huds/base_huds/hud_base/cl_init.lua +++ b/gamemodes/terrortown/gamemode/shared/huds/base_huds/hud_base/cl_init.lua @@ -21,7 +21,7 @@ HUD.savingKeys = {} -- @return table -- @realm client function HUD:GetSavingKeys() - return self.savingKeys + return self.savingKeys end --- @@ -32,11 +32,11 @@ end -- @param string elementID -- @realm client function HUD:ForceElement(elementID) - local elem = hudelements.GetStored(elementID) + local elem = hudelements.GetStored(elementID) - if elem and elem.type and not self.forcedElements[elem.type] then - self.forcedElements[elem.type] = elementID - end + if elem and elem.type and not self.forcedElements[elem.type] then + self.forcedElements[elem.type] = elementID + end end --- @@ -44,7 +44,7 @@ end -- @return table -- @realm client function HUD:GetForcedElements() - return table.Copy(self.forcedElements) + return table.Copy(self.forcedElements) end --- @@ -53,7 +53,7 @@ end -- @param string elementType -- @realm client function HUD:HideType(elementType) - self.disabledTypes[elementType] = true + self.disabledTypes[elementType] = true end --- @@ -65,16 +65,16 @@ end -- @return boolean -- @realm client function HUD:ShouldShow(elementType) - local elem = self:GetElementByType(elementType) - if elem then - if elem.togglable and not GetGlobalBool("ttt2_elem_toggled_" .. elem.id, false) then - return false - end - - return true - else - return false - end + local elem = self:GetElementByType(elementType) + if elem then + if elem.togglable and not GetGlobalBool("ttt2_elem_toggled_" .. elem.id, false) then + return false + end + + return true + else + return false + end end --- @@ -83,22 +83,22 @@ end -- loads new values either for itself (eg. basecolor) or for its children. -- @realm client function HUD:PerformLayout() - local elems = self:GetElements() + local elems = self:GetElements() - for i = 1, #elems do - local elemName = elems[i] + for i = 1, #elems do + local elemName = elems[i] - local elem = hudelements.GetStored(elemName) - if not elem then - Msg("Error: Hudelement not found during PerformLayout: " .. elemName) + local elem = hudelements.GetStored(elemName) + if not elem then + ErrorNoHaltWithStack("Error: Hudelement not found during PerformLayout: " .. elemName) - continue - end + continue + end - if not elem:IsChild() then - elem:PerformLayout() - end - end + if not elem:IsChild() then + elem:PerformLayout() + end + end end --- @@ -107,42 +107,54 @@ end -- @{HUD:PerformLayout} before setting the HUDELEMENT.initialized parameter. -- @realm client function HUD:Initialize() - local elems = self:GetElements() - - -- Initialize elements default values - for i = 1, #elems do - local elemName = elems[i] - - local elem = hudelements.GetStored(elemName) - if not elem then - Msg("Error: HUD " .. (self.id or "?") .. " has unknown element named " .. elemName .. "\n") - - continue - end - - if not elem:IsChild() then - elem:Initialize() - end - end - - self:PerformLayout() - - -- Initialize elements default values - for i = 1, #elems do - local elemName = elems[i] - - local elem = hudelements.GetStored(elemName) - if not elem then - Msg("Error: HUD " .. (self.id or "?") .. " has unknown element named " .. elemName .. "\n") - - continue - end - - elem.initialized = true - end - - -- Cache elements after initialization - self.cachedElems = elems + local elems = self:GetElements() + + -- Initialize elements default values + for i = 1, #elems do + local elemName = elems[i] + + local elem = hudelements.GetStored(elemName) + if not elem then + ErrorNoHaltWithStack( + "Error: HUD " + .. (self.id or "?") + .. " has unknown element named " + .. elemName + .. "\n" + ) + + continue + end + + if not elem:IsChild() then + elem:Initialize() + end + end + + self:PerformLayout() + + -- Initialize elements default values + for i = 1, #elems do + local elemName = elems[i] + + local elem = hudelements.GetStored(elemName) + if not elem then + ErrorNoHaltWithStack( + "Error: HUD " + .. (self.id or "?") + .. " has unknown element named " + .. elemName + .. "\n" + ) + + continue + end + + elem.initialized = true + end + + -- Cache elements after initialization + self.cachedElems = elems end --- @@ -152,7 +164,7 @@ end -- @return boolean -- @realm client function HUD:HasElementType(elementType) - return self:GetElementByType(elementType) ~= nil + return self:GetElementByType(elementType) ~= nil end --- @@ -164,30 +176,33 @@ end -- @return boolean -- @realm client function HUD:CanUseElement(elementTbl) - -- return false if the table is empty. - if not elementTbl then - return false - end - - -- return false if the element is disabled unless it is forced and it is not forced in this HUD. - if elementTbl.disabledUnlessForced and not table.HasValue(self.forcedElements, elementTbl.id) then - return false - end - - -- check if the element is a child and if it is parent element is a part of this HUD. - if elementTbl:IsChild() then - local parent, parentIsType = elementTbl:GetParentRelation() - - if not parent or parentIsType == nil then - return false - end - - -- find the parent element, if the element is bound to a type call this method again implicitly (check if element is used by the HUD) and if not, - -- get the specific element and call this method on it. - return (parentIsType and self:HasElementType(parent)) or (not parentIsType and self:CanUseElement(hudelements.GetStored(parent))) - end - - return true + -- return false if the table is empty. + if not elementTbl then + return false + end + + -- return false if the element is disabled unless it is forced and it is not forced in this HUD. + if + elementTbl.disabledUnlessForced and not table.HasValue(self.forcedElements, elementTbl.id) + then + return false + end + + -- check if the element is a child and if it is parent element is a part of this HUD. + if elementTbl:IsChild() then + local parent, parentIsType = elementTbl:GetParentRelation() + + if not parent or parentIsType == nil then + return false + end + + -- find the parent element, if the element is bound to a type call this method again implicitly (check if element is used by the HUD) and if not, + -- get the specific element and call this method on it. + return (parentIsType and self:HasElementType(parent)) + or (not parentIsType and self:CanUseElement(hudelements.GetStored(parent))) + end + + return true end --- @@ -201,49 +216,53 @@ end -- @return table -- @realm client function HUD:GetElementByType(elementType) - -- If elements are cached, only check them, instead of searching for a suitable one again. - if self.cachedElems ~= nil then - for i = 1, #self.cachedElems do - local cachedElem = hudelements.GetStored(self.cachedElems[i]) - if cachedElem.type == elementType then - return cachedElem - end - end - - return - end - - -- element type is hidden in this HUD so return nil - if self.disabledTypes[elementType] then return end - - local forcedElement = self.forcedElements[elementType] - local elementTbl = nil - - -- Check if an element is forced by the HUD for the given type - if forcedElement ~= nil then - elementTbl = hudelements.GetStored(forcedElement) - end - - -- Evaluate the forcedElement or try to find a usable element. - if self:CanUseElement(elementTbl) then - return elementTbl - else - -- fallback and search for the first usable element for this type - local availableElements = hudelements.GetAllTypeElements(elementType) - - -- find first valid element - for i = 1, #availableElements do - local el = availableElements[i] - - if not self:CanUseElement(el) then continue end - - elementTbl = el - - break - end - end - - return elementTbl + -- If elements are cached, only check them, instead of searching for a suitable one again. + if self.cachedElems ~= nil then + for i = 1, #self.cachedElems do + local cachedElem = hudelements.GetStored(self.cachedElems[i]) + if cachedElem.type == elementType then + return cachedElem + end + end + + return + end + + -- element type is hidden in this HUD so return nil + if self.disabledTypes[elementType] then + return + end + + local forcedElement = self.forcedElements[elementType] + local elementTbl = nil + + -- Check if an element is forced by the HUD for the given type + if forcedElement ~= nil then + elementTbl = hudelements.GetStored(forcedElement) + end + + -- Evaluate the forcedElement or try to find a usable element. + if self:CanUseElement(elementTbl) then + return elementTbl + else + -- fallback and search for the first usable element for this type + local availableElements = hudelements.GetAllTypeElements(elementType) + + -- find first valid element + for i = 1, #availableElements do + local el = availableElements[i] + + if not self:CanUseElement(el) then + continue + end + + elementTbl = el + + break + end + end + + return elementTbl end --- @@ -253,24 +272,26 @@ end -- @return table -- @realm client function HUD:GetElements() - if self.cachedElems then - return self.cachedElems - end + if self.cachedElems then + return self.cachedElems + end - -- loop through all types and if the hud does not provide an element take the first found instance for the type - local elems = {} - local elemTypes = hudelements.GetElementTypes() + -- loop through all types and if the hud does not provide an element take the first found instance for the type + local elems = {} + local elemTypes = hudelements.GetElementTypes() - for i = 1, #elemTypes do - local typ = elemTypes[i] + for i = 1, #elemTypes do + local typ = elemTypes[i] - local el = self:GetElementByType(typ) - if not el then continue end + local el = self:GetElementByType(typ) + if not el then + continue + end - elems[#elems + 1] = el.id - end + elems[#elems + 1] = el.id + end - return elems + return elems end --- @@ -284,30 +305,31 @@ end -- @param table elem -- @realm client function HUD:DrawElemAndChildren(elem) - --- - -- @realm client - if not elem.initialized or not elem.type or not hook.Run("HUDShouldDraw", elem.type) or not self:ShouldShow(elem.type) or not elem:ShouldDraw() then return end + --- + -- @realm client + -- stylua: ignore + if not elem.initialized or not elem.type or not hook.Run("HUDShouldDraw", elem.type) or not self:ShouldShow(elem.type) or not elem:ShouldDraw() then return end - local children = elem:GetChildren() + local children = elem:GetChildren() - for i = 1, #children do - local childName = children[i] + for i = 1, #children do + local childName = children[i] - local child = hudelements.GetStored(childName) - if not child then - MsgN("Error: Hudelement with name " .. childName .. " not found!") + local child = hudelements.GetStored(childName) + if not child then + Dev(1, "Error: Hudelement with name " .. childName .. " not found!") - continue - end + continue + end - self:DrawElemAndChildren(child) - end + self:DrawElemAndChildren(child) + end - elem:Draw() + elem:Draw() - if HUDEditor then - HUDEditor.DrawElem(elem) - end + if HUDEditor then + HUDEditor.DrawElem(elem) + end end --- @@ -316,22 +338,22 @@ end -- a child and have a HUDELEMENT.type (these are all non-base elements). -- @realm client function HUD:Draw() - local elems = self:GetElements() + local elems = self:GetElements() - for i = 1, #elems do - local elemName = elems[i] + for i = 1, #elems do + local elemName = elems[i] - local elem = hudelements.GetStored(elemName) - if not elem then - MsgN("Error: Hudelement with name " .. elemName .. " not found!") + local elem = hudelements.GetStored(elemName) + if not elem then + Dev(1, "Error: Hudelement with name " .. elemName .. " not found!") - continue - end + continue + end - if elem.type and not elem:IsChild() then - self:DrawElemAndChildren(elem) - end - end + if elem.type and not elem:IsChild() then + self:DrawElemAndChildren(elem) + end + end end --- @@ -340,22 +362,22 @@ end -- on non-child elements. -- @realm client function HUD:Reset() - local elems = self:GetElements() + local elems = self:GetElements() - for i = 1, #elems do - local elemName = elems[i] + for i = 1, #elems do + local elemName = elems[i] - local elem = hudelements.GetStored(elemName) - if not elem then - Msg("Error: Hudelement not found during Reset: " .. elemName) + local elem = hudelements.GetStored(elemName) + if not elem then + ErrorNoHaltWithStack("Error: Hudelement not found during Reset: " .. elemName) - continue - end + continue + end - if not elem:IsChild() then - elem:Reset() - end - end + if not elem:IsChild() then + elem:Reset() + end + end end --- @@ -364,20 +386,22 @@ end -- current HUD or editing a HUD in the HUDEditor. -- @realm client function HUD:SaveData() - -- save data for the HUD - sql.Save("ttt2_huds", self.id, self, self:GetSavingKeys()) + -- save data for the HUD + sql.Save("ttt2_huds", self.id, self, self:GetSavingKeys()) - local elems = self:GetElements() + local elems = self:GetElements() - -- save data of all elements of this HUD - for i = 1, #elems do - local elemName = elems[i] + -- save data of all elements of this HUD + for i = 1, #elems do + local elemName = elems[i] - local elem = hudelements.GetStored(elemName) - if not elem then continue end + local elem = hudelements.GetStored(elemName) + if not elem then + continue + end - elem:SaveData() - end + elem:SaveData() + end end --- @@ -387,28 +411,30 @@ end -- / changes to this HUD. -- @realm client function HUD:LoadData() - local skeys = self:GetSavingKeys() + local skeys = self:GetSavingKeys() - -- load and initialize all HUD data from database - if sql.CreateSqlTable("ttt2_huds", skeys) then - local loaded = sql.Load("ttt2_huds", self.id, self, skeys) + -- load and initialize all HUD data from database + if sql.CreateSqlTable("ttt2_huds", skeys) then + local loaded = sql.Load("ttt2_huds", self.id, self, skeys) - if not loaded then - sql.Init("ttt2_huds", self.id, self, skeys) - end - end + if not loaded then + sql.Init("ttt2_huds", self.id, self, skeys) + end + end - local elems = self:GetElements() + local elems = self:GetElements() - -- load data of all elements in this HUD - for i = 1, #elems do - local elemName = elems[i] + -- load data of all elements in this HUD + for i = 1, #elems do + local elemName = elems[i] - local elem = hudelements.GetStored(elemName) - if not elem then continue end + local elem = hudelements.GetStored(elemName) + if not elem then + continue + end - elem:LoadData() - end + elem:LoadData() + end - self:PerformLayout() + self:PerformLayout() end diff --git a/gamemodes/terrortown/gamemode/shared/huds/base_huds/scalable_hud/cl_init.lua b/gamemodes/terrortown/gamemode/shared/huds/base_huds/scalable_hud/cl_init.lua index 55041b0c0..6c565606e 100644 --- a/gamemodes/terrortown/gamemode/shared/huds/base_huds/scalable_hud/cl_init.lua +++ b/gamemodes/terrortown/gamemode/shared/huds/base_huds/scalable_hud/cl_init.lua @@ -5,8 +5,6 @@ local base = "hud_base" -local savingKeys - DEFINE_BASECLASS(base) HUD.Base = base @@ -19,9 +17,9 @@ HUD.scale = HUD.defaultscale -- @{HUD:PerformLayout} before setting the HUDELEMENT.initialized parameter. -- @realm client function HUD:Initialize() - self.basecolor = table.Copy(self.defaultcolor) + self.basecolor = table.Copy(self.defaultcolor) - BaseClass.Initialize(self) + BaseClass.Initialize(self) end --- @@ -30,19 +28,17 @@ end -- @return table -- @realm client function HUD:GetSavingKeys() - if not savingKeys then - savingKeys = BaseClass.GetSavingKeys(self) - savingKeys.basecolor = { - typ = "color", - desc = "label_hud_basecolor", - OnChange = function(slf, col) - slf:PerformLayout() - slf:SaveData() - end - } - end - - return table.Copy(savingKeys) + local savingKeys = BaseClass.GetSavingKeys(self) or {} + savingKeys.basecolor = { + typ = "color", + desc = "label_hud_basecolor", + OnChange = function(slf, col) + slf:PerformLayout() + slf:SaveData() + end, + } + + return table.Copy(savingKeys) end --- @@ -52,22 +48,24 @@ end -- / changes to this HUD. -- @realm client function HUD:LoadData() - BaseClass.LoadData(self) + BaseClass.LoadData(self) - local elems = self:GetElements() - local scale = appearance.GetGlobalScale() + local elems = self:GetElements() + local scale = appearance.GetGlobalScale() - for i = 1, #elems do - local elemName = elems[i] + for i = 1, #elems do + local elemName = elems[i] - local elem = hudelements.GetStored(elemName) - if not elem then continue end + local elem = hudelements.GetStored(elemName) + if not elem then + continue + end - local min_size = elem:GetDefaults().minsize + local min_size = elem:GetDefaults().minsize - elem:SetMinSize(min_size.w * scale, min_size.h * scale) - elem:PerformLayout() - end + elem:SetMinSize(min_size.w * scale, min_size.h * scale) + elem:PerformLayout() + end end --- @@ -75,27 +73,29 @@ end -- @param number scale -- @realm client function HUD:ApplyScale(scale) - local elems = self:GetElements() + local elems = self:GetElements() - for i = 1, #elems do - local elemName = elems[i] + for i = 1, #elems do + local elemName = elems[i] - local elem = hudelements.GetStored(elemName) - if not elem then continue end + local elem = hudelements.GetStored(elemName) + if not elem then + continue + end - local size = elem:GetSize() - local min_size = elem:GetMinSize() + local size = elem:GetSize() + local min_size = elem:GetMinSize() - elem:SetMinSize(min_size.w * scale, min_size.h * scale) - elem:SetSize(size.w * scale, size.h * scale) - elem:PerformLayout() + elem:SetMinSize(min_size.w * scale, min_size.h * scale) + elem:SetSize(size.w * scale, size.h * scale) + elem:PerformLayout() - --reset position to new calculated default position - local defaultPos = elem:GetDefaults().basepos + --reset position to new calculated default position + local defaultPos = elem:GetDefaults().basepos - elem:SetBasePos(defaultPos.x, defaultPos.y) - elem:PerformLayout() - end + elem:SetBasePos(defaultPos.x, defaultPos.y) + elem:PerformLayout() + end end --- @@ -104,9 +104,9 @@ end -- on non-child elements. -- @realm client function HUD:Reset() - self.basecolor = table.Copy(self.defaultcolor) + self.basecolor = table.Copy(self.defaultcolor) - BaseClass.Reset(self) + BaseClass.Reset(self) - self:ApplyScale(appearance.GetGlobalScale()) + self:ApplyScale(appearance.GetGlobalScale()) end diff --git a/gamemodes/terrortown/gamemode/shared/huds/old_ttt/cl_init.lua b/gamemodes/terrortown/gamemode/shared/huds/old_ttt/cl_init.lua index 879c6b6b7..a63e14f09 100644 --- a/gamemodes/terrortown/gamemode/shared/huds/old_ttt/cl_init.lua +++ b/gamemodes/terrortown/gamemode/shared/huds/old_ttt/cl_init.lua @@ -6,10 +6,10 @@ local surface = surface -- Fonts -surface.CreateFont("TraitorState", {font = "Trebuchet24", size = 28, weight = 1000}) -surface.CreateFont("TimeLeft", {font = "Trebuchet24", size = 24, weight = 800}) -surface.CreateFont("HealthAmmo", {font = "Trebuchet24", size = 24, weight = 750}) -surface.CreateFont("ItemInfo", {font = "Trebuchet24", size = 14, weight = 700}) +surface.CreateFont("TraitorState", { font = "Tahoma", size = 28, weight = 1000, extended = true }) +surface.CreateFont("TimeLeft", { font = "Tahoma", size = 24, weight = 800, extended = true }) +surface.CreateFont("HealthAmmo", { font = "Tahoma", size = 24, weight = 750, extended = true }) +surface.CreateFont("ItemInfo", { font = "Tahoma", size = 14, weight = 700, extended = true }) local base = "hud_base" @@ -23,28 +23,24 @@ HUD.previewImage = Material("vgui/ttt/huds/old_ttt/preview.png") -- Loads this HUD and connects with special @{HUDELEMENT} -- @realm client function HUD:Initialize() - self:ForceElement("old_ttt_info") - self:ForceElement("old_ttt_punchometer") - self:ForceElement("old_ttt_sidebar") - self:ForceElement("old_ttt_mstack") - self:ForceElement("old_ttt_wswitch") - self:ForceElement("old_ttt_target") - self:ForceElement("old_ttt_pickup") - self:ForceElement("old_ttt_revival") - - BaseClass.Initialize(self) + self:ForceElement("old_ttt_info") + self:ForceElement("old_ttt_punchometer") + self:ForceElement("old_ttt_sidebar") + self:ForceElement("old_ttt_mstack") + self:ForceElement("old_ttt_wswitch") + self:ForceElement("old_ttt_target") + self:ForceElement("old_ttt_pickup") + self:ForceElement("old_ttt_revival") + + BaseClass.Initialize(self) end --- -- this is doing nothing, to prevent the hud from saving data to the database -- @realm client -function HUD:SaveData() - -end +function HUD:SaveData() end --- -- this is doing nothing, to prevent the hud from loading data from the database -- @realm client -function HUD:LoadData() - -end +function HUD:LoadData() end diff --git a/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_drawing_functions.lua b/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_drawing_functions.lua index 86feb1384..88b6da03a 100644 --- a/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_drawing_functions.lua +++ b/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_drawing_functions.lua @@ -15,8 +15,8 @@ local shadow_border = surface.GetTextureID("vgui/ttt/dynamic/hud_components/shad -- @param Color c color -- @realm client function DrawHUDElementBg(x, y, w, h, c) - surface.SetDrawColor(clr(c)) - surface.DrawRect(x, y, w, h) + surface.SetDrawColor(clr(c)) + surface.DrawRect(x, y, w, h) end --- @@ -27,19 +27,91 @@ end -- @param number a alpha -- @realm client function DrawHUDElementLines(x, y, w, h, a) - local corner_size = 7 - local shadow_size = 4 - local edge_size = 3 + local corner_size = 7 + local shadow_size = 4 + local edge_size = 3 - surface.SetDrawColor(255, 255, 255, a) - surface.SetTexture(shadow_border) + surface.SetDrawColor(255, 255, 255, a) + surface.SetTexture(shadow_border) - surface.DrawTexturedRectUV(x - shadow_size, y + h - edge_size, corner_size, corner_size, 3.5 / 63, 52.5 / 63, 10.5 / 63, 59.5 / 63) - surface.DrawTexturedRectUV(x + w - edge_size, y + h - edge_size, corner_size, corner_size, 52.5 / 63, 52.5 / 63, 59.5 / 63, 59.5 / 63) - surface.DrawTexturedRectUV(x - shadow_size, y - shadow_size, corner_size, corner_size, 3.5 / 63, 3.5 / 63, 10.5 / 63, 10.5 / 63) - surface.DrawTexturedRectUV(x + w - edge_size, y - shadow_size, corner_size, corner_size, 52.5 / 63, 3.5 / 63, 59.5 / 63, 10.4 / 63) - surface.DrawTexturedRectUV(x + edge_size, y + h - edge_size, w - 2 * edge_size, corner_size, 31.5 / 63, 52.5 / 63, 32.5 / 63, 59.5 / 63) - surface.DrawTexturedRectUV(x - shadow_size, y + edge_size, corner_size, h - 2 * edge_size, 3.5 / 63, 31.5 / 63, 10.5 / 63, 32.5 / 63) - surface.DrawTexturedRectUV(x + w - edge_size, y + edge_size, corner_size, h - 2 * edge_size, 52.5 / 63, 31.5 / 63, 59.5 / 63, 32.5 / 63) - surface.DrawTexturedRectUV(x + edge_size, y - shadow_size, w - 2 * edge_size, corner_size, 31.5 / 63, 3.5 / 63, 32.5 / 63, 10.5 / 63) + surface.DrawTexturedRectUV( + x - shadow_size, + y + h - edge_size, + corner_size, + corner_size, + 3.5 / 63, + 52.5 / 63, + 10.5 / 63, + 59.5 / 63 + ) + surface.DrawTexturedRectUV( + x + w - edge_size, + y + h - edge_size, + corner_size, + corner_size, + 52.5 / 63, + 52.5 / 63, + 59.5 / 63, + 59.5 / 63 + ) + surface.DrawTexturedRectUV( + x - shadow_size, + y - shadow_size, + corner_size, + corner_size, + 3.5 / 63, + 3.5 / 63, + 10.5 / 63, + 10.5 / 63 + ) + surface.DrawTexturedRectUV( + x + w - edge_size, + y - shadow_size, + corner_size, + corner_size, + 52.5 / 63, + 3.5 / 63, + 59.5 / 63, + 10.4 / 63 + ) + surface.DrawTexturedRectUV( + x + edge_size, + y + h - edge_size, + w - 2 * edge_size, + corner_size, + 31.5 / 63, + 52.5 / 63, + 32.5 / 63, + 59.5 / 63 + ) + surface.DrawTexturedRectUV( + x - shadow_size, + y + edge_size, + corner_size, + h - 2 * edge_size, + 3.5 / 63, + 31.5 / 63, + 10.5 / 63, + 32.5 / 63 + ) + surface.DrawTexturedRectUV( + x + w - edge_size, + y + edge_size, + corner_size, + h - 2 * edge_size, + 52.5 / 63, + 31.5 / 63, + 59.5 / 63, + 32.5 / 63 + ) + surface.DrawTexturedRectUV( + x + edge_size, + y - shadow_size, + w - 2 * edge_size, + corner_size, + 31.5 / 63, + 3.5 / 63, + 32.5 / 63, + 10.5 / 63 + ) end diff --git a/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_init.lua b/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_init.lua index d74009979..f06cc4e6f 100644 --- a/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_init.lua +++ b/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_init.lua @@ -6,17 +6,46 @@ local surface = surface -- Fonts -surface.CreateAdvancedFont("PureSkinMSTACKImageMsg", {font = "Trebuchet24", size = 21, weight = 1000}) -surface.CreateAdvancedFont("PureSkinMSTACKMsg", {font = "Trebuchet18", size = 15, weight = 900}) -surface.CreateAdvancedFont("PureSkinRole", {font = "Trebuchet24", size = 30, weight = 700}) -surface.CreateAdvancedFont("PureSkinBar", {font = "Trebuchet24", size = 21, weight = 1000}) -surface.CreateAdvancedFont("PureSkinWep", {font = "Trebuchet24", size = 21, weight = 1000}) -surface.CreateAdvancedFont("PureSkinWepNum", {font = "Trebuchet24", size = 21, weight = 700}) -surface.CreateAdvancedFont("PureSkinItemInfo", {font = "Trebuchet24", size = 14, weight = 700}) -surface.CreateAdvancedFont("PureSkinTimeLeft", {font = "Trebuchet24", size = 24, weight = 800}) - -surface.CreateAdvancedFont("PureSkinPopupTitle", {font = "Trebuchet24", size = 48, weight = 600}) -surface.CreateAdvancedFont("PureSkinPopupText", {font = "Trebuchet18", size = 18, weight = 600}) +surface.CreateAdvancedFont( + "PureSkinMSTACKImageMsg", + { font = "Tahoma", size = 21, weight = 1000, extended = true } +) +surface.CreateAdvancedFont( + "PureSkinMSTACKMsg", + { font = "Tahoma", size = 15, weight = 900, extended = true } +) +surface.CreateAdvancedFont( + "PureSkinRole", + { font = "Tahoma", size = 30, weight = 700, extended = true } +) +surface.CreateAdvancedFont( + "PureSkinBar", + { font = "Tahoma", size = 21, weight = 1000, extended = true } +) +surface.CreateAdvancedFont( + "PureSkinWep", + { font = "Tahoma", size = 21, weight = 1000, extended = true } +) +surface.CreateAdvancedFont( + "PureSkinWepNum", + { font = "Tahoma", size = 21, weight = 700, extended = true } +) +surface.CreateAdvancedFont( + "PureSkinItemInfo", + { font = "Tahoma", size = 14, weight = 700, extended = true } +) +surface.CreateAdvancedFont( + "PureSkinTimeLeft", + { font = "Tahoma", size = 24, weight = 800, extended = true } +) +surface.CreateAdvancedFont( + "PureSkinPopupTitle", + { font = "Tahoma", size = 48, weight = 600, extended = true } +) +surface.CreateAdvancedFont( + "PureSkinPopupText", + { font = "Tahoma", size = 18, weight = 600, extended = true } +) -- base drawing functions include("cl_drawing_functions.lua") @@ -34,20 +63,20 @@ HUD.defaultcolor = Color(49, 71, 94) -- Loads this HUD and connects with special @{HUDELEMENT} -- @realm client function HUD:Initialize() - self:ForceElement("pure_skin_playerinfo") - self:ForceElement("pure_skin_roundinfo") - self:ForceElement("pure_skin_wswitch") - self:ForceElement("pure_skin_drowning") - self:ForceElement("pure_skin_mstack") - self:ForceElement("pure_skin_sidebar") - self:ForceElement("pure_skin_miniscoreboard") - self:ForceElement("pure_skin_punchometer") - self:ForceElement("pure_skin_target") - self:ForceElement("pure_skin_pickup") - self:ForceElement("pure_skin_revival") - self:ForceElement("pure_skin_teamindicator") - - BaseClass.Initialize(self) + self:ForceElement("pure_skin_playerinfo") + self:ForceElement("pure_skin_roundinfo") + self:ForceElement("pure_skin_wswitch") + self:ForceElement("pure_skin_drowning") + self:ForceElement("pure_skin_mstack") + self:ForceElement("pure_skin_sidebar") + self:ForceElement("pure_skin_miniscoreboard") + self:ForceElement("pure_skin_punchometer") + self:ForceElement("pure_skin_target") + self:ForceElement("pure_skin_pickup") + self:ForceElement("pure_skin_revival") + self:ForceElement("pure_skin_teamindicator") + + BaseClass.Initialize(self) end -- Voice overriding diff --git a/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_popup.lua b/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_popup.lua index aaae01dd4..44507594b 100644 --- a/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_popup.lua +++ b/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_popup.lua @@ -10,6 +10,6 @@ -- @param number h height -- @realm client function HUD.PopupPaint(pnl, w, h) - DrawHUDElementBg(0, 0, w, h, pnl.paintColor) - DrawHUDElementLines(0, 0, w, h) + DrawHUDElementBg(0, 0, w, h, pnl.paintColor) + DrawHUDElementLines(0, 0, w, h) end diff --git a/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_voice.lua b/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_voice.lua index 12b157837..0401d3ac2 100644 --- a/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_voice.lua +++ b/gamemodes/terrortown/gamemode/shared/huds/pure_skin/cl_voice.lua @@ -10,8 +10,10 @@ -- @param number h height -- @realm client function HUD.VoicePaint(pnl, w, h) - if not IsValid(pnl.ply) then return end + if not IsValid(pnl.ply) then + return + end - DrawHUDElementBg(0, 0, w, h, pnl.Color) - DrawHUDElementLines(0, 0, w, h) + DrawHUDElementBg(0, 0, w, h, pnl.Color) + DrawHUDElementLines(0, 0, w, h) end diff --git a/gamemodes/terrortown/gamemode/shared/sh_armor.lua b/gamemodes/terrortown/gamemode/shared/sh_armor.lua index 5f12dd8a3..a498144b4 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_armor.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_armor.lua @@ -3,9 +3,9 @@ local plymeta = FindMetaTable("Player") if not plymeta then - Error("FAILED TO FIND PLAYER TABLE") + ErrorNoHaltWithStack("FAILED TO FIND PLAYER TABLE") - return + return end --- @@ -13,7 +13,7 @@ end -- @return[default=0] number armor -- @realm shared function plymeta:GetArmor() - return self.armor or 0 + return self.armor or 0 end --- @@ -21,7 +21,7 @@ end -- @return[default=100] number max armor -- @realm shared function plymeta:GetMaxArmor() - return self.armor_max or 0 + return self.armor_max or 0 end --- @@ -29,5 +29,6 @@ end -- @return boolean is armor reinforced -- @realm shared function plymeta:ArmorIsReinforced() - return GetGlobalBool("ttt_armor_enable_reinforced", false) and self:GetArmor() > GetGlobalInt("ttt_armor_threshold_for_reinforced", 0) + return GetGlobalBool("ttt_armor_enable_reinforced", false) + and self:GetArmor() > GetGlobalInt("ttt_armor_threshold_for_reinforced", 0) end diff --git a/gamemodes/terrortown/gamemode/shared/sh_corpse.lua b/gamemodes/terrortown/gamemode/shared/sh_corpse.lua index 91d8d390e..3d1d81e19 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_corpse.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_corpse.lua @@ -8,9 +8,9 @@ local IsValid = IsValid -- Manual datatable indexing CORPSE.dti = { - BOOL_FOUND = 0, - ENT_PLAYER = 0, - INT_CREDITS = 0 + BOOL_FOUND = 0, + ENT_PLAYER = 0, + INT_CREDITS = 0, } local dti = CORPSE.dti @@ -23,7 +23,7 @@ local dti = CORPSE.dti -- @note networked data abstraction -- @realm shared function CORPSE.GetFound(rag, default) - return rag and rag:GetDTBool(dti.BOOL_FOUND) or default + return rag and rag:GetDTBool(dti.BOOL_FOUND) or default end --- @@ -33,17 +33,17 @@ end -- @return string -- @realm shared function CORPSE.GetPlayerNick(rag, default) - if not IsValid(rag) then - return default - end + if not IsValid(rag) then + return default + end - local ply = rag:GetDTEntity(dti.ENT_PLAYER) + local ply = rag:GetDTEntity(dti.ENT_PLAYER) - if IsValid(ply) then - return ply:Nick() - else - return rag:GetNWString("nick", default) - end + if IsValid(ply) then + return ply:Nick() + else + return rag:GetNWString("nick", default) + end end --- @@ -53,11 +53,11 @@ end -- @return number -- @realm shared function CORPSE.GetCredits(rag, default) - if not IsValid(rag) then - return default - end + if not IsValid(rag) then + return default + end - return rag:GetDTInt(dti.INT_CREDITS) + return rag:GetDTInt(dti.INT_CREDITS) end --- @@ -66,11 +66,11 @@ end -- @return Player owner -- @realm shared function CORPSE.GetPlayer(rag) - if not IsValid(rag) then - return NULL - end + if not IsValid(rag) then + return NULL + end - return rag:GetDTEntity(dti.ENT_PLAYER) + return rag:GetDTEntity(dti.ENT_PLAYER) end --- @@ -80,5 +80,5 @@ end -- @return boolean Returns if the ragdoll is valid -- @realm shared function CORPSE.IsValidBody(rag) - return IsValid(rag) and CORPSE.GetPlayerNick(rag, false) ~= false + return IsValid(rag) and CORPSE.GetPlayerNick(rag, false) ~= false end diff --git a/gamemodes/terrortown/gamemode/shared/sh_cvar_handler.lua b/gamemodes/terrortown/gamemode/shared/sh_cvar_handler.lua index 4b39d1331..5fd74bb7d 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_cvar_handler.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_cvar_handler.lua @@ -1,30 +1,26 @@ --- -- All replicated convars are handled in this file ---- --- @realm shared -CreateConVar("ttt2_confirm_detective_only", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED}) - ---- --- @realm shared -CreateConVar("ttt2_inspect_detective_only", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED}) - ---- -- @realm shared +-- stylua: ignore CreateConVar("ttt2_radar_charge_time", "30", {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED}) --- -- @realm shared +-- stylua: ignore CreateConVar("ttt_bem_allow_change", 1, {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Allow clients to change the look of the Traitor/Detective menu") --- -- @realm shared +-- stylua: ignore CreateConVar("ttt_bem_sv_cols", 4, {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Sets the number of columns in the Traitor/Detective menu's item list (serverside)") --- -- @realm shared +-- stylua: ignore CreateConVar("ttt_bem_sv_rows", 5, {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Sets the number of rows in the Traitor/Detective menu's item list (serverside)") --- -- @realm shared +-- stylua: ignore CreateConVar("ttt_bem_sv_size", 64, {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Sets the item size in the Traitor/Detective menu's item list (serverside)") diff --git a/gamemodes/terrortown/gamemode/shared/sh_decal.lua b/gamemodes/terrortown/gamemode/shared/sh_decal.lua index 3e0cb457d..1e041aec9 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_decal.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_decal.lua @@ -11,182 +11,190 @@ -- decal is removed. This might be improved in the future. if CLIENT then - -- Cache original decal function, but make sure that no infinite recursion (stack overflow) - local utilDecal = util.OverwriteFunction("util.Decal") - - -- table of all added decals - local decals = {} - - --- - -- @module util - - --- - -- Adds a new decal with an unique id that can be removed with this id - -- @param number id The unique id of this specific decal - -- @param string name The name of this decal type to be rendered - -- @param Vector startpos The start of the trace - -- @param Vector endpos The end of the trace - -- @param[opt] Entity filter If set, the decal will not be able to be placed on given entity. Can also be a table of entities - -- @return string The unique id of the decal - -- @realm client - function util.DecalRemovable(id, name, startpos, endpos, filter) - -- make sure each name is unique - if decals[id] then - return util.DecalRemovable(id .. '_', name, startpos, endpos, filter) - end - - -- cache added decal - decals[id] = { - name = name, - startpos = startpos, - endpos = endpos, - filter = filter - } - - -- run vanilla decal function - utilDecal(name, startpos, endpos, filter) - end - - --- - -- Removed one specific decal by its id - -- @param string id The unique id of the decal that should be removed - -- @realm client - function util.RemoveDecal(id) - decals[id] = nil - - RunConsoleCommand("r_cleardecals") - - -- strangely enough, running the console command seems to be asynchronus - -- therefore the decals should be readded after a short amount of time - timer.Simple(0.1, function() - for did, decal in pairs(decals) do - utilDecal(decal.name, decal.startpos, decal.endpos, decal.filter) - end - end) - end - - --- - -- Clears all existing decals on the map - -- @realm client - function util.ClearDecals() - decals = {} - - RunConsoleCommand("r_cleardecals") - end - - -- mirror the function calls from the server to the client - net.Receive("TTT2RegDecal", function() - game.AddDecal(net.ReadString(), net.ReadTable()) - end) - - net.Receive("TTT2AddDecal", function() - util.DecalRemovable(net.ReadString(), net.ReadString(), net.ReadVector(), net.ReadVector(), net.ReadTable()) - end) - - net.Receive("TTT2RemoveDecal", function() - util.RemoveDecal(net.ReadString()) - end) - - net.Receive("TTT2ClearDecals", function() - util.ClearDecals() - end) + -- Cache original decal function, but make sure that no infinite recursion (stack overflow) + local utilDecal = util.OverwriteFunction("util.Decal") + + -- table of all added decals + local decals = {} + + --- + -- @module util + + --- + -- Adds a new decal with an unique id that can be removed with this id + -- @param number id The unique id of this specific decal + -- @param string name The name of this decal type to be rendered + -- @param Vector startpos The start of the trace + -- @param Vector endpos The end of the trace + -- @param[opt] Entity filter If set, the decal will not be able to be placed on given entity. Can also be a table of entities + -- @return string The unique id of the decal + -- @realm client + function util.DecalRemovable(id, name, startpos, endpos, filter) + -- make sure each name is unique + if decals[id] then + return util.DecalRemovable(id .. "_", name, startpos, endpos, filter) + end + + -- cache added decal + decals[id] = { + name = name, + startpos = startpos, + endpos = endpos, + filter = filter, + } + + -- run vanilla decal function + utilDecal(name, startpos, endpos, filter) + end + + --- + -- Removed one specific decal by its id + -- @param string id The unique id of the decal that should be removed + -- @realm client + function util.RemoveDecal(id) + decals[id] = nil + + RunConsoleCommand("r_cleardecals") + + -- strangely enough, running the console command seems to be asynchronus + -- therefore the decals should be readded after a short amount of time + timer.Simple(0.1, function() + for did, decal in pairs(decals) do + utilDecal(decal.name, decal.startpos, decal.endpos, decal.filter) + end + end) + end + + --- + -- Clears all existing decals on the map + -- @realm client + function util.ClearDecals() + decals = {} + + RunConsoleCommand("r_cleardecals") + end + + -- mirror the function calls from the server to the client + net.Receive("TTT2RegDecal", function() + game.AddDecal(net.ReadString(), net.ReadTable()) + end) + + net.Receive("TTT2AddDecal", function() + util.DecalRemovable( + net.ReadString(), + net.ReadString(), + net.ReadVector(), + net.ReadVector(), + net.ReadTable() + ) + end) + + net.Receive("TTT2RemoveDecal", function() + util.RemoveDecal(net.ReadString()) + end) + + net.Receive("TTT2ClearDecals", function() + util.ClearDecals() + end) end if SERVER then - util.AddNetworkString("TTT2AddDecal") - util.AddNetworkString("TTT2RemoveDecal") - util.AddNetworkString("TTT2ClearDecals") - util.AddNetworkString("TTT2RegDecal") - - -- cache original game.AddDecal - local gameAddDecal = util.OverwriteFunction("game.AddDecal") - - --- - -- @module game - - --- - -- Registers a new decal. When called on the server, the decal is registered on both client and server. - -- @warning This functions has to be either run on both server and client, or inside a hook that is called - -- after all files are loaded, e.g. @{GM:Initialize} - -- @param string decalName The name of the decal - -- @param string decalName The material to be used for the decal. May also be a list of material names, - -- in which case a random material from that list will be chosen every time the decal is placed. - -- @realm server - function game.AddDecal(decalName, materialName) - materialName = istable(materialName) and materialName or {materialName} - - gameAddDecal(decalName, materialName) - - net.Start("TTT2RegDecal") - net.WriteString(decalName) - net.WriteTable(materialName) - net.Broadcast() - end - - --- - -- @module util - - --- - -- Adds a new decal with an unique id that can be removed with this id - -- @param number id The unique id of this specific decal - -- @param string name The name of this decal type to be rendered - -- @param Vector startpos The start of the trace - -- @param Vector endpos The end of the trace - -- @param[opt] Entity filter If set, the decal will not be able to be placed on given entity. Warning: Must be a table on the server - -- @param[opt] Entity playerlist If set, it defines which player will see the decal; visible to all players if not set - -- @realm server - function util.DecalRemovable(id, name, startpos, endpos, filter, playerlist) - if isfunction(filter) then - filter = nil - - print("Warning: Do not set the filter to a function if used on a server.") - end - - filter = istable(filter) and filter or {filter} - - net.Start("TTT2AddDecal") - net.WriteString(id) - net.WriteString(name) - net.WriteVector(startpos) - net.WriteVector(endpos) - net.WriteTable(filter) - - if playerlist then - net.Send(playerlist) - else - net.Broadcast() - end - end - - --- - -- Removed one specific decal by its id - -- @param string id The unique id of the decal that should be removed - -- @param[opt] Entity playerlist If set, it defines which player will see the decal removal; visible to all players if not set - -- @realm server - function util.RemoveDecal(id, playerlist) - net.Start("TTT2RemoveDecal") - net.WriteString(id) - - if playerlist then - net.Send(playerlist) - else - net.Broadcast() - end - end - - --- - -- Clears all existing decals on the map - -- @param[opt] Entity playerlist If set, it defines which player will see the decal removal; visible to all players if not set - -- @realm server - function util.ClearDecals(playerlist) - net.Start("TTT2ClearDecals") - - if playerlist then - net.Send(playerlist) - else - net.Broadcast() - end - end + util.AddNetworkString("TTT2AddDecal") + util.AddNetworkString("TTT2RemoveDecal") + util.AddNetworkString("TTT2ClearDecals") + util.AddNetworkString("TTT2RegDecal") + + -- cache original game.AddDecal + local gameAddDecal = util.OverwriteFunction("game.AddDecal") + + --- + -- @module game + + --- + -- Registers a new decal. When called on the server, the decal is registered on both client and server. + -- @warning This functions has to be either run on both server and client, or inside a hook that is called + -- after all files are loaded, e.g. @{GM:Initialize} + -- @param string decalName The name of the decal + -- @param string materialName The material to be used for the decal. May also be a list of material names, + -- in which case a random material from that list will be chosen every time the decal is placed. + -- @realm server + function game.AddDecal(decalName, materialName) + materialName = istable(materialName) and materialName or { materialName } + + gameAddDecal(decalName, materialName) + + net.Start("TTT2RegDecal") + net.WriteString(decalName) + net.WriteTable(materialName) + net.Broadcast() + end + + --- + -- @module util + + --- + -- Adds a new decal with an unique id that can be removed with this id + -- @param number id The unique id of this specific decal + -- @param string name The name of this decal type to be rendered + -- @param Vector startpos The start of the trace + -- @param Vector endpos The end of the trace + -- @param[opt] Entity filter If set, the decal will not be able to be placed on given entity. Warning: Must be a table on the server + -- @param[opt] Entity playerlist If set, it defines which player will see the decal; visible to all players if not set + -- @realm server + function util.DecalRemovable(id, name, startpos, endpos, filter, playerlist) + if isfunction(filter) then + filter = nil + + ErrorNoHaltWithStack( + "Warning: Do not set the filter to a function if used on a server." + ) + end + + filter = istable(filter) and filter or { filter } + + net.Start("TTT2AddDecal") + net.WriteString(id) + net.WriteString(name) + net.WriteVector(startpos) + net.WriteVector(endpos) + net.WriteTable(filter) + + if playerlist then + net.Send(playerlist) + else + net.Broadcast() + end + end + + --- + -- Removed one specific decal by its id + -- @param string id The unique id of the decal that should be removed + -- @param[opt] Entity playerlist If set, it defines which player will see the decal removal; visible to all players if not set + -- @realm server + function util.RemoveDecal(id, playerlist) + net.Start("TTT2RemoveDecal") + net.WriteString(id) + + if playerlist then + net.Send(playerlist) + else + net.Broadcast() + end + end + + --- + -- Clears all existing decals on the map + -- @param[opt] Entity playerlist If set, it defines which player will see the decal removal; visible to all players if not set + -- @realm server + function util.ClearDecals(playerlist) + net.Start("TTT2ClearDecals") + + if playerlist then + net.Send(playerlist) + else + net.Broadcast() + end + end end --- @@ -199,8 +207,8 @@ end -- @return string The unique id of the decal -- @realm shared function util.Decal(name, startpos, endpos, filter, playerlist) - -- call the removable decal function, unique name is handled on the client - util.DecalRemovable(name, name, startpos, endpos, filter, playerlist) + -- call the removable decal function, unique name is handled on the client + util.DecalRemovable(name, name, startpos, endpos, filter, playerlist) end --- @@ -212,14 +220,14 @@ end -- server and functions here are very slow in general. -- @realm shared function util.PaintDown(start, effname, ignore) - local btr = util.TraceLine({ - start = start, - endpos = start + Vector(0, 0, -256), - filter = ignore, - mask = MASK_SOLID - }) - - util.Decal(effname, btr.HitPos + btr.HitNormal, btr.HitPos - btr.HitNormal, ignore) + local btr = util.TraceLine({ + start = start, + endpos = start + Vector(0, 0, -256), + filter = ignore, + mask = MASK_SOLID, + }) + + util.Decal(effname, btr.HitPos + btr.HitNormal, btr.HitPos - btr.HitNormal, ignore) end --- @@ -233,12 +241,12 @@ end -- server and functions here are very slow in general. -- @realm shared function util.PaintDownRemovable(id, start, effname, ignore) - local btr = util.TraceLine({ - start = start, - endpos = start + Vector(0, 0, -256), - filter = ignore, - mask = MASK_SOLID - }) - - util.DecalRemovable(id, effname, btr.HitPos + btr.HitNormal, btr.HitPos - btr.HitNormal, ignore) + local btr = util.TraceLine({ + start = start, + endpos = start + Vector(0, 0, -256), + filter = ignore, + mask = MASK_SOLID, + }) + + util.DecalRemovable(id, effname, btr.HitPos + btr.HitNormal, btr.HitPos - btr.HitNormal, ignore) end diff --git a/gamemodes/terrortown/gamemode/shared/sh_door.lua b/gamemodes/terrortown/gamemode/shared/sh_door.lua index 4234c0613..0b17788a2 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_door.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_door.lua @@ -8,17 +8,17 @@ local entmeta = assert(FindMetaTable("Entity"), "FAILED TO FIND ENTITY TABLE") -- builds a data string based on a player and the previous data string local function GetDataString(ply, data) - local dataTable = {} + local dataTable = {} - if IsValid(ply) then - dataTable[#dataTable + 1] = "sid=" .. ply:SteamID64() - end + if IsValid(ply) then + dataTable[#dataTable + 1] = "sid=" .. ply:SteamID64() + end - if data and data ~= "" then - dataTable[#dataTable + 1] = data - end + if data and data ~= "" then + dataTable[#dataTable + 1] = data + end - return string.Implode("||", dataTable) + return table.concat(dataTable, "||") end --- @@ -26,14 +26,14 @@ end -- @return boolean Returns true if it is a valid door -- @realm shared function entmeta:IsDoor() - local cls = self:GetClass() - local valid = door.GetValid() + local cls = self:GetClass() + local valid = door.GetValid() - if IsValid(self) and (valid.normal[cls] or valid.special[cls]) then - return true - end + if IsValid(self) and (valid.normal[cls] or valid.special[cls]) then + return true + end - return false + return false end --- @@ -41,9 +41,11 @@ end -- @return boolean The door state; true: locked, false: unlocked, nil: no valid door -- @realm shared function entmeta:IsDoorLocked() - if not self:IsDoor() then return end + if not self:IsDoor() then + return + end - return self:GetNWBool("ttt2_door_locked", false) + return self:GetNWBool("ttt2_door_locked", false) end --- @@ -51,9 +53,11 @@ end -- @return boolean The door state; true: forceclosed, false: not forceclosed, nil: no valid door -- @realm shared function entmeta:IsDoorForceclosed() - if not self:IsDoor() then return end + if not self:IsDoor() then + return + end - return self:GetNWBool("ttt2_door_forceclosed", false) + return self:GetNWBool("ttt2_door_forceclosed", false) end --- @@ -62,9 +66,11 @@ end -- @return boolean If the door can be opened with the use key -- @realm shared function entmeta:UseOpensDoor() - if not self:IsDoor() then return end + if not self:IsDoor() then + return + end - return self:GetNWBool("ttt2_door_player_use", false) + return self:GetNWBool("ttt2_door_player_use", false) end --- @@ -72,9 +78,11 @@ end -- @return boolean If the door can be opened with proximity -- @realm shared function entmeta:TouchOpensDoor() - if not self:IsDoor() then return end + if not self:IsDoor() then + return + end - return self:GetNWBool("ttt2_door_player_touch", false) + return self:GetNWBool("ttt2_door_player_touch", false) end --- @@ -82,9 +90,11 @@ end -- @return boolean If the door can be opened -- @realm shared function entmeta:PlayerCanOpenDoor() - if not self:IsDoor() then return end + if not self:IsDoor() then + return + end - return self:UseOpensDoor() or self:TouchOpensDoor() + return self:UseOpensDoor() or self:TouchOpensDoor() end --- @@ -92,9 +102,11 @@ end -- @return boolean If the door closes automatically -- @realm shared function entmeta:DoorAutoCloses() - if not self:IsDoor() then return end + if not self:IsDoor() then + return + end - return self:GetNWBool("ttt2_door_auto_close", false) + return self:GetNWBool("ttt2_door_auto_close", false) end --- @@ -102,9 +114,11 @@ end -- @return boolean If a door is destructible -- @realm shared function entmeta:DoorIsDestructible() - if not self:IsDoor() then return end + if not self:IsDoor() then + return + end - return self:GetNWBool("ttt2_door_is_destructable", false) + return self:GetNWBool("ttt2_door_is_destructable", false) end --- @@ -112,9 +126,11 @@ end -- @return boolean The door state; true: open, false: close, nil: no valid door -- @realm shared function entmeta:IsDoorOpen() - if not self:IsDoor() then return end + if not self:IsDoor() then + return + end - return self:GetNWBool("ttt2_door_open", false) + return self:GetNWBool("ttt2_door_open", false) end --- @@ -122,273 +138,311 @@ end -- @return number The synced health -- @realm shared function entmeta:GetFastSyncedHealth() - return math.max(0, self:GetNWInt("fast_sync_health", 100)) + return math.max(0, self:GetNWInt("fast_sync_health", 100)) end if SERVER then - --- - -- @realm server - local cvDoorHealth = CreateConVar("ttt2_doors_health", "100", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - local cvDoorPropHealth = CreateConVar("ttt2_doors_prop_health", "50", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- Locks a door. - -- @param[opt] Player ply The player that will be passed through as the activator - -- @param[opt] string data Optional data that can be passed through - -- @param[default=0] number delay The delay until the event is fired - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @realm server - function entmeta:LockDoor(ply, data, delay, surpressPair) - if not self:IsDoor() then return end - - self:Fire("Lock", GetDataString(ply, data), delay or 0) - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(self.otherPairDoor) then - self.otherPairDoor:LockDoor(ply, data, delay, true) - end - end - - --- - -- Unlocks a door. - -- @param[opt] Player ply The player that will be passed through as the activator - -- @param[opt] string data Optional data that can be passed through - -- @param[default=0] number delay The delay until the event is fired - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @realm server - function entmeta:UnlockDoor(ply, data, delay, surpressPair) - if not self:IsDoor() then return end - - self:Fire("Unlock", GetDataString(ply, data), delay or 0) - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(self.otherPairDoor) then - self.otherPairDoor:UnlockDoor(ply, data, delay, true) - end - end - - --- - -- Opens the door. - -- @param[opt] Player ply The player that will be passed through as the activator - -- @param[opt] string data Optional data that can be passed through - -- @param[default=0] number delay The delay until the event is fired - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @realm server - function entmeta:OpenDoor(ply, data, delay, surpressPair) - if not self:IsDoor() then return end - - self:Fire("Open", GetDataString(ply, data), delay or 0) - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(self.otherPairDoor) then - self.otherPairDoor:OpenDoor(ply, data, delay, true) - end - end - - --- - -- Closes a door. - -- @param[opt] Player ply The player that will be passed through as the activator - -- @param[opt] string data Optional data that can be passed through - -- @param[default=0] number delay The delay until the event is fired - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @realm server - function entmeta:CloseDoor(ply, data, delay, surpressPair) - if not self:IsDoor() then return end - - self:Fire("Close", GetDataString(ply, data), delay or 0) - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(self.otherPairDoor) then - self.otherPairDoor:CloseDoor(ply, data, delay, true) - end - end - - --- - -- Toggles a door between open and closed. - -- @param[opt] Player ply The player that will be passed through as the activator - -- @param[opt] string data Optional data that can be passed through - -- @param[default=0] number delay The delay until the event is fired - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @realm server - function entmeta:ToggleDoor(ply, data, delay, surpressPair) - if not self:IsDoor() then return end - - self:Fire("Toggle", GetDataString(ply, data), delay or 0) - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(self.otherPairDoor) then - self.otherPairDoor:ToggleDoor(ply, data, delay, true) - end - end - - --- - -- Sets the state if a door can be opened on touch. - -- @param boolean state The new state - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @realm server - function entmeta:SetDoorCanTouchOpen(state, surpressPair) - door.SetPlayerCanTouch(self, state) - - self:SetNWBool("ttt2_door_player_touch", PlayerCanTouchDoor(self)) - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(self.otherPairDoor) then - self.otherPairDoor:SetDoorCanTouchOpen(state, true) - end - end - - --- - -- Sets the state if a door can be opened on use. - -- @param boolean state The new state - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @realm server - function entmeta:SetDoorCanUseOpen(state, surpressPair) - door.SetPlayerCanUse(self, state) - - self:SetNWBool("ttt2_door_player_use", PlayerCanUseDoor(self)) - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(self.otherPairDoor) then - self.otherPairDoor:SetDoorCanUseOpen(state, true) - end - end - - --- - -- Sets the state if a door closes automatically. - -- @param boolean state The new state - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @realm server - function entmeta:SetDoorAutoCloses(state, surpressPair) - door.SetAutoClose(self, state) - - self:SetNWBool("ttt2_door_auto_close", DoorAutoCloses(self)) - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(self.otherPairDoor) then - self.otherPairDoor:SetDoorAutoCloses(state, true) - end - end - - --- - -- Sets the state if a door is destructible. - -- @param boolean state The new state - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @realm server - function entmeta:MakeDoorDestructable(state, surpressPair) - if not self:PlayerCanOpenDoor() or not door.IsValidNormal(self:GetClass()) then return end - - self:SetNWBool("ttt2_door_is_destructable", state) - - if self:Health() == 0 then - self:SetHealth(cvDoorHealth:GetInt()) - - self:SetNWInt("fast_sync_health", self:Health()) - end - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(self.otherPairDoor) then - self.otherPairDoor:MakeDoorDestructable(state, true) - end - end - - --- - -- Destroys a door in a safe manner. This means the door will be removed and spawned a - -- prop. Furthermore it makes sure that the door will not leave a unrendered room behind - -- (problems with area portals). If it is a double door, both doors will be destroyed by - -- default. - -- @param Player ply The player that wants to destroy the door - -- @param[default=Vector(0, 0, 0)] Vector pushForce The push force for the door - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @return Entity Returns the entity of the created prop - -- @realm server - function entmeta:SafeDestroyDoor(ply, pushForce, surpressPair) - if self.isDestroyed or not self:PlayerCanOpenDoor() or not door.IsValidNormal(self:GetClass()) then return end - - --- - -- @realm server - if hook.Run("TTT2BlockDoorDestruction", self, ply) then return end - - -- if door is destroyed, spawn a prop in the world - local doorProp = ents.Create("prop_physics") - doorProp:SetCollisionGroup(COLLISION_GROUP_NONE) - doorProp:SetMoveType(MOVETYPE_VPHYSICS) - doorProp:SetSolid(SOLID_BBOX) - doorProp:SetPos(self:GetPos() + Vector(0, 0, 2)) - doorProp:SetAngles(self:GetAngles()) - doorProp:SetModel(self:GetModel()) - doorProp:SetSkin(self:GetSkin()) - - door.HandleDestruction(self) - - -- disable the door move sound for the destruction - self:SetKeyValue("soundmoveoverride", "") - - -- before the entity is killed, we have to trigger a door opening - self:OpenDoor() - - -- set flag that this door is destroyed to prevent multiple prop spawns in case - -- this function is called multiple times for the same door in the same tick - self.isDestroyed = true - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(self.otherPairDoor) then - self.otherPairDoor:SafeDestroyDoor(ply, pushForce, true) - end - - timer.Simple(0, function() - if not IsValid(self) or not IsValid(doorProp) then return end - - -- we have to kill the entity here instead of removing it because this way we - -- have no problems with area portals (invisible rooms after door is destroyed) - self:Fire("Kill", "", 0) - - if IsValid(ply) and ply:IsPlayer() then - DamageLog("TTT2Doors: The door with the index " .. self:EntIndex() .. " has been destroyed by " .. ply:Nick() .. ".") - else - DamageLog("TTT2Doors: The door with the index " .. self:EntIndex() .. " has been destroyed.") - end - - doorProp:Spawn() - doorProp:SetHealth(cvDoorPropHealth:GetInt()) - - doorProp.isDoorProp = true - - doorProp:GetPhysicsObject():ApplyForceCenter(pushForce or Vector(0, 0, 0)) - - --- - -- @realm server - hook.Run("TTT2DoorDestroyed", doorProp, ply) - end) - - return doorProp - end - - --- - -- Returns if a door is currently transitioning between beeing opened and closed - -- @return boolean The door state; true: open, false: close, nil: no valid door - -- @realm server - function entmeta:DoorIsTransitioning() - if not self:IsDoor() then return end - - local cls = self:GetClass() - - if door.IsValidNormal(cls) then - -- some doors have an auto-close feature - if self:DoorAutoCloses() and self:GetInternalVariable("m_eDoorState") == 2 then - return true - end - - return self:GetInternalVariable("m_eDoorState") == 1 or self:GetInternalVariable("m_eDoorState") == 3 - elseif door.IsValidSpecial(cls) then - -- some doors have an auto-close feature - if self:DoorAutoCloses() and self:GetInternalVariable("m_toggle_state") == 0 then - return true - end - - return self:GetInternalVariable("m_toggle_state") == 2 or self:GetInternalVariable("m_toggle_state") == 3 - end - end + --- + -- @realm server + -- stylua: ignore + local cvDoorHealth = CreateConVar("ttt2_doors_health", "100", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + --- + -- @realm server + -- stylua: ignore + local cvDoorPropHealth = CreateConVar("ttt2_doors_prop_health", "50", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + --- + -- Locks a door. + -- @param[opt] Player ply The player that will be passed through as the activator + -- @param[opt] string data Optional data that can be passed through + -- @param[default=0] number delay The delay until the event is fired + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @realm server + function entmeta:LockDoor(ply, data, delay, surpressPair) + if not self:IsDoor() then + return + end + + self:Fire("Lock", GetDataString(ply, data), delay or 0) + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(self.otherPairDoor) then + self.otherPairDoor:LockDoor(ply, data, delay, true) + end + end + + --- + -- Unlocks a door. + -- @param[opt] Player ply The player that will be passed through as the activator + -- @param[opt] string data Optional data that can be passed through + -- @param[default=0] number delay The delay until the event is fired + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @realm server + function entmeta:UnlockDoor(ply, data, delay, surpressPair) + if not self:IsDoor() then + return + end + + self:Fire("Unlock", GetDataString(ply, data), delay or 0) + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(self.otherPairDoor) then + self.otherPairDoor:UnlockDoor(ply, data, delay, true) + end + end + + --- + -- Opens the door. + -- @param[opt] Player ply The player that will be passed through as the activator + -- @param[opt] string data Optional data that can be passed through + -- @param[default=0] number delay The delay until the event is fired + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @realm server + function entmeta:OpenDoor(ply, data, delay, surpressPair) + if not self:IsDoor() then + return + end + + self:Fire("Open", GetDataString(ply, data), delay or 0) + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(self.otherPairDoor) then + self.otherPairDoor:OpenDoor(ply, data, delay, true) + end + end + + --- + -- Closes a door. + -- @param[opt] Player ply The player that will be passed through as the activator + -- @param[opt] string data Optional data that can be passed through + -- @param[default=0] number delay The delay until the event is fired + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @realm server + function entmeta:CloseDoor(ply, data, delay, surpressPair) + if not self:IsDoor() then + return + end + + self:Fire("Close", GetDataString(ply, data), delay or 0) + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(self.otherPairDoor) then + self.otherPairDoor:CloseDoor(ply, data, delay, true) + end + end + + --- + -- Toggles a door between open and closed. + -- @param[opt] Player ply The player that will be passed through as the activator + -- @param[opt] string data Optional data that can be passed through + -- @param[default=0] number delay The delay until the event is fired + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @realm server + function entmeta:ToggleDoor(ply, data, delay, surpressPair) + if not self:IsDoor() then + return + end + + self:Fire("Toggle", GetDataString(ply, data), delay or 0) + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(self.otherPairDoor) then + self.otherPairDoor:ToggleDoor(ply, data, delay, true) + end + end + + --- + -- Sets the state if a door can be opened on touch. + -- @param boolean state The new state + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @realm server + function entmeta:SetDoorCanTouchOpen(state, surpressPair) + door.SetPlayerCanTouch(self, state) + + self:SetNWBool("ttt2_door_player_touch", door.PlayerCanTouch(self)) + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(self.otherPairDoor) then + self.otherPairDoor:SetDoorCanTouchOpen(state, true) + end + end + + --- + -- Sets the state if a door can be opened on use. + -- @param boolean state The new state + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @realm server + function entmeta:SetDoorCanUseOpen(state, surpressPair) + door.SetPlayerCanUse(self, state) + + self:SetNWBool("ttt2_door_player_use", door.PlayerCanUse(self)) + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(self.otherPairDoor) then + self.otherPairDoor:SetDoorCanUseOpen(state, true) + end + end + + --- + -- Sets the state if a door closes automatically. + -- @param boolean state The new state + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @realm server + function entmeta:SetDoorAutoCloses(state, surpressPair) + door.SetAutoClose(self, state) + + self:SetNWBool("ttt2_door_auto_close", door.AutoCloses(self)) + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(self.otherPairDoor) then + self.otherPairDoor:SetDoorAutoCloses(state, true) + end + end + + --- + -- Sets the state if a door is destructible. + -- @param boolean state The new state + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @realm server + function entmeta:MakeDoorDestructable(state, surpressPair) + if not self:PlayerCanOpenDoor() or not door.IsValidNormal(self:GetClass()) then + return + end + + self:SetNWBool("ttt2_door_is_destructable", state) + + if self:Health() == 0 then + self:SetHealth(cvDoorHealth:GetInt()) + + self:SetNWInt("fast_sync_health", self:Health()) + end + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(self.otherPairDoor) then + self.otherPairDoor:MakeDoorDestructable(state, true) + end + end + + --- + -- Destroys a door in a safe manner. This means the door will be removed and spawned a + -- prop. Furthermore it makes sure that the door will not leave a unrendered room behind + -- (problems with area portals). If it is a double door, both doors will be destroyed by + -- default. + -- @param Player ply The player that wants to destroy the door + -- @param[default=Vector(0, 0, 0)] Vector pushForce The push force for the door + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @return Entity Returns the entity of the created prop + -- @realm server + function entmeta:SafeDestroyDoor(ply, pushForce, surpressPair) + if + self.isDestroyed + or not self:PlayerCanOpenDoor() + or not door.IsValidNormal(self:GetClass()) + then + return + end + + --- + -- @realm server + -- stylua: ignore + if hook.Run("TTT2BlockDoorDestruction", self, ply) then return end + + -- if door is destroyed, spawn a prop in the world + local doorProp = ents.Create("prop_physics") + doorProp:SetCollisionGroup(COLLISION_GROUP_NONE) + doorProp:SetMoveType(MOVETYPE_VPHYSICS) + doorProp:SetSolid(SOLID_BBOX) + doorProp:SetPos(self:GetPos() + Vector(0, 0, 2)) + doorProp:SetAngles(self:GetAngles()) + doorProp:SetModel(self:GetModel()) + doorProp:SetSkin(self:GetSkin()) + + door.HandleDestruction(self) + + -- disable the door move sound for the destruction + self:SetKeyValue("soundmoveoverride", "") + + -- before the entity is killed, we have to trigger a door opening + self:OpenDoor() + + -- set flag that this door is destroyed to prevent multiple prop spawns in case + -- this function is called multiple times for the same door in the same tick + self.isDestroyed = true + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(self.otherPairDoor) then + self.otherPairDoor:SafeDestroyDoor(ply, pushForce, true) + end + + timer.Simple(0, function() + if not IsValid(self) or not IsValid(doorProp) then + return + end + + -- we have to kill the entity here instead of removing it because this way we + -- have no problems with area portals (invisible rooms after door is destroyed) + self:Fire("Kill", "", 0) + + if IsValid(ply) and ply:IsPlayer() then + DamageLog( + "TTT2Doors: The door with the index " + .. self:EntIndex() + .. " has been destroyed by " + .. ply:Nick() + .. "." + ) + else + DamageLog( + "TTT2Doors: The door with the index " + .. self:EntIndex() + .. " has been destroyed." + ) + end + + doorProp:Spawn() + doorProp:SetHealth(cvDoorPropHealth:GetInt()) + + doorProp.isDoorProp = true + + doorProp:GetPhysicsObject():ApplyForceCenter(pushForce or Vector(0, 0, 0)) + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2DoorDestroyed", doorProp, ply) + end) + + return doorProp + end + + --- + -- Returns if a door is currently transitioning between beeing opened and closed + -- @return boolean The door state; true: open, false: close, nil: no valid door + -- @realm server + function entmeta:DoorIsTransitioning() + if not self:IsDoor() then + return + end + + local cls = self:GetClass() + + if door.IsValidNormal(cls) then + -- some doors have an auto-close feature + if self:DoorAutoCloses() and self:GetInternalVariable("m_eDoorState") == 2 then + return true + end + + return self:GetInternalVariable("m_eDoorState") == 1 + or self:GetInternalVariable("m_eDoorState") == 3 + elseif door.IsValidSpecial(cls) then + -- some doors have an auto-close feature + if self:DoorAutoCloses() and self:GetInternalVariable("m_toggle_state") == 0 then + return true + end + + return self:GetInternalVariable("m_toggle_state") == 2 + or self:GetInternalVariable("m_toggle_state") == 3 + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/sh_equip_items.lua b/gamemodes/terrortown/gamemode/shared/sh_equip_items.lua index 82d1cb4c2..e68ec40ee 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_equip_items.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_equip_items.lua @@ -1,6 +1,6 @@ --- -- This table is used by the client to show items in the equipment menu, and by --- the server to check if a certain role is allowed to buy a certain item.local math = math +-- the server to check if a certain role is allowed to buy a certain item. -- @section Equipment local table = table @@ -9,6 +9,7 @@ local player = player local pairs = pairs local util = util local hook = hook +local math = math -- Details you shouldn't need: -- The number should increase by a factor of two for every item (ie. ids @@ -31,29 +32,43 @@ RANDOMSAVEDSHOPS = RANDOMSAVEDSHOPS or {} -- saved random shops -- JUST used to convert old items to new ones local itemMt = { - __newindex = function(tbl, key, val) - print("[TTT2][INFO] You are using an add-on that is trying to add a new ITEM ('" .. key .. "' = '" .. val .. "') in the wrong way. This will not be available in the shop and lead to errors!") - end + __newindex = function(tbl, key, val) + ErrorNoHaltWithStack( + "[TTT2][INFO] You are using an add-on that is trying to add a new ITEM ('" + .. key + .. "' = '" + .. val + .. "') in the wrong way. This will not be available in the shop and lead to errors!" + ) + end, } -EquipmentItems = EquipmentItems or setmetatable( - { - [ROLE_TRAITOR] = setmetatable({}, itemMt), - [ROLE_DETECTIVE] = setmetatable({}, itemMt) - }, - { - __index = function(tbl, key) - ErrorNoHalt("\n[TTT2][WARNING] You are using an add-on that is trying to access an unsupported var ('" .. key .. "'). This will lead to errors!\n\n") - end, - __newindex = function(tbl, key, val) - ErrorNoHalt("\n[TTT2][WARNING] You are using an add-on that is trying to add a new role ('" .. key .. "' = '" .. val .. "') to an unsupported var. This will lead to errors!\n\n") - - if istable(val) then - tbl[key] = setmetatable(val, itemMt) - end - end - } -) +EquipmentItems = EquipmentItems + or setmetatable({ + [ROLE_TRAITOR] = setmetatable({}, itemMt), + [ROLE_DETECTIVE] = setmetatable({}, itemMt), + }, { + __index = function(tbl, key) + ErrorNoHaltWithStack( + "\n[TTT2][WARNING] You are using an add-on that is trying to access an unsupported var ('" + .. key + .. "'). This will lead to errors!\n\n" + ) + end, + __newindex = function(tbl, key, val) + ErrorNoHaltWithStack( + "\n[TTT2][WARNING] You are using an add-on that is trying to add a new role ('" + .. key + .. "' = '" + .. val + .. "') to an unsupported var. This will lead to errors!\n\n" + ) + + if istable(val) then + tbl[key] = setmetatable(val, itemMt) + end + end, + }) --- -- Adds all needed parameters for TTT2 @@ -62,37 +77,37 @@ EquipmentItems = EquipmentItems or setmetatable( -- @internal -- @realm shared function AddEquipmentKeyValues(eq, name) - local data = eq.EquipMenuData or {} - - local tbl = { - id = name, - name = name, - PrintName = data.name or data.PrintName or eq.PrintName or name, - limited = eq.limited or eq.LimitedStock, - kind = eq.Kind or WEAPON_NONE, - slot = (eq.Slot or 0) + 1, - material = eq.Icon or eq.material or "vgui/ttt/icon_id", - -- the below should be specified in EquipMenuData, in which case - -- these values are overwritten - type = "Type not specified", - model = "models/weapons/w_bugbait.mdl", - desc = "No description specified.", - inited = true - } - - table.Merge(tbl, data) - - for key in pairs(ShopEditor.savingKeys) do - if not tbl[key] then - tbl[key] = eq[key] - end - end - - for k, v in pairs(tbl) do - eq[k] = v - end - - return eq + local data = eq.EquipMenuData or {} + + local tbl = { + id = name, + name = name, + PrintName = data.name or data.PrintName or eq.PrintName or name, + limited = eq.limited or eq.LimitedStock, + kind = eq.Kind or WEAPON_NONE, + slot = (eq.Slot or 0) + 1, + material = eq.Icon or eq.material or "vgui/ttt/icon_id", + -- the below should be specified in EquipMenuData, in which case + -- these values are overwritten + type = "Type not specified", + model = "models/weapons/w_bugbait.mdl", + desc = "No description specified.", + inited = true, + } + + table.Merge(tbl, data) + + for key in pairs(ShopEditor.savingKeys) do + if not tbl[key] then + tbl[key] = eq[key] + end + end + + for k, v in pairs(tbl) do + eq[k] = v + end + + return eq end --- @@ -103,30 +118,30 @@ end -- @internal -- @realm shared function GetEquipmentBase(eq) - if not eq or eq.inited then - return eq - end + if not eq or eq.inited then + return eq + end - local name = WEPS.GetClass(eq) - if not name then - return - end + local name = WEPS.GetClass(eq) + if not name then + return + end - AddEquipmentKeyValues(eq, name) + AddEquipmentKeyValues(eq, name) - return eq + return eq end --- -- Creates an equipment --- @param eq +-- @param table eq -- @return table equipment table -- @internal -- @realm shared function CreateEquipment(eq) - if not eq.Doublicated then - return GetEquipmentBase(eq) - end + if not eq.Duplicated then + return GetEquipmentBase(eq) + end end --- @@ -135,20 +150,26 @@ end -- @param ROLE roleData -- @realm shared function AddWeaponIntoFallbackTable(wepClass, roleData) - if not roleData.fallbackTable then return end - - local wep = weapons.GetStored(wepClass) - if not wep then return end - - wep.CanBuy = wep.CanBuy or {} - wep.CanBuy[roleData.index] = roleData.index - - local eq = CreateEquipment(wep) - if not eq then return end - - if not table.HasValue(roleData.fallbackTable, eq) then - roleData.fallbackTable[#roleData.fallbackTable + 1] = eq - end + if not roleData.fallbackTable then + return + end + + local wep = weapons.GetStored(wepClass) + if not wep then + return + end + + wep.CanBuy = wep.CanBuy or {} + wep.CanBuy[roleData.index] = roleData.index + + local eq = CreateEquipment(wep) + if not eq then + return + end + + if not table.HasValue(roleData.fallbackTable, eq) then + roleData.fallbackTable[#roleData.fallbackTable + 1] = eq + end end --- @@ -161,34 +182,34 @@ end -- @todo improve description -- @realm shared function GetShopFallback(subrole, tbl) - local rd = roles.GetByIndex(subrole) - local shopFallback = GetGlobalString("ttt_" .. rd.abbr .. "_shop_fallback") + local rd = roles.GetByIndex(subrole) + local shopFallback = GetGlobalString("ttt_" .. rd.abbr .. "_shop_fallback") - local fb = roles.GetStored(shopFallback) - fb = fb and fb.index or ROLE_NONE + local fb = roles.GetStored(shopFallback) + fb = fb and fb.index or ROLE_NONE - if not fb or shopFallback == SHOP_UNSET or shopFallback == SHOP_DISABLED then - return subrole, fb - end + if not fb or shopFallback == SHOP_UNSET or shopFallback == SHOP_DISABLED then + return subrole, fb + end - if not tbl then - tbl = {subrole, fb} + if not tbl then + tbl = { subrole, fb } - fb, subrole = GetShopFallback(fb, tbl) - elseif not table.HasValue(tbl, fb) then - tbl[#tbl + 1] = fb + fb, subrole = GetShopFallback(fb, tbl) + elseif not table.HasValue(tbl, fb) then + tbl[#tbl + 1] = fb - local nfb + local nfb - nfb, subrole = GetShopFallback(fb, tbl) + nfb, subrole = GetShopFallback(fb, tbl) - if nfb ~= fb then - subrole = fb - fb = nfb - end - end + if nfb ~= fb then + subrole = fb + fb = nfb + end + end - return fb, subrole -- return deepest value and the value before the deepest value + return fb, subrole -- return deepest value and the value before the deepest value end --- @@ -199,481 +220,528 @@ end -- @todo improve description -- @realm shared function GetShopFallbackTable(subrole) - local rd = roles.GetByIndex(subrole) + local rd = roles.GetByIndex(subrole) - local shopFallback = GetGlobalString("ttt_" .. rd.abbr .. "_shop_fallback") - if shopFallback == SHOP_DISABLED then return end + local shopFallback = GetGlobalString("ttt_" .. rd.abbr .. "_shop_fallback") + if shopFallback == SHOP_DISABLED then + return + end - local fallback + local fallback - subrole, fallback = GetShopFallback(subrole) + subrole, fallback = GetShopFallback(subrole) - if fallback == ROLE_NONE then -- fallback is SHOP_UNSET - rd = roles.GetByIndex(subrole) + if fallback == ROLE_NONE then -- fallback is SHOP_UNSET + rd = roles.GetByIndex(subrole) - if rd.fallbackTable then - return rd.fallbackTable - end - end + if rd.fallbackTable then + return rd.fallbackTable + end + end end if CLIENT then - - --- - -- Returns a list of equipment that is available for a @{ROLE} - -- @param Player ply - -- @param number subrole id of @{ROLE} - -- @param[opt] boolean noModification whether the modified equipment (e.g. randomshop) table should be returned or the original one - -- @internal - -- @todo improve description - -- @realm client - function GetEquipmentForRole(ply, subrole, noModification) - local fallbackTable = GetShopFallbackTable(subrole) - - if not noModification then - fallbackTable = GetModifiedEquipment(ply, fallbackTable) - end - - if fallbackTable then - return fallbackTable - end - - local fallback = GetShopFallback(subrole) - - Equipment = Equipment or {} - - -- need to build equipment cache? - if not Equipment[fallback] then - local tbl = {} - local v - - -- find buyable items to load info from - local itms = items.GetList() - - for i = 1, #itms do - v = itms[i] - - if v and not v.Doublicated and v.CanBuy and v.CanBuy[fallback] then - local base = GetEquipmentBase(v) - if base then - tbl[#tbl + 1] = base - end - end - end - - -- find buyable weapons to load info from - local weps = weapons.GetList() - - for i = 1, #weps do - v = weps[i] - - if v and not v.Doublicated and v.CanBuy and v.CanBuy[fallback] then - local base = GetEquipmentBase(v) - if base then - tbl[#tbl + 1] = base - end - end - end - - -- mark custom items - for k = 1, #tbl do - v = tbl[k] - - if not v or not v.id then continue end - - v.custom = not table.HasValue(DefaultEquipment[fallback], v.id) - end - - Equipment[fallback] = tbl - end - - return not noModification and GetModifiedEquipment(ply, Equipment[fallback]) or Equipment[fallback] - end + --- + -- Returns a list of equipment that is available for a @{ROLE} + -- @param Player ply + -- @param number subrole id of @{ROLE} + -- @param[opt] boolean noModification whether the modified equipment (e.g. randomshop) table should be returned or the original one + -- @internal + -- @todo improve description + -- @realm client + function GetEquipmentForRole(ply, subrole, noModification) + local fallbackTable = GetShopFallbackTable(subrole) + + if not noModification then + fallbackTable = GetModifiedEquipment(ply, fallbackTable) + end + + if fallbackTable then + return fallbackTable + end + + local fallback = GetShopFallback(subrole) + + Equipment = Equipment or {} + + -- need to build equipment cache? + if not Equipment[fallback] then + local tbl = {} + local v + + -- find buyable items to load info from + local itms = items.GetList() + + for i = 1, #itms do + v = itms[i] + + if v and not v.Duplicated and v.CanBuy and v.CanBuy[fallback] then + local base = GetEquipmentBase(v) + if base then + tbl[#tbl + 1] = base + end + end + end + + -- find buyable weapons to load info from + local weps = weapons.GetList() + + for i = 1, #weps do + v = weps[i] + + if v and not v.Duplicated and v.CanBuy and v.CanBuy[fallback] then + local base = GetEquipmentBase(v) + if base then + tbl[#tbl + 1] = base + end + end + end + + -- mark custom items + for k = 1, #tbl do + v = tbl[k] + + if not v or not v.id then + continue + end + + v.custom = not table.HasValue(DefaultEquipment[fallback], v.id) + end + + Equipment[fallback] = tbl + end + + return not noModification and GetModifiedEquipment(ply, Equipment[fallback]) + or Equipment[fallback] + end end -- Sync Equipment local function EncodeForStream(tbl) - -- may want to filter out data later - -- just serialize for now + -- may want to filter out data later + -- just serialize for now - local result = util.TableToJSON(tbl) - if not result then - ErrorNoHalt("Round report event encoding failed!\n") + local result = util.TableToJSON(tbl) + if not result then + ErrorNoHaltWithStack("Round report event encoding failed!\n") - return false - else - return result - end + return false + else + return result + end end -- Search if an item is in the equipment table of a given subrole, and return it if -- it exists, else return nil. if SERVER then - --- - -- @realm server - local random_shops = CreateConVar("ttt2_random_shops", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Set to 0 to disable") - - --- - -- @realm server - local random_shop_items = CreateConVar("ttt2_random_shop_items", "10", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Set the number of shown items in a random shop.") - - --- - -- @realm server - local random_team_shops = CreateConVar("ttt2_random_team_shops", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Set to 0 to disable") - - --- - -- @realm server - local random_shop_reroll = CreateConVar("ttt2_random_shop_reroll", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Set to 0 to disable") - - --- - -- @realm server - local random_shop_reroll_cost = CreateConVar("ttt2_random_shop_reroll_cost", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Credit cost per reroll") - - --- - -- @realm server - local random_shop_reroll_per_buy = CreateConVar("ttt2_random_shop_reroll_per_buy", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Should the random shop reroll after every purchase") - - util.AddNetworkString("TTT2SyncRandomShops") - - local function SyncRandomShops(plys) - if not RANDOMSHOP then return end - - for ply, tbl in pairs(RANDOMSHOP) do - if plys and not table.HasValue(plys, ply) then continue end - - local tmp = {} - - for i = 1, #tbl do - tmp[i] = tbl[i].id - end - - if #tmp <= 0 then continue end - - local s = EncodeForStream(tmp) - if not s then continue end + --- + -- @realm server + -- stylua: ignore + local random_shops = CreateConVar("ttt2_random_shops", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Set to 0 to disable") - -- divide into happy lil bits. - -- this was necessary with user messages, now it's - -- a just-in-case thing if a round somehow manages to be > 64K - local cut = {} - local max = 64000 + --- + -- @realm server + -- stylua: ignore + local random_shop_items = CreateConVar("ttt2_random_shop_items", "10", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Set the number of shown items in a random shop.") - while #s ~= 0 do - local bit = string.sub(s, 1, max - 1) + --- + -- @realm server + -- stylua: ignore + local random_team_shops = CreateConVar("ttt2_random_team_shops", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Set to 0 to disable") - cut[#cut + 1] = bit + --- + -- @realm server + -- stylua: ignore + local random_shop_reroll = CreateConVar("ttt2_random_shop_reroll", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Set to 0 to disable") - s = string.sub(s, max, - 1) - end + --- + -- @realm server + -- stylua: ignore + local random_shop_reroll_cost = CreateConVar("ttt2_random_shop_reroll_cost", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Credit cost per reroll") - local parts = #cut + --- + -- @realm server + -- stylua: ignore + local random_shop_reroll_per_buy = CreateConVar("ttt2_random_shop_reroll_per_buy", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE}, "Should the random shop reroll after every purchase") - for k = 1, parts do - net.Start("TTT2SyncRandomShops") - net.WriteBit(k ~= parts) -- continuation bit, 1 if there's more coming - net.WriteString(cut[k]) - net.Send(ply) - end - end - end + util.AddNetworkString("TTT2SyncRandomShops") - local function GetValidShopFallbackTable(fallback) - local fallbackTable = GetShopFallbackTable(fallback) - if not fallbackTable then - fallbackTable = {} + local function SyncRandomShops(plys) + if not RANDOMSHOP then + return + end - local itms = items.GetList() + for ply, tbl in pairs(RANDOMSHOP) do + if plys and not table.HasValue(plys, ply) then + continue + end - for i = 1, #itms do - local equip = itms[i] + local tmp = {} - if not equip.CanBuy or not equip.CanBuy[fallback] then continue end + for i = 1, #tbl do + tmp[i] = tbl[i].id + end - fallbackTable[#fallbackTable + 1] = equip - end + if #tmp <= 0 then + continue + end - local weps = weapons.GetList() + local s = EncodeForStream(tmp) + if not s then + continue + end - for i = 1, #weps do - local equip = weps[i] + -- divide into happy lil bits. + -- this was necessary with user messages, now it's + -- a just-in-case thing if a round somehow manages to be > 64K + local cut = {} + local max = 64000 - if not equip.CanBuy or not equip.CanBuy[fallback] then continue end + while #s ~= 0 do + local bit = string.sub(s, 1, max - 1) - fallbackTable[#fallbackTable + 1] = equip - end - end + cut[#cut + 1] = bit + + s = string.sub(s, max, -1) + end + + local parts = #cut + + for k = 1, parts do + net.Start("TTT2SyncRandomShops") + net.WriteBit(k ~= parts) -- continuation bit, 1 if there's more coming + net.WriteString(cut[k]) + net.Send(ply) + end + end + end + + local function GetValidShopFallbackTable(fallback) + local fallbackTable = GetShopFallbackTable(fallback) + if not fallbackTable then + fallbackTable = {} + + local itms = items.GetList() + + for i = 1, #itms do + local equip = itms[i] + + if not equip.CanBuy or not equip.CanBuy[fallback] then + continue + end + + fallbackTable[#fallbackTable + 1] = equip + end + + local weps = weapons.GetList() + + for i = 1, #weps do + local equip = weps[i] + + if not equip.CanBuy or not equip.CanBuy[fallback] then + continue + end + + fallbackTable[#fallbackTable + 1] = equip + end + end + + return fallbackTable + end + + local function GetValidTeamShops(fallback, amount, team, fallbackTable) + local teamShops = RANDOMTEAMSHOPS + + if team and not teamShops[fallback] then + local fallbackTblCount = #fallbackTable + + if amount < fallbackTblCount then + teamShops[fallback] = {} - return fallbackTable - end + local tmp2 = {} - local function GetValidTeamShops(fallback, amount, team, fallbackTable) - local teamShops = RANDOMTEAMSHOPS + for i = 1, fallbackTblCount do + local equip = fallbackTable[i] - if team and not teamShops[fallback] then - local fallbackTblCount = #fallbackTable + if equip.notBuyable then + continue + end - if amount < fallbackTblCount then - teamShops[fallback] = {} + if equip.NoRandom then + amount = amount - 1 - local tmp2 = {} + teamShops[fallback][#teamShops[fallback] + 1] = equip + else + tmp2[#tmp2 + 1] = equip + end + end - for i = 1, fallbackTblCount do - local equip = fallbackTable[i] + if amount > 0 then + for i = 1, amount do + local rndm = math.random(#tmp2) - if equip.notBuyable then continue end + teamShops[fallback][#teamShops[fallback] + 1] = tmp2[rndm] - if equip.NoRandom then - amount = amount - 1 + table.remove(tmp2, rndm) + end + end + else + teamShops[fallback] = fallbackTable + end + end - teamShops[fallback][#teamShops[fallback] + 1] = equip - else - tmp2[#tmp2 + 1] = equip - end - end + return teamShops + end - if amount > 0 then - for i = 1, amount do - local rndm = math.random(#tmp2) + --- + -- Update the random shops + -- @param table plys list of @{Player} + -- @param number val + -- @param string team @{ROLE} team + -- @internal + -- @realm server + function UpdateRandomShops(plys, val, team) + if plys then + for i = 1, #plys do + RANDOMSHOP[plys[i]] = {} -- reset ply + end + else + RANDOMSHOP = {} -- reset everyone + RANDOMTEAMSHOPS = {} -- reset team equipment + RANDOMSAVEDSHOPS = {} -- reset saved shops + end + + local tbl = roles.GetShopRoles() - teamShops[fallback][#teamShops[fallback] + 1] = tmp2[rndm] + -- at first, get all available equipment per team + for _, rd in pairs(tbl) do + local fallback = GetShopFallback(rd.index) - table.remove(tmp2, rndm) - end - end - else - teamShops[fallback] = fallbackTable - end - end + if not RANDOMSAVEDSHOPS[fallback] then + local fallbackTable = GetValidShopFallbackTable(fallback) - return teamShops - end + RANDOMSAVEDSHOPS[fallback] = fallbackTable + RANDOMTEAMSHOPS = GetValidTeamShops(fallback, val, team, fallbackTable) + end + end - --- - -- Update the random shops - -- @param table plys list of @{Player} - -- @param number val - -- @param string team @{ROLE} team - -- @internal - -- @realm server - function UpdateRandomShops(plys, val, team) - if plys then - for i = 1, #plys do - RANDOMSHOP[plys[i]] = {} -- reset ply - end - else - RANDOMSHOP = {} -- reset everyone - RANDOMTEAMSHOPS = {} -- reset team equipment - RANDOMSAVEDSHOPS = {} -- reset saved shops - end + local tmpTbl = plys or player.GetAll() - local tbl = roles.GetShopRoles() + local mathrandom = math.random + local tableremove = table.remove - -- at first, get all available equipment per team - for _, rd in pairs(tbl) do - local fallback = GetShopFallback(rd.index) + -- now set the individual random shop + if team then -- the shop is synced with the team + for i = 1, #tmpTbl do + local ply = tmpTbl[i] + local srd = ply:GetSubRoleData() - if not RANDOMSAVEDSHOPS[fallback] then - local fallbackTable = GetValidShopFallbackTable(fallback) + if not srd:IsShoppingRole() then + continue + end - RANDOMSAVEDSHOPS[fallback] = fallbackTable - RANDOMTEAMSHOPS = GetValidTeamShops(fallback, val, team, fallbackTable) - end - end + RANDOMSHOP[ply] = RANDOMTEAMSHOPS[GetShopFallback(srd.index)] + end + else -- every player has his own shop + for i = 1, #tmpTbl do + local ply = tmpTbl[i] + local srd = ply:GetSubRoleData() - local tmpTbl = plys or player.GetAll() + if not srd:IsShoppingRole() then + continue + end - local mathrandom = math.random - local tableremove = table.remove + local amount = val + local tmp2 = {} - -- now set the individual random shop - if team then -- the shop is synced with the team - for i = 1, #tmpTbl do - local ply = tmpTbl[i] - local srd = ply:GetSubRoleData() + RANDOMSHOP[ply] = {} + + local cachedTbl = RANDOMSAVEDSHOPS[GetShopFallback(srd.index)] - if not srd:IsShoppingRole() then continue end + for k = 1, #cachedTbl do + local equip = cachedTbl[k] + + if equip.notBuyable then + continue + end + + if equip.NoRandom then + amount = amount - 1 + + RANDOMSHOP[ply][#RANDOMSHOP[ply] + 1] = equip + else + tmp2[#tmp2 + 1] = equip + end + end + + if amount > 0 then + for k = 1, amount do + local rndm = mathrandom(#tmp2) + + RANDOMSHOP[ply][#RANDOMSHOP[ply] + 1] = tmp2[rndm] + + tableremove(tmp2, rndm) + end + end + end + end + + SyncRandomShops(plys) + end + + --- + -- Reset the random shops for a @{ROLE} + -- @param number role subrole id of a @{ROLE} + -- @param number amount + -- @param string team @{ROLE} team + -- @internal + -- @realm server + function ResetRandomShopsForRole(role, amount, team) + local fallback = GetShopFallback(role) + + RANDOMTEAMSHOPS[fallback] = nil + RANDOMSAVEDSHOPS[fallback] = nil + + local plys_with_fb = {} + local plys = player.GetAll() + + for i = 1, #plys do + local ply = plys[i] - RANDOMSHOP[ply] = RANDOMTEAMSHOPS[GetShopFallback(srd.index)] - end - else -- every player has his own shop - for i = 1, #tmpTbl do - local ply = tmpTbl[i] - local srd = ply:GetSubRoleData() + if GetShopFallback(ply:GetSubRole()) ~= fallback then + continue + end - if not srd:IsShoppingRole() then continue end + plys_with_fb[#plys_with_fb + 1] = ply + end - local amount = val - local tmp2 = {} + UpdateRandomShops(plys_with_fb, amount, team) + end + + cvars.AddChangeCallback("ttt2_random_shops", function(name, old, new) + local tmp = tobool(new) + local amount = GetGlobalInt("ttt2_random_shop_items") + + SetGlobalBool("ttt2_random_shops", tmp) - RANDOMSHOP[ply] = {} + if tmp and amount > 0 then + UpdateRandomShops(nil, amount, GetGlobalBool("ttt2_random_team_shops", true)) + end + end, "ttt2changeshops") + + cvars.AddChangeCallback("ttt2_random_shop_items", function(name, old, new) + local tmp = tonumber(new) + + SetGlobalInt("ttt2_random_shop_items", tmp) + + if GetGlobalBool("ttt2_random_shops") and tmp > 0 then + UpdateRandomShops(nil, tmp, GetGlobalBool("ttt2_random_team_shops", true)) + end + end, "ttt2changeshopitems") - local cachedTbl = RANDOMSAVEDSHOPS[GetShopFallback(srd.index)] + cvars.AddChangeCallback("ttt2_random_team_shops", function(name, old, new) + local tmp = tobool(new) + + SetGlobalBool("ttt2_random_team_shops", tmp) - for k = 1, #cachedTbl do - local equip = cachedTbl[k] + if new ~= old and GetGlobalBool("ttt2_random_shops", true) then + UpdateRandomShops(nil, GetGlobalInt("ttt2_random_shop_items"), tmp) + end + end, "ttt2changeteamshops") - if equip.notBuyable then continue end + cvars.AddChangeCallback("ttt2_random_shop_reroll", function(name, old, new) + SetGlobalBool("ttt2_random_shop_reroll", tobool(new)) + end, "ttt2updatererollglobal") - if equip.NoRandom then - amount = amount - 1 + cvars.AddChangeCallback("ttt2_random_shop_reroll_cost", function(name, old, new) + SetGlobalInt("ttt2_random_shop_reroll_cost", tonumber(new)) + end, "ttt2updatererollcostglobal") + + cvars.AddChangeCallback("ttt2_random_shop_reroll_per_buy", function(name, old, new) + SetGlobalBool("ttt2_random_shop_reroll_per_buy", tobool(new)) + end, "ttt2updatererollperbuyglobal") + + hook.Add("TTTPrepareRound", "TTT2InitRandomShops", function() + if not GetGlobalBool("ttt2_random_shops") then + return + end + + UpdateRandomShops( + nil, + GetGlobalInt("ttt2_random_shop_items"), + GetGlobalBool("ttt2_random_team_shops", true) + ) + end) + + hook.Add("TTT2UpdateSubrole", "TTT2UpdateRandomShop", function(ply) + if not GetGlobalBool("ttt2_random_shops") then + return + end + + UpdateRandomShops( + { ply }, + GetGlobalInt("ttt2_random_shop_items"), + GetGlobalBool("ttt2_random_team_shops", true) + ) + end) + + hook.Add("PlayerInitialSpawn", "TTT2InitRandomShops", function(ply) + local random_shop = random_shops:GetBool() + + SetGlobalBool("ttt2_random_shops", random_shop) + SetGlobalInt("ttt2_random_shop_items", random_shop_items:GetInt()) + SetGlobalBool("ttt2_random_team_shops", random_team_shops:GetBool()) + SetGlobalBool("ttt2_random_shop_reroll", random_shop_reroll:GetBool()) + SetGlobalInt("ttt2_random_shop_reroll_cost", random_shop_reroll_cost:GetInt()) + SetGlobalBool("ttt2_random_shop_reroll_per_buy", random_shop_reroll_per_buy:GetBool()) - RANDOMSHOP[ply][#RANDOMSHOP[ply] + 1] = equip - else - tmp2[#tmp2 + 1] = equip - end - end + if not random_shop then + return + end - if amount > 0 then - for k = 1, amount do - local rndm = mathrandom(#tmp2) - - RANDOMSHOP[ply][#RANDOMSHOP[ply] + 1] = tmp2[rndm] - - tableremove(tmp2, rndm) - end - end - end - end - - SyncRandomShops(plys) - end - - --- - -- Reset the random shops for a @{ROLE} - -- @param number role subrole id of a @{ROLE} - -- @param number amount - -- @param string team @{ROLE} team - -- @internal - -- @realm server - function ResetRandomShopsForRole(role, amount, team) - local fallback = GetShopFallback(role) - - RANDOMTEAMSHOPS[fallback] = nil - RANDOMSAVEDSHOPS[fallback] = nil - - local plys_with_fb = {} - local plys = player.GetAll() - - for i = 1, #plys do - local ply = plys[i] - - if GetShopFallback(ply:GetSubRole()) ~= fallback then continue end - - plys_with_fb[#plys_with_fb + 1] = ply - end - - UpdateRandomShops(plys_with_fb, amount, team) - end - - cvars.AddChangeCallback("ttt2_random_shops", function(name, old, new) - local tmp = tobool(new) - local amount = GetGlobalInt("ttt2_random_shop_items") - - SetGlobalBool("ttt2_random_shops", tmp) - - if tmp and amount > 0 then - UpdateRandomShops(nil, amount, GetGlobalBool("ttt2_random_team_shops", true)) - end - end, "ttt2changeshops") - - cvars.AddChangeCallback("ttt2_random_shop_items", function(name, old, new) - local tmp = tonumber(new) - - SetGlobalInt("ttt2_random_shop_items", tmp) - - if GetGlobalBool("ttt2_random_shops") and tmp > 0 then - UpdateRandomShops(nil, tmp, GetGlobalBool("ttt2_random_team_shops", true)) - end - end, "ttt2changeshopitems") - - cvars.AddChangeCallback("ttt2_random_team_shops", function(name, old, new) - local tmp = tobool(new) - - SetGlobalBool("ttt2_random_team_shops", tmp) - - if new ~= old and GetGlobalBool("ttt2_random_shops", true) then - UpdateRandomShops(nil, GetGlobalInt("ttt2_random_shop_items"), tmp) - end - end, "ttt2changeteamshops") - - cvars.AddChangeCallback("ttt2_random_shop_reroll", function(name, old, new) - SetGlobalBool("ttt2_random_shop_reroll", tobool(new)) - end, "ttt2updatererollglobal") - - cvars.AddChangeCallback("ttt2_random_shop_reroll_cost", function(name, old, new) - SetGlobalInt("ttt2_random_shop_reroll_cost", tonumber(new)) - end, "ttt2updatererollcostglobal") - - cvars.AddChangeCallback("ttt2_random_shop_reroll_per_buy", function(name, old, new) - SetGlobalBool("ttt2_random_shop_reroll_per_buy", tobool(new)) - end, "ttt2updatererollperbuyglobal") - - hook.Add("TTTPrepareRound", "TTT2InitRandomShops", function() - if not GetGlobalBool("ttt2_random_shops") then return end - - UpdateRandomShops(nil, GetGlobalInt("ttt2_random_shop_items"), GetGlobalBool("ttt2_random_team_shops", true)) - end) - - hook.Add("TTT2UpdateSubrole", "TTT2UpdateRandomShop", function(ply) - if not GetGlobalBool("ttt2_random_shops") then return end - - UpdateRandomShops({ply}, GetGlobalInt("ttt2_random_shop_items"), GetGlobalBool("ttt2_random_team_shops", true)) - end) - - hook.Add("PlayerInitialSpawn", "TTT2InitRandomShops", function(ply) - local random_shop = random_shops:GetBool() - - SetGlobalBool("ttt2_random_shops", random_shop) - SetGlobalInt("ttt2_random_shop_items", random_shop_items:GetInt()) - SetGlobalBool("ttt2_random_team_shops", random_team_shops:GetBool()) - SetGlobalBool("ttt2_random_shop_reroll", random_shop_reroll:GetBool()) - SetGlobalInt("ttt2_random_shop_reroll_cost", random_shop_reroll_cost:GetInt()) - SetGlobalBool("ttt2_random_shop_reroll_per_buy", random_shop_reroll_per_buy:GetBool()) - - if not random_shop then return end - - SyncRandomShops({ply}) - end) + SyncRandomShops({ ply }) + end) else -- CLIENT - local buff = "" - - local function TTT2SyncRandomShops(len) - local cont = net.ReadBit() == 1 - - buff = buff .. net.ReadString() - - if cont then - return - else - -- do stuff with buffer contents - local json_shop = buff -- util.Decompress(buff) - - if not json_shop then - ErrorNoHalt("RANDOMSHOP decompression failed!\n") - else - -- convert the json string back to a table - local tmp = util.JSONToTable(json_shop) - - if istable(tmp) then - local tmp2 = {} - - for i = 1, #tmp do - local id = tmp[i] - - tmp2[i] = not items.IsItem(id) and weapons.GetStored(id) or items.GetStored(id) - end - - RANDOMSHOP[LocalPlayer()] = tmp2 - else - ErrorNoHalt("RANDOMSHOP decoding failed!\n") - end - end - - -- flush - buff = "" - end - end - net.Receive("TTT2SyncRandomShops", TTT2SyncRandomShops) + local buff = "" + + local function TTT2SyncRandomShops(len) + local cont = net.ReadBit() == 1 + + buff = buff .. net.ReadString() + + if cont then + return + else + -- do stuff with buffer contents + local json_shop = buff -- util.Decompress(buff) + + if not json_shop then + ErrorNoHaltWithStack("RANDOMSHOP decompression failed!\n") + else + -- convert the json string back to a table + local tmp = util.JSONToTable(json_shop) + + if istable(tmp) then + local tmp2 = {} + + for i = 1, #tmp do + local id = tmp[i] + + tmp2[i] = not items.IsItem(id) and weapons.GetStored(id) + or items.GetStored(id) + end + + RANDOMSHOP[LocalPlayer()] = tmp2 + else + ErrorNoHaltWithStack("RANDOMSHOP decoding failed!\n") + end + end + + -- flush + buff = "" + end + end + net.Receive("TTT2SyncRandomShops", TTT2SyncRandomShops) end --- @@ -684,25 +752,27 @@ end -- @internal -- @realm shared function GetModifiedEquipment(ply, fallback) - if ply and fallback and RANDOMSHOP[ply] and GetGlobalBool("ttt2_random_shops") then - local tmp = {} + if ply and fallback and RANDOMSHOP[ply] and GetGlobalBool("ttt2_random_shops") then + local tmp = {} - for i = 1, #RANDOMSHOP[ply] do - local equip = RANDOMSHOP[ply][i] + for i = 1, #RANDOMSHOP[ply] do + local equip = RANDOMSHOP[ply][i] - for _, eq in pairs(fallback) do - if eq.id ~= equip.id then continue end + for _, eq in pairs(fallback) do + if eq.id ~= equip.id then + continue + end - tmp[#tmp + 1] = eq - end - end + tmp[#tmp + 1] = eq + end + end - if #tmp > 0 then - return tmp - end - end + if #tmp > 0 then + return tmp + end + end - return fallback + return fallback end --- @@ -713,25 +783,33 @@ end -- @deprecated -- @realm shared function GenerateNewEquipmentID() - EQUIP_MAX = EQUIP_MAX * 2 + EQUIP_MAX = EQUIP_MAX * 2 - local val = EQUIP_MAX + local val = EQUIP_MAX - timer.Simple(0, function() - local itms = items.GetList() + timer.Simple(0, function() + local itms = items.GetList() - for i = 1, #itms do - local v = itms[i] + for i = 1, #itms do + local v = itms[i] - if v.oldId ~= val or not v.id then continue end + if v.oldId ~= val or not v.id then + continue + end - print("[TTT2][WARNING] TTT2 doesn't support old items completely since they are limited to an amount of 16. If the item '" .. v.id .. "' with id '" .. val .. "' doesn't work as intended, modify the old item and use the new items system instead.") + ErrorNoHalt( + "[TTT2][WARNING] TTT2 doesn't support old items completely since they are limited to an amount of 16. If the item '" + .. v.id + .. "' with id '" + .. val + .. "' doesn't work as intended, modify the old item and use the new items system instead." + ) - break - end - end) + break + end + end) - return EQUIP_MAX + return EQUIP_MAX end --- @@ -741,17 +819,17 @@ end -- @return[default=false] boolean -- @realm shared function EquipmentTableHasValue(tbl, equip) - if not tbl then - return false - end + if not tbl then + return false + end - for _, eq in pairs(tbl) do - if eq.id == equip.id or eq.name == equip.name then - return true - end - end + for _, eq in pairs(tbl) do + if eq.id == equip.id or eq.name == equip.name then + return true + end + end - return false + return false end --- @@ -759,22 +837,26 @@ end -- @internal -- @realm shared function InitFallbackShops() - local tbl = {TRAITOR, DETECTIVE} - - for i = 1, #tbl do - local v = tbl[i] - - local fallback = GetShopFallbackTable(v.index) - if not fallback then continue end - - for k = 1, #fallback do - local equip = GetEquipmentByName(fallback[k].id) - if not equip then continue end - - equip.CanBuy = equip.CanBuy or {} - equip.CanBuy[v.index] = v.index - end - end + local tbl = { roles.TRAITOR, roles.DETECTIVE } + + for i = 1, #tbl do + local v = tbl[i] + + local fallback = GetShopFallbackTable(v.index) + if not fallback then + continue + end + + for k = 1, #fallback do + local equip = GetEquipmentByName(fallback[k].id) + if not equip then + continue + end + + equip.CanBuy = equip.CanBuy or {} + equip.CanBuy[v.index] = v.index + end + end end --- @@ -785,20 +867,22 @@ end -- @internal -- @realm shared function InitFallbackShop(roleData, fallbackTable, avoidSet) - if not avoidSet then - roleData.fallbackTable = fallbackTable - end - - local fallback = GetShopFallbackTable(roleData.index) - if fallback then - for i = 1, #fallbackTable do - local equip = GetEquipmentByName(fallbackTable[i].id) - if not equip then continue end - - equip.CanBuy = equip.CanBuy or {} - equip.CanBuy[roleData.index] = roleData.index - end - end + if not avoidSet then + roleData.fallbackTable = fallbackTable + end + + local fallback = GetShopFallbackTable(roleData.index) + if fallback then + for i = 1, #fallbackTable do + local equip = GetEquipmentByName(fallbackTable[i].id) + if not equip then + continue + end + + equip.CanBuy = equip.CanBuy or {} + equip.CanBuy[roleData.index] = roleData.index + end + end end --- @@ -809,17 +893,19 @@ end -- @internal -- @realm shared function AddToShopFallback(fallback, subrole, eq) - if not table.HasValue(fallback, eq) then - fallback[#fallback + 1] = eq - end - - if GetShopFallbackTable(subrole) then - local equip = GetEquipmentByName(eq.id) - if not equip then return end - - equip.CanBuy = equip.CanBuy or {} - equip.CanBuy[subrole] = subrole - end + if not table.HasValue(fallback, eq) then + fallback[#fallback + 1] = eq + end + + if GetShopFallbackTable(subrole) then + local equip = GetEquipmentByName(eq.id) + if not equip then + return + end + + equip.CanBuy = equip.CanBuy or {} + equip.CanBuy[subrole] = subrole + end end --- @@ -829,25 +915,28 @@ end -- @internal -- @realm shared local function InitDefaultEquipmentForRole(roleData, eq) - local tbl = roleData.fallbackTable or {} + local tbl = roleData.fallbackTable or {} - -- is a buyable equipment to load info from - if not eq or eq.Doublicated or not eq.CanBuy or not eq.CanBuy[roleData.index] then return end + -- is a buyable equipment to load info from + if not eq or eq.Duplicated or not eq.CanBuy or not eq.CanBuy[roleData.index] then + return + end - local base = GetEquipmentBase(eq) - if not base then return end + local base = GetEquipmentBase(eq) + if not base then + return + end - tbl[#tbl + 1] = base + tbl[#tbl + 1] = base - -- mark custom equipment - if base and base.id then - base.custom = not table.HasValue(DefaultEquipment[roleData.index], base.id) - end + -- mark custom equipment + if base and base.id then + base.custom = not table.HasValue(DefaultEquipment[roleData.index], base.id) + end - roleData.fallbackTable = tbl + roleData.fallbackTable = tbl end - --- -- A table with structure tbl[key] = value is turned into tbl[value] = value by this function. -- This allows you to easily access the table using the value as an index. @@ -857,16 +946,16 @@ end -- @realm shared -- @local local function ValueToKey(tbl) - local tmp = tmp or {} + local tmp = {} - for key, value in pairs(tbl) do - tmp[value] = value - tbl[key] = nil - end + for key, value in pairs(tbl) do + tmp[value] = value + tbl[key] = nil + end - for key in pairs(tmp) do - tbl[key] = tmp[key] - end + for key in pairs(tmp) do + tbl[key] = tmp[key] + end end --- @@ -878,9 +967,9 @@ end -- @realm shared -- @local local function CleanUpDefaultCanBuyIndices(eq) - eq.CanBuy = eq.CanBuy or {} + eq.CanBuy = eq.CanBuy or {} - ValueToKey(eq.CanBuy) + ValueToKey(eq.CanBuy) end --- @@ -889,9 +978,9 @@ end -- @internal -- @realm shared function InitDefaultEquipment(eq) - CleanUpDefaultCanBuyIndices(eq) - InitDefaultEquipmentForRole(TRAITOR, eq) - InitDefaultEquipmentForRole(DETECTIVE, eq) + CleanUpDefaultCanBuyIndices(eq) + InitDefaultEquipmentForRole(roles.TRAITOR, eq) + InitDefaultEquipmentForRole(roles.DETECTIVE, eq) end --- @@ -901,62 +990,70 @@ end -- @internal -- @realm shared local function ResetDefaultEquipmentForRole(roleData, eq) - local tbl = roleData.fallbackTable or {} - local tblSize = #tbl - - -- is a buyable equipment to load info from - if not eq or eq.Doublicated then return end - - local base = GetEquipmentBase(eq) - if not base then return end - - local addEquipment = tobool(eq.CanBuy and eq.CanBuy[roleData.index]) - - if addEquipment then - local tableHasValue = false - - for i = 1, tblSize do - if tbl[i].id == base.id then - tableHasValue = true - - break - end - end - - if not tableHasValue then - tbl[tblSize + 1] = base - end - else - local counter = 0 - - for i = 1, tblSize do - -- Skip the equipment that should be removed - if tbl[i].id == base.id then continue end - - counter = counter + 1 - - if counter == i then continue end - - -- Replace skipped equipments with higher indices - tbl[counter] = tbl[i] - end - - local diff = tblSize - counter - - -- Remove last table entries - if diff > 0 then - for i = 0, diff - 1 do - table.remove(tbl, tblSize - i) - end - end - end - - -- mark custom equipment - if base and base.id then - base.custom = not table.HasValue(DefaultEquipment[roleData.index], base.id) - end - - roleData.fallbackTable = tbl + local tbl = roleData.fallbackTable or {} + local tblSize = #tbl + + -- is a buyable equipment to load info from + if not eq or eq.Duplicated then + return + end + + local base = GetEquipmentBase(eq) + if not base then + return + end + + local addEquipment = tobool(eq.CanBuy and eq.CanBuy[roleData.index]) + + if addEquipment then + local tableHasValue = false + + for i = 1, tblSize do + if tbl[i].id == base.id then + tableHasValue = true + + break + end + end + + if not tableHasValue then + tbl[tblSize + 1] = base + end + else + local counter = 0 + + for i = 1, tblSize do + -- Skip the equipment that should be removed + if tbl[i].id == base.id then + continue + end + + counter = counter + 1 + + if counter == i then + continue + end + + -- Replace skipped equipments with higher indices + tbl[counter] = tbl[i] + end + + local diff = tblSize - counter + + -- Remove last table entries + if diff > 0 then + for i = 0, diff - 1 do + table.remove(tbl, tblSize - i) + end + end + end + + -- mark custom equipment + if base and base.id then + base.custom = not table.HasValue(DefaultEquipment[roleData.index], base.id) + end + + roleData.fallbackTable = tbl end --- @@ -966,350 +1063,320 @@ end -- @internal -- @realm shared function ResetDefaultEquipment(eq) - CleanUpDefaultCanBuyIndices(eq) - ResetDefaultEquipmentForRole(TRAITOR, eq) - ResetDefaultEquipmentForRole(DETECTIVE, eq) + CleanUpDefaultCanBuyIndices(eq) + ResetDefaultEquipmentForRole(roles.TRAITOR, eq) + ResetDefaultEquipmentForRole(roles.DETECTIVE, eq) end if SERVER then - util.AddNetworkString("TTT2SyncEquipment") - - --- - -- Synces equipment with a @{Player} - -- @param Player ply - -- @param[opt=true] boolean add - -- @internal - -- @realm server - function SyncEquipment(ply, add) - --print("[TTT2][SHOP] Sending new SHOP list to " .. ply:Nick() .. "...") - - local s = EncodeForStream(SYNC_EQUIP) - if not s then return end - - add = add == nil and true or add - - -- divide into happy lil bits. - -- this was necessary with user messages, now it's - -- a just-in-case thing if a round somehow manages to be > 64K - local cut = {} - local max = 65499 - - while #s ~= 0 do - local bit = string.sub(s, 1, max - 1) - - cut[#cut + 1] = bit - - s = string.sub(s, max, - 1) - end - - local parts = #cut - - for k = 1, parts do - net.Start("TTT2SyncEquipment") - net.WriteBool(add) - net.WriteBit(k ~= parts) -- continuation bit, 1 if there's more coming - net.WriteString(cut[k]) - net.Send(ply) - end - end - - --- - -- Synces single equipment with a @{Player} - -- @param Player ply - -- @param number role subrole id of a @{ROLE} - -- @param number equipId - -- @param[opt=true] boolean add - -- @internal - -- @realm server - function SyncSingleEquipment(ply, role, equipId, add) - local s = EncodeForStream({[role] = {equipId}}) - if not s then return end - - add = add == nil and true or add - - -- divide into happy lil bits. - -- this was necessary with user messages, now it's - -- a just-in-case thing if a round somehow manages to be > 64K - local cut = {} - local max = 65498 - - while #s ~= 0 do - local bit = string.sub(s, 1, max - 1) - - cut[#cut + 1] = bit - - s = string.sub(s, max, - 1) - end - - local parts = #cut - - for k = 1, parts do - net.Start("TTT2SyncEquipment") - net.WriteBool(add) - net.WriteBit(k ~= parts) -- continuation bit, 1 if there's more coming - net.WriteString(cut[k]) - net.Send(ply) - end - end - - --- - -- Loads a single shop of a @{ROLE} - -- @param ROLE roleData - -- @internal - -- @realm server - function LoadSingleShopEquipment(roleData) - local fallback = GetGlobalString("ttt_" .. roleData.abbr .. "_shop_fallback") - - if fallback ~= roleData.name then return end -- TODO why? remove and replace SHOP_UNSET with index of the current role - - --- - -- @realm server - hook.Run("TTT2LoadSingleShopEquipment", roleData) - - SYNC_EQUIP = SYNC_EQUIP or {} - SYNC_EQUIP[roleData.index] = {} -- reset - - -- init equipment - local result = ShopEditor.GetShopEquipments(roleData) - - for i = 1, #result do - local equip = GetEquipmentByName(result[i].name) - if not equip then continue end - - equip.CanBuy = equip.CanBuy or {} - equip.CanBuy[roleData.index] = roleData.index - - -- - - SYNC_EQUIP[roleData.index] = SYNC_EQUIP[roleData.index] or {} - - if not table.HasValue(SYNC_EQUIP[roleData.index], equip.id) then - SYNC_EQUIP[roleData.index][#SYNC_EQUIP[roleData.index] + 1] = equip.id - end - end - end - - --- - -- Adds an equipment into a @{ROLE}'s equipment table - -- @param number subrole subrole id - -- @param table equip_table - -- @realm server - function AddEquipmentToRole(subrole, equip_table) - equip_table.CanBuy = equip_table.CanBuy or {} - equip_table.CanBuy[subrole] = subrole - - -- - - SYNC_EQUIP[subrole] = SYNC_EQUIP[subrole] or {} - - if not table.HasValue(SYNC_EQUIP[subrole], equip_table.id) then - SYNC_EQUIP[subrole][#SYNC_EQUIP[subrole] + 1] = equip_table.id - end - - local plys = player.GetAll() - - for i = 1, #plys do - SyncSingleEquipment(plys[i], subrole, equip_table.id, true) - end - end - - --- - -- Removes an equipment from a @{ROLE}'s equipment table - -- @param number subrole subrole id - -- @param table equip_table - -- @realm server - function RemoveEquipmentFromRole(subrole, equip_table) - local tableremove = table.remove - - if not equip_table.CanBuy then - equip_table.CanBuy = {} - else - equip_table.CanBuy[subrole] = nil - end - -- - - SYNC_EQUIP[subrole] = SYNC_EQUIP[subrole] or {} - - for k, v in pairs(SYNC_EQUIP[subrole]) do - if v ~= equip_table.id then continue end - - tableremove(SYNC_EQUIP[subrole], k) - - break - end - - local plys = player.GetAll() - - for i = 1, #plys do - SyncSingleEquipment(plys[i], subrole, equip_table.id, false) - end - end - - hook.Add("TTT2UpdateTeam", "TTT2SyncTeambuyEquipment", function(ply, oldTeam, team) - if not TEAMBUYTABLE then return end - - if oldTeam and oldTeam ~= TEAM_NONE then - net.Start("TTT2ResetTBEq") - net.WriteString(oldTeam) - net.Send(ply) - end - - if team and team ~= TEAM_NONE and not TEAMS[team].alone and TEAMBUYTABLE[team] then - local filter = GetTeamFilter(team) - - for id in pairs(TEAMBUYTABLE[team]) do - net.Start("TTT2ReceiveTBEq") - net.WriteString(id) - net.Send(filter) - end - end - end) - - --- - -- Called on the load of the single shop of a @{ROLE}. - -- @param ROLE roleData - -- @hook - -- @realm server - function GM:TTT2LoadSingleShopEquipment(roleData) - - end + util.AddNetworkString("TTT2SyncEquipment") + + --- + -- Synces equipment with a @{Player} + -- @param Player ply + -- @param[opt=true] boolean add + -- @internal + -- @realm server + function SyncEquipment(ply, add) + local s = EncodeForStream(SYNC_EQUIP) + if not s then + return + end + + add = add == nil and true or add + + -- divide into happy lil bits. + -- this was necessary with user messages, now it's + -- a just-in-case thing if a round somehow manages to be > 64K + local cut = {} + local max = 65499 + + while #s ~= 0 do + local bit = string.sub(s, 1, max - 1) + + cut[#cut + 1] = bit + + s = string.sub(s, max, -1) + end + + local parts = #cut + + for k = 1, parts do + net.Start("TTT2SyncEquipment") + net.WriteBool(add) + net.WriteBit(k ~= parts) -- continuation bit, 1 if there's more coming + net.WriteString(cut[k]) + net.Send(ply) + end + end + + --- + -- Synces single equipment with a @{Player} + -- @param Player ply + -- @param number role subrole id of a @{ROLE} + -- @param number equipId + -- @param[opt=true] boolean add + -- @internal + -- @realm server + function SyncSingleEquipment(ply, role, equipId, add) + local s = EncodeForStream({ [role] = { equipId } }) + if not s then + return + end + + add = add == nil and true or add + + -- divide into happy lil bits. + -- this was necessary with user messages, now it's + -- a just-in-case thing if a round somehow manages to be > 64K + local cut = {} + local max = 65498 + + while #s ~= 0 do + local bit = string.sub(s, 1, max - 1) + + cut[#cut + 1] = bit + + s = string.sub(s, max, -1) + end + + local parts = #cut + + for k = 1, parts do + net.Start("TTT2SyncEquipment") + net.WriteBool(add) + net.WriteBit(k ~= parts) -- continuation bit, 1 if there's more coming + net.WriteString(cut[k]) + net.Send(ply) + end + end + + --- + -- Loads a single shop of a @{ROLE} + -- @param ROLE roleData + -- @internal + -- @realm server + function LoadSingleShopEquipment(roleData) + local fallback = GetGlobalString("ttt_" .. roleData.abbr .. "_shop_fallback") + + if fallback ~= roleData.name then + return + end -- TODO why? remove and replace SHOP_UNSET with index of the current role + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2LoadSingleShopEquipment", roleData) + + SYNC_EQUIP = SYNC_EQUIP or {} + SYNC_EQUIP[roleData.index] = {} -- reset + + -- init equipment + local result = ShopEditor.GetShopEquipments(roleData) + + for i = 1, #result do + local equip = GetEquipmentByName(result[i].name) + if not equip then + continue + end + + equip.CanBuy = equip.CanBuy or {} + equip.CanBuy[roleData.index] = roleData.index + + -- + + SYNC_EQUIP[roleData.index] = SYNC_EQUIP[roleData.index] or {} + + if not table.HasValue(SYNC_EQUIP[roleData.index], equip.id) then + SYNC_EQUIP[roleData.index][#SYNC_EQUIP[roleData.index] + 1] = equip.id + end + end + end + + --- + -- Adds an equipment into a @{ROLE}'s equipment table + -- @param number subrole subrole id + -- @param table equip_table + -- @realm server + function AddEquipmentToRole(subrole, equip_table) + equip_table.CanBuy = equip_table.CanBuy or {} + equip_table.CanBuy[subrole] = subrole + + -- + + SYNC_EQUIP[subrole] = SYNC_EQUIP[subrole] or {} + + if not table.HasValue(SYNC_EQUIP[subrole], equip_table.id) then + SYNC_EQUIP[subrole][#SYNC_EQUIP[subrole] + 1] = equip_table.id + end + + local plys = player.GetAll() + + for i = 1, #plys do + SyncSingleEquipment(plys[i], subrole, equip_table.id, true) + end + end + + --- + -- Removes an equipment from a @{ROLE}'s equipment table + -- @param number subrole subrole id + -- @param table equip_table + -- @realm server + function RemoveEquipmentFromRole(subrole, equip_table) + local tableremove = table.remove + + if not equip_table.CanBuy then + equip_table.CanBuy = {} + else + equip_table.CanBuy[subrole] = nil + end + -- + + SYNC_EQUIP[subrole] = SYNC_EQUIP[subrole] or {} + + for k, v in pairs(SYNC_EQUIP[subrole]) do + if v ~= equip_table.id then + continue + end + + tableremove(SYNC_EQUIP[subrole], k) + + break + end + + local plys = player.GetAll() + + for i = 1, #plys do + SyncSingleEquipment(plys[i], subrole, equip_table.id, false) + end + end + + hook.Add("TTT2UpdateTeam", "TTT2SyncTeambuyEquipment", function(ply, oldTeam, team) + shop.ResetTeamBuy(ply, oldTeam) + shop.SendEquipmentTeamBought(ply) + end) + + --- + -- Called on the load of the single shop of a @{ROLE}. + -- @param ROLE roleData + -- @hook + -- @realm server + function GM:TTT2LoadSingleShopEquipment(roleData) end else -- CLIENT - local function ReceiveTeambuyEquipment() - local s = net.ReadString() - local team = LocalPlayer():GetTeam() - - if team and team ~= TEAM_NONE and not TEAMS[team].alone then - TEAMBUYTABLE[team] = TEAMBUYTABLE[team] or {} - TEAMBUYTABLE[team][s] = true - end - end - net.Receive("TTT2ReceiveTBEq", ReceiveTeambuyEquipment) - - local function ReceiveGlobalbuyEquipment() - local s = net.ReadString() - - BUYTABLE[s] = true - end - net.Receive("TTT2ReceiveGBEq", ReceiveGlobalbuyEquipment) - - local function ResetTeambuyEquipment() - local s = net.ReadString() - - if not s or s == TEAM_NONE then return end - - TEAMBUYTABLE[s] = nil - end - net.Receive("TTT2ResetTBEq", ResetTeambuyEquipment) - - --- - -- Adds an equipment into a @{ROLE}'s equipment table - -- @param number subrole subrole id - -- @param table equip - -- @realm client - function AddEquipmentToRoleEquipment(subrole, equip) - -- start with all the non-weapon goodies - local toadd - - -- find buyable equip to load info from - equip.CanBuy = equip.CanBuy or {} - equip.CanBuy[subrole] = subrole - - if equip and not equip.Doublicated then - local base = GetEquipmentBase(equip) - if base then - toadd = base - end - end - - -- mark custom items - if toadd and toadd.id then - toadd.custom = not table.HasValue(DefaultEquipment[subrole], toadd.id) - end - - Equipment[subrole] = Equipment[subrole] or {} - - if toadd and not EquipmentTableHasValue(Equipment[subrole], toadd) then - Equipment[subrole][#Equipment[subrole] + 1] = toadd - end - end - - --- - -- Removes an equipment from a @{ROLE}'s equipment table - -- @param number subrole subrole id - -- @param table equip - -- @realm client - function RemoveEquipmentFromRoleEquipment(subrole, equip) - local tableremove = table.remove - - equip.CanBuy[subrole] = nil - - for k, eq in pairs(Equipment[subrole]) do - if eq.id ~= equip.id then continue end - - tableremove(Equipment[subrole], k) - - break - end - end - - -- sync GetRoles() - local buff = "" - - local function TTT2SyncEquipment(len) - --print("[TTT2][SHOP] Received new SHOP list from server! Updating...") - - local add = net.ReadBool() - local cont = net.ReadBit() == 1 - - buff = buff .. net.ReadString() - - -- wait for the complete message - if cont then return end - - -- do stuff with buffer contents - local json_shop = buff -- util.Decompress(buff) - - -- flush - buff = "" - - if not json_shop then - ErrorNoHalt("SHOP decompression failed!\n") - - return - end - - -- convert the json string back to a table - local tmp = util.JSONToTable(json_shop) - - if not istable(tmp) then - ErrorNoHalt("SHOP decoding failed!\n") - - return - end - - for subrole, tbl in pairs(tmp) do - -- init - Equipment = Equipment or {} - - if not Equipment[subrole] then - GetEquipmentForRole(nil, subrole, true) -- TODO test - end - - for _, equip in pairs(tbl) do - local equip_table = not items.IsItem(equip) and weapons.GetStored(equip) or items.GetStored(equip) - if not equip_table then continue end - - equip_table.CanBuy = equip_table.CanBuy or {} - - if add then - AddEquipmentToRoleEquipment(subrole, equip_table) - else - RemoveEquipmentFromRoleEquipment(subrole, equip_table) - end - end - end - end - net.Receive("TTT2SyncEquipment", TTT2SyncEquipment) + --- + -- Adds an equipment into a @{ROLE}'s equipment table + -- @param number subrole subrole id + -- @param table equip + -- @realm client + function AddEquipmentToRoleEquipment(subrole, equip) + -- start with all the non-weapon goodies + local toadd + + -- find buyable equip to load info from + equip.CanBuy = equip.CanBuy or {} + equip.CanBuy[subrole] = subrole + + if equip and not equip.Duplicated then + local base = GetEquipmentBase(equip) + if base then + toadd = base + end + end + + -- mark custom items + if toadd and toadd.id then + toadd.custom = not table.HasValue(DefaultEquipment[subrole], toadd.id) + end + + Equipment[subrole] = Equipment[subrole] or {} + + if toadd and not EquipmentTableHasValue(Equipment[subrole], toadd) then + Equipment[subrole][#Equipment[subrole] + 1] = toadd + end + end + + --- + -- Removes an equipment from a @{ROLE}'s equipment table + -- @param number subrole subrole id + -- @param table equip + -- @realm client + function RemoveEquipmentFromRoleEquipment(subrole, equip) + local tableremove = table.remove + + equip.CanBuy[subrole] = nil + + for k, eq in pairs(Equipment[subrole]) do + if eq.id ~= equip.id then + continue + end + + tableremove(Equipment[subrole], k) + + break + end + end + + -- sync GetRoles() + local buff = "" + + local function TTT2SyncEquipment(len) + local add = net.ReadBool() + local cont = net.ReadBit() == 1 + + buff = buff .. net.ReadString() + + -- wait for the complete message + if cont then + return + end + + -- do stuff with buffer contents + local json_shop = buff -- util.Decompress(buff) + + -- flush + buff = "" + + if not json_shop then + ErrorNoHaltWithStack("SHOP decompression failed!\n") + + return + end + + -- convert the json string back to a table + local tmp = util.JSONToTable(json_shop) + + if not istable(tmp) then + ErrorNoHaltWithStack("SHOP decoding failed!\n") + + return + end + + for subrole, tbl in pairs(tmp) do + -- init + Equipment = Equipment or {} + + if not Equipment[subrole] then + GetEquipmentForRole(nil, subrole, true) -- TODO test + end + + for _, equip in pairs(tbl) do + local equip_table = not items.IsItem(equip) and weapons.GetStored(equip) + or items.GetStored(equip) + if not equip_table then + continue + end + + equip_table.CanBuy = equip_table.CanBuy or {} + + if add then + AddEquipmentToRoleEquipment(subrole, equip_table) + else + RemoveEquipmentFromRoleEquipment(subrole, equip_table) + end + end + end + end + net.Receive("TTT2SyncEquipment", TTT2SyncEquipment) end --- @@ -1320,5 +1387,5 @@ end -- @deprecated -- @realm shared function GetEquipmentItem(role, id) - return items.GetRoleItem(role, id) + return items.GetRoleItem(role, id) end diff --git a/gamemodes/terrortown/gamemode/shared/sh_hud_module.lua b/gamemodes/terrortown/gamemode/shared/sh_hud_module.lua index be02a7b7b..b0210dc42 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_hud_module.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_hud_module.lua @@ -1,22 +1,22 @@ local HUDS_ABSTRACT_FOLDER = "base_huds" local function includeFoldersFiles(base, fld, fls) - for i = 1, #fls do - local fl = fls[i] - local filename = base .. fld .. "/" .. fl - - if SERVER then - AddCSLuaFile(filename) - end - - if CLIENT and fl == "cl_init.lua" then - include(filename) - elseif SERVER and fl == "init.lua" then - include(filename) - elseif fl == "shared.lua" then - include(filename) - end - end + for i = 1, #fls do + local fl = fls[i] + local filename = base .. fld .. "/" .. fl + + if SERVER then + AddCSLuaFile(filename) + end + + if CLIENT and fl == "cl_init.lua" then + include(filename) + elseif SERVER and fl == "init.lua" then + include(filename) + elseif fl == "shared.lua" then + include(filename) + end + end end -- @@ -28,46 +28,46 @@ local pathFiles = file.Find(pathBase .. "*.lua", "LUA") -- include HUD Elements files for i = 1, #pathFiles do - local fl = pathFiles[i] + local fl = pathFiles[i] - HUD = {} + HUD = {} - if SERVER then - AddCSLuaFile(pathBase .. fl) - end + if SERVER then + AddCSLuaFile(pathBase .. fl) + end - include(pathBase .. fl) + include(pathBase .. fl) - local cls = string.sub(fl, 0, #fl - 4) + local cls = string.sub(fl, 0, #fl - 4) - HUD.isAbstract = true + HUD.isAbstract = true - huds.Register(HUD, cls) + huds.Register(HUD, cls) - MsgN("[TTT2][Huds] Registered abstract HUD " .. cls) + Dev(1, "[TTT2][Huds] Registered abstract HUD " .. cls) - HUD = nil + HUD = nil end -- include HUD Elements folders local _, subFolders = file.Find(pathBase .. "*", "LUA") for i = 1, #subFolders do - local folder = subFolders[i] - local subFiles = file.Find(pathBase .. folder .. "/*.lua", "LUA") + local folder = subFolders[i] + local subFiles = file.Find(pathBase .. folder .. "/*.lua", "LUA") - -- all huds will be loaded here - HUD = {} + -- all huds will be loaded here + HUD = {} - includeFoldersFiles(pathBase, folder, subFiles) + includeFoldersFiles(pathBase, folder, subFiles) - HUD.isAbstract = true + HUD.isAbstract = true - huds.Register(HUD, folder) + huds.Register(HUD, folder) - MsgN("[TTT2][Huds] Registered abstract HUD " .. folder) + Dev(1, "[TTT2][Huds] Registered abstract HUD " .. folder) - HUD = nil + HUD = nil end -- @@ -80,43 +80,45 @@ local pathFiles2 = file.Find(pathBase .. "*.lua", "LUA") -- include HUD Elements files for i = 1, #pathFiles2 do - local fl = pathFiles2[i] + local fl = pathFiles2[i] - HUD = {} + HUD = {} - if SERVER then - AddCSLuaFile(pathBase .. fl) - end + if SERVER then + AddCSLuaFile(pathBase .. fl) + end - include(pathBase .. fl) + include(pathBase .. fl) - local cls = string.sub(fl, 0, #fl - 4) + local cls = string.sub(fl, 0, #fl - 4) - huds.Register(HUD, cls) + huds.Register(HUD, cls) - MsgN("[TTT2][Huds] Registered HUD " .. cls) + Dev(1, "[TTT2][Huds] Registered HUD " .. cls) - HUD = nil + HUD = nil end -- include HUD Elements folders local _, subFolders2 = file.Find(pathBase .. "*", "LUA") for i = 1, #subFolders2 do - local folder = subFolders2[i] + local folder = subFolders2[i] - if folder == HUDS_ABSTRACT_FOLDER then continue end + if folder == HUDS_ABSTRACT_FOLDER then + continue + end - local subFiles = file.Find(pathBase .. folder .. "/*.lua", "LUA") + local subFiles = file.Find(pathBase .. folder .. "/*.lua", "LUA") - -- all huds will be loaded here - HUD = {} + -- all huds will be loaded here + HUD = {} - includeFoldersFiles(pathBase, folder, subFiles) + includeFoldersFiles(pathBase, folder, subFiles) - huds.Register(HUD, folder) + huds.Register(HUD, folder) - MsgN("[TTT2][Huds] Registered HUD " .. folder) + Dev(1, "[TTT2][Huds] Registered HUD " .. folder) - HUD = nil + HUD = nil end diff --git a/gamemodes/terrortown/gamemode/shared/sh_hudelement_module.lua b/gamemodes/terrortown/gamemode/shared/sh_hudelement_module.lua index 50d67d232..d1d87efd3 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_hudelement_module.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_hudelement_module.lua @@ -2,22 +2,22 @@ local HUDELEMENTS_SHARED_FUNCTIONS_FOLDER = "shared_base" local HUDELEMENTS_ABSTRACT_FOLDER = "base_elements" local function includeFoldersFiles(pathBase, folder, filestbl) - for i = 1, #filestbl do - local fl = filestbl[i] - local filename = pathBase .. folder .. "/" .. fl - - if SERVER then - AddCSLuaFile(filename) - end - - if CLIENT and fl == "cl_init.lua" then - include(filename) - elseif SERVER and fl == "init.lua" then - include(filename) - elseif fl == "shared.lua" then - include(filename) - end - end + for i = 1, #filestbl do + local fl = filestbl[i] + local filename = pathBase .. folder .. "/" .. fl + + if SERVER then + AddCSLuaFile(filename) + end + + if CLIENT and fl == "cl_init.lua" then + include(filename) + elseif SERVER and fl == "init.lua" then + include(filename) + elseif fl == "shared.lua" then + include(filename) + end + end end -- @@ -29,63 +29,63 @@ local pathBase = "terrortown/gamemode/shared/hud_elements/" local _, pathFolders = file.Find(pathBase .. "*", "LUA") for i = 1, #pathFolders do - local typ = pathFolders[i] - local shortPath = pathBase .. typ .. "/" - local pathFiles = file.Find(shortPath .. "*.lua", "LUA") + local typ = pathFolders[i] + local shortPath = pathBase .. typ .. "/" + local pathFiles = file.Find(shortPath .. "*.lua", "LUA") - -- include HUD Elements files - for k = 1, #pathFiles do - local fl = pathFiles[k] + -- include HUD Elements files + for k = 1, #pathFiles do + local fl = pathFiles[k] - HUDELEMENT = {} + HUDELEMENT = {} - if SERVER then - AddCSLuaFile(shortPath .. fl) - end + if SERVER then + AddCSLuaFile(shortPath .. fl) + end - include(shortPath .. fl) + include(shortPath .. fl) - local cls = string.sub(fl, 0, #fl - 4) + local cls = string.sub(fl, 0, #fl - 4) - if typ ~= HUDELEMENTS_ABSTRACT_FOLDER then - HUDELEMENT.type = typ - end + if typ ~= HUDELEMENTS_ABSTRACT_FOLDER then + HUDELEMENT.type = typ + end - hudelements.Register(HUDELEMENT, cls) + hudelements.Register(HUDELEMENT, cls) - HUDELEMENT = nil - end + HUDELEMENT = nil + end - -- include HUD Elements folders - local _, subFolders = file.Find(shortPath .. "*", "LUA") + -- include HUD Elements folders + local _, subFolders = file.Find(shortPath .. "*", "LUA") - for k = 1, #subFolders do - local folder = subFolders[k] - local subFiles = file.Find(shortPath .. folder .. "/*.lua", "LUA") + for k = 1, #subFolders do + local folder = subFolders[k] + local subFiles = file.Find(shortPath .. folder .. "/*.lua", "LUA") - -- add special folder to clients, this is for shared functions between - -- different implementations of element types - if folder == HUDELEMENTS_SHARED_FUNCTIONS_FOLDER then - for kk = 1, #subFiles do - local fl = subFiles[kk] - local filename = pathBase .. folder .. "/" .. fl + -- add special folder to clients, this is for shared functions between + -- different implementations of element types + if folder == HUDELEMENTS_SHARED_FUNCTIONS_FOLDER then + for kk = 1, #subFiles do + local fl = subFiles[kk] + local filename = pathBase .. folder .. "/" .. fl - if SERVER then - AddCSLuaFile(filename) - end - end - else - HUDELEMENT = {} + if SERVER then + AddCSLuaFile(filename) + end + end + else + HUDELEMENT = {} - includeFoldersFiles(shortPath, folder, subFiles) + includeFoldersFiles(shortPath, folder, subFiles) - if typ ~= HUDELEMENTS_ABSTRACT_FOLDER then - HUDELEMENT.type = typ - end + if typ ~= HUDELEMENTS_ABSTRACT_FOLDER then + HUDELEMENT.type = typ + end - hudelements.Register(HUDELEMENT, folder) + hudelements.Register(HUDELEMENT, folder) - HUDELEMENT = nil - end - end + HUDELEMENT = nil + end + end end diff --git a/gamemodes/terrortown/gamemode/shared/sh_include.lua b/gamemodes/terrortown/gamemode/shared/sh_include.lua index 95cc42d4f..23e5a3fdf 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_include.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_include.lua @@ -1,160 +1,197 @@ TTT2DIR = "terrortown/gamemode/" TTTFiles = { - -- client files - cl_armor = {file = "cl_armor.lua", on = "client"}, - cl_awards = {file = "cl_awards.lua", on = "client"}, - cl_changes = {file = "cl_changes.lua", on = "client"}, - cl_chat = {file = "cl_chat.lua", on = "client"}, - cl_damage_indicator = {file = "cl_damage_indicator.lua", on = "client"}, - cl_equip = {file = "cl_equip.lua", on = "client"}, - cl_eventpopup = {file = "cl_eventpopup.lua", on = "client"}, - cl_help = {file = "cl_help.lua", on = "client"}, - cl_hud_editor = {file = "cl_hud_editor.lua", on = "client"}, - cl_hud_manager = {file = "cl_hud_manager.lua", on = "client"}, - cl_hudpickup = {file = "cl_hudpickup.lua", on = "client"}, - cl_inventory = {file = "cl_inventory.lua", on = "client"}, - cl_karma = {file = "cl_karma.lua", on = "client"}, - cl_keys = {file = "cl_keys.lua", on = "client"}, - cl_main = {file = "cl_main.lua", on = "client"}, - cl_msgstack = {file = "cl_msgstack.lua", on = "client"}, - cl_network_sync = {file = "cl_network_sync.lua", on = "client"}, - cl_player_ext = {file = "cl_player_ext.lua", on = "client"}, - cl_popups = {file = "cl_popups.lua", on = "client"}, - cl_radio = {file = "cl_radio.lua", on = "client"}, - cl_reroll = {file = "cl_reroll.lua", on = "client"}, - cl_scoreboard = {file = "cl_scoreboard.lua", on = "client"}, - cl_scoring = {file = "cl_scoring.lua", on = "client"}, - cl_search = {file = "cl_search.lua", on = "client"}, - cl_shopeditor = {file = "cl_shopeditor.lua", on = "client"}, - cl_status = {file = "cl_status.lua", on = "client"}, - cl_target_data = {file = "cl_target_data.lua", on = "client"}, - cl_targetid = {file = "cl_targetid.lua", on = "client"}, - cl_tbuttons = {file = "cl_tbuttons.lua", on = "client"}, - cl_tradio = {file = "cl_tradio.lua", on = "client"}, - cl_tips = {file = "cl_tips.lua", on = "client"}, - cl_transfer = {file = "cl_transfer.lua", on = "client"}, - cl_voice = {file = "cl_voice.lua", on = "client"}, - cl_weapon_pickup = {file = "cl_weapon_pickup.lua", on = "client"}, - cl_wepswitch = {file = "cl_wepswitch.lua", on = "client"}, - - -- shared files - sh_armor = {file = "sh_armor.lua", on = "shared"}, - sh_corpse = {file = "sh_corpse.lua", on = "shared"}, - sh_decal = {file = "sh_decal.lua", on = "shared"}, - sh_door = {file = "sh_door.lua", on = "shared"}, - sh_equip_items = {file = "sh_equip_items.lua", on = "shared"}, - sh_hud_module = {file = "sh_hud_module.lua", on = "shared"}, - sh_hudelement_module = {file = "sh_hudelement_module.lua", on = "shared"}, - sh_init = {file = "sh_init.lua", on = "shared"}, - sh_inventory = {file = "sh_inventory.lua", on = "shared"}, - sh_item_module = {file = "sh_item_module.lua", on = "shared"}, - sh_lang = {file = "sh_lang.lua", on = "shared"}, - sh_main = {file = "sh_main.lua", on = "shared"}, - sh_network_sync = {file = "sh_network_sync.lua", on = "shared"}, - sh_player_ext = {file = "sh_player_ext.lua", on = "shared"}, - sh_printmessage_override = {file = "sh_printmessage_override.lua", on = "shared"}, - sh_cvar_handler = {file = "sh_cvar_handler.lua", on = "shared"}, - sh_role_module = {file = "sh_role_module.lua", on = "shared"}, - sh_rolelayering = {file = "sh_rolelayering.lua", on = "shared"}, - sh_scoring = {file = "sh_scoring.lua", on = "shared"}, - sh_shopeditor = {file = "sh_shopeditor.lua", on = "shared"}, - sh_sql = {file = "sh_sql.lua", on = "shared"}, - sh_sprint = {file = "sh_sprint.lua", on = "shared"}, - sh_voice = {file = "sh_voice.lua", on = "shared"}, - sh_speed = {file = "sh_speed.lua", on = "shared"}, - sh_weaponry = {file = "sh_weaponry.lua", on = "shared"}, - - -- vgui client files - vgui__cl_coloredbox = {file = "vgui/cl_coloredbox.lua", on = "client"}, - vgui__cl_droleimage = {file = "vgui/cl_droleimage.lua", on = "client"}, - vgui__cl_progressbar = {file = "vgui/cl_progressbar.lua", on = "client"}, - vgui__cl_sb_info = {file = "vgui/cl_sb_info.lua", on = "client"}, - vgui__cl_sb_main = {file = "vgui/cl_sb_main.lua", on = "client"}, - vgui__cl_sb_row = {file = "vgui/cl_sb_row.lua", on = "client"}, - vgui__cl_sb_team = {file = "vgui/cl_sb_team.lua", on = "client"}, - vgui__cl_scrolllabel = {file = "vgui/cl_scrolllabel.lua", on = "client"}, - vgui__cl_simpleclickicon = {file = "vgui/cl_simpleclickicon.lua", on = "client"}, - vgui__cl_simpleicon = {file = "vgui/cl_simpleicon.lua", on = "client"}, - vgui__cl_simpleroleicon = {file = "vgui/cl_simpleroleicon.lua", on = "client"}, - - -- cl_vskin client files - cl_vskin__default_skin = {file = "cl_vskin/default_skin.lua", on = "client"}, - cl_vskin__vgui__dpanel = {file = "cl_vskin/vgui/dpanel_ttt2.lua", on = "client"}, - cl_vskin__vgui__droleimage = {file = "cl_vskin/vgui/droleimage_ttt2.lua", on = "client"}, - cl_vskin__vgui__dframe = {file = "cl_vskin/vgui/dframe_ttt2.lua", on = "client"}, - cl_vskin__vgui__dimagecheckbox = {file = "cl_vskin/vgui/dimagecheckbox_ttt2.lua", on = "client"}, - cl_vskin__vgui__dmenubutton = {file = "cl_vskin/vgui/dmenubutton_ttt2.lua", on = "client"}, - cl_vskin__vgui__dsubmenubutton = {file = "cl_vskin/vgui/dsubmenubutton_ttt2.lua", on = "client"}, - cl_vskin__vgui__dnavpanel = {file = "cl_vskin/vgui/dnavpanel_ttt2.lua", on = "client"}, - cl_vskin__vgui__dcontentpanel = {file = "cl_vskin/vgui/dcontentpanel_ttt2.lua", on = "client"}, - cl_vskin__vgui__dcard = {file = "cl_vskin/vgui/dcard_ttt2.lua", on = "client"}, - cl_vskin__vgui__dbuttonpanel = {file = "cl_vskin/vgui/dbuttonpanel_ttt2.lua", on = "client"}, - cl_vskin__vgui__dcategoryheader = {file = "cl_vskin/vgui/dcategoryheader_ttt2.lua", on = "client"}, - cl_vskin__vgui__dcategorycollapse = {file = "cl_vskin/vgui/dcategorycollapse_ttt2.lua", on = "client"}, - cl_vskin__vgui__dform = {file = "cl_vskin/vgui/dform_ttt2.lua", on = "client"}, - cl_vskin__vgui__dbutton = {file = "cl_vskin/vgui/dbutton_ttt2.lua", on = "client"}, - cl_vskin__vgui__dbinder = {file = "cl_vskin/vgui/dbinder_ttt2.lua", on = "client"}, - cl_vskin__vgui__dlabel = {file = "cl_vskin/vgui/dlabel_ttt2.lua", on = "client"}, - cl_vskin__vgui__dcombobox = {file = "cl_vskin/vgui/dcombobox_ttt2.lua", on = "client"}, - cl_vskin__vgui__dcheckboxlabel = {file = "cl_vskin/vgui/dcheckboxlabel_ttt2.lua", on = "client"}, - cl_vskin__vgui__dnumslider = {file = "cl_vskin/vgui/dnumslider_ttt2.lua", on = "client"}, - cl_vskin__vgui__dbinderpanel = {file = "cl_vskin/vgui/dbinderpanel_ttt2.lua", on = "client"}, - cl_vskin__vgui__dscrollpanel = {file = "cl_vskin/vgui/dscrollpanel_ttt2.lua", on = "client"}, - cl_vskin__vgui__dvscrollbar = {file = "cl_vskin/vgui/dvscrollbar_ttt2.lua", on = "client"}, - cl_vskin__vgui__dcoloredbox = {file = "cl_vskin/vgui/dcoloredbox_ttt2.lua", on = "client"}, - cl_vskin__vgui__dcoloredtextbox = {file = "cl_vskin/vgui/dcoloredtextbox_ttt2.lua", on = "client"}, - cl_vskin__vgui__dtooltip = {file = "cl_vskin/vgui/dtooltip_ttt2.lua", on = "client"}, - cl_vskin__vgui__deventbox = {file = "cl_vskin/vgui/deventbox_ttt2.lua", on = "client"}, - cl_vskin__vgui__ddragbase = {file = "cl_vskin/vgui/ddragbase_ttt2.lua", on = "client"}, - cl_vskin__vgui__drolelayeringreceiver = {file = "cl_vskin/vgui/drolelayeringreceiver_ttt2.lua", on = "client"}, - cl_vskin__vgui__drolelayeringsender = {file = "cl_vskin/vgui/drolelayeringsender_ttt2.lua", on = "client"}, - cl_vskin__vgui__dsearchbar = {file = "cl_vskin/vgui/dsearchbar_ttt2.lua", on = "client"}, - cl_vskin__vgui__dsubmenulist = {file = "cl_vskin/vgui/dsubmenulist_ttt2.lua", on = "client"} + -- client files + cl_armor = { file = "cl_armor.lua", on = "client" }, + cl_awards = { file = "cl_awards.lua", on = "client" }, + cl_changes = { file = "cl_changes.lua", on = "client" }, + cl_chat = { file = "cl_chat.lua", on = "client" }, + cl_damage_indicator = { file = "cl_damage_indicator.lua", on = "client" }, + cl_equip = { file = "cl_equip.lua", on = "client" }, + cl_eventpopup = { file = "cl_eventpopup.lua", on = "client" }, + cl_help = { file = "cl_help.lua", on = "client" }, + cl_hud_editor = { file = "cl_hud_editor.lua", on = "client" }, + cl_hud_manager = { file = "cl_hud_manager.lua", on = "client" }, + cl_hudpickup = { file = "cl_hudpickup.lua", on = "client" }, + cl_inventory = { file = "cl_inventory.lua", on = "client" }, + cl_karma = { file = "cl_karma.lua", on = "client" }, + cl_keys = { file = "cl_keys.lua", on = "client" }, + cl_main = { file = "cl_main.lua", on = "client" }, + cl_msgstack = { file = "cl_msgstack.lua", on = "client" }, + cl_network_sync = { file = "cl_network_sync.lua", on = "client" }, + cl_player_ext = { file = "cl_player_ext.lua", on = "client" }, + cl_popups = { file = "cl_popups.lua", on = "client" }, + cl_radio = { file = "cl_radio.lua", on = "client" }, + cl_reroll = { file = "cl_reroll.lua", on = "client" }, + cl_scoreboard = { file = "cl_scoreboard.lua", on = "client" }, + cl_scoring = { file = "cl_scoring.lua", on = "client" }, + cl_search = { file = "cl_search.lua", on = "client" }, + cl_shop = { file = "cl_shop.lua", on = "client" }, + cl_shopeditor = { file = "cl_shopeditor.lua", on = "client" }, + cl_status = { file = "cl_status.lua", on = "client" }, + cl_marker_vision_data = { file = "cl_marker_vision_data.lua", on = "client" }, + cl_target_data = { file = "cl_target_data.lua", on = "client" }, + cl_targetid = { file = "cl_targetid.lua", on = "client" }, + cl_tbuttons = { file = "cl_tbuttons.lua", on = "client" }, + cl_tradio = { file = "cl_tradio.lua", on = "client" }, + cl_tips = { file = "cl_tips.lua", on = "client" }, + cl_transfer = { file = "cl_transfer.lua", on = "client" }, + cl_voice = { file = "cl_voice.lua", on = "client" }, + cl_weapon_pickup = { file = "cl_weapon_pickup.lua", on = "client" }, + cl_wepswitch = { file = "cl_wepswitch.lua", on = "client" }, + + -- shared files + sh_armor = { file = "sh_armor.lua", on = "shared" }, + sh_corpse = { file = "sh_corpse.lua", on = "shared" }, + sh_decal = { file = "sh_decal.lua", on = "shared" }, + sh_door = { file = "sh_door.lua", on = "shared" }, + sh_equip_items = { file = "sh_equip_items.lua", on = "shared" }, + sh_hud_module = { file = "sh_hud_module.lua", on = "shared" }, + sh_hudelement_module = { file = "sh_hudelement_module.lua", on = "shared" }, + sh_init = { file = "sh_init.lua", on = "shared" }, + sh_inventory = { file = "sh_inventory.lua", on = "shared" }, + sh_item_module = { file = "sh_item_module.lua", on = "shared" }, + sh_lang = { file = "sh_lang.lua", on = "shared" }, + sh_main = { file = "sh_main.lua", on = "shared" }, + sh_network_sync = { file = "sh_network_sync.lua", on = "shared" }, + sh_player_ext = { file = "sh_player_ext.lua", on = "shared" }, + sh_playerclass = { file = "sh_playerclass.lua", on = "shared" }, + sh_printmessage_override = { file = "sh_printmessage_override.lua", on = "shared" }, + sh_cvar_handler = { file = "sh_cvar_handler.lua", on = "shared" }, + sh_role_module = { file = "sh_role_module.lua", on = "shared" }, + sh_rolelayering = { file = "sh_rolelayering.lua", on = "shared" }, + sh_scoring = { file = "sh_scoring.lua", on = "shared" }, + sh_shop = { file = "sh_shop.lua", on = "shared" }, + sh_shopeditor = { file = "sh_shopeditor.lua", on = "shared" }, + sh_sql = { file = "sh_sql.lua", on = "shared" }, + sh_sprint = { file = "sh_sprint.lua", on = "shared" }, + sh_voice = { file = "sh_voice.lua", on = "shared" }, + sh_speed = { file = "sh_speed.lua", on = "shared" }, + sh_weaponry = { file = "sh_weaponry.lua", on = "shared" }, + sh_marker_vision_element = { file = "sh_marker_vision_element.lua", on = "shared" }, + + -- vgui client files + vgui__cl_coloredbox = { file = "vgui/cl_coloredbox.lua", on = "client" }, + vgui__cl_droleimage = { file = "vgui/cl_droleimage.lua", on = "client" }, + vgui__cl_progressbar = { file = "vgui/cl_progressbar.lua", on = "client" }, + vgui__cl_sb_info = { file = "vgui/cl_sb_info.lua", on = "client" }, + vgui__cl_sb_main = { file = "vgui/cl_sb_main.lua", on = "client" }, + vgui__cl_sb_row = { file = "vgui/cl_sb_row.lua", on = "client" }, + vgui__cl_sb_team = { file = "vgui/cl_sb_team.lua", on = "client" }, + vgui__cl_scrolllabel = { file = "vgui/cl_scrolllabel.lua", on = "client" }, + vgui__cl_simpleclickicon = { file = "vgui/cl_simpleclickicon.lua", on = "client" }, + vgui__cl_simpleicon = { file = "vgui/cl_simpleicon.lua", on = "client" }, + vgui__cl_simpleroleicon = { file = "vgui/cl_simpleroleicon.lua", on = "client" }, + + -- cl_vskin client files + cl_vskin__default_skin = { file = "cl_vskin/default_skin.lua", on = "client" }, + cl_vskin__vgui__dpanel = { file = "cl_vskin/vgui/dpanel_ttt2.lua", on = "client" }, + cl_vskin__vgui__droleimage = { file = "cl_vskin/vgui/droleimage_ttt2.lua", on = "client" }, + cl_vskin__vgui__dframe = { file = "cl_vskin/vgui/dframe_ttt2.lua", on = "client" }, + cl_vskin__vgui__dimagecheckbox = { + file = "cl_vskin/vgui/dimagecheckbox_ttt2.lua", + on = "client", + }, + cl_vskin__vgui__dmenubutton = { file = "cl_vskin/vgui/dmenubutton_ttt2.lua", on = "client" }, + cl_vskin__vgui__dsubmenubutton = { + file = "cl_vskin/vgui/dsubmenubutton_ttt2.lua", + on = "client", + }, + cl_vskin__vgui__dnavpanel = { file = "cl_vskin/vgui/dnavpanel_ttt2.lua", on = "client" }, + cl_vskin__vgui__dcontentpanel = { file = "cl_vskin/vgui/dcontentpanel_ttt2.lua", on = "client" }, + cl_vskin__vgui__dcard = { file = "cl_vskin/vgui/dcard_ttt2.lua", on = "client" }, + cl_vskin__vgui__dbuttonpanel = { file = "cl_vskin/vgui/dbuttonpanel_ttt2.lua", on = "client" }, + cl_vskin__vgui__dcategoryheader = { + file = "cl_vskin/vgui/dcategoryheader_ttt2.lua", + on = "client", + }, + cl_vskin__vgui__dcategorycollapse = { + file = "cl_vskin/vgui/dcategorycollapse_ttt2.lua", + on = "client", + }, + cl_vskin__vgui__dform = { file = "cl_vskin/vgui/dform_ttt2.lua", on = "client" }, + cl_vskin__vgui__dbutton = { file = "cl_vskin/vgui/dbutton_ttt2.lua", on = "client" }, + cl_vskin__vgui__dbinder = { file = "cl_vskin/vgui/dbinder_ttt2.lua", on = "client" }, + cl_vskin__vgui__dlabel = { file = "cl_vskin/vgui/dlabel_ttt2.lua", on = "client" }, + cl_vskin__vgui__dcombobox = { file = "cl_vskin/vgui/dcombobox_ttt2.lua", on = "client" }, + cl_vskin__vgui__dcheckboxlabel = { + file = "cl_vskin/vgui/dcheckboxlabel_ttt2.lua", + on = "client", + }, + cl_vskin__vgui__dnumslider = { file = "cl_vskin/vgui/dnumslider_ttt2.lua", on = "client" }, + cl_vskin__vgui__dtextentry = { file = "cl_vskin/vgui/dtextentry_ttt2.lua", on = "client" }, + cl_vskin__vgui__dbinderpanel = { file = "cl_vskin/vgui/dbinderpanel_ttt2.lua", on = "client" }, + cl_vskin__vgui__dscrollpanel = { file = "cl_vskin/vgui/dscrollpanel_ttt2.lua", on = "client" }, + cl_vskin__vgui__dvscrollbar = { file = "cl_vskin/vgui/dvscrollbar_ttt2.lua", on = "client" }, + cl_vskin__vgui__dcoloredbox = { file = "cl_vskin/vgui/dcoloredbox_ttt2.lua", on = "client" }, + cl_vskin__vgui__dcoloredtextbox = { + file = "cl_vskin/vgui/dcoloredtextbox_ttt2.lua", + on = "client", + }, + cl_vskin__vgui__dtooltip = { file = "cl_vskin/vgui/dtooltip_ttt2.lua", on = "client" }, + cl_vskin__vgui__deventbox = { file = "cl_vskin/vgui/deventbox_ttt2.lua", on = "client" }, + cl_vskin__vgui__ddragbase = { file = "cl_vskin/vgui/ddragbase_ttt2.lua", on = "client" }, + cl_vskin__vgui__drolelayeringreceiver = { + file = "cl_vskin/vgui/drolelayeringreceiver_ttt2.lua", + on = "client", + }, + cl_vskin__vgui__drolelayeringsender = { + file = "cl_vskin/vgui/drolelayeringsender_ttt2.lua", + on = "client", + }, + cl_vskin__vgui__dsearchbar = { file = "cl_vskin/vgui/dsearchbar_ttt2.lua", on = "client" }, + cl_vskin__vgui__dsubmenulist = { file = "cl_vskin/vgui/dsubmenulist_ttt2.lua", on = "client" }, + cl_vskin__vgui__dinfoitem = { file = "cl_vskin/vgui/dinfoitem_ttt2.lua", on = "client" }, + cl_vskin__vgui__dprofilepanel = { file = "cl_vskin/vgui/dprofilepanel_ttt2.lua", on = "client" }, + cl_vskin__vgui__dweaponpreview = { + file = "cl_vskin/vgui/dweaponpreview_ttt2.lua", + on = "client", + }, } if SERVER then - local tmp = { -- server files, don't show them for the client - sv_addonchecker = {file = "sv_addonchecker.lua", on = "server"}, - sv_admin = {file = "sv_admin.lua", on = "server"}, - sv_armor = {file = "sv_armor.lua", on = "server"}, - sv_corpse = {file = "sv_corpse.lua", on = "server"}, - sv_ent_replace = {file = "sv_ent_replace.lua", on = "server"}, - sv_entity = {file = "sv_entity.lua", on = "server"}, - sv_eventpopup = {file = "sv_eventpopup.lua", on = "server"}, - sv_gamemsg = {file = "sv_gamemsg.lua", on = "server"}, - sv_hud_manager = {file = "sv_hud_manager.lua", on = "server"}, - sv_inventory = {file = "sv_inventory.lua", on = "server"}, - sv_karma = {file = "sv_karma.lua", on = "server"}, - sv_main = {file = "sv_main.lua", on = "server"}, - sv_networking = {file = "sv_networking.lua", on = "server"}, - sv_network_sync = {file = "sv_network_sync.lua", on = "server"}, - sv_player_ext = {file = "sv_player_ext.lua", on = "server"}, - sv_player = {file = "sv_player.lua", on = "server"}, - sv_propspec = {file = "sv_propspec.lua", on = "server"}, - sv_roleselection = {file = "sv_roleselection.lua", on = "server"}, - sv_scoring = {file = "sv_scoring.lua", on = "server"}, - sv_shop = {file = "sv_shop.lua", on = "server"}, - sv_shopeditor = {file = "sv_shopeditor.lua", on = "server"}, - sv_status = {file = "sv_status.lua", on = "server"}, - sv_voice = {file = "sv_voice.lua", on = "server"}, - sv_weapon_pickup = {file = "sv_weapon_pickup.lua", on = "server"}, - sv_weaponry = {file = "sv_weaponry.lua", on = "server"}, - } - - table.Merge(TTTFiles, tmp) + local tmp = { -- server files, don't show them for the client + sv_addonchecker = { file = "sv_addonchecker.lua", on = "server" }, + sv_admin = { file = "sv_admin.lua", on = "server" }, + sv_armor = { file = "sv_armor.lua", on = "server" }, + sv_corpse = { file = "sv_corpse.lua", on = "server" }, + sv_ent_replace = { file = "sv_ent_replace.lua", on = "server" }, + sv_entity = { file = "sv_entity.lua", on = "server" }, + sv_eventpopup = { file = "sv_eventpopup.lua", on = "server" }, + sv_gamemsg = { file = "sv_gamemsg.lua", on = "server" }, + sv_hud_manager = { file = "sv_hud_manager.lua", on = "server" }, + sv_inventory = { file = "sv_inventory.lua", on = "server" }, + sv_karma = { file = "sv_karma.lua", on = "server" }, + sv_main = { file = "sv_main.lua", on = "server" }, + sv_networking = { file = "sv_networking.lua", on = "server" }, + sv_network_sync = { file = "sv_network_sync.lua", on = "server" }, + sv_player_ext = { file = "sv_player_ext.lua", on = "server" }, + sv_player = { file = "sv_player.lua", on = "server" }, + sv_propspec = { file = "sv_propspec.lua", on = "server" }, + sv_roleselection = { file = "sv_roleselection.lua", on = "server" }, + sv_scoring = { file = "sv_scoring.lua", on = "server" }, + sv_shop = { file = "sv_shop.lua", on = "server" }, + sv_shopeditor = { file = "sv_shopeditor.lua", on = "server" }, + sv_status = { file = "sv_status.lua", on = "server" }, + sv_voice = { file = "sv_voice.lua", on = "server" }, + sv_weapon_pickup = { file = "sv_weapon_pickup.lua", on = "server" }, + sv_weaponry = { file = "sv_weaponry.lua", on = "server" }, + } + + table.Merge(TTTFiles, tmp) end --- -- @realm shared +-- stylua: ignore hook.Run("TTT2ModifyFiles", TTTFiles) if SERVER then - for _, inc in pairs(TTTFiles) do - if inc.on == "client" or inc.on == "shared" then - AddCSLuaFile(TTT2DIR .. inc.on .. "/" .. inc.file) - end - end + for _, inc in pairs(TTTFiles) do + if inc.on == "client" or inc.on == "shared" then + AddCSLuaFile(TTT2DIR .. inc.on .. "/" .. inc.file) + end + end end --- @@ -162,21 +199,23 @@ end -- @param string filename The registered filename-pseudo, but not the path -- @realm shared function ttt_include(filename) - local fd = TTTFiles[filename] + local fd = TTTFiles[filename] - if not fd then - error("[TTT2][ERROR] Tried to include missing file " .. filename) - end + if not fd then + error("[TTT2][ERROR] Tried to include missing file " .. filename) + end - local file = fd.file + local file = fd.file - if file then - file = TTT2DIR .. fd.on .. "/" .. file - end + if file then + file = TTT2DIR .. fd.on .. "/" .. file + end - if not file then return end + if not file then + return + end - include(file) + include(file) end --- @@ -185,6 +224,4 @@ end -- @param table fileTbl The table of files that should be loaded -- @hook -- @realm shared -function GM:TTT2ModifyFiles(fileTbl) - -end +function GM:TTT2ModifyFiles(fileTbl) end diff --git a/gamemodes/terrortown/gamemode/shared/sh_init.lua b/gamemodes/terrortown/gamemode/shared/sh_init.lua index e8e8b27f6..e540174d8 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_init.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_init.lua @@ -1,11 +1,11 @@ --- -- This file contains all shared vars, tables and functions -GM.Name = "TTT2 (Advanced Update)" +GM.Name = "TTT2" GM.Author = "Bad King Urgrain, Alf21, saibotk, Mineotopia, LeBroomer, Histalek, ZenBre4ker" GM.Email = "ttt2@neoxult.de" GM.Website = "ttt.badking.net, docs.ttt2.neoxult.de" -GM.Version = "0.11.6b" +GM.Version = "0.13.0b" GM.Customized = true TTT2 = true -- identifier for TTT2. Just use "if TTT2 then ... end" @@ -18,63 +18,63 @@ ROUND_POST = 4 -- equipment setups INNO_EQUIPMENT = { - "weapon_ttt_confgrenade", - "weapon_ttt_m16", - "weapon_ttt_smokegrenade", - "weapon_ttt_unarmed", - "weapon_ttt_wtester", - "weapon_tttbase", - "weapon_tttbasegrenade", - "weapon_zm_carry", - "weapon_zm_improvised", - "weapon_zm_mac10", - "weapon_zm_molotov", - "weapon_zm_pistol", - "weapon_zm_revolver", - "weapon_zm_rifle", - "weapon_zm_shotgun", - "weapon_zm_sledge", - "weapon_ttt_glock" + "weapon_ttt_confgrenade", + "weapon_ttt_m16", + "weapon_ttt_smokegrenade", + "weapon_ttt_unarmed", + "weapon_ttt_wtester", + "weapon_tttbase", + "weapon_tttbasegrenade", + "weapon_zm_carry", + "weapon_zm_improvised", + "weapon_zm_mac10", + "weapon_zm_molotov", + "weapon_zm_pistol", + "weapon_zm_revolver", + "weapon_zm_rifle", + "weapon_zm_shotgun", + "weapon_zm_sledge", + "weapon_ttt_glock", } SPECIAL_EQUIPMENT = { - "weapon_ttt_unarmed", - "weapon_zm_carry", - "weapon_zm_improvised", - "weapon_ttt_binoculars", - "weapon_ttt_defuser", - "weapon_ttt_health_station", - "weapon_ttt_stungun", - "weapon_ttt_cse", - "weapon_ttt_teleport", - "item_ttt_armor", - "item_ttt_radar", - "item_ttt_nodrowningdmg", - "item_ttt_noenergydmg", - "item_ttt_noexplosiondmg", - "item_ttt_nofalldmg", - "item_ttt_nofiredmg", - "item_ttt_nohazarddmg", - "item_ttt_nopropdmg", - "item_ttt_speedrun" + "weapon_ttt_unarmed", + "weapon_zm_carry", + "weapon_zm_improvised", + "weapon_ttt_binoculars", + "weapon_ttt_defuser", + "weapon_ttt_health_station", + "weapon_ttt_stungun", + "weapon_ttt_cse", + "weapon_ttt_teleport", + "item_ttt_armor", + "item_ttt_radar", + "item_ttt_nodrowningdmg", + "item_ttt_noenergydmg", + "item_ttt_noexplosiondmg", + "item_ttt_nofalldmg", + "item_ttt_nofiredmg", + "item_ttt_nohazarddmg", + "item_ttt_nopropdmg", + "item_ttt_speedrun", } TRAITOR_EQUIPMENT = { - "weapon_ttt_unarmed", - "weapon_zm_carry", - "weapon_zm_improvised", - "weapon_ttt_c4", - "weapon_ttt_flaregun", - "weapon_ttt_knife", - "weapon_ttt_phammer", - "weapon_ttt_push", - "weapon_ttt_radio", - "weapon_ttt_sipistol", - "weapon_ttt_teleport", - "weapon_ttt_decoy", - "item_ttt_armor", - "item_ttt_radar", - "item_ttt_disguiser" + "weapon_ttt_unarmed", + "weapon_zm_carry", + "weapon_zm_improvised", + "weapon_ttt_c4", + "weapon_ttt_flaregun", + "weapon_ttt_knife", + "weapon_ttt_phammer", + "weapon_ttt_push", + "weapon_ttt_radio", + "weapon_ttt_sipistol", + "weapon_ttt_teleport", + "weapon_ttt_decoy", + "item_ttt_armor", + "item_ttt_radar", + "item_ttt_disguiser", } -- role teams to have an identifier @@ -96,23 +96,24 @@ ROLE_DETECTIVE = 2 ROLE_NONE = 3 -- TEAM_ARRAY -TEAMS = TEAMS or { - [TEAM_INNOCENT] = { - icon = "vgui/ttt/dynamic/roles/icon_inno", - iconMaterial = Material("vgui/ttt/dynamic/roles/icon_inno"), - color = Color(80, 173, 59, 255) - }, - [TEAM_TRAITOR] = { - icon = "vgui/ttt/dynamic/roles/icon_traitor", - iconMaterial = Material("vgui/ttt/dynamic/roles/icon_traitor"), - color = Color(209, 43, 39, 255) - }, - [TEAM_NONE] = { - icon = "vgui/ttt/dynamic/roles/icon_no_team", - iconMaterial = Material("vgui/ttt/dynamic/roles/icon_no_team"), - color = Color(91, 94, 99, 255) - } -} +TEAMS = TEAMS + or { + [TEAM_INNOCENT] = { + icon = "vgui/ttt/dynamic/roles/icon_inno", + iconMaterial = Material("vgui/ttt/dynamic/roles/icon_inno"), + color = Color(80, 173, 59, 255), + }, + [TEAM_TRAITOR] = { + icon = "vgui/ttt/dynamic/roles/icon_traitor", + iconMaterial = Material("vgui/ttt/dynamic/roles/icon_traitor"), + color = Color(209, 43, 39, 255), + }, + [TEAM_NONE] = { + icon = "vgui/ttt/dynamic/roles/icon_no_team", + iconMaterial = Material("vgui/ttt/dynamic/roles/icon_no_team"), + color = Color(91, 94, 99, 255), + }, + } ACTIVEROLES = ACTIVEROLES or {} @@ -139,7 +140,7 @@ REVIVAL_BITS = 2 -- @realm shared -- @deprecated function GetRoles() - return roles.GetList() + return roles.GetList() end --- @@ -150,7 +151,7 @@ end -- @realm shared -- @deprecated function SortRolesTable(tbl) - roles.SortTable(tbl) + roles.SortTable(tbl) end --- @@ -161,7 +162,7 @@ end -- @realm shared -- @deprecated function GetRoleByIndex(index) - return roles.GetByIndex(index) + return roles.GetByIndex(index) end --- @@ -172,7 +173,7 @@ end -- @realm shared -- @deprecated function GetRoleByName(name) - return roles.GetByName(name) + return roles.GetByName(name) end --- @@ -183,7 +184,7 @@ end -- @realm shared -- @deprecated function GetRoleByAbbr(abbr) - return roles.GetByAbbr(abbr) + return roles.GetByAbbr(abbr) end --- @@ -194,9 +195,9 @@ end -- @see ROLE:GetStartingCredits -- @deprecated function GetStartingCredits(abbr) - local roleData = roles.GetByAbbr(abbr) + local roleData = roles.GetByAbbr(abbr) - return roleData:GetStartingCredits() + return roleData:GetStartingCredits() end --- @@ -207,9 +208,9 @@ end -- @see ROLE:IsShoppingRole -- @deprecated function IsShoppingRole(subrole) - local roleData = roles.GetByIndex(subrole) + local roleData = roles.GetByIndex(subrole) - return roleData:IsShoppingRole() + return roleData:IsShoppingRole() end --- @@ -219,7 +220,7 @@ end -- @see roles.GetShopRoles -- @deprecated function GetShopRoles() - return roles.GetShopRoles() + return roles.GetShopRoles() end --- @@ -230,7 +231,7 @@ end -- @see ROLE:IsBaseRole -- @deprecated function IsBaseRole(roleData) - return roleData:IsBaseRole() + return roleData:IsBaseRole() end --- @@ -241,22 +242,21 @@ end -- @see ROLE:GetBaseRole -- @deprecated function GetBaseRole(subrole) - return roles.GetByIndex(subrole):GetBaseRole() + return roles.GetByIndex(subrole):GetBaseRole() end if SERVER then - - --- - -- Checks whether a role is able to get selected (and maybe assigned to a @{Player}) if the round starts - -- @param ROLE roleData - -- @param boolean avoidHook should the @{hook.TTT2RoleNotSelectable} hook be ignored? - -- @return boolean - -- @realm server - -- @see ROLE:IsSelectable - -- @deprecated - function IsRoleSelectable(roleData, avoidHook) - return roleData:IsSelectable(avoidHook) - end + --- + -- Checks whether a role is able to get selected (and maybe assigned to a @{Player}) if the round starts + -- @param ROLE roleData + -- @param boolean avoidHook should the @{hook.TTT2RoleNotSelectable} hook be ignored? + -- @return boolean + -- @realm server + -- @see ROLE:IsSelectable + -- @deprecated + function IsRoleSelectable(roleData, avoidHook) + return roleData:IsSelectable(avoidHook) + end end --- @@ -267,9 +267,9 @@ end -- @see ROLE:GetSubRoles -- @deprecated function GetSubRoles(subrole) - local roleData = roles.GetByIndex(subrole) + local roleData = roles.GetByIndex(subrole) - return roleData:GetSubRoles() + return roleData:GetSubRoles() end --- @@ -280,7 +280,7 @@ end -- @see roles.GetDefaultTeamRole -- @deprecated function GetDefaultTeamRole(team) - return roles.GetDefaultTeamRole(team) + return roles.GetDefaultTeamRole(team) end --- @@ -291,7 +291,7 @@ end -- @see roles.GetDefaultTeamRoles -- @deprecated function GetDefaultTeamRoles(team) - return roles.GetDefaultTeamRoles(team) + return roles.GetDefaultTeamRoles(team) end --- @@ -302,7 +302,7 @@ end -- @see roles.GetTeamMembers -- @deprecated function GetTeamMembers(team) - return roles.GetTeamMembers(team) + return roles.GetTeamMembers(team) end --- @@ -312,7 +312,7 @@ end -- @see roles.GetWinTeams -- @deprecated function GetWinTeams() - return roles.GetWinTeams() + return roles.GetWinTeams() end --- @@ -322,7 +322,7 @@ end -- @see roles.GetAvailableTeams -- @deprecated function GetAvailableTeams() - return roles.GetAvailableTeams() + return roles.GetAvailableTeams() end --- @@ -332,7 +332,7 @@ end -- @see roles.GetSortedRoles -- @deprecated function GetSortedRoles() - return roles.GetSortedRoles() + return roles.GetSortedRoles() end --- @@ -340,7 +340,7 @@ end -- @return table -- @realm shared function GetActiveRoles() - return ACTIVEROLES + return ACTIVEROLES end --- @@ -349,7 +349,7 @@ end -- @return number -- @realm shared function GetActiveRolesCount(rd) - return ACTIVEROLES[rd] or 0 + return ACTIVEROLES[rd] or 0 end --- @@ -358,7 +358,7 @@ end -- @param number count -- @realm shared function SetActiveRolesCount(rd, count) - ACTIVEROLES[rd] = count == 0 and nil or count + ACTIVEROLES[rd] = count == 0 and nil or count end --- @@ -368,16 +368,18 @@ end -- @realm shared -- @deprecated function GetTraitors() - local trs = {} - local plys = player.GetAll() + local trs = {} + local plys = player.GetAll() - for i = 1, #plys do - if not plys[i]:IsTraitor() then continue end + for i = 1, #plys do + if not plys[i]:IsTraitor() then + continue + end - trs[#trs + 1] = plys[i] - end + trs[#trs + 1] = plys[i] + end - return trs + return trs end --- @@ -387,59 +389,61 @@ end -- @realm shared -- @deprecated function CountTraitors() - return #GetTraitors() + return #GetTraitors() end -- TODO move to client file if CLIENT then - local SafeTranslate - - --- - -- Returns an equipment's translation based on the user's language - -- @param string name - -- @param string printName - -- @return string the translated text - -- @realm client - function GetEquipmentTranslation(name, printName) - SafeTranslate = SafeTranslate or LANG.TryTranslation - - local val = printName - local str = SafeTranslate(val) - - if str == val and name then - val = name - str = SafeTranslate(val) - end - - if str == val and printName then - str = printName - end - - return str - end - - --- - -- Sorts an equipment table - -- @param table tbl the equipment table - -- @realm client - function SortEquipmentTable(tbl) - if not tbl or #tbl < 2 then return end - - local _func = function(adata, bdata) - a = adata.id - b = bdata.id - - if tonumber(a) and not tonumber(b) then - return true - elseif tonumber(b) and not tonumber(a) then - return false - else - return a < b - end - end - - table.sort(tbl, _func) - end + local SafeTranslate + + --- + -- Returns an equipment's translation based on the user's language + -- @param string name + -- @param string printName + -- @return string the translated text + -- @realm client + function GetEquipmentTranslation(name, printName) + SafeTranslate = SafeTranslate or LANG.TryTranslation + + local val = printName + local str = SafeTranslate(val) + + if str == val and name then + val = name + str = SafeTranslate(val) + end + + if str == val and printName then + str = printName + end + + return str + end + + --- + -- Sorts an equipment table + -- @param table tbl the equipment table + -- @realm client + function SortEquipmentTable(tbl) + if not tbl or #tbl < 2 then + return + end + + local _func = function(adata, bdata) + a = adata.id + b = bdata.id + + if tonumber(a) and not tonumber(b) then + return true + elseif tonumber(b) and not tonumber(a) then + return false + else + return a < b + end + end + + table.sort(tbl, _func) + end end WIN_NONE = WIN_NONE or 1 @@ -511,6 +515,11 @@ MUTE_TERROR = 1 MUTE_ALL = 2 MUTE_SPEC = 1002 -- TODO why not 3? +-- Drop On Death override types +DROP_ON_DEATH_TYPE_DEFAULT = 0 +DROP_ON_DEATH_TYPE_FORCE = 1 +DROP_ON_DEATH_TYPE_DENY = 2 + COLOR_WHITE = Color(255, 255, 255, 255) COLOR_BLACK = Color(0, 0, 0, 255) COLOR_GREEN = Color(0, 255, 0, 255) @@ -528,12 +537,17 @@ COLOR_OLIVE = Color(100, 100, 0, 255) COLOR_BROWN = Color(70, 45, 10) COLOR_LBROWN = Color(135, 105, 70) COLOR_WARMGRAY = Color(91, 94, 99, 255) +COLOR_GOLD = Color(255, 215, 30) + +-- include independent extensions +include("ttt2/extensions/debug.lua") -- include independent libraries (other extensions might require them) include("ttt2/libraries/pon.lua") -- include extensions include("ttt2/extensions/math.lua") +include("ttt2/extensions/player.lua") include("ttt2/extensions/net.lua") include("ttt2/extensions/sql.lua") include("ttt2/extensions/string.lua") @@ -543,8 +557,11 @@ include("ttt2/extensions/surface.lua") include("ttt2/extensions/draw.lua") include("ttt2/extensions/input.lua") include("ttt2/extensions/cvars.lua") +include("ttt2/extensions/render.lua") -- include libraries +include("ttt2/libraries/none.lua") +include("ttt2/libraries/fastutf8.lua") include("ttt2/libraries/huds.lua") include("ttt2/libraries/hudelements.lua") include("ttt2/libraries/items.lua") @@ -565,10 +582,14 @@ include("ttt2/libraries/thermalvision.lua") include("ttt2/libraries/roles.lua") include("ttt2/libraries/events.lua") include("ttt2/libraries/eventdata.lua") -include("ttt2/libraries/none.lua") include("ttt2/libraries/targetid.lua") include("ttt2/libraries/playermodels.lua") include("ttt2/libraries/entspawnscript.lua") +include("ttt2/libraries/bodysearch.lua") +include("ttt2/libraries/keyhelp.lua") +include("ttt2/libraries/marker_vision.lua") +include("ttt2/libraries/weaponrenderer.lua") +include("ttt2/libraries/game_effects.lua") -- include ttt required files ttt_include("sh_decal") @@ -579,6 +600,10 @@ ttt_include("sh_hudelement_module") ttt_include("sh_equip_items") ttt_include("sh_role_module") ttt_include("sh_item_module") +ttt_include("sh_playerclass") + +-- include files that need all the above +include("ttt2/libraries/migrations.lua") --- -- Returns the equipment's file name @@ -586,7 +611,7 @@ ttt_include("sh_item_module") -- @return string -- @realm shared function GetEquipmentFileName(name) - return string.gsub(string.lower(name), "[%W%s]", "_") -- clean string + return string.gsub(string.lower(name), "[%W%s]", "_") -- clean string end --- @@ -596,9 +621,9 @@ end -- @return string name -- @realm shared function GetEquipmentByName(name) - name = GetEquipmentFileName(name) + name = GetEquipmentFileName(name) - return not items.IsItem(name) and weapons.GetStored(name) or items.GetStored(name), name + return not items.IsItem(name) and weapons.GetStored(name) or items.GetStored(name), name end --- @@ -606,7 +631,7 @@ end -- @return boolean -- @realm shared function DetectiveMode() - return GetGlobalBool("ttt_detective", false) + return GetGlobalBool("ttt_detective", false) end --- @@ -614,7 +639,7 @@ end -- @return boolean -- @realm shared function HasteMode() - return GetGlobalBool("ttt_haste", false) + return GetGlobalBool("ttt_haste", false) end -- Create teams @@ -629,82 +654,24 @@ TEAM_SPEC = TEAM_SPECTATOR -- @return table list of default equipment -- @realm shared function GetDefaultEquipment() - local defaultEquipment = {} - local rlsList = roles.GetList() + local defaultEquipment = {} + local rlsList = roles.GetList() - for i = 1, #rlsList do - local v = rlsList[i] + for i = 1, #rlsList do + local v = rlsList[i] - if not v.defaultEquipment then continue end + if not v.defaultEquipment then + continue + end - defaultEquipment[v.index] = v.defaultEquipment - end + defaultEquipment[v.index] = v.defaultEquipment + end - return defaultEquipment + return defaultEquipment end DefaultEquipment = { - [0] = {}, - [1] = {}, - [2] = {} + [0] = {}, + [1] = {}, + [2] = {}, } - -BUYTABLE = BUYTABLE or {} -TEAMBUYTABLE = TEAMBUYTABLE or {} - ---- --- Checks whether an equipment is buyable --- @param table tbl equipment table --- @param Player player --- @return boolean --- @return string text as an icon --- @return string result or error --- @realm shared -function EquipmentIsBuyable(tbl, ply) - local valPly = IsValid(ply) and ply:IsPlayer() - if not tbl or not valPly then - return false, "X", "error" - end - - local team = ply:GetTeam() - - if not tbl.id then - ErrorNoHalt("[TTT2][ERROR] Missing id in table:", tbl) - PrintTable(tbl) - - return false, "X", "ID error" - end - - if tbl.notBuyable then - return false, "X", "This equipment cannot be bought." - end - - if tbl.minPlayers and tbl.minPlayers > 1 then - local choices = {} - local plys = player.GetAll() - - for i = 1, #plys do - local v = plys[i] - - -- everyone on the forcespec team is in specmode - if not IsValid(v) or v:GetForceSpec() then continue end - - choices[#choices + 1] = v - end - - if #choices < tbl.minPlayers then - return false, " " .. #choices .. " / " .. tbl.minPlayers, "Minimum amount of active players needed." - end - end - - if tbl.globalLimited and BUYTABLE[tbl.id] or team and tbl.teamLimited and TEAMS[team] and not TEAMS[team].alone and TEAMBUYTABLE[team] and TEAMBUYTABLE[team][tbl.id] or tbl.limited and ply:HasBought(tbl.ClassName) then - return false, "X", "This equipment is limited and is already bought." - end - - -- weapon whitelist check - if not tbl.CanBuy[GetShopFallback(ply:GetSubRole())] then - return false, "X", "Your role can't buy this equipment." - end - - return true, "✔", "ok" -end diff --git a/gamemodes/terrortown/gamemode/shared/sh_inventory.lua b/gamemodes/terrortown/gamemode/shared/sh_inventory.lua index a4b9ee3f5..1fab6fd9e 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_inventory.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_inventory.lua @@ -2,93 +2,101 @@ -- @section Inventory if SERVER then - --- - -- @realm server - local maxMeleeSlots = CreateConVar("ttt2_max_melee_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of melee weapons, a player can carry (-1 = infinite)") - - --- - -- @realm server - local maxSecondarySlots = CreateConVar("ttt2_max_secondary_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of secondary weapons, a player can carry (-1 = infinite)") - - --- - -- @realm server - local maxPrimarySlots = CreateConVar("ttt2_max_primary_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of primary weapons, a player can carry (-1 = infinite)") - - --- - -- @realm server - local maxNadeSlots = CreateConVar("ttt2_max_nade_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of grenades, a player can carry (-1 = infinite)") - - --- - -- @realm server - local maxCarrySlots = CreateConVar("ttt2_max_carry_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of carry tools, a player can carry (-1 = infinite)") - - --- - -- @realm server - local maxUnarmedSlots = CreateConVar("ttt2_max_unarmed_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of unarmed slots, a player can have (-1 = infinite)") - - --- - -- @realm server - local maxSpecialSlots = CreateConVar("ttt2_max_special_slots", "2", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of special weapons, a player can carry (-1 = infinite)") - - --- - -- @realm server - local maxExtraSlots = CreateConVar("ttt2_max_extra_slots", "-1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of extra weapons, a player can carry (-1 = infinite)") - - hook.Add("TTT2SyncGlobals", "AddInventoryGlobals", function() - SetGlobalInt(maxMeleeSlots:GetName(), maxMeleeSlots:GetInt()) - SetGlobalInt(maxSecondarySlots:GetName(), maxSecondarySlots:GetInt()) - SetGlobalInt(maxPrimarySlots:GetName(), maxPrimarySlots:GetInt()) - SetGlobalInt(maxNadeSlots:GetName(), maxNadeSlots:GetInt()) - SetGlobalInt(maxCarrySlots:GetName(), maxCarrySlots:GetInt()) - SetGlobalInt(maxUnarmedSlots:GetName(), maxUnarmedSlots:GetInt()) - SetGlobalInt(maxSpecialSlots:GetName(), maxSpecialSlots:GetInt()) - SetGlobalInt(maxExtraSlots:GetName(), maxExtraSlots:GetInt()) - SetGlobalInt("ttt2_max_class_slots", -1) - end) - - cvars.AddChangeCallback(maxMeleeSlots:GetName(), function(name, old, new) - SetGlobalInt(name, tonumber(new)) - end, "TTT2MaxMeleeSlotsChange") - - cvars.AddChangeCallback(maxSecondarySlots:GetName(), function(name, old, new) - SetGlobalInt(name, tonumber(new)) - end, "TTT2MaxSecondarySlotsChange") - - cvars.AddChangeCallback(maxPrimarySlots:GetName(), function(name, old, new) - SetGlobalInt(name, tonumber(new)) - end, "TTT2MaxPrimarySlotsChange") - - cvars.AddChangeCallback(maxNadeSlots:GetName(), function(name, old, new) - SetGlobalInt(name, tonumber(new)) - end, "TTT2MaxNadeSlotsChange") - - cvars.AddChangeCallback(maxCarrySlots:GetName(), function(name, old, new) - SetGlobalInt(name, tonumber(new)) - end, "TTT2MaxCarrySlotsChange") - - cvars.AddChangeCallback(maxUnarmedSlots:GetName(), function(name, old, new) - SetGlobalInt(name, tonumber(new)) - end, "TTT2MaxUnarmedSlotsChange") - - cvars.AddChangeCallback(maxSpecialSlots:GetName(), function(name, old, new) - SetGlobalInt(name, tonumber(new)) - end, "TTT2MaxSpecialSlotsChange") - - cvars.AddChangeCallback(maxExtraSlots:GetName(), function(name, old, new) - SetGlobalInt(name, tonumber(new)) - end, "TTT2MaxExtraSlotsChange") + --- + -- @realm server + -- stylua: ignore + local maxMeleeSlots = CreateConVar("ttt2_max_melee_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of melee weapons, a player can carry (-1 = infinite)") + + --- + -- @realm server + -- stylua: ignore + local maxSecondarySlots = CreateConVar("ttt2_max_secondary_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of secondary weapons, a player can carry (-1 = infinite)") + + --- + -- @realm server + -- stylua: ignore + local maxPrimarySlots = CreateConVar("ttt2_max_primary_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of primary weapons, a player can carry (-1 = infinite)") + + --- + -- @realm server + -- stylua: ignore + local maxNadeSlots = CreateConVar("ttt2_max_nade_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of grenades, a player can carry (-1 = infinite)") + + --- + -- @realm server + -- stylua: ignore + local maxCarrySlots = CreateConVar("ttt2_max_carry_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of carry tools, a player can carry (-1 = infinite)") + + --- + -- @realm server + -- stylua: ignore + local maxUnarmedSlots = CreateConVar("ttt2_max_unarmed_slots", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of unarmed slots, a player can have (-1 = infinite)") + + --- + -- @realm server + -- stylua: ignore + local maxSpecialSlots = CreateConVar("ttt2_max_special_slots", "2", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of special weapons, a player can carry (-1 = infinite)") + + --- + -- @realm server + -- stylua: ignore + local maxExtraSlots = CreateConVar("ttt2_max_extra_slots", "-1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of extra weapons, a player can carry (-1 = infinite)") + + hook.Add("TTT2SyncGlobals", "AddInventoryGlobals", function() + SetGlobalInt(maxMeleeSlots:GetName(), maxMeleeSlots:GetInt()) + SetGlobalInt(maxSecondarySlots:GetName(), maxSecondarySlots:GetInt()) + SetGlobalInt(maxPrimarySlots:GetName(), maxPrimarySlots:GetInt()) + SetGlobalInt(maxNadeSlots:GetName(), maxNadeSlots:GetInt()) + SetGlobalInt(maxCarrySlots:GetName(), maxCarrySlots:GetInt()) + SetGlobalInt(maxUnarmedSlots:GetName(), maxUnarmedSlots:GetInt()) + SetGlobalInt(maxSpecialSlots:GetName(), maxSpecialSlots:GetInt()) + SetGlobalInt(maxExtraSlots:GetName(), maxExtraSlots:GetInt()) + SetGlobalInt("ttt2_max_class_slots", -1) + end) + + cvars.AddChangeCallback(maxMeleeSlots:GetName(), function(name, old, new) + SetGlobalInt(name, tonumber(new)) + end, "TTT2MaxMeleeSlotsChange") + + cvars.AddChangeCallback(maxSecondarySlots:GetName(), function(name, old, new) + SetGlobalInt(name, tonumber(new)) + end, "TTT2MaxSecondarySlotsChange") + + cvars.AddChangeCallback(maxPrimarySlots:GetName(), function(name, old, new) + SetGlobalInt(name, tonumber(new)) + end, "TTT2MaxPrimarySlotsChange") + + cvars.AddChangeCallback(maxNadeSlots:GetName(), function(name, old, new) + SetGlobalInt(name, tonumber(new)) + end, "TTT2MaxNadeSlotsChange") + + cvars.AddChangeCallback(maxCarrySlots:GetName(), function(name, old, new) + SetGlobalInt(name, tonumber(new)) + end, "TTT2MaxCarrySlotsChange") + + cvars.AddChangeCallback(maxUnarmedSlots:GetName(), function(name, old, new) + SetGlobalInt(name, tonumber(new)) + end, "TTT2MaxUnarmedSlotsChange") + + cvars.AddChangeCallback(maxSpecialSlots:GetName(), function(name, old, new) + SetGlobalInt(name, tonumber(new)) + end, "TTT2MaxSpecialSlotsChange") + + cvars.AddChangeCallback(maxExtraSlots:GetName(), function(name, old, new) + SetGlobalInt(name, tonumber(new)) + end, "TTT2MaxExtraSlotsChange") end ORDERED_SLOT_TABLE = { - [WEAPON_MELEE] = "ttt2_max_melee_slots", - [WEAPON_PISTOL] = "ttt2_max_secondary_slots", - [WEAPON_HEAVY] = "ttt2_max_primary_slots", - [WEAPON_NADE] = "ttt2_max_nade_slots", - [WEAPON_CARRY] = "ttt2_max_carry_slots", - [WEAPON_UNARMED] = "ttt2_max_unarmed_slots", - [WEAPON_SPECIAL] = "ttt2_max_special_slots", - [WEAPON_EXTRA] = "ttt2_max_extra_slots", - [WEAPON_CLASS] = "ttt2_max_class_slots" + [WEAPON_MELEE] = "ttt2_max_melee_slots", + [WEAPON_PISTOL] = "ttt2_max_secondary_slots", + [WEAPON_HEAVY] = "ttt2_max_primary_slots", + [WEAPON_NADE] = "ttt2_max_nade_slots", + [WEAPON_CARRY] = "ttt2_max_carry_slots", + [WEAPON_UNARMED] = "ttt2_max_unarmed_slots", + [WEAPON_SPECIAL] = "ttt2_max_special_slots", + [WEAPON_EXTRA] = "ttt2_max_extra_slots", + [WEAPON_CLASS] = "ttt2_max_class_slots", } --- @@ -97,11 +105,11 @@ ORDERED_SLOT_TABLE = { -- @return number valid kind -- @realm shared function MakeKindValid(kind) - if not kind or kind > WEAPON_CLASS or kind < WEAPON_MELEE then - return WEAPON_EXTRA - else - return kind - end + if not kind or kind > WEAPON_CLASS or kind < WEAPON_MELEE then + return WEAPON_EXTRA + else + return kind + end end --- @@ -111,9 +119,11 @@ end -- @param Player ply -- @realm shared function CleanupInventoryIfDirty(ply) - if ply.inventory and not ply.refresh_inventory_cache then return end + if ply.inventory and not ply.refresh_inventory_cache then + return + end - CleanupInventory(ply) + CleanupInventory(ply) end --- @@ -121,31 +131,35 @@ end -- @param Player ply -- @realm shared function CleanupInventory(ply) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply.refresh_inventory_cache = false - ply.inventory = {} + ply.refresh_inventory_cache = false + ply.inventory = {} - for k in pairs(ORDERED_SLOT_TABLE) do - ply.inventory[k] = {} - end + for k in pairs(ORDERED_SLOT_TABLE) do + ply.inventory[k] = {} + end - -- add weapons which are already in inventory - local weaponsInInventory = 0 - local weps = ply:GetWeapons() + -- add weapons which are already in inventory + local weaponsInInventory = 0 + local weps = ply:GetWeapons() - for i = 1, #weps do - if not weps[i].Kind then continue end + for i = 1, #weps do + if not weps[i].Kind then + continue + end - AddWeaponToInventory(ply, weps[i]) + AddWeaponToInventory(ply, weps[i]) - weaponsInInventory = weaponsInInventory + 1 - end + weaponsInInventory = weaponsInInventory + 1 + end - -- no valid weapons found (try again) - if weaponsInInventory == 0 then - ply.refresh_inventory_cache = true - end + -- no valid weapons found (try again) + if weaponsInInventory == 0 then + ply.refresh_inventory_cache = true + end end --- @@ -155,16 +169,16 @@ end -- @return boolean -- @realm shared function InventorySlotFree(ply, kind) - if not IsValid(ply) then - return false - end + if not IsValid(ply) then + return false + end - CleanupInventoryIfDirty(ply) + CleanupInventoryIfDirty(ply) - local invSlot = MakeKindValid(kind) - local slotCount = GetGlobalInt(ORDERED_SLOT_TABLE[invSlot]) + local invSlot = MakeKindValid(kind) + local slotCount = GetGlobalInt(ORDERED_SLOT_TABLE[invSlot]) - return slotCount < 0 or #ply.inventory[invSlot] < slotCount + return slotCount < 0 or #ply.inventory[invSlot] < slotCount end SWITCHMODE_PICKUP = 0 @@ -181,61 +195,60 @@ SWITCHMODE_NOSPACE = 3 -- @return boolean The switchmode -- @realm shared function GetBlockingWeapon(ply, wep) - -- start the drop weapon check by checking the active weapon - local activeWeapon = ply:GetActiveWeapon() - local throwWeapon, switchMode - - local tr = util.QuickTrace(ply:GetShootPos(), ply:GetAimVector() * 32, ply) - - -- if there is no room to drop the weapon, the pickup should be prohibited - if tr.HitWorld then - throwWeapon = nil - switchMode = SWITCHMODE_NOSPACE - - -- if the player already has this weapon class, the weapon has to be dropped - elseif ply:HasWeapon(WEPS.GetClass(wep)) then - throwWeapon = ply:GetWeapon(WEPS.GetClass(wep)) - switchMode = SWITCHMODE_SWITCH - - -- if the player has a slot free while also not yet having this weapon class - elseif InventorySlotFree(ply, wep.Kind) then - throwWeapon = nil - switchMode = SWITCHMODE_PICKUP - - -- if the player has already a weapon with the same class selected - drop this one - elseif IsValid(activeWeapon) and activeWeapon.AllowDrop and activeWeapon.Kind == wep.Kind then - throwWeapon = activeWeapon - switchMode = SWITCHMODE_SWITCH - - -- try to find a dropable weapon in the selected slot - else - local weps = ply.inventory[MakeKindValid(wep.Kind)] - switchMode = SWITCHMODE_FULLINV - - -- get droppable weapon from given slot - for i = 1, #weps do - local wep_iter = weps[i] - - -- found a weapon that is allowed to be dropped - if IsValid(wep_iter) and wep_iter.AllowDrop then - throwWeapon = wep_iter - switchMode = SWITCHMODE_SWITCH - - break - end - end - end - - -- now make sure the selected weapon is valid and dropable - if IsValid(throwWeapon) and not throwWeapon.AllowDrop then - throwWeapon = nil - switchMode = SWITCHMODE_FULLINV - end - - return throwWeapon, throwWeapon == activeWeapon, switchMode + -- start the drop weapon check by checking the active weapon + local activeWeapon = ply:GetActiveWeapon() + local throwWeapon, switchMode + + local tr = util.QuickTrace(ply:GetShootPos(), ply:GetAimVector() * 32, ply) + + -- if there is no room to drop the weapon, the pickup should be prohibited + if tr.HitWorld then + throwWeapon = nil + switchMode = SWITCHMODE_NOSPACE + + -- if the player already has this weapon class, the weapon has to be dropped + elseif ply:HasWeapon(WEPS.GetClass(wep)) then + throwWeapon = ply:GetWeapon(WEPS.GetClass(wep)) + switchMode = SWITCHMODE_SWITCH + + -- if the player has a slot free while also not yet having this weapon class + elseif InventorySlotFree(ply, wep.Kind) then + throwWeapon = nil + switchMode = SWITCHMODE_PICKUP + + -- if the player has already a weapon with the same class selected - drop this one + elseif IsValid(activeWeapon) and activeWeapon.AllowDrop and activeWeapon.Kind == wep.Kind then + throwWeapon = activeWeapon + switchMode = SWITCHMODE_SWITCH + + -- try to find a dropable weapon in the selected slot + else + local weps = ply.inventory[MakeKindValid(wep.Kind)] + switchMode = SWITCHMODE_FULLINV + + -- get droppable weapon from given slot + for i = 1, #weps do + local wep_iter = weps[i] + + -- found a weapon that is allowed to be dropped + if IsValid(wep_iter) and wep_iter.AllowDrop then + throwWeapon = wep_iter + switchMode = SWITCHMODE_SWITCH + + break + end + end + end + + -- now make sure the selected weapon is valid and dropable + if IsValid(throwWeapon) and not throwWeapon.AllowDrop then + throwWeapon = nil + switchMode = SWITCHMODE_FULLINV + end + + return throwWeapon, throwWeapon == activeWeapon, switchMode end - --- -- Adds a @{Weapon} into the Inventory -- @param Player ply @@ -243,17 +256,17 @@ end -- @return boolean whether it was successful -- @realm shared function AddWeaponToInventory(ply, wep) - if not IsValid(ply) then - return false - end + if not IsValid(ply) then + return false + end - CleanupInventoryIfDirty(ply) + CleanupInventoryIfDirty(ply) - local invSlot = MakeKindValid(wep.Kind) + local invSlot = MakeKindValid(wep.Kind) - ply.inventory[invSlot][#ply.inventory[invSlot] + 1] = wep + ply.inventory[invSlot][#ply.inventory[invSlot] + 1] = wep - return true + return true end --- @@ -263,15 +276,15 @@ end -- @return boolean whether it was successful -- @realm shared function RemoveWeaponFromInventory(ply, wep) - if not IsValid(ply) then - return false - end + if not IsValid(ply) then + return false + end - CleanupInventoryIfDirty(ply) + CleanupInventoryIfDirty(ply) - local invSlot = MakeKindValid(wep.Kind) + local invSlot = MakeKindValid(wep.Kind) - table.RemoveByValue(ply.inventory[invSlot], wep) + table.RemoveByValue(ply.inventory[invSlot], wep) - return true + return true end diff --git a/gamemodes/terrortown/gamemode/shared/sh_item_module.lua b/gamemodes/terrortown/gamemode/shared/sh_item_module.lua index 1677efac9..98bc3d9db 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_item_module.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_item_module.lua @@ -6,51 +6,51 @@ local itemsFiles = file.Find(itemsPre .. "*.lua", "LUA") local _, itemsFolders = file.Find(itemsPre .. "*", "LUA") for i = 1, #itemsFiles do - local fl = itemsFiles[i] + local fl = itemsFiles[i] - ITEM = {} + ITEM = {} - include(itemsPre .. fl) + include(itemsPre .. fl) - local cls = string.sub(fl, 0, #fl - 4) + local cls = string.sub(fl, 0, #fl - 4) - items.Register(ITEM, cls) + items.Register(ITEM, cls) - ITEM = nil + ITEM = nil end for i = 1, #itemsFolders do - local folder = itemsFolders[i] + local folder = itemsFolders[i] - ITEM = {} + ITEM = {} - local subFiles = file.Find(itemsPre .. folder .. "/*.lua", "LUA") + local subFiles = file.Find(itemsPre .. folder .. "/*.lua", "LUA") - for k = 1, #subFiles do - local fl = subFiles[k] + for k = 1, #subFiles do + local fl = subFiles[k] - if fl == "init.lua" then - if SERVER then - include(itemsPre .. folder .. "/" .. fl) - end - elseif fl == "cl_init.lua" then - if SERVER then - AddCSLuaFile(itemsPre .. folder .. "/" .. fl) - else - include(itemsPre .. folder .. "/" .. fl) - end - else - if SERVER and fl == "shared.lua" then - AddCSLuaFile(itemsPre .. folder .. "/" .. fl) - end + if fl == "init.lua" then + if SERVER then + include(itemsPre .. folder .. "/" .. fl) + end + elseif fl == "cl_init.lua" then + if SERVER then + AddCSLuaFile(itemsPre .. folder .. "/" .. fl) + else + include(itemsPre .. folder .. "/" .. fl) + end + else + if SERVER and fl == "shared.lua" then + AddCSLuaFile(itemsPre .. folder .. "/" .. fl) + end - include(itemsPre .. folder .. "/" .. fl) - end - end + include(itemsPre .. folder .. "/" .. fl) + end + end - items.Register(ITEM, folder) + items.Register(ITEM, folder) - ITEM = nil + ITEM = nil end ITEM = oldITEM diff --git a/gamemodes/terrortown/gamemode/shared/sh_lang.lua b/gamemodes/terrortown/gamemode/shared/sh_lang.lua index 53bdcb2f8..e88b7ceef 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_lang.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_lang.lua @@ -4,7 +4,9 @@ -- tbl is first created here on both server and client -- could make it a module but meh -if LANG then return end +if LANG then + return +end LANG = {} @@ -29,139 +31,143 @@ local pairs = pairs local string = string fileloader.LoadFolder("terrortown/lang/", false, CLIENT_FILE, function(path) - MsgN("Added TTT2 core language file: ", path) + Dev(1, "Added TTT2 core language file: ", path) end) if SERVER then - local count = table.Count - - --- - -- Sends a message to (a) specific target(s) in their selected language - -- @param[opt] number|table|Player arg1 the target(s) that should receive this message - -- @param string arg2 the translation key name - -- @param any arg3 - -- @param any arg4 params - -- @note Can be called as: - -- 1) LANG.Msg(ply, name, params, mode) -- sent to ply - -- 2) LANG.Msg(name, params, mode) -- sent to all - -- 3) LANG.Msg(role, name, params, mode) -- sent to plys with role - -- @realm server - function LANG.Msg(arg1, arg2, arg3, arg4) - if isstring(arg1) then - LANG.ProcessMsg(nil, arg1, arg2, arg3) - elseif isnumber(arg1) then - LANG.ProcessMsg(GetRoleChatFilter(arg1), arg2, arg3, arg4) - else - LANG.ProcessMsg(arg1, arg2, arg3, arg4) - end - end - - --- - -- Sends a message to (a) specific target(s) in their selected language - -- @param table|Player send_to the target(s) that should receive this message - -- @param string name the translation key name - -- @param any params params - -- @param number mode [MSG_MSTACK_ROLE, MSG_MSTACK_WARN, MSG_MSTACK_PLAIN, - -- MSG_CHAT_ROLE, MSG_CHAT_WARN, MSG_CHAT_PLAIN, MSG_CONSOLE] - -- @realm server - -- @internal - function LANG.ProcessMsg(send_to, name, params, mode) - -- don't want to send to null ents, but can't just IsValid send_to because - -- it may be a recipientfilter, so type check first - if type(send_to) == "Player" and not IsValid(send_to) then return end - - -- make mode valid, use MSG_MSTACK_PLAIN as default since it was always this way - mode = mode or MSG_MSTACK_PLAIN - - -- number of keyval param pairs to send - local c = params and count(params) or 0 - - net.Start("TTT_LangMsg") - net.WriteString(name) - net.WriteUInt(mode, MSG_MODE_BITS) - net.WriteUInt(c, 8) - - if c > 0 then - for k, v in pairs(params) do - - -- assume keys are strings, but vals may be numbers - net.WriteString(k) - net.WriteString(tostring(v)) - end - end - - if send_to then - net.Send(send_to) - else - net.Broadcast() - end - end - - --- - -- Sends a message to all players in their selected language - -- @param string name the translation key name - -- @param any params params - -- @param number mode [MSG_MSTACK_ROLE, MSG_MSTACK_WARN, MSG_MSTACK_PLAIN, - -- MSG_CHAT_ROLE, MSG_CHAT_WARN, MSG_CHAT_PLAIN, MSG_CONSOLE] - -- @realm server - -- @internal - function LANG.MsgAll(name, params, mode) - LANG.Msg(nil, name, params, mode) - end - - --- - -- @realm server - local cv_ttt_lang_serverdefault = CreateConVar("ttt_lang_serverdefault", "en", FCVAR_ARCHIVE) - - local function ServerLangRequest(ply, cmd, args) - if not IsValid(ply) then return end - - net.Start("TTT_ServerLang") - net.WriteString(cv_ttt_lang_serverdefault:GetString()) - net.Send(ply) - end - - concommand.Add("_ttt_request_serverlang", ServerLangRequest) + local count = table.Count + + --- + -- Sends a message to (a) specific target(s) in their selected language + -- @param[opt] number|table|Player arg1 the target(s) that should receive this message + -- @param string arg2 the translation key name + -- @param any arg3 + -- @param any arg4 params + -- @note Can be called as: + -- 1) LANG.Msg(ply, name, params, mode) -- sent to ply + -- 2) LANG.Msg(name, params, mode) -- sent to all + -- 3) LANG.Msg(role, name, params, mode) -- sent to plys with role + -- @realm server + function LANG.Msg(arg1, arg2, arg3, arg4) + if isstring(arg1) then + LANG.ProcessMsg(nil, arg1, arg2, arg3) + elseif isnumber(arg1) then + LANG.ProcessMsg(GetRoleChatFilter(arg1), arg2, arg3, arg4) + else + LANG.ProcessMsg(arg1, arg2, arg3, arg4) + end + end + + --- + -- Sends a message to (a) specific target(s) in their selected language + -- @param table|Player send_to the target(s) that should receive this message + -- @param string name the translation key name + -- @param any params params + -- @param number mode [MSG_MSTACK_ROLE, MSG_MSTACK_WARN, MSG_MSTACK_PLAIN, + -- MSG_CHAT_ROLE, MSG_CHAT_WARN, MSG_CHAT_PLAIN, MSG_CONSOLE] + -- @realm server + -- @internal + function LANG.ProcessMsg(send_to, name, params, mode) + -- don't want to send to null ents, but can't just IsValid send_to because + -- it may be a recipientfilter, so type check first + if type(send_to) == "Player" and not IsValid(send_to) then + return + end + + -- make mode valid, use MSG_MSTACK_PLAIN as default since it was always this way + mode = mode or MSG_MSTACK_PLAIN + + -- number of keyval param pairs to send + local c = params and count(params) or 0 + + net.Start("TTT_LangMsg") + net.WriteString(name) + net.WriteUInt(mode, MSG_MODE_BITS) + net.WriteUInt(c, 8) + + if c > 0 then + for k, v in pairs(params) do + -- assume keys are strings, but vals may be numbers + net.WriteString(k) + net.WriteString(tostring(v)) + end + end + + if send_to then + net.Send(send_to) + else + net.Broadcast() + end + end + + --- + -- Sends a message to all players in their selected language + -- @param string name the translation key name + -- @param any params params + -- @param number mode [MSG_MSTACK_ROLE, MSG_MSTACK_WARN, MSG_MSTACK_PLAIN, + -- MSG_CHAT_ROLE, MSG_CHAT_WARN, MSG_CHAT_PLAIN, MSG_CONSOLE] + -- @realm server + -- @internal + function LANG.MsgAll(name, params, mode) + LANG.Msg(nil, name, params, mode) + end + + --- + -- @realm server + -- stylua: ignore + local cv_ttt_lang_serverdefault = CreateConVar("ttt_lang_serverdefault", "en", FCVAR_ARCHIVE) + + local function ServerLangRequest(ply, cmd, args) + if not IsValid(ply) then + return + end + + net.Start("TTT_ServerLang") + net.WriteString(cv_ttt_lang_serverdefault:GetString()) + net.Send(ply) + end + + concommand.Add("_ttt_request_serverlang", ServerLangRequest) else -- CLIENT - local function RecvMsg() - local name = net.ReadString() - local mode = net.ReadUInt(MSG_MODE_BITS) - local c = net.ReadUInt(8) + local function RecvMsg() + local name = net.ReadString() + local mode = net.ReadUInt(MSG_MODE_BITS) + local c = net.ReadUInt(8) - local params + local params - if c > 0 then - params = {} + if c > 0 then + params = {} - for i = 1, c do - params[net.ReadString()] = net.ReadString() - end - end + for i = 1, c do + params[net.ReadString()] = net.ReadString() + end + end - -- this is LANG.ProcessMsg - LANG.Msg(name, params, mode) - end - net.Receive("TTT_LangMsg", RecvMsg) + -- this is LANG.ProcessMsg + LANG.Msg(name, params, mode) + end + net.Receive("TTT_LangMsg", RecvMsg) - -- maybe this really strange coding style has a reason ... - LANG.Msg = LANG.ProcessMsg + -- maybe this really strange coding style has a reason ... + LANG.Msg = LANG.ProcessMsg - local function RecvServerLang() - local lang_name = net.ReadString() + local function RecvServerLang() + local lang_name = net.ReadString() - lang_name = LANG.GetNameFromAlias(lang_name) + lang_name = LANG.GetNameFromAlias(lang_name) - if LANG.Strings[lang_name] then - if LANG.IsServerDefault(GetConVar("ttt_language"):GetString()) then - LANG.SetActiveLanguage(lang_name) - end + if LANG.Strings[lang_name] then + if LANG.IsServerDefault(GetConVar("ttt_language"):GetString()) then + LANG.SetActiveLanguage(lang_name) + end - LANG.ServerLanguage = lang_name + LANG.ServerLanguage = lang_name - print("Server default language is: ", lang_name) - end - end - net.Receive("TTT_ServerLang", RecvServerLang) + Dev(1, "Server default language is: ", lang_name) + end + end + net.Receive("TTT_ServerLang", RecvServerLang) end --- @@ -173,7 +179,7 @@ end -- @return string transformed name -- @realm shared function LANG.NameParam(name) - return "LID\t" .. name + return "LID\t" .. name end LANG.Param = LANG.NameParam @@ -184,5 +190,5 @@ LANG.Param = LANG.NameParam -- @return table list of matched params -- @realm shared function LANG.GetNameParam(str) - return string.match(str, "^LID\t([%w_]+)$") + return string.match(str, "^LID\t([%w_]+)$") end diff --git a/gamemodes/terrortown/gamemode/shared/sh_main.lua b/gamemodes/terrortown/gamemode/shared/sh_main.lua index 45063e73b..6052568a0 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_main.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_main.lua @@ -4,7 +4,6 @@ local IsValid = IsValid local hook = hook local team = team -local UpdateSprint = UpdateSprint local MAX_DROWN_TIME = 8 @@ -12,139 +11,190 @@ local sneakSpeedSquared = math.pow(150, 2) TTT2ShopFallbackInitialized = false +local callbackIdentifier = "TTT2RegisteredSWEPCallback" + +--- +-- Add callback for equipment and insert changes in the given equipmentTable +-- @param string name the database-name of the equipment +-- @param table equipmentTable the table to insert changes to +-- @realm shared +local function AddCallbacks(name, equipmentTable) + -- Make sure that on hot reloads old callbacks are removed before adding the new one + database.RemoveChangeCallback(ShopEditor.accessName, name, nil, callbackIdentifier) + database.AddChangeCallback( + ShopEditor.accessName, + name, + nil, + function(accessName, itemName, key, oldValue, newValue) + if not istable(equipmentTable) then + database.RemoveChangeCallback(ShopEditor.accessName, name, nil, callbackIdentifier) + + return + end + + equipmentTable[key] = newValue + end, + callbackIdentifier + ) +end + --- -- Initializes the equipment with necessary data for ttt2 -- Also handles hotreload when called with the `PreRegisterSWEP` hook -- @param table equipment equipment to register -- @param string name equipment name --- @param bool initialize should the real weapon table be initialized and not hotreloaded? +-- @param boolean initialize should the real weapon table be initialized and not hotreloaded? -- @internal -- @realm shared local function TTT2RegisterSWEP(equipment, name, initialize) - local doHotreload = TTT2ShopFallbackInitialized - - -- Handle first initialization or do hotreload - if initialize then - equipment = weapons.GetStored(name) - doHotreload = false - elseif not doHotreload then - return - end - - if doHotreload then - MsgN("[TTT2] Trying to hotreload ", name, " .") - end - - -- Initialize Equipment - AddEquipmentKeyValues(equipment, name) - ShopEditor.InitDefaultData(equipment) - - if doHotreload then - local oldSWEP = weapons.GetStored(name) - - -- Keep custom changed data from the old SWEP if hotReloadableKeys are given - if istable(equipment.HotReloadableKeys) and #equipment.HotReloadableKeys > 0 and oldSWEP then - MsgN("[TTT2] Hotreloading ", #equipment.HotReloadableKeys, " given Keys from old SWEP-file.") - - for _, keys in pairs(equipment.HotReloadableKeys) do - local eqKeyField = equipment - local oldKeyField = oldSWEP - local keyString = "" - - -- If only a single key is given, not a table, convert it - if isstring(keys) then - keys = {keys} - end - - if not istable(keys) then continue end - - local continueOuterLoop = false - local counter = 0 - local saveKey = keys[1] - - for _, key in pairs(keys) do - if not isstring(key) or not oldKeyField then - continueOuterLoop = true - - break - end - - - keyString = keyString .. "." .. key - counter = counter + 1 - eqKeyField[key] = eqKeyField[key] or {} - oldKeyField = oldKeyField[key] - - -- To keep eqKeyField as reference check for tables or create one - if counter < #keys and not istable(eqKeyField[key]) then - eqKeyField[key] = {} - elseif counter == #keys then - saveKey = key - - break - end - - eqKeyField = eqKeyField[key] - end - - if continueOuterLoop then continue end - - MsgN("[TTT2] Overwriting SWEP", keyString, " = ", tostring(eqKeyField[saveKey]), " with ", tostring(oldKeyField)) - - eqKeyField[saveKey] = oldKeyField - end - end - - ResetDefaultEquipment(equipment) - end - - if SERVER and sql.CreateSqlTable("ttt2_items", ShopEditor.savingKeys) then - local loaded, changed = sql.Load("ttt2_items", name, equipment, ShopEditor.savingKeys) - - if not loaded then - sql.Init("ttt2_items", name, equipment, ShopEditor.savingKeys) - elseif changed then - local counter = #CHANGED_EQUIPMENT + 1 - - if TTT2ShopFallbackInitialized then - for i = 1, #CHANGED_EQUIPMENT do - if CHANGED_EQUIPMENT[i][1] == name then - counter = i - - break - end - end - end - - CHANGED_EQUIPMENT[counter] = {name, equipment} - end - end - - if not doHotreload then return end - - -- initialize fallback shops - InitFallbackShops() - - if SERVER then - LoadShopsEquipment() - - -- Force Precache Models - if equipment.WorldModel then - util.PrecacheModel(equipment.WorldModel) - end - - if equipment.ViewModel then - util.PrecacheModel(equipment.ViewModel) - end - elseif CLIENT then - TTT2CacheEquipMaterials(equipment) - net.Start("TTT2SyncShopsWithServer") - net.SendToServer() - end - - MsgN("[TTT2] Hotreloading ", name, " was successful.") - - return + local doHotreload = TTT2ShopFallbackInitialized + + -- Handle first initialization or do hotreload + if initialize then + equipment = weapons.GetStored(name) + doHotreload = false + elseif not doHotreload then + return + end + + if doHotreload then + Dev(1, "[TTT2] Trying to hotreload ", name, " .") + end + + -- Initialize Equipment + AddEquipmentKeyValues(equipment, name) + ShopEditor.InitDefaultData(equipment) + + if doHotreload then + local oldSWEP = weapons.GetStored(name) + + -- Keep custom changed data from the old SWEP if hotReloadableKeys are given + if + istable(equipment.HotReloadableKeys) + and #equipment.HotReloadableKeys > 0 + and oldSWEP + then + Dev( + 1, + "[TTT2] Hotreloading ", + #equipment.HotReloadableKeys, + " given Keys from old SWEP-file." + ) + + for _, keys in pairs(equipment.HotReloadableKeys) do + local eqKeyField = equipment + local oldKeyField = oldSWEP + local keyString = "" + + -- If only a single key is given, not a table, convert it + if isstring(keys) then + keys = { keys } + end + + if not istable(keys) then + continue + end + + local continueOuterLoop = false + local counter = 0 + local saveKey = keys[1] + + for _, key in pairs(keys) do + if not isstring(key) or not oldKeyField then + continueOuterLoop = true + + break + end + + keyString = keyString .. "." .. key + counter = counter + 1 + eqKeyField[key] = eqKeyField[key] or {} + oldKeyField = oldKeyField[key] + + -- To keep eqKeyField as reference check for tables or create one + if counter < #keys and not istable(eqKeyField[key]) then + eqKeyField[key] = {} + elseif counter == #keys then + saveKey = key + + break + end + + eqKeyField = eqKeyField[key] + end + + if continueOuterLoop then + continue + end + + Dev( + 1, + "[TTT2] Overwriting SWEP", + keyString, + " = ", + tostring(eqKeyField[saveKey]), + " with ", + tostring(oldKeyField) + ) + + eqKeyField[saveKey] = oldKeyField + end + end + + ResetDefaultEquipment(equipment) + end + + if + SERVER + and database.Register( + ShopEditor.sqlItemsName, + ShopEditor.accessName, + ShopEditor.savingKeys, + TTT2_DATABASE_ACCESS_ANY + ) + then + database.SetDefaultValuesFromItem(ShopEditor.accessName, name, equipment) + local databaseExists, itemTable = database.GetValue(ShopEditor.accessName, name) + if databaseExists then + table.Merge(equipment, itemTable) + end + AddCallbacks(name, equipment) + elseif CLIENT then + database.GetValue(ShopEditor.accessName, name, nil, function(databaseExists, itemTable) + if databaseExists then + table.Merge(equipment, itemTable) + AddCallbacks(name, equipment) + end + end) + end + + if not doHotreload then + return + end + + -- initialize fallback shops + InitFallbackShops() + + if SERVER then + LoadShopsEquipment() + + -- Force Precache Models + if equipment.WorldModel then + util.PrecacheModel(equipment.WorldModel) + end + + if equipment.ViewModel then + util.PrecacheModel(equipment.ViewModel) + end + elseif CLIENT then + ShopEditor.BuildValidEquipmentCache() + net.Start("TTT2SyncShopsWithServer") + net.SendToServer() + end + + Dev(1, "[TTT2] Hotreloading ", name, " was successful.") + + return end --- @@ -159,34 +209,33 @@ hook.Add("PreRegisterSWEP", "TTT2RegisterSWEP", TTT2RegisterSWEP) -- @hook -- @realm shared function GM:TTT2Initialize() - -- load all roles - roles.OnLoaded() + -- load all roles + roles.OnLoaded() - --- - -- @realm shared - hook.Run("TTT2RolesLoaded") + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2RolesLoaded") - --- - -- @realm shared - hook.Run("TTT2BaseRoleInit") + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2BaseRoleInit") - DefaultEquipment = GetDefaultEquipment() + DefaultEquipment = GetDefaultEquipment() end --- +-- Called when TTT2 is finished loading on startup and hotreload. -- @hook -- @realm shared -function GM:TTT2FinishedLoading() - -end +function GM:TTT2FinishedLoading() end --- -- Called after everything in the @{GM:Initialize} hook is called. -- @hook -- @realm shared -function GM:PostInitialize() - -end +function GM:PostInitialize() end --- -- Create teams @@ -194,12 +243,12 @@ end -- @internal -- @realm shared function GM:CreateTeams() - team.SetUp(TEAM_TERROR, "Terrorists", Color(0, 200, 0, 255), false) - team.SetUp(TEAM_SPEC, "Spectators", Color(200, 200, 0, 255), true) + team.SetUp(TEAM_TERROR, "Terrorists", Color(0, 200, 0, 255), false) + team.SetUp(TEAM_SPEC, "Spectators", Color(200, 200, 0, 255), true) - -- Not that we use this, but feels good - team.SetSpawnPoint(TEAM_TERROR, "info_player_deathmatch") - team.SetSpawnPoint(TEAM_SPEC, "info_player_deathmatch") + -- Not that we use this, but feels good + team.SetSpawnPoint(TEAM_TERROR, "info_player_deathmatch") + team.SetSpawnPoint(TEAM_SPEC, "info_player_deathmatch") end --- @@ -218,10 +267,10 @@ end -- @realm shared -- @ref https://wiki.facepunch.com/gmod/GM:PlayerFootstep function GM:PlayerFootstep(ply, pos, foot, sound, volume, rf) - if IsValid(ply) and (ply:GetVelocity():LengthSqr() < sneakSpeedSquared or ply:IsSpec()) then - -- do not play anything, just prevent normal sounds from playing - return true - end + if IsValid(ply) and (ply:GetVelocity():LengthSqr() < sneakSpeedSquared or ply:IsSpec()) then + -- do not play anything, just prevent normal sounds from playing + return true + end end --- @@ -233,59 +282,62 @@ end -- This hook is called after @{GM:PlayerTick}. -- See Game Movement for an explanation on the move system. -- @param Player ply The player --- @param MoveData moveData Movement information +-- @param CMoveData moveData Movement information -- @predicted -- @hook -- @realm shared -- @ref https://wiki.facepunch.com/gmod/GM:Move function GM:Move(ply, moveData) - SPEED:HandleSpeedCalculation(ply, moveData) + SPEED:HandleSpeedCalculation(ply, moveData) - local mul = ply:GetSpeedMultiplier() + local mul = ply:GetSpeedMultiplier() - if ply.sprintMultiplier and (ply.sprintProgress or 0) > 0 then - local sprintMultiplierModifier = {1} + mul = mul * SPRINT:HandleSpeedMultiplierCalculation(ply) - --- - -- @realm shared - hook.Run("TTT2PlayerSprintMultiplier", ply, sprintMultiplierModifier) - - mul = mul * ply.sprintMultiplier * sprintMultiplierModifier[1] - end + moveData:SetMaxClientSpeed(moveData:GetMaxClientSpeed() * mul) + moveData:SetMaxSpeed(moveData:GetMaxSpeed() * mul) +end - moveData:SetMaxClientSpeed(moveData:GetMaxClientSpeed() * mul) - moveData:SetMaxSpeed(moveData:GetMaxSpeed() * mul) +-- @param Player ply The player +-- @param MoveData moveData Movement information +-- @predicted +-- @hook +-- @realm shared +-- @ref https://wiki.facepunch.com/gmod/GM:FinishMove +function GM:FinishMove(ply, moveData) + SPRINT:HandleStaminaCalculation(ply) end local ttt_playercolors = { - all = { - COLOR_WHITE, - COLOR_BLACK, - COLOR_GREEN, - COLOR_DGREEN, - COLOR_RED, - COLOR_YELLOW, - COLOR_LGRAY, - COLOR_BLUE, - COLOR_NAVY, - COLOR_PINK, - COLOR_OLIVE, - COLOR_ORANGE - }, - serious = { - COLOR_WHITE, - COLOR_BLACK, - COLOR_NAVY, - COLOR_LGRAY, - COLOR_DGREEN, - COLOR_OLIVE - } + all = { + COLOR_WHITE, + COLOR_BLACK, + COLOR_GREEN, + COLOR_DGREEN, + COLOR_RED, + COLOR_YELLOW, + COLOR_LGRAY, + COLOR_BLUE, + COLOR_NAVY, + COLOR_PINK, + COLOR_OLIVE, + COLOR_ORANGE, + }, + serious = { + COLOR_WHITE, + COLOR_BLACK, + COLOR_NAVY, + COLOR_LGRAY, + COLOR_DGREEN, + COLOR_OLIVE, + }, } local ttt_playercolors_all_count = #ttt_playercolors.all local ttt_playercolors_serious_count = #ttt_playercolors.serious --- -- @realm shared +-- stylua: ignore local colormode = CreateConVar("ttt_playercolor_mode", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED}) --- @@ -293,19 +345,19 @@ local colormode = CreateConVar("ttt_playercolor_mode", "1", {FCVAR_NOTIFY, FCVAR -- @hook -- @realm shared function GM:TTTPlayerColor(model) - local mode = colormode:GetInt() - - if mode == 1 then - return ttt_playercolors.serious[math.random(ttt_playercolors_serious_count)] - elseif mode == 2 then - return ttt_playercolors.all[math.random(ttt_playercolors_all_count)] - elseif mode == 3 then - -- Full randomness - return Color(math.random(0, 255), math.random(0, 255), math.random(0, 255)) - end - - -- No coloring - return COLOR_WHITE + local mode = colormode:GetInt() + + if mode == 1 then + return ttt_playercolors.serious[math.random(ttt_playercolors_serious_count)] + elseif mode == 2 then + return ttt_playercolors.all[math.random(ttt_playercolors_all_count)] + elseif mode == 3 then + -- Full randomness + return Color(math.random(0, 255), math.random(0, 255), math.random(0, 255)) + end + + -- No coloring + return COLOR_WHITE end --- @@ -318,11 +370,9 @@ end -- @realm shared -- @ref https://wiki.facepunch.com/gmod/GM:Think function GM:Think() - UpdateSprint() - - if CLIENT then - EPOP:Think() - end + if CLIENT then + EPOP:Think() + end end --- @@ -331,10 +381,8 @@ end -- @param Player ply The player whose sprint speed should be changed -- @param table sprintMultiplierModifier The modieable table with the sprint speed multiplier -- @hook --- @realm server -function GM:TTT2PlayerSprintMultiplier(ply, sprintMultiplierModifier) - -end +-- @realm shared +function GM:TTT2PlayerSprintMultiplier(ply, sprintMultiplierModifier) end --- -- A hook that is called whenever the gamemode needs to check if the player is in the superadmin usergroup. @@ -345,7 +393,7 @@ end -- @hook -- @realm shared function GM:TTT2AdminCheck(ply) - return ply:IsSuperAdmin() + return ply:IsSuperAdmin() end -- Drowning and such @@ -358,83 +406,86 @@ local tm, ply, plys -- @realm shared -- @ref https://wiki.facepunch.com/gmod/GM:Tick function GM:Tick() - local client = CLIENT and LocalPlayer() - - if client and not IsValid(client) then return end - - -- three cheers for micro-optimizations - plys = client and {client} or player.GetAll() - - for i = 1, #plys do - ply = plys[i] - tm = ply:Team() - - if tm == TEAM_TERROR and ply:Alive() then - if ply:WaterLevel() == 3 then -- Drowning - if SERVER and ply:IsOnFire() then - ply:Extinguish() - end - - local drowningTime = ply.drowningTime or MAX_DROWN_TIME - - if ply.drowning then - if ply:HasEquipmentItem("item_ttt_nodrowningdmg") then - ply.drowningProgress = MAX_DROWN_TIME - ply.drowning = CurTime() + MAX_DROWN_TIME - else - ply.drowningProgress = math.max(0, (ply.drowning - CurTime()) * (1 / drowningTime)) - end - - if SERVER and ply.drowning < CurTime() then - local dmginfo = DamageInfo() - - dmginfo:SetDamage(15) - dmginfo:SetDamageType(DMG_DROWN) - dmginfo:SetAttacker(game.GetWorld()) - dmginfo:SetInflictor(game.GetWorld()) - dmginfo:SetDamageForce(Vector(0, 0, 1)) - - ply:TakeDamageInfo(dmginfo) - - -- have started drowning properly - ply:StartDrowning(true, 1, drowningTime) - end - elseif SERVER then - ply:StartDrowning(true, drowningTime, drowningTime) - end - elseif SERVER then - ply:StartDrowning(false) - end - - -- Run DNA Scanner think also when it is not deployed - if ply:HasWeapon("weapon_ttt_wtester") then - ply:GetWeapon("weapon_ttt_wtester"):PassiveThink() - end - elseif SERVER and tm == TEAM_SPEC then - if ply.propspec then - PROPSPEC.Recharge(ply) - - if IsValid(ply:GetObserverTarget()) then - ply:SetPos(ply:GetObserverTarget():GetPos()) - end - end - - -- if spectators are alive, ie. they picked spectator mode, then - -- DeathThink doesn't run, so we have to SpecThink here - if ply:Alive() then - self:SpectatorThink(ply) - end - end - end - - if CLIENT then - if client:Alive() and client:Team() ~= TEAM_SPEC then - WSWITCH:Think() - RADIO:StoreTarget() - end - - VOICE.Tick() - end + local client = CLIENT and LocalPlayer() + + if client and not IsValid(client) then + return + end + + -- three cheers for micro-optimizations + plys = client and { client } or player.GetAll() + + for i = 1, #plys do + ply = plys[i] + tm = ply:Team() + + if tm == TEAM_TERROR and ply:Alive() then + if ply:WaterLevel() == 3 then -- Drowning + if SERVER and ply:IsOnFire() then + ply:Extinguish() + end + + local drowningTime = ply.drowningTime or MAX_DROWN_TIME + + if ply.drowning then + if ply:HasEquipmentItem("item_ttt_nodrowningdmg") then + ply.drowningProgress = MAX_DROWN_TIME + ply.drowning = CurTime() + MAX_DROWN_TIME + else + ply.drowningProgress = + math.max(0, (ply.drowning - CurTime()) * (1 / drowningTime)) + end + + if SERVER and ply.drowning < CurTime() then + local dmginfo = DamageInfo() + + dmginfo:SetDamage(15) + dmginfo:SetDamageType(DMG_DROWN) + dmginfo:SetAttacker(game.GetWorld()) + dmginfo:SetInflictor(game.GetWorld()) + dmginfo:SetDamageForce(Vector(0, 0, 1)) + + ply:TakeDamageInfo(dmginfo) + + -- have started drowning properly + ply:StartDrowning(true, 1, drowningTime) + end + elseif SERVER then + ply:StartDrowning(true, drowningTime, drowningTime) + end + elseif SERVER then + ply:StartDrowning(false) + end + + -- Run DNA Scanner think also when it is not deployed + if ply:HasWeapon("weapon_ttt_wtester") then + ply:GetWeapon("weapon_ttt_wtester"):PassiveThink() + end + elseif SERVER and tm == TEAM_SPEC then + if ply.propspec then + PROPSPEC.Recharge(ply) + + if IsValid(ply:GetObserverTarget()) then + ply:SetPos(ply:GetObserverTarget():GetPos()) + end + end + + -- if spectators are alive, ie. they picked spectator mode, then + -- DeathThink doesn't run, so we have to SpecThink here + if ply:Alive() then + self:SpectatorThink(ply) + end + end + end + + if CLIENT then + if client:Alive() and client:Team() ~= TEAM_SPEC then + WSWITCH:Think() + RADIO:StoreTarget() + end + + VOICE.Tick() + end end --- @@ -442,24 +493,19 @@ end -- @hook -- @realm shared function GM:TTTPrepareRound() - BUYTABLE = {} - TEAMBUYTABLE = {} + shop.Reset() end --- -- A hook that is called when the round begins. -- @hook -- @realm shared -function GM:TTTBeginRound() - -end +function GM:TTTBeginRound() end -- A hook that is called when the round ends. -- @hook -- @realm shared -function GM:TTTEndRound() - -end +function GM:TTTEndRound() end --- -- Called right after the map has been cleaned up (usually because game.CleanUpMap was called). @@ -467,96 +513,84 @@ end -- registered. -- @hook -- @realm shared -function GM:TTT2PostCleanupMap() - -end +function GM:TTT2PostCleanupMap() end --- -- This hook is run inside @{GM:InitPostEntity} prior to the initialization of items, -- @hook -- @realm shared -function GM:TTTInitPostEntity() - -end +function GM:TTTInitPostEntity() end --- -- This hook is run inside @{GM:InitPostEntity} after all items are initialized. -- @hook -- @realm shared -function GM:PostInitPostEntity() - -end +function GM:PostInitPostEntity() end --- -- This hook is run on the initialization of the fallback shops. -- @hook -- @realm shared -function GM:InitFallbackShops() - -end +function GM:InitFallbackShops() end --- -- This hook is run after the initialization of the fallback shops. -- @hook -- @realm shared -function GM:LoadedFallbackShops() - -end +function GM:LoadedFallbackShops() end --- -- Called right after all doors are initialized on the map. -- @param table doorsTable A table with the newly registered door entities -- @hook -- @realm shared -function GM:TTT2PostDoorSetup(doorsTable) - -end +function GM:TTT2PostDoorSetup(doorsTable) end -- Called after all roles were loaded, @{ROLE:Preinitialize} and @{ROLE:Initialize} were called -- and their convars were set up. -- @hook -- @realm shared -function GM:TTT2RolesLoaded() - -end +function GM:TTT2RolesLoaded() end -- Called after all roles were loaded, @{ROLE:Preinitialize} and @{ROLE:Initialize} were called -- and their convars were set up. -- @hook -- @realm shared -function GM:TTT2BaseRoleInit() - -end +function GM:TTT2BaseRoleInit() end --- -- Called to register equipment and assign an id. Returns true if it is successfully registered. -- @param table eq the equipment copy to register with an id --- @return bool if the eq is succesfully registered +-- @return boolean if the eq is succesfully registered -- @hook -- @realm shared function GM:TTT2RegisterWeaponID(eq) - if eq.id then return true end + if eq.id then + return true + end - local class = WEPS.GetClass(eq) + local class = WEPS.GetClass(eq) - TTT2RegisterSWEP(eq, class, true) + TTT2RegisterSWEP(eq, class, true) - eq = weapons.Get(class) + eq = weapons.Get(class) - if eq.id then - return true - end + if eq.id then + return true + end - local name = eq.PrintName or class + local name = eq.PrintName or class - if name then - print(name .. " cant be assigned an id.") - else - print("No id could be assigned. Equipment has no name.") - end + if name then + Dev(2, name .. " cant be assigned an id.") + else + Dev(2, "No id could be assigned. Equipment has no name.") + end - ErrorNoHalt("[TTT2][IDCHECK][ERROR] Equipment is invalid after registration attempt and has no id.\n") - PrintTable(eq) + ErrorNoHaltWithStack( + "[TTT2][IDCHECK][ERROR] Equipment is invalid after registration attempt and has no id.\n" + ) + PrintTable(eq) - return false + return false end diff --git a/gamemodes/terrortown/gamemode/shared/sh_marker_vision_element.lua b/gamemodes/terrortown/gamemode/shared/sh_marker_vision_element.lua new file mode 100644 index 000000000..09110e39e --- /dev/null +++ b/gamemodes/terrortown/gamemode/shared/sh_marker_vision_element.lua @@ -0,0 +1,217 @@ +--- +-- This class handles the syncing and tracking of markerVision elements +-- between the server and the client. Each markerVision element has its +-- own instance of this class that contains all the data. +-- @author Mineotopia +-- @class MARKER_VISION_ELEMENT + +MARKER_VISION_ELEMENT = {} +MARKER_VISION_ELEMENT.data = {} + +--- +-- Sets the mark color to the element. This should be set if the automatic +-- color selection based on the VisibleFor flag is not desired. +-- @param Color color The color +-- @realm shared +function MARKER_VISION_ELEMENT:SetColor(color) + self.data.color = color +end + +--- +-- Gets the mark color to the element. Uses the hard defined color if set +-- or the dynamic color based on VisibleFor flag. +-- @return Color The color +-- @realm shared +function MARKER_VISION_ELEMENT:GetColor() + if self.data.color then + return self.data.color + end + + if self.data.visibleFor == VISIBLE_FOR_ALL then + return TEAMS[TEAM_INNOCENT].color + elseif self.data.visibleFor == VISIBLE_FOR_PLAYER then + return TEAMS[TEAM_NONE].color + elseif self.data.visibleFor == VISIBLE_FOR_ROLE then + if IsPlayer(self.data.owner) then + return self.data.owner:GetRoleColor() + else + -- handle static role + return roles.GetByIndex(self.data.owner).color + end + elseif self.data.visibleFor == VISIBLE_FOR_TEAM then + if IsPlayer(self.data.owner) then + return TEAMS[self.data.owner:GetTeam()].color + else + -- handle static team + return TEAMS[self.data.owner].color + end + end + + return COLOR_WHITE +end + +--- +-- Sets the marked entity to the element. +-- @param Entity ent The marked entity +-- @realm shared +function MARKER_VISION_ELEMENT:SetEnt(ent) + self.data.ent = ent +end + +--- +-- Gets the marked entity to the element. +-- @return Entity The marked entity +-- @realm shared +function MARKER_VISION_ELEMENT:GetEnt() + return self.data.ent +end + +--- +-- Sets the unique identifier to the element. +-- @param string identifier The unique identifier +-- @realm shared +function MARKER_VISION_ELEMENT:SetIdentifier(identifier) + self.data.identifier = identifier +end + +--- +-- Gets the unique identifier to the element. +-- @return string The unique identifier +-- @realm shared +function MARKER_VISION_ELEMENT:GetIdentifier() + return self.data.identifier +end + +--- +-- Sets the mark owner to the element. +-- @param number|string|Player owner The owner of the wallhack that takes their ownership with them on +-- role/team change; can also be a team (string) or role (number) if it shouldn't be bound to a player +-- @realm shared +function MARKER_VISION_ELEMENT:SetOwner(owner) + self.data.owner = owner +end + +--- +-- Gets the mark owner to the element. +-- @return Entity The mark owner +-- @realm shared +function MARKER_VISION_ELEMENT:GetOwner() + return self.data.owner +end + +--- +-- Sets the visible for flag to the element. +-- @param number visibleFor Visibility setting: `VISIBLE_FOR_PLAYER`, `VISIBLE_FOR_ROLE`, +-- `VISIBLE_FOR_TEAM`, `VISIBLE_FOR_ALL` +-- @realm shared +function MARKER_VISION_ELEMENT:SetVisibleFor(visibleFor) + self.data.visibleFor = visibleFor +end + +--- +-- Gets the visible for flag to the element. +-- @return number The visible for flag +-- @realm shared +function MARKER_VISION_ELEMENT:GetVisibleFor() + return self.data.visibleFor +end + +--- +-- Gets the visible for flag as a language string to the element. +-- @return string The visible for flag as a language string +-- @realm shared +function MARKER_VISION_ELEMENT:GetVisibleForTranslationKey() + return "marker_vision_visible_for_" .. tostring(self:GetVisibleFor()) +end + +--- +-- Checks if this element is the searched object defined by the unique +-- identifier and the entity that is marked. +-- @param Entity ent The entitiy that is possibly marked +-- @param string identifier The unique identifier +-- @return boolean Returns true if the element matches +-- @realm shared +function MARKER_VISION_ELEMENT:IsObjectFor(ent, identifier) + return self.data.ent == ent and self.data.identifier == identifier +end + +--- +-- Checks if the entity is valid and the identifier is set. +-- @return boolean Returns true if valid +-- @realm shared +function MARKER_VISION_ELEMENT:IsValid() + if not IsValid(self.data.ent) then + return false + end + + if not self.data.identifier or self.data.identifier == "" then + return false + end + + return true +end + +if SERVER then + --- + -- Syncs the marker vision element to the client that should receive it. + -- @note The receipients should be set first with @{MARKER_VISION_ELEMENT:SetOwner} and + -- @{MARKER_VISION_ELEMENT:SetVisibleFor}. + -- @note If the marker vision element was synced already, it is updated on the client. + -- @param[opt] table receiverListOverwrite A table of players that should receive the update + -- if not the automatic receipient selection should be used. + -- @realm server + function MARKER_VISION_ELEMENT:SyncToClients(receiverListOverwrite) + if not self.data.owner then + return + end + + if self.data.visibleFor == VISIBLE_FOR_PLAYER then + self.receiverList = { self.data.owner } + elseif self.data.visibleFor == VISIBLE_FOR_ROLE then + if IsPlayer(self.data.owner) then + self.receiverList = GetRoleFilter(self.data.owner:GetSubRole(), false) + else + -- handle static role + self.receiverList = GetRoleFilter(self.data.owner, false) + end + elseif self.data.visibleFor == VISIBLE_FOR_TEAM then + if IsPlayer(self.data.owner) then + self.receiverList = GetTeamFilter(self.data.owner:GetTeam(), false, true) + else + -- handle static team + self.receiverList = GetTeamFilter(self.data.owner, false, true) + end + else + self.receiverList = nil + end + + -- note: when VISIBLE_FOR_ALL is used, the receiver will be nil and + -- therefore SendStream uses net.Broadcast + + self.receiverList = receiverListOverwrite or self.receiverList + + -- If data was previously set, it should be removed first on the client. This is a + -- simple solution to a complex problem where the marks color has to be updated or + -- removed on the client before updating it. + -- A more in depth solution that reduces network traffic would be possible, but as + -- this won't be called that often anyway, this shouldn't be an issue + if self.lastReceiverList then + net.Start("ttt2_marker_vision_entity_removed") + net.WriteEntity(self.data.ent) + net.WriteString(self.data.identifier) + net.Send(self.lastReceiverList) + end + + -- delay adding wallhack by a tick so that all possible removals are always done first + timer.Simple(0, function() + if not IsValid(self) then + return + end + + net.SendStream("ttt2_marker_vision_entity", self.data, self.receiverList) + + -- cache this, so we know where to remove the old data on update + self.lastReceiverList = self.receiverList + end) + end +end diff --git a/gamemodes/terrortown/gamemode/shared/sh_network_sync.lua b/gamemodes/terrortown/gamemode/shared/sh_network_sync.lua index 50251a914..f184a732b 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_network_sync.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_network_sync.lua @@ -29,7 +29,7 @@ local plymeta = assert(FindMetaTable("Player"), "[TTT2NET] FAILED TO FIND PLAYER -- @return boolean The value at the path or fallback if the value is nil -- @realm shared function plymeta:TTT2NETGetBool(path, fallback) - return tobool(ttt2net.GetOnPlayer(path, self) == true or fallback) + return tobool(ttt2net.GetOnPlayer(path, self) == true or fallback) end --- @@ -40,7 +40,7 @@ end -- @return number The value at the path or fallback if the value is nil -- @realm shared function plymeta:TTT2NETGetInt(path, fallback) - return tonumber(ttt2net.GetOnPlayer(path, self) or fallback) + return tonumber(ttt2net.GetOnPlayer(path, self) or fallback) end --- @@ -51,7 +51,7 @@ end -- @return number The value at the path or fallback if the value is nil -- @realm shared function plymeta:TTT2NETGetUInt(path, fallback) - return tonumber(ttt2net.GetOnPlayer(path, self) or fallback) + return tonumber(ttt2net.GetOnPlayer(path, self) or fallback) end --- @@ -62,7 +62,7 @@ end -- @return number The value at the path or fallback if the value is nil -- @realm shared function plymeta:TTT2NETGetFloat(path, fallback) - return tonumber(ttt2net.GetOnPlayer(path, self) or fallback) + return tonumber(ttt2net.GetOnPlayer(path, self) or fallback) end --- @@ -73,5 +73,5 @@ end -- @return string The value at the path or fallback if the value is nil -- @realm shared function plymeta:TTT2NETGetString(path, fallback) - return tostring(ttt2net.GetOnPlayer(path, self) or fallback) + return tostring(ttt2net.GetOnPlayer(path, self) or fallback) end diff --git a/gamemodes/terrortown/gamemode/shared/sh_player_ext.lua b/gamemodes/terrortown/gamemode/shared/sh_player_ext.lua index 1cb8f1ac9..f273c035b 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_player_ext.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_player_ext.lua @@ -1,6 +1,6 @@ --- -- Shared extensions to player table --- @ref https://wiki.garrysmod.com/page/Category:Player +-- @ref https://wiki.facepunch.com/gmod/Player -- @class Player local net = net @@ -9,11 +9,37 @@ local IsValid = IsValid local hook = hook local math = math +-- Distinguish between 3 modes to reset, add or remove equipped items +EQUIPITEMS_RESET = 0 +EQUIPITEMS_ADD = 1 +EQUIPITEMS_REMOVE = 2 + +---@class Player local plymeta = FindMetaTable("Player") if not plymeta then - Error("FAILED TO FIND PLAYER TABLE") + ErrorNoHaltWithStack("FAILED TO FIND PLAYER TABLE") - return + return +end + +--- +-- @internal +-- @realm shared +function plymeta:SetupDataTables() + -- This has to be transferred, because we need the value when predicting the player movement + -- It turned out that this is the only reliable way to fix all prediction errors. + self:NetworkVar("Float", 0, "SprintStamina") + + if SERVER then + self:SetSprintStamina(1) + end + + -- these are networked variables for the custom FOV handling + self:NetworkVar("Float", 1, "FOVTime") + self:NetworkVar("Float", 2, "FOVTransitionTime") + self:NetworkVar("Float", 3, "FOVValue") + self:NetworkVar("Float", 4, "FOVLastValue") + self:NetworkVar("Bool", 0, "FOVIsFixed") end --- @@ -21,7 +47,7 @@ end -- @return boolean -- @realm shared function plymeta:IsTerror() - return self:Team() == TEAM_TERROR + return self:Team() == TEAM_TERROR end --- @@ -29,7 +55,7 @@ end -- @return boolean -- @realm shared function plymeta:IsSpec() - return self:Team() == TEAM_SPEC + return self:Team() == TEAM_SPEC end --- @@ -37,7 +63,7 @@ end -- @return boolean -- @realm shared function plymeta:GetForceSpec() - return self:GetNWBool("force_spec") + return self:GetNWBool("force_spec") end --- @@ -45,7 +71,7 @@ end -- @return[default=0] number -- @realm shared function plymeta:GetSubRole() - return self.subrole or ROLE_NONE + return self.subrole or ROLE_NONE end --- @@ -53,7 +79,7 @@ end -- @return[default=0] number -- @realm shared function plymeta:GetBaseRole() - return self.role or ROLE_NONE + return self.role or ROLE_NONE end --- @@ -62,7 +88,7 @@ end -- @realm shared -- @see Player:GetBaseRole function plymeta:GetRole() - return self.role or ROLE_NONE + return self.role or ROLE_NONE end --- @@ -74,102 +100,127 @@ end -- @param boolean suppressEvent Set this to true if no rolechange event should be triggered -- @realm shared function plymeta:SetRole(subrole, team, forceHooks, suppressEvent) - local oldBaseRole = self.role and self:GetBaseRole() or nil - local oldSubrole = self.subrole and self:GetSubRole() or nil - local oldTeam = self.roleteam and self:GetTeam() or nil - - local roleData = roles.GetByIndex(subrole) - - self.role = roleData.baserole or subrole - self.subrole = subrole - - -- this update team hook should suppress the event trigger - self:UpdateTeam(team or roleData.defaultTeam or TEAM_NONE, true, true) - - local newBaseRole = self:GetBaseRole() - local newTeam = self:GetTeam() - - if oldSubrole ~= subrole then - local activeRolesCount = GetActiveRolesCount(roleData) + 1 - - SetActiveRolesCount(roleData, activeRolesCount) - - if activeRolesCount > 0 then - --- - -- @realm shared - hook.Run("TTT2ToggleRole", roleData, true) - end - - if oldSubrole then - local oldRoleData = roles.GetByIndex(oldSubrole) - local oldActiveRolesCount = GetActiveRolesCount(oldRoleData) - 1 - - SetActiveRolesCount(oldRoleData, oldActiveRolesCount) - - if oldActiveRolesCount <= 0 then - --- - -- @realm shared - hook.Run("TTT2ToggleRole", oldRoleData, false) - end - end - end - - if oldBaseRole ~= newBaseRole or forceHooks then - --- - -- @realm shared - hook.Run("TTT2UpdateBaserole", self, oldBaseRole, newBaseRole) - end - - if oldSubrole ~= subrole or forceHooks then - --- - -- @realm shared - hook.Run("TTT2UpdateSubrole", self, oldSubrole, subrole) - end - - if oldTeam ~= newTeam or forceHooks then - --- - -- @realm shared - hook.Run("TTT2UpdateTeam", self, oldTeam, newTeam) - end - - if SERVER and not suppressEvent and (oldRole ~= subrole or oldTeam ~= newTeam or forceHooks) then - events.Trigger(EVENT_ROLECHANGE, self, oldSubrole, subrole, oldTeam, newTeam) - end - - -- ye olde hooks - if subrole ~= oldSubrole or forceHooks then - self:SetRoleColor(roleData.color) - self:SetRoleDkColor(roleData.dkcolor) - self:SetRoleLtColor(roleData.ltcolor) - self:SetRoleBgColor(roleData.bgcolor) - - if SERVER then - --- - -- @realm server - hook.Run("PlayerLoadout", self, false) - - -- Don't update the model if oldSubrole is nil (player isn't already spawned, leading to an initialization error) - if oldSubrole and GetConVar("ttt_enforce_playermodel"):GetBool() then - -- update subroleModel - self:SetModel(self:GetSubRoleModel()) - end - - -- Always clear color state, may later be changed in TTTPlayerSetColor - self:SetColor(COLOR_WHITE) - - --- - -- @realm server - hook.Run("TTTPlayerSetColor", self) - end - end - - if SERVER then - roleData:GiveRoleLoadout(self, true) - - if oldSubrole then - roles.GetByIndex(oldSubrole):RemoveRoleLoadout(self, true) - end - end + local oldBaseRole = self.role and self:GetBaseRole() or nil + local oldSubrole = self.subrole and self:GetSubRole() or nil + local oldTeam = self.roleteam and self:GetTeam() or nil + + local roleData = roles.GetByIndex(subrole) + + self.role = roleData.baserole or subrole + self.subrole = subrole + + -- this update team hook should suppress the event trigger + self:UpdateTeam(team or roleData.defaultTeam or TEAM_NONE, true, true) + + local newBaseRole = self:GetBaseRole() + local newTeam = self:GetTeam() + + if oldSubrole ~= subrole then + local activeRolesCount = GetActiveRolesCount(roleData) + 1 + + SetActiveRolesCount(roleData, activeRolesCount) + + if activeRolesCount > 0 then + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2ToggleRole", roleData, true) + end + + if oldSubrole then + local oldRoleData = roles.GetByIndex(oldSubrole) + local oldActiveRolesCount = GetActiveRolesCount(oldRoleData) - 1 + + SetActiveRolesCount(oldRoleData, oldActiveRolesCount) + + if oldActiveRolesCount <= 0 then + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2ToggleRole", oldRoleData, false) + end + end + end + + if oldBaseRole ~= newBaseRole or forceHooks then + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2UpdateBaserole", self, oldBaseRole, newBaseRole) + end + + if oldSubrole ~= subrole or forceHooks then + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2UpdateSubrole", self, oldSubrole, subrole) + + if SERVER then + -- Trigger the update in the next tick to make sure the role is updated + -- on the client first before any markerVision updates are sent. + -- This is important so that functions such as GetRoleColor() return the + -- correct color instead of the color from the old role. + timer.Simple(0, function() + if not IsValid(self) then + return + end + + markerVision.PlayerUpdatedRole(self, oldSubrole, subrole) + end) + end + end + + if oldTeam ~= newTeam or forceHooks then + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2UpdateTeam", self, oldTeam, newTeam) + end + + if + SERVER + and not suppressEvent + and (oldSubrole ~= subrole or oldTeam ~= newTeam or forceHooks) + then + events.Trigger(EVENT_ROLECHANGE, self, oldSubrole, subrole, oldTeam, newTeam) + end + + -- ye olde hooks + if subrole ~= oldSubrole or forceHooks then + self:SetRoleColor(roleData.color) + self:SetRoleDkColor(roleData.dkcolor) + self:SetRoleLtColor(roleData.ltcolor) + self:SetRoleBgColor(roleData.bgcolor) + + if SERVER then + --- + -- @realm server + -- stylua: ignore + hook.Run("PlayerLoadout", self, false) + + -- Don't update the model if oldSubrole is nil (player isn't already spawned, leading to an initialization error) + if oldSubrole and GetConVar("ttt_enforce_playermodel"):GetBool() then + -- update subroleModel + self:SetModel(self:GetSubRoleModel()) + end + + -- Always clear color state, may later be changed in TTTPlayerSetColor + self:SetColor(COLOR_WHITE) + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTTPlayerSetColor", self) + end + end + + if SERVER then + roleData:GiveRoleLoadout(self, true) + + if oldSubrole then + roles.GetByIndex(oldSubrole):RemoveRoleLoadout(self, true) + end + end end --- @@ -177,11 +228,11 @@ end -- @return Color -- @realm shared function plymeta:GetRoleColor() - if not self.roleColor then - self.roleColor = roles.NONE.color - end + if not self.roleColor then + self.roleColor = roles.NONE.color + end - return self.roleColor + return self.roleColor end --- @@ -189,9 +240,10 @@ end -- @param Color col -- @realm shared function plymeta:SetRoleColor(col) - --- - -- @realm shared - self.roleColor = hook.Run("TTT2ModifyRoleColor", self, col) or col + --- + -- @realm shared + -- stylua: ignore + self.roleColor = hook.Run("TTT2ModifyRoleColor", self, col) or col end --- @@ -199,7 +251,7 @@ end -- @return Color -- @realm shared function plymeta:GetRoleDkColor() - return self.roleDkColor + return self.roleDkColor end --- @@ -207,9 +259,10 @@ end -- @param Color col -- @realm shared function plymeta:SetRoleDkColor(col) - --- - -- @realm shared - self.roleDkColor = hook.Run("TTT2ModifyRoleDkColor", self, col) or col + --- + -- @realm shared + -- stylua: ignore + self.roleDkColor = hook.Run("TTT2ModifyRoleDkColor", self, col) or col end --- @@ -217,7 +270,7 @@ end -- @return Color -- @realm shared function plymeta:GetRoleLtColor() - return self.roleLtColor + return self.roleLtColor end --- @@ -225,9 +278,10 @@ end -- @param Color col -- @realm shared function plymeta:SetRoleLtColor(col) - --- - -- @realm shared - self.roleLtColor = hook.Run("TTT2ModifyRoleLtColor", self, col) or col + --- + -- @realm shared + -- stylua: ignore + self.roleLtColor = hook.Run("TTT2ModifyRoleLtColor", self, col) or col end --- @@ -235,7 +289,7 @@ end -- @return Color -- @realm shared function plymeta:GetRoleBgColor() - return self.roleBgColor + return self.roleBgColor end --- @@ -243,36 +297,41 @@ end -- @param Color col -- @realm shared function plymeta:SetRoleBgColor(col) - --- - -- @realm shared - self.roleBgColor = hook.Run("TTT2ModifyRoleBgColor", self, col) or col + --- + -- @realm shared + -- stylua: ignore + self.roleBgColor = hook.Run("TTT2ModifyRoleBgColor", self, col) or col end if SERVER then - util.AddNetworkString("TTT2SyncModel") - util.AddNetworkString("TTT2SyncSubroleModel") + util.AddNetworkString("TTT2SyncModel") + util.AddNetworkString("TTT2SyncSubroleModel") else - net.Receive("TTT2SyncModel", function() - local mdl = net.ReadString() - local ply = net.ReadEntity() + net.Receive("TTT2SyncModel", function() + local mdl = net.ReadString() + local ply = net.ReadEntity() - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply:SetModel(mdl) - end) + ply:SetModel(mdl) + end) - net.Receive("TTT2SyncSubroleModel", function() - local mdl = net.ReadString() - local ply = net.ReadEntity() + net.Receive("TTT2SyncSubroleModel", function() + local mdl = net.ReadString() + local ply = net.ReadEntity() - if mdl == "" then - mdl = nil - end + if mdl == "" then + mdl = nil + end - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply:SetSubRoleModel(mdl) - end) + ply:SetSubRoleModel(mdl) + end) end --- @@ -282,13 +341,13 @@ end -- Use @{Player:GetRealTeam()} instead if this behavior is not intended. -- @realm shared function plymeta:GetTeam() - local tm = self.roleteam + local tm = self.roleteam - if tm ~= nil and not TEAMS[tm].alone then - return tm - end + if tm ~= nil and not TEAMS[tm].alone then + return tm + end - return TEAM_NONE + return TEAM_NONE end --- @@ -296,7 +355,7 @@ end -- @return nil|string -- @realm shared function plymeta:GetRealTeam() - return self.roleteam + return self.roleteam end --- @@ -307,27 +366,46 @@ end -- @param boolean suppressHook Set this to true if no updateTeam hook should be triggered -- @realm shared function plymeta:UpdateTeam(team, suppressEvent, suppressHook) - if team == TEAM_NOCHANGE then return end + if team == TEAM_NOCHANGE then + return + end - local oldTeam = self:GetTeam() + local oldTeam = self:GetTeam() - self.roleteam = team or TEAM_NONE + self.roleteam = team or TEAM_NONE - local newTeam = self:GetTeam() + local newTeam = self:GetTeam() - if oldTeam == newTeam then return end + if oldTeam == newTeam then + return + end - if not suppressHook then - --- - -- @realm shared - hook.Run("TTT2UpdateTeam", self, oldTeam, newTeam) - end + if not suppressHook then + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2UpdateTeam", self, oldTeam, newTeam) + end - if SERVER and not suppressEvent then - local subrole = self:GetSubRole() + if SERVER then + -- Trigger the update in the next tick to make sure the team is updated + -- on the client first before any markerVision updates are sent. + -- This is important so that the team color assess returns the + -- correct color instead of the color from the old team. + timer.Simple(0, function() + if not IsValid(self) then + return + end - events.Trigger(EVENT_ROLECHANGE, self, subrole, subrole, oldTeam, newTeam) - end + markerVision.PlayerUpdatedTeam(self, oldTeam, newTeam) + end) + end + + if SERVER and not suppressEvent then + local subrole = self:GetSubRole() + + events.Trigger(EVENT_ROLECHANGE, self, subrole, subrole, oldTeam, newTeam) + end end --- @@ -335,9 +413,9 @@ end -- @return boolean -- @realm shared function plymeta:HasTeam() - local tm = self:GetTeam() + local tm = self:GetTeam() - return tm ~= TEAM_NONE and not TEAMS[tm].alone + return tm ~= TEAM_NONE and not TEAMS[tm].alone end --- @@ -346,7 +424,7 @@ end -- @return boolean -- @realm shared function plymeta:IsInTeam(ply) - return self:HasTeam() and self:GetTeam() == ply:GetTeam() + return self:HasTeam() and self:GetTeam() == ply:GetTeam() end -- Role access @@ -357,7 +435,7 @@ end -- @realm shared -- @deprecated function plymeta:GetInnocent() - return self:GetBaseRole() == ROLE_INNOCENT + return self:GetBaseRole() == ROLE_INNOCENT end --- @@ -368,7 +446,7 @@ end -- @realm shared -- @deprecated function plymeta:GetTraitor() - return self:GetBaseRole() == ROLE_TRAITOR + return self:GetBaseRole() == ROLE_TRAITOR end --- @@ -377,7 +455,7 @@ end -- @realm shared -- @deprecated function plymeta:GetDetective() - return self:GetBaseRole() == ROLE_DETECTIVE + return self:GetBaseRole() == ROLE_DETECTIVE end --- @@ -385,16 +463,18 @@ end -- @return[default=NONE] ROLE -- @realm shared function plymeta:GetSubRoleData() - local rlsList = roles.GetList() - local subrl = self:GetSubRole() + local rlsList = roles.GetList() + local subrl = self:GetSubRole() - for i = 1, #rlsList do - if rlsList[i].index ~= subrl then continue end + for i = 1, #rlsList do + if rlsList[i].index ~= subrl then + continue + end - return rlsList[i] - end + return rlsList[i] + end - return roles.NONE + return roles.NONE end --- @@ -402,16 +482,18 @@ end -- @return[default=NONE] ROLE -- @realm shared function plymeta:GetBaseRoleData() - local rlsList = roles.GetList() - local bsrl = self:GetBaseRole() + local rlsList = roles.GetList() + local bsrl = self:GetBaseRole() - for i = 1, #rlsList do - if rlsList[i].index ~= bsrl then continue end + for i = 1, #rlsList do + if rlsList[i].index ~= bsrl then + continue + end - return rlsList[i] - end + return rlsList[i] + end - return roles.NONE + return roles.NONE end --- @@ -449,7 +531,7 @@ plymeta.IsDetective = plymeta.GetDetective -- @return boolean Returns true if the player has a special role -- @realm shared function plymeta:HasSpecialRole() - return self:GetSubRole() ~= ROLE_INNOCENT and self:GetSubRole() ~= ROLE_NONE + return self:GetSubRole() ~= ROLE_INNOCENT and self:GetSubRole() ~= ROLE_NONE end --- @@ -467,7 +549,7 @@ plymeta.IsSpecial = plymeta.HasSpecialRole -- @return boolean Returns true if the role is evil -- @realm shared function plymeta:HasEvilTeam() - return util.IsEvilTeam(self:GetTeam()) + return util.IsEvilTeam(self:GetTeam()) end --- @@ -475,7 +557,7 @@ end -- @return boolean -- @realm shared function plymeta:IsActive() - return GetRoundState() == ROUND_ACTIVE and self:IsTerror() + return GetRoundState() == ROUND_ACTIVE and self:IsTerror() end --- @@ -486,10 +568,10 @@ end -- @return boolean -- @realm shared function plymeta:IsRole(subrole) - local br = self:GetBaseRole() - local sr = self:GetSubRole() + local br = self:GetBaseRole() + local sr = self:GetSubRole() - return subrole == sr or subrole == br + return subrole == sr or subrole == br end --- @@ -497,7 +579,7 @@ end -- @return boolean -- @realm shared function plymeta:HasRole() - return self:GetSubRole() ~= ROLE_NONE + return self:GetSubRole() ~= ROLE_NONE end --- @@ -508,7 +590,7 @@ end -- @see Player:IsActive -- @see Player:IsRole function plymeta:IsActiveRole(subrole) - return self:IsActive() and self:IsRole(subrole) + return self:IsActive() and self:IsRole(subrole) end --- @@ -517,7 +599,7 @@ end -- @realm shared -- @see Player:IsActiveRole function plymeta:IsActiveInnocent() - return self:IsActiveRole(ROLE_INNOCENT) + return self:IsActiveRole(ROLE_INNOCENT) end --- @@ -526,7 +608,7 @@ end -- @realm shared -- @see Player:IsActiveRole function plymeta:IsActiveTraitor() - return self:IsActiveRole(ROLE_TRAITOR) + return self:IsActiveRole(ROLE_TRAITOR) end --- @@ -535,7 +617,7 @@ end -- @realm shared -- @see Player:IsActiveRole function plymeta:IsActiveDetective() - return self:IsActiveRole(ROLE_DETECTIVE) + return self:IsActiveRole(ROLE_DETECTIVE) end --- @@ -545,7 +627,7 @@ end -- @see Player:IsActive -- @see Player:HasSpecialRole function plymeta:IsActiveSpecial() - return self:IsActive() and self:HasSpecialRole() + return self:IsActive() and self:HasSpecialRole() end --- @@ -554,7 +636,7 @@ end -- @realm shared -- @see ROLE:IsShoppingRole function plymeta:IsShopper() - return self:GetSubRoleData():IsShoppingRole() + return self:GetSubRoleData():IsShoppingRole() end --- @@ -564,7 +646,7 @@ end -- @see Player:IsActive -- @see Player:IsShopper function plymeta:IsActiveShopper() - return self:IsActive() and self:IsShopper() + return self:IsActive() and self:IsShopper() end local GetRTranslation = CLIENT and LANG.GetRawTranslation or util.passthrough @@ -574,9 +656,9 @@ local GetRTranslation = CLIENT and LANG.GetRawTranslation or util.passthrough -- @return string -- @realm shared function plymeta:GetRoleString() - local name = self:GetSubRoleData().name + local name = self:GetSubRoleData().name - return GetRTranslation(name) or name + return GetRTranslation(name) or name end --- @@ -584,7 +666,7 @@ end -- @return string -- @realm shared function plymeta:GetRoleStringRaw() - return self:GetSubRoleData().name + return self:GetSubRoleData().name end --- @@ -592,7 +674,7 @@ end -- @return number -- @realm shared function plymeta:GetBaseKarma() - return self:GetNWFloat("karma", 1000) + return self:GetNWFloat("karma", 1000) end --- @@ -600,17 +682,19 @@ end -- @return boolean -- @realm shared function plymeta:HasEquipmentWeapon() - local weps = self:GetWeapons() + local weps = self:GetWeapons() - for i = 1, #weps do - local wep = weps[i] + for i = 1, #weps do + local wep = weps[i] - if not IsValid(wep) or not WEPS.IsEquipment(wep) then continue end + if not IsValid(wep) or not WEPS.IsEquipment(wep) then + continue + end - return true - end + return true + end - return false + return false end --- @@ -619,21 +703,23 @@ end -- @return boolean -- @realm shared function plymeta:CanCarryWeapon(wep) - if not wep or not wep.Kind then - return false - end + if not wep or not wep.Kind then + return false + end - -- appeareantly TTT can't handle two times the same weapon - local weps = self:GetWeapons() - local wepCls = WEPS.GetClass(wep) + -- appeareantly TTT can't handle two times the same weapon + local weps = self:GetWeapons() + local wepCls = WEPS.GetClass(wep) - for k = 1, #weps do - if wepCls ~= weps[k]:GetClass() then continue end + for k = 1, #weps do + if wepCls ~= weps[k]:GetClass() then + continue + end - return false - end + return false + end - return self:CanCarryType(wep.Kind) + return self:CanCarryType(wep.Kind) end --- @@ -642,11 +728,11 @@ end -- @return boolean -- @realm shared function plymeta:CanCarryType(t) - if not t then - return false - end + if not t then + return false + end - return InventorySlotFree(self, t) + return InventorySlotFree(self, t) end --- @@ -654,9 +740,9 @@ end -- @return table -- @realm shared function plymeta:GetInventory() - CleanupInventoryIfDirty(self) + CleanupInventoryIfDirty(self) - return self.inventory + return self.inventory end --- @@ -665,9 +751,11 @@ end -- @return table -- @realm shared function plymeta:GetWeaponsOnSlot(slot) - if slot > WEAPON_CLASS then return end + if slot > WEAPON_CLASS then + return + end - return self:GetInventory()[slot] + return self:GetInventory()[slot] end --- @@ -675,7 +763,7 @@ end -- @return table -- @realm shared function plymeta:GetMeleeWeapons() - return self:GetWeaponsOnSlot(WEAPON_MELEE) + return self:GetWeaponsOnSlot(WEAPON_MELEE) end --- @@ -683,7 +771,7 @@ end -- @return table -- @realm shared function plymeta:GetPrimaryWeapons() - return self:GetWeaponsOnSlot(WEAPON_HEAVY) + return self:GetWeaponsOnSlot(WEAPON_HEAVY) end --- @@ -691,7 +779,7 @@ end -- @return table -- @realm shared function plymeta:GetSecondaryWeapons() - return self:GetWeaponsOnSlot(WEAPON_PISTOL) + return self:GetWeaponsOnSlot(WEAPON_PISTOL) end --- @@ -699,7 +787,7 @@ end -- @return table -- @realm shared function plymeta:GetNades() - return self:GetWeaponsOnSlot(WEAPON_NADE) + return self:GetWeaponsOnSlot(WEAPON_NADE) end --- @@ -707,7 +795,7 @@ end -- @return table -- @realm shared function plymeta:GetCarryWeapons() - return self:GetWeaponsOnSlot(WEAPON_CARRY) + return self:GetWeaponsOnSlot(WEAPON_CARRY) end --- @@ -715,7 +803,7 @@ end -- @return table -- @realm shared function plymeta:GetSpecialWeapons() - return self:GetWeaponsOnSlot(WEAPON_SPECIAL) + return self:GetWeaponsOnSlot(WEAPON_SPECIAL) end --- @@ -723,7 +811,7 @@ end -- @return table -- @realm shared function plymeta:GetExtraWeapons() - return self:GetWeaponsOnSlot(WEAPON_EXTRA) + return self:GetWeaponsOnSlot(WEAPON_EXTRA) end --- @@ -731,7 +819,7 @@ end -- @return table -- @realm shared function plymeta:GetClassWeapons() - return self:GetWeaponsOnSlot(WEAPON_CLASS) + return self:GetWeaponsOnSlot(WEAPON_CLASS) end --- @@ -740,7 +828,7 @@ end -- @realm shared -- @see Player:IsSpec function plymeta:IsDeadTerror() - return self:IsSpec() and not self:Alive() + return self:IsSpec() and not self:Alive() end --- @@ -749,7 +837,7 @@ end -- @return boolean -- @realm shared function plymeta:HasBought(id) - return self.bought and table.HasValue(self.bought, id) + return self.bought and table.HasValue(self.bought, id) end --- @@ -757,7 +845,7 @@ end -- @return number -- @realm shared function plymeta:GetCredits() - return self.equipment_credits or 0 + return self.equipment_credits or 0 end --- @@ -765,7 +853,27 @@ end -- @return table -- @realm shared function plymeta:GetEquipmentItems() - return self.equipmentItems or {} + return self.equipmentItems or {} +end + +--- +-- Resets the equipment item table to the provided one +-- @param[opt] table items The table with the item entities +-- @realm shared +function plymeta:SetEquipmentItems(items) + self.equipmentItems = items or {} + + if SERVER then + -- we use this instead of SendEquipment here to prevent any of the + -- equipment reset functions to be triggered + net.SendStream("TTT2_SetEquipmentItems", self.equipmentItems, self) + end +end + +if CLIENT then + net.ReceiveStream("TTT2_SetEquipmentItems", function(equipmentItems) + LocalPlayer():SetEquipmentItems(equipmentItems) + end) end --- @@ -775,24 +883,24 @@ end -- @return boolean -- @realm shared function plymeta:HasEquipmentItem(id) - if not id then - return #self:GetEquipmentItems() > 0 - else - local itms = self:GetEquipmentItems() + if not id then + return #self:GetEquipmentItems() > 0 + else + local itms = self:GetEquipmentItems() - if table.HasValue(itms, id) then - return true - end + if table.HasValue(itms, id) then + return true + end - for i = 1, #itms do - local item = items.GetStored(itms[i]) - if item and item.oldId and item.oldId == id then - return true - end - end - end + for i = 1, #itms do + local item = items.GetStored(itms[i]) + if item and item.oldId and item.oldId == id then + return true + end + end + end - return false + return false end --- @@ -800,7 +908,7 @@ end -- @return boolean -- @realm shared function plymeta:HasEquipment() - return self:HasEquipmentItem() or self:HasEquipmentWeapon() + return self:HasEquipmentItem() or self:HasEquipmentWeapon() end --- @@ -809,29 +917,29 @@ end -- never use cursor tracing anyway. -- @param MASK mask The trace mask. This determines what the trace should hit and what it shouldn't hit. -- A mask is a combination of CONTENTS_Enums - you can use these for more advanced masks. --- @ref https://wiki.garrysmod.com/page/Structures/Trace +-- @ref https://wiki.facepunch.com/gmod/Structures/Trace -- @realm shared function plymeta:GetEyeTrace(mask) - mask = mask or MASK_SOLID + mask = mask or MASK_SOLID - if CLIENT then - local framenum = FrameNumber() + if CLIENT then + local framenum = FrameNumber() - if self.LastPlayerTrace == framenum and self.LastPlayerTraceMask == mask then - return self.PlayerTrace - end + if self.LastPlayerTrace == framenum and self.LastPlayerTraceMask == mask then + return self.PlayerTrace + end - self.LastPlayerTrace = framenum - self.LastPlayerTraceMask = mask - end + self.LastPlayerTrace = framenum + self.LastPlayerTraceMask = mask + end - local tr = util.GetPlayerTrace(self) - tr.mask = mask + local tr = util.GetPlayerTrace(self) + tr.mask = mask - tr = util.TraceLine(tr) - self.PlayerTrace = tr + tr = util.TraceLine(tr) + self.PlayerTrace = tr - return tr + return tr end --- @@ -841,28 +949,28 @@ end -- @param number duration the current duration / time a @{Player} is diving -- @realm shared function plymeta:StartDrowning(bool, time, duration) - if bool then - -- will start drowning soon - self.drowning = CurTime() + time - self.drowningTime = duration - self.drowningProgress = math.max(0, time * (1 / duration)) - else - self.drowning = nil - self.drowningTime = nil - self.drowningProgress = -1 - end + if bool then + -- will start drowning soon + self.drowning = CurTime() + time + self.drowningTime = duration + self.drowningProgress = math.max(0, time * (1 / duration)) + else + self.drowning = nil + self.drowningTime = nil + self.drowningProgress = -1 + end - if SERVER then - net.Start("StartDrowning") - net.WriteBool(bool) + if SERVER then + net.Start("StartDrowning") + net.WriteBool(bool) - if bool then - net.WriteUInt(time, 16) - net.WriteUInt(self.drowningTime, 16) - end + if bool then + net.WriteUInt(time, 16) + net.WriteUInt(self.drowningTime, 16) + end - net.Send(self) - end + net.Send(self) + end end --- @@ -870,7 +978,7 @@ end -- @return Player target -- @realm shared function plymeta:GetTargetPlayer() - return self.targetPlayer + return self.targetPlayer end --- @@ -878,17 +986,17 @@ end -- @param Player ply -- @realm shared function plymeta:SetTargetPlayer(ply) - self.targetPlayer = ply + self.targetPlayer = ply - if SERVER then - net.Start("TTT2TargetPlayer") - net.WriteEntity(ply) - net.Send(self) - end + if SERVER then + net.Start("TTT2TargetPlayer") + net.WriteEntity(ply) + net.Send(self) + end end local function checkModel(mdl) - return mdl and mdl ~= "" and mdl ~= "models/player.mdl" + return mdl and mdl ~= "" and mdl ~= "models/player.mdl" end --- @@ -896,7 +1004,7 @@ end -- @return string the @{ROLE} @{Model} -- @realm shared function plymeta:GetSubRoleModel() - return self.subroleModel + return self.subroleModel end --- @@ -904,18 +1012,18 @@ end -- @param string mdl the @{ROLE} @{Model} -- @realm shared function plymeta:SetSubRoleModel(mdl) - if not checkModel(mdl) then - mdl = nil - end + if not checkModel(mdl) then + mdl = nil + end - self.subroleModel = mdl + self.subroleModel = mdl - if SERVER then - net.Start("TTT2SyncSubroleModel") - net.WriteString(mdl or "") - net.WriteEntity(self) - net.Broadcast() - end + if SERVER then + net.Start("TTT2SyncSubroleModel") + net.WriteString(mdl or "") + net.WriteEntity(self) + net.Broadcast() + end end --- @@ -923,7 +1031,7 @@ end -- @return boolean -- @realm shared function plymeta:OnceFound() - return self:TTT2NETGetFloat("t_first_found", -1) >= 0 + return self:TTT2NETGetFloat("t_first_found", -1) >= 0 end --- @@ -931,7 +1039,7 @@ end -- @return boolean -- @realm shared function plymeta:RoleKnown() - return self:TTT2NETGetBool("role_found", false) + return self:TTT2NETGetBool("role_found", false) end --- @@ -939,7 +1047,7 @@ end -- @return boolean -- @realm shared function plymeta:WasRevivedAndConfirmed() - return not self:TTT2NETGetBool("body_found", false) and self:OnceFound() + return not self:IsSpec() and not self:TTT2NETGetBool("body_found", false) and self:OnceFound() end --- @@ -947,7 +1055,7 @@ end -- @return boolean -- @realm shared function plymeta:GetFirstFound() - return math.Round(self:TTT2NETGetFloat("t_first_found", -1)) + return math.Round(self:TTT2NETGetFloat("t_first_found", -1)) end --- @@ -956,7 +1064,7 @@ end -- @return boolean -- @realm shared function plymeta:IsReady() - return self.isReady or false + return self.isReady or false end --- @@ -965,9 +1073,7 @@ end -- @param Player ply The @{Player} that is now ready -- @hook -- @realm shared -function GM:TTT2PlayerReady(ply) - -end +function GM:TTT2PlayerReady(ply) end local oldSetModel = plymeta.SetModel or plymeta.MetaBaseClass.SetModel @@ -977,65 +1083,65 @@ local oldSetModel = plymeta.SetModel or plymeta.MetaBaseClass.SetModel -- @note override to fix PS/ModelSelector/... issues -- @realm shared function plymeta:SetModel(mdlName) - local mdl + local mdl - local curMdl = mdlName or self:GetModel() + local curMdl = mdlName or self:GetModel() - if not checkModel(curMdl) then - curMdl = self.defaultModel + if not checkModel(curMdl) then + curMdl = self.defaultModel - if not checkModel(curMdl) then - if not checkModel(GAMEMODE.playermodel) then - GAMEMODE.playermodel = GAMEMODE.force_plymodel + if not checkModel(curMdl) then + if not checkModel(GAMEMODE.playermodel) then + GAMEMODE.playermodel = GAMEMODE.force_plymodel - if not checkModel(GAMEMODE.playermodel) then - GAMEMODE.playermodel = "models/player/phoenix.mdl" - end - end + if not checkModel(GAMEMODE.playermodel) then + GAMEMODE.playermodel = "models/player/phoenix.mdl" + end + end - curMdl = GAMEMODE.playermodel - end - end + curMdl = GAMEMODE.playermodel + end + end - local srMdl = self:GetSubRoleModel() - if srMdl then - mdl = srMdl + local srMdl = self:GetSubRoleModel() + if srMdl then + mdl = srMdl - if curMdl ~= srMdl then - self.oldModel = curMdl - end - else - if self.oldModel then - mdl = self.oldModel - self.oldModel = nil - else - mdl = curMdl - end - end + if curMdl ~= srMdl then + self.oldModel = curMdl + end + else + if self.oldModel then + mdl = self.oldModel + self.oldModel = nil + else + mdl = curMdl + end + end - -- last but not least, we fix this grey model "bug" - if not checkModel(mdl) then - mdl = "models/player/phoenix.mdl" - end + -- last but not least, we fix this grey model "bug" + if not checkModel(mdl) then + mdl = "models/player/phoenix.mdl" + end - oldSetModel(self, Model(mdl)) + oldSetModel(self, Model(mdl)) - if SERVER then - net.Start("TTT2SyncModel") - net.WriteString(mdl) - net.WriteEntity(self) - net.Broadcast() + if SERVER then + net.Start("TTT2SyncModel") + net.WriteString(mdl) + net.WriteEntity(self) + net.Broadcast() - self:SetupHands() - end + self:SetupHands() + end end hook.Add("TTTEndRound", "TTTEndRound4TTT2TargetPlayer", function() - local plys = player.GetAll() + local plys = player.GetAll() - for i = 1, #plys do - plys[i].targetPlayer = nil - end + for i = 1, #plys do + plys[i].targetPlayer = nil + end end) --- @@ -1043,7 +1149,7 @@ end) -- @return boolean The blocking status -- @realm shared function plymeta:IsReviving() - return self.isReviving or false + return self.isReviving or false end --- @@ -1051,7 +1157,7 @@ end -- @return boolean The blocking status -- @realm shared function plymeta:IsBlockingRevival() - return self.revivalBlockMode and self.revivalBlockMode > REVIVAL_BLOCK_NONE + return self.revivalBlockMode and self.revivalBlockMode > REVIVAL_BLOCK_NONE end --- @@ -1059,7 +1165,7 @@ end -- @return number The blocking mode -- @realm shared function plymeta:GetRevivalBlockMode() - return self.revivalBlockMode or REVIVAL_BLOCK_NONE + return self.revivalBlockMode or REVIVAL_BLOCK_NONE end --- @@ -1067,7 +1173,7 @@ end -- @return[default=@{CurTime()}] number The time when the revival started in seconds -- @realm shared function plymeta:GetRevivalStartTime() - return self.revivalStartTime or CurTime() + return self.revivalStartTime or CurTime() end --- @@ -1075,7 +1181,7 @@ end -- @return[default=1.0] number The time for the revival in seconds -- @realm shared function plymeta:GetRevivalDuration() - return self.revivalDurarion or 0.0 + return self.revivalDurarion or 0.0 end --- @@ -1084,7 +1190,7 @@ end -- @return boolean Returns if the player was active -- @realm shared function plymeta:WasActiveInRound() - return self:TTT2NETGetBool("player_was_active_in_round", false) + return self:TTT2NETGetBool("player_was_active_in_round", false) end --- @@ -1092,7 +1198,7 @@ end -- @return number The amoutn of deaths in the active round -- @realm shared function plymeta:GetDeathsInRound() - return self:TTT2NETGetUInt("player_round_deaths", 0) + return self:TTT2NETGetUInt("player_round_deaths", 0) end --- @@ -1100,7 +1206,7 @@ end -- @return boolean Returns if the player died in the round -- @realm shared function plymeta:HasDiedInRound() - return self:GetDeathsInRound() > 0 + return self:GetDeathsInRound() > 0 end --- @@ -1108,9 +1214,116 @@ end -- @return boolean Returns if the player was revived -- @realm shared function plymeta:WasRevivedInRound() - return self:HasDiedInRound() + return self:HasDiedInRound() +end + +--- +-- Returns the player height vector based on the curren thead bone +-- position. X and Y move along the head bone, Z contains the actual +-- height. +-- @note Uses the player's bounding box as a fallback if the head +-- bone is not defined +-- @note Respects the model scale for the height calculation +-- @return vector The player height +-- @realm shared +function plymeta:GetHeightVector() + local matrix + local bone = self:LookupBone("ValveBiped.Bip01_Head1") + + -- if the bone is defined, the bone matrix is defined as well; + -- however on hot reloads this can momentarily break before it + -- fixes itself again after a short time + if bone then + matrix = self:GetBoneMatrix(bone) + end + + if matrix then + local pos = matrix:GetTranslation() + + -- note: the 8 is the assumed height of the head after the head bone + -- this might not work for every model + pos.z = pos.z + 8 * self:GetModelScale() * self:GetManipulateBoneScale(bone).z + + return pos - self:GetPos() + + -- if the model has no head bone for some reason, use the player + -- position as a fallback + else + local obbmMaxs = self:OBBMaxs() + obbmMaxs.x = 0 + obbmMaxs.y = 0 + + return obbmMaxs * self:GetModelScale() + end +end + +-- to make it hotreload safe, we have to make sure it is not +-- called recursively by only caching the original function +if debug.getinfo(plymeta.SetFOV, "flLnSu").what == "C" then + plymeta.SetOldFOV = plymeta.SetFOV +end + +--- +-- Set a player's FOV (Field Of View) over a certain amount of time. +-- @param number fov The angle of perception (FOV); set to 0 to return to default user FOV +-- @param[default=0] number time The time it takes to transition to the FOV expressed in a floating point +-- @param[default=self] Entity requester The requester or "owner" of the zoom event; only this entity will be able to change the player's FOV until it is set back to 0 +-- @realm shared +function plymeta:SetFOV(fov, time, requester) + -- if dynamic FOV is disabled, the default function should be used + if not self:GetPlayerSetting("enable_dynamic_fov") then + self:SetOldFOV(fov, time, requester) + + return + end + + -- these values have to be set for our custom FOV handling in GM:CalcView + self:SetFOVLastValue(self:GetFOVValue()) + self:SetFOVValue(fov or 0) + self:SetFOVTime(CurTime()) + self:SetFOVTransitionTime(time) + self:SetFOVIsFixed(fov and fov ~= 0) + + -- set time to 0 so our custom FOV code can handle the zoom out + if not fov or fov == 0 then + time = 0 + end + + self:SetOldFOV(fov, time, requester) +end + +--- +-- Checks if a player is in iron sights. +-- @return boolean Returns true if the player is in iron sights +-- @realm shared +function plymeta:IsInIronsights() + local wep = self:GetActiveWeapon() + + return IsValid(wep) + and not wep.NoSights + and isfunction(wep.GetIronsights) + and wep:GetIronsights() +end + +--- +-- Get the value of a shared player setting. +-- @param string identifier The identifier of the setting +-- @return any The value of the setting, nil if not set +-- @realm shared +function plymeta:GetPlayerSetting(identifier) + return self.playerSettings and self.playerSettings[identifier] end +--- +-- A hook that is called on change of a player setting on the server. +-- @param Player ply The player whose setting was changed +-- @param string identifier The setting's identifier +-- @param any oldValue The old value of the setting +-- @param any newValue The new value of the settings +-- @hook +-- @realm shared +function GM:TTT2PlayerSettingChanged(ply, identifier, oldValue, newValue) end + --- -- A hook that is called on the change of a role. It is called once for the old role -- and once for the new role if some criteria are met. @@ -1118,9 +1331,7 @@ end -- @param boolean isNewRole True if it is the new role, false if it is the old role -- @hook -- @realm shared -function GM:TTT2ToggleRole(roleData, isNewRole) - -end +function GM:TTT2ToggleRole(roleData, isNewRole) end --- -- This hook is called on the change of a player's base role. @@ -1129,9 +1340,7 @@ end -- @param number newBaserole The numeric identifier of the new role -- @hook -- @realm shared -function GM:TTT2UpdateBaserole(ply, oldBaserole, newBaserole) - -end +function GM:TTT2UpdateBaserole(ply, oldBaserole, newBaserole) end --- -- This hook is called on the change of a player's sub role. @@ -1140,9 +1349,7 @@ end -- @param number newSubrole The numeric identifier of the new role -- @hook -- @realm shared -function GM:TTT2UpdateSubrole(ply, oldSubrole, newSubrole) - -end +function GM:TTT2UpdateSubrole(ply, oldSubrole, newSubrole) end --- -- This hook is called on the change of a player's team. @@ -1151,9 +1358,7 @@ end -- @param string newTeam The identifier of the new team -- @hook -- @realm shared -function GM:TTT2UpdateTeam(ply, oldTeam, newTeam) - -end +function GM:TTT2UpdateTeam(ply, oldTeam, newTeam) end --- -- This hook is called (mostly on rolechanges) when the player's role color @@ -1163,9 +1368,7 @@ end -- @return nil|Color The new color that is intended for the player -- @hook -- @realm shared -function GM:TTT2ModifyRoleColor(ply, clr) - -end +function GM:TTT2ModifyRoleColor(ply, clr) end --- -- This hook is called (mostly on rolechanges) when the player's darkened role color @@ -1175,9 +1378,7 @@ end -- @return nil|Color The new color that is intended for the player -- @hook -- @realm shared -function GM:TTT2ModifyRoleDkColor(ply, clr) - -end +function GM:TTT2ModifyRoleDkColor(ply, clr) end --- -- This hook is called (mostly on rolechanges) when the player's lightened role color @@ -1187,9 +1388,7 @@ end -- @return nil|Color The new color that is intended for the player -- @hook -- @realm shared -function GM:TTT2ModifyRoleLtColor(ply, clr) - -end +function GM:TTT2ModifyRoleLtColor(ply, clr) end --- -- This hook is called (mostly on rolechanges) when the player's background role color @@ -1199,6 +1398,4 @@ end -- @return nil|Color The new color that is intended for the player -- @hook -- @realm shared -function GM:TTT2ModifyRoleBgColor(ply, clr) - -end +function GM:TTT2ModifyRoleBgColor(ply, clr) end diff --git a/gamemodes/terrortown/gamemode/shared/sh_playerclass.lua b/gamemodes/terrortown/gamemode/shared/sh_playerclass.lua new file mode 100644 index 000000000..156983e38 --- /dev/null +++ b/gamemodes/terrortown/gamemode/shared/sh_playerclass.lua @@ -0,0 +1,18 @@ +DEFINE_BASECLASS("player_default") + +local PLAYER = {} + +PLAYER.WalkSpeed = 220 +PLAYER.RunSpeed = 220 +PLAYER.JumpPower = 160 +PLAYER.CrouchedWalkSpeed = 0.3 + +-- This is needed to allow adding custom networked variables to the player entity +-- Can be useful for e.g. variables necessary for prediction like the stamina. +-- @realm shared +-- @internal +function PLAYER:SetupDataTables() + self.Player:SetupDataTables() +end + +player_manager.RegisterClass("player_ttt", PLAYER, "player_default") diff --git a/gamemodes/terrortown/gamemode/shared/sh_printmessage_override.lua b/gamemodes/terrortown/gamemode/shared/sh_printmessage_override.lua index 99359c62f..7ef334b67 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_printmessage_override.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_printmessage_override.lua @@ -9,36 +9,36 @@ local plymeta = assert(FindMetaTable("Player"), "FAILED TO FIND PLAYER TABLE") if SERVER then - --- - -- Displays a message in the chat, console, or center of screen of every player / the defined players. - -- @param number type Which type of message should be sent to the players (see Enums/HUD) - -- @param string message Message to be sent to the players - -- @param table|Player A table of players or a single player to receive the message, is broadcasted if set to nil - -- @realm server - function PrintMessage(type, message, plys) - if type == HUD_PRINTNOTIFY or type == HUD_PRINTCONSOLE then - LANG.Msg(plys, message, nil, MSG_CONSOLE) - elseif type == HUD_PRINTTALK then - LANG.Msg(plys, message, nil, MSG_CHAT_PLAIN) - elseif type == HUD_PRINTCENTER then - EPOP:AddMessage(plys, message, nil, 6, true) - end - end + --- + -- Displays a message in the chat, console, or center of screen of every player / the defined players. + -- @param number type Which type of message should be sent to the players (see Enums/HUD) + -- @param string message Message to be sent to the players + -- @param table|Player A table of players or a single player to receive the message, is broadcasted if set to nil + -- @realm server + function PrintMessage(type, message, plys) + if type == HUD_PRINTNOTIFY or type == HUD_PRINTCONSOLE then + LANG.Msg(plys, message, nil, MSG_CONSOLE) + elseif type == HUD_PRINTTALK then + LANG.Msg(plys, message, nil, MSG_CHAT_PLAIN) + elseif type == HUD_PRINTCENTER then + EPOP:AddMessage(plys, message, nil, 6, true) + end + end else -- CLIENT - --- - -- Displays a message in the chat, console, or center of screen of the local player. - -- @param number type Which type of message should be sent to the players (see Enums/HUD) - -- @param string message Message to be sent to the players - -- @realm client - function PrintMessage(type, message) - if type == HUD_PRINTNOTIFY or type == HUD_PRINTCONSOLE then - LANG.Msg(message, nil, MSG_CONSOLE) - elseif type == HUD_PRINTTALK then - LANG.Msg(message, nil, MSG_CHAT_PLAIN) - elseif type == HUD_PRINTCENTER then - EPOP:AddMessage(message, nil, 6, nil, true) - end - end + --- + -- Displays a message in the chat, console, or center of screen of the local player. + -- @param number type Which type of message should be sent to the players (see Enums/HUD) + -- @param string message Message to be sent to the players + -- @realm client + function PrintMessage(type, message) + if type == HUD_PRINTNOTIFY or type == HUD_PRINTCONSOLE then + LANG.Msg(message, nil, MSG_CONSOLE) + elseif type == HUD_PRINTTALK then + LANG.Msg(message, nil, MSG_CHAT_PLAIN) + elseif type == HUD_PRINTCENTER then + EPOP:AddMessage(message, nil, 6, nil, true) + end + end end --- @@ -50,5 +50,5 @@ end -- @param string message Message to be displayed on the screen on the player -- @realm shared function plymeta:PrintMessage(type, message) - PrintMessage(type, message, self) + PrintMessage(type, message, self) end diff --git a/gamemodes/terrortown/gamemode/shared/sh_role_module.lua b/gamemodes/terrortown/gamemode/shared/sh_role_module.lua index 07d41e51b..3399041d4 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_role_module.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_role_module.lua @@ -4,52 +4,52 @@ local rolesFiles = file.Find(rolesPre .. "*.lua", "LUA") local _, rolesFolders = file.Find(rolesPre .. "*", "LUA") for i = 1, #rolesFiles do - local fl = rolesFiles[i] + local fl = rolesFiles[i] - ROLE = {} + ROLE = {} - local cls = string.sub(fl, 0, #fl - 4) + local cls = string.sub(fl, 0, #fl - 4) - ROLE.name = cls + ROLE.name = cls - include(rolesPre .. fl) + include(rolesPre .. fl) - roles.Register(ROLE, cls) + roles.Register(ROLE, cls) - ROLE = nil + ROLE = nil end for i = 1, #rolesFolders do - local folder = rolesFolders[i] - - ROLE = {} - ROLE.name = folder - - local subFiles = file.Find(rolesPre .. folder .. "/*.lua", "LUA") - - for k = 1, #subFiles do - local fl = subFiles[k] - - if fl == "init.lua" then - if SERVER then - include(rolesPre .. folder .. "/" .. fl) - end - elseif fl == "cl_init.lua" then - if SERVER then - AddCSLuaFile(rolesPre .. folder .. "/" .. fl) - else - include(rolesPre .. folder .. "/" .. fl) - end - else - if SERVER and fl == "shared.lua" then - AddCSLuaFile(rolesPre .. folder .. "/" .. fl) - end - - include(rolesPre .. folder .. "/" .. fl) - end - end - - roles.Register(ROLE, folder) - - ROLE = nil + local folder = rolesFolders[i] + + ROLE = {} + ROLE.name = folder + + local subFiles = file.Find(rolesPre .. folder .. "/*.lua", "LUA") + + for k = 1, #subFiles do + local fl = subFiles[k] + + if fl == "init.lua" then + if SERVER then + include(rolesPre .. folder .. "/" .. fl) + end + elseif fl == "cl_init.lua" then + if SERVER then + AddCSLuaFile(rolesPre .. folder .. "/" .. fl) + else + include(rolesPre .. folder .. "/" .. fl) + end + else + if SERVER and fl == "shared.lua" then + AddCSLuaFile(rolesPre .. folder .. "/" .. fl) + end + + include(rolesPre .. folder .. "/" .. fl) + end + end + + roles.Register(ROLE, folder) + + ROLE = nil end diff --git a/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua b/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua index 4268efd37..ba367d231 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua @@ -7,39 +7,39 @@ rolelayering = {} local function SendLayerData(layerTable) - local layerTableSize = #layerTable + local layerTableSize = #layerTable - net.WriteUInt(layerTableSize, ROLE_BITS) + net.WriteUInt(layerTableSize, ROLE_BITS) - for i = 1, layerTableSize do - local currentLayer = layerTable[i] - local layerDepth = #currentLayer + for i = 1, layerTableSize do + local currentLayer = layerTable[i] + local layerDepth = #currentLayer - net.WriteUInt(layerDepth, ROLE_BITS) + net.WriteUInt(layerDepth, ROLE_BITS) - for cDepth = 1, layerDepth do - -- the role's index - net.WriteUInt(currentLayer[cDepth], ROLE_BITS) - end - end + for cDepth = 1, layerDepth do + -- the role's index + net.WriteUInt(currentLayer[cDepth], ROLE_BITS) + end + end end local function ReadLayerData() - local layerTable = {} - local layerTableSize = net.ReadUInt(ROLE_BITS) + local layerTable = {} + local layerTableSize = net.ReadUInt(ROLE_BITS) - for i = 1, layerTableSize do - local currentLayer = {} - local layerDepth = net.ReadUInt(ROLE_BITS) + for i = 1, layerTableSize do + local currentLayer = {} + local layerDepth = net.ReadUInt(ROLE_BITS) - for cDepth = 1, layerDepth do - currentLayer[cDepth] = net.ReadUInt(ROLE_BITS) - end + for cDepth = 1, layerDepth do + currentLayer[cDepth] = net.ReadUInt(ROLE_BITS) + end - layerTable[i] = currentLayer - end + layerTable[i] = currentLayer + end - return layerTable + return layerTable end --- @@ -48,165 +48,170 @@ end -- @return table A table with roles that have at least two subroles -- @realm shared function rolelayering.GetLayerableBaserolesWithSubroles() - local availableBaseRolesTable = {} - local availableSubRolesTable = {} - local availableBaseRolesAmount = 0 - - local roleList = roles.GetList() - - for i = 1, #roleList do - local roleData = roleList[i] - - -- if the role was created with the intention of never getting selected without any special fulfilled condition, it should be excluded from the layering. - -- here, we don't care about server settings like whether all special roles were deactivated or similar things. Unselectable roles (because of server-related settings) - -- are automatically excluded in the selection process - -- But we could gray the roles that aren't selectable because of server settings, to simplify the layering process? - if roleData.notSelectable then continue end - - if not roleData:IsBaseRole() then - local baserole = roleData:GetBaseRole() - - availableSubRolesTable[baserole] = availableSubRolesTable[baserole] or {} - availableSubRolesTable[baserole][#availableSubRolesTable[baserole] + 1] = roleData.index - else - availableBaseRolesAmount = availableBaseRolesAmount + 1 - - availableBaseRolesTable[availableBaseRolesAmount] = roleData.index - end - end - - -- now get the subroles if there are more than 1 subrole of a related baserole - for cBase = 1, availableBaseRolesAmount do - local baserole = availableBaseRolesTable[cBase] - local currentSubrolesTable = availableSubRolesTable[baserole] - - if currentSubrolesTable == nil or #currentSubrolesTable < 2 then -- related subroles table - availableSubRolesTable[baserole] = nil -- reset if not enough related subroles so a layer wouldn't make any sense - end - end - - -- all selectable baseroles, all selectable subroles with related baseroles - return availableBaseRolesTable, availableSubRolesTable + local availableBaseRolesTable = {} + local availableSubRolesTable = {} + local availableBaseRolesAmount = 0 + + local roleList = roles.GetList() + + for i = 1, #roleList do + local roleData = roleList[i] + + -- if the role was created with the intention of never getting selected without any special fulfilled condition, it should be excluded from the layering. + -- here, we don't care about server settings like whether all special roles were deactivated or similar things. Unselectable roles (because of server-related settings) + -- are automatically excluded in the selection process + -- But we could gray the roles that aren't selectable because of server settings, to simplify the layering process? + if roleData.notSelectable then + continue + end + + if not roleData:IsBaseRole() then + local baserole = roleData:GetBaseRole() + + availableSubRolesTable[baserole] = availableSubRolesTable[baserole] or {} + availableSubRolesTable[baserole][#availableSubRolesTable[baserole] + 1] = roleData.index + else + availableBaseRolesAmount = availableBaseRolesAmount + 1 + + availableBaseRolesTable[availableBaseRolesAmount] = roleData.index + end + end + + -- now get the subroles if there are more than 1 subrole of a related baserole + for cBase = 1, availableBaseRolesAmount do + local baserole = availableBaseRolesTable[cBase] + local currentSubrolesTable = availableSubRolesTable[baserole] + + if currentSubrolesTable == nil or #currentSubrolesTable < 2 then -- related subroles table + availableSubRolesTable[baserole] = nil -- reset if not enough related subroles so a layer wouldn't make any sense + end + end + + -- all selectable baseroles, all selectable subroles with related baseroles + return availableBaseRolesTable, availableSubRolesTable end if SERVER then - util.AddNetworkString("TTT2SyncRolelayerData") + util.AddNetworkString("TTT2SyncRolelayerData") - net.Receive("TTT2SyncRolelayerData", function(_, ply) - if not IsValid(ply) or not ply:IsAdmin() then return end + net.Receive("TTT2SyncRolelayerData", function(_, ply) + if not IsValid(ply) or not ply:IsAdmin() then + return + end - -- ROLE_NONE = 3 is reserved and here used to indicate a baserole request. If a valid baserole is given, the - -- subrole list is requested. For further information, see @{roles.GenerateNewRoleID()} @{function} + -- ROLE_NONE = 3 is reserved and here used to indicate a baserole request. If a valid baserole is given, the + -- subrole list is requested. For further information, see @{roles.GenerateNewRoleID()} @{function} - -- requests are only sent back to the player, data updates are broadcasted - local isDataUpdated = net.ReadBit() == 1 + -- requests are only sent back to the player, data updates are broadcasted + local isDataUpdated = net.ReadBit() == 1 - -- read the table the client requested - local requestedRoleTable = net.ReadUInt(ROLE_BITS) + -- read the table the client requested + local requestedRoleTable = net.ReadUInt(ROLE_BITS) - -- define a table of receivers for the netmessage - local receiverTable = {} + -- define a table of receivers for the netmessage + local receiverTable = {} - if isDataUpdated then - local layerData = ReadLayerData() + if isDataUpdated then + local layerData = ReadLayerData() - if requestedRoleTable == ROLE_NONE then - roleselection.baseroleLayers = layerData - else - roleselection.subroleLayers[requestedRoleTable] = layerData - end + if requestedRoleTable == ROLE_NONE then + roleselection.baseroleLayers = layerData + else + roleselection.subroleLayers[requestedRoleTable] = layerData + end - roleselection.SaveLayers() + roleselection.SaveLayers() - if #layerData == 0 then -- is a reset - receiverTable = player.GetAll() - else - -- send back to everyone but the person updating the data - local plys = player.GetAll() + if #layerData == 0 then -- is a reset + receiverTable = player.GetAll() + else + -- send back to everyone but the person updating the data + local plys = player.GetAll() - for i = 1, #plys do - local p = plys[i] + for i = 1, #plys do + local p = plys[i] - if p == ply then continue end + if p == ply then + continue + end - receiverTable[#receiverTable + 1] = ply - end - end - else - receiverTable = {ply} - end + receiverTable[#receiverTable + 1] = ply + end + end + else + receiverTable = { ply } + end - -- always send back data to the clients if something happened - local layerTable + -- always send back data to the clients if something happened + local layerTable - if requestedRoleTable == ROLE_NONE then - layerTable = roleselection.baseroleLayers - else - layerTable = roleselection.subroleLayers[requestedRoleTable] - end + if requestedRoleTable == ROLE_NONE then + layerTable = roleselection.baseroleLayers + else + layerTable = roleselection.subroleLayers[requestedRoleTable] + end - net.Start("TTT2SyncRolelayerData") - net.WriteUInt(requestedRoleTable, ROLE_BITS) + net.Start("TTT2SyncRolelayerData") + net.WriteUInt(requestedRoleTable, ROLE_BITS) - SendLayerData(layerTable or {}) + SendLayerData(layerTable or {}) - net.Send(receiverTable) - end) + net.Send(receiverTable) + end) end if CLIENT then - --- - -- A helper function to request role layering data from the server for a specific baserole. - -- @param[default=ROLE_NONE] number role The role to request the layer table of - -- @note The answer triggers @{GM:TTT2ReceivedRolelayerData} - -- @note ROLE_NONE = 3 is reserved and here used to indicate a baserole request. If a valid - -- baserole is given, the subrole list is requested. For further information, see - -- @{roles.GenerateNewRoleID()} @{function}. - -- @realm client - function rolelayering.RequestDataFromServer(role) - net.Start("TTT2SyncRolelayerData") - net.WriteBit(0) -- Request data = 0, Send data = 1 - net.WriteUInt(role or ROLE_NONE, ROLE_BITS) - net.SendToServer() - end - - --- - -- A helper function to send updated role layers to the server. - -- @param[default=ROLE_NONE] number role The role to update the layer table of - -- @param table layers The new updated layer table - -- @note ROLE_NONE = 3 is reserved and here used to indicate a baserole request. If a valid - -- baserole is given, the subrole list is requested. For further information, see - -- @{roles.GenerateNewRoleID()} @{function}. - -- @realm client - function rolelayering.SendDataToServer(role, layers) - net.Start("TTT2SyncRolelayerData") - net.WriteBit(1) -- Request data = 0, Send data = 1 - - net.WriteUInt(role or ROLE_NONE, ROLE_BITS) - - SendLayerData(layers) - - net.SendToServer() - end - - net.Receive("TTT2SyncRolelayerData", function() - local roleIndex = net.ReadUInt(ROLE_BITS) - - -- get the role-index value-based table and directly convert it into a role-data value-based table - local layerTable = ReadLayerData() - - --- - -- @realm client - hook.Run("TTT2ReceivedRolelayerData", roleIndex, layerTable) - end) - - --- - -- This hook is called after the server sent a new updated layer table to the client. - -- @param number roleIndex The role whose layer table got updated - -- @param table layerTable The updated layer table - -- @hook - -- @realm client - function GM:TTT2ReceivedRolelayerData(roleIndex, layerTable) - - end + --- + -- A helper function to request role layering data from the server for a specific baserole. + -- @param[default=ROLE_NONE] number role The role to request the layer table of + -- @note The answer triggers @{GM:TTT2ReceivedRolelayerData} + -- @note ROLE_NONE = 3 is reserved and here used to indicate a baserole request. If a valid + -- baserole is given, the subrole list is requested. For further information, see + -- @{roles.GenerateNewRoleID()} @{function}. + -- @realm client + function rolelayering.RequestDataFromServer(role) + net.Start("TTT2SyncRolelayerData") + net.WriteBit(0) -- Request data = 0, Send data = 1 + net.WriteUInt(role or ROLE_NONE, ROLE_BITS) + net.SendToServer() + end + + --- + -- A helper function to send updated role layers to the server. + -- @param[default=ROLE_NONE] number role The role to update the layer table of + -- @param table layers The new updated layer table + -- @note ROLE_NONE = 3 is reserved and here used to indicate a baserole request. If a valid + -- baserole is given, the subrole list is requested. For further information, see + -- @{roles.GenerateNewRoleID()} @{function}. + -- @realm client + function rolelayering.SendDataToServer(role, layers) + net.Start("TTT2SyncRolelayerData") + net.WriteBit(1) -- Request data = 0, Send data = 1 + + net.WriteUInt(role or ROLE_NONE, ROLE_BITS) + + SendLayerData(layers) + + net.SendToServer() + end + + net.Receive("TTT2SyncRolelayerData", function() + local roleIndex = net.ReadUInt(ROLE_BITS) + + -- get the role-index value-based table and directly convert it into a role-data value-based table + local layerTable = ReadLayerData() + + --- + -- @realm client + -- stylua: ignore + hook.Run("TTT2ReceivedRolelayerData", roleIndex, layerTable) + end) + + --- + -- This hook is called after the server sent a new updated layer table to the client. + -- @param number roleIndex The role whose layer table got updated + -- @param table layerTable The updated layer table + -- @hook + -- @realm client + function GM:TTT2ReceivedRolelayerData(roleIndex, layerTable) end end diff --git a/gamemodes/terrortown/gamemode/shared/sh_scoring.lua b/gamemodes/terrortown/gamemode/shared/sh_scoring.lua index a0d460d09..8fdb82faf 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_scoring.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_scoring.lua @@ -49,31 +49,35 @@ local WeaponNames -- @return table -- @realm shared function GetWeaponClassNames() - if WeaponNames then - return WeaponNames - end + if WeaponNames then + return WeaponNames + end - local tbl = {} - local weps = weapons.GetList() + local tbl = {} + local weps = weapons.GetList() - for i = 1, #weps do - local v = weps[i] + for i = 1, #weps do + local v = weps[i] - if v == nil or v.WeaponID == nil then continue end + if v == nil or v.WeaponID == nil then + continue + end - tbl[v.WeaponID] = WEPS.GetClass(v) - end + tbl[v.WeaponID] = WEPS.GetClass(v) + end - for _, v in pairs(scripted_ents.GetList()) do - local id = istable(v) and (v.WeaponID or (v.t and v.t.WeaponID)) or nil - if id == nil then continue end + for _, v in pairs(scripted_ents.GetList()) do + local id = istable(v) and (v.WeaponID or (v.t and v.t.WeaponID)) or nil + if id == nil then + continue + end - tbl[id] = WEPS.GetClass(v) - end + tbl[id] = WEPS.GetClass(v) + end - WeaponNames = tbl + WeaponNames = tbl - return WeaponNames + return WeaponNames end --- @@ -83,13 +87,13 @@ end -- @return Weapon -- @realm shared function EnumToSWEP(ammo) - local e2w = GetWeaponClassNames() or {} + local e2w = GetWeaponClassNames() or {} - if e2w[ammo] then - return util.WeaponForClass(e2w[ammo]) - else - return - end + if e2w[ammo] then + return util.WeaponForClass(e2w[ammo]) + else + return + end end --- @@ -99,9 +103,9 @@ end -- @return any -- @realm shared function EnumToSWEPKey(ammo, key) - local swep = EnumToSWEP(ammo) + local swep = EnumToSWEP(ammo) - return swep and swep[key] + return swep and swep[key] end --- @@ -114,7 +118,7 @@ end -- @realm shared -- @see EnumToSWEPKey function EnumToWep(ammo) - return EnumToSWEPKey(ammo, "PrintName") + return EnumToSWEPKey(ammo, "PrintName") end --- @@ -123,7 +127,9 @@ end -- @return string the @{Weapon} id -- @realm shared function WepToEnum(wep) - if not IsValid(wep) then return end + if not IsValid(wep) then + return + end - return wep.WeaponID + return wep.WeaponID end diff --git a/gamemodes/terrortown/gamemode/shared/sh_shop.lua b/gamemodes/terrortown/gamemode/shared/sh_shop.lua new file mode 100644 index 000000000..52968c752 --- /dev/null +++ b/gamemodes/terrortown/gamemode/shared/sh_shop.lua @@ -0,0 +1,449 @@ +--- +-- A class to handle all shop requirements +-- @author ZenBre4ker +-- @class Shop + +shop = shop or {} + +shop.statusCode = { + SUCCESS = 1, + SUCCESSIGNORECOST = 2, + INVALIDPLAYER = 3, + INVALIDID = 4, + NOTEXISTING = 5, + NOTENOUGHCREDITS = 6, + NOTBUYABLE = 7, + NOTENOUGHPLAYERS = 8, + LIMITEDBOUGHT = 9, + TEAMLIMITEDBOUGHT = 10, + GLOBALLIMITEDBOUGHT = 11, + NOTBUYABLEFORROLE = 12, + PENDINGORDER = 13, + NOSHOP = 14, + NORANDOMSHOP = 15, + NOTINSHOP = 16, + BLOCKEDBYOLDHOOK = 17, + BLOCKEDBYTTT2HOOK = 18, +} + +shop.buyTable = shop.buyTable or {} +shop.globalBuyTable = shop.globalBuyTable or {} +shop.teamBuyTable = shop.teamBuyTable or {} + +--- +-- Resets buy tables of shop +-- @realm shared +function shop.Reset() + shop.buyTable = {} + shop.globalBuyTable = {} + shop.teamBuyTable = {} +end + +--- +-- Resets team buy tables of shop +-- @param Player ply The player to reset the table of +-- @param string oldTeam The identifier of the old team +-- @realm shared +function shop.ResetTeamBuy(ply, oldTeam) + if CLIENT then + shop.teamBuyTable[oldTeam] = nil + elseif SERVER and oldTeam and shop.teamBuyTable[oldTeam] then + net.Start("TTT2ResetTBEq") + net.WriteString(oldTeam) + net.Send(ply) + end +end + +--- +-- Returns if the equipment is already bought for the player +-- @param Player ply The player to check +-- @param string equipmentName The name of the equipment to check +-- @return bool If the Equipment was bought +-- @realm shared +function shop.IsBoughtFor(ply, equipmentName) + return shop.buyTable[ply] and shop.buyTable[ply][equipmentName] +end + +--- +-- Returns if the equipment is already globally bought by a player +-- @param string equipmentName The name of the equipment to check +-- @return bool If the Equipment was globally bought +-- @realm shared +function shop.IsGlobalBought(equipmentName) + return shop.globalBuyTable[equipmentName] +end + +--- +-- Returns if the equipment is already bought for the players team +-- @param Player ply The player to check the team of +-- @param string equipmentName The name of the equipment to check +-- @return bool If the Equipment was bought by a teammate +-- @realm shared +function shop.IsTeamBoughtFor(ply, equipmentName) + local team = ply:GetTeam() + + return team and shop.teamBuyTable[team] and shop.teamBuyTable[team][equipmentName] +end + +--- +-- Marks the equipment as already bought for the player +-- @param Player ply The player to set it for +-- @param string equipmentName The name of the equipment to set +-- @realm shared +function shop.SetEquipmentBought(ply, equipmentName) + shop.buyTable[ply] = shop.buyTable[ply] or {} + shop.buyTable[ply][equipmentName] = true + + if CLIENT then + return + end + + shop.BroadcastEquipmentGlobalBought(equipmentName) +end + +--- +-- Marks the equipment as already globally bought +-- @param string equipmentName The name of the equipment to set +-- @realm shared +function shop.SetEquipmentGlobalBought(equipmentName) + shop.globalBuyTable[equipmentName] = true + + if CLIENT then + return + end + + shop.BroadcastEquipmentGlobalBought(equipmentName) +end + +--- +-- Marks the equipment as already bought for the team of the player +-- @param Player ply The player to set it for +-- @param string equipmentName The name of the equipment to set +-- @realm shared +function shop.SetEquipmentTeamBought(ply, equipmentName) + local team = ply:GetTeam() + + if not team or team == TEAM_NONE or TEAMS[team].alone then + return + end + + shop.teamBuyTable[team] = shop.teamBuyTable[team] or {} + shop.teamBuyTable[team][equipmentName] = true + + if SERVER then + net.Start("TTT2ReceiveTBEq") + net.WriteString(equipmentName) + net.Send(GetTeamFilter(team)) + end +end + +--- +-- Check if an equipment is currently buyable for a player +-- @param Player ply The player to buy the equipment for +-- @param string equipmentName The name of the equipment to buy +-- @return bool True, if equipment can be bought +-- @return number The shop.statusCode, that lead to the decision +-- @realm shared +function shop.CanBuyEquipment(ply, equipmentName) + if not IsValid(ply) or not ply:IsActive() then + return false, shop.statusCode.INVALIDPLAYER + end + + if not equipmentName then + return false, shop.statusCode.INVALIDID + end + + local isItem = items.IsItem(equipmentName) + + -- we use weapons.GetStored to save time making an unnecessary copy, we will not be modifying it + local equipment = isItem and items.GetStored(equipmentName) or weapons.GetStored(equipmentName) + + if not istable(equipment) then + return false, shop.statusCode.NOTEXISTING + end + + local credits = equipment.credits or 1 + + if credits > ply:GetCredits() then + return false, shop.statusCode.NOTENOUGHCREDITS + end + + if equipment.notBuyable then + return false, shop.statusCode.NOTBUYABLE + end + + if equipment.minPlayers and equipment.minPlayers > 1 then + local activePlys = util.GetActivePlayers() + + if #activePlys < equipment.minPlayers then + return false, shop.statusCode.NOTENOUGHPLAYERS + end + end + + if equipment.limited and shop.IsBoughtFor(ply, equipmentName) then + return false, shop.statusCode.LIMITEDBOUGHT + end + + if equipment.globalLimited and shop.IsGlobalBought(equipmentName) then + return false, shop.statusCode.GLOBALLIMITEDBOUGHT + end + + if equipment.teamLimited and shop.IsTeamBoughtFor(ply, equipmentName) then + return false, shop.statusCode.TEAMLIMITEDBOUGHT + end + + local subrole = GetShopFallback(ply:GetSubRole()) + + -- weapon whitelist check + if not equipment.CanBuy[subrole] then + return false, shop.statusCode.NOTBUYABLEFORROLE + end + + if CLIENT then + return true, shop.statusCode.SUCCESS + end + + -- if we have a pending order because we are in a confined space, don't + -- start a new one + if timer.Exists("give_equipment" .. ply:UniqueID()) then + return false, shop.statusCode.PENDINGORDER + end + + local rd = roles.GetByIndex(subrole) + local shopFallback = GetGlobalString("ttt_" .. rd.abbr .. "_shop_fallback") + + if shopFallback == SHOP_DISABLED then + return false, shop.statusCode.NOSHOP + end + + if GetGlobalBool("ttt2_random_shops") then + if not RANDOMSHOP[ply] or #RANDOMSHOP[ply] == 0 then + return false, shop.statusCode.NORANDOMSHOP + end + + local containedInRandomShop = false + + for i = 1, #RANDOMSHOP[ply] do + if RANDOMSHOP[ply][i].id ~= equipmentName then + continue + end + + containedInRandomShop = true + end + + if not containedInRandomShop then + return false, shop.statusCode.NOTINSHOP + end + end + + -- Still support old items + local oldId = isItem and equipment.oldId or equipmentName + + --- + -- @note Keep compatibility with old addons + -- @realm server + -- stylua: ignore + if not hook.Run("TTTCanOrderEquipment", ply, oldId, isItem) then + return false, shop.statusCode.BLOCKEDBYOLDHOOK + end + + --- + -- @note Add our own hook with more consistent class parameter and some more information + -- @realm server + -- stylua: ignore + local allow, ignoreCost = hook.Run("TTT2CanOrderEquipment", ply, equipmentName, isItem, credits) + if not allow then + return false, shop.statusCode.BLOCKEDBYTTT2HOOK + end + + if ignoreCost then + return true, shop.statusCode.SUCCESSIGNORECOST + end + + return true, shop.statusCode.SUCCESS +end + +--- +-- Buys for player the equipment with the corresponding Id +-- @param Player ply The player to buy the equipment for +-- @param string equipmentName The name of the equipment to buy +-- @return bool True, if equipment can be bought +-- @return number The shop.statusCode, that lead to the decision +-- @realm shared +function shop.BuyEquipment(ply, equipmentName) + local isBuyable, statusCode = shop.CanBuyEquipment(ply, equipmentName) + if not isBuyable then + return false, statusCode + end + + if CLIENT then + net.Start("TTT2OrderEquipment") + net.WriteString(equipmentName) + net.SendToServer() + else + local isItem = items.IsItem(equipmentName) + + -- we use weapons.GetStored to save time making an unnecessary copy, we will not be modifying it + local equipment = isItem and items.GetStored(equipmentName) + or weapons.GetStored(equipmentName) + local credits = equipment.credits or 1 + local ignoreCost = statusCode == shop.statusCode.SUCCESSIGNORECOST + + if not ignoreCost then + ply:SubtractCredits(credits) + end + + ply:AddBought(equipmentName) + + -- no longer restricted to only WEAPON_EQUIP weapons, just anything that + -- is whitelisted and carryable + if isItem then + local item = ply:GiveEquipmentItem(equipmentName) + + if isfunction(item.Bought) then + item:Bought(ply) + end + else + ply:GiveEquipmentWeapon(equipmentName, function(_ply, _equipmentName, _weapon) + if isfunction(_weapon.WasBought) then + -- some weapons give extra ammo after being bought, etc + _weapon:WasBought(_ply) + end + end) + end + + LANG.Msg(ply, "buy_received", nil, MSG_MSTACK_ROLE) + + timer.Simple(0.5, function() + if not IsValid(ply) then + return + end + + net.Start("TTT_BoughtItem") + net.WriteString(equipmentName) + net.Send(ply) + end) + + if GetGlobalBool("ttt2_random_shop_reroll_per_buy") then + shop.ForceRerollShop(ply) + end + + -- Still support old items + local oldId = isItem and equipment.oldId or equipmentName + + --- + -- @note Keep compatibility with old addons + -- @realm server + -- stylua: ignore + hook.Run("TTTOrderedEquipment", ply, oldId, isItem) + + --- + -- @note Add our own hook with more consistent class parameter + -- @realm server + -- stylua: ignore + hook.Run("TTT2OrderedEquipment", ply, equipmentName, isItem, credits, ignoreCost) + end + + return true, statusCode +end + +--- +-- Check if the player can reroll their shop +-- @param Player ply The player to reroll the shop for +-- @return bool True, if shop can be rerolled +-- @realm shared +function shop.CanRerollShop(ply) + return GetGlobalBool("ttt2_random_shops") + and GetGlobalBool("ttt2_random_shop_reroll") + and IsValid(ply) + and ply:IsActiveShopper() + and ply:GetCredits() >= GetGlobalInt("ttt2_random_shop_reroll_cost") +end + +--- +-- Reroll shop for player and subtract the credits of it +-- @note Use `shop.ForceRerollShop(ply)` to reroll without cost and restrictions +-- @param Player ply The player to reroll the shop for +-- @return bool True, if shop was successfully rerolled +-- @realm shared +function shop.TryRerollShop(ply) + if not shop.CanRerollShop(ply) then + return false + end + + if CLIENT then + RunConsoleCommand("ttt2_reroll_shop") + else + ply:SubtractCredits(GetGlobalInt("ttt2_random_shop_reroll_cost")) + shop.ForceRerollShop(ply) + hook.Run("TTT2OrderedEquipment", ply, "reroll_shop", false, GetGlobalInt("ttt2_random_shop_reroll_cost"), false) + end + + return true +end + +--- +-- Transfer credits from one player to another +-- @param Player The player to transfer the credits from +-- @param string The SteamID64 of the player to transfer the credits to +-- @param number The number of credits to transfer +-- @realm shared +function shop.TransferCredits(ply, targetPlyId64, credits) + if not IsValid(ply) or not isstring(targetPlyId64) or not isnumber(credits) or credits <= 0 then + return + end + + if CLIENT then + RunConsoleCommand("ttt_transfer_credits", targetPlyId64, credits) + else + local target = player.GetBySteamID64(targetPlyId64) + + if not IsValid(target) or target == ply then + LANG.Msg(ply, "xfer_no_recip", nil, MSG_MSTACK_ROLE) + + return + end + + if ply:GetCredits() < credits then + LANG.Msg(ply, "xfer_no_credits", nil, MSG_MSTACK_ROLE) + + return + end + + credits = math.Clamp(credits, 0, ply:GetCredits()) + + if credits == 0 then + return + end + + --- + -- @realm server + -- stylua: ignore + local allow, _ = hook.Run("TTT2CanTransferCredits", ply, target, credits) + if allow == false then + return + end + + ply:SubtractCredits(credits) + + if target:IsTerror() and target:Alive() then + target:AddCredits(credits) + hook.Run("TTT2TransferedCredits", ply, target, credits, false) + else + -- The would be recipient is dead, which the sender may not know. + -- Instead attempt to send the credits to the target's corpse, where they can be picked up. + local rag = target:FindCorpse() + + if IsValid(rag) then + CORPSE.SetCredits(rag, CORPSE.GetCredits(rag, 0) + credits) + hook.Run("TTT2TransferedCredits", ply, target, credits, false) + end + end + + net.Start("TTT2CreditTransferUpdate") + net.Send(ply) + + LANG.Msg(ply, "xfer_success", { player = target:Nick() }, MSG_MSTACK_ROLE) + LANG.Msg(target, "xfer_received", { player = ply:Nick(), num = credits }, MSG_MSTACK_ROLE) + end +end diff --git a/gamemodes/terrortown/gamemode/shared/sh_shopeditor.lua b/gamemodes/terrortown/gamemode/shared/sh_shopeditor.lua index 3601ca7cc..7a9b46614 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_shopeditor.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_shopeditor.lua @@ -2,7 +2,6 @@ -- @author Alf21 -- @module ShopEditor - ShopEditor = ShopEditor or {} ShopEditor.MODE_DEFAULT = 1 @@ -10,195 +9,141 @@ ShopEditor.MODE_ADDED = 2 ShopEditor.MODE_INHERIT_ADDED = 3 ShopEditor.MODE_INHERIT_REMOVED = 4 -ShopEditor.groupTitles = { - [1] = "header_equipment_weapon_spawn_setup", - [2] = "header_equipment_setup", - [3] = "header_equipment_value_setup" -} +ShopEditor.sqlItemsName = "ttt2_items" +ShopEditor.accessName = "Items" ShopEditor.savingKeys = { - AutoSpawnable = { - group = 1, - order = 10, - typ = "bool", - default = false, - name = "auto_spawnable", - inverted = false, - b_desc = true, - showForItem = false, - master = nil - }, - spawnType = { - group = 1, - order = 20, - typ = "number", - subtype = "enum", - bits = 5, - default = WEAPON_TYPE_SPECIAL, - choices = entspawnscript.GetEntTypeList(SPAWN_TYPE_WEAPON, {[WEAPON_TYPE_RANDOM] = true}), - lookupNamesFunc = function(entType) - return entspawnscript.GetLangIdentifierFromSpawnType(SPAWN_TYPE_WEAPON, entType) - end, - name = "spawn_type", - b_desc = false, - showForItem = false, - master = "AutoSpawnable" - }, - notBuyable = { - group = 2, - order = 30, - typ = "bool", - default = false, - name = "not_buyable", - inverted = true, - b_desc = true, - showForItem = true, - master = nil - }, - NoRandom = { - group = 2, - order = 40, - typ = "bool", - default = false, - name = "not_random", - inverted = false, - b_desc = true, - showForItem = true, - master = "notBuyable" - }, - globalLimited = { - group = 2, - order = 50, - typ = "bool", - default = false, - name = "global_limited", - inverted = false, - b_desc = true, - showForItem = true, - master = "notBuyable" - }, - teamLimited = { - group = 2, - order = 60, - typ = "bool", - default = false, - name = "team_limited", - inverted = false, - b_desc = true, - showForItem = true, - master = "notBuyable" - }, - limited = { - group = 2, - order = 70, - typ = "bool", - default = false, - name = "player_limited", - inverted = false, - b_desc = true, - showForItem = true, - master = "notBuyable" - }, - minPlayers = { - group = 3, - order = 80, - typ = "number", - bits = 6, - default = 0, - min = 0, - max = 63, - name = "min_players", - b_desc = false, - showForItem = true, - master = "notBuyable" - }, - credits = { - group = 3, - order = 90, - typ = "number", - bits = 5, - default = 1, - min = 0, - max = 20, - name = "credits", - b_desc = false, - showForItem = true, - master = "notBuyable" - } + AutoSpawnable = { + typ = "bool", + default = false, + }, + spawnType = { + typ = "number", + bits = 5, + default = WEAPON_TYPE_SPECIAL, + }, + notBuyable = { + typ = "bool", + default = false, + }, + NoRandom = { + typ = "bool", + default = false, + }, + globalLimited = { + typ = "bool", + default = false, + }, + teamLimited = { + typ = "bool", + default = false, + }, + limited = { + typ = "bool", + default = false, + }, + minPlayers = { + typ = "number", + bits = 6, + default = 0, + }, + credits = { + typ = "number", + bits = 5, + default = 1, + }, + damageScaling = { + typ = "float", + bits = 8, + default = 1, + }, + AllowDrop = { + typ = "bool", + default = true, + }, + overrideDropOnDeath = { + typ = "number", + bits = 5, + default = DROP_ON_DEATH_TYPE_DEFAULT, + }, + Kind = { + typ = "number", + bits = 5, + default = 3, + }, } ShopEditor.savingKeysCount = table.Count(ShopEditor.savingKeys) ShopEditor.savingKeysBitCount = math.ceil(math.log(ShopEditor.savingKeysCount + 1, 2)) ShopEditor.cvars = { - ttt2_random_shops = { - order = 1, - typ = "bool", - default = 0, - name = "random_shops", - b_desc = true - }, - ttt2_random_shop_items = { - order = 2, - typ = "number", - bits = 6, - default = 10, - min = 1, - max = 60, - name = "random_shop_items", - b_desc = true - }, - ttt2_random_team_shops = { - order = 3, - typ = "bool", - default = 1, - name = "random_team_shops", - b_desc = false - }, - ttt2_random_shop_reroll = { - order = 4, - typ = "bool", - default = 1, - name = "random_shop_reroll", - b_desc = false - }, - ttt2_random_shop_reroll_cost = { - order = 5, - typ = "number", - bits = 4, - default = 1, - min = 0, - max = 10, - name = "random_shop_reroll_cost", - b_desc = false - }, - ttt2_random_shop_reroll_per_buy = { - order = 6, - typ = "bool", - default = 0, - name = "random_shop_reroll_per_buy", - b_desc = false - } + ttt2_random_shops = { + order = 1, + typ = "bool", + default = 0, + name = "random_shops", + b_desc = true, + }, + ttt2_random_shop_items = { + order = 2, + typ = "number", + bits = 6, + default = 10, + min = 1, + max = 60, + name = "random_shop_items", + b_desc = true, + }, + ttt2_random_team_shops = { + order = 3, + typ = "bool", + default = 1, + name = "random_team_shops", + b_desc = false, + }, + ttt2_random_shop_reroll = { + order = 4, + typ = "bool", + default = 1, + name = "random_shop_reroll", + b_desc = false, + }, + ttt2_random_shop_reroll_cost = { + order = 5, + typ = "number", + bits = 4, + default = 1, + min = 0, + max = 10, + name = "random_shop_reroll_cost", + b_desc = false, + }, + ttt2_random_shop_reroll_per_buy = { + order = 6, + typ = "bool", + default = 0, + name = "random_shop_reroll_per_buy", + b_desc = false, + }, } -- Table which contains all equipment and is sorted by their translated equipment names ShopEditor.sortedEquipmentList = {} -local net = net local pairs = pairs local function getDefaultValue(item, key, data) - if key == "spawnType" then - return entspawnscript.GetSpawnTypeFromKind(item.Kind) or data.default or WEAPON_TYPE_SPECIAL - end - - if data.typ == "number" then - return data.default or 0 - elseif data.typ == "bool" then - return data.default or false - else - return data.default or "" - end + if key == "spawnType" then + return entspawnscript.GetSpawnTypeFromKind(item.Kind) or data.default or WEAPON_TYPE_SPECIAL + end + + if data.typ == "number" or data.typ == "float" then + return data.default or 0 + elseif data.typ == "bool" then + return data.default or false + else + return data.default or "" + end end --- @@ -206,96 +151,17 @@ end -- @param ITEM|Weapon item -- @realm shared function ShopEditor.InitDefaultData(item) - if not item then return end - - item.defaultValues = item.defaultValues or {} - - for key, data in pairs(ShopEditor.savingKeys) do - if item[key] == nil then - item[key] = getDefaultValue(item, key, data) - end - - item.defaultValues[key] = item[key] - end -end - ---- --- Writes the @{ITEM} or @{Weapon} data to the network --- @param string messageName --- @param string name --- @param ITEM|Weapon item --- @param table|Player plys --- @realm shared -function ShopEditor.WriteItemData(messageName, name, item, plys) - name = GetEquipmentFileName(name) - - if not name or not item then return end - - net.Start(messageName) - net.WriteString(name) - net.WriteUInt(ShopEditor.savingKeysCount, ShopEditor.savingKeysBitCount or 16) - - for key, data in pairs(ShopEditor.savingKeys) do - net.WriteString(key) - - if data.typ == "number" then - net.WriteUInt(item[key], data.bits or 16) - elseif data.typ == "bool" then - net.WriteBool(item[key]) - else - net.WriteString(item[key]) - end - end - - if SERVER then - local matched = false - - for k = 1, #CHANGED_EQUIPMENT do - if CHANGED_EQUIPMENT[k][1] ~= name then continue end - - matched = true - end - - if not matched then - CHANGED_EQUIPMENT[#CHANGED_EQUIPMENT + 1] = {name, item} - end - - if plys then - net.Send(plys) - else - net.Broadcast() - end - else - net.SendToServer() - end -end - ---- --- Reads the @{ITEM} or @{Weapon} data from the network --- @return string name of the equipment --- @return ITEM|Weapon equipment table --- @realm shared -function ShopEditor.ReadItemData() - local equip, name = GetEquipmentByName(net.ReadString()) - - if not equip then - return name - end - - local keyCount = net.ReadUInt(ShopEditor.savingKeysBitCount or 16) + if not item then + return + end - for i = 1, keyCount do - local key = net.ReadString() - local data = ShopEditor.savingKeys[key] + item.defaultValues = item.defaultValues or {} - if data.typ == "number" then - equip[key] = net.ReadUInt(data.bits or 16) - elseif data.typ == "bool" then - equip[key] = net.ReadBool() - else - equip[key] = net.ReadString() - end - end + for key, data in pairs(ShopEditor.savingKeys) do + if item[key] == nil then + item[key] = getDefaultValue(item, key, data) + end - return name, equip + item.defaultValues[key] = item[key] + end end diff --git a/gamemodes/terrortown/gamemode/shared/sh_speed.lua b/gamemodes/terrortown/gamemode/shared/sh_speed.lua index f1ff9ce6a..c398176bc 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_speed.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_speed.lua @@ -1,53 +1,54 @@ --- --- @class SPEED +---@class SPEED -local plymeta = assert(FindMetaTable("Player"), "FAILED TO FIND ENTITY TABLE") - -SPEED = SPEED or {} +SPEED = {} --- -- Handles the speed calculation based on the @{GM:TTTPlayerSpeedModifier} hook -- @param Player ply The player whose speed should be changed --- @param MoveData moveData The move data +-- @param CMoveData moveData The move data -- @internal -- @realm shared function SPEED:HandleSpeedCalculation(ply, moveData) - if not ply:IsTerror() then return end - - local baseMultiplier = 1 - local isSlowed = false - - -- Slow down ironsighters - local wep = ply:GetActiveWeapon() - - if IsValid(wep) and wep.GetIronsights and wep:GetIronsights() then - baseMultiplier = 120 / 220 - isSlowed = true - end - - local speedMultiplierModifier = {1} - - --- - -- @realm shared - local returnMultiplier = hook.Run("TTTPlayerSpeedModifier", ply, isSlowed, moveData, speedMultiplierModifier) or 1 - - local oldval = ply:GetSpeedMultiplier() - ply.speedModifier = baseMultiplier * returnMultiplier * speedMultiplierModifier[1] - - if SERVER then return end - - local newval = math.Round(ply.speedModifier, 1) - - if newval == 1.0 then - STATUS:RemoveStatus("ttt_speed_status_good") - STATUS:RemoveStatus("ttt_speed_status_bad") - elseif newval > 1.0 and oldval <= 1.0 then - STATUS:RemoveStatus("ttt_speed_status_bad") - STATUS:AddStatus("ttt_speed_status_good") - elseif newval < 1.0 and oldval >= 1.0 then - STATUS:RemoveStatus("ttt_speed_status_good") - STATUS:AddStatus("ttt_speed_status_bad") - end + if not ply:IsTerror() then + return + end + + local baseMultiplier = 1 + local isSlowed = false + + -- Slow down ironsighters + if ply:IsInIronsights() then + baseMultiplier = 120 / 220 + isSlowed = true + end + + local speedMultiplierModifier = { 1 } + + --- + -- @realm shared + -- stylua: ignore + local returnMultiplier = hook.Run("TTTPlayerSpeedModifier", ply, isSlowed, moveData, speedMultiplierModifier) or 1 + + local oldval = ply:GetSpeedMultiplier() + ply.speedModifier = baseMultiplier * returnMultiplier * speedMultiplierModifier[1] + + if SERVER then + return + end + + local newval = math.Round(ply.speedModifier, 1) + + if newval == 1.0 then + STATUS:RemoveStatus("ttt_speed_status_good") + STATUS:RemoveStatus("ttt_speed_status_bad") + elseif newval > 1.0 and oldval <= 1.0 then + STATUS:RemoveStatus("ttt_speed_status_bad") + STATUS:AddStatus("ttt_speed_status_good") + elseif newval < 1.0 and oldval >= 1.0 then + STATUS:RemoveStatus("ttt_speed_status_good") + STATUS:AddStatus("ttt_speed_status_bad") + end end --- @@ -55,50 +56,53 @@ end -- @note This hook is predicted and should be therefore added on both server and client. -- @param Player ply The player whose speed should be modified -- @param boolean isSlowed Is true if the player uses iron sights --- @param MoveData moveData The move data +-- @param CMoveData moveData The move data -- @param table speedMultiplierModifier The speed modifier table. Modify the first table entry to change the player speed -- @return[deprecated] number The deprecated way of changing the player speed -- @hook -- @realm shared -function GM:TTTPlayerSpeedModifier(ply, isSlowed, moveData, speedMultiplierModifier) - -end +function GM:TTTPlayerSpeedModifier(ply, isSlowed, moveData, speedMultiplierModifier) end if CLIENT then - --- - -- Initializes the speed system once the game is ready. - -- It is called in @{GM:Initialize}. - -- @realm client - function SPEED:Initialize() - STATUS:RegisterStatus("ttt_speed_status_good", { - hud = { - Material("vgui/ttt/perks/hud_speedrun.png") - }, - type = "good", - DrawInfo = function() - return math.Round(LocalPlayer():GetSpeedMultiplier(), 1) - end - }) - - STATUS:RegisterStatus("ttt_speed_status_bad", { - hud = { - Material("vgui/ttt/perks/hud_speedrun.png") - }, - type = "bad", - DrawInfo = function() - return math.Round(LocalPlayer():GetSpeedMultiplier(), 1) - end - }) - end + --- + -- Initializes the speed system once the game is ready. + -- It is called in @{GM:Initialize}. + -- @realm client + function SPEED:Initialize() + STATUS:RegisterStatus("ttt_speed_status_good", { + hud = { + Material("vgui/ttt/perks/hud_speedrun.png"), + }, + type = "good", + DrawInfo = function() + return math.Round(LocalPlayer():GetSpeedMultiplier(), 1) + end, + name = "status_speed_name", + sidebarDescription = "status_speed_description_good", + }) + + STATUS:RegisterStatus("ttt_speed_status_bad", { + hud = { + Material("vgui/ttt/perks/hud_speedrun.png"), + }, + type = "bad", + DrawInfo = function() + return math.Round(LocalPlayer():GetSpeedMultiplier(), 1) + end, + name = "status_speed_name", + sidebarDescription = "status_speed_description_bad", + }) + end end --- --- @class Player +---@class Player +local plymeta = assert(FindMetaTable("Player"), "FAILED TO FIND ENTITY TABLE") --- -- Returns the current player speed modifier -- @return number The speed modifier -- @realm shared function plymeta:GetSpeedMultiplier() - return self.speedModifier or 1.0 + return self.speedModifier or 1.0 end diff --git a/gamemodes/terrortown/gamemode/shared/sh_sprint.lua b/gamemodes/terrortown/gamemode/shared/sh_sprint.lua index a199a7057..98e760e6a 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_sprint.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_sprint.lua @@ -1,188 +1,110 @@ -local function PlayerSprint(trySprinting, moveKey) - if SERVER then return end - - local client = LocalPlayer() - - if trySprinting and not GetGlobalBool("ttt2_sprint_enabled", true) then return end - if not trySprinting and not client.isSprinting or trySprinting and client.isSprinting then return end - if client.isSprinting and (client.moveKey and not moveKey or not client.moveKey and moveKey) then return end - - client.oldSprintProgress = client.sprintProgress - client.sprintMultiplier = trySprinting and (1 + GetGlobalFloat("ttt2_sprint_max", 0)) or nil - client.isSprinting = trySprinting - client.moveKey = moveKey +--- +---@class SPRINT +SPRINT = { + -- Set up ConVars + convars = { + -- @realm shared + -- stylua: ignore + enabled = CreateConVar("ttt2_sprint_enabled", "1", { FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED }, "Toggle Sprint (Def: 1)"), + -- @realm shared + -- stylua: ignore + multiplier = CreateConVar("ttt2_sprint_max", "0.5", { FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED }, "The speed modifier the player will receive. Will be added on top of 1, so 0.5 => 1.5 speed. (Def: 0.5)"), + -- @realm shared + -- stylua: ignore + consumption = CreateConVar("ttt2_sprint_stamina_consumption", "0.6", { FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED }, "The speed of the stamina consumption (per second; Def: 0.6)"), + -- @realm shared + -- stylua: ignore + regeneration = CreateConVar("ttt2_sprint_stamina_regeneration", "0.3", { FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED }, "The regeneration time of the stamina (per second; Def: 0.3)"), + }, +} - net.Start("TTT2SprintToggle") - net.WriteBool(trySprinting) - net.SendToServer() +--- +-- Checks if the player is pressing any movement keys and the sprint key at the same time. +-- @param Player ply +-- @return boolean +-- @realm shared +function SPRINT:PlayerWantsToSprint(ply) + local inSprint = ply:KeyDown(IN_SPEED) + local inMovement = ply:KeyDown(IN_FORWARD) + or ply:KeyDown(IN_BACK) + or ply:KeyDown(IN_MOVERIGHT) + or ply:KeyDown(IN_MOVELEFT) + + return inSprint and inMovement end -if SERVER then - util.AddNetworkString("TTT2SprintToggle") - - -- Set ConVars - - --- - -- @realm server - local sprintEnabled = CreateConVar("ttt2_sprint_enabled", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "Toggle Sprint (Def: 1)") - - --- - -- @realm server - local maxSprintMul = CreateConVar("ttt2_sprint_max", "0.5", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "The maximum speed modifier the player will receive (Def: 0.5)") - - --- - -- @realm server - local consumption = CreateConVar("ttt2_sprint_stamina_consumption", "0.6", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "The speed of the stamina consumption (per second; Def: 0.6)") - - --- - -- @realm server - local stamreg = CreateConVar("ttt2_sprint_stamina_regeneration", "0.3", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "The regeneration time of the stamina (per second; Def: 0.3)") - - --- - -- @realm server - local showCrosshair = CreateConVar("ttt2_sprint_crosshair", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "Should the Crosshair be visible while sprinting? (Def: 0)") - - hook.Add("TTT2SyncGlobals", "AddSprintGlobals", function() - SetGlobalBool(sprintEnabled:GetName(), sprintEnabled:GetBool()) - SetGlobalFloat(maxSprintMul:GetName(), maxSprintMul:GetFloat()) - SetGlobalFloat(consumption:GetName(), consumption:GetFloat()) - SetGlobalFloat(stamreg:GetName(), stamreg:GetFloat()) - SetGlobalBool(showCrosshair:GetName(), showCrosshair:GetBool()) - end) - - cvars.AddChangeCallback(sprintEnabled:GetName(), function(name, old, new) - SetGlobalBool(name, tobool(new)) - end, "TTT2SprintENChange") - - cvars.AddChangeCallback(maxSprintMul:GetName(), function(name, old, new) - SetGlobalFloat(name, new) - end, "TTT2SprintSMulChange") - - cvars.AddChangeCallback(consumption:GetName(), function(name, old, new) - SetGlobalFloat(name, new) - end, "TTT2SprintSCChange") - - cvars.AddChangeCallback(stamreg:GetName(), function(name, old, new) - SetGlobalFloat(name, new) - end, "TTT2SprintSRChange") - - cvars.AddChangeCallback(showCrosshair:GetName(), function(name, old, new) - SetGlobalBool(name, tobool(new)) - end, "TTT2SprintCHChange") - - net.Receive("TTT2SprintToggle", function(_, ply) - if not sprintEnabled:GetBool() or not IsValid(ply) then return end - - local bool = net.ReadBool() - - ply.oldSprintProgress = ply.sprintProgress - ply.sprintMultiplier = bool and (1 + maxSprintMul:GetFloat()) or nil - ply.isSprinting = bool - end) -else -- CLIENT - --- - -- @realm client - local enable_doubletap_sprint = CreateConVar("ttt2_enable_doubletap_sprint", "1", {FCVAR_ARCHIVE}) - - --- - -- @realm client - local doubletap_sprint_anykey = CreateConVar("ttt2_doubletap_sprint_anykey", "1", {FCVAR_ARCHIVE}) - - local lastPress = 0 - local lastPressedMoveKey = nil - - --- - -- @param Player ply - -- @param number key - -- @param boolean pressed - -- @realm client - function UpdateInputSprint(ply, key, pressed) - if pressed then - if ply.isSprinting or not enable_doubletap_sprint:GetBool() or ply.preventSprint then return end - - local time = CurTime() - - if lastPressedMoveKey == key and time - lastPress < 0.4 then - PlayerSprint(true, key) - end - - lastPressedMoveKey = key - lastPress = time - else - if not ply.isSprinting then return end - - local moveKey = ply.moveKey - local wantsToMove = ply:KeyDown(IN_FORWARD) or ply:KeyDown(IN_BACK) or ply:KeyDown(IN_MOVERIGHT) or ply:KeyDown(IN_MOVELEFT) - local anyKey = doubletap_sprint_anykey:GetBool() - - if not moveKey or anyKey and wantsToMove or not anyKey and key ~= moveKey then return end - - PlayerSprint(false, key) - end - end - - bind.Register("ttt2_sprint", function() - if not LocalPlayer().preventSprint then - PlayerSprint(true) - end - end, - function() - PlayerSprint(false) - end, "header_bindings_ttt2", "label_bind_sprint", KEY_LSHIFT) +--- +-- Checks if the player wants to sprint and actually can sprint. +-- @param Player ply +-- @return boolean +-- @realm shared +function SPRINT:IsSprinting(ply) + return self.convars.enabled:GetBool() + and self:PlayerWantsToSprint(ply) + and ply:GetSprintStamina() > 0 + and not ply:IsInIronsights() end --- +-- Calculates the new stamina values for a given player. +-- @param Player ply -- @realm shared -function UpdateSprint() - local client - - if CLIENT then - client = LocalPlayer() - - if not IsValid(client) then return end - end - - local plys = client and {client} or player.GetAll() - - for i = 1, #plys do - local ply = plys[i] - - if not ply:OnGround() then continue end - - local wantsToMove = ply:KeyDown(IN_FORWARD) or ply:KeyDown(IN_BACK) or ply:KeyDown(IN_MOVERIGHT) or ply:KeyDown(IN_MOVELEFT) - - if ply.sprintProgress == 1 and (not ply.isSprinting or not wantsToMove) then continue end - if ply.sprintProgress == 0 and ply.isSprinting and wantsToMove then - ply.sprintResetDelayCounter = ply.sprintResetDelayCounter + FrameTime() - - -- If the player keeps sprinting even though they have no stamina, start refreshing stamina after 1.5 seconds automatically - if CLIENT and ply.sprintResetDelayCounter > 1.5 then - PlayerSprint(false, ply.moveKey) - end - - continue - end - - ply.sprintResetDelayCounter = 0 +function SPRINT:HandleStaminaCalculation(ply) + local staminaRegeneratonRate = self.convars.regeneration:GetFloat() + local staminaConsumptionRate = self.convars.consumption:GetFloat() + + local sprintStamina = ply:GetSprintStamina() + local playerWantsToSprint = self:PlayerWantsToSprint(ply) and not ply:IsInIronsights() + + if + (sprintStamina == 1 and not playerWantsToSprint) + or (sprintStamina == 0 and playerWantsToSprint) + then + return + end + + -- Note: This is a table, because it is passed by reference and multiple addons can adjust the value. + local rateModifier = { 1 } + local newStamina = 0 + + if playerWantsToSprint then + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2StaminaDrain", ply, rateModifier) + + newStamina = + math.max(sprintStamina - FrameTime() * rateModifier[1] * staminaConsumptionRate, 0) + else + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2StaminaRegen", ply, rateModifier) + + newStamina = + math.min(sprintStamina + FrameTime() * rateModifier[1] * staminaRegeneratonRate, 1) + end + + ply:SetSprintStamina(newStamina) +end - local modifier = {1} -- Multiple hooking support +--- +-- Calculates the sprint speed multiplier value for a given player. +-- @param Player ply +-- @realm shared +function SPRINT:HandleSpeedMultiplierCalculation(ply) + if not self:IsSprinting(ply) then + return 1 + end - if not ply.isSprinting or not wantsToMove then - --- - -- @realm shared - hook.Run("TTT2StaminaRegen", ply, modifier) + local sprintMultiplierModifier = { 1 } - ply.sprintProgress = math.min((ply.oldSprintProgress or 0) + FrameTime() * modifier[1] * GetGlobalFloat("ttt2_sprint_stamina_regeneration"), 1) - ply.oldSprintProgress = ply.sprintProgress - elseif wantsToMove then - --- - -- @realm shared - hook.Run("TTT2StaminaDrain", ply, modifier) + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTT2PlayerSprintMultiplier", ply, sprintMultiplierModifier) - ply.sprintProgress = math.max((ply.oldSprintProgress or 0) - FrameTime() * modifier[1] * GetGlobalFloat("ttt2_sprint_stamina_consumption"), 0) - ply.oldSprintProgress = ply.sprintProgress - end - end + return (1 + self.convars.multiplier:GetFloat()) * sprintMultiplierModifier[1] end --- @@ -192,9 +114,7 @@ end -- @param table modifierTbl The table in which the modifier can be changed -- @hook -- @realm shared -function GM:TTT2StaminaRegen(ply, modifierTbl) - -end +function GM:TTT2StaminaRegen(ply, modifierTbl) end --- -- A hook that is called once every frame/tick to modify the stamina drain. @@ -203,6 +123,4 @@ end -- @param table modifierTbl The table in which the modifier can be changed -- @hook -- @realm shared -function GM:TTT2StaminaDrain(ply, modifierTbl) - -end +function GM:TTT2StaminaDrain(ply, modifierTbl) end diff --git a/gamemodes/terrortown/gamemode/shared/sh_sql.lua b/gamemodes/terrortown/gamemode/shared/sh_sql.lua index 069caa7b6..245568f7f 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_sql.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_sql.lua @@ -22,51 +22,53 @@ sql = sql or {} -- @realm shared -- @todo usage function sql.GetParsedData(key, data, res) - if key == "BaseClass" then return end - - local val = res[key] - - if val == "NULL" then - return nil - end - - if data.typ == "number" then - val = tonumber(val) - elseif data.typ == "bool" then - val = val == "1" - elseif data.typ == "pos" then - val = { - x = tonumber(res[key .. "_x"]), - y = tonumber(res[key .. "_y"]) - } - - if not val.x or not val.y then - val = nil - end - elseif data.typ == "size" then - val = { - w = tonumber(res[key .. "_w"]), - h = tonumber(res[key .. "_h"]) - } - - if not val.w or not val.h then - val = nil - end - elseif data.typ == "color" then - val = {} - val.r = tonumber(res[key .. "_r"]) - val.g = tonumber(res[key .. "_g"]) - val.b = tonumber(res[key .. "_b"]) - val.a = tonumber(res[key .. "_a"] or 255) - - if not val.r or not val.g or not val.b then - val = nil - else - val = Color(val.r, val.g, val.b, val.a) - end - end - - return val + if key == "BaseClass" then + return + end + + local val = res[key] + + if val == "NULL" then + return nil + end + + if data.typ == "number" or data.typ == "float" then + val = tonumber(val) + elseif data.typ == "bool" then + val = val == "1" + elseif data.typ == "pos" then + val = { + x = tonumber(res[key .. "_x"]), + y = tonumber(res[key .. "_y"]), + } + + if not val.x or not val.y then + val = nil + end + elseif data.typ == "size" then + val = { + w = tonumber(res[key .. "_w"]), + h = tonumber(res[key .. "_h"]), + } + + if not val.w or not val.h then + val = nil + end + elseif data.typ == "color" then + val = {} + val.r = tonumber(res[key .. "_r"]) + val.g = tonumber(res[key .. "_g"]) + val.b = tonumber(res[key .. "_b"]) + val.a = tonumber(res[key .. "_a"] or 255) + + if not val.r or not val.g or not val.b then + val = nil + else + val = Color(val.r, val.g, val.b, val.a) + end + end + + return val end --- @@ -78,40 +80,44 @@ end -- @realm shared -- @todo usage function sql.ParseData(tbl, keys) - local tmp = {} - - for key, data in pairs(keys) do - if key == "BaseClass" then continue end - - local dat = tbl[key] - - if dat == nil then - dat = data.default - end - - if dat == nil then continue end - - if data.typ == "bool" then - dat = dat and 1 or 0 - - tmp[key] = dat - elseif data.typ == "pos" then - tmp[key .. "_x"] = dat.x or 0 - tmp[key .. "_y"] = dat.y or 0 - elseif data.typ == "size" then - tmp[key .. "_w"] = dat.w or 0 - tmp[key .. "_h"] = dat.h or 0 - elseif data.typ == "color" then - tmp[key .. "_r"] = dat.r or 255 - tmp[key .. "_g"] = dat.g or 255 - tmp[key .. "_b"] = dat.b or 255 - tmp[key .. "_a"] = dat.a or 255 - else - tmp[key] = dat - end - end - - return tmp + local tmp = {} + + for key, data in pairs(keys) do + if key == "BaseClass" then + continue + end + + local dat = tbl[key] + + if dat == nil then + dat = data.default + end + + if dat == nil then + continue + end + + if data.typ == "bool" then + dat = dat and 1 or 0 + + tmp[key] = dat + elseif data.typ == "pos" then + tmp[key .. "_x"] = dat.x or 0 + tmp[key .. "_y"] = dat.y or 0 + elseif data.typ == "size" then + tmp[key .. "_w"] = dat.w or 0 + tmp[key .. "_h"] = dat.h or 0 + elseif data.typ == "color" then + tmp[key .. "_r"] = dat.r or 255 + tmp[key .. "_g"] = dat.g or 255 + tmp[key .. "_b"] = dat.b or 255 + tmp[key .. "_a"] = dat.a or 255 + else + tmp[key] = dat + end + end + + return tmp end --- @@ -122,21 +128,32 @@ end -- @realm shared -- @todo usage function sql.ParseDataString(key, data) - if key == "BaseClass" then return end - - local sanitizedKey = sql.SQLStr(key, true) - - if data.typ == "bool" or data.typ == "number" then - return sanitizedKey .. " INTEGER" - elseif data.typ == "pos" then - return sanitizedKey .. "_x INTEGER," .. sanitizedKey .. "_y INTEGER" - elseif data.typ == "size" then - return sanitizedKey .. "_w INTEGER," .. sanitizedKey .. "_h INTEGER" - elseif data.typ == "color" then - return sanitizedKey .. "_r INTEGER," .. sanitizedKey .. "_g INTEGER," .. sanitizedKey .. "_b INTEGER," .. sanitizedKey .. "_a INTEGER" - end - - return sanitizedKey .. " TEXT" + if key == "BaseClass" then + return + end + + local sanitizedKey = sql.SQLStr(key, true) + + if data.typ == "bool" or data.typ == "number" then + return sanitizedKey .. " INTEGER" + elseif data.typ == "float" then + return sanitizedKey .. " REAL" + elseif data.typ == "pos" then + return sanitizedKey .. "_x INTEGER," .. sanitizedKey .. "_y INTEGER" + elseif data.typ == "size" then + return sanitizedKey .. "_w INTEGER," .. sanitizedKey .. "_h INTEGER" + elseif data.typ == "color" then + return sanitizedKey + .. "_r INTEGER," + .. sanitizedKey + .. "_g INTEGER," + .. sanitizedKey + .. "_b INTEGER," + .. sanitizedKey + .. "_a INTEGER" + end + + return sanitizedKey .. " TEXT" end -- @@ -154,25 +171,27 @@ end -- @realm shared -- @todo usage function sql.BuildInsertString(tableName, name, tbl, keys) - if not keys then return end + if not keys then + return + end - local tmp = sql.ParseData(tbl, keys) + local tmp = sql.ParseData(tbl, keys) - local str = "INSERT INTO " .. sql.SQLStr(tableName) .. " (name" + local str = "INSERT INTO " .. sql.SQLStr(tableName) .. " (name" - for k in pairs(tmp) do - str = str .. "," .. sql.SQLStr(k) - end + for k in pairs(tmp) do + str = str .. "," .. sql.SQLStr(k) + end - str = str .. ") VALUES (" .. sql.SQLStr(name) + str = str .. ") VALUES (" .. sql.SQLStr(name) - for _, v in pairs(tmp) do - str = str .. "," .. sql.SQLStr(v) - end + for _, v in pairs(tmp) do + str = str .. "," .. sql.SQLStr(v) + end - str = str .. ")" + str = str .. ")" - return str + return str end --- @@ -185,25 +204,27 @@ end -- @realm shared -- @todo usage function sql.BuildUpdateString(tableName, name, tbl, keys) - if not keys then return end + if not keys then + return + end - local tmp = sql.ParseData(tbl, keys) + local tmp = sql.ParseData(tbl, keys) - local b = true - local str = "UPDATE " .. sql.SQLStr(tableName) .. " SET " + local b = true + local str = "UPDATE " .. sql.SQLStr(tableName) .. " SET " - for k, v in pairs(tmp) do - if not b then - str = str .. "," - end + for k, v in pairs(tmp) do + if not b then + str = str .. "," + end - b = false - str = str .. sql.SQLStr(k) .. "=" .. sql.SQLStr(v) - end + b = false + str = str .. sql.SQLStr(k) .. "=" .. sql.SQLStr(v) + end - str = str .. " WHERE name=" .. sql.SQLStr(name) + str = str .. " WHERE name=" .. sql.SQLStr(name) - return str + return str end --- @@ -214,44 +235,50 @@ end -- @realm shared -- @todo usage function sql.CreateSqlTable(tableName, keys) - local result + local result - if not sql.TableExists(tableName) then - local str = "CREATE TABLE " .. sql.SQLStr(tableName) .. " (name TEXT PRIMARY KEY" + if not sql.TableExists(tableName) then + local str = "CREATE TABLE " .. sql.SQLStr(tableName) .. " (name TEXT PRIMARY KEY" - for key, data in pairs(keys) do - str = str .. ", " .. sql.ParseDataString(key, data) - end + for key, data in pairs(keys) do + str = str .. ", " .. sql.ParseDataString(key, data) + end - str = str .. ")" + str = str .. ")" - result = sql.Query(str) - else - local clmns = sql.Query("PRAGMA table_info(" .. sql.SQLStr(tableName) .. ")") + result = sql.Query(str) + else + local clmns = sql.Query("PRAGMA table_info(" .. sql.SQLStr(tableName) .. ")") - for key, data in pairs(keys) do - local exists = false + for key, data in pairs(keys) do + local exists = false - for i = 1, #clmns do - if clmns[i].name ~= key then continue end + for i = 1, #clmns do + if clmns[i].name ~= key then + continue + end - exists = true - end + exists = true + end - if exists then continue end + if exists then + continue + end - local res = sql.ParseDataString(key, data) - if not res then continue end + local res = sql.ParseDataString(key, data) + if not res then + continue + end - local resArr = string.Explode(",", res) + local resArr = string.Explode(",", res) - for i = 1, #resArr do - sql.Query("ALTER TABLE " .. sql.SQLStr(tableName) .. " ADD " .. resArr[i]) - end - end - end + for i = 1, #resArr do + sql.Query("ALTER TABLE " .. sql.SQLStr(tableName) .. " ADD " .. resArr[i]) + end + end + end - return result ~= false + return result ~= false end --- @@ -264,12 +291,16 @@ end -- @realm shared -- @todo usage function sql.Init(tableName, name, tbl, keys) - if not keys or table.IsEmpty(keys) then return end + if not keys or table.IsEmpty(keys) then + return + end - local query = sql.BuildInsertString(tableName, name, tbl, keys) - if not query then return end + local query = sql.BuildInsertString(tableName, name, tbl, keys) + if not query then + return + end - return sql.Query(query) + return sql.Query(query) end --- @@ -282,12 +313,16 @@ end -- @realm shared -- @todo usage function sql.Save(tableName, name, tbl, keys) - if not keys or table.IsEmpty(keys) then return end + if not keys or table.IsEmpty(keys) then + return + end - local query = sql.BuildUpdateString(tableName, name, tbl, keys) - if not query then return end + local query = sql.BuildUpdateString(tableName, name, tbl, keys) + if not query then + return + end - return sql.Query(query) + return sql.Query(query) end --- @@ -302,26 +337,33 @@ end -- @realm shared -- @todo usage function sql.Load(tableName, name, tbl, keys) - if not keys or table.IsEmpty(keys) then return end + if not keys or table.IsEmpty(keys) then + return + end - local result = sql.Query("SELECT * FROM " .. sql.SQLStr(tableName) .. " WHERE name = " .. sql.SQLStr(name)) + local result = + sql.Query("SELECT * FROM " .. sql.SQLStr(tableName) .. " WHERE name = " .. sql.SQLStr(name)) - if not result or not result[1] then - return false, false - end + if not result or not result[1] then + return false, false + end - local res = result[1] - local changed = false + local res = result[1] + local changed = false - for key, data in pairs(keys) do - if key == "BaseClass" then continue end + for key, data in pairs(keys) do + if key == "BaseClass" then + continue + end - local nres = sql.GetParsedData(key, data, res) - if nres == nil or nres == "NULL" or tbl[key] == nres then continue end + local nres = sql.GetParsedData(key, data, res) + if nres == nil or nres == "NULL" or tbl[key] == nres then + continue + end - tbl[key] = nres -- overwrite with saved one - changed = true - end + tbl[key] = nres -- overwrite with saved one + changed = true + end - return true, changed + return true, changed end diff --git a/gamemodes/terrortown/gamemode/shared/sh_voice.lua b/gamemodes/terrortown/gamemode/shared/sh_voice.lua index f7c04b7ad..ec032563f 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_voice.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_voice.lua @@ -2,7 +2,7 @@ -- @section voice_manager --- --- Whether or not the @{Player} can use the voice chat. +-- Whether or not the @{Player} can use the voice chat. -- @note Has to be registered on both client and server to hide the UI -- element and stop the voicechat -- @param Player ply @{Player} who wants to use the voice chat @@ -11,5 +11,5 @@ -- @hook -- @realm shared function GM:TTT2CanUseVoiceChat(ply, isTeam) - return true + return true end diff --git a/gamemodes/terrortown/gamemode/shared/sh_weaponry.lua b/gamemodes/terrortown/gamemode/shared/sh_weaponry.lua index feae03136..7ea55d9a5 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_weaponry.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_weaponry.lua @@ -18,9 +18,9 @@ local scriptedEntsGetList = scripted_ents.GetList -- @return boolean weapon type (kind) -- @realm shared function WEPS.TypeForWeapon(class) - local tbl = util.WeaponForClass(class) + local tbl = util.WeaponForClass(class) - return tbl and tbl.Kind or WEAPON_NONE + return tbl and tbl.Kind or WEAPON_NONE end --- @@ -29,7 +29,7 @@ end -- @return boolean whether the table is a valid equipment (weapon) -- @realm shared function WEPS.IsEquipment(wep) - return wep and wep.Kind and wep.Kind >= WEAPON_EQUIP + return wep and wep.Kind and wep.Kind >= WEAPON_EQUIP end --- @@ -38,11 +38,11 @@ end -- @return nil|string weapon's class -- @realm shared function WEPS.GetClass(wep) - if istable(wep) then - return wep.ClassName or wep.Classname or wep.id or wep.name - elseif IsValid(wep) then - return wep:GetClass() - end + if istable(wep) then + return wep.ClassName or wep.Classname or wep.id or wep.name + elseif IsValid(wep) then + return wep:GetClass() + end end --- @@ -51,27 +51,31 @@ end -- @return table An indexed table with all spawnable weapons including those with invalid spawn types -- @realm shared function WEPS.GetWeaponsForSpawnTypes() - local wepsForSpawns = {} - local wepsTable = {} - local weps = wepGetList() + local wepsForSpawns = {} + local wepsTable = {} + local weps = wepGetList() - for i = 1, #weps do - local wep = weps[i] - local spawnType = wep.spawnType + for i = 1, #weps do + local wep = weps[i] + local spawnType = wep.spawnType - if not wep.AutoSpawnable then continue end + if not wep.AutoSpawnable then + continue + end - -- add these entities to the random weapon table even if they might - -- not have a spawn type defined - wepsTable[#wepsTable + 1] = wep + -- add these entities to the random weapon table even if they might + -- not have a spawn type defined + wepsTable[#wepsTable + 1] = wep - if not spawnType then continue end + if not spawnType then + continue + end - wepsForSpawns[spawnType] = wepsForSpawns[spawnType] or {} - wepsForSpawns[spawnType][#wepsForSpawns[spawnType] + 1] = wep - end + wepsForSpawns[spawnType] = wepsForSpawns[spawnType] or {} + wepsForSpawns[spawnType][#wepsForSpawns[spawnType] + 1] = wep + end - return wepsForSpawns, wepsTable + return wepsForSpawns, wepsTable end --- @@ -80,30 +84,36 @@ end -- @return table An indexed table with all spawnable ammo including those with invalid spawn types -- @realm shared function WEPS.GetAmmoForSpawnTypes() - local ammoForSpawns = {} - local ammoTable = {} + local ammoForSpawns = {} + local ammoTable = {} - local allEnts = scriptedEntsGetList() + local allEnts = scriptedEntsGetList() - for _, entData in pairs(allEnts) do - if entData.Base ~= "base_ammo_ttt" then continue end + for _, entData in pairs(allEnts) do + if entData.Base ~= "base_ammo_ttt" then + continue + end - local ammo = entData.t - local spawnType = ammo.spawnType + local ammo = entData.t + local spawnType = ammo.spawnType - if not ammo.AutoSpawnable then continue end + if not ammo.AutoSpawnable then + continue + end - -- add these entities to the random ammo table even if they might - -- not have a spawn type defined - ammoTable[#ammoTable + 1] = ammo + -- add these entities to the random ammo table even if they might + -- not have a spawn type defined + ammoTable[#ammoTable + 1] = ammo - if not spawnType then continue end + if not spawnType then + continue + end - ammoForSpawns[spawnType] = ammoForSpawns[spawnType] or {} - ammoForSpawns[spawnType][#ammoForSpawns[spawnType] + 1] = ammo - end + ammoForSpawns[spawnType] = ammoForSpawns[spawnType] or {} + ammoForSpawns[spawnType][#ammoForSpawns[spawnType] + 1] = ammo + end - return ammoForSpawns, ammoTable + return ammoForSpawns, ammoTable end --- @@ -111,12 +121,14 @@ end -- @param Player ply -- @realm shared function WEPS.DisguiseToggle(ply) - if not IsValid(ply) or not ply:IsActive() then return end - - if not ply:GetNWBool("disguised", false) then - RunConsoleCommand("ttt_set_disguise", "1") - else - RunConsoleCommand("ttt_set_disguise", "0") - end + if not IsValid(ply) or not ply:IsActive() then + return + end + + if not ply:GetNWBool("disguised", false) then + RunConsoleCommand("ttt_set_disguise", "1") + else + RunConsoleCommand("ttt_set_disguise", "0") + end end concommand.Add("ttt_toggle_disguise", WEPS.DisguiseToggle) diff --git a/gamemodes/terrortown/ttt2.fgd b/gamemodes/terrortown/ttt2.fgd index dbfe212aa..eed61d0ba 100644 --- a/gamemodes/terrortown/ttt2.fgd +++ b/gamemodes/terrortown/ttt2.fgd @@ -17,6 +17,8 @@ @PointClass base(Item) studio("models/items/BoxBuckshot.mdl")= item_box_buckshot_ttt : "Box Buckshot" [] @PointClass base(Item) studio("models/items/357ammo.mdl")= item_ammo_revolver_ttt : "Box of Deagle ammo" [] +@PointClass base(Item) studio("models/ttt/deerstalker.mdl")= ttt_hat_deerstalker : "Detective's Hat" [] + @PointClass base(Item) studio("models/items/boxflares.mdl")= ttt_random_ammo : "Random ammo" [] @PointClass base(Item) studio("models/weapons/w_shotgun.mdl")= ttt_random_weapon : "Random weapon" [ auto_ammo(integer) : "Autospawn ammo items" : 0 : "Number of matching ammo entities to spawn around the weapon. Leave some room above this entity for the ammo, it will be spawned there, fall and end up around the gun. Set to 0 to disable." diff --git a/lua/autorun/client/b-draw_lib.lua b/lua/autorun/client/b-draw_lib.lua index 3d91ee6c6..9a36bfb1c 100644 --- a/lua/autorun/client/b-draw_lib.lua +++ b/lua/autorun/client/b-draw_lib.lua @@ -1,4 +1,6 @@ -if engine.ActiveGamemode() ~= "terrortown" then return end +if engine.ActiveGamemode() ~= "terrortown" then + return +end --- -- A Simple Garry's mod drawing library @@ -18,6 +20,7 @@ file.CreateDir("downloaded_assets") local exists = file.Exists local write = file.Write +local delete = file.Delete local fetch = http.Fetch local white = Color(255, 255, 255) local surface = surface @@ -30,48 +33,67 @@ local mats = {} local fetched_avatar_urls = {} local function FetchAsset(url) - if not url then return end + if not url then + return + end - if mats[url] then - return mats[url] - end + if mats[url] then + return mats[url] + end - local crcUrl = crc(url) + local crcUrl = crc(url) - if exists("downloaded_assets/" .. crcUrl .. ".png", "DATA") then - mats[url] = Material("data/downloaded_assets/" .. crcUrl .. ".png") + if exists("downloaded_assets/" .. crcUrl .. ".png", "DATA") then + mats[url] = Material("data/downloaded_assets/" .. crcUrl .. ".png") - return mats[url] - end + return mats[url] + end - fetch(url, function(data) - write("downloaded_assets/" .. crcUrl .. ".png", data) + fetch(url, function(data) + write("downloaded_assets/" .. crcUrl .. ".png", data) - mats[url] = Material("data/downloaded_assets/" .. crcUrl .. ".png") - end) + mats[url] = Material("data/downloaded_assets/" .. crcUrl .. ".png") + end) end local function FetchAvatarAsset(id64, size) - if not id64 then - return _bot_avatar - end + if not id64 then + return _bot_avatar + end + + size = size == "medium" and "_medium" or size == "large" and "_full" or "" + + local key = id64 .. size - size = size == "medium" and "_medium" or size == "large" and "_full" or "" + if fetched_avatar_urls[key] then + return FetchAsset(fetched_avatar_urls[key]) + end - local key = id64 .. size + --- + -- @realm client + -- stylua: ignore + local data = hook.Run("TTT2FetchAvatar", id64, size) + if data ~= nil then + local url = "hook://" .. key + local crcUrl = crc(url) - if fetched_avatar_urls[key] then - return FetchAsset(fetched_avatar_urls[key]) - end + fetched_avatar_urls[key] = url - fetch("http://steamcommunity.com/profiles/" .. id64 .. "/?xml=1", function(body) - local link = body:match("<%!%[CDATA%[(.-)%]%]><%/avatarIcon>") + write("downloaded_assets/" .. crcUrl .. ".png", data) - if not link then return end + return + end - fetched_avatar_urls[key] = link:Replace(".jpg", size .. ".jpg") - FetchAsset(fetched_avatar_urls[key]) - end) + fetch("http://steamcommunity.com/profiles/" .. id64 .. "/?xml=1", function(body) + local link = body:match("<%!%[CDATA%[(.-)%]%]><%/avatarIcon>") + + if not link then + return + end + + fetched_avatar_urls[key] = link:Replace(".jpg", size .. ".jpg") + FetchAsset(fetched_avatar_urls[key]) + end) end --- @@ -81,7 +103,29 @@ end -- @param string size the avatar's size, this can be small, medium or large -- @realm client function draw.CacheAvatar(id64, size) - FetchAvatarAsset(id64, size) + FetchAvatarAsset(id64, size) +end + +--- +-- Deletes the avatar material for a steamid64 +-- when a cached avatar is found it will be destroyed. +-- @param string id64 The player's steamid64 +-- @param string size The avatar's size, this can be small, medium or large +-- @realm client +function draw.DropCacheAvatar(id64, size) + size = size == "medium" and "_medium" or size == "large" and "_full" or "" + local key = id64 .. size + + local url = fetched_avatar_urls[key] + local crcUrl = crc(url) + local uri = "data/downloaded_assets/" .. crcUrl .. ".png" + + if exists(uri, "DATA") then + delete(uri, "DATA") + end + + mats[url] = nil + fetched_avatar_urls[key] = nil end --- @@ -96,20 +140,20 @@ end -- @param boolean cornerorigin if it is set to true, the WebImage will be centered based on the x- and y-coordinate -- @realm client local function DrawImage(material, x, y, width, height, color, angle, cornerorigin) - color = color or white - - surface.SetDrawColor(color.r, color.g, color.b, color.a) - surface.SetMaterial(material) - - if not angle then - surface.DrawTexturedRect(x, y, width, height) - else - if not cornerorigin then - surface.DrawTexturedRectRotated(x, y, width, height, angle) - else - surface.DrawTexturedRectRotated(x + width * 0.5, y + height * 0.5, width, height, angle) - end - end + color = color or white + + surface.SetDrawColor(color.r, color.g, color.b, color.a) + surface.SetMaterial(material) + + if not angle then + surface.DrawTexturedRect(x, y, width, height) + else + if not cornerorigin then + surface.DrawTexturedRectRotated(x, y, width, height, angle) + else + surface.DrawTexturedRectRotated(x + width * 0.5, y + height * 0.5, width, height, angle) + end + end end --- @@ -124,7 +168,7 @@ end -- @param boolean cornerorigin if it is set to true, the WebImage will be centered based on the x- and y-coordinate -- @realm client function draw.WebImage(url, x, y, width, height, color, angle, cornerorigin) - DrawImage(FetchAsset(url) or _error, x, y, width, height, color, angle, cornerorigin) + DrawImage(FetchAsset(url) or _error, x, y, width, height, color, angle, cornerorigin) end --- @@ -137,15 +181,15 @@ end -- @param Color color -- @realm client function draw.SeamlessWebImage(url, parentwidth, parentheight, xrep, yrep, color) - color = color or white + color = color or white - local xiwx, yihy = math.ceil(parentwidth / xrep), math.ceil(parentheight / yrep) + local xiwx, yihy = math.ceil(parentwidth / xrep), math.ceil(parentheight / yrep) - for x = 0, xrep - 1 do - for y = 0, yrep - 1 do - draw.WebImage(url, x * xiwx, y * yihy, xiwx, yihy, color) - end - end + for x = 0, xrep - 1 do + for y = 0, yrep - 1 do + draw.WebImage(url, x * xiwx, y * yihy, xiwx, yihy, color) + end + end end --- @@ -161,7 +205,16 @@ end -- @param boolean cornerorigin if it is set to true, the WebImage will be centered based on the x- and y-coordinate -- @realm client function draw.SteamAvatar(id64, size, x, y, width, height, color, angle, cornerorigin) - DrawImage(FetchAvatarAsset(id64, size) or _default_avatar, x, y, width, height, color, angle, cornerorigin) + DrawImage( + FetchAvatarAsset(id64, size) or _default_avatar, + x, + y, + width, + height, + color, + angle, + cornerorigin + ) end --- @@ -172,5 +225,5 @@ end -- @return Material -- @realm client function draw.GetAvatarMaterial(id64, size) - return FetchAvatarAsset(id64, size) or _default_avatar + return FetchAvatarAsset(id64, size) or _default_avatar end diff --git a/lua/autorun/gs_crazyphysics.lua b/lua/autorun/gs_crazyphysics.lua index 84408f917..bdba13280 100644 --- a/lua/autorun/gs_crazyphysics.lua +++ b/lua/autorun/gs_crazyphysics.lua @@ -1,4 +1,6 @@ -if engine.ActiveGamemode() ~= "terrortown" then return end +if engine.ActiveGamemode() ~= "terrortown" then + return +end --- -- Entity Crash Catcher v2 @@ -14,79 +16,126 @@ RunConsoleCommand("sv_crazyphysics_remove", "1") ------------------- Script ------------------- local function DebugMessage( - bRemove, - Entity, - vEntityPosition, - aEntityRotation, - vEntityVelocity, - vObjectPosition --[[= "N/A"]], - aObjectRotation --[[= "N/A"]], - vObjectVelocity --[[ = "N/A"]] - ) - - return "\n[GS CrazyPhysics] " .. tostring(Entity) - .. (bRemove and " removed!" or " frozen!") - .. "\nEntity position:\t" .. tostring(vEntityPosition) - .. "\nEntity angle:\t" .. tostring(aEntityRotation) - .. "\nEntity velocity:\t" .. tostring(vEntityVelocity) - .. "\nPhysics object position:\t" .. (vObjectPosition == nil and "N/A" or tostring(vObjectPosition)) - .. "\nPhysics object rotation:\t" .. (aObjectRotation == nil and "N/A" or tostring(aObjectRotation)) - .. "\nPhysics object velocity:\t" .. (vObjectVelocity == nil and "N/A" or tostring(vObjectVelocity)) .. "\n" + bRemove, + Entity, + vEntityPosition, + aEntityRotation, + vEntityVelocity, + vObjectPosition --[[= "N/A"]], + aObjectRotation --[[= "N/A"]], + vObjectVelocity --[[ = "N/A"]] +) + return "\n[GS CrazyPhysics] " + .. tostring(Entity) + .. (bRemove and " removed!" or " frozen!") + .. "\nEntity position:\t" + .. tostring(vEntityPosition) + .. "\nEntity angle:\t" + .. tostring(aEntityRotation) + .. "\nEntity velocity:\t" + .. tostring(vEntityVelocity) + .. "\nPhysics object position:\t" + .. (vObjectPosition == nil and "N/A" or tostring(vObjectPosition)) + .. "\nPhysics object rotation:\t" + .. (aObjectRotation == nil and "N/A" or tostring(aObjectRotation)) + .. "\nPhysics object velocity:\t" + .. (vObjectVelocity == nil and "N/A" or tostring(vObjectVelocity)) + .. "\n" end if CLIENT then - net.Receive("GS_CrazyPhysics_Defuse", function() - chat.AddText(DebugMessage(false, "Entity [" .. net.ReadUInt(16) .. "][" .. net.ReadString() .. "]", - net.ReadVector(), net.ReadAngle(), net.ReadVector())) - end) - - net.Receive("GS_CrazyPhysics_Remove", function() - chat.AddText(DebugMessage(true, "Entity [" .. net.ReadUInt(16) .. "][" .. net.ReadString() .. "]", - net.ReadVector(), net.ReadAngle(), net.ReadVector())) - end) - - net.Receive("GS_CrazyPhysics_Defuse_Object", function() - chat.AddText(DebugMessage(false, "Entity [" .. net.ReadUInt(16) .. "][" .. net.ReadString() .. "]", - net.ReadVector(), net.ReadAngle(), net.ReadVector(), net.ReadVector(), net.ReadAngle(), net.ReadVector())) - end) - - net.Receive("GS_CrazyPhysics_Remove_Object", function() - chat.AddText(DebugMessage(true, "Entity [" .. net.ReadUInt(16) .. "][" .. net.ReadString() .. "]", - net.ReadVector(), net.ReadAngle(), net.ReadVector(), net.ReadVector(), net.ReadAngle(), net.ReadVector())) - end) - - return -- following code is serverside-only + net.Receive("GS_CrazyPhysics_Defuse", function() + chat.AddText( + DebugMessage( + false, + "Entity [" .. net.ReadUInt(16) .. "][" .. net.ReadString() .. "]", + net.ReadVector(), + net.ReadAngle(), + net.ReadVector() + ) + ) + end) + + net.Receive("GS_CrazyPhysics_Remove", function() + chat.AddText( + DebugMessage( + true, + "Entity [" .. net.ReadUInt(16) .. "][" .. net.ReadString() .. "]", + net.ReadVector(), + net.ReadAngle(), + net.ReadVector() + ) + ) + end) + + net.Receive("GS_CrazyPhysics_Defuse_Object", function() + chat.AddText( + DebugMessage( + false, + "Entity [" .. net.ReadUInt(16) .. "][" .. net.ReadString() .. "]", + net.ReadVector(), + net.ReadAngle(), + net.ReadVector(), + net.ReadVector(), + net.ReadAngle(), + net.ReadVector() + ) + ) + end) + + net.Receive("GS_CrazyPhysics_Remove_Object", function() + chat.AddText( + DebugMessage( + true, + "Entity [" .. net.ReadUInt(16) .. "][" .. net.ReadString() .. "]", + net.ReadVector(), + net.ReadAngle(), + net.ReadVector(), + net.ReadVector(), + net.ReadAngle(), + net.ReadVector() + ) + ) + end) + + return -- following code is serverside-only end -- Options --- -- @realm server +-- stylua: ignore local gs_crazyphysics = CreateConVar("gs_crazyphysics", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Enables Lua crazyphysics detection") --- -- @realm server +-- stylua: ignore local gs_crazyphysics_echo = CreateConVar("gs_crazyphysics_echo", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Inform players of ragdoll freezing/removal") --- -- @realm server +-- stylua: ignore local gs_crazyphysics_interval = CreateConVar("gs_crazyphysics_interval", "0.1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "How often to check entities for extreme velocity") --- -- @realm server +-- stylua: ignore local gs_crazyphysics_speed_defuse = CreateConVar("gs_crazyphysics_speed_defuse", "4000", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Max velocity in in/s an entity can reach before it's frozen") --- -- @realm server +-- stylua: ignore local gs_crazyphysics_speed_remove = CreateConVar("gs_crazyphysics_speed_remove", "6000", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Max velocity in in/s an entity can reach before it's removed") --- -- @realm server +-- stylua: ignore local gs_crazyphysics_defusetime = CreateConVar("gs_crazyphysics_defusetime", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "How long to freeze the entity for during diffusal") -- Add entity classes you want checked local tEntitiesToCheck = { - "prop_ragdoll" + "prop_ragdoll", } local bTTT @@ -95,17 +144,17 @@ local cv_ttt_announce_body_found local cv_ttt2_confirm_killlist hook.Add("Initialize", "TTT2GSCrazyPhysics", function() - -- Change check if your terrortown folder is named something different - bTTT = engine.ActiveGamemode():lower() == "terrortown" and TTT2 and istable(CORPSE) - if bTTT then - -- Add entity classes you want identified - tIdentifyEntities = { - prop_ragdoll = true - } - - cv_ttt_announce_body_found = GetConVar("ttt_announce_body_found") - cv_ttt2_confirm_killlist = GetConVar("ttt2_confirm_killlist") - end + -- Change check if your terrortown folder is named something different + bTTT = engine.ActiveGamemode():lower() == "terrortown" and TTT2 and istable(CORPSE) + if bTTT then + -- Add entity classes you want identified + tIdentifyEntities = { + prop_ragdoll = true, + } + + cv_ttt_announce_body_found = GetConVar("ttt_announce_body_found") + cv_ttt2_confirm_killlist = GetConVar("ttt2_confirm_killlist") + end end) util.AddNetworkString("GS_CrazyPhysics_Defuse") @@ -116,236 +165,294 @@ util.AddNetworkString("GS_CrazyPhysics_Remove_Object") local iEntityLen = #tEntitiesToCheck local function SetAbsVelocity(pEntity, vAbsVelocity) - if pEntity:GetSaveTable()["m_vecAbsVelocity"] ~= vAbsVelocity then - -- The abs velocity won't be dirty since we're setting it here - pEntity:RemoveEFlags(EFL_DIRTY_ABSVELOCITY) - - -- All children are invalid, but we are not - local tChildren = pEntity:GetChildren() - - for i = 1, #tChildren do - tChildren[i]:AddEFlags(EFL_DIRTY_ABSVELOCITY) - end - - pEntity:SetSaveValue("m_vecAbsVelocity", vAbsVelocity) - - -- NOTE: Do *not* do a network state change in this case. - -- m_vVelocity is only networked for the player, which is not manual mode - local pMoveParent = pEntity:GetMoveParent() - - if IsValid(pMoveParent) then - -- First subtract out the parent's abs velocity to get a relative - -- velocity measured in world space - -- Transform relative velocity into parent space - -- FIXME - --pEntity:SetSaveValue("m_vecVelocity", (vAbsVelocity - pMoveParent:_GetAbsVelocity()):IRotate(pMoveParent:EntityToWorldTransform())) - pEntity:SetSaveValue("velocity", vAbsVelocity) - else - pEntity:SetSaveValue("velocity", vAbsVelocity) - end - end + if pEntity:GetSaveTable()["m_vecAbsVelocity"] ~= vAbsVelocity then + -- The abs velocity won't be dirty since we're setting it here + pEntity:RemoveEFlags(EFL_DIRTY_ABSVELOCITY) + + -- All children are invalid, but we are not + local tChildren = pEntity:GetChildren() + + for i = 1, #tChildren do + tChildren[i]:AddEFlags(EFL_DIRTY_ABSVELOCITY) + end + + pEntity:SetSaveValue("m_vecAbsVelocity", vAbsVelocity) + + -- NOTE: Do *not* do a network state change in this case. + -- m_vVelocity is only networked for the player, which is not manual mode + local pMoveParent = pEntity:GetMoveParent() + + if IsValid(pMoveParent) then + -- First subtract out the parent's abs velocity to get a relative + -- velocity measured in world space + -- Transform relative velocity into parent space + -- FIXME + --pEntity:SetSaveValue("m_vecVelocity", (vAbsVelocity - pMoveParent:_GetAbsVelocity()):IRotate(pMoveParent:EntityToWorldTransform())) + pEntity:SetSaveValue("velocity", vAbsVelocity) + else + pEntity:SetSaveValue("velocity", vAbsVelocity) + end + end end local function KillVelocity(pEntity) - pEntity:CollisionRulesChanged() - pEntity:SetLocalVelocity(vector_origin) -- ::SetLocalVelocity - pEntity:SetVelocity(vector_origin) -- ::SetBaseVelocity - - SetAbsVelocity(pEntity, vector_origin) -- ::SetAbsVelocity - - for i = 0, pEntity:GetPhysicsObjectCount() - 1 do - local pPhysObj = pEntity:GetPhysicsObjectNum(i) - pPhysObj:EnableMotion(false) - pPhysObj:SetVelocity(vector_origin) - pPhysObj:SetVelocityInstantaneous(vector_origin) - pPhysObj:RecheckCollisionFilter() - pPhysObj:Sleep() - end + pEntity:CollisionRulesChanged() + pEntity:SetLocalVelocity(vector_origin) -- ::SetLocalVelocity + pEntity:SetVelocity(vector_origin) -- ::SetBaseVelocity + + SetAbsVelocity(pEntity, vector_origin) -- ::SetAbsVelocity + + for i = 0, pEntity:GetPhysicsObjectCount() - 1 do + local pPhysObj = pEntity:GetPhysicsObjectNum(i) + pPhysObj:EnableMotion(false) + pPhysObj:SetVelocity(vector_origin) + pPhysObj:SetVelocityInstantaneous(vector_origin) + pPhysObj:RecheckCollisionFilter() + pPhysObj:Sleep() + end end local function IdentifyCorpse(pCorpse) - if CORPSE.GetFound(pCorpse, false) then return end - - CORPSE.SetFound(pCorpse, true) - - local pPlayer = pCorpse:GetDTEntity(CORPSE.dti.ENT_PLAYER) - local nRole = ROLE_NONE - local nTeam = TEAM_NONE - - if IsValid(pPlayer) then - pPlayer:TTT2NETSetBool("body_found", true) - - nRole = pCorpse.was_role or pPlayer:GetSubRole() - nTeam = pCorpse.was_team or pPlayer:GetTeam() - --[[ - if nRole == ROLE_TRAITOR then - SendConfirmedTraitors(GetInnocentFilter(false)) - end - ]]-- - SendFullStateUpdate() - else - local sSteamID = pCorpse.sid64 - - if sSteamID then - pPlayer = player.GetBySteamID64(sSteamID) - - if IsValid(pPlayer) then - pPlayer:TTT2NETSetBool("body_found", true) - - nRole = pCorpse.was_role or pPlayer:GetSubRole() - nTeam = pCorpse.was_team or pPlayer:GetTeam() - --[[ - if nRole == ROLE_TRAITOR then - SendConfirmedTraitors(GetInnocentFilter(false)) - end - ]]-- - SendFullStateUpdate() - end - end - end - - if cv_ttt_announce_body_found:GetBool() then - if GetGlobalBool("ttt2_confirm_team") then -- TODO adjust the new messages - LANG.Msg("body_found", { - finder = "The Server", - victim = CORPSE.GetPlayerNick(pCorpse, nil) or pPlayer:GetName(), - role = LANG.Param("body_found_" .. roles.GetByIndex(nRole).abbr), - team = LANG.Param(nTeam) - }) - else - LANG.Msg("body_found", { - finder = "The Server", - victim = CORPSE.GetPlayerNick(pCorpse, nil) or pPlayer:GetName(), - role = LANG.Param("body_found_" .. roles.GetByIndex(nRole).abbr) - }) - end - end - - if cv_ttt2_confirm_killlist:GetBool() then - local tKills = pCorpse.kills - if tKills then - for i = 1, #tKills do - local pVictim = player.GetBySteamID64(tKills[i]) - - if not IsValid(pVictim) or pVictim:TTT2NETGetBool("body_found") then continue end - - pVictim:TTT2NETSetBool("body_found", true) - - LANG.Msg("body_confirm", { - finder = "The Server", - victim = pVictim:GetName() - }) - end - end - end + if CORPSE.GetFound(pCorpse, false) then + return + end + + CORPSE.SetFound(pCorpse, true) + + local pPlayer = pCorpse:GetDTEntity(CORPSE.dti.ENT_PLAYER) + local nRole = ROLE_NONE + local nTeam = TEAM_NONE + + if IsValid(pPlayer) then + pPlayer:TTT2NETSetBool("body_found", true) + + nRole = pCorpse.was_role or pPlayer:GetSubRole() + nTeam = pCorpse.was_team or pPlayer:GetTeam() + --[[ + if nRole == ROLE_TRAITOR then + SendConfirmedTraitors(GetInnocentFilter(false)) + end + ]] + -- + SendFullStateUpdate() + else + local sSteamID = pCorpse.sid64 + + if sSteamID then + pPlayer = player.GetBySteamID64(sSteamID) + + if IsValid(pPlayer) then + pPlayer:TTT2NETSetBool("body_found", true) + + nRole = pCorpse.was_role or pPlayer:GetSubRole() + nTeam = pCorpse.was_team or pPlayer:GetTeam() + --[[ + if nRole == ROLE_TRAITOR then + SendConfirmedTraitors(GetInnocentFilter(false)) + end + ]] + -- + SendFullStateUpdate() + end + end + end + + if cv_ttt_announce_body_found:GetBool() then + if GetGlobalBool("ttt2_confirm_team") then -- TODO adjust the new messages + LANG.Msg("body_found", { + finder = "The Server", + victim = CORPSE.GetPlayerNick(pCorpse, nil) or pPlayer:GetName(), + role = LANG.Param("body_found_" .. roles.GetByIndex(nRole).abbr), + team = LANG.Param(nTeam), + }) + else + LANG.Msg("body_found", { + finder = "The Server", + victim = CORPSE.GetPlayerNick(pCorpse, nil) or pPlayer:GetName(), + role = LANG.Param("body_found_" .. roles.GetByIndex(nRole).abbr), + }) + end + end + + if cv_ttt2_confirm_killlist:GetBool() then + local tKills = pCorpse.kills + if tKills then + for i = 1, #tKills do + local pVictim = player.GetBySteamID64(tKills[i]) + + if not IsValid(pVictim) or pVictim:TTT2NETGetBool("body_found") then + continue + end + + pVictim:TTT2NETSetBool("body_found", true) + + LANG.Msg("body_confirm", { + finder = "The Server", + victim = pVictim:GetName(), + }) + end + end + end end -local function SendMessage(bRemove, bCheckObjectVel, pEntity, vEntityPos, aEntityRot, vEntityVel, vObjectPos, aObjectRot, vObjectVel) - ServerLog(DebugMessage(bRemove, pEntity, vEntityPos, aEntityRot, vEntityVel, vObjectPos, aObjectRot, vObjectVel)) - - if gs_crazyphysics_echo:GetBool() then - net.Start(bRemove and (bCheckObjectVel and "GS_CrazyPhysics_Remove_Object" or "GS_CrazyPhysics_Remove") - or (bCheckObjectVel and "GS_CrazyPhysics_Defuse_Object" or "GS_CrazyPhysics_Defuse")) - - net.WriteUInt(pEntity:EntIndex(), 16) - net.WriteString(pEntity:GetClass()) - net.WriteVector(vEntityPos) - net.WriteAngle(aEntityRot) - net.WriteVector(vEntityVel) - - if bCheckObjectVel then - net.WriteVector(vObjectPos) - net.WriteAngle(aObjectRot) - net.WriteVector(vObjectVel) - end - - net.Broadcast() - end +local function SendMessage( + bRemove, + bCheckObjectVel, + pEntity, + vEntityPos, + aEntityRot, + vEntityVel, + vObjectPos, + aObjectRot, + vObjectVel +) + ServerLog( + DebugMessage( + bRemove, + pEntity, + vEntityPos, + aEntityRot, + vEntityVel, + vObjectPos, + aObjectRot, + vObjectVel + ) + ) + + if gs_crazyphysics_echo:GetBool() then + net.Start( + bRemove + and (bCheckObjectVel and "GS_CrazyPhysics_Remove_Object" or "GS_CrazyPhysics_Remove") + or (bCheckObjectVel and "GS_CrazyPhysics_Defuse_Object" or "GS_CrazyPhysics_Defuse") + ) + + net.WriteUInt(pEntity:EntIndex(), 16) + net.WriteString(pEntity:GetClass()) + net.WriteVector(vEntityPos) + net.WriteAngle(aEntityRot) + net.WriteVector(vEntityVel) + + if bCheckObjectVel then + net.WriteVector(vObjectPos) + net.WriteAngle(aObjectRot) + net.WriteVector(vObjectVel) + end + + net.Broadcast() + end end local flNextCheck = 0 hook.Add("Think", "GS_CrazyPhysics", function() - if not gs_crazyphysics:GetBool() then return end - - local flCurTime = CurTime() - - if flNextCheck > flCurTime then return end - - flNextCheck = flCurTime + gs_crazyphysics_interval:GetFloat() - - local flRemoveSpeed = gs_crazyphysics_speed_remove:GetFloat() - flRemoveSpeed = flRemoveSpeed * flRemoveSpeed - - local flDefuseSpeed = gs_crazyphysics_speed_defuse:GetFloat() - flDefuseSpeed = flDefuseSpeed * flDefuseSpeed - - for i = 1, iEntityLen do - local sClass = tEntitiesToCheck[i] - local tEntities = ents.FindByClass(sClass) - - for i2 = 1, #tEntities do - local pEntity = tEntities[i2] - local vEntityVel = pEntity:GetVelocity() -- ::GetAbsVelocity - local flEntityVel = vEntityVel:LengthSqr() - local pPhysObj = pEntity:GetPhysicsObject() - local bCheckObjectVel = IsValid(pPhysObj) - local vObjectVel, flObjectVel - - if bCheckObjectVel then - vObjectVel = pPhysObj:GetVelocity() - flObjectVel = vObjectVel:LengthSqr() - end - - if flEntityVel >= flRemoveSpeed or bCheckObjectVel and flObjectVel >= flRemoveSpeed then - KillVelocity(pEntity) - - pEntity:Remove() - - if bTTT and tIdentifyEntities[sClass] then - IdentifyCorpse(pEntity) - end - - local vObjectPos, aObjectRot - - if bCheckObjectVel then - vObjectPos = pPhysObj:GetPos() - aObjectRot = pPhysObj:GetAngles() - end - - SendMessage(true, bCheckObjectVel, pEntity, pEntity:GetPos(), pEntity:GetAngles(), vEntityVel, vObjectPos, aObjectRot, vObjectVel) - elseif flEntityVel >= flDefuseSpeed or bCheckObjectVel and flObjectVel >= flDefuseSpeed then - KillVelocity(pEntity) - - timer.Simple(gs_crazyphysics_defusetime:GetFloat(), function() - if not IsValid(pEntity) then return end - - pEntity:SetLocalVelocity(vector_origin) -- ::SetLocalVelocity - pEntity:SetVelocity(vector_origin) -- ::SetBaseVelocity - - SetAbsVelocity(pEntity, vector_origin) -- ::SetAbsVelocity - - for i3 = 0, pEntity:GetPhysicsObjectCount() - 1 do - local pPhysObj2 = pEntity:GetPhysicsObjectNum(i3) - pPhysObj2:EnableMotion(true) - pPhysObj2:SetVelocity(vector_origin) - pPhysObj2:SetVelocityInstantaneous(vector_origin) - pPhysObj2:Wake() - pPhysObj2:RecheckCollisionFilter() - end - - pEntity:CollisionRulesChanged() - end) - - local vObjectPos, aObjectRot - - if bCheckObjectVel then - vObjectPos = pPhysObj:GetPos() - aObjectRot = pPhysObj:GetAngles() - end - - SendMessage(false, bCheckObjectVel, pEntity, pEntity:GetPos(), pEntity:GetAngles(), vEntityVel, vObjectPos, aObjectRot, vObjectVel) - end - end - end + if not gs_crazyphysics:GetBool() then + return + end + + local flCurTime = CurTime() + + if flNextCheck > flCurTime then + return + end + + flNextCheck = flCurTime + gs_crazyphysics_interval:GetFloat() + + local flRemoveSpeed = gs_crazyphysics_speed_remove:GetFloat() + flRemoveSpeed = flRemoveSpeed * flRemoveSpeed + + local flDefuseSpeed = gs_crazyphysics_speed_defuse:GetFloat() + flDefuseSpeed = flDefuseSpeed * flDefuseSpeed + + for i = 1, iEntityLen do + local sClass = tEntitiesToCheck[i] + local tEntities = ents.FindByClass(sClass) + + for i2 = 1, #tEntities do + local pEntity = tEntities[i2] + local vEntityVel = pEntity:GetVelocity() -- ::GetAbsVelocity + local flEntityVel = vEntityVel:LengthSqr() + local pPhysObj = pEntity:GetPhysicsObject() + local bCheckObjectVel = IsValid(pPhysObj) + local vObjectVel, flObjectVel + + if bCheckObjectVel then + vObjectVel = pPhysObj:GetVelocity() + flObjectVel = vObjectVel:LengthSqr() + end + + if flEntityVel >= flRemoveSpeed or bCheckObjectVel and flObjectVel >= flRemoveSpeed then + KillVelocity(pEntity) + + pEntity:Remove() + + if bTTT and tIdentifyEntities[sClass] then + IdentifyCorpse(pEntity) + end + + local vObjectPos, aObjectRot + + if bCheckObjectVel then + vObjectPos = pPhysObj:GetPos() + aObjectRot = pPhysObj:GetAngles() + end + + SendMessage( + true, + bCheckObjectVel, + pEntity, + pEntity:GetPos(), + pEntity:GetAngles(), + vEntityVel, + vObjectPos, + aObjectRot, + vObjectVel + ) + elseif + flEntityVel >= flDefuseSpeed or bCheckObjectVel and flObjectVel >= flDefuseSpeed + then + KillVelocity(pEntity) + + timer.Simple(gs_crazyphysics_defusetime:GetFloat(), function() + if not IsValid(pEntity) then + return + end + + pEntity:SetLocalVelocity(vector_origin) -- ::SetLocalVelocity + pEntity:SetVelocity(vector_origin) -- ::SetBaseVelocity + + SetAbsVelocity(pEntity, vector_origin) -- ::SetAbsVelocity + + for i3 = 0, pEntity:GetPhysicsObjectCount() - 1 do + local pPhysObj2 = pEntity:GetPhysicsObjectNum(i3) + pPhysObj2:EnableMotion(true) + pPhysObj2:SetVelocity(vector_origin) + pPhysObj2:SetVelocityInstantaneous(vector_origin) + pPhysObj2:Wake() + pPhysObj2:RecheckCollisionFilter() + end + + pEntity:CollisionRulesChanged() + end) + + local vObjectPos, aObjectRot + + if bCheckObjectVel then + vObjectPos = pPhysObj:GetPos() + aObjectRot = pPhysObj:GetAngles() + end + + SendMessage( + false, + bCheckObjectVel, + pEntity, + pEntity:GetPos(), + pEntity:GetAngles(), + vEntityVel, + vObjectPos, + aObjectRot, + vObjectVel + ) + end + end + end end) diff --git a/lua/autorun/server/ttt2_force_download.lua b/lua/autorun/server/ttt2_force_download.lua deleted file mode 100644 index 0bdb7c188..000000000 --- a/lua/autorun/server/ttt2_force_download.lua +++ /dev/null @@ -1,212 +0,0 @@ -if engine.ActiveGamemode() ~= "terrortown" then return end - --- This file forces clients to download the icons --- If you are distributing those files via FastDL, comment out the line below. - --- logo -resource.AddFile("gamemodes/terrortown/logo.png") -resource.AddFile("materials/vgui/ttt/score_logo_2.vmt") - --- BEM -resource.AddFile("materials/vgui/ttt/equip/coin.png") -resource.AddFile("materials/vgui/ttt/equip/briefcase.png") -resource.AddFile("materials/vgui/ttt/equip/package.png") -resource.AddFile("materials/vgui/ttt/equip/icon_info.vmt") -resource.AddFile("materials/vgui/ttt/equip/icon_team_limited.vmt") -resource.AddFile("materials/vgui/ttt/equip/icon_global_limited.vmt") - -resource.AddFile("materials/vgui/ttt/dynamic/roles/icon_inno.vmt") -resource.AddFile("materials/vgui/ttt/dynamic/roles/icon_traitor.vmt") -resource.AddFile("materials/vgui/ttt/dynamic/roles/icon_det.vmt") -resource.AddFile("materials/vgui/ttt/dynamic/roles/icon_no_team.vmt") -resource.AddFile("materials/vgui/ttt/dynamic/roles/icon_none.vmt") - -resource.AddFile("materials/vgui/ttt/equip/reroll.png") - --- ShopEditor -resource.AddFile("materials/vgui/ttt/dynamic/roles/icon_disabled.vmt") -resource.AddFile("materials/vgui/ttt/dynamic/roles/icon_shop_default.vmt") -resource.AddFile("materials/vgui/ttt/dynamic/roles/icon_shop_custom.vmt") - --- dynamic -resource.AddFile("materials/vgui/ttt/dynamic/base.vmt") -resource.AddFile("materials/vgui/ttt/dynamic/base_overlay.vmt") - -resource.AddFile("materials/vgui/ttt/dynamic/icon_base.vmt") -resource.AddFile("materials/vgui/ttt/dynamic/icon_base_base.vmt") -resource.AddFile("materials/vgui/ttt/dynamic/icon_base_base_overlay.vmt") - -resource.AddFile("materials/vgui/ttt/dynamic/sprite_base.vmt") -resource.AddFile("materials/vgui/ttt/dynamic/sprite_base_overlay.vmt") - -resource.AddFile("materials/vgui/ttt/icon_drown.vmt") - --- old ttt hud items background -resource.AddFile("materials/vgui/ttt/perks/old_ttt_bg.png") - --- pureSkin Hud border and shadow -resource.AddFile("materials/vgui/ttt/dynamic/hud_components/shadow_border.vmt") - --- target icon -resource.AddFile("materials/vgui/ttt/target_icon.vmt") - --- watching icon -resource.AddFile("materials/vgui/ttt/watching_icon.vmt") - --- credit icons -resource.AddFile("materials/vgui/ttt/equip/credits_default.vmt") -resource.AddFile("materials/vgui/ttt/equip/credits_zero.vmt") - --- old ttt hud preview -resource.AddFile("materials/vgui/ttt/huds/old_ttt/preview.png") - --- pure skin hud preview -resource.AddFile("materials/vgui/ttt/huds/pure_skin/preview.png") - --- ttt indicators -resource.AddFile("materials/vgui/ttt/ttt2_indicator_dev.vmt") -- ttt2 dev -resource.AddFile("materials/vgui/ttt/ttt2_indicator_vip.vmt") -- vip -resource.AddFile("materials/vgui/ttt/ttt2_indicator_addondev.vmt") -- ttt2 addon dev -resource.AddFile("materials/vgui/ttt/ttt2_indicator_admin.vmt") -- server admin -resource.AddFile("materials/vgui/ttt/ttt2_indicator_streamer.vmt") -- streamer -resource.AddFile("materials/vgui/ttt/ttt2_indicator_heroes.vmt") -- ttt2 heroes - --- traitorbutton -resource.AddFile("materials/vgui/ttt/ttt2_hand_line.vmt") -- ttt2 traitor button hand unfocused -resource.AddFile("materials/vgui/ttt/ttt2_hand_filled.vmt") -- ttt2 traitor button hand focused -resource.AddFile("materials/vgui/ttt/ttt2_hand_outline.vmt") -- ttt2 traitor button hand outline - --- miniscoreboard indicator -resource.AddFile("materials/vgui/ttt/indirect_confirmed.vmt") -resource.AddFile("materials/vgui/ttt/revived.vmt") - --- items -resource.AddFile("materials/vgui/ttt/icon_armor.vmt") -- armor -resource.AddFile("materials/vgui/ttt/missing_equip_icon.vmt") -resource.AddFile("materials/vgui/ttt/perks/hud_disguiser.png") -- disguiser -resource.AddFile("materials/vgui/ttt/perks/hud_radar.png") -- radar - -resource.AddFile("materials/vgui/ttt/perks/hud_armor.png") -- armor HUD -resource.AddFile("materials/vgui/ttt/perks/hud_armor_reinforced.png") -- armor reinforced HUD -resource.AddFile("materials/vgui/ttt/hud_armor.vmt") -- playerinfo armor -resource.AddFile("materials/vgui/ttt/hud_armor_reinforced.vmt") -- playerinfo armor reinforced - -resource.AddFile("materials/vgui/ttt/hud_blocking_revival.vmt") - --- essential items -resource.AddFile("materials/vgui/ttt/icon_speedrun.vmt") -resource.AddFile("materials/vgui/ttt/perks/hud_speedrun.png") -resource.AddFile("materials/vgui/ttt/icon_nofiredmg.vmt") -resource.AddFile("materials/vgui/ttt/perks/hud_nofiredmg.png") -resource.AddFile("materials/vgui/ttt/icon_nofalldmg.vmt") -resource.AddFile("materials/vgui/ttt/perks/hud_nofalldmg.png") -resource.AddFile("materials/vgui/ttt/icon_noexplosiondmg.vmt") -resource.AddFile("materials/vgui/ttt/perks/hud_noexplosiondmg.png") -resource.AddFile("materials/vgui/ttt/icon_nodrowningdmg.vmt") -resource.AddFile("materials/vgui/ttt/perks/hud_nodrowningdmg.png") -resource.AddFile("materials/vgui/ttt/icon_nopropdmg.vmt") -resource.AddFile("materials/vgui/ttt/perks/hud_nopropdmg.png") -resource.AddFile("materials/vgui/ttt/icon_noenergydmg.vmt") -resource.AddFile("materials/vgui/ttt/perks/hud_noenergydmg.png") -resource.AddFile("materials/vgui/ttt/icon_nohazarddmg.vmt") -resource.AddFile("materials/vgui/ttt/perks/hud_nohazarddmg.png") - -resource.AddFile("materials/vgui/ttt/icon_magneto_stick.vmt") - --- pickup symbols -resource.AddFile("materials/vgui/ttt/pickup/icon_heavy.png") -resource.AddFile("materials/vgui/ttt/pickup/icon_pistol.png") -resource.AddFile("materials/vgui/ttt/pickup/icon_nades.png") -resource.AddFile("materials/vgui/ttt/pickup/icon_special.png") -resource.AddFile("materials/vgui/ttt/pickup/icon_extra.png") -resource.AddFile("materials/vgui/ttt/pickup/icon_class.png") -resource.AddFile("materials/vgui/ttt/pickup/icon_ammo.png") - --- dmgindicator themes -resource.AddFile("materials/vgui/ttt/dmgindicator/themes/default.png") -resource.AddFile("materials/vgui/ttt/dmgindicator/themes/simple.png") -resource.AddFile("materials/vgui/ttt/dmgindicator/themes/vanilla.png") - --- target ID icons -resource.AddFile("materials/vgui/ttt/tid/tid_big_role_not_known.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_corpse.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_tbutton_pointer.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_door.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_ammo_deagle.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_ammo_mac10.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_ammo_pistol.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_ammo_random.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_ammo_rifle.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_ammo_shotgun.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_player.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_weapon_assault.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_weapon_melee.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_weapon_nade.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_weapon_pistol.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_weapon_random.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_weapon_shotgun.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_weapon_sniper.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_big_weapon_special.vmt") - --- target ID inline icons -resource.AddFile("materials/vgui/ttt/tid/tid_credits.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_detective.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_locked.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_auto_close.vmt") -resource.AddFile("materials/vgui/ttt/tid/tid_destructible.vmt") - --- derma skin graphics -resource.AddFile("materials/vgui/ttt/vskin/icon_close.vmt") -resource.AddFile("materials/vgui/ttt/vskin/icon_back.vmt") -resource.AddFile("materials/vgui/ttt/vskin/icon_collapse_closed.vmt") -resource.AddFile("materials/vgui/ttt/vskin/icon_collapse_opened.vmt") -resource.AddFile("materials/vgui/ttt/vskin/icon_reset.vmt") -resource.AddFile("materials/vgui/ttt/vskin/icon_disable.vmt") -resource.AddFile("materials/vgui/ttt/vskin/icon_headbox_yes.vmt") -resource.AddFile("materials/vgui/ttt/vskin/icon_headbox_no.vmt") -resource.AddFile("materials/vgui/ttt/vskin/icon_hattable_yes.vmt") -resource.AddFile("materials/vgui/ttt/vskin/icon_hattable_no.vmt") -resource.AddFile("materials/vgui/ttt/vskin/rhombus.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/addons.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/appearance.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/bindings.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/changelog.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/gameplay.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/guide.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/language.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/legacy.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/equipment.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/shops.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/administration.vmt") -resource.AddFile("materials/vgui/ttt/vskin/helpscreen/roles.vmt") -resource.AddFile("materials/vgui/ttt/vskin/roundend/events.vmt") -resource.AddFile("materials/vgui/ttt/vskin/roundend/info.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/base_event.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/bodyfound.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/c4disarm.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/c4explode.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/c4plant.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/creditfound.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/finish.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/game.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/kill.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/respawn.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/rolechange.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/selected.vmt") -resource.AddFile("materials/vgui/ttt/vskin/events/spawn.vmt") - --- dna scanner icons, mats and models -resource.AddFile("models/weapons/v_ttt2_dna_scanner.mdl") -resource.AddFile("models/weapons/w_ttt2_dna_scanner.mdl") -resource.AddFile("materials/models/ttt2_dna_scanner/camera.vmt") -resource.AddFile("materials/models/ttt2_dna_scanner/screen.vmt") -resource.AddFile("materials/models/ttt2_dna_scanner/screen/arrow.vmt") -resource.AddFile("materials/models/ttt2_dna_scanner/screen/background.vmt") -resource.AddFile("materials/models/ttt2_dna_scanner/screen/check.vmt") -resource.AddFile("materials/models/ttt2_dna_scanner/screen/circle.vmt") -resource.AddFile("materials/models/ttt2_dna_scanner/screen/fail.vmt") -resource.AddFile("materials/vgui/ttt/dnascanner/dna_hud.vmt") - --- b-draw icons -resource.AddFile("materials/vgui/ttt/b-draw/icon_avatar_bot.vmt") -resource.AddFile("materials/vgui/ttt/b-draw/icon_avatar_default.vmt") diff --git a/lua/terrortown/entities/items/item_base/shared.lua b/lua/terrortown/entities/items/item_base/shared.lua index fc3e31314..db8173134 100644 --- a/lua/terrortown/entities/items/item_base/shared.lua +++ b/lua/terrortown/entities/items/item_base/shared.lua @@ -21,58 +21,56 @@ ITEM.limited = true ITEM.Category = "TTT" -- the @{ITEM} Category if CLIENT then - - -- If this is a buyable @{ITEM} (ie. CanBuy is not nil) EquipMenuData must be - -- a table containing some information to show in the Equipment Menu. See - -- default equipment items for real-world examples. - ITEM.EquipMenuData = nil - - -- Example data: - -- ITEM.EquipMenuData = { - -- - -- Type tells players if it's a weapon or @{ITEM} - -- type = "Weapon", - -- - -- Desc is the description in the menu. Needs manual linebreaks (via \n). - -- desc = "Text." - -- } - - -- This sets the icon shown for the @{ITEM} in the DNA sampler, search window, - -- equipment menu (if buyable), etc. - ITEM.material = "vgui/ttt/icon_nades" -- most generic icon I guess - - -- set to false if item should not be shown in body search - ITEM.populateSearch = true - - -- You can make your own @{ITEM} icon using the template in: - -- /garrysmod/gamemodes/terrortown/template/ - - -- Open one of TTT's icons with VTFEdit to see what kind of settings to use - -- when exporting to VTF. Once you have a VTF and VMT, you can - -- resource.AddFile("materials/vgui/...") them here. GIVE YOUR ICON A UNIQUE - -- FILENAME, or it WILL be overwritten by other servers! Gmod does not check - -- if the files are different, it only looks at the name. I recommend you - -- create your own directory so that this does not happen, - -- eg. /materials/vgui/ttt/mycoolserver/mygun.vmt - - --- - -- Draw some information in a small box next to the icon in the hud if a @{Player} is owning this @{ITEM} - -- @hook - -- @realm client - function ITEM:DrawInfo() - - end - - --- - -- This hook can be used by item addons to populate the equipment settings page - -- with custom convars. The parent is the submenu, where a new form has to - -- be added. - -- @param DPanel parent The parent panel which is the submenu - -- @hook - -- @realm client - function ITEM:AddToSettingsMenu(parent) - - end + -- If this is a buyable @{ITEM} (ie. CanBuy is not nil) EquipMenuData must be + -- a table containing some information to show in the Equipment Menu. See + -- default equipment items for real-world examples. + ITEM.EquipMenuData = nil + + -- Example data: + -- ITEM.EquipMenuData = { + -- + -- Type tells players if it's a weapon or @{ITEM} + -- type = "Weapon", + -- + -- Desc is the description in the menu. Needs manual linebreaks (via \n). + -- desc = "Text." + -- } + + -- A short description for the sidebar + ITEM.sidebarDescription = nil + + -- This sets the icon shown for the @{ITEM} in the DNA sampler, search window, + -- equipment menu (if buyable), etc. + ITEM.material = "vgui/ttt/icon_nades" -- most generic icon I guess + + -- set to false if item should not be shown in body search + ITEM.populateSearch = true + + -- You can make your own @{ITEM} icon using the template in: + -- /garrysmod/gamemodes/terrortown/template/ + + -- Open one of TTT's icons with VTFEdit to see what kind of settings to use + -- when exporting to VTF. Once you have a VTF and VMT, you can + -- resource.AddFile("materials/vgui/...") them here. GIVE YOUR ICON A UNIQUE + -- FILENAME, or it WILL be overwritten by other servers! Gmod does not check + -- if the files are different, it only looks at the name. I recommend you + -- create your own directory so that this does not happen, + -- eg. /materials/vgui/ttt/mycoolserver/mygun.vmt + + --- + -- Draw some information in a small box next to the icon in the hud if a @{Player} is owning this @{ITEM} + -- @hook + -- @realm client + function ITEM:DrawInfo() end + + --- + -- This hook can be used by item addons to populate the equipment settings page + -- with custom convars. The parent is the submenu, where a new form has to + -- be added. + -- @param DPanel parent The parent panel which is the submenu + -- @hook + -- @realm client + function ITEM:AddToSettingsMenu(parent) end end --- @@ -80,39 +78,31 @@ end -- @param Player ply -- @hook -- @realm shared -function ITEM:Reset(ply) - -end +function ITEM:Reset(ply) end --- -- A player or NPC has picked the @{ITEM} up -- @param Player ply -- @hook -- @realm shared -function ITEM:Equip(ply) - -end +function ITEM:Equip(ply) end --- -- A player or NPC has bought the @{ITEM} -- @param Player ply -- @hook -- @realm shared -function ITEM:Bought(ply) - -end +function ITEM:Bought(ply) end --- -- Called when the @{ITEM} is created. -- @realm shared -function ITEM:Initialize() - -end +function ITEM:Initialize() end --- -- useable, but do not modify this! -- @return boolean whether this @{ITEM} is an equipment -- @realm shared function ITEM:IsEquipment() - return WEPS.IsEquipment(self) + return WEPS.IsEquipment(self) end diff --git a/lua/terrortown/entities/items/item_ttt_armor.lua b/lua/terrortown/entities/items/item_ttt_armor.lua index bc424ca22..73ffc864f 100644 --- a/lua/terrortown/entities/items/item_ttt_armor.lua +++ b/lua/terrortown/entities/items/item_ttt_armor.lua @@ -1,58 +1,62 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ITEM.EquipMenuData = { - type = "item_passive", - name = "item_armor", - desc = "item_armor_desc" + type = "item_passive", + name = "item_armor", + desc = "item_armor_desc", } ITEM.material = "vgui/ttt/icon_armor" -ITEM.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +ITEM.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } ITEM.oldId = EQUIP_ARMOR or 1 ITEM.limited = false +ITEM.builtin = true if SERVER then - --- - -- @ignore - function ITEM:Equip(buyer) - buyer:GiveArmor(GetConVar("ttt_item_armor_value"):GetInt()) - end - - --- - -- @ignore - function ITEM:Reset(buyer) - buyer:RemoveArmor(GetConVar("ttt_item_armor_value"):GetInt()) - end + --- + -- @ignore + function ITEM:Equip(buyer) + buyer:GiveArmor(GetConVar("ttt_item_armor_value"):GetInt()) + end + + --- + -- @ignore + function ITEM:Reset(buyer) + buyer:RemoveArmor(GetConVar("ttt_item_armor_value"):GetInt()) + end else -- CLIENT - -- HANDLE ITEM CLASSIC MODE - hook.Add("TTTBeginRound", "ttt2_base_register_armor_text", function() - if GetGlobalBool("ttt_armor_dynamic") then return end - - local item = items.GetStored("item_ttt_armor") - if not item then return end - - -- limit item when in classic mode - item.limited = true - end) - - --- - -- @ignore - function ITEM:AddToSettingsMenu(parent) - local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") - - form:MakeHelp({ - label = "help_item_armor_value" - }) - - form:MakeSlider({ - serverConvar = "ttt_item_armor_value", - label = "label_armor_value", - min = 0, - max = 100, - decimal = 0 - }) - - end + -- HANDLE ITEM CLASSIC MODE + hook.Add("TTTBeginRound", "ttt2_base_register_armor_text", function() + if GetGlobalBool("ttt_armor_dynamic") then + return + end + + local item = items.GetStored("item_ttt_armor") + if not item then + return + end + + -- limit item when in classic mode + item.limited = true + end) + + --- + -- @ignore + function ITEM:AddToSettingsMenu(parent) + local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") + + form:MakeHelp({ + label = "help_item_armor_value", + }) + + form:MakeSlider({ + serverConvar = "ttt_item_armor_value", + label = "label_armor_value", + min = 0, + max = 100, + decimal = 0, + }) + end end diff --git a/lua/terrortown/entities/items/item_ttt_disguiser.lua b/lua/terrortown/entities/items/item_ttt_disguiser.lua index 62bd16542..c9a54807f 100644 --- a/lua/terrortown/entities/items/item_ttt_disguiser.lua +++ b/lua/terrortown/entities/items/item_ttt_disguiser.lua @@ -2,120 +2,155 @@ -- Disguiser @{ITEM} -- @module DISGUISE +local materialIconDisguiser = Material("vgui/ttt/hudhelp/item_disguiser") + DISGUISE = CLIENT and {} if SERVER then - AddCSLuaFile() + AddCSLuaFile() end -ITEM.EquipMenuData = { - type = "item_active", - name = "item_disg", - desc = "item_disg_desc" -} -ITEM.material = "vgui/ttt/icon_disguise" -ITEM.CanBuy = {ROLE_TRAITOR} +ITEM.CanBuy = { ROLE_TRAITOR } ITEM.oldId = EQUIP_DISGUISE or 4 +ITEM.builtin = true if CLIENT then - local trans - - --- - -- Creates the Disguiser menu on the parent panel - -- @param Panel parent parent panel - -- @return Panel created disguiser menu panel - -- @realm client - function DISGUISE.CreateMenu(parent) - trans = trans or LANG.GetTranslation - - local dform = vgui.Create("DForm", parent) - dform:SetName(trans("disg_menutitle")) - dform:StretchToParent(0, 0, 0, 0) - dform:SetAutoSize(false) - - local owned = LocalPlayer():HasEquipmentItem("item_ttt_disguiser") - - if not owned then - dform:Help(trans("disg_not_owned")) - - return dform - end - - local dcheck = vgui.Create("DCheckBoxLabel", dform) - dcheck:SetText(trans("disg_enable")) - dcheck:SetIndent(5) - dcheck:SetValue(LocalPlayer():GetNWBool("disguised", false)) - - dcheck.OnChange = function(s, val) - RunConsoleCommand("ttt_set_disguise", val and "1" or "0") - end - - dform:AddItem(dcheck) - dform:Help(trans("disg_help1")) - dform:Help(trans("disg_help2")) - dform:SetVisible(true) - - return dform - end - - hook.Add("TTTEquipmentTabs", "TTTItemDisguiser", function(dsheet) - trans = trans or LANG.GetTranslation - - if not LocalPlayer():HasEquipmentItem("item_ttt_disguiser") then return end - - local ddisguise = DISGUISE.CreateMenu(dsheet) - - dsheet:AddSheet(trans("disg_name"), ddisguise, "icon16/user.png", false, false, trans("equip_tooltip_disguise")) - end) - - hook.Add("Initialize", "TTTItemDisguiserInitStatus", function() - STATUS:RegisterStatus("item_disguiser_status", { - hud = Material("vgui/ttt/perks/hud_disguiser.png"), - type = "good" - }) - - bind.Register("ttt2_disguiser_toggle", function() - WEPS.DisguiseToggle(LocalPlayer()) - end, - nil, "header_bindings_ttt2", "label_bind_disguiser", KEY_PAD_ENTER) - end) + local trans + + ITEM.EquipMenuData = { + type = "item_active", + name = "item_disg", + desc = "item_disg_desc", + } + ITEM.material = "vgui/ttt/icon_disguise" + ITEM.hud = Material("vgui/ttt/perks/hud_disguiser.png") + + --- + -- @ignore + function ITEM:DrawInfo() + return LocalPlayer():GetNWBool("disguised") and "status_on" or "status_off" + end + + --- + -- Creates the Disguiser menu on the parent panel + -- @param Panel parent parent panel + -- @return Panel created disguiser menu panel + -- @realm client + function DISGUISE.CreateMenu(parent) + trans = trans or LANG.GetTranslation + + local dform = vgui.Create("DForm", parent) + dform:SetName(trans("disg_menutitle")) + dform:StretchToParent(0, 0, 0, 0) + dform:SetAutoSize(false) + + local owned = LocalPlayer():HasEquipmentItem("item_ttt_disguiser") + + if not owned then + dform:Help(trans("disg_not_owned")) + + return dform + end + + local dcheck = vgui.Create("DCheckBoxLabel", dform) + dcheck:SetText(trans("disg_enable")) + dcheck:SetIndent(5) + dcheck:SetValue(LocalPlayer():GetNWBool("disguised", false)) + + dcheck.OnChange = function(s, val) + RunConsoleCommand("ttt_set_disguise", val and "1" or "0") + end + + dform:AddItem(dcheck) + dform:Help(trans("disg_help1")) + dform:Help(trans("disg_help2")) + dform:SetVisible(true) + + return dform + end + + hook.Add("TTTEquipmentTabs", "TTTItemDisguiser", function(dsheet) + trans = trans or LANG.GetTranslation + + if not LocalPlayer():HasEquipmentItem("item_ttt_disguiser") then + return + end + + local ddisguise = DISGUISE.CreateMenu(dsheet) + + dsheet:AddSheet( + trans("disg_name"), + ddisguise, + "icon16/user.png", + false, + false, + trans("equip_tooltip_disguise") + ) + end) + + hook.Add("TTT2FinishedLoading", "TTTItemDisguiserInitStatus", function() + bind.Register("ttt2_disguiser_toggle", function() + WEPS.DisguiseToggle(LocalPlayer()) + end, nil, "header_bindings_ttt2", "label_bind_disguiser", KEY_PAD_ENTER) + + keyhelp.RegisterKeyHelper( + "ttt2_disguiser_toggle", + materialIconDisguiser, + KEYHELP_EQUIPMENT, + "label_keyhelper_disguiser", + function(client) + if client:IsSpec() or not client:HasEquipmentItem("item_ttt_disguiser") then + return + end + + return true + end + ) + end) else -- SERVER - local function SetDisguise(ply, cmd, args) - if not IsValid(ply) or not ply:IsActive() or not ply:HasEquipmentItem("item_ttt_disguiser") then return end - - local state = #args == 1 and tobool(args[1]) - - --- - -- @realm server - if hook.Run("TTTToggleDisguiser", ply, state) then return end - - ply:SetNWBool("disguised", state) - - LANG.Msg(ply, state and "disg_turned_on" or "disg_turned_off", nil, MSG_MSTACK_ROLE) - end - concommand.Add("ttt_set_disguise", SetDisguise) - - hook.Add("TTT2PlayerReady", "TTTItemDisguiserPlayerReady", function(ply) - ply:SetNWVarProxy("disguised", function(ent, name, old, new) - if not IsValid(ent) or not ent:IsPlayer() or name ~= "disguised" then return end - - if new then - STATUS:AddStatus(ent, "item_disguiser_status") - else - STATUS:RemoveStatus(ent, "item_disguiser_status") - end - end) - end) - - --- - -- This hook is called once the disguiser state is about to be updated - -- and can be used to cancel this change. - -- @param Player ply The player whose disguising state should be changed - -- @param boolean state The state that should be set - -- @return nil|boolean Return true to cancel the state change - -- @hook - -- @realm server - function GM:TTTToggleDisguiser(ply, state) - - end + local function SetDisguise(ply, cmd, args) + if + not IsValid(ply) + or not ply:IsActive() + or not ply:HasEquipmentItem("item_ttt_disguiser") + then + return + end + + local state = #args == 1 and tobool(args[1]) + + --- + -- @realm server + -- stylua: ignore + if hook.Run("TTTToggleDisguiser", ply, state) then return end + + ply:SetNWBool("disguised", state) + + LANG.Msg(ply, state and "disg_turned_on" or "disg_turned_off", nil, MSG_MSTACK_ROLE) + end + concommand.Add("ttt_set_disguise", SetDisguise) + + hook.Add("TTT2PlayerReady", "TTTItemDisguiserPlayerReady", function(ply) + ply:SetNWVarProxy("disguised", function(ent, name, old, new) + if not IsValid(ent) or not ent:IsPlayer() or name ~= "disguised" then + return + end + + if new then + STATUS:AddStatus(ent, "item_disguiser_status") + else + STATUS:RemoveStatus(ent, "item_disguiser_status") + end + end) + end) + + --- + -- This hook is called once the disguiser state is about to be updated + -- and can be used to cancel this change. + -- @param Player ply The player whose disguising state should be changed + -- @param boolean state The state that should be set + -- @return nil|boolean Return true to cancel the state change + -- @hook + -- @realm server + function GM:TTTToggleDisguiser(ply, state) end end diff --git a/lua/terrortown/entities/items/item_ttt_nodrowningdmg.lua b/lua/terrortown/entities/items/item_ttt_nodrowningdmg.lua index 0eff30bea..e23ca5c53 100644 --- a/lua/terrortown/entities/items/item_ttt_nodrowningdmg.lua +++ b/lua/terrortown/entities/items/item_ttt_nodrowningdmg.lua @@ -1,23 +1,30 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ITEM.EquipMenuData = { - type = "item_passive", - name = "item_no_drown_damage", - desc = "item_no_drown_damage_desc" + type = "item_passive", + name = "item_no_drown_damage", + desc = "item_no_drown_damage_desc", } -ITEM.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +ITEM.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } ITEM.hud = Material("vgui/ttt/perks/hud_nodrowningdmg.png") ITEM.material = "vgui/ttt/icon_nodrowningdmg" +ITEM.builtin = true if SERVER then - hook.Add("EntityTakeDamage", "TTT2NoDrownDmg", function(target, dmginfo) - if not IsValid(target) or not target:IsPlayer() or not dmginfo:IsDamageType(DMG_DROWN) then return end + hook.Add("EntityTakeDamage", "TTT2NoDrownDmg", function(target, dmginfo) + if not IsValid(target) or not target:IsPlayer() or not dmginfo:IsDamageType(DMG_DROWN) then + return + end - if target:Alive() and target:IsTerror() and target:HasEquipmentItem("item_ttt_nodrowningdmg") then - dmginfo:ScaleDamage(0) - end - end) + if + target:Alive() + and target:IsTerror() + and target:HasEquipmentItem("item_ttt_nodrowningdmg") + then + dmginfo:ScaleDamage(0) + end + end) end diff --git a/lua/terrortown/entities/items/item_ttt_noenergydmg.lua b/lua/terrortown/entities/items/item_ttt_noenergydmg.lua index 78a545ebd..5432a3ba5 100644 --- a/lua/terrortown/entities/items/item_ttt_noenergydmg.lua +++ b/lua/terrortown/entities/items/item_ttt_noenergydmg.lua @@ -1,25 +1,40 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ITEM.EquipMenuData = { - type = "item_passive", - name = "item_no_energy_damage", - desc = "item_no_energy_damage_desc" + type = "item_passive", + name = "item_no_energy_damage", + desc = "item_no_energy_damage_desc", } -ITEM.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +ITEM.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } ITEM.hud = Material("vgui/ttt/perks/hud_noenergydmg.png") ITEM.material = "vgui/ttt/icon_noenergydmg" +ITEM.builtin = true if SERVER then - hook.Add("EntityTakeDamage", "TTT2NoEnergyDmg", function(target, dmginfo) - if not IsValid(target) or not target:IsPlayer() - or not (dmginfo:IsDamageType(DMG_SHOCK) or dmginfo:IsDamageType(DMG_SONIC) or dmginfo:IsDamageType(DMG_ENERGYBEAM) or dmginfo:IsDamageType(DMG_PHYSGUN) or dmginfo:IsDamageType(DMG_PLASMA)) - then return end + hook.Add("EntityTakeDamage", "TTT2NoEnergyDmg", function(target, dmginfo) + if + not IsValid(target) + or not target:IsPlayer() + or not ( + dmginfo:IsDamageType(DMG_SHOCK) + or dmginfo:IsDamageType(DMG_SONIC) + or dmginfo:IsDamageType(DMG_ENERGYBEAM) + or dmginfo:IsDamageType(DMG_PHYSGUN) + or dmginfo:IsDamageType(DMG_PLASMA) + ) + then + return + end - if target:Alive() and target:IsTerror() and target:HasEquipmentItem("item_ttt_noenergydmg") then - dmginfo:ScaleDamage(0) - end - end) + if + target:Alive() + and target:IsTerror() + and target:HasEquipmentItem("item_ttt_noenergydmg") + then + dmginfo:ScaleDamage(0) + end + end) end diff --git a/lua/terrortown/entities/items/item_ttt_noexplosiondmg.lua b/lua/terrortown/entities/items/item_ttt_noexplosiondmg.lua index 48a16a986..755744afd 100644 --- a/lua/terrortown/entities/items/item_ttt_noexplosiondmg.lua +++ b/lua/terrortown/entities/items/item_ttt_noexplosiondmg.lua @@ -1,23 +1,30 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ITEM.EquipMenuData = { - type = "item_passive", - name = "item_no_explosion_damage", - desc = "item_no_explosion_damage_desc" + type = "item_passive", + name = "item_no_explosion_damage", + desc = "item_no_explosion_damage_desc", } -ITEM.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +ITEM.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } ITEM.hud = Material("vgui/ttt/perks/hud_noexplosiondmg.png") ITEM.material = "vgui/ttt/icon_noexplosiondmg" +ITEM.builtin = true if SERVER then - hook.Add("EntityTakeDamage", "TTT2NoExplosionDmg", function(target, dmginfo) - if not IsValid(target) or not target:IsPlayer() or not dmginfo:IsExplosionDamage() then return end + hook.Add("EntityTakeDamage", "TTT2NoExplosionDmg", function(target, dmginfo) + if not IsValid(target) or not target:IsPlayer() or not dmginfo:IsExplosionDamage() then + return + end - if target:Alive() and target:IsTerror() and target:HasEquipmentItem("item_ttt_noexplosiondmg") then - dmginfo:ScaleDamage(0) - end - end) + if + target:Alive() + and target:IsTerror() + and target:HasEquipmentItem("item_ttt_noexplosiondmg") + then + dmginfo:ScaleDamage(0) + end + end) end diff --git a/lua/terrortown/entities/items/item_ttt_nofalldmg.lua b/lua/terrortown/entities/items/item_ttt_nofalldmg.lua index 814e0a117..3df6f8429 100644 --- a/lua/terrortown/entities/items/item_ttt_nofalldmg.lua +++ b/lua/terrortown/entities/items/item_ttt_nofalldmg.lua @@ -1,29 +1,36 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ITEM.EquipMenuData = { - type = "item_passive", - name = "item_no_fall_damage", - desc = "item_no_fall_damage_desc" + type = "item_passive", + name = "item_no_fall_damage", + desc = "item_no_fall_damage_desc", } -ITEM.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +ITEM.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } ITEM.hud = Material("vgui/ttt/perks/hud_nofalldmg.png") ITEM.material = "vgui/ttt/icon_nofalldmg" +ITEM.builtin = true if SERVER then - hook.Add("EntityTakeDamage", "TTT2NoFallDmg", function(target, dmginfo) - if not IsValid(target) or not target:IsPlayer() or not dmginfo:IsFallDamage() then return end + hook.Add("EntityTakeDamage", "TTT2NoFallDmg", function(target, dmginfo) + if not IsValid(target) or not target:IsPlayer() or not dmginfo:IsFallDamage() then + return + end - if target:Alive() and target:IsTerror() and target:HasEquipmentItem("item_ttt_nofalldmg") then - dmginfo:ScaleDamage(0) - end - end) + if + target:Alive() + and target:IsTerror() + and target:HasEquipmentItem("item_ttt_nofalldmg") + then + dmginfo:ScaleDamage(0) + end + end) - hook.Add("OnPlayerHitGround", "TTT2NoFallDmg", function(ply) - if ply:Alive() and ply:IsTerror() and ply:HasEquipmentItem("item_ttt_nofalldmg") then - return false - end - end) + hook.Add("OnPlayerHitGround", "TTT2NoFallDmg", function(ply) + if ply:Alive() and ply:IsTerror() and ply:HasEquipmentItem("item_ttt_nofalldmg") then + return false + end + end) end diff --git a/lua/terrortown/entities/items/item_ttt_nofiredmg.lua b/lua/terrortown/entities/items/item_ttt_nofiredmg.lua index 7181fde8d..ffff122b3 100644 --- a/lua/terrortown/entities/items/item_ttt_nofiredmg.lua +++ b/lua/terrortown/entities/items/item_ttt_nofiredmg.lua @@ -1,23 +1,30 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ITEM.EquipMenuData = { - type = "item_passive", - name = "item_no_fire_damage", - desc = "item_no_fire_damage_desc" + type = "item_passive", + name = "item_no_fire_damage", + desc = "item_no_fire_damage_desc", } -ITEM.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +ITEM.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } ITEM.hud = Material("vgui/ttt/perks/hud_nofiredmg.png") ITEM.material = "vgui/ttt/icon_nofiredmg" +ITEM.builtin = true if SERVER then - hook.Add("EntityTakeDamage", "TTT2NoFireDmg", function(target, dmginfo) - if not IsValid(target) or not target:IsPlayer() or not dmginfo:IsDamageType(DMG_BURN) then return end + hook.Add("EntityTakeDamage", "TTT2NoFireDmg", function(target, dmginfo) + if not IsValid(target) or not target:IsPlayer() or not dmginfo:IsDamageType(DMG_BURN) then + return + end - if target:Alive() and target:IsTerror() and target:HasEquipmentItem("item_ttt_nofiredmg") then - dmginfo:ScaleDamage(0) - end - end) + if + target:Alive() + and target:IsTerror() + and target:HasEquipmentItem("item_ttt_nofiredmg") + then + dmginfo:ScaleDamage(0) + end + end) end diff --git a/lua/terrortown/entities/items/item_ttt_nohazarddmg.lua b/lua/terrortown/entities/items/item_ttt_nohazarddmg.lua index ac69aa261..97b2d32b9 100644 --- a/lua/terrortown/entities/items/item_ttt_nohazarddmg.lua +++ b/lua/terrortown/entities/items/item_ttt_nohazarddmg.lua @@ -1,25 +1,40 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ITEM.EquipMenuData = { - type = "item_passive", - name = "item_no_hazard_damage", - desc = "item_no_hazard_damage_desc" + type = "item_passive", + name = "item_no_hazard_damage", + desc = "item_no_hazard_damage_desc", } -ITEM.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +ITEM.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } ITEM.hud = Material("vgui/ttt/perks/hud_nohazarddmg.png") ITEM.material = "vgui/ttt/icon_nohazarddmg" +ITEM.builtin = true if SERVER then - hook.Add("EntityTakeDamage", "TTT2NoHazardDmg", function(target, dmginfo) - if not IsValid(target) or not target:IsPlayer() - or not (dmginfo:IsDamageType(DMG_POISON) or dmginfo:IsDamageType(DMG_NERVEGAS) or dmginfo:IsDamageType(DMG_RADIATION) or dmginfo:IsDamageType(DMG_ACID) or dmginfo:IsDamageType(DMG_DISSOLVE)) - then return end + hook.Add("EntityTakeDamage", "TTT2NoHazardDmg", function(target, dmginfo) + if + not IsValid(target) + or not target:IsPlayer() + or not ( + dmginfo:IsDamageType(DMG_POISON) + or dmginfo:IsDamageType(DMG_NERVEGAS) + or dmginfo:IsDamageType(DMG_RADIATION) + or dmginfo:IsDamageType(DMG_ACID) + or dmginfo:IsDamageType(DMG_DISSOLVE) + ) + then + return + end - if target:Alive() and target:IsTerror() and target:HasEquipmentItem("item_ttt_nohazarddmg") then - dmginfo:ScaleDamage(0) - end - end) + if + target:Alive() + and target:IsTerror() + and target:HasEquipmentItem("item_ttt_nohazarddmg") + then + dmginfo:ScaleDamage(0) + end + end) end diff --git a/lua/terrortown/entities/items/item_ttt_nopropdmg.lua b/lua/terrortown/entities/items/item_ttt_nopropdmg.lua index a55cf6ab4..944274194 100644 --- a/lua/terrortown/entities/items/item_ttt_nopropdmg.lua +++ b/lua/terrortown/entities/items/item_ttt_nopropdmg.lua @@ -1,23 +1,30 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ITEM.EquipMenuData = { - type = "item_passive", - name = "item_no_prop_damage", - desc = "item_no_prop_damage_desc" + type = "item_passive", + name = "item_no_prop_damage", + desc = "item_no_prop_damage_desc", } -ITEM.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +ITEM.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } ITEM.hud = Material("vgui/ttt/perks/hud_nopropdmg.png") ITEM.material = "vgui/ttt/icon_nopropdmg" +ITEM.builtin = true if SERVER then - hook.Add("EntityTakeDamage", "TTT2NoPropDmg", function(target, dmginfo) - if not IsValid(target) or not target:IsPlayer() or not dmginfo:IsDamageType(DMG_CRUSH) then return end + hook.Add("EntityTakeDamage", "TTT2NoPropDmg", function(target, dmginfo) + if not IsValid(target) or not target:IsPlayer() or not dmginfo:IsDamageType(DMG_CRUSH) then + return + end - if target:Alive() and target:IsTerror() and target:HasEquipmentItem("item_ttt_nopropdmg") then - dmginfo:ScaleDamage(0) - end - end) + if + target:Alive() + and target:IsTerror() + and target:HasEquipmentItem("item_ttt_nopropdmg") + then + dmginfo:ScaleDamage(0) + end + end) end diff --git a/lua/terrortown/entities/items/item_ttt_radar/cl_init.lua b/lua/terrortown/entities/items/item_ttt_radar/cl_init.lua index e80df9af4..3fdcf1068 100644 --- a/lua/terrortown/entities/items/item_ttt_radar/cl_init.lua +++ b/lua/terrortown/entities/items/item_ttt_radar/cl_init.lua @@ -35,8 +35,8 @@ RADAR.called_corpses = {} -- @internal -- @realm client function RADAR:EndScan() - self.enable = false - self.startTime = 0 + self.enable = false + self.startTime = 0 end --- @@ -44,12 +44,12 @@ end -- @internal -- @realm client function RADAR:Clear() - self:EndScan() + self:EndScan() - self.bombs = {} - self.samples = {} - self.bombs_count = 0 - self.samples_count = 0 + self.bombs = {} + self.samples = {} + self.bombs_count = 0 + self.samples_count = 0 end --- @@ -57,89 +57,93 @@ end -- @internal -- @realm client function RADAR.CacheEnts() - -- also do some corpse cleanup here - for k, corpse in pairs(RADAR.called_corpses) do - if corpse.called + 45 < CurTime() then - RADAR.called_corpses[k] = nil -- will make # inaccurate, no big deal - end - end - - if RADAR.bombs_count == 0 then return end - - -- Update bomb positions for those we know about - for idx, b in pairs(RADAR.bombs) do - local ent = Entity(idx) - - if IsValid(ent) then - b.pos = ent:GetPos() - end - end + -- also do some corpse cleanup here + for k, corpse in pairs(RADAR.called_corpses) do + if corpse.called + 45 < CurTime() then + RADAR.called_corpses[k] = nil -- will make # inaccurate, no big deal + end + end + + if RADAR.bombs_count == 0 then + return + end + + -- Update bomb positions for those we know about + for idx, b in pairs(RADAR.bombs) do + local ent = Entity(idx) + + if IsValid(ent) then + b.pos = ent:GetPos() + end + end end --- -- @ignore function ITEM:Equip(ply) - RunConsoleCommand("ttt_radar_scan") + RunConsoleCommand("ttt_radar_scan") end --- -- @ignore function ITEM:DrawInfo() - return math.ceil(math.max(0, (LocalPlayer().radarTime or 30) - (CurTime() - RADAR.startTime))) + return math.ceil(math.max(0, (LocalPlayer().radarTime or 30) - (CurTime() - RADAR.startTime))) end --- -- @ignore function ITEM:AddToSettingsMenu(parent) - local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") - - form:MakeSlider({ - serverConvar = "ttt2_radar_charge_time", - label = "label_radar_charge_time", - min = 0, - max = 60, - decimal = 0 - }) + local form = vgui.CreateTTT2Form(parent, "header_equipment_additional") + + form:MakeSlider({ + serverConvar = "ttt2_radar_charge_time", + label = "label_radar_charge_time", + min = 0, + max = 60, + decimal = 0, + }) end - - local function DrawTarget(tgt, size, offset, no_shrink) - local scrpos = tgt.pos:ToScreen() -- sweet - local sz = (util.IsOffScreen(scrpos) and not no_shrink) and (size * 0.5) or size - - scrpos.x = math.Clamp(scrpos.x, sz, ScrW() - sz) - scrpos.y = math.Clamp(scrpos.y, sz, ScrH() - sz) - - if util.IsOffScreen(scrpos) then return end - - surface.DrawTexturedRect(scrpos.x - sz, scrpos.y - sz, sz * 2, sz * 2) - - -- Drawing full size? - if sz == size then - local text = math.ceil(LocalPlayer():GetPos():Distance(tgt.pos)) - local w, h = surface.GetTextSize(text) - - -- Show range to target - surface.SetTextPos(scrpos.x - w * 0.5, scrpos.y + offset * sz - h * 0.5) - surface.DrawText(text) - - if tgt.t then - -- Show time - text = util.SimpleTime(tgt.t - CurTime(), "%02i:%02i") - w, h = surface.GetTextSize(text) - - surface.SetTextPos(scrpos.x - w * 0.5, scrpos.y + sz * 0.5) - surface.DrawText(text) - elseif tgt.nick then - -- Show nickname - text = tgt.nick - w, h = surface.GetTextSize(text) - - surface.SetTextPos(scrpos.x - w * 0.5, scrpos.y + sz * 0.5) - surface.DrawText(text) - end - end + local scrpos = tgt.pos:ToScreen() -- sweet + local sz = (util.IsOffScreen(scrpos) and not no_shrink) and (size * 0.5) or size + + scrpos.x = math.Clamp(scrpos.x, sz, ScrW() - sz) + scrpos.y = math.Clamp(scrpos.y, sz, ScrH() - sz) + + if util.IsOffScreen(scrpos) then + return + end + + surface.DrawTexturedRect(scrpos.x - sz, scrpos.y - sz, sz * 2, sz * 2) + + -- Drawing full size? + if sz == size then + local text = tostring( + math.Round(util.HammerUnitsToMeters(LocalPlayer():EyePos():Distance(tgt.pos)), 0) + ) .. "m" + local w, h = surface.GetTextSize(text) + + -- Show range to target + surface.SetTextPos(scrpos.x - w * 0.5, scrpos.y + offset * sz - h * 0.5) + surface.DrawText(text) + + if tgt.t then + -- Show time + text = util.SimpleTime(tgt.t - CurTime(), "%02i:%02i") + w, h = surface.GetTextSize(text) + + surface.SetTextPos(scrpos.x - w * 0.5, scrpos.y + sz * 0.5) + surface.DrawText(text) + elseif tgt.nick then + -- Show nickname + text = tgt.nick + w, h = surface.GetTextSize(text) + + surface.SetTextPos(scrpos.x - w * 0.5, scrpos.y + sz * 0.5) + surface.DrawText(text) + end + end end --- @@ -149,153 +153,138 @@ end -- @internal -- @realm client function RADAR:Draw(client) - if not IsValid(client) then return end - - GetPTranslation = GetPTranslation or LANG.GetParamTranslation - - surface.SetFont("HudSelectionText") - - -- C4 warnings - if self.bombs_count ~= 0 and client:IsActive() and not client:GetSubRoleData().unknownTeam then - surface.SetTexture(c4warn) - surface.SetTextColor(client:GetRoleColor()) - surface.SetDrawColor(255, 255, 255, 200) - - for _, bomb in pairs(self.bombs) do - if bomb.team ~= nil and bomb.team == client:GetTeam() then - DrawTarget(bomb, 24, 0, true) - end - end - end - - -- Corpse calls - if not table.IsEmpty(self.called_corpses) then - surface.SetTexture(det_beacon) - surface.SetTextColor(255, 255, 255, 240) - surface.SetDrawColor(255, 255, 255, 230) - - for _, corpse in pairs(self.called_corpses) do - DrawTarget(corpse, 16, 0.5) - end - end - - -- Samples - if self.samples_count ~= 0 then - surface.SetTexture(sample_scan) - surface.SetTextColor(200, 50, 50, 255) - surface.SetDrawColor(255, 255, 255, 240) - - for _, sample in pairs(self.samples) do - DrawTarget(sample, 16, 0.5, true) - end - end - - -- Player radar - if not self.enable then return end - - surface.SetTexture(indicator) - - local radarTime = client.radarTime or 30 - local remaining = math.max(0, radarTime - (CurTime() - RADAR.startTime)) - local alpha_base = 50 + 180 * (remaining / radarTime) - local mpos = Vector(ScrW() * 0.5, ScrH() * 0.5, 0) - - local subrole, alpha, scrpos, md - - for i = 1, #RADAR.targets do - local tgt = RADAR.targets[i] - - alpha = alpha_base - scrpos = tgt.pos:ToScreen() - - if scrpos.visible then - md = mpos:Distance(Vector(scrpos.x, scrpos.y, 0)) - - if md < near_cursor_dist then - alpha = math.Clamp(alpha * (md / near_cursor_dist), 40, 230) - end - - subrole = tgt.subrole or ROLE_INNOCENT - - local roleData = roles.GetByIndex(subrole) - local c = roleData.radarColor or (TEAMS[tgt.team] and TEAMS[tgt.team].color or colorFallback) - - if tgt.color then - surface.SetDrawColor(tgt.color.r, tgt.color.g, tgt.color.b, alpha) - surface.SetTextColor(tgt.color.r, tgt.color.g, tgt.color.b, alpha) - else - surface.SetDrawColor(c.r, c.g, c.b, alpha) - surface.SetTextColor(c.r, c.g, c.b, alpha) - end - - DrawTarget(tgt, 24, 0) - end - end -end - -local function ReceiveC4Warn() - local idx = net.ReadUInt(16) - local armed = net.ReadBit() == 1 - - if armed then - local pos = net.ReadVector() - local etime = net.ReadFloat() - local team = net.ReadString() - - RADAR.bombs[idx] = {pos = pos, t = etime, team = team} - else - RADAR.bombs[idx] = nil - end - - RADAR.bombs_count = table.Count(RADAR.bombs) + if not IsValid(client) then + return + end + + GetPTranslation = GetPTranslation or LANG.GetParamTranslation + + surface.SetFont("HudSelectionText") + + -- bomb warnings + -- note: This is deprecated use markerVision instead + if self.bombs_count ~= 0 and client:IsActive() and not client:GetSubRoleData().unknownTeam then + surface.SetTexture(c4warn) + surface.SetTextColor(client:GetRoleColor()) + surface.SetDrawColor(255, 255, 255, 200) + + for _, bomb in pairs(self.bombs) do + if bomb.team ~= nil and bomb.team == client:GetTeam() then + DrawTarget(bomb, 24, 0, true) + end + end + end + + -- corpse calls + if not table.IsEmpty(self.called_corpses) then + surface.SetTexture(det_beacon) + surface.SetTextColor(255, 255, 255, 240) + surface.SetDrawColor(255, 255, 255, 230) + + for _, corpse in pairs(self.called_corpses) do + DrawTarget(corpse, 16, 0.5) + end + end + + -- DNA samples + if self.samples_count ~= 0 then + surface.SetTexture(sample_scan) + surface.SetTextColor(200, 50, 50, 255) + surface.SetDrawColor(255, 255, 255, 240) + + for _, sample in pairs(self.samples) do + DrawTarget(sample, 16, 0.5, true) + end + end + + -- player radar + if not self.enable then + return + end + + surface.SetTexture(indicator) + + local radarTime = client.radarTime or 30 + local remaining = math.max(0, radarTime - (CurTime() - RADAR.startTime)) + local alpha_base = 55 + 200 * (remaining / radarTime) + local mpos = Vector(ScrW() * 0.5, ScrH() * 0.5, 0) + + local subrole, alpha, scrpos, md + + for i = 1, #RADAR.targets do + local tgt = RADAR.targets[i] + + alpha = alpha_base + scrpos = tgt.pos:ToScreen() + + if scrpos.visible then + md = mpos:Distance(Vector(scrpos.x, scrpos.y, 0)) + + if md < near_cursor_dist then + alpha = math.Clamp(alpha * (md / near_cursor_dist), 40, 230) + end + + subrole = tgt.subrole or ROLE_INNOCENT + + local roleData = roles.GetByIndex(subrole) + local c = roleData.radarColor + or (TEAMS[tgt.team] and TEAMS[tgt.team].color or colorFallback) + + if tgt.color then + surface.SetDrawColor(tgt.color.r, tgt.color.g, tgt.color.b, alpha) + surface.SetTextColor(tgt.color.r, tgt.color.g, tgt.color.b, alpha) + else + surface.SetDrawColor(c.r, c.g, c.b, alpha) + surface.SetTextColor(c.r, c.g, c.b, alpha) + end + + DrawTarget(tgt, 24, 0) + end + end end -net.Receive("TTT_C4Warn", ReceiveC4Warn) local function TTT_CorpseCall() - local pos = net.ReadVector() - local _tmp = {pos = pos, called = CurTime()} + local pos = net.ReadVector() + local _tmp = { pos = pos, called = CurTime() } - table.insert(RADAR.called_corpses, _tmp) + table.insert(RADAR.called_corpses, _tmp) end net.Receive("TTT_CorpseCall", TTT_CorpseCall) local function ReceiveRadarScan() - local num_targets = net.ReadUInt(16) + local num_targets = net.ReadUInt(16) - RADAR.targets = {} + RADAR.targets = {} - for i = 1, num_targets do - local pos = Vector( - net.ReadInt(32), - net.ReadInt(32), - net.ReadInt(32) - ) + for i = 1, num_targets do + local pos = Vector(net.ReadInt(32), net.ReadInt(32), net.ReadInt(32)) - local hasSubrole = net.ReadBool() - local subrole + local hasSubrole = net.ReadBool() + local subrole - if hasSubrole then - subrole = net.ReadUInt(ROLE_BITS) - end + if hasSubrole then + subrole = net.ReadUInt(ROLE_BITS) + end - local team = net.ReadString() + local team = net.ReadString() - local color + local color - if net.ReadBool() then - color = net.ReadColor() - end + if net.ReadBool() then + color = net.ReadColor() + end - RADAR.targets[i] = {pos = pos, subrole = subrole, team = team, hasSubrole = hasSubrole, color = color} - end + RADAR.targets[i] = + { pos = pos, subrole = subrole, team = team, hasSubrole = hasSubrole, color = color } + end - RADAR.enable = true - RADAR.startTime = CurTime() + RADAR.enable = true + RADAR.startTime = CurTime() end net.Receive("TTT_Radar", ReceiveRadarScan) local function ReceiveRadarTime() - LocalPlayer().radarTime = net.ReadUInt(8) + LocalPlayer().radarTime = net.ReadUInt(8) end net.Receive("TTT2RadarUpdateTime", ReceiveRadarTime) @@ -307,67 +296,63 @@ net.Receive("TTT2RadarUpdateTime", ReceiveRadarTime) -- @internal -- @realm client function RADAR.CreateMenu(parent, frame) - GetTranslation = GetTranslation or LANG.GetTranslation - GetPTranslation = GetPTranslation or LANG.GetParamTranslation - --local w, h = parent:GetSize() + GetTranslation = GetTranslation or LANG.GetTranslation + GetPTranslation = GetPTranslation or LANG.GetParamTranslation + --local w, h = parent:GetSize() - local dform = vgui.Create("DForm", parent) - dform:SetName(GetTranslation("radar_menutitle")) - dform:StretchToParent(0, 0, 0, 0) - dform:SetAutoSize(false) + local dform = vgui.Create("DForm", parent) + dform:SetName(GetTranslation("radar_menutitle")) + dform:StretchToParent(0, 0, 0, 0) + dform:SetAutoSize(false) - local owned = LocalPlayer():HasEquipmentItem("item_ttt_radar") - if not owned then - dform:Help(GetTranslation("radar_not_owned")) + local owned = LocalPlayer():HasEquipmentItem("item_ttt_radar") + if not owned then + dform:Help(GetTranslation("radar_not_owned")) - return dform - end + return dform + end - local bw, bh = 100, 25 + local bw, bh = 100, 25 - local dscan = vgui.Create("DButton", dform) - dscan:SetSize(bw, bh) - dscan:SetText(GetTranslation("radar_scan")) + local dscan = vgui.Create("DButton", dform) + dscan:SetSize(bw, bh) + dscan:SetText(GetTranslation("radar_scan")) - dscan.DoClick = function(s) - RunConsoleCommand("ttt_radar_scan") + dscan.DoClick = function(s) + RunConsoleCommand("ttt_radar_scan") - frame:Close() - end + frame:Close() + end - dform:AddItem(dscan) + dform:AddItem(dscan) - local dlabel = vgui.Create("DLabel", dform) - dlabel:SetText(GetPTranslation("radar_help", {num = LocalPlayer().radarTime or 30})) - dlabel:SetWrap(true) - dlabel:SetTall(50) + local dlabel = vgui.Create("DLabel", dform) + dlabel:SetText(GetPTranslation("radar_help", { num = LocalPlayer().radarTime or 30 })) + dlabel:SetWrap(true) + dlabel:SetTall(50) - dform:AddItem(dlabel) + dform:AddItem(dlabel) - local dcheck = vgui.Create("DCheckBoxLabel", dform) - dcheck:SetText(GetTranslation("radar_auto")) - dcheck:SetIndent(5) - dcheck:SetValue(RADAR.repeating) + local dcheck = vgui.Create("DCheckBoxLabel", dform) + dcheck:SetText(GetTranslation("radar_auto")) + dcheck:SetIndent(5) + dcheck:SetValue(RADAR.repeating) - dcheck.OnChange = function(s, val) - net.Start("TTT2RadarUpdateAutoScan") - net.WriteBool(val) - net.SendToServer() + dcheck.OnChange = function(s, val) + net.Start("TTT2RadarUpdateAutoScan") + net.WriteBool(val) + net.SendToServer() - RADAR.repeating = val - end + RADAR.repeating = val + end - dform:AddItem(dcheck) + dform:AddItem(dcheck) - dform.Think = function(s) - if RADAR.repeating or not owned then - dscan:SetDisabled(true) - else - dscan:SetDisabled(false) - end - end + dform.Think = function(s) + dscan:SetEnabled(not RADAR.repeating and owned) + end - dform:SetVisible(true) + dform:SetVisible(true) - return dform + return dform end diff --git a/lua/terrortown/entities/items/item_ttt_radar/init.lua b/lua/terrortown/entities/items/item_ttt_radar/init.lua index dd2241d39..bd3f3c502 100644 --- a/lua/terrortown/entities/items/item_ttt_radar/init.lua +++ b/lua/terrortown/entities/items/item_ttt_radar/init.lua @@ -17,17 +17,19 @@ util.AddNetworkString("TTT2RadarUpdateTime") --- -- @ignore function ITEM:Initialize() - cv_radarCharge = GetConVar("ttt2_radar_charge_time") + cv_radarCharge = GetConVar("ttt2_radar_charge_time") end local function UpdateTimeOnPlayer(ply) - if ply.lastRadarTime == ply.radarTime then return end + if ply.lastRadarTime == ply.radarTime then + return + end - ply.lastRadarTime = ply.radarTime + ply.lastRadarTime = ply.radarTime - net.Start("TTT2RadarUpdateTime") - net.WriteUInt(ply.radarTime, 8) - net.Send(ply) + net.Start("TTT2RadarUpdateTime") + net.WriteUInt(ply.radarTime, 8) + net.Send(ply) end --- @@ -41,85 +43,93 @@ RADAR = RADAR or {} -- @internal -- @realm server function RADAR.TriggerRadarScan(ply) - if not IsValid(ply) or not ply:IsTerror() then return end + if not IsValid(ply) or not ply:IsTerror() then + return + end - if not ply:HasEquipmentItem("item_ttt_radar") then - LANG.Msg(ply, "radar_not_owned", nil, MSG_CHAT_WARN) + if not ply:HasEquipmentItem("item_ttt_radar") then + LANG.Msg(ply, "radar_not_owned", nil, MSG_CHAT_WARN) - return - end + return + end - if ply.radar_charge > CurTime() then - LANG.Msg(ply, "radar_charging", nil, MSG_CHAT_WARN) + if ply.radar_charge > CurTime() then + LANG.Msg(ply, "radar_charging", nil, MSG_CHAT_WARN) - return - end + return + end - -- update radar time after the previous scan was finished - UpdateTimeOnPlayer(ply) + -- update radar time after the previous scan was finished + UpdateTimeOnPlayer(ply) - -- remove 0.1 seconds to account for rounding errors - ply.radar_charge = CurTime() + ply.radarTime - 0.1 + -- remove 0.1 seconds to account for rounding errors + ply.radar_charge = CurTime() + ply.radarTime - 0.1 - local targets, customradar + local targets, customradar - if ply:GetSubRoleData() and ply:GetSubRoleData().CustomRadar then - customradar = ply:GetSubRoleData().CustomRadar(ply) - end + if ply:GetSubRoleData() and ply:GetSubRoleData().CustomRadar then + customradar = ply:GetSubRoleData().CustomRadar(ply) + end - if istable(customradar) then - targets = table.Copy(customradar) - else -- if we get no value we use default radar - targets = {} + if istable(customradar) then + targets = table.Copy(customradar) + else -- if we get no value we use default radar + targets = {} - local scan_ents = player.GetAll() + local scan_ents = player.GetAll() - table.Add(scan_ents, ents.FindByClass("ttt_decoy")) + table.Add(scan_ents, ents.FindByClass("ttt_decoy")) - for i = 1, #scan_ents do - local ent = scan_ents[i] + for i = 1, #scan_ents do + local ent = scan_ents[i] - if not IsValid(ent) or ply == ent or ent:IsPlayer() and (not ent:IsTerror() or ent:GetNWBool("disguised", false)) then continue end + if + not IsValid(ent) + or ply == ent + or ent:IsPlayer() and (not ent:IsTerror() or ent:GetNWBool("disguised", false)) + then + continue + end - local pos = ent:LocalToWorld(ent:OBBCenter()) + local pos = ent:LocalToWorld(ent:OBBCenter()) - -- Round off, easier to send and inaccuracy does not matter - pos.x = math.Round(pos.x) - pos.y = math.Round(pos.y) - pos.z = math.Round(pos.z) + -- Round off, easier to send and inaccuracy does not matter + pos.x = math.Round(pos.x) + pos.y = math.Round(pos.y) + pos.z = math.Round(pos.z) - targets[#targets + 1] = RADAR.CreateTargetTable(ply, pos, ent) - end - end + targets[#targets + 1] = RADAR.CreateTargetTable(ply, pos, ent) + end + end - net.Start("TTT_Radar") - net.WriteUInt(#targets, 16) + net.Start("TTT_Radar") + net.WriteUInt(#targets, 16) - for i = 1, #targets do - local tgt = targets[i] + for i = 1, #targets do + local tgt = targets[i] - net.WriteInt(tgt.pos.x, 32) - net.WriteInt(tgt.pos.y, 32) - net.WriteInt(tgt.pos.z, 32) + net.WriteInt(tgt.pos.x, 32) + net.WriteInt(tgt.pos.y, 32) + net.WriteInt(tgt.pos.z, 32) - if tgt.subrole == -1 then - net.WriteBool(false) - else - net.WriteBool(true) - net.WriteUInt(tgt.subrole, ROLE_BITS) - end + if tgt.subrole == -1 then + net.WriteBool(false) + else + net.WriteBool(true) + net.WriteUInt(tgt.subrole, ROLE_BITS) + end - net.WriteString(tgt.team or "none") + net.WriteString(tgt.team or "none") - if tgt.color then - net.WriteBool(true) - net.WriteColor(tgt.color) - else - net.WriteBool(false) - end - end + if tgt.color then + net.WriteBool(true) + net.WriteColor(tgt.color) + else + net.WriteBool(false) + end + end - net.Send(ply) + net.Send(ply) end concommand.Add("ttt_radar_scan", RADAR.TriggerRadarScan) @@ -131,35 +141,45 @@ concommand.Add("ttt_radar_scan", RADAR.TriggerRadarScan) -- @return nil|string The modified team -- @hook -- @realm server -function GM:TTT2ModifyRadarRole(ply, target) - -end +function GM:TTT2ModifyRadarRole(ply, target) end local function GetDataForRadar(ply, ent) - local subrole, team = -1, "none" - - if not IsValid(ent) then - subrole = -1 - elseif not ent:IsPlayer() then - -- Decoys appear as innocents for players from other teams - if ent:GetNWString("decoy_owner_team", "none") ~= ply:GetTeam() then - subrole = ROLE_INNOCENT - end - else - --- - -- @realm server - subrole, team = hook.Run("TTT2ModifyRadarRole", ply, ent) - - if not subrole then - subrole = (ent:IsInTeam(ply) or table.HasValue(ent:GetSubRoleData().visibleForTeam, ply:GetTeam())) and ent:GetSubRole() or ROLE_INNOCENT - end - - if not team then - team = (ent:IsInTeam(ply) or table.HasValue(ent:GetSubRoleData().visibleForTeam, ply:GetTeam())) and ent:GetTeam() or TEAM_INNOCENT - end - end - - return subrole, team + local subrole, team = -1, "none" + + if not IsValid(ent) then + subrole = -1 + elseif not ent:IsPlayer() then + -- Decoys appear as innocents for players from other teams + if ent:GetNWString("decoy_owner_team", "none") ~= ply:GetTeam() then + subrole = ROLE_INNOCENT + team = TEAM_INNOCENT + end + else + --- + -- @realm server + -- stylua: ignore + subrole, team = hook.Run("TTT2ModifyRadarRole", ply, ent) + + if not subrole then + subrole = ( + ent:IsInTeam(ply) + or table.HasValue(ent:GetSubRoleData().visibleForTeam, ply:GetTeam()) + ) + and ent:GetSubRole() + or ROLE_INNOCENT + end + + if not team then + team = ( + ent:IsInTeam(ply) + or table.HasValue(ent:GetSubRoleData().visibleForTeam, ply:GetTeam()) + ) + and ent:GetTeam() + or TEAM_INNOCENT + end + end + + return subrole, team end --- @@ -171,14 +191,14 @@ end -- @return table -- @realm server function RADAR.CreateTargetTable(ply, pos, ent, color) - local subrole, team = GetDataForRadar(ply, ent) - - return { - pos = pos, - subrole = subrole, - team = team, - color = color - } + local subrole, team = GetDataForRadar(ply, ent) + + return { + pos = pos, + subrole = subrole, + team = team, + color = color, + } end --- @@ -187,14 +207,18 @@ end -- @internal -- @realm server function RADAR.SetupRadarScan(ply) - timer.Create("radarTimeout_" .. ply:SteamID64(), ply.radarTime, 1, function() - if not IsValid(ply) or not ply:HasEquipmentItem("item_ttt_radar") - or ply.radarDoesNotRepeat - then return end - - RADAR.TriggerRadarScan(ply) - RADAR.SetupRadarScan(ply) - end) + timer.Create("radarTimeout_" .. ply:SteamID64(), ply.radarTime, 1, function() + if + not IsValid(ply) + or not ply:HasEquipmentItem("item_ttt_radar") + or ply.radarDoesNotRepeat + then + return + end + + RADAR.TriggerRadarScan(ply) + RADAR.SetupRadarScan(ply) + end) end --- @@ -203,12 +227,14 @@ end -- @param Player ply The player who owens the radar -- @realm server function RADAR.Init(ply) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply:ResetRadarTime() + ply:ResetRadarTime() - RADAR.TriggerRadarScan(ply) - RADAR.SetupRadarScan(ply) + RADAR.TriggerRadarScan(ply) + RADAR.SetupRadarScan(ply) end --- @@ -217,15 +243,19 @@ end -- @param Player ply The player who owned the radar -- @realm server function RADAR.Deinit(ply) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - timer.Remove("radarTimeout_" .. ply:SteamID64()) + timer.Remove("radarTimeout_" .. ply:SteamID64()) end net.Receive("TTT2RadarUpdateAutoScan", function(_, ply) - if not IsValid(ply) then return end + if not IsValid(ply) then + return + end - ply.radarDoesNotRepeat = not net.ReadBool() + ply.radarDoesNotRepeat = not net.ReadBool() end) --- @@ -238,14 +268,14 @@ local plymeta = assert(FindMetaTable("Player"), "FAILED TO FIND PLAYER TABLE") -- @param number time The radar time interval -- @realm server function plymeta:SetRadarTime(time) - self.radarTime = time + self.radarTime = time end --- -- Sets the radar time interval to the role or convar default, lets the current scan run out before it is changed. -- @realm server function plymeta:ResetRadarTime() - self.radarTime = self:GetSubRoleData().radarTime or cv_radarCharge:GetInt() + self.radarTime = self:GetSubRoleData().radarTime or cv_radarCharge:GetInt() end --- @@ -253,13 +283,15 @@ end -- call this function after @{plymeta:SetRadarTime} to enforce an immediate change. -- @realm server function plymeta:ForceRadarScan() - if not self:HasEquipmentItem("item_ttt_radar") then return end + if not self:HasEquipmentItem("item_ttt_radar") then + return + end - RADAR.Deinit(self) + RADAR.Deinit(self) - -- reset the radar charge end time to now to allow a new scan - self.radar_charge = CurTime() + -- reset the radar charge end time to now to allow a new scan + self.radar_charge = CurTime() - RADAR.TriggerRadarScan(self) - RADAR.SetupRadarScan(self) + RADAR.TriggerRadarScan(self) + RADAR.SetupRadarScan(self) end diff --git a/lua/terrortown/entities/items/item_ttt_radar/shared.lua b/lua/terrortown/entities/items/item_ttt_radar/shared.lua index 7c82184f0..d6058fdd8 100644 --- a/lua/terrortown/entities/items/item_ttt_radar/shared.lua +++ b/lua/terrortown/entities/items/item_ttt_radar/shared.lua @@ -1,31 +1,35 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ITEM.hud = Material("vgui/ttt/perks/hud_radar.png") ITEM.EquipMenuData = { - type = "item_active", - name = "item_radar", - desc = "item_radar_desc" + type = "item_active", + name = "item_radar", + desc = "item_radar_desc", } ITEM.material = "vgui/ttt/icon_radar" -ITEM.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +ITEM.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } ITEM.oldId = EQUIP_RADAR or 2 +ITEM.builtin = true --- -- @ignore function ITEM:Equip(buyer) - if SERVER then - RADAR.Init(buyer) - end + if SERVER then + RADAR.Init(buyer) + end end --- -- @ignore function ITEM:Reset(buyer) - if SERVER then - RADAR.Deinit(buyer) - end + if SERVER then + RADAR.Deinit(buyer) + end - buyer.radar_charge = 0 + buyer.radar_charge = 0 + if CLIENT then + RADAR:Clear() + end end diff --git a/lua/terrortown/entities/items/item_ttt_speedrun.lua b/lua/terrortown/entities/items/item_ttt_speedrun.lua index dd349146e..484f9e66d 100644 --- a/lua/terrortown/entities/items/item_ttt_speedrun.lua +++ b/lua/terrortown/entities/items/item_ttt_speedrun.lua @@ -1,18 +1,21 @@ if SERVER then - AddCSLuaFile() + AddCSLuaFile() end ITEM.EquipMenuData = { - type = "item_passive", - name = "item_speedrun", - desc = "item_speedrun_desc" + type = "item_passive", + name = "item_speedrun", + desc = "item_speedrun_desc", } -ITEM.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE} +ITEM.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } ITEM.material = "vgui/ttt/icon_speedrun" +ITEM.builtin = true hook.Add("TTTPlayerSpeedModifier", "TTT2SpeedRun", function(ply, _, _, speedMultiplierModifier) - if not IsValid(ply) or not ply:HasEquipmentItem("item_ttt_speedrun") then return end + if not IsValid(ply) or not ply:HasEquipmentItem("item_ttt_speedrun") then + return + end - speedMultiplierModifier[1] = speedMultiplierModifier[1] * 1.5 + speedMultiplierModifier[1] = speedMultiplierModifier[1] * 1.5 end) diff --git a/lua/terrortown/entities/roles/detective/shared.lua b/lua/terrortown/entities/roles/detective/shared.lua index 0a86b28b2..6de90ab4c 100644 --- a/lua/terrortown/entities/roles/detective/shared.lua +++ b/lua/terrortown/entities/roles/detective/shared.lua @@ -5,47 +5,47 @@ ROLE.index = ROLE_DETECTIVE --- -- @ignore function ROLE:PreInitialize() - self.color = Color(31, 77, 191, 255) + self.color = Color(31, 77, 191, 255) - self.abbr = "det" + self.abbr = "det" - self.defaultTeam = TEAM_INNOCENT - self.defaultEquipment = SPECIAL_EQUIPMENT + self.builtin = true - self.score.killsMultiplier = 8 - self.score.teamKillsMultiplier = -8 - self.score.bodyFoundMuliplier = 3 - self.fallbackTable = {} - self.unknownTeam = true + self.defaultTeam = TEAM_INNOCENT + self.defaultEquipment = SPECIAL_EQUIPMENT - self.isPublicRole = true - self.isPolicingRole = true + self.score.killsMultiplier = 8 + self.score.teamKillsMultiplier = -8 + self.score.bodyFoundMuliplier = 3 + self.fallbackTable = {} + self.unknownTeam = true - -- conVarData - self.conVarData = { - pct = 0.13, - maximum = 32, - minPlayers = 8, - minKarma = 600, + self.isPublicRole = true + self.isPolicingRole = true - credits = 1, - creditsAwardDeadEnable = 1, - creditsAwardKillEnable = 0, + -- conVarData + self.conVarData = { + pct = 0.13, + maximum = 32, + minPlayers = 8, + minKarma = 600, - togglable = true - } + credits = 1, + creditsAwardDeadEnable = 1, + creditsAwardKillEnable = 0, + } end if SERVER then - --- - -- @ignore - function ROLE:GiveRoleLoadout(ply) - ply:GiveEquipmentWeapon("weapon_ttt_wtester") - end - - --- - -- @ignore - function ROLE:RemoveRoleLoadout(ply) - ply:RemoveEquipmentWeapon("weapon_ttt_wtester") - end + --- + -- @ignore + function ROLE:GiveRoleLoadout(ply) + ply:GiveEquipmentWeapon("weapon_ttt_wtester") + end + + --- + -- @ignore + function ROLE:RemoveRoleLoadout(ply) + ply:RemoveEquipmentWeapon("weapon_ttt_wtester") + end end diff --git a/lua/terrortown/entities/roles/innocent/shared.lua b/lua/terrortown/entities/roles/innocent/shared.lua index 0ee3ec158..35a3add53 100644 --- a/lua/terrortown/entities/roles/innocent/shared.lua +++ b/lua/terrortown/entities/roles/innocent/shared.lua @@ -5,27 +5,28 @@ ROLE.index = ROLE_INNOCENT --- -- @ignore function ROLE:PreInitialize() - self.color = Color(80, 173, 59, 255) + self.color = Color(80, 173, 59, 255) - self.abbr = "inno" + self.abbr = "inno" - self.defaultTeam = TEAM_INNOCENT - self.defaultEquipment = SPECIAL_EQUIPMENT + self.defaultTeam = TEAM_INNOCENT + self.defaultEquipment = SPECIAL_EQUIPMENT - self.builtin = true - self.score.killsMultiplier = 2 - self.score.teamKillsMultiplier = -8 - self.unknownTeam = true + self.builtin = true + self.score.killsMultiplier = 2 + self.score.teamKillsMultiplier = -8 + self.unknownTeam = true end if SERVER then - --- - -- @realm server - local ttt_min_inno_pct = CreateConVar("ttt_min_inno_pct", "0.47", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Minimum multiplicator for each player to calculate the minimum amount of innocents") + --- + -- @realm server + -- stylua: ignore + local ttt_min_inno_pct = CreateConVar("ttt_min_inno_pct", "0.47", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Minimum multiplicator for each player to calculate the minimum amount of innocents") - --- - -- @ignore - function ROLE:GetAvailableRoleCount(ply_count) - return math.floor(ply_count * ttt_min_inno_pct:GetFloat()) - end + --- + -- @ignore + function ROLE:GetAvailableRoleCount(ply_count) + return math.floor(ply_count * ttt_min_inno_pct:GetFloat()) + end end diff --git a/lua/terrortown/entities/roles/none/shared.lua b/lua/terrortown/entities/roles/none/shared.lua index 777355162..62e25fcff 100644 --- a/lua/terrortown/entities/roles/none/shared.lua +++ b/lua/terrortown/entities/roles/none/shared.lua @@ -5,13 +5,13 @@ ROLE.index = ROLE_NONE --- -- @ignore function ROLE:PreInitialize() - self.color = COLOR_WARMGRAY + self.color = COLOR_WARMGRAY - self.abbr = "none" + self.abbr = "none" - self.defaultTeam = TEAM_NONE - self.defaultEquipment = {} + self.defaultTeam = TEAM_NONE + self.defaultEquipment = {} - self.builtin = true - self.notSelectable = true + self.builtin = true + self.notSelectable = true end diff --git a/lua/terrortown/entities/roles/traitor/shared.lua b/lua/terrortown/entities/roles/traitor/shared.lua index 3586afe24..ee9eba357 100644 --- a/lua/terrortown/entities/roles/traitor/shared.lua +++ b/lua/terrortown/entities/roles/traitor/shared.lua @@ -5,33 +5,34 @@ ROLE.index = ROLE_TRAITOR --- -- @ignore function ROLE:PreInitialize() - self.color = Color(209, 43, 39, 255) + self.color = Color(209, 43, 39, 255) - self.abbr = "traitor" + self.abbr = "traitor" - self.builtin = true + self.builtin = true - self.defaultTeam = TEAM_TRAITOR - self.defaultEquipment = TRAITOR_EQUIPMENT + self.defaultTeam = TEAM_TRAITOR + self.defaultEquipment = TRAITOR_EQUIPMENT - self.score.surviveBonusMultiplier = 0.5 - self.score.timelimitMultiplier = -0.5 - self.score.killsMultiplier = 2 - self.score.teamKillsMultiplier = -16 - self.score.bodyFoundMuliplier = 0 + self.score.surviveBonusMultiplier = 0.5 + self.score.timelimitMultiplier = -0.5 + self.score.killsMultiplier = 2 + self.score.teamKillsMultiplier = -16 + self.score.bodyFoundMuliplier = 0 - self.isOmniscientRole = true + self.isOmniscientRole = true - self.fallbackTable = {} + self.fallbackTable = {} - -- conVarData - self.conVarData = { - pct = 0.4, - maximum = 32, - minPlayers = 1, - traitorButton = 1, - credits = 2, - creditsAwardDeadEnable = 1, - creditsAwardKillEnable = 1 - } + -- conVarData + self.conVarData = { + pct = 0.4, + maximum = 32, + minPlayers = 1, + traitorButton = 1, + ragdollPinning = 1, + credits = 2, + creditsAwardDeadEnable = 1, + creditsAwardKillEnable = 1, + } end diff --git a/lua/terrortown/entities/roles/ttt_role_base/cl_init.lua b/lua/terrortown/entities/roles/ttt_role_base/cl_init.lua index a27ae8ba7..f1d55c7c7 100644 --- a/lua/terrortown/entities/roles/ttt_role_base/cl_init.lua +++ b/lua/terrortown/entities/roles/ttt_role_base/cl_init.lua @@ -11,9 +11,7 @@ -- @param DPanel parent The parent panel which is the submenu -- @hook -- @realm client -function ROLE:AddToSettingsMenu(parent) - -end +function ROLE:AddToSettingsMenu(parent) end --- -- This hook can be used by role addons to populate the role credit settings form @@ -22,6 +20,4 @@ end -- @param DPanel parent The parent panel which is the credits form -- @hook -- @realm client -function ROLE:AddToSettingsMenuCreditsForm(parent) - -end +function ROLE:AddToSettingsMenuCreditsForm(parent) end diff --git a/lua/terrortown/entities/roles/ttt_role_base/init.lua b/lua/terrortown/entities/roles/ttt_role_base/init.lua index 6efeefa27..2f9469968 100644 --- a/lua/terrortown/entities/roles/ttt_role_base/init.lua +++ b/lua/terrortown/entities/roles/ttt_role_base/init.lua @@ -10,9 +10,7 @@ -- @param Player ply -- @param boolean isRoleChange This is true for a rolechange, but not for a respawn -- @realm server -function ROLE:GiveRoleLoadout(ply, isRoleChange) - -end +function ROLE:GiveRoleLoadout(ply, isRoleChange) end --- -- Function that is overwritten by the role and is called on rolechange and death. @@ -20,9 +18,7 @@ end -- @param Player ply -- @param boolean isRoleChange This is true for a rolechange, but not for death -- @realm server -function ROLE:RemoveRoleLoadout(ply, isRoleChange) - -end +function ROLE:RemoveRoleLoadout(ply, isRoleChange) end --- -- Checks whether a role is able to get selected (and maybe assigned to a @{Player}) if the round starts @@ -30,13 +26,15 @@ end -- @return boolean -- @realm server function ROLE:IsSelectable(avoidHook) - return self == INNOCENT or self == TRAITOR - or (GetConVar("ttt_newroles_enabled"):GetBool() or self == DETECTIVE) - and not self.notSelectable - and GetConVar("ttt_" .. self.name .. "_enabled"):GetBool() - --- - -- @realm server - and (avoidHook or not hook.Run("TTT2RoleNotSelectable", self)) + return self == roles.INNOCENT + or self == roles.TRAITOR + or (GetConVar("ttt_newroles_enabled"):GetBool() or self == roles.DETECTIVE) + and not self.notSelectable + and GetConVar("ttt_" .. self.name .. "_enabled"):GetBool() + --- + -- @realm server + -- stylua: ignore + and (avoidHook or not hook.Run("TTT2RoleNotSelectable", self)) end --- @@ -45,20 +43,22 @@ end -- @return number selectable amount of this role -- @realm server function ROLE:GetAvailableRoleCount(ply_count) - if ply_count < GetConVar("ttt_" .. self.name .. "_min_players"):GetInt() then - return 0 - end + if ply_count < GetConVar("ttt_" .. self.name .. "_min_players"):GetInt() then + return 0 + end - local maxCVar = GetConVar("ttt_" .. self.name .. "_max") - local maxAmount = maxCVar and maxCVar:GetInt() or 1 + local maxCVar = GetConVar("ttt_" .. self.name .. "_max") + local maxAmount = maxCVar and maxCVar:GetInt() or 1 - if maxAmount <= 1 then return 1 end + if maxAmount <= 1 then + return 1 + end - -- get number of role members: pct of players rounded down - local role_count = math.floor(ply_count * GetConVar("ttt_" .. self.name .. "_pct"):GetFloat()) + -- get number of role members: pct of players rounded down + local role_count = math.floor(ply_count * GetConVar("ttt_" .. self.name .. "_pct"):GetFloat()) - -- make sure there is at least 1 of the role - return math.Clamp(role_count, 1, maxAmount) + -- make sure there is at least 1 of the role + return math.Clamp(role_count, 1, maxAmount) end -- Returns if the role can be awarded credits for a kill. Is is intended to award credits @@ -67,9 +67,9 @@ end -- @return boolean Returns true if the player can be awarded with credits -- @realm server function ROLE:IsAwardedCreditsForKill() - local cv = GetConVar("ttt_" .. self.abbr .. "_credits_award_kill_enb") + local cv = GetConVar("ttt_" .. self.abbr .. "_credits_award_kill_enb") - return cv and cv:GetBool() or false + return cv and cv:GetBool() or false end --- @@ -80,9 +80,9 @@ end -- @return boolean Returns true if the player can be awarded with credits -- @realm server function ROLE:IsAwardedCreditsForPlayerDead() - local cv = GetConVar("ttt_" .. self.abbr .. "_credits_award_dead_enb") + local cv = GetConVar("ttt_" .. self.abbr .. "_credits_award_dead_enb") - return cv and cv:GetBool() or false + return cv and cv:GetBool() or false end --- @@ -91,6 +91,4 @@ end -- @return nil|boolean Return true to cancel selection -- @hook -- @realm server -function GM:TTT2RoleNotSelectable(roleData) - -end +function GM:TTT2RoleNotSelectable(roleData) end diff --git a/lua/terrortown/entities/roles/ttt_role_base/shared.lua b/lua/terrortown/entities/roles/ttt_role_base/shared.lua index 479029a47..4beaf9cbb 100644 --- a/lua/terrortown/entities/roles/ttt_role_base/shared.lua +++ b/lua/terrortown/entities/roles/ttt_role_base/shared.lua @@ -7,96 +7,93 @@ ROLE.isAbstract = true ROLE.score = { - -- The multiplier that is used to calculate the score penalty - -- that is added if this role kills a team member. - teamKillsMultiplier = 0, - - -- The multiplier that is used to calculate the gained score - -- by killing someone from a different team. - killsMultiplier = 0, - - -- The amount of score points gained by confirming a body. - bodyFoundMuliplier = 1, - - -- The amount of score points gained by surviving a round, - -- based on the amount of dead enemy players. Only applied when - -- in winning team. - surviveBonusMultiplier = 0, - - -- The amount of score point lost by surviving a round, based - -- on the amount of surviving team players. Only applied when - -- not in winning team. - survivePenaltyMultiplier = 0, - - -- The amount of score points granted due to a survival of the - -- round for every teammate alive. - aliveTeammatesBonusMultiplier = 1, - - -- Multiplier for a score for every player alive at the end of - -- the round. Can be negative for roles that should kill everyone. - allSurviveBonusMultiplier = 0, - - -- The amount of score points gained by being alive if the - -- round ended with nobody winning, usually a negative number. - timelimitMultiplier = 0, - - -- The amount of points gained by killing yourself. Should be a - -- negative number for most roles. - suicideMultiplier = -1 + -- The multiplier that is used to calculate the score penalty + -- that is added if this role kills a team member. + teamKillsMultiplier = 0, + + -- The multiplier that is used to calculate the gained score + -- by killing someone from a different team. + killsMultiplier = 0, + + -- The amount of score points gained by confirming a body. + bodyFoundMuliplier = 1, + + -- The amount of score points gained by surviving a round, + -- based on the amount of dead enemy players. Only applied when + -- in winning team. + surviveBonusMultiplier = 0, + + -- The amount of score point lost by surviving a round, based + -- on the amount of surviving team players. Only applied when + -- not in winning team. + survivePenaltyMultiplier = 0, + + -- The amount of score points granted due to a survival of the + -- round for every teammate alive. + aliveTeammatesBonusMultiplier = 1, + + -- Multiplier for a score for every player alive at the end of + -- the round. Can be negative for roles that should kill everyone. + allSurviveBonusMultiplier = 0, + + -- The amount of score points gained by being alive if the + -- round ended with nobody winning, usually a negative number. + timelimitMultiplier = 0, + + -- The amount of points gained by killing yourself. Should be a + -- negative number for most roles. + suicideMultiplier = -1, } ROLE.karma = { - -- The multiplier that is used to calculate the Karma penalty for a team kill. - -- Keep in mind that the game will increase the multiplier further if it was avoidable - -- like a kill on a public policing role. - teamKillPenaltyMultiplier = 1, - - -- The multiplier that is used to calculate the Karma penalty for team damage. - -- Keep in mind that the game will increase the multiplier further if it was avoidable - -- like damage applied to a public policing role. - teamHurtPenaltyMultiplier = 1, - - -- The multiplier that is used to change the Karma given to the killer if a player - -- from an enemy team is killed. - enemyKillBonusMultiplier = 1, - - -- The multiplier that is used to change the Karma given to the attacker if a player - -- from an enemy team is damaged. - enemyHurtBonusMultiplier = 1, + -- The multiplier that is used to calculate the Karma penalty for a team kill. + -- Keep in mind that the game will increase the multiplier further if it was avoidable + -- like a kill on a public policing role. + teamKillPenaltyMultiplier = 1, + + -- The multiplier that is used to calculate the Karma penalty for team damage. + -- Keep in mind that the game will increase the multiplier further if it was avoidable + -- like damage applied to a public policing role. + teamHurtPenaltyMultiplier = 1, + + -- The multiplier that is used to change the Karma given to the killer if a player + -- from an enemy team is killed. + enemyKillBonusMultiplier = 1, + + -- The multiplier that is used to change the Karma given to the attacker if a player + -- from an enemy team is damaged. + enemyHurtBonusMultiplier = 1, } ROLE.conVarData = { - -- The percentage of players that get this role. - pct = 0.4, + -- The percentage of players that get this role. + pct = 0.4, - -- The maximum amount of players that get this role. - maximum = 32, + -- The maximum amount of players that get this role. + maximum = 32, - -- The minimum amount of players that have to be available fot this role to be selected. - minPlayers = 1, + -- The minimum amount of players that have to be available fot this role to be selected. + minPlayers = 1, - -- The minimum amount of Karma needed for selection. - minKarma = 0, + -- The minimum amount of Karma needed for selection. + minKarma = 0, - -- Defines if the role has access to traitor buttons. - traitorButton = 0, + -- Defines if the role has access to traitor buttons. + traitorButton = 0, - -- Sets the amount of credits the role is starting with. - credits = 0, + -- Sets the amount of credits the role is starting with. + credits = 0, - -- Defines if this role gains credits if a certain percentage of players - -- from other teams is dead. - creditsAwardDeadEnable = 0, + -- Defines if this role gains credits if a certain percentage of players + -- from other teams is dead. + creditsAwardDeadEnable = 0, - -- Defines if this role is awarded with credits for the kill of a high profile. - -- policing role, such as a detective. - creditsAwardKillEnable = 0, + -- Defines if this role is awarded with credits for the kill of a high profile. + -- policing role, such as a detective. + creditsAwardKillEnable = 0, - -- Sets the shop for this role, by default roles have no shop set. - shopFallback = SHOP_DISABLED, - - -- If this is enabled the 'avoid role selection' can be toggled for this role. - togglable = true + -- Sets the shop for this role, by default roles have no shop set. + shopFallback = SHOP_DISABLED, } -- This role variable makes the role unselectable if set to true. This might be @@ -176,9 +173,7 @@ ROLE.disabledTeamVoiceRecv = false -- This is mostly used to register the defaultTeam, shopFallback, etc... -- @hook -- @realm shared -function ROLE:PreInitialize() - -end +function ROLE:PreInitialize() end --- -- This function is called after all roles have been loaded with their @@ -189,18 +184,16 @@ end -- (eg. @{LANG} function calls). -- @hook -- @realm shared -function ROLE:Initialize() - -end +function ROLE:Initialize() end --- -- Returns the starting credits of a @{ROLE} based on ConVar settings or default traitor settings -- @return[default=0] number -- @realm shared function ROLE:GetStartingCredits() - local cv = GetConVar("ttt_" .. self.abbr .. "_credits_starting") + local cv = GetConVar("ttt_" .. self.abbr .. "_credits_starting") - return cv and cv:GetInt() or 0 + return cv and cv:GetInt() or 0 end --- @@ -208,13 +201,13 @@ end -- @return[default=false] boolean -- @realm shared function ROLE:IsShoppingRole() - if self.subrole == ROLE_NONE then - return false - end + if self.subrole == ROLE_NONE then + return false + end - local shopFallback = GetGlobalString("ttt_" .. self.abbr .. "_shop_fallback") + local shopFallback = GetGlobalString("ttt_" .. self.abbr .. "_shop_fallback") - return shopFallback ~= SHOP_DISABLED + return shopFallback ~= SHOP_DISABLED end --- @@ -222,7 +215,7 @@ end -- @return boolean -- @realm shared function ROLE:IsBaseRole() - return self.baserole == nil + return self.baserole == nil end --- @@ -231,9 +224,11 @@ end -- @deprecated -- @realm shared function ROLE:SetBaseRole(baserole) - print("[TTT2][DEPRECATION] ROLE:SetBaseRole will be removed in the near future! You should call roles.SetBaseRole(self, ROLENAME) in the ROLE:Initialize() function!") + ErrorNoHaltWithStack( + "[TTT2][DEPRECATION] ROLE:SetBaseRole will be removed in the near future! You should call roles.SetBaseRole(self, ROLENAME) in the ROLE:Initialize() function!" + ) - roles.SetBaseRole(self, baserole) + roles.SetBaseRole(self, baserole) end --- @@ -241,7 +236,7 @@ end -- @return number subrole id of the BaseRole (@{ROLE}) -- @realm shared function ROLE:GetBaseRole() - return self.baserole or self.index + return self.baserole or self.index end --- @@ -249,19 +244,19 @@ end -- @return table list of @{ROLE} -- @realm shared function ROLE:GetSubRoles() - local br = self:GetBaseRole() - local tmp = {} - local rlsList = roles.GetList() + local br = self:GetBaseRole() + local tmp = {} + local rlsList = roles.GetList() - for k = 1, #rlsList do - local v = rlsList[k] + for k = 1, #rlsList do + local v = rlsList[k] - if v.baserole and v.baserole == br or v.index == br then - tmp[#tmp + 1] = v - end - end + if v.baserole and v.baserole == br or v.index == br then + tmp[#tmp + 1] = v + end + end - return tmp + return tmp end --- @@ -269,7 +264,7 @@ end -- @return boolean -- @realm shared function ROLE:CanUseTraitorButton() - local cv = GetConVar("ttt_" .. self.name .. "_traitor_button") + local cv = GetConVar("ttt_" .. self.name .. "_traitor_button") - return cv and cv:GetBool() or false + return cv and cv:GetBool() or false end diff --git a/lua/terrortown/events/base_event.lua b/lua/terrortown/events/base_event.lua index b2f240a50..95ef7deb8 100644 --- a/lua/terrortown/events/base_event.lua +++ b/lua/terrortown/events/base_event.lua @@ -9,8 +9,8 @@ local tableAdd = table.Add local mathRound = math.Round if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/base_event") - EVENT.title = "" + EVENT.icon = Material("vgui/ttt/vskin/events/base_event") + EVENT.title = "" end EVENT.type = "base_event" @@ -26,7 +26,7 @@ EVENT.plys64 = {} -- @internal -- @realm shared function EVENT:SetEventTable(event) - self.event = event + self.event = event end --- @@ -35,7 +35,7 @@ end -- @internal -- @realm shared function EVENT:SetScoreTable(score) - self.score = score + self.score = score end --- @@ -44,7 +44,7 @@ end -- @internal -- @realm shared function EVENT:SetKarmaTable(karma) - self.karma = karma + self.karma = karma end --- @@ -54,8 +54,8 @@ end -- @internal -- @realm shared function EVENT:SetPlayersTable(plys64, plys) - self.plys64 = plys64 - self.plys = plys + self.plys64 = plys64 + self.plys = plys end --- @@ -65,9 +65,11 @@ end -- @param[opt] table score The score data table that should be set -- @realm shared function EVENT:SetPlayerScore(ply64, score) - if not ply64 then return end + if not ply64 then + return + end - self.score[ply64] = score + self.score[ply64] = score end --- @@ -76,9 +78,11 @@ end -- @param[opt] table karma The karma changes data table that should be set -- @realm shared function EVENT:SetPlayerKarma(sid64, karma) - if not sid64 then return end + if not sid64 then + return + end - self.karma[sid64] = karma + self.karma[sid64] = karma end --- @@ -87,7 +91,7 @@ end -- @return table The score table for the player -- @realm shared function EVENT:GetPlayerScore(ply64) - return self.score[ply64] + return self.score[ply64] end --- @@ -98,9 +102,7 @@ end -- @return nil|table The event table in the deprecated format -- @internal -- @realm shared -function EVENT:GetDeprecatedFormat() - -end +function EVENT:GetDeprecatedFormat() end --- -- Checks whether the given player has scored in this event or not. @@ -108,7 +110,7 @@ end -- @return boolean Returns true if the player has a score table, they could still have received 0 points -- @realm shared function EVENT:HasPlayerScore(ply64) - return self.score[ply64] ~= nil + return self.score[ply64] ~= nil end --- @@ -117,7 +119,7 @@ end -- @return boolean Returns true if the player has a karma change table -- @realm shared function EVENT:HasPlayerKarma(sid64) - return self.karma[sid64] ~= nil + return self.karma[sid64] ~= nil end --- @@ -125,7 +127,7 @@ end -- @return table The score table, indexed with sid64 -- @realm shared function EVENT:GetScore() - return self.score + return self.score end --- @@ -133,7 +135,7 @@ end -- @return table The karma table, indexed with sid64 -- @realm shared function EVENT:GetKarma() - return self.karma + return self.karma end --- @@ -141,7 +143,7 @@ end -- @return boolean Returns true if there is score added in this event -- @realm server function EVENT:HasScore() - return tableCount(self.score) + return tableCount(self.score) end --- @@ -149,7 +151,7 @@ end -- @return boolean Returns true if there is score added in this event -- @realm server function EVENT:HasKarma() - return not tableEmpty(self.karma) + return not tableEmpty(self.karma) end --- @@ -157,7 +159,7 @@ end -- @return table A table of all the steamID64s -- @realm shared function EVENT:GetScoredPlayers() - return tableGetKeys(self.score) + return tableGetKeys(self.score) end --- @@ -167,17 +169,17 @@ end -- @return[default=0] number The amount of score gained by this player in this event -- @realm shared function EVENT:GetSummedPlayerScore(ply64) - if not self:HasPlayerScore(ply64) then - return 0 - end + if not self:HasPlayerScore(ply64) then + return 0 + end - local scoreSum = 0 + local scoreSum = 0 - for _, score in pairs(self.score[ply64]) do - scoreSum = scoreSum + score - end + for _, score in pairs(self.score[ply64]) do + scoreSum = scoreSum + score + end - return scoreSum + return scoreSum end --- @@ -187,18 +189,20 @@ end -- @return table A table with the scoring text -- @realm shared function EVENT:GetRawScoreText(ply64) - local rawTable = {} + local rawTable = {} - for name, score in pairs(self.score[ply64]) do - if score == 0 then continue end + for name, score in pairs(self.score[ply64]) do + if score == 0 then + continue + end - rawTable[#rawTable + 1] = { - name = self.type .. "_" .. name, - score = score - } - end + rawTable[#rawTable + 1] = { + name = self.type .. "_" .. name, + score = score, + } + end - return rawTable + return rawTable end --- @@ -206,7 +210,7 @@ end -- @return table A table of steamID64s -- @realm shared function EVENT:GetAffectedPlayer64s() - return self.plys64 + return self.plys64 end --- @@ -214,7 +218,7 @@ end -- @return table A table of player names -- @realm shared function EVENT:GetAffectedPlayers() - return self.plys + return self.plys end --- @@ -223,11 +227,11 @@ end -- @return nil|string The player's nick name -- @realm shared function EVENT:GetNameFrom64(ply64) - for i = 1, #self.plys64 do - if self.plys64[i] == ply64 then - return self.plys[i] - end - end + for i = 1, #self.plys64 do + if self.plys64[i] == ply64 then + return self.plys[i] + end + end end --- @@ -236,7 +240,7 @@ end -- @return boolean Returns true if the player was affected by this event. -- @realm shared function EVENT:HasAffectedPlayer(ply64) - return tableHasValue(self.plys64, ply64) + return tableHasValue(self.plys64, ply64) end --- @@ -245,134 +249,129 @@ end -- @return nil|string The serialized string -- @internal -- @realm shared -function EVENT:Serialize() - -end +function EVENT:Serialize() end --- -- Returns the events time in seconds after round begin -- @return number The event time -- @realm shared function EVENT:GetTime() - return mathRound(self.event.time / 1000, 0) + return mathRound(self.event.time / 1000, 0) end if CLIENT then - --- - -- Generates the textparameters needed for the event timeline - -- @note This function should be overwritten but not be called. - -- @return table A table of identifier-param pairs - -- @realm client - function EVENT:GetText() - return {} - end + --- + -- Generates the textparameters needed for the event timeline + -- @note This function should be overwritten but not be called. + -- @return table A table of identifier-param pairs + -- @realm client + function EVENT:GetText() + return {} + end end if SERVER then - --- - -- Adds players that are affected by this event. - -- @param table plys64 A table of player steamID64 - -- @param table plys A table of player namees - -- @realm server - function EVENT:AddAffectedPlayers(plys64, plys) - tableAdd(self.plys64, plys64) - tableAdd(self.plys, plys) - end - - --- - -- Adds the event data table to an event. Also adds some generic data as well. - -- Inside this function the hook @{GM:TTT2OnTriggeredEvent} is called to make - -- sure this event should really be added. - -- @param table event The event data table that is about to be added - -- @return boolean Return true if addition was successful, false if not - -- @internal - -- @realm server - function EVENT:Add(event) - -- store the event time in relation to the round start time in milliseconds - event.time = math.Round((CurTime() - GAMEMODE.RoundStartTime) * 1000, 0) - event.roundState = GetRoundState() - - --- - -- Call hook that a new event is about to be added, can be canceled or - -- modified from that hook - -- @realm server - if hook.Run("TTT2OnTriggeredEvent", self.type, event) == false then - return false - end - - self:SetEventTable(event) - - -- after the event is added, it should be passed on to the - -- scoring function to directly calculate the score - self:CalculateScore() - - -- Synchronize Karma Changes - if self:ShouldKarmaChangeSynchronize() then - self:SynchronizeKarmaChanges() - end - - return true - end - - --- - -- This function generates a table with all the data that should be networked. - -- You probably don't want to overwrite it. - -- @return table A table of the data that is networked - -- @internal - -- @realm server - function EVENT:GetNetworkedData() - return { - type = self.type, - event = self.event, - score = self.score, - karma = self.karma, - plys64 = self.plys64, - plys = self.plys - } - end - - --- - -- The main function of an event. It contains all the event handling. - -- @note This function should be overwritten but not not called. - -- @param any ... A variable amount of arguments passed to this event - -- @internal - -- @realm server - function EVENT:Trigger(...) - - end - - --- - -- This function calculates the score gained for this event. It should be - -- overwritten if the event should yield a score. - -- @note This function should be overwritten but not not called. - -- @note The event table can be accessed via `self.event`. - -- @internal - -- @realm server - function EVENT:CalculateScore() - - end - - --- - -- Return true if Karma should be synchronized - -- @note This function should be overwritten but not called. - -- @note The event table can be accessed via `self.event`. - -- @internal - -- @realm server - function EVENT:ShouldKarmaChangeSynchronize() - return false - end - - --- - -- This function puts KarmaChanges into event-data - -- @note The event table can be accessed via `self.event`. - -- @internal - -- @realm server - function EVENT:SynchronizeKarmaChanges() - local plys = self.event.plys - - for i = 1, #plys do - local sid64 = plys[i].sid64 - self:SetPlayerKarma(sid64, KARMA.GetKarmaChangesBySteamID64(sid64)) - end - end + --- + -- Adds players that are affected by this event. + -- @param table plys64 A table of player steamID64 + -- @param table plys A table of player namees + -- @realm server + function EVENT:AddAffectedPlayers(plys64, plys) + tableAdd(self.plys64, plys64) + tableAdd(self.plys, plys) + end + + --- + -- Adds the event data table to an event. Also adds some generic data as well. + -- Inside this function the hook @{GM:TTT2OnTriggeredEvent} is called to make + -- sure this event should really be added. + -- @param table event The event data table that is about to be added + -- @return boolean Return true if addition was successful, false if not + -- @internal + -- @realm server + function EVENT:Add(event) + -- store the event time in relation to the round start time in milliseconds + event.time = math.Round((CurTime() - GAMEMODE.RoundStartTime) * 1000, 0) + event.roundState = GetRoundState() + + --- + -- Call hook that a new event is about to be added, can be canceled or + -- modified from that hook + -- @realm server + -- stylua: ignore + if hook.Run("TTT2OnTriggeredEvent", self.type, event) == false then + return false + end + + self:SetEventTable(event) + + -- after the event is added, it should be passed on to the + -- scoring function to directly calculate the score + self:CalculateScore() + + -- Synchronize Karma Changes + if self:ShouldKarmaChangeSynchronize() then + self:SynchronizeKarmaChanges() + end + + return true + end + + --- + -- This function generates a table with all the data that should be networked. + -- You probably don't want to overwrite it. + -- @return table A table of the data that is networked + -- @internal + -- @realm server + function EVENT:GetNetworkedData() + return { + type = self.type, + event = self.event, + score = self.score, + karma = self.karma, + plys64 = self.plys64, + plys = self.plys, + } + end + + --- + -- The main function of an event. It contains all the event handling. + -- @note This function should be overwritten but not not called. + -- @param any ... A variable amount of arguments passed to this event + -- @internal + -- @realm server + function EVENT:Trigger(...) end + + --- + -- This function calculates the score gained for this event. It should be + -- overwritten if the event should yield a score. + -- @note This function should be overwritten but not not called. + -- @note The event table can be accessed via `self.event`. + -- @internal + -- @realm server + function EVENT:CalculateScore() end + + --- + -- Return true if Karma should be synchronized + -- @note This function should be overwritten but not called. + -- @note The event table can be accessed via `self.event`. + -- @internal + -- @realm server + function EVENT:ShouldKarmaChangeSynchronize() + return false + end + + --- + -- This function puts KarmaChanges into event-data + -- @note The event table can be accessed via `self.event`. + -- @internal + -- @realm server + function EVENT:SynchronizeKarmaChanges() + local plys = self.event.plys + + for i = 1, #plys do + local sid64 = plys[i].sid64 + self:SetPlayerKarma(sid64, KARMA.GetKarmaChangesBySteamID64(sid64)) + end + end end diff --git a/lua/terrortown/events/bodyfound.lua b/lua/terrortown/events/bodyfound.lua index 49aa8312e..79d7d4f48 100644 --- a/lua/terrortown/events/bodyfound.lua +++ b/lua/terrortown/events/bodyfound.lua @@ -1,91 +1,96 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/bodyfound") - EVENT.title = "title_event_bodyfound" + EVENT.icon = Material("vgui/ttt/vskin/events/bodyfound") + EVENT.title = "title_event_bodyfound" - function EVENT:GetText() - local text = { - { - string = "desc_event_bodyfound", - params = { - finder = self.event.finder.nick, - found = self.event.found.nick, - firole = roles.GetByIndex(self.event.finder.role).name, - fiteam = self.event.finder.team, - forole = roles.GetByIndex(self.event.found.role).name, - foteam = self.event.found.team, - credits = self.event.found.credits - }, - translateParams = true - } - } + function EVENT:GetText() + local text = { + { + string = "desc_event_bodyfound", + params = { + finder = self.event.finder.nick, + found = self.event.found.nick, + firole = roles.GetByIndex(self.event.finder.role).name, + fiteam = self.event.finder.team, + forole = roles.GetByIndex(self.event.found.role).name, + foteam = self.event.found.team, + credits = self.event.found.credits, + }, + translateParams = true, + }, + } - if self.event.found.headshot then - text[2] = { - string = "desc_event_bodyfound_headshot" - } - end + if self.event.found.headshot then + text[2] = { + string = "desc_event_bodyfound_headshot", + } + end - return text - end + return text + end end if SERVER then - function EVENT:Trigger(finder, rag) - self:AddAffectedPlayers( - {finder:SteamID64(), CORPSE.GetPlayerSID64(rag)}, - {finder:Nick(), CORPSE.GetPlayerNick(rag, "A Terrorist")} - ) + function EVENT:Trigger(finder, rag) + self:AddAffectedPlayers( + { finder:SteamID64(), CORPSE.GetPlayerSID64(rag) }, + { finder:Nick(), CORPSE.GetPlayerNick(rag, "A Terrorist") } + ) - return self:Add({ - finder = { - nick = finder:Nick(), - sid64 = finder:SteamID64(), - role = finder:GetSubRole(), - team = finder:GetTeam() - }, - found = { - nick = CORPSE.GetPlayerNick(rag, "A Terrorist"), - sid64 = CORPSE.GetPlayerSID64(rag), - role = CORPSE.GetPlayerRole(rag), - team = CORPSE.GetPlayerTeam(rag), - credits = CORPSE.GetCredits(rag, 0), - headshot = CORPSE.WasHeadshot(rag) or false, - time = math.Round((CORPSE.GetPlayerDeathTime(rag) - GAMEMODE.RoundStartTime) * 1000, 0) - } - }) - end + return self:Add({ + finder = { + nick = finder:Nick(), + sid64 = finder:SteamID64(), + role = finder:GetSubRole(), + team = finder:GetTeam(), + }, + found = { + nick = CORPSE.GetPlayerNick(rag, "A Terrorist"), + sid64 = CORPSE.GetPlayerSID64(rag), + role = CORPSE.GetPlayerRole(rag), + team = CORPSE.GetPlayerTeam(rag), + credits = CORPSE.GetCredits(rag, 0), + headshot = CORPSE.WasHeadshot(rag) or false, + time = math.Round( + (CORPSE.GetPlayerDeathTime(rag) - GAMEMODE.RoundStartTime) * 1000, + 0 + ), + }, + }) + end - function EVENT:CalculateScore() - local event = self.event - local finder = event.finder + function EVENT:CalculateScore() + local event = self.event + local finder = event.finder - self:SetPlayerScore(finder.sid64, { - score = roles.GetByIndex(finder.role).score.bodyFoundMuliplier - }) - end + self:SetPlayerScore(finder.sid64, { + score = roles.GetByIndex(finder.role).score.bodyFoundMuliplier, + }) + end end function EVENT:Serialize() - return self.event.finder.nick .. " has found the body of " .. self.event.found.nick .. "." + return self.event.finder.nick .. " has found the body of " .. self.event.found.nick .. "." end function EVENT:GetDeprecatedFormat() - local event = self.event + local event = self.event - if event.roundState ~= ROUND_ACTIVE then return end + if event.roundState ~= ROUND_ACTIVE then + return + end - local finder = event.finder - local found = event.found + local finder = event.finder + local found = event.found - return { - id = self.type, - t = event.time / 1000, - ni = finder.nick, - sid64 = finder.sid64, - r = finder.role, - tm = finder.team, - b = found.nick - } + return { + id = self.type, + t = event.time / 1000, + ni = finder.nick, + sid64 = finder.sid64, + r = finder.role, + tm = finder.team, + b = found.nick, + } end diff --git a/lua/terrortown/events/c4disarm.lua b/lua/terrortown/events/c4disarm.lua index f310f5d86..c57d109db 100644 --- a/lua/terrortown/events/c4disarm.lua +++ b/lua/terrortown/events/c4disarm.lua @@ -1,87 +1,95 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/c4disarm") - EVENT.title = "title_event_c4_disarm" + EVENT.icon = Material("vgui/ttt/vskin/events/c4disarm") + EVENT.title = "title_event_c4_disarm" - function EVENT:GetText() - if self.event.successful then - return { - { - string = "desc_event_c4_disarm_success", - params = { - owner = self.event.owner.nick, - disarmer = self.event.disarmer.nick, - orole = roles.GetByIndex(self.event.owner.role).name, - oteam = self.event.owner.team, - drole = roles.GetByIndex(self.event.disarmer.role).name, - dteam = self.event.disarmer.team - }, - translateParams = true - } - } - else - return { - { - string = "desc_event_c4_disarm_failed", - params = { - owner = self.event.owner.nick, - disarmer = self.event.disarmer.nick, - orole = roles.GetByIndex(self.event.owner.role), - oteam = self.event.owner.team, - drole = roles.GetByIndex(self.event.disarmer.role), - dteam = self.event.disarmer.team - }, - translateParams = true - } - } - end - end + function EVENT:GetText() + if self.event.successful then + return { + { + string = "desc_event_c4_disarm_success", + params = { + owner = self.event.owner.nick, + disarmer = self.event.disarmer.nick, + orole = roles.GetByIndex(self.event.owner.role).name, + oteam = self.event.owner.team, + drole = roles.GetByIndex(self.event.disarmer.role).name, + dteam = self.event.disarmer.team, + }, + translateParams = true, + }, + } + else + return { + { + string = "desc_event_c4_disarm_failed", + params = { + owner = self.event.owner.nick, + disarmer = self.event.disarmer.nick, + orole = roles.GetByIndex(self.event.owner.role), + oteam = self.event.owner.team, + drole = roles.GetByIndex(self.event.disarmer.role), + dteam = self.event.disarmer.team, + }, + translateParams = true, + }, + } + end + end end if SERVER then - function EVENT:Trigger(owner, disarmer, successful) - self:AddAffectedPlayers( - {owner:SteamID64(), disarmer:SteamID64()}, - {owner:Nick(), disarmer:Nick()} - ) + function EVENT:Trigger(owner, disarmer, successful) + self:AddAffectedPlayers( + { owner:SteamID64(), disarmer:SteamID64() }, + { owner:Nick(), disarmer:Nick() } + ) - return self:Add({ - successful = successful, - owner = { - nick = owner:Nick(), - sid64 = owner:SteamID64(), - role = owner:GetSubRole(), - team = owner:GetTeam() - }, - disarmer = { - nick = disarmer:Nick(), - sid64 = disarmer:SteamID64(), - role = disarmer:GetSubRole(), - team = disarmer:GetTeam() - } - }) - end + return self:Add({ + successful = successful, + owner = { + nick = owner:Nick(), + sid64 = owner:SteamID64(), + role = owner:GetSubRole(), + team = owner:GetTeam(), + }, + disarmer = { + nick = disarmer:Nick(), + sid64 = disarmer:SteamID64(), + role = disarmer:GetSubRole(), + team = disarmer:GetTeam(), + }, + }) + end end function EVENT:Serialize() - if self.event.successful then - return self.event.disarmer.nick .. " has successfully disarmed the C4 charge placed by " .. self.event.owner.nick .. "." - else - return self.event.disarmer.nick .. " tried to disarm the C4 charge placed by " .. self.event.owner.nick .. ". They failed." - end + if self.event.successful then + return self.event.disarmer.nick + .. " has successfully disarmed the C4 charge placed by " + .. self.event.owner.nick + .. "." + else + return self.event.disarmer.nick + .. " tried to disarm the C4 charge placed by " + .. self.event.owner.nick + .. ". They failed." + end end function EVENT:GetDeprecatedFormat() - local event = self.event + local event = self.event - if event.roundState ~= ROUND_ACTIVE then return end + if event.roundState ~= ROUND_ACTIVE then + return + end - return { - id = self.type, - t = event.time / 1000, - ni = event.disarmer.nick, - own = event.owner.nick, - s = event.successful - } + return { + id = self.type, + t = event.time / 1000, + ni = event.disarmer.nick, + own = event.owner.nick, + s = event.successful, + } end diff --git a/lua/terrortown/events/c4explode.lua b/lua/terrortown/events/c4explode.lua index 7cfa420b6..da47adf97 100644 --- a/lua/terrortown/events/c4explode.lua +++ b/lua/terrortown/events/c4explode.lua @@ -1,52 +1,51 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/c4explode") - EVENT.title = "title_event_c4_explode" - - function EVENT:GetText() - return { - { - string = "desc_event_c4_explode", - params = { - owner = self.event.nick, - role = roles.GetByIndex(self.event.role).name, - team = self.event.team - }, - translateParams = true - } - } - end + EVENT.icon = Material("vgui/ttt/vskin/events/c4explode") + EVENT.title = "title_event_c4_explode" + + function EVENT:GetText() + return { + { + string = "desc_event_c4_explode", + params = { + owner = self.event.nick, + role = roles.GetByIndex(self.event.role).name, + team = self.event.team, + }, + translateParams = true, + }, + } + end end if SERVER then - function EVENT:Trigger(owner) - self:AddAffectedPlayers( - {owner:SteamID64()}, - {owner:Nick()} - ) - - return self:Add({ - nick = owner:Nick(), - sid64 = owner:SteamID64(), - role = owner:GetSubRole(), - team = owner:GetTeam() - }) - end + function EVENT:Trigger(owner) + self:AddAffectedPlayers({ owner:SteamID64() }, { owner:Nick() }) + + return self:Add({ + nick = owner:Nick(), + sid64 = owner:SteamID64(), + role = owner:GetSubRole(), + team = owner:GetTeam(), + }) + end end function EVENT:Serialize() - return "The C4 charge placed by " .. self.event.nick .. " exploded." + return "The C4 charge placed by " .. self.event.nick .. " exploded." end function EVENT:GetDeprecatedFormat() - local event = self.event + local event = self.event - if event.roundState ~= ROUND_ACTIVE then return end + if event.roundState ~= ROUND_ACTIVE then + return + end - return { - id = self.type, - t = event.time / 1000, - ni = event.nick, - } + return { + id = self.type, + t = event.time / 1000, + ni = event.nick, + } end diff --git a/lua/terrortown/events/c4plant.lua b/lua/terrortown/events/c4plant.lua index 70c9f0bed..efb95c023 100644 --- a/lua/terrortown/events/c4plant.lua +++ b/lua/terrortown/events/c4plant.lua @@ -1,52 +1,51 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/c4plant") - EVENT.title = "title_event_c4_plant" - - function EVENT:GetText() - return { - { - string = "desc_event_c4_plant", - params = { - owner = self.event.nick, - role = roles.GetByIndex(self.event.role).name, - team = self.event.team - }, - translateParams = true - } - } - end + EVENT.icon = Material("vgui/ttt/vskin/events/c4plant") + EVENT.title = "title_event_c4_plant" + + function EVENT:GetText() + return { + { + string = "desc_event_c4_plant", + params = { + owner = self.event.nick, + role = roles.GetByIndex(self.event.role).name, + team = self.event.team, + }, + translateParams = true, + }, + } + end end if SERVER then - function EVENT:Trigger(planter) - self:AddAffectedPlayers( - {planter:SteamID64()}, - {planter:Nick()} - ) - - return self:Add({ - nick = planter:Nick(), - sid64 = planter:SteamID64(), - role = planter:GetSubRole(), - team = planter:GetTeam() - }) - end + function EVENT:Trigger(planter) + self:AddAffectedPlayers({ planter:SteamID64() }, { planter:Nick() }) + + return self:Add({ + nick = planter:Nick(), + sid64 = planter:SteamID64(), + role = planter:GetSubRole(), + team = planter:GetTeam(), + }) + end end function EVENT:Serialize() - return self.event.nick .. " placed a new C4 charge." + return self.event.nick .. " placed a new C4 charge." end function EVENT:GetDeprecatedFormat() - local event = self.event + local event = self.event - if event.roundState ~= ROUND_ACTIVE then return end + if event.roundState ~= ROUND_ACTIVE then + return + end - return { - id = self.type, - t = event.time / 1000, - ni = event.nick, - } + return { + id = self.type, + t = event.time / 1000, + ni = event.nick, + } end diff --git a/lua/terrortown/events/creditfound.lua b/lua/terrortown/events/creditfound.lua index 680d9c7be..7d4460fda 100644 --- a/lua/terrortown/events/creditfound.lua +++ b/lua/terrortown/events/creditfound.lua @@ -1,68 +1,75 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/creditfound") - EVENT.title = "title_event_creditfound" + EVENT.icon = Material("vgui/ttt/vskin/events/creditfound") + EVENT.title = "title_event_creditfound" - function EVENT:GetText() - return { - { - string = "desc_event_creditfound", - params = { - finder = self.event.finder.nick, - found = self.event.found.nick, - firole = roles.GetByIndex(self.event.finder.role).name, - fiteam = self.event.finder.team, - forole = roles.GetByIndex(self.event.found.role).name, - foteam = self.event.found.team, - credits = self.event.found.credits - }, - translateParams = true - } - } - end + function EVENT:GetText() + return { + { + string = "desc_event_creditfound", + params = { + finder = self.event.finder.nick, + found = self.event.found.nick, + firole = roles.GetByIndex(self.event.finder.role).name, + fiteam = self.event.finder.team, + forole = roles.GetByIndex(self.event.found.role).name, + foteam = self.event.found.team, + credits = self.event.found.credits, + }, + translateParams = true, + }, + } + end end if SERVER then - function EVENT:Trigger(finder, rag, credits) - self:AddAffectedPlayers( - {finder:SteamID64(), CORPSE.GetPlayerSID64(rag)}, - {finder:Nick(), CORPSE.GetPlayerNick(rag, "A Terrorist")} - ) + function EVENT:Trigger(finder, rag, credits) + self:AddAffectedPlayers( + { finder:SteamID64(), CORPSE.GetPlayerSID64(rag) }, + { finder:Nick(), CORPSE.GetPlayerNick(rag, "A Terrorist") } + ) - return self:Add({ - finder = { - nick = finder:Nick(), - sid64 = finder:SteamID64(), - role = finder:GetSubRole(), - team = finder:GetTeam() - }, - found = { - nick = CORPSE.GetPlayerNick(rag, "A Terrorist"), - sid64 = CORPSE.GetPlayerSID64(rag), - role = CORPSE.GetPlayerRole(rag), - team = CORPSE.GetPlayerTeam(rag), - credits = credits - } - }) - end + return self:Add({ + finder = { + nick = finder:Nick(), + sid64 = finder:SteamID64(), + role = finder:GetSubRole(), + team = finder:GetTeam(), + }, + found = { + nick = CORPSE.GetPlayerNick(rag, "A Terrorist"), + sid64 = CORPSE.GetPlayerSID64(rag), + role = CORPSE.GetPlayerRole(rag), + team = CORPSE.GetPlayerTeam(rag), + credits = credits, + }, + }) + end end function EVENT:Serialize() - return self.event.finder.nick .. " has found " .. tostring(self.event.found.credits) .. " in the body of " .. self.event.found.nick .. "." + return self.event.finder.nick + .. " has found " + .. tostring(self.event.found.credits) + .. " in the body of " + .. self.event.found.nick + .. "." end function EVENT:GetDeprecatedFormat() - local event = self.event + local event = self.event - if event.roundState ~= ROUND_ACTIVE then return end + if event.roundState ~= ROUND_ACTIVE then + return + end - return { - id = self.type, - t = event.time / 1000, - ni = event.finder.nick, - sid64 = event.finder.sid64, - b = event.found.nick, - cr = event.found.credits - } + return { + id = self.type, + t = event.time / 1000, + ni = event.finder.nick, + sid64 = event.finder.sid64, + b = event.found.nick, + cr = event.found.credits, + } end diff --git a/lua/terrortown/events/finish.lua b/lua/terrortown/events/finish.lua index 3462e1a43..2cb8bb09f 100644 --- a/lua/terrortown/events/finish.lua +++ b/lua/terrortown/events/finish.lua @@ -1,138 +1,149 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/finish") - EVENT.title = "title_event_finish" - - function EVENT:GetText() - local alive = 0 - - for i = 1, #self.event.plys do - if self.event.plys[i].alive then - alive = alive + 1 - end - end - - local time = self:GetTime() - local minutes = math.floor(time / 60) - local seconds = string.format("%02d", math.floor(time % 60)) - - return { - { - string = "desc_event_finish", - params = { - alive = alive, - minutes = minutes, - seconds = seconds - } - } - } - end + EVENT.icon = Material("vgui/ttt/vskin/events/finish") + EVENT.title = "title_event_finish" + + function EVENT:GetText() + local alive = 0 + + for i = 1, #self.event.plys do + if self.event.plys[i].alive then + alive = alive + 1 + end + end + + local time = self:GetTime() + local minutes = math.floor(time / 60) + local seconds = string.format("%02d", math.floor(time % 60)) + + return { + { + string = "desc_event_finish", + params = { + alive = alive, + minutes = minutes, + seconds = seconds, + }, + }, + } + end end if SERVER then - function EVENT:Trigger(wintype) - local plys = player.GetAll() - local eventPlys = {} - - for i = 1, #plys do - local ply = plys[i] - - eventPlys[#eventPlys + 1] = { - nick = ply:Nick(), - sid64 = ply:SteamID64(), - team = ply:GetTeam(), - role = ply:GetSubRole(), - alive = ply:Alive() and ply:IsTerror() - } - - self:AddAffectedPlayers( - {ply:SteamID64()}, - {ply:Nick()} - ) - end - - return self:Add({ - wintype = wintype, - plys = eventPlys - }) - end + function EVENT:Trigger(wintype) + local plys = player.GetAll() + local eventPlys = {} + + for i = 1, #plys do + local ply = plys[i] + + eventPlys[#eventPlys + 1] = { + nick = ply:Nick(), + sid64 = ply:SteamID64(), + team = ply:GetTeam(), + role = ply:GetSubRole(), + alive = ply:Alive() and ply:IsTerror(), + } + + self:AddAffectedPlayers({ ply:SteamID64() }, { ply:Nick() }) + end + + return self:Add({ + wintype = wintype, + plys = eventPlys, + }) + end end function EVENT:CalculateScore() - local event = self.event - local plys = event.plys - local wintype = event.wintype - local alive = {} - local dead = {} - local aliveAll = 0 - - -- Check who is alive and who is dead on a teambased approach - for i = 1, #plys do - local ply = plys[i] - local state = ply.alive and alive or dead - local team = ply.team - - if ply.alive then - aliveAll = aliveAll + 1 - end - - if team ~= TEAM_NONE then - state[team] = (state[team] or 0) + 1 - end - end - - -- In a second pass, calculate the score based on the players that - -- are still alive. The more of their team have survived, the greater - -- their bonus. Additionally many dead players from a different team - -- can grant extra points. - for i = 1, #plys do - local ply = plys[i] - local team = ply.team - local roleData = roles.GetByIndex(ply.role) - local otherDeadPlayers = 0 - local otherAlivePlayers = 0 - - -- Count dead players that are in a different team - for otherTeam, amount in pairs(dead) do - if team ~= TEAM_NONE and team == otherTeam and not TEAMS[team].alone then continue end - - otherDeadPlayers = otherDeadPlayers + amount - end - - -- Count alive players that are in a different team - for otherTeam, amount in pairs(alive) do - if team ~= TEAM_NONE and team == otherTeam and not TEAMS[team].alone then continue end - - otherAlivePlayers = otherAlivePlayers + amount - end - - self:SetPlayerScore(ply.sid64, { - score_alive_teammates = wintype == team and ((alive[team] or 0) * roleData.score.aliveTeammatesBonusMultiplier) or 0, - score_alive_all = aliveAll * roleData.score.allSurviveBonusMultiplier, - score_dead_enemies = wintype == team and math.ceil(otherDeadPlayers * roleData.score.surviveBonusMultiplier) or 0, - score_penalty_alive_teammates = (wintype ~= team and ply.alive) and ((alive[team] or 0) * roleData.score.survivePenaltyMultiplier) or 0, - score_timelimit = wintype == WIN_TIMELIMIT and math.ceil(otherAlivePlayers * roleData.score.timelimitMultiplier) or 0 - }) - end + local event = self.event + local plys = event.plys + local wintype = event.wintype + local alive = {} + local dead = {} + local aliveAll = 0 + + -- Check who is alive and who is dead on a teambased approach + for i = 1, #plys do + local ply = plys[i] + local state = ply.alive and alive or dead + local team = ply.team + + if ply.alive then + aliveAll = aliveAll + 1 + end + + if team ~= TEAM_NONE then + state[team] = (state[team] or 0) + 1 + end + end + + -- In a second pass, calculate the score based on the players that + -- are still alive. The more of their team have survived, the greater + -- their bonus. Additionally many dead players from a different team + -- can grant extra points. + for i = 1, #plys do + local ply = plys[i] + local team = ply.team + local roleData = roles.GetByIndex(ply.role) + local otherDeadPlayers = 0 + local otherAlivePlayers = 0 + + -- Count dead players that are in a different team + for otherTeam, amount in pairs(dead) do + if team ~= TEAM_NONE and team == otherTeam and not TEAMS[team].alone then + continue + end + + otherDeadPlayers = otherDeadPlayers + amount + end + + -- Count alive players that are in a different team + for otherTeam, amount in pairs(alive) do + if team ~= TEAM_NONE and team == otherTeam and not TEAMS[team].alone then + continue + end + + otherAlivePlayers = otherAlivePlayers + amount + end + + self:SetPlayerScore(ply.sid64, { + score_alive_teammates = wintype == team + and ((alive[team] or 0) * roleData.score.aliveTeammatesBonusMultiplier) + or 0, + score_alive_all = aliveAll * roleData.score.allSurviveBonusMultiplier, + score_dead_enemies = wintype == team and math.ceil( + otherDeadPlayers * roleData.score.surviveBonusMultiplier + ) or 0, + score_penalty_alive_teammates = (wintype ~= team and ply.alive) + and ((alive[team] or 0) * roleData.score.survivePenaltyMultiplier) + or 0, + score_timelimit = wintype == WIN_TIMELIMIT and math.ceil( + otherAlivePlayers * roleData.score.timelimitMultiplier + ) or 0, + }) + end end function EVENT:ShouldKarmaChangeSynchronize() - return true + return true end function EVENT:Serialize() - return "The round has ended." + return "The round has ended." end function EVENT:GetDeprecatedFormat() - local event = self.event + local event = self.event - if event.roundState ~= ROUND_ACTIVE then return end + if event.roundState ~= ROUND_ACTIVE then + return + end - return { - id = self.type, - t = event.time / 1000, - win = event.wintype - } + return { + id = self.type, + t = event.time / 1000, + win = event.wintype, + } end diff --git a/lua/terrortown/events/game.lua b/lua/terrortown/events/game.lua index e50c018fc..0c5311f45 100644 --- a/lua/terrortown/events/game.lua +++ b/lua/terrortown/events/game.lua @@ -1,38 +1,40 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/game") - EVENT.title = "title_event_game" + EVENT.icon = Material("vgui/ttt/vskin/events/game") + EVENT.title = "title_event_game" - function EVENT:GetText() - return { - { - string = "desc_event_game", - } - } - end + function EVENT:GetText() + return { + { + string = "desc_event_game", + }, + } + end end if SERVER then - function EVENT:Trigger(roundstate) - return self:Add({ - newstate = roundstate - }) - end + function EVENT:Trigger(roundstate) + return self:Add({ + newstate = roundstate, + }) + end end function EVENT:Serialize() - return "A new round has started." + return "A new round has started." end function EVENT:GetDeprecatedFormat() - local event = self.event + local event = self.event - if event.roundState ~= ROUND_ACTIVE then return end + if event.roundState ~= ROUND_ACTIVE then + return + end - return { - id = self.type, - t = event.time / 1000, - state = event.newstate - } + return { + id = self.type, + t = event.time / 1000, + state = event.newstate, + } end diff --git a/lua/terrortown/events/kill.lua b/lua/terrortown/events/kill.lua index 29cdf47d8..4f95cb650 100644 --- a/lua/terrortown/events/kill.lua +++ b/lua/terrortown/events/kill.lua @@ -1,294 +1,300 @@ --- @ignore local function is_dmg(dmg_t, bit) - -- deal with large-number workaround for TableToJSON by - -- parsing back to number here - return util.BitSet(tonumber(dmg_t), bit) + -- deal with large-number workaround for TableToJSON by + -- parsing back to number here + return util.BitSet(tonumber(dmg_t), bit) end --- -- Helper fn for kill events local function GetWeaponName(gun) - local wname + local wname - -- Standard TTT weapons are sent as numeric IDs to save bandwidth - if tonumber(gun) then - wname = EnumToWep(gun) - elseif isstring(gun) then - -- Custom weapons or ones that are otherwise ID-less are sent as - -- string - local wep = util.WeaponForClass(gun) + -- Standard TTT weapons are sent as numeric IDs to save bandwidth + if tonumber(gun) then + wname = EnumToWep(gun) + elseif isstring(gun) then + -- Custom weapons or ones that are otherwise ID-less are sent as + -- string + local wep = util.WeaponForClass(gun) - wname = wep and wep.PrintName - end + wname = wep and wep.PrintName + end - return wname + return wname end --- -- Generating the text for a kill event requires a lot of logic for special -- cases, resulting in a long function, so defining it separately herevent. local function KillText(event) - local dmg = event.dmg - local trap = dmg.name - - if trap == "" then - trap = nil - end - - local weapon = GetWeaponName(dmg.weapon) - - -- there is only ever one piece of equipment present in a language string, - -- all the different names like "trap", "tool" and "weapon" are aliases. - local equip = trap or weapon - - local params = { - victim = event.victim.nick, - vrole = roles.GetByIndex(event.victim.role).name, - vteam = event.victim.team, - trap = equip, - tool = equip, - weapon = equip - } - - if event.attacker then - params.attacker = event.attacker.nick - params.arole = roles.GetByIndex(event.attacker.role).name - params.ateam = event.attacker.team - end - - if event.attacker and event.attacker.sid64 == event.victim.sid64 then - if is_dmg(dmg.type, DMG_BLAST) then - return trap and "desc_event_kill_blowup_trap" or "desc_event_kill_blowup", params - elseif is_dmg(dmg.type, DMG_SONIC) then - return "desc_event_kill_tele_self", params - else - return trap and "desc_event_kill_sui_using" or "desc_event_kill_sui", params - end - end - - local txt - - -- we will want to know if the death was caused by a player or not - -- (eg. push vs fall) - local attackerWasPlayer = true - - -- if we are dealing with an accidental trap death for example, we want to - -- use the trap name as "attacker" - if not event.attacker then - attackerWasPlayer = false - - params.attacker = trap or "trap_something" - end - - -- typically the "_using" strings are only for traps - local using = not weapon - - if is_dmg(dmg.type, DMG_FALL) then - if attackerWasPlayer then - txt = "desc_event_kill_fall_pushed" - else - txt = "desc_event_kill_fall" - end - elseif is_dmg(dmg.type, DMG_BULLET) then - txt = "desc_event_kill_shot" - - using = true - elseif is_dmg(dmg.type, DMG_DROWN) then - txt = "desc_event_kill_drown" - elseif is_dmg(dmg.type, DMG_BLAST) then - txt = "desc_event_kill_boom" - elseif is_dmg(dmg.type, DMG_BURN) or is_dmg(dmg.type, DMG_DIRECT) then - txt = "desc_event_kill_burn" - elseif is_dmg(dmg.type, DMG_CLUB) then - txt = "desc_event_kill_club" - elseif is_dmg(dmg.type, DMG_SLASH) then - txt = "desc_event_kill_slash" - elseif is_dmg(dmg.type, DMG_SONIC) then - txt = "desc_event_kill_tele" - elseif is_dmg(dmg.type, DMG_PHYSGUN) then - txt = "desc_event_kill_goomba" - - using = false - elseif is_dmg(dmg.type, DMG_CRUSH) then - txt = "desc_event_kill_crush" - else - txt = "desc_event_kill_other" - end - - if attackerWasPlayer and (trap or weapon) and using then - txt = txt .. "_using" - end - - return txt, params + local dmg = event.dmg + local trap = dmg.name + + if trap == "" then + trap = nil + end + + local weapon = GetWeaponName(dmg.weapon) + + -- there is only ever one piece of equipment present in a language string, + -- all the different names like "trap", "tool" and "weapon" are aliases. + local equip = trap or weapon + + local params = { + victim = event.victim.nick, + vrole = roles.GetByIndex(event.victim.role).name, + vteam = event.victim.team, + trap = equip, + tool = equip, + weapon = equip, + } + + if event.attacker then + params.attacker = event.attacker.nick + params.arole = roles.GetByIndex(event.attacker.role).name + params.ateam = event.attacker.team + end + + if event.attacker and event.attacker.sid64 == event.victim.sid64 then + if is_dmg(dmg.type, DMG_BLAST) then + return trap and "desc_event_kill_blowup_trap" or "desc_event_kill_blowup", params + elseif is_dmg(dmg.type, DMG_SONIC) then + return "desc_event_kill_tele_self", params + else + return trap and "desc_event_kill_sui_using" or "desc_event_kill_sui", params + end + end + + local txt + + -- we will want to know if the death was caused by a player or not + -- (eg. push vs fall) + local attackerWasPlayer = true + + -- if we are dealing with an accidental trap death for example, we want to + -- use the trap name as "attacker" + if not event.attacker then + attackerWasPlayer = false + + params.attacker = trap or "trap_something" + end + + -- typically the "_using" strings are only for traps + local using = not weapon + + if is_dmg(dmg.type, DMG_FALL) then + if attackerWasPlayer then + txt = "desc_event_kill_fall_pushed" + else + txt = "desc_event_kill_fall" + end + elseif is_dmg(dmg.type, DMG_BULLET) then + txt = "desc_event_kill_shot" + + using = true + elseif is_dmg(dmg.type, DMG_DROWN) then + txt = "desc_event_kill_drown" + elseif is_dmg(dmg.type, DMG_BLAST) then + txt = "desc_event_kill_boom" + elseif is_dmg(dmg.type, DMG_BURN) or is_dmg(dmg.type, DMG_DIRECT) then + txt = "desc_event_kill_burn" + elseif is_dmg(dmg.type, DMG_CLUB) then + txt = "desc_event_kill_club" + elseif is_dmg(dmg.type, DMG_SLASH) then + txt = "desc_event_kill_slash" + elseif is_dmg(dmg.type, DMG_SONIC) then + txt = "desc_event_kill_tele" + elseif is_dmg(dmg.type, DMG_PHYSGUN) then + txt = "desc_event_kill_goomba" + + using = false + elseif is_dmg(dmg.type, DMG_CRUSH) then + txt = "desc_event_kill_crush" + else + txt = "desc_event_kill_other" + end + + if attackerWasPlayer and (trap or weapon) and using then + txt = txt .. "_using" + end + + return txt, params end if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/kill") - EVENT.title = "title_event_kill" - - function EVENT:GetText() - local string, params = KillText(self.event) - local text = { - { - string = string, - params = params, - translateParams = true - } - } - - if self.event.type == KILL_SUICIDE then - text[2] = { - string = "desc_event_kill_suicide" - } - elseif self.event.type == KILL_TEAM then - text[2] = { - string = "desc_event_kill_team" - } - end - - return text - end + EVENT.icon = Material("vgui/ttt/vskin/events/kill") + EVENT.title = "title_event_kill" + + function EVENT:GetText() + local string, params = KillText(self.event) + local text = { + { + string = string, + params = params, + translateParams = true, + }, + } + + if self.event.type == KILL_SUICIDE then + text[2] = { + string = "desc_event_kill_suicide", + } + elseif self.event.type == KILL_TEAM then + text[2] = { + string = "desc_event_kill_team", + } + end + + return text + end end if SERVER then - function EVENT:Trigger(victim, attacker, dmgInfo) - if not IsValid(victim) or not victim:IsPlayer() then return end - - self:AddAffectedPlayers( - {victim:SteamID64()}, - {victim:Nick()} - ) - - local event = { - victim = { - nick = victim:Nick(), - sid64 = victim:SteamID64(), - role = victim:GetSubRole(), - team = victim:GetTeam() - }, - dmg = { - headshot = victim.was_headshot, - type = dmgInfo:GetDamageType(), - damage = dmgInfo:GetDamage() - } - } - - if IsValid(attacker) and attacker:IsPlayer() then - event.attacker = { - nick = attacker:Nick(), - health = attacker:Health(), - armor = attacker:GetArmor(), - sid64 = attacker:SteamID64(), - role = attacker:GetSubRole(), - team = attacker:GetTeam() - } - - self:AddAffectedPlayers( - {attacker:SteamID64()}, - {attacker:Nick()} - ) - - -- set death type - if event.attacker.sid64 == event.victim.sid64 then - event.type = KILL_SUICIDE - else - if event.attacker.team ~= TEAM_NONE and event.attacker.team == event.victim.team and not TEAMS[event.attacker.team].alone then - event.type = KILL_TEAM - else - event.type = KILL_NORMAL - end - end - end - - local wep = util.WeaponFromDamage(dmgInfo) - - if wep then - local id = WepToEnum(wep) - - if id then - event.dmg.weapon = id - else - event.dmg.weapon = wep:GetClass() - end - else - -- handle the name of the inflictor if it was caused by a trap - local inflictor = dmgInfo:GetInflictor() - - if IsValid(inflictor) and inflictor.ScoreName then - event.dmg.name = inflictor.ScoreName - end - end - - return self:Add(event) - end - - function EVENT:CalculateScore() - local event = self.event - - -- the event is only counted if it happened while the round was active - if event.roundState ~= ROUND_ACTIVE then return end - - local attacker = event.attacker - local deathType = event.type - - -- if there is no killer, it wasn't a suicide or a kill, therefore - -- no points should be granted to anyone - if not attacker then return end - - -- the score is dependent of the teams/roles - local roleData = roles.GetByIndex(attacker.role) - - if deathType == KILL_SUICIDE then - self:SetPlayerScore(attacker.sid64, { - score_suicide = roleData.score.suicideMultiplier - }) - elseif deathType == KILL_TEAM then - self:SetPlayerScore(attacker.sid64, { - score_team = roleData.score.teamKillsMultiplier - }) - else - self:SetPlayerScore(attacker.sid64, { - score = roleData.score.killsMultiplier - }) - end - end + function EVENT:Trigger(victim, attacker, dmgInfo) + if not IsValid(victim) or not victim:IsPlayer() then + return + end + + self:AddAffectedPlayers({ victim:SteamID64() }, { victim:Nick() }) + + local event = { + victim = { + nick = victim:Nick(), + sid64 = victim:SteamID64(), + role = victim:GetSubRole(), + team = victim:GetTeam(), + }, + dmg = { + headshot = victim.was_headshot, + type = dmgInfo:GetDamageType(), + damage = dmgInfo:GetDamage(), + }, + } + + if IsValid(attacker) and attacker:IsPlayer() then + event.attacker = { + nick = attacker:Nick(), + health = attacker:Health(), + armor = attacker:GetArmor(), + sid64 = attacker:SteamID64(), + role = attacker:GetSubRole(), + team = attacker:GetTeam(), + } + + self:AddAffectedPlayers({ attacker:SteamID64() }, { attacker:Nick() }) + + -- set death type + if event.attacker.sid64 == event.victim.sid64 then + event.type = KILL_SUICIDE + else + if + event.attacker.team ~= TEAM_NONE + and event.attacker.team == event.victim.team + and not TEAMS[event.attacker.team].alone + then + event.type = KILL_TEAM + else + event.type = KILL_NORMAL + end + end + end + + local wep = util.WeaponFromDamage(dmgInfo) + + if wep then + local id = WepToEnum(wep) + + if id then + event.dmg.weapon = id + else + event.dmg.weapon = wep:GetClass() + end + else + -- handle the name of the inflictor if it was caused by a trap + local inflictor = dmgInfo:GetInflictor() + + if IsValid(inflictor) and inflictor.ScoreName then + event.dmg.name = inflictor.ScoreName + end + end + + return self:Add(event) + end + + function EVENT:CalculateScore() + local event = self.event + + -- the event is only counted if it happened while the round was active + if event.roundState ~= ROUND_ACTIVE then + return + end + + local attacker = event.attacker + local deathType = event.type + + -- if there is no killer, it wasn't a suicide or a kill, therefore + -- no points should be granted to anyone + if not attacker then + return + end + + -- the score is dependent of the teams/roles + local roleData = roles.GetByIndex(attacker.role) + + if deathType == KILL_SUICIDE then + self:SetPlayerScore(attacker.sid64, { + score_suicide = roleData.score.suicideMultiplier, + }) + elseif deathType == KILL_TEAM then + self:SetPlayerScore(attacker.sid64, { + score_team = roleData.score.teamKillsMultiplier, + }) + else + self:SetPlayerScore(attacker.sid64, { + score = roleData.score.killsMultiplier, + }) + end + end end function EVENT:Serialize() - return self.event.victim.nick .. " has died." + return self.event.victim.nick .. " has died." end function EVENT:GetDeprecatedFormat() - local event = self.event - - if event.roundState ~= ROUND_ACTIVE then return end - - local attacker = event.attacker - local victim = event.victim - local dmg = event.dmg - - return { - id = self.type, - t = event.time / 1000, - att = { - ni = attacker and attacker.nick or "", - sid64 = attacker and attacker.sid64 or -1, - r = attacker and attacker.role or -1, - t = attacker and attacker.team or "" - }, - vic = { - ni = victim.nick, - sid64 = victim.sid64, - r = victim.role, - t = victim.team - }, - dmg = { - t = dmg.type, - a = dmg.damage, - h = dmg.headshot, - g = dmg.weapon, - n = dmg.name - } - } + local event = self.event + + if event.roundState ~= ROUND_ACTIVE then + return + end + + local attacker = event.attacker + local victim = event.victim + local dmg = event.dmg + + return { + id = self.type, + t = event.time / 1000, + att = { + ni = attacker and attacker.nick or "", + sid64 = attacker and attacker.sid64 or -1, + r = attacker and attacker.role or -1, + t = attacker and attacker.team or "", + }, + vic = { + ni = victim.nick, + sid64 = victim.sid64, + r = victim.role, + t = victim.team, + }, + dmg = { + t = dmg.type, + a = dmg.damage, + h = dmg.headshot, + g = dmg.weapon, + n = dmg.name, + }, + } end diff --git a/lua/terrortown/events/respawn.lua b/lua/terrortown/events/respawn.lua index 9a30ce658..92207cc52 100644 --- a/lua/terrortown/events/respawn.lua +++ b/lua/terrortown/events/respawn.lua @@ -1,35 +1,32 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/respawn") - EVENT.title = "title_event_respawn" + EVENT.icon = Material("vgui/ttt/vskin/events/respawn") + EVENT.title = "title_event_respawn" - function EVENT:GetText() - return { - { - string = "desc_event_respawn", - params = { - player = self.event.nick - } - } - } - end + function EVENT:GetText() + return { + { + string = "desc_event_respawn", + params = { + player = self.event.nick, + }, + }, + } + end end if SERVER then - function EVENT:Trigger(ply) - self:AddAffectedPlayers( - {ply:SteamID64()}, - {ply:Nick()} - ) + function EVENT:Trigger(ply) + self:AddAffectedPlayers({ ply:SteamID64() }, { ply:Nick() }) - return self:Add({ - nick = ply:Nick(), - sid64 = ply:SteamID64() - }) - end + return self:Add({ + nick = ply:Nick(), + sid64 = ply:SteamID64(), + }) + end end function EVENT:Serialize() - return self.event.nick .. " just respawned." + return self.event.nick .. " just respawned." end diff --git a/lua/terrortown/events/rolechange.lua b/lua/terrortown/events/rolechange.lua index f2a2d5140..76d6b42ab 100644 --- a/lua/terrortown/events/rolechange.lua +++ b/lua/terrortown/events/rolechange.lua @@ -1,47 +1,46 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/rolechange") - EVENT.title = "title_event_rolechange" + EVENT.icon = Material("vgui/ttt/vskin/events/rolechange") + EVENT.title = "title_event_rolechange" - function EVENT:GetText() - return { - { - string = "desc_event_rolechange", - params = { - player = self.event.nick, - orole = roles.GetByIndex(self.event.oldRole).name, - oteam = self.event.oldTeam, - nrole = roles.GetByIndex(self.event.newRole).name, - nteam = self.event.newTeam - }, - translateParams = true - } - } - end + function EVENT:GetText() + return { + { + string = "desc_event_rolechange", + params = { + player = self.event.nick, + orole = roles.GetByIndex(self.event.oldRole).name, + oteam = self.event.oldTeam, + nrole = roles.GetByIndex(self.event.newRole).name, + nteam = self.event.newTeam, + }, + translateParams = true, + }, + } + end end if SERVER then - function EVENT:Trigger(ply, oldRole, newRole, oldTeam, newTeam) - -- do not trigger events if the role or team is nil - if not oldRole or not newRole or not oldTeam or not newTeam then return end + function EVENT:Trigger(ply, oldRole, newRole, oldTeam, newTeam) + -- do not trigger events if the role or team is nil + if not oldRole or not newRole or not oldTeam or not newTeam then + return + end - self:AddAffectedPlayers( - {ply:SteamID64()}, - {ply:Nick()} - ) + self:AddAffectedPlayers({ ply:SteamID64() }, { ply:Nick() }) - return self:Add({ - nick = ply:Nick(), - sid64 = ply:SteamID64(), - oldRole = oldRole, - newRole = newRole, - oldTeam = oldTeam, - newTeam = newTeam - }) - end + return self:Add({ + nick = ply:Nick(), + sid64 = ply:SteamID64(), + oldRole = oldRole, + newRole = newRole, + oldTeam = oldTeam, + newTeam = newTeam, + }) + end end function EVENT:Serialize() - return self.event.nick .. " just changed their tole." + return self.event.nick .. " just changed their tole." end diff --git a/lua/terrortown/events/selected.lua b/lua/terrortown/events/selected.lua index a8eb54100..b4785948f 100644 --- a/lua/terrortown/events/selected.lua +++ b/lua/terrortown/events/selected.lua @@ -1,79 +1,78 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/selected") - EVENT.title = "title_event_selected" - - function EVENT:GetText() - return { - { - string = "desc_event_selected", - params = { - amount = #self.event.plys - } - } - } - end + EVENT.icon = Material("vgui/ttt/vskin/events/selected") + EVENT.title = "title_event_selected" + + function EVENT:GetText() + return { + { + string = "desc_event_selected", + params = { + amount = #self.event.plys, + }, + }, + } + end end if SERVER then - function EVENT:Trigger() - local event = { - plys = {} - } - local eventPlys = event.plys - local plys = player.GetAll() - - for i = 1, #plys do - local ply = plys[i] - - eventPlys[i] = { - nick = ply:Nick(), - sid64 = ply:SteamID64(), - role = ply:GetSubRole(), - team = ply:GetTeam() - } - - self:AddAffectedPlayers( - {ply:SteamID64()}, - {ply:Nick()} - ) - end - - return self:Add(event) - end + function EVENT:Trigger() + local event = { + plys = {}, + } + local eventPlys = event.plys + local plys = player.GetAll() + + for i = 1, #plys do + local ply = plys[i] + + eventPlys[i] = { + nick = ply:Nick(), + sid64 = ply:SteamID64(), + role = ply:GetSubRole(), + team = ply:GetTeam(), + } + + self:AddAffectedPlayers({ ply:SteamID64() }, { ply:Nick() }) + end + + return self:Add(event) + end end function EVENT:Serialize() - return "The roles have been selected." + return "The roles have been selected." end function EVENT:GetDeprecatedFormat() - local event = self.event + local event = self.event - if event.roundState ~= ROUND_ACTIVE then return end + if event.roundState ~= ROUND_ACTIVE then + return + end - local eventRoles, eventTeams = {}, {} + local eventRoles, eventTeams = {}, {} - for i = 1, #event.plys do - local ply = event.plys[i] + for i = 1, #event.plys do + local ply = event.plys[i] - local subrole = ply.role - local team = ply.team + local subrole = ply.role + local team = ply.team - eventRoles[subrole] = eventRoles[subrole] or {} - eventRoles[subrole][#eventRoles[subrole] + 1] = ply.sid64 + eventRoles[subrole] = eventRoles[subrole] or {} + eventRoles[subrole][#eventRoles[subrole] + 1] = ply.sid64 - if team ~= TEAM_NONE then - eventTeams[team] = eventTeams[team] or {} - eventTeams[team][#eventTeams[team] + 1] = ply.sid64 - end - end + if team ~= TEAM_NONE then + eventTeams[team] = eventTeams[team] or {} + eventTeams[team][#eventTeams[team] + 1] = ply.sid64 + end + end - return { - id = self.type, - t = event.time / 1000, - rt = eventRoles, - tms = eventTeams - } + return { + id = self.type, + t = event.time / 1000, + rt = eventRoles, + tms = eventTeams, + } end diff --git a/lua/terrortown/events/spawn.lua b/lua/terrortown/events/spawn.lua index 79a4399ed..cfdcf828f 100644 --- a/lua/terrortown/events/spawn.lua +++ b/lua/terrortown/events/spawn.lua @@ -1,48 +1,47 @@ --- @ignore if CLIENT then - EVENT.icon = Material("vgui/ttt/vskin/events/spawn") - EVENT.title = "title_event_spawn" - - function EVENT:GetText() - return { - { - string = "desc_event_spawn", - params = { - player = self.event.finder.nick, - } - } - } - end + EVENT.icon = Material("vgui/ttt/vskin/events/spawn") + EVENT.title = "title_event_spawn" + + function EVENT:GetText() + return { + { + string = "desc_event_spawn", + params = { + player = self.event.finder.nick, + }, + }, + } + end end if SERVER then - function EVENT:Trigger(ply) - self:AddAffectedPlayers( - {ply:SteamID64()}, - {ply:Nick()} - ) - - return self:Add({ - nick = ply:Nick(), - sid64 = ply:SteamID64() - }) - end + function EVENT:Trigger(ply) + self:AddAffectedPlayers({ ply:SteamID64() }, { ply:Nick() }) + + return self:Add({ + nick = ply:Nick(), + sid64 = ply:SteamID64(), + }) + end end function EVENT:Serialize() - return self.event.nick .. " just spawned." + return self.event.nick .. " just spawned." end function EVENT:GetDeprecatedFormat() - local event = self.event - - if event.roundState ~= ROUND_ACTIVE then return end - - return { - id = self.type, - t = event.time / 1000, - ni = event.nick, - sid64 = event.sid64 - } + local event = self.event + + if event.roundState ~= ROUND_ACTIVE then + return + end + + return { + id = self.type, + t = event.time / 1000, + ni = event.nick, + sid64 = event.sid64, + } end diff --git a/lua/terrortown/lang/chef.lua b/lua/terrortown/lang/chef.lua index 2d00a1137..c6d60b9e2 100644 --- a/lua/terrortown/lang/chef.lua +++ b/lua/terrortown/lang/chef.lua @@ -1,5 +1,5 @@ -- Test/gimmick lang --- Not an example of how you should translate something. See english.lua for that. +-- Not an example of how you should translate something. See en.lua for that. local L = LANG.CreateLanguage("swedish_chef") @@ -8,13 +8,13 @@ L.lang_name = "Swedish Chef (Bork)" local gsub = string.gsub local function Borkify(word) - local b = string.byte(word:sub(1, 1)) + local b = string.byte(word:sub(1, 1)) - if b > 64 and b < 91 then - return "Bork" - end + if b > 64 and b < 91 then + return "Bork" + end - return "bork" + return "bork" end local realised = false @@ -22,15 +22,15 @@ local realised = false -- Upon selection, borkify every english string. -- Even with all the string manipulation this only takes a few ms. local function LanguageChanged(old, new) - if realised or new ~= "swedish_chef" then return end + if realised or new ~= "swedish_chef" then return end - local eng = LANG.GetUnsafeNamed("en") + local eng = LANG.GetUnsafeNamed("en") - for k, v in pairs(eng) do - L[k] = gsub(v, "[{}%w]+", Borkify) - end + for k, v in pairs(eng) do + L[k] = gsub(v, "[{}%w]+", Borkify) + end - realised = true + realised = true end hook.Add("TTTLanguageChanged", "ActivateChef", LanguageChanged) @@ -38,7 +38,7 @@ hook.Add("TTTLanguageChanged", "ActivateChef", LanguageChanged) local GetFrom = LANG.GetTranslationFromLanguage setmetatable(L, { - __index = function(_, k) - return gsub(GetFrom(k, "en") or "bork", "[{}%w]+", "BORK") - end + __index = function(_, k) + return gsub(GetFrom(k, "en") or "bork", "[{}%w]+", "BORK") + end }) diff --git a/lua/terrortown/lang/de.lua b/lua/terrortown/lang/de.lua index c44e57dbb..833422f4a 100644 --- a/lua/terrortown/lang/de.lua +++ b/lua/terrortown/lang/de.lua @@ -35,7 +35,7 @@ L.round_traitors_more = "Verräter, dies sind die Namen deiner Verbündeten: {na L.win_time = "Die Zeit ist abgelaufen. Die Verräter haben verloren." L.win_traitors = "Die Verräter haben gewonnen!" -L.win_innocents = "Die Innos haben gewonnen!" +L.win_innocents = "Die Unschuldigen haben gewonnen!" L.win_nones = "Die Bienen haben gewonnen! (Es ist ein Unentschieden)" L.win_showreport = "Schauen wir uns den Rundenbericht die nächste(n) {num} Sekunde(n) an." @@ -44,8 +44,8 @@ L.limit_time = "Zeitlimit erreicht. Die nächste Map wird bald geladen." L.limit_left = "{num} Runde(n) oder {time} Minute(n) verbleibend bis die Map gewechselt wird." -- Credit awards -L.credit_all = "Deinem Team wurde(n) {num} Ausrüstungs-Credit(s) für eure Leistung gegeben." -L.credit_kill = "Dir wurde(n) {num} Credit(s) gegeben, da du einen {role} getötet hast." +L.credit_all = "Deinem Team wurde(n) {num} Ausrüstungspunkt(e) für eure Leistung gegeben." +L.credit_kill = "Dir wurde(n) {num} Ausrüstungspunkt(e) gegeben, da du einen {role} getötet hast." -- Karma L.karma_dmg_full = "Dein Karma ist {amount}, also verteilst du diese Runde vollen Schaden!" @@ -60,13 +60,11 @@ L.body_found_traitor = "Er war ein Verräter!" L.body_found_det = "Er war ein Detektiv." L.body_found_inno = "Er war unschuldig." -L.body_confirm = "{finder} bestätigte den Tod von {victim}." - L.body_call = "{player} rief einen Detektiv zum Körper von {victim}!" L.body_call_error = "Du musst erst den Tod dieses Spielers bestätigen, bevor du einen Detektiv rufen kannst!" L.body_burning = "Autsch! Diese Leiche brennt lichterloh!" -L.body_credits = "Du hast {num} Credit(s) an diesem Körper gefunden!" +L.body_credits = "Du hast {num} Ausrüstungspunkt(e) an diesem Körper gefunden!" -- Menus and windows L.close = "Schließen" @@ -81,8 +79,8 @@ L.equip_title = "Ausrüstung" L.equip_tabtitle = "Ausrüstung bestellen" L.equip_status = "Bestellstatus" -L.equip_cost = "Du hast {num} Credit(s) übrig." -L.equip_help_cost = "Jedes Ausrüstungsteil, das du kaufst, kostet 1 Credit." +L.equip_cost = "Du hast {num} Ausrüstungspunkt(e) übrig." +L.equip_help_cost = "Jedes Ausrüstungsteil, das du kaufst, kostet 1 Ausrüstungspunkt." L.equip_help_carry = "Du kannst nur das kaufen, für das du auch Platz hast." L.equip_carry = "Du kannst diese Ausrüstung tragen." @@ -122,13 +120,13 @@ L.radar_charging = "Dein Radar lädt immer noch auf!" -- Transfer tab in equipment menu L.xfer_name = "Transfer" -L.xfer_menutitle = "Credits transferieren" -L.xfer_send = "Sende einen Credit" +L.xfer_menutitle = "Ausrüstungspunkte transferieren" +L.xfer_send = "Sende einen Ausrüstungspunkt" -L.xfer_no_recip = "Der Empfänger ist ungültig, Credit-Transfer abgebrochen." -L.xfer_no_credits = "Ungenügend Credits für einen Transfer." -L.xfer_success = "Credit-Transfer an {player} abgeschlossen." -L.xfer_received = "{player} gab dir {num} Credit(s)." +L.xfer_no_recip = "Der Empfänger ist ungültig, Ausrüstungspunkt-Transfer abgebrochen." +L.xfer_no_credits = "Ungenügend Ausrüstungspunkte für einen Transfer." +L.xfer_success = "Ausrüstungspunkt-Transfer an {player} abgeschlossen." +L.xfer_received = "{player} gab dir {num} Ausrüstungspunkt(e)." -- Radio tab in equipment menu L.radio_name = "Radio" @@ -172,50 +170,10 @@ L.quick_disg = "jemand(en) in Tarnung" L.quick_corpse = "ein(en) unidentifizierten/r Körper" L.quick_corpse_id = "{player}'s Leiche" --- Body search window -L.search_title = "Ergebnisse der Leichenuntersuchung" -L.search_info = "Information" -L.search_confirm = "Tod bestätigen" -L.search_call = "Detektiv rufen" - --- Descriptions of pieces of information found -L.search_nick = "Dies ist der Körper von {player}." - -L.search_role_traitor = "Diese Person war ein Verräter!" -L.search_role_det = "Diese Person war ein Detektiv." -L.search_role_inno = "Diese Person war ein unschuldiger Terrorist!" - -L.search_words = "Etwas sagt dir, dass die letzten Worte dieser Person \"{lastwords}\" waren." -L.search_armor = "Sie trug eine nicht-standardmäßige Körperrüstung." -L.search_disg = "Sie trug ein Gerät, dass ihre Identität verstecken konnte." -L.search_radar = "Sie trug eine Form eines Radars. Es funktioniert nicht mehr." -L.search_c4 = "In der Tasche war eine Notiz. Sie besagt, dass das Durchschneiden des Drahtes {num} die Bombe sicher entschärfen wird." - -L.search_dmg_crush = "Viele Knochen des Opfers sind gebrochen. Es scheint, als habe der Einschlag eines schweren Objekts zum Tode geführt." -L.search_dmg_bullet = "Es ist offensichtlich, dass die Person erschossen wurde." -L.search_dmg_fall = "Sie fiel in ihren Tod." -L.search_dmg_boom = "Ihre Wunden und die versengte Kleidung weisen auf eine Explosion hin, die ihr ein Ende bereitet hat." -L.search_dmg_club = "Der Körper ist ramponiert und verbeult. Die Person wurde mit Sicherheit zu Tode geprügelt." -L.search_dmg_drown = "Der Körper zeigt Anzeichen und Symptome von Ertrinken." -L.search_dmg_stab = "Sie wurde stark geschnitten und hatte tiefe Wunden und verblutete schlussendlich." -L.search_dmg_burn = "Es riecht hier nach gerösteten Terroristen..." -L.search_dmg_tele = "Es scheint, als sei ihre DNA durch Tachyonen verunstaltet worden!" -L.search_dmg_car = "Als diese Person die Straße überquerte, wurde sie von einem rücksichtslosen Fahrer überrollt." -L.search_dmg_other = "Du kannst keinen spezifischen Grund für den Tod dieser Person finden." - -L.search_weapon = "Es scheint, als wurde ein(e) {weapon} benutzt, um sie zu töten." -L.search_head = "Die tödliche Wunde war ein Kopfschuss. Keine Zeit, um zu schreien." -L.search_time = "Sie wurde etwa {time} getötet, bevor du die Untersuchung begonnen hast." -L.search_dna = "Erlange eine Probe der DNA des Mörders mit dem DNA-Scanner. Die DNA-Probe wird in etwa {time} verfallen." - -L.search_kills1 = "Du fandest eine Liste an Tötungen, die den Tod von {player} beweist." -L.search_kills2 = "Du fandest eine Liste an Tötungen mit diesen Namen:" -L.search_eyes = "Mit deinen Detektiv-Fähigkeiten identifizierst du die letzte Person, die sie sah: {player}. Der Mörder oder ein Zufall?" - -- Scoreboard L.sb_playing = "Du spielst auf..." L.sb_mapchange = "Die Karte wechselt in {num} Runden oder in {time}" ---L.sb_mapchange_disabled = "Session limits are disabled." +L.sb_mapchange_disabled = "Das Sitzungslimit ist deaktiviert." L.sb_mia = "Vermisst" L.sb_confirmed = "Definitiv tot" @@ -267,7 +225,6 @@ Versteckt deine ID. Vermeidet außerdem, dass du die letzte vom Opfer gesehene P Schalte es im Reiter "Tarnung" ein oder aus oder drücke Enter auf dem Numpad.]] -- C4 -L.c4_hint = "Drücke {usekey} zum Scharfstellen oder Entschärfen." L.c4_disarm_warn = "Eine Ladung C4, die du platziert hast, ist entschärft worden." L.c4_armed = "Du hast die Bombe erfolgreich scharf gestellt." L.c4_disarmed = "Du hast die Bombe erfolgreich entschärft." @@ -288,6 +245,7 @@ L.c4_remove_destroy2 = "Bestätigen: Vernichten" L.c4_disarm = "C4 entschärfen" L.c4_disarm_cut = "Klicke zum Durchschneiden von Kabel {num}" +L.c4_disarm_t = "Durchschneide ein Kabel zum Entschärfen der Bombe. Wenn du Verräter bist, ist jedes Kabel sicher. Unschuldige haben es da nicht so einfach!" L.c4_disarm_owned = "Durchschneide ein Kabel zum Entschärfen der Bombe. Es ist deine Bombe, also wird jedes Kabel sie sicher entschärfen." L.c4_disarm_other = "Durchschneide das richtige Kabel, um die Bombe zu entschärfen. Sie explodiert, wenn du das falsche triffst!" @@ -296,7 +254,6 @@ L.c4_status_disarmed = "ENTSCHÄRFT" -- Visualizer L.vis_name = "Visualisierer" -L.vis_hint = "Drücke {usekey} zum Aufheben (nur Detektive)." L.vis_desc = [[ Tatort-Visualisierungs-Gerät. @@ -305,7 +262,6 @@ Analysiere eine Leiche, um zu sehen, wie die Person umgebracht wurde, funktionie -- Decoy L.decoy_name = "Attrappe" -L.decoy_no_room = "Du kannst diese Attrappe nicht tragen." L.decoy_broken = "Deine Attrappe wurde zerstört!" L.decoy_short_desc = "Diese Attrappe erzeugt ein gefälschtes Radar-Signal sichtbar für andere Teams" @@ -316,7 +272,6 @@ Zeigt Detektiven ein gefälschtes Radar-Signal und bewirkt, dass der DNA-Scanner -- Defuser L.defuser_name = "Entschärfer" -L.defuser_help = "{primaryfire} entschärft anvisiertes C4." L.defuser_desc = [[ Entschärft sofort eine C4-Bombe. @@ -335,7 +290,6 @@ Das Verbrennen einer Leiche macht ein ganz bestimmtes Geräusch.]] L.hstation_name = "Gesundheitsstation" L.hstation_broken = "Deine Gesundheitsstation wurde zerstört!" -L.hstation_help = "{primaryfire} platziert die Gesundheitsstation." L.hstation_desc = [[ Ermöglicht bei Platzierung, dass sich jeder Spieler an ihr heilen kann. @@ -359,7 +313,6 @@ Die Energiespitzen schädigen nahestehende Spieler.]] -- Radio L.radio_broken = "Dein Radio wurde zerstört!" -L.radio_help_pri = "{primaryfire} platziert das Radio." L.radio_desc = [[ Spielt Geräusche zur Ablenkung ab. @@ -422,7 +375,7 @@ L.grenade_smoke = "Rauchgranate" L.grenade_fire = "Brandgranate" L.unarmed_name = "Unbewaffnet" -L.crowbar_name = "Brecheisen" +L.crowbar_name = "Brechstange" L.pistol_name = "Pistole" L.rifle_name = "Gewehr" L.shotgun_name = "Schrotgewehr" @@ -474,7 +427,7 @@ L.hp_wounded = "Verwundet" L.hp_badwnd = "Schwer Verwundet" L.hp_death = "Dem Tode nah" --- TargetID karma status +-- TargetID Karma status L.karma_max = "Verlässlich" L.karma_high = "Grob" L.karma_med = "Schießwütig" @@ -483,13 +436,11 @@ L.karma_min = "Verantwortungslos" -- TargetID misc L.corpse = "Leiche" -L.corpse_hint = "Drücke [{usekey}] zum Durchsuchen. [{walkkey} + {usekey}] um verdeckt zu untersuchen." +L.corpse_hint = "Drücke [{usekey}] zum Durchsuchen und Bestätigen. [{walkkey} + {usekey}] um verdeckt zu untersuchen." L.target_disg = "(Getarnt)" L.target_unid = "Unidentifizierter Körper" ---L.target_unknown = "A Terrorist" - -L.target_credits = "Durchsuche, um ungenutzte Credits zu erhalten." +L.target_unknown = "Ein Terrorist" -- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "Einmaliger Gebrauch" @@ -505,7 +456,6 @@ L.mute_off = "Niemanden stumm gestellt" -- Spectators and prop possession L.punch_title = "PUNCH-O-METER" -L.punch_help = "Die Bewegungstasten oder Springen: Objekt bewegen. Ducken: Objekt verlassen." L.punch_bonus = "Deine schlechte Punktzahl hat dein Punch-O-Meter Limit um {num} verringert." L.punch_malus = "Deine gute Punktzahl hat dein Punch-O-Meter Limit um {num} erhöht!" @@ -567,19 +517,19 @@ L.tip3 = "Detektive können Leichen untersuchen, um herauszufinden, wer ‘in de L.tip4 = "Niemand wird von deinem Tod erfahren, bis jemand deine Leiche gefunden und untersucht hat." -L.tip5 = "Wenn ein Verräter einen Detektiv tötet, erlangen diese direkt einen Credit als Belohnung." +L.tip5 = "Wenn ein Verräter einen Detektiv tötet, erlangen diese direkt einen Ausrüstungspunkt als Belohnung." -L.tip6 = "Wenn ein Verräter von einem Detektiv getötet wird, erhalten alle Detektive einen Credit." +L.tip6 = "Wenn ein Verräter von einem Detektiv getötet wird, erhalten alle Detektive einen Ausrüstungspunkt." -L.tip7 = "Wenn Verräter einen guten Fortschritt beim Töten von Unschuldigen gemacht haben, erhalten sie als Belohnung einen Credit." +L.tip7 = "Wenn Verräter einen guten Fortschritt beim Töten von Unschuldigen gemacht haben, erhalten sie als Belohnung einen Ausrüstungspunkt." -L.tip8 = "Verräter und Detektive können unverbrauchte Credits von Leichen anderer Verräter oder Detektive aufsammeln." +L.tip8 = "Verräter und Detektive können unverbrauchte Ausrüstungspunkte von Leichen anderer Verräter oder Detektive aufsammeln." L.tip9 = "Der Poltergeist kann physikalische Objekte in tödliche Projektile verwandeln. Jeder Schlag ist begleitet von einem Energieimpuls, der jeden in der Nähe verletzt." L.tip10 = "Halte als Verräter auf rote oder als Detektiv auf blaue Nachrichten in der oberen rechten Bildschirmecke Ausschau. Diese sind wichtig für dich." -L.tip11 = "Behalte als Verräter oder Detektiv im Kopf, dass du Credits verdienst, wenn deine Partner gut arbeiten. Vergiss nicht diese auch auszugeben!" +L.tip11 = "Behalte als Verräter oder Detektiv im Kopf, dass du Ausrüstungspunkte verdienst, wenn deine Partner gut arbeiten. Vergiss nicht diese auch auszugeben!" L.tip12 = "Der DNA-Scanner des Detektivs kann genutzt werden, um DNA-Proben von Waffen und Objekten zu erhalten. Diese können zum Scannen benutzt werden, um die Position des Spielers herauszufinden, der diese benutzt hat. Nützlich, wenn du eine Probe von einer Leiche oder einer entschärften Ladung C4 erhalten hast!" @@ -595,7 +545,7 @@ L.tip17 = "Stehen die Unschuldigen alle zusammen und sind schwer einzeln zu erle L.tip18 = "Du kannst mit dem platzierten Radio als Verräter Sounds im Ausrüstungsmenü abspielen. Du kannst mehrere Sounds hintereinander in Warteschlange geben, indem du sie in der Reihenfolge anklickst, in der sie gespielt werden sollen." -L.tip19 = "Wenn du als Detektiv Credits übrighast, kannst du deinen Entschärfer an einen glaubwürdigen Unschuldigen abgeben, dich um Wichtigeres kümmern und ihm den gefährlichen Job des Entschärfens überlassen." +L.tip19 = "Wenn du als Detektiv Ausrüstungspunkte übrighast, kannst du deinen Entschärfer an einen glaubwürdigen Unschuldigen abgeben, dich um Wichtigeres kümmern und ihm den gefährlichen Job des Entschärfens überlassen." L.tip20 = "Das Fernglas der Detektive kann Leichen aus großer Distanz untersuchen. Schlechte Nachrichten für die Verräter, wenn die die Leiche als Lockmittel nutzen wollten. Allerdings ist der Detektiv währenddessen unbewaffnet und abgelenkt..." @@ -856,7 +806,7 @@ L.aw_fnd3_title = "Geruch des Todes" L.aw_fnd3_text = "stolperte immer wieder über irgendwelche Leichen. {num} Mal diese Runde." L.aw_crd1_title = "Leichenfledderer" -L.aw_crd1_text = "schnorrte sich {num} zurückgelassene Credits von Leichen zusammen." +L.aw_crd1_text = "schnorrte sich {num} zurückgelassene Ausrüstungspunkte von Leichen zusammen." L.aw_tod1_title = "Teuer erkaufter Sieg" L.aw_tod1_text = "starb nur wenige Sekunden bevor sein Team die Runde gewann." @@ -881,8 +831,8 @@ L.equip_tooltip_main = "Ausrüstungsmenü" L.equip_tooltip_radar = "Radar-Einstellungen" L.equip_tooltip_disguise = "Tarnungs-Einstellungen" L.equip_tooltip_radio = "Radio-Einstellungen" -L.equip_tooltip_xfer = "Credits transferieren" ---L.equip_tooltip_reroll = "Reroll equipment" +L.equip_tooltip_xfer = "Ausrüstungspunkte transferieren" +L.equip_tooltip_reroll = "Ausrüstung neu auswürfeln" L.confgrenade_name = "Discombobulator" L.polter_name = "Poltergeist" @@ -903,10 +853,10 @@ L.shop_default = "Standart-Shop verwenden" -- 2019-05-05 L.reroll_name = "Reroll" ---L.reroll_menutitle = "Reroll equipment" -L.reroll_no_credits = "Du brauchst {amount} Credits zum neu ausrollen!" +L.reroll_menutitle = "Ausrüstung auswürfeln" +L.reroll_no_credits = "Du brauchst {amount} Ausrüstungspunkt(e) zum neu ausrollen!" L.reroll_button = "Reroll" ---L.reroll_help = "Use {amount} credits to get a new random set of equipment in your shop!" +L.reroll_help = "Verwende {amount} Ausrüstungspunkt(e), um ein neues zufälliges Set von Ausrüstungsgegenständen in deinem Shop zu erhalten!" -- 2019-05-06 L.equip_not_alive = "Du kannst alle verfügbaren Items sehen, wenn du eine Rolle auf der rechten Seite auswählst. Denk dran, du kannst zu jeder Zeit Favoriten hinzufügen!" @@ -933,16 +883,13 @@ L.shop_role_select = "Wähle eine Rolle" L.shop_role_selected = "Der {role} Shop wurde gewählt!" L.shop_search = "Suche" -L.spec_help = "Klicke, um Spielern zu zuschauen, oder drücke {usekey} auf ein physikalisches Objekt, um die Kontrolle zu erhalten." -L.spec_help2 = "Zum Verlassen des Zuschauer-Modus öffne das Menü mit {helpkey}, navigiere in 'Gameplay' und schalte den Zuschauermodus um." - -- 2019-10-19 L.drop_ammo_prevented = "Etwas hindert dich daran deine Munition fallenzulassen." -- 2019-10-28 L.target_c4 = "Drücke [{usekey}] um C4 Menü zu öffnen" L.target_c4_armed = "Drücke [{usekey}] um C4 zu entschärfen" -L.target_c4_armed_defuser = "Drücke [{usekey}] um Entschärfer zu verwenden" +L.target_c4_armed_defuser = "Drücke [{primaryfire}] um Entschärfer zu verwenden" L.target_c4_not_disarmable = "Du kannst kein C4 eines lebenden Teamkollegen entschärfen" L.c4_short_desc = "Etwas sehr explosives" @@ -950,16 +897,15 @@ L.target_pickup = "Drücke [{usekey}] um aufzuheben" L.target_slot_info = "Inventarplatz: {slot}" L.target_pickup_weapon = "Drücke [{usekey}] um Waffe aufzuheben" L.target_switch_weapon = "Drücke [{usekey}] um mit aktueller Waffe zu tauschen" -L.target_pickup_weapon_hidden = ", drücke [{usekey} + {walkkey}] für verstecktes Aufheben" -L.target_switch_weapon_hidden = ", drücke [{usekey} + {walkkey}] für verstecktes Tauschen" +L.target_pickup_weapon_hidden = ", drücke [{walkkey} + {usekey}] für verstecktes Aufheben" +L.target_switch_weapon_hidden = ", drücke [{walkkey} + {usekey}] für verstecktes Tauschen" L.target_switch_weapon_nospace = "Es ist kein Invetarplatz frei für diese Waffe" L.target_switch_drop_weapon_info = "Lasse {name} aus Inventarplatz {slot} fallen" L.target_switch_drop_weapon_info_noslot = "In Inventatplatz {slot} ist keine wegwerfbare Waffe" -L.corpse_searched_by_detective = "Diese Leiche wurde von einem Detektiv untersucht" +L.corpse_searched_by_detective = "Diese Leiche wurde von einer öffentlichen Ordnungsrolle untersucht" L.corpse_too_far_away = "Leiche zu weit weg zum Untersuchen." -L.radio_pickup_wrong_team = "Du kannst nicht das Radio eines anderen Teams aufheben." L.radio_short_desc = "Waffengeräusche sind Musik für mich" L.hstation_subtitle = "Drücke [{usekey}] um Leben zu regenerieren." @@ -1004,7 +950,6 @@ L.mute_team = "{team} stummgestellt." L.door_auto_closes = "Diese Tür schließt automatisch" L.door_open_touch = "Laufe gegen die Tür um sie zu öffnen." L.door_open_touch_and_use = "Laufe gegen die Tür oder drücke [{usekey}] um Tür zu öffnen." -L.hud_health = "Leben" -- 2020-03-09 L.help_title = "Hilfe und Einstellungen" @@ -1026,11 +971,11 @@ L.menu_guide_description = "Hilft dir mit TTT2 zurecht zu kommen und erklärt ei L.menu_bindings_description = "Belege Tasten von TTT2 und seinen Erweiterungen nach deinem Geschmack" L.menu_language_description = "Stelle die Sprache des Spiels ein" L.menu_appearance_description = "Verändere die Erscheinung und Performance der UI" -L.menu_gameplay_description = "Vermeide Rollen und verändere ein paar andere Funktionen" +L.menu_gameplay_description = "Verändere Lautstärke-, Barrierefreiheits- und Gameplay-Einstellungen." L.menu_addons_description = "Stelle lokale Addons nach deinem Geschmack ein" L.menu_legacy_description = "Einstellungen von alten TTT Erweiterungen, die zum neuen UI-System konvertiert werden sollten" L.menu_administration_description = "Allgemeine Einstellungen für HUDs, Ausrüstung und Shops" -L.menu_equipment_description = "Stelle Credits, Limitierungen, Verfügbarkeiten und anderes ein" +L.menu_equipment_description = "Stelle Ausrüstungspunkte, Limitierungen, Verfügbarkeiten und anderes ein" L.menu_shops_description = "Entferne oder füge Shops zu Rollen hinzu und definiere die Ausrüstung in diesen" L.submenu_guide_gameplay_title = "Gameplay" @@ -1050,10 +995,8 @@ L.submenu_appearance_crosshair_title = "Fadenkreuz" L.submenu_appearance_dmgindicator_title = "Schadensanzeige" L.submenu_appearance_performance_title = "Performance" L.submenu_appearance_interface_title = "Interface" -L.submenu_appearance_miscellaneous_title = "Verschiedenes" L.submenu_gameplay_general_title = "Allgemein" -L.submenu_gameplay_avoidroles_title = "Vermeide Rollen" L.submenu_administration_hud_title = "HUD Einstellungen" L.submenu_administration_randomshop_title = "Zufälliger Shop" @@ -1090,17 +1033,12 @@ L.label_shop_show_slot = "Zeige Slot Symbol" L.label_shop_show_custom = "Zeige Symbol für benutzerdefiniertes Element" L.label_shop_show_fav = "Zeige Symbol für favorisiertes Element" L.label_crosshair_enable = "Aktiviere Fadenkreuz" -L.label_crosshair_gap_enable = "Aktiviere benutzerdefinierte Fadenkreuz-Lücke" -L.label_crosshair_gap = "Benutzerdefinierte Fadenkreuz-Lücke" L.label_crosshair_opacity = "Transparenz des Fadenkreuzes" L.label_crosshair_ironsight_opacity = "Durchlässigkeit des Fadenkreuz-Visiers" L.label_crosshair_size = "Fadenkreuz Größe" L.label_crosshair_thickness = "Fadenkreuz Dicke" L.label_crosshair_thickness_outline = "Dicke der Umrandung des Fadenkreuzes" -L.label_crosshair_static_enable = "Aktiviere statisches Fadenkreuz" -L.label_crosshair_dot_enable = "Aktiviere Fadenkreuz-Punkt" -L.label_crosshair_lines_enable = "Aktiviere Fadenkreuz-Linien" -L.label_crosshair_scale_enable = "Aktiviere die Skalierung des Fadenkreuzes" +L.label_crosshair_scale_enable = "Aktiviere die dynamische Skalierung des Fadenkreuzes" L.label_crosshair_ironsight_low_enabled = "Senke Waffe beim Zielen durch Kimme und Korn" L.label_damage_indicator_enable = "Aktiviere Schadensanzeige" L.label_damage_indicator_mode = "Schadensanzeigen Thema" @@ -1119,13 +1057,10 @@ L.label_gameplay_specmode = "Nur-Zuschauer-Modus (bleibe immer Zuschauer)" L.label_gameplay_fastsw = "Schneller Waffenwechsel" L.label_gameplay_hold_aim = "Aktiviere Halten zum Anvisieren" L.label_gameplay_mute = "Stelle lebende Spieler stumm, wenn du tot bist" -L.label_gameplay_dtsprint_enable = "Aktiviere Double-Tap Sprinten" -L.label_gameplay_dtsprint_anykey = "Setze Double-Tap Sprinten fort, bis du stehen bleibst" L.label_hud_default = "Standard HUD" L.label_hud_force = "Erzwungenes HUD" L.label_bind_weaponswitch = "Waffe aufheben" -L.label_bind_sprint = "Sprinten" L.label_bind_voice = "Globaler Sprachchat" L.label_bind_voice_team = "Team Sprachchat" @@ -1149,7 +1084,6 @@ L.header_damage_indicator = "Schadensanzeigen Einstellungen" L.header_performance_settings = "Performance Einstellungen" L.header_interface_settings = "Anzeigeeinstellungen" L.header_gameplay_settings = "Spieleinstellungen" -L.header_roleselection = "Aktierung Rollenzuweisung" L.header_hud_administration = "Wähle Standard-HUD und Erzwungenes-HUD" L.header_hud_enabled = "De-/aktiviere HUDs" @@ -1196,11 +1130,7 @@ L.hud_revival_time = "{time}s" L.door_destructible = "Tür ist zerstörbar ({health}HP)" -- 2020-05-28 -L.confirm_detective_only = "Nur Detektive können Leichen bestätigen" -L.inspect_detective_only = "Nur Detektive können Leichen untersuchen" -L.corpse_hint_no_inspect = "Nur ein Detektiv kann diesen Körper untersuchen." -L.corpse_hint_inspect_only = "Drücke [{usekey}] zum Durchsuchen. Nur Detektive können diesen Körper bestätigen." -L.corpse_hint_inspect_only_credits = "Drücke [{usekey}] zum Erhalten der Credits. Nur ein Detektiv kann diesen Körper untersuchen." +L.corpse_hint_inspect_limited = "Drücke [{usekey}] zum Untersuchen. [{walkkey} + {usekey}] um nur die Untersuchungs-UI anzuzeigen." -- 2020-06-04 L.label_bind_disguiser = "Tarnung umschalten" @@ -1215,7 +1145,6 @@ L.binoc_help_sec = "Ändere Zoom-Level." L.vis_help_pri = "Lass das aktivierte Gerät fallen." -L.decoy_help_pri = "Platziere die Attrappe." -- 2020-08-07 L.pickup_error_spec = "Du kannst eine Waffe als Zuschauer nicht aufheben." @@ -1233,7 +1162,7 @@ Beachte, dass diese Übersetzungen Communitybasiert sind. Hilf mit, wenn Du Fehl L.title_score_info = "Rundenendeninfo" L.title_score_events = "Ereignistimeline" ---L.label_bind_clscore = "Open round report" +L.label_bind_clscore = "Öffne den Rundenbericht" L.title_player_score = "Punkte von {player}:" L.label_show_events = "Zeige Ereignisse von" @@ -1248,16 +1177,16 @@ L.hilite_win_innocents = "TEAM UNSCHULDIGE GEWANN" L.hilite_win_tie = "UNENTSCHIEDEN" L.hilite_win_time = "ZEIT VORBEI" ---L.tooltip_karma_gained = "Karma changes for this round:" ---L.tooltip_score_gained = "Score changes for this round:" ---L.tooltip_roles_time = "Role changes for this round:" +L.tooltip_karma_gained = "Karmaänderungen für diese Runde:" +L.tooltip_score_gained = "Punkteänderungen für diese Runde:" +L.tooltip_roles_time = "Rollenwechsel für diese Runde:" L.tooltip_finish_score_alive_teammates = "Lebende Teammitglieder: {score}" L.tooltip_finish_score_alive_all = "Lebende Spieler: {score}" L.tooltip_finish_score_timelimit = "Zeit vorbei: {score}" L.tooltip_finish_score_dead_enemies = "Tote Gegner: {score}" L.tooltip_kill_score = "Mord: {score}" ---L.tooltip_bodyfound_score = "Body found: {score}" +L.tooltip_bodyfound_score = "Leichenfund: {score}" L.finish_score_alive_teammates = "Lebende Teammitglieder:" L.finish_score_alive_all = "Lebende Spieler:" @@ -1267,30 +1196,30 @@ L.kill_score = "Mord:" L.bodyfound_score = "Leichenfindung:" L.title_event_bodyfound = "Eine Leiche wurde gefunden" ---L.title_event_c4_disarm = "A C4 was disarmed" ---L.title_event_c4_explode = "A C4 exploded" ---L.title_event_c4_plant = "A C4 was armed" +L.title_event_c4_disarm = "Ein C4 wurde entschärft" +L.title_event_c4_explode = "Ein C4 ist explodiert" +L.title_event_c4_plant = "Ein C4 wurde scharf geschaltet" L.title_event_creditfound = "Ausrüstungspunkte wurden gefunden" L.title_event_finish = "Die Runde ist vorbei" L.title_event_game = "Eine neue Runde hat begonnen" L.title_event_kill = "Ein Spieler wurde umgebracht" L.title_event_respawn = "Ein Spieler wurde wiederbelebt" L.title_event_rolechange = "Ein Spieler hat seine Rolle oder sein Team geändert" ---L.title_event_selected = "The roles were distributed" +L.title_event_selected = "Die Rollen wurden verteilt" L.title_event_spawn = "Ein Spieler ist erschienen" L.desc_event_bodyfound = "{finder} ({firole} / {fiteam}) hat die Leiche von {found} ({forole} / {foteam}) gefunden. Sie hatte {credits} Ausrüstungspunkt(e) in sich." ---L.desc_event_bodyfound_headshot = "The victim was killed by a headshot." ---L.desc_event_c4_disarm_success = "{disarmer} ({drole} / {dteam}) successfully disarmed the C4 armed by {owner} ({orole} / {oteam})." ---L.desc_event_c4_disarm_failed = "{disarmer} ({drole} / {dteam}) tried to disarm the C4 armed by {owner} ({orole} / {oteam}). They failed." ---L.desc_event_c4_explode = "The C4 armed by {owner} ({role} / {team}) exploded." ---L.desc_event_c4_plant = "{owner} ({role} / {team}) armed an explosive C4." +L.desc_event_bodyfound_headshot = "Das Opfer wurde durch einen Kopfschuss getötet." +L.desc_event_c4_disarm_success = "{disarmer} ({drole} / {dteam}) hat erfolgreich das C4 entschärft, welches von {owner} ({orole} / {oteam}) scharf geschaltet wurde." +L.desc_event_c4_disarm_failed = "{disarmer} ({drole} / {dteam}) hat versucht, das C4 zu entschärfen, welches von {owner} ({orole} / {oteam}) scharf geschaltet wurde. Der Versuch war nicht erfolgreich." +L.desc_event_c4_explode = "Das C4 von {owner} ({role} / {team}) explodierte." +L.desc_event_c4_plant = "{owner} ({role} / {team}) hat ein C4 scharf geschaltet." L.desc_event_creditfound = "{finder} ({firole} / {fiteam}) hat {credits} Ausrüstungspunkt(e) in der Leiche von {found} ({forole} / {foteam}) gefunden." L.desc_event_finish = "Die Runde dauerte {minutes}:{seconds}. Am Ende waren {alive} Spieler am Leben." L.desc_event_game = "Eine neue Runde hat begonnen." L.desc_event_respawn = "{player} wurde wiederbelebt." L.desc_event_rolechange = "{player} hat seine Rolle/Team von {orole} ({oteam}) zu {nrole} ({nteam}) geändert." ---L.desc_event_selected = "The teams and roles were distributed for all {amount} player(s)." +L.desc_event_selected = "Die Teams und Rollen wurden für alle {amount} Spieler verteilt." L.desc_event_spawn = "{player} ist erschienen." -- Name of a trap that killed us that has not been named by the mapper @@ -1344,45 +1273,45 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) wurde von {attacke L.none = "Keine Rolle" -- 2021-04-24 ---L.karma_teamkill_tooltip = "Teammate killed" ---L.karma_teamhurt_tooltip = "Teammate damaged" ---L.karma_enemykill_tooltip = "Enemy killed" +L.karma_teamkill_tooltip = "Teammate getötet" +L.karma_teamhurt_tooltip = "Teammate geschadet" +L.karma_enemykill_tooltip = "Gegner getötet" L.karma_enemyhurt_tooltip = "Schaden" L.karma_cleanround_tooltip = "Saubere Runde" ---L.karma_roundheal_tooltip = "Karma restoration" +L.karma_roundheal_tooltip = "Karma wiederhergestellt" L.karma_unknown_tooltip = "Unbekannt" -- 2021-05-07 ---L.header_random_shop_administration = "Random Shop Settings" +L.header_random_shop_administration = "Zufallsshop Einstellungen" L.header_random_shop_value_administration = "Balance Einstellungen" L.shopeditor_name_random_shops = "Aktiviere Zufalls-Shop" ---L.shopeditor_desc_random_shops = [[Random shops give every player a limited randomized set of all available equipments. ---Team shops forcefully give the same set to all players in a team instead of individual ones. ---Rerolling allows you to get a new randomized set of equipment for credits.]] +L.shopeditor_desc_random_shops = [[Zufallsshops geben jedem Spieler ein begrenztes, zufällig ausgewähltes Set aller verfügbaren Ausrüstungen. +Teamshops geben allen Spielern in einem Team zwangsweise dasselbe Set, anstelle von individuellen. +Das Neuauswürfeln ermöglicht es dir, für Ausrüstungspunkte ein neues zufälliges Ausrüstungsset zu erhalten.]] L.shopeditor_name_random_shop_items = "Anzahl zufälliger Equipments" ---L.shopeditor_desc_random_shop_items = "This includes equipments, which are marked with \"Always available in shop\". So choose a high enough number or you only get those." +L.shopeditor_desc_random_shop_items = "Dies schließt Ausrüstungsgegenstände ein, die mit \"Immer im Shop verfügbar\" markiert sind. Wähle also eine ausreichend hohe Zahl, oder du bekommst nur diese." L.shopeditor_name_random_team_shops = "Aktiviere Team-Shops" L.shopeditor_name_random_shop_reroll = "Aktiviere Möglichkeit Shop neu auszuwürfeln" L.shopeditor_name_random_shop_reroll_cost = "Kosten pro Auswürfeln" L.shopeditor_name_random_shop_reroll_per_buy = "Automatisches Auswürfeln nach jedem Kauf" -- 2021-06-04 ---L.header_equipment_setup = "Equipment Settings" +L.header_equipment_setup = "Ausrüstungseinstellungen" L.header_equipment_value_setup = "Balance Einstellungen" ---L.equipmenteditor_name_not_buyable = "Can be bought" +L.equipmenteditor_name_not_buyable = "Kann gekauft werden" L.equipmenteditor_desc_not_buyable = "Das Equipment ist in keinem Shop zu finden, wenn dies deaktiviert ist. Rollen, die dieses Equipment als Standardausrüstung bekommen sind davon jedoch nicht betroffen." L.equipmenteditor_name_not_random = "Immer im Shop verfügbar" ---L.equipmenteditor_desc_not_random = "If enabled, the equipment is always available in the shop. When the random shop is enabled, it takes one available random slot and always reserves it for this equipment." +L.equipmenteditor_desc_not_random = "Wenn aktiviert, ist die Ausrüstung immer im Shop verfügbar. Wenn der Zufallsshop aktiviert ist, wird ein verfügbarer zufälliger Slot genommen und immer für diese Ausrüstung reserviert." L.equipmenteditor_name_global_limited = "Global limitierte Anzahl" ---L.equipmenteditor_desc_global_limited = "If enabled, the equipment can be bought only once on the server in the active round." +L.equipmenteditor_desc_global_limited = "Wenn aktiviert, kann die Ausrüstung nur einmal auf dem Server in der laufenden Runde gekauft werden." L.equipmenteditor_name_team_limited = "Team limitierte Anzahl" ---L.equipmenteditor_desc_team_limited = "If enabled, the equipment can be bought only once per team in the active round." +L.equipmenteditor_desc_team_limited = "Wenn aktiviert, kann die Ausrüstung nur einmal pro Team in der laufenden Runde gekauft werden." L.equipmenteditor_name_player_limited = "Spieler limitierte Anzahl" ---L.equipmenteditor_desc_player_limited = "If enabled, the equipment can be bought only once per player in the active round." ---L.equipmenteditor_name_min_players = "Minimum amount of players for buying" -L.equipmenteditor_name_credits = "Kosten in Credits" +L.equipmenteditor_desc_player_limited = "Wenn aktiviert, kann die Ausrüstung nur einmal pro Spieler in der laufenden Runde gekauft werden." +L.equipmenteditor_name_min_players = "Mindestanzahl von Spielern zum Kauf" +L.equipmenteditor_name_credits = "Kosten in Ausrüstungspunkten" -- 2021-06-08 L.equip_not_added = "nicht enthalten" @@ -1393,12 +1322,12 @@ L.equip_inherit_removed = "entfernt (geerbt)" -- 2021-06-09 L.layering_not_layered = "Ohne Ebene" L.layering_layer = "Ebene {layer}" ---L.header_rolelayering_role = "{role} layering" ---L.header_rolelayering_baserole = "Base role layering" +L.header_rolelayering_role = "{role}-Ebene" +L.header_rolelayering_baserole = "Basisrollenebenen" L.submenu_administration_rolelayering_title = "Rollenebenen" L.header_rolelayering_info = "Rollenebeneninformationen" ---L.help_rolelayering_roleselection = "The role distribution process is split into two stages. In the first stage base roles are distributed, which are innocent, traitor and those listed in the 'base role layer' box below. The second stage is used to upgrade those base roles to a subrole." ---L.help_rolelayering_layers = "From each layer only one role is selected. First the roles from the custom layers are distributed starting from the first layer until the last is reached or no more roles can be upgraded. Whichever happens first, if upgradeable slots are still available, the unlayered roles will be distributed as well." +L.help_rolelayering_roleselection = "Der Rollenverteilungsprozess ist in zwei Phasen unterteilt. In der ersten Phase werden Basisrollen verteilt, zu denen Unschuldige, Verräter und diejenigen gehören, welche in der 'Basisrollenebene' unten aufgeführt sind. Die zweite Phase dient dazu, diese Basisrollen zu Unterrollen aufzuwerten." +L.help_rolelayering_layers = "Aus jeder Ebene wird nur eine Rolle ausgewählt. Zuerst werden die Rollen aus den benutzerdefinierten Ebenen verteilt, beginnend mit der ersten Ebene, bis die letzte erreicht ist oder keine Rollen mehr aufgewertet werden können. Was auch immer zuerst passiert, wenn noch aufwertbare Slots verfügbar sind, werden auch die nicht geschichteten Rollen verteilt." L.scoreboard_voice_tooltip = "Scrolle um die Lautstärke zu ändern" -- 2021-06-15 @@ -1421,7 +1350,7 @@ L.spawneditor_desc = "Wird verwendet, um Waffen-, Munitions- und Spielerspawns i L.spawneditor_place = "Platziere Spawn" L.spawneditor_remove = "Entferne Spawn" L.spawneditor_change = "Ändere den Spawntyp (halte [SHIFT] zum Umkehren)" ---L.spawneditor_ammo_edit = "Hold to edit amount of autospawning ammo on weapon spawns" +L.spawneditor_ammo_edit = "Halten bei Waffen-Spawns, um autogenerierte Munition zu bearbeiten" L.spawn_weapon_random = "Zufallswaffenspawn" L.spawn_weapon_melee = "Nahkampfwaffenspawn" @@ -1439,7 +1368,7 @@ L.spawn_ammo_rifle = "Gewehrmunitionsspawn" L.spawn_ammo_shotgun = "Schrotflintenmunitionsspawn" L.spawn_player_random = "Zufallsspielerspawn" -L.spawn_weapon_ammo = " (Munition: {ammo})" +L.spawn_weapon_ammo = "(Munition: {ammo})" L.spawn_weapon_edit_ammo = "Halte [{walkkey}] und drücke [{primaryfire} oder {secondaryfire}], um die Munition für diesen Waffenspawn zu erhöhen / reduzieren" @@ -1455,19 +1384,19 @@ L.button_start_entspawn_edit = "Starte Spawn Editor" L.button_delete_all_spawns = "Lösche alle Spawns" L.label_dynamic_spawns_enable = "Aktiviere dynamische Spawns für diese Map" ---L.label_dynamic_spawns_global_enable = "Enable dynamic spawns for all maps" +L.label_dynamic_spawns_global_enable = "Aktiviere dynamische Spawns für alle Karten" L.header_equipment_weapon_spawn_setup = "Waffenspawneinstellungen" ---L.help_spawn_editor_info = [[ ---The spawn editor is used to place, remove and edit spawns in the world. These spawns are for weapons, ammunition and players. +L.help_spawn_editor_info = [[ +Der Spawndeditor wird verwendet, um Spawns in der Welt zu platzieren, zu entfernen und zu bearbeiten. Diese Spawns sind für Waffen, Munition und Spieler. ---These spawns are saved in files located in 'data/ttt/weaponspawnscripts/'. They can be deleted for a hard reset. The initial spawn files are created from spawns found on the map and in the original TTT weapon spawn scripts. Pressing the reset button always reverts to the initial state. +Diese Spawns werden in Dateien im Verzeichnis 'data/ttt/weaponspawnscripts/' gespeichert. Sie können für einen harten Reset gelöscht werden. Die ursprünglichen Spawndateien werden aus den auf der Karte gefundenen Spawns und den ursprünglichen TTT-Waffenspawnskripten erstellt. Das Drücken der Zurücksetzen-Schaltfläche führt immer zur Ausgangsposition zurück. ---It should be noted that this spawn system uses dynamic spawns. This is most interesting for weapons because it no longer defines a specific weapon, but a type of weapons. For example instead of a TTT shotgun spawn, there is now a general shotgun spawn where any weapon defined as shotgun can spawn. The spawn type for each weapon can be set in the 'Edit Equipment' menu. This makes it possible for any weapon to spawn on the map, or to disable certain default weapons. +Es sollte beachtet werden, dass dieses Spawnsystem dynamische Spawns verwendet. Dies ist besonders interessant für Waffen, da es nicht mehr eine bestimmte Waffe definiert, sondern einen Typ von Waffen. Zum Beispiel gibt es anstelle eines TTT-Schrotgewehr-Spawns jetzt einen allgemeinen Schrotgewehr-Spawn, auf dem jede als Schrotgewehr definierte Waffe erscheinen kann. Der Spawntyp für jede Waffe kann im 'Ausrüstung bearbeiten'-Menü festgelegt werden. Dies ermöglicht es, dass jede Waffe auf der Karte erscheinen kann, oder bestimmte Standardwaffen deaktiviert werden können. ---Keep in mind that many changes only take effect after a new round has started.]] ---L.help_spawn_editor_enable = "On some maps it might be advised to use the original spawns found on the map without replacing them with the dynamic system. Changing this option below only affects the currently active map, so the dynamic system will still be used for every other map." +Beachte, dass viele Änderungen erst nach Beginn einer neuen Runde wirksam werden.]] +L.help_spawn_editor_enable = "Auf einigen Karten kann es ratsam sein, die ursprünglichen Spawns auf der Karte zu verwenden, anstatt sie durch das dynamische System zu ersetzen. Die Änderung dieser Option unten betrifft nur die derzeit aktive Karte, sodass das dynamische System weiterhin für jede andere Karte verwendet wird." L.help_spawn_editor_hint = "Hinweis: Öffne das Gamemode Menü erneut, um den Spawneditor zu verlassen" L.help_spawn_editor_spawn_amount = [[ Aktuell existieren {weapon} Waffenspawns, {ammo} Munitionssapawns und {player} Spielerspawns auf dieser Map. @@ -1493,10 +1422,10 @@ Drücke 'Starte Spawn Editor' um diese zu ändern. L.equipmenteditor_name_auto_spawnable = "Ausrüstung spawnt zufällig in der Welt" L.equipmenteditor_name_spawn_type = "Wähle Spawntyp" ---L.equipmenteditor_desc_auto_spawnable = [[ ---The TTT2 spawn system allows every weapon to spawn in the world. By default only weapons marked as 'AutoSpawnable' by the creator will spawn in the world, however this can be changed from within this menu. +L.equipmenteditor_desc_auto_spawnable = [[ +Das TTT2-Spawnsystem ermöglicht es, dass jede Waffe in der Welt erscheint. Standardmäßig erscheinen nur Waffen, die vom Ersteller als 'AutoSpawnable' markiert wurden, in der Welt. Dies kann jedoch in diesem Menü geändert werden. ---Most of the equipment is set to 'special weapon spawns' by default. This means that equipment only spawns on random weapon spawns. However it is possible to place special weapon spawns in the world or change the spawn type here to use other existing spawn types.]] +Die meisten Ausrüstungsgegenstände sind standardmäßig auf 'Spezialwaffenspawn' eingestellt. Das bedeutet, dass die Ausrüstungsgegenstände nur auf zufälligen Waffenspawns erscheint. Es ist jedoch möglich, spezielle Waffenspawns in der Welt zu platzieren oder den Spawntyp hier zu ändern, um andere vorhandene Spawntypen zu verwenden.]] L.pickup_error_inv_cached = "Du kannst dies im AUgenblick nicht aufheben, da dein Inventar gecached ist." @@ -1506,25 +1435,25 @@ L.header_playermodels_general = "Allgemeine Spielermodelleinstellungen" L.header_playermodels_selection = "Wähle Spielermodellliste" L.label_enforce_playermodel = "Erzwinge rollenspezifisches Spielermodell" ---L.label_use_custom_models = "Use a randomly selected player model" +L.label_use_custom_models = "Verwende ein zufällig ausgewähltes Spielermodell" L.label_prefer_map_models = "Bevorzuge kartenspezifische Modelle über die Standardspielermodelle" ---L.label_select_model_per_round = "Select a new random model each round (only on map change if disabled)" +L.label_select_model_per_round = "Wähle in jeder Runde ein neues zufälliges Modell aus (wenn deaktiviert, dann nur bei Kartenwechsel)" ---L.help_prefer_map_models = [[ ---Some maps define their own player models. By default these models have a higher priority than those that are assigned automatically. By disabling this setting, map specific models are disabled. +L.help_prefer_map_models = [[ +Einige Karten definieren ihre eigenen Spielermodelle. Standardmäßig haben diese Modelle eine höhere Priorität als die automatisch zugewiesenen Modelle. Durch das Deaktivieren dieser Einstellung werden die kartenspezifischen Modelle deaktiviert. ---Role specific models always have a higher priority and are unaffected by this setting.]] ---L.help_enforce_playermodel = [[ ---Some roles have custom player models. They can be disabled which can be relevant for compatibility with some player model selectors. ---Random default models can still be selected, if this setting is disabled.]] ---L.help_use_custom_models = [[ ---By default only the CS:S Phoenix player model is assigned to all players. By enabling this option however it is possible to select a player model pool. With this setting enabled each player will still be assigned the same player model, however it is a random model from the defined model pool. +Rollen-spezifische Modelle haben immer Vorrang und werden von dieser Einstellung nicht beeinflusst.]] +L.help_enforce_playermodel = [[ +Einige Rollen haben benutzerdefinierte Spielermodelle. Diese können deaktiviert werden, was in Bezug auf die Kompatibilität mit bestimmten Spielermodell-Selektoren relevant sein kann. +Zufällige Standardmodelle können immer noch ausgewählt werden, wenn diese Einstellung deaktiviert ist.]] +L.help_use_custom_models = [[ +Standardmäßig wird allen Spielern standardmäßig das CS:S Phoenix-Spielermodell zugewiesen. Durch Aktivieren dieser Option ist es jedoch möglich, einen Spielermodell-Pool auszuwählen. Bei dieser Einstellung wird jedem Spieler immer noch dasselbe Spielermodell zugewiesen, jedoch handelt es sich um ein zufälliges Modell aus dem definierten Modell-Pool. ---This selection of models can be extended by installing more player models.]] +Diese Auswahl an Modellen kann durch die Installation weiterer Spielermodelle erweitert werden.]] -- 2021-10-06 L.menu_server_addons_title = "Server Addons" ---L.menu_server_addons_description = "Server-wide admin only settings for addons." +L.menu_server_addons_description = "Serverweite Administrator-Einstellungen für Addons." L.tooltip_finish_score_penalty_alive_teammates = "Lebende Teammitglieder (Strafe): {score}" L.finish_score_penalty_alive_teammates = "Lebende Teammitglieder (Strafe):" @@ -1534,10 +1463,10 @@ L.tooltip_kill_score_team = "Teammord: {score}" L.kill_score_team = "Teammord:" -- 2021-10-09 ---L.help_models_select = [[ ---Left click on the models to add them to the player model pool. Left click again to remove them. Right clicking toggles between enabled and disabled detective hats for the focused model. +L.help_models_select = [[ +Linksklick auf die Modelle, um sie dem Spielermodell-Pool hinzuzufügen. Klicke erneut mit der linken Maustaste, um sie zu entfernen. Das Rechtsklicken schaltet die aktivierten und deaktivierten Detektivhüte für das ausgewählte Modell um. ---The small indicator in the top left shows if the player model has a head hitbox. The icon below shows if this model is applicable for a detective hat.]] +Der kleine Indikator oben links zeigt an, ob das Spielermodell eine Kopftrefferbox hat. Das Symbol darunter zeigt an, ob dieses Modell für einen Detektivhut geeignet ist.]] L.menu_roles_title = "Rollen Einstellungen" L.menu_roles_description = "Richte Rollenspawning, Ausrüsungspunkte und mehr ein." @@ -1546,46 +1475,46 @@ L.submenu_administration_roles_general_title = "Allgemeine Rolleneinstellungen" L.header_roles_info = "Roleninformationen" L.header_roles_selection = "Rollenauswahlparameter" ---L.header_roles_tbuttons = "Traitor Buttons Access" +L.header_roles_tbuttons = "Zugriff zu Verräterknöpfen" L.header_roles_credits = "Rollenausrüstungspunkte" L.header_roles_additional = "Weitere Rolleneinstellungen" L.header_roles_reward_credits = "Belohnungsausrüstungspunkte" L.help_roles_default_team = "Standardteam: {team}" ---L.help_roles_unselectable = "This role is not distributable. It is not considered in the role distribution process. Most of the times this means that this is a role that is manually assigned during the round through an event like a revival, a sidekick deagle or something similar." ---L.help_roles_selectable = "This role is distributable. If all criteria is met, this role is considered in the role distribution process." ---L.help_roles_credits = "Equipment credits are used to buy equipment in the shop. It mostly makes sense to give them only for those roles that have access to the shops. However, since it is possible to find credits on corpses, you can also give starting credits to roles as a reward to their killer." ---L.help_roles_selection_short = "The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role." ---L.help_roles_selection = [[ ---The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role. This also means that at least 5 players are needed for this role to be distributed at all. ---Keep in mind that all of this only applies if the role is considered for distribution process. - ---The aforementioned role distribution has a special integration with the lower limit of players. If the role is considered for distribution and the minimum value is below the value given by the distribution factor, but the amount of players is equal or greater than the lower limit, a single player can still receive this role. The distribution process then works as usual for the second player.]] ---L.help_roles_award_info = "Some roles (if enabled in their credits settings) receive equipment credits if a certain percentage of enemies has died. Related values can be tweaked here." ---L.help_roles_award_pct = "When this percentage of enemies are dead, specific roles are awarded equipment credits." ---L.help_roles_award_repeat = "Whether the credit award is handed out multiple times. For example, if the percentage is set to '0.25', and this setting is enabled, players will be awarded credits at '25%', '50%' and '75%' dead enemies respectively." ---L.help_roles_advanced_warning = "WARNING: These are advanced settings that can completely mess up the role distribution process. When in doubt keep all values at '0'. This value means that no limits are applied and the role distribution will try to assign as many roles as possible." ---L.help_roles_max_roles = [[ ---The term roles here includes both the base roles and the subroles. By default, there is no limit on how many different roles can be assigned. However, here are two different ways to limit them. - ---1. Limit them by a fixed amount. ---2. Limit them by a percentage. - ---The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] ---L.help_roles_max_baseroles = [[ ---Base roles are only those roles others inherit from. For example, the Innocent role is a base role, while a Pharaoh is a subrole of this role. By default, there is no limit on how many different base roles can be assigned. However, here are two different ways to limit them. - ---1. Limit them by a fixed amount. ---2. Limit them by a percentage. - ---The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] +L.help_roles_unselectable = "Diese Rolle ist nicht verteilbar. Sie wird im Rollenverteilungsprozess nicht berücksichtigt. In den meisten Fällen handelt es sich dabei um eine Rolle, die während der Runde manuell durch ein Ereignis wie eine Wiederbelebung, eine Sidekick-Deagle oder etwas Ähnliches zugewiesen wird." +L.help_roles_selectable = "Diese Rolle ist verteilbar. Wenn alle Kriterien erfüllt sind, wird diese Rolle im Rollenverteilungsprozess berücksichtigt." +L.help_roles_credits = "Ausrüstungspunkte werden verwendet, um Ausrüstung im Shop zu kaufen. Es ergibt meistens Sinn, sie nur für die Rollen zu vergeben, die Zugang zu Shops haben. Da es jedoch möglich ist, Ausrüstungspunkte in Leichen zu finden, kannst du Rollen auch Start-Ausrüstungspunkte als Belohnung für ihren Mörder geben." +L.help_roles_selection_short = "Die Rollenverteilung pro Spieler definiert den Prozentsatz der Spieler, denen diese Rolle zugewiesen wird. Zum Beispiel, wenn der Wert auf '0,2' eingestellt ist, erhält jeder fünfte Spieler diese Rolle." +L.help_roles_selection = [[ +Die Rollenverteilung pro Spieler definiert den Prozentsatz der Spieler, denen diese Rolle zugewiesen wird. Zum Beispiel, wenn der Wert auf '0,2' eingestellt ist, erhält jeder fünfte Spieler diese Rolle. Dies bedeutet auch, dass mindestens 5 Spieler benötigt werden, damit diese Rolle überhaupt verteilt wird. +Beachte, dass all dies nur gilt, wenn die Rolle für den Verteilungsprozess berücksichtigt wird. + +Die oben genannte Rollenverteilung hat eine besondere Integration mit der unteren Spielerbegrenzung. Wenn die Rolle für die Verteilung in Betracht gezogen wird und der Mindestwert unter dem Wert liegt, der durch den Verteilungsfaktor angegeben wird, aber die Anzahl der Spieler gleich oder größer als die untere Begrenzung ist, kann immer noch ein einzelner Spieler diese Rolle erhalten. Der Verteilungsprozess funktioniert dann wie gewohnt für den zweiten Spieler.]] +L.help_roles_award_info = "Einige Rollen (wenn in ihren Ausrüstungspunkt-Einstellungen aktiviert) erhalten Ausrüstungspunkte, wenn ein bestimmter Prozentsatz an Feinden gestorben ist. Die damit verbundenen Werte können hier angepasst werden." +L.help_roles_award_pct = "Wenn dieser Prozentsatz der Feinde tot ist, erhalten bestimmte Rollen Ausrüstungspunkte als Belohnung." +L.help_roles_award_repeat = "Ob die Ausrüstungspunktevergabe mehrmals erfolgt. Zum Beispiel, wenn der Prozentsatz auf '0.25' eingestellt ist und diese Einstellung aktiviert ist, erhalten Spieler bei '25%', '50%' und '75%' toten Feinden jeweils Ausrüstungspunkte." +L.help_roles_advanced_warning = "WARNUNG: Dies sind fortgeschrittene Einstellungen, die den Rollenverteilungsprozess komplett durcheinander bringen können. Bei Unsicherheit sollten alle Werte auf '0' belassen werden. Dieser Wert bedeutet, dass keine Grenzen gelten und die Rollenverteilung versuchen wird, so viele Rollen wie möglich zuzuweisen." +L.help_roles_max_roles = [[ +Der Begriff "Rollen" umfasst hier sowohl die Basisrollen als auch die Unterrollen. Standardmäßig gibt es keine Begrenzung, wie viele verschiedene Rollen zugewiesen werden können. Es gibt jedoch zwei verschiedene Möglichkeiten, sie zu beschränken: + +1. Begrenzung durch eine feste Menge. +2. Begrenzung durch einen Prozentsatz. + +Letzteres wird nur verwendet, wenn die feste Menge '0' beträgt und setzt eine Obergrenze auf Grundlage des festgelegten Prozentsatzes der verfügbaren Spieler.]] +L.help_roles_max_baseroles = [[ +Basisrollen sind nur die Rollen, von denen andere erben. Zum Beispiel ist die Unschuldigen-Rolle eine Basisrolle, während ein Pharao eine Unterrolle dieser Rolle ist. Standardmäßig gibt es keine Begrenzung, wie viele verschiedene Basisrollen zugewiesen werden können. Es gibt jedoch zwei verschiedene Möglichkeiten, sie zu beschränken: + +1. Begrenzung durch eine feste Menge. +2. Begrenzung durch einen Prozentsatz. + +Letzteres wird nur verwendet, wenn die feste Menge '0' beträgt und setzt eine Obergrenze auf Grundlage des festgelegten Prozentsatzes der verfügbaren Spieler.]] L.label_roles_enabled = "Aktiviere Rolle" L.label_roles_min_inno_pct = "Unschuldigenverteilung pro Spieler" L.label_roles_pct = "Rollenverteilung pro Spieler" L.label_roles_max = "Oberes Limit von Spielern mit dieser Rolle" ---L.label_roles_random = "Chance this role is distributed" ---L.label_roles_min_players = "Lower limit of players to consider distribution" +L.label_roles_random = "Chance, dass diese Rolle verteilt wird" +L.label_roles_min_players = "Die untere Spielerbegrenzung, um die Verteilung in Betracht zu ziehen" L.label_roles_tbutton = "Rolle kann Verräterknöpfe nutzen" L.label_roles_credits_starting = "Ausrüstungspunkte zu Beginn" L.label_roles_credits_award_pct = "Belohnungsspieleranteil" @@ -1594,269 +1523,671 @@ L.label_roles_credits_award_repeat = "Mehrfache Belohnung" L.label_roles_newroles_enabled = "Aktiviere eigene Rollen" L.label_roles_max_roles = "Obere Grenze für Rollen" L.label_roles_max_roles_pct = "Prozentuale obere Grenze für Rollen" ---L.label_roles_max_baseroles = "Upper base role limit" ---L.label_roles_max_baseroles_pct = "Upper base role limit by percentage" ---L.label_detective_hats = "Enable hats for policing roles like the Detective (if player model allows to have them)" +L.label_roles_max_baseroles = "Obere Basisrollenbegrenzung" +L.label_roles_max_baseroles_pct = "Obere Basisrollenbegrenzung in Prozent" +L.label_detective_hats = "Aktiviere Hüte für öffentliche Ordnungsrollen wie den Detektiv (sofern das Spielermodell dies zulässt)" ---L.ttt2_desc_innocent = "An Innocent has no special abilities. They have to find the evil ones among the terrorists and kill them. But they have to be careful not to kill their teammates." ---L.ttt2_desc_traitor = "The Traitor is the enemy of the Innocent. They have an equipment menu with which they are being able to buy special equipment. They have to kill everyone but their teammates." +L.ttt2_desc_innocent = "Ein Unschuldiger hat keine besonderen Fähigkeiten. Sie müssen die Bösen unter den Terroristen finden und sie töten. Dabei müssen sie jedoch vorsichtig sein, ihre Teammitglieder nicht zu töten." +L.ttt2_desc_traitor = "Der Verräter ist der Feind der Unschuldigen. Sie verfügen über ein Ausrüstungsmenü, mit dem sie spezielle Ausrüstung kaufen können. Ihr Ziel ist es, jeden zu töten, außer ihre Teammitglieder." L.ttt2_desc_detective = "Der Detektiv ist derjenige, dem die Unschuldigen trauen können. Aber wer sind die Unschuldigen? Der Detektiv muss genau das herausfinden. Ihr Ausrüstungsshop hilft ihnen vielleicht dabei." -- 2021-10-10 L.button_reset_models = "Spielermodelle Zurücksetzen" -- 2021-10-13 -L.help_roles_credits_award_kill = "Ein weiterer Weg Ausrüstungspunkte zu erhalten ist es wichtige Spieler mit 'offenen Rollen' (wie beispielsweise dem Detektiv) zu töten. Wenn die Rolle des Mörders dies aktiviert hat, dann bekommt der Spieler die hier definierte Anzahl an Ausrüstungspunkten." ---L.help_roles_credits_award = [[ ---There are two different ways to be awarded credits in base TTT2: +L.help_roles_credits_award_kill = "Ein weiterer Weg Ausrüstungspunkte zu erhalten ist es wichtige Spieler mit 'öffentlichen Rollen' (wie beispielsweise dem Detektiv) zu töten. Wenn die Rolle des Mörders dies aktiviert hat, dann bekommt der Spieler die hier definierte Anzahl an Ausrüstungspunkten." +L.help_roles_credits_award = [[ +Es gibt zwei verschiedene Möglichkeiten, um in Basis-TTT2 Ausrüstungspunkte zu erhalten: ---1. If a certain percentage of the enemy team is dead, the whole team is awarded credits. ---2. If a player killed a high value player with a 'public role' such as a Detective, the killer is awarded credits. +1. Wenn ein bestimmter Prozentsatz des feindlichen Teams tot ist, erhält das gesamte Team Ausrüstungspunkte. +2. Wenn ein Spieler einen Spieler mit einer 'öffentlichen Rolle' wie einem Detektiv getötet hat, werden dem Mörder Ausrüstungspunkte verliehen. ---Please note, that this still can be enabled/disabled for every role, even if the whole team is awarded. For example, if team Innocent is awarded, but the Innocent role has this disabled, only the Detective will receive their credits. ---The balancing values for this feature can be set in 'Administration' -> 'General Role Settings'.]] ---L.help_detective_hats = [[ ---Policing roles such as the Detective may wear hats to show their authority. They lose them on death or if damaged at the head. +Bitte beachte, dass dies immer noch für jede Rolle aktiviert/deaktiviert werden kann, selbst wenn das gesamte Team belohnt wird. Zum Beispiel, wenn das Team Unschuldige belohnt wird, aber die Rolle Unschuldiger diese Funktion deaktiviert hat, erhält nur der Detektiv seine Ausrüstungspunkte. +Die Balanceeinstellungen für diese Funktion können in 'Administration' -> 'Allgemeine Rolleneinstellungen' festgelegt werden.]] +L.help_detective_hats = [[ +Öffentliche Ordnungsrollen wie der Detektiv können Hüte tragen, um ihre Autorität zu zeigen. Sie verlieren diese Hüte bei ihrem Tod oder wenn der Kopf beschädigt wird. ---Some player models do not support hats by default. This can be changed in 'Administration' -> 'Player Models']] +Einige Spielermodelle unterstützen standardmäßig keine Hüte. Dies kann in 'Administration' -> 'Spielermodell' geändert werden.]] ---L.label_roles_credits_award_kill = "Credit reward amount for the kill" +L.label_roles_credits_award_kill = "Anzahl an Ausrüstungspunkten für die Tötung" L.label_roles_credits_dead_award = "Aktiviere Ausrüstungspunktebelohnung für gewissen Anteil an toten Gegnern" L.label_roles_credits_kill_award = "Aktiviere Ausrüstungspunktebelohnung für Mord an wichtigem Spieler" ---L.label_roles_min_karma = "Lower limit of Karma to consider distribution" +L.label_roles_min_karma = "Untere Grenze des Karmas, um die Verteilung in Betracht zu ziehen" -- 2021-11-07 ---L.submenu_administration_administration_title = "Administration" ---L.submenu_administration_voicechat_title = "Voice chat / Text chat" ---L.submenu_administration_round_setup_title = "Round Settings" ---L.submenu_administration_mapentities_title = "Map Entities" ---L.submenu_administration_inventory_title = "Inventory" ---L.submenu_administration_karma_title = "Karma" ---L.submenu_administration_sprint_title = "Sprinting" ---L.submenu_administration_playersettings_title = "Player Settings" - ---L.header_roles_special_settings = "Special Role Settings" ---L.header_equipment_additional = "Additional Equipment Settings" ---L.header_administration_general = "General Administrative Settings" ---L.header_administration_logging = "Logging" ---L.header_administration_misc = "Miscellaneous" ---L.header_entspawn_plyspawn = "Player Spawn Settings" ---L.header_voicechat_general = "General Voice chat Settings" ---L.header_voicechat_battery = "Voice chat Battery" ---L.header_voicechat_locational = "Proximity Voice chat" ---L.header_playersettings_plyspawn = "Player Spawn Settings" ---L.header_round_setup_prep = "Round: Preparing" ---L.header_round_setup_round = "Round: Active" ---L.header_round_setup_post = "Round: Post" ---L.header_round_setup_map_duration = "Map Session" ---L.header_textchat = "Text chat" ---L.header_round_dead_players = "Dead Player Settings" ---L.header_administration_scoreboard = "Scoreboard Settings" ---L.header_hud_toggleable = "Toggleable HUD Elements" ---L.header_mapentities_prop_possession = "Prop Possession" ---L.header_mapentities_doors = "Doors" ---L.header_karma_tweaking = "Karma Tweaking" ---L.header_karma_kick = "Karma Kick and Ban" ---L.header_karma_logging = "Karma Logging" ---L.header_inventory_gernal = "Inventory Size" ---L.header_inventory_pickup = "Inventory Weapon Pickup" ---L.header_sprint_general = "Sprint Settings" ---L.header_playersettings_armor = "Armor System Settings" - ---L.help_killer_dna_range = "When a player is killed by another player, a DNA sample is left on their body. The setting below defines the maximum distance in hammer units for DNA samples to be left. If the killer is further away than this value when the victim dies, no sample will be left on the corpse." ---L.help_killer_dna_basetime = "The base time in seconds until a DNA sample decays, if the killer is 0 Hammer units away. The farther the killer is, the less time will be given to the DNA sample to decay." ---L.help_dna_radar = "The TTT2 DNA scanner shows the exact distance and direction of the selected DNA sample if equipped. However, there is also a classic DNA scanner mode that updates the selected sample with an in-world rendering every time the cooldown has passed." ---L.help_idle = "The idle mode is used to forcefully move idle players into the spectator mode. To leave this mode, they will have to disable it in their 'gameplay' menu." ---L.help_namechange_kick = [[ ---A name change during an active round could be abused. Therefore, this is prohibited by default and will lead to the offending player being kicked from the server. - ---If the bantime is greater than 0, the player will be unable to reconnect to the server until that time has passed.]] ---L.help_damage_log = "Each time a player is damaged, a damage log entry is added to the console if enabled. This can also be stored to disk after a round has ended. The file is located at 'data/terrortown/logs/'" ---L.help_spawn_waves = [[ ---If this variable is set to 0, all players are spawned at once. For servers with huge amounts of players, it can be beneficial to spawn the players in waves. The spawn wave interval is the time between each spawn wave. A spawn wave always spawns as many players as there are valid spawn points. - ---Note: Make sure that the preparing time is long enough for the desired amount of spawn waves.]] ---L.help_voicechat_battery = [[ ---Voice chatting with enabled voice chat battery reduces battery charge. When it's empty, the player can't use voice chat and has to wait for it to recharge. This can help to prevent excessive voice chat usage. - ---Note: 'Tick' refers to a game tick. For example, if the tick rate is set to 66, then it will be 1/66th of a second.]] ---L.help_ply_spawn = "Player settings that are used on player (re-)spawn." ---L.help_haste_mode = [[ ---Haste mode balances the game by increasing the round time with every dead player. Only roles that see missing in action players can see the real round time. Every other role can only see the haste mode starting time. - ---If haste mode is enabled, the fixed round time is ignored.]] ---L.help_round_limit = "After one of the set limit conditions is met, a map change is triggered." ---L.help_armor_balancing = "The following values can be used to balance the armor." ---L.help_item_armor_classic = "If classic armor mode is enabled, only the previous settings matter. Classic armor mode means that a player can only buy armor once in a round, and that this armor blocks 30% of the incoming bullet and crowbar damage until they die." ---L.help_item_armor_dynamic = [[ ---Dynamic armor is the TTT2 approach to make armor more interesting. The amount of armor that can be bought is now unlimited, and the armor value stacks. Getting damaged decreases the armor value. The armor value per bought armor item is set in the 'Equipment Settings' of said item. - ---When taking damage, a certain percentage of this damage is converted into armor damage, a different percentage is still applied to the player and the rest vanishes. - ---If reinforced armor is enabled, the damage applied to the player is decreased by 15% as long as the armor value is above the reinforcement threshold.]] ---L.help_sherlock_mode = "The sherlock mode is the classic TTT mode. If the sherlock mode is disabled, dead bodies can not be confirmed, the scoreboard shows everyone as alive and the spectators can talk to the living players." ---L.help_prop_possession = [[ ---Prop possession can be used by spectators to possess props lying in the world and use the slowly recharging 'punch-o-meter' to move said prop around. - ---The maximum value of the 'punch-o-meter' consists of a possession base value, where the kills/deaths difference clamped inbetween two defined limits is added. The meter slowly recharges over time. The set recharge time is the time needed to recharge a single point in the 'punch-o-meter'.]] ---L.help_karma = "Players start with a certain amount of Karma, and lose it when they damage/kill teammates. The amount they lose is dependent on the Karma of the person they hurt or killed. Lower Karma reduces damage given." ---L.help_karma_strict = "If strict Karma is enabled, the damage penalty increases more quickly as Karma goes down. When it is off, the damage penalty is very low when people stay above 800. Enabling strict mode makes Karma play a larger role in discouraging any unnecessary kills, while disabling it results in a more “loose” game where Karma only hurts players who constantly kill teammates." ---L.help_karma_max = "Setting the value of the max Karma above 1000 doesn't give a damage bonus to players with more than 1000 Karma. It can be used as a Karma buffer." ---L.help_karma_ratio = "The ratio of the damage that is used to compute how much of the victim's Karma is subtracted from the attacker's if both are in the same team. If a team kill happens, a further penalty is applied." ---L.help_karma_traitordmg_ratio = "The ratio of the damage that is used to compute how much of the victim's Karma is added to the attacker's if both are in different teams. If an enemy kill happens, a further bonus is applied." ---L.help_karma_bonus = "There are also two different passive ways to gain Karma during a round. First is a karma restoration which applied to every player at the round end. Then a secondary clean round bonus is given if no teammates were hurt or killed by a player." ---L.help_karma_clean_half = [[ ---When a player's Karma is above the starting level (meaning the Karma max has been configured to be higher than that), all their Karma increases will be reduced based on how far their Karma is above that starting level. So it goes up slower the higher it is. - ---This reduction goes in a curve of exponential decay: initially it's fast, and it slows down as the increment gets smaller. This convar sets at what point the bonus has been halved (so the half-life). With the default value of 0.25, if the starting amount of Karma is 1000 and the max 1500, and a player has Karma 1125 ((1500 - 1000) * 0.25 = 125), then his clean round bonus will be 30 / 2 = 15. So to make the bonus go down faster you’d set this convar lower, to make it go down slower you’d increase it towards 1.]] ---L.help_max_slots = "Sets the maximum amount of weapons per slot. '-1' means that there is no limit." ---L.help_item_armor_value = "This is the armor value given by the armor item in dynamic mode. If classic mode is enabled (see 'Administration' -> 'Player Settings') then every value greater than 0 is counted as existing armor." - ---L.label_killer_dna_range = "Max kill range to leave DNA" ---L.label_killer_dna_basetime = "Sample life base time" ---L.label_dna_scanner_slots = "DNA sample slots" ---L.label_dna_radar = "Enable classic DNA scanner mode" ---L.label_dna_radar_cooldown = "DNA scanner cooldown" ---L.label_radar_charge_time = "Recharge time after being used" ---L.label_crowbar_shove_delay = "Cooldown after crowbar push" ---L.label_idle = "Enable idle mode" ---L.label_idle_limit = "Maximum idle time in seconds" ---L.label_namechange_kick = "Enable name change kick" ---L.label_namechange_bantime = "Banned time in minutes after kick" ---L.label_log_damage_for_console = "Enable damage logging in console" ---L.label_damagelog_save = "Save damage log to disk" ---L.label_debug_preventwin = "Prevent any win condition [debug]" ---L.label_bots_are_spectators = "Bots are always spectators" ---L.label_tbutton_admin_show = "Show traitor buttons to admins" ---L.label_ragdoll_carrying = "Enable ragdoll carrying" ---L.label_prop_throwing = "Enable prop throwing" ---L.label_ragdoll_pinning = "Enable ragdoll pinning for non-Innocent roles" ---L.label_ragdoll_pinning_innocents = "Enable ragdoll pinning for Innocent roles" ---L.label_weapon_carrying = "Enable weapon carrying" ---L.label_weapon_carrying_range = "Weapon carry range" ---L.label_prop_carrying_force = "Prop pickup force" ---L.label_teleport_telefrags = "Kill blocking player(s) when teleporting (telefrag)" ---L.label_allow_discomb_jump = "Allow disco jump for grenade thrower" ---L.label_spawn_wave_interval = "Spawn wave interval in seconds" ---L.label_voice_enable = "Enable voice chat" ---L.label_voice_drain = "Enable the voice chat battery feature" ---L.label_voice_drain_normal = "Drain per tick for normal players" ---L.label_voice_drain_admin = "Drain per tick for admins and public policing roles" ---L.label_voice_drain_recharge = "Recharge rate per tick of not voice chatting" ---L.label_locational_voice = "Enable proximity voice chat for living players" ---L.label_armor_on_spawn = "Player armor on (re-)spawn" ---L.label_prep_respawn = "Enable instant respawn during preparing phase" ---L.label_preptime_seconds = "Preparing time in seconds" ---L.label_firstpreptime_seconds = "First preparing time in seconds" ---L.label_roundtime_minutes = "Fixed round time in minutes" ---L.label_haste = "Enable haste mode" ---L.label_haste_starting_minutes = "Haste mode starting time in minutes" ---L.label_haste_minutes_per_death = "Additional time in minutes per death" ---L.label_posttime_seconds = "Postround time in seconds" ---L.label_round_limit = "Upper limit of rounds" ---L.label_time_limit_minutes = "Upper limit of playtime in minutes" ---L.label_nade_throw_during_prep = "Enable grenade throwing during preparing time" ---L.label_postround_dm = "Enable deathmatch after round ended" ---L.label_session_limits_enabled = "Enable session limits" ---L.label_spectator_chat = "Enable spectators chatting with everybody" ---L.label_lastwords_chatprint = "Print last words to chat if killed while typing" ---L.label_identify_body_woconfirm = "Identify corpse without pressing the 'confirm' button" ---L.label_announce_body_found = "Announce that a body was found" ---L.label_confirm_killlist = "Announce kill list of confirmed corpse" ---L.label_inspect_detective_only = "Limit corpse search to policing roles only" ---L.label_confirm_detective_only = "Limit corpse confirmation to policing roles only" ---L.label_dyingshot = "Shoot on death if in ironsights [experimental]" ---L.label_armor_block_headshots = "Enable armor blocking headshots" ---L.label_armor_block_blastdmg = "Enable armor blocking blast damage" ---L.label_armor_dynamic = "Enable dynamic armor" ---L.label_armor_value = "Amount of armor given by the armor item" ---L.label_armor_damage_block_pct = "Damage percentage taken by armor" ---L.label_armor_damage_health_pct = "Damage percentage taken by player" ---L.label_armor_enable_reinforced = "Enable reinforced armor" ---L.label_armor_threshold_for_reinforced = "Reinforced armor threshold" ---L.label_sherlock_mode = "Enable sherlock mode" ---L.label_highlight_admins = "Highlight server admins" ---L.label_highlight_dev = "Highlight TTT2 developer" ---L.label_highlight_vip = "Highlight TTT2 supporter" ---L.label_highlight_addondev = "Highlight TTT2 addon developer" ---L.label_highlight_supporter = "Highlight others" ---L.label_enable_hud_element = "Enable {elem} HUD element" ---L.label_spec_prop_control = "Enable prop possession" ---L.label_spec_prop_base = "Possession base value" ---L.label_spec_prop_maxpenalty = "Lower possession bonus limit" ---L.label_spec_prop_maxbonus = "Upper possession bonus limit" ---L.label_spec_prop_force = "Possession push force" ---L.label_spec_prop_rechargetime = "Recharge time in seconds" ---L.label_doors_force_pairs = "Force close-by doors as double doors" ---L.label_doors_destructible = "Enable destructible doors" ---L.label_doors_locked_indestructible = "Initially locked doors are indestructible" ---L.label_doors_health = "Door health" ---L.label_doors_prop_health = "Destructed door health" ---L.label_minimum_players = "Minimum player amount to start round" ---L.label_karma = "Enable Karma" ---L.label_karma_strict = "Enable strict Karma" ---L.label_karma_starting = "Starting Karma" ---L.label_karma_max = "Maximum Karma" ---L.label_karma_ratio = "Penalty ratio for team damage" ---L.label_karma_kill_penalty = "Kill penalty for team kill" ---L.label_karma_round_increment = "Karma restoration" ---L.label_karma_clean_bonus = "Clean round bonus" ---L.label_karma_traitordmg_ratio = "Bonus ratio for enemy damage" ---L.label_karma_traitorkill_bonus = "Kill bonus for enemy kill" ---L.label_karma_clean_half = "Clean round bonus reduction" ---L.label_karma_persist = "Karma persists over map changes" ---L.label_karma_low_autokick = "Automatically kick players with low Karma" ---L.label_karma_low_amount = "Low Karma threshold" ---L.label_karma_low_ban = "Ban picked players with low Karma" ---L.label_karma_low_ban_minutes = "Ban time in minutes" ---L.label_karma_debugspam = "Enable debug output to console about Karma changes" ---L.label_max_melee_slots = "Max melee slots" ---L.label_max_secondary_slots = "Max secondary slots" ---L.label_max_primary_slots = "Max primary slots" ---L.label_max_nade_slots = "Max grenade slots" ---L.label_max_carry_slots = "Max carry slots" ---L.label_max_unarmed_slots = "Max unarmed slots" ---L.label_max_special_slots = "Max special slots" ---L.label_max_extra_slots = "Max extra slots" ---L.label_weapon_autopickup = "Enable automatic weapon pickup" ---L.label_sprint_enabled = "Enable sprinting" ---L.label_sprint_max = "Max sprinting stamina" ---L.label_sprint_stamina_consumption = "Stamina consumption factor" ---L.label_sprint_stamina_regeneration = "Stamina regeneration factor" ---L.label_sprint_crosshair = "Show crosshair while sprinting" ---L.label_crowbar_unlocks = "Primary attack can be used as interaction (i.e. unlocking)" ---L.label_crowbar_pushforce = "Crowbar push force" +L.submenu_administration_administration_title = "Administration" +L.submenu_administration_voicechat_title = "Sprachchat / Textchat" +L.submenu_administration_round_setup_title = "Rundeneinstellungen" +L.submenu_administration_mapentities_title = "Karten Objekte" +L.submenu_administration_inventory_title = "Inventar" +L.submenu_administration_karma_title = "Karma" +L.submenu_administration_sprint_title = "Sprinten" +L.submenu_administration_playersettings_title = "Spielereinstellungen" + +L.header_roles_special_settings = "Spezialrolleneinstellungen" +L.header_equipment_additional = "Zusätzliche Ausrüstungseinstellungen" +L.header_administration_general = "Allgemeine administrative Einstellungen" +L.header_administration_logging = "Protokollierung" +L.header_administration_misc = "Verschiedenes" +L.header_entspawn_plyspawn = "Player Spawn Einstellungen" +L.header_voicechat_general = "Allgemeine Sprachchat Einstellungen" +L.header_voicechat_battery = "Sprachchat Batterie" +L.header_voicechat_locational = "Proximity Sprachchat" +L.header_playersettings_plyspawn = "Player Spawn Einstellungen" +L.header_round_setup_prep = "Runde: Vorbereitung" +L.header_round_setup_round = "Runde: Aktiv" +L.header_round_setup_post = "Runde: Beendet" +L.header_round_setup_map_duration = "Karten Sitzung" +L.header_textchat = "Textchat" +L.header_round_dead_players = "Einstellungen für tote Spieler" +L.header_administration_scoreboard = "Scoreboardeinstellungen" +L.header_hud_toggleable = "Einblendbare HUD-Elemente" +L.header_mapentities_prop_possession = "Prop Übernahme" +L.header_mapentities_doors = "Türen" +L.header_karma_tweaking = "Karma-Anpassung" +L.header_karma_kick = "Karma Kick und Ban" +L.header_karma_logging = "Karma Protokollierung" +L.header_inventory_gernal = "Inventargröße" +L.header_inventory_pickup = "Inventar Waffenaufnahme" +L.header_sprint_general = "Sprinteinstellungen" +L.header_playersettings_armor = "Rüstungssystem Einstellungen" + +L.help_killer_dna_range = "Wenn ein Spieler von einem anderen Spieler getötet wird, wird eine DNA-Probe auf ihrem Körper hinterlassen. Die untenstehende Einstellung definiert die maximale Entfernung in Hammer-Einheiten, in der DNA-Proben hinterlassen werden. Wenn der Mörder weiter entfernt ist als dieser Wert, wenn das Opfer stirbt, wird keine Probe auf der Leiche hinterlassen." +L.help_killer_dna_basetime = "Die Grundzeit in Sekunden, bis eine DNA-Probe zerfällt, wenn der Mörder 0 Hammer-Einheiten entfernt ist. Je weiter der Mörder entfernt ist, desto weniger Zeit wird der DNA-Probe gegeben, um zu zerfallen." +L.help_dna_radar = "Der TTT2 DNA-Scanner zeigt die genaue Entfernung und Richtung der ausgewählten DNA-Probe, wenn er ausgerüstet ist. Es gibt jedoch auch einen klassischen DNA-Scanner-Modus, der die ausgewählte Probe mit einer In-World-Anzeige aktualisiert, sobald die Abklingzeit abgelaufen ist." +L.help_idle = "Der Idle-Modus wird verwendet, um inaktive Spieler zwangsweise in den Zuschauermodus zu versetzen. Um diesen Modus zu verlassen, müssen die Spieler ihn in ihrem 'Gameplay'-Menü deaktivieren." +L.help_namechange_kick = [[ +Eine Namensänderung während einer laufenden Runde könnte missbraucht werden. Daher ist dies standardmäßig verboten und führt dazu, dass der betroffene Spieler vom Server gekickt wird. + +Wenn die Sperrzeit größer als 0 ist, wird der Spieler erst nach Ablauf dieser Zeit wieder in der Lage sein, sich erneut mit dem Server zu verbinden.]] +L.help_damage_log = "Jedes Mal, wenn ein Spieler Schaden erleidet, wird ein Schadensprotokolleintrag in der Konsole hinzugefügt, wenn dies aktiviert ist. Dies kann auch auf die Festplatte gespeichert werden, nachdem eine Runde beendet wurde. Die Datei befindet sich unter 'data/terrortown/logs/'" +L.help_spawn_waves = [[ +Wenn diese Variable auf 0 gesetzt ist, werden alle Spieler auf einmal gespawnt. Für Server mit einer großen Anzahl von Spielern kann es vorteilhaft sein, die Spieler in Wellen zu spawnen. Der Spawnwellenintervall ist die Zeit zwischen jeder Spawnwelle. Eine Spawnwelle spawnt immer so viele Spieler wie es gültige Spawnpunkte gibt. + +Hinweis: Stellen Sie sicher, dass die Vorbereitungszeit lang genug für die gewünschte Anzahl von Spawnwellen ist.]] +L.help_voicechat_battery = [[ +Die Verwendung des Sprachchats mit aktivierter Sprachchat-Batterie reduziert dabei die Batterieladung. Wenn die Batterie leer ist, kann der Spieler den Sprachchat nicht mehr verwenden und muss warten, bis sie sich wieder auflädt. Dies kann dazu beitragen, den übermäßigen Gebrauch des Sprachchats zu verhindern. + +Hinweis: 'Tick' bezieht sich auf einen Spieltick. Wenn beispielsweise die Tickrate auf 66 eingestellt ist, entspricht dies 1/66 Sekunde.]] +L.help_ply_spawn = "Spieler-Einstellungen, die beim Spieler (Neu-)Spawn verwendet werden." +L.help_haste_mode = [[ +Der Hast-Modus balanciert das Spiel, indem er die Rundenzeit mit jedem getöteten Spieler erhöht. Nur Rollen, die tote Spieler sehen können, können die tatsächliche Rundenzeit sehen. Alle anderen Rollen können nur die Startzeit des Hast-Modus sehen. + +Wenn der Hast-Modus aktiviert ist, wird die festgelegte Rundenzeit ignoriert.]] +L.help_round_limit = "Nachdem eine der festgelegten Bedingungen erfüllt ist, wird ein Kartenwechsel ausgelöst." +L.help_armor_balancing = "Die folgenden Werte können verwendet werden, um die Rüstung auszugleichen." +L.help_item_armor_classic = "Wenn der klassische Rüstungsmodus aktiviert ist, sind nur die vorangegangenen Einstellungen relevant. Im klassischen Rüstungsmodus kann ein Spieler in einer Runde nur einmal Rüstung kaufen, und diese Rüstung blockiert 30% des eintreffenden Kugel- und Brechstangenschadens, bis sie sterben." +L.help_item_armor_dynamic = [[ +Die dynamische Rüstung ist der Ansatz von TTT2, um Rüstung interessanter zu gestalten. Die Menge an Rüstung, die gekauft werden kann, ist jetzt unbegrenzt, und der Rüstungswert stapelt sich. Bei Verletzungen wird der Rüstungswert verringert. Der Rüstungswert pro gekauftem Rüstungsgegenstand wird in den 'Ausrüstungseinstellungen' dieses Gegenstands festgelegt. + +Bei Schäden wird ein bestimmter Prozentsatz dieses Schadens in Rüstungsschaden umgewandelt, ein anderer Prozentsatz wird immer noch auf den Spieler angewendet und der Rest verschwindet. + +Wenn verstärkte Rüstung aktiviert ist, wird der dem Spieler zugefügte Schaden um 15% verringert, solange der Rüstungswert über der Verstärkungsschwelle liegt.]] +L.help_sherlock_mode = "Der Sherlock-Modus ist der klassische TTT-Modus. Wenn der Sherlock-Modus deaktiviert ist, können tote Körper nicht bestätigt werden, das Scoreboard zeigt jeden als lebendig an, und die Zuschauer können mit den lebenden Spielern sprechen." +L.help_prop_possession = [[ +Die Prop-Übernahme kann von Zuschauern verwendet werden, um im Spiel liegende Gegenstände zu besitzen und den langsam wieder aufladenden 'Schlagzähler' zu verwenden, um den genannten Gegenstand zu bewegen. + +Der maximale Wert des 'Schlagzählers' berechnet sich aus dem Grundwert für die Prop Übernahme und dem Addieren der Differenz zwischen Kills und Toden zwischen den zwei definierten Grenzen. Der Zähler lädt sich im Laufe der Zeit langsam auf. Die eingestellte Aufladezeit ist die Zeit, die benötigt wird, um einen einzelnen Punkt im 'Schlagzähler' aufzuladen.]] +L.help_karma = "Spieler starten mit einer bestimmten Menge an Karma und verlieren es, wenn sie Teammitglieder verletzen/töten. Die Menge, die sie verlieren, hängt vom Karma der Person ab, die sie verletzt oder getötet haben. Niedrigeres Karma reduziert den verursachten Schaden." +L.help_karma_strict = "Wenn das strenge Karma aktiviert ist, erhöht sich die Schadensstrafe schneller, wenn das Karma sinkt. Wenn es deaktiviert ist, ist die Schadensstrafe sehr gering, solange die Spieler über 800 bleiben. Das Aktivieren des strengen Modus bewirkt, dass das Karma eine größere Rolle spielt, um unnötige Tötungen zu verhindern, während die Deaktivierung zu einem 'lockeren' Spiel führt, bei dem das Karma nur Spieler bestraft, die ständig Teammitglieder töten." +L.help_karma_max = "Das Festlegen des Werts des maximalen Karma über 1000 verleiht Spielern mit mehr als 1000 Karma keinen Schadensbonus. Es kann als Karma-Puffer verwendet werden." +L.help_karma_ratio = "Das Verhältnis des Schadens, das zur Berechnung abgezogen wird, um festzulegen, wie viel des Opferkarmas vom Angreifer abgezogen wird, wenn beide im selben Team sind. Bei einem Team-Kill wird eine zusätzliche Strafe verhängt." +L.help_karma_traitordmg_ratio = "Das Verhältnis des Schadens, das zur Berechnung verwendet wird, um festzulegen, wie viel des Opferkarmas dem Angreifer hinzugefügt wird, wenn beide in unterschiedlichen Teams sind. Bei einem Feind-Kill wird ein weiterer Bonus angewendet." +L.help_karma_bonus = "Es gibt auch zwei verschiedene passive Möglichkeiten, während einer Runde Karma zu gewinnen. Erstens gibt es eine Karma-Wiederherstellung, die am Ende der Runde auf jeden Spieler angewendet wird. Dann wird ein sekundärer 'Saubere Runde'-Bonus vergeben, wenn kein Teammitglied von einem Spieler verletzt oder getötet wurde." +L.help_karma_clean_half = [[ +Wenn das Karma eines Spielers über dem Startniveau liegt (was bedeutet, dass das maximale Karma so konfiguriert wurde, dass es höher ist), werden alle Karma-Erhöhungen reduziert, basierend darauf, wie weit ihr Karma über diesem Startniveau liegt. Das bedeutet, dass es langsamer steigt, je höher es ist. + +Diese Reduzierung erfolgt in einer Kurve exponentiellen Zerfalls: Anfangs ist sie schnell und verlangsamt sich, wenn die Inkremente kleiner werden. Diese Einstellung legt fest, an welchem Punkt der Bonus halbiert wurde (also die Halbwertszeit). Mit dem Standardwert von 0,25, wenn die Startmenge des Karmas 1000 beträgt und das Maximum 1500 beträgt, und ein Spieler Karma 1125 hat ((1500 - 1000) * 0,25 = 125), dann wird sein Saubere-Runde-Bonus 30 / 2 = 15 betragen. Um den Bonus schneller zu verringern, würde man diesen Wert niedriger einstellen, um ihn langsamer zu verringern, würde man ihn in Richtung 1 erhöhen.]] +L.help_max_slots = "Setzt die maximale Anzahl von Waffen pro Slot. '-1' bedeutet, dass es keine Begrenzung gibt." +L.help_item_armor_value = "Dies ist der Rüstungswert, der durch den Rüstungsgegenstand im dynamischen Modus verliehen wird. Wenn der klassische Modus aktiviert ist (siehe 'Administration' -> 'Spieler-Einstellungen'), wird jeder Wert größer als 0 als vorhandene Rüstung betrachtet." + +L.label_killer_dna_range = "Maximale Tötungsentfernung, um DNA zu hinterlassen" +L.label_killer_dna_basetime = "Basiszeit für die Lebensdauer der Probe" +L.label_dna_scanner_slots = "DNA-Proben Slots" +L.label_dna_radar = "Aktiviere den klassischen DNA Scanner Modus" +L.label_dna_radar_cooldown = "DNA Scanner Abklingzeit" +L.label_radar_charge_time = "Aufladezeit nach Verwendung" +L.label_crowbar_shove_delay = "Abklingzeit nach einem Brechstangenschubser" +L.label_idle = "Aktiviere den Inaktivitäts Modus" +L.label_idle_limit = "Maximale inaktive Zeit in Sekunden" +L.label_namechange_kick = "Aktiviere Kick bei Namensänderung" +L.label_namechange_bantime = "Sperrzeit in Minuten nach dem Kick" +L.label_log_damage_for_console = "Aktiviere Schadensprotokollierung in der Konsole" +L.label_damagelog_save = "Speichere Schadensprotokollierung auf der Festplatte" +L.label_debug_preventwin = "Verhindere jegliche Gewinnbedingung [debug]" +L.label_bots_are_spectators = "Bots sind immer Zuschauer" +L.label_tbutton_admin_show = "Zeige Verräterknöpfe für Admins" +L.label_ragdoll_carrying = "Aktiviere das Tragen von Ragdolls" +L.label_prop_throwing = "Aktiviere das Werfen von Props" +L.label_weapon_carrying = "Aktiviere das Tragen von Waffen" +L.label_weapon_carrying_range = "Tragreichweite für Waffen" +L.label_prop_carrying_force = "Prop Aufhebkraft" +L.label_teleport_telefrags = "Töte blockierende Spieler beim Teleportieren (telefrag)" +L.label_allow_discomb_jump = "Erlaube Discombobulator-Sprünge für den Werfer" +L.label_spawn_wave_interval = "Intervall zwischen den Spawn-Wellen in Sekunden" +L.label_voice_enable = "Aktiviere Sprachchat" +L.label_voice_drain = "Aktiviere die Sprachchat Batterie" +L.label_voice_drain_normal = "Entladung pro Tick für normale Spieler" +L.label_voice_drain_admin = "Entladung pro Tick für Admins und öffentliche Ordnungsrollen" +L.label_voice_drain_recharge = "Aufladungsrate pro Tick wenn nicht gesprochen wird" +L.label_locational_voice = "Aktiviere Proximity Sprachchat für lebende Spieler" +L.label_armor_on_spawn = "Spielerrüstung beim (Neu-)Spawnen" +L.label_prep_respawn = "Aktiviere automatischen Respawn während der Vorbereitungszeit" +L.label_preptime_seconds = "Vorbereitungszeit in Sekunden" +L.label_firstpreptime_seconds = "Erste Vorbereitungszeit in Sekunden" +L.label_roundtime_minutes = "Fixe Rundenzeit in Minuten" +L.label_haste = "Aktiviere Hast Modus" +L.label_haste_starting_minutes = "Hast Modus Startzeit in Minuten" +L.label_haste_minutes_per_death = "Zusätzliche Zeit pro Tod in Minuten" +L.label_posttime_seconds = "Zeit nach der Runde in Sekunden" +L.label_round_limit = "Rundenlimit" +L.label_time_limit_minutes = "Spielzeitlimit" +L.label_nade_throw_during_prep = "Erlaube das Werfen von Granaten während der Vorbereitungszeit" +L.label_postround_dm = "Aktiviere Deathmatch nach Rundenende" +L.label_session_limits_enabled = "Aktiviere Sitzungs Limitierungen" +L.label_spectator_chat = "Aktiviere, dass Zuschauer mit jedem chatten können" +L.label_lastwords_chatprint = "Gib die letzten Worte im Chat aus, wenn der Spieler getötet wird, während er tippt" +L.label_identify_body_woconfirm = "Leichnam identifizieren, ohne die Schaltfläche 'Tod Bestätigen' drücken zu müssen" +L.label_announce_body_found = "Ankündigen, dass ein Leichnam gefunden wurde, wenn dieser bestätigt wird" +L.label_confirm_killlist = "Die Todesliste des bestätigten Leichnams verkünden" +L.label_dyingshot = "Beim Tod schießen, wenn Visier aktiv [experimentell]" +L.label_armor_block_headshots = "Aktiviere das Blockieren von Kopfschüssen durch Rüstung" +L.label_armor_block_blastdmg = "Aktiviere das Blockieren von Explosionsschaden durch Rüstung" +L.label_armor_dynamic = "Aktiviere dynamische Rüstung" +L.label_armor_value = "Anzahl an Rüstung welche vom Rüstungsgegenstand gegeben wird" +L.label_armor_damage_block_pct = "Prozentsatz des durch die Rüstung aufgenommenen Schadens" +L.label_armor_damage_health_pct = "Prozentsatz des durch den Spieler aufgenommenen Schadens" +L.label_armor_enable_reinforced = "Aktiviere verstärkte Rüstung" +L.label_armor_threshold_for_reinforced = "Schwelle für verstärkte Rüstung" +L.label_sherlock_mode = "Aktiviere den Sherlock Modus" +L.label_highlight_admins = "Server Admins hervorherben" +L.label_highlight_dev = "TTT2 Entwickler hervorherben" +L.label_highlight_vip = "TTT2 Supporter hervorherben" +L.label_highlight_addondev = "TTT2 Addon Entwickler hervorherben" +L.label_highlight_supporter = "Andere hervorherben" +L.label_enable_hud_element = "Aktiviere das {elem} HUD Element" +L.label_spec_prop_control = "Aktiviere Prop Übernahme" +L.label_spec_prop_base = "Übernahme Basiswert" +L.label_spec_prop_maxpenalty = "Untere Übernahme-Bonus-Grenze" +L.label_spec_prop_maxbonus = "Obere Übernahme-Bonus-Grenze" +L.label_spec_prop_force = "Prop Übernahme Stoßkraft" +L.label_spec_prop_rechargetime = "Aufladezeit in Sekunden" +L.label_doors_force_pairs = "Behandle nahestehende Türen als Doppeltüren" +L.label_doors_destructible = "Aktiviere zerstörbare Türen" +L.label_doors_locked_indestructible = "Standardmäßig versperrte Türen sind unzerstörbar" +L.label_doors_health = "Tür-Lebenspunkte" +L.label_doors_prop_health = "Zerstörte-Tür-Lebenspunkte" +L.label_minimum_players = "Mindestspieleranzahl, um die Runde zu starten" +L.label_karma = "Aktiviere Karma" +L.label_karma_strict = "Aktiviere strenges Karma" +L.label_karma_starting = "Initiales Karma" +L.label_karma_max = "Maximales Karma" +L.label_karma_ratio = "Strafverhältnis für Team-Schäden" +L.label_karma_kill_penalty = "Strafverhältnis für Team-Tötungen" +L.label_karma_round_increment = "Karma Wiederherstellung" +L.label_karma_clean_bonus = "Saubere Runde Bonus" +L.label_karma_traitordmg_ratio = "Bonusverhältnis für Schäden an Feinden" +L.label_karma_traitorkill_bonus = "Bonus für das Töten von Feinden" +L.label_karma_clean_half = "Reduzierung des 'Saubere-Runde'-Bonus" +L.label_karma_persist = "Karma bleibt über Kartenwechsel erhalten" +L.label_karma_low_autokick = "Spieler mit niedrigem Karma automatisch rauswerfen" +L.label_karma_low_amount = "Schwellenwert für niedriges Karma" +L.label_karma_low_ban = "Banne Spieler mit zu niedrigem Karma" +L.label_karma_low_ban_minutes = "Bann-Zeit in Minuten" +L.label_karma_debugspam = "Aktiviere Debug-Ausgabe in die Konsole über Karma-Veränderungen" +L.label_max_melee_slots = "Maximale Nahkampf Slots" +L.label_max_secondary_slots = "Maximale Sekundär Slots" +L.label_max_primary_slots = "Maximale Primär Slots" +L.label_max_nade_slots = "Maximale Granat Slots" +L.label_max_carry_slots = "Maximale Trage Slots" +L.label_max_unarmed_slots = "Maximale Unbewaffnete Slots" +L.label_max_special_slots = "Maximale Spezial Slots" +L.label_max_extra_slots = "Maximale extra Slots" +L.label_weapon_autopickup = "Aktiviere automatisches Aufheben von Waffen" +L.label_sprint_enabled = "Aktiviere Sprinten" +L.label_sprint_max = "Maximale Sprint-Ausdauer" +L.label_sprint_stamina_consumption = "Faktor für den Verbrauch von Ausdauer" +L.label_sprint_stamina_regeneration = "Faktor für die Regeneration von Ausdauer" +L.label_crowbar_unlocks = "Der Primärangriff kann als Interaktion (z. B. Entsperren) verwendet werden" +L.label_crowbar_pushforce = "Brechstangen-Schubsstärke" -- 2022-07-02 ---L.header_playersettings_falldmg = "Fall Damage Settings" +L.header_playersettings_falldmg = "Fallschaden Einstellungen" ---L.label_falldmg_enable = "Enable fall damage" ---L.label_falldmg_min_velocity = "Minimum velocity threshold for fall damage to occur" ---L.label_falldmg_exponent = "Exponent to increase fall damage in relation to velocity" +L.label_falldmg_enable = "Aktiviere Fallschaden" +L.label_falldmg_min_velocity = "Mindestgeschwindigkeit, bei der Fallschaden auftritt." +L.label_falldmg_exponent = "Exponent mit dem der Fallschaden in Relation zur Geschwindigkeit erhöht wird" ---L.help_falldmg_exponent = [[ ---This value modifies how exponentially fall damage is increased with the speed the player hits the ground at. +L.help_falldmg_exponent = [[ +Dieser Wert verändert, wie exponentiell der Fallschaden mit der Geschwindigkeit zunimmt, mit der der Spieler auf den Boden aufprallt. ---Take care when altering this value. Setting it too high can make even the smallest falls lethal, while setting it too low will allow players to fall from extreme heights and suffer little to no damage.]] +Sei vorsichtig bei der Änderung dieses Werts. Wenn er zu hoch eingestellt wird, kann selbst der kleinste Sturz tödlich sein, während ein zu niedriger Wert es Spielern ermöglicht, aus extremen Höhen zu fallen und nur wenig oder gar keinen Schaden zu erleiden.]] -- 2023-02-08 ---L.testpopup_title = "A Test Popup, now with a multiline title, how NICE!" ---L.testpopup_subtitle = "Well, hello there! This is a fancy popup with some special information. The text can be also multiline, how fancy! Ugh, I could add so much more text if I'd had any ideas..." - ---L.hudeditor_chat_hint1 = "[TTT2][INFO] Hover over an element, press and hold [LMB] and move the mouse to MOVE or RESIZE it." ---L.hudeditor_chat_hint2 = "[TTT2][INFO] Press and hold the ALT key for symmetric resizing." ---L.hudeditor_chat_hint3 = "[TTT2][INFO] Press and hold the SHIFT key to move on axis and to keep the aspect ratio." ---L.hudeditor_chat_hint4 = "[TTT2][INFO] Press [RMB] -> 'Close' to exit the HUD Editor!" - ---L.guide_nothing_title = "Nothing here yet!" ---L.guide_nothing_desc = "This is work in progress, help us by contributing to the project on GitHub." - ---L.sb_rank_tooltip_developer = "TTT2 Developer" ---L.sb_rank_tooltip_vip = "TTT2 Supporter" ---L.sb_rank_tooltip_addondev = "TTT2 Addon Developer" ---L.sb_rank_tooltip_admin = "Server Admin" ---L.sb_rank_tooltip_streamer = "Streamer" ---L.sb_rank_tooltip_heroes = "TTT2 Heroes" ---L.sb_rank_tooltip_team = "Team" - ---L.tbut_adminarea = "ADMIN AREA:" \ No newline at end of file +L.testpopup_title = "Ein Test Popup, jetzt auch mit Mehrzeilen-Titel, wie cool!" +L.testpopup_subtitle = "Nun, hallo! Dies ist ein schickes Popup mit einigen besonderen Informationen. Der Text kann auch mehrzeilig sein, wie schick! Oh, ich könnte noch so viel mehr Text hinzufügen, wenn ich Ideen hätte..." + +L.hudeditor_chat_hint1 = "[TTT2][INFO] Fahre mit der Maus über ein Element, drücke und halte [LMT] und bewege die Maus, um es zu VERSCHIEBEN oder zu VERGRÖßERN." +L.hudeditor_chat_hint2 = "[TTT2][INFO] Halte die ALT-Taste gedrückt, um eine symmetrische Vergrößerung/Verkleinerung durchzuführen." +L.hudeditor_chat_hint3 = "[TTT2][INFO] Halte die UMSCHALT-Taste gedrückt, um auf der Achse zu bewegen und das Seitenverhältnis beizubehalten." +L.hudeditor_chat_hint4 = "[TTT2][INFO] Drücke [RMT] -> 'Schließen' um den HUD Editor zu beenden!" + +L.guide_nothing_title = "Hier gibt es noch nichts zu sehen!" +L.guide_nothing_desc = "Dieser Bereich ist noch in Arbeit. Hilf uns dabei, indem du auf GitHub dazu beiträgst." + +L.sb_rank_tooltip_developer = "TTT2 Entwickler" +L.sb_rank_tooltip_vip = "TTT2 Supporter" +L.sb_rank_tooltip_addondev = "TTT2 Addon Entwickler" +L.sb_rank_tooltip_admin = "Server Admin" +L.sb_rank_tooltip_streamer = "Streamer" +L.sb_rank_tooltip_heroes = "TTT2 Heroes" +L.sb_rank_tooltip_team = "Team" + +L.tbut_adminarea = "ADMIN BEREICH:" + +-- 2023-08-10 +L.equipmenteditor_name_damage_scaling = "Schadensskalierung" + +-- 2023-08-11 +L.equipmenteditor_name_allow_drop = "Erlaube Fallenlassen" +L.equipmenteditor_desc_allow_drop = "Wenn aktiviert, kann Ausrüstung beliebig vom Spieler fallengelassen werden" + +L.equipmenteditor_name_drop_on_death_type = "Fallenlassen beim Tod" +L.equipmenteditor_desc_drop_on_death_type = "Versuche, die Aktion zu überschreiben, die festlegt, ob die Ausrüstung beim Tod des Spielers fallengelassen wird." + +L.drop_on_death_type_default = "Standard (pro Waffe definiert)" +L.drop_on_death_type_force = "Erzwinge Fallenlassen beim Tod" +L.drop_on_death_type_deny = "Verhindere Fallenlassen beim Tod" + +-- 2023-08-26 +L.equipmenteditor_name_kind = "Ausrüstungs Slot" +L.equipmenteditor_desc_kind = "Der Inventar Slot, welcher von Ausrüstungsgegenständen verwendet wird" + +L.slot_weapon_melee = "Nahrkampf Slot" +L.slot_weapon_pistol = "Pistolen Slot" +L.slot_weapon_heavy = "Schwerer Slot" +L.slot_weapon_nade = "Granaten Slot" +L.slot_weapon_carry = "Trage Slot" +L.slot_weapon_unarmed = "Unbewaffneter Slot" +L.slot_weapon_special = "Spezial Slot" +L.slot_weapon_extra = "Extra Slot" +L.slot_weapon_class = "Klassen Slot" + +-- 2023-10-04 +L.label_voice_duck_spectator = "Dämpfe Zuschauerstimmen" +L.label_voice_duck_spectator_amount = "Zuschauerdämpfung" +L.label_voice_scaling = "Skalierungsmodus der Lautstärke der Stimme" +L.label_voice_scaling_mode_linear = "Linear" +L.label_voice_scaling_mode_power4 = "Hoch 4" +L.label_voice_scaling_mode_log = "Logarithmisch" + +-- 2023-10-07 +L.search_title = "Ergebnisse der Leichenuntersuchung - {player}" +L.search_info = "Information" +L.search_confirm = "Tod bestätigen" +L.search_confirm_credits = "Bestätigen (+{credits} Ausrüstungspunkt(e))" +L.search_take_credits = "Nehme {credits} Ausrüstungspunkt(e)" +L.search_confirm_forbidden = "Bestätigen verboten" +L.search_confirmed = "Tod bestätigt" +L.search_call = "Tod melden" +L.search_called = "Tod gemeldet" + +--L.search_team_role_unknown = "???" + +L.search_words = "Etwas sagt dir, dass die letzten Worte des Opfers \"{lastwords}\" waren." +L.search_armor = "Das Opfer trug eine nicht-standardmäßige Körperrüstung." +L.search_disguiser = "Das Opfer trug ein Gerät, dass die Identität verstecken konnte." +L.search_radar = "Das Opfer trug eine Form eines Radars. Es funktioniert nicht mehr." +L.search_c4 = "In der Tasche war eine Notiz. Sie besagt, dass das Durchschneiden des Drahtes {num} die Bombe sicher entschärfen wird." + +L.search_dmg_crush = "Viele Knochen des Opfers sind gebrochen. Es scheint, als habe der Einschlag eines schweren Objekts zum Tode geführt." +L.search_dmg_bullet = "Es ist offensichtlich, dass das Opfer erschossen wurde." +L.search_dmg_fall = "Das Opfer fiel in den Tod." +L.search_dmg_boom = "Die Wunden und die versengte Kleidung weisen auf eine Explosion hin, die das Ende bereitet hat." +L.search_dmg_club = "Der Körper ist ramponiert und verbeult. Das Opfer wurde mit Sicherheit zu Tode geprügelt." +L.search_dmg_drown = "Der Körper zeigt Anzeichen und Symptome von Ertrinken." +L.search_dmg_stab = "Das Opfer wurde stark geschnitten und hatte tiefe Wunden und verblutete schlussendlich." +L.search_dmg_burn = "Es riecht hier nach gerösteten Terroristen..." +L.search_dmg_teleport = "Es scheint, als wäre die DNA durch Tachyonen verunstaltet worden!" +L.search_dmg_car = "Als das Opfer die Straße überquerte, wurde es von einem rücksichtslosen Fahrer überrollt." +L.search_dmg_other = "Du kannst keinen spezifischen Grund für den Tod des Opfers finden." + +L.search_floor_antlions = "Es gibt immer noch Ameisenlöwen überall am Körper. Der Boden muss mit ihnen bedeckt sein." +L.search_floor_bloodyflesh = "Das Blut an diesem Körper sieht alt und eklig aus. Es sind sogar kleine Stücke blutigen Fleisches an den Schuhen kleben geblieben." +L.search_floor_concrete = "Grauer Staub bedeckt die Schuhe und Knie. Es sieht so aus, als hätte der Tatort einen Betonboden." +L.search_floor_dirt = "Es riecht erdig. Das kommt wahrscheinlich von dem Schmutz, der an den Schuhen des Opfers haftet." +L.search_floor_eggshell = "Ekelhaft aussehende weiße Flecken bedecken den Körper des Opfers. Es sieht aus wie Eierschalen." +L.search_floor_flesh = "Die Kleidung des Opfers fühlt sich irgendwie feucht an. Als ob es auf eine nasse Oberfläche gefallen wäre. Wie eine fleischige Oberfläche oder der sandige Boden eines Gewässers." +L.search_floor_grate = "Die Haut des Opfers sieht aus wie ein Steak. Dicke Linien, die in einem Raster angeordnet sind, sind überall auf ihr sichtbar. Hat es auf einem Rost gelegen?" +L.search_floor_alienflesh = "Außerirdisches Fleisch, denkst du? Klingt ziemlich abwegig. Aber dein Detektivhilfebuch führt es als mögliche Bodenoberfläche auf." +L.search_floor_snow = "Auf den ersten Blick fühlt sich die Kleidung nur nass und eiskalt an. Aber sobald du den weißen Schaum an den Rändern siehst, verstehst du es. Es ist Schnee!" +L.search_floor_plastic = "'Autsch, das muss wehtun.' Der Körper ist von Verbrennungen bedeckt. Sie sehen aus wie die, die entstehen, wenn man über eine Plastikoberfläche rutscht." +L.search_floor_metal = "Zumindest kann das Opfer jetzt keine Tetanusinfektion bekommen, da es tot ist. Rost bedeckt die Wunden. Es ist wahrscheinlich auf einer metallischen Oberfläche gestorben." +L.search_floor_sand = "Kleine grobe Steine kleben am kalten Körper des Opfers. Wie rauer Sand vom Strand. Er ist einfach überall!" +L.search_floor_foliage = "Die Natur ist wunderbar. Die blutigen Wunden des Opfers sind mit genügend Laub bedeckt, dass sie fast versteckt sind." +L.search_floor_computer = "Beep-boop. Der Körper ist von einer Computeroberfläche bedeckt! Wie sieht das aus, fragst du vielleicht? Nun, wer weiß!" +L.search_floor_slosh = "Feucht und vielleicht sogar ein wenig schleimig. Der ganze Körper ist damit bedeckt und die Kleider sind durchnässt. Es stinkt!" +L.search_floor_tile = "Kleine Scherben kleben an der Haut. Wie Scherben von Bodenfliesen, die beim Aufprall zerbrochen sind." +L.search_floor_grass = "Es riecht nach frisch geschnittenem Gras. Der Geruch überwältigt fast den Geruch von Blut und Tod." +L.search_floor_vent = "Du spürst einen frischen Luftstoß, wenn du den Körper berührst. Ist das Opfer in einem Lüftungsschacht gestorben und hat die Luft mitgenommen?" +L.search_floor_wood = "Was gibt es Schöneres, als auf einem Holzboden zu sitzen und in Gedanken zu versinken? Zumindest nicht tot auf einem Holzboden liegen!" +L.search_floor_default = "Das scheint so grundlegend, so normal. Fast standardmäßig. Du kannst nichts über die Art der Oberfläche sagen." +L.search_floor_glass = "Der Körper ist mit vielen blutigen Schnitten bedeckt. In einigen von ihnen stecken Glasscherben, die ziemlich bedrohlich aussehen." +L.search_floor_warpshield = "Ein Boden aus Warpshield? Ja, wir sind genauso verwirrt wie du es warst. Aber unsere Notizen erwähnen es eindeutig. Warpshield." + +L.search_water_1 = "Die Schuhe des Opfers sind nass, aber der Rest scheint trocken zu sein. Es wurden wahrscheinlich getötet, während die Füße im Wasser waren." +L.search_water_2 = "Die Schuhe und Hosen des Opfers sind durchnässt. Ist es durch Wasser gelaufen, bevor es getötet wurde?" +L.search_water_3 = "Der gesamte Körper ist nass und geschwollen. Es ist wahrscheinlich gestorben, während es vollständig untergetaucht war." + +L.search_weapon = "Es scheint, als wurde ein(e) {weapon} zum töten benutzt." +L.search_head = "Die tödliche Wunde war ein Kopfschuss. Keine Zeit, um zu schreien." +L.search_time = "Das Opfer wurde eine Weile bevor du die Untersuchung begonnen hast getötet." +L.search_dna = "Erlange eine Probe der DNA des Mörders mit dem DNA-Scanner. Die DNA-Probe verfällt nach einer Weile." + +L.search_kills1 = "Du fandest eine Liste an Tötungen, die den Tod von {player} beweist." +L.search_kills2 = "Du fandest eine Liste an Tötungen mit diesen Namen: {player}" +L.search_eyes = "Mit deinen Detektiv-Fähigkeiten identifizierst du die Person, die vom Opfer zuletzt gesehen wurde: {player}. Der Mörder oder ein Zufall?" + +L.search_credits = "Das Opfer hat {credits} Ausrüstungspunkt(e) in der Tasche. Eine Shopping-Rolle könnte sie an sich nehmen und sinnvoll verwenden. Behalte es im Auge!" + +L.search_kill_distance_point_blank = "Es war ein Angriff aus nächster Nähe." +L.search_kill_distance_close = "Der Angriff erfolgte aus kurzer Entfernung." +L.search_kill_distance_far = "Das Opfer wurde aus großer Entfernung angegriffen." + +L.search_kill_from_front = "Das Opfer wurde von vorne erschossen." +L.search_kill_from_back = "Das Opfer wurde von hinten erschossen." +L.search_kill_from_side = "Das Opfer wurde von der Seite erschossen." + +L.search_hitgroup_head = "Das Projektil wurde im Kopf gefunden." +L.search_hitgroup_chest = "Das Projektil wurde in der Brust gefunden." +L.search_hitgroup_stomach = "Das Projektil wurde im Bauch gefunden." +L.search_hitgroup_rightarm = "Das Projektil wurde im rechten Arm gefunden." +L.search_hitgroup_leftarm = "Das Projektil wurde im linken Arm gefunden." +L.search_hitgroup_rightleg = "Das Projektil wurde im rechten Bein gefunden." +L.search_hitgroup_leftleg = "Das Projektil wurde im linken Bein gefunden." +L.search_hitgroup_gear = "Das Projektil wurde in der Hüfte gefunden." + +L.search_policingrole_report_confirm = [[ +Eine öffentliche Ordnungsrolle kann nur zu einer Leiche gerufen werden, nachdem der Tod des Opfers bestätigt wurde.]] +L.search_policingrole_confirm_disabled_1 = [[ +Die Leiche kann nur von einer öffentlichen Ordnungsrolle bestätigt werden. Melde die Leiche, um sie zu informieren!]] +L.search_policingrole_confirm_disabled_2 = [[ +Die Leiche kann nur von einer öffentlichen Ordnungsrolle bestätigt werden. Melde die Leiche, um sie darüber zu informieren! +Du kannst die Informationen hier sehen, nachdem sie es bestätigt haben.]] +L.search_spec = [[ +Als Zuschauer kannst du alle Informationen einer Leiche sehen, bist aber nicht in der Lage, mit der Benutzeroberfläche zu interagieren.]] + +L.search_title_words = "Die letzten Worte des Opfers" +L.search_title_c4 = "Missgeschick bei der Entschärfung" +L.search_title_dmg_crush = "Quetschschaden ({amount} HP)" +L.search_title_dmg_bullet = "Kugelschaden ({amount} HP)" +L.search_title_dmg_fall = "Fallschaden ({amount} HP)" +L.search_title_dmg_boom = "Explosionsschaden ({amount} HP)" +L.search_title_dmg_club = "Schlagstockschaden ({amount} HP)" +L.search_title_dmg_drown = "Ertrinkungsschaden ({amount} HP)" +L.search_title_dmg_stab = "Stichschaden ({amount} HP)" +L.search_title_dmg_burn = "Brandschaden ({amount} HP)" +L.search_title_dmg_teleport = "Teleportationsschaden ({amount} HP)" +L.search_title_dmg_car = "Autounfall ({amount} HP)" +L.search_title_dmg_other = "Unbekannter Schaden ({amount} HP)" +L.search_title_time = "Todeszeit" +L.search_title_dna = "Zerfall von DNA-Proben" +L.search_title_kills = "Die Tötungsliste des Opfers" +L.search_title_eyes = "Der Schatten des Mörders" +L.search_title_floor = "Der Boden des Tatorts" +L.search_title_credits = "{credits} Ausrüstungspunkt(e)" +L.search_title_water = "Wasserpegel {level}" +L.search_title_policingrole_report_confirm = "Bestätige um den Tod zu melden" +L.search_title_policingrole_confirm_disabled = "Leiche melden" +L.search_title_spectator = "Du bist ein Zuschauer" + +L.target_credits_on_confirm = "Bestätige Toten, um ungenutzte Ausrüstungspunkte zu erhalten" +L.target_credits_on_search = "Untersuchen, um ungenutzte Ausrüstungspunkte zu erhalten" +L.corpse_hint_no_inspect_details = "Nur öffentliche Ordnungsrollen können Informationen über diese Leiche finden." +L.corpse_hint_inspect_limited_details = "Nur öffentliche Ordnungsrollen können diesen Körper bestätigen." +L.corpse_hint_spectator = "Drücke [{usekey}] um die Untersuchungs-UI anzuzeigen." +L.corpse_hint_public_policing_searched = "Drücke [{usekey}] um die Untersuchungsergebnisse einer öffentlichen Ordnungsrolle einzusehen" + +L.label_inspect_confirm_mode = "Wähle den Körfper-Untersuchungsmodus" +L.choice_inspect_confirm_mode_0 = "Modus 0: standard TTT" +L.choice_inspect_confirm_mode_1 = "Modus 1: limitiertes Bestätigen" +L.choice_inspect_confirm_mode_2 = "Modus 2: limitiertes Untersuchen" +L.help_inspect_confirm_mode = [[ +Es gibt drei verschiedene Körper-Untersuchungs-/Bestätigungsmodi in diesem Spielmodus. Die Auswahl dieses Modus hat einen großen Einfluss auf die Wichtigkeit von öffentlichen Ordnungsrollen wie den Detektiv. + +Modus 0: Dieser Modus ist der standard TTT-Modus. Jeder kann Körper untersuchen und bestätigen. Um einen Körper zu melden, oder die Ausrüstungspunkte zu nehmen, muss der Körper zuerst bestätigt werden. Dies macht es etwas schwerer für Shopping-Rollen heimlich an die Ausrüstungspunkte zu kommen. Jedoch müssen auch Unschuldige den Körper zuerst melden, bevor sie eine öffentliche Ordnungsrolle rufen können. + +Modus 1: Dieser Modus erhöht die Wichtigkeit von öffentlichen Ordnungsrollen, indem die Bestätigungsoption auf diese limitiert wird. Das bedeutet auch, dass das Nehmen von Ausrüstungspunkten und das Melden von Körpern nun vor dem Bestätigen möglich ist. Jeder ist in der Lage Leichen zu untersuchen und Informationen dadurch zu gewinnen, diese können aber nicht an alle verkündet werden. + +Modus 2: Diser Modus ist noch etwas strikter als Modus 1. In diesem Modus wird die Untersuchungsoption für normale Spieler ebenfalls entfernt. Das bedeutet, dass das melden von Leichen an eine öffentliche Ordnungsrolle nun der einzige Weg ist, um an Informationen der Leiche zu kommen.]] + +-- 2023-10-19 +L.label_grenade_trajectory_ui = "Anzeige der Granatenflugbahn" + +-- 2023-10-23 +L.label_hud_pulsate_health_enable = "Pulsieren der Lebensleiste bei weniger als 25% Gesundheit" +L.header_hud_elements_customize = "Passe die HUD-Elemente an" +L.help_hud_elements_special_settings = "Dies sind die HUD-Element spezifischen Einstellungen." + +-- 2023-10-25 +L.help_keyhelp = [[ +Tastenhelfer sind ein UI Element, welches dauerhaft relevante Tastenbelegungen für den Spieler anzeigen. Gerade unerfahrene Spieler können hiervon profitieren. Es gibt drei unterschiedliche Kategorien von Tastenbelegungen: + +Core: Diese Kategorie enthält die wichtigsten Tastenbelegungen in TTT2. Ohne diese ist es schwer das volle Potential von TTT2 zu nutzen. +Extra: Ähnlich zu 'core', enthält aber eher nicht durchgehend nötige Tastenbelegungen. Dinge wie der Chat, Sprachchat und Taschenlampe sind hier enthalten. Gerade für neue Spieler kann das aktivieren sinnvoll sein. +Equipment: Ausrüstungsgegenstände können eigene Tastenbelegungen haben, diese werden in dieser Kategorie angezeigt. + +Deaktivierte Kategorien werden weiterhin angezeigt solange die Punktetafel geöffnet ist]] + +L.label_keyhelp_show_core = "Aktiviere dauerhaftes anzeigen der 'core' Tastenhelfer" +L.label_keyhelp_show_extra = "Aktiviere dauerhaftes anzeigen der 'extra' Tastenhelfer" +L.label_keyhelp_show_equipment = "Aktiviere dauerhaftes anzeigen der 'equipment' Tastenhelfer" + +L.header_interface_keys = "Tastenhelfer Einstellungen" +L.header_interface_wepswitch = "Waffenwechsel UI Einstellungen" + +L.label_keyhelper_help = "öffne Spielmodus Menü" +L.label_keyhelper_mutespec = "Wechsle Zuschauer Sprachmodus" +L.label_keyhelper_shop = "öffne Ausrüstungsshop" +L.label_keyhelper_show_pointer = "Befreie Mauszeiger" +L.label_keyhelper_possess_focus_entity = "fokusiertes Objekt übernehmen" +L.label_keyhelper_spec_focus_player = "fokusiertem Spieler zuschauen" +L.label_keyhelper_spec_previous_player = "vorheriger Spieler" +L.label_keyhelper_spec_next_player = "nächster Spieler" +L.label_keyhelper_spec_player = "Spieler zuschauen" +L.label_keyhelper_possession_jump = "Objekt: springen" +L.label_keyhelper_possession_left = "Objekt: links" +L.label_keyhelper_possession_right = "Objekt: rechts" +L.label_keyhelper_possession_forward = "Objekt: vorwärts" +L.label_keyhelper_possession_backward = "Objekt: rückwärts" +L.label_keyhelper_free_roam = "verlasse Objekt und bewege dich frei" +L.label_keyhelper_flashlight = "Taschenlampe an-/ausschalten" +L.label_keyhelper_quickchat = "Schnellchat öffnen" +L.label_keyhelper_voice_global = "Globaler Sprachchat" +L.label_keyhelper_voice_team = "Team Sprachchat" +L.label_keyhelper_chat_global = "Globaler Chat" +L.label_keyhelper_chat_team = "Team Chat" +L.label_keyhelper_show_all = "Alle anzeigen" +L.label_keyhelper_disguiser = "Tarnung an-/ausschalten" +L.label_keyhelper_save_exit = "Speichern und Verlassen" +L.label_keyhelper_spec_third_person = "Wechsle Ansicht aus dritter Person" + +-- 2023-10-26 +L.item_armor_reinforced = "Verstärkte Rüstung" +L.item_armor_sidebar = "Rüstung bietet dir etwas Schutz vor Kugelschüssen. Aber nicht auf Dauer." +L.item_disguiser_sidebar = "Die Tarnung versteckt deine Identität vor anderen Spielern." +L.status_speed_name = "Geschwindigkeits Faktor" +L.status_speed_description_good = "Du bist schneller als normal. Gegenstände, Ausrüstung oder verschiedene Effekte können dies beeinflussen." +L.status_speed_description_bad = "Du bist langsamer als normal. Gegenstände, Ausrüstung oder verschiedene Effekte können dies beeinflussen." + +L.status_on = "an" +L.status_off = "aus" + +L.crowbar_help_primary = "Zuschlagen" +L.crowbar_help_secondary = "Spieler schubsen" + +-- 2023-10-27 +L.help_HUD_enable_description = [[ +Manche HUD-Elemente, wie der Tastenhelfer oder die Seitenleiste, zeigen detailliertere Informationen an, wenn das Scoreboard offen ist. Dies kann deaktiviert werden, um Unordnung zu reduzieren.]] +L.label_HUD_enable_description = "Aktiviere Beschreibungen, wenn das Scoreboard offen ist" +L.label_HUD_enable_box_blur = "Aktiviere Hintergrund-Verschwommenheit der UI-Box" + +-- 2023-10-28 +L.submenu_gameplay_voiceandvolume_title = "Sprache & Lautstärke" +L.header_soundeffect_settings = "Sound Effekte" +L.header_voiceandvolume_settings = "Sprache- & Lautstärkeeinstellungen" + +-- 2023-11-06 +L.drop_reserve_prevented = "Etwas hindert dich daran, deine Reserve-Munition abzulegen." +L.drop_no_reserve = "Nicht genügend Munition in deinem Vorrat, um als Munitionskiste abgelegt zu werden." +L.drop_no_room_ammo = "Hier ist kein Platz, um deine Munition fallen zu lassen!" + +-- 2023-11-14 +L.hat_deerstalker_name = "Detektivshut" + +-- 2023-11-16 +L.help_prop_spec_dash = [[ +Propspec-Schübe sind Bewegungen in Richtung des Zielvektors. Sie können eine höhere Kraft als die normale Bewegung haben. Eine höhere Kraft bedeutet auch einen höheren Verbrauch des Basiswerts. + +Diese Variable ist ein Multiplikator der Push-Force.]] +L.label_spec_prop_dash = "Schubkraft Multiplikator" +L.label_keyhelper_possession_dash = "prop: Schub in Blickrichtung" +L.label_keyhelper_weapon_drop = "wenn möglich, ausgewählte Waffe ablegen" +L.label_keyhelper_ammo_drop = "Munition der ausgewählten Waffe aus dem Magazin ablegen" + +-- 2023-12-07 +L.c4_help_primary = "C4 Platzieren" +L.c4_help_secondary = "An Oberfläche kleben" + +-- 2023-12-11 +L.magneto_help_primary = "Entität wegschieben" +L.magneto_help_secondary = "Entität heranziehen / aufheben" +L.knife_help_primary = "Stechen" +L.knife_help_secondary = "Messer werfen" +L.polter_help_primary = "Feuere Klopfer" +L.polter_help_secondary = "Langstreckenschuss aufladen" + +-- 2023-12-12 +L.newton_help_primary = "Rückstoßschuss" +L.newton_help_secondary = "Aufgeladener Rückstoßschuss" + +-- 2023-12-13 +L.vis_no_pickup = "Nur öffentliche Ordnungsrollen können den Visualisierer aufheben" +L.newton_force = "KRAFT" +L.defuser_help_primary = "Ausgewähltes C4 entschärfen" +L.radio_help_primary = "Radio platzieren" +L.radio_help_secondary = "An Oberfläche kleben" +L.hstation_help_primary = "Gesundheitsstation platzieren" +L.flaregun_help_primary = "Körper/Entität verbrennen" + +-- 2023-12-14 +L.marker_vision_owner = "Besitzer: {owner}" +L.marker_vision_distance = "Distanz: {distance}m" +L.marker_vision_distance_collapsed = "{distance}m" + +L.c4_marker_vision_time = "Explosionszeit: {time}" +L.c4_marker_vision_collapsed = "{time} / {distance}m" + +L.c4_marker_vision_safe_zone = "Bombensicherheitszone" +L.c4_marker_vision_damage_zone = "Bombenschadenszone" +L.c4_marker_vision_kill_zone = "Bombentötungszone" + +L.beacon_marker_vision_player = "Verfolgter Spieler" +L.beacon_marker_vision_player_tracked = "Dieser Spieler wird von einem Peilsender verfolgt." + +-- 2023-12-18 +L.beacon_help_pri = "Peilsender auf den Boden werfen" +L.beacon_help_sec = "Peilsender an Oberfläche kleben" +L.beacon_name = "Peilsender" +L.beacon_desc = [[ +Überträgt die Spielerpositionen an alle in einer Kugel um diesen Peilsender herum. + +Verwenden, um Orte auf der Karte im Auge zu behalten, die schwer zu sehen sind.]] + +L.msg_beacon_destroyed = "Einer deiner Peilsender wurde zerstört!" +L.msg_beacon_death = "Ein Spieler ist in unmittelbarer Nähe eines deiner Peilsender gestorben." + +L.beacon_pickup_disabled = "Nur der Besitzer des Peilsenders kann ihn aufheben" +L.beacon_short_desc = "Peilsender werden von öffentlichen Ordnungsrollen verwendet, um lokale Wallhacks um sie herum hinzuzufügen" + +-- 2023-12-18 +L.entity_pickup_owner_only = "Nur der Besitzer kann dies aufheben" + +-- 2023-12-18 +L.body_confirm_one = "{finder} bestätigte den Tod von {victim}." +L.body_confirm_more = "{finder} bestätigte {count} Tode von: {victims}." + +-- 2023-12-19 +L.builtin_marker = "Integriert." +L.equipmenteditor_desc_builtin = "Diese Ausrüstung ist integriert, sie kommt mit TTT2!" +L.help_roles_builtin = "Diese Rolle ist integriert, sie kommt mit TTT2!" +L.header_equipment_info = "Ausrüstungsinformationen" + + +-- 2023-12-24 +L.submenu_gameplay_accessibility_title = "Barrierefreiheit" + +L.header_accessibility_settings = "Einstellungen für Barrierefreiheit" + +L.label_enable_dynamic_fov = "Aktiviere das dynamische Sichtfeld" +L.label_enable_bobbing = "Aktiviere 'Sichtwackeln'" +L.label_enable_bobbing_strafe = "Aktiviere 'Sichtwackeln' beim seitwärts gehen" + +L.help_enable_dynamic_fov = "Das dynamische Sichtfeld wird je nach Geschwindigkeit des Spielers angewendet. Wenn ein Spieler zum Beispiel rennt, wird das Sichtfeld vergrößert, um die Geschwindigkeit zu visualisieren." +L.help_enable_bobbing_strafe = "'View bobbing' ist das leichte Kamerawackeln beim Gehen, Schwimmen oder Fallen." +-- 2023-12-20 +L.equipmenteditor_desc_damage_scaling = [[Multipliziert den Basisschadenswert der Waffe um diesen Faktor. +Für eine Schrotflinte würde sich dies auf jedes Geschoss auswirken. +Für ein Gewehr würde sich dies nur auf die Kugel auswirken. +Für den Poltergeist würde sich dies auf jeden Stoß und die finale Explosion auswirken. + +0.5 = Halbiert den zugefügten Schaden. +2 = Verdoppelt den zugefügten Schaden. + +Notiz: Einige Waffen verwenden diesen Wert möglicherweise nicht, was dazu führt, dass dieser Modifikator unwirksam ist.]] + +-- 2023-12-24 +L.binoc_help_reload = "Ziel löschen." +L.cl_sb_row_sresult_direct_conf = "Direkte Bestätigung" +L.cl_sb_row_sresult_pub_police = "Bestätigung einer öffentlichen Ordnungsrolle" + +-- 2024-01-05 +L.label_crosshair_thickness_outline_enable = "Aktiviere Umrandung des Fadenkreuz" +L.label_crosshair_outline_high_contrast = "Aktiviere hohen Kontrast der Umrandung" +L.label_crosshair_mode = "Fadenkreuzmodus" +L.label_crosshair_static_length = "Aktiviere konstante Länge der Fadenkreuzlinien" + +L.choice_crosshair_mode_0 = "Linien und Punkt" +L.choice_crosshair_mode_1 = "Nur Linien" +L.choice_crosshair_mode_2 = "Nur Punkt" + +L.help_crosshair_scale_enable = [[ +Das dynamische Fadenkreuz ermöglicht das Skalieren des Fadenkreuzes je nach Streukegel der Waffe. Der Streukegel wird von der Basisgenauigkeit der Waffe beeinflusst, multipliziert mit externen Faktoren wie Springen und Sprinten. + +Wenn die Linienlänge konstant bleibt, skaliert nur der Abstand mit den Streukegeländerungen.]] + +L.header_weapon_settings = "Waffeneinstellungen" + + +L.marker_vision_visible_for_0 = "Für dich sichtbar" +L.marker_vision_visible_for_1 = "Für deine Rolle sichtbar" +L.marker_vision_visible_for_2 = "Für dein Team sichtbar" +L.marker_vision_visible_for_3 = "Für jeden sichtbar" + +-- 2024-01-27 +L.decoy_help_primary = "Platziere die Attrappe" +L.decoy_help_secondary = "Attrappe an Oberfläche kleben" + +-- 2024-01-24 +L.grenade_fuse = "LUNTE" + +-- 2024-01-25 +L.header_roles_magnetostick = "Magneto-Stick" +L.label_roles_ragdoll_pinning = "Aktivere das anheften von Ragdolls" +L.magneto_stick_help_carry_rag_pin = "Ragdoll anheften" +L.magneto_stick_help_carry_rag_drop = "Ragdoll fallenlassen" +L.magneto_stick_help_carry_prop_release = "Prop freilassen" +L.magneto_stick_help_carry_prop_drop = "Prop fallenlassen" + +-- 2024-02-14 +L.throw_no_room = "Hier ist kein Platz, um dieses Gerät zu werfen." diff --git a/lua/terrortown/lang/en.lua b/lua/terrortown/lang/en.lua index 46bbb0ba2..e3ab27d3c 100644 --- a/lua/terrortown/lang/en.lua +++ b/lua/terrortown/lang/en.lua @@ -60,8 +60,6 @@ L.body_found_traitor = "They were a Traitor!" L.body_found_det = "They were a Detective." L.body_found_inno = "They were Innocent." -L.body_confirm = "{finder} confirmed the death of {victim}." - L.body_call = "{player} called a Detective to the body of {victim}!" L.body_call_error = "You must confirm the death of this player before calling a Detective!" @@ -172,46 +170,6 @@ L.quick_disg = "someone in disguise" L.quick_corpse = "an unidentified body" L.quick_corpse_id = "{player}'s corpse" --- Body search window -L.search_title = "Body Search Results" -L.search_info = "Information" -L.search_confirm = "Confirm Death" -L.search_call = "Call Detective" - --- Descriptions of pieces of information found -L.search_nick = "This is the body of {player}." - -L.search_role_traitor = "This person was a Traitor!" -L.search_role_det = "This person was a Detective." -L.search_role_inno = "This person was an innocent terrorist." - -L.search_words = "Something tells you some of this person's last words were: '{lastwords}'" -L.search_armor = "They were wearing nonstandard body armor." -L.search_disg = "They were carrying a device that could hide their identity." -L.search_radar = "They were carrying some sort of radar. It is no longer functioning." -L.search_c4 = "In a pocket you found a note. It states that cutting wire {num} will safely disarm the bomb." - -L.search_dmg_crush = "Many of their bones are broken. It seems the impact of a heavy object killed them." -L.search_dmg_bullet = "It is obvious they were shot to death." -L.search_dmg_fall = "They fell to their death." -L.search_dmg_boom = "Their wounds and singed clothes indicate an explosion caused their end." -L.search_dmg_club = "The body is bruised and battered. Clearly they were clubbed to death." -L.search_dmg_drown = "The body shows the telltale signs of drowning." -L.search_dmg_stab = "They were stabbed and cut before quickly bleeding to death." -L.search_dmg_burn = "Smells like roasted terrorist around here..." -L.search_dmg_tele = "It looks like their DNA was scrambled by tachyon emissions!" -L.search_dmg_car = "When this terrorist crossed the road, they were run over by a reckless driver." -L.search_dmg_other = "You cannot find a specific cause of this terrorist's death." - -L.search_weapon = "It appears a {weapon} was used to kill them." -L.search_head = "The fatal wound was a headshot. No time to scream." -L.search_time = "They died roughly {time} before you conducted the search." -L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay roughly {time} from now." - -L.search_kills1 = "You found a list of kills that confirms the death of {player}." -L.search_kills2 = "You found a list of kills with these names:" -L.search_eyes = "Using your detective skills, you identified the last person they saw: {player}. The killer, or a coincidence?" - -- Scoreboard L.sb_playing = "You are playing on..." L.sb_mapchange = "Map changes in {num} rounds or in {time}" @@ -267,7 +225,6 @@ Hides your ID info while on. Also avoids being the person last seen by a victim. Toggle in the Disguise tab of this menu or press Numpad Enter.]] -- C4 -L.c4_hint = "Press {usekey} to arm or disarm." L.c4_disarm_warn = "A C4 explosive you planted has been disarmed." L.c4_armed = "You have successfully armed the bomb." L.c4_disarmed = "You have successfully disarmed the bomb." @@ -288,6 +245,7 @@ L.c4_remove_destroy2 = "Confirm: destroy" L.c4_disarm = "Disarm C4" L.c4_disarm_cut = "Click to cut wire {num}" +L.c4_disarm_t = "Cut a wire to disarm the bomb. As you are Traitor, every wire is safe. Innocents don't have it so easy!" L.c4_disarm_owned = "Cut a wire to disarm the bomb. It's your bomb, so every wire will disarm it." L.c4_disarm_other = "Cut a safe wire to disarm the bomb. It will explode if you get it wrong!" @@ -296,7 +254,6 @@ L.c4_status_disarmed = "DISARMED" -- Visualizer L.vis_name = "Visualizer" -L.vis_hint = "Press {usekey} to pick up (Detectives only)." L.vis_desc = [[ Crime scene visualization device. @@ -305,7 +262,6 @@ Analyzes a corpse to show how the victim was killed, but only if they died of gu -- Decoy L.decoy_name = "Decoy" -L.decoy_no_room = "You cannot carry this decoy." L.decoy_broken = "Your Decoy has been destroyed!" L.decoy_short_desc = "This decoy shows a fake radar sign visible for other teams" @@ -316,7 +272,6 @@ Shows a fake radar sign to other teams, and makes the DNA scanner show the locat -- Defuser L.defuser_name = "Defuser" -L.defuser_help = "{primaryfire} defuses targeted C4." L.defuser_desc = [[ Instantly defuse a C4 explosive. @@ -335,7 +290,6 @@ Burning a corpse makes a distinct sound.]] L.hstation_name = "Health Station" L.hstation_broken = "Your Health Station has been destroyed!" -L.hstation_help = "{primaryfire} places the Health Station." L.hstation_desc = [[ Allows people to heal when placed. @@ -359,7 +313,6 @@ The energy bursts damage people in close proximity.]] -- Radio L.radio_broken = "Your Radio has been destroyed!" -L.radio_help_pri = "{primaryfire} places the Radio." L.radio_desc = [[ Plays sounds to distract or deceive. @@ -483,14 +436,12 @@ L.karma_min = "Liability" -- TargetID misc L.corpse = "Corpse" -L.corpse_hint = "Press [{usekey}] to search. [{walkkey} + {usekey}] to search covertly." +L.corpse_hint = "Press [{usekey}] to search and confirm. [{walkkey} + {usekey}] to search covertly." L.target_disg = "(disguised)" L.target_unid = "Unidentified body" L.target_unknown = "A Terrorist" -L.target_credits = "Search to receive unspent credits" - -- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "Single use" L.tbut_reuse = "Reusable" @@ -505,7 +456,6 @@ L.mute_off = "None muted" -- Spectators and prop possession L.punch_title = "PUNCH-O-METER" -L.punch_help = "Move keys or jump: punch object. Crouch: leave object." L.punch_bonus = "Your bad score lowered your punch-o-meter limit by {num}" L.punch_malus = "Your good score increased your punch-o-meter limit by {num}!" @@ -933,16 +883,13 @@ L.shop_role_select = "Select a role" L.shop_role_selected = "{role}'s shop was selected!" L.shop_search = "Search" -L.spec_help = "Click to spectate players, or press {usekey} on a physics object to possess it." -L.spec_help2 = "To leave the spectator mode, open the menu by pressing {helpkey}, go to 'gameplay' and toggle the spectator mode." - -- 2019-10-19 L.drop_ammo_prevented = "Something prevents you from dropping your ammo." -- 2019-10-28 L.target_c4 = "Press [{usekey}] to open C4 menu" L.target_c4_armed = "Press [{usekey}] to disarm C4" -L.target_c4_armed_defuser = "Press [{usekey}] to use defuser" +L.target_c4_armed_defuser = "Press [{primaryfire}] to use defuser" L.target_c4_not_disarmable = "You can't disarm C4 of a living teammate" L.c4_short_desc = "Something very explosive" @@ -950,16 +897,15 @@ L.target_pickup = "Press [{usekey}] to pick up" L.target_slot_info = "Slot: {slot}" L.target_pickup_weapon = "Press [{usekey}] to pickup weapon" L.target_switch_weapon = "Press [{usekey}] to swap with your current weapon" -L.target_pickup_weapon_hidden = ", press [{usekey} + {walkkey}] for hidden pickup" -L.target_switch_weapon_hidden = ", press [{usekey} + {walkkey}] for hidden switch" +L.target_pickup_weapon_hidden = ", press [{walkkey} + {usekey}] for hidden pickup" +L.target_switch_weapon_hidden = ", press [{walkkey} + {usekey}] for hidden switch" L.target_switch_weapon_nospace = "There is no inventory slot available for this weapon" L.target_switch_drop_weapon_info = "Dropping {name} from slot {slot}" L.target_switch_drop_weapon_info_noslot = "There is no droppable weapon in slot {slot}" -L.corpse_searched_by_detective = "This corpse was searched by a detective" +L.corpse_searched_by_detective = "This corpse was searched by a public policing role" L.corpse_too_far_away = "The corpse is too far away." -L.radio_pickup_wrong_team = "You can't pick up the radio from another team." L.radio_short_desc = "Weapon sounds are music to me" L.hstation_subtitle = "Press [{usekey}] to receive health." @@ -1004,7 +950,6 @@ L.mute_team = "{team} muted." L.door_auto_closes = "This door closes automatically." L.door_open_touch = "Walk into door to open." L.door_open_touch_and_use = "Walk into door or press [{usekey}] to open." -L.hud_health = "Health" -- 2020-03-09 L.help_title = "Help and Settings" @@ -1026,7 +971,7 @@ L.menu_guide_description = "Helps you to get started with TTT2 and explains some L.menu_bindings_description = "Bind specific features of TTT2 and its addons to your own liking." L.menu_language_description = "Select the language of the gamemode." L.menu_appearance_description = "Tweak the appearance and performance of the UI." -L.menu_gameplay_description = "Avoid roles and tweak some features." +L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." L.menu_addons_description = "Configure local addons to your liking." L.menu_legacy_description = "A panel with converted tabs from the original TTT that should be ported over to the new system." L.menu_administration_description = "General settings for HUDs, shops etc." @@ -1050,10 +995,8 @@ L.submenu_appearance_crosshair_title = "Crosshair" L.submenu_appearance_dmgindicator_title = "Damage Indicator" L.submenu_appearance_performance_title = "Performance" L.submenu_appearance_interface_title = "Interface" -L.submenu_appearance_miscellaneous_title = "Misellaneous" L.submenu_gameplay_general_title = "General" -L.submenu_gameplay_avoidroles_title = "Avoid Role Selection" L.submenu_administration_hud_title = "HUD Settings" L.submenu_administration_randomshop_title = "Random Shop" @@ -1090,17 +1033,12 @@ L.label_shop_show_slot = "Show slot marker" L.label_shop_show_custom = "Show custom item marker" L.label_shop_show_fav = "Show favourite item marker" L.label_crosshair_enable = "Enable crosshair" -L.label_crosshair_gap_enable = "Enable custom crosshair gap" -L.label_crosshair_gap = "Custom crosshair gap" L.label_crosshair_opacity = "Crosshair opacity" L.label_crosshair_ironsight_opacity = "Ironsight crosshair opacity" -L.label_crosshair_size = "Crosshair size" -L.label_crosshair_thickness = "Crosshair thickness" -L.label_crosshair_thickness_outline = "Crosshair outline thickness" -L.label_crosshair_static_enable = "Enable static crosshair" -L.label_crosshair_dot_enable = "Enable crosshair dot" -L.label_crosshair_lines_enable = "Enable crosshair lines" -L.label_crosshair_scale_enable = "Enable weapon dependant weapon scale" +L.label_crosshair_size = "Crosshair size multiplier" +L.label_crosshair_thickness = "Crosshair thickness multiplier" +L.label_crosshair_thickness_outline = "Crosshair outline thickness multiplier" +L.label_crosshair_scale_enable = "Enable dynamic crosshair scale" L.label_crosshair_ironsight_low_enabled = "Lower weapon when using ironsights" L.label_damage_indicator_enable = "Enable damage indicator" L.label_damage_indicator_mode = "Select damage indicator theme" @@ -1119,13 +1057,10 @@ L.label_gameplay_specmode = "Spectate-only mode (always stay spectator)" L.label_gameplay_fastsw = "Fast weapon switch" L.label_gameplay_hold_aim = "Enable hold to aim" L.label_gameplay_mute = "Mute living players when dead" -L.label_gameplay_dtsprint_enable = "Enable double tap sprinting" -L.label_gameplay_dtsprint_anykey = "Continue double tap sprinting until you stop moving" L.label_hud_default = "Default HUD" L.label_hud_force = "Forced HUD" L.label_bind_weaponswitch = "Pickup Weapon" -L.label_bind_sprint = "Sprint" L.label_bind_voice = "Global Voice Chat" L.label_bind_voice_team = "Team Voice Chat" @@ -1149,7 +1084,6 @@ L.header_damage_indicator = "Damage Indicator Settings" L.header_performance_settings = "Performance Settings" L.header_interface_settings = "Interface Settings" L.header_gameplay_settings = "Gameplay Settings" -L.header_roleselection = "Select Avoiding Roles" L.header_hud_administration = "Select Default and Forced HUDs" L.header_hud_enabled = "Enable/Disable HUDs" @@ -1196,11 +1130,7 @@ L.hud_revival_time = "{time}s" L.door_destructible = "This door is destructible ({health}HP)." -- 2020-05-28 -L.confirm_detective_only = "Only detectives can confirm bodies." -L.inspect_detective_only = "Only detectives can search bodies." -L.corpse_hint_no_inspect = "Only detectives can search this body." -L.corpse_hint_inspect_only = "Press [{usekey}] to search. Only detectives can confirm the body." -L.corpse_hint_inspect_only_credits = "Press [{usekey}] to receive credits. Only detectives can search this body." +L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." -- 2020-06-04 L.label_bind_disguiser = "Toggle disguiser" @@ -1215,7 +1145,6 @@ L.binoc_help_sec = "Change zoom level." L.vis_help_pri = "Drop the activated device." -L.decoy_help_pri = "Plant the Decoy." -- 2020-08-07 L.pickup_error_spec = "You cannot pick this up as a spectator." @@ -1421,7 +1350,7 @@ L.spawneditor_desc = "Used to place weapon, ammo and player spawns in the world. L.spawneditor_place = "Place spawn" L.spawneditor_remove = "Remove spawn" L.spawneditor_change = "Change spawn type (hold [SHIFT] to reverse)" -L.spawneditor_ammo_edit = "Hold to edit amount of autospawning ammo on weapon spawns" +L.spawneditor_ammo_edit = "Hold on weapon spawn to edit autospawning ammo" L.spawn_weapon_random = "Random Weapon Spawn" L.spawn_weapon_melee = "Melee Weapon Spawn" @@ -1439,7 +1368,7 @@ L.spawn_ammo_rifle = "Rifle ammo spawn" L.spawn_ammo_shotgun = "Shotgun ammo spawn" L.spawn_player_random = "Random player spawn" -L.spawn_weapon_ammo = " (Ammo: {ammo})" +L.spawn_weapon_ammo = "(Ammo: {ammo})" L.spawn_weapon_edit_ammo = "Hold [{walkkey}] and press [{primaryfire} or {secondaryfire}] to increase or decrease the ammo for this weapon spawn" @@ -1730,8 +1659,6 @@ L.label_bots_are_spectators = "Bots are always spectators" L.label_tbutton_admin_show = "Show traitor buttons to admins" L.label_ragdoll_carrying = "Enable ragdoll carrying" L.label_prop_throwing = "Enable prop throwing" -L.label_ragdoll_pinning = "Enable ragdoll pinning for non-Innocent roles" -L.label_ragdoll_pinning_innocents = "Enable ragdoll pinning for Innocent roles" L.label_weapon_carrying = "Enable weapon carrying" L.label_weapon_carrying_range = "Weapon carry range" L.label_prop_carrying_force = "Prop pickup force" @@ -1761,10 +1688,8 @@ L.label_session_limits_enabled = "Enable session limits" L.label_spectator_chat = "Enable spectators chatting with everybody" L.label_lastwords_chatprint = "Print last words to chat if killed while typing" L.label_identify_body_woconfirm = "Identify corpse without pressing the 'confirm' button" -L.label_announce_body_found = "Announce that a body was found" +L.label_announce_body_found = "Announce that a body was found when the body was confirmed" L.label_confirm_killlist = "Announce kill list of confirmed corpse" -L.label_inspect_detective_only = "Limit corpse search to policing roles only" -L.label_confirm_detective_only = "Limit corpse confirmation to policing roles only" L.label_dyingshot = "Shoot on death if in ironsights [experimental]" L.label_armor_block_headshots = "Enable armor blocking headshots" L.label_armor_block_blastdmg = "Enable armor blocking blast damage" @@ -1823,7 +1748,6 @@ L.label_sprint_enabled = "Enable sprinting" L.label_sprint_max = "Max sprinting stamina" L.label_sprint_stamina_consumption = "Stamina consumption factor" L.label_sprint_stamina_regeneration = "Stamina regeneration factor" -L.label_sprint_crosshair = "Show crosshair while sprinting" L.label_crowbar_unlocks = "Primary attack can be used as interaction (i.e. unlocking)" L.label_crowbar_pushforce = "Crowbar push force" @@ -1859,4 +1783,411 @@ L.sb_rank_tooltip_streamer = "Streamer" L.sb_rank_tooltip_heroes = "TTT2 Heroes" L.sb_rank_tooltip_team = "Team" -L.tbut_adminarea = "ADMIN AREA:" \ No newline at end of file +L.tbut_adminarea = "ADMIN AREA:" + +-- 2023-08-10 +L.equipmenteditor_name_damage_scaling = "Damage Scaling" + +-- 2023-08-11 +L.equipmenteditor_name_allow_drop = "Allow Drop" +L.equipmenteditor_desc_allow_drop = "If enabled, the equipment can be dropped freely by the player." + +L.equipmenteditor_name_drop_on_death_type = "Drop on Death" +L.equipmenteditor_desc_drop_on_death_type = "Attempt overriding the action taken for whether the equipment is dropped on player's death." + +L.drop_on_death_type_default = "Default (weapon-defined)" +L.drop_on_death_type_force = "Force Drop on Death" +L.drop_on_death_type_deny = "Deny Drop on Death" + +-- 2023-08-26 +L.equipmenteditor_name_kind = "Equipment Slot" +L.equipmenteditor_desc_kind = "The inventory slot the equipment will occupy." + +L.slot_weapon_melee = "Melee Slot" +L.slot_weapon_pistol = "Pistol Slot" +L.slot_weapon_heavy = "Heavy Slot" +L.slot_weapon_nade = "Grenade Slot" +L.slot_weapon_carry = "Carry Slot" +L.slot_weapon_unarmed = "Unarmed Slot" +L.slot_weapon_special = "Special Slot" +L.slot_weapon_extra = "Extra Slot" +L.slot_weapon_class = "Class Slot" + +-- 2023-10-04 +L.label_voice_duck_spectator = "Duck spectator voices" +L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +L.label_voice_scaling = "Voice Volume Scaling Mode" +L.label_voice_scaling_mode_linear = "Linear" +L.label_voice_scaling_mode_power4 = "Power 4" +L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "Body Search Results - {player}" +L.search_info = "Information" +L.search_confirm = "Confirm Death" +L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +L.search_take_credits = "Take {credits} Credit(s)" +L.search_confirm_forbidden = "Confirm forbidden" +L.search_confirmed = "Death Confirmed" +L.search_call = "Report Death" +L.search_called = "Death Reported" + +L.search_team_role_unknown = "???" + +L.search_words = "Something tells you some of this person's last words were: '{lastwords}'" +L.search_armor = "They were wearing nonstandard body armor." +L.search_disguiser = "They were carrying a device that could hide their identity." +L.search_radar = "They were carrying some sort of radar. It is no longer functioning." +L.search_c4 = "In a pocket you found a note. It states that cutting wire {num} will safely disarm the bomb." + +L.search_dmg_crush = "Many of their bones are broken. It seems the impact of a heavy object killed them." +L.search_dmg_bullet = "It is obvious they were shot to death." +L.search_dmg_fall = "They fell to their death." +L.search_dmg_boom = "Their wounds and singed clothes indicate an explosion caused their end." +L.search_dmg_club = "The body is bruised and battered. Clearly they were clubbed to death." +L.search_dmg_drown = "The body shows the telltale signs of drowning." +L.search_dmg_stab = "They were stabbed and cut before quickly bleeding to death." +L.search_dmg_burn = "Smells like roasted terrorist around here..." +L.search_dmg_teleport = "It looks like their DNA was scrambled by tachyon emissions!" +L.search_dmg_car = "When this terrorist crossed the road, they were run over by a reckless driver." +L.search_dmg_other = "You cannot find a specific cause of this terrorist's death." + +L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "It appears a {weapon} was used to kill them." +L.search_head = "The fatal wound was a headshot. No time to scream." +L.search_time = "They died a while before you conducted the search." +L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "You found a list of kills that confirms the death of {player}." +L.search_kills2 = "You found a list of kills with these names: {player}" +L.search_eyes = "Using your detective skills, you identified the last person they saw: {player}. The killer, or a coincidence?" + +L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +L.search_kill_distance_point_blank = "It was a point blank attack." +L.search_kill_distance_close = "The attack came from a short distance." +L.search_kill_distance_far = "The victim was attacked from a long distance away." + +L.search_kill_from_front = "The victim was shot from the front." +L.search_kill_from_back = "The victim was shot from behind." +L.search_kill_from_side = "The victim was shot from the side." + +L.search_hitgroup_head = "The projectile was found in their head." +L.search_hitgroup_chest = "The projectile was found in their chest." +L.search_hitgroup_stomach = "The projectile was found in their stomach." +L.search_hitgroup_rightarm = "The projectile was found in their right arm." +L.search_hitgroup_leftarm = "The projectile was found in their left arm." +L.search_hitgroup_rightleg = "The projectile was found in their right leg." +L.search_hitgroup_leftleg = "The projectile was found in their left leg." +L.search_hitgroup_gear = "The projectile was found in their hip." + +L.search_policingrole_report_confirm = [[ +A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +L.search_policingrole_confirm_disabled_1 = [[ +The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +L.search_policingrole_confirm_disabled_2 = [[ +The corpse can only be confirmed by a public policing role. Report the body to let them know! +You can see the information in here after they confirmed it.]] +L.search_spec = [[ +As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +L.search_title_words = "Victim's last words" +L.search_title_c4 = "Defusion mishap" +L.search_title_dmg_crush = "Crush damage ({amount} HP)" +L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +L.search_title_dmg_fall = "Fall damage ({amount} HP)" +L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +L.search_title_dmg_club = "Club damage ({amount} HP)" +L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +L.search_title_dmg_burn = "Burning damage ({amount} HP)" +L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +L.search_title_dmg_car = "Car accident ({amount} HP)" +L.search_title_dmg_other = "Unknown damage ({amount} HP)" +L.search_title_time = "Death time" +L.search_title_dna = "DNA sample decay" +L.search_title_kills = "The victim's kill list" +L.search_title_eyes = "The killer's shadow" +L.search_title_floor = "Floor of the crime scene" +L.search_title_credits = "{credits} Equipment credit(s)" +L.search_title_water = "Water level {level}" +L.search_title_policingrole_report_confirm = "Confirm to report death" +L.search_title_policingrole_confirm_disabled = "Report corpse" +L.search_title_spectator = "You are a spectator" + +L.target_credits_on_confirm = "Confirm to receive unspent credits" +L.target_credits_on_search = "Search to receive unspent credits" +L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +L.label_inspect_confirm_mode = "Select body search mode" +L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +L.help_inspect_confirm_mode = [[ +There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. + +mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. + +mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. + +mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +L.header_hud_elements_customize = "Customize the HUD-Elements" +L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +L.help_keyhelp = [[ +Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: + +Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +Equipment: Some equipment items have their own bindings, these are shown in this category. + +Disabled categories are still shown when the scoreboard is visible]] + +L.label_keyhelp_show_core = "Enable always showing the core bindings" +L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +L.header_interface_keys = "Key helper settings" +L.header_interface_wepswitch = "Weapon switch UI settings" + +L.label_keyhelper_help = "open gamemode menu" +L.label_keyhelper_mutespec = "cycle spectator voice mode" +L.label_keyhelper_shop = "open equipment shop" +L.label_keyhelper_show_pointer = "free mouse pointer" +L.label_keyhelper_possess_focus_entity = "possess focused entity" +L.label_keyhelper_spec_focus_player = "spectate focused player" +L.label_keyhelper_spec_previous_player = "previous player" +L.label_keyhelper_spec_next_player = "next player" +L.label_keyhelper_spec_player = "spectate random player" +L.label_keyhelper_possession_jump = "prop: jump" +L.label_keyhelper_possession_left = "prop: left" +L.label_keyhelper_possession_right = "prop: right" +L.label_keyhelper_possession_forward = "prop: forward" +L.label_keyhelper_possession_backward = "prop: backward" +L.label_keyhelper_free_roam = "leave object and roam free" +L.label_keyhelper_flashlight = "toggle flashlight" +L.label_keyhelper_quickchat = "open quickchat" +L.label_keyhelper_voice_global = "global voice chat" +L.label_keyhelper_voice_team = "team voice chat" +L.label_keyhelper_chat_global = "global chat" +L.label_keyhelper_chat_team = "team chat" +L.label_keyhelper_show_all = "show all" +L.label_keyhelper_disguiser = "toggle disguiser" +L.label_keyhelper_save_exit = "save and exit" +L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +L.item_armor_reinforced = "Reinforced Armor" +L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +L.status_speed_name = "Speed Multiplier" +L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +L.status_on = "on" +L.status_off = "off" + +L.crowbar_help_primary = "Attack" +L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +L.help_HUD_enable_description = [[ +Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +L.header_soundeffect_settings = "Sound Effects" +L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +L.help_prop_spec_dash = [[ +Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. + +This variable is a multiplier of the push force.]] +L.label_spec_prop_dash = "Dash force multiplier" +L.label_keyhelper_possession_dash = "prop: dash in view direction" +L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +L.c4_help_primary = "Place the C4" +L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +L.magneto_help_primary = "Push entity" +L.magneto_help_secondary = "Pull / pickup entity" +L.knife_help_primary = "Stab" +L.knife_help_secondary = "Throw knife" +L.polter_help_primary = "Fire thumper" +L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +L.newton_help_primary = "Knockback shot" +L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +L.newton_force = "FORCE" +L.defuser_help_primary = "Defuse targeted C4" +L.radio_help_primary = "Place the Radio" +L.radio_help_secondary = "Stick to surface" +L.hstation_help_primary = "Place the Health Station" +L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +L.marker_vision_owner = "Owner: {owner}" +L.marker_vision_distance = "Distance: {distance}m" +L.marker_vision_distance_collapsed = "{distance}m" + +L.c4_marker_vision_time = "Detonation time: {time}" +L.c4_marker_vision_collapsed = "{time} / {distance}m" + +L.c4_marker_vision_safe_zone = "Bomb safe zone" +L.c4_marker_vision_damage_zone = "Bomb damage zone" +L.c4_marker_vision_kill_zone = "Bomb kill zone" + +L.beacon_marker_vision_player = "Tracked Player" +L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +L.beacon_help_pri = "Throw Beacon on the ground" +L.beacon_help_sec = "Stick Beacon to surface" +L.beacon_name = "Beacon" +L.beacon_desc = [[ +Broadcasts player locations to everyone in a sphere around this beacon. + +Use to keep track of locations on the map that are hard to see.]] + +L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder} confirmed the death of {victim}." +L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +L.builtin_marker = "Built-in." +L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +L.submenu_gameplay_accessibility_title = "Accessibility" + +L.header_accessibility_settings = "Accessibility Settings" + +L.label_enable_dynamic_fov = "Enable dynamic FOV change" +L.label_enable_bobbing = "Enable view bobbing" +L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +For a shotgun, this would affect each pellet. +For a rifle, this would affect just the bullet. +For the poltergeist, this would affect each "thump" and the final explosion. + +0.5 = Deal half the amount of damage. +2 = Deal twice the amount of damage. + +Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +L.binoc_help_reload = "Clear target." +L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +L.label_crosshair_mode = "Crosshair mode" +L.label_crosshair_static_length = "Enable static crosshair line length" + +L.choice_crosshair_mode_0 = "Lines and dot" +L.choice_crosshair_mode_1 = "Lines only" +L.choice_crosshair_mode_2 = "Dot only" + +L.help_crosshair_scale_enable = [[ +Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. + +If the line length is kept static, only the gap scales with cone changes.]] + +L.header_weapon_settings = "Weapon Settings" + +--2024-01-29 +L.marker_vision_visible_for_0 = "Visible for you" +L.marker_vision_visible_for_1 = "Visible for your role" +L.marker_vision_visible_for_2 = "Visible for your team" +L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +L.decoy_help_primary = "Throw Decoy on the ground" +L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +L.grenade_fuse = "FUSE" + +-- 2024-01-25 +L.header_roles_magnetostick = "Magneto Stick" +L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +L.magneto_stick_help_carry_prop_release = "Release prop" +L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/es.lua b/lua/terrortown/lang/es.lua index 5019a8727..46872d8b4 100644 --- a/lua/terrortown/lang/es.lua +++ b/lua/terrortown/lang/es.lua @@ -1,6 +1,6 @@ -- Spanish language strings -local L = LANG.GetLanguageTableReference("es") +local L = LANG.CreateLanguage("es") -- Compatibility language name that might be removed soon. -- the alias name is based on the original TTT language name: @@ -60,8 +60,6 @@ L.body_found_traitor = "¡Era un Traidor!" L.body_found_det = "Era un Detective." L.body_found_inno = "Era un Inocente." -L.body_confirm = "{finder} confirmó la muerte de {victim}." - L.body_call = "¡{player} llamó a un detective al cuerpo de {victim}!" L.body_call_error = "¡Debes confirmar el cadáver antes de llamar a un detective!" @@ -172,46 +170,6 @@ L.quick_disg = "alguien disfrazado" L.quick_corpse = "un cuerpo sin identificar" L.quick_corpse_id = "el cadáver de {player}" --- Body search window -L.search_title = "Resultados de búsqueda" -L.search_info = "Información" -L.search_confirm = "Confirmar Muerte" -L.search_call = "Llamar a un Detective" - --- Descriptions of pieces of information found -L.search_nick = "Este es el cuerpo de {player}." - -L.search_role_traitor = "¡Esta persona era un Traidor!" -L.search_role_det = "Esta persona era Inocente." -L.search_role_inno = "Esta persona era un terrorista inocente." - -L.search_words = "Algo te dice una de las últimas palabras de esta persona fueron: '{lastwords}'" -L.search_armor = "Estaba utilizando protección antibalas." -L.search_disg = "Estaba utilizando un dispositivo para camuflar su identidad." -L.search_radar = "Estaba llevando un radar. Pero ya no funciona." -L.search_c4 = "En el bolsillo encuentras una nota. Pone que cortando el cable {num} se desactiva una bomba." - -L.search_dmg_crush = "Varios de sus huesos están rotos. Parece ser que ha muerto por el impacto de un objeto." -L.search_dmg_bullet = "Es evidente que le han disparado hasta morir." -L.search_dmg_fall = "Cayó directo hacia su muerte." -L.search_dmg_boom = "Sus heridas y la ropa chamuscada indican que una explosión acabó con la vida de esta persona." -L.search_dmg_club = "El cuerpo está golpeado y amorotonado. Fue apalizado hasta la muerte." -L.search_dmg_drown = "El cuerpo muestra signos de axfisia. Ha muerto ahogado" -L.search_dmg_stab = "Fue apuñalado y cortado para después desangrarse rápidamente hasta morir." -L.search_dmg_burn = "Aquí huele a terrorista quemado..." -L.search_dmg_tele = "¡Parece que su ADN fue alterado por partículas de taquión!" -L.search_dmg_car = "Cuando este terrorista quiso crusar la calle, fue atropellado por un conductor descuidado." -L.search_dmg_other = "No puedes determinar la causa de muerte de esta persona." - -L.search_weapon = "Parece que se uso un/una {weapon} para matarlo." -L.search_head = "Murió de un solo disparo en la cabeza. No le dio tiempo a gritar." -L.search_time = "Murió apróximadamente {time} antes de empezar la investigación." -L.search_dna = "Recoge una muestra del ADN del asesino con un Escáner ADN. La muestra de ADN caducará en {time}." - -L.search_kills1 = "Has encontrado una lista de asesinatos que confirman la muerte de {player}." -L.search_kills2 = "Has encontrado una lista de asesinatos con los siguientes nombres:" -L.search_eyes = "Gracias a tus habilidades de detective, has identificado que la última persona a la que vio fue: {player}. ¿Es el asesino o sólo fue una coincidencia?" - -- Scoreboard L.sb_playing = "Estás jugando en..." L.sb_mapchange = "El mapa cambia en {num} rondas o en {time}" @@ -267,7 +225,6 @@ Esconde tu ID mientras está en uso. Además evita ser la última persona vista Actívalo en la pestaña disfraz de este menú o presiona el numpad Enter.]] -- C4 -L.c4_hint = "Presiona {usekey} para activar o desactivar." L.c4_disarm_warn = "Un C4 que has plantado fue desactivado." L.c4_armed = "Has activado la bomba correctamente." L.c4_disarmed = "Has desactivado la bomba correctamente." @@ -288,6 +245,7 @@ L.c4_remove_destroy2 = "Confirmar: destruir" L.c4_disarm = "Desactivar C4" L.c4_disarm_cut = "Click para cortar el cable {num}" +L.c4_disarm_t = "Corta un cable para desactivar la bomba. Cualquiera vale, porque eres traidor. ¡Los inocentes no lo tienen tan fácil!" L.c4_disarm_owned = "Corta un cable para desactivar la bomba. Es tu bomba, así que cualquier cable la desactivará." L.c4_disarm_other = "Corta un cable para desactivar la bomba ¡Explotará si cortas el incorrecto!" @@ -296,7 +254,6 @@ L.c4_status_disarmed = "DESACTIVADA" -- Visualizer L.vis_name = "Visualizador" -L.vis_hint = "Pulsa {usekey} para recogerlo (Sólo detectives)." L.vis_desc = [[ Dispositivo para visualizar crímenes. @@ -305,7 +262,6 @@ Analiza el cuerpo para saber cómo la víctima fue aseinada, únicamente si muri -- Decoy L.decoy_name = "Señuelo" -L.decoy_no_room = "No puedes llevar este señuelo." L.decoy_broken = "¡Tu señuelo fue destruído!" L.decoy_short_desc = "Este señuelo muestra una señal de vida falsa en el radar" @@ -316,7 +272,6 @@ Muestra una señal falsa en el radar,y hace que el escáner ADN muestre una fals -- Defuser L.defuser_name = "Kit de Desactivación" -L.defuser_help = "{primaryfire} desactiva el C4." L.defuser_desc = [[ Desactiva instantáneamente un C4. @@ -335,7 +290,6 @@ Quemar un cuerpo hace un sonido distintivo.]] L.hstation_name = "Estación de salud" L.hstation_broken = "¡Tu estación de salud ha sido destruida!" -L.hstation_help = "{primaryfire} coloca una estación de salud." L.hstation_desc = [[ Permite que las personas se curen. @@ -359,7 +313,6 @@ El daño de energía daña a las personas en corta distancia.]] -- Radio L.radio_broken = "¡Tu radio fue destruida!" -L.radio_help_pri = "{primaryfire} coloca la Radio." L.radio_desc = [[ Reproduce sonidos para distraer o confundir. @@ -405,7 +358,7 @@ L.dna_killer = "¡Se ha encontrado una muestra de ADN del asesino en el cadáver L.dna_duplicate = "¡Duplicado! Ya tienes esta muestra de ADN en tu escáner." L.dna_no_killer = "El ADN no pudo ser analizado (¿Asesino desconectado?)." L.dna_armed = "¡Hay una bomba colocada! ¡Desactívala primero!" -L.dna_object = "Se han encontrado {num} muestra(s) de ADN nueva(s) del objeto." +--L.dna_object = "Collected a sample of the last owner from the object." L.dna_gone = "No se han encontrado restos de ADN en esta zona." L.dna_desc = [[ @@ -474,7 +427,7 @@ L.hp_wounded = "Herido" L.hp_badwnd = "Gravemente herido" L.hp_death = "Casi muerto" --- TargetID karma status +-- TargetID Karma status L.karma_max = "Respetable" L.karma_high = "Vulgar" L.karma_med = "Gatillo fácil" @@ -483,14 +436,12 @@ L.karma_min = "Responsable" -- TargetID misc L.corpse = "Cadáver" -L.corpse_hint = "Pulsa [{usekey}] para inspeccionar. [{walkkey} + {usekey}] para inspeccionar silenciosamente." +--L.corpse_hint = "Press [{usekey}] to search and confirm. [{walkkey} + {usekey}] to search covertly." L.target_disg = "(disfrazado)" L.target_unid = "Cuerpo sin identificar" --L.target_unknown = "A Terrorist" -L.target_credits = "Inspecciona para recibir los créditos no usados" - -- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "Un solo uso" L.tbut_reuse = "Reutilizable" @@ -505,7 +456,6 @@ L.mute_off = "Nadie silenciado" -- Spectators and prop possession L.punch_title = "PUÑÓMETRO" -L.punch_help = "Teclas de movimiento o salto: golpea el objeto. Agacharse: dejar el objeto." L.punch_bonus = "Tu bajo puntaje redujo el límite de tu PUÑÓMETRO a {num}" L.punch_malus = "¡Tu alto puntaje incrementó el límite de tu PUÑÓMETRO por {num}!" @@ -933,16 +883,13 @@ L.shop_role_select = "Selecciona un rol" L.shop_role_selected = "La tienda de {role} fue seleccionada" L.shop_search = "Buscar" -L.spec_help = "Click para ver o presiona {usekey} en un prop (physics) para controlarlo." -L.spec_help2 = "Para salir del modo espectador, abre el menú presionando {helpkey}, ve a 'Jugabilidad' y desactiva el modo espectador." - -- 2019-10-19 --L.drop_ammo_prevented = "Something prevents you from dropping your ammo." -- 2019-10-28 L.target_c4 = "Pulsa [{usekey}] para abrir el menú de C4" L.target_c4_armed = "Pulsa [{usekey}] para desactivar el C4" -L.target_c4_armed_defuser = "Pulsa [{usekey}] para usar el kit de desactivación" +L.target_c4_armed_defuser = "Pulsa [{primaryfire}] para usar el kit de desactivación" L.target_c4_not_disarmable = "No puedes desactivar el C4 de un compañero vivo" L.c4_short_desc = "Algo muy explosivo" @@ -950,16 +897,15 @@ L.target_pickup = "Pulsa [{usekey}] para recoger" L.target_slot_info = "Espacio: {slot}" L.target_pickup_weapon = "Pulsa [{usekey}] para recoger el arma" L.target_switch_weapon = "Pulsa [{usekey}] para intercambiar con tu arma actual" -L.target_pickup_weapon_hidden = ", pulsa [{usekey} + {walkkey}] para recogerla silenciosamente" -L.target_switch_weapon_hidden = ", pulsa [{usekey} + {walkkey}] para intercambiarla silenciosamente" +L.target_pickup_weapon_hidden = ", pulsa [{walkkey} + {usekey}] para recogerla silenciosamente" +L.target_switch_weapon_hidden = ", pulsa [{walkkey} + {usekey}] para intercambiarla silenciosamente" L.target_switch_weapon_nospace = "No hay espacio disponible para esta arma en tu inventario" L.target_switch_drop_weapon_info = "Soltando {name} del espacio {slot}" L.target_switch_drop_weapon_info_noslot = "No hay un arma que esté ocupando el espacio {slot}" -L.corpse_searched_by_detective = "Este cadáver fue inspeccionado por un detective." +--L.corpse_searched_by_detective = "This corpse was searched by a public policing role" L.corpse_too_far_away = "El cadáver está muy lejos." -L.radio_pickup_wrong_team = "No puedes recoger la radio de otro equipo." L.radio_short_desc = "Los sonidos de las armas son música para mis oídos" L.hstation_subtitle = "Mantén presionado [{usekey}] para recibir curación." @@ -1004,7 +950,6 @@ L.mute_team = "{team} silenciado." L.door_auto_closes = "Esta puerta se cierra automáticamente." L.door_open_touch = "Acércate a la puerta para abrirla." L.door_open_touch_and_use = "Acércate a la puerta y pulsa [{usekey}] para abrirla." -L.hud_health = "Salud" -- 2020-03-09 --L.help_title = "Help and Settings" @@ -1026,7 +971,7 @@ L.hud_health = "Salud" --L.menu_bindings_description = "Bind specific features of TTT2 and its addons to your own liking." --L.menu_language_description = "Select the language of the gamemode." --L.menu_appearance_description = "Tweak the appearance and performance of the UI." ---L.menu_gameplay_description = "Avoid roles and tweak some features." +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." --L.menu_addons_description = "Configure local addons to your liking." --L.menu_legacy_description = "A panel with converted tabs from the original TTT that should be ported over to the new system." --L.menu_administration_description = "General settings for HUDs, shops etc." @@ -1050,10 +995,8 @@ L.hud_health = "Salud" --L.submenu_appearance_dmgindicator_title = "Damage Indicator" --L.submenu_appearance_performance_title = "Performance" --L.submenu_appearance_interface_title = "Interface" ---L.submenu_appearance_miscellaneous_title = "Misellaneous" --L.submenu_gameplay_general_title = "General" ---L.submenu_gameplay_avoidroles_title = "Avoid Role Selection" --L.submenu_administration_hud_title = "HUD Settings" --L.submenu_administration_randomshop_title = "Random Shop" @@ -1090,17 +1033,12 @@ L.hud_health = "Salud" --L.label_shop_show_custom = "Show custom item marker" --L.label_shop_show_fav = "Show favourite item marker" --L.label_crosshair_enable = "Enable crosshair" ---L.label_crosshair_gap_enable = "Enable custom crosshair gap" ---L.label_crosshair_gap = "Custom crosshair gap" --L.label_crosshair_opacity = "Crosshair opacity" --L.label_crosshair_ironsight_opacity = "Ironsight crosshair opacity" ---L.label_crosshair_size = "Crosshair size" ---L.label_crosshair_thickness = "Crosshair thickness" ---L.label_crosshair_thickness_outline = "Crosshair outline thickness" ---L.label_crosshair_static_enable = "Enable static crosshair" ---L.label_crosshair_dot_enable = "Enable crosshair dot" ---L.label_crosshair_lines_enable = "Enable crosshair lines" ---L.label_crosshair_scale_enable = "Enable weapon dependant weapon scale" +--L.label_crosshair_size = "Crosshair size multiplier" +--L.label_crosshair_thickness = "Crosshair thickness multiplier" +--L.label_crosshair_thickness_outline = "Crosshair outline thickness multiplier" +--L.label_crosshair_scale_enable = "Enable dynamic crosshair scale" --L.label_crosshair_ironsight_low_enabled = "Lower weapon when using ironsights" --L.label_damage_indicator_enable = "Enable damage indicator" --L.label_damage_indicator_mode = "Select damage indicator theme" @@ -1119,13 +1057,10 @@ L.hud_health = "Salud" --L.label_gameplay_fastsw = "Fast weapon switch" --L.label_gameplay_hold_aim = "Enable hold to aim" --L.label_gameplay_mute = "Mute living players when dead" ---L.label_gameplay_dtsprint_enable = "Enable double tap sprinting" ---L.label_gameplay_dtsprint_anykey = "Continue double tap sprinting until you stop moving" --L.label_hud_default = "Default HUD" --L.label_hud_force = "Forced HUD" --L.label_bind_weaponswitch = "Pickup Weapon" ---L.label_bind_sprint = "Sprint" --L.label_bind_voice = "Global Voice Chat" --L.label_bind_voice_team = "Team Voice Chat" @@ -1149,7 +1084,6 @@ L.hud_health = "Salud" --L.header_performance_settings = "Performance Settings" --L.header_interface_settings = "Interface Settings" --L.header_gameplay_settings = "Gameplay Settings" ---L.header_roleselection = "Select Avoiding Roles" --L.header_hud_administration = "Select Default and Forced HUDs" --L.header_hud_enabled = "Enable/Disable HUDs" @@ -1196,11 +1130,7 @@ L.hud_revival_time = "{time}s" L.door_destructible = "La puerta es desctructible (VIDA {health})" -- 2020-05-28 -L.confirm_detective_only = "Sólo los detectives pueden confirmar cuerpos" -L.inspect_detective_only = "Sólo los detectives pueden inspeccionar cuerpos" -L.corpse_hint_no_inspect = "Sólo los detectives pueden inspeccionar este cuerpo." -L.corpse_hint_inspect_only = "Pulsa [{usekey}] para inspeccionar. Sólo los detectives pueden confirmar este cuerpo." -L.corpse_hint_inspect_only_credits = "Pulsa [{usekey}] para recibir los créditos. Sólo los detectives pueden inspeccionar este cuerpo." +--L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." -- 2020-06-04 L.label_bind_disguiser = "Disfraz" @@ -1215,7 +1145,6 @@ L.binoc_help_sec = "Cambiar nivel de zoom." L.vis_help_pri = "Soltar el dispositivo activo." -L.decoy_help_pri = "Colocar el señuelo." -- 2020-08-07 L.pickup_error_spec = "No puedes recoger esto como espectador." @@ -1226,7 +1155,7 @@ L.pickup_error_noslot = "No puedes recoger esto porque no tienes un espacio disp --L.lang_server_default = "Server Default" --L.help_lang_info = [[ --This translation is {coverage}% complete with the English language taken as a default reference. - +-- --Keep in mind that these translations are made by the community. Feel free to contribute if something is missing or incorrect.]] -- 2021-04-13 @@ -1421,7 +1350,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.spawneditor_place = "Place spawn" --L.spawneditor_remove = "Remove spawn" --L.spawneditor_change = "Change spawn type (hold [SHIFT] to reverse)" ---L.spawneditor_ammo_edit = "Hold to edit amount of autospawning ammo on weapon spawns" +--L.spawneditor_ammo_edit = "Hold on weapon spawn to edit autospawning ammo" --L.spawn_weapon_random = "Random Weapon Spawn" --L.spawn_weapon_melee = "Melee Weapon Spawn" @@ -1439,7 +1368,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.spawn_ammo_shotgun = "Shotgun ammo spawn" --L.spawn_player_random = "Random player spawn" ---L.spawn_weapon_ammo = " (Ammo: {ammo})" +--L.spawn_weapon_ammo = "(Ammo: {ammo})" --L.spawn_weapon_edit_ammo = "Hold [{walkkey}] and press [{primaryfire} or {secondaryfire}] to increase or decrease the ammo for this weapon spawn" @@ -1461,18 +1390,18 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.help_spawn_editor_info = [[ --The spawn editor is used to place, remove and edit spawns in the world. These spawns are for weapons, ammunition and players. - +-- --These spawns are saved in files located in 'data/ttt/weaponspawnscripts/'. They can be deleted for a hard reset. The initial spawn files are created from spawns found on the map and in the original TTT weapon spawn scripts. Pressing the reset button always reverts to the initial state. - +-- --It should be noted that this spawn system uses dynamic spawns. This is most interesting for weapons because it no longer defines a specific weapon, but a type of weapons. For example instead of a TTT shotgun spawn, there is now a general shotgun spawn where any weapon defined as shotgun can spawn. The spawn type for each weapon can be set in the 'Edit Equipment' menu. This makes it possible for any weapon to spawn on the map, or to disable certain default weapons. - +-- --Keep in mind that many changes only take effect after a new round has started.]] --L.help_spawn_editor_enable = "On some maps it might be advised to use the original spawns found on the map without replacing them with the dynamic system. Changing this option below only affects the currently active map, so the dynamic system will still be used for every other map." --L.help_spawn_editor_hint = "Hint: To leave the spawn editor, reopen the gamemode menu." --L.help_spawn_editor_spawn_amount = [[ --There currently are {weapon} weapon spawns, {ammo} ammunition spawns and {player} player spawns on this map. --Click 'start spawn edit' to change this amount. - +-- --{weaponrandom}x Random weapon spawn --{weaponmelee}x Melee weapon spawn --{weaponnade}x Grenade weapon spawn @@ -1481,21 +1410,21 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --{weaponsniper}x Sniper weapon spawn --{weaponpistol}x Pistol weapon spawn --{weaponspecial}x Special weapon spawn - +-- --{ammorandom}x Random ammo spawn --{ammodeagle}x Deagle ammo spawn --{ammopistol}x Pistol ammo spawn --{ammomac10}x Mac10 ammo spawn --{ammorifle}x Rifle ammo spawn --{ammoshotgun}x Shotgun ammo spawn - +-- --{playerrandom}x Random player spawn]] --L.equipmenteditor_name_auto_spawnable = "Equipment spawns randomly in world" --L.equipmenteditor_name_spawn_type = "Select spawn type" --L.equipmenteditor_desc_auto_spawnable = [[ --The TTT2 spawn system allows every weapon to spawn in the world. By default only weapons marked as 'AutoSpawnable' by the creator will spawn in the world, however this can be changed from within this menu. - +-- --Most of the equipment is set to 'special weapon spawns' by default. This means that equipment only spawns on random weapon spawns. However it is possible to place special weapon spawns in the world or change the spawn type here to use other existing spawn types.]] --L.pickup_error_inv_cached = "You cannot pick this up right now because your inventory is cached." @@ -1512,14 +1441,14 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.help_prefer_map_models = [[ --Some maps define their own player models. By default these models have a higher priority than those that are assigned automatically. By disabling this setting, map specific models are disabled. - +-- --Role specific models always have a higher priority and are unaffected by this setting.]] --L.help_enforce_playermodel = [[ --Some roles have custom player models. They can be disabled which can be relevant for compatibility with some player model selectors. --Random default models can still be selected, if this setting is disabled.]] --L.help_use_custom_models = [[ --By default only the CS:S Phoenix player model is assigned to all players. By enabling this option however it is possible to select a player model pool. With this setting enabled each player will still be assigned the same player model, however it is a random model from the defined model pool. - +-- --This selection of models can be extended by installing more player models.]] -- 2021-10-06 @@ -1536,7 +1465,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por -- 2021-10-09 --L.help_models_select = [[ --Left click on the models to add them to the player model pool. Left click again to remove them. Right clicking toggles between enabled and disabled detective hats for the focused model. - +-- --The small indicator in the top left shows if the player model has a head hitbox. The icon below shows if this model is applicable for a detective hat.]] --L.menu_roles_title = "Role Settings" @@ -1559,7 +1488,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.help_roles_selection = [[ --The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role. This also means that at least 5 players are needed for this role to be distributed at all. --Keep in mind that all of this only applies if the role is considered for distribution process. - +-- --The aforementioned role distribution has a special integration with the lower limit of players. If the role is considered for distribution and the minimum value is below the value given by the distribution factor, but the amount of players is equal or greater than the lower limit, a single player can still receive this role. The distribution process then works as usual for the second player.]] --L.help_roles_award_info = "Some roles (if enabled in their credits settings) receive equipment credits if a certain percentage of enemies has died. Related values can be tweaked here." --L.help_roles_award_pct = "When this percentage of enemies are dead, specific roles are awarded equipment credits." @@ -1567,17 +1496,17 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.help_roles_advanced_warning = "WARNING: These are advanced settings that can completely mess up the role distribution process. When in doubt keep all values at '0'. This value means that no limits are applied and the role distribution will try to assign as many roles as possible." --L.help_roles_max_roles = [[ --The term roles here includes both the base roles and the subroles. By default, there is no limit on how many different roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.help_roles_max_baseroles = [[ --Base roles are only those roles others inherit from. For example, the Innocent role is a base role, while a Pharaoh is a subrole of this role. By default, there is no limit on how many different base roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.label_roles_enabled = "Enable role" @@ -1609,15 +1538,15 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.help_roles_credits_award_kill = "Another way of gaining credits is by killing high value players with a 'public role' such as a Detective. If the killer's role has this enabled, they gain the below defined amount of credits." --L.help_roles_credits_award = [[ --There are two different ways to be awarded credits in base TTT2: - +-- --1. If a certain percentage of the enemy team is dead, the whole team is awarded credits. --2. If a player killed a high value player with a 'public role' such as a Detective, the killer is awarded credits. - +-- --Please note, that this still can be enabled/disabled for every role, even if the whole team is awarded. For example, if team Innocent is awarded, but the Innocent role has this disabled, only the Detective will receive their credits. --The balancing values for this feature can be set in 'Administration' -> 'General Role Settings'.]] --L.help_detective_hats = [[ --Policing roles such as the Detective may wear hats to show their authority. They lose them on death or if damaged at the head. - +-- --Some player models do not support hats by default. This can be changed in 'Administration' -> 'Player Models']] --L.label_roles_credits_award_kill = "Credit reward amount for the kill" @@ -1669,35 +1598,35 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.help_idle = "The idle mode is used to forcefully move idle players into the spectator mode. To leave this mode, they will have to disable it in their 'gameplay' menu." --L.help_namechange_kick = [[ --A name change during an active round could be abused. Therefore, this is prohibited by default and will lead to the offending player being kicked from the server. - +-- --If the bantime is greater than 0, the player will be unable to reconnect to the server until that time has passed.]] --L.help_damage_log = "Each time a player is damaged, a damage log entry is added to the console if enabled. This can also be stored to disk after a round has ended. The file is located at 'data/terrortown/logs/'" --L.help_spawn_waves = [[ --If this variable is set to 0, all players are spawned at once. For servers with huge amounts of players, it can be beneficial to spawn the players in waves. The spawn wave interval is the time between each spawn wave. A spawn wave always spawns as many players as there are valid spawn points. - +-- --Note: Make sure that the preparing time is long enough for the desired amount of spawn waves.]] --L.help_voicechat_battery = [[ --Voice chatting with enabled voice chat battery reduces battery charge. When it's empty, the player can't use voice chat and has to wait for it to recharge. This can help to prevent excessive voice chat usage. - +-- --Note: 'Tick' refers to a game tick. For example, if the tick rate is set to 66, then it will be 1/66th of a second.]] --L.help_ply_spawn = "Player settings that are used on player (re-)spawn." --L.help_haste_mode = [[ --Haste mode balances the game by increasing the round time with every dead player. Only roles that see missing in action players can see the real round time. Every other role can only see the haste mode starting time. - +-- --If haste mode is enabled, the fixed round time is ignored.]] --L.help_round_limit = "After one of the set limit conditions is met, a map change is triggered." --L.help_armor_balancing = "The following values can be used to balance the armor." --L.help_item_armor_classic = "If classic armor mode is enabled, only the previous settings matter. Classic armor mode means that a player can only buy armor once in a round, and that this armor blocks 30% of the incoming bullet and crowbar damage until they die." --L.help_item_armor_dynamic = [[ --Dynamic armor is the TTT2 approach to make armor more interesting. The amount of armor that can be bought is now unlimited, and the armor value stacks. Getting damaged decreases the armor value. The armor value per bought armor item is set in the 'Equipment Settings' of said item. - +-- --When taking damage, a certain percentage of this damage is converted into armor damage, a different percentage is still applied to the player and the rest vanishes. - +-- --If reinforced armor is enabled, the damage applied to the player is decreased by 15% as long as the armor value is above the reinforcement threshold.]] --L.help_sherlock_mode = "The sherlock mode is the classic TTT mode. If the sherlock mode is disabled, dead bodies can not be confirmed, the scoreboard shows everyone as alive and the spectators can talk to the living players." --L.help_prop_possession = [[ --Prop possession can be used by spectators to possess props lying in the world and use the slowly recharging 'punch-o-meter' to move said prop around. - +-- --The maximum value of the 'punch-o-meter' consists of a possession base value, where the kills/deaths difference clamped inbetween two defined limits is added. The meter slowly recharges over time. The set recharge time is the time needed to recharge a single point in the 'punch-o-meter'.]] --L.help_karma = "Players start with a certain amount of Karma, and lose it when they damage/kill teammates. The amount they lose is dependent on the Karma of the person they hurt or killed. Lower Karma reduces damage given." --L.help_karma_strict = "If strict Karma is enabled, the damage penalty increases more quickly as Karma goes down. When it is off, the damage penalty is very low when people stay above 800. Enabling strict mode makes Karma play a larger role in discouraging any unnecessary kills, while disabling it results in a more “loose” game where Karma only hurts players who constantly kill teammates." @@ -1707,7 +1636,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.help_karma_bonus = "There are also two different passive ways to gain Karma during a round. First is a karma restoration which applied to every player at the round end. Then a secondary clean round bonus is given if no teammates were hurt or killed by a player." --L.help_karma_clean_half = [[ --When a player's Karma is above the starting level (meaning the Karma max has been configured to be higher than that), all their Karma increases will be reduced based on how far their Karma is above that starting level. So it goes up slower the higher it is. - +-- --This reduction goes in a curve of exponential decay: initially it's fast, and it slows down as the increment gets smaller. This convar sets at what point the bonus has been halved (so the half-life). With the default value of 0.25, if the starting amount of Karma is 1000 and the max 1500, and a player has Karma 1125 ((1500 - 1000) * 0.25 = 125), then his clean round bonus will be 30 / 2 = 15. So to make the bonus go down faster you’d set this convar lower, to make it go down slower you’d increase it towards 1.]] --L.help_max_slots = "Sets the maximum amount of weapons per slot. '-1' means that there is no limit." --L.help_item_armor_value = "This is the armor value given by the armor item in dynamic mode. If classic mode is enabled (see 'Administration' -> 'Player Settings') then every value greater than 0 is counted as existing armor." @@ -1730,8 +1659,6 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.label_tbutton_admin_show = "Show traitor buttons to admins" --L.label_ragdoll_carrying = "Enable ragdoll carrying" --L.label_prop_throwing = "Enable prop throwing" ---L.label_ragdoll_pinning = "Enable ragdoll pinning for non-Innocent roles" ---L.label_ragdoll_pinning_innocents = "Enable ragdoll pinning for Innocent roles" --L.label_weapon_carrying = "Enable weapon carrying" --L.label_weapon_carrying_range = "Weapon carry range" --L.label_prop_carrying_force = "Prop pickup force" @@ -1761,10 +1688,8 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.label_spectator_chat = "Enable spectators chatting with everybody" --L.label_lastwords_chatprint = "Print last words to chat if killed while typing" --L.label_identify_body_woconfirm = "Identify corpse without pressing the 'confirm' button" ---L.label_announce_body_found = "Announce that a body was found" +--L.label_announce_body_found = "Announce that a body was found when the body was confirmed" --L.label_confirm_killlist = "Announce kill list of confirmed corpse" ---L.label_inspect_detective_only = "Limit corpse search to policing roles only" ---L.label_confirm_detective_only = "Limit corpse confirmation to policing roles only" --L.label_dyingshot = "Shoot on death if in ironsights [experimental]" --L.label_armor_block_headshots = "Enable armor blocking headshots" --L.label_armor_block_blastdmg = "Enable armor blocking blast damage" @@ -1823,7 +1748,6 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.label_sprint_max = "Max sprinting stamina" --L.label_sprint_stamina_consumption = "Stamina consumption factor" --L.label_sprint_stamina_regeneration = "Stamina regeneration factor" ---L.label_sprint_crosshair = "Show crosshair while sprinting" --L.label_crowbar_unlocks = "Primary attack can be used as interaction (i.e. unlocking)" --L.label_crowbar_pushforce = "Crowbar push force" @@ -1836,7 +1760,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.help_falldmg_exponent = [[ --This value modifies how exponentially fall damage is increased with the speed the player hits the ground at. - +-- --Take care when altering this value. Setting it too high can make even the smallest falls lethal, while setting it too low will allow players to fall from extreme heights and suffer little to no damage.]] -- 2023-02-08 @@ -1859,4 +1783,411 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) fue asesinado por --L.sb_rank_tooltip_heroes = "TTT2 Heroes" --L.sb_rank_tooltip_team = "Team" ---L.tbut_adminarea = "ADMIN AREA:" \ No newline at end of file +--L.tbut_adminarea = "ADMIN AREA:" + +-- 2023-08-10 +--L.equipmenteditor_name_damage_scaling = "Damage Scaling" + +-- 2023-08-11 +--L.equipmenteditor_name_allow_drop = "Allow Drop" +--L.equipmenteditor_desc_allow_drop = "If enabled, the equipment can be dropped freely by the player." + +--L.equipmenteditor_name_drop_on_death_type = "Drop on Death" +--L.equipmenteditor_desc_drop_on_death_type = "Attempt overriding the action taken for whether the equipment is dropped on player's death." + +--L.drop_on_death_type_default = "Default (weapon-defined)" +--L.drop_on_death_type_force = "Force Drop on Death" +--L.drop_on_death_type_deny = "Deny Drop on Death" + +-- 2023-08-26 +--L.equipmenteditor_name_kind = "Equipment Slot" +--L.equipmenteditor_desc_kind = "The inventory slot the equipment will occupy." + +--L.slot_weapon_melee = "Melee Slot" +--L.slot_weapon_pistol = "Pistol Slot" +--L.slot_weapon_heavy = "Heavy Slot" +--L.slot_weapon_nade = "Grenade Slot" +--L.slot_weapon_carry = "Carry Slot" +--L.slot_weapon_unarmed = "Unarmed Slot" +--L.slot_weapon_special = "Special Slot" +--L.slot_weapon_extra = "Extra Slot" +--L.slot_weapon_class = "Class Slot" + +-- 2023-10-04 +--L.label_voice_duck_spectator = "Duck spectator voices" +--L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +--L.label_voice_scaling = "Voice Volume Scaling Mode" +--L.label_voice_scaling_mode_linear = "Linear" +--L.label_voice_scaling_mode_power4 = "Power 4" +--L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "Resultados de búsqueda - {player}" +L.search_info = "Información" +L.search_confirm = "Confirmar Muerte" +--L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +--L.search_take_credits = "Take {credits} Credit(s)" +--L.search_confirm_forbidden = "Confirm forbidden" +--L.search_confirmed = "Death Confirmed" +--L.search_call = "Report Death" +--L.search_called = "Death Reported" + +--L.search_team_role_unknown = "???" + +L.search_words = "Algo te dice una de las últimas palabras de esta persona fueron: '{lastwords}'" +L.search_armor = "Estaba utilizando protección antibalas." +L.search_disguiser = "Estaba utilizando un dispositivo para camuflar su identidad." +L.search_radar = "Estaba llevando un radar. Pero ya no funciona." +L.search_c4 = "En el bolsillo encuentras una nota. Pone que cortando el cable {num} se desactiva una bomba." + +L.search_dmg_crush = "Varios de sus huesos están rotos. Parece ser que ha muerto por el impacto de un objeto." +L.search_dmg_bullet = "Es evidente que le han disparado hasta morir." +L.search_dmg_fall = "Cayó directo hacia su muerte." +L.search_dmg_boom = "Sus heridas y la ropa chamuscada indican que una explosión acabó con la vida de esta persona." +L.search_dmg_club = "El cuerpo está golpeado y amorotonado. Fue apalizado hasta la muerte." +L.search_dmg_drown = "El cuerpo muestra signos de axfisia. Ha muerto ahogado" +L.search_dmg_stab = "Fue apuñalado y cortado para después desangrarse rápidamente hasta morir." +L.search_dmg_burn = "Aquí huele a terrorista quemado..." +L.search_dmg_teleport = "¡Parece que su ADN fue alterado por partículas de taquión!" +L.search_dmg_car = "Cuando este terrorista quiso crusar la calle, fue atropellado por un conductor descuidado." +L.search_dmg_other = "No puedes determinar la causa de muerte de esta persona." + +--L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +--L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +--L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +--L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +--L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +--L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +--L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +--L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +--L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +--L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +--L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +--L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +--L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +--L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +--L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +--L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +--L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +--L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +--L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +--L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +--L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +--L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +--L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +--L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +--L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "Parece que se uso un/una {weapon} para matarlo." +L.search_head = "Murió de un solo disparo en la cabeza. No le dio tiempo a gritar." +--L.search_time = "They died a while before you conducted the search." +--L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "Has encontrado una lista de asesinatos que confirman la muerte de {player}." +L.search_kills2 = "Has encontrado una lista de asesinatos con los siguientes nombres: {player}" +L.search_eyes = "Gracias a tus habilidades de detective, has identificado que la última persona a la que vio fue: {player}. ¿Es el asesino o sólo fue una coincidencia?" + +--L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +--L.search_kill_distance_point_blank = "It was a point blank attack." +--L.search_kill_distance_close = "The attack came from a short distance." +--L.search_kill_distance_far = "The victim was attacked from a long distance away." + +--L.search_kill_from_front = "The victim was shot from the front." +--L.search_kill_from_back = "The victim was shot from behind." +--L.search_kill_from_side = "The victim was shot from the side." + +--L.search_hitgroup_head = "The projectile was found in their head." +--L.search_hitgroup_chest = "The projectile was found in their chest." +--L.search_hitgroup_stomach = "The projectile was found in their stomach." +--L.search_hitgroup_rightarm = "The projectile was found in their right arm." +--L.search_hitgroup_leftarm = "The projectile was found in their left arm." +--L.search_hitgroup_rightleg = "The projectile was found in their right leg." +--L.search_hitgroup_leftleg = "The projectile was found in their left leg." +--L.search_hitgroup_gear = "The projectile was found in their hip." + +--L.search_policingrole_report_confirm = [[ +--A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +--L.search_policingrole_confirm_disabled_1 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +--L.search_policingrole_confirm_disabled_2 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know! +--You can see the information in here after they confirmed it.]] +--L.search_spec = [[ +--As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +--L.search_title_words = "Victim's last words" +--L.search_title_c4 = "Defusion mishap" +--L.search_title_dmg_crush = "Crush damage ({amount} HP)" +--L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +--L.search_title_dmg_fall = "Fall damage ({amount} HP)" +--L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +--L.search_title_dmg_club = "Club damage ({amount} HP)" +--L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +--L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +--L.search_title_dmg_burn = "Burning damage ({amount} HP)" +--L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +--L.search_title_dmg_car = "Car accident ({amount} HP)" +--L.search_title_dmg_other = "Unknown damage ({amount} HP)" +--L.search_title_time = "Death time" +--L.search_title_dna = "DNA sample decay" +--L.search_title_kills = "The victim's kill list" +--L.search_title_eyes = "The killer's shadow" +--L.search_title_floor = "Floor of the crime scene" +--L.search_title_credits = "{credits} Equipment credit(s)" +--L.search_title_water = "Water level {level}" +--L.search_title_policingrole_report_confirm = "Confirm to report death" +--L.search_title_policingrole_confirm_disabled = "Report corpse" +--L.search_title_spectator = "You are a spectator" + +--L.target_credits_on_confirm = "Confirm to receive unspent credits" +--L.target_credits_on_search = "Search to receive unspent credits" +--L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +--L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +--L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +--L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +--L.label_inspect_confirm_mode = "Select body search mode" +--L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +--L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +--L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +--L.help_inspect_confirm_mode = [[ +--There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. +-- +--mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. +-- +--mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. +-- +--mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +--L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +--L.header_hud_elements_customize = "Customize the HUD-Elements" +--L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +--L.help_keyhelp = [[ +--Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: +-- +--Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +--Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +--Equipment: Some equipment items have their own bindings, these are shown in this category. +-- +--Disabled categories are still shown when the scoreboard is visible]] + +--L.label_keyhelp_show_core = "Enable always showing the core bindings" +--L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +--L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +--L.header_interface_keys = "Key helper settings" +--L.header_interface_wepswitch = "Weapon switch UI settings" + +--L.label_keyhelper_help = "open gamemode menu" +--L.label_keyhelper_mutespec = "cycle spectator voice mode" +--L.label_keyhelper_shop = "open equipment shop" +--L.label_keyhelper_show_pointer = "free mouse pointer" +--L.label_keyhelper_possess_focus_entity = "possess focused entity" +--L.label_keyhelper_spec_focus_player = "spectate focused player" +--L.label_keyhelper_spec_previous_player = "previous player" +--L.label_keyhelper_spec_next_player = "next player" +--L.label_keyhelper_spec_player = "spectate random player" +--L.label_keyhelper_possession_jump = "prop: jump" +--L.label_keyhelper_possession_left = "prop: left" +--L.label_keyhelper_possession_right = "prop: right" +--L.label_keyhelper_possession_forward = "prop: forward" +--L.label_keyhelper_possession_backward = "prop: backward" +--L.label_keyhelper_free_roam = "leave object and roam free" +--L.label_keyhelper_flashlight = "toggle flashlight" +--L.label_keyhelper_quickchat = "open quickchat" +--L.label_keyhelper_voice_global = "global voice chat" +--L.label_keyhelper_voice_team = "team voice chat" +--L.label_keyhelper_chat_global = "global chat" +--L.label_keyhelper_chat_team = "team chat" +--L.label_keyhelper_show_all = "show all" +--L.label_keyhelper_disguiser = "toggle disguiser" +--L.label_keyhelper_save_exit = "save and exit" +--L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +--L.item_armor_reinforced = "Reinforced Armor" +--L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +--L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +--L.status_speed_name = "Speed Multiplier" +--L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +--L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +--L.status_on = "on" +--L.status_off = "off" + +--L.crowbar_help_primary = "Attack" +--L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +--L.help_HUD_enable_description = [[ +--Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +--L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +--L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +--L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +--L.header_soundeffect_settings = "Sound Effects" +--L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +--L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +--L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +--L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +--L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +--L.help_prop_spec_dash = [[ +--Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. +-- +--This variable is a multiplier of the push force.]] +--L.label_spec_prop_dash = "Dash force multiplier" +--L.label_keyhelper_possession_dash = "prop: dash in view direction" +--L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +--L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +--L.c4_help_primary = "Place the C4" +--L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +--L.magneto_help_primary = "Push entity" +--L.magneto_help_secondary = "Pull / pickup entity" +--L.knife_help_primary = "Stab" +--L.knife_help_secondary = "Throw knife" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +--L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +--L.newton_force = "FORCE" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +--L.beacon_help_pri = "Throw Beacon on the ground" +--L.beacon_help_sec = "Stick Beacon to surface" +--L.beacon_name = "Beacon" +--L.beacon_desc = [[ +--Broadcasts player locations to everyone in a sphere around this beacon. +-- +--Use to keep track of locations on the map that are hard to see.]] + +--L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +--L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +--L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +--L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +--L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder} confirmó la muerte de {victim}." +--L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +--L.builtin_marker = "Built-in." +--L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +--L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +--L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +--L.submenu_gameplay_accessibility_title = "Accessibility" + +--L.header_accessibility_settings = "Accessibility Settings" + +--L.label_enable_dynamic_fov = "Enable dynamic FOV change" +--L.label_enable_bobbing = "Enable view bobbing" +--L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +--L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +--L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +--L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +--For a shotgun, this would affect each pellet. +--For a rifle, this would affect just the bullet. +--For the poltergeist, this would affect each "thump" and the final explosion. +-- +--0.5 = Deal half the amount of damage. +--2 = Deal twice the amount of damage. +-- +--Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +--L.binoc_help_reload = "Clear target." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +--L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +--L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +--L.label_crosshair_mode = "Crosshair mode" +--L.label_crosshair_static_length = "Enable static crosshair line length" + +--L.choice_crosshair_mode_0 = "Lines and dot" +--L.choice_crosshair_mode_1 = "Lines only" +--L.choice_crosshair_mode_2 = "Dot only" + +--L.help_crosshair_scale_enable = [[ +--Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. +-- +--If the line length is kept static, only the gap scales with cone changes.]] + +--L.header_weapon_settings = "Weapon Settings" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +L.decoy_help_primary = "Colocar el señuelo" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/fr.lua b/lua/terrortown/lang/fr.lua index 50530a2fc..5799a0435 100644 --- a/lua/terrortown/lang/fr.lua +++ b/lua/terrortown/lang/fr.lua @@ -55,13 +55,11 @@ L.karma_dmg_other = "Votre Karma est à {amount}. De ce fait, tous les dégâts L.body_found = "{finder} à trouvé le corps de {victim}. {role}" L.body_found_team = "{finder} à trouvé le corps de {victim}. {role} ({team})" --- The {role} in body_found will be replaced by one of the following : +-- The {role} in body_found will be replaced by one of the following: L.body_found_traitor = "C'était un Traitre!" L.body_found_det = "C'était un Détective." L.body_found_inno = "C'était un Innocent." -L.body_confirm = "{finder} a confirmé la mort de {victim}." - L.body_call = "{player} appelle un Détective sur le corps de {victim}!" L.body_call_error = "Veuillez identifier le corps avant d'appeler un Détective!" @@ -172,46 +170,6 @@ L.quick_disg = "Quelqu'un de déguisé" L.quick_corpse = "un corps non-identifié" L.quick_corpse_id = "le corps de {player}" --- Body search window -L.search_title = "Résultats de la fouille" -L.search_info = "Information" -L.search_confirm = "Confirmer la mort" -L.search_call = "Appeler un Détective" - --- Descriptions of pieces of information found -L.search_nick = "C'est le corps de {player}." - -L.search_role_traitor = "C'était un Traitre!" -L.search_role_det = "C'était un Détective." -L.search_role_inno = "C'était un Terroriste Innocent." - -L.search_words = "Quelque chose vous dit que quelques-unes des dernières paroles de cette personne étaient: '{lastwords}'" -L.search_armor = "Il avait une armure non-standard." -L.search_disg = "Il portait un dispositif qui permet de cacher son identité." -L.search_radar = "Il portait une sorte de radar. Ce radar ne fonctionne plus." -L.search_c4 = "Dans une poche, il y a une note. Elle dit que couper le fil numéro {num} va désamorcer le C4." - -L.search_dmg_crush = "Il a beaucoup de fractures. On dirait que l'impact d'un objet lourd l'a tué." -L.search_dmg_bullet = "Il est évident qu'on lui a tiré dessus jusqu'à la mort." -L.search_dmg_fall = "Une chute lui a été fatale." -L.search_dmg_boom = "Les blessures et les vêtements déchirés indiquent qu'une explosion l'a tué." -L.search_dmg_club = "Il est couvert d'ecchymoses et semble avoir été battu. Très clairement, il a été frappé à mort." -L.search_dmg_drown = "Le corps montre les signes d'une inévitable noyade." -L.search_dmg_stab = "Il s'est fait couper et poignarder avant de saigner à mort." -L.search_dmg_burn = "Ça sent le terroriste grillé, non?" -L.search_dmg_tele = "On dirait que son ADN a été altéré par des émissions de tachyons!" -L.search_dmg_car = "Pendant que cette personne traversait la route, il s'est fait rouler dessus par un conducteur imprudent." -L.search_dmg_other = "Vous n'arrivez pas à identifier la cause de sa mort." - -L.search_weapon = "Il semblerait qu'un {weapon} a été utilisé pour le tuer." -L.search_head = "Une blessure fatale a été portée à la tête. Impossible de crier." -L.search_time = "Il est mort {time} avant que vous fassiez l'enquête." -L.search_dna = "Ramassez un échantillon de l'ADN du tueur avec un Scanner ADN. Cet échantillon va se décomposer dans à peu près {time}." - -L.search_kills1 = "Vous avez trouvé une liste de meurtres qui confirme la mort de {player}." -L.search_kills2 = "Vous avez trouvé une liste de meurtres avec les noms suivants:" -L.search_eyes = "En utilisant vos compétences de détective, vous avez identifié la dernière personne qu'il a vue: {player}. Serait-ce le tueur, ou une coïncidence?" - -- Scoreboard L.sb_playing = "Vous jouez sur..." L.sb_mapchange = "On change de carte dans {num} round(s) ou dans {time}" @@ -267,7 +225,6 @@ Cache votre ID. Évite de paraître comme la dernière personne vue avant de mou Activer/Désactiver le déguisement vers l'onglet Déguisement de ce menu ou appuyer sur Numpad Enter.]] -- C4 -L.c4_hint = "Utilisez {usekey} pour amorcer ou désamorcer." L.c4_disarm_warn = "Un explosif C4 que vous avez planté a été désamorcé." L.c4_armed = "Vous avez amorcé le C4 avec succès." L.c4_disarmed = "Vous avez désamorcé le C4 avec succès." @@ -288,6 +245,7 @@ L.c4_remove_destroy2 = "Confirmer: destruction" L.c4_disarm = "Désamorcer le C4" L.c4_disarm_cut = "Couper le fil {num}" +L.c4_disarm_t = "Coupez un fil pour désamorcer la bombe. En tant que Traitre, tous les fils fonctionneront. Les innocents n'ont pas cette chance !" L.c4_disarm_owned = "Coupez un fil pour désamorcer la bombe. C'est votre bombe, donc tous les fils fonctionneront." L.c4_disarm_other = "Coupez un fil pour désamorcer la bombe. Si vous vous trompez, ça va péter!" @@ -296,7 +254,6 @@ L.c4_status_disarmed = "DÉSARMÉE" -- Visualizer L.vis_name = "Visualiseur" -L.vis_hint = "Appuyez sur {usekey} pour ramasser (Détectives seulement)." L.vis_desc = [[ Dispositif de visualisation de scène de crime. @@ -305,7 +262,6 @@ Analyse un corps pour montrer comment la victime a été tuée, mais seulement s -- Decoy L.decoy_name = "Leurre" -L.decoy_no_room = "Vous ne pouvez pas prendre ce leurre." L.decoy_broken = "Votre leurre a été détruit!" L.decoy_short_desc = "Ce leurre montre un faux signal radar visible par les autres teams" @@ -316,7 +272,6 @@ Montre un faux signe sur le radar des autres teams, et fait que leur scanner ADN -- Defuser L.defuser_name = "Kit de désamorçage" -L.defuser_help = "{primaryfire} désamorce le C4 ciblé." L.defuser_desc = [[ Désamorce instantanément un explosif C4. @@ -335,7 +290,6 @@ Brûler un corps fait un son distinct.]] L.hstation_name = "Station de Soins" L.hstation_broken = "Votre Station de Soins a été détruite!" -L.hstation_help = "{primaryfire} place la Station de Soins." L.hstation_desc = [[ Soigne les personnes qui l'utilisent. @@ -359,7 +313,6 @@ Ces éclats d'énergie peuvent frapper les gens à proximité.]] -- Radio L.radio_broken = "Votre Radio a été détruite!" -L.radio_help_pri = "{primaryfire} place la Radio." L.radio_desc = [[ Joue des sons pour distraire ou tromper. @@ -405,7 +358,7 @@ L.dna_killer = "Échantillon ADN du tueur récupéré sur le corps!" --L.dna_duplicate = "Match! You already have this DNA sample in your scanner." L.dna_no_killer = "L'ADN n'a pas pu être récupérée (le tueur s'est déconnecté?)." L.dna_armed = "La bombe est amorcée! Désamorcez-la d'abord!" -L.dna_object = "Vous avez collecté {num} échantillon(s) d'ADN sur cet objet." +--L.dna_object = "Collected a sample of the last owner from the object." L.dna_gone = "Aucun ADN détecté dans la zone." L.dna_desc = [[ @@ -474,7 +427,7 @@ L.hp_wounded = "Blessé" L.hp_badwnd = "Grièvement blessé" L.hp_death = "Proche de la Mort" --- TargetID karma status +-- TargetID Karma status L.karma_max = "Réputé" L.karma_high = "Honnnête" L.karma_med = "Gâchette facile" @@ -483,14 +436,12 @@ L.karma_min = "Irresponsable" -- TargetID misc L.corpse = "Corps" -L.corpse_hint = "Appuyez sur {usekey} pour fouiller. {walkkey} + {usekey} pour fouiller discrètement." +--L.corpse_hint = "Press [{usekey}] to search and confirm. [{walkkey} + {usekey}] to search covertly." L.target_disg = "(DÉGUISÉ)" L.target_unid = "Corps non-identifié" --L.target_unknown = "A Terrorist" -L.target_credits = "Fouiller pour récupérer des crédit(s) non dépensés" - -- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "Usage unique" L.tbut_reuse = "Réutilisable" @@ -505,7 +456,6 @@ L.mute_off = "Aucun mutés" -- Spectators and prop possession L.punch_title = "FRAPPE-O-METRE" -L.punch_help = "Touche de déplacement ou saut: déplace l'objet. S'accroupir: quitter l'objet." L.punch_bonus = "Votre mauvais score a baissé votre limite frappe-o-metre de {num}" L.punch_malus = "Votre bon score a augmenté votre limite frappe-o-metre de {num}!" @@ -933,16 +883,13 @@ L.shop_role_select = "Sélectionnez un rôle" L.shop_role_selected = "{role}'s shop a été sélectionné!" L.shop_search = "Recherche" -L.spec_help = "Cliquez pour voir la vue du joueur, ou {usekey} sur un objet pour en prendre possession." -L.spec_help2 = "Pour quitter le mode spectateur, ouvrez le menu en appuyant sur {helpkey}, allez vers Jouabilité -> Général -> Paramètres de jeu -> 'Mode Spectateur'." - -- 2019-10-19 --L.drop_ammo_prevented = "Something prevents you from dropping your ammo." -- 2019-10-28 L.target_c4 = "Appuyez sur [{usekey}] pour ouvrir le menu du C4" L.target_c4_armed = "Appuyez sur [{usekey}] pour désarmer le C4" -L.target_c4_armed_defuser = "Appuyez sur [{usekey}] pour désamorcer le C4" +L.target_c4_armed_defuser = "Appuyez sur [{primaryfire}] pour désamorcer le C4" L.target_c4_not_disarmable = "Vous ne pouvez pas désamorcer le C4 d'un autre Traitre, à moins qu'il soit mort" L.c4_short_desc = "Quelque chose de très explosif" @@ -950,16 +897,15 @@ L.target_pickup = "Appuyez sur [{usekey}] pour ramasser" L.target_slot_info = "Emplacement: {slot}" L.target_pickup_weapon = "Appuyez sur [{usekey}] pour ramasser l'arme" L.target_switch_weapon = "Appuyez sur [{usekey}] pour changer d'arme" -L.target_pickup_weapon_hidden = ", Appuyez sur [{usekey} + {walkkey}] pour ramasser discrètement" -L.target_switch_weapon_hidden = ", Appuyez sur [{usekey} + {walkkey}] pour changer discrètement" +L.target_pickup_weapon_hidden = ", Appuyez sur [{walkkey} + {usekey}] pour ramasser discrètement" +L.target_switch_weapon_hidden = ", Appuyez sur [{walkkey} + {usekey}] pour changer discrètement" L.target_switch_weapon_nospace = "Il n'y a pas de place disponible dans l'inventaire pour cette arme" L.target_switch_drop_weapon_info = "Lâcher {name} du slot {slot}" L.target_switch_drop_weapon_info_noslot = "Il n'y a pas d'arme à lâcher dans ce slot {slot}" -L.corpse_searched_by_detective = "Ce cadavre a été fouillé par un détective" +--L.corpse_searched_by_detective = "This corpse was searched by a public policing role" L.corpse_too_far_away = "Ce cadavre est trop loin." -L.radio_pickup_wrong_team = "Vous ne pouvez pas prendre la radio d'une autre team." L.radio_short_desc = "Les sons des armes sont de la musique pour moi" L.hstation_subtitle = "Appuyez sur [{usekey}] pour recevoir des soins." @@ -1004,7 +950,6 @@ L.mute_team = "{team} muet." L.door_auto_closes = "Cette porte se ferme automatiquement" L.door_open_touch = "Marchez vers la porte pour l'ouvrir." L.door_open_touch_and_use = "Marchez vers la porte ou appuyez sur [{usekey}] pour ouvrir." -L.hud_health = "SantÉ" -- 2020-03-09 L.help_title = "Aide et paramètres" @@ -1026,7 +971,7 @@ L.menu_guide_description = "Vous aide à démarrer TTT2 et vous explique certain L.menu_bindings_description = "Configurer les caractéristiques spécifiques de TTT2 et de ses addons" L.menu_language_description = "Sélectionnez la langue du jeu" L.menu_appearance_description = "Modifier l'apparence et la performance de votre UI" -L.menu_gameplay_description = "Bloquer des rôles et modifie certains éléments" +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." L.menu_addons_description = "Configurer les addons locaux à votre convenance" L.menu_legacy_description = "Une interface avec des onglets convertis à partir du TTT original, ils devraient être portés sur le nouveau système" L.menu_administration_description = "Paramètres généraux pour les HUD, les shops, etc." @@ -1050,10 +995,8 @@ L.submenu_appearance_crosshair_title = "Réticule" L.submenu_appearance_dmgindicator_title = "Indicateur de dégats" L.submenu_appearance_performance_title = "Performance" L.submenu_appearance_interface_title = "Interface" -L.submenu_appearance_miscellaneous_title = "Autre" L.submenu_gameplay_general_title = "Général" -L.submenu_gameplay_avoidroles_title = "Blacklistage de rôle" L.submenu_administration_hud_title = "Paramètres HUD" L.submenu_administration_randomshop_title = "Shop Aléatoire" @@ -1090,17 +1033,12 @@ L.label_shop_show_slot = "Afficher le marqueur de slot" L.label_shop_show_custom = "Afficher le marqueur d'objet personnalisé" L.label_shop_show_fav = "Afficher le marqueur d'article favoris" L.label_crosshair_enable = "Activer le réticule" -L.label_crosshair_gap_enable = "Activer le réticule centré" -L.label_crosshair_gap = "Personnalisé l'écartement du réticule centré" L.label_crosshair_opacity = "Opacité du réticule" L.label_crosshair_ironsight_opacity = "Opacité du réticule du viseur" L.label_crosshair_size = "Taille du réticule" L.label_crosshair_thickness = "Épaisseur du réticule" L.label_crosshair_thickness_outline = "Épaisseur du contour du réticule" -L.label_crosshair_static_enable = "Activer le réticule statique" -L.label_crosshair_dot_enable = "Activer le point du réticule " -L.label_crosshair_lines_enable = "Activer les lignes du réticule" -L.label_crosshair_scale_enable = "Activer la dépendance du réticule par rapport au type d'arme" +--L.label_crosshair_scale_enable = "Enable dynamic crosshair scale" L.label_crosshair_ironsight_low_enabled = "Baissez votre arme lorsque vous utilisez le viseur" L.label_damage_indicator_enable = "Activer l'indicateur de dégâts" L.label_damage_indicator_mode = "Sélectionnez le thème de l'indicateur de dégâts" @@ -1119,13 +1057,10 @@ L.label_gameplay_specmode = "Mode Spectateur (toujours resté en spectateur)" L.label_gameplay_fastsw = "Changement d'arme rapide" L.label_gameplay_hold_aim = "Permettre de maintenir pour viser" L.label_gameplay_mute = "Mettez en sourdine les joueurs vivants lorsqu'ils sont morts" -L.label_gameplay_dtsprint_enable = "Permettre le sprint en appuyant deux fois sur la touche Avancer" -L.label_gameplay_dtsprint_anykey = "Continuez à sprinter en appuyant deux fois sur la touche Avancer jusqu'à ce que vous arrêtiez de bouger" L.label_hud_default = "HUD par défaut" L.label_hud_force = "HUD Obligatoire" L.label_bind_weaponswitch = "Ramasser une arme" -L.label_bind_sprint = "Sprint" L.label_bind_voice = "Chat Vocal Global" L.label_bind_voice_team = "Chat Vocal de Team" @@ -1149,7 +1084,6 @@ L.header_damage_indicator = "Paramètres de l'indicateur de dégâts" L.header_performance_settings = "Paramètres de performance" L.header_interface_settings = "Paramètres de l'interface" L.header_gameplay_settings = "Paramètres de jeu" -L.header_roleselection = "Activer/Désactiver l'attribution de certains rôles" L.header_hud_administration = "Sélectionnez l'HUDs par Défaut et Obligatoire" L.header_hud_enabled = "Activer/Désactiver l'HUDs" @@ -1196,11 +1130,7 @@ L.hud_revival_time = "{time}s" L.door_destructible = "La porte est destructible ({health}HP)" -- 2020-05-28 -L.confirm_detective_only = "Seuls les détectives peuvent confirmer les corps" -L.inspect_detective_only = "Seuls les détectives peuvent inspecter les corps" -L.corpse_hint_no_inspect = "Seuls les détectives peuvent fouiller ce corps." -L.corpse_hint_inspect_only = "Appuyez sur [{usekey}] pour effectuer une recherche. Seuls les détectives peuvent confirmer le corps." -L.corpse_hint_inspect_only_credits = "Appuyez sur [{usekey}] pour recevoir des crédits. Seuls les détectives peuvent fouiller ce corps." +--L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." -- 2020-06-04 L.label_bind_disguiser = "Activer/Désactiver le déguisement" @@ -1215,7 +1145,6 @@ L.binoc_help_sec = "Changer le niveau de zoom." L.vis_help_pri = "Lâcher l'appareil activé." -L.decoy_help_pri = "Planter le leurre." -- 2020-08-07 L.pickup_error_spec = "Vous ne pouvez pas prendre cela en tant que spectateur." @@ -1421,7 +1350,7 @@ L.karma_unknown_tooltip = "Inconnu" --L.spawneditor_place = "Place spawn" --L.spawneditor_remove = "Remove spawn" --L.spawneditor_change = "Change spawn type (hold [SHIFT] to reverse)" ---L.spawneditor_ammo_edit = "Hold to edit amount of autospawning ammo on weapon spawns" +--L.spawneditor_ammo_edit = "Hold on weapon spawn to edit autospawning ammo" --L.spawn_weapon_random = "Random Weapon Spawn" --L.spawn_weapon_melee = "Melee Weapon Spawn" @@ -1439,7 +1368,7 @@ L.karma_unknown_tooltip = "Inconnu" --L.spawn_ammo_shotgun = "Shotgun ammo spawn" --L.spawn_player_random = "Random player spawn" ---L.spawn_weapon_ammo = " (Ammo: {ammo})" +--L.spawn_weapon_ammo = "(Ammo: {ammo})" --L.spawn_weapon_edit_ammo = "Hold [{walkkey}] and press [{primaryfire} or {secondaryfire}] to increase or decrease the ammo for this weapon spawn" @@ -1461,18 +1390,18 @@ L.karma_unknown_tooltip = "Inconnu" --L.help_spawn_editor_info = [[ --The spawn editor is used to place, remove and edit spawns in the world. These spawns are for weapons, ammunition and players. - +-- --These spawns are saved in files located in 'data/ttt/weaponspawnscripts/'. They can be deleted for a hard reset. The initial spawn files are created from spawns found on the map and in the original TTT weapon spawn scripts. Pressing the reset button always reverts to the initial state. - +-- --It should be noted that this spawn system uses dynamic spawns. This is most interesting for weapons because it no longer defines a specific weapon, but a type of weapons. For example instead of a TTT shotgun spawn, there is now a general shotgun spawn where any weapon defined as shotgun can spawn. The spawn type for each weapon can be set in the 'Edit Equipment' menu. This makes it possible for any weapon to spawn on the map, or to disable certain default weapons. - +-- --Keep in mind that many changes only take effect after a new round has started.]] --L.help_spawn_editor_enable = "On some maps it might be advised to use the original spawns found on the map without replacing them with the dynamic system. Changing this option below only affects the currently active map, so the dynamic system will still be used for every other map." --L.help_spawn_editor_hint = "Hint: To leave the spawn editor, reopen the gamemode menu." --L.help_spawn_editor_spawn_amount = [[ --There currently are {weapon} weapon spawns, {ammo} ammunition spawns and {player} player spawns on this map. --Click 'start spawn edit' to change this amount. - +-- --{weaponrandom}x Random weapon spawn --{weaponmelee}x Melee weapon spawn --{weaponnade}x Grenade weapon spawn @@ -1481,21 +1410,21 @@ L.karma_unknown_tooltip = "Inconnu" --{weaponsniper}x Sniper weapon spawn --{weaponpistol}x Pistol weapon spawn --{weaponspecial}x Special weapon spawn - +-- --{ammorandom}x Random ammo spawn --{ammodeagle}x Deagle ammo spawn --{ammopistol}x Pistol ammo spawn --{ammomac10}x Mac10 ammo spawn --{ammorifle}x Rifle ammo spawn --{ammoshotgun}x Shotgun ammo spawn - +-- --{playerrandom}x Random player spawn]] --L.equipmenteditor_name_auto_spawnable = "Equipment spawns randomly in world" --L.equipmenteditor_name_spawn_type = "Select spawn type" --L.equipmenteditor_desc_auto_spawnable = [[ --The TTT2 spawn system allows every weapon to spawn in the world. By default only weapons marked as 'AutoSpawnable' by the creator will spawn in the world, however this can be changed from within this menu. - +-- --Most of the equipment is set to 'special weapon spawns' by default. This means that equipment only spawns on random weapon spawns. However it is possible to place special weapon spawns in the world or change the spawn type here to use other existing spawn types.]] --L.pickup_error_inv_cached = "You cannot pick this up right now because your inventory is cached." @@ -1512,14 +1441,14 @@ L.karma_unknown_tooltip = "Inconnu" --L.help_prefer_map_models = [[ --Some maps define their own player models. By default these models have a higher priority than those that are assigned automatically. By disabling this setting, map specific models are disabled. - +-- --Role specific models always have a higher priority and are unaffected by this setting.]] --L.help_enforce_playermodel = [[ --Some roles have custom player models. They can be disabled which can be relevant for compatibility with some player model selectors. --Random default models can still be selected, if this setting is disabled.]] --L.help_use_custom_models = [[ --By default only the CS:S Phoenix player model is assigned to all players. By enabling this option however it is possible to select a player model pool. With this setting enabled each player will still be assigned the same player model, however it is a random model from the defined model pool. - +-- --This selection of models can be extended by installing more player models.]] -- 2021-10-06 @@ -1536,7 +1465,7 @@ L.karma_unknown_tooltip = "Inconnu" -- 2021-10-09 --L.help_models_select = [[ --Left click on the models to add them to the player model pool. Left click again to remove them. Right clicking toggles between enabled and disabled detective hats for the focused model. - +-- --The small indicator in the top left shows if the player model has a head hitbox. The icon below shows if this model is applicable for a detective hat.]] --L.menu_roles_title = "Role Settings" @@ -1559,7 +1488,7 @@ L.karma_unknown_tooltip = "Inconnu" --L.help_roles_selection = [[ --The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role. This also means that at least 5 players are needed for this role to be distributed at all. --Keep in mind that all of this only applies if the role is considered for distribution process. - +-- --The aforementioned role distribution has a special integration with the lower limit of players. If the role is considered for distribution and the minimum value is below the value given by the distribution factor, but the amount of players is equal or greater than the lower limit, a single player can still receive this role. The distribution process then works as usual for the second player.]] --L.help_roles_award_info = "Some roles (if enabled in their credits settings) receive equipment credits if a certain percentage of enemies has died. Related values can be tweaked here." --L.help_roles_award_pct = "When this percentage of enemies are dead, specific roles are awarded equipment credits." @@ -1567,17 +1496,17 @@ L.karma_unknown_tooltip = "Inconnu" --L.help_roles_advanced_warning = "WARNING: These are advanced settings that can completely mess up the role distribution process. When in doubt keep all values at '0'. This value means that no limits are applied and the role distribution will try to assign as many roles as possible." --L.help_roles_max_roles = [[ --The term roles here includes both the base roles and the subroles. By default, there is no limit on how many different roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.help_roles_max_baseroles = [[ --Base roles are only those roles others inherit from. For example, the Innocent role is a base role, while a Pharaoh is a subrole of this role. By default, there is no limit on how many different base roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.label_roles_enabled = "Enable role" @@ -1609,15 +1538,15 @@ L.karma_unknown_tooltip = "Inconnu" --L.help_roles_credits_award_kill = "Another way of gaining credits is by killing high value players with a 'public role' such as a Detective. If the killer's role has this enabled, they gain the below defined amount of credits." --L.help_roles_credits_award = [[ --There are two different ways to be awarded credits in base TTT2: - +-- --1. If a certain percentage of the enemy team is dead, the whole team is awarded credits. --2. If a player killed a high value player with a 'public role' such as a Detective, the killer is awarded credits. - +-- --Please note, that this still can be enabled/disabled for every role, even if the whole team is awarded. For example, if team Innocent is awarded, but the Innocent role has this disabled, only the Detective will receive their credits. --The balancing values for this feature can be set in 'Administration' -> 'General Role Settings'.]] --L.help_detective_hats = [[ --Policing roles such as the Detective may wear hats to show their authority. They lose them on death or if damaged at the head. - +-- --Some player models do not support hats by default. This can be changed in 'Administration' -> 'Player Models']] --L.label_roles_credits_award_kill = "Credit reward amount for the kill" @@ -1669,35 +1598,35 @@ L.karma_unknown_tooltip = "Inconnu" --L.help_idle = "The idle mode is used to forcefully move idle players into the spectator mode. To leave this mode, they will have to disable it in their 'gameplay' menu." --L.help_namechange_kick = [[ --A name change during an active round could be abused. Therefore, this is prohibited by default and will lead to the offending player being kicked from the server. - +-- --If the bantime is greater than 0, the player will be unable to reconnect to the server until that time has passed.]] --L.help_damage_log = "Each time a player is damaged, a damage log entry is added to the console if enabled. This can also be stored to disk after a round has ended. The file is located at 'data/terrortown/logs/'" --L.help_spawn_waves = [[ --If this variable is set to 0, all players are spawned at once. For servers with huge amounts of players, it can be beneficial to spawn the players in waves. The spawn wave interval is the time between each spawn wave. A spawn wave always spawns as many players as there are valid spawn points. - +-- --Note: Make sure that the preparing time is long enough for the desired amount of spawn waves.]] --L.help_voicechat_battery = [[ --Voice chatting with enabled voice chat battery reduces battery charge. When it's empty, the player can't use voice chat and has to wait for it to recharge. This can help to prevent excessive voice chat usage. - +-- --Note: 'Tick' refers to a game tick. For example, if the tick rate is set to 66, then it will be 1/66th of a second.]] --L.help_ply_spawn = "Player settings that are used on player (re-)spawn." --L.help_haste_mode = [[ --Haste mode balances the game by increasing the round time with every dead player. Only roles that see missing in action players can see the real round time. Every other role can only see the haste mode starting time. - +-- --If haste mode is enabled, the fixed round time is ignored.]] --L.help_round_limit = "After one of the set limit conditions is met, a map change is triggered." --L.help_armor_balancing = "The following values can be used to balance the armor." --L.help_item_armor_classic = "If classic armor mode is enabled, only the previous settings matter. Classic armor mode means that a player can only buy armor once in a round, and that this armor blocks 30% of the incoming bullet and crowbar damage until they die." --L.help_item_armor_dynamic = [[ --Dynamic armor is the TTT2 approach to make armor more interesting. The amount of armor that can be bought is now unlimited, and the armor value stacks. Getting damaged decreases the armor value. The armor value per bought armor item is set in the 'Equipment Settings' of said item. - +-- --When taking damage, a certain percentage of this damage is converted into armor damage, a different percentage is still applied to the player and the rest vanishes. - +-- --If reinforced armor is enabled, the damage applied to the player is decreased by 15% as long as the armor value is above the reinforcement threshold.]] --L.help_sherlock_mode = "The sherlock mode is the classic TTT mode. If the sherlock mode is disabled, dead bodies can not be confirmed, the scoreboard shows everyone as alive and the spectators can talk to the living players." --L.help_prop_possession = [[ --Prop possession can be used by spectators to possess props lying in the world and use the slowly recharging 'punch-o-meter' to move said prop around. - +-- --The maximum value of the 'punch-o-meter' consists of a possession base value, where the kills/deaths difference clamped inbetween two defined limits is added. The meter slowly recharges over time. The set recharge time is the time needed to recharge a single point in the 'punch-o-meter'.]] --L.help_karma = "Players start with a certain amount of Karma, and lose it when they damage/kill teammates. The amount they lose is dependent on the Karma of the person they hurt or killed. Lower Karma reduces damage given." --L.help_karma_strict = "If strict Karma is enabled, the damage penalty increases more quickly as Karma goes down. When it is off, the damage penalty is very low when people stay above 800. Enabling strict mode makes Karma play a larger role in discouraging any unnecessary kills, while disabling it results in a more “loose” game where Karma only hurts players who constantly kill teammates." @@ -1707,7 +1636,7 @@ L.karma_unknown_tooltip = "Inconnu" --L.help_karma_bonus = "There are also two different passive ways to gain Karma during a round. First is a karma restoration which applied to every player at the round end. Then a secondary clean round bonus is given if no teammates were hurt or killed by a player." --L.help_karma_clean_half = [[ --When a player's Karma is above the starting level (meaning the Karma max has been configured to be higher than that), all their Karma increases will be reduced based on how far their Karma is above that starting level. So it goes up slower the higher it is. - +-- --This reduction goes in a curve of exponential decay: initially it's fast, and it slows down as the increment gets smaller. This convar sets at what point the bonus has been halved (so the half-life). With the default value of 0.25, if the starting amount of Karma is 1000 and the max 1500, and a player has Karma 1125 ((1500 - 1000) * 0.25 = 125), then his clean round bonus will be 30 / 2 = 15. So to make the bonus go down faster you’d set this convar lower, to make it go down slower you’d increase it towards 1.]] --L.help_max_slots = "Sets the maximum amount of weapons per slot. '-1' means that there is no limit." --L.help_item_armor_value = "This is the armor value given by the armor item in dynamic mode. If classic mode is enabled (see 'Administration' -> 'Player Settings') then every value greater than 0 is counted as existing armor." @@ -1730,8 +1659,6 @@ L.karma_unknown_tooltip = "Inconnu" --L.label_tbutton_admin_show = "Show traitor buttons to admins" --L.label_ragdoll_carrying = "Enable ragdoll carrying" --L.label_prop_throwing = "Enable prop throwing" ---L.label_ragdoll_pinning = "Enable ragdoll pinning for non-Innocent roles" ---L.label_ragdoll_pinning_innocents = "Enable ragdoll pinning for Innocent roles" --L.label_weapon_carrying = "Enable weapon carrying" --L.label_weapon_carrying_range = "Weapon carry range" --L.label_prop_carrying_force = "Prop pickup force" @@ -1761,10 +1688,8 @@ L.karma_unknown_tooltip = "Inconnu" --L.label_spectator_chat = "Enable spectators chatting with everybody" --L.label_lastwords_chatprint = "Print last words to chat if killed while typing" --L.label_identify_body_woconfirm = "Identify corpse without pressing the 'confirm' button" ---L.label_announce_body_found = "Announce that a body was found" +--L.label_announce_body_found = "Announce that a body was found when the body was confirmed" --L.label_confirm_killlist = "Announce kill list of confirmed corpse" ---L.label_inspect_detective_only = "Limit corpse search to policing roles only" ---L.label_confirm_detective_only = "Limit corpse confirmation to policing roles only" --L.label_dyingshot = "Shoot on death if in ironsights [experimental]" --L.label_armor_block_headshots = "Enable armor blocking headshots" --L.label_armor_block_blastdmg = "Enable armor blocking blast damage" @@ -1823,7 +1748,6 @@ L.karma_unknown_tooltip = "Inconnu" --L.label_sprint_max = "Max sprinting stamina" --L.label_sprint_stamina_consumption = "Stamina consumption factor" --L.label_sprint_stamina_regeneration = "Stamina regeneration factor" ---L.label_sprint_crosshair = "Show crosshair while sprinting" --L.label_crowbar_unlocks = "Primary attack can be used as interaction (i.e. unlocking)" --L.label_crowbar_pushforce = "Crowbar push force" @@ -1836,7 +1760,7 @@ L.karma_unknown_tooltip = "Inconnu" --L.help_falldmg_exponent = [[ --This value modifies how exponentially fall damage is increased with the speed the player hits the ground at. - +-- --Take care when altering this value. Setting it too high can make even the smallest falls lethal, while setting it too low will allow players to fall from extreme heights and suffer little to no damage.]] -- 2023-02-08 @@ -1859,4 +1783,411 @@ L.karma_unknown_tooltip = "Inconnu" --L.sb_rank_tooltip_heroes = "TTT2 Heroes" --L.sb_rank_tooltip_team = "Team" ---L.tbut_adminarea = "ADMIN AREA:" \ No newline at end of file +--L.tbut_adminarea = "ADMIN AREA:" + +-- 2023-08-10 +--L.equipmenteditor_name_damage_scaling = "Damage Scaling" + +-- 2023-08-11 +--L.equipmenteditor_name_allow_drop = "Allow Drop" +--L.equipmenteditor_desc_allow_drop = "If enabled, the equipment can be dropped freely by the player." + +--L.equipmenteditor_name_drop_on_death_type = "Drop on Death" +--L.equipmenteditor_desc_drop_on_death_type = "Attempt overriding the action taken for whether the equipment is dropped on player's death." + +--L.drop_on_death_type_default = "Default (weapon-defined)" +--L.drop_on_death_type_force = "Force Drop on Death" +--L.drop_on_death_type_deny = "Deny Drop on Death" + +-- 2023-08-26 +--L.equipmenteditor_name_kind = "Equipment Slot" +--L.equipmenteditor_desc_kind = "The inventory slot the equipment will occupy." + +--L.slot_weapon_melee = "Melee Slot" +--L.slot_weapon_pistol = "Pistol Slot" +--L.slot_weapon_heavy = "Heavy Slot" +--L.slot_weapon_nade = "Grenade Slot" +--L.slot_weapon_carry = "Carry Slot" +--L.slot_weapon_unarmed = "Unarmed Slot" +--L.slot_weapon_special = "Special Slot" +--L.slot_weapon_extra = "Extra Slot" +--L.slot_weapon_class = "Class Slot" + +-- 2023-10-04 +--L.label_voice_duck_spectator = "Duck spectator voices" +--L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +--L.label_voice_scaling = "Voice Volume Scaling Mode" +--L.label_voice_scaling_mode_linear = "Linear" +--L.label_voice_scaling_mode_power4 = "Power 4" +--L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "Résultats de la fouille - {player}" +L.search_info = "Information" +L.search_confirm = "Confirmer la mort" +--L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +--L.search_take_credits = "Take {credits} Credit(s)" +--L.search_confirm_forbidden = "Confirm forbidden" +--L.search_confirmed = "Death Confirmed" +--L.search_call = "Report Death" +--L.search_called = "Death Reported" + +--L.search_team_role_unknown = "???" + +L.search_words = "Quelque chose vous dit que quelques-unes des dernières paroles de cette personne étaient: '{lastwords}'" +L.search_armor = "Il avait une armure non-standard." +L.search_disguiser = "Il portait un dispositif qui permet de cacher son identité." +L.search_radar = "Il portait une sorte de radar. Ce radar ne fonctionne plus." +L.search_c4 = "Dans une poche, il y a une note. Elle dit que couper le fil numéro {num} va désamorcer le C4." + +L.search_dmg_crush = "Il a beaucoup de fractures. On dirait que l'impact d'un objet lourd l'a tué." +L.search_dmg_bullet = "Il est évident qu'on lui a tiré dessus jusqu'à la mort." +L.search_dmg_fall = "Une chute lui a été fatale." +L.search_dmg_boom = "Les blessures et les vêtements déchirés indiquent qu'une explosion l'a tué." +L.search_dmg_club = "Il est couvert d'ecchymoses et semble avoir été battu. Très clairement, il a été frappé à mort." +L.search_dmg_drown = "Le corps montre les signes d'une inévitable noyade." +L.search_dmg_stab = "Il s'est fait couper et poignarder avant de saigner à mort." +L.search_dmg_burn = "Ça sent le terroriste grillé, non?" +L.search_dmg_teleport = "On dirait que son ADN a été altéré par des émissions de tachyons!" +L.search_dmg_car = "Pendant que cette personne traversait la route, il s'est fait rouler dessus par un conducteur imprudent." +L.search_dmg_other = "Vous n'arrivez pas à identifier la cause de sa mort." + +--L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +--L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +--L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +--L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +--L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +--L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +--L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +--L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +--L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +--L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +--L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +--L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +--L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +--L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +--L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +--L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +--L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +--L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +--L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +--L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +--L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +--L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +--L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +--L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +--L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "Il semblerait qu'un {weapon} a été utilisé pour le tuer." +L.search_head = "Une blessure fatale a été portée à la tête. Impossible de crier." +--L.search_time = "They died a while before you conducted the search." +--L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "Vous avez trouvé une liste de meurtres qui confirme la mort de {player}." +L.search_kills2 = "Vous avez trouvé une liste de meurtres avec les noms suivants: {player}" +L.search_eyes = "En utilisant vos compétences de détective, vous avez identifié la dernière personne qu'il a vue: {player}. Serait-ce le tueur, ou une coïncidence?" + +--L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +--L.search_kill_distance_point_blank = "It was a point blank attack." +--L.search_kill_distance_close = "The attack came from a short distance." +--L.search_kill_distance_far = "The victim was attacked from a long distance away." + +--L.search_kill_from_front = "The victim was shot from the front." +--L.search_kill_from_back = "The victim was shot from behind." +--L.search_kill_from_side = "The victim was shot from the side." + +--L.search_hitgroup_head = "The projectile was found in their head." +--L.search_hitgroup_chest = "The projectile was found in their chest." +--L.search_hitgroup_stomach = "The projectile was found in their stomach." +--L.search_hitgroup_rightarm = "The projectile was found in their right arm." +--L.search_hitgroup_leftarm = "The projectile was found in their left arm." +--L.search_hitgroup_rightleg = "The projectile was found in their right leg." +--L.search_hitgroup_leftleg = "The projectile was found in their left leg." +--L.search_hitgroup_gear = "The projectile was found in their hip." + +--L.search_policingrole_report_confirm = [[ +--A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +--L.search_policingrole_confirm_disabled_1 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +--L.search_policingrole_confirm_disabled_2 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know! +--You can see the information in here after they confirmed it.]] +--L.search_spec = [[ +--As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +--L.search_title_words = "Victim's last words" +--L.search_title_c4 = "Defusion mishap" +--L.search_title_dmg_crush = "Crush damage ({amount} HP)" +--L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +--L.search_title_dmg_fall = "Fall damage ({amount} HP)" +--L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +--L.search_title_dmg_club = "Club damage ({amount} HP)" +--L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +--L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +--L.search_title_dmg_burn = "Burning damage ({amount} HP)" +--L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +--L.search_title_dmg_car = "Car accident ({amount} HP)" +--L.search_title_dmg_other = "Unknown damage ({amount} HP)" +--L.search_title_time = "Death time" +--L.search_title_dna = "DNA sample decay" +--L.search_title_kills = "The victim's kill list" +--L.search_title_eyes = "The killer's shadow" +--L.search_title_floor = "Floor of the crime scene" +--L.search_title_credits = "{credits} Equipment credit(s)" +--L.search_title_water = "Water level {level}" +--L.search_title_policingrole_report_confirm = "Confirm to report death" +--L.search_title_policingrole_confirm_disabled = "Report corpse" +--L.search_title_spectator = "You are a spectator" + +--L.target_credits_on_confirm = "Confirm to receive unspent credits" +--L.target_credits_on_search = "Search to receive unspent credits" +--L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +--L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +--L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +--L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +--L.label_inspect_confirm_mode = "Select body search mode" +--L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +--L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +--L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +--L.help_inspect_confirm_mode = [[ +--There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. +-- +--mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. +-- +--mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. +-- +--mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +--L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +--L.header_hud_elements_customize = "Customize the HUD-Elements" +--L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +--L.help_keyhelp = [[ +--Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: +-- +--Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +--Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +--Equipment: Some equipment items have their own bindings, these are shown in this category. +-- +--Disabled categories are still shown when the scoreboard is visible]] + +--L.label_keyhelp_show_core = "Enable always showing the core bindings" +--L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +--L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +--L.header_interface_keys = "Key helper settings" +--L.header_interface_wepswitch = "Weapon switch UI settings" + +--L.label_keyhelper_help = "open gamemode menu" +--L.label_keyhelper_mutespec = "cycle spectator voice mode" +--L.label_keyhelper_shop = "open equipment shop" +--L.label_keyhelper_show_pointer = "free mouse pointer" +--L.label_keyhelper_possess_focus_entity = "possess focused entity" +--L.label_keyhelper_spec_focus_player = "spectate focused player" +--L.label_keyhelper_spec_previous_player = "previous player" +--L.label_keyhelper_spec_next_player = "next player" +--L.label_keyhelper_spec_player = "spectate random player" +--L.label_keyhelper_possession_jump = "prop: jump" +--L.label_keyhelper_possession_left = "prop: left" +--L.label_keyhelper_possession_right = "prop: right" +--L.label_keyhelper_possession_forward = "prop: forward" +--L.label_keyhelper_possession_backward = "prop: backward" +--L.label_keyhelper_free_roam = "leave object and roam free" +--L.label_keyhelper_flashlight = "toggle flashlight" +--L.label_keyhelper_quickchat = "open quickchat" +--L.label_keyhelper_voice_global = "global voice chat" +--L.label_keyhelper_voice_team = "team voice chat" +--L.label_keyhelper_chat_global = "global chat" +--L.label_keyhelper_chat_team = "team chat" +--L.label_keyhelper_show_all = "show all" +--L.label_keyhelper_disguiser = "toggle disguiser" +--L.label_keyhelper_save_exit = "save and exit" +--L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +--L.item_armor_reinforced = "Reinforced Armor" +--L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +--L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +--L.status_speed_name = "Speed Multiplier" +--L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +--L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +--L.status_on = "on" +--L.status_off = "off" + +--L.crowbar_help_primary = "Attack" +--L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +--L.help_HUD_enable_description = [[ +--Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +--L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +--L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +--L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +--L.header_soundeffect_settings = "Sound Effects" +--L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +--L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +--L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +--L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +--L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +--L.help_prop_spec_dash = [[ +--Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. +-- +--This variable is a multiplier of the push force.]] +--L.label_spec_prop_dash = "Dash force multiplier" +--L.label_keyhelper_possession_dash = "prop: dash in view direction" +--L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +--L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +--L.c4_help_primary = "Place the C4" +--L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +--L.magneto_help_primary = "Push entity" +--L.magneto_help_secondary = "Pull / pickup entity" +--L.knife_help_primary = "Stab" +--L.knife_help_secondary = "Throw knife" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +--L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +--L.newton_force = "FORCE" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +--L.beacon_help_pri = "Throw Beacon on the ground" +--L.beacon_help_sec = "Stick Beacon to surface" +--L.beacon_name = "Beacon" +--L.beacon_desc = [[ +--Broadcasts player locations to everyone in a sphere around this beacon. +-- +--Use to keep track of locations on the map that are hard to see.]] + +--L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +--L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +--L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +--L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +--L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder} a confirmé la mort de {victim}." +--L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +--L.builtin_marker = "Built-in." +--L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +--L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +--L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +--L.submenu_gameplay_accessibility_title = "Accessibility" + +--L.header_accessibility_settings = "Accessibility Settings" + +--L.label_enable_dynamic_fov = "Enable dynamic FOV change" +--L.label_enable_bobbing = "Enable view bobbing" +--L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +--L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +--L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +--L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +--For a shotgun, this would affect each pellet. +--For a rifle, this would affect just the bullet. +--For the poltergeist, this would affect each "thump" and the final explosion. +-- +--0.5 = Deal half the amount of damage. +--2 = Deal twice the amount of damage. +-- +--Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +--L.binoc_help_reload = "Clear target." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +--L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +--L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +--L.label_crosshair_mode = "Crosshair mode" +--L.label_crosshair_static_length = "Enable static crosshair line length" + +--L.choice_crosshair_mode_0 = "Lines and dot" +--L.choice_crosshair_mode_1 = "Lines only" +--L.choice_crosshair_mode_2 = "Dot only" + +--L.help_crosshair_scale_enable = [[ +--Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. +-- +--If the line length is kept static, only the gap scales with cone changes.]] + +--L.header_weapon_settings = "Weapon Settings" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +L.decoy_help_primary = "Planter le leurre" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/it.lua b/lua/terrortown/lang/it.lua index b93f25af1..248ef2666 100644 --- a/lua/terrortown/lang/it.lua +++ b/lua/terrortown/lang/it.lua @@ -60,8 +60,6 @@ L.body_found_traitor = "Era un Traditore!" L.body_found_det = "Era un Detective." L.body_found_inno = "Era un Innocente." -L.body_confirm = "{finder} ha confermato la morte di {victim}." - L.body_call = "{player} ha chiamato un detective al corpo di {victim}!" L.body_call_error = "Devi confermare la morte di questo giocatore prima di chiamare un Detective!" @@ -172,46 +170,6 @@ L.quick_disg = "qualcuno con travestimento" L.quick_corpse = "un corpo non identificato" L.quick_corpse_id = " il corpo di {player}" --- Body search window -L.search_title = "Risultati ricerca" -L.search_info = "Informazione" -L.search_confirm = "Conferma morte" -L.search_call = "Chiama Detective" - --- Descriptions of pieces of information found -L.search_nick = "Questo è il corpo di {player}." - -L.search_role_traitor = "Questa persona era un Traditore!" -L.search_role_det = "Questa persona era un Detective." -L.search_role_inno = "Questa persona era un Innocente." - -L.search_words = "Qualcosa ti dice che le ultime parole di questa persona erano: '{lastwords}'" -L.search_armor = "Stava indossando un'armatura speciale." -L.search_disg = "Stava usando un dispositivo che poteva nascondere la sua identità." -L.search_radar = "Avevano qualche tipo di radar. Non funziona più." -L.search_c4 = "In una tasca hai trovato una nota. Dice che tagliare il filo {num} disarmerà la bomba." - -L.search_dmg_crush = "Molte delle sua ossa sono rotte. Sembra che l'impatto di un oggetto pesante lo abbia ucciso." -L.search_dmg_bullet = "È ovvio che sia stato ucciso da dei colpi di arma da fuoco." -L.search_dmg_fall = "Sono morti di caduta." -L.search_dmg_boom = "Le sue ferite e i vestiti bruciati indicano che un'esplosione lo hanno ucciso." -L.search_dmg_club = "Il suo corpo è pieno di ferite. Chiaramente è stato ucciso a bastonate." -L.search_dmg_drown = "Il corpo morta i segni dell'affogamento." -L.search_dmg_stab = "È stato accoltellato prima di morire velocemente dissanguato." -L.search_dmg_burn = "Si sente odore di terrorista bruciato qui..." -L.search_dmg_tele = "Sembra che il DNA stato criptato da delle emissioni tachioniche!" -L.search_dmg_car = "Quando il terrorista ha attraversato la strada, sono stati investiti da un pilota spericolato." -L.search_dmg_other = "Non puoi trovare una causa di morte specifica per la morte di questo terrorista." - -L.search_weapon = "Sembra che sia stato usato un {weapon} per ucciderlo." -L.search_head = "La ferita fatale è stata un colpo in testa. Non c'era il tempo di urlare." -L.search_time = "È morto all'incirca {time} prima che facessi la ricerca." -L.search_dna = "Prendi un campione di DNA con il DNA Scanner. Il campione di DNA scadrà {time} da ora." - -L.search_kills1 = "Hai trovato una lista di uccisioni che conferma la morte di {player}." -L.search_kills2 = "Hai trovato una lista di uccisioni con questi nomi:" -L.search_eyes = "Usando le tue abilità da detective, hai identificato che l'ultima persona che ha visto è: {player}. L'assassino, o una coincidenza?" - -- Scoreboard L.sb_playing = "Stai giocando su..." L.sb_mapchange = "La mappa cambierà tra {num} round o tra {time}" @@ -267,7 +225,6 @@ Nasconde le tue informazioni quando attivo. Evita anche di essere l'ultima perso Disabilitalo nella finestra Travestimento di questo menù o premi Numpad Enter.]] -- C4 -L.c4_hint = "Premi {usekey} per innescare o disinnescare." L.c4_disarm_warn = "Un C4 che hai piazzato è stato disinnescato." L.c4_armed = "Hai innescato la bomba con successo." L.c4_disarmed = "Hai disinnescato la bomba con successo." @@ -288,6 +245,7 @@ L.c4_remove_destroy2 = "Conferma: distruggi" L.c4_disarm = "Disinnesca C4" L.c4_disarm_cut = "Clicca per tagliare il filo {num}" +L.c4_disarm_t = "Taglia un filo per disinnescare la bomba. Come Traditore, ogni filo va bene. Per gli Innocenti non è così facile!" L.c4_disarm_owned = "Taglia un filo per disinnescare la bomba. È la tua bomba, quindi ogni filo la disinnescherà." L.c4_disarm_other = "Taglia un filo sicuro per disinnescare la bomba. Esploderà se tagli quello sbagliato!" @@ -296,7 +254,6 @@ L.c4_status_disarmed = "DISINNESCATA" -- Visualizer L.vis_name = "Visualizzatore" -L.vis_hint = "Premi {usekey} per raccoglierlo (solo Detective)." L.vis_desc = [[ Dispositivo per visualizzare una scena del crimine. @@ -305,7 +262,6 @@ Analizza un cadavere per mostrare come la vittima è stata uccisa, ma solo se è -- Decoy L.decoy_name = "Esca" -L.decoy_no_room = "Non puoi portare questa Esca." L.decoy_broken = "la tua Esca è stata distrutta!" L.decoy_short_desc = "Questa esca mostra un finto segnale radar per gli altri team" @@ -316,7 +272,6 @@ Mostra un segnale falso sul radar dei Detective, e mostra sui loro DNA scanner l -- Defuser L.defuser_name = "Disinnescatore" -L.defuser_help = "{primaryfire} disinnesca C4 selezionato." L.defuser_desc = [[ Disinnesca istantaneamente un C4. @@ -335,7 +290,6 @@ Bruciare un cadavere fa un suono distinto.]] L.hstation_name = "Stazione di Cura" L.hstation_broken = "La tua Stazione di Cura è stata distrutta!" -L.hstation_help = "{primaryfire} piazza la Stazione di Cura." L.hstation_desc = [[ Permette ai giocatori di curarsi una volta piazzata. @@ -359,7 +313,6 @@ Le scariche di energia danneggiano i giocatori nelle vicinanze.]] -- Radio L.radio_broken = "La tua Radio è stata distrutta!" -L.radio_help_pri = "{primaryfire} piazza una Radio." L.radio_desc = [[ Fa dei suoni per distrarre o ingannare. @@ -405,7 +358,7 @@ L.dna_killer = "Preso un campione di DNA dell'assassino dal cadavere!" --L.dna_duplicate = "Match! You already have this DNA sample in your scanner." L.dna_no_killer = "Il DNA non può essere preso (assassino disconnesso?)." L.dna_armed = "La bomba è innescata! Disinnescala prima!" -L.dna_object = "Preso {num} nuovo campione di DNA dall'oggetto." +--L.dna_object = "Collected a sample of the last owner from the object." L.dna_gone = "DNA non rilevato nella zona." L.dna_desc = [[ @@ -474,7 +427,7 @@ L.hp_wounded = "Ferito" L.hp_badwnd = "Ferito gravemente" L.hp_death = "Quasi morto" --- TargetID karma status +-- TargetID Karma status L.karma_max = "Affidabile" L.karma_high = "Poco affidabile" L.karma_med = "Grilletto facile" @@ -483,19 +436,17 @@ L.karma_min = "Irresponsabile" -- TargetID misc L.corpse = "Cadavere" -L.corpse_hint = "Premi {usekey} per identificare. {walkkey} + {usekey} per identificare segretamente." +--L.corpse_hint = "Press [{usekey}] to search and confirm. [{walkkey} + {usekey}] to search covertly." L.target_disg = "travestito" L.target_unid = "Corpo non identificato" --L.target_unknown = "A Terrorist" -L.target_credits = "Identifica per ricevere i crediti non spesi" - --- Traitor buttons (HUD buttons with hand icons that only traditori can see) +-- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "Uso singolo" L.tbut_reuse = "Riutilizzabile" L.tbut_retime = "Riutilizzabile dopo {num} secondi" -L.tbut_help = "Premi {key} per attivare" +L.tbut_help = "Premi {usekey} per attivare" -- Spectator muting of living/dead L.mute_living = "Giocatori in vita mutati" @@ -505,7 +456,6 @@ L.mute_off = "Nessuno mutato" -- Spectators and prop possession L.punch_title = "PUNCH-O-METER" -L.punch_help = "Tasti per il movimento o salto: lancia oggetto. Abbassati: lascia oggetto." L.punch_bonus = "Il tuo punteggio basso ha diminuito il livello del punch-o-meter di {num}" L.punch_malus = "Il tuo punteggio alto ha aumentato il livello del punch-o-meter di {num}!" @@ -904,7 +854,7 @@ L.shop_default = "Usa shop di default" -- 2019-05-05 L.reroll_name = "Rimescola" --L.reroll_menutitle = "Reroll equipment" -L.reroll_no_credits = "Non hai crediti per rimescolare!" +--L.reroll_no_credits = "You need {amount} credits to reroll!" L.reroll_button = "Rimescola" --L.reroll_help = "Use {amount} credits to get a new random set of equipment in your shop!" @@ -930,19 +880,16 @@ L.hud_forced_failed = "Fallito nell'impostare l'HUD {hudname}. Sei un admin ed e L.hud_restricted_failed = "Fallito nell'impostare l'HUD {hudname}. Sei un admin?" L.shop_role_select = "Seleziona un ruolo" -L.shop_role_selected = "Lo shop del {roles} è stato selezionato!" +L.shop_role_selected = "Lo shop del {role} è stato selezionato!" L.shop_search = "Cerca" -L.spec_help = "Clicca per guardare i giocatori, o premi {usekey} su un oggetto per possederlo." -L.spec_help2 = "Per lasciare la modalità spettatore devi aprire il menù premendo {helpkey}, vai a 'gameplay' e rimuovi la modalità spettatore." - -- 2019-10-19 L.drop_ammo_prevented = "Qualcosa ti impedisce di lasciare queste munizioni." -- 2019-10-28 L.target_c4 = "Premi [{usekey}] per aprire il menù del C4" L.target_c4_armed = "Premi [{usekey}] per disinnescare il C4" -L.target_c4_armed_defuser = "Premi [{usekey}] per usare il disinnescatore" +L.target_c4_armed_defuser = "Premi [{primaryfire}] per usare il disinnescatore" L.target_c4_not_disarmable = "Non puoi disinnescare il C4 di un compagno in vita" L.c4_short_desc = "Qualcosa molto esplosivo" @@ -950,16 +897,15 @@ L.target_pickup = "Premi [{usekey}] per raccogliere" L.target_slot_info = "Slot: {slot}" L.target_pickup_weapon = "Premi [{usekey}] per prendere l'arma" L.target_switch_weapon = "Premi [{usekey}] per scambiare con la tua arma corrente" -L.target_pickup_weapon_hidden = ", premi [{usekey} + {walkkey}] per prenderla silenziosamente" -L.target_switch_weapon_hidden = ", premi [{usekey} + {walkkey}] per scambiarla silenziosamente" +L.target_pickup_weapon_hidden = ", premi [{walkkey} + {usekey}] per prenderla silenziosamente" +L.target_switch_weapon_hidden = ", premi [{walkkey} + {usekey}] per scambiarla silenziosamente" L.target_switch_weapon_nospace = "Non hai uno slot nell'inventario adatto a quest'arma" L.target_switch_drop_weapon_info = "Lasciando l'arma {name} nello slot {slot}" L.target_switch_drop_weapon_info_noslot = "Non c'è un'arma che puoi lasciare nello slot {slot}" -L.corpse_searched_by_detective = "Questo cadavere è stato identificato da un detective" +--L.corpse_searched_by_detective = "This corpse was searched by a public policing role" L.corpse_too_far_away = "Il cadavere è troppo lontano." -L.radio_pickup_wrong_team = "Non puoi prendere la radio di un'altra squadra." L.radio_short_desc = "I suoni delle armi sono come musica per me" L.hstation_subtitle = "Premi [{usekey}] per ricevere vita." @@ -1004,7 +950,6 @@ L.mute_team = "{team} mutato." L.door_auto_closes = "Questa porta si chiude automaticamente" L.door_open_touch = "Cammina addosso ad una porta per aprirla." L.door_open_touch_and_use = "Cammina addosso ad una porta o premi [{usekey}] per aprire." -L.hud_health = "Vita" -- 2020-03-09 L.help_title = "Aiuto e Impostazioni" @@ -1026,7 +971,7 @@ L.menu_guide_description = "Ti aiuta a cominciare con TTT2 e ti spiega delle cos L.menu_bindings_description = "Assegna specifiche funzioni di TTT2 e le sue addon a tuo piacimento" L.menu_language_description = "Seleziona la lingua del gioco" L.menu_appearance_description = "Modifica l'aspetto e la performane dell'interfaccia" -L.menu_gameplay_description = "Evita dei ruoli e modifica alcune funzioni" +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." L.menu_addons_description = "Configura le addon locali a tuo piacimento" L.menu_legacy_description = "Un pannello con le finestre convertite dal TTT originale, dovrebbero essere portate al nuovo sistema" L.menu_administration_description = "Impostazioni generli per gli HUD, shop ecc." @@ -1050,10 +995,8 @@ L.submenu_appearance_crosshair_title = "Mirino" L.submenu_appearance_dmgindicator_title = "Indicatore del Danno" L.submenu_appearance_performance_title = "Performance" L.submenu_appearance_interface_title = "Interfaccia" -L.submenu_appearance_miscellaneous_title = "Varie" L.submenu_gameplay_general_title = "Generale" -L.submenu_gameplay_avoidroles_title = "Evita Selezione Ruoli" L.submenu_administration_hud_title = "Impostazioni HUD" L.submenu_administration_randomshop_title = "Shop Casuale" @@ -1090,16 +1033,11 @@ L.label_shop_show_slot = "Mostra indicatore dello slot" L.label_shop_show_custom = "Mostra indicatore dell'oggetto personalizzato" L.label_shop_show_fav = "Mostra indicatore oggetto preferito" L.label_crosshair_enable = "Abilita Crosshair" -L.label_crosshair_gap_enable = "Abilita gap del mirino personalizzato" -L.label_crosshair_gap = "Gap mirino personalizzato" L.label_crosshair_opacity = "Opacità mirino" L.label_crosshair_ironsight_opacity = "Opacità mirino di ferro" L.label_crosshair_size = "Grandezza mirino" L.label_crosshair_thickness = "Spessore mirino" L.label_crosshair_thickness_outline = "Spessore contorno del mirino" -L.label_crosshair_static_enable = "Abilita mirino statico" -L.label_crosshair_dot_enable = "Abilita punto del mirino" -L.label_crosshair_lines_enable = "Abilita linee del mirino" L.label_crosshair_scale_enable = "Abilita scalatura del mirino dell'arma" L.label_crosshair_ironsight_low_enabled = "Abbassa arma quando usi il mirino di ferro" L.label_damage_indicator_enable = "Abilita indicatore del danno" @@ -1119,13 +1057,10 @@ L.label_gameplay_specmode = "Modalità solo spettatore (rimani sempre in spettat L.label_gameplay_fastsw = "Cambio armi veloce" L.label_gameplay_hold_aim = "Tieni premuto per mirare" L.label_gameplay_mute = "Muta tutti i giocatori vivi quando muori" -L.label_gameplay_dtsprint_enable = "Abilita la corsa con il doppio tap" -L.label_gameplay_dtsprint_anykey = "Continua la corsa finché non smette di muoverti" L.label_hud_default = "HUD di Default" L.label_hud_force = "Forza HUD" L.label_bind_weaponswitch = "Prendi Arma" -L.label_bind_sprint = "Corsa" L.label_bind_voice = "Chat Vocale Globale" L.label_bind_voice_team = "Chat Vocale di Squadra" @@ -1149,7 +1084,6 @@ L.header_damage_indicator = "Impostazioni Indicatori del Danno" L.header_performance_settings = "Impostazioni Performance" L.header_interface_settings = "Impostazioni Interfaccia" L.header_gameplay_settings = "Impostazioni del Gameplay" -L.header_roleselection = "Abilita Assegnamento Ruoli" L.header_hud_administration = "Seleziona HUD di Default e Forzati" L.header_hud_enabled = "Abilita/Disabilita HUD" @@ -1196,11 +1130,7 @@ L.hud_revival_time = "{time}s" L.door_destructible = "Questa porta è distruttibile ({health}HP)" -- 2020-05-28 -L.confirm_detective_only = "Solo i detective possono confermare i cadaveri." -L.inspect_detective_only = "Solo i detective possono ispezionare i cadaveri." -L.corpse_hint_no_inspect = "Solo i detective possono ispezione questo cadavere." -L.corpse_hint_inspect_only = "Premi [{usekey}] per ispezionare. Solo i detective possono confermare questo cadavere." -L.corpse_hint_inspect_only_credits = "Premi [{usekey}] per ricevere i crediti. Solo i detective possono ispezionare questo cadavere." +--L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." -- 2020-06-04 L.label_bind_disguiser = "Abilita travestimento" @@ -1215,7 +1145,6 @@ L.binoc_help_sec = "Cambia livello di zoom." L.vis_help_pri = "Getta il dispositivo." -L.decoy_help_pri = "Piazza un esca." -- 2020-08-07 L.pickup_error_spec = "Non puoi prendere questo da spettatore." @@ -1226,7 +1155,7 @@ L.pickup_error_noslot = "Non puoi prendere questo perchè non hai nessuno slot l --L.lang_server_default = "Server Default" --L.help_lang_info = [[ --This translation is {coverage}% complete with the English language taken as a default reference. - +-- --Keep in mind that these translations are made by the community. Feel free to contribute if something is missing or incorrect.]] -- 2021-04-13 @@ -1421,7 +1350,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.spawneditor_place = "Place spawn" --L.spawneditor_remove = "Remove spawn" --L.spawneditor_change = "Change spawn type (hold [SHIFT] to reverse)" ---L.spawneditor_ammo_edit = "Hold to edit amount of autospawning ammo on weapon spawns" +--L.spawneditor_ammo_edit = "Hold on weapon spawn to edit autospawning ammo" --L.spawn_weapon_random = "Random Weapon Spawn" --L.spawn_weapon_melee = "Melee Weapon Spawn" @@ -1439,7 +1368,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.spawn_ammo_shotgun = "Shotgun ammo spawn" --L.spawn_player_random = "Random player spawn" ---L.spawn_weapon_ammo = " (Ammo: {ammo})" +--L.spawn_weapon_ammo = "(Ammo: {ammo})" --L.spawn_weapon_edit_ammo = "Hold [{walkkey}] and press [{primaryfire} or {secondaryfire}] to increase or decrease the ammo for this weapon spawn" @@ -1461,18 +1390,18 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.help_spawn_editor_info = [[ --The spawn editor is used to place, remove and edit spawns in the world. These spawns are for weapons, ammunition and players. - +-- --These spawns are saved in files located in 'data/ttt/weaponspawnscripts/'. They can be deleted for a hard reset. The initial spawn files are created from spawns found on the map and in the original TTT weapon spawn scripts. Pressing the reset button always reverts to the initial state. - +-- --It should be noted that this spawn system uses dynamic spawns. This is most interesting for weapons because it no longer defines a specific weapon, but a type of weapons. For example instead of a TTT shotgun spawn, there is now a general shotgun spawn where any weapon defined as shotgun can spawn. The spawn type for each weapon can be set in the 'Edit Equipment' menu. This makes it possible for any weapon to spawn on the map, or to disable certain default weapons. - +-- --Keep in mind that many changes only take effect after a new round has started.]] --L.help_spawn_editor_enable = "On some maps it might be advised to use the original spawns found on the map without replacing them with the dynamic system. Changing this option below only affects the currently active map, so the dynamic system will still be used for every other map." --L.help_spawn_editor_hint = "Hint: To leave the spawn editor, reopen the gamemode menu." --L.help_spawn_editor_spawn_amount = [[ --There currently are {weapon} weapon spawns, {ammo} ammunition spawns and {player} player spawns on this map. --Click 'start spawn edit' to change this amount. - +-- --{weaponrandom}x Random weapon spawn --{weaponmelee}x Melee weapon spawn --{weaponnade}x Grenade weapon spawn @@ -1481,21 +1410,21 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --{weaponsniper}x Sniper weapon spawn --{weaponpistol}x Pistol weapon spawn --{weaponspecial}x Special weapon spawn - +-- --{ammorandom}x Random ammo spawn --{ammodeagle}x Deagle ammo spawn --{ammopistol}x Pistol ammo spawn --{ammomac10}x Mac10 ammo spawn --{ammorifle}x Rifle ammo spawn --{ammoshotgun}x Shotgun ammo spawn - +-- --{playerrandom}x Random player spawn]] --L.equipmenteditor_name_auto_spawnable = "Equipment spawns randomly in world" --L.equipmenteditor_name_spawn_type = "Select spawn type" --L.equipmenteditor_desc_auto_spawnable = [[ --The TTT2 spawn system allows every weapon to spawn in the world. By default only weapons marked as 'AutoSpawnable' by the creator will spawn in the world, however this can be changed from within this menu. - +-- --Most of the equipment is set to 'special weapon spawns' by default. This means that equipment only spawns on random weapon spawns. However it is possible to place special weapon spawns in the world or change the spawn type here to use other existing spawn types.]] --L.pickup_error_inv_cached = "You cannot pick this up right now because your inventory is cached." @@ -1512,14 +1441,14 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.help_prefer_map_models = [[ --Some maps define their own player models. By default these models have a higher priority than those that are assigned automatically. By disabling this setting, map specific models are disabled. - +-- --Role specific models always have a higher priority and are unaffected by this setting.]] --L.help_enforce_playermodel = [[ --Some roles have custom player models. They can be disabled which can be relevant for compatibility with some player model selectors. --Random default models can still be selected, if this setting is disabled.]] --L.help_use_custom_models = [[ --By default only the CS:S Phoenix player model is assigned to all players. By enabling this option however it is possible to select a player model pool. With this setting enabled each player will still be assigned the same player model, however it is a random model from the defined model pool. - +-- --This selection of models can be extended by installing more player models.]] -- 2021-10-06 @@ -1536,7 +1465,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da -- 2021-10-09 --L.help_models_select = [[ --Left click on the models to add them to the player model pool. Left click again to remove them. Right clicking toggles between enabled and disabled detective hats for the focused model. - +-- --The small indicator in the top left shows if the player model has a head hitbox. The icon below shows if this model is applicable for a detective hat.]] --L.menu_roles_title = "Role Settings" @@ -1559,7 +1488,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.help_roles_selection = [[ --The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role. This also means that at least 5 players are needed for this role to be distributed at all. --Keep in mind that all of this only applies if the role is considered for distribution process. - +-- --The aforementioned role distribution has a special integration with the lower limit of players. If the role is considered for distribution and the minimum value is below the value given by the distribution factor, but the amount of players is equal or greater than the lower limit, a single player can still receive this role. The distribution process then works as usual for the second player.]] --L.help_roles_award_info = "Some roles (if enabled in their credits settings) receive equipment credits if a certain percentage of enemies has died. Related values can be tweaked here." --L.help_roles_award_pct = "When this percentage of enemies are dead, specific roles are awarded equipment credits." @@ -1567,17 +1496,17 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.help_roles_advanced_warning = "WARNING: These are advanced settings that can completely mess up the role distribution process. When in doubt keep all values at '0'. This value means that no limits are applied and the role distribution will try to assign as many roles as possible." --L.help_roles_max_roles = [[ --The term roles here includes both the base roles and the subroles. By default, there is no limit on how many different roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.help_roles_max_baseroles = [[ --Base roles are only those roles others inherit from. For example, the Innocent role is a base role, while a Pharaoh is a subrole of this role. By default, there is no limit on how many different base roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.label_roles_enabled = "Enable role" @@ -1609,15 +1538,15 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.help_roles_credits_award_kill = "Another way of gaining credits is by killing high value players with a 'public role' such as a Detective. If the killer's role has this enabled, they gain the below defined amount of credits." --L.help_roles_credits_award = [[ --There are two different ways to be awarded credits in base TTT2: - +-- --1. If a certain percentage of the enemy team is dead, the whole team is awarded credits. --2. If a player killed a high value player with a 'public role' such as a Detective, the killer is awarded credits. - +-- --Please note, that this still can be enabled/disabled for every role, even if the whole team is awarded. For example, if team Innocent is awarded, but the Innocent role has this disabled, only the Detective will receive their credits. --The balancing values for this feature can be set in 'Administration' -> 'General Role Settings'.]] --L.help_detective_hats = [[ --Policing roles such as the Detective may wear hats to show their authority. They lose them on death or if damaged at the head. - +-- --Some player models do not support hats by default. This can be changed in 'Administration' -> 'Player Models']] --L.label_roles_credits_award_kill = "Credit reward amount for the kill" @@ -1669,35 +1598,35 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.help_idle = "The idle mode is used to forcefully move idle players into the spectator mode. To leave this mode, they will have to disable it in their 'gameplay' menu." --L.help_namechange_kick = [[ --A name change during an active round could be abused. Therefore, this is prohibited by default and will lead to the offending player being kicked from the server. - +-- --If the bantime is greater than 0, the player will be unable to reconnect to the server until that time has passed.]] --L.help_damage_log = "Each time a player is damaged, a damage log entry is added to the console if enabled. This can also be stored to disk after a round has ended. The file is located at 'data/terrortown/logs/'" --L.help_spawn_waves = [[ --If this variable is set to 0, all players are spawned at once. For servers with huge amounts of players, it can be beneficial to spawn the players in waves. The spawn wave interval is the time between each spawn wave. A spawn wave always spawns as many players as there are valid spawn points. - +-- --Note: Make sure that the preparing time is long enough for the desired amount of spawn waves.]] --L.help_voicechat_battery = [[ --Voice chatting with enabled voice chat battery reduces battery charge. When it's empty, the player can't use voice chat and has to wait for it to recharge. This can help to prevent excessive voice chat usage. - +-- --Note: 'Tick' refers to a game tick. For example, if the tick rate is set to 66, then it will be 1/66th of a second.]] --L.help_ply_spawn = "Player settings that are used on player (re-)spawn." --L.help_haste_mode = [[ --Haste mode balances the game by increasing the round time with every dead player. Only roles that see missing in action players can see the real round time. Every other role can only see the haste mode starting time. - +-- --If haste mode is enabled, the fixed round time is ignored.]] --L.help_round_limit = "After one of the set limit conditions is met, a map change is triggered." --L.help_armor_balancing = "The following values can be used to balance the armor." --L.help_item_armor_classic = "If classic armor mode is enabled, only the previous settings matter. Classic armor mode means that a player can only buy armor once in a round, and that this armor blocks 30% of the incoming bullet and crowbar damage until they die." --L.help_item_armor_dynamic = [[ --Dynamic armor is the TTT2 approach to make armor more interesting. The amount of armor that can be bought is now unlimited, and the armor value stacks. Getting damaged decreases the armor value. The armor value per bought armor item is set in the 'Equipment Settings' of said item. - +-- --When taking damage, a certain percentage of this damage is converted into armor damage, a different percentage is still applied to the player and the rest vanishes. - +-- --If reinforced armor is enabled, the damage applied to the player is decreased by 15% as long as the armor value is above the reinforcement threshold.]] --L.help_sherlock_mode = "The sherlock mode is the classic TTT mode. If the sherlock mode is disabled, dead bodies can not be confirmed, the scoreboard shows everyone as alive and the spectators can talk to the living players." --L.help_prop_possession = [[ --Prop possession can be used by spectators to possess props lying in the world and use the slowly recharging 'punch-o-meter' to move said prop around. - +-- --The maximum value of the 'punch-o-meter' consists of a possession base value, where the kills/deaths difference clamped inbetween two defined limits is added. The meter slowly recharges over time. The set recharge time is the time needed to recharge a single point in the 'punch-o-meter'.]] --L.help_karma = "Players start with a certain amount of Karma, and lose it when they damage/kill teammates. The amount they lose is dependent on the Karma of the person they hurt or killed. Lower Karma reduces damage given." --L.help_karma_strict = "If strict Karma is enabled, the damage penalty increases more quickly as Karma goes down. When it is off, the damage penalty is very low when people stay above 800. Enabling strict mode makes Karma play a larger role in discouraging any unnecessary kills, while disabling it results in a more “loose” game where Karma only hurts players who constantly kill teammates." @@ -1707,7 +1636,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.help_karma_bonus = "There are also two different passive ways to gain Karma during a round. First is a karma restoration which applied to every player at the round end. Then a secondary clean round bonus is given if no teammates were hurt or killed by a player." --L.help_karma_clean_half = [[ --When a player's Karma is above the starting level (meaning the Karma max has been configured to be higher than that), all their Karma increases will be reduced based on how far their Karma is above that starting level. So it goes up slower the higher it is. - +-- --This reduction goes in a curve of exponential decay: initially it's fast, and it slows down as the increment gets smaller. This convar sets at what point the bonus has been halved (so the half-life). With the default value of 0.25, if the starting amount of Karma is 1000 and the max 1500, and a player has Karma 1125 ((1500 - 1000) * 0.25 = 125), then his clean round bonus will be 30 / 2 = 15. So to make the bonus go down faster you’d set this convar lower, to make it go down slower you’d increase it towards 1.]] --L.help_max_slots = "Sets the maximum amount of weapons per slot. '-1' means that there is no limit." --L.help_item_armor_value = "This is the armor value given by the armor item in dynamic mode. If classic mode is enabled (see 'Administration' -> 'Player Settings') then every value greater than 0 is counted as existing armor." @@ -1730,8 +1659,6 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.label_tbutton_admin_show = "Show traitor buttons to admins" --L.label_ragdoll_carrying = "Enable ragdoll carrying" --L.label_prop_throwing = "Enable prop throwing" ---L.label_ragdoll_pinning = "Enable ragdoll pinning for non-Innocent roles" ---L.label_ragdoll_pinning_innocents = "Enable ragdoll pinning for Innocent roles" --L.label_weapon_carrying = "Enable weapon carrying" --L.label_weapon_carrying_range = "Weapon carry range" --L.label_prop_carrying_force = "Prop pickup force" @@ -1761,10 +1688,8 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.label_spectator_chat = "Enable spectators chatting with everybody" --L.label_lastwords_chatprint = "Print last words to chat if killed while typing" --L.label_identify_body_woconfirm = "Identify corpse without pressing the 'confirm' button" ---L.label_announce_body_found = "Announce that a body was found" +--L.label_announce_body_found = "Announce that a body was found when the body was confirmed" --L.label_confirm_killlist = "Announce kill list of confirmed corpse" ---L.label_inspect_detective_only = "Limit corpse search to policing roles only" ---L.label_confirm_detective_only = "Limit corpse confirmation to policing roles only" --L.label_dyingshot = "Shoot on death if in ironsights [experimental]" --L.label_armor_block_headshots = "Enable armor blocking headshots" --L.label_armor_block_blastdmg = "Enable armor blocking blast damage" @@ -1823,7 +1748,6 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.label_sprint_max = "Max sprinting stamina" --L.label_sprint_stamina_consumption = "Stamina consumption factor" --L.label_sprint_stamina_regeneration = "Stamina regeneration factor" ---L.label_sprint_crosshair = "Show crosshair while sprinting" --L.label_crowbar_unlocks = "Primary attack can be used as interaction (i.e. unlocking)" --L.label_crowbar_pushforce = "Crowbar push force" @@ -1836,7 +1760,7 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.help_falldmg_exponent = [[ --This value modifies how exponentially fall damage is increased with the speed the player hits the ground at. - +-- --Take care when altering this value. Setting it too high can make even the smallest falls lethal, while setting it too low will allow players to fall from extreme heights and suffer little to no damage.]] -- 2023-02-08 @@ -1859,4 +1783,411 @@ L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) è stata ucciso da --L.sb_rank_tooltip_heroes = "TTT2 Heroes" --L.sb_rank_tooltip_team = "Team" ---L.tbut_adminarea = "ADMIN AREA:" \ No newline at end of file +--L.tbut_adminarea = "ADMIN AREA:" + +-- 2023-08-10 +--L.equipmenteditor_name_damage_scaling = "Damage Scaling" + +-- 2023-08-11 +--L.equipmenteditor_name_allow_drop = "Allow Drop" +--L.equipmenteditor_desc_allow_drop = "If enabled, the equipment can be dropped freely by the player." + +--L.equipmenteditor_name_drop_on_death_type = "Drop on Death" +--L.equipmenteditor_desc_drop_on_death_type = "Attempt overriding the action taken for whether the equipment is dropped on player's death." + +--L.drop_on_death_type_default = "Default (weapon-defined)" +--L.drop_on_death_type_force = "Force Drop on Death" +--L.drop_on_death_type_deny = "Deny Drop on Death" + +-- 2023-08-26 +--L.equipmenteditor_name_kind = "Equipment Slot" +--L.equipmenteditor_desc_kind = "The inventory slot the equipment will occupy." + +--L.slot_weapon_melee = "Melee Slot" +--L.slot_weapon_pistol = "Pistol Slot" +--L.slot_weapon_heavy = "Heavy Slot" +--L.slot_weapon_nade = "Grenade Slot" +--L.slot_weapon_carry = "Carry Slot" +--L.slot_weapon_unarmed = "Unarmed Slot" +--L.slot_weapon_special = "Special Slot" +--L.slot_weapon_extra = "Extra Slot" +--L.slot_weapon_class = "Class Slot" + +-- 2023-10-04 +--L.label_voice_duck_spectator = "Duck spectator voices" +--L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +--L.label_voice_scaling = "Voice Volume Scaling Mode" +--L.label_voice_scaling_mode_linear = "Linear" +--L.label_voice_scaling_mode_power4 = "Power 4" +--L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "Risultati ricerca - {player}" +L.search_info = "Informazione" +L.search_confirm = "Conferma morte" +--L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +--L.search_take_credits = "Take {credits} Credit(s)" +--L.search_confirm_forbidden = "Confirm forbidden" +--L.search_confirmed = "Death Confirmed" +--L.search_call = "Report Death" +--L.search_called = "Death Reported" + +--L.search_team_role_unknown = "???" + +L.search_words = "Qualcosa ti dice che le ultime parole di questa persona erano: '{lastwords}'" +L.search_armor = "Stava indossando un'armatura speciale." +L.search_disguiser = "Stava usando un dispositivo che poteva nascondere la sua identità." +L.search_radar = "Avevano qualche tipo di radar. Non funziona più." +L.search_c4 = "In una tasca hai trovato una nota. Dice che tagliare il filo {num} disarmerà la bomba." + +L.search_dmg_crush = "Molte delle sua ossa sono rotte. Sembra che l'impatto di un oggetto pesante lo abbia ucciso." +L.search_dmg_bullet = "È ovvio che sia stato ucciso da dei colpi di arma da fuoco." +L.search_dmg_fall = "Sono morti di caduta." +L.search_dmg_boom = "Le sue ferite e i vestiti bruciati indicano che un'esplosione lo hanno ucciso." +L.search_dmg_club = "Il suo corpo è pieno di ferite. Chiaramente è stato ucciso a bastonate." +L.search_dmg_drown = "Il corpo morta i segni dell'affogamento." +L.search_dmg_stab = "È stato accoltellato prima di morire velocemente dissanguato." +L.search_dmg_burn = "Si sente odore di terrorista bruciato qui..." +L.search_dmg_teleport = "Sembra che il DNA stato criptato da delle emissioni tachioniche!" +L.search_dmg_car = "Quando il terrorista ha attraversato la strada, sono stati investiti da un pilota spericolato." +L.search_dmg_other = "Non puoi trovare una causa di morte specifica per la morte di questo terrorista." + +--L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +--L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +--L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +--L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +--L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +--L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +--L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +--L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +--L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +--L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +--L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +--L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +--L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +--L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +--L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +--L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +--L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +--L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +--L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +--L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +--L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +--L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +--L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +--L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +--L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "Sembra che sia stato usato un {weapon} per ucciderlo." +L.search_head = "La ferita fatale è stata un colpo in testa. Non c'era il tempo di urlare." +--L.search_time = "They died a while before you conducted the search." +--L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "Hai trovato una lista di uccisioni che conferma la morte di {player}." +L.search_kills2 = "Hai trovato una lista di uccisioni con questi nomi: {player}" +L.search_eyes = "Usando le tue abilità da detective, hai identificato che l'ultima persona che ha visto è: {player}. L'assassino, o una coincidenza?" + +--L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +--L.search_kill_distance_point_blank = "It was a point blank attack." +--L.search_kill_distance_close = "The attack came from a short distance." +--L.search_kill_distance_far = "The victim was attacked from a long distance away." + +--L.search_kill_from_front = "The victim was shot from the front." +--L.search_kill_from_back = "The victim was shot from behind." +--L.search_kill_from_side = "The victim was shot from the side." + +--L.search_hitgroup_head = "The projectile was found in their head." +--L.search_hitgroup_chest = "The projectile was found in their chest." +--L.search_hitgroup_stomach = "The projectile was found in their stomach." +--L.search_hitgroup_rightarm = "The projectile was found in their right arm." +--L.search_hitgroup_leftarm = "The projectile was found in their left arm." +--L.search_hitgroup_rightleg = "The projectile was found in their right leg." +--L.search_hitgroup_leftleg = "The projectile was found in their left leg." +--L.search_hitgroup_gear = "The projectile was found in their hip." + +--L.search_policingrole_report_confirm = [[ +--A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +--L.search_policingrole_confirm_disabled_1 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +--L.search_policingrole_confirm_disabled_2 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know! +--You can see the information in here after they confirmed it.]] +--L.search_spec = [[ +--As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +--L.search_title_words = "Victim's last words" +--L.search_title_c4 = "Defusion mishap" +--L.search_title_dmg_crush = "Crush damage ({amount} HP)" +--L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +--L.search_title_dmg_fall = "Fall damage ({amount} HP)" +--L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +--L.search_title_dmg_club = "Club damage ({amount} HP)" +--L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +--L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +--L.search_title_dmg_burn = "Burning damage ({amount} HP)" +--L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +--L.search_title_dmg_car = "Car accident ({amount} HP)" +--L.search_title_dmg_other = "Unknown damage ({amount} HP)" +--L.search_title_time = "Death time" +--L.search_title_dna = "DNA sample decay" +--L.search_title_kills = "The victim's kill list" +--L.search_title_eyes = "The killer's shadow" +--L.search_title_floor = "Floor of the crime scene" +--L.search_title_credits = "{credits} Equipment credit(s)" +--L.search_title_water = "Water level {level}" +--L.search_title_policingrole_report_confirm = "Confirm to report death" +--L.search_title_policingrole_confirm_disabled = "Report corpse" +--L.search_title_spectator = "You are a spectator" + +--L.target_credits_on_confirm = "Confirm to receive unspent credits" +--L.target_credits_on_search = "Search to receive unspent credits" +--L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +--L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +--L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +--L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +--L.label_inspect_confirm_mode = "Select body search mode" +--L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +--L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +--L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +--L.help_inspect_confirm_mode = [[ +--There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. +-- +--mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. +-- +--mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. +-- +--mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +--L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +--L.header_hud_elements_customize = "Customize the HUD-Elements" +--L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +--L.help_keyhelp = [[ +--Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: +-- +--Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +--Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +--Equipment: Some equipment items have their own bindings, these are shown in this category. +-- +--Disabled categories are still shown when the scoreboard is visible]] + +--L.label_keyhelp_show_core = "Enable always showing the core bindings" +--L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +--L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +--L.header_interface_keys = "Key helper settings" +--L.header_interface_wepswitch = "Weapon switch UI settings" + +--L.label_keyhelper_help = "open gamemode menu" +--L.label_keyhelper_mutespec = "cycle spectator voice mode" +--L.label_keyhelper_shop = "open equipment shop" +--L.label_keyhelper_show_pointer = "free mouse pointer" +--L.label_keyhelper_possess_focus_entity = "possess focused entity" +--L.label_keyhelper_spec_focus_player = "spectate focused player" +--L.label_keyhelper_spec_previous_player = "previous player" +--L.label_keyhelper_spec_next_player = "next player" +--L.label_keyhelper_spec_player = "spectate random player" +--L.label_keyhelper_possession_jump = "prop: jump" +--L.label_keyhelper_possession_left = "prop: left" +--L.label_keyhelper_possession_right = "prop: right" +--L.label_keyhelper_possession_forward = "prop: forward" +--L.label_keyhelper_possession_backward = "prop: backward" +--L.label_keyhelper_free_roam = "leave object and roam free" +--L.label_keyhelper_flashlight = "toggle flashlight" +--L.label_keyhelper_quickchat = "open quickchat" +--L.label_keyhelper_voice_global = "global voice chat" +--L.label_keyhelper_voice_team = "team voice chat" +--L.label_keyhelper_chat_global = "global chat" +--L.label_keyhelper_chat_team = "team chat" +--L.label_keyhelper_show_all = "show all" +--L.label_keyhelper_disguiser = "toggle disguiser" +--L.label_keyhelper_save_exit = "save and exit" +--L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +--L.item_armor_reinforced = "Reinforced Armor" +--L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +--L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +--L.status_speed_name = "Speed Multiplier" +--L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +--L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +--L.status_on = "on" +--L.status_off = "off" + +--L.crowbar_help_primary = "Attack" +--L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +--L.help_HUD_enable_description = [[ +--Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +--L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +--L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +--L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +--L.header_soundeffect_settings = "Sound Effects" +--L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +--L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +--L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +--L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +--L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +--L.help_prop_spec_dash = [[ +--Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. +-- +--This variable is a multiplier of the push force.]] +--L.label_spec_prop_dash = "Dash force multiplier" +--L.label_keyhelper_possession_dash = "prop: dash in view direction" +--L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +--L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +--L.c4_help_primary = "Place the C4" +--L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +--L.magneto_help_primary = "Push entity" +--L.magneto_help_secondary = "Pull / pickup entity" +--L.knife_help_primary = "Stab" +--L.knife_help_secondary = "Throw knife" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +--L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +--L.newton_force = "FORCE" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +--L.beacon_help_pri = "Throw Beacon on the ground" +--L.beacon_help_sec = "Stick Beacon to surface" +--L.beacon_name = "Beacon" +--L.beacon_desc = [[ +--Broadcasts player locations to everyone in a sphere around this beacon. +-- +--Use to keep track of locations on the map that are hard to see.]] + +--L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +--L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +--L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +--L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +--L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder} ha confermato la morte di {victim}." +--L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +--L.builtin_marker = "Built-in." +--L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +--L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +--L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +--L.submenu_gameplay_accessibility_title = "Accessibility" + +--L.header_accessibility_settings = "Accessibility Settings" + +--L.label_enable_dynamic_fov = "Enable dynamic FOV change" +--L.label_enable_bobbing = "Enable view bobbing" +--L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +--L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +--L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +--L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +--For a shotgun, this would affect each pellet. +--For a rifle, this would affect just the bullet. +--For the poltergeist, this would affect each "thump" and the final explosion. +-- +--0.5 = Deal half the amount of damage. +--2 = Deal twice the amount of damage. +-- +--Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +--L.binoc_help_reload = "Clear target." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +--L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +--L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +--L.label_crosshair_mode = "Crosshair mode" +--L.label_crosshair_static_length = "Enable static crosshair line length" + +--L.choice_crosshair_mode_0 = "Lines and dot" +--L.choice_crosshair_mode_1 = "Lines only" +--L.choice_crosshair_mode_2 = "Dot only" + +--L.help_crosshair_scale_enable = [[ +--Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. +-- +--If the line length is kept static, only the gap scales with cone changes.]] + +--L.header_weapon_settings = "Weapon Settings" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +L.decoy_help_primary = "Piazza un esca" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/ja.lua b/lua/terrortown/lang/ja.lua index 12c5a952c..1899c9531 100644 --- a/lua/terrortown/lang/ja.lua +++ b/lua/terrortown/lang/ja.lua @@ -60,8 +60,6 @@ L.body_found_traitor = "奴はTraitorだったな!" L.body_found_det = "奴はDetectiveだったようだ…" L.body_found_inno = "奴はInnocentだったようだ…" -L.body_confirm = "{finder}は{victim}の死を確認した。" - L.body_call = "{player}はDetectiveを{victim}の死体の場所に呼んだ!" L.body_call_error = "Detectiveを呼ぶ前にこのプレイヤーの死の確認が必要だ!" @@ -172,50 +170,10 @@ L.quick_disg = "変装中" L.quick_corpse = "死体" L.quick_corpse_id = "{player}の死体" --- Body search window -L.search_title = "調査結果" -L.search_info = "情報" -L.search_confirm = "確認済み" -L.search_call = "探偵を呼ぶ" - --- Descriptions of pieces of information found -L.search_nick = "こいつは{player}の死体だ。" - -L.search_role_traitor = "こいつはTraitorだったな!" -L.search_role_det = "こいつはDetectiveだった。" -L.search_role_inno = "こいつはInnocentだった。" - -L.search_words = "遺言:「{lastwords}」" -L.search_armor = "ボディアーマーを着ていたようだ。" -L.search_disg = "変装をしていたようだ。" -L.search_radar = "レーダーを所持していたようだ。もう機能していないがな。" -L.search_c4 = "ポケットからメモを見つけた。「爆弾を解除するには{num}番のワイヤーをカットしろ」と書かれている。" - -L.search_dmg_crush = "こいつの骨の多くが折れている。重たい物でもぶつかって死んだようだ。" -L.search_dmg_bullet = "こいつは撃たれて死んだようだな。" -L.search_dmg_fall = "こいつは転落死したようだな。" -L.search_dmg_boom = "こいつの傷と焼けた衣服から見ると、爆発で死んだように思えるな。" -L.search_dmg_club = "死体には打撲傷と殴られた跡がある。殴られて死んだようだな。" -L.search_dmg_drown = "死因は溺死のようだ。" -L.search_dmg_stab = "こいつは刃物に刺されて出血死したようだ。。" -L.search_dmg_burn = "この辺りにはテロリストが焼けたような臭いがするな..." -L.search_dmg_tele = "こいつのDNAはタキオン粒子の放出によってかき混ぜられたように見えるな。" -L.search_dmg_car = "このテロリストが道路を渡った際、野蛮なドライバーにでも轢かれたのか。" -L.search_dmg_other = "このテロリストの死因を特定できない。" - -L.search_weapon = "{weapon}によって殺されたようだな。" -L.search_head = "ヘッドショットされたのか。叫ぶ暇も無いな。" -L.search_time = "こいつは調査のおおよそ{time}秒前に死んだな。" -L.search_dna = "殺害者のDNAサンプルをDNAスキャナーで回収しなくては。DNAサンプルは今からおおよそ{time}秒で腐敗するだろう。" - -L.search_kills1 = "{player}の死を立証するための殺害リストを見つけた。" -L.search_kills2 = "これらの名前の載った殺害リストを見つけた:" -L.search_eyes = "こいつが最後の人物は、{player}。こいつは敵か、それとも偶然か?" - -- Scoreboard L.sb_playing = "サーバー名" L.sb_mapchange = "マップ変更まで{num}ラウンドか{time}秒" ---L.sb_mapchange_disabled = "Session limits are disabled." +L.sb_mapchange_disabled = "セッションの制限を無くしました。" L.sb_mia = "行方不明" L.sb_confirmed = "死亡確認" @@ -251,6 +209,7 @@ L.item_weapon = "武器" L.item_armor = "ボディアーマー" L.item_armor_desc = [[ 弾丸、炎、爆発によるダメージを軽減。延長時間になったら使い物にならない。 + 複数の購入が可能。ある特定の装甲値に達した後、アーマーは強化される。]] L.item_radar = "レーダー" @@ -261,12 +220,11 @@ L.item_radar_desc = [[ L.item_disg = "変装装置" L.item_disg_desc = [[ -変装中はあなたのID情報を隠せます。 さらに、 -獲物が最期に目撃した人物になるのも避けれます。 +変装中はあなたのID情報を隠せます。 さらに、獲物が最期に目撃した人物になるのも避けれます。 + このメニューの変装メニュー内かテンキーのEnterで切り替え。]] -- C4 -L.c4_hint = "{usekey}を押して起動もしくは解除" L.c4_disarm_warn = "C4が解除されてしまった。" L.c4_armed = "爆弾は起動完了だ。" L.c4_disarmed = "爆弾の解除に成功した。" @@ -287,6 +245,7 @@ L.c4_remove_destroy2 = "確認:破壊" L.c4_disarm = "C4を解除" L.c4_disarm_cut = "クリックして{num}本目のワイヤーを切断する" +L.c4_disarm_t = "ワイヤーを切って爆弾を解除するんだ。Traitorならどのワイヤーでも安全だが、Innocentならそう簡単にはいかないぞ!" L.c4_disarm_owned = "ワイヤーをカットして爆弾を解除してくれ。自分の爆弾だからどのワイヤーでも安全だ。" L.c4_disarm_other = "安全なワイヤーをカットして爆弾を解除するんだ。間違えたら即爆発だ!" @@ -295,16 +254,14 @@ L.c4_status_disarmed = "解除済み" -- Visualizer L.vis_name = "可視化装置" -L.vis_hint = "{usekey}で拾う(探偵のみ)" L.vis_desc = [[ 殺害現場を可視化してくれる機械。 -死体を分析して被害者がどのように殺害されたかを表示しますが、 -被害者が銃撃の傷で死亡した場合のみ。]] + +死体を分析して被害者がどのように殺害されたかを表示しますが、被害者が銃撃の傷で死亡した場合のみ。]] -- Decoy L.decoy_name = "デコイ" -L.decoy_no_room = "この狭い所ではデコイは持てないようだ。" L.decoy_broken = "デコイが破壊された!" L.decoy_short_desc = "このデコイは別陣営のレーダーに偽のレーダー反応を示してくれるぞ。" @@ -315,61 +272,59 @@ Detectiveに偽のレーダー反応を表示させ、彼らがあなたのDNA -- Defuser L.defuser_name = "C4除去装置" -L.defuser_help = "{primaryfire}でC4除去" L.defuser_desc = [[ C4爆弾を即座に除去する。 -使用回数は無制限。 -これさえ持っていればC4に気がつくのに容易でしょう。]] + +使用回数は無制限。これさえ持っていればC4に気がつくのに容易でしょう。]] -- Flare gun L.flare_name = "信号拳銃" L.flare_desc = [[ 死体を燃やすことができる拳銃。証拠隠滅に必須。 -弾は限られているので注意。 -燃えている死体からは大きな燃焼音を発するので注意。]] + +弾は限られているので注意。燃えている死体からは大きな燃焼音を発するので注意。]] -- Health station L.hstation_name = "回復ステーション" L.hstation_broken = "回復ステーションが破壊された!" -L.hstation_help = "{primaryfire}で回復ステーション設置" L.hstation_desc = [[ 回復が可能な設置型の機械。チャージは遅く、 -誰でも使用することができますが、耐久力があるので注意。 -使用者のDNAサンプルをチェックすることができます。]] + +誰でも使用することができますが、耐久力があるので注意。使用者のDNAサンプルをチェックすることができます。]] -- Knife L.knife_name = "ナイフ" L.knife_thrown = "ナイフ投擲" L.knife_desc = [[ -怪我した者なら即座に静かに始末できますが、 -一度しか使用できません。 +怪我した者なら即座に静かに始末できますが、一度しか使用できません。 + オルトファイアで投擲できます。]] -- Poltergeist L.polter_desc = [[ -オブジェクトにThumperを設置すると、 -使用者の意志に関係なくそのオブジェクトが暴れまわり、 +オブジェクトにThumperを設置すると、使用者の意志に関係なくそのオブジェクトが暴れまわり、 + 暴れ終わった後のThumperの爆発は近くの人間にダメージを与えます。]] -- Radio L.radio_broken = "ラジオが破壊された!" -L.radio_help_pri = "{primaryfire}でラジオを置く" L.radio_desc = [[ 注意を逸らしたり欺くために音を再生できる機械。 -どこか適当な場所にラジオを置いてから、 -ショップメニュー内のラジオメニューから音を再生できます。]] + +どこか適当な場所にラジオを置いてから、ショップメニュー内のラジオメニューから音を再生できます。]] -- Silenced pistol L.sipistol_name = "消音ピストル" L.sipistol_desc = [[ サプレッサー付きのハンドガン。通常のピストルの弾丸を使用する。 + 撃たれた犠牲者は悲鳴をあげることはないだろう。]] -- Newton launcher @@ -377,6 +332,7 @@ L.newton_name = "ニュートンランチャー" L.newton_desc = [[ 遠距離からでも人を弾き飛ばせる弾を発射する。 + 弾は無制限だが、次の弾を発射するのに時間がかかる。]] -- Binoculars @@ -384,6 +340,7 @@ L.binoc_name = "双眼鏡" L.binoc_desc = [[ 遠く離れた距離から死体まで拡大し、確認することができる。 + 無制限で使用できる、確認するのに数秒かかる。]] -- UMP @@ -401,7 +358,7 @@ L.dna_killer = "死体から殺害者のDNAサンプルを入手した!" L.dna_duplicate = "一致した!スキャナーにこのDNAが登録されたぞ。" L.dna_no_killer = "DNAは回収されることができないようだ (殺害者はゲームを退出したんだろうか?)." L.dna_armed = "この爆弾は稼働中だ!早く解除するんだ!" -L.dna_object = "オブジェクトから{num}個の新しいDNAサンプルを入手した。" +--L.dna_object = "Collected a sample of the last owner from the object." L.dna_gone = "このエリアにDNA反応はないようだ。" L.dna_desc = [[ @@ -470,7 +427,7 @@ L.hp_wounded = "怪我" L.hp_badwnd = "重傷" L.hp_death = "瀕死" --- TargetID karma status +-- TargetID Karma status L.karma_max = "安全" L.karma_high = "粗野" L.karma_med = "トリガーハッピー" @@ -479,14 +436,12 @@ L.karma_min = "どうしようもない" -- TargetID misc L.corpse = "死体" -L.corpse_hint = "{usekey}を押して調査。{walkkey} + {usekey}で密かに調査。" +--L.corpse_hint = "Press [{usekey}] to search and confirm. [{walkkey} + {usekey}] to search covertly." L.target_disg = "(変装中)" L.target_unid = "誰かの死体" L.target_unknown = "テロリスト" -L.target_credits = "調べて未使用クレジットを入手する" - -- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "一度きり" L.tbut_reuse = "再使用可能" @@ -501,7 +456,6 @@ L.mute_off = "ミュートを解除した" -- Spectators and prop possession L.punch_title = "パンチ・オー・メーター" -L.punch_help = "移動キーもしくはジャンプ:オブジェクト移動。しゃがみ:オブジェクトを離れる。" L.punch_bonus = "スコアが低かったため、パンチ・オー・メーターの最大値が{num}下がった。" L.punch_malus = "スコアが高かったため、パンチ・オー・メーターの最大値が{num}上がった!" @@ -926,19 +880,16 @@ L.hud_forced_failed = " {hudname} を固定できなかった。これを行う L.hud_restricted_failed = " {hudname} を制限できなかった。あなたはそれを行う権限がないようだ。" L.shop_role_select = "役職選択" -L.shop_role_selected = "{roles}のショップを選択した" +L.shop_role_selected = "{role}のショップを選択した" L.shop_search = "検索" -L.spec_help = "プレイヤー達を観戦する場合はクリックするか、{usekey}を押して、オブジェクトに憑依できる。" -L.spec_help2 = "観戦者モードをやめるには、{helpkey}でメニューを開き、「ゲーム設定」から観戦者モードを切り替えよう。" - -- 2019-10-19 L.drop_ammo_prevented = "何かが弾を捨てるのを妨げているようだ。" -- 2019-10-28 L.target_c4 = "[{usekey}]でC4メニューを開く" L.target_c4_armed = "[{usekey}]でC4を解除する" -L.target_c4_armed_defuser = "[{usekey}]で除去装置を使う" +L.target_c4_armed_defuser = "[{primaryfire}]で除去装置を使う" L.target_c4_not_disarmable = "あなたは生存しているチームメイトのC4を解除することはできない。" L.c4_short_desc = "巨大な爆発を引き起こす" @@ -946,16 +897,15 @@ L.target_pickup = "[{usekey}]で拾う" L.target_slot_info = "スロット: {slot}" L.target_pickup_weapon = "[{usekey}]で武器を拾う" L.target_switch_weapon = "[{usekey}]で今手に持っている武器と交換" -L.target_pickup_weapon_hidden = ", [{usekey} + {walkkey}]で隠密に拾う" -L.target_switch_weapon_hidden = ", [{usekey} + {walkkey}]で隠密に交換" +L.target_pickup_weapon_hidden = ", [{walkkey} + {usekey}]で隠密に拾う" +L.target_switch_weapon_hidden = ", [{walkkey} + {usekey}]で隠密に交換" L.target_switch_weapon_nospace = "この武器のインベントリがないな。" L.target_switch_drop_weapon_info = "{name}をスロット{slot}から捨てる" L.target_switch_drop_weapon_info_noslot = "スロット{slot}には捨てるものがないな。" -L.corpse_searched_by_detective = "Detectiveにより調査済み" +--L.corpse_searched_by_detective = "This corpse was searched by a public policing role" L.corpse_too_far_away = "その死体から遠すぎる。" -L.radio_pickup_wrong_team = "別陣営が所有するラジオは使えないようだ。" L.radio_short_desc = "銃声こそ音楽だ" L.hstation_subtitle = "[{usekey}]で回復する." @@ -1000,7 +950,6 @@ L.mute_team = "{team}がミュートされた。" L.door_auto_closes = "このドアは自動で閉まるようだ。" L.door_open_touch = "触れるとドアが開くようだ。" L.door_open_touch_and_use = "このドアは触れるか、[{usekey}] で開くようだ。" -L.hud_health = "HP" -- 2020-03-09 L.help_title = "ヘルプと設定" @@ -1022,7 +971,7 @@ L.menu_guide_description = "TTT2が初めての方への説明、遊び方、役 L.menu_bindings_description = "TTT2用の特定の機能、\nまたはそれ対応のアドオン関連のキーを設定。" L.menu_language_description = "言語を設定できます。" L.menu_appearance_description = "外見やユーザーインターフェイスを\n微調整できます。" -L.menu_gameplay_description = "ある役職になることを避けたりなど、\nそのほかの微調整ができます。" +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." L.menu_addons_description = "個人のお好きで、現在導入されている\nアドオン構成ができます。" L.menu_legacy_description = "旧TTTのように設定ができ、\nその設定はTTT2にも適用されます。" L.menu_administration_description = "HUD、ショップやその他の設定。" @@ -1046,10 +995,8 @@ L.submenu_appearance_crosshair_title = "クロスヘア" L.submenu_appearance_dmgindicator_title = "ダメージインジケータ" L.submenu_appearance_performance_title = "パフォーマンス" L.submenu_appearance_interface_title = "インターフェイス" -L.submenu_appearance_miscellaneous_title = "その他" L.submenu_gameplay_general_title = "基本設定" -L.submenu_gameplay_avoidroles_title = "役職設定" L.submenu_administration_hud_title = "HUD設定" L.submenu_administration_randomshop_title = "ランダムショップ" @@ -1086,17 +1033,12 @@ L.label_shop_show_slot = "スロットマーカーを表示" L.label_shop_show_custom = "カスタムアイテムマーカーを表示" L.label_shop_show_fav = "お気に入りアイテムマーカーを表示" L.label_crosshair_enable = "クロスヘアあり" -L.label_crosshair_gap_enable = "カスタムクロスヘアギャップあり" -L.label_crosshair_gap = "カスタムクロスヘアギャップ" L.label_crosshair_opacity = "クロスヘア不透明度" L.label_crosshair_ironsight_opacity = "アイアンサイトのクロスヘアの不透明度" L.label_crosshair_size = "クロスヘアの大きさ" L.label_crosshair_thickness = "クロスヘアの太さ" L.label_crosshair_thickness_outline = "クロスヘアの外枠の太さ" -L.label_crosshair_static_enable = "スタティッククロスヘアを有効" -L.label_crosshair_dot_enable = "クロスヘアドットを有効" -L.label_crosshair_lines_enable = "クロスヘアラインを有効" -L.label_crosshair_scale_enable = "武器依存の武器スケールを有効にする" +--L.label_crosshair_scale_enable = "Enable dynamic crosshair scale" L.label_crosshair_ironsight_low_enabled = "アイアンサイトを使用する場合は武器を提げる" L.label_damage_indicator_enable = "ダメージインジケーターを有効" L.label_damage_indicator_mode = "ダメージインジケーターのテーマを選択" @@ -1115,13 +1057,10 @@ L.label_gameplay_specmode = "観戦者モード(常時観戦者になれます L.label_gameplay_fastsw = "高速武器スイッチ" L.label_gameplay_hold_aim = "Aimの固定を有効" L.label_gameplay_mute = "死んだとき生存者のボイスチャットをミュートにする" -L.label_gameplay_dtsprint_enable = "タブルタップ走行を有効" -L.label_gameplay_dtsprint_anykey = "止まるまでダブルタップ走行を止めない" L.label_hud_default = "デフォルトHUD" L.label_hud_force = "強制的HUD" L.label_bind_weaponswitch = "武器を拾う" -L.label_bind_sprint = "ダッシュ" L.label_bind_voice = "通常ボイスチャット" L.label_bind_voice_team = "チームボイスチャット" @@ -1145,7 +1084,6 @@ L.header_damage_indicator = "ダメージインジケータ設定" L.header_performance_settings = "パフォーマンス設定" L.header_interface_settings = "インターフェイス設定" L.header_gameplay_settings = "ゲーム設定" -L.header_roleselection = "あまりなりたくない役職を選択(必ずならないわけではありません)" L.header_hud_administration = "デフォルトと強制的HUDを選択" L.header_hud_enabled = "HUD 有効/無効" @@ -1192,11 +1130,7 @@ L.hud_revival_time = "{time}秒" L.door_destructible = "ドアが破損している({health}HP)" -- 2020-05-28 -L.confirm_detective_only = "Detectiveにしか死体を確認できないようだ。" -L.inspect_detective_only = "Detectiveにしか死体を検査できないようだ。" -L.corpse_hint_no_inspect = "Detectiveにしかこの死体を探せないようだ。" -L.corpse_hint_inspect_only = "[{usekey}] で探す。Detectiveにしか死体を確認できないようだ。" -L.corpse_hint_inspect_only_credits = "[{usekey}] でクレジットを受け取る。Detectiveにしかこの死体を探せないようだ。" +--L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." -- 2020-06-04 L.label_bind_disguiser = "変装する" @@ -1211,7 +1145,6 @@ L.binoc_help_sec = "ズームレベル変更" L.vis_help_pri = "可視化装置を落とす" -L.decoy_help_pri = "デコイを設置する" -- 2020-08-07 L.pickup_error_spec = "観戦者のためこれは拾えないようだ。" @@ -1397,11 +1330,11 @@ L.header_rolelayering_info = "役職レイヤー情報" --L.help_rolelayering_layers = "From each layer only one role is selected. First the roles from the custom layers are distributed starting from the first layer until the last is reached or no more roles can be upgraded. Whichever happens first, if upgradeable slots are still available, the unlayered roles will be distributed as well." L.scoreboard_voice_tooltip = "音量を変更" ---2021-06-15 +-- 2021-06-15 L.header_shop_linker = "設定" L.label_shop_linker_set = "ショップ設定" ---2021-06-18 +-- 2021-06-18 L.xfer_team_indicator = "陣営" -- 2021-06-25 @@ -1417,7 +1350,7 @@ L.spawneditor_desc = "武器、弾薬やプレイヤーのスポーン位置を L.spawneditor_place = "スポーン位置設置" L.spawneditor_remove = "スポーン位置削除" L.spawneditor_change = "スポーンタイプを変更([SHIFT]を押しながらだと逆になります)" ---L.spawneditor_ammo_edit = "Hold to edit amount of autospawning ammo on weapon spawns" +--L.spawneditor_ammo_edit = "Hold on weapon spawn to edit autospawning ammo" L.spawn_weapon_random = "おまかせ武器" L.spawn_weapon_melee = "近接武器" @@ -1435,7 +1368,7 @@ L.spawn_ammo_rifle = "ライフル弾" L.spawn_ammo_shotgun = "バックショット" L.spawn_player_random = "プレイヤースポーン" -L.spawn_weapon_ammo = " (弾薬:{ammo})" +L.spawn_weapon_ammo = "(弾薬:{ammo})" L.spawn_weapon_edit_ammo = "[{walkkey}]を押しながら[{primaryfire}又は{secondaryfire}]を押すとこちらのスポーンの弾薬を増加又は減少させることができます。" @@ -1457,33 +1390,33 @@ L.header_equipment_weapon_spawn_setup = "武器スポーン設定" --L.help_spawn_editor_info = [[ --The spawn editor is used to place, remove and edit spawns in the world. These spawns are for weapons, ammunition and players. - +-- --These spawns are saved in files located in 'data/ttt/weaponspawnscripts/'. They can be deleted for a hard reset. The initial spawn files are created from spawns found on the map and in the original TTT weapon spawn scripts. Pressing the reset button always reverts to the initial state. - +-- --It should be noted that this spawn system uses dynamic spawns. This is most interesting for weapons because it no longer defines a specific weapon, but a type of weapons. For example instead of a TTT shotgun spawn, there is now a general shotgun spawn where any weapon defined as shotgun can spawn. The spawn type for each weapon can be set in the 'Edit Equipment' menu. This makes it possible for any weapon to spawn on the map, or to disable certain default weapons. - +-- --Keep in mind that many changes only take effect after a new round has started.]] --L.help_spawn_editor_enable = "On some maps it might be advised to use the original spawns found on the map without replacing them with the dynamic system. Changing this option below only affects the currently active map, so the dynamic system will still be used for every other map." L.help_spawn_editor_hint = "スポーンエディタを終了したい場合は設定画面を再度開いてください。" L.help_spawn_editor_spawn_amount = [[ -このマップには{weapon}個の武器、{ammo}個の弾薬と{player}人のスポーン位置が設定されています。 +このマップには {weapon} 個の武器、{ammo} 個の弾薬と {player} 人のスポーン位置が設定されています。 変更したい場合は'始める'を押しましょう。 -{weaponrandom}xおまかせ武器 -{weaponmelee}x近接武器 -{weaponnade}xグレネード -{weaponshotgun}xショットガン -{weaponheavy}x重機関銃 -{weaponsniper}xスナイパー -{weaponpistol}xピストル -{weaponspecial}x特殊武器 - -{ammorandom}xおまかせ弾薬 -{ammodeagle}xマグナム弾 -{ammopistol}x9mm弾 -{ammomac10}xSMG弾 -{ammorifle}xライフル弾 -{ammoshotgun}xバックショット +{weaponrandom}x おまかせ武器 +{weaponmelee}x 近接武器 +{weaponnade}x グレネード +{weaponshotgun}x ショットガン +{weaponheavy}x 重機関銃 +{weaponsniper}x スナイパー +{weaponpistol}x ピストル +{weaponspecial}x 特殊武器 + +{ammorandom}x おまかせ弾薬 +{ammodeagle}x マグナム弾 +{ammopistol}x 9mm弾 +{ammomac10}x SMG弾 +{ammorifle}x ライフル弾 +{ammoshotgun}x バックショット {playerrandom}xプレイヤースポーン位置]] @@ -1491,7 +1424,7 @@ L.equipmenteditor_name_auto_spawnable = "ワールド内にランダムでスポ L.equipmenteditor_name_spawn_type = "スポーンタイプ選択" --L.equipmenteditor_desc_auto_spawnable = [[ --The TTT2 spawn system allows every weapon to spawn in the world. By default only weapons marked as 'AutoSpawnable' by the creator will spawn in the world, however this can be changed from within this menu. - +-- --Most of the equipment is set to 'special weapon spawns' by default. This means that equipment only spawns on random weapon spawns. However it is possible to place special weapon spawns in the world or change the spawn type here to use other existing spawn types.]] L.pickup_error_inv_cached = "インベントリに空きがないため拾うことはできません。" @@ -1508,14 +1441,14 @@ L.label_prefer_map_models = "デフォルトプレイヤーモデルよりもマ --L.help_prefer_map_models = [[ --Some maps define their own player models. By default these models have a higher priority than those that are assigned automatically. By disabling this setting, map specific models are disabled. - +-- --Role specific models always have a higher priority and are unaffected by this setting.]] --L.help_enforce_playermodel = [[ --Some roles have custom player models. They can be disabled which can be relevant for compatibility with some player model selectors. --Random default models can still be selected, if this setting is disabled.]] --L.help_use_custom_models = [[ --By default only the CS:S Phoenix player model is assigned to all players. By enabling this option however it is possible to select a player model pool. With this setting enabled each player will still be assigned the same player model, however it is a random model from the defined model pool. - +-- --This selection of models can be extended by installing more player models.]] -- 2021-10-06 @@ -1532,7 +1465,7 @@ L.kill_score_team = "チームキル:" -- 2021-10-09 --L.help_models_select = [[ --Left click on the models to add them to the player model pool. Left click again to remove them. Right clicking toggles between enabled and disabled detective hats for the focused model. - +-- --The small indicator in the top left shows if the player model has a head hitbox. The icon below shows if this model is applicable for a detective hat.]] L.menu_roles_title = "役職設定" @@ -1555,7 +1488,7 @@ L.help_roles_default_team = "デフォルト陣営:{team}" --L.help_roles_selection = [[ --The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role. This also means that at least 5 players are needed for this role to be distributed at all. --Keep in mind that all of this only applies if the role is considered for distribution process. - +-- --The aforementioned role distribution has a special integration with the lower limit of players. If the role is considered for distribution and the minimum value is below the value given by the distribution factor, but the amount of players is equal or greater than the lower limit, a single player can still receive this role. The distribution process then works as usual for the second player.]] --L.help_roles_award_info = "Some roles (if enabled in their credits settings) receive equipment credits if a certain percentage of enemies has died. Related values can be tweaked here." --L.help_roles_award_pct = "When this percentage of enemies are dead, specific roles are awarded equipment credits." @@ -1563,17 +1496,17 @@ L.help_roles_default_team = "デフォルト陣営:{team}" --L.help_roles_advanced_warning = "WARNING: These are advanced settings that can completely mess up the role distribution process. When in doubt keep all values at '0'. This value means that no limits are applied and the role distribution will try to assign as many roles as possible." --L.help_roles_max_roles = [[ --The term roles here includes both the base roles and the subroles. By default, there is no limit on how many different roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.help_roles_max_baseroles = [[ --Base roles are only those roles others inherit from. For example, the Innocent role is a base role, while a Pharaoh is a subrole of this role. By default, there is no limit on how many different base roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] L.label_roles_enabled = "追加する" @@ -1605,15 +1538,15 @@ L.button_reset_models = "リセット" L.help_roles_credits_award_kill = "クレジットを獲得するもう一つの方法は、Detectiveのような確白の役職のプレイヤーを殺すことです。\nそうすることで、以下の設定されたクレジット数を得ます。" --L.help_roles_credits_award = [[ --There are two different ways to be awarded credits in base TTT2: - +-- --1. If a certain percentage of the enemy team is dead, the whole team is awarded credits. --2. If a player killed a high value player with a 'public role' such as a Detective, the killer is awarded credits. - +-- --Please note, that this still can be enabled/disabled for every role, even if the whole team is awarded. For example, if team Innocent is awarded, but the Innocent role has this disabled, only the Detective will receive their credits. --The balancing values for this feature can be set in 'Administration' -> 'General Role Settings'.]] --L.help_detective_hats = [[ --Policing roles such as the Detective may wear hats to show their authority. They lose them on death or if damaged at the head. - +-- --Some player models do not support hats by default. This can be changed in 'Administration' -> 'Player Models']] --L.label_roles_credits_award_kill = "Credit reward amount for the kill" @@ -1665,36 +1598,36 @@ L.header_playersettings_armor = "アーマーシステム設定" --L.help_idle = "The idle mode is used to forcefully move idle players into the spectator mode. To leave this mode, they will have to disable it in their 'gameplay' menu." --L.help_namechange_kick = [[ --A name change during an active round could be abused. Therefore, this is prohibited by default and will lead to the offending player being kicked from the server. - +-- --If the bantime is greater than 0, the player will be unable to reconnect to the server until that time has passed.]] L.help_damage_log = "プレーヤーがダメージを受けるたびに、有効になっている場合は、ダメージログエントリがコンソールに追加されます。\nラウンド終了後にディスクに保存することもできます。ファイルは「data/terrortown/log/」に保存されています。" L.help_spawn_waves = [[ -0に設定すると、すべてのプレイヤーが一度にスポーンされます。大人数のプレイヤーがいるサーバーでは、ウェーブ間隔でプレイヤーをスポーンさせるのが良いでしょう。 -スポーンウェーブ間隔は、各スポーンウェーブの間の時間です。スポーンウェーブは、スポーンポイントの数だけプレイヤーをスポーンさせます。 +0に設定すると、すべてのプレイヤーが一度にスポーンされます。大人数のプレイヤーがいるサーバーでは、ウェーブ間隔でプレイヤーをスポーンさせるのが良いでしょう。スポーンウェーブ間隔は、各スポーンウェーブの間の時間です。スポーンウェーブは、スポーンポイントの数だけプレイヤーをスポーンさせます。 + 注意 : 準備時間が希望する量のスポーンウェーブに十分な長さであることを確認してください。]] --L.help_voicechat_battery = [[ --Voice chatting with enabled voice chat battery reduces battery charge. When it's empty, the player can't use voice chat and has to wait for it to recharge. This can help to prevent excessive voice chat usage. - +-- --Note: 'Tick' refers to a game tick. For example, if the tick rate is set to 66, then it will be 1/66th of a second.]] --L.help_ply_spawn = "Player settings that are used on player (re-)spawn." L.help_haste_mode = [[ -HASTEモードは、プレイヤーが一人死亡するたびのラウンド時間追加により、ゲームのバランスを取ります。 -Traitor陣営の役職、又は観戦者のみが、実際のラウンド時間を見ることができます。他の役職は見れません。 +HASTEモードは、プレイヤーが一人死亡するたびのラウンド時間追加により、ゲームのバランスを取ります。Traitor陣営の役職、又は観戦者のみが、実際のラウンド時間を見ることができます。他の役職は見れません。 + HASTEモードが有効になっている場合、通常ラウンド時間は無視されます。]] L.help_round_limit = "設定された制限条件の1つが満たされると、マップ変更が開始されます。" L.help_armor_balancing = "アーマーのバランス調整ができる機能です。" L.help_item_armor_classic = "クラシックアーマーモードは、プレイヤーがラウンドで一度だけボディアーマーを購入することができ、\nアーマーは弾丸とバールによるダメージの30%を軽減できます。" L.help_item_armor_dynamic = [[ -動的アーマーモードは購入できるアーマーの量は無制限で、アーマー値の重複が可能なモードです。 -ダメージを受けると、アーマーの値が減少します。購入したアーマーの耐久値は、上記項目の「装備設定」に設定されています。 +動的アーマーモードは購入できるアーマーの量は無制限で、アーマー値の重複が可能なモードです。ダメージを受けると、アーマーの値が減少します。購入したアーマーの耐久値は、上記項目の「装備設定」に設定されています。 + ダメージを受けると、このダメージの一定の割合だけアーマーへのダメージに変換され、プレイヤーに対しては異なる割合が適用され、残りは消滅します。 + 強化アーマーが有効な場合、耐久値が補強しきい値を超える限り、プレイヤーに与えるダメージは15%減少します。]] L.help_sherlock_mode = "シャーロックモードは、古典的なTTTモードです。シャーロックモードが無効になっている場合、\n死体は確認できず、スコアボードは生きている全ての人を示し、観戦者は生存者と会話が可能です。" L.help_prop_possession = [[ -オブジェクト憑依は、観戦者がマップに存在するオブジェクトに憑依し、 -ゆっくりとチャージされていく「パンチ・オー・メーター」を使用して、そのオブジェクトを操作できる機能です。 -「パンチ・オー・メーター」の最大値は、2つの定義された制限の間に遮断された死量/死の差が追加される基本的憑依値で構成されています。 -メーターは時間の経過とともにゆっくりチャージされます。セットの再チャージ時間は、「パンチ・オー・メーター」の単一ポイントをチャージするのに必要な時間です。]] +オブジェクト憑依は、観戦者がマップに存在するオブジェクトに憑依し、ゆっくりとチャージされていく「パンチ・オー・メーター」を使用して、そのオブジェクトを操作できる機能です。 + +「パンチ・オー・メーター」の最大値は、2つの定義された制限の間に遮断された死量/死の差が追加される基本的憑依値で構成されています。メーターは時間の経過とともにゆっくりチャージされます。セットの再チャージ時間は、「パンチ・オー・メーター」の単一ポイントをチャージするのに必要な時間です。]] --L.help_karma = "Players start with a certain amount of Karma, and lose it when they damage/kill teammates. The amount they lose is dependent on the Karma of the person they hurt or killed. Lower Karma reduces damage given." --L.help_karma_strict = "If strict Karma is enabled, the damage penalty increases more quickly as Karma goes down. When it is off, the damage penalty is very low when people stay above 800. Enabling strict mode makes Karma play a larger role in discouraging any unnecessary kills, while disabling it results in a more “loose” game where Karma only hurts players who constantly kill teammates." --L.help_karma_max = "Setting the value of the max Karma above 1000 doesn't give a damage bonus to players with more than 1000 Karma. It can be used as a Karma buffer." @@ -1702,7 +1635,9 @@ L.help_karma_ratio = "両者が同じ陣営にいる場合に、カルマを加 L.help_karma_traitordmg_ratio = "両者が異なる陣営にいる場合に、加害者のカルマの量を攻撃者から差し引く計算するためのダメージ比率。\nチームキルが発生した場合は、さらにボーナスが適用されます。" L.help_karma_bonus = "ラウンド中にカルマを獲得する2つの異なる受動的な方法もあります。まず、ラウンド復帰はすべてのプレイヤーに適用されます。\nその後、チームメイトがダメージを受けなかったり殺されたりしなかった場合、二次的な整理ボーナスが与えられます。" L.help_karma_clean_half = [[ -プレイヤーのカルマが開始レベルを超えている場合(カルマの最大値がそれより高く設定されている場合)、\nカルマがその開始レベルをどれだけ上回っているかによって、全てのカルマの増加が減少。高いほど遅く上がります。\nこの減少は指数的な減衰の曲線に入ります。最初は速く増分が小さくなるにつれて減速します。この設定は、ボーナスが半分になった時点で設定されます(所謂半減期)。\nデフォルト値が0.25だと、カルマの開始量が1000と最大1500 で、プレイヤーがカルマ 1125 ((1500 - 1000) * 0.25 = 125 を持つ場合、\nラウンド整理ボーナスは30/2 = 15になります。つまり、ボーナスをより速く下げるために、この設定を低く設定し、それが遅くなるように、1に向かってそれを増やすでしょう。]] +プレイヤーのカルマが開始レベルを超えている場合(カルマの最大値がそれより高く設定されている場合)、カルマがその開始レベルをどれだけ上回っているかによって、全てのカルマの増加が減少。高いほど遅く上がります。 + +この減少は指数的な減衰の曲線に入ります。最初は速く増分が小さくなるにつれて減速します。この設定は、ボーナスが半分になった時点で設定されます(所謂半減期)。\nデフォルト値が0.25だと、カルマの開始量が1000と最大1500 で、プレイヤーがカルマ 1125 ((1500 - 1000) * 0.25 = 125 を持つ場合、\nラウンド整理ボーナスは30/2 = 15になります。つまり、ボーナスをより速く下げるために、この設定を低く設定し、それが遅くなるように、1に向かってそれを増やすでしょう。]] L.help_max_slots = "スロットあたりの武器の最大量を設定します。'-1' は制限がないということです。" L.help_item_armor_value = "これは、ダイナミックモードでアーマーアイテムによって与えられるアーマー値です。クラシックモードが\n有効になっている場合(「管理」->'プレイヤー設定'を参照)、0より大きいすべての値が既存のアーマーとしてカウントされます。" @@ -1724,8 +1659,6 @@ L.label_bots_are_spectators = "Botは観戦者状態にする" L.label_tbutton_admin_show = "管理者側にTraitorトラップを表示" L.label_ragdoll_carrying = "Ragdollの運搬を有効" L.label_prop_throwing = "オブジェクトを投げることを有効" -L.label_ragdoll_pinning = "Innocent以外の役職に対してRagdollの張り付けを有効" -L.label_ragdoll_pinning_innocents = "InnocentのRagdollの張り付けを有効" L.label_weapon_carrying = "武器の運搬を有効" L.label_weapon_carrying_range = "武器を運べるまでの距離" L.label_prop_carrying_force = "オブジェクト拾得力" @@ -1751,13 +1684,12 @@ L.label_round_limit = "ラウンド最大数" L.label_time_limit_minutes = "ラウンド時間の上限(分)" --L.label_nade_throw_during_prep = "Enable grenade throwing during preparing time" L.label_postround_dm = "ラウンド終了時間中のデスマッチを有効" +L.label_session_limits_enabled = "セッションの制限を有効にする" L.label_spectator_chat = "観戦者同士でのチャットの有無" L.label_lastwords_chatprint = "タイピング中に殺されたら遺言を送信する" L.label_identify_body_woconfirm = "'確認'ボタン無しで死体を特定" -L.label_announce_body_found = "死体発見時の報告" +--L.label_announce_body_found = "Announce that a body was found when the body was confirmed" L.label_confirm_killlist = "確認済みの死体のリストの報告" ---L.label_inspect_detective_only = "Limit corpse search to policing roles only" ---L.label_confirm_detective_only = "Limit corpse confirmation to policing roles only" L.label_dyingshot = "アイアンサイト中で死に撃つ[実験的]" L.label_armor_block_headshots = "ヘッドショットへの耐久" L.label_armor_block_blastdmg = "爆破ダメージへの耐久" @@ -1816,14 +1748,9 @@ L.label_sprint_enabled = "走行を有効" L.label_sprint_max = "走行用スタミナ最大値" L.label_sprint_stamina_consumption = "スタミナ消費率" L.label_sprint_stamina_regeneration = "スタミナ再生率" -L.label_sprint_crosshair = "走行中のクロスヘアの表示" L.label_crowbar_unlocks = "バールによる鍵解除" L.label_crowbar_pushforce = "バールで押す力" ---2022-04-13 -L.label_session_limits_enabled = "セッションの制限を有効にする" -L.sb_mapchange_disabled = "セッションの制限を無くしました。" - -- 2022-07-02 L.header_playersettings_falldmg = "落下ダメージ設定" @@ -1832,7 +1759,8 @@ L.label_falldmg_min_velocity = "落下ダメージが発生するまでの最小 L.label_falldmg_exponent = "落下速度に対する落下ダメージ増加指数" L.help_falldmg_exponent = [[ -この値は、プレイヤーが地面に当たる速度に応じて、落下ダメージが指数関数的に増加する方法を変更します。 +この値は、プレイヤーが地面に当たる速度に応じて、落下ダメージが指数関数的に増加する方法を変更します。 + この値を変更するときは注意してください。高すぎると、少し下っただけでも致命的になる可能性があり、低すぎると、プレイヤーは極端な高さから落下してもほとんどもしくは全くダメージを受けなくなってしまいます。]] -- 2023-02-08 @@ -1855,4 +1783,411 @@ L.help_falldmg_exponent = [[ --L.sb_rank_tooltip_heroes = "TTT2 Heroes" --L.sb_rank_tooltip_team = "Team" ---L.tbut_adminarea = "ADMIN AREA:" \ No newline at end of file +--L.tbut_adminarea = "ADMIN AREA:" + +-- 2023-08-10 +--L.equipmenteditor_name_damage_scaling = "Damage Scaling" + +-- 2023-08-11 +--L.equipmenteditor_name_allow_drop = "Allow Drop" +--L.equipmenteditor_desc_allow_drop = "If enabled, the equipment can be dropped freely by the player." + +--L.equipmenteditor_name_drop_on_death_type = "Drop on Death" +--L.equipmenteditor_desc_drop_on_death_type = "Attempt overriding the action taken for whether the equipment is dropped on player's death." + +--L.drop_on_death_type_default = "Default (weapon-defined)" +--L.drop_on_death_type_force = "Force Drop on Death" +--L.drop_on_death_type_deny = "Deny Drop on Death" + +-- 2023-08-26 +--L.equipmenteditor_name_kind = "Equipment Slot" +--L.equipmenteditor_desc_kind = "The inventory slot the equipment will occupy." + +--L.slot_weapon_melee = "Melee Slot" +--L.slot_weapon_pistol = "Pistol Slot" +--L.slot_weapon_heavy = "Heavy Slot" +--L.slot_weapon_nade = "Grenade Slot" +--L.slot_weapon_carry = "Carry Slot" +--L.slot_weapon_unarmed = "Unarmed Slot" +--L.slot_weapon_special = "Special Slot" +--L.slot_weapon_extra = "Extra Slot" +--L.slot_weapon_class = "Class Slot" + +-- 2023-10-04 +--L.label_voice_duck_spectator = "Duck spectator voices" +--L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +--L.label_voice_scaling = "Voice Volume Scaling Mode" +--L.label_voice_scaling_mode_linear = "Linear" +--L.label_voice_scaling_mode_power4 = "Power 4" +--L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "調査結果 - {player}" +L.search_info = "情報" +L.search_confirm = "確認済み" +--L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +--L.search_take_credits = "Take {credits} Credit(s)" +--L.search_confirm_forbidden = "Confirm forbidden" +--L.search_confirmed = "Death Confirmed" +--L.search_call = "Report Death" +--L.search_called = "Death Reported" + +--L.search_team_role_unknown = "???" + +L.search_words = "遺言:「{lastwords}」" +L.search_armor = "ボディアーマーを着ていたようだ。" +L.search_disguiser = "変装をしていたようだ。" +L.search_radar = "レーダーを所持していたようだ。もう機能していないがな。" +L.search_c4 = "ポケットからメモを見つけた。「爆弾を解除するには{num}番のワイヤーをカットしろ」と書かれている。" + +L.search_dmg_crush = "こいつの骨の多くが折れている。重たい物でもぶつかって死んだようだ。" +L.search_dmg_bullet = "こいつは撃たれて死んだようだな。" +L.search_dmg_fall = "こいつは転落死したようだな。" +L.search_dmg_boom = "こいつの傷と焼けた衣服から見ると、爆発で死んだように思えるな。" +L.search_dmg_club = "死体には打撲傷と殴られた跡がある。殴られて死んだようだな。" +L.search_dmg_drown = "死因は溺死のようだ。" +L.search_dmg_stab = "こいつは刃物に刺されて出血死したようだ。。" +L.search_dmg_burn = "この辺りにはテロリストが焼けたような臭いがするな..." +L.search_dmg_teleport = "こいつのDNAはタキオン粒子の放出によってかき混ぜられたように見えるな。" +L.search_dmg_car = "このテロリストが道路を渡った際、野蛮なドライバーにでも轢かれたのか。" +L.search_dmg_other = "このテロリストの死因を特定できない。" + +--L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +--L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +--L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +--L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +--L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +--L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +--L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +--L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +--L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +--L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +--L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +--L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +--L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +--L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +--L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +--L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +--L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +--L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +--L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +--L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +--L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +--L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +--L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +--L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +--L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "{weapon}によって殺されたようだな。" +L.search_head = "ヘッドショットされたのか。叫ぶ暇も無いな。" +--L.search_time = "They died a while before you conducted the search." +--L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "{player}の死を立証するための殺害リストを見つけた。" +L.search_kills2 = "これらの名前の載った殺害リストを見つけた:{player}" +L.search_eyes = "こいつが最後の人物は、{player}。こいつは敵か、それとも偶然か?" + +--L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +--L.search_kill_distance_point_blank = "It was a point blank attack." +--L.search_kill_distance_close = "The attack came from a short distance." +--L.search_kill_distance_far = "The victim was attacked from a long distance away." + +--L.search_kill_from_front = "The victim was shot from the front." +--L.search_kill_from_back = "The victim was shot from behind." +--L.search_kill_from_side = "The victim was shot from the side." + +--L.search_hitgroup_head = "The projectile was found in their head." +--L.search_hitgroup_chest = "The projectile was found in their chest." +--L.search_hitgroup_stomach = "The projectile was found in their stomach." +--L.search_hitgroup_rightarm = "The projectile was found in their right arm." +--L.search_hitgroup_leftarm = "The projectile was found in their left arm." +--L.search_hitgroup_rightleg = "The projectile was found in their right leg." +--L.search_hitgroup_leftleg = "The projectile was found in their left leg." +--L.search_hitgroup_gear = "The projectile was found in their hip." + +--L.search_policingrole_report_confirm = [[ +--A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +--L.search_policingrole_confirm_disabled_1 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +--L.search_policingrole_confirm_disabled_2 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know! +--You can see the information in here after they confirmed it.]] +--L.search_spec = [[ +--As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +--L.search_title_words = "Victim's last words" +--L.search_title_c4 = "Defusion mishap" +--L.search_title_dmg_crush = "Crush damage ({amount} HP)" +--L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +--L.search_title_dmg_fall = "Fall damage ({amount} HP)" +--L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +--L.search_title_dmg_club = "Club damage ({amount} HP)" +--L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +--L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +--L.search_title_dmg_burn = "Burning damage ({amount} HP)" +--L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +--L.search_title_dmg_car = "Car accident ({amount} HP)" +--L.search_title_dmg_other = "Unknown damage ({amount} HP)" +--L.search_title_time = "Death time" +--L.search_title_dna = "DNA sample decay" +--L.search_title_kills = "The victim's kill list" +--L.search_title_eyes = "The killer's shadow" +--L.search_title_floor = "Floor of the crime scene" +--L.search_title_credits = "{credits} Equipment credit(s)" +--L.search_title_water = "Water level {level}" +--L.search_title_policingrole_report_confirm = "Confirm to report death" +--L.search_title_policingrole_confirm_disabled = "Report corpse" +--L.search_title_spectator = "You are a spectator" + +--L.target_credits_on_confirm = "Confirm to receive unspent credits" +--L.target_credits_on_search = "Search to receive unspent credits" +--L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +--L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +--L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +--L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +--L.label_inspect_confirm_mode = "Select body search mode" +--L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +--L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +--L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +--L.help_inspect_confirm_mode = [[ +--There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. +-- +--mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. +-- +--mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. +-- +--mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +--L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +--L.header_hud_elements_customize = "Customize the HUD-Elements" +--L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +--L.help_keyhelp = [[ +--Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: +-- +--Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +--Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +--Equipment: Some equipment items have their own bindings, these are shown in this category. +-- +--Disabled categories are still shown when the scoreboard is visible]] + +--L.label_keyhelp_show_core = "Enable always showing the core bindings" +--L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +--L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +--L.header_interface_keys = "Key helper settings" +--L.header_interface_wepswitch = "Weapon switch UI settings" + +--L.label_keyhelper_help = "open gamemode menu" +--L.label_keyhelper_mutespec = "cycle spectator voice mode" +--L.label_keyhelper_shop = "open equipment shop" +--L.label_keyhelper_show_pointer = "free mouse pointer" +--L.label_keyhelper_possess_focus_entity = "possess focused entity" +--L.label_keyhelper_spec_focus_player = "spectate focused player" +--L.label_keyhelper_spec_previous_player = "previous player" +--L.label_keyhelper_spec_next_player = "next player" +--L.label_keyhelper_spec_player = "spectate random player" +--L.label_keyhelper_possession_jump = "prop: jump" +--L.label_keyhelper_possession_left = "prop: left" +--L.label_keyhelper_possession_right = "prop: right" +--L.label_keyhelper_possession_forward = "prop: forward" +--L.label_keyhelper_possession_backward = "prop: backward" +--L.label_keyhelper_free_roam = "leave object and roam free" +--L.label_keyhelper_flashlight = "toggle flashlight" +--L.label_keyhelper_quickchat = "open quickchat" +--L.label_keyhelper_voice_global = "global voice chat" +--L.label_keyhelper_voice_team = "team voice chat" +--L.label_keyhelper_chat_global = "global chat" +--L.label_keyhelper_chat_team = "team chat" +--L.label_keyhelper_show_all = "show all" +--L.label_keyhelper_disguiser = "toggle disguiser" +--L.label_keyhelper_save_exit = "save and exit" +--L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +--L.item_armor_reinforced = "Reinforced Armor" +--L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +--L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +--L.status_speed_name = "Speed Multiplier" +--L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +--L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +--L.status_on = "on" +--L.status_off = "off" + +--L.crowbar_help_primary = "Attack" +--L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +--L.help_HUD_enable_description = [[ +--Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +--L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +--L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +--L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +--L.header_soundeffect_settings = "Sound Effects" +--L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +--L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +--L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +--L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +--L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +--L.help_prop_spec_dash = [[ +--Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. +-- +--This variable is a multiplier of the push force.]] +--L.label_spec_prop_dash = "Dash force multiplier" +--L.label_keyhelper_possession_dash = "prop: dash in view direction" +--L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +--L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +--L.c4_help_primary = "Place the C4" +--L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +--L.magneto_help_primary = "Push entity" +--L.magneto_help_secondary = "Pull / pickup entity" +--L.knife_help_primary = "Stab" +--L.knife_help_secondary = "Throw knife" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +--L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +--L.newton_force = "FORCE" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +--L.beacon_help_pri = "Throw Beacon on the ground" +--L.beacon_help_sec = "Stick Beacon to surface" +--L.beacon_name = "Beacon" +--L.beacon_desc = [[ +--Broadcasts player locations to everyone in a sphere around this beacon. +-- +--Use to keep track of locations on the map that are hard to see.]] + +--L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +--L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +--L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +--L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +--L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder}は{victim}の死を確認した。" +--L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +--L.builtin_marker = "Built-in." +--L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +--L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +--L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +--L.submenu_gameplay_accessibility_title = "Accessibility" + +--L.header_accessibility_settings = "Accessibility Settings" + +--L.label_enable_dynamic_fov = "Enable dynamic FOV change" +--L.label_enable_bobbing = "Enable view bobbing" +--L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +--L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +--L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +--L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +--For a shotgun, this would affect each pellet. +--For a rifle, this would affect just the bullet. +--For the poltergeist, this would affect each "thump" and the final explosion. +-- +--0.5 = Deal half the amount of damage. +--2 = Deal twice the amount of damage. +-- +--Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +--L.binoc_help_reload = "Clear target." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +--L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +--L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +--L.label_crosshair_mode = "Crosshair mode" +--L.label_crosshair_static_length = "Enable static crosshair line length" + +--L.choice_crosshair_mode_0 = "Lines and dot" +--L.choice_crosshair_mode_1 = "Lines only" +--L.choice_crosshair_mode_2 = "Dot only" + +--L.help_crosshair_scale_enable = [[ +--Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. +-- +--If the line length is kept static, only the gap scales with cone changes.]] + +--L.header_weapon_settings = "Weapon Settings" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +L.decoy_help_primary = "デコイを設置する" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/pl.lua b/lua/terrortown/lang/pl.lua index 6f8d96e7e..829e53e69 100644 --- a/lua/terrortown/lang/pl.lua +++ b/lua/terrortown/lang/pl.lua @@ -41,7 +41,7 @@ L.win_showreport = "Spójrzmy na raport rundy na {num} sekund." --L.limit_round = "Round limit reached. The next map will load soon." --L.limit_time = "Time limit reached. The next map will load soon." -L.limit_left = "{num} rund(a), albo {time} minut, do zmiany mapy na {mapname}." +--L.limit_left = "{num} round(s) or {time} minutes remaining before the map changes." -- Credit awards L.credit_all = "Twój team został nagrodzony {num} kredytami na zakupy." @@ -60,8 +60,6 @@ L.body_found_traitor = "On był zdrajcą!" L.body_found_det = "On był detektywem." L.body_found_inno = "On był niewinny." -L.body_confirm = "{finder} potwierdził śmierć {victim}." - L.body_call = "{player} zawołał detektywa do ciała {victim}!" L.body_call_error = "Musisz potwierdić zgon tego gracza, zanim zawołasz detektywa!" @@ -172,46 +170,6 @@ L.quick_disg = "ktoś w przebraniu" L.quick_corpse = "niezidentyfikowane zwłoki" L.quick_corpse_id = "ciało gracza {player}" --- Body search window -L.search_title = "Rezultaty przeszukania ciała" -L.search_info = "Informacje" -L.search_confirm = "Potwierdź śmierć" -L.search_call = "Zawołaj detektywa" - --- Descriptions of pieces of information found -L.search_nick = "To jest ciało gracza {player}." - -L.search_role_traitor = "Ta osoba była zdrajcą!" -L.search_role_det = "Ta osoba była detektywem." -L.search_role_inno = "Ta osoba była niewinnym terrorystą." - -L.search_words = "Coś ci mówi, że jego ostanie słowa to: '{lastwords}'" -L.search_armor = "On nosił niestandardową kamizelkę kuloodporną." -L.search_disg = "Trzymał urządzenie, które mogło ukryć jego tożsamość." -L.search_radar = "Miał jakiegoś rodzaju radar. Już nie działa." -L.search_c4 = "W kieszeni znalazłeś notkę. Stwierdza, że przecięcie przewodu {num} bezpiecznie rozbroi bombę." - -L.search_dmg_crush = "Wiele z jego kości jest połamanych. To pokazuje, że zabił go jakiś duży obiekt." -L.search_dmg_bullet = "Jest oczywiste, że został zastrzelony na śmierć." -L.search_dmg_fall = "Spadł i połamał sobie kark." -L.search_dmg_boom = "Stan jego ubrania pokazują, że wybuch zakończył jego żywot." -L.search_dmg_club = "Ciało jest posiniaczone i poobijane. Najwyraźniej został zatłuczony na śmierć." -L.search_dmg_drown = "Ciało wykazuje, że delikwent utonął." -L.search_dmg_stab = "Został dźgnięty nożem, zanim szybko się wykrwawił." -L.search_dmg_burn = "Pachnie jak pieczony terrorysta..." -L.search_dmg_tele = "Jego DNA zostało uszkodzone przez fale tachionu." -L.search_dmg_car = "Gdy ten terrorysta przechodził przez drogę, został przejechany przez lekkomyślnego kierowce." -L.search_dmg_other = "Nie możesz znaleźć konkretnej przyczyny śmierci tego terrorysty." - -L.search_weapon = "Wygląda na to, że {weapon} posłużyła do tego zabójstwa." -L.search_head = "Rana śmiertelna była strzałem w głowę. Nie miał czasu, aby krzyczeć." -L.search_time = "Zmarł w przybliżeniu {time}, zanim go przeszukałeś." -L.search_dna = "Pobierz próbkę DNA zabójcy za pomocą skanera DNA. Próbka DNA przeterminuje się za {time}." - -L.search_kills1 = "Znalazłeś listę zabójstw, co potwierdza śmierć {player}." -L.search_kills2 = "Znalazłeś listę zabójstw z tymi imionami:" -L.search_eyes = "Używając umiejętności detektywa, zidentyfikowałeś ostatnią osobę, która go widziała: {player}. Morderca, czy zbieg okoliczności?" - -- Scoreboard L.sb_playing = "Grasz na..." L.sb_mapchange = "Mapa zmieni się za {num} rund(ę) lub za {time}" @@ -267,7 +225,6 @@ Ukrywa twój status, gdy jest włączone. Także unika, bycia ostanią osobą wi Przełącz w zakładce Przebrania w tym menu lub kliknij Enter na Numpadzie.]] -- C4 -L.c4_hint = "Kliknij {usekey}, by uzbroić lub rozbroić." L.c4_disarm_warn = "C4, które uzbroiłeś, zostało rozbrojone." L.c4_armed = "Pomyślnie uzbroiłeś bombę." L.c4_disarmed = "Pomyślnie rozbroiłeś bombę." @@ -288,6 +245,7 @@ L.c4_remove_destroy2 = "Potwierdź: zniszcz" L.c4_disarm = "Rozbrój C4" L.c4_disarm_cut = "Kliknij, by przeciąć kabel {num}" +L.c4_disarm_t = "Przetnij kabel, aby rozbroić bombę. Jako że jesteś Zdrajcą, każdy kabel jest bezpieczny. Niewinni nie mają tak łatwo!" L.c4_disarm_owned = "Przetnij kabel, by rozbroić bombe. To twoja bomba, więc każdy kabel ją rozbraja" L.c4_disarm_other = "Przetnij odpowiedni kabel, by rozbroić bombe. Jak się pomylisz, to ona wybuchnie!" @@ -296,7 +254,6 @@ L.c4_status_disarmed = "ROZBROJONA" -- Visualizer L.vis_name = "Wizualizer" -L.vis_hint = "Kliknij {usekey} by podnieść (tylko Detektywi)." L.vis_desc = [[ Wizualizator chwili zabójstwa. @@ -305,7 +262,6 @@ Analizuje ciało by pokazać jak jak ofiara zostałą zabita, ale tylko jak zgin -- Decoy L.decoy_name = "Wabik" -L.decoy_no_room = "Nie możesz wziąć tego wabika." L.decoy_broken = "Twój wabik został zniszczony!" L.decoy_short_desc = "Pokazuje oszukaną pozycję na radarze" @@ -316,7 +272,6 @@ Pokazuje fałszywy znacznik na radarze Detektywów, i sprawia, że DNA skaner po -- Defuser L.defuser_name = "Rozbrajacz" -L.defuser_help = "{primaryfire} rozbraja zaznaczone C4." L.defuser_desc = [[ Natychmiastowo robraja ładunek C4. @@ -335,7 +290,6 @@ Palenie zwłok wydaje charakterystyczny dźwięk.]] L.hstation_name = "Stacja Lecząca" L.hstation_broken = "Twoja stacja lecząca została zniszczona!" -L.hstation_help = "{primaryfire} kładzie stacje." L.hstation_desc = [[ Pozwala ludzią się leczyć, gdy jest położone. @@ -359,7 +313,6 @@ Rani ludzi, których trafi.]] -- Radio L.radio_broken = "Twoje radio zostało zniszczone!" -L.radio_help_pri = "{primaryfire} kładzie radio." L.radio_desc = [[ Odtwarza dźwięki lub odgłosy. @@ -405,7 +358,7 @@ L.dna_killer = "Zebrano próbkę DNA zabójcy z trupa!" --L.dna_duplicate = "Match! You already have this DNA sample in your scanner." L.dna_no_killer = "Nie można było pobrać DNA (zabójca się rozłączył?)." L.dna_armed = "Ta bomba jest uzbrojona! Rozbrój ją najpierw!" -L.dna_object = "Zebrano {num} nową próbkę DNA z obiektu." +--L.dna_object = "Collected a sample of the last owner from the object." L.dna_gone = "DNA nie zostało znalezione na tym terenie." L.dna_desc = [[ @@ -474,7 +427,7 @@ L.hp_wounded = "Ranny" L.hp_badwnd = "Bardzo ranny" L.hp_death = "Bliski śmierci" --- TargetID karma status +-- TargetID Karma status L.karma_max = "Renomowany" L.karma_high = "Szorski" L.karma_med = "Brutalny" @@ -483,14 +436,12 @@ L.karma_min = "Szaleniec" -- TargetID misc L.corpse = "Zwłoki" -L.corpse_hint = "Kliknij [{usekey}] by zbadać. [{walkkey} + {usekey}], aby je zbadać po cichu." +--L.corpse_hint = "Press [{usekey}] to search and confirm. [{walkkey} + {usekey}] to search covertly." L.target_disg = "przebrany" L.target_unid = "Niezidentyfikowane ciało" --L.target_unknown = "A Terrorist" -L.target_credits = "Przeszukaj, aby otrzymywać niewykorzystane kredyty" - -- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "Jednorazowe" L.tbut_reuse = "Wielorazowe" @@ -505,7 +456,6 @@ L.mute_off = "None muted" -- Spectators and prop possession L.punch_title = "PUNCH-O-METER" -L.punch_help = "Klawisze ruchu lub skok: uderz obiekt. Skradanie: opuść obiekt." L.punch_bonus = "Twój słaby wynik pomniejszył twój limit punch-o-metera o {num}" L.punch_malus = "Twój dobry wynik powiększył twój limit punch-o-metera o {num}!" @@ -676,7 +626,7 @@ L.aw_sui2_title = "Samotny i zdesperowany" L.aw_sui2_text = "był jedynym, który zabił samego siebie." L.aw_exp1_title = "Badania MateriaŁÓw Wybuchowych" -L.aw_exp1_text = "został wyrÓżniony za badania nad eksplozjami. Pomogło mu [num} obiektów." +L.aw_exp1_text = "został wyrÓżniony za badania nad eksplozjami. Pomogło mu {num} obiektów." L.aw_exp2_title = "Badania Terenowe" L.aw_exp2_text = "sprawdził swoją odporność na wybuchy. Nie była wystarczająco wysoka." @@ -930,19 +880,16 @@ L.hud_forced_failed = "Nie udało się wymusić HUD {hudname}. Nie masz permisji L.hud_restricted_failed = "Nie udało się nadać restrykcji HUDa {hudname}. Nie masz permisji." L.shop_role_select = "Wybierz rolę" -L.shop_role_selected = "{roles} wybrano do sklepu!" +L.shop_role_selected = "{role} wybrano do sklepu!" L.shop_search = "Szukaj" -L.spec_help = "Kliknij by obserować gracza, lub kliknij {usekey} na obiekt fizyczny, by go posiąść." -L.spec_help2 = "Aby opuścić tryb widza, naciśnij guzik {helpkey}, idź do 'rozgrywa' i zmień tryb obserwatora." - -- 2019-10-19 L.drop_ammo_prevented = "Coś Cię powstrzymuje przed wyrzuceniem amunicji." -- 2019-10-28 L.target_c4 = "Naciśnij [{usekey}] aby otworzyć menu C4" L.target_c4_armed = "Naciśnij [{usekey}] aby rozborić C4" -L.target_c4_armed_defuser = "Naciśnij [{usekey}] aby rozborić przyżądem" +L.target_c4_armed_defuser = "Naciśnij [{primaryfire}] aby rozborić przyżądem" L.target_c4_not_disarmable = "Nie możesz rozbroić bomby żywego gracza" L.c4_short_desc = "Coś bardzo wybuchowego" @@ -950,16 +897,15 @@ L.target_pickup = "Naciśnij [{usekey}] aby podnieść" L.target_slot_info = "Slot: {slot}" L.target_pickup_weapon = "Naciśnij [{usekey}] aby ponieść broń" L.target_switch_weapon = "Naciśnij [{usekey}] aby zamienić broń" -L.target_pickup_weapon_hidden = ", Naciśnij [{usekey} + {walkkey}] dla ukrytego podniesienia" -L.target_switch_weapon_hidden = ", Naciśnij [{usekey} + {walkkey}] dla ukrytej zamiany" +L.target_pickup_weapon_hidden = ", Naciśnij [{walkkey} + {usekey}] dla ukrytego podniesienia" +L.target_switch_weapon_hidden = ", Naciśnij [{walkkey} + {usekey}] dla ukrytej zamiany" L.target_switch_weapon_nospace = "Brak slotu!" L.target_switch_drop_weapon_info = "Upuszczanie {name} ze slotu {slot}" L.target_switch_drop_weapon_info_noslot = "Broni z tego slotu {slot} nie można wywalić" -L.corpse_searched_by_detective = "Te ciało przeszukał detektyw" +--L.corpse_searched_by_detective = "This corpse was searched by a public policing role" L.corpse_too_far_away = "Ciało jest za daleko." -L.radio_pickup_wrong_team = "Nie możesz mieć radio innego teamu." L.radio_short_desc = "Wystrzały broni są dla mnie muzyką" L.hstation_subtitle = "Naciśnij [{usekey}] aby otrzymać życie." @@ -1004,7 +950,6 @@ L.mute_team = "{team} zmutowani." L.door_auto_closes = "Te drzwi zamykają się same" L.door_open_touch = "Wejdź w drzwi żeby je otworzyć." L.door_open_touch_and_use = "Wejdź w drzwi i naciśnij [{usekey}] aby otworzyć." -L.hud_health = "Zdrowie" -- 2020-03-09 L.help_title = "Pomoc i ustawienia" @@ -1026,7 +971,7 @@ L.menu_guide_description = "Pomaga zapoznać się z TTT i pomóc Ci je zrozumie L.menu_bindings_description = "Ustaw swoje własne bindy dotyczące rozgrywki" L.menu_language_description = "Wybierz język gry" L.menu_appearance_description = "Popraw wygląd i wydajność UI" -L.menu_gameplay_description = "Unikaj ról, bądź widzem itp" +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." L.menu_addons_description = "Konfiguracja addonów" L.menu_legacy_description = "Panle z przekonwertowanymi addonami TTT, powinny być przeportowane do nowego systemu" L.menu_administration_description = "Ustawienia generalne HUDu, sklepów itd." @@ -1050,10 +995,8 @@ L.submenu_appearance_crosshair_title = "Celownik" L.submenu_appearance_dmgindicator_title = "Pow. o obrażeniach" L.submenu_appearance_performance_title = "Wydajność" L.submenu_appearance_interface_title = "Interfejs" -L.submenu_appearance_miscellaneous_title = "Różne" L.submenu_gameplay_general_title = "Główne" -L.submenu_gameplay_avoidroles_title = "Unikaj ról" L.submenu_administration_hud_title = "Ustawienia HUDa" L.submenu_administration_randomshop_title = "Losowy Sklep" @@ -1090,16 +1033,11 @@ L.label_shop_show_slot = "Pokaż sloty" L.label_shop_show_custom = "Pokaż customowe intemy" L.label_shop_show_fav = "Pokaż ulubione itemy" L.label_crosshair_enable = "Włącz celownik" -L.label_crosshair_gap_enable = "Umożliw odstęp" -L.label_crosshair_gap = "Odstęp celownika" L.label_crosshair_opacity = "Ukrycie celownika podczas korzystania z celowniku mechanicznego" L.label_crosshair_ironsight_opacity = "Widoczność celownika z przycelowania" L.label_crosshair_size = "Wielkość celownika" L.label_crosshair_thickness = "Grubość celownika" L.label_crosshair_thickness_outline = "Grubość otoczki celownika" -L.label_crosshair_static_enable = "Umożliw statyczny celownik " -L.label_crosshair_dot_enable = "Umożliw kropkę celownika" -L.label_crosshair_lines_enable = "Zezwól na linie celownika" L.label_crosshair_scale_enable = "Umożliw różne wielkości" L.label_crosshair_ironsight_low_enabled = "Obniż broń podczas użycia celowniku mechanicznego" L.label_damage_indicator_enable = "Enable damage indicator" @@ -1119,13 +1057,10 @@ L.label_gameplay_specmode = "Tryb obserwatora (zawsze bądź obserwatorem)" L.label_gameplay_fastsw = "Szybkie przełącznie broni" L.label_gameplay_hold_aim = "Trzymaj aby celować" L.label_gameplay_mute = "Wycisz żyjących graczy, gdy zginiesz" -L.label_gameplay_dtsprint_enable = "Włącz 2x przód, aby sprintować" -L.label_gameplay_dtsprint_anykey = "Sprintuj dopóki Ci się nie szkończy wytrzymałość" L.label_hud_default = "Zwykły HUD" L.label_hud_force = "Wymuszony HUD" L.label_bind_weaponswitch = "Zmień broń" -L.label_bind_sprint = "Sprint" L.label_bind_voice = "Globalny Czat" L.label_bind_voice_team = "Teamowy Czat" @@ -1149,7 +1084,6 @@ L.header_damage_indicator = "Ustawienia powiadomień obrażeń" L.header_performance_settings = "Ustawienia Wydajności" L.header_interface_settings = "Ustawienia interfejsu" L.header_gameplay_settings = "Ustawienia rozgrywki" -L.header_roleselection = "Włącz przypisywanie ról" L.header_hud_administration = "Wybierz Domyślne i wymuś HUDy" L.header_hud_enabled = "Włącz/Wyłącz HUDy" @@ -1196,11 +1130,7 @@ L.hud_revival_time = "{time}s" L.door_destructible = "Te drzwi da się zniszczyć ({health}PW)" -- 2020-05-28 -L.confirm_detective_only = "Tylko detektywni mogą potwierdzić ciało" -L.inspect_detective_only = "Tylko detektywni mogą sprawdzać ciało" -L.corpse_hint_no_inspect = "Tylko detektywni mogą przeszukać ciało" -L.corpse_hint_inspect_only = "Naciśnij [{usekey}] aby wyszukać. Tylko detektywni mogą potwierdzić ciało." -L.corpse_hint_inspect_only_credits = "Naciśnij [{usekey}] aby otrzymać kredyty. Tylko detektywni mogą przeszukać ciało." +--L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." -- 2020-06-04 L.label_bind_disguiser = "Przełącz przebranie" @@ -1215,7 +1145,6 @@ L.binoc_help_sec = "Zmień Przybliżenie." L.vis_help_pri = "Wyrzuć aktywne urządzenie." -L.decoy_help_pri = "Rozstaw Wabik." -- 2020-08-07 L.pickup_error_spec = "Nie możesz tego zrobić jako widz." @@ -1226,7 +1155,7 @@ L.pickup_error_noslot = "Nie możesz tego podnieść, bo nie masz na to miejsca. L.lang_server_default = "Ustawienie Serwera" --L.help_lang_info = [[ --This translation is {coverage}% complete with the English language taken as a default reference. - +-- --Keep in mind that these translations are made by the community. Feel free to contribute if something is missing or incorrect.]] -- 2021-04-13 @@ -1421,7 +1350,7 @@ L.none = "Brak Roli" --L.spawneditor_place = "Place spawn" --L.spawneditor_remove = "Remove spawn" --L.spawneditor_change = "Change spawn type (hold [SHIFT] to reverse)" ---L.spawneditor_ammo_edit = "Hold to edit amount of autospawning ammo on weapon spawns" +--L.spawneditor_ammo_edit = "Hold on weapon spawn to edit autospawning ammo" --L.spawn_weapon_random = "Random Weapon Spawn" --L.spawn_weapon_melee = "Melee Weapon Spawn" @@ -1439,7 +1368,7 @@ L.none = "Brak Roli" --L.spawn_ammo_shotgun = "Shotgun ammo spawn" --L.spawn_player_random = "Random player spawn" ---L.spawn_weapon_ammo = " (Ammo: {ammo})" +--L.spawn_weapon_ammo = "(Ammo: {ammo})" --L.spawn_weapon_edit_ammo = "Hold [{walkkey}] and press [{primaryfire} or {secondaryfire}] to increase or decrease the ammo for this weapon spawn" @@ -1461,18 +1390,18 @@ L.none = "Brak Roli" --L.help_spawn_editor_info = [[ --The spawn editor is used to place, remove and edit spawns in the world. These spawns are for weapons, ammunition and players. - +-- --These spawns are saved in files located in 'data/ttt/weaponspawnscripts/'. They can be deleted for a hard reset. The initial spawn files are created from spawns found on the map and in the original TTT weapon spawn scripts. Pressing the reset button always reverts to the initial state. - +-- --It should be noted that this spawn system uses dynamic spawns. This is most interesting for weapons because it no longer defines a specific weapon, but a type of weapons. For example instead of a TTT shotgun spawn, there is now a general shotgun spawn where any weapon defined as shotgun can spawn. The spawn type for each weapon can be set in the 'Edit Equipment' menu. This makes it possible for any weapon to spawn on the map, or to disable certain default weapons. - +-- --Keep in mind that many changes only take effect after a new round has started.]] --L.help_spawn_editor_enable = "On some maps it might be advised to use the original spawns found on the map without replacing them with the dynamic system. Changing this option below only affects the currently active map, so the dynamic system will still be used for every other map." --L.help_spawn_editor_hint = "Hint: To leave the spawn editor, reopen the gamemode menu." --L.help_spawn_editor_spawn_amount = [[ --There currently are {weapon} weapon spawns, {ammo} ammunition spawns and {player} player spawns on this map. --Click 'start spawn edit' to change this amount. - +-- --{weaponrandom}x Random weapon spawn --{weaponmelee}x Melee weapon spawn --{weaponnade}x Grenade weapon spawn @@ -1481,21 +1410,21 @@ L.none = "Brak Roli" --{weaponsniper}x Sniper weapon spawn --{weaponpistol}x Pistol weapon spawn --{weaponspecial}x Special weapon spawn - +-- --{ammorandom}x Random ammo spawn --{ammodeagle}x Deagle ammo spawn --{ammopistol}x Pistol ammo spawn --{ammomac10}x Mac10 ammo spawn --{ammorifle}x Rifle ammo spawn --{ammoshotgun}x Shotgun ammo spawn - +-- --{playerrandom}x Random player spawn]] --L.equipmenteditor_name_auto_spawnable = "Equipment spawns randomly in world" --L.equipmenteditor_name_spawn_type = "Select spawn type" --L.equipmenteditor_desc_auto_spawnable = [[ --The TTT2 spawn system allows every weapon to spawn in the world. By default only weapons marked as 'AutoSpawnable' by the creator will spawn in the world, however this can be changed from within this menu. - +-- --Most of the equipment is set to 'special weapon spawns' by default. This means that equipment only spawns on random weapon spawns. However it is possible to place special weapon spawns in the world or change the spawn type here to use other existing spawn types.]] --L.pickup_error_inv_cached = "You cannot pick this up right now because your inventory is cached." @@ -1512,14 +1441,14 @@ L.none = "Brak Roli" --L.help_prefer_map_models = [[ --Some maps define their own player models. By default these models have a higher priority than those that are assigned automatically. By disabling this setting, map specific models are disabled. - +-- --Role specific models always have a higher priority and are unaffected by this setting.]] --L.help_enforce_playermodel = [[ --Some roles have custom player models. They can be disabled which can be relevant for compatibility with some player model selectors. --Random default models can still be selected, if this setting is disabled.]] --L.help_use_custom_models = [[ --By default only the CS:S Phoenix player model is assigned to all players. By enabling this option however it is possible to select a player model pool. With this setting enabled each player will still be assigned the same player model, however it is a random model from the defined model pool. - +-- --This selection of models can be extended by installing more player models.]] -- 2021-10-06 @@ -1536,7 +1465,7 @@ L.none = "Brak Roli" -- 2021-10-09 --L.help_models_select = [[ --Left click on the models to add them to the player model pool. Left click again to remove them. Right clicking toggles between enabled and disabled detective hats for the focused model. - +-- --The small indicator in the top left shows if the player model has a head hitbox. The icon below shows if this model is applicable for a detective hat.]] --L.menu_roles_title = "Role Settings" @@ -1559,7 +1488,7 @@ L.none = "Brak Roli" --L.help_roles_selection = [[ --The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role. This also means that at least 5 players are needed for this role to be distributed at all. --Keep in mind that all of this only applies if the role is considered for distribution process. - +-- --The aforementioned role distribution has a special integration with the lower limit of players. If the role is considered for distribution and the minimum value is below the value given by the distribution factor, but the amount of players is equal or greater than the lower limit, a single player can still receive this role. The distribution process then works as usual for the second player.]] --L.help_roles_award_info = "Some roles (if enabled in their credits settings) receive equipment credits if a certain percentage of enemies has died. Related values can be tweaked here." --L.help_roles_award_pct = "When this percentage of enemies are dead, specific roles are awarded equipment credits." @@ -1567,17 +1496,17 @@ L.none = "Brak Roli" --L.help_roles_advanced_warning = "WARNING: These are advanced settings that can completely mess up the role distribution process. When in doubt keep all values at '0'. This value means that no limits are applied and the role distribution will try to assign as many roles as possible." --L.help_roles_max_roles = [[ --The term roles here includes both the base roles and the subroles. By default, there is no limit on how many different roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.help_roles_max_baseroles = [[ --Base roles are only those roles others inherit from. For example, the Innocent role is a base role, while a Pharaoh is a subrole of this role. By default, there is no limit on how many different base roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.label_roles_enabled = "Enable role" @@ -1609,15 +1538,15 @@ L.none = "Brak Roli" --L.help_roles_credits_award_kill = "Another way of gaining credits is by killing high value players with a 'public role' such as a Detective. If the killer's role has this enabled, they gain the below defined amount of credits." --L.help_roles_credits_award = [[ --There are two different ways to be awarded credits in base TTT2: - +-- --1. If a certain percentage of the enemy team is dead, the whole team is awarded credits. --2. If a player killed a high value player with a 'public role' such as a Detective, the killer is awarded credits. - +-- --Please note, that this still can be enabled/disabled for every role, even if the whole team is awarded. For example, if team Innocent is awarded, but the Innocent role has this disabled, only the Detective will receive their credits. --The balancing values for this feature can be set in 'Administration' -> 'General Role Settings'.]] --L.help_detective_hats = [[ --Policing roles such as the Detective may wear hats to show their authority. They lose them on death or if damaged at the head. - +-- --Some player models do not support hats by default. This can be changed in 'Administration' -> 'Player Models']] --L.label_roles_credits_award_kill = "Credit reward amount for the kill" @@ -1669,35 +1598,35 @@ L.none = "Brak Roli" --L.help_idle = "The idle mode is used to forcefully move idle players into the spectator mode. To leave this mode, they will have to disable it in their 'gameplay' menu." --L.help_namechange_kick = [[ --A name change during an active round could be abused. Therefore, this is prohibited by default and will lead to the offending player being kicked from the server. - +-- --If the bantime is greater than 0, the player will be unable to reconnect to the server until that time has passed.]] --L.help_damage_log = "Each time a player is damaged, a damage log entry is added to the console if enabled. This can also be stored to disk after a round has ended. The file is located at 'data/terrortown/logs/'" --L.help_spawn_waves = [[ --If this variable is set to 0, all players are spawned at once. For servers with huge amounts of players, it can be beneficial to spawn the players in waves. The spawn wave interval is the time between each spawn wave. A spawn wave always spawns as many players as there are valid spawn points. - +-- --Note: Make sure that the preparing time is long enough for the desired amount of spawn waves.]] --L.help_voicechat_battery = [[ --Voice chatting with enabled voice chat battery reduces battery charge. When it's empty, the player can't use voice chat and has to wait for it to recharge. This can help to prevent excessive voice chat usage. - +-- --Note: 'Tick' refers to a game tick. For example, if the tick rate is set to 66, then it will be 1/66th of a second.]] --L.help_ply_spawn = "Player settings that are used on player (re-)spawn." --L.help_haste_mode = [[ --Haste mode balances the game by increasing the round time with every dead player. Only roles that see missing in action players can see the real round time. Every other role can only see the haste mode starting time. - +-- --If haste mode is enabled, the fixed round time is ignored.]] --L.help_round_limit = "After one of the set limit conditions is met, a map change is triggered." --L.help_armor_balancing = "The following values can be used to balance the armor." --L.help_item_armor_classic = "If classic armor mode is enabled, only the previous settings matter. Classic armor mode means that a player can only buy armor once in a round, and that this armor blocks 30% of the incoming bullet and crowbar damage until they die." --L.help_item_armor_dynamic = [[ --Dynamic armor is the TTT2 approach to make armor more interesting. The amount of armor that can be bought is now unlimited, and the armor value stacks. Getting damaged decreases the armor value. The armor value per bought armor item is set in the 'Equipment Settings' of said item. - +-- --When taking damage, a certain percentage of this damage is converted into armor damage, a different percentage is still applied to the player and the rest vanishes. - +-- --If reinforced armor is enabled, the damage applied to the player is decreased by 15% as long as the armor value is above the reinforcement threshold.]] --L.help_sherlock_mode = "The sherlock mode is the classic TTT mode. If the sherlock mode is disabled, dead bodies can not be confirmed, the scoreboard shows everyone as alive and the spectators can talk to the living players." --L.help_prop_possession = [[ --Prop possession can be used by spectators to possess props lying in the world and use the slowly recharging 'punch-o-meter' to move said prop around. - +-- --The maximum value of the 'punch-o-meter' consists of a possession base value, where the kills/deaths difference clamped inbetween two defined limits is added. The meter slowly recharges over time. The set recharge time is the time needed to recharge a single point in the 'punch-o-meter'.]] --L.help_karma = "Players start with a certain amount of Karma, and lose it when they damage/kill teammates. The amount they lose is dependent on the Karma of the person they hurt or killed. Lower Karma reduces damage given." --L.help_karma_strict = "If strict Karma is enabled, the damage penalty increases more quickly as Karma goes down. When it is off, the damage penalty is very low when people stay above 800. Enabling strict mode makes Karma play a larger role in discouraging any unnecessary kills, while disabling it results in a more “loose” game where Karma only hurts players who constantly kill teammates." @@ -1707,7 +1636,7 @@ L.none = "Brak Roli" --L.help_karma_bonus = "There are also two different passive ways to gain Karma during a round. First is a karma restoration which applied to every player at the round end. Then a secondary clean round bonus is given if no teammates were hurt or killed by a player." --L.help_karma_clean_half = [[ --When a player's Karma is above the starting level (meaning the Karma max has been configured to be higher than that), all their Karma increases will be reduced based on how far their Karma is above that starting level. So it goes up slower the higher it is. - +-- --This reduction goes in a curve of exponential decay: initially it's fast, and it slows down as the increment gets smaller. This convar sets at what point the bonus has been halved (so the half-life). With the default value of 0.25, if the starting amount of Karma is 1000 and the max 1500, and a player has Karma 1125 ((1500 - 1000) * 0.25 = 125), then his clean round bonus will be 30 / 2 = 15. So to make the bonus go down faster you’d set this convar lower, to make it go down slower you’d increase it towards 1.]] --L.help_max_slots = "Sets the maximum amount of weapons per slot. '-1' means that there is no limit." --L.help_item_armor_value = "This is the armor value given by the armor item in dynamic mode. If classic mode is enabled (see 'Administration' -> 'Player Settings') then every value greater than 0 is counted as existing armor." @@ -1730,8 +1659,6 @@ L.none = "Brak Roli" --L.label_tbutton_admin_show = "Show traitor buttons to admins" --L.label_ragdoll_carrying = "Enable ragdoll carrying" --L.label_prop_throwing = "Enable prop throwing" ---L.label_ragdoll_pinning = "Enable ragdoll pinning for non-Innocent roles" ---L.label_ragdoll_pinning_innocents = "Enable ragdoll pinning for Innocent roles" --L.label_weapon_carrying = "Enable weapon carrying" --L.label_weapon_carrying_range = "Weapon carry range" --L.label_prop_carrying_force = "Prop pickup force" @@ -1761,10 +1688,8 @@ L.none = "Brak Roli" --L.label_spectator_chat = "Enable spectators chatting with everybody" --L.label_lastwords_chatprint = "Print last words to chat if killed while typing" --L.label_identify_body_woconfirm = "Identify corpse without pressing the 'confirm' button" ---L.label_announce_body_found = "Announce that a body was found" +--L.label_announce_body_found = "Announce that a body was found when the body was confirmed" --L.label_confirm_killlist = "Announce kill list of confirmed corpse" ---L.label_inspect_detective_only = "Limit corpse search to policing roles only" ---L.label_confirm_detective_only = "Limit corpse confirmation to policing roles only" --L.label_dyingshot = "Shoot on death if in ironsights [experimental]" --L.label_armor_block_headshots = "Enable armor blocking headshots" --L.label_armor_block_blastdmg = "Enable armor blocking blast damage" @@ -1823,7 +1748,6 @@ L.none = "Brak Roli" --L.label_sprint_max = "Max sprinting stamina" --L.label_sprint_stamina_consumption = "Stamina consumption factor" --L.label_sprint_stamina_regeneration = "Stamina regeneration factor" ---L.label_sprint_crosshair = "Show crosshair while sprinting" --L.label_crowbar_unlocks = "Primary attack can be used as interaction (i.e. unlocking)" --L.label_crowbar_pushforce = "Crowbar push force" @@ -1836,7 +1760,7 @@ L.none = "Brak Roli" --L.help_falldmg_exponent = [[ --This value modifies how exponentially fall damage is increased with the speed the player hits the ground at. - +-- --Take care when altering this value. Setting it too high can make even the smallest falls lethal, while setting it too low will allow players to fall from extreme heights and suffer little to no damage.]] -- 2023-02-08 @@ -1859,4 +1783,411 @@ L.none = "Brak Roli" --L.sb_rank_tooltip_heroes = "TTT2 Heroes" --L.sb_rank_tooltip_team = "Team" ---L.tbut_adminarea = "ADMIN AREA:" \ No newline at end of file +--L.tbut_adminarea = "ADMIN AREA:" + +-- 2023-08-10 +--L.equipmenteditor_name_damage_scaling = "Damage Scaling" + +-- 2023-08-11 +--L.equipmenteditor_name_allow_drop = "Allow Drop" +--L.equipmenteditor_desc_allow_drop = "If enabled, the equipment can be dropped freely by the player." + +--L.equipmenteditor_name_drop_on_death_type = "Drop on Death" +--L.equipmenteditor_desc_drop_on_death_type = "Attempt overriding the action taken for whether the equipment is dropped on player's death." + +--L.drop_on_death_type_default = "Default (weapon-defined)" +--L.drop_on_death_type_force = "Force Drop on Death" +--L.drop_on_death_type_deny = "Deny Drop on Death" + +-- 2023-08-26 +--L.equipmenteditor_name_kind = "Equipment Slot" +--L.equipmenteditor_desc_kind = "The inventory slot the equipment will occupy." + +--L.slot_weapon_melee = "Melee Slot" +--L.slot_weapon_pistol = "Pistol Slot" +--L.slot_weapon_heavy = "Heavy Slot" +--L.slot_weapon_nade = "Grenade Slot" +--L.slot_weapon_carry = "Carry Slot" +--L.slot_weapon_unarmed = "Unarmed Slot" +--L.slot_weapon_special = "Special Slot" +--L.slot_weapon_extra = "Extra Slot" +--L.slot_weapon_class = "Class Slot" + +-- 2023-10-04 +--L.label_voice_duck_spectator = "Duck spectator voices" +--L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +--L.label_voice_scaling = "Voice Volume Scaling Mode" +--L.label_voice_scaling_mode_linear = "Linear" +--L.label_voice_scaling_mode_power4 = "Power 4" +--L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "Rezultaty przeszukania ciała - {player}" +L.search_info = "Informacje" +L.search_confirm = "Potwierdź śmierć" +--L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +--L.search_take_credits = "Take {credits} Credit(s)" +--L.search_confirm_forbidden = "Confirm forbidden" +--L.search_confirmed = "Death Confirmed" +--L.search_call = "Report Death" +--L.search_called = "Death Reported" + +--L.search_team_role_unknown = "???" + +L.search_words = "Coś ci mówi, że jego ostanie słowa to: '{lastwords}'" +L.search_armor = "On nosił niestandardową kamizelkę kuloodporną." +L.search_disguiser = "Trzymał urządzenie, które mogło ukryć jego tożsamość." +L.search_radar = "Miał jakiegoś rodzaju radar. Już nie działa." +L.search_c4 = "W kieszeni znalazłeś notkę. Stwierdza, że przecięcie przewodu {num} bezpiecznie rozbroi bombę." + +L.search_dmg_crush = "Wiele z jego kości jest połamanych. To pokazuje, że zabił go jakiś duży obiekt." +L.search_dmg_bullet = "Jest oczywiste, że został zastrzelony na śmierć." +L.search_dmg_fall = "Spadł i połamał sobie kark." +L.search_dmg_boom = "Stan jego ubrania pokazują, że wybuch zakończył jego żywot." +L.search_dmg_club = "Ciało jest posiniaczone i poobijane. Najwyraźniej został zatłuczony na śmierć." +L.search_dmg_drown = "Ciało wykazuje, że delikwent utonął." +L.search_dmg_stab = "Został dźgnięty nożem, zanim szybko się wykrwawił." +L.search_dmg_burn = "Pachnie jak pieczony terrorysta..." +L.search_dmg_teleport = "Jego DNA zostało uszkodzone przez fale tachionu." +L.search_dmg_car = "Gdy ten terrorysta przechodził przez drogę, został przejechany przez lekkomyślnego kierowce." +L.search_dmg_other = "Nie możesz znaleźć konkretnej przyczyny śmierci tego terrorysty." + +--L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +--L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +--L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +--L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +--L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +--L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +--L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +--L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +--L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +--L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +--L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +--L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +--L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +--L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +--L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +--L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +--L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +--L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +--L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +--L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +--L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +--L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +--L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +--L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +--L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "Wygląda na to, że {weapon} posłużyła do tego zabójstwa." +L.search_head = "Rana śmiertelna była strzałem w głowę. Nie miał czasu, aby krzyczeć." +--L.search_time = "They died a while before you conducted the search." +--L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "Znalazłeś listę zabójstw, co potwierdza śmierć {player}." +L.search_kills2 = "Znalazłeś listę zabójstw z tymi imionami: {player}" +L.search_eyes = "Używając umiejętności detektywa, zidentyfikowałeś ostatnią osobę, która go widziała: {player}. Morderca, czy zbieg okoliczności?" + +--L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +--L.search_kill_distance_point_blank = "It was a point blank attack." +--L.search_kill_distance_close = "The attack came from a short distance." +--L.search_kill_distance_far = "The victim was attacked from a long distance away." + +--L.search_kill_from_front = "The victim was shot from the front." +--L.search_kill_from_back = "The victim was shot from behind." +--L.search_kill_from_side = "The victim was shot from the side." + +--L.search_hitgroup_head = "The projectile was found in their head." +--L.search_hitgroup_chest = "The projectile was found in their chest." +--L.search_hitgroup_stomach = "The projectile was found in their stomach." +--L.search_hitgroup_rightarm = "The projectile was found in their right arm." +--L.search_hitgroup_leftarm = "The projectile was found in their left arm." +--L.search_hitgroup_rightleg = "The projectile was found in their right leg." +--L.search_hitgroup_leftleg = "The projectile was found in their left leg." +--L.search_hitgroup_gear = "The projectile was found in their hip." + +--L.search_policingrole_report_confirm = [[ +--A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +--L.search_policingrole_confirm_disabled_1 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +--L.search_policingrole_confirm_disabled_2 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know! +--You can see the information in here after they confirmed it.]] +--L.search_spec = [[ +--As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +--L.search_title_words = "Victim's last words" +--L.search_title_c4 = "Defusion mishap" +--L.search_title_dmg_crush = "Crush damage ({amount} HP)" +--L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +--L.search_title_dmg_fall = "Fall damage ({amount} HP)" +--L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +--L.search_title_dmg_club = "Club damage ({amount} HP)" +--L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +--L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +--L.search_title_dmg_burn = "Burning damage ({amount} HP)" +--L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +--L.search_title_dmg_car = "Car accident ({amount} HP)" +--L.search_title_dmg_other = "Unknown damage ({amount} HP)" +--L.search_title_time = "Death time" +--L.search_title_dna = "DNA sample decay" +--L.search_title_kills = "The victim's kill list" +--L.search_title_eyes = "The killer's shadow" +--L.search_title_floor = "Floor of the crime scene" +--L.search_title_credits = "{credits} Equipment credit(s)" +--L.search_title_water = "Water level {level}" +--L.search_title_policingrole_report_confirm = "Confirm to report death" +--L.search_title_policingrole_confirm_disabled = "Report corpse" +--L.search_title_spectator = "You are a spectator" + +--L.target_credits_on_confirm = "Confirm to receive unspent credits" +--L.target_credits_on_search = "Search to receive unspent credits" +--L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +--L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +--L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +--L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +--L.label_inspect_confirm_mode = "Select body search mode" +--L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +--L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +--L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +--L.help_inspect_confirm_mode = [[ +--There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. +-- +--mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. +-- +--mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. +-- +--mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +--L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +--L.header_hud_elements_customize = "Customize the HUD-Elements" +--L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +--L.help_keyhelp = [[ +--Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: +-- +--Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +--Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +--Equipment: Some equipment items have their own bindings, these are shown in this category. +-- +--Disabled categories are still shown when the scoreboard is visible]] + +--L.label_keyhelp_show_core = "Enable always showing the core bindings" +--L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +--L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +--L.header_interface_keys = "Key helper settings" +--L.header_interface_wepswitch = "Weapon switch UI settings" + +--L.label_keyhelper_help = "open gamemode menu" +--L.label_keyhelper_mutespec = "cycle spectator voice mode" +--L.label_keyhelper_shop = "open equipment shop" +--L.label_keyhelper_show_pointer = "free mouse pointer" +--L.label_keyhelper_possess_focus_entity = "possess focused entity" +--L.label_keyhelper_spec_focus_player = "spectate focused player" +--L.label_keyhelper_spec_previous_player = "previous player" +--L.label_keyhelper_spec_next_player = "next player" +--L.label_keyhelper_spec_player = "spectate random player" +--L.label_keyhelper_possession_jump = "prop: jump" +--L.label_keyhelper_possession_left = "prop: left" +--L.label_keyhelper_possession_right = "prop: right" +--L.label_keyhelper_possession_forward = "prop: forward" +--L.label_keyhelper_possession_backward = "prop: backward" +--L.label_keyhelper_free_roam = "leave object and roam free" +--L.label_keyhelper_flashlight = "toggle flashlight" +--L.label_keyhelper_quickchat = "open quickchat" +--L.label_keyhelper_voice_global = "global voice chat" +--L.label_keyhelper_voice_team = "team voice chat" +--L.label_keyhelper_chat_global = "global chat" +--L.label_keyhelper_chat_team = "team chat" +--L.label_keyhelper_show_all = "show all" +--L.label_keyhelper_disguiser = "toggle disguiser" +--L.label_keyhelper_save_exit = "save and exit" +--L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +--L.item_armor_reinforced = "Reinforced Armor" +--L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +--L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +--L.status_speed_name = "Speed Multiplier" +--L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +--L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +--L.status_on = "on" +--L.status_off = "off" + +--L.crowbar_help_primary = "Attack" +--L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +--L.help_HUD_enable_description = [[ +--Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +--L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +--L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +--L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +--L.header_soundeffect_settings = "Sound Effects" +--L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +--L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +--L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +--L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +--L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +--L.help_prop_spec_dash = [[ +--Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. +-- +--This variable is a multiplier of the push force.]] +--L.label_spec_prop_dash = "Dash force multiplier" +--L.label_keyhelper_possession_dash = "prop: dash in view direction" +--L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +--L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +--L.c4_help_primary = "Place the C4" +--L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +--L.magneto_help_primary = "Push entity" +--L.magneto_help_secondary = "Pull / pickup entity" +--L.knife_help_primary = "Stab" +--L.knife_help_secondary = "Throw knife" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +--L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +--L.newton_force = "FORCE" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +--L.beacon_help_pri = "Throw Beacon on the ground" +--L.beacon_help_sec = "Stick Beacon to surface" +--L.beacon_name = "Beacon" +--L.beacon_desc = [[ +--Broadcasts player locations to everyone in a sphere around this beacon. +-- +--Use to keep track of locations on the map that are hard to see.]] + +--L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +--L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +--L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +--L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +--L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder} potwierdził śmierć {victim}." +--L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +--L.builtin_marker = "Built-in." +--L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +--L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +--L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +--L.submenu_gameplay_accessibility_title = "Accessibility" + +--L.header_accessibility_settings = "Accessibility Settings" + +--L.label_enable_dynamic_fov = "Enable dynamic FOV change" +--L.label_enable_bobbing = "Enable view bobbing" +--L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +--L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +--L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +--L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +--For a shotgun, this would affect each pellet. +--For a rifle, this would affect just the bullet. +--For the poltergeist, this would affect each "thump" and the final explosion. +-- +--0.5 = Deal half the amount of damage. +--2 = Deal twice the amount of damage. +-- +--Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +--L.binoc_help_reload = "Clear target." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +--L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +--L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +--L.label_crosshair_mode = "Crosshair mode" +--L.label_crosshair_static_length = "Enable static crosshair line length" + +--L.choice_crosshair_mode_0 = "Lines and dot" +--L.choice_crosshair_mode_1 = "Lines only" +--L.choice_crosshair_mode_2 = "Dot only" + +--L.help_crosshair_scale_enable = [[ +--Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. +-- +--If the line length is kept static, only the gap scales with cone changes.]] + +--L.header_weapon_settings = "Weapon Settings" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +L.decoy_help_primary = "Rozstaw Wabik" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/pt_br.lua b/lua/terrortown/lang/pt_br.lua index 4a2d84865..fe54adc35 100644 --- a/lua/terrortown/lang/pt_br.lua +++ b/lua/terrortown/lang/pt_br.lua @@ -60,8 +60,6 @@ L.body_found_traitor = "Ele(a) era um(a) Traidor(a)!" L.body_found_det = "Ele(a) era um(a) Detetive." L.body_found_inno = "Ele(a) era um(a) Inocente." -L.body_confirm = "{finder} confirmou a morte de {victim}." - L.body_call = "{player} chamou o Detetive para o corpo de {victim}!" L.body_call_error = "Você deve confirmar a morte deste jogador antes de chamar um Detetive!" @@ -172,46 +170,6 @@ L.quick_disg = "alguém disfarçado" L.quick_corpse = "um corpo não identificado" L.quick_corpse_id = "o cadáver de {player}" --- Body search window -L.search_title = "Resultados de investigação corporal" -L.search_info = "Informação" -L.search_confirm = "Confirmar Morte" -L.search_call = "Chamar Detetive" - --- Descriptions of pieces of information found -L.search_nick = "Este é o corpo de {player}." - -L.search_role_traitor = "Este jogador era um Traidor!" -L.search_role_det = "Este jogador era um Detetive." -L.search_role_inno = "Este jogador era um terrorista inocente." - -L.search_words = "Algo lhe diz que algumas das últimas palavras desta pessoa foram: '{lastwords}'" -L.search_armor = "Ele estava vestindo um colete balístico atípico." -L.search_disg = "Ele estava carregando um dispositivo que podia ocultar sua identidade." -L.search_radar = "Ele estava carregando algum tipo de radar. Não está mais funcionando." -L.search_c4 = "Você encontrou uma nota em um bolso. Ela diz que ao cortar o fio {num}, a bomba será desarmada com segurança." - -L.search_dmg_crush = "A maioria dos ossos dele estão quebrados. Parece que ele foi esmagado por algo pesado." -L.search_dmg_bullet = "É óbvio que ele foi baleado." -L.search_dmg_fall = "Ele caiu para sua morte." -L.search_dmg_boom = "Seus ferimentos e roupas rasgadas indicam que uma explosão o matou." -L.search_dmg_club = "Este corpo está muito ferido. Claramente ele foi espancado até a morte." -L.search_dmg_drown = "O corpo revela sinais de afogamento." -L.search_dmg_stab = "Ele foi esfaqueado e cortado antes de rapidamente sangrar até a morte." -L.search_dmg_burn = "Cheiro de terrorista assado por aqui..." -L.search_dmg_tele = "Parece que a amostra de DNA dele foi embaraçada por emissões de táquion!" -L.search_dmg_car = "Quando este terrorista atravessou a estrada, acabou sendo atropelado por um motorista com a CNH vencida." -L.search_dmg_other = "Você não pôde encontrar uma causa específica da morte deste terrorista." - -L.search_weapon = "Aparentemente, um(a) {weapon} foi usado(a) para matá-lo." -L.search_head = "O disparo foi direto na cabeça. A vítima não teve tempo para gritar." -L.search_time = "Ele morreu aproximadamente após {time} antes de seu corpo ser encontrado." -L.search_dna = "Recupere uma amostra do DNA do assassino utilizando um Scanner de DNA. A amostra de DNA sumirá em aproximadamente {time} a partir de agora." - -L.search_kills1 = "Você encontrou uma lista de assassinatos que comprovam a morte de {player}." -L.search_kills2 = "Você encontrou uma lista de assassinatos com estes nomes:" -L.search_eyes = "Usando suas técnicas de detetive, você identificou a última pessoa que ele viu: {player}. O assassino, ou uma coincidência?" - -- Scoreboard L.sb_playing = "Você está jogando em..." L.sb_mapchange = "O mapa será mudado em {num} rodadas ou em {time}" @@ -250,8 +208,7 @@ L.item_weapon = "Arma" L.item_armor = "Colete Balístico" L.item_armor_desc = [[ -Reduz o dano das balas em 30% -quando você é atingido. +Reduz o dano das balas em 30% quando você é atingido. Equipamento padrão de Detetives.]] @@ -259,19 +216,15 @@ L.item_radar = "Radar" L.item_radar_desc = [[ Permite varrer sinais vitais. -Varre automaticamente assim que você o compra. -Configure-o na aba Radar deste menu.]] +Varre automaticamente assim que você o compra. Configure-o na aba Radar deste menu.]] L.item_disg = "Disfarce" L.item_disg_desc = [[ -Oculta sua identidade enquanto habilitado. Também -evita ser a última pessoa vista por uma vítima. +Oculta sua identidade enquanto habilitado. Também evita ser a última pessoa vista por uma vítima. -Habilite-o na aba Disfarce deste menu -ou aperte a tecla Enter do teclado numérico.]] +Habilite-o na aba Disfarce deste menu ou aperte a tecla Enter do teclado numérico.]] -- C4 -L.c4_hint = "Pressione {usekey} para armar ou desarmar." L.c4_disarm_warn = "Um explosivo C4 que você plantou foi desarmado." L.c4_armed = "Você armou a bomba com sucesso." L.c4_disarmed = "Você desarmou a bomba com sucesso." @@ -292,6 +245,7 @@ L.c4_remove_destroy2 = "onfirmar: destruir" L.c4_disarm = "Desarmar C4" L.c4_disarm_cut = "Clique para cortar o fio {num}" +L.c4_disarm_t = "Corte um fio para desarmar a bomba. Como você é um Traidor, todos os fios são seguros. Para os Inocentes, porém, não é tão fácil assim!" L.c4_disarm_owned = "Corte um fio para desarmar a bomba. É a sua bomba, então todos os fios a desarmarão." L.c4_disarm_other = "Corte um fio seguro para desarmar a bomba. Vai explodir se você errar!" @@ -300,45 +254,35 @@ L.c4_status_disarmed = "DESARMADO" -- Visualizer L.vis_name = "Visualizador" -L.vis_hint = "Pressione {usekey} para pegar (somente Detetives)." L.vis_desc = [[ Permite visualizar uma cena de crime. -Analisa um cadáver para mostrar como -a vítima morreu, mas somente se -ela tiver morrido por ferimentos de armas de fogo.]] +Analisa um cadáver para mostrar como a vítima morreu, mas somente se ela tiver morrido por ferimentos de armas de fogo.]] -- Decoy L.decoy_name = "Isca" -L.decoy_no_room = "Você não pode pegar esta Isca." L.decoy_broken = "Sua Isca foi destruída!" L.decoy_short_desc = "Está isca, mostrará um radar falso para o outro time." L.decoy_pickup_wrong_team = "Você não pode pegar está isca, pertence ao time diferente!" L.decoy_desc = [[ -Mostra um sinal de radar falso para Detetives, -e faz os scanners de DNA deles indicar a -localização da sua Isca se eles procurarem -a amostra do seu DNA.]] +Mostra um sinal de radar falso para Detetives, e faz os scanners de DNA deles indicar a localização da sua Isca se eles procurarem a amostra do seu DNA.]] -- Defuser L.defuser_name = "Kit de Desarme" -L.defuser_help = "{primaryfire} desarma um C4 que está sob sua mira." L.defuser_desc = [[ Instantaneamente desarma um explosivo C4. -Usos ilimitados. Um C4 será mais fácil de -ser notado se você estiver com isto equipado.]] +Usos ilimitados. Um C4 será mais fácil de ser notado se você estiver com isto equipado.]] -- Flare gun L.flare_name = "Pistola Sinalizadora" L.flare_desc = [[ -Pode ser usado para queimar cadáveres para que -eles nunca sejam encontrados. Munição limitada. +Pode ser usado para queimar cadáveres para que eles nunca sejam encontrados. Munição limitada. Queimar um cadáver emite um som estranho.]] @@ -346,51 +290,40 @@ Queimar um cadáver emite um som estranho.]] L.hstation_name = "Estação de Cura" L.hstation_broken = "Sua Estação de Cura foi destruída!" -L.hstation_help = "{primaryfire} posiciona a Estação de Cura." L.hstation_desc = [[ Permite que as pessoas se curem quando posicionada. -Seu tempo de recarga é lento. Qualquer um pode -usá-la, e ela pode ser danificada. Pode ser usada -para analisar o DNA de seus utilizadores.]] +Seu tempo de recarga é lento. Qualquer um pode usá-la, e ela pode ser danificada. Pode ser usada para analisar o DNA de seus utilizadores.]] -- Knife L.knife_name = "Faca" L.knife_thrown = "Faca arremessada" L.knife_desc = [[ -Mata o alvo instantaneamente e de forma silenciosa, -mas só pode ser usada uma vez. +Mata o alvo instantaneamente e de forma silenciosa, mas só pode ser usada uma vez. -Pode ser arremessada ao usar -o botão de ataque alternativo.]] +Pode ser arremessada ao usar o botão de ataque alternativo.]] -- Poltergeist L.polter_desc = [[ -Planta batedores em objetos para empurrar pessoas -à sua volta de maneira violenta. +Planta batedores em objetos para empurrar pessoas à sua volta de maneira violenta. -A energia causa dano em pessoas -que estejam nas proximidades.]] +A energia causa dano em pessoas que estejam nas proximidades.]] -- Radio L.radio_broken = "Seu Rádio foi destruído!" -L.radio_help_pri = "{primaryfire} posiciona o Rádio." L.radio_desc = [[ Reproduz sons para distrair e/ou enganar. -Posicione o Rádio em algum lugar, e então -reproduza os sons nele utilizando a aba Rádio -deste menu.]] +Posicione o Rádio em algum lugar, e então reproduza os sons nele utilizando a aba Rádio deste menu.]] -- Silenced pistol L.sipistol_name = "Pistola Silenciada" L.sipistol_desc = [[ -Pistola de baixo ruído, usa munição -de pistola normal. +Pistola de baixo ruído, usa munição de pistola normal. As vítimas não gritarão quando forem mortas.]] @@ -406,16 +339,13 @@ Sua munição é infinita, mas dispara lentamente.]] L.binoc_name = "Binóculos" L.binoc_desc = [[ -Permite dar zoom em cadáveres para identificá-los -a partir de uma longa distância. +Permite dar zoom em cadáveres para identificá-los a partir de uma longa distância. -Seus usos são ilimitados, porém o processo -de identificação demora alguns segundos.]] +Seus usos são ilimitados, porém o processo de identificação demora alguns segundos.]] -- UMP L.ump_desc = [[ -SMG experimental que desorienta -alvos. +SMG experimental que desorienta alvos. Usa munição de SMG comum.]] @@ -432,12 +362,9 @@ L.dna_object = "A mostra coletada para o dono deste objeto." L.dna_gone = "Não há nenhuma amostra de DNA nesta área." L.dna_desc = [[ -Colete amostras de DNA de objetos -e analise-as para saber quem os usou. +Colete amostras de DNA de objetos e analise-as para saber quem os usou. -Utilize-o em terroristas recentemente mortos -para coletar a amostra do DNA do assassino e assim -poder rastreá-lo.]] +Utilize-o em terroristas recentemente mortos para coletar a amostra do DNA do assassino e assim poder rastreá-lo.]] -- Magneto stick L.magnet_name = "Magneto-stick" @@ -465,14 +392,13 @@ L.tele_no_mark = "Nenhum local marcado. Marque um local para teletransportar-se. L.tele_no_mark_ground = "Você não pode marcar um local para teletransportar-se se você não estiver em um chão sólido!" L.tele_no_mark_crouch = "Você não pode marcar um local para teletransportar-se enquanto estiver agachado!" -L.tele_help_pri = "{primaryfire} teletransporta para o local marcado." -L.tele_help_sec = "{secondaryfire} marca um local de teletransporte." +--L.tele_help_pri = "Teleports to marked location" +--L.tele_help_sec = "Marks current location" L.tele_desc = [[ Teletransporta para um local previamente marcado. -Teletransportar-se faz barulho, e tem -um número limitado de usos.]] +Teletransportar-se faz barulho, e tem um número limitado de usos.]] -- Ammo names, shown when picked up L.ammo_pistol = "Munição de Pistola" @@ -501,7 +427,7 @@ L.hp_wounded = "Ferido" L.hp_badwnd = "Muito Ferido" L.hp_death = "Quase Morto" --- TargetID karma status +-- TargetID Karma status L.karma_max = "Respeitável" L.karma_high = "Bruto" L.karma_med = "Guerreiro" @@ -510,19 +436,17 @@ L.karma_min = "Irresponsável" -- TargetID misc L.corpse = "Cadáver" -L.corpse_hint = "Pressione {usekey} para investigar. {walkkey} + {usekey} para investigar furtivamente." +--L.corpse_hint = "Press [{usekey}] to search and confirm. [{walkkey} + {usekey}] to search covertly." L.target_disg = " (DISFARÇADO)" L.target_unid = "Corpo não identificado" L.target_unknown = "Um terrorista" -L.target_credits = "Vasculhe para receber créditos não gastos" - -- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "Uso único" L.tbut_reuse = "Reutilizável" L.tbut_retime = "Reutilizável após {num} seg" -L.tbut_help = "Pressione {key} para ativar" +L.tbut_help = "Pressione {usekey} para ativar" -- Spectator muting of living/dead L.mute_living = "Jogadores vivos emudecidos" @@ -532,7 +456,6 @@ L.mute_off = "Ninguém emudecido" -- Spectators and prop possession L.punch_title = "SOCÔMETRO" -L.punch_help = "Movimentar-se ou pular: soca o objeto. Agachar-se: sai do objeto." L.punch_bonus = "Sua má pontuação diminuiu seu limite do socômetro em {num}" L.punch_malus = "Sua boa pontuação aumentou seu limite do socômetro em {num}!" @@ -575,7 +498,7 @@ Você esteve inativo por {num} segundos e foi movido para o modo Somente-Especta Você pode alterná-lo a qualquer momento ao pressionar {helpkey} e desmarcar a opção correspondente na aba Configurações. Você pode optar por desabilitá-lo agora mesmo.]] L.idle_popup_close = "Fazer nada" -L.idle_popup_off = "Desabilitar Somente-Espectador" +L.idle_popup_off = "Desabilitar Somente-Espectador" L.idle_warning = "Aviso: você aparenta estar ausente, e será movido para a equipe dos espectadores a não ser que demonstre alguma atividade!" @@ -583,7 +506,7 @@ L.spec_mode_warning = "Você está no Modo Espectador e não renascerá quando u -- Tips panel L.tips_panel_title = "Dicas" -L.tips_panel_tip = "Dica:" +L.tips_panel_tip = "Dica:" -- Tip texts L.tip1 = "Traidores podem investigar cadáveres furtivamente, sem confirmar a sua morte, ao segurar {walkkey} e pressionar {usekey} no cadáver." @@ -684,16 +607,16 @@ L.report_save_error = "Não há nenhum Registro de Acontecimentos a ser salvo." L.report_save_result = "O Registro de Acontecimentos foi salvo em:" -- Columns -L.col_time = "Tempo" -L.col_event = "Acontecimento" +L.col_time = "Tempo" +L.col_event = "Acontecimento" L.col_player = "Jogador" --L.col_roles = "Role(s)" --L.col_teams = "Team(s)" L.col_kills1 = "Inocentes mortos" L.col_kills2 = "Traidores mortos" L.col_points = "Pontos" -L.col_team = "Bônus de equipe" -L.col_total = "Total de pontos" +L.col_team = "Bônus de equipe" +L.col_total = "Total de pontos" -- Awards/highlights L.aw_sui1_title = "LÍDER DA SEITA SUICIDA" @@ -960,16 +883,13 @@ L.shop_role_select = "Selecione o papel" L.shop_role_selected = "Loja de {role} foi selecionada!" L.shop_search = "Procurar" -L.spec_help = "Clique para espectar um jogador, ou pressione {usekey} em um objeto para possuir ele.." -L.spec_help2 = "Para sair do modo espectador, abra o menu pressionando {helpkey}, vá em 'gameplay' e desative o modo espectador.." - -- 2019-10-19 L.drop_ammo_prevented = "Alguma coisa fez você soltar sua munição.." -- 2019-10-28 L.target_c4 = "Pressione [{usekey}] para abrir o menu da C4" L.target_c4_armed = "Pressione [{usekey}] para desarmar a C4" -L.target_c4_armed_defuser = "Pressione [{usekey}] para usar o desarme" +L.target_c4_armed_defuser = "Pressione [{primaryfire}] para usar o desarme" L.target_c4_not_disarmable = "Você não pode desarmar uma C4 de um companheiro ainda vivo" L.c4_short_desc = "Alguma coisa muito explosivo" @@ -977,16 +897,15 @@ L.target_pickup = "Pressione [{usekey}] para pegar" L.target_slot_info = "Slot: {slot}" L.target_pickup_weapon = "Pressione [{usekey}] para pegar a arma" L.target_switch_weapon = "Pressione [{usekey}] para trocar com a sua arma no momento" -L.target_pickup_weapon_hidden = ", pressione [{usekey} + {walkkey}] para esconder a arma" -L.target_switch_weapon_hidden = ", pressione [{usekey} + {walkkey}] para esconder" +L.target_pickup_weapon_hidden = ", pressione [{walkkey} + {usekey}] para esconder a arma" +L.target_switch_weapon_hidden = ", pressione [{walkkey} + {usekey}] para esconder" L.target_switch_weapon_nospace = "Não há slot de inventário para esta arma" L.target_switch_drop_weapon_info = "Dropar {name} do slot {slot}" L.target_switch_drop_weapon_info_noslot = "Não há uma arma dropavel no slot {slot}" -L.corpse_searched_by_detective = "Esse corpo foi encontrado por um detetive" +--L.corpse_searched_by_detective = "This corpse was searched by a public policing role" L.corpse_too_far_away = "Este corpo está muito longe." -L.radio_pickup_wrong_team = "Você não pode pegar a rádio de outro time." L.radio_short_desc = "Tiros de arma são músicas para meus ouvidos" L.hstation_subtitle = "Pressione [{usekey}] para receber vida." @@ -1025,13 +944,12 @@ L.door_locked = "Esta porta esta trancada." -- 2020-02-11 L.automoved_to_spec = "(MENSAGEM AUTOMATICA) Eu fui movimentado para o Espetador porque eu estava Parado/AFK." -L.mute_team = "{time} mutado." +L.mute_team = "{team} mutado." -- 2020-02-16 L.door_auto_closes = "Esta porta fecha automaticamente.." L.door_open_touch = "Ande até a porta para abrir." L.door_open_touch_and_use = "Ande até a porta ou pressione [{usekey}] para abrir." -L.hud_health = "Vida" -- 2020-03-09 L.help_title = "Ajuda e Configurações" @@ -1053,7 +971,7 @@ L.menu_guide_description = "Ajuda você a começar no TTT2 explicando sobre o ga L.menu_bindings_description = "Binds especificas do TTT2 para fazer você virar pro." L.menu_language_description = "Selecione a linguagem do modo." L.menu_appearance_description = "Aparencias personalizadas do TTT2." -L.menu_gameplay_description = "Evite funções e outras coisas também." +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." L.menu_addons_description = "Configure addons locais do seu jeito." L.menu_legacy_description = "Um painel com guias convertidas do TTT original que deve ser portado para o novo sistema." L.menu_administration_description = "Configurações gerais para Huds, lojas e etc." @@ -1077,10 +995,8 @@ L.submenu_appearance_crosshair_title = "Mira" L.submenu_appearance_dmgindicator_title = "Indicador de Dano" L.submenu_appearance_performance_title = "Desempenho" L.submenu_appearance_interface_title = "Interface" -L.submenu_appearance_miscellaneous_title = "Outros" L.submenu_gameplay_general_title = "Geral" -L.submenu_gameplay_avoidroles_title = "Evitar funções" L.submenu_administration_hud_title = "Configurações da HUD" L.submenu_administration_randomshop_title = "Loja Aleatória" @@ -1117,17 +1033,12 @@ L.label_shop_show_slot = "Mostrar marcação de slot" L.label_shop_show_custom = "Mostrar marca de item customizado" L.label_shop_show_fav = "Mostar marcação de itens favoritos" L.label_crosshair_enable = "Ativar mira" -L.label_crosshair_gap_enable = "Ativar lacuna da mira personalizada" -L.label_crosshair_gap = "Lacuna personalizada da mira" L.label_crosshair_opacity = "Opacidade da mira" L.label_crosshair_ironsight_opacity = "Opacidade da mira de ferro" L.label_crosshair_size = "Tamanho da mira" L.label_crosshair_thickness = "Espessura da mira" L.label_crosshair_thickness_outline = "Contorno da espessura da mira" -L.label_crosshair_static_enable = "Ativar mira estática" -L.label_crosshair_dot_enable = "Ativar ponto na mira" -L.label_crosshair_lines_enable = "Ativar linha da mira" -L.label_crosshair_scale_enable = "Ativar escala dependendo da arma" +--L.label_crosshair_scale_enable = "Enable dynamic crosshair scale" L.label_crosshair_ironsight_low_enabled = "Mira de ferro para armas fracas" L.label_damage_indicator_enable = "Ativar indicador de dano" L.label_damage_indicator_mode = "Selecione o tema do indicador de dano" @@ -1146,13 +1057,10 @@ L.label_gameplay_specmode = "Ativar modo espectador (ficar sempre em modo espect L.label_gameplay_fastsw = "Troca de arma rápida" L.label_gameplay_hold_aim = "Ativar segurar para mirar" L.label_gameplay_mute = "Mutar jogadores vivos quando morrer" -L.label_gameplay_dtsprint_enable = "Ativar apertar duas veze para correr" -L.label_gameplay_dtsprint_anykey = "Continuar apertando duas vezes antes de você parar" L.label_hud_default = "HUD Padrão" L.label_hud_force = "HUD Forçada" L.label_bind_weaponswitch = "Pegar arma" -L.label_bind_sprint = "Correr" L.label_bind_voice = "Microfone Global" L.label_bind_voice_team = "Microfone do Time" @@ -1176,7 +1084,6 @@ L.header_damage_indicator = "Configuração do indicador de dano" L.header_performance_settings = "Configuração de Performance" L.header_interface_settings = "Configuração da Interface" L.header_gameplay_settings = "Configuração do Gameplay" -L.header_roleselection = "Selecione a função que deseja evitar" L.header_hud_administration = "Selecionar HUD padrão e forçada" L.header_hud_enabled = "Ativar/Desativar HUD" @@ -1223,11 +1130,7 @@ L.hud_revival_time = "{time}s" L.door_destructible = "Está porta é destrutível ({health}HP)." -- 2020-05-28 -L.confirm_detective_only = "Apenas detetives podem confirmar o corpo." -L.inspect_detective_only = "Apenas detetive podem encontrar corpos." -L.corpse_hint_no_inspect = "Apenas detetives podem encontrar este corpo." -L.corpse_hint_inspect_only = "Pressione [{usekey}] para procurar. Apenas detetive pode confirmar o corpo." -L.corpse_hint_inspect_only_credits = "Pressione [{usekey}] para pegar os créditos. Apenas detetives podem encontrar este corpo." +--L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." -- 2020-06-04 L.label_bind_disguiser = "Alternar disfarce" @@ -1242,7 +1145,6 @@ L.binoc_help_sec = "Alterar o zoom." L.vis_help_pri = "Largar o dispositivo ativo." -L.decoy_help_pri = "Plantar a isca." -- 2020-08-07 L.pickup_error_spec = "Você não pode pegar isto como espectaor." @@ -1256,7 +1158,6 @@ Esta tradução está {coverage}% completa com a linguagem padrão em inglês. Por favor, leve em mente que a tradução é feita pela comunidade. Sinta-se livre para contribuir caso houver erros ou palavras incorretas.]] - -- 2021-04-13 L.title_score_info = "Informação do final da Rodada" L.title_score_events = "Evento" @@ -1449,7 +1350,7 @@ L.xfer_team_indicator = "Time" --L.spawneditor_place = "Place spawn" --L.spawneditor_remove = "Remove spawn" --L.spawneditor_change = "Change spawn type (hold [SHIFT] to reverse)" ---L.spawneditor_ammo_edit = "Hold to edit amount of autospawning ammo on weapon spawns" +--L.spawneditor_ammo_edit = "Hold on weapon spawn to edit autospawning ammo" --L.spawn_weapon_random = "Random Weapon Spawn" --L.spawn_weapon_melee = "Melee Weapon Spawn" @@ -1467,7 +1368,7 @@ L.xfer_team_indicator = "Time" --L.spawn_ammo_shotgun = "Shotgun ammo spawn" --L.spawn_player_random = "Random player spawn" ---L.spawn_weapon_ammo = " (Ammo: {ammo})" +--L.spawn_weapon_ammo = "(Ammo: {ammo})" --L.spawn_weapon_edit_ammo = "Hold [{walkkey}] and press [{primaryfire} or {secondaryfire}] to increase or decrease the ammo for this weapon spawn" @@ -1489,18 +1390,18 @@ L.xfer_team_indicator = "Time" --L.help_spawn_editor_info = [[ --The spawn editor is used to place, remove and edit spawns in the world. These spawns are for weapons, ammunition and players. - +-- --These spawns are saved in files located in 'data/ttt/weaponspawnscripts/'. They can be deleted for a hard reset. The initial spawn files are created from spawns found on the map and in the original TTT weapon spawn scripts. Pressing the reset button always reverts to the initial state. - +-- --It should be noted that this spawn system uses dynamic spawns. This is most interesting for weapons because it no longer defines a specific weapon, but a type of weapons. For example instead of a TTT shotgun spawn, there is now a general shotgun spawn where any weapon defined as shotgun can spawn. The spawn type for each weapon can be set in the 'Edit Equipment' menu. This makes it possible for any weapon to spawn on the map, or to disable certain default weapons. - +-- --Keep in mind that many changes only take effect after a new round has started.]] --L.help_spawn_editor_enable = "On some maps it might be advised to use the original spawns found on the map without replacing them with the dynamic system. Changing this option below only affects the currently active map, so the dynamic system will still be used for every other map." --L.help_spawn_editor_hint = "Hint: To leave the spawn editor, reopen the gamemode menu." --L.help_spawn_editor_spawn_amount = [[ --There currently are {weapon} weapon spawns, {ammo} ammunition spawns and {player} player spawns on this map. --Click 'start spawn edit' to change this amount. - +-- --{weaponrandom}x Random weapon spawn --{weaponmelee}x Melee weapon spawn --{weaponnade}x Grenade weapon spawn @@ -1509,21 +1410,21 @@ L.xfer_team_indicator = "Time" --{weaponsniper}x Sniper weapon spawn --{weaponpistol}x Pistol weapon spawn --{weaponspecial}x Special weapon spawn - +-- --{ammorandom}x Random ammo spawn --{ammodeagle}x Deagle ammo spawn --{ammopistol}x Pistol ammo spawn --{ammomac10}x Mac10 ammo spawn --{ammorifle}x Rifle ammo spawn --{ammoshotgun}x Shotgun ammo spawn - +-- --{playerrandom}x Random player spawn]] --L.equipmenteditor_name_auto_spawnable = "Equipment spawns randomly in world" --L.equipmenteditor_name_spawn_type = "Select spawn type" --L.equipmenteditor_desc_auto_spawnable = [[ --The TTT2 spawn system allows every weapon to spawn in the world. By default only weapons marked as 'AutoSpawnable' by the creator will spawn in the world, however this can be changed from within this menu. - +-- --Most of the equipment is set to 'special weapon spawns' by default. This means that equipment only spawns on random weapon spawns. However it is possible to place special weapon spawns in the world or change the spawn type here to use other existing spawn types.]] --L.pickup_error_inv_cached = "You cannot pick this up right now because your inventory is cached." @@ -1540,14 +1441,14 @@ L.xfer_team_indicator = "Time" --L.help_prefer_map_models = [[ --Some maps define their own player models. By default these models have a higher priority than those that are assigned automatically. By disabling this setting, map specific models are disabled. - +-- --Role specific models always have a higher priority and are unaffected by this setting.]] --L.help_enforce_playermodel = [[ --Some roles have custom player models. They can be disabled which can be relevant for compatibility with some player model selectors. --Random default models can still be selected, if this setting is disabled.]] --L.help_use_custom_models = [[ --By default only the CS:S Phoenix player model is assigned to all players. By enabling this option however it is possible to select a player model pool. With this setting enabled each player will still be assigned the same player model, however it is a random model from the defined model pool. - +-- --This selection of models can be extended by installing more player models.]] -- 2021-10-06 @@ -1564,7 +1465,7 @@ L.xfer_team_indicator = "Time" -- 2021-10-09 --L.help_models_select = [[ --Left click on the models to add them to the player model pool. Left click again to remove them. Right clicking toggles between enabled and disabled detective hats for the focused model. - +-- --The small indicator in the top left shows if the player model has a head hitbox. The icon below shows if this model is applicable for a detective hat.]] --L.menu_roles_title = "Role Settings" @@ -1587,7 +1488,7 @@ L.xfer_team_indicator = "Time" --L.help_roles_selection = [[ --The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role. This also means that at least 5 players are needed for this role to be distributed at all. --Keep in mind that all of this only applies if the role is considered for distribution process. - +-- --The aforementioned role distribution has a special integration with the lower limit of players. If the role is considered for distribution and the minimum value is below the value given by the distribution factor, but the amount of players is equal or greater than the lower limit, a single player can still receive this role. The distribution process then works as usual for the second player.]] --L.help_roles_award_info = "Some roles (if enabled in their credits settings) receive equipment credits if a certain percentage of enemies has died. Related values can be tweaked here." --L.help_roles_award_pct = "When this percentage of enemies are dead, specific roles are awarded equipment credits." @@ -1595,17 +1496,17 @@ L.xfer_team_indicator = "Time" --L.help_roles_advanced_warning = "WARNING: These are advanced settings that can completely mess up the role distribution process. When in doubt keep all values at '0'. This value means that no limits are applied and the role distribution will try to assign as many roles as possible." --L.help_roles_max_roles = [[ --The term roles here includes both the base roles and the subroles. By default, there is no limit on how many different roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.help_roles_max_baseroles = [[ --Base roles are only those roles others inherit from. For example, the Innocent role is a base role, while a Pharaoh is a subrole of this role. By default, there is no limit on how many different base roles can be assigned. However, here are two different ways to limit them. - +-- --1. Limit them by a fixed amount. --2. Limit them by a percentage. - +-- --The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] --L.label_roles_enabled = "Enable role" @@ -1637,15 +1538,15 @@ L.xfer_team_indicator = "Time" --L.help_roles_credits_award_kill = "Another way of gaining credits is by killing high value players with a 'public role' such as a Detective. If the killer's role has this enabled, they gain the below defined amount of credits." --L.help_roles_credits_award = [[ --There are two different ways to be awarded credits in base TTT2: - +-- --1. If a certain percentage of the enemy team is dead, the whole team is awarded credits. --2. If a player killed a high value player with a 'public role' such as a Detective, the killer is awarded credits. - +-- --Please note, that this still can be enabled/disabled for every role, even if the whole team is awarded. For example, if team Innocent is awarded, but the Innocent role has this disabled, only the Detective will receive their credits. --The balancing values for this feature can be set in 'Administration' -> 'General Role Settings'.]] --L.help_detective_hats = [[ --Policing roles such as the Detective may wear hats to show their authority. They lose them on death or if damaged at the head. - +-- --Some player models do not support hats by default. This can be changed in 'Administration' -> 'Player Models']] --L.label_roles_credits_award_kill = "Credit reward amount for the kill" @@ -1697,35 +1598,35 @@ L.xfer_team_indicator = "Time" --L.help_idle = "The idle mode is used to forcefully move idle players into the spectator mode. To leave this mode, they will have to disable it in their 'gameplay' menu." --L.help_namechange_kick = [[ --A name change during an active round could be abused. Therefore, this is prohibited by default and will lead to the offending player being kicked from the server. - +-- --If the bantime is greater than 0, the player will be unable to reconnect to the server until that time has passed.]] --L.help_damage_log = "Each time a player is damaged, a damage log entry is added to the console if enabled. This can also be stored to disk after a round has ended. The file is located at 'data/terrortown/logs/'" --L.help_spawn_waves = [[ --If this variable is set to 0, all players are spawned at once. For servers with huge amounts of players, it can be beneficial to spawn the players in waves. The spawn wave interval is the time between each spawn wave. A spawn wave always spawns as many players as there are valid spawn points. - +-- --Note: Make sure that the preparing time is long enough for the desired amount of spawn waves.]] --L.help_voicechat_battery = [[ --Voice chatting with enabled voice chat battery reduces battery charge. When it's empty, the player can't use voice chat and has to wait for it to recharge. This can help to prevent excessive voice chat usage. - +-- --Note: 'Tick' refers to a game tick. For example, if the tick rate is set to 66, then it will be 1/66th of a second.]] --L.help_ply_spawn = "Player settings that are used on player (re-)spawn." --L.help_haste_mode = [[ --Haste mode balances the game by increasing the round time with every dead player. Only roles that see missing in action players can see the real round time. Every other role can only see the haste mode starting time. - +-- --If haste mode is enabled, the fixed round time is ignored.]] --L.help_round_limit = "After one of the set limit conditions is met, a map change is triggered." --L.help_armor_balancing = "The following values can be used to balance the armor." --L.help_item_armor_classic = "If classic armor mode is enabled, only the previous settings matter. Classic armor mode means that a player can only buy armor once in a round, and that this armor blocks 30% of the incoming bullet and crowbar damage until they die." --L.help_item_armor_dynamic = [[ --Dynamic armor is the TTT2 approach to make armor more interesting. The amount of armor that can be bought is now unlimited, and the armor value stacks. Getting damaged decreases the armor value. The armor value per bought armor item is set in the 'Equipment Settings' of said item. - +-- --When taking damage, a certain percentage of this damage is converted into armor damage, a different percentage is still applied to the player and the rest vanishes. - +-- --If reinforced armor is enabled, the damage applied to the player is decreased by 15% as long as the armor value is above the reinforcement threshold.]] --L.help_sherlock_mode = "The sherlock mode is the classic TTT mode. If the sherlock mode is disabled, dead bodies can not be confirmed, the scoreboard shows everyone as alive and the spectators can talk to the living players." --L.help_prop_possession = [[ --Prop possession can be used by spectators to possess props lying in the world and use the slowly recharging 'punch-o-meter' to move said prop around. - +-- --The maximum value of the 'punch-o-meter' consists of a possession base value, where the kills/deaths difference clamped inbetween two defined limits is added. The meter slowly recharges over time. The set recharge time is the time needed to recharge a single point in the 'punch-o-meter'.]] --L.help_karma = "Players start with a certain amount of Karma, and lose it when they damage/kill teammates. The amount they lose is dependent on the Karma of the person they hurt or killed. Lower Karma reduces damage given." --L.help_karma_strict = "If strict Karma is enabled, the damage penalty increases more quickly as Karma goes down. When it is off, the damage penalty is very low when people stay above 800. Enabling strict mode makes Karma play a larger role in discouraging any unnecessary kills, while disabling it results in a more “loose” game where Karma only hurts players who constantly kill teammates." @@ -1735,7 +1636,7 @@ L.xfer_team_indicator = "Time" --L.help_karma_bonus = "There are also two different passive ways to gain Karma during a round. First is a karma restoration which applied to every player at the round end. Then a secondary clean round bonus is given if no teammates were hurt or killed by a player." --L.help_karma_clean_half = [[ --When a player's Karma is above the starting level (meaning the Karma max has been configured to be higher than that), all their Karma increases will be reduced based on how far their Karma is above that starting level. So it goes up slower the higher it is. - +-- --This reduction goes in a curve of exponential decay: initially it's fast, and it slows down as the increment gets smaller. This convar sets at what point the bonus has been halved (so the half-life). With the default value of 0.25, if the starting amount of Karma is 1000 and the max 1500, and a player has Karma 1125 ((1500 - 1000) * 0.25 = 125), then his clean round bonus will be 30 / 2 = 15. So to make the bonus go down faster you’d set this convar lower, to make it go down slower you’d increase it towards 1.]] --L.help_max_slots = "Sets the maximum amount of weapons per slot. '-1' means that there is no limit." --L.help_item_armor_value = "This is the armor value given by the armor item in dynamic mode. If classic mode is enabled (see 'Administration' -> 'Player Settings') then every value greater than 0 is counted as existing armor." @@ -1758,8 +1659,6 @@ L.xfer_team_indicator = "Time" --L.label_tbutton_admin_show = "Show traitor buttons to admins" --L.label_ragdoll_carrying = "Enable ragdoll carrying" --L.label_prop_throwing = "Enable prop throwing" ---L.label_ragdoll_pinning = "Enable ragdoll pinning for non-Innocent roles" ---L.label_ragdoll_pinning_innocents = "Enable ragdoll pinning for Innocent roles" --L.label_weapon_carrying = "Enable weapon carrying" --L.label_weapon_carrying_range = "Weapon carry range" --L.label_prop_carrying_force = "Prop pickup force" @@ -1789,10 +1688,8 @@ L.xfer_team_indicator = "Time" --L.label_spectator_chat = "Enable spectators chatting with everybody" --L.label_lastwords_chatprint = "Print last words to chat if killed while typing" --L.label_identify_body_woconfirm = "Identify corpse without pressing the 'confirm' button" ---L.label_announce_body_found = "Announce that a body was found" +--L.label_announce_body_found = "Announce that a body was found when the body was confirmed" --L.label_confirm_killlist = "Announce kill list of confirmed corpse" ---L.label_inspect_detective_only = "Limit corpse search to policing roles only" ---L.label_confirm_detective_only = "Limit corpse confirmation to policing roles only" --L.label_dyingshot = "Shoot on death if in ironsights [experimental]" --L.label_armor_block_headshots = "Enable armor blocking headshots" --L.label_armor_block_blastdmg = "Enable armor blocking blast damage" @@ -1851,7 +1748,6 @@ L.xfer_team_indicator = "Time" --L.label_sprint_max = "Max sprinting stamina" --L.label_sprint_stamina_consumption = "Stamina consumption factor" --L.label_sprint_stamina_regeneration = "Stamina regeneration factor" ---L.label_sprint_crosshair = "Show crosshair while sprinting" --L.label_crowbar_unlocks = "Primary attack can be used as interaction (i.e. unlocking)" --L.label_crowbar_pushforce = "Crowbar push force" @@ -1864,7 +1760,7 @@ L.xfer_team_indicator = "Time" --L.help_falldmg_exponent = [[ --This value modifies how exponentially fall damage is increased with the speed the player hits the ground at. - +-- --Take care when altering this value. Setting it too high can make even the smallest falls lethal, while setting it too low will allow players to fall from extreme heights and suffer little to no damage.]] -- 2023-02-08 @@ -1887,4 +1783,411 @@ L.xfer_team_indicator = "Time" --L.sb_rank_tooltip_heroes = "TTT2 Heroes" --L.sb_rank_tooltip_team = "Team" ---L.tbut_adminarea = "ADMIN AREA:" \ No newline at end of file +--L.tbut_adminarea = "ADMIN AREA:" + +-- 2023-08-10 +--L.equipmenteditor_name_damage_scaling = "Damage Scaling" + +-- 2023-08-11 +--L.equipmenteditor_name_allow_drop = "Allow Drop" +--L.equipmenteditor_desc_allow_drop = "If enabled, the equipment can be dropped freely by the player." + +--L.equipmenteditor_name_drop_on_death_type = "Drop on Death" +--L.equipmenteditor_desc_drop_on_death_type = "Attempt overriding the action taken for whether the equipment is dropped on player's death." + +--L.drop_on_death_type_default = "Default (weapon-defined)" +--L.drop_on_death_type_force = "Force Drop on Death" +--L.drop_on_death_type_deny = "Deny Drop on Death" + +-- 2023-08-26 +--L.equipmenteditor_name_kind = "Equipment Slot" +--L.equipmenteditor_desc_kind = "The inventory slot the equipment will occupy." + +--L.slot_weapon_melee = "Melee Slot" +--L.slot_weapon_pistol = "Pistol Slot" +--L.slot_weapon_heavy = "Heavy Slot" +--L.slot_weapon_nade = "Grenade Slot" +--L.slot_weapon_carry = "Carry Slot" +--L.slot_weapon_unarmed = "Unarmed Slot" +--L.slot_weapon_special = "Special Slot" +--L.slot_weapon_extra = "Extra Slot" +--L.slot_weapon_class = "Class Slot" + +-- 2023-10-04 +--L.label_voice_duck_spectator = "Duck spectator voices" +--L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +--L.label_voice_scaling = "Voice Volume Scaling Mode" +--L.label_voice_scaling_mode_linear = "Linear" +--L.label_voice_scaling_mode_power4 = "Power 4" +--L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "Resultados de investigação corporal - {player}" +L.search_info = "Informação" +L.search_confirm = "Confirmar Morte" +--L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +--L.search_take_credits = "Take {credits} Credit(s)" +--L.search_confirm_forbidden = "Confirm forbidden" +--L.search_confirmed = "Death Confirmed" +--L.search_call = "Report Death" +--L.search_called = "Death Reported" + +--L.search_team_role_unknown = "???" + +L.search_words = "Algo lhe diz que algumas das últimas palavras desta pessoa foram: '{lastwords}'" +L.search_armor = "Ele estava vestindo um colete balístico atípico." +L.search_disguiser = "Ele estava carregando um dispositivo que podia ocultar sua identidade." +L.search_radar = "Ele estava carregando algum tipo de radar. Não está mais funcionando." +L.search_c4 = "Você encontrou uma nota em um bolso. Ela diz que ao cortar o fio {num}, a bomba será desarmada com segurança." + +L.search_dmg_crush = "A maioria dos ossos dele estão quebrados. Parece que ele foi esmagado por algo pesado." +L.search_dmg_bullet = "É óbvio que ele foi baleado." +L.search_dmg_fall = "Ele caiu para sua morte." +L.search_dmg_boom = "Seus ferimentos e roupas rasgadas indicam que uma explosão o matou." +L.search_dmg_club = "Este corpo está muito ferido. Claramente ele foi espancado até a morte." +L.search_dmg_drown = "O corpo revela sinais de afogamento." +L.search_dmg_stab = "Ele foi esfaqueado e cortado antes de rapidamente sangrar até a morte." +L.search_dmg_burn = "Cheiro de terrorista assado por aqui..." +L.search_dmg_teleport = "Parece que a amostra de DNA dele foi embaraçada por emissões de táquion!" +L.search_dmg_car = "Quando este terrorista atravessou a estrada, acabou sendo atropelado por um motorista com a CNH vencida." +L.search_dmg_other = "Você não pôde encontrar uma causa específica da morte deste terrorista." + +--L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +--L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +--L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +--L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +--L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +--L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +--L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +--L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +--L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +--L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +--L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +--L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +--L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +--L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +--L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +--L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +--L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +--L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +--L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +--L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +--L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +--L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +--L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +--L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +--L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "Aparentemente, um(a) {weapon} foi usado(a) para matá-lo." +L.search_head = "O disparo foi direto na cabeça. A vítima não teve tempo para gritar." +--L.search_time = "They died a while before you conducted the search." +--L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "Você encontrou uma lista de assassinatos que comprovam a morte de {player}." +L.search_kills2 = "Você encontrou uma lista de assassinatos com estes nomes: {player}" +L.search_eyes = "Usando suas técnicas de detetive, você identificou a última pessoa que ele viu: {player}. O assassino, ou uma coincidência?" + +--L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +--L.search_kill_distance_point_blank = "It was a point blank attack." +--L.search_kill_distance_close = "The attack came from a short distance." +--L.search_kill_distance_far = "The victim was attacked from a long distance away." + +--L.search_kill_from_front = "The victim was shot from the front." +--L.search_kill_from_back = "The victim was shot from behind." +--L.search_kill_from_side = "The victim was shot from the side." + +--L.search_hitgroup_head = "The projectile was found in their head." +--L.search_hitgroup_chest = "The projectile was found in their chest." +--L.search_hitgroup_stomach = "The projectile was found in their stomach." +--L.search_hitgroup_rightarm = "The projectile was found in their right arm." +--L.search_hitgroup_leftarm = "The projectile was found in their left arm." +--L.search_hitgroup_rightleg = "The projectile was found in their right leg." +--L.search_hitgroup_leftleg = "The projectile was found in their left leg." +--L.search_hitgroup_gear = "The projectile was found in their hip." + +--L.search_policingrole_report_confirm = [[ +--A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +--L.search_policingrole_confirm_disabled_1 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +--L.search_policingrole_confirm_disabled_2 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know! +--You can see the information in here after they confirmed it.]] +--L.search_spec = [[ +--As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +--L.search_title_words = "Victim's last words" +--L.search_title_c4 = "Defusion mishap" +--L.search_title_dmg_crush = "Crush damage ({amount} HP)" +--L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +--L.search_title_dmg_fall = "Fall damage ({amount} HP)" +--L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +--L.search_title_dmg_club = "Club damage ({amount} HP)" +--L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +--L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +--L.search_title_dmg_burn = "Burning damage ({amount} HP)" +--L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +--L.search_title_dmg_car = "Car accident ({amount} HP)" +--L.search_title_dmg_other = "Unknown damage ({amount} HP)" +--L.search_title_time = "Death time" +--L.search_title_dna = "DNA sample decay" +--L.search_title_kills = "The victim's kill list" +--L.search_title_eyes = "The killer's shadow" +--L.search_title_floor = "Floor of the crime scene" +--L.search_title_credits = "{credits} Equipment credit(s)" +--L.search_title_water = "Water level {level}" +--L.search_title_policingrole_report_confirm = "Confirm to report death" +--L.search_title_policingrole_confirm_disabled = "Report corpse" +--L.search_title_spectator = "You are a spectator" + +--L.target_credits_on_confirm = "Confirm to receive unspent credits" +--L.target_credits_on_search = "Search to receive unspent credits" +--L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +--L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +--L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +--L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +--L.label_inspect_confirm_mode = "Select body search mode" +--L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +--L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +--L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +--L.help_inspect_confirm_mode = [[ +--There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. +-- +--mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. +-- +--mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. +-- +--mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +--L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +--L.header_hud_elements_customize = "Customize the HUD-Elements" +--L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +--L.help_keyhelp = [[ +--Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: +-- +--Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +--Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +--Equipment: Some equipment items have their own bindings, these are shown in this category. +-- +--Disabled categories are still shown when the scoreboard is visible]] + +--L.label_keyhelp_show_core = "Enable always showing the core bindings" +--L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +--L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +--L.header_interface_keys = "Key helper settings" +--L.header_interface_wepswitch = "Weapon switch UI settings" + +--L.label_keyhelper_help = "open gamemode menu" +--L.label_keyhelper_mutespec = "cycle spectator voice mode" +--L.label_keyhelper_shop = "open equipment shop" +--L.label_keyhelper_show_pointer = "free mouse pointer" +--L.label_keyhelper_possess_focus_entity = "possess focused entity" +--L.label_keyhelper_spec_focus_player = "spectate focused player" +--L.label_keyhelper_spec_previous_player = "previous player" +--L.label_keyhelper_spec_next_player = "next player" +--L.label_keyhelper_spec_player = "spectate random player" +--L.label_keyhelper_possession_jump = "prop: jump" +--L.label_keyhelper_possession_left = "prop: left" +--L.label_keyhelper_possession_right = "prop: right" +--L.label_keyhelper_possession_forward = "prop: forward" +--L.label_keyhelper_possession_backward = "prop: backward" +--L.label_keyhelper_free_roam = "leave object and roam free" +--L.label_keyhelper_flashlight = "toggle flashlight" +--L.label_keyhelper_quickchat = "open quickchat" +--L.label_keyhelper_voice_global = "global voice chat" +--L.label_keyhelper_voice_team = "team voice chat" +--L.label_keyhelper_chat_global = "global chat" +--L.label_keyhelper_chat_team = "team chat" +--L.label_keyhelper_show_all = "show all" +--L.label_keyhelper_disguiser = "toggle disguiser" +--L.label_keyhelper_save_exit = "save and exit" +--L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +--L.item_armor_reinforced = "Reinforced Armor" +--L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +--L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +--L.status_speed_name = "Speed Multiplier" +--L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +--L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +--L.status_on = "on" +--L.status_off = "off" + +--L.crowbar_help_primary = "Attack" +--L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +--L.help_HUD_enable_description = [[ +--Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +--L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +--L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +--L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +--L.header_soundeffect_settings = "Sound Effects" +--L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +--L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +--L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +--L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +--L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +--L.help_prop_spec_dash = [[ +--Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. +-- +--This variable is a multiplier of the push force.]] +--L.label_spec_prop_dash = "Dash force multiplier" +--L.label_keyhelper_possession_dash = "prop: dash in view direction" +--L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +--L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +--L.c4_help_primary = "Place the C4" +--L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +--L.magneto_help_primary = "Push entity" +--L.magneto_help_secondary = "Pull / pickup entity" +--L.knife_help_primary = "Stab" +--L.knife_help_secondary = "Throw knife" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +--L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +--L.newton_force = "FORCE" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +--L.beacon_help_pri = "Throw Beacon on the ground" +--L.beacon_help_sec = "Stick Beacon to surface" +--L.beacon_name = "Beacon" +--L.beacon_desc = [[ +--Broadcasts player locations to everyone in a sphere around this beacon. +-- +--Use to keep track of locations on the map that are hard to see.]] + +--L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +--L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +--L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +--L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +--L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder} confirmou a morte de {victim}." +--L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +--L.builtin_marker = "Built-in." +--L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +--L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +--L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +--L.submenu_gameplay_accessibility_title = "Accessibility" + +--L.header_accessibility_settings = "Accessibility Settings" + +--L.label_enable_dynamic_fov = "Enable dynamic FOV change" +--L.label_enable_bobbing = "Enable view bobbing" +--L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +--L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +--L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +--L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +--For a shotgun, this would affect each pellet. +--For a rifle, this would affect just the bullet. +--For the poltergeist, this would affect each "thump" and the final explosion. +-- +--0.5 = Deal half the amount of damage. +--2 = Deal twice the amount of damage. +-- +--Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +--L.binoc_help_reload = "Clear target." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +--L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +--L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +--L.label_crosshair_mode = "Crosshair mode" +--L.label_crosshair_static_length = "Enable static crosshair line length" + +--L.choice_crosshair_mode_0 = "Lines and dot" +--L.choice_crosshair_mode_1 = "Lines only" +--L.choice_crosshair_mode_2 = "Dot only" + +--L.help_crosshair_scale_enable = [[ +--Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. +-- +--If the line length is kept static, only the gap scales with cone changes.]] + +--L.header_weapon_settings = "Weapon Settings" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +L.decoy_help_primary = "Plantar a isca" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/ru.lua b/lua/terrortown/lang/ru.lua index 677a09250..678e662db 100644 --- a/lua/terrortown/lang/ru.lua +++ b/lua/terrortown/lang/ru.lua @@ -2,7 +2,6 @@ -- This localization file is being moderated and constantly updated by Satton(RU). -- Please, in case you are making some changes ping @Satton2 on GitHub, contact him on Steam (STEAM_0:0:85981967) or Discord (Satton(RU)#5794). - local L = LANG.CreateLanguage("ru") -- Compatibility language name that might be removed soon. @@ -63,8 +62,6 @@ L.body_found_traitor = "Он был предателем!" L.body_found_det = "Он был детективом." L.body_found_inno = "Он был невиновным." -L.body_confirm = "{finder} подтверждает смерть {victim}." - L.body_call = "{player} зовёт детектива к телу {victim}!" L.body_call_error = "Вы должны подтвердить смерть игрока, прежде чем звать детектива!" @@ -175,46 +172,6 @@ L.quick_disg = "кто-то замаскированный" L.quick_corpse = "неопознанное тело" L.quick_corpse_id = "{player} (тело)" --- Body search window -L.search_title = "Результаты осмотра тела" -L.search_info = "Информация" -L.search_confirm = "Подтвердить смерть" -L.search_call = "Позвать детектива" - --- Descriptions of pieces of information found -L.search_nick = "Это труп {player}." - -L.search_role_traitor = "Этот человек был предателем!" -L.search_role_det = "Этот человек был детективом." -L.search_role_inno = "Этот человек был невиновным." - -L.search_words = "Что-то подсказывает вам, что его последними словами были: «{lastwords}»" -L.search_armor = "Он носил нестандартный бронежилет." -L.search_disg = "Он носил устройство, которое могло скрыть его личность." -L.search_radar = "Он носил некое подобие радара, которое больше не работает." -L.search_c4 = "В кармане вы нашли записку. В ней сказано, что можно безопасно обезвредить бомбу, перерезав {num}-й провод." - -L.search_dmg_crush = "Много костей было сломано. Видимо, удар чего-то тяжёлого послужил причиной смерти." -L.search_dmg_bullet = "Очевидно, его застрелили." -L.search_dmg_fall = "Он разбился насмерть." -L.search_dmg_boom = "Полученные ранения и опалённая одежда свидетельствуют о том, что причиной смерти был взрыв." -L.search_dmg_club = "Всё тело в синяках и побоях. Его явно забили до смерти." -L.search_dmg_drown = "На теле видны явные признаки утопления." -L.search_dmg_stab = "Он был зарезан и умер, прежде чем истёк кровью." -L.search_dmg_burn = "Здесь пахнет жареным террористом..." -L.search_dmg_tele = "Похоже, ДНК было зашифровано тахионным излучением!" -L.search_dmg_car = "Когда этот террорист переходил дорогу, его переехал лихач." -L.search_dmg_other = "Невозможно определить конкретную причину смерти этого террориста." - -L.search_weapon = "Похоже, для убийства использовался (-ась) {weapon}." -L.search_head = "Смертельным ранением был выстрел в голову. Не было времени на крики." -L.search_time = "Он умер примерно за {time} до того, как вы его осмотрели." -L.search_dna = "Соберите образец ДНК убийцы с помощью Сканера ДНК. Образец ДНК разложится примерно через {time}." - -L.search_kills1 = "Вы нашли список убийств, подтверждающий смерть {player}." -L.search_kills2 = "Вы нашли список убийств, подтверждающий смерть:" -L.search_eyes = "Используя свои детективные навыки вы выяснили, что последним, кого он видел, был {player}. Убийца или совпадение?" - -- Scoreboard L.sb_playing = "Вы играете на..." L.sb_mapchange = "Карта сменится через {num} раунд (а/ов) или {time}" @@ -270,7 +227,6 @@ L.item_disg_desc = [[ Включить/выключить Маскировку можно во вкладке «Маскировка» или нажав Numpad Enter.]] -- C4 -L.c4_hint = "Нажмите {usekey}, чтобы заложить/обезвредить." L.c4_disarm_warn = "С4, заложенная вами, была обезврежена." L.c4_armed = "Вы заложили бомбу." L.c4_disarmed = "Вы успешно обезвредили бомбу." @@ -291,6 +247,7 @@ L.c4_remove_destroy2 = "Подтвердить" L.c4_disarm = "Обезвредить" L.c4_disarm_cut = "Нажмите, чтобы перерезать {num}-й провод." +L.c4_disarm_t = "Перережьте провод, чтобы обезвредить бомбу. Для предателей любой провод безопасен. Невиновным это не так просто!" L.c4_disarm_owned = "Перережьте провод, чтобы обезвредить бомбу. Это ваша бомба, поэтому любой провод безопасен." L.c4_disarm_other = "Перережьте безопасный провод, чтобы обезвредить бомбу. Она взорвётся, если вы ошибётесь!" @@ -299,7 +256,6 @@ L.c4_status_disarmed = "ОБЕЗВРЕ-\nЖЕНА" -- Visualizer L.vis_name = "Визуализатор" -L.vis_hint = "Нажмите {usekey}, чтобы подобрать. (только для детективов)" L.vis_desc = [[ Устройство, визуализирующее сцену преступления. @@ -308,7 +264,6 @@ L.vis_desc = [[ -- Decoy L.decoy_name = "Приманка" -L.decoy_no_room = "Вы не можете подобрать эту Приманку." L.decoy_broken = "Ваша Приманка уничтожена!" L.decoy_short_desc = "Эта Приманка создаёт фальшивую метку на радарах других команд." @@ -319,7 +274,6 @@ L.decoy_desc = [[ -- Defuser L.defuser_name = "Набор сапёра" -L.defuser_help = "{primaryfire}: обезвредить С4." L.defuser_desc = [[ Мгновенно обезвреживает С4. @@ -338,7 +292,6 @@ L.flare_desc = [[ L.hstation_name = "Лечебная станция" L.hstation_broken = "Ваша Лечебная станция уничтожена!" -L.hstation_help = "{primaryfire}: разместить Лечебную станцию." L.hstation_desc = [[ Позволяет игрокам восстанавливать здоровье. @@ -362,7 +315,6 @@ L.polter_desc = [[ -- Radio L.radio_broken = "Ваше Радио уничтожено!" -L.radio_help_pri = "{primaryfire}: поместить Радио." L.radio_desc = [[ Воспроизводит звуки для отвлечения или обмана. @@ -408,7 +360,7 @@ L.dna_killer = "Вы собрали образец ДНК убийцы с это L.dna_duplicate = "Совпадение! У вас уже есть этот образец ДНК в сканере." L.dna_no_killer = "Образец ДНК не может быть собран (убийца покинул сервер?)." L.dna_armed = "Бомба все ещё работает! Сначала обезвредьте её!" -L.dna_object = "Собрано новых образцов ДНК: {num}." +--L.dna_object = "Collected a sample of the last owner from the object." L.dna_gone = "ДНК не обнаружено в этой области." L.dna_desc = [[ @@ -477,7 +429,7 @@ L.hp_wounded = "Ранен" L.hp_badwnd = "Тяжело ранен" L.hp_death = "При смерти" --- TargetID karma status +-- TargetID Karma status L.karma_max = "Уважаемый" L.karma_high = "Малоуважаемый" L.karma_med = "Легкомысленный" @@ -486,15 +438,13 @@ L.karma_min = "Безответственный" -- TargetID misc L.corpse = "Тело" -L.corpse_hint = "[{usekey}]: осмотреть тело. [{walkkey} + {usekey}]: скрытно осмотреть тело." +--L.corpse_hint = "Press [{usekey}] to search and confirm. [{walkkey} + {usekey}] to search covertly." L.target_disg = "(под маскировкой)" L.target_unid = "Неопознанное тело" L.target_unknown = "Террорист" -L.target_credits = "Осмотрите тело, чтобы получить неиспользованные кредиты." - --- Traitor buttons (HUD buttons with hand icons that only traitors can see) +-- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "Одноразовое использование." L.tbut_reuse = "Многоразовое использование." L.tbut_retime = "Можно использовать повторно через {num} сек." @@ -508,7 +458,6 @@ L.mute_off = "Никто не заглушён" -- Spectators and prop possession L.punch_title = "ТОЛКОМЕТР" -L.punch_help = "Клавиши управления или прыжок: толкнуть предмет. Клавиша приседания: покинуть предмет." L.punch_bonus = "Ваш плохой счёт понизил предел толкометра на {num}." L.punch_malus = "Ваш хороший счёт повысил предел толкометра на {num}!" @@ -808,7 +757,7 @@ L.aw_flg2_title = "Сигнальная ракета обозначает ого L.aw_flg2_text = "рассказал {num} людям об опасности ношения легковоспламеняющейся одежды." L.aw_hug1_title = "Большой разброс" -L.aw_hug1_text = "был в гармонии со своим H.U.G.E, умудрившись как-то заставить свои пули убить 4 человек." +--L.aw_hug1_text = "was in tune with their H.U.G.E, somehow managing to make their bullets hit {num} people." L.aw_hug2_title = "Терпеливая пара" L.aw_hug2_text = "продолжал стрелять из H.U.G.E. и обнаружил, что терпение вознаградило его {num} убийствами." @@ -936,16 +885,13 @@ L.shop_role_select = "Выберите роль" L.shop_role_selected = "Выбран магазин роли {role}!" L.shop_search = "Поиск" -L.spec_help = "Щёлкните мышью, чтобы наблюдать за игроками, или нажмите {usekey}, чтобы вселиться в предмет, на который вы смотрите." -L.spec_help2 = "Чтобы покинуть режим наблюдения, откройте меню, нажав {helpkey}, перейдите в раздел «Игра» и уберите галочку с режима наблюдения." - -- 2019-10-19 L.drop_ammo_prevented = "Что-то не даёт вам выбросить боеприпасы." -- 2019-10-28 L.target_c4 = "[{usekey}]: открыть меню C4." L.target_c4_armed = "[{usekey}]: обезвредить C4." -L.target_c4_armed_defuser = "[{usekey}]: использовать Набор сапёра." +L.target_c4_armed_defuser = "[{primaryfire}]: использовать Набор сапёра." L.target_c4_not_disarmable = "Нельзя обезвредить C4 живого напарника." L.c4_short_desc = "Кое-что крайне взрывоопасное." @@ -953,16 +899,15 @@ L.target_pickup = "[{usekey}]: подобрать." L.target_slot_info = "Слот: {slot}" L.target_pickup_weapon = "[{usekey}]: подобрать оружие." L.target_switch_weapon = "[{usekey}]: заменить текущее оружие на это." -L.target_pickup_weapon_hidden = " [{usekey} + {walkkey}]: подобрать незаметно." -L.target_switch_weapon_hidden = " [{usekey} + {walkkey}]: заменить незаметно." +L.target_pickup_weapon_hidden = " [{walkkey} + {usekey}]: подобрать незаметно." +L.target_switch_weapon_hidden = " [{walkkey} + {usekey}]: заменить незаметно." L.target_switch_weapon_nospace = "Нет свободного слота для этого оружия." L.target_switch_drop_weapon_info = "Из слота {slot} будет выброшен (-а) {name}." L.target_switch_drop_weapon_info_noslot = "В слоте {slot} нет выбрасываемого оружия." -L.corpse_searched_by_detective = "Это тело осмотрено детективом." +--L.corpse_searched_by_detective = "This corpse was searched by a public policing role" L.corpse_too_far_away = "Тело слишком далеко." -L.radio_pickup_wrong_team = "Вы не можете подобрать Радио другой команды." L.radio_short_desc = "Звуки выстрелов для меня словно музыка" L.hstation_subtitle = "[{usekey}]: восстановить здоровье." @@ -1007,7 +952,6 @@ L.mute_team = "Команда «{team}» заглушена." L.door_auto_closes = "Эта дверь закрывается автоматически." L.door_open_touch = "Подойдите к двери, чтобы открыть её." L.door_open_touch_and_use = "Подойдите к двери или нажмите [{usekey}], чтобы открыть её." -L.hud_health = "Здоровье" -- 2020-03-09 L.help_title = "Руководство и настройки" @@ -1029,7 +973,7 @@ L.menu_guide_description = "Поможет вам освоиться в TTT2 и L.menu_bindings_description = "Назначьте клавиши на различные функции TTT2 и его дополнений по своему вкусу." L.menu_language_description = "Выберите язык режима игры." L.menu_appearance_description = "Настройки внешний вида и производительности интерфейса." -L.menu_gameplay_description = "Настройки некоторых функций и избегания ролей." +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." L.menu_addons_description = "Настройте локальные дополнения по своему вкусу." L.menu_legacy_description = "Панель с конвертированными вкладками из оригинального TTT, которые должны быть перенесены на новую систему." L.menu_administration_description = "Общие настройки интерфейсов, магазинов и прочего." @@ -1053,10 +997,8 @@ L.submenu_appearance_crosshair_title = "Прицел" L.submenu_appearance_dmgindicator_title = "Индикатор урона" L.submenu_appearance_performance_title = "Производительность" L.submenu_appearance_interface_title = "Интерфейс" -L.submenu_appearance_miscellaneous_title = "Прочее" L.submenu_gameplay_general_title = "Общее" -L.submenu_gameplay_avoidroles_title = "Выбор избегаемых ролей" L.submenu_administration_hud_title = "Настройки интерфейса" L.submenu_administration_randomshop_title = "Случайный магазин" @@ -1093,16 +1035,11 @@ L.label_shop_show_slot = "Отображать метку слота" L.label_shop_show_custom = "Отображать метку предмета сервера" L.label_shop_show_fav = "Отображать метку избранного предмета" L.label_crosshair_enable = "Включить прицел." -L.label_crosshair_gap_enable = "Включить пользовательский зазор прицела." -L.label_crosshair_gap = "Пользовательский зазор" L.label_crosshair_opacity = "Непрозрачность прицела" L.label_crosshair_ironsight_opacity = "Непрозрачность прицела при прицеливании" L.label_crosshair_size = "Размер прицела" L.label_crosshair_thickness = "Толщина прицела" L.label_crosshair_thickness_outline = "Толщина обводки прицела" -L.label_crosshair_static_enable = "Включить статичный прицел." -L.label_crosshair_dot_enable = "Включить точку прицела." -L.label_crosshair_lines_enable = "Включить линии прицела." L.label_crosshair_scale_enable = "Включить разные размеры прицела для разного оружия." L.label_crosshair_ironsight_low_enabled = "Опускать оружие при прицеливании." L.label_damage_indicator_enable = "Включить индикатор урона." @@ -1122,13 +1059,10 @@ L.label_gameplay_specmode = "Режим наблюдения (всегда бы L.label_gameplay_fastsw = "Быстрая смена оружия." L.label_gameplay_hold_aim = "Включить прицеливание при удерживании." L.label_gameplay_mute = "Заглушать живых игроков после смерти." -L.label_gameplay_dtsprint_enable = "Включить ускорение двойным нажатием." -L.label_gameplay_dtsprint_anykey = "Продолжать ускорение по двойному нажатию до прекращения движения." L.label_hud_default = "Интерфейс по умолчанию" L.label_hud_force = "Принудительно назначаемый интерфейс" L.label_bind_weaponswitch = "Смена оружия" -L.label_bind_sprint = "Ускорение" L.label_bind_voice = "Глобальный голосовой чат" L.label_bind_voice_team = "Командный голосовой чат" @@ -1152,7 +1086,6 @@ L.header_damage_indicator = "Настройки индикатора урона" L.header_performance_settings = "Настройки производительности" L.header_interface_settings = "Настройки интерфейса" L.header_gameplay_settings = "Настройки игры" -L.header_roleselection = "Выбор избегаемых ролей" L.header_hud_administration = "Выбор интерфейса по умолчанию и принудительного интерфейса" L.header_hud_enabled = "Включение или выключение интерфейсов" @@ -1199,11 +1132,7 @@ L.hud_revival_time = "{time} сек." L.door_destructible = "Эта дверь разрушаема (прочность: {health})." -- 2020-05-28 -L.confirm_detective_only = "Только детективы могут подтверждать смерть." -L.inspect_detective_only = "Только детективы могут осматривать тела." -L.corpse_hint_no_inspect = "Только детективы могут осмотреть это тело." -L.corpse_hint_inspect_only = "[{usekey}] Осмотреть тело. Только детективы могут подтвердить смерть." -L.corpse_hint_inspect_only_credits = "[{usekey}] Получить кредиты. Только детективы могут осмотреть это тело." +--L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." -- 2020-06-04 L.label_bind_disguiser = "Переключить Маскировку." @@ -1218,7 +1147,6 @@ L.binoc_help_sec = "Изменить уровень приближения." L.vis_help_pri = "Бросить активированное устройство." -L.decoy_help_pri = "Установить Приманку." -- 2020-08-07 L.pickup_error_spec = "Вы не можете подобрать это за наблюдателя." @@ -1264,8 +1192,8 @@ L.tooltip_bodyfound_score = "Нахождение тела: {score}" L.finish_score_alive_teammates = "Живые товарищи:" L.finish_score_alive_all = "Живые игроки:" -L.finish_score_dead_enemies = "Мёртвые противники:" L.finish_score_timelimit = "Истечение времени:" +L.finish_score_dead_enemies = "Мёртвые противники:" L.kill_score = "Убийство:" L.bodyfound_score = "Нахождение тела:" @@ -1442,7 +1370,7 @@ L.spawn_ammo_rifle = "Точка боеприпасов: снайперские" L.spawn_ammo_shotgun = "Точка боеприпасов: дробовиков" L.spawn_player_random = "Точка случайного игрока" -L.spawn_weapon_ammo = " (Боеприпасов: {ammo})" +L.spawn_weapon_ammo = "(Боеприпасов: {ammo})" L.spawn_weapon_edit_ammo = "[{walkkey}] + [{primaryfire} или {secondaryfire}]: увеличить или уменьшить боеприпасы этой точки оружия" @@ -1733,8 +1661,6 @@ L.label_bots_are_spectators = "Боты всегда наблюдатели" L.label_tbutton_admin_show = "Показывать кнопки предателей администраторам" L.label_ragdoll_carrying = "Включить поднятие рэгдоллов" L.label_prop_throwing = "Включить метание предметов" -L.label_ragdoll_pinning = "Включить прикрепление тел для не невиновных ролей" -L.label_ragdoll_pinning_innocents = "Включить прикрепление для невиновных ролей" L.label_weapon_carrying = "Включить поднятие оружия" L.label_weapon_carrying_range = "Дальность поднятого оружия" L.label_prop_carrying_force = "Сила поднятия предметов" @@ -1764,10 +1690,8 @@ L.label_session_limits_enabled = "Включить лимиты сеанса" L.label_spectator_chat = "Включить общение наблюдателей со всеми" L.label_lastwords_chatprint = "Включить вывод последних слов в чат при смерти во время написания" L.label_identify_body_woconfirm = "Опознавать тела без нажатия кнопки подтверждения" -L.label_announce_body_found = "Объявлять о нахождении тел" +--L.label_announce_body_found = "Announce that a body was found when the body was confirmed" L.label_confirm_killlist = "Объявлять список убитых с подтверждённых тел" -L.label_inspect_detective_only = "Ограничить осмотр тел только полицейским ролям" -L.label_confirm_detective_only = "Ограничить подтверждение тел только полицейским ролям" L.label_dyingshot = "Стрелять в момент смерти, если кто-то под прицелом [экспериментальное]" L.label_armor_block_headshots = "Включить блокирование выстрелов в голову бронёй" L.label_armor_block_blastdmg = "Включить блокирование урона от взрывов бронёй" @@ -1826,7 +1750,6 @@ L.label_sprint_enabled = "Включить ускорение" L.label_sprint_max = "Максимальная выносливость ускорения" L.label_sprint_stamina_consumption = "Коэффициент потребления выносливости" L.label_sprint_stamina_regeneration = "Коэффициент восстановления выносливости" -L.label_sprint_crosshair = "Показывать прицел при ускорении" L.label_crowbar_unlocks = "Основная атака может использоваться для взаимодействия (например, открытия)" L.label_crowbar_pushforce = "Сила толчка монтировки" @@ -1862,4 +1785,411 @@ L.sb_rank_tooltip_streamer = "Стример" L.sb_rank_tooltip_heroes = "Герои TTT2" L.sb_rank_tooltip_team = "Команда" -L.tbut_adminarea = "Администраторская зона:" \ No newline at end of file +L.tbut_adminarea = "Администраторская зона:" + +-- 2023-08-10 +--L.equipmenteditor_name_damage_scaling = "Damage Scaling" + +-- 2023-08-11 +--L.equipmenteditor_name_allow_drop = "Allow Drop" +--L.equipmenteditor_desc_allow_drop = "If enabled, the equipment can be dropped freely by the player." + +--L.equipmenteditor_name_drop_on_death_type = "Drop on Death" +--L.equipmenteditor_desc_drop_on_death_type = "Attempt overriding the action taken for whether the equipment is dropped on player's death." + +--L.drop_on_death_type_default = "Default (weapon-defined)" +--L.drop_on_death_type_force = "Force Drop on Death" +--L.drop_on_death_type_deny = "Deny Drop on Death" + +-- 2023-08-26 +--L.equipmenteditor_name_kind = "Equipment Slot" +--L.equipmenteditor_desc_kind = "The inventory slot the equipment will occupy." + +--L.slot_weapon_melee = "Melee Slot" +--L.slot_weapon_pistol = "Pistol Slot" +--L.slot_weapon_heavy = "Heavy Slot" +--L.slot_weapon_nade = "Grenade Slot" +--L.slot_weapon_carry = "Carry Slot" +--L.slot_weapon_unarmed = "Unarmed Slot" +--L.slot_weapon_special = "Special Slot" +--L.slot_weapon_extra = "Extra Slot" +--L.slot_weapon_class = "Class Slot" + +-- 2023-10-04 +--L.label_voice_duck_spectator = "Duck spectator voices" +--L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +--L.label_voice_scaling = "Voice Volume Scaling Mode" +--L.label_voice_scaling_mode_linear = "Linear" +--L.label_voice_scaling_mode_power4 = "Power 4" +--L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "Результаты осмотра тела - {player}" +L.search_info = "Информация" +L.search_confirm = "Подтвердить смерть" +--L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +--L.search_take_credits = "Take {credits} Credit(s)" +--L.search_confirm_forbidden = "Confirm forbidden" +--L.search_confirmed = "Death Confirmed" +--L.search_call = "Report Death" +--L.search_called = "Death Reported" + +--L.search_team_role_unknown = "???" + +L.search_words = "Что-то подсказывает вам, что его последними словами были: «{lastwords}»" +L.search_armor = "Он носил нестандартный бронежилет." +L.search_disguiser = "Он носил устройство, которое могло скрыть его личность." +L.search_radar = "Он носил некое подобие радара, которое больше не работает." +L.search_c4 = "В кармане вы нашли записку. В ней сказано, что можно безопасно обезвредить бомбу, перерезав {num}-й провод." + +L.search_dmg_crush = "Много костей было сломано. Видимо, удар чего-то тяжёлого послужил причиной смерти." +L.search_dmg_bullet = "Очевидно, его застрелили." +L.search_dmg_fall = "Он разбился насмерть." +L.search_dmg_boom = "Полученные ранения и опалённая одежда свидетельствуют о том, что причиной смерти был взрыв." +L.search_dmg_club = "Всё тело в синяках и побоях. Его явно забили до смерти." +L.search_dmg_drown = "На теле видны явные признаки утопления." +L.search_dmg_stab = "Он был зарезан и умер, прежде чем истёк кровью." +L.search_dmg_burn = "Здесь пахнет жареным террористом..." +L.search_dmg_teleport = "Похоже, ДНК было зашифровано тахионным излучением!" +L.search_dmg_car = "Когда этот террорист переходил дорогу, его переехал лихач." +L.search_dmg_other = "Невозможно определить конкретную причину смерти этого террориста." + +--L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +--L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +--L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +--L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +--L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +--L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +--L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +--L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +--L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +--L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +--L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +--L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +--L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +--L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +--L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +--L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +--L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +--L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +--L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +--L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +--L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +--L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +--L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +--L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +--L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "Похоже, для убийства использовался (-ась) {weapon}." +L.search_head = "Смертельным ранением был выстрел в голову. Не было времени на крики." +--L.search_time = "They died a while before you conducted the search." +--L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "Вы нашли список убийств, подтверждающий смерть {player}." +L.search_kills2 = "Вы нашли список убийств, подтверждающий смерть: {player}" +L.search_eyes = "Используя свои детективные навыки вы выяснили, что последним, кого он видел, был {player}. Убийца или совпадение?" + +--L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +--L.search_kill_distance_point_blank = "It was a point blank attack." +--L.search_kill_distance_close = "The attack came from a short distance." +--L.search_kill_distance_far = "The victim was attacked from a long distance away." + +--L.search_kill_from_front = "The victim was shot from the front." +--L.search_kill_from_back = "The victim was shot from behind." +--L.search_kill_from_side = "The victim was shot from the side." + +--L.search_hitgroup_head = "The projectile was found in their head." +--L.search_hitgroup_chest = "The projectile was found in their chest." +--L.search_hitgroup_stomach = "The projectile was found in their stomach." +--L.search_hitgroup_rightarm = "The projectile was found in their right arm." +--L.search_hitgroup_leftarm = "The projectile was found in their left arm." +--L.search_hitgroup_rightleg = "The projectile was found in their right leg." +--L.search_hitgroup_leftleg = "The projectile was found in their left leg." +--L.search_hitgroup_gear = "The projectile was found in their hip." + +--L.search_policingrole_report_confirm = [[ +--A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +--L.search_policingrole_confirm_disabled_1 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +--L.search_policingrole_confirm_disabled_2 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know! +--You can see the information in here after they confirmed it.]] +--L.search_spec = [[ +--As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +--L.search_title_words = "Victim's last words" +--L.search_title_c4 = "Defusion mishap" +--L.search_title_dmg_crush = "Crush damage ({amount} HP)" +--L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +--L.search_title_dmg_fall = "Fall damage ({amount} HP)" +--L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +--L.search_title_dmg_club = "Club damage ({amount} HP)" +--L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +--L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +--L.search_title_dmg_burn = "Burning damage ({amount} HP)" +--L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +--L.search_title_dmg_car = "Car accident ({amount} HP)" +--L.search_title_dmg_other = "Unknown damage ({amount} HP)" +--L.search_title_time = "Death time" +--L.search_title_dna = "DNA sample decay" +--L.search_title_kills = "The victim's kill list" +--L.search_title_eyes = "The killer's shadow" +--L.search_title_floor = "Floor of the crime scene" +--L.search_title_credits = "{credits} Equipment credit(s)" +--L.search_title_water = "Water level {level}" +--L.search_title_policingrole_report_confirm = "Confirm to report death" +--L.search_title_policingrole_confirm_disabled = "Report corpse" +--L.search_title_spectator = "You are a spectator" + +--L.target_credits_on_confirm = "Confirm to receive unspent credits" +--L.target_credits_on_search = "Search to receive unspent credits" +--L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +--L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +--L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +--L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +--L.label_inspect_confirm_mode = "Select body search mode" +--L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +--L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +--L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +--L.help_inspect_confirm_mode = [[ +--There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. +-- +--mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. +-- +--mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. +-- +--mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +--L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +--L.header_hud_elements_customize = "Customize the HUD-Elements" +--L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +--L.help_keyhelp = [[ +--Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: +-- +--Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +--Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +--Equipment: Some equipment items have their own bindings, these are shown in this category. +-- +--Disabled categories are still shown when the scoreboard is visible]] + +--L.label_keyhelp_show_core = "Enable always showing the core bindings" +--L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +--L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +--L.header_interface_keys = "Key helper settings" +--L.header_interface_wepswitch = "Weapon switch UI settings" + +--L.label_keyhelper_help = "open gamemode menu" +--L.label_keyhelper_mutespec = "cycle spectator voice mode" +--L.label_keyhelper_shop = "open equipment shop" +--L.label_keyhelper_show_pointer = "free mouse pointer" +--L.label_keyhelper_possess_focus_entity = "possess focused entity" +--L.label_keyhelper_spec_focus_player = "spectate focused player" +--L.label_keyhelper_spec_previous_player = "previous player" +--L.label_keyhelper_spec_next_player = "next player" +--L.label_keyhelper_spec_player = "spectate random player" +--L.label_keyhelper_possession_jump = "prop: jump" +--L.label_keyhelper_possession_left = "prop: left" +--L.label_keyhelper_possession_right = "prop: right" +--L.label_keyhelper_possession_forward = "prop: forward" +--L.label_keyhelper_possession_backward = "prop: backward" +--L.label_keyhelper_free_roam = "leave object and roam free" +--L.label_keyhelper_flashlight = "toggle flashlight" +--L.label_keyhelper_quickchat = "open quickchat" +--L.label_keyhelper_voice_global = "global voice chat" +--L.label_keyhelper_voice_team = "team voice chat" +--L.label_keyhelper_chat_global = "global chat" +--L.label_keyhelper_chat_team = "team chat" +--L.label_keyhelper_show_all = "show all" +--L.label_keyhelper_disguiser = "toggle disguiser" +--L.label_keyhelper_save_exit = "save and exit" +--L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +--L.item_armor_reinforced = "Reinforced Armor" +--L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +--L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +--L.status_speed_name = "Speed Multiplier" +--L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +--L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +--L.status_on = "on" +--L.status_off = "off" + +--L.crowbar_help_primary = "Attack" +--L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +--L.help_HUD_enable_description = [[ +--Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +--L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +--L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +--L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +--L.header_soundeffect_settings = "Sound Effects" +--L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +--L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +--L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +--L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +--L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +--L.help_prop_spec_dash = [[ +--Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. +-- +--This variable is a multiplier of the push force.]] +--L.label_spec_prop_dash = "Dash force multiplier" +--L.label_keyhelper_possession_dash = "prop: dash in view direction" +--L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +--L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +--L.c4_help_primary = "Place the C4" +--L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +--L.magneto_help_primary = "Push entity" +--L.magneto_help_secondary = "Pull / pickup entity" +--L.knife_help_primary = "Stab" +--L.knife_help_secondary = "Throw knife" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +--L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +--L.newton_force = "FORCE" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +--L.beacon_help_pri = "Throw Beacon on the ground" +--L.beacon_help_sec = "Stick Beacon to surface" +--L.beacon_name = "Beacon" +--L.beacon_desc = [[ +--Broadcasts player locations to everyone in a sphere around this beacon. +-- +--Use to keep track of locations on the map that are hard to see.]] + +--L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +--L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +--L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +--L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +--L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder} подтверждает смерть {victim}." +--L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +--L.builtin_marker = "Built-in." +--L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +--L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +--L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +--L.submenu_gameplay_accessibility_title = "Accessibility" + +--L.header_accessibility_settings = "Accessibility Settings" + +--L.label_enable_dynamic_fov = "Enable dynamic FOV change" +--L.label_enable_bobbing = "Enable view bobbing" +--L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +--L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +--L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +--L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +--For a shotgun, this would affect each pellet. +--For a rifle, this would affect just the bullet. +--For the poltergeist, this would affect each "thump" and the final explosion. +-- +--0.5 = Deal half the amount of damage. +--2 = Deal twice the amount of damage. +-- +--Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +--L.binoc_help_reload = "Clear target." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +--L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +--L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +--L.label_crosshair_mode = "Crosshair mode" +--L.label_crosshair_static_length = "Enable static crosshair line length" + +--L.choice_crosshair_mode_0 = "Lines and dot" +--L.choice_crosshair_mode_1 = "Lines only" +--L.choice_crosshair_mode_2 = "Dot only" + +--L.help_crosshair_scale_enable = [[ +--Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. +-- +--If the line length is kept static, only the gap scales with cone changes.]] + +--L.header_weapon_settings = "Weapon Settings" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +L.decoy_help_primary = "Установить Приманку" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/sv.lua b/lua/terrortown/lang/sv.lua new file mode 100644 index 000000000..2d79ddcb9 --- /dev/null +++ b/lua/terrortown/lang/sv.lua @@ -0,0 +1,2193 @@ +-- Swedish language strings + +local L = LANG.CreateLanguage("sv") + +-- Compatibility language name that might be removed soon. +-- the alias name is based on the original TTT language name: +-- https://github.com/Facepunch/garrysmod/blob/master/garrysmod/gamemodes/terrortown/gamemode/lang/swedish.lua +L.__alias = "Svenska" + +L.lang_name = "Svenska (Swedish)" + +-- General text used in various places +L.traitor = "Förrädare" +L.detective = "Detektiv" +L.innocent = "Oskyldig" +L.last_words = "Sista Ord" + +L.terrorists = "Terrorister" +L.spectators = "Åskådare" + +--L.nones = "No Team" +--L.innocents = "Team Innocent" +--L.traitors = "Team Traitor" + +-- Round status messages +L.round_minplayers = "För få spelare för att påbörja ny runda..." +L.round_voting = "Omröstning pågår, fördröjer ny runda med {num} sekunder..." +L.round_begintime = "En ny runda påbörjas om {num} sekunder. Bered dig." +L.round_selected = "Förrädarna har blivit utvalda." +L.round_started = "Rundan har påbörjats!" +L.round_restart = "Rundan har blivit omstartad av en admin." + +L.round_traitors_one = "Förrädare, du står ensam" +L.round_traitors_more = "Förrädare, detta är dina lagkamrater: {names}" + +L.win_time = "Tiden har tagit slut. Förrädarna har förlorat." +--L.win_traitors = "The Traitors have won!" +--L.win_innocents = "The Innos have won!" +--L.win_nones = "No-one won!" +L.win_showreport = "Låt oss ta en titt på rund-rapporten i {num} sekunder." + +--L.limit_round = "Round limit reached. The next map will load soon." +--L.limit_time = "Time limit reached. The next map will load soon." +--L.limit_left = "{num} round(s) or {time} minutes remaining before the map changes." + +-- Credit awards +--L.credit_all = "Your team have been awarded {num} equipment credit(s) for your performance." +L.credit_kill = "Du har blivit tilldelad {num} kredit(er) för att ha dödat en {role}." + +-- Karma +L.karma_dmg_full = "Din Karma är {amount}, så du utdelar full skada denna runda!" +L.karma_dmg_other = "Din Karma är {amount}. Till följd av detta är skadan du utdelar reducerad med {num}%" + +-- Body identification messages +L.body_found = "{finder} fann {victim}s kropp. {role}" +--L.body_found_team = "{finder} found the body of {victim}. {role} ({team})" + +-- The {role} in body_found will be replaced by one of the following: +--L.body_found_traitor = "They were a Traitor!" +--L.body_found_det = "They were a Detective." +--L.body_found_inno = "They were Innocent." + +L.body_call = "{player} kallade en Detektiv till {victim}s kropp!" +L.body_call_error = "Du måste bekräfta denna spelarens död för att kunna kalla på en Detektiv!" + +L.body_burning = "Aj! Den här kroppen brinner!" +L.body_credits = "Du fann {num} kredit(er) på kroppen!" + +-- Menus and windows +L.close = "Stäng" +L.cancel = "Avbryt" + +-- For navigation buttons +L.next = "Nästa" +L.prev = "Föregående" + +-- Equipment buying menu +L.equip_title = "Verktyg" +L.equip_tabtitle = "Beställ verktyg" + +L.equip_status = "Beställnings-status" +L.equip_cost = "Du har {num} kredit(er) kvar." +L.equip_help_cost = "Varje verktyg du köper kostar 1 kredit." + +L.equip_help_carry = "Du kan endast köpa verktyg för vilka du har rum." +L.equip_carry = "Du kan bära detta verktyg." +L.equip_carry_own = "Du bär redan detta verktyg." +L.equip_carry_slot = "Du har redan ett vapen i nummer {slot}." +--L.equip_carry_minplayers = "There are not enough players on the server to enable this weapon." + +L.equip_help_stock = "Vissa verktyg kan du endast köpa en utav varje runda." +L.equip_stock_deny = "Detta verktyg har tagit slut." +L.equip_stock_ok = "Detta verktyg finns tillgängligt." + +L.equip_custom = "Egengjort verktyg tillagt av denna server." + +L.equip_spec_name = "Namn:" +L.equip_spec_type = "Typ:" +L.equip_spec_desc = "Beskrivning:" + +L.equip_confirm = "Köp föremål" + +-- Disguiser tab in equipment menu +L.disg_name = "Förklädare" +L.disg_menutitle = "Förklädningskontroll" +L.disg_not_owned = "Du har ingen Förklädare!" +L.disg_enable = "Aktivera förklädare" + +L.disg_help1 = "När din förklädare är påslagen visas inte ditt namn, din hälsa eller din karma när någon tittar på dig. Du syns heller inte på en Detektivs radar." +L.disg_help2 = "Tryck på Numpad Enter för att sätta på/stänga av förklädnaden utan att använda menyn. Du kan även binda en annan tangent till 'ttt_toggle_disguise' genom att använda konsollen." + +-- Radar tab in equipment menu +L.radar_name = "Radar" +L.radar_menutitle = "Radar-kontroll" +L.radar_not_owned = "Du har ingen Radar!" +L.radar_scan = "Utför skanning" +L.radar_auto = "Auto-repetera skanning" +L.radar_help = "Skannings-resultat visas i {num} sekunder. Efter detta laddas Radarn om och kan användas igen." +L.radar_charging = "Din Radar laddar fortfarande!" + +-- Transfer tab in equipment menu +L.xfer_name = "Överför" +L.xfer_menutitle = "Överför krediter" +L.xfer_send = "Sänd en kredit" + +L.xfer_no_recip = "Mottagare ej giltig avbryter kredit-överföring." +L.xfer_no_credits = "Du har inga krediter att ge!" +L.xfer_success = "Kredit-överföring till {player} utförd." +L.xfer_received = "{player} har gett dig {num} krediter." + +-- Radio tab in equipment menu +L.radio_name = "Radio" +L.radio_help = "Tryck på en knapp för att spela det ljudet på Radion." +L.radio_notplaced = "Du måste placera Radion för att kunna spela ljud på den." + +-- Radio soundboard buttons +L.radio_button_scream = "Skrik" +L.radio_button_expl = "Explosion" +L.radio_button_pistol = "Pistol-skott" +L.radio_button_m16 = "M16-skott" +L.radio_button_deagle = "Deagle-skott" +L.radio_button_mac10 = "MAC10-skott" +L.radio_button_shotgun = "Hagelgevär-skott" +L.radio_button_rifle = "Gevärsskott" +L.radio_button_huge = "H.U.G.E-salva" +L.radio_button_c4 = "C4-pip" +L.radio_button_burn = "Brand" +L.radio_button_steps = "Fotsteg" + +-- Intro screen shown after joining +L.intro_help = "Om du är ny till detta spel, tryck F1 för att få instruktioner!" + +-- Radiocommands/quickchat +L.quick_title = "Snabb-knappar" + +L.quick_yes = "Ja." +L.quick_no = "Nej." +L.quick_help = "Hjälp!" +L.quick_imwith = "Jag är med {player}." +L.quick_see = "Jag ser {player}." +L.quick_suspect = "{player} beter sig skumt." +L.quick_traitor = "{player} är en Förrädare!" +L.quick_inno = "{player} är oskyldig." +L.quick_check = "Lever någon fortfarande?" + +-- {player} in the quickchat text normally becomes a player nickname, but can +-- also be one of the below. Keep these lowercase. +L.quick_nobody = "ingen" +L.quick_disg = "någon i förklädnad" +L.quick_corpse = "ett oidentifierat lik" +L.quick_corpse_id = "{player}s lik" + +-- Scoreboard +L.sb_playing = "Du spelar på..." +L.sb_mapchange = "Kartan ändras om {num} rundor eller om {time}" +--L.sb_mapchange_disabled = "Session limits are disabled." + +L.sb_mia = "Saknad I Strid" +L.sb_confirmed = "Bekräftad Död" + +L.sb_ping = "Ping" +L.sb_deaths = "Döda" +L.sb_score = "Poäng" +L.sb_karma = "Karma" + +L.sb_info_help = "Om du kollar igenom denna spelares lik kan du se resultaten här." + +L.sb_tag_friend = "VÄN" +L.sb_tag_susp = "MISSTÄNKT" +L.sb_tag_avoid = "UNDVIK" +L.sb_tag_kill = "DÖDA" +L.sb_tag_miss = "SAKNAD" + +-- Equipment actions, like buying and dropping +L.buy_no_stock = "Det här vapnet finns inte tillgängligt: du har redan köpt det den här rundan." +L.buy_pending = "Du har redan en beställning som väntar, vänta tills du får den." +L.buy_received = "Du har fått ditt specialverktyg." + +L.drop_no_room = "Det finns ingen plats här för att släppa ditt vapen!" + +L.disg_turned_on = "Förklädnad aktiverad!" +L.disg_turned_off = "Förklädnad avaktiverad." + +-- Equipment item descriptions +L.item_passive = "Passivt verktyg" +L.item_active = "Aktiv användning-verktyg" +L.item_weapon = "Vapen" + +L.item_armor = "Kroppsrustning" +L.item_armor_desc = [[ +Reducerar skottskador med 30% när du blir träffad. + +Standard-verktyg för Detektiver.]] + +L.item_radar = "Radar" +L.item_radar_desc = [[ +Tillåter dig att skanna efter livstecken. + +Startar automatiskt så fort du köper den. Konfigurera den i Radar-fliken i den här menyn.]] + +L.item_disg = "Förklädare" +L.item_disg_desc = [[ +Döljer din identitet när den är påslagen. Hindrar även att man blir den sista sedda personen av ett offer. + +Slå av/på i Förklädnads-fliken i den här menyn eller tryck Numpad Enter.]] + +-- C4 +L.c4_disarm_warn = "En C4 som du har placerat har blivit desarmerad." +L.c4_armed = "Du lyckades armera bomben." +L.c4_disarmed = "Du lyckades desarmera bomben." +L.c4_no_room = "Du kan ej bära denna C4." + +L.c4_desc = "Kraftfullt tidsbestämt sprängämne." + +L.c4_arm = "Armera C4" +L.c4_arm_timer = "Timer" +L.c4_arm_seconds = "Sekunder tills detonering:" +L.c4_arm_attempts = "Vid desarmeringsförsök kommer {num} av de 6 sladdarna att orsaka en omedelbar detonering vid avklippning." + +L.c4_remove_title = "Avlägsnande" +L.c4_remove_pickup = "Plocka upp C4" +L.c4_remove_destroy1 = "Förstör C4" +L.c4_remove_destroy2 = "Bekräfta: förstör" + +L.c4_disarm = "Desarmera C4" +L.c4_disarm_cut = "Klicka för att klippa av sladd nummer {num}" + +L.c4_disarm_t = "Klipp av en sladd för att desarmera bomben. Eftersom du är en Förrädare är varje sladd säker. De oskylda har det inte lika lätt!" +L.c4_disarm_owned = "Klipp av en sladd för att desarmera bomben. Det är din bomb, så varje sladd kommer att desarmera den." +L.c4_disarm_other = "Klipp av en säker sladd för att desarmera bomben. Den kommer att explodera om du klipper fel!" + +L.c4_status_armed = "ARMERAD" +L.c4_status_disarmed = "DESARMERAD" + +-- Visualizer +L.vis_name = "Visualiserare" + +L.vis_desc = [[ +Verktyg för brottsscens-visualisering. + +Analyserar ett lik för att visa hur offret dog, men enbart om han dog av skottskador.]] + +-- Decoy +L.decoy_name = "Lockbete" +L.decoy_broken = "Ditt Lockbete har förstörts!" + +--L.decoy_short_desc = "This decoy shows a fake radar sign visible for other teams" +--L.decoy_pickup_wrong_team = "You can't pick it up as it belongs to a different team" + +L.decoy_desc = [[ +Visar ett fejkat radar-spår för detektiver, och gör så att deras DNA-skanner visa platsen för Lockbetet om de skannar efter ditt DNA.]] + +-- Defuser +L.defuser_name = "Desarmerare" + +L.defuser_desc = [[ +Desarmerar C4 omedelbart. + +Kan användas oändligt många gånger. C4 märks tydligare om du bär detta verktyg.]] + +-- Flare gun +L.flare_name = "Signalpistol" + +L.flare_desc = [[ +Kan användas till att bränna lik så att de aldrig återfinns. Begränsad ammunition. + +Att bränna ett lik gör ett tydligt ljud.]] + +-- Health station +L.hstation_name = "Hälsostation" + +L.hstation_broken = "Din Hälsostation har blivit förstörd!" + +L.hstation_desc = [[ +Tillåter spelare att helas när den är utplacerad. + +Långsam omladdning. Vem som helst kan använda den, och den kan utsättas för skada. Kan kollas efter DNA-prov av dess användare.]] + +-- Knife +L.knife_name = "Kniv" +L.knife_thrown = "Kastkniv" + +--L.knife_desc = [[ +--Kills wounded targets instantly and silently, but only has a single use. +-- +--Can be thrown using alternate fire.]] + +-- Poltergeist +L.polter_desc = [[ +Placerar enheter på föremål som knuffar omkring dem med våldsam kraft. + +Explosionerna skadar spelare i närheten.]] + +-- Radio +L.radio_broken = "Din Radio har blivit förstörd!" + +L.radio_desc = [[ +Spelar upp distraherande eller bedragande ljud. + +Placera radion någonstans, och spela sedan upp ljud på den genom att använda Radio-fliken i den här menyn.]] + +-- Silenced pistol +L.sipistol_name = "Ljuddämpad Pistol" + +L.sipistol_desc = [[ +Pistol med dämpade ljud. Använder vanlig pistol-ammunition. + +Offer skriker inte när de blir dödade.]] + +-- Newton launcher +L.newton_name = "Avståndsknuffare" + +L.newton_desc = [[ +Knuffa spelare från ett bekvämt avstånd. + +Oändligt med ammunition, men laddar om långsamt.]] + +-- Binoculars +L.binoc_name = "Kikare" + +L.binoc_desc = [[ +Zooma in på lik och identifiera dem från ett långt avstånd. + +Kan användas oändligt många gånger, men identifieringen tar några sekunder.]] + +-- UMP +L.ump_desc = [[ +Experimentell k-pist som desorienterar målen. + +Använder vanlig k-pist-ammunition.]] + +-- DNA scanner +L.dna_name = "DNA-skanner" +L.dna_notfound = "Inget DNA-prov kunde hittas på målet." +L.dna_limit = "Lagringsbegränsningen nådd. Ta bort gamla prov för att lägga till nya." +L.dna_decayed = "Mördarens DNA-prov har förruttnat." +L.dna_killer = "Hittade ett prov av mördarens DNA på liket!" +--L.dna_duplicate = "Match! You already have this DNA sample in your scanner." +L.dna_no_killer = "DNAt kunde inte återfås (har mördaren gått ur spelet?)" +L.dna_armed = "Bomben går fortfarande! Desarmera den först!" +--L.dna_object = "Collected a sample of the last owner from the object." +L.dna_gone = "DNA kunde inte hittas i området. (Har mördaren gått ur spelet?)" + +L.dna_desc = [[ +Samla DNA-prov från saker och använd dem för att hitta dess ägare. + +Använd på färska lik för att få mördarens DNA och söka upp honom.]] + +-- Magneto stick +L.magnet_name = "Magnetstav" +L.magnet_help = "{primaryfire} för att sätta fast kropp på ytan." + +-- Grenades and misc +L.grenade_smoke = "Rökgranat" +L.grenade_fire = "Brandbomb" + +L.unarmed_name = "Hölstrad" +L.crowbar_name = "Kofot" +L.pistol_name = "Pistol" +L.rifle_name = "Gevär" +L.shotgun_name = "Hagelgevär" + +-- Teleporter +L.tele_name = "Teleportör" +L.tele_failed = "Teleportering misslyckad." +L.tele_marked = "Teleporteringsplats markerad." + +L.tele_no_ground = "Kan ej teleportera om du inte står på fast mark!" +L.tele_no_crouch = "Kan ej teleportera när du duckar!" +L.tele_no_mark = "Ingen plats markerad. Markera en destination innan du teleporterar." + +L.tele_no_mark_ground = "Kan ej markera en teleporteringsplats om du inte står på fast mark!" +L.tele_no_mark_crouch = "Kan ej markera en teleporteringsplats om du duckar!" + +--L.tele_help_pri = "Teleports to marked location" +--L.tele_help_sec = "Marks current location" + +L.tele_desc = [[ +Teleportera till en tidigare markerad plats. + +Teleportering orsakar oljud, och antalet gånger den kan användas är begränsat.]] + +-- Ammo names, shown when picked up +L.ammo_pistol = "9mm ammunition" + +L.ammo_smg1 = "K-pist-ammunition" +L.ammo_buckshot = "Hagelgevärsammunition" +L.ammo_357 = "Gevärsammunition" +L.ammo_alyxgun = "Deagle-ammunition" +L.ammo_ar2altfire = "Signalammunition" +L.ammo_gravity = "Poltergeist-ammunition" + +-- Round status +L.round_wait = "Väntar" +L.round_prep = "Förbereder" +L.round_active = "Pågår" +L.round_post = "Runda över" + +-- Health, ammo and time area +L.overtime = "ÖVERTID" +L.hastemode = "HETSLÄGE" + +-- TargetID health status +L.hp_healthy = "Frisk" +L.hp_hurt = "Skadad" +L.hp_wounded = "Ganska Skadad" +L.hp_badwnd = "Svårt Skadad" +L.hp_death = "Nära Döden" + +-- TargetID Karma status +L.karma_max = "Ansedd" +L.karma_high = "Klumpig" +L.karma_med = "Skjutglad" +L.karma_low = "Farlig" +L.karma_min = "Varning" + +-- TargetID misc +L.corpse = "Lik" +L.corpse_hint = "Tryck {usekey} för att söka igenom. {usekey} + {walkkey} för att söka i smyg." + +L.target_disg = " (FÖRKLÄDD)" +L.target_unid = "Oidentifierad kropp" +--L.target_unknown = "A Terrorist" + +-- HUD buttons with hand icons that only some roles can see and use +L.tbut_single = "Engångsanvändning" +L.tbut_reuse = "Omanvändningsbar" +L.tbut_retime = "Omanvändningsbar efter {num} sekunder" +L.tbut_help = "Tryck {usekey} för att aktivera" + +-- Spectator muting of living/dead +L.mute_living = "Levande spelare nedtystade" +L.mute_specs = "Åskådare nedtystade" +--L.mute_all = "All muted" +L.mute_off = "Ingen nedtystad" + +-- Spectators and prop possession +L.punch_title = "KNUFF-MÄTARE" +L.punch_bonus = "Din dåliga poäng har sänkt din knuff-mätargräns med {num}" +L.punch_malus = "Din goda poäng har höjt din knuff-mätargräns med {num}!" + +-- Info popups shown when the round starts +L.info_popup_innocent = [[ +Du är en oskyldig Terrorist! Men det finns förrädare omkring... +Vem kan du lita på, och vem är ute för att mörda dig? + +Var aktsam och arbeta med dina kamrater för att komma ut härifrån levande!]] + +L.info_popup_detective = [[ +Du är en Detektiv! Terrorist-högkvarteren har givit dig speciella resurser för att finna förrädarna. +Använd dem för att hjälpa de oskyldiga överleva, men var försiktig: +förrädarna kommer att försöka ta ned dig först! + +Tryck {menukey} för att få dina verktyg!]] + +L.info_popup_traitor_alone = [[ +Du är en FÖRRÄDARE! Du har inga förrädarkamrater denna runda. + +Döda alla andra för att vinna! + +Tryck {menukey} för att få dina verktyg!]] + +L.info_popup_traitor = [[ +Du är en FÖRRÄDARE! Arbeta med dina förrädarkamrater för att döda alla andra. +Men var försiktig, annars kan ditt förräderi upptäckas... + +Detta är dina kamrater: +{traitorlist} + +Tryck {menukey} för att få dina verktyg!]] + +-- Various other text +L.name_kick = "En spelare blev utsparkad automatiskt för att ha bytt sitt namn under en runda." + +L.idle_popup = [[ +Du var borta i {num} sekunder och blev därför förflyttad till Åskådar-läge. Så länge du är i detta läge kommer du inte att kunna spela när en ny runda börjar. + +Du kan slå av/på Åskådar-läge när som helst genom att trycka på {helpkey} och bocka av lådan i inställningsfliken. Du kan även välja att stänga av det nu medsamma.]] + +L.idle_popup_close = "Gör ingenting" +L.idle_popup_off = "Stäng av Åskådar-läge nu" + +L.idle_warning = "Varning: du verkar vara borta, och kommer att bli tvingad att åskåda om du inte gör någonting!" + +L.spec_mode_warning = "Du är i Åskådar-läge och kommer inte att starta när en runda påbörjas. För att stänga av detta läge, tryck F1, gå till inställningar och bocka av 'Åskådar-läge'." + +-- Tips panel +L.tips_panel_title = "Tips" +L.tips_panel_tip = "Tips:" + +-- Tip texts +L.tip1 = "Förrädare kan söka igenom kroppar tyst, utan att bekräfta deras död, genom att hålla in {walkkey} och trycka {usekey} på liket." + +L.tip2 = "Genom att armera en C4 med en längre timer kommer antalet sladdar som orsakar omedelbar detonering att öka. Den kommer även att pipa svagare och med större mellanrum mellan pipen." + +L.tip3 = "Detektiver kan söka igenom lik för att se vem som 'reflekteras på näthinnan'. Detta är den sista personen som den döde snubben såg. Det är dock inte nödvändigtvis mördaren om han skjöts i ryggen." + +L.tip4 = "Ingen kommer att veta om att du har dött förrän de har funnit din döda kropp och identifierat den genom att söka igenom den." + +L.tip5 = "När en Förrädare dödar en Detektiv får han omedelbart en kreditbelöning." + +L.tip6 = "När en Förrädare dör får alla Detektiver kreditbelöning." + +L.tip7 = "När Förrädarna har dödat tillräckligt många får de en kreditbelöning." + +L.tip8 = "Förrädare och Detektiver kan samla på sig oanvända verktygskrediter från döda Förrädar- och Detektiv-kroppar." + +L.tip9 = "Poltergeisten kan förvandla vilket rörligt föremål som helst till en dödlig projektil. Varje knuff medför en stark smäll som skadar alla i närheten." + +L.tip10 = "Som en Förrädare eller som en Detektiv bör du hålla ett öga på röda meddelanden i det övre högra hörnet av skärmen, då dessa innehåller viktig information." + +L.tip11 = "Som en Förrädare eller som en Detektiv får du extra verktygskrediter om dina kamrater arbetar väl. Kom också ihåg att spendera dem!" + +L.tip12 = "Detektivernas DNA-skanner kan användas till att samla DNA-prov från vapen och föremål för att sedan skanna och finna dess användare. Detta är användbart när du kan hitta ett prov från ett lik eller en avstängd C4!" + +L.tip13 = "Om du är nära ditt mordoffer kommer ditt DNA att hamna på liket. DNAt kan sedan användas med Detektivernas DNA-skannrar för att hitta var du är. Du gör därför bäst i att gömma liken efter att du har knivhuggit det!" + +L.tip14 = "Ju längre ifrån du är ditt mordoffer, desto kortare tid kommer ditt DNA finnas kvar på liket innan det förruttnar." + +L.tip15 = "Tänkte du skjuta prick som Förrädare? Överväg att använda en Förklädare. Om du missar ett skott kan du springa till en säker plats, stänga av apparaten och komma ut som om ingenting hade hänt." + +L.tip16 = "Som Förrädare kan Teleportören hjälpa dig fly när du är jagad, och tillåter dig att komma från plats A till plats B på väldigt kort tid. Se bara till så att du har en säker plats markerad." + +L.tip17 = "Är de oskylda ihopklumpade och svåra att ha ihjäl? Prova att använda Radion och spela upp C4-pip eller ljud av en skottlossning för att splittra gruppen." + +L.tip18 = "Genom att använda Radion som Förrädare kan du spela upp ljud genom din Verktygs-meny efter att Radion har blivit placerad. Köa flera ljud genom att klicka på flera knappar i den ordning du vill att de ska spelas upp i." + +L.tip19 = "Om du har krediter över kan du som Detektiv ge en Desarmerare till en oskyldig som du litar på. Då kan du spendera mer tid till att utreda och lämna den riskfulla bombdesarmeringen till dem." + +L.tip20 = "Detektivernas Kikare tillåter likidentifiering från långa avstånd. Det är dåliga nyheter för Förrädarna, om de tänkte använda liket som lockbete. Å andra sidan är Detektiven distraherad och obeväpnad när han använder Kikaren..." + +L.tip21 = "Detektivernas Hälsostation tillåter skadade spelare att återhämta sig. Å andra sidan kan de skadade spelarna vara Förrädare..." + +L.tip22 = "Genom att använda DNA-skannern på Hälsostationen kan man få fram DNA-prov från alla som använt den." + +L.tip23 = "Till skillnad från vapen och C4 lämnas inga DNA-spår på Radion, så Förrädare behöver inte oroa sig för att Detektiver hittar den och förstör deras dag." + +L.tip24 = "Tryck på {helpkey} för att se en kort genomgång eller ändra några inställningar för TTT. Till exempel kan du stänga av dessa tips där för gott." + +L.tip25 = "När en Detektiv söker igenom en kropp kan resultaten hittas för alla spelare på poängtavlan genom att klicka på den döde personens namn." + +L.tip26 = "På poängtavlan betyder ett förstoringsglass bredvid någons namn att det finns sökinformation om den personen. Om ikonen är ljus kommer informationen från en Detektiv och kan innehålla mer information." + +L.tip27 = "Som en Detektiv betyder ett förstoringsglas bredvid någons namn att kroppen har blivit genomsökt och att informationen finns tillgänglig för alla spelare via poängtavlan." + +L.tip28 = "Åskådare kan trycka på {mutekey} för att rotera mellan alternativ för att tysta ned åskådare eller levande spelare." + +L.tip29 = "If the server has installed additional languages, you can switch to a different language at any time in the Settings menu." + +L.tip30 = "Snabbknappar eller 'radio-kommandon' kan användas genom att trycka på {zoomkey}." + +L.tip31 = "Som en åskådare kan du trycka på {duckkey} för att låsa upp din muspekare och klicka på knapparna på den här panelen. Tryck på {duckkey} igen för att komma tillbaka till musstyrning." + +L.tip32 = "Kofotens alternativa avfyrningsläge knuffar andra spelare." + +L.tip33 = "Genom att använda siktet på vapnen kommer träffsäkerheten att höjas något och rekylen minskas. Att ducka gör ingen skillnad." + +L.tip34 = "Rökgranater är effektiva inomhus, särskilt för att skapa förvirring i ett rum proppfullt med folk." + +L.tip35 = "Som en Förrädare bör du komma ihåg att du kan gömma kroppar från oskyldiga och Detektivers snokande ögon." + +L.tip36 = "En genomgång av spelläget finns tillgängligt under {helpkey}. I den finns en översikt över viktiga element i spelet." + +L.tip37 = "På poängtavlan kan du klicka på någons namn och välja en tagg för dem, t.ex. 'misstänkt' eller 'vän'. Denna tagg dyker sedan upp om du siktar på spelaren." + +--L.tip38 = "Many of the placeable equipment items (such as C4, Radio) can be stuck on walls using secondary fire." + +L.tip39 = "C4 som exploderar på grund av en misslyckad desarmering har mindre explosionsradie än en C4 där timern når noll." + +L.tip40 = "Om det står 'HETSLÄGE' ovanför rundtimern kommer rundan först bara att vara några minuter lång, men efter varje oskyldig död kommer tiden att ökas. Det här läget pressar Förrädarna att göra någonting och inte bara stå still hela rundan." + +-- Round report +L.report_title = "Rundrapport" + +-- Tabs +L.report_tab_hilite = "Höjdpunkter" +L.report_tab_hilite_tip = "Rund-höjdpunkter" +L.report_tab_events = "Händelser" +L.report_tab_events_tip = "Händelseloggen om vad som hände den här rundan" +L.report_tab_scores = "Poäng" +L.report_tab_scores_tip = "Varje spelares poäng den här rundan" + +-- Event log saving +L.report_save = "Spara Log .txt" +L.report_save_tip = "Sparar Händelseloggen i en textfil" +L.report_save_error = "Ingen Händelselogg-data att spara." +L.report_save_result = "Händelseloggen har sparats i:" + +-- Columns +L.col_time = "Tid" +L.col_event = "Händelse" +L.col_player = "Spelare" +--L.col_roles = "Role(s)" +--L.col_teams = "Team(s)" +L.col_kills1 = "Oskyldiga dödade" +L.col_kills2 = "Förrädare dödade" +L.col_points = "Poäng" +L.col_team = "Lagbonus" +L.col_total = "Totala poäng" + +-- Awards/highlights +L.aw_sui1_title = "Ledare av Själmordssekt" +L.aw_sui1_text = "visade de andra självmördarna hur man gjorde genom att göra det själv först." + +L.aw_sui2_title = "Ensam och Deprimerad" +L.aw_sui2_text = "var den ende som begick självmord." + +L.aw_exp1_title = "Explosivt Forskningsbidrag" +L.aw_exp1_text = "vart erkänd för sin forskning kring explosioner. {num} försöksobjekt hjälpte till." + +L.aw_exp2_title = "Fältforskning" +L.aw_exp2_text = "provade sin motståndskraft mot explosioner. Den var inte tillräckligt hög." + +L.aw_fst1_title = "Första Förräderiet" +L.aw_fst1_text = "levererade det första oskyldiga mordet genom en förrädares händer." + +L.aw_fst2_title = "Första Dumma DödandetFirst Bloody Stupid Kill" +L.aw_fst2_text = "fick det första dödandet genom att skjuta en förrädarkamrat. Bra jobbat." + +L.aw_fst3_title = "Första Tabben" +L.aw_fst3_text = "var den första att döda. Synd att det var en oskyldig kamrat." + +L.aw_fst4_title = "Första Slaget" +L.aw_fst4_text = "slog det första slaget för de oskyldiga terroristerna genom att göra den första döden till en förrädares." + +L.aw_all1_title = "Dödlig Bland Jämlikar" +L.aw_all1_text = "var ansvarig för alla de oskyldigas dödande den här rundan." + +L.aw_all2_title = "Ensamvarg" +L.aw_all2_text = "var ansvarig för alla förrädarnas dödande den här rundan." + +L.aw_nkt1_title = "Jag Fick En, Chefen!" +L.aw_nkt1_text = "lyckades döda en enda oskyldig. Schysst!!" + +L.aw_nkt2_title = "En Kula För Två" +L.aw_nkt2_text = "visade att den första inte bara var tur genom att döda en till." + +L.aw_nkt3_title = "Serie-Förrädare" +L.aw_nkt3_text = "tog idag slut på tre oskyldiga terrorist-liv." + +L.aw_nkt4_title = "En Varg i Fårakläder" +L.aw_nkt4_text = "äter oskyldiga terrorister till middag. En middag bestående av {num} rätter." + +L.aw_nkt5_title = "Kontra-Terroristisk Operation" +L.aw_nkt5_text = "får betalt för varje dödad. Kan nu köpa ännu en lyxjakt." + +L.aw_nki1_title = "Förräd detta" +L.aw_nki1_text = "hittade en förrädare. Sköt en förrädare. Enkelt." + +L.aw_nki2_title = "Ansökte till Rättvise-Patrullen" +L.aw_nki2_text = "eskorterade två förrädare till den andra sidan." + +L.aw_nki3_title = "Drömmer Förrädare Om Förrädiska Får?" +L.aw_nki3_text = "hjälpte tre förrädare till sömns." + +L.aw_nki4_title = "Anställd på Interna Affärer" +L.aw_nki4_text = "får betalt för varje dödad. Kan nu beställa sin femte simbassäng." + +L.aw_fal1_title = "Fia med Knuff" +L.aw_fal1_text = "knuffade någon från en hög höjd." + +L.aw_fal2_title = "Däckad" +L.aw_fal2_text = "föll från en väldigt hög höjd." + +L.aw_fal3_title = "Den Mänsklige Meteoriten" +L.aw_fal3_text = "krossade en man med sin tyngd genom att falla på honom." + +L.aw_hed1_title = "Effektivitet" +L.aw_hed1_text = "upptäckte nöjet med huvudskott och utförde {num}." + +L.aw_hed2_title = "Hjärnkirurg" +L.aw_hed2_text = "avlägsnade {num} hjärnor från dess huvud för en närmare analys." + +L.aw_hed3_title = "Jag Beskyller TV-Spelen" +L.aw_hed3_text = "applicerade sin mordsimulerings-träning och placerade {num} dödliga huvudskott." + +L.aw_cbr1_title = "Dunk Dunk Dunk" +L.aw_cbr1_text = "har en riktig bra svingarm med kofoten, vilket {num} offer fick reda på." + +L.aw_cbr2_title = "Martin Timell" +L.aw_cbr2_text = "täckte sin kofot med inte mindre än {num} människors hjärnor." + +L.aw_pst1_title = "Ihärdig Liten Rackare" +L.aw_pst1_text = "dödade {num} spelare med hjälp av pistolen. Sedan gick han och kramade någon till döds." + +L.aw_pst2_title = "Slakt Med Låg Kaliber" +L.aw_pst2_text = "dödade en liten armé på {num} med en pistol. Förmodligen har han ett litet hagelgevär i mynningen." + +L.aw_sgn1_title = "Lätt Som En Plätt" +L.aw_sgn1_text = "applicerade haglet där det gör som mest skada, och dödade {num} mål på kuppen." + +L.aw_sgn2_title = "Tusen Små Hagel" +L.aw_sgn2_text = "gillade inte riktigt sitt hagel, så han gav bort allt. {num} mottagare levde inte länge nog för att ha kul med det." + +L.aw_rfl1_title = "Peka och Klicka" +L.aw_rfl1_text = "visar att allt du behöver för {num} döda är ett gevär och en stadig hand." + +L.aw_rfl2_title = "Jag Kan Se Ditt Huvud Härifrån" +L.aw_rfl2_text = "kände sitt gevär utan och innan. Nu känner {num} andra också hans till det." + +L.aw_dgl1_title = "Gevär I Mini-Format" +L.aw_dgl1_text = "börjar få grepp om sin Desert Eagle och dödade {num} människor." + +L.aw_dgl2_title = "Deaglar'n" +L.aw_dgl2_text = "blåste iväg {num} människor med sin handkanon." + +L.aw_mac1_title = "Sikta(inte) och Skjut" +L.aw_mac1_text = "dödade {num} människor med sin MAC10, men vägrar uppge hur många skott som krävdes." + +L.aw_mac2_title = "Köttbullar med MACaroner" +L.aw_mac2_text = "undrar vad som skulle hända om han kunde använda två MAC10 samtidigt. {num} gånger två?" + +L.aw_sip1_title = "Var Tyst" +L.aw_sip1_text = "fick tyst på {num} människor med den ljuddämpade pistolen." + +L.aw_sip2_title = "Tyst Men Dödlig" +L.aw_sip2_text = "dödade {num} människor som inte kunde höra sig själv dö." + +L.aw_knf1_title = "Knivig Situation" +L.aw_knf1_text = "stack någon i ansiktet över internet." + +L.aw_knf2_title = "Var Hittade Du Den Där?" +L.aw_knf2_text = "var inte en Förrädare, men lyckades ändå döda någon med en kniv." + +L.aw_knf3_title = "En Knivig Situation" +L.aw_knf3_text = "hittade {num} knivar, och gjorde det bästa med dem." + +L.aw_knf4_title = "En Knivslug Man" +L.aw_knf4_text = "dödade {num} människor med knivar. Fråga mig inte hur." + +L.aw_flg1_title = "Till Undsättning" +L.aw_flg1_text = "använde sin signalpistol till att signalera {num} mord." + +L.aw_flg2_title = "Ingen Rök Utan Eld" +L.aw_flg2_text = "lärde {num} män om faran med att ha lättantändliga kläder på sig." + +L.aw_hug1_title = "HUGEnottkrig" +L.aw_hug1_text = "gick ut i krig med sin H.U.G.E, och lyckades på något sätt skjuta ihjäl {num} människor." + +L.aw_hug2_title = "Fett Mycket Para" +L.aw_hug2_text = "hade väldigt många skott, och investerade dem i {num} människor." + +L.aw_msx1_title = "Ett M16-Protokoll" +L.aw_msx1_text = "prickade av {num} människor från listan med sin M16." + +L.aw_msx2_title = "Galenskap På Medelavstånd" +L.aw_msx2_text = "lyckades plocka ner {num} mål med sin M16." + +L.aw_tkl1_title = "Aja-baja!" +L.aw_tkl1_text = "slant med fingret när han siktade på en kompis." + +L.aw_tkl2_title = "De Såg Ut Som Förrädare" +L.aw_tkl2_text = "trodde att han lyckats döda en Förrädare två gånger, men hade fel båda gångerna." + +L.aw_tkl3_title = "Låg Karma" +L.aw_tkl3_text = "kunde inte sluta efter att ha dödat två medspelare. Hans turnummer är tre." + +L.aw_tkl4_title = "Lagslakt" +L.aw_tkl4_text = "mördade hela sitt lag. Lyckligtvis är han nog inte kvar så länge till." + +L.aw_tkl5_title = "Rollspelare" +L.aw_tkl5_text = "antog sig rollen som was roleplaying en blådåre. Det är därför han dödade merparten av sitt lag." + +L.aw_tkl6_title = "Pucko" +L.aw_tkl6_text = "förstod inte vilken sida han var på, så han dödade mer än hälften av sina lagkamrater." + +L.aw_tkl7_title = "Bondlurk" +L.aw_tkl7_text = "försvarade sin mark genom att döda mer än en fjärdedel av sitt lag." + +L.aw_brn1_title = "Precis Som Mormors" +L.aw_brn1_text = "grillade flera människor tills de fått en fin yta." + +L.aw_brn2_title = "Leker Med Elden" +L.aw_brn2_text = "lärde sig tydligen inte vad mamma lärt honom, vilket flera människor får sota för." + +L.aw_brn3_title = "Pyrrhic Burnery" +L.aw_brn3_text = "burned them all, but is now all out of incendiary grenades! How will he cope!?" + +L.aw_fnd1_title = "Coroner" +L.aw_fnd1_text = "found {num} bodies lying around." + +L.aw_fnd2_title = "Gotta Catch Em All" +L.aw_fnd2_text = "found {num} corpses for his collection." + +L.aw_fnd3_title = "Death Scent" +L.aw_fnd3_text = "keeps stumbling on random corpses, {num} times this round." + +L.aw_crd1_title = "Recycler" +L.aw_crd1_text = "scrounged up {num} leftover credits from corpses." + +L.aw_tod1_title = "Pyrrhusseger" +L.aw_tod1_text = "dog bara några sekunder innan hans lag vann rundan." + +L.aw_tod2_title = "Jag Hatar Detta Spel" +L.aw_tod2_text = "dog precis efter att rundan påbörjats." + +-- New and modified pieces of text are placed below this point, marked with the +-- version in which they were added, to make updating translations easier. + +-- v24 +L.drop_no_ammo = "Otillräcklig ammunition i vapnets klipp att släppa som en ammo låda." + +-- 2015-05-25 +L.hat_retrieve = "Du plockade upp hatten av en detektiv." + +-- 2017-09-03 +--L.sb_sortby = "Sort By:" + +-- 2018-07-24 +--L.equip_tooltip_main = "Equipment menu" +--L.equip_tooltip_radar = "Radar control" +--L.equip_tooltip_disguise = "Disguise control" +--L.equip_tooltip_radio = "Radio control" +--L.equip_tooltip_xfer = "Transfer credits" +--L.equip_tooltip_reroll = "Reroll equipment" + +--L.confgrenade_name = "Discombobulator" +--L.polter_name = "Poltergeist" +--L.stungun_name = "UMP Prototype" + +--L.knife_instant = "INSTANT KILL" + +--L.binoc_zoom_level = "Zoom Level" +--L.binoc_body = "BODY DETECTED" + +--L.idle_popup_title = "Idle" + +-- 2019-01-31 +--L.create_own_shop = "Create own shop" +--L.shop_link = "Link with" +--L.shop_disabled = "Disable shop" +--L.shop_default = "Use default shop" + +-- 2019-05-05 +--L.reroll_name = "Reroll" +--L.reroll_menutitle = "Reroll equipment" +--L.reroll_no_credits = "You need {amount} credits to reroll!" +--L.reroll_button = "Reroll" +--L.reroll_help = "Use {amount} credits to get a new random set of equipment in your shop!" + +-- 2019-05-06 +--L.equip_not_alive = "You can view all available items by selecting a role on the right. Don't forget to mark your favorites!" + +-- 2019-06-27 +--L.shop_editor_title = "Shop Editor" +--L.shop_edit_items_weapong = "Edit Items / Weapons" +--L.shop_edit = "Edit Shops" +--L.shop_settings = "Settings" +--L.shop_select_role = "Select Role" +--L.shop_edit_items = "Edit Items" +--L.shop_edit_shop = "Edit Shop" +--L.shop_create_shop = "Create Custom Shop" +--L.shop_selected = "Selected {role}" +--L.shop_settings_desc = "Change the values to adapt Random Shop ConVars. Don't forget to save your changes!" + +--L.bindings_new = "New bound key for {name}: {key}" + +--L.hud_default_failed = "Failed to set the HUD {hudname} as new default. You don't have permission to do that, or this HUD doesn't exist." +--L.hud_forced_failed = "Failed to force the HUD {hudname}. You don't have permission to do that, or this HUD doesn't exist." +--L.hud_restricted_failed = "Failed to restrict the HUD {hudname}. You don't have permission to do that." + +--L.shop_role_select = "Select a role" +--L.shop_role_selected = "{role}'s shop was selected!" +--L.shop_search = "Search" + +-- 2019-10-19 +--L.drop_ammo_prevented = "Something prevents you from dropping your ammo." + +-- 2019-10-28 +--L.target_c4 = "Press [{usekey}] to open C4 menu" +--L.target_c4_armed = "Press [{usekey}] to disarm C4" +--L.target_c4_armed_defuser = "Press [{primaryfire}] to use defuser" +--L.target_c4_not_disarmable = "You can't disarm C4 of a living teammate" +--L.c4_short_desc = "Something very explosive" + +--L.target_pickup = "Press [{usekey}] to pick up" +--L.target_slot_info = "Slot: {slot}" +--L.target_pickup_weapon = "Press [{usekey}] to pickup weapon" +--L.target_switch_weapon = "Press [{usekey}] to swap with your current weapon" +--L.target_pickup_weapon_hidden = ", press [{walkkey} + {usekey}] for hidden pickup" +--L.target_switch_weapon_hidden = ", press [{walkkey} + {usekey}] for hidden switch" +--L.target_switch_weapon_nospace = "There is no inventory slot available for this weapon" +--L.target_switch_drop_weapon_info = "Dropping {name} from slot {slot}" +--L.target_switch_drop_weapon_info_noslot = "There is no droppable weapon in slot {slot}" + +--L.corpse_searched_by_detective = "This corpse was searched by a public policing role" +--L.corpse_too_far_away = "The corpse is too far away." + +--L.radio_short_desc = "Weapon sounds are music to me" + +--L.hstation_subtitle = "Press [{usekey}] to receive health." +--L.hstation_charge = "Remaining charge of health station: {charge}" +--L.hstation_empty = "There is no more charge left in this health station" +--L.hstation_maxhealth = "Your health is full" +--L.hstation_short_desc = "The heath station slowly recharges over time" + +-- 2019-11-03 +--L.vis_short_desc = "Visualizes a crime scene if the victim died by a gunshot wound" +--L.corpse_binoculars = "Press [{key}] to search corpse with binoculars." +--L.binoc_progress = "Search progress: {progress}%" + +--L.pickup_no_room = "You have no space in your inventory for this weapon kind." +--L.pickup_fail = "You cannot pick up this weapon." +--L.pickup_pending = "You already picked up a weapon, wait until you receive it." + +-- 2020-01-07 +--L.tbut_help_admin = "Edit traitor button settings" +--L.tbut_role_toggle = "[{walkkey} + {usekey}] to toggle this button for {role}" +--L.tbut_role_config = "Role: {current}" +--L.tbut_team_toggle = "[SHIFT + {walkkey} + {usekey}] to toggle this button for team {team}" +--L.tbut_team_config = "Team: {current}" +--L.tbut_current_config = "Current config:" +--L.tbut_intended_config = "Intended config by map creator:" +--L.tbut_admin_mode_only = "You see this button because you're an admin and '{cv}' is set to '1'." +--L.tbut_allow = "Allow" +--L.tbut_prohib = "Prohibit" +--L.tbut_default = "Default" + +-- 2020-02-09 +--L.name_door = "Door" +--L.door_open = "Press [{usekey}] to open door." +--L.door_close = "Press [{usekey}] to close door." +--L.door_locked = "This door is locked." + +-- 2020-02-11 +--L.automoved_to_spec = "(AUTOMATED MESSAGE) I have been moved to the Spectator team because I was idle/AFK." +--L.mute_team = "{team} muted." + +-- 2020-02-16 +--L.door_auto_closes = "This door closes automatically." +--L.door_open_touch = "Walk into door to open." +--L.door_open_touch_and_use = "Walk into door or press [{usekey}] to open." + +-- 2020-03-09 +L.help_title = "Hjälp och Inställningar" + +--L.menu_changelog_title = "Changelog" +--L.menu_guide_title = "TTT2 Guide" +--L.menu_bindings_title = "Key Bindings" +--L.menu_language_title = "Language" +--L.menu_appearance_title = "Appearance" +--L.menu_gameplay_title = "Gameplay" +--L.menu_addons_title = "Addons" +--L.menu_legacy_title = "Legacy Addons" +--L.menu_administration_title = "Administration" +--L.menu_equipment_title = "Edit Equipment" +--L.menu_shops_title = "Edit Shops" + +--L.menu_changelog_description = "A list of changes and fixes in recent versions." +--L.menu_guide_description = "Helps you to get started with TTT2 and explains some things about gameplay, roles and other stuff." +--L.menu_bindings_description = "Bind specific features of TTT2 and its addons to your own liking." +--L.menu_language_description = "Select the language of the gamemode." +--L.menu_appearance_description = "Tweak the appearance and performance of the UI." +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." +--L.menu_addons_description = "Configure local addons to your liking." +--L.menu_legacy_description = "A panel with converted tabs from the original TTT that should be ported over to the new system." +--L.menu_administration_description = "General settings for HUDs, shops etc." +--L.menu_equipment_description = "Set credits, limitations, availability and other stuff." +--L.menu_shops_description = "Add/Remove shops for roles and configure what equipment they have." + +--L.submenu_guide_gameplay_title = "Gameplay" +--L.submenu_guide_roles_title = "Roles" +--L.submenu_guide_equipment_title = "Equipment" + +--L.submenu_bindings_bindings_title = "Bindings" + +--L.submenu_language_language_title = "Language" + +--L.submenu_appearance_general_title = "General" +--L.submenu_appearance_hudswitcher_title = "HUD Switcher" +--L.submenu_appearance_vskin_title = "VSkin" +--L.submenu_appearance_targetid_title = "TargetID" +--L.submenu_appearance_shop_title = "Shop Settings" +--L.submenu_appearance_crosshair_title = "Crosshair" +--L.submenu_appearance_dmgindicator_title = "Damage Indicator" +--L.submenu_appearance_performance_title = "Performance" +--L.submenu_appearance_interface_title = "Interface" + +--L.submenu_gameplay_general_title = "General" + +--L.submenu_administration_hud_title = "HUD Settings" +--L.submenu_administration_randomshop_title = "Random Shop" + +--L.help_color_desc = "If this setting is enabled, you can choose a global color that will be used for the targetID outline and the crosshair." +--L.help_scale_factor = "This scale factor influences all UI elements (HUD, VGUI and TargetID). It is automatically updated if the screen resolution is changed. Changing this value will reset the HUD!" +--L.help_hud_game_reload = "The HUD is not available right now. Reconnect to the server or relaunch the game." +--L.help_hud_special_settings = "These are specific settings of this HUD." +--L.help_vskin_info = "VSkin (VGUI skin) is the skin applied to all menu elements like the current one. They can be easily created with a simple Lua script and can change colors and some size parameters." +--L.help_targetid_info = "TargetID is the information rendered when pointing your crosshair at an entity. Its color can be configured in the 'General' tab." +--L.help_hud_default_desc = "Sets the default HUD for all players. Players that have not yet selected a HUD will receive this HUD as their default. Changing this won't change the HUD for players that have already selected their HUD." +--L.help_hud_forced_desc = "Forces a HUD for all players. This disables the HUD selection feature for everyone." +--L.help_hud_enabled_desc = "Enable/Disable HUDs to restrict the selection of these HUDs." +--L.help_damage_indicator_desc = "The damage indicator is the overlay shown when the player is damaged. To add a new theme, place a png in 'materials/vgui/ttt/damageindicator/themes/'." +--L.help_shop_key_desc = "Open the shop by pressing the shop key instead of the score menu during preparing / at the end of a round?" + +--L.label_menu_menu = "MENU" +--L.label_menu_admin_spacer = "Admin Area (not shown to normal users)" +--L.label_language_set = "Select language" +--L.label_global_color_enable = "Enable global color" +--L.label_global_color = "Global color" +--L.label_global_scale_factor = "Global scale factor" +--L.label_hud_select = "Select HUD" +--L.label_vskin_select = "Select VSkin" +--L.label_blur_enable = "Enable VSkin background blur" +--L.label_color_enable = "Enable VSkin background color" +--L.label_minimal_targetid = "Minimalist Target ID under crosshair (no Karma text, hints etc.)" +--L.label_shop_always_show = "Always show the shop" +--L.label_shop_double_click_buy = "Enable an item purchase by double-clicking on it in the shop" +--L.label_shop_num_col = "Number of columns" +--L.label_shop_num_row = "Number of rows" +--L.label_shop_item_size = "Icon size" +--L.label_shop_show_slot = "Show slot marker" +--L.label_shop_show_custom = "Show custom item marker" +--L.label_shop_show_fav = "Show favourite item marker" +--L.label_crosshair_enable = "Enable crosshair" +--L.label_crosshair_opacity = "Crosshair opacity" +--L.label_crosshair_ironsight_opacity = "Ironsight crosshair opacity" +--L.label_crosshair_size = "Crosshair size multiplier" +--L.label_crosshair_thickness = "Crosshair thickness multiplier" +--L.label_crosshair_thickness_outline = "Crosshair outline thickness multiplier" +--L.label_crosshair_scale_enable = "Enable dynamic crosshair scale" +--L.label_crosshair_ironsight_low_enabled = "Lower weapon when using ironsights" +--L.label_damage_indicator_enable = "Enable damage indicator" +--L.label_damage_indicator_mode = "Select damage indicator theme" +--L.label_damage_indicator_duration = "Fade time after getting hit (in seconds)" +--L.label_damage_indicator_maxdamage = "Damage needed for the maximum opacity" +--L.label_damage_indicator_maxalpha = "Maximum opacity" +--L.label_performance_halo_enable = "Draw an outline around some entities while looking at them" +--L.label_performance_spec_outline_enable = "Enable controlled objects' outlines" +--L.label_performance_ohicon_enable = "Enable role icons over players' heads" +--L.label_interface_tips_enable = "Show gameplay tips at the bottom of the screen while spectating" +--L.label_interface_popup = "Start of round info popup duration" +--L.label_interface_fastsw_menu = "Enable menu with fast weapon switch" +--L.label_inferface_wswitch_hide_enable = "Enable weapon switch menu auto-closing" +--L.label_inferface_scues_enable = "Play sound cue when a round begins or ends" +--L.label_gameplay_specmode = "Spectate-only mode (always stay spectator)" +--L.label_gameplay_fastsw = "Fast weapon switch" +--L.label_gameplay_hold_aim = "Enable hold to aim" +--L.label_gameplay_mute = "Mute living players when dead" +--L.label_hud_default = "Default HUD" +--L.label_hud_force = "Forced HUD" + +--L.label_bind_weaponswitch = "Pickup Weapon" +--L.label_bind_voice = "Global Voice Chat" +--L.label_bind_voice_team = "Team Voice Chat" + +--L.label_hud_basecolor = "Base Color" + +--L.label_menu_not_populated = "This submenu does not contain any content." + +--L.header_bindings_ttt2 = "TTT2 Bindings" +--L.header_bindings_other = "Other Bindings" +--L.header_language = "Language Settings" +--L.header_global_color = "Select Global Color" +--L.header_hud_select = "Select a HUD" +--L.header_hud_customize = "Customize the HUD" +--L.header_vskin_select = "Select and Customize the VSkin" +--L.header_targetid = "TargetID Settings" +--L.header_shop_settings = "Equipment Shop Settings" +--L.header_shop_layout = "Item List Layout" +--L.header_shop_marker = "Item Marker Settings" +--L.header_crosshair_settings = "Crosshair Settings" +--L.header_damage_indicator = "Damage Indicator Settings" +--L.header_performance_settings = "Performance Settings" +--L.header_interface_settings = "Interface Settings" +--L.header_gameplay_settings = "Gameplay Settings" +--L.header_hud_administration = "Select Default and Forced HUDs" +--L.header_hud_enabled = "Enable/Disable HUDs" + +--L.button_menu_back = "Back" +--L.button_none = "None" +--L.button_press_key = "Press a key" +--L.button_save = "Save" +--L.button_reset = "Reset" +--L.button_close = "Close" +--L.button_hud_editor = "HUD Editor" + +-- 2020-04-20 +--L.item_speedrun = "Speedrun" +--L.item_speedrun_desc = [[Makes you 50% faster!]] +--L.item_no_explosion_damage = "No Explosion Damage" +--L.item_no_explosion_damage_desc = [[Makes you immune to explosion damage.]] +--L.item_no_fall_damage = "No Fall Damage" +--L.item_no_fall_damage_desc = [[Makes you immune to fall damage.]] +--L.item_no_fire_damage = "No Fire Damage" +--L.item_no_fire_damage_desc = [[Makes you immune to fire damage.]] +--L.item_no_hazard_damage = "No Hazard Damage" +--L.item_no_hazard_damage_desc = [[Makes you immune to hazard damage such as poison, radiation and acid.]] +--L.item_no_energy_damage = "No Energy Damage" +--L.item_no_energy_damage_desc = [[Makes you immune to energy damage such as lasers, plasma and lightning.]] +--L.item_no_prop_damage = "No Prop Damage" +--L.item_no_prop_damage_desc = [[Makes you immune to prop damage.]] +--L.item_no_drown_damage = "No Drowning Damage" +--L.item_no_drown_damage_desc = [[Makes you immune to drowning damage.]] + +-- 2020-04-21 +--L.dna_tid_possible = "Scan possible." +--L.dna_tid_impossible = "No scan possible." +--L.dna_screen_ready = "No DNA" +--L.dna_screen_match = "Match" + +-- 2020-04-30 +--L.message_revival_canceled = "Revival canceled." +--L.message_revival_failed = "Revival failed." +--L.message_revival_failed_missing_body = "You have not been revived because your corpse no longer exists." +--L.hud_revival_title = "Time left until revival:" +--L.hud_revival_time = "{time}s" + +-- 2020-05-03 +--L.door_destructible = "This door is destructible ({health}HP)." + +-- 2020-05-28 +--L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." + +-- 2020-06-04 +--L.label_bind_disguiser = "Toggle disguiser" + +-- 2020-06-24 +--L.dna_help_primary = "Collect a DNA sample" +--L.dna_help_secondary = "Switch the DNA slot" +--L.dna_help_reload = "Delete a sample" + +--L.binoc_help_pri = "Search a body." +--L.binoc_help_sec = "Change zoom level." + +--L.vis_help_pri = "Drop the activated device." + + +-- 2020-08-07 +--L.pickup_error_spec = "You cannot pick this up as a spectator." +--L.pickup_error_owns = "You cannot pick this up because you already have this weapon." +--L.pickup_error_noslot = "You cannot pick this up because you have no free slot available." + +-- 2020-11-02 +--L.lang_server_default = "Server Default" +--L.help_lang_info = [[ +--This translation is {coverage}% complete with the English language taken as a default reference. +-- +--Keep in mind that these translations are made by the community. Feel free to contribute if something is missing or incorrect.]] + +-- 2021-04-13 +--L.title_score_info = "Round End Info" +--L.title_score_events = "Event Timeline" + +--L.label_bind_clscore = "Open round report" +--L.title_player_score = "{player}'s score:" + +--L.label_show_events = "Show events from" +--L.button_show_events_you = "You" +--L.button_show_events_global = "Global" +--L.label_show_roles = "Show role distribution from" +--L.button_show_roles_begin = "Round Begin" +--L.button_show_roles_end = "Round End" + +L.hilite_win_traitors = "FÖRRÄDISK VINST" +--L.hilite_win_innocents = "TEAM INNOCENT WON" +--L.hilite_win_tie = "IT IS A TIE" +--L.hilite_win_time = "TIME IS UP" + +--L.tooltip_karma_gained = "Karma changes for this round:" +--L.tooltip_score_gained = "Score changes for this round:" +--L.tooltip_roles_time = "Role changes for this round:" + +--L.tooltip_finish_score_alive_teammates = "Alive teammates: {score}" +--L.tooltip_finish_score_alive_all = "Alive players: {score}" +--L.tooltip_finish_score_timelimit = "Time is up: {score}" +--L.tooltip_finish_score_dead_enemies = "Dead enemies: {score}" +--L.tooltip_kill_score = "Kill: {score}" +--L.tooltip_bodyfound_score = "Body found: {score}" + +--L.finish_score_alive_teammates = "Alive teammates:" +--L.finish_score_alive_all = "Alive players:" +--L.finish_score_timelimit = "Time is up:" +--L.finish_score_dead_enemies = "Dead enemies:" +--L.kill_score = "Kill:" +--L.bodyfound_score = "Body found:" + +--L.title_event_bodyfound = "A body was found" +--L.title_event_c4_disarm = "A C4 was disarmed" +--L.title_event_c4_explode = "A C4 exploded" +--L.title_event_c4_plant = "A C4 was armed" +--L.title_event_creditfound = "Equipment credits were found" +--L.title_event_finish = "The round has ended" +--L.title_event_game = "A new round has started" +--L.title_event_kill = "A player was killed" +--L.title_event_respawn = "A player respawned" +--L.title_event_rolechange = "A player changed their role or team" +--L.title_event_selected = "The roles were distributed" +--L.title_event_spawn = "A player spawned" + +--L.desc_event_bodyfound = "{finder} ({firole} / {fiteam}) has found the body of {found} ({forole} / {foteam}). The corpse has {credits} equipment credit(s)." +--L.desc_event_bodyfound_headshot = "The victim was killed by a headshot." +--L.desc_event_c4_disarm_success = "{disarmer} ({drole} / {dteam}) successfully disarmed the C4 armed by {owner} ({orole} / {oteam})." +--L.desc_event_c4_disarm_failed = "{disarmer} ({drole} / {dteam}) tried to disarm the C4 armed by {owner} ({orole} / {oteam}). They failed." +--L.desc_event_c4_explode = "The C4 armed by {owner} ({role} / {team}) exploded." +--L.desc_event_c4_plant = "{owner} ({role} / {team}) armed an explosive C4." +--L.desc_event_creditfound = "{finder} ({firole} / {fiteam}) has found {credits} equipment credit(s) in the corpse of {found} ({forole} / {foteam})." +--L.desc_event_finish = "The round lasted {minutes}:{seconds}. There were {alive} player(s) alive in the end." +--L.desc_event_game = "A new round has started." +--L.desc_event_respawn = "{player} has respawned." +--L.desc_event_rolechange = "{player} changed their role/team from {orole} ({oteam}) to {nrole} ({nteam})." +--L.desc_event_selected = "The teams and roles were distributed for all {amount} player(s)." +--L.desc_event_spawn = "{player} has spawned." + +-- Name of a trap that killed us that has not been named by the mapper +--L.trap_something = "something" + +-- Kill events +--L.desc_event_kill_suicide = "It was suicide." +--L.desc_event_kill_team = "It was a team kill." + +--L.desc_event_kill_blowup = "{victim} ({vrole} / {vteam}) blew themselves up." +--L.desc_event_kill_blowup_trap = "{victim} ({vrole} / {vteam}) was blown up by {trap}." + +--L.desc_event_kill_tele_self = "{victim} ({vrole} / {vteam}) telefragged themselves." +--L.desc_event_kill_sui = "{victim} ({vrole} / {vteam}) couldn't take it and killed themselves." +--L.desc_event_kill_sui_using = "{victim} ({vrole} / {vteam}) killed themselves using {tool}." + +--L.desc_event_kill_fall = "{victim} ({vrole} / {vteam}) fell to their death." +--L.desc_event_kill_fall_pushed = "{victim} ({vrole} / {vteam}) fell to their death after {attacker} pushed them." +--L.desc_event_kill_fall_pushed_using = "{victim} ({vrole} / {vteam}) fell to their death after {attacker} ({arole} / {ateam}) used {trap} to push them." + +--L.desc_event_kill_shot = "{victim} ({vrole} / {vteam}) was shot by {attacker}." +--L.desc_event_kill_shot_using = "{victim} ({vrole} / {vteam}) was shot by {attacker} ({arole} / {ateam}) using a {weapon}." + +--L.desc_event_kill_drown = "{victim} ({vrole} / {vteam}) was drowned by {attacker}." +--L.desc_event_kill_drown_using = "{victim} ({vrole} / {vteam}) was drowned by {trap} triggered by {attacker} ({arole} / {ateam})." + +--L.desc_event_kill_boom = "{victim} ({vrole} / {vteam}) was exploded by {attacker}." +--L.desc_event_kill_boom_using = "{victim} ({vrole} / {vteam}) was blown up by {attacker} ({arole} / {ateam}) using {trap}." + +--L.desc_event_kill_burn = "{victim} ({vrole} / {vteam}) was fried by {attacker}." +--L.desc_event_kill_burn_using = "{victim} ({vrole} / {vteam}) was burned by {trap} due to {attacker} ({arole} / {ateam})." + +--L.desc_event_kill_club = "{victim} ({vrole} / {vteam}) was beaten up by {attacker}." +--L.desc_event_kill_club_using = "{victim} ({vrole} / {vteam}) was pummeled to death by {attacker} ({arole} / {ateam}) using {trap}." + +--L.desc_event_kill_slash = "{victim} ({vrole} / {vteam}) was stabbed by {attacker}." +--L.desc_event_kill_slash_using = "{victim} ({vrole} / {vteam}) was cut up by {attacker} ({arole} / {ateam}) using {trap}." + +--L.desc_event_kill_tele = "{victim} ({vrole} / {vteam}) was telefragged by {attacker}." +--L.desc_event_kill_tele_using = "{victim} ({vrole} / {vteam}) was atomized by {trap} set by {attacker} ({arole} / {ateam})." + +--L.desc_event_kill_goomba = "{victim} ({vrole} / {vteam}) was crushed by the massive bulk of {attacker} ({arole} / {ateam})." + +--L.desc_event_kill_crush = "{victim} ({vrole} / {vteam}) was crushed by {attacker}." +--L.desc_event_kill_crush_using = "{victim} ({vrole} / {vteam}) was crushed by {trap} of {attacker} ({arole} / {ateam})." + +--L.desc_event_kill_other = "{victim} ({vrole} / {vteam}) was killed by {attacker}." +--L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) was killed by {attacker} ({arole} / {ateam}) using {trap}." + +-- 2021-04-20 +--L.none = "No Role" + +-- 2021-04-24 +--L.karma_teamkill_tooltip = "Teammate killed" +--L.karma_teamhurt_tooltip = "Teammate damaged" +--L.karma_enemykill_tooltip = "Enemy killed" +--L.karma_enemyhurt_tooltip = "Enemy damaged" +--L.karma_cleanround_tooltip = "Clean round" +--L.karma_roundheal_tooltip = "Karma restoration" +--L.karma_unknown_tooltip = "Unknown" + +-- 2021-05-07 +--L.header_random_shop_administration = "Random Shop Settings" +--L.header_random_shop_value_administration = "Balance Settings" + +--L.shopeditor_name_random_shops = "Enable random shops" +--L.shopeditor_desc_random_shops = [[Random shops give every player a limited randomized set of all available equipments. +--Team shops forcefully give the same set to all players in a team instead of individual ones. +--Rerolling allows you to get a new randomized set of equipment for credits.]] +--L.shopeditor_name_random_shop_items = "Number of random equipments" +--L.shopeditor_desc_random_shop_items = "This includes equipments, which are marked with \"Always available in shop\". So choose a high enough number or you only get those." +--L.shopeditor_name_random_team_shops = "Enable team shops" +--L.shopeditor_name_random_shop_reroll = "Enable shop reroll availability" +--L.shopeditor_name_random_shop_reroll_cost = "Cost per reroll" +--L.shopeditor_name_random_shop_reroll_per_buy = "Auto reroll after buy" + +-- 2021-06-04 +--L.header_equipment_setup = "Equipment Settings" +--L.header_equipment_value_setup = "Balance Settings" + +--L.equipmenteditor_name_not_buyable = "Can be bought" +--L.equipmenteditor_desc_not_buyable = "If disabled the equipment will not show in the shop. Roles that have this equipment assigned will still receive it." +--L.equipmenteditor_name_not_random = "Always available in shop" +--L.equipmenteditor_desc_not_random = "If enabled, the equipment is always available in the shop. When the random shop is enabled, it takes one available random slot and always reserves it for this equipment." +--L.equipmenteditor_name_global_limited = "Global limited amount" +--L.equipmenteditor_desc_global_limited = "If enabled, the equipment can be bought only once on the server in the active round." +--L.equipmenteditor_name_team_limited = "Team limited amount" +--L.equipmenteditor_desc_team_limited = "If enabled, the equipment can be bought only once per team in the active round." +--L.equipmenteditor_name_player_limited = "Player limited amount" +--L.equipmenteditor_desc_player_limited = "If enabled, the equipment can be bought only once per player in the active round." +--L.equipmenteditor_name_min_players = "Minimum amount of players for buying" +--L.equipmenteditor_name_credits = "Price in credits" + +-- 2021-06-08 +--L.equip_not_added = "not added" +--L.equip_added = "added" +--L.equip_inherit_added = "added (inherit)" +--L.equip_inherit_removed = "removed (inherit)" + +-- 2021-06-09 +--L.layering_not_layered = "Not layered" +--L.layering_layer = "Layer {layer}" +--L.header_rolelayering_role = "{role} layering" +--L.header_rolelayering_baserole = "Base role layering" +--L.submenu_administration_rolelayering_title = "Role Layering" +--L.header_rolelayering_info = "Role layering information" +--L.help_rolelayering_roleselection = "The role distribution process is split into two stages. In the first stage base roles are distributed, which are innocent, traitor and those listed in the 'base role layer' box below. The second stage is used to upgrade those base roles to a subrole." +--L.help_rolelayering_layers = "From each layer only one role is selected. First the roles from the custom layers are distributed starting from the first layer until the last is reached or no more roles can be upgraded. Whichever happens first, if upgradeable slots are still available, the unlayered roles will be distributed as well." +--L.scoreboard_voice_tooltip = "Scroll to change the volume" + +-- 2021-06-15 +--L.header_shop_linker = "Settings" +--L.label_shop_linker_set = "Select shop type:" + +-- 2021-06-18 +--L.xfer_team_indicator = "Team" + +-- 2021-06-25 +--L.searchbar_default_placeholder = "Search in list..." + +-- 2021-07-11 +--L.spec_about_to_revive = "Spectating is limited during revival period." + +-- 2021-09-01 +--L.spawneditor_name = "Spawn Editor Tool" +--L.spawneditor_desc = "Used to place weapon, ammo and player spawns in the world. Can only be used by super admin." + +--L.spawneditor_place = "Place spawn" +--L.spawneditor_remove = "Remove spawn" +--L.spawneditor_change = "Change spawn type (hold [SHIFT] to reverse)" +--L.spawneditor_ammo_edit = "Hold on weapon spawn to edit autospawning ammo" + +--L.spawn_weapon_random = "Random Weapon Spawn" +--L.spawn_weapon_melee = "Melee Weapon Spawn" +--L.spawn_weapon_nade = "Grenade Weapon Spawn" +--L.spawn_weapon_shotgun = "Shotgun Weapon Spawn" +--L.spawn_weapon_heavy = "Heavy Weapon Spawn" +--L.spawn_weapon_sniper = "Sniper Weapon Spawn" +--L.spawn_weapon_pistol = "Pistol Weapon Spawn" +--L.spawn_weapon_special = "Special Weapon Spawn" +--L.spawn_ammo_random = "Random ammo spawn" +--L.spawn_ammo_deagle = "Deagle ammo spawn" +--L.spawn_ammo_pistol = "Pistol ammo spawn" +--L.spawn_ammo_mac10 = "Mac10 ammo spawn" +--L.spawn_ammo_rifle = "Rifle ammo spawn" +--L.spawn_ammo_shotgun = "Shotgun ammo spawn" +--L.spawn_player_random = "Random player spawn" + +--L.spawn_weapon_ammo = "(Ammo: {ammo})" + +--L.spawn_weapon_edit_ammo = "Hold [{walkkey}] and press [{primaryfire} or {secondaryfire}] to increase or decrease the ammo for this weapon spawn" + +--L.spawn_type_weapon = "This is a weapon spawn" +--L.spawn_type_ammo = "This is an ammunition spawn" +--L.spawn_type_player = "This is a player spawn" + +--L.spawn_remove = "Press [{secondaryfire}] to remove this spawn" + +--L.submenu_administration_entspawn_title = "Spawn Editor" +--L.header_entspawn_settings = "Spawn Editor Settings" +--L.button_start_entspawn_edit = "Start Spawn Edit" +--L.button_delete_all_spawns = "Delete all Spawns" + +--L.label_dynamic_spawns_enable = "Enable dynamic spawns for this map" +--L.label_dynamic_spawns_global_enable = "Enable dynamic spawns for all maps" + +--L.header_equipment_weapon_spawn_setup = "Weapon Spawn Settings" + +--L.help_spawn_editor_info = [[ +--The spawn editor is used to place, remove and edit spawns in the world. These spawns are for weapons, ammunition and players. +-- +--These spawns are saved in files located in 'data/ttt/weaponspawnscripts/'. They can be deleted for a hard reset. The initial spawn files are created from spawns found on the map and in the original TTT weapon spawn scripts. Pressing the reset button always reverts to the initial state. +-- +--It should be noted that this spawn system uses dynamic spawns. This is most interesting for weapons because it no longer defines a specific weapon, but a type of weapons. For example instead of a TTT shotgun spawn, there is now a general shotgun spawn where any weapon defined as shotgun can spawn. The spawn type for each weapon can be set in the 'Edit Equipment' menu. This makes it possible for any weapon to spawn on the map, or to disable certain default weapons. +-- +--Keep in mind that many changes only take effect after a new round has started.]] +--L.help_spawn_editor_enable = "On some maps it might be advised to use the original spawns found on the map without replacing them with the dynamic system. Changing this option below only affects the currently active map, so the dynamic system will still be used for every other map." +--L.help_spawn_editor_hint = "Hint: To leave the spawn editor, reopen the gamemode menu." +--L.help_spawn_editor_spawn_amount = [[ +--There currently are {weapon} weapon spawns, {ammo} ammunition spawns and {player} player spawns on this map. +--Click 'start spawn edit' to change this amount. +-- +--{weaponrandom}x Random weapon spawn +--{weaponmelee}x Melee weapon spawn +--{weaponnade}x Grenade weapon spawn +--{weaponshotgun}x Shotgun weapon spawn +--{weaponheavy}x Heavy weapon spawn +--{weaponsniper}x Sniper weapon spawn +--{weaponpistol}x Pistol weapon spawn +--{weaponspecial}x Special weapon spawn +-- +--{ammorandom}x Random ammo spawn +--{ammodeagle}x Deagle ammo spawn +--{ammopistol}x Pistol ammo spawn +--{ammomac10}x Mac10 ammo spawn +--{ammorifle}x Rifle ammo spawn +--{ammoshotgun}x Shotgun ammo spawn +-- +--{playerrandom}x Random player spawn]] + +--L.equipmenteditor_name_auto_spawnable = "Equipment spawns randomly in world" +--L.equipmenteditor_name_spawn_type = "Select spawn type" +--L.equipmenteditor_desc_auto_spawnable = [[ +--The TTT2 spawn system allows every weapon to spawn in the world. By default only weapons marked as 'AutoSpawnable' by the creator will spawn in the world, however this can be changed from within this menu. +-- +--Most of the equipment is set to 'special weapon spawns' by default. This means that equipment only spawns on random weapon spawns. However it is possible to place special weapon spawns in the world or change the spawn type here to use other existing spawn types.]] + +--L.pickup_error_inv_cached = "You cannot pick this up right now because your inventory is cached." + +-- 2021-09-02 +--L.submenu_administration_playermodels_title = "Player Models" +--L.header_playermodels_general = "General Player Model Settings" +--L.header_playermodels_selection = "Select Player Model Pool" + +--L.label_enforce_playermodel = "Enforce role player model" +--L.label_use_custom_models = "Use a randomly selected player model" +--L.label_prefer_map_models = "Prefer map specific models over default models" +--L.label_select_model_per_round = "Select a new random model each round (only on map change if disabled)" + +--L.help_prefer_map_models = [[ +--Some maps define their own player models. By default these models have a higher priority than those that are assigned automatically. By disabling this setting, map specific models are disabled. +-- +--Role specific models always have a higher priority and are unaffected by this setting.]] +--L.help_enforce_playermodel = [[ +--Some roles have custom player models. They can be disabled which can be relevant for compatibility with some player model selectors. +--Random default models can still be selected, if this setting is disabled.]] +--L.help_use_custom_models = [[ +--By default only the CS:S Phoenix player model is assigned to all players. By enabling this option however it is possible to select a player model pool. With this setting enabled each player will still be assigned the same player model, however it is a random model from the defined model pool. +-- +--This selection of models can be extended by installing more player models.]] + +-- 2021-10-06 +--L.menu_server_addons_title = "Server Addons" +--L.menu_server_addons_description = "Server-wide admin only settings for addons." + +--L.tooltip_finish_score_penalty_alive_teammates = "Alive teammates penalty: {score}" +--L.finish_score_penalty_alive_teammates = "Alive teammates penalty:" +--L.tooltip_kill_score_suicide = "Suicide: {score}" +--L.kill_score_suicide = "Suicide:" +--L.tooltip_kill_score_team = "Team kill: {score}" +--L.kill_score_team = "Team kill:" + +-- 2021-10-09 +--L.help_models_select = [[ +--Left click on the models to add them to the player model pool. Left click again to remove them. Right clicking toggles between enabled and disabled detective hats for the focused model. +-- +--The small indicator in the top left shows if the player model has a head hitbox. The icon below shows if this model is applicable for a detective hat.]] + +--L.menu_roles_title = "Role Settings" +--L.menu_roles_description = "Set up the spawning, equipment credits and more." + +--L.submenu_administration_roles_general_title = "General Role Settings" + +--L.header_roles_info = "Role Information" +--L.header_roles_selection = "Role Selection Parameters" +--L.header_roles_tbuttons = "Traitor Buttons Access" +--L.header_roles_credits = "Role Equipment Credits" +--L.header_roles_additional = "Additional Role Settings" +--L.header_roles_reward_credits = "Reward Equipment Credits" + +--L.help_roles_default_team = "Default team: {team}" +--L.help_roles_unselectable = "This role is not distributable. It is not considered in the role distribution process. Most of the times this means that this is a role that is manually assigned during the round through an event like a revival, a sidekick deagle or something similar." +--L.help_roles_selectable = "This role is distributable. If all criteria is met, this role is considered in the role distribution process." +--L.help_roles_credits = "Equipment credits are used to buy equipment in the shop. It mostly makes sense to give them only for those roles that have access to the shops. However, since it is possible to find credits on corpses, you can also give starting credits to roles as a reward to their killer." +--L.help_roles_selection_short = "The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role." +--L.help_roles_selection = [[ +--The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role. This also means that at least 5 players are needed for this role to be distributed at all. +--Keep in mind that all of this only applies if the role is considered for distribution process. +-- +--The aforementioned role distribution has a special integration with the lower limit of players. If the role is considered for distribution and the minimum value is below the value given by the distribution factor, but the amount of players is equal or greater than the lower limit, a single player can still receive this role. The distribution process then works as usual for the second player.]] +--L.help_roles_award_info = "Some roles (if enabled in their credits settings) receive equipment credits if a certain percentage of enemies has died. Related values can be tweaked here." +--L.help_roles_award_pct = "When this percentage of enemies are dead, specific roles are awarded equipment credits." +--L.help_roles_award_repeat = "Whether the credit award is handed out multiple times. For example, if the percentage is set to '0.25', and this setting is enabled, players will be awarded credits at '25%', '50%' and '75%' dead enemies respectively." +--L.help_roles_advanced_warning = "WARNING: These are advanced settings that can completely mess up the role distribution process. When in doubt keep all values at '0'. This value means that no limits are applied and the role distribution will try to assign as many roles as possible." +--L.help_roles_max_roles = [[ +--The term roles here includes both the base roles and the subroles. By default, there is no limit on how many different roles can be assigned. However, here are two different ways to limit them. +-- +--1. Limit them by a fixed amount. +--2. Limit them by a percentage. +-- +--The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] +--L.help_roles_max_baseroles = [[ +--Base roles are only those roles others inherit from. For example, the Innocent role is a base role, while a Pharaoh is a subrole of this role. By default, there is no limit on how many different base roles can be assigned. However, here are two different ways to limit them. +-- +--1. Limit them by a fixed amount. +--2. Limit them by a percentage. +-- +--The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] + +--L.label_roles_enabled = "Enable role" +--L.label_roles_min_inno_pct = "Innocent distribution per player" +--L.label_roles_pct = "Role distribution per player" +--L.label_roles_max = "Upper limit of players assigned for this role" +--L.label_roles_random = "Chance this role is distributed" +--L.label_roles_min_players = "Lower limit of players to consider distribution" +--L.label_roles_tbutton = "Role can use Traitor buttons" +--L.label_roles_credits_starting = "Starting credits" +--L.label_roles_credits_award_pct = "Credit reward percentage" +--L.label_roles_credits_award_size = "Credit reward size" +--L.label_roles_credits_award_repeat = "Credit reward repeat" +--L.label_roles_newroles_enabled = "Enable custom roles" +--L.label_roles_max_roles = "Upper role limit" +--L.label_roles_max_roles_pct = "Upper role limit by percentage" +--L.label_roles_max_baseroles = "Upper base role limit" +--L.label_roles_max_baseroles_pct = "Upper base role limit by percentage" +--L.label_detective_hats = "Enable hats for policing roles like the Detective (if player model allows to have them)" + +--L.ttt2_desc_innocent = "An Innocent has no special abilities. They have to find the evil ones among the terrorists and kill them. But they have to be careful not to kill their teammates." +--L.ttt2_desc_traitor = "The Traitor is the enemy of the Innocent. They have an equipment menu with which they are being able to buy special equipment. They have to kill everyone but their teammates." +--L.ttt2_desc_detective = "The Detective is the one whom the Innocents can trust. But who even is an Innocent? The mighty Detective has to find all the evil terrorists. The equipment in their shop may help them with this task." + +-- 2021-10-10 +--L.button_reset_models = "Reset Player Models" + +-- 2021-10-13 +--L.help_roles_credits_award_kill = "Another way of gaining credits is by killing high value players with a 'public role' such as a Detective. If the killer's role has this enabled, they gain the below defined amount of credits." +--L.help_roles_credits_award = [[ +--There are two different ways to be awarded credits in base TTT2: +-- +--1. If a certain percentage of the enemy team is dead, the whole team is awarded credits. +--2. If a player killed a high value player with a 'public role' such as a Detective, the killer is awarded credits. +-- +--Please note, that this still can be enabled/disabled for every role, even if the whole team is awarded. For example, if team Innocent is awarded, but the Innocent role has this disabled, only the Detective will receive their credits. +--The balancing values for this feature can be set in 'Administration' -> 'General Role Settings'.]] +--L.help_detective_hats = [[ +--Policing roles such as the Detective may wear hats to show their authority. They lose them on death or if damaged at the head. +-- +--Some player models do not support hats by default. This can be changed in 'Administration' -> 'Player Models']] + +--L.label_roles_credits_award_kill = "Credit reward amount for the kill" +--L.label_roles_credits_dead_award = "Enable credits award for certain percentage of dead enemies" +--L.label_roles_credits_kill_award = "Enable credits award for high value player kill" +--L.label_roles_min_karma = "Lower limit of Karma to consider distribution" + +-- 2021-11-07 +--L.submenu_administration_administration_title = "Administration" +--L.submenu_administration_voicechat_title = "Voice chat / Text chat" +--L.submenu_administration_round_setup_title = "Round Settings" +--L.submenu_administration_mapentities_title = "Map Entities" +--L.submenu_administration_inventory_title = "Inventory" +--L.submenu_administration_karma_title = "Karma" +--L.submenu_administration_sprint_title = "Sprinting" +--L.submenu_administration_playersettings_title = "Player Settings" + +--L.header_roles_special_settings = "Special Role Settings" +--L.header_equipment_additional = "Additional Equipment Settings" +--L.header_administration_general = "General Administrative Settings" +--L.header_administration_logging = "Logging" +--L.header_administration_misc = "Miscellaneous" +--L.header_entspawn_plyspawn = "Player Spawn Settings" +--L.header_voicechat_general = "General Voice chat Settings" +--L.header_voicechat_battery = "Voice chat Battery" +--L.header_voicechat_locational = "Proximity Voice chat" +--L.header_playersettings_plyspawn = "Player Spawn Settings" +--L.header_round_setup_prep = "Round: Preparing" +--L.header_round_setup_round = "Round: Active" +--L.header_round_setup_post = "Round: Post" +--L.header_round_setup_map_duration = "Map Session" +--L.header_textchat = "Text chat" +--L.header_round_dead_players = "Dead Player Settings" +--L.header_administration_scoreboard = "Scoreboard Settings" +--L.header_hud_toggleable = "Toggleable HUD Elements" +--L.header_mapentities_prop_possession = "Prop Possession" +--L.header_mapentities_doors = "Doors" +--L.header_karma_tweaking = "Karma Tweaking" +--L.header_karma_kick = "Karma Kick and Ban" +--L.header_karma_logging = "Karma Logging" +--L.header_inventory_gernal = "Inventory Size" +--L.header_inventory_pickup = "Inventory Weapon Pickup" +--L.header_sprint_general = "Sprint Settings" +--L.header_playersettings_armor = "Armor System Settings" + +--L.help_killer_dna_range = "When a player is killed by another player, a DNA sample is left on their body. The setting below defines the maximum distance in hammer units for DNA samples to be left. If the killer is further away than this value when the victim dies, no sample will be left on the corpse." +--L.help_killer_dna_basetime = "The base time in seconds until a DNA sample decays, if the killer is 0 Hammer units away. The farther the killer is, the less time will be given to the DNA sample to decay." +--L.help_dna_radar = "The TTT2 DNA scanner shows the exact distance and direction of the selected DNA sample if equipped. However, there is also a classic DNA scanner mode that updates the selected sample with an in-world rendering every time the cooldown has passed." +--L.help_idle = "The idle mode is used to forcefully move idle players into the spectator mode. To leave this mode, they will have to disable it in their 'gameplay' menu." +--L.help_namechange_kick = [[ +--A name change during an active round could be abused. Therefore, this is prohibited by default and will lead to the offending player being kicked from the server. +-- +--If the bantime is greater than 0, the player will be unable to reconnect to the server until that time has passed.]] +--L.help_damage_log = "Each time a player is damaged, a damage log entry is added to the console if enabled. This can also be stored to disk after a round has ended. The file is located at 'data/terrortown/logs/'" +--L.help_spawn_waves = [[ +--If this variable is set to 0, all players are spawned at once. For servers with huge amounts of players, it can be beneficial to spawn the players in waves. The spawn wave interval is the time between each spawn wave. A spawn wave always spawns as many players as there are valid spawn points. +-- +--Note: Make sure that the preparing time is long enough for the desired amount of spawn waves.]] +--L.help_voicechat_battery = [[ +--Voice chatting with enabled voice chat battery reduces battery charge. When it's empty, the player can't use voice chat and has to wait for it to recharge. This can help to prevent excessive voice chat usage. +-- +--Note: 'Tick' refers to a game tick. For example, if the tick rate is set to 66, then it will be 1/66th of a second.]] +--L.help_ply_spawn = "Player settings that are used on player (re-)spawn." +--L.help_haste_mode = [[ +--Haste mode balances the game by increasing the round time with every dead player. Only roles that see missing in action players can see the real round time. Every other role can only see the haste mode starting time. +-- +--If haste mode is enabled, the fixed round time is ignored.]] +--L.help_round_limit = "After one of the set limit conditions is met, a map change is triggered." +--L.help_armor_balancing = "The following values can be used to balance the armor." +--L.help_item_armor_classic = "If classic armor mode is enabled, only the previous settings matter. Classic armor mode means that a player can only buy armor once in a round, and that this armor blocks 30% of the incoming bullet and crowbar damage until they die." +--L.help_item_armor_dynamic = [[ +--Dynamic armor is the TTT2 approach to make armor more interesting. The amount of armor that can be bought is now unlimited, and the armor value stacks. Getting damaged decreases the armor value. The armor value per bought armor item is set in the 'Equipment Settings' of said item. +-- +--When taking damage, a certain percentage of this damage is converted into armor damage, a different percentage is still applied to the player and the rest vanishes. +-- +--If reinforced armor is enabled, the damage applied to the player is decreased by 15% as long as the armor value is above the reinforcement threshold.]] +--L.help_sherlock_mode = "The sherlock mode is the classic TTT mode. If the sherlock mode is disabled, dead bodies can not be confirmed, the scoreboard shows everyone as alive and the spectators can talk to the living players." +--L.help_prop_possession = [[ +--Prop possession can be used by spectators to possess props lying in the world and use the slowly recharging 'punch-o-meter' to move said prop around. +-- +--The maximum value of the 'punch-o-meter' consists of a possession base value, where the kills/deaths difference clamped inbetween two defined limits is added. The meter slowly recharges over time. The set recharge time is the time needed to recharge a single point in the 'punch-o-meter'.]] +--L.help_karma = "Players start with a certain amount of Karma, and lose it when they damage/kill teammates. The amount they lose is dependent on the Karma of the person they hurt or killed. Lower Karma reduces damage given." +--L.help_karma_strict = "If strict Karma is enabled, the damage penalty increases more quickly as Karma goes down. When it is off, the damage penalty is very low when people stay above 800. Enabling strict mode makes Karma play a larger role in discouraging any unnecessary kills, while disabling it results in a more “loose” game where Karma only hurts players who constantly kill teammates." +--L.help_karma_max = "Setting the value of the max Karma above 1000 doesn't give a damage bonus to players with more than 1000 Karma. It can be used as a Karma buffer." +--L.help_karma_ratio = "The ratio of the damage that is used to compute how much of the victim's Karma is subtracted from the attacker's if both are in the same team. If a team kill happens, a further penalty is applied." +--L.help_karma_traitordmg_ratio = "The ratio of the damage that is used to compute how much of the victim's Karma is added to the attacker's if both are in different teams. If an enemy kill happens, a further bonus is applied." +--L.help_karma_bonus = "There are also two different passive ways to gain Karma during a round. First is a karma restoration which applied to every player at the round end. Then a secondary clean round bonus is given if no teammates were hurt or killed by a player." +--L.help_karma_clean_half = [[ +--When a player's Karma is above the starting level (meaning the Karma max has been configured to be higher than that), all their Karma increases will be reduced based on how far their Karma is above that starting level. So it goes up slower the higher it is. +-- +--This reduction goes in a curve of exponential decay: initially it's fast, and it slows down as the increment gets smaller. This convar sets at what point the bonus has been halved (so the half-life). With the default value of 0.25, if the starting amount of Karma is 1000 and the max 1500, and a player has Karma 1125 ((1500 - 1000) * 0.25 = 125), then his clean round bonus will be 30 / 2 = 15. So to make the bonus go down faster you’d set this convar lower, to make it go down slower you’d increase it towards 1.]] +--L.help_max_slots = "Sets the maximum amount of weapons per slot. '-1' means that there is no limit." +--L.help_item_armor_value = "This is the armor value given by the armor item in dynamic mode. If classic mode is enabled (see 'Administration' -> 'Player Settings') then every value greater than 0 is counted as existing armor." + +--L.label_killer_dna_range = "Max kill range to leave DNA" +--L.label_killer_dna_basetime = "Sample life base time" +--L.label_dna_scanner_slots = "DNA sample slots" +--L.label_dna_radar = "Enable classic DNA scanner mode" +--L.label_dna_radar_cooldown = "DNA scanner cooldown" +--L.label_radar_charge_time = "Recharge time after being used" +--L.label_crowbar_shove_delay = "Cooldown after crowbar push" +--L.label_idle = "Enable idle mode" +--L.label_idle_limit = "Maximum idle time in seconds" +--L.label_namechange_kick = "Enable name change kick" +--L.label_namechange_bantime = "Banned time in minutes after kick" +--L.label_log_damage_for_console = "Enable damage logging in console" +--L.label_damagelog_save = "Save damage log to disk" +--L.label_debug_preventwin = "Prevent any win condition [debug]" +--L.label_bots_are_spectators = "Bots are always spectators" +--L.label_tbutton_admin_show = "Show traitor buttons to admins" +--L.label_ragdoll_carrying = "Enable ragdoll carrying" +--L.label_prop_throwing = "Enable prop throwing" +--L.label_weapon_carrying = "Enable weapon carrying" +--L.label_weapon_carrying_range = "Weapon carry range" +--L.label_prop_carrying_force = "Prop pickup force" +--L.label_teleport_telefrags = "Kill blocking player(s) when teleporting (telefrag)" +--L.label_allow_discomb_jump = "Allow disco jump for grenade thrower" +--L.label_spawn_wave_interval = "Spawn wave interval in seconds" +--L.label_voice_enable = "Enable voice chat" +--L.label_voice_drain = "Enable the voice chat battery feature" +--L.label_voice_drain_normal = "Drain per tick for normal players" +--L.label_voice_drain_admin = "Drain per tick for admins and public policing roles" +--L.label_voice_drain_recharge = "Recharge rate per tick of not voice chatting" +--L.label_locational_voice = "Enable proximity voice chat for living players" +--L.label_armor_on_spawn = "Player armor on (re-)spawn" +--L.label_prep_respawn = "Enable instant respawn during preparing phase" +--L.label_preptime_seconds = "Preparing time in seconds" +--L.label_firstpreptime_seconds = "First preparing time in seconds" +--L.label_roundtime_minutes = "Fixed round time in minutes" +--L.label_haste = "Enable haste mode" +--L.label_haste_starting_minutes = "Haste mode starting time in minutes" +--L.label_haste_minutes_per_death = "Additional time in minutes per death" +--L.label_posttime_seconds = "Postround time in seconds" +--L.label_round_limit = "Upper limit of rounds" +--L.label_time_limit_minutes = "Upper limit of playtime in minutes" +--L.label_nade_throw_during_prep = "Enable grenade throwing during preparing time" +--L.label_postround_dm = "Enable deathmatch after round ended" +--L.label_session_limits_enabled = "Enable session limits" +--L.label_spectator_chat = "Enable spectators chatting with everybody" +--L.label_lastwords_chatprint = "Print last words to chat if killed while typing" +--L.label_identify_body_woconfirm = "Identify corpse without pressing the 'confirm' button" +--L.label_announce_body_found = "Announce that a body was found when the body was confirmed" +--L.label_confirm_killlist = "Announce kill list of confirmed corpse" +--L.label_dyingshot = "Shoot on death if in ironsights [experimental]" +--L.label_armor_block_headshots = "Enable armor blocking headshots" +--L.label_armor_block_blastdmg = "Enable armor blocking blast damage" +--L.label_armor_dynamic = "Enable dynamic armor" +--L.label_armor_value = "Amount of armor given by the armor item" +--L.label_armor_damage_block_pct = "Damage percentage taken by armor" +--L.label_armor_damage_health_pct = "Damage percentage taken by player" +--L.label_armor_enable_reinforced = "Enable reinforced armor" +--L.label_armor_threshold_for_reinforced = "Reinforced armor threshold" +--L.label_sherlock_mode = "Enable sherlock mode" +--L.label_highlight_admins = "Highlight server admins" +--L.label_highlight_dev = "Highlight TTT2 developer" +--L.label_highlight_vip = "Highlight TTT2 supporter" +--L.label_highlight_addondev = "Highlight TTT2 addon developer" +--L.label_highlight_supporter = "Highlight others" +--L.label_enable_hud_element = "Enable {elem} HUD element" +--L.label_spec_prop_control = "Enable prop possession" +--L.label_spec_prop_base = "Possession base value" +--L.label_spec_prop_maxpenalty = "Lower possession bonus limit" +--L.label_spec_prop_maxbonus = "Upper possession bonus limit" +--L.label_spec_prop_force = "Possession push force" +--L.label_spec_prop_rechargetime = "Recharge time in seconds" +--L.label_doors_force_pairs = "Force close-by doors as double doors" +--L.label_doors_destructible = "Enable destructible doors" +--L.label_doors_locked_indestructible = "Initially locked doors are indestructible" +--L.label_doors_health = "Door health" +--L.label_doors_prop_health = "Destructed door health" +--L.label_minimum_players = "Minimum player amount to start round" +--L.label_karma = "Enable Karma" +--L.label_karma_strict = "Enable strict Karma" +--L.label_karma_starting = "Starting Karma" +--L.label_karma_max = "Maximum Karma" +--L.label_karma_ratio = "Penalty ratio for team damage" +--L.label_karma_kill_penalty = "Kill penalty for team kill" +--L.label_karma_round_increment = "Karma restoration" +--L.label_karma_clean_bonus = "Clean round bonus" +--L.label_karma_traitordmg_ratio = "Bonus ratio for enemy damage" +--L.label_karma_traitorkill_bonus = "Kill bonus for enemy kill" +--L.label_karma_clean_half = "Clean round bonus reduction" +--L.label_karma_persist = "Karma persists over map changes" +--L.label_karma_low_autokick = "Automatically kick players with low Karma" +--L.label_karma_low_amount = "Low Karma threshold" +--L.label_karma_low_ban = "Ban picked players with low Karma" +--L.label_karma_low_ban_minutes = "Ban time in minutes" +--L.label_karma_debugspam = "Enable debug output to console about Karma changes" +--L.label_max_melee_slots = "Max melee slots" +--L.label_max_secondary_slots = "Max secondary slots" +--L.label_max_primary_slots = "Max primary slots" +--L.label_max_nade_slots = "Max grenade slots" +--L.label_max_carry_slots = "Max carry slots" +--L.label_max_unarmed_slots = "Max unarmed slots" +--L.label_max_special_slots = "Max special slots" +--L.label_max_extra_slots = "Max extra slots" +--L.label_weapon_autopickup = "Enable automatic weapon pickup" +--L.label_sprint_enabled = "Enable sprinting" +--L.label_sprint_max = "Max sprinting stamina" +--L.label_sprint_stamina_consumption = "Stamina consumption factor" +--L.label_sprint_stamina_regeneration = "Stamina regeneration factor" +--L.label_crowbar_unlocks = "Primary attack can be used as interaction (i.e. unlocking)" +--L.label_crowbar_pushforce = "Crowbar push force" + +-- 2022-07-02 +--L.header_playersettings_falldmg = "Fall Damage Settings" + +--L.label_falldmg_enable = "Enable fall damage" +--L.label_falldmg_min_velocity = "Minimum velocity threshold for fall damage to occur" +--L.label_falldmg_exponent = "Exponent to increase fall damage in relation to velocity" + +--L.help_falldmg_exponent = [[ +--This value modifies how exponentially fall damage is increased with the speed the player hits the ground at. +-- +--Take care when altering this value. Setting it too high can make even the smallest falls lethal, while setting it too low will allow players to fall from extreme heights and suffer little to no damage.]] + +-- 2023-02-08 +--L.testpopup_title = "A Test Popup, now with a multiline title, how NICE!" +--L.testpopup_subtitle = "Well, hello there! This is a fancy popup with some special information. The text can be also multiline, how fancy! Ugh, I could add so much more text if I'd had any ideas..." + +--L.hudeditor_chat_hint1 = "[TTT2][INFO] Hover over an element, press and hold [LMB] and move the mouse to MOVE or RESIZE it." +--L.hudeditor_chat_hint2 = "[TTT2][INFO] Press and hold the ALT key for symmetric resizing." +--L.hudeditor_chat_hint3 = "[TTT2][INFO] Press and hold the SHIFT key to move on axis and to keep the aspect ratio." +--L.hudeditor_chat_hint4 = "[TTT2][INFO] Press [RMB] -> 'Close' to exit the HUD Editor!" + +--L.guide_nothing_title = "Nothing here yet!" +--L.guide_nothing_desc = "This is work in progress, help us by contributing to the project on GitHub." + +--L.sb_rank_tooltip_developer = "TTT2 Developer" +--L.sb_rank_tooltip_vip = "TTT2 Supporter" +--L.sb_rank_tooltip_addondev = "TTT2 Addon Developer" +--L.sb_rank_tooltip_admin = "Server Admin" +--L.sb_rank_tooltip_streamer = "Streamer" +--L.sb_rank_tooltip_heroes = "TTT2 Heroes" +--L.sb_rank_tooltip_team = "Team" + +--L.tbut_adminarea = "ADMIN AREA:" + +-- 2023-08-10 +--L.equipmenteditor_name_damage_scaling = "Damage Scaling" + +-- 2023-08-11 +--L.equipmenteditor_name_allow_drop = "Allow Drop" +--L.equipmenteditor_desc_allow_drop = "If enabled, the equipment can be dropped freely by the player." + +--L.equipmenteditor_name_drop_on_death_type = "Drop on Death" +--L.equipmenteditor_desc_drop_on_death_type = "Attempt overriding the action taken for whether the equipment is dropped on player's death." + +--L.drop_on_death_type_default = "Default (weapon-defined)" +--L.drop_on_death_type_force = "Force Drop on Death" +--L.drop_on_death_type_deny = "Deny Drop on Death" + +-- 2023-08-26 +--L.equipmenteditor_name_kind = "Equipment Slot" +--L.equipmenteditor_desc_kind = "The inventory slot the equipment will occupy." + +--L.slot_weapon_melee = "Melee Slot" +--L.slot_weapon_pistol = "Pistol Slot" +--L.slot_weapon_heavy = "Heavy Slot" +--L.slot_weapon_nade = "Grenade Slot" +--L.slot_weapon_carry = "Carry Slot" +--L.slot_weapon_unarmed = "Unarmed Slot" +--L.slot_weapon_special = "Special Slot" +--L.slot_weapon_extra = "Extra Slot" +--L.slot_weapon_class = "Class Slot" + +-- 2023-10-04 +--L.label_voice_duck_spectator = "Duck spectator voices" +--L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +--L.label_voice_scaling = "Voice Volume Scaling Mode" +--L.label_voice_scaling_mode_linear = "Linear" +--L.label_voice_scaling_mode_power4 = "Power 4" +--L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "Resultat från Kroppsvisit - {player}" +L.search_info = "Information" +L.search_confirm = "Bekräfta Död" +--L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +--L.search_take_credits = "Take {credits} Credit(s)" +--L.search_confirm_forbidden = "Confirm forbidden" +--L.search_confirmed = "Death Confirmed" +L.search_call = "Kalla på Detektiv" +--L.search_called = "Death Reported" + +--L.search_team_role_unknown = "???" + +L.search_words = "Någonting säger dig att en del av den här personens sista ord var: '{lastwords}'" +L.search_armor = "Han bar skyddsutrustning som inte var av vanlig standard." +--L.search_disguiser = "They were carrying a device that could hide their identity." +L.search_radar = "Han bar någon sorts radar. Den fungerar inte längre." +L.search_c4 = "In en av fickorna hittar du en lapp. Det står att om sladd nummer {num} klipps av, kommer bomben att stängas av." + +L.search_dmg_crush = "Många av hans ben är brutna. Det verkar som att ett tungt föremål dödade honom." +L.search_dmg_bullet = "Det är uppenbart att han blev ihjälskjuten." +L.search_dmg_fall = "Han föll till sin död." +L.search_dmg_boom = "Hans skador och svedda kläder antyder att en explosion blev slutet för honom." +L.search_dmg_club = "Kroppen är alldeles blåslagen och mörbultad. Uppenbarligen blev han ihjälklubbad." +L.search_dmg_drown = "Kroppen visar tecken på drunkning." +L.search_dmg_stab = "Han blev stucken och förblödde hastigt." +L.search_dmg_burn = "Det luktar grillad terrorist häromkring..." +--L.search_dmg_teleport = "It looks like their DNA was scrambled by tachyon emissions!" +L.search_dmg_car = "När den här terroristen gick över vägen, blev han överkörd av en hänsynslös bilförare." +L.search_dmg_other = "Du kan inte hitta en specifik orsakt till den här terroristens död." + +--L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +--L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +--L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +--L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +--L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +--L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +--L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +--L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +--L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +--L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +--L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +--L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +--L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +--L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +--L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +--L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +--L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +--L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +--L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +--L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +--L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +--L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +--L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +--L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +--L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "En {weapon} användes för att döda den här terroristen." +L.search_head = "Det slutgiltiga skottet var i huvudet. Det fanns ingen tid till att skrika." +--L.search_time = "They died a while before you conducted the search." +--L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "Du hittade en lista över mordoffer som bekräftar morden på {player}." +L.search_kills2 = "Du hittade en lista över mordoffer med dessa namn: {player}" +L.search_eyes = "Genom att använda dina detektivfärdigheter kan du identifiera den sista personen han såg: {player}. Mördaren, eller ett sammanträffande?" + +--L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +--L.search_kill_distance_point_blank = "It was a point blank attack." +--L.search_kill_distance_close = "The attack came from a short distance." +--L.search_kill_distance_far = "The victim was attacked from a long distance away." + +--L.search_kill_from_front = "The victim was shot from the front." +--L.search_kill_from_back = "The victim was shot from behind." +--L.search_kill_from_side = "The victim was shot from the side." + +--L.search_hitgroup_head = "The projectile was found in their head." +--L.search_hitgroup_chest = "The projectile was found in their chest." +--L.search_hitgroup_stomach = "The projectile was found in their stomach." +--L.search_hitgroup_rightarm = "The projectile was found in their right arm." +--L.search_hitgroup_leftarm = "The projectile was found in their left arm." +--L.search_hitgroup_rightleg = "The projectile was found in their right leg." +--L.search_hitgroup_leftleg = "The projectile was found in their left leg." +--L.search_hitgroup_gear = "The projectile was found in their hip." + +--L.search_policingrole_report_confirm = [[ +--A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +--L.search_policingrole_confirm_disabled_1 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +--L.search_policingrole_confirm_disabled_2 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know! +--You can see the information in here after they confirmed it.]] +--L.search_spec = [[ +--As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +--L.search_title_words = "Victim's last words" +--L.search_title_c4 = "Defusion mishap" +--L.search_title_dmg_crush = "Crush damage ({amount} HP)" +--L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +--L.search_title_dmg_fall = "Fall damage ({amount} HP)" +--L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +--L.search_title_dmg_club = "Club damage ({amount} HP)" +--L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +--L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +--L.search_title_dmg_burn = "Burning damage ({amount} HP)" +--L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +--L.search_title_dmg_car = "Car accident ({amount} HP)" +--L.search_title_dmg_other = "Unknown damage ({amount} HP)" +--L.search_title_time = "Death time" +--L.search_title_dna = "DNA sample decay" +--L.search_title_kills = "The victim's kill list" +--L.search_title_eyes = "The killer's shadow" +--L.search_title_floor = "Floor of the crime scene" +--L.search_title_credits = "{credits} Equipment credit(s)" +--L.search_title_water = "Water level {level}" +--L.search_title_policingrole_report_confirm = "Confirm to report death" +--L.search_title_policingrole_confirm_disabled = "Report corpse" +--L.search_title_spectator = "You are a spectator" + +--L.target_credits_on_confirm = "Confirm to receive unspent credits" +--L.target_credits_on_search = "Search to receive unspent credits" +--L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +--L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +--L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +--L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +--L.label_inspect_confirm_mode = "Select body search mode" +--L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +--L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +--L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +--L.help_inspect_confirm_mode = [[ +--There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. +-- +--mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. +-- +--mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. +-- +--mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +--L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +--L.header_hud_elements_customize = "Customize the HUD-Elements" +--L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +--L.help_keyhelp = [[ +--Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: +-- +--Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +--Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +--Equipment: Some equipment items have their own bindings, these are shown in this category. +-- +--Disabled categories are still shown when the scoreboard is visible]] + +--L.label_keyhelp_show_core = "Enable always showing the core bindings" +--L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +--L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +--L.header_interface_keys = "Key helper settings" +--L.header_interface_wepswitch = "Weapon switch UI settings" + +--L.label_keyhelper_help = "open gamemode menu" +--L.label_keyhelper_mutespec = "cycle spectator voice mode" +--L.label_keyhelper_shop = "open equipment shop" +--L.label_keyhelper_show_pointer = "free mouse pointer" +--L.label_keyhelper_possess_focus_entity = "possess focused entity" +--L.label_keyhelper_spec_focus_player = "spectate focused player" +--L.label_keyhelper_spec_previous_player = "previous player" +--L.label_keyhelper_spec_next_player = "next player" +--L.label_keyhelper_spec_player = "spectate random player" +--L.label_keyhelper_possession_jump = "prop: jump" +--L.label_keyhelper_possession_left = "prop: left" +--L.label_keyhelper_possession_right = "prop: right" +--L.label_keyhelper_possession_forward = "prop: forward" +--L.label_keyhelper_possession_backward = "prop: backward" +--L.label_keyhelper_free_roam = "leave object and roam free" +--L.label_keyhelper_flashlight = "toggle flashlight" +--L.label_keyhelper_quickchat = "open quickchat" +--L.label_keyhelper_voice_global = "global voice chat" +--L.label_keyhelper_voice_team = "team voice chat" +--L.label_keyhelper_chat_global = "global chat" +--L.label_keyhelper_chat_team = "team chat" +--L.label_keyhelper_show_all = "show all" +--L.label_keyhelper_disguiser = "toggle disguiser" +--L.label_keyhelper_save_exit = "save and exit" +--L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +--L.item_armor_reinforced = "Reinforced Armor" +--L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +--L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +--L.status_speed_name = "Speed Multiplier" +--L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +--L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +--L.status_on = "on" +--L.status_off = "off" + +--L.crowbar_help_primary = "Attack" +--L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +--L.help_HUD_enable_description = [[ +--Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +--L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +--L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +--L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +--L.header_soundeffect_settings = "Sound Effects" +--L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +--L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +--L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +--L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +--L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +--L.help_prop_spec_dash = [[ +--Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. +-- +--This variable is a multiplier of the push force.]] +--L.label_spec_prop_dash = "Dash force multiplier" +--L.label_keyhelper_possession_dash = "prop: dash in view direction" +--L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +--L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +--L.c4_help_primary = "Place the C4" +--L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +--L.magneto_help_primary = "Push entity" +--L.magneto_help_secondary = "Pull / pickup entity" +--L.knife_help_primary = "Stab" +--L.knife_help_secondary = "Throw knife" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +--L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +--L.newton_force = "FORCE" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +--L.beacon_help_pri = "Throw Beacon on the ground" +--L.beacon_help_sec = "Stick Beacon to surface" +--L.beacon_name = "Beacon" +--L.beacon_desc = [[ +--Broadcasts player locations to everyone in a sphere around this beacon. +-- +--Use to keep track of locations on the map that are hard to see.]] + +--L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +--L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +--L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +--L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +--L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder} bekräftade att {victim} har dött." +--L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +--L.builtin_marker = "Built-in." +--L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +--L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +--L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +--L.submenu_gameplay_accessibility_title = "Accessibility" + +--L.header_accessibility_settings = "Accessibility Settings" + +--L.label_enable_dynamic_fov = "Enable dynamic FOV change" +--L.label_enable_bobbing = "Enable view bobbing" +--L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +--L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +--L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +--L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +--For a shotgun, this would affect each pellet. +--For a rifle, this would affect just the bullet. +--For the poltergeist, this would affect each "thump" and the final explosion. +-- +--0.5 = Deal half the amount of damage. +--2 = Deal twice the amount of damage. +-- +--Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +--L.binoc_help_reload = "Clear target." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +--L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +--L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +--L.label_crosshair_mode = "Crosshair mode" +--L.label_crosshair_static_length = "Enable static crosshair line length" + +--L.choice_crosshair_mode_0 = "Lines and dot" +--L.choice_crosshair_mode_1 = "Lines only" +--L.choice_crosshair_mode_2 = "Dot only" + +--L.help_crosshair_scale_enable = [[ +--Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. +-- +--If the line length is kept static, only the gap scales with cone changes.]] + +--L.header_weapon_settings = "Weapon Settings" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +--L.decoy_help_primary = "Throw Decoy on the ground" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/tr.lua b/lua/terrortown/lang/tr.lua new file mode 100644 index 000000000..1cc92dd58 --- /dev/null +++ b/lua/terrortown/lang/tr.lua @@ -0,0 +1,2191 @@ +-- Turkish language strings + +local L = LANG.CreateLanguage("tr") + +-- Compatibility language name that might be removed soon. +-- the alias name is based on the original TTT language name + +L.__alias = "türkçe" + +L.lang_name = "Türkçe (Turkish)" + +-- General text used in various places +L.traitor = "Hain" +L.detective = "Dedektif" +L.innocent = "Masum" +L.last_words = "Son Sözleri" + +L.terrorists = "Teröristler" +L.spectators = "İzleyiciler" + +L.nones = "Takım Yok" +L.innocents = "Masum Takımı" +L.traitors = "Hain Takımı" + +-- Round status messages +L.round_minplayers = "Yeni raunda başlamak için yeterli sayıda oyuncu yok..." +L.round_voting = "Oylama devam ediyor, yeni raunt {num} saniye geciktirilecek..." +L.round_begintime = "Yeni raunt {num} saniyede başlayacak. Kendini hazırla." +L.round_selected = "Hainler seçildi." +L.round_started = "Raunt başladı!" +L.round_restart = "Raunt bir yönetici tarafından yeniden başlatıldı." + +L.round_traitors_one = "Hey hain, yalnızsın." +L.round_traitors_more = "Hey hain, bunlar senin müttefiklerin {names}" + +L.win_time = "Süre doldu. Hainler kaybetti." +L.win_traitors = "Hainler kazandı!" +L.win_innocents = "Masumlar kazandı!" +L.win_nones = "Kimse kazanamadı!" +L.win_showreport = "{num} saniye boyunca raunt raporuna bakalım." + +L.limit_round = "Raunt sınırına ulaşıldı. Bir sonraki harita yakında yüklenecek." +L.limit_time = "Zaman sınırına ulaşıldı. Bir sonraki harita yakında yüklenecek." +L.limit_left = "Harita değişmeden önce {num} raunt veya {time} dakika kaldı." + +-- Credit awards +L.credit_all = "Takımınıza performansınız için {num} ekipman kredisi verildi." +L.credit_kill = "Bir {role} öldürdüğünüz için {num} kredi aldınız." + +-- Karma +L.karma_dmg_full = "Karman {amount}, bu yüzden bu raunt tam hasar veriyorsun!" +L.karma_dmg_other = "Karman {amount}. Sonuç olarak, verdiğiniz tüm hasar ​%{num}​ azaltılır." + +-- Body identification messages +L.body_found = "{finder}, {victim} adlı kişinin cesedini buldu. {role}" +L.body_found_team = "{finder}, {victim} adlı kişinin cesedini buldu. {role} ({team})" + +-- The {role} in body_found will be replaced by one of the following: +L.body_found_traitor = "Onlar bir Haindi!" +L.body_found_det = "Onlar bir Dedektifti." +L.body_found_inno = "Onlar bir Masumdu." + +L.body_call = "{player}, {victim} adlı kurbanın cesedine Dedektif çağırdı!" +L.body_call_error = "Bir Dedektif çağırmadan önce bu oyuncunun ölümünü onaylamalısın!" + +L.body_burning = "Ah! Bu ceset yanıyor!" +L.body_credits = "Cesette {num} kredi buldunuz!" + +-- Menus and windows +L.close = "Kapat" +L.cancel = "İptal" + +-- For navigation buttons +L.next = "Sonraki" +L.prev = "Önceki" + +-- Equipment buying menu +L.equip_title = "Ekipman" +L.equip_tabtitle = "Ekipman Sipariş Et" + +L.equip_status = "Sipariş durumu" +L.equip_cost = "{num} krediniz kaldı." +L.equip_help_cost = "Satın aldığınız her ekipman parçası 1 krediye mal olur." + +L.equip_help_carry = "Yalnızca yeriniz olan şeyleri satın alabilirsiniz." +L.equip_carry = "Bu ekipmanı taşıyabilirsiniz." +L.equip_carry_own = "Bu öğeyi zaten taşıyorsunuz." +L.equip_carry_slot = "{slot} yuvasında zaten bir silah taşıyorsun." +L.equip_carry_minplayers = "Sunucuda bu silahı etkinleştirmek için yeterli oyuncu yok." + +L.equip_help_stock = "Belirli öğelerden her rauntta yalnızca bir tane satın alabilirsiniz." +L.equip_stock_deny = "Bu ürün artık stokta yok." +L.equip_stock_ok = "Bu ürün stokta mevcut." + +L.equip_custom = "Bu sunucu tarafından eklenen özel öğe." + +L.equip_spec_name = "Ad" +L.equip_spec_type = "Tür" +L.equip_spec_desc = "Açıklama" + +L.equip_confirm = "Ekipman satın al" + +-- Disguiser tab in equipment menu +L.disg_name = "Kılık Değiştirici" +L.disg_menutitle = "Kılık değiştirme kontrolü" +L.disg_not_owned = "Kılık Değiştirici taşımıyorsun!" +L.disg_enable = "Kılık değiştirmeyi etkinleştir" + +L.disg_help1 = "Kılık değiştiğinde, biri sana baktığında adın, sağlığın ve Karman gösterilmez. Ayrıca, bir Dedektifin radarından gizleneceksiniz." +L.disg_help2 = "Menüyü kullanmadan kılık değiştirmek için Numpad Enter tuşuna basın. Konsolu kullanarak farklı bir tuşa 'ttt_toggle_digguise' olarak da atayabilirsiniz." + +-- Radar tab in equipment menu +L.radar_name = "Radar" +L.radar_menutitle = "Radar kontrolü" +L.radar_not_owned = "Radar taşımıyorsun!" +L.radar_scan = "Tarama yap" +L.radar_auto = "Otomatik tekrarlı tarama" +L.radar_help = "Tarama sonuçları {num} saniye boyunca gösterilir, bundan sonra Radar yeniden yüklenir ve tekrar kullanılabilir." +L.radar_charging = "Radarın hala şarj oluyor!" + +-- Transfer tab in equipment menu +L.xfer_name = "Transfer" +L.xfer_menutitle = "Kredi transferi" +L.xfer_send = "Kredi gönder" + +L.xfer_no_recip = "Alıcı geçerli değil, kredi transferi iptal edildi." +L.xfer_no_credits = "Transfer için yetersiz kredi." +L.xfer_success = "{player} adlı oyuncuya kredi transferi tamamlandı." +L.xfer_received = "{player} sana {num} kredi verdi." + +-- Radio tab in equipment menu +L.radio_name = "Radyo" +L.radio_help = "Radyonuzun bu sesi çalmasını sağlamak için bir düğmeye tıklayın." +L.radio_notplaced = "Sesi çalmak için Radyoyu yerleştirmelisiniz." + +-- Radio soundboard buttons +L.radio_button_scream = "Çığlık" +L.radio_button_expl = "Patlama" +L.radio_button_pistol = "Tabanca atışları" +L.radio_button_m16 = "M16 atışları" +L.radio_button_deagle = "Deagle atışları" +L.radio_button_mac10 = "MAC10 atışları" +L.radio_button_shotgun = "Pompalı tüfek atışları" +L.radio_button_rifle = "Tüfek atışı" +L.radio_button_huge = "H.U.G.E. patlaması" +L.radio_button_c4 = "C4 bip sesi" +L.radio_button_burn = "Yanma sesi" +L.radio_button_steps = "Adım sesi" + +-- Intro screen shown after joining +L.intro_help = "Oyunda yeniyseniz, talimatlar için F1'e basın!" + +-- Radiocommands/quickchat +L.quick_title = "Hızlı sohbet tuşları" + +L.quick_yes = "Evet." +L.quick_no = "Hayır." +L.quick_help = "Yardım edin!" +L.quick_imwith = "{player} ile birlikteyim." +L.quick_see = "{player} adlı oyuncuyu görüyorum." +L.quick_suspect = "{player} şüpheli davranıyor." +L.quick_traitor = "{player} bir Hain!" +L.quick_inno = "{player} masum." +L.quick_check = "Kimse hayatta mı?" + +-- {player} in the quickchat text normally becomes a player nickname, but can +-- also be one of the below. Keep these lowercase. +L.quick_nobody = "hiç kimse" +L.quick_disg = "kılık değiştirmiş biri var" +L.quick_corpse = "kimliği belirsiz bir ceset var" +L.quick_corpse_id = "{player} oyuncusunun cesedi" + +-- Scoreboard +L.sb_playing = "Şu anda bu sunucuda oynuyorsunuz..." +L.sb_mapchange = "{num} rauntta veya {time} içinde harita değişecektir." +L.sb_mapchange_disabled = "Oturum sınırları devre dışı." + +L.sb_mia = "Eylem Eksik" +L.sb_confirmed = "Onaylanmış Ölü" + +L.sb_ping = "Gecikme" +L.sb_deaths = "Ölümler" +L.sb_score = "Puan" +L.sb_karma = "Karma" + +L.sb_info_help = "Bu oyuncunun cesedinde arama yapıp sonuçları buradan inceleyebilirsiniz." + +L.sb_tag_friend = "ARKADAŞ" +L.sb_tag_susp = "ŞÜPHELİ" +L.sb_tag_avoid = "KAÇIN" +L.sb_tag_kill = "ÖLDÜR" +L.sb_tag_miss = "KAYIP" + +-- Equipment actions, like buying and dropping +L.buy_no_stock = "Silahı bu rauntta aldın ve stoğu tükendi." +L.buy_pending = "Zaten bekleyen bir siparişiniz var, alana kadar bekleyin." +L.buy_received = "Özel ekipmanınızı aldınız." + +L.drop_no_room = "Burada silahını bırakacak yerin yok!" + +L.disg_turned_on = "Kılık değiştirme etkinleştirildi!" +L.disg_turned_off = "Kılık değiştirme devre dışı." + +-- Equipment item descriptions +L.item_passive = "Pasif etki öğesi" +L.item_active = "Aktif kullanım öğesi" +L.item_weapon = "Silah" + +L.item_armor = "Vücut Zırhı" +L.item_armor_desc = [[ +Mermi, ateş ve patlama hasarını azaltır. Zamanla tükenir. + +Birden çok kez satın alınabilir. Belirli bir zırh değerine ulaştıktan sonra, zırh güçlenir.]] + +L.item_radar = "Radar" +L.item_radar_desc = [[ +Yaşam belirtilerini taramanızı sağlar. + +Satın alır almaz otomatik taramaları başlatır. Bu menünün Radar sekmesinde yapılandırın.]] + +L.item_disg = "Kılık Değiştirici" +L.item_disg_desc = [[ +Açıkken kimlik bilgilerinizi gizler. Ayrıca bir mağdur tarafından en son görülen kişi olmaktan korur. + +Bu menünün Kılık Değiştirme sekmesini açın veya Numpad Enter tuşuna basın.]] + +-- C4 +L.c4_disarm_warn = "Yerleştirdiğiniz bir C4 patlayıcı etkisiz hale getirildi." +L.c4_armed = "Bombayı başarıyla devreye aldınız." +L.c4_disarmed = "Bombayı başarıyla etkisiz hale getirdiniz." +L.c4_no_room = "Bu C4'ü taşıyamazsınız." + +L.c4_desc = "Güçlü zaman ayarlı patlayıcı." + +L.c4_arm = "C4 kur" +L.c4_arm_timer = "Zamanlayıcı" +L.c4_arm_seconds = "Patlamaya saniye kaldı" +L.c4_arm_attempts = "Etkisiz hale getirme girişimlerinde, 6 telden {num} tanesi kesildiğinde anında patlamaya neden olur." + +L.c4_remove_title = "Kaldırma" +L.c4_remove_pickup = "C4'ü al" +L.c4_remove_destroy1 = "C4'ü yok et" +L.c4_remove_destroy2 = "İmhayı onayla" + +L.c4_disarm = "C4'ü devre dışı bırak" +L.c4_disarm_cut = "{num} telini kesmek için tıklayın" + +L.c4_disarm_t = "Bombayı etkisiz hale getirmek için bir kablo kesin. Hain olduğun için her kablo güvende. Masumlar için iş o kadar kolay değil!" +L.c4_disarm_owned = "Bombayı etkisiz hale getirmek için bir kablo kesin. Bu senin bomban, bu yüzden her kablo onu etkisiz hale getirecek." +L.c4_disarm_other = "Bombayı etkisiz hale getirmek için bir kablo kesin. Yanlış yaparsan patlar!" + +L.c4_status_armed = "DEVREYE ALINDI" +L.c4_status_disarmed = "DEVRE DIŞI" + +-- Visualizer +L.vis_name = "Görüntüleyici" + +L.vis_desc = [[ +Olay yeri görüntüleme cihazı. + +Kurbanın nasıl öldürüldüğünü göstermek için bir cesedi analiz eder, ancak sadece kurşun yaralarından ölmüşlerse.]] + +-- Decoy +L.decoy_name = "Tuzak" +L.decoy_broken = "Tuzağınız yok edildi!" + +L.decoy_short_desc = "Bu tuzak, diğer takımlar tarafından görülebilen sahte bir radar işareti gösterir" +L.decoy_pickup_wrong_team = "Farklı bir takıma ait olduğu için alamazsınız" + +L.decoy_desc = [[ +Diğer takımlara sahte bir radar işareti gösterir ve biri DNA'nızı tararsa DNA tarayıcısının tuzağın yerini göstermesini sağlar.]] + +-- Defuser +L.defuser_name = "İmha Kiti" + +L.defuser_desc = [[ +Bir C4 patlayıcısını anında etkisiz hale getirin. + +Sınırsız kullanım. Bunu taşırsanız C4'ün fark edilmesi daha kolay olacaktır.]] + +-- Flare gun +L.flare_name = "İşaret Fişeği" + +L.flare_desc = [[ +Cesetleri asla bulunamayacak şekilde yakmak için kullanılabilir. Sınırlı cephane. + +Bir cesedi yakmak belirgin bir ses çıkarır.]] + +-- Health station +L.hstation_name = "Sağlık İstasyonu" + +L.hstation_broken = "Sağlık İstasyonun yok edildi!" + +L.hstation_desc = [[ +Yerleştirildiğinde insanların iyileşmesini sağlar. + +Yavaş şarj. Herkes kullanabilir ve zarar görebilir. Kullanıcılarının DNA örnekleri için kontrol edilebilir.]] + +-- Knife +L.knife_name = "Bıçak" +L.knife_thrown = "Fırlatılan bıçak" + +L.knife_desc = [[ +Yaralı hedefleri anında ve sessizce öldürür, ancak yalnızca tek bir kullanımı vardır. + +Alternatif ateş kullanılarak atılabilir.]] + +-- Poltergeist +L.polter_desc = [[ +Katilleri nesnelerle şiddetle itip kakarlar. + +Enerji patlamaları yakındaki insanlara zarar verir.]] + +-- Radio +L.radio_broken = "Radyonuz yok edildi!" + +L.radio_desc = [[ +Dikkat dağıtmak veya kandırmak için sesler çıkarır. + +Radyoyu bir yere yerleştirin ve ardından bu menüdeki Radyo sekmesini kullanarak üzerindeki sesleri çalın.]] + +-- Silenced pistol +L.sipistol_name = "Susturuculu Tabanca" + +L.sipistol_desc = [[ +Düşük gürültülü tabanca, normal tabanca mermisi kullanır. + +Kurbanlar öldürüldüklerinde çığlık atmazlar.]] + +-- Newton launcher +L.newton_name = "Newton Fırlatıcı" + +L.newton_desc = [[ +İnsanları güvenli bir mesafeden itin. + +Sonsuz cephane, ama ateş etmesi yavaş.]] + +-- Binoculars +L.binoc_name = "Dürbün" + +L.binoc_desc = [[ +Cesetleri yakınlaştırın ve uzak mesafeden teşhis edin. + +Sınırsız kullanım, ancak teşhis birkaç saniye sürer.]] + +-- UMP +L.ump_desc = [[ +Hedeflerin yönünü şaşırtan deneysel bir SMG. + +Standart SMG cephanesi kullanır.]] + +-- DNA scanner +L.dna_name = "DNA tarayıcı" +L.dna_notfound = "Hedefte DNA örneği bulunamadı." +L.dna_limit = "Depolama sınırına ulaşıldı. Yenilerini eklemek için eski numuneleri kaldırın." +L.dna_decayed = "Katilin DNA örneği çürümüş." +L.dna_killer = "Cesetten katilin DNA'sından bir örnek toplandı!" +L.dna_duplicate = "Eşleşme! Tarayıcınızda bu DNA örneği zaten var." +L.dna_no_killer = "DNA alınamadı (katil bağlantısı kesildi)." +L.dna_armed = "Bu bomba aktif! Önce onu etkisiz hale getir!" +L.dna_object = "Nesneden son sahibin bir örneği toplandı." +L.dna_gone = "Bölgede DNA tespit edilmedi." + +L.dna_desc = [[ +Nesnelerden DNA örnekleri toplayın ve bunları DNA'nın sahibini bulmak için kullanın. + +Katilin DNA'sını almak ve izini sürmek için taze cesetler üzerinde kullanın.]] + +-- Magneto stick +L.magnet_name = "Manyeto çubuğu" +L.magnet_help = "Cesedi yüzeye tutturmak için {primaryfire}" + +-- Grenades and misc +L.grenade_smoke = "Duman bombası" +L.grenade_fire = "Yanıcı bomba" + +L.unarmed_name = "Gizlendi" +L.crowbar_name = "Levye" +L.pistol_name = "Tabanca" +L.rifle_name = "Tüfek" +L.shotgun_name = "Pompalı tüfek" + +-- Teleporter +L.tele_name = "Işınlayıcı" +L.tele_failed = "Işınlama başarısız oldu." +L.tele_marked = "Işınlanma konumu işaretlendi." + +L.tele_no_ground = "Sağlam bir zemin üzerinde durmadan ışınlanamazsın!" +L.tele_no_crouch = "Çömelmişken ışınlanamazsın!" +L.tele_no_mark = "Konum işaretlenmedi. Işınlanmadan önce varış noktasını işaretleyin." + +L.tele_no_mark_ground = "Sağlam bir zemin üzerinde durmadan ışınlanamazsın!" +L.tele_no_mark_crouch = "Çömelmişken ışınlanma konumu işaretlenemez!" + +L.tele_help_pri = "İşaretli konuma ışınlanır" +L.tele_help_sec = "Mevcut konumu işaretler" + +L.tele_desc = [[ +Daha önce işaretlenmiş bir noktaya ışınlanın. + +Işınlanma gürültü yapar ve kullanım sayısı sınırlıdır.]] + +-- Ammo names, shown when picked up +L.ammo_pistol = "9mm cephanesi" + +L.ammo_smg1 = "SMG cephanesi" +L.ammo_buckshot = "Pompalı Tüfek cephanesi" +L.ammo_357 = "Tüfek cephanesi" +L.ammo_alyxgun = "Deagle cephanesi" +L.ammo_ar2altfire = "İşaret Fişeği cephanesi" +L.ammo_gravity = "Afacan Peri cephanesi" + +-- Round status +L.round_wait = "Bekleniyor" +L.round_prep = "Hazırlanıyor" +L.round_active = "Devam ediyor" +L.round_post = "Raunt bitti" + +-- Health, ammo and time area +L.overtime = "UZATMA" +L.hastemode = "HIZLI MOD" + +-- TargetID health status +L.hp_healthy = "Sağlıklı" +L.hp_hurt = "Hasar Görmüş" +L.hp_wounded = "Yaralı" +L.hp_badwnd = "Kötü Yaralanmış" +L.hp_death = "Ölüme Yakın" + +-- TargetID Karma status +L.karma_max = "Saygın" +L.karma_high = "İyi" +L.karma_med = "Tetik Çekmeye Meyilli" +L.karma_low = "Tehlikeli" +L.karma_min = "Sorumsuz" + +-- TargetID misc +L.corpse = "Ceset" +L.corpse_hint = "Arama yapmak için [{usekey}] tuşuna basın. Gizlice arama yapmak için [{walkkey}+{usekey}]." + +L.target_disg = "(gizlenmiş)" +L.target_unid = "Tanımlanamayan ceset" +L.target_unknown = "Terörist" + +-- HUD buttons with hand icons that only some roles can see and use +L.tbut_single = "Tek kullanımlık" +L.tbut_reuse = "Yeniden kullanılabilir" +L.tbut_retime = "{num} saniye sonra tekrar kullanılabilir" +L.tbut_help = "Etkinleştirmek için [{usekey}] tuşuna basın" + +-- Spectator muting of living/dead +L.mute_living = "Yaşayan oyuncular sessize alındı" +L.mute_specs = "İzleyiciler sessize alındı" +L.mute_all = "Tümü sessiz" +L.mute_off = "Hiçbiri sessiz değil" + +-- Spectators and prop possession +L.punch_title = "GÜÇ ÖLÇER" +L.punch_bonus = "Kötü puanınız güç ölçer sınırınızı {num} düşürdü." +L.punch_malus = "İyi puanın güç ölçer sınırını {num} arttırdı!" + +-- Info popups shown when the round starts +L.info_popup_innocent = [[ +Sen masum bir teröristsin! Ama etrafta hainler var... +Kime güvenebilirsin ve seni kurşuna dizmek isteyen kim olabilir? + +Arkanı kolla ve bu işten canlı çıkmak için yoldaşlarınla birlikte çalış!]] + +L.info_popup_detective = [[ +Sen bir Dedektifsin! Terörist Karargahı, hainleri bulman için sana özel kaynaklar verdi. +Masumların hayatta kalmasına yardımcı olmak için bunları kullanın, ancak dikkatli olun +hainler önce seni alaşağı etmek isteyecekler! + +Ekipmanınızı almak için {menukey} tuşuna basın!]] + +L.info_popup_traitor_alone = [[ +Sen bir HAİNSİN! Bu rauntta hain arkadaşın yok. + +Kazanmak için diğerlerini öldür! + +Ekipmanınızı almak için {menukey} tuşuna basın!]] + +L.info_popup_traitor = [[ +Sen bir HAİNSİN! Diğerlerini öldürmek için diğer hainlerle birlikte çalışın. +Kendine iyi bak, yoksa vatan hainliğin ortaya çıkabilir... + +Bunlar senin yoldaşların +{traitorlist} + +Ekipmanınızı almak için {menukey} tuşuna basın!]] + +-- Various other text +L.name_kick = "Rauntta bir oyuncu ismini değiştirdiği için otomatik olarak atıldı." + +L.idle_popup = [[ +{num} saniye boyunca boşta olduğun için İzleyici moduna aktarıldın. Bu moddayken, yeni bir raunt başladığında oyuna başlamayacaksın. + +{helpkey} tuşuna basarak ve Ayarlar sekmesindeki kutunun işaretini kaldırarak istediğiniz zaman İzleyici modunu değiştirebilirsiniz. Ayrıca şu anda devre dışı bırakmayı da seçebilirsiniz.]] + +L.idle_popup_close = "Hiçbir şey yapma" +L.idle_popup_off = "İzleyici modunu şimdi devre dışı bırak" + +L.idle_warning = "Uyarı! Boşta gibi görünüyorsunuz ve hareket etmediğiniz sürece izleyiciye alınacaksınız!" + +L.spec_mode_warning = "İzleyici modundasın ve bir raunt başladığında oyuna başlamayacaksın. Bu modu devre dışı bırakmak için F1'e basın, 'Oynanış'a gidin ve 'Yalnızca İzle modu'nun işaretini kaldırın." + +-- Tips panel +L.tips_panel_title = "İpuçları" +L.tips_panel_tip = "İpucu" + +-- Tip texts +L.tip1 = "Hainler, {walkkey} tuşunu basılı tutarak ve {usekey} tuşuna basarak, ölümü onaylamadan bir cesedi sessizce arayabilirler." + +L.tip2 = "Bir C4'ü daha uzun bir zamanlayıcıyla donatmak, masum biri onu etkisiz hale getirmeye çalıştığında anında patlamasına neden olan tellerin sayısını artıracaktır. Ayrıca daha yumuşak ve daha az sıklıkta bip sesi çıkaracaktır." + +L.tip3 = "Dedektifler, 'gözlerine yansıyanı' bulmak için bir cesedi arayabilirler. Bu, ölü adamın gördüğü son kişi. Arkadan vurulduysa katil olmak zorunda değil." + +L.tip4 = "Kimse cesedinizi bulana ve sizi arayarak teşhis edene kadar öldüğünüzü bilmeyecek." + +L.tip5 = "Bir Hain bir Dedektifi öldürdüğünde, anında bir kredi ödülü alır." + +L.tip6 = "Bir Hain öldüğünde, tüm Dedektifler ekipman kredisi ile ödüllendirilir." + +L.tip7 = "Hainler masumları öldürmede önemli ilerleme kaydettiklerinde, ödül olarak bir ekipman kredisi alacaklar." + +L.tip8 = "Hainler ve Dedektifler, diğer Hainlerin ve Dedektiflerin cesetlerinden harcanmamış ekipman kredileri toplayabilir." + +L.tip9 = "Afacan Peri herhangi bir fizik nesnesini ölümcül bir mermiye dönüştürebilir. Her darbeye, yakındaki herkese zarar veren bir enerji patlaması eşlik eder." + +L.tip10 = "Hain veya Dedektifseniz, sağ üstteki kırmızı mesajlara dikkat edin. Bunlar sizin için önemli olacak." + +L.tip11 = "Hain veya Dedektif olarak, siz ve yoldaşlarınız iyi performans gösterirseniz ekstra ekipman kredisi ile ödüllendirileceğinizi unutmayın. Harcamayı unutmayın!" + +L.tip12 = "Dedektiflerin DNA Tarayıcısı, silahlardan ve eşyalardan DNA örnekleri toplamak ve daha sonra bunları kullanan oyuncunun yerini bulmak için tarama yapmak için kullanılabilir. Bir cesetten veya etkisiz hale getirilmiş bir C4'ten numune alabildiğinizde kullanışlıdır!" + +L.tip13 = "Öldürdüğünüz birine yakın olduğunuzda, DNA'nızın bir kısmı cesedin üzerinde kalır. Bu DNA, mevcut konumunuzu bulmak için bir Dedektifin DNA Tarayıcısı ile kullanılabilir. Birini bıçakladıktan sonra cesedi saklasan iyi olur!" + +L.tip14 = "Öldürdüğünüz birinden ne kadar uzaktaysanız, vücudundaki DNA örneğiniz o kadar hızlı bozulur." + +L.tip15 = "Hain misin ve keskin nişancılık mı yapıyorsun? Kılık Değiştiriciyi denemeyi düşün. Bir atışı kaçırırsan, güvenli bir yere kaç, Kılık Değiştiriciyi devre dışı bırak ve hiç kimse onlara ateş edenin sen olduğunu bilmeyecek." + +L.tip16 = "Hain olarak Işınlayıcı, kovalandığında kaçmana yardımcı olabilir ve büyük bir harita üzerinde hızlı bir şekilde seyahat etmeni sağlar. Her zaman işaretli güvenli bir pozisyonunuz olduğundan emin olun." + +L.tip17 = "Masumların hepsi gruplanmış ve öldürmesi zor mu? C4 seslerini çalmak için Radyoyu veya bazılarını uzaklaştırmak için ateş etmeyi düşünün." + +L.tip18 = "Radyoyu Hainken, radyo yerleştirildikten sonra Ekipman Menünüzden sesleri çalabilirsiniz. İstediğiniz sırayla birden fazla düğmeye tıklayarak birden fazla sesi sıraya koyun." + +L.tip19 = "Dedektifken, kalan kredileriniz varsa, güvenilir bir Masuma İmha Kiti verebilirsiniz. O zaman zamanınızı ciddi araştırma çalışmaları yaparak geçirebilir ve riskli bomba imha işini onlara bırakabilirsiniz." + +L.tip20 = "Dedektiflerin Dürbünü, cesetlerin uzun menzilli aranmasına ve tanımlanmasına izin verir. Hainler bir cesedi yem olarak kullanmayı umuyorsa kötü haber. Elbette, Dürbünü kullanırken bir Dedektif silahsızdır ve dikkati dağılır..." + +L.tip21 = "Dedektiflerin Sağlık İstasyonu, yaralı oyuncuların iyileşmesini sağlar. Tabii o yaralılar da Hain olabilir..." + +L.tip22 = "Sağlık İstasyonu, onu kullanan herkesin DNA örneğini kaydeder. Dedektifler bunu DNA Tarayıcısı ile kimin iyileştiğini bulmak için kullanabilirler." + +L.tip23 = "Silahlar ve C4'ten farklı olarak, Hainler için Radyo ekipmanı, onu yerleştiren kişinin DNA örneğini içermez. Dedektiflerin onu bulması ve kimliğini ifşa etmesi konusunda endişelenme." + +L.tip24 = "Kısa bir öğreticiyi görüntülemek veya TTT'ye özgü bazı ayarları değiştirmek için {helpkey} tuşuna basın. Örneğin, bu ipuçlarını kalıcı olarak devre dışı bırakabilirsiniz." + +L.tip25 = "Dedektif bir cesedi aradığında, sonuç ölü kişinin adına tıklayarak puan panosu aracılığıyla tüm oyuncuların kullanımına açıktır." + +L.tip26 = "Skor tablosunda, birinin adının yanındaki büyüteç simgesi, o kişi hakkında arama bilgilerine sahip olduğunuzu gösterir. Simge parlaksa, veriler bir Dedektiften gelir ve ek bilgiler içerebilir." + +L.tip27 = "Dedektif olarak, takma addan sonra büyüteç takılan cesetler bir Dedektif tarafından arandı ve sonuçları skor tablosu aracılığıyla tüm oyunculara açıktır." + +L.tip28 = "İzleyiciler, diğer izleyicileri veya yaşayan oyuncuları susturmak için {mutekey} tuşuna basabilir." + +L.tip29 = "Sunucu ek diller yüklediyse, Ayarlar menüsünden istediğiniz zaman farklı bir dile geçebilirsiniz." + +L.tip30 = "Hızlı sohbet veya 'radyo' komutları {zoomkey} tuşuna basılarak kullanılabilir." + +L.tip31 = "İzleyiciyken, fare imlecinin kilidini açmak için {duckkey} tuşuna bas ve bu ipuçları panelindeki düğmelere tıkla. Fare görünümüne geri dönmek için {duckkey} tuşuna tekrar basın." + +L.tip32 = "Levyenin ikincil ateşi diğer oyuncuları itecektir." + +L.tip33 = "Nişangahı kullanarak ateş etmek, isabetini biraz artıracak ve geri tepmeyi azaltacaktır. Çömelmek işe yaramaz." + +L.tip34 = "Duman bombaları, özellikle kalabalık odalarda kafa karışıklığı yaratmak için iç mekanlarda etkilidir." + +L.tip35 = "Hain olarak, cesetleri taşıyabileceğinizi ve onları masumların ve Dedektiflerinin meraklı gözlerinden saklayabileceğinizi unutmayın." + +L.tip36 = "{helpkey} altında bulunan öğretici, oyunun en önemli ince ayrıntılarına genel bir bakış içerir." + +L.tip37 = "Skor tablosunda, yaşayan bir oyuncunun adına tıklayıp 'şüpheli' veya 'arkadaş' gibi bir etiket seçebilirsiniz. Bu etiket, nişangahınızın altındaysa görünecektir." + +L.tip38 = "Yerleştirilebilir ekipman öğelerinin çoğu (C4, Radyo gibi) ikincil ateş kullanılarak duvarlara yapıştırılabilir." + +L.tip39 = "Etkisiz hale getirilirken bir hata nedeniyle patlayan C4, zamanlayıcısında sıfıra ulaşan C4'ten daha küçük bir patlamaya sahiptir." + +L.tip40 = "Raunt zamanlayıcısının üzerinde 'HIZLI MOD' yazıyorsa, raunt ilk başta sadece birkaç dakika uzunluğunda olacaktır, ancak her ölümle birlikte mevcut süre artar (TF2'deki bir noktayı yakalamak gibi). Bu mod, hainlere işlerini devam ettirmeleri için baskı yapar." + +-- Round report +L.report_title = "Raunt Raporu" + +-- Tabs +L.report_tab_hilite = "Öne Çıkanlar" +L.report_tab_hilite_tip = "Rauntta Öne Çıkanlar" +L.report_tab_events = "Olaylar" +L.report_tab_events_tip = "Bu raunt gerçekleşen olayların kaydı" +L.report_tab_scores = "Puanlar" +L.report_tab_scores_tip = "Sadece bu rauntta her oyuncunun aldığı puan" + +-- Event log saving +L.report_save = "Kaydı .txt olarak kaydet" +L.report_save_tip = "Olay Kaydını bir metin dosyasına kaydeder" +L.report_save_error = "Kaydedilecek Olay Kaydı verisi yok." +L.report_save_result = "Olay Kaydı şuraya kaydedildi:" + +-- Columns +L.col_time = "Zaman" +L.col_event = "Olay" +L.col_player = "Oyuncu" +L.col_roles = "Rol(ler)" +L.col_teams = "Takım(lar)" +L.col_kills1 = "Öldürmeler" +L.col_kills2 = "Takım öldürmeleri" +L.col_points = "Puanlar" +L.col_team = "Takım bonusu" +L.col_total = "Toplam puan" + +-- Awards/highlights +L.aw_sui1_title = "İntihar Tarikatı Lideri" +L.aw_sui1_text = "ilk giden olarak diğer intihar edenlere nasıl yapılacağını gösterdi." + +L.aw_sui2_title = "Yalnız ve Depresyonda" +L.aw_sui2_text = "kendini öldüren tek kişiydi." + +L.aw_exp1_title = "Patlayıcı Araştırma Hibesi" +L.aw_exp1_text = "patlamalar üzerine yaptıkları araştırmalarla tanındı. {num} denek yardımcı oldu." + +L.aw_exp2_title = "Saha Araştırması" +L.aw_exp2_text = "patlamalara karşı kendi dirençlerini test etti. Yeterince yüksek değildi." + +L.aw_fst1_title = "İlk Kan" +L.aw_fst1_text = "bir hainin elindeki ilk masum ölümü teslim etti." + +L.aw_fst2_title = "İlk Kanlı Aptal Öldürme" +L.aw_fst2_text = "bir hain dostunu vurarak ilk cinayeti işledi. İyi iş." + +L.aw_fst3_title = "İlk Hatacı" +L.aw_fst3_text = "ilk öldüren oldu. Masum bir yoldaş olması çok kötü oldu." + +L.aw_fst4_title = "İlk Darbe" +L.aw_fst4_text = "ilk ölümü hainin yaparak masum teröristlere ilk darbeyi vurdu." + +L.aw_all1_title = "Eşitler Arasında En Ölümcül" +L.aw_all1_text = "bu rauntta masumlar tarafından yapılan her cinayetten sorumluydu." + +L.aw_all2_title = "Yalnız Kurt" +L.aw_all2_text = "bu rauntta bir hain tarafından yapılan her cinayetten sorumluydu." + +L.aw_nkt1_title = "Birini Aldım Patron!" +L.aw_nkt1_text = "tek bir masumu öldürmeyi başardı. Güzel!" + +L.aw_nkt2_title = "İki Kişilik Kurşun" +L.aw_nkt2_text = "ilkinin bir başkasını öldürerek şanslı bir atış olmadığını gösterdi." + +L.aw_nkt3_title = "Seri Hain" +L.aw_nkt3_text = "bugün üç masum terörizm hayatına son verdi." + +L.aw_nkt4_title = "Daha Fazla Koyun Benzeri Kurt Arasında Kurt" +L.aw_nkt4_text = "Masum teröristleri akşam yemeğinde yer. {num} yemekten oluşan bir akşam yemeği." + +L.aw_nkt5_title = "Terörle Mücadele Operatörü" +L.aw_nkt5_text = "öldürme başına ödeme alır. Artık başka bir lüks yat satın alabilirim." + +L.aw_nki1_title = "Buna İhanet Et" +L.aw_nki1_text = "bir hain buldu. Bir haini vurdu. Kolaydı." + +L.aw_nki2_title = "Adalet Ekibine Müracaat Edildi" +L.aw_nki2_text = "iki haine büyük öteye kadar eşlik etti." + +L.aw_nki3_title = "Hainler Hain Koyun Düşler mi" +L.aw_nki3_text = "üç haine tatlı rüyalar gördür." + +L.aw_nki4_title = "İçişleri Çalışanı" +L.aw_nki4_text = "öldürme başına ödeme alır. Artık beşinci yüzme havuzunu sipariş edebilir." + +L.aw_fal1_title = "Hayır Bay Bond, Düşmenizi Bekliyorum" +L.aw_fal1_text = "birini büyük bir yükseklikten itti." + +L.aw_fal2_title = "Döşenmiş" +L.aw_fal2_text = "kayda değer bir yükseklikten düştükten sonra bedenlerinin yere çarpmasına izin verin." + +L.aw_fal3_title = "İnsan Göktaşı" +L.aw_fal3_text = "büyük bir yükseklikten birinin üstüne düşerek ezdi." + +L.aw_hed1_title = "Verimlilik" +L.aw_hed1_text = "kafadan vurma sevincini keşfetti ve {num} kazandı." + +L.aw_hed2_title = "Nöroloji" +L.aw_hed2_text = "daha yakından incelemek için beyinleri {num} kafadan çıkardı." + +L.aw_hed3_title = "Video Oyunları Bana Zorla Yaptırdı" +L.aw_hed3_text = "cinayet simülasyonu eğitimini uyguladı ve {num} düşmanı kafadan vurdu." + +L.aw_cbr1_title = "Sırra Kadem Bas" +L.aw_cbr1_text = "levyeyi gelişigüzel sallar ve bunu {num} kurban öğrenir." + +L.aw_cbr2_title = "Freeman" +L.aw_cbr2_text = "levyelerini en az {num} kişinin beynine sapladı." + +L.aw_pst1_title = "Israrcı Küçük Alçak" +L.aw_pst1_text = "tabancayı kullanarak {num} kişi öldürdü. Sonra birine ölümüne sarılmaya devam ettiler." + +L.aw_pst2_title = "Küçük Kalibreli Kesim" +L.aw_pst2_text = "{num} kişilik küçük bir orduyu tabancayla öldürdü. Muhtemelen namlunun içine küçük bir av tüfeği yerleştirdi." + +L.aw_sgn1_title = "Kolay Mod" +L.aw_sgn1_text = "iri saçmayı acıdığı yere uygulayarak {num} hedefi öldürür." + +L.aw_sgn2_title = "Bin Küçük Saçma Tanesi" +L.aw_sgn2_text = "saçmalarını gerçekten beğenmedi, bu yüzden hepsini verdiler. {num} alıcı bundan zevk alacak kadar yaşamadı." + +L.aw_rfl1_title = "İşaretle ve Tıkla" +L.aw_rfl1_text = "{num} öldürme için ihtiyacın olan tek şeyin bir tüfek ve titremeyen bir el olduğunu gösterir." + +L.aw_rfl2_title = "Kafanı Buradan Görebiliyorum" +L.aw_rfl2_text = "tüfeğini biliyor. Şimdi {num} kişi daha tüfeği biliyor." + +L.aw_dgl1_title = "Küçük Bir Tüfek Gibi" +L.aw_dgl1_text = "Desert Eagle'a alışıp {num} kişiyi öldürdü." + +L.aw_dgl2_title = "Deagle Ustası" +L.aw_dgl2_text = "{num} kişiyi deagle ile darmadağın etti." + +L.aw_mac1_title = "Dua Et ve Öldür" +L.aw_mac1_text = "MAC10 ile {num} kişiyi öldürdü, ama öldürmek için ne kadar mermiye ihtiyaç duyduğunu söylemeyecek." + +L.aw_mac2_title = "Gülümse ve Seyret" +L.aw_mac2_text = "iki MAC10 kullanabilseler ne olacağını merak ediyor. {num} çarpı iki" + +L.aw_sip1_title = "Sessiz Ol" +L.aw_sip1_text = "{num} kişiyi susturulmuş tabancayla sustur." + +L.aw_sip2_title = "Susturulmuş Suikastçı" +L.aw_sip2_text = "kendini ölürken duymayan {num} kişiyi öldürdü." + +L.aw_knf1_title = "Seni Bilen Bıçak" +L.aw_knf1_text = "internet üzerinden birini yüzünden bıçakladı." + +L.aw_knf2_title = "Bunu Nereden Aldın" +L.aw_knf2_text = "bir Hain değildi, ama yine de bıçakla birini öldürdü." + +L.aw_knf3_title = "Böyle Bir Bıçak Adam" +L.aw_knf3_text = "etrafta {num} bıçak buldu ve bunlardan faydalandı." + +L.aw_knf4_title = "Dünyanın En Bıçak Adamı" +L.aw_knf4_text = "{num} kişiyi bıçakla öldürdü. Nasıl olduğunu sorma." + +L.aw_flg1_title = "Kurtarmaya" +L.aw_flg1_text = "işaret fişeklerini {num} ölüm sinyali vermek için kullandı." + +L.aw_flg2_title = "İşaret Fişeği Yangını Gösterir" +L.aw_flg2_text = "{num} erkeğe yanıcı kıyafet giymenin tehlikesini öğretti." + +L.aw_hug1_title = "H.U.G.E Felaketi" +L.aw_hug1_text = "H.U.G.E.'leriyle uyumluydu, bir şekilde mermilerini {num} kişiye isabet ettirmeyi başardı." + +L.aw_hug2_title = "Sabırlı Er" +L.aw_hug2_text = "sadece ateş etmeye devam etti ve H.U.G.E.'nin sabrının {num} öldürme ile ödüllendirildiğini gördü." + +L.aw_msx1_title = "Bam Bam Bam" +L.aw_msx1_text = "M16 ile {num} kişiyi öldürdü." + +L.aw_msx2_title = "Orta Menzil Çılgınlığı" +L.aw_msx2_text = "M16 ile hedefleri nasıl indireceğini bilir ve {num} düşman öldürür." + +L.aw_tkl1_title = "Sakar Şakir" +L.aw_tkl1_text = "tam bir arkadaşa nişan alırken parmakları kaydı." + +L.aw_tkl2_title = "Çifte Sakar Şakir" +L.aw_tkl2_text = "iki kez Hain aldıklarını düşündüler, ancak her iki seferde de yanıldılar." + +L.aw_tkl3_title = "Karma bilincine sahip" +L.aw_tkl3_text = "iki takım arkadaşını öldürdükten sonra duramadı. Üç onların uğurlu sayısıdır." + +L.aw_tkl4_title = "Takım Katili" +L.aw_tkl4_text = "tüm takımını öldürdü. ALLAH'INI SEVEN BANLASIN." + +L.aw_tkl5_title = "Rol Oyuncusu" +L.aw_tkl5_text = "dürüstçe deli bir adamı canlandırıyordu. Bu yüzden takımlarının çoğunu öldürdüler." + +L.aw_tkl6_title = "Aptal" +L.aw_tkl6_text = "hangi tarafta olduklarını anlayamadılar ve yoldaşlarının yarısından fazlasını öldürdüler." + +L.aw_tkl7_title = "Cahil" +L.aw_tkl7_text = "takım arkadaşlarının dörtte birinden fazlasını öldürerek bölgelerini gerçekten iyi korudu." + +L.aw_brn1_title = "Eskiden Büyükannemin Yaptığı Gibi" +L.aw_brn1_text = "birkaç kişiyi güzelce kızarttı." + +L.aw_brn2_title = "Kundakçı" +L.aw_brn2_text = "kurbanlarından birini yaktıktan sonra yüksek sesle kıkırdadığı duyuldu." + +L.aw_brn3_title = "Kundakçının Ateşi Söner" +L.aw_brn3_text = "hepsini yaktı, ama şimdi hiç yakıcı el bombası kalmadı! Nasıl başa çıkacaklar!" + +L.aw_fnd1_title = "Adli tabip" +L.aw_fnd1_text = "etrafta {num} ceset bulundu." + +L.aw_fnd2_title = "Hepsini Yakalamalıyım" +L.aw_fnd2_text = "koleksiyonları için {num} ceset bulundu." + +L.aw_fnd3_title = "Ölüm Kokusu" +L.aw_fnd3_text = "bu turda {num} kez rastgele cesetler üzerinde tökezler." + +L.aw_crd1_title = "Geri Dönüştürücü" +L.aw_crd1_text = "cesetlerden {num} kredi toplandı." + +L.aw_tod1_title = "Kundakçının Zaferi" +L.aw_tod1_text = "takımları raundu kazanmadan sadece saniyeler önce öldü." + +L.aw_tod2_title = "Bu Oyundan Nefret Ediyorum" +L.aw_tod2_text = "raundun başlamasından hemen sonra öldü." + +-- New and modified pieces of text are placed below this point, marked with the +-- version in which they were added, to make updating translations easier. + +-- v24 +L.drop_no_ammo = "Silahının şarjöründe cephane kutusu olarak düşecek yeterli cephane yok." + +-- 2015-05-25 +L.hat_retrieve = "Bir Dedektif'in şapkasını aldın." + +-- 2017-09-03 +L.sb_sortby = "Sıralama Ölçütü" + +-- 2018-07-24 +L.equip_tooltip_main = "Ekipman menüsü" +L.equip_tooltip_radar = "Radar kontrolü" +L.equip_tooltip_disguise = "Kılık değiştirme kontrolü" +L.equip_tooltip_radio = "Radyo kontrolü" +L.equip_tooltip_xfer = "Kredileri aktar" +L.equip_tooltip_reroll = "Ekipmanı yeniden dağıt" + +L.confgrenade_name = "Kafa Karıştırıcı" +L.polter_name = "Afacan Peri" +L.stungun_name = "UMP Prototipi" + +L.knife_instant = "ANINDA ÖLDÜRME" + +L.binoc_zoom_level = "Yakınlaştırma Seviyesi" +L.binoc_body = "CESET ALGILANDI" + +L.idle_popup_title = "Boşta" + +-- 2019-01-31 +L.create_own_shop = "Kendi mağazanı oluştur" +L.shop_link = "Şununla bağlantı kur" +L.shop_disabled = "Mağazayı devre dışı bırak" +L.shop_default = "Varsayılan mağazayı kullan" + +-- 2019-05-05 +L.reroll_name = "Yeniden dağıt" +L.reroll_menutitle = "Ekipmanı yeniden dağıt" +L.reroll_no_credits = "Yeniden dağıtmak için {amount} krediye ihtiyacınız var!" +L.reroll_button = "Yeniden Dağıt" +L.reroll_help = "Mağazanızda yeni bir rastgele ekipman seti almak için {amount} kredi kullanın!" + +-- 2019-05-06 +L.equip_not_alive = "Sağdan bir rol seçerek mevcut tüm öğeleri görüntüleyebilirsiniz. Favorilerinizi işaretlemeyi unutmayın!" + +-- 2019-06-27 +L.shop_editor_title = "Mağaza Düzenleme" +L.shop_edit_items_weapong = "Silahları Düzenle" +L.shop_edit = "Mağazaları Düzenle" +L.shop_settings = "Ayarlar" +L.shop_select_role = "Rol Seç" +L.shop_edit_items = "Öğeleri Düzenle" +L.shop_edit_shop = "Mağazayı Düzenle" +L.shop_create_shop = "Özel Mağaza Oluştur" +L.shop_selected = "Seçili {role}" +L.shop_settings_desc = "Rastgele Mağaza Konsol Değişkeni uyarlamak için değerleri değiştirin. Değişikliklerinizi kaydetmeyi unutmayın!" + +L.bindings_new = "{name} {key} için atanan yeni tuş" + +L.hud_default_failed = "{hudname} arayüzü varsayılan olarak ayarlanamadı. Bunu yapma izniniz yok veya bu arayüz mevcut değil." +L.hud_forced_failed = "{hudname} arayüzü zorlanamadı. Bunu yapma izniniz yok veya bu arayüz mevcut değil." +L.hud_restricted_failed = "{hudname} arayüzü kısıtlanamadı. Bunu yapmak için iznin yok." + +L.shop_role_select = "Bir rol seçin" +L.shop_role_selected = "{role} adlı rolün mağazası seçildi!" +L.shop_search = "Ara" + +-- 2019-10-19 +L.drop_ammo_prevented = "Bir şey cephanenizi düşürmenizi engelliyor." + +-- 2019-10-28 +L.target_c4 = "C4 menüsünü açmak için [{usekey}] tuşuna basın" +L.target_c4_armed = "C4'ü devre dışı bırakmak için [{usekey}] tuşuna basın" +L.target_c4_armed_defuser = "İmha kitini kullanmak için [{primaryfire}] tuşuna basın" +L.target_c4_not_disarmable = "Yaşayan bir takım arkadaşının C4'ünü devre dışı bırakamazsın" +L.c4_short_desc = "Çok patlayıcı bir şey" + +L.target_pickup = "Almak için [{usekey}] tuşuna basın" +L.target_slot_info = "{slot} yuvası" +L.target_pickup_weapon = "Silahı almak için [{usekey}] tuşuna basın" +L.target_switch_weapon = "Mevcut silahınla değiştirmek için [{usekey}] tuşuna basın" +L.target_pickup_weapon_hidden = "Gizli alım için [{walkkey} + {usekey}] tuşuna basın" +L.target_switch_weapon_hidden = "Gizli anahtar için [{walkkey} + {usekey}] tuşuna basın" +L.target_switch_weapon_nospace = "Bu silah için kullanılabilir envanter yuvası yok" +L.target_switch_drop_weapon_info = "{name} {slot} yuvasından bırakılıyor" +L.target_switch_drop_weapon_info_noslot = "{slot} yuvasında düşürülebilir silah yok" + +L.corpse_searched_by_detective = "Bu ceset bir dedektif tarafından arandı" +L.corpse_too_far_away = "Ceset çok uzakta." + +L.radio_short_desc = "Silah sesleri benim için müziktir" + +L.hstation_subtitle = "Sağlık almak için [{usekey}] tuşuna basın." +L.hstation_charge = "Sağlık istasyonunun kalan şarjı {charge}" +L.hstation_empty = "Bu sağlık istasyonunda daha fazla şarj kalmadı" +L.hstation_maxhealth = "Sağlığınız tam" +L.hstation_short_desc = "Sağlık istasyonu zaman içinde yavaş yavaş şarj olur" + +-- 2019-11-03 +L.vis_short_desc = "Kurban ateşli silahla yaralanarak öldüyse olay yerini gösterir" +L.corpse_binoculars = "Cesedi dürbünle aramak için [{key}] tuşuna basın." +L.binoc_progress = "Aranıyor: %{progress}" + +L.pickup_no_room = "Bu silah türü için envanterinizde yer yok." +L.pickup_fail = "Bu silahı alamazsın." +L.pickup_pending = "Zaten bir silah aldın, alana kadar bekle." + +-- 2020-01-07 +L.tbut_help_admin = "Hain düğmesi ayarlarını düzenle" +L.tbut_role_toggle = "[{walkkey} + {usekey}] düğmesi {role} için" +L.tbut_role_config = "Rol {current}" +L.tbut_team_toggle = "{team} takımı için bu düğmeyi değiştirmek için [SHIFT + {walkkey} + {usekey}]" +L.tbut_team_config = "Takım {current}" +L.tbut_current_config = "Geçerli yapılandırma" +L.tbut_intended_config = "Harita oluşturucu tarafından tasarlanan yapılandırma" +L.tbut_admin_mode_only = "Yönetici olduğunuz ve '{cv}' öğesi '1' olarak ayarlandığı için bu düğmeyi görüyorsunuz." +L.tbut_allow = "İzin ver" +L.tbut_prohib = "Yasakla" +L.tbut_default = "Varsayılan" + +-- 2020-02-09 +L.name_door = "Kapı" +L.door_open = "Kapıyı açmak için [{usekey}] tuşuna basın." +L.door_close = "Kapıyı kapatmak için [{usekey}] tuşuna basın." +L.door_locked = "Bu kapı kilitli." + +-- 2020-02-11 +L.automoved_to_spec = "(OTOMATİK MESAJ) Boşta olduğum için İzleyici takımına alındım." +L.mute_team = "{team} sessize alındı." + +-- 2020-02-16 +L.door_auto_closes = "Bu kapı otomatik olarak kapanır." +L.door_open_touch = "Açmak için kapıya doğru yürü." +L.door_open_touch_and_use = "Kapıya doğru yürü veya açmak için [{usekey}] tuşuna bas." + +-- 2020-03-09 +L.help_title = "Ayarlar ve Yardım" + +L.menu_changelog_title = "Değişiklik günlüğü" +L.menu_guide_title = "TTT2 Kılavuzu" +L.menu_bindings_title = "Tuş Atamaları" +L.menu_language_title = "Dil" +L.menu_appearance_title = "Görünüm" +L.menu_gameplay_title = "Oynanış" +L.menu_addons_title = "Eklentiler" +L.menu_legacy_title = "Eski Eklentiler" +L.menu_administration_title = "Yönetim" +L.menu_equipment_title = "Ekipmanı Düzenle" +L.menu_shops_title = "Mağazaları Düzenle" + +L.menu_changelog_description = "Son sürümlerdeki değişikliklerin ve düzeltmelerin listesi." +L.menu_guide_description = "TTT2'ye başlamanıza yardımcı olur ve oyun, roller ve diğer şeyler hakkında bazı şeyleri açıklar." +L.menu_bindings_description = "TTT2'nin ve eklentilerinin belirli özelliklerini kendi beğeninize göre ayarlayın." +L.menu_language_description = "Oyun modunun dilini seçin." +L.menu_appearance_description = "Kullanıcı arayüzünün görünümünü ve performansını değiştirin." +L.menu_gameplay_description = "Ses, erişilebilirlik ve oynanış ayarlarını düzenleyin." +L.menu_addons_description = "Yerel eklentileri istediğiniz gibi yapılandırın." +L.menu_legacy_description = "Orijinal TTT'den dönüştürülen sekmelerin yeni sisteme taşınması gereken bir panel." +L.menu_administration_description = "Arayüzler, mağazalar vb. için genel ayarlar" +L.menu_equipment_description = "Kredileri, sınırlamaları, kullanılabilirliği ve diğer şeyleri ayarlayın." +L.menu_shops_description = "Mağazaları istediğiniz roller için ekleyin ve hangi ekipmanlara sahip olduklarını yapılandırın." + +L.submenu_guide_gameplay_title = "Oynanış" +L.submenu_guide_roles_title = "Roller" +L.submenu_guide_equipment_title = "Ekipman" + +L.submenu_bindings_bindings_title = "Atamalar" + +L.submenu_language_language_title = "Dil" + +L.submenu_appearance_general_title = "Genel" +L.submenu_appearance_hudswitcher_title = "Arayüz Değiştirici" +L.submenu_appearance_vskin_title = "Valve Arayüzü" +L.submenu_appearance_targetid_title = "Hedef Kimliği" +L.submenu_appearance_shop_title = "Mağaza Ayarları" +L.submenu_appearance_crosshair_title = "Nişangah" +L.submenu_appearance_dmgindicator_title = "Hasar Göstergesi" +L.submenu_appearance_performance_title = "Performans" +L.submenu_appearance_interface_title = "Arayüz" + +L.submenu_gameplay_general_title = "Genel" + +L.submenu_administration_hud_title = "Arayüz Ayarları" +L.submenu_administration_randomshop_title = "Rasgele Mağaza" + +L.help_color_desc = "Bu ayar etkinleştirilirse, hedef kimliği dış çizgisi ve nişangah için kullanılacak genel bir renk seçebilirsiniz." +L.help_scale_factor = "Bu ölçek faktörü tüm arayüz öğelerini (Arayüz, Valve Grafiksel Kullanıcı Arayüzü ve Hedef Kimliği) etkiler. Ekran çözünürlüğü değiştirilirse otomatik olarak güncellenir. Bu değerin değiştirilmesi arayüzü sıfırlayacaktır!" +L.help_hud_game_reload = "Arayüz şu anda kullanılamıyor. Sunucuya yeniden bağlanın veya oyunu yeniden başlatın." +L.help_hud_special_settings = "Bunlar bu arayüzün özel ayarlarıdır." +L.help_vskin_info = "Valve Arayüzü (Valve Grafiksel Kullanıcı Arayüz görünümü), mevcut olan tüm menü öğelerine uygulanan görünümdür. Basit bir Lua komut dosyası ile kolayca oluşturulabilirler ve renkleri ve bazı boyut parametrelerini değiştirebilirler." +L.help_targetid_info = "Hedef Kimliği, nişangahınızı bir varlığa yönlendirirken oluşturulan bilgilerdir. Rengi 'Genel' sekmesinde yapılandırılabilir." +L.help_hud_default_desc = "Tüm oyuncular için varsayılan arayüz değerini ayarlar. Henüz bir arayüz seçmemiş olan oyuncular, varsayılan olarak bu arayüzü alacaklardır. Bunu değiştirmek, arayüzlerini zaten seçmiş olan oyuncuların arayüzlerini değiştirmez." +L.help_hud_forced_desc = "Tüm oyuncular için bir arayüz zorlar. Bu, arayüz seçim özelliğini herkes için devre dışı bırakır." +L.help_hud_enabled_desc = "Bu arayüzlerin seçimini kısıtlamak için etkinleştir veya devre dışı bırak." +L.help_damage_indicator_desc = "Hasar göstergesi, oyuncu hasar gördüğünde gösterilen bir göstergedir. Yeni bir tema eklemek için 'materialsvguitttdamageindicatorthemes' içine bir png yerleştirin." +L.help_shop_key_desc = "Bir raundun sonunda hazırlanırken skor menüsü yerine mağaza tuşuna basarak mağazayı açın" + +L.label_menu_menu = "MENÜ" +L.label_menu_admin_spacer = "Yönetici Alanı (normal kullanıcılara gösterilmez)" +L.label_language_set = "Dil seç" +L.label_global_color_enable = "Genel rengi etkinleştir" +L.label_global_color = "Genel renk" +L.label_global_scale_factor = "Genel ölçek faktörü" +L.label_hud_select = "Arayüz Seç" +L.label_vskin_select = "Valve Arayüzü seçin" +L.label_blur_enable = "Valve Arayüz arka plan bulanıklığını etkinleştir" +L.label_color_enable = "Valve Arayüz arka plan rengini etkinleştir" +L.label_minimal_targetid = "Nişangah altında minimalist Hedef Kimliği (Karma metni, ipuçları vb.)" +L.label_shop_always_show = "Her zaman mağazayı göster" +L.label_shop_double_click_buy = "Mağazada üzerine çift tıklayarak bir ürün satın almayı etkinleştir" +L.label_shop_num_col = "Sütun sayısı" +L.label_shop_num_row = "Satır sayısı" +L.label_shop_item_size = "Simge boyutu" +L.label_shop_show_slot = "Yuva işaretini göster" +L.label_shop_show_custom = "Özel öğe işaretini göster" +L.label_shop_show_fav = "Favori öğe işaretini göster" +L.label_crosshair_enable = "Nişangahı etkinleştir" +L.label_crosshair_opacity = "Nişangah opaklığı" +L.label_crosshair_ironsight_opacity = "Gez ve arpacık opaklığı" +L.label_crosshair_size = "Nişangah boyutu" +L.label_crosshair_thickness = "Nişangah kalınlığı" +L.label_crosshair_thickness_outline = "Nişangah dış çizgi kalınlığı" +L.label_crosshair_scale_enable = "Silaha bağlı nişangah ölçeğini etkinleştir" +L.label_crosshair_ironsight_low_enabled = "Gez ve arpacık kullanırken silahı indirin" +L.label_damage_indicator_enable = "Hasar göstergesini etkinleştir" +L.label_damage_indicator_mode = "Hasar göstergesi temasını seçin" +L.label_damage_indicator_duration = "Vurulduktan sonra solma süresi (saniye olarak)" +L.label_damage_indicator_maxdamage = "Maksimum opaklık için gereken hasar" +L.label_damage_indicator_maxalpha = "Maksimum opaklık" +L.label_performance_halo_enable = "Bazı varlıklara bakarken etrafına bir dış çizgi çizin" +L.label_performance_spec_outline_enable = "Kontrol edilen nesnelerin dış çizgilerini etkinleştir" +L.label_performance_ohicon_enable = "Oyuncuların başındaki rol simgelerini etkinleştir" +L.label_interface_tips_enable = "İzlerken ekranın alt kısmında oyun ipuçlarını göster" +L.label_interface_popup = "Raunt başlangıç bilgisini gösteren açılan pencerenin süresi" +L.label_interface_fastsw_menu = "Hızlı silah değişme ile menüyü etkinleştir" +L.label_inferface_wswitch_hide_enable = "Silah değişme menüsünün otomatik olarak kapanmasını etkinleştir" +L.label_inferface_scues_enable = "Bir raunt başladığında veya bittiğinde ses işaretini çal" +L.label_gameplay_specmode = "Yalnızca İzle modu (her zaman izleyici olarak kal)" +L.label_gameplay_fastsw = "Hızlı silah değişme" +L.label_gameplay_hold_aim = "Nişan almak için tutmayı etkinleştir" +L.label_gameplay_mute = "Öldüğünde canlı oyuncuları sessize al" +L.label_hud_default = "Varsayılan Arayüz" +L.label_hud_force = "Zorunlu Arayüz" + +L.label_bind_weaponswitch = "Silahı Al" +L.label_bind_voice = "Genel Sesli Sohbet" +L.label_bind_voice_team = "Takım Sesli Sohbeti" + +L.label_hud_basecolor = "Temel Renk" + +L.label_menu_not_populated = "Bu alt menü herhangi bir içerik içermiyor." + +L.header_bindings_ttt2 = "TTT2 Tuş Atamaları" +L.header_bindings_other = "Diğer Atamalar" +L.header_language = "Dil Ayarları" +L.header_global_color = "Genel Rengi Seç" +L.header_hud_select = "Bir arayüz seçin" +L.header_hud_customize = "Arayüzü Özelleştir" +L.header_vskin_select = "Valve Arayüzü Seç ve Özelleştir" +L.header_targetid = "Hedef Kimliği Ayarları" +L.header_shop_settings = "Ekipman Mağazası Ayarları" +L.header_shop_layout = "Öğe Listesi Düzeni" +L.header_shop_marker = "Öğe İşaretleyici Ayarları" +L.header_crosshair_settings = "Nişangah Ayarları" +L.header_damage_indicator = "Hasar Göstergesi Ayarları" +L.header_performance_settings = "Performans Ayarları" +L.header_interface_settings = "Arayüz Ayarları" +L.header_gameplay_settings = "Oynanış Ayarları" +L.header_hud_administration = "Varsayılan ve Zorunlu Arayüzleri Seç" +L.header_hud_enabled = "Arayüzleri etkinleştir veya devre dışı bırak" + +L.button_menu_back = "Geri" +L.button_none = "Yok" +L.button_press_key = "Bir tuşa basın" +L.button_save = "Kaydet" +L.button_reset = "Sıfırla" +L.button_close = "Kapat" +L.button_hud_editor = "Arayüz Düzenleyici" + +-- 2020-04-20 +L.item_speedrun = "Hız" +L.item_speedrun_desc = [[Sizi %50 daha hızlı yapar!]] +L.item_no_explosion_damage = "Patlama Hasarı Yok" +L.item_no_explosion_damage_desc = [[Patlama hasarına karşı bağışıklık kazandırır.]] +L.item_no_fall_damage = "Düşme Hasarı Yok" +L.item_no_fall_damage_desc = [[Düşme hasarına karşı bağışıklık kazandırır.]] +L.item_no_fire_damage = "Yanma Hasarı Yok" +L.item_no_fire_damage_desc = [[Yanma hasarına karşı bağışıklık kazandırır.]] +L.item_no_hazard_damage = "Tehlike Hasarı Yok" +L.item_no_hazard_damage_desc = [[Zehir, radyasyon ve asit gibi tehlike hasarlarına karşı bağışıklık kazandırır.]] +L.item_no_energy_damage = "Enerji Hasarı Yok" +L.item_no_energy_damage_desc = [[Lazer, plazma ve yıldırım gibi enerji hasarlarına karşı bağışıklık kazandırır.]] +L.item_no_prop_damage = "Nesne Hasarı Yok" +L.item_no_prop_damage_desc = [[Nesne hasarına karşı bağışıklık kazandırır.]] +L.item_no_drown_damage = "Boğulma Hasarı Yok" +L.item_no_drown_damage_desc = [[Boğulma hasarına karşı bağışıklık kazandırır.]] + +-- 2020-04-21 +L.dna_tid_possible = "Tarama yapılabilir." +L.dna_tid_impossible = "Tarama yapılamaz." +L.dna_screen_ready = "DNA yok" +L.dna_screen_match = "Eşleşme" + +-- 2020-04-30 +L.message_revival_canceled = "Diriliş iptal edildi." +L.message_revival_failed = "Diriliş başarısız oldu." +L.message_revival_failed_missing_body = "Cesediniz artık mevcut olmadığı için diriltilemediniz." +L.hud_revival_title = "Dirilişe kalan süre" +L.hud_revival_time = "{time}sn" + +-- 2020-05-03 +L.door_destructible = "Bu kapı yok edilebilir ({health}SP)." + +-- 2020-05-28 +L.corpse_hint_inspect_limited = "Arama yapmak için [{usekey}] tuşuna basın. Yalnızca arama kullanıcı arayüzünü görüntülemek için [{walkkey} + {usekey}]" + +-- 2020-06-04 +L.label_bind_disguiser = "Kılık Değiştiriciyi aç/kapat" + +-- 2020-06-24 +L.dna_help_primary = "DNA örneği al" +L.dna_help_secondary = "DNA yuvasını değiştirin" +L.dna_help_reload = "Numuneyi sil" + +L.binoc_help_pri = "Bir ceset ara." +L.binoc_help_sec = "Yakınlaştırma seviyesini değiştirin." + +L.vis_help_pri = "Etkinleştirilmiş cihazı bırakın." + + +-- 2020-08-07 +L.pickup_error_spec = "Bunu izleyici olarak alamazsın." +L.pickup_error_owns = "Bu silah zaten sende olduğu için bunu alamazsın." +L.pickup_error_noslot = "Boş alanın olmadığı için bunu alamazsın." + +-- 2020-11-02 +L.lang_server_default = "Sunucu Varsayılanı" +L.help_lang_info = [[ +Bu çeviri %{coverage} oranında tamamlandı ve İngilizce dili varsayılan referans olarak alındı. + +Bu çevirilerin topluluk tarafından yapıldığını unutmayın. Bir şey eksik veya yanlışsa katkıda bulunmaktan çekinmeyin.]] + +-- 2021-04-13 +L.title_score_info = "Raunt Sonu Bilgisi" +L.title_score_events = "Olay Zaman Çizelgesi" + +L.label_bind_clscore = "Açık raunt raporu" +L.title_player_score = "{player} puanı" + +L.label_show_events = "Şuradaki olayları göster" +L.button_show_events_you = "Siz" +L.button_show_events_global = "Genel" +L.label_show_roles = "Rol dağılımını göster" +L.button_show_roles_begin = "Raunt Başlangıcı" +L.button_show_roles_end = "Raunt Sonu" + +L.hilite_win_traitors = "HAİN TAKIMI KAZANDI" +L.hilite_win_innocents = "MASUM TAKIMI KAZANDI" +L.hilite_win_tie = "BERABERE" +L.hilite_win_time = "SÜRE DOLDU" + +L.tooltip_karma_gained = "Bu raunt için Karma değişiklikleri" +L.tooltip_score_gained = "Bu raunt için puan değişiklikleri" +L.tooltip_roles_time = "Bu raunt için rol değişiklikleri" + +L.tooltip_finish_score_alive_teammates = "Canlı takım arkadaşları {score}" +L.tooltip_finish_score_alive_all = "Canlı oyuncular {score}" +L.tooltip_finish_score_timelimit = "Süre doldu {score}" +L.tooltip_finish_score_dead_enemies = "Ölü düşmanlar {score}" +L.tooltip_kill_score = "Öldürme {score}" +L.tooltip_bodyfound_score = "Ceset bulundu {score}" + +L.finish_score_alive_teammates = "Canlı takım arkadaşları" +L.finish_score_alive_all = "Canlı oyuncular" +L.finish_score_timelimit = "Süre doldu" +L.finish_score_dead_enemies = "Ölü düşmanlar" +L.kill_score = "Öldürme" +L.bodyfound_score = "Ceset bulundu" + +L.title_event_bodyfound = "Bir ceset bulundu" +L.title_event_c4_disarm = "Bir C4 devre dışı bırakıldı" +L.title_event_c4_explode = "Bir C4 patladı" +L.title_event_c4_plant = "Bir C4 kuruldu" +L.title_event_creditfound = "Ekipman kredileri bulundu" +L.title_event_finish = "Raunt sona erdi" +L.title_event_game = "Yeni bir raunt başladı" +L.title_event_kill = "Bir oyuncu öldürüldü" +L.title_event_respawn = "Bir oyuncu yeniden canlandı" +L.title_event_rolechange = "Bir oyuncu rolünü veya takımını değiştirdi" +L.title_event_selected = "Roller dağıtıldı" +L.title_event_spawn = "Bir oyuncu canlandı" + +L.desc_event_bodyfound = "{finder} ({firole} {fiteam}), {found} ({forole} {foteam}) adlı kişinin cesedini buldu. Cesedin {credits} ekipman kredisi var." +L.desc_event_bodyfound_headshot = "Kurban kafadan vurularak öldürüldü." +L.desc_event_c4_disarm_success = "{disarmer} ({drole} {dteam}), {owner} ({orole} {oteam}) tarafından kurulan C4'ü başarıyla etkisiz hale getirdi." +L.desc_event_c4_disarm_failed = "{disarmer} ({drole} {dteam}), {owner} ({orole} {oteam}) tarafından kurulan C4'ü etkisiz hale getirmeye çalıştı. Başarısız oldular." +L.desc_event_c4_explode = "{owner} ({role} {team}) tarafından kurulan C4 patladı." +L.desc_event_c4_plant = "{owner} ({role} {team}), bir C4 patlayıcı kurdu." +L.desc_event_creditfound = "{finder} ({firole} {fiteam}), {found} ({forole} {foteam}) cesedinde {credits} ekipman kredisi buldu." +L.desc_event_finish = "Raunt {minutes}{seconds} sürdü. Sonunda {alive} oyuncu hayatta kaldı." +L.desc_event_game = "Yeni bir raunt başladı." +L.desc_event_respawn = "{player} yeniden canlandı." +L.desc_event_rolechange = "{player}, {orole} ({oteam}) olan rol takımını {nrole} ({nteam}) olarak değiştirdi." +L.desc_event_selected = "Takımlar ve roller tüm {amount} oyuncu için dağıtıldı." +L.desc_event_spawn = "{player} canlandı." + +-- Name of a trap that killed us that has not been named by the mapper +L.trap_something = "bir şey" + +-- Kill events +L.desc_event_kill_suicide = "Bir intihar vakası." +L.desc_event_kill_team = "Bu bir takım öldürmesiydi." + +L.desc_event_kill_blowup = "{victim} ({vrole} {vteam}) kendini havaya uçurdu." +L.desc_event_kill_blowup_trap = "{victim} ({vrole} {vteam}), {trap} tarafından havaya uçuruldu." + +L.desc_event_kill_tele_self = "{victim} ({vrole} {vteam}) kendilerini ışınlanarak öldürdüler." +L.desc_event_kill_sui = "{victim} ({vrole} {vteam}) bunu kaldıramadı ve kendini öldürdü." +L.desc_event_kill_sui_using = "{victim} ({vrole} {vteam}), {tool} kullanarak kendini öldürdü." + +L.desc_event_kill_fall = "{victim} ({vrole} {vteam}) ölümüne düştü." +L.desc_event_kill_fall_pushed = "{victim} ({vrole} {vteam}, {attacker} onları ittikten sonra ölüme düştü." +L.desc_event_kill_fall_pushed_using = "{victim} ({vrole} {vteam}), {attacker} ({arole} {ateam}) onları itmek için {trap} kullandıktan sonra ölümüne düştü." + +L.desc_event_kill_shot = "{victim} ({vrole} {vteam}), {attacker} tarafından vuruldu." +L.desc_event_kill_shot_using = "{victim} ({vrole} {vteam}), {attacker} ({arole} {ateam}) tarafından bir {weapon} kullanılarak vuruldu." + +L.desc_event_kill_drown = "{victim} ({vrole} {vteam}), {attacker} tarafından boğuldu." +L.desc_event_kill_drown_using = "{victim} ({vrole} {vteam}), {attacker} ({arole} {ateam}) tarafından tetiklenen {trap} tarafından boğuldu." + +L.desc_event_kill_boom = "{victim} ({vrole} {vteam}), {attacker} tarafından havaya uçuruldu." +L.desc_event_kill_boom_using = "{victim} ({vrole} {vteam}), {trap} kullanılarak {attacker} ({arole} {ateam}) tarafından havaya uçuruldu." + +L.desc_event_kill_burn = "{victim} ({vrole} {vteam}) / {attacker} tarafından vuruldu." +L.desc_event_kill_burn_using = "{victim} ({vrole} {vteam}), {attacker} ({arole} {ateam}) nedeniyle {trap} tarafından yakıldı." + +L.desc_event_kill_club = "{victim} ({vrole} {vteam}), {attacker} tarafından dövüldü." +L.desc_event_kill_club_using = "{victim} ({vrole} {vteam}), {trap} kullanılarak {attacker} ({arole} {ateam}) tarafından dövülerek öldürüldü." + +L.desc_event_kill_slash = "{victim} ({vrole} {vteam}), {attacker} tarafından bıçaklandı." +L.desc_event_kill_slash_using = "{victim} ({vrole} {vteam}), {trap} kullanılarak {attacker} ({arole} {ateam}) tarafından havaya uçuruldu." + +L.desc_event_kill_tele = "{victim} ({vrole} {vteam}), {attacker} tarafından ışınlanarak öldürüldü." +L.desc_event_kill_tele_using = "{victim} ({vrole} {vteam}), {attacker} ({arole} {ateam}) tarafından ayarlanan {trap} tarafından atomlarına ayrıldı." + +L.desc_event_kill_goomba = "{victim} ({vrole} {vteam}), {attacker} ({arole} {ateam}) kendi cüssesiyle onu ezdi." + +L.desc_event_kill_crush = "{victim} ({vrole} {vteam}), {attacker} tarafından ezildi." +L.desc_event_kill_crush_using = "{victim} ({vrole} {vteam}), {trap} aracılıyla {attacker} ({arole} {ateam}) tarafından ezildi." + +L.desc_event_kill_other = "{victim} ({vrole} {vteam}), {attacker} tarafından öldürüldü." +L.desc_event_kill_other_using = "{victim} ({vrole} {vteam}), {trap} kullanılarak {attacker} ({arole} {ateam}) tarafından havaya uçuruldu." + +-- 2021-04-20 +L.none = "Rol Yok" + +-- 2021-04-24 +L.karma_teamkill_tooltip = "Öldürülen takım arkadaşı" +L.karma_teamhurt_tooltip = "Hasar verilen takım arkadaşı" +L.karma_enemykill_tooltip = "Öldürülen düşman" +L.karma_enemyhurt_tooltip = "Hasar verilen düşman" +L.karma_cleanround_tooltip = "Raundu Temizle" +L.karma_roundheal_tooltip = "Karma yenileme" +L.karma_unknown_tooltip = "Bilinmiyor" + +-- 2021-05-07 +L.header_random_shop_administration = "Rastgele Mağaza Ayarları" +L.header_random_shop_value_administration = "Bakiye Ayarları" + +L.shopeditor_name_random_shops = "Rastgele mağazaları etkinleştir" +L.shopeditor_desc_random_shops = [[Rastgele mağazalar, her oyuncuya mevcut tüm ekipmanların sınırlı bir rastgele setini verir." +Takım mağazaları, aynı seti bireysel olanlar yerine bir takımdaki tüm oyunculara zorla verir." +Yeniden dağıtma, yeni bir rastgele ekipman seti almanızı sağlar.]] +L.shopeditor_name_random_shop_items = "Rastgele ekipman sayısı" +L.shopeditor_desc_random_shop_items = "Bu, her zaman mağazada mevcut olarak işaretlenmiş ekipmanları içerir. Bu yüzden yeterince yüksek bir sayı seç yoksa sadece onları alırsın." +L.shopeditor_name_random_team_shops = "Takım mağazalarını etkinleştir" +L.shopeditor_name_random_shop_reroll = "Mağaza yeniden dağıtım kullanılabilirliğini etkinleştir" +L.shopeditor_name_random_shop_reroll_cost = "Yeniden dağıtım başına maliyet" +L.shopeditor_name_random_shop_reroll_per_buy = "Satın aldıktan sonra otomatik olarak yeniden dağıt" + +-- 2021-06-04 +L.header_equipment_setup = "Ekipman Ayarları" +L.header_equipment_value_setup = "Bakiye Ayarları" + +L.equipmenteditor_name_not_buyable = "Satın alınabilir" +L.equipmenteditor_desc_not_buyable = "Devre dışı bırakılırsa ekipman mağazada gösterilmez. Bu ekipmanın atandığı roller yine de onu alacaktır." +L.equipmenteditor_name_not_random = "Mağazada her zaman mevcuttur" +L.equipmenteditor_desc_not_random = "Etkinleştirilirse, ekipman her zaman mağazada mevcuttur. Rastgele mağaza etkinleştirildiğinde, mevcut bir rastgele yuva alır ve her zaman bu ekipman için ayırır." +L.equipmenteditor_name_global_limited = "Genel sınırlı miktar" +L.equipmenteditor_desc_global_limited = "Etkinleştirilirse, ekipman aktif rauntta sunucuda yalnızca bir kez satın alınabilir." +L.equipmenteditor_name_team_limited = "Takım sınırlı miktar" +L.equipmenteditor_desc_team_limited = "Etkinleştirilirse, ekipman aktif rauntta takım başına yalnızca bir kez satın alınabilir." +L.equipmenteditor_name_player_limited = "Oyuncu sınırlı miktarı" +L.equipmenteditor_desc_player_limited = "Etkinleştirilirse, ekipman aktif rauntta oyuncu başına yalnızca bir kez satın alınabilir." +L.equipmenteditor_name_min_players = "Satın almak için minimum oyuncu sayısı" +L.equipmenteditor_name_credits = "Kredi cinsinden fiyat" + +-- 2021-06-08 +L.equip_not_added = "eklenmedi" +L.equip_added = "eklendi" +L.equip_inherit_added = "eklendi (devralma)" +L.equip_inherit_removed = "kaldırıldı (devral)" + +-- 2021-06-09 +L.layering_not_layered = "Katmanlı değil" +L.layering_layer = "Katman {layer}" +L.header_rolelayering_role = "{role} dağıtımı" +L.header_rolelayering_baserole = "Temel rol dağıtma" +L.submenu_administration_rolelayering_title = "Rol Dağıtma" +L.header_rolelayering_info = "Rol dağıtma bilgileri" +L.help_rolelayering_roleselection = "Rol dağılım süreci iki aşamaya ayrılmıştır. İlk aşamada masum, hain ve aşağıdaki 'temel rol dağıtımı' kutusunda listelenen temel roller dağıtılır. İkinci aşama, bu temel rolleri bir alt role yükseltmek için kullanılır." +L.help_rolelayering_layers = "Her dağıtımdan yalnızca bir rol seçilir. İlk olarak, özel katmanlardan gelen roller, ilk katmandan başlayarak son katmana ulaşılana kadar dağıtılır veya daha fazla rol yükseltilemez. Hangisi önce olursa olsun, yükseltilebilir slotlar hala mevcutsa, katmanlanmamış roller de dağıtılacaktır." +L.scoreboard_voice_tooltip = "Ses seviyesini değiştirmek için kaydırın" + +-- 2021-06-15 +L.header_shop_linker = "Ayarlar" +L.label_shop_linker_set = "Mağaza türünü seçin" + +-- 2021-06-18 +L.xfer_team_indicator = "Takım" + +-- 2021-06-25 +L.searchbar_default_placeholder = "Listede ara..." + +-- 2021-07-11 +L.spec_about_to_revive = "İzleme, canlanma sırasında sınırlıdır." + +-- 2021-09-01 +L.spawneditor_name = "Oluşum Düzenleyici Aracı" +L.spawneditor_desc = "Dünyaya silah, cephane ve oyuncu canlanma noktası yerleştirmek için kullanılır. Yalnızca süper yönetici tarafından kullanılabilir." + +L.spawneditor_place = "Oluşum noktasını yerleştir" +L.spawneditor_remove = "Oluşum noktasını kaldır" +L.spawneditor_change = "Oluşum noktası türünü değiştirin (geri almak için [SHIFT] tuşunu basılı tutun)" +L.spawneditor_ammo_edit = "Otomatik ortaya çıkan cephaneyi düzenlemek için silahın ortaya çıkmasını bekle" + +L.spawn_weapon_random = "Rastgele Silah Oluşum Noktası" +L.spawn_weapon_melee = "Yakın Dövüş Silahı Oluşum Noktası" +L.spawn_weapon_nade = "Bomba Oluşum Noktası" +L.spawn_weapon_shotgun = "Pompalı Oluşum Noktası" +L.spawn_weapon_heavy = "Ağır Silah Oluşum Noktası" +L.spawn_weapon_sniper = "Keskin Nişancı Silahı Oluşum Noktası" +L.spawn_weapon_pistol = "Tabanca Silahı Oluşum Noktası" +L.spawn_weapon_special = "Özel Silah Oluşum Noktası" +L.spawn_ammo_random = "Rastgele Cephane Oluşum Noktası" +L.spawn_ammo_deagle = "Deagle Cephanesi Oluşum Noktası" +L.spawn_ammo_pistol = "Tabanca Cephanesi Oluşum Noktası" +L.spawn_ammo_mac10 = "Mac10 Cephanesi Oluşum Noktası" +L.spawn_ammo_rifle = "Tüfek Cephanesi Oluşum Noktası" +L.spawn_ammo_shotgun = "Pompalı Cephanesi Oluşum Noktası" +L.spawn_player_random = "Rastgele Oyuncu Canlanma Noktası" + +L.spawn_weapon_ammo = "(Cephane {ammo})" + +L.spawn_weapon_edit_ammo = "Bu silahın oluşum noktasında cephaneyi artırmak veya azaltmak için [{walkkey}] tuşunu basılı tutun ve [{primaryfire} veya {secondaryfire}] tuşuna basın" + +L.spawn_type_weapon = "Bu bir silah oluşum noktasıdır" +L.spawn_type_ammo = "Bu bir cephane oluşum noktasıdır" +L.spawn_type_player = "Bu bir oyuncu canlanma noktasıdır" + +L.spawn_remove = "Bu oluşum noktasını kaldırmak için [{secondaryfire}] tuşuna basın" + +L.submenu_administration_entspawn_title = "Oluşum Noktası Düzenleyici" +L.header_entspawn_settings = "Oluşum Noktası Düzenleyici Ayarları" +L.button_start_entspawn_edit = "Oluşum Noktası Düzenlemesini Başlat" +L.button_delete_all_spawns = "Tüm Oluşum Noktalarını Sil" + +L.label_dynamic_spawns_enable = "Bu harita için dinamik oluşum noktalarını etkinleştir" +L.label_dynamic_spawns_global_enable = "Tüm haritalar için dinamik oluşum noktalarını etkinleştir" + +L.header_equipment_weapon_spawn_setup = "Silah Oluşum Ayarları" + +L.help_spawn_editor_info = [[ +Oluşum noktası düzenleyicisi, dünyadaki oluşum noktalarını yerleştirmek, kaldırmak ve düzenlemek için kullanılır. Bu oluşum noktaları silahlar, cephaneler ve oyuncular içindir. + +Bu oluşum noktaları, 'datatttweaponspawnscripts' içinde bulunan dosyalara kaydedilir. Donanım sıfırlaması için silinebilirler. İlk oluşum noktası dosyaları, haritada ve orijinal TTT silah oluşum noktası komut dosyalarında bulunan oluşum noktalarından oluşturulur. Sıfırlama düğmesine basıldığında her zaman başlangıç durumuna geri dönülür. + +Bu oluşum noktası sisteminin dinamik oluşumları kullandığı unutulmamalıdır. Bu, silahlar için en ilginç olanıdır, çünkü artık belirli bir silahı değil, bir tür silahı tanımlar. Örneğin, bir TTT pompalı oluşum noktası yerine, artık pompalı olarak tanımlanan herhangi bir silahın çıkabileceği genel bir pompalı oluşum noktası var. Her silah için oluşum türü 'Ekipmanı Düzenle' menüsünden ayarlanabilir. Bu, herhangi bir silahın haritada ortaya çıkmasını veya belirli varsayılan silahları devre dışı bırakmasını mümkün kılar. + +Birçok değişikliğin ancak yeni bir raunt başladıktan sonra yürürlüğe gireceğini unutmayın.]] +L.help_spawn_editor_enable = "Bazı haritalarda, haritada bulunan orijinal oluşum noktalarının dinamik sistemle değiştirilmeden kullanılması önerilebilir. Aşağıdaki bu seçeneğin değiştirilmesi yalnızca şu anda etkin olan haritayı etkiler, bu nedenle dinamik sistem diğer tüm haritalar için kullanılmaya devam edecektir." +L.help_spawn_editor_hint = "İpucu oluşum düzenleyicisinden çıkmak için oyun modu menüsünü yeniden açın." +L.help_spawn_editor_spawn_amount = [[ +Şu anda bu haritada {weapon} silah oluşumu, {ammo} cephane oluşumu ve {player} oyuncu canlanma noktaları var. +Bu miktarı değiştirmek için 'ON düzenlemesini başlat'a tıklayın. + +{weaponrandom}x Rastgele Silah Oluşumu +{weaponmelee}x Yakın Dövüş Silahı Oluşumu +{weaponnade}x El Bombası Oluşumu +{weaponshotgun}x Pompalı Silahı Oluşumu +{weaponheavy}x Ağır Silah Oluşumu +{weaponsniper}x Keskin Nişancı Oluşumu +{weaponpistol}x Tabanca Oluşumu +{weaponspecial}x Özel Silah Oluşumu + +{ammorandom}x Rastgele Cephane Oluşumu +{ammodeagle}x Deagle Cephane Oluşumu +{ammopistol}x Tabanca Cephane Oluşumu +{ammomac10}x Mac10 Cephane Oluşumu +{ammorifle}x Tüfek Cephane Oluşumu +{ammoshotgun}x Pompalı Cephane Oluşumu + +{playerrandom}x Rastgele Oyuncu Canlanması]] + +L.equipmenteditor_name_auto_spawnable = "Ekipman dünyada rastgele ortaya çıkar" +L.equipmenteditor_name_spawn_type = "Canlanma türünü seçin" +L.equipmenteditor_desc_auto_spawnable = [[ +TTT2 oluşum noktası sistemi, dünyadaki her silahın çıkmasına izin verir. Varsayılan olarak, yalnızca yaratıcı tarafından 'Otomatik Çıkabilir' olarak işaretlenen silahlar dünyada ortaya çıkacaktır, ancak bu menüden değiştirilebilir. + +Ekipmanın çoğu, varsayılan olarak 'özel silahların ortaya çıkmasına' ayarlanmıştır. Bu, ekipmanın yalnızca rastgele silah oluşumlarında ortaya çıktığı anlamına gelir. Bununla birlikte, mevcut diğer oluşum türlerini kullanmak için dünyaya özel silah oluşum noktaları yerleştirmek veya burada oluşum noktası türünü değiştirmek mümkündür.]] + +L.pickup_error_inv_cached = "Envanteriniz önbelleğe alındığı için şu anda bunu alamazsınız." + +-- 2021-09-02 +L.submenu_administration_playermodels_title = "Oyuncu Modelleri" +L.header_playermodels_general = "Genel Oyuncu Modeli Ayarları" +L.header_playermodels_selection = "Oyuncu Modeli Havuzunu Seçin" + +L.label_enforce_playermodel = "Rol oyuncu modelini uygula" +L.label_use_custom_models = "Rastgele seçilen bir oyuncu modeli kullan" +L.label_prefer_map_models = "Varsayılan modeller yerine haritaya özgü modelleri tercih edin" +L.label_select_model_per_round = "Her rauntta yeni bir rastgele model seçin (devre dışı bırakılmışsa yalnızca harita değişikliğinde)" + +L.help_prefer_map_models = [[ +Bazı haritalar kendi oyuncu modellerini tanımlar. Varsayılan olarak, bu modeller otomatik olarak atananlardan daha yüksek bir önceliğe sahiptir. Bu ayar devre dışı bırakıldığında, haritaya özgü modeller devre dışı bırakılır. + +Role özgü modeller her zaman daha yüksek önceliğe sahiptir ve bu ayardan etkilenmez.]] +L.help_enforce_playermodel = [[ +Bazı rollerin özel oyuncu modelleri vardır. Bazı oyuncu modeli seçicileriyle uyumluluk için uygun olabilecek şekilde devre dışı bırakılabilirler. +Bu ayar devre dışı bırakılırsa, rastgele varsayılan modeller yine de seçilebilir.]] +L.help_use_custom_models = [[ +Varsayılan olarak tüm oyunculara yalnızca CSS Phoenix oyuncu modeli atanır. Ancak bu seçeneği etkinleştirerek bir oyuncu modeli havuzu seçmek mümkündür. Bu ayar etkinleştirildiğinde, her oyuncuya yine aynı oyuncu modeli atanacaktır, ancak tanımlanan model havuzundan rastgele bir modeldir. + +Model seçimleri daha fazla oyuncu modeli yükleyerek genişletilebilir.]] + +-- 2021-10-06 +L.menu_server_addons_title = "Sunucu Eklentileri" +L.menu_server_addons_description = "Sunucu genelinde yalnızca eklentiler için yönetici ayarları." + +L.tooltip_finish_score_penalty_alive_teammates = "Canlı takım arkadaşlarının cezası {score}" +L.finish_score_penalty_alive_teammates = "Canlı takım arkadaşlarının cezası" +L.tooltip_kill_score_suicide = "İntihar {score}" +L.kill_score_suicide = "İntihar" +L.tooltip_kill_score_team = "Takım arkadaşı öldürme {score}" +L.kill_score_team = "Takım arkadaşı öldürme" + +-- 2021-10-09 +L.help_models_select = [[ +Oyuncu modeli havuzuna eklemek için modellere sol tıklayın. Kaldırmak için tekrar sol tıklayın. Odaklanan model için etkin ve devre dışı dedektif şapkaları arasında sağ tıklama geçiş yapar. + +Sol üstteki küçük gösterge, oyuncu modelinin bir kafa vuruş kutusuna sahip olup olmadığını gösterir. Aşağıdaki simge, bu modelin bir dedektif şapkası için geçerli olup olmadığını gösterir.]] + +L.menu_roles_title = "Rol Ayarları" +L.menu_roles_description = "Oluşum noktalarını, ekipman kredilerini ve daha fazlasını ayarla." + +L.submenu_administration_roles_general_title = "Genel Rol Ayarları" + +L.header_roles_info = "Rol Bilgileri" +L.header_roles_selection = "Rol Seçim Parametreleri" +L.header_roles_tbuttons = "Hain Düğmelerine Erişim" +L.header_roles_credits = "Rol Ekipmanı Kredileri" +L.header_roles_additional = "Ek Rol Ayarları" +L.header_roles_reward_credits = "Ödül Ekipmanı Kredileri" + +L.help_roles_default_team = "Varsayılan takım {team}" +L.help_roles_unselectable = "Bu rol dağıtılamaz. Rol dağılım sürecinde dikkate alınmaz. Çoğu zaman bu, bunun bir canlanma, bir yardımcı deagle veya benzeri bir olay aracılığıyla raunt sırasında manuel olarak atanan bir rol olduğu anlamına gelir." +L.help_roles_selectable = "Bu rol dağıtılabilir. Tüm kriterlerin karşılanması durumunda bu rol, rol dağılımı sürecinde dikkate alınır." +L.help_roles_credits = "Ekipman kredileri, mağazadan ekipman satın almak için kullanılır. Onlara yalnızca mağazalara erişimi olan roller için vermek çoğunlukla mantıklıdır. Bununla birlikte, cesetler üzerinde kredi bulmak mümkün olduğundan, katillerine ödül olarak rollere başlangıç kredisi de verebilirsiniz." +L.help_roles_selection_short = "Oyuncu başına rol dağılımı, bu role atanan oyuncuların yüzdesini tanımlar. Örneğin, değer '0.2' olarak ayarlanırsa, her beşinci oyuncu bu rolü alır." +L.help_roles_selection = [[ +Oyuncu başına rol dağılımı, bu role atanan oyuncuların yüzdesini tanımlar. Örneğin, değer '0.2' olarak ayarlanırsa, her beşinci oyuncu bu rolü alır. Bu aynı zamanda bu rolün dağıtılması için en az 5 oyuncunun gerekli olduğu anlamına gelir. +Tüm bunların yalnızca rolün dağıtım süreci için dikkate alınması durumunda geçerli olduğunu unutmayın. + +Söz konusu rol dağılımı, oyuncuların alt sınırı ile özel bir entegrasyona sahiptir. Rol dağıtım için düşünülürse ve minimum değer dağıtım faktörünün verdiği değerin altındaysa, ancak oyuncu miktarı alt sınıra eşit veya daha büyükse, tek bir oyuncu yine de bu rolü alabilir. Dağıtım süreci daha sonra ikinci oyuncu için her zamanki gibi çalışır.]] +L.help_roles_award_info = "Bazı roller (kredi ayarlarında etkinleştirilmişse), düşmanların belirli bir yüzdesi öldüğünde ekipman kredisi alır. İlgili değerler burada düzeltilebilir." +L.help_roles_award_pct = "Düşmanların bu yüzdesi öldüğünde, belirli rollere ekipman kredisi verilir." +L.help_roles_award_repeat = "Kredi ödülünün birden çok kez verilip verilmediği. Örneğin, yüzde '0.25' olarak ayarlanırsa ve bu ayar etkinleştirilirse, oyunculara sırasıyla '%25', '%50' ve '%75' ölü düşmanlarda kredi verilecektir." +L.help_roles_advanced_warning = "UYARI Bunlar, rol dağıtım sürecini tamamen bozabilecek gelişmiş ayarlardır. Şüpheye düştüğünüzde tüm değerleri '0' da tutun. Bu değer, herhangi bir sınır uygulanmadığı ve rol dağılımının mümkün olduğunca çok rol atamaya çalışacağı anlamına gelir." +L.help_roles_max_roles = [[ +Buradaki roller terimi hem temel rolleri hem de alt rolleri içerir. Varsayılan olarak, kaç farklı rolün atanabileceği konusunda bir sınır yoktur. Ancak, bunları sınırlamanın iki farklı yolu vardır. + +1. Sabit bir miktarla sınırlayın. +2. Onları bir yüzde ile sınırlayın. + +İkincisi, yalnızca sabit miktar '0' ise ve mevcut oyuncuların ayarlanan yüzdesine göre bir üst sınır belirlerse kullanılır.]] +L.help_roles_max_baseroles = [[ +Temel roller yalnızca başkalarının devraldığı rollerdir. Örneğin, Masum rolü temel bir roldür, Firavun ise bu rolün bir alt rolüdür. Varsayılan olarak, kaç farklı rolün atanabileceği konusunda bir sınır yoktur. Ancak, bunları sınırlamanın iki farklı yolu vardır. + +1. Sabit bir miktarla sınırlayın. +2. Onları bir yüzde ile sınırlayın. + +İkincisi, yalnızca sabit miktar '0' ise ve mevcut oyuncuların ayarlanan yüzdesine göre bir üst sınır belirlerse kullanılır.]] + +L.label_roles_enabled = "Rolü etkinleştir" +L.label_roles_min_inno_pct = "Oyuncu başına masum dağılımı" +L.label_roles_pct = "Oyuncu başına rol dağılımı" +L.label_roles_max = "Bu rol için atanan oyuncuların üst sınırı" +L.label_roles_random = "Bu rolün dağıtılma şansı" +L.label_roles_min_players = "Dağıtımı göz önünde bulundurmak için oyuncuların alt sınırı" +L.label_roles_tbutton = "Rol, Hain düğmelerini kullanabilir" +L.label_roles_credits_starting = "Başlangıç kredileri" +L.label_roles_credits_award_pct = "Kredi ödül yüzdesi" +L.label_roles_credits_award_size = "Kredi ödülü boyutu" +L.label_roles_credits_award_repeat = "Kredi ödülü tekrarı" +L.label_roles_newroles_enabled = "Özel rolleri etkinleştir" +L.label_roles_max_roles = "Üst rol sınırı" +L.label_roles_max_roles_pct = "Yüzde olarak üst rol sınırı" +L.label_roles_max_baseroles = "Üst temel rol sınırı" +L.label_roles_max_baseroles_pct = "Yüzde olarak üst temel rol sınırı" +L.label_detective_hats = "Dedektif gibi polislik rolleri için şapkaları etkinleştir (oyuncu modeli izin veriyorsa)" + +L.ttt2_desc_innocent = "Bir Masum, hiçbir özel yeteneğe sahip değildir. Teröristler arasında kötüleri bulup öldürmek zorundalar. Ayrıca takım arkadaşlarını öldürmemeye dikkat etmek zorundalar." +L.ttt2_desc_traitor = "Hain, Masumların düşmanıdır. Özel ekipman satın alabilecekleri bir ekipman menüsü vardır. Takım arkadaşları hariç herkesi öldürmek zorundalar." +L.ttt2_desc_detective = "Masumların güvenebileceği kişi Dedektiftir. Kudretli Dedektif tüm kötü teröristleri bulmak zorundadır. Mağazalarındaki ekipmanlar bu görevde onlara yardımcı olabilir." + +-- 2021-10-10 +L.button_reset_models = "Oyuncu Modellerini Sıfırla" + +-- 2021-10-13 +L.help_roles_credits_award_kill = "Kredi kazanmanın bir başka yolu da Dedektif gibi 'herkese açık bir rolü' olan yüksek değerli oyuncuları öldürmektir. Eğer katilin rolü bunu etkinleştirdiyse, aşağıda tanımlanan miktarda kredi kazanır." +L.help_roles_credits_award = [[ +Temel TTT2'de kredi almanın iki farklı yolu vardır + +1. Düşman takımın belirli bir yüzdesi ölmüşse, tüm takıma kredi verilir. +2. Bir oyuncu, Dedektif gibi 'herkese açık bir role' sahip yüksek değerli bir oyuncuyu öldürdüyse, katile kredi verilir. + +Tüm takım ödüllendirilse bile bunun yine de her rol için etkinleştirilebileceğini lütfen unutmayın. Örneğin, Masum takımı ödüllendirilirse, ancak Masum rolünün bu özelliği devre dışı bırakılmışsa, kredilerini yalnızca Dedektif alacaktır. +Bu özelliğin dengeleme değerleri 'Yönetim' - 'Genel Rol Ayarları' bölümünden ayarlanabilir.]] +L.help_detective_hats = [[ +Dedektif gibi polislik rolleri, yetkilerini göstermek için şapka takabilir. Onları öldüklerinde veya kafadan hasar gördüklerinde kaybederler. + +Bazı oyuncu modelleri varsayılan olarak şapkaları desteklemez. Bu, 'Yönetim' - 'Oyuncu Modelleri' bölümünden değiştirilebilir]] + +L.label_roles_credits_award_kill = "Öldürme için kredi ödülü" +L.label_roles_credits_dead_award = "Ölü düşmanların belirli bir yüzdesi için kredi ödülünü etkinleştir" +L.label_roles_credits_kill_award = "Yüksek değerli oyuncu öldürme için kredi ödülünü etkinleştir" +L.label_roles_min_karma = "Dağılımı göz önünde bulundurmak için Karma'nın alt sınırı" + +-- 2021-11-07 +L.submenu_administration_administration_title = "Yönetim" +L.submenu_administration_voicechat_title = "Sesli sohbet Metin sohbeti" +L.submenu_administration_round_setup_title = "Raunt Ayarları" +L.submenu_administration_mapentities_title = "Harita Varlıkları" +L.submenu_administration_inventory_title = "Envanter" +L.submenu_administration_karma_title = "Karma" +L.submenu_administration_sprint_title = "Koşma" +L.submenu_administration_playersettings_title = "Oyuncu Ayarları" + +L.header_roles_special_settings = "Özel Rol Ayarları" +L.header_equipment_additional = "Ek Ekipman Ayarları" +L.header_administration_general = "Genel Yönetim Ayarları" +L.header_administration_logging = "Günlük Kaydı" +L.header_administration_misc = "Diğer" +L.header_entspawn_plyspawn = "Oyuncu Canlanma Ayarları" +L.header_voicechat_general = "Genel Sesli Sohbet Ayarları" +L.header_voicechat_battery = "Sesli Sohbet Pili" +L.header_voicechat_locational = "Sesli Sohbet Mesafesi" +L.header_playersettings_plyspawn = "Oyuncu Canlanma Ayarları" +L.header_round_setup_prep = "Raunt Hazırlığı" +L.header_round_setup_round = "Raunt Aktif" +L.header_round_setup_post = "Raunt Sonu" +L.header_round_setup_map_duration = "Harita Oturumu" +L.header_textchat = "Metin sohbeti" +L.header_round_dead_players = "Ölü Oyuncu Ayarları" +L.header_administration_scoreboard = "Puan Tablosu Ayarları" +L.header_hud_toggleable = "Değiştirilebilir Arayüz Öğeleri" +L.header_mapentities_prop_possession = "Nesne Kontrolü" +L.header_mapentities_doors = "Kapılar" +L.header_karma_tweaking = "Karma Düzenlemesi" +L.header_karma_kick = "Sunucudan Atma ve Yasaklama Karması" +L.header_karma_logging = "Karma Günlüğü" +L.header_inventory_gernal = "Envanter Boyutu" +L.header_inventory_pickup = "Envanter Silah Toplama" +L.header_sprint_general = "Koşma Ayarları" +L.header_playersettings_armor = "Zırh Sistemi Ayarları" + +L.help_killer_dna_range = "Bir oyuncu başka bir oyuncu tarafından öldürüldüğünde, cesedinde bir DNA örneği kalır. Aşağıdaki ayar, bırakılacak DNA numuneleri için hammer ünitelerindeki maksimum mesafeyi tanımlar. Kurban öldüğünde katil bu değerden daha uzaktaysa, ceset üzerinde hiçbir örnek kalmayacaktır." +L.help_killer_dna_basetime = "Katil 0 hammer birimi uzaktaysa, bir DNA örneği bozulana kadar saniye cinsinden baz süre. Katil ne kadar uzaktaysa, DNA örneğinin çürümesi için o kadar az zaman verilecektir." +L.help_dna_radar = "TTT2 DNA tarayıcısı, varsa seçilen DNA örneğinin tam mesafesini ve yönünü gösterir. Bununla birlikte, bekleme süresi her geçtiğinde seçilen numuneyi bir dünya içi render ile güncelleyen klasik bir DNA tarayıcı modu da vardır." +L.help_idle = "Boşta modu, boşta kalan oyuncuları izleyici moduna zorlamak için kullanılır. Bu moddan çıkmak için 'oyun' menüsünde devre dışı bırakmaları gerekir." +L.help_namechange_kick = [[ +Aktif bir raunt sırasında isim değişikliği kötüye kullanılabilir. Bu nedenle, bu varsayılan olarak yasaktır ve suç işleyen oyuncunun sunucudan atılmasına neden olacaktır. + +Yasaklama süresi 0'dan büyükse, oyuncu bu süre geçene kadar sunucuya yeniden bağlanamaz.]] +L.help_damage_log = "Bir oyuncu her hasar aldığında, etkinleştirilirse konsola bir hasar kaydı girişi eklenir. Bu, bir raunt sona erdikten sonra da diske kaydedilebilir. Dosya 'dataterrortownlogs' adresinde bulunur" +L.help_spawn_waves = [[ +Bu değişken 0 olarak ayarlanırsa, tüm oyuncular bir kerede ortaya çıkar. Çok sayıda oyuncuya sahip sunucular için, oyuncuları dalgalar halinde canlandırmak faydalı olabilir. Canlandırma dalgası aralığı, her bir canlanma dalgası arasındaki süredir. Bir canlanma dalgası, geçerli canlanma noktaları olduğu sürece her zaman çok sayıda oyuncu canlandırır. + +Not: Hazırlama süresinin istenen miktarda canlanma dalgası için yeterince uzun olduğundan emin olun.]] +L.help_voicechat_battery = [[ +Etkinleştirilmiş sesli sohbet pili ile sesli sohbet, pil şarjını azaltır. Bittiğinde, oyuncu sesli sohbeti kullanamaz ve şarj olmasını beklemek zorundadır. Bu, aşırı sesli sohbet kullanımını önlemeye yardımcı olabilir. + +Not: 'Tik' bir oyun tikini ifade eder. Örneğin, tik hızı 66 olarak ayarlanırsa, saniyenin 166'sı olacaktır.]] +L.help_ply_spawn = "Oyuncu (yeniden) canlanışında kullanılan oyuncu ayarları." +L.help_haste_mode = [[ +Hız modu, her ölü oyuncu ile raunt süresini artırarak oyunu dengeler. Yalnızca eylemsiz oyuncularda eksik olan roller gerçek raunt zamanını görebilir. Diğer tüm roller yalnızca hız modu başlangıç zamanını görebilir. + +Hız modu etkinleştirilirse, sabit raunt süresi göz ardı edilir.]] +L.help_round_limit = "Ayarlanan sınır koşullarından biri karşılandıktan sonra, bir harita değişikliği tetiklenir." +L.help_armor_balancing = "Zırhı dengelemek için aşağıdaki değerler kullanılabilir." +L.help_item_armor_classic = "Klasik zırh modu etkinleştirilmişse, yalnızca önceki ayarlar önemlidir. Klasik zırh modu, bir oyuncunun bir turda yalnızca bir kez zırh satın alabileceği ve bu zırhın gelen mermi ve levye hasarının %30'unu ölene kadar bloke ettiği anlamına gelir." +L.help_item_armor_dynamic = [[ +Dinamik zırh, zırhı daha ilginç hale getirmek için TTT2 yaklaşımıdır. Satın alınabilecek zırh miktarı artık sınırsızdır ve zırh değeri birikir. Hasar almak, zırh değerini azaltır. Satın alınan zırh öğesi başına zırh değeri, söz konusu öğenin 'Ekipman Ayarları'nda ayarlanır. + +Hasar alırken, bu hasarın belirli bir yüzdesi zırh hasarına dönüştürülür, oyuncuya hala farklı bir yüzde uygulanır ve geri kalanı kaybolur. + +Güçlendirilmiş zırh etkinleştirilirse, zırh değeri takviye eşiğinin üzerinde olduğu sürece oyuncuya uygulanan hasar %15 azaltılır.]] +L.help_sherlock_mode = "Sherlock modu klasik TTT modudur. Sherlock modu devre dışı bırakılırsa, cesetler onaylanamaz, puan tablosu herkesi canlı olarak gösterir ve izleyiciler yaşayan oyuncularla konuşabilir." +L.help_prop_possession = [[ +Nesne kontrolü, izleyiciler tarafından dünyada bulunan nesneleri kontrol etmek için kullanılabilir ve söz konusu nesneyi hareket ettirmek için yavaş şarj olan 'güç ölçeri' kullanılabilir. + +'Güç Ölçeri'nin maksimum değeri, tanımlanmış iki sınır arasına sıkıştırılmış ölüm farkının eklendiği bir topa sahip olma temel değerinden oluşur. Sayaç zamanla yavaş yavaş şarj olur. Ayarlanan şarj süresi, 'güç ölçerde' tek bir noktayı şarj etmek için gereken süredir.]] +L.help_karma = "Oyuncular belirli miktarda Karma ile başlar ve takım arkadaşlarına zarar verdiklerinde kaybederler. Kaybettikleri miktar, hasar verdikleri veya öldürdükleri kişinin Karmasına bağlıdır. Düşük Karma, verilen hasarı azaltır." +L.help_karma_strict = "Katı Karma etkinleştirilirse, Karma düştükçe hasar cezası daha hızlı artar. Kapalı olduğunda, insanlar 800'ün üzerinde kaldığında hasar cezası çok düşüktür. Katı modu etkinleştirmek, Karma'nın gereksiz öldürmeleri caydırmada daha büyük bir rol oynamasını sağlarken, onu devre dışı bırakmak, Karma'nın yalnızca takım arkadaşlarını sürekli olarak öldüren oyunculara zarar verdiği daha \"gevşek\" bir oyunla sonuçlanır." +L.help_karma_max = "Maks. Karmanın değerini 1000'in üzerine ayarlamak, 1000'den fazla Karmaya sahip oyunculara hasar bonusu vermez. Karma sınırı olarak kullanılabilir." +L.help_karma_ratio = "Her ikisi de aynı takımdaysa, kurbanın Karmasının ne kadarının saldırgandan çıkarıldığını hesaplamak için kullanılan hasarın oranıdır. Bir takım öldürme gerçekleşirse, başka bir ceza uygulanır." +L.help_karma_traitordmg_ratio = "Her ikisi de farklı takımlarda ise, kurbanın Karmasının ne kadarının saldırgana eklendiğini hesaplamak için kullanılan hasarın oranı. Eğer bir düşman öldürülürse, bir bonus daha uygulanır." +L.help_karma_bonus = "Bir rauntta Karma kazanmanın iki farklı pasif yolu da vardır. Birincisi, raunt sonundaki her oyuncuya uygulanan bir karma restorasyonudur. Hiçbir takım arkadaşı bir oyuncu tarafından yaralanmamış veya öldürülmemişse, ikincil bir temiz raunt bonusu verilir." +L.help_karma_clean_half = [[ +Bir oyuncunun Karması başlangıç seviyesinin üzerinde olduğunda (yani maksimum Karma bundan daha yüksek olacak şekilde yapılandırıldığında), tüm Karma artışları, Karmalarının başlangıç seviyesinin ne kadar üzerinde olduğuna bağlı olarak azaltılacaktır. Yani ne kadar yüksek olursa o kadar yavaş yükselir. + +Bu azalma, başlangıçta hızlı olan üstel bir bozunma eğrisine girer ve artış küçüldükçe yavaşlar. Bu konvar, bonusun hangi noktada yarıya indirildiğini (yani yarılanma ömrünü) belirler. Varsayılan değer 0.25 ile, Karma'nın başlangıç miktarı 1000 ve maksimum 1500 ise ve bir oyuncu Karma 1125'e ((1500 - 1000) 0.25 = 125) sahipse, temiz raunt bonusu 30 2 = 15 olacaktır. Böylece bonusu daha hızlı düşürmek için bu konvarı düşürürsünüz, daha yavaş düşürmek için 1'e yükseltirsiniz.]] +L.help_max_slots = "Yuva başına maksimum silah miktarını ayarlar. '-1', sınır olmadığı anlamına gelir." +L.help_item_armor_value = "Dinamik modda zırh ögesinin verdiği zırh değeridir. Klasik mod etkinleştirilirse (bkz. 'Yönetim' - 'Oyuncu Ayarları'), 0'dan büyük her değer mevcut zırh olarak sayılır." + +L.label_killer_dna_range = "DNA bırakmak için maksimum öldürme aralığı" +L.label_killer_dna_basetime = "Numune ömrü baz süresi" +L.label_dna_scanner_slots = "DNA numune yuvaları" +L.label_dna_radar = "Klasik DNA tarayıcı modunu etkinleştir" +L.label_dna_radar_cooldown = "DNA tarayıcı bekleme süresi" +L.label_radar_charge_time = "Kullanıldıktan sonra şarj süresi" +L.label_crowbar_shove_delay = "Levye itme işleminden sonra bekleme süresi" +L.label_idle = "Boşta modunu etkinleştir" +L.label_idle_limit = "Saniye cinsinden maksimum boşta kalma süresi" +L.label_namechange_kick = "İsim değiştirildiğinde atmayı etkinleştir" +L.label_namechange_bantime = "Attıktan sonra dakika cinsinden yasaklanan süre" +L.label_log_damage_for_console = "Konsolda hasar günlüğünü etkinleştir" +L.label_damagelog_save = "Hasar kaydını diske kaydet" +L.label_debug_preventwin = "Herhangi bir kazanma koşulunu önleyin [debug]" +L.label_bots_are_spectators = "Botlar her zaman izleyicidir" +L.label_tbutton_admin_show = "Hain düğmelerini yöneticilere göster" +L.label_ragdoll_carrying = "Ceset taşımayı etkinleştir" +L.label_prop_throwing = "Nesne fırlatmayı etkinleştir" +L.label_weapon_carrying = "Silah taşımayı etkinleştir" +L.label_weapon_carrying_range = "Silah taşıma menzili" +L.label_prop_carrying_force = "Nesne kaldırma gücü" +L.label_teleport_telefrags = "Işınlanırken engelleyen oyuncuları öldür" +L.label_allow_discomb_jump = "Bomba atıcı için disko sıçramasına izin ver" +L.label_spawn_wave_interval = "Saniye cinsinden canlanma aralığı" +L.label_voice_enable = "Sesli sohbeti etkinleştir" +L.label_voice_drain = "Sesli sohbet pil özelliğini etkinleştir" +L.label_voice_drain_normal = "Normal oyuncular için tik başına azalma" +L.label_voice_drain_admin = "Yöneticiler ve genel polislik rolleri için tik başına azalma" +L.label_voice_drain_recharge = "Sesli sohbet etmeme işareti başına şarj oranı" +L.label_locational_voice = "Canlı oyuncular için yakın sesli sohbeti etkinleştir" +L.label_armor_on_spawn = "(Yeniden) canlanmada oyuncu zırhı" +L.label_prep_respawn = "Hazırlık aşamasında anında yeniden canlanmayı etkinleştir" +L.label_preptime_seconds = "Saniye cinsinden hazırlık süresi" +L.label_firstpreptime_seconds = "Saniye cinsinden ilk hazırlık süresi" +L.label_roundtime_minutes = "Dakika cinsinden sabit raunt süresi" +L.label_haste = "Hız modunu etkinleştir" +L.label_haste_starting_minutes = "Dakika cinsinden hızlı mod başlangıç süresi" +L.label_haste_minutes_per_death = "Ölüm başına dakika cinsinden ek süre" +L.label_posttime_seconds = "Saniye cinsinden raunt sonu süresi" +L.label_round_limit = "Rauntların üst sınırı" +L.label_time_limit_minutes = "Oyun süresinin dakika cinsinden üst sınırı" +L.label_nade_throw_during_prep = "Hazırlık süresi boyunca bomba atmayı etkinleştir" +L.label_postround_dm = "Raunt bittikten sonra ölüm maçını etkinleştir" +L.label_session_limits_enabled = "Oturum sınırlarını etkinleştir" +L.label_spectator_chat = "İzleyicilerin herkesle sohbet etmesini sağla" +L.label_lastwords_chatprint = "Yazarken öldürülürse sohbete son kelimelerini yazdır" +L.label_identify_body_woconfirm = "'Onayla' düğmesine basmadan cesedi tanımla" +L.label_announce_body_found = "Bir ceset bulunduğunu duyurun" +L.label_confirm_killlist = "Onaylanmış cesedin ölüm listesini duyur" +L.label_dyingshot = "Gez ve arpacıkta ölürken ateş et [deneysel]" +L.label_armor_block_headshots = "Zırh engelleyici kafadan vuruşları etkinleştir" +L.label_armor_block_blastdmg = "Patlama hasarını engelleyen zırhı etkinleştir" +L.label_armor_dynamic = "Dinamik zırhı etkinleştir" +L.label_armor_value = "Zırh öğesi tarafından verilen zırh miktarı" +L.label_armor_damage_block_pct = "Zırhın aldığı hasar yüzdesi" +L.label_armor_damage_health_pct = "Oyuncunun aldığı hasar yüzdesi" +L.label_armor_enable_reinforced = "Güçlendirilmiş zırhı etkinleştir" +L.label_armor_threshold_for_reinforced = "Güçlendirilmiş zırh eşiği" +L.label_sherlock_mode = "Sherlock modunu etkinleştir" +L.label_highlight_admins = "Sunucu yöneticilerini vurgula" +L.label_highlight_dev = "TTT2 geliştiricisini vurgula" +L.label_highlight_vip = "TTT2 destekçisini vurgula" +L.label_highlight_addondev = "TTT2 eklenti geliştiricisini vurgula" +L.label_highlight_supporter = "Diğerlerini vurgula" +L.label_enable_hud_element = "{elem} arayüz öğesini etkinleştir" +L.label_spec_prop_control = "Nesne kontrolünü etkinleştir" +L.label_spec_prop_base = "Kontrol temel değeri" +L.label_spec_prop_maxpenalty = "Daha düşük kontrol bonusu limiti" +L.label_spec_prop_maxbonus = "Üst kontrole sahip olma bonus limiti" +L.label_spec_prop_force = "Kontrol itme kuvveti" +L.label_spec_prop_rechargetime = "Saniye cinsinden şarj süresi" +L.label_doors_force_pairs = "Yakın kapıları çift kapı olarak zorla" +L.label_doors_destructible = "Yok edilebilir kapıları etkinleştir" +L.label_doors_locked_indestructible = "Başlangıçta kilitli kapılar yok edilemez" +L.label_doors_health = "Kapı sağlığı" +L.label_doors_prop_health = "Yok edilen kapı sağlığı" +L.label_minimum_players = "Raunda başlamak için minimum oyuncu miktarı" +L.label_karma = "Karmayı Etkinleştir" +L.label_karma_strict = "Katı Karmayı etkinleştir" +L.label_karma_starting = "Başlangıç Karması" +L.label_karma_max = "Maksimum Karma" +L.label_karma_ratio = "Takım hasarı için ceza oranı" +L.label_karma_kill_penalty = "Takım öldürme için öldürme cezası" +L.label_karma_round_increment = "Karma restorasyonu" +L.label_karma_clean_bonus = "Temiz raunt bonusu" +L.label_karma_traitordmg_ratio = "Düşman hasarı için bonus oranı" +L.label_karma_traitorkill_bonus = "Düşman öldürme bonusu" +L.label_karma_clean_half = "Temiz raunt bonus azaltımı" +L.label_karma_persist = "Harita değişiklikleri üzerinde Karma devam etsin" +L.label_karma_low_autokick = "Karması düşük olan oyuncuları otomatik olarak tekmele" +L.label_karma_low_amount = "Düşük Karma eşiği" +L.label_karma_low_ban = "Düşük Karmaya sahip oyuncuları yasakla" +L.label_karma_low_ban_minutes = "Dakika cinsinden yasaklama süresi" +L.label_karma_debugspam = "Karma değişiklikleri hakkında konsol kurmak için hata ayıklama çıkışını etkinleştir" +L.label_max_melee_slots = "Maksimum yakın dövüş yuvası" +L.label_max_secondary_slots = "Maksimum ikincil yuva" +L.label_max_primary_slots = "Maksimum birincil yuva" +L.label_max_nade_slots = "Maksimum bomba yuvası" +L.label_max_carry_slots = "Maksimum taşıma yuvası" +L.label_max_unarmed_slots = "Maksimum silahsız yuva" +L.label_max_special_slots = "Maksimum özel yuva" +L.label_max_extra_slots = "Maksimum ekstra yuva" +L.label_weapon_autopickup = "Otomatik silah alımını etkinleştir" +L.label_sprint_enabled = "Koşmayı etkinleştir" +L.label_sprint_max = "Maksimum koşma dayanıklılığı" +L.label_sprint_stamina_consumption = "Dayanıklılık tüketim faktörü" +L.label_sprint_stamina_regeneration = "Dayanıklılık yenileme faktörü" +L.label_crowbar_unlocks = "Birincil saldırı etkileşim (yani kilit açma) olarak kullanılabilir" +L.label_crowbar_pushforce = "Levye itme kuvveti" + +-- 2022-07-02 +L.header_playersettings_falldmg = "Düşme Hasarı Ayarları" + +L.label_falldmg_enable = "Düşme hasarını etkinleştir" +L.label_falldmg_min_velocity = "Düşme hasarının oluşması için minimum hız eşiği" +L.label_falldmg_exponent = "Hıza bağlı olarak düşme hasarını artıran üs" + +L.help_falldmg_exponent = [[ +Bu değer, oyuncunun yere çarpma hızı ile katlanarak düşme hasarının ne kadar arttığını değiştirir. + +Bu değeri değiştirirken dikkatli olun. Çok yükseğe ayarlamak en küçük düşüşleri bile ölümcül hale getirebilirken, çok düşük ayarlamak oyuncuların aşırı yüksekliklerden düşmesine ve çok az hasar görmesine veya hiç hasar görmemesine izin verecektir.]] + +-- 2023-02-08 +L.testpopup_title = "Çok satırlı bir başlık içeren bir test açılır penceresi, ne GÜZEL!" +L.testpopup_subtitle = "Aa merhaba! Bu, bazı özel bilgiler içeren süslü bir açılır penceredir. Metin çok satırlı da olabilir, ne kadar süslü! Off, herhangi bir fikrim olsaydı çok daha fazla metin ekleyebilirdim..." + +L.hudeditor_chat_hint1 = "[TTT2][BİLGİ] Bir öğenin üzerine gelin, [SOL TIK] tuşuna basın ve basılı tutun. TAŞIMAK veya YENİDEN BOYUTLANDIRMAK için fareyi hareket ettirin." +L.hudeditor_chat_hint2 = "[TTT2][BİLGİ] Simetrik yeniden boyutlandırma için ALT tuşuna basın ve basılı tutun." +L.hudeditor_chat_hint3 = "[TTT2][BİLGİ] Eksen üzerinde hareket etmek ve en boy oranını korumak için SHIFT tuşunu basılı tutun." +L.hudeditor_chat_hint4 = "[TTT2][BİLGİ] Arayüz Düzenleyiciden çıkmak için [SAĞ TIK] - 'Kapat'a bas!" + +L.guide_nothing_title = "Henüz burada bir şey yok!" +L.guide_nothing_desc = "Bu devam eden bir çalışmadır. GitHub'daki projeye katkıda bulunarak bize yardımcı olun." + +L.sb_rank_tooltip_developer = "TTT2 Geliştirici" +L.sb_rank_tooltip_vip = "TTT2 Destekçisi" +L.sb_rank_tooltip_addondev = "TTT2 Eklenti Geliştirici" +L.sb_rank_tooltip_admin = "Sunucu Yöneticisi" +L.sb_rank_tooltip_streamer = "Yayıncı" +L.sb_rank_tooltip_heroes = "TTT2 Kahramanları" +L.sb_rank_tooltip_team = "Takım" + +L.tbut_adminarea = "YÖNETİCİ ALANI" + +-- 2023-08-10 +L.equipmenteditor_name_damage_scaling = "Hasar Boyutu" + +-- 2023-08-11 +L.equipmenteditor_name_allow_drop = "Bırakmaya İzin Ver" +L.equipmenteditor_desc_allow_drop = "Etkinleştirilirse, ekipman oyuncu tarafından serbestçe bırakılabilir." + +L.equipmenteditor_name_drop_on_death_type = "Ölürken Bırak" +L.equipmenteditor_desc_drop_on_death_type = "Oyuncunun ölümü üzerine ekipmanın düşürülüp düşürülmediğine ilişkin eylemi geçersiz kılmaya çalışır." + +L.drop_on_death_type_default = "Varsayılan (silah tanımlı)" +L.drop_on_death_type_force = "Ölürken Bırakmaya Zorla" +L.drop_on_death_type_deny = "Ölürken Bırakmayı Reddet" + +-- 2023-08-26 +L.equipmenteditor_name_kind = "Ekipman Yuvası" +L.equipmenteditor_desc_kind = "Ekipmanın olduğu envanter yuvası." + +L.slot_weapon_melee = "Yakın Dövüş Yuvası" +L.slot_weapon_pistol = "Tabanca Yuvası" +L.slot_weapon_heavy = "Ağır Silah Yuvası" +L.slot_weapon_nade = "Bomba Yuvası" +L.slot_weapon_carry = "Taşıma Yuvası" +L.slot_weapon_unarmed = "Silahsız Yuva" +L.slot_weapon_special = "Özel Yuva" +L.slot_weapon_extra = "Ekstra Yuva" +L.slot_weapon_class = "Sınıf Yuvası" + +-- 2023-10-04 +L.label_voice_duck_spectator = "İzleyici seslerini azalt" +L.label_voice_duck_spectator_amount = "İzleyici seslerini azaltma miktarı" +L.label_voice_scaling = "Ses Seviyesi Ölçekleme Modu" +L.label_voice_scaling_mode_linear = "Doğrusal" +L.label_voice_scaling_mode_power4 = "Güç 4" +L.label_voice_scaling_mode_log = "Logaritmik" + +-- 2023-10-07 +L.search_title = "Ceset Arama Sonuçları - {player}" +L.search_info = "Bilgi" +L.search_confirm = "Ölümü Onayla" +L.search_confirm_credits = "+{credits} Krediyi Onayla" +L.search_take_credits = "{credits} Kredi Al" +L.search_confirm_forbidden = "Yasak Onayla" +L.search_confirmed = "Ölüm Onaylandı" +L.search_call = "Dedektifi Ara" +L.search_called = "Ölüm Bildirildi" + +L.search_team_role_unknown = "???" + +L.search_words = "İçinizden bir ses bu kişinin son sözlerinden bazılarının '{lastwords}' olduğunu söylüyor." +L.search_armor = "Standart olmayan vücut zırhı giyiyorlardı." +L.search_disguiser = "Kimliklerini saklamaya yarayan bir cihaz taşıyorlardı." +L.search_radar = "Bir çeşit radar taşıyorlardı. Artık çalışmıyor." +L.search_c4 = "Cebinde bir not buldun. Tel {num} kesmenin bombayı güvenli bir şekilde etkisiz hale getireceğini belirtiyor." + +L.search_dmg_crush = "Kemiklerinin çoğu kırılmış. Ağır bir nesnenin çarpması onları öldürmüş gibi görünüyor." +L.search_dmg_bullet = "Vurularak öldürüldükleri belli." +L.search_dmg_fall = "Düşüp öldüler." +L.search_dmg_boom = "Yaraları ve yanmış kıyafetleri bir patlamanın sonlarına neden olduğunu gösteriyor." +L.search_dmg_club = "Ceset çürümüş ve hırpalanmış. Belli ki dövülerek öldürülmüşler." +L.search_dmg_drown = "Ceset boğulma belirtileri gösteriyor." +L.search_dmg_stab = "Kanamadan hızlı bir şekilde ölmeden önce bıçaklandılar ve kesildiler." +L.search_dmg_burn = "Buralar kızartılmış terörist gibi kokuyor..." +L.search_dmg_teleport = "DNA'ları takyon emisyonları tarafından karıştırılmış gibi görünüyor!" +L.search_dmg_car = "Bu terörist yolu geçtiğinde, dikkatsiz bir sürücü tarafından ezildi." +L.search_dmg_other = "Bu teröristin ölümünün belirli bir nedenini bulamazsın." + +L.search_floor_antlions = "Vücudun her yerinde hala antlionlar var. Zemin onlarla kaplı olmalı." +L.search_floor_bloodyflesh = "Bu vücuttaki kan eski ve iğrenç görünüyor. Ayakkabılarına yapışmış küçük kanlı et parçaları bile var." +L.search_floor_concrete = "Ayakkabılarını ve dizlerini gri toz kaplamış. Olay yerinin beton zemini varmış gibi görünüyor." +L.search_floor_dirt = "Toprak gibi kokuyor. Muhtemelen kurbanın ayakkabılarına yapışan topraktan kaynaklanıyor." +L.search_floor_eggshell = "İğrenç görünümlü beyaz lekeler kurbanın vücudunu kaplamış. Yumurta kabuğuna benziyor." +L.search_floor_flesh = "Kurbanın giysileri biraz nemli geliyor. Sanki ıslak bir yüzeye düşmüş gibi. Etli bir yüzey veya bir su kütlesinin kumlu zemini gibi." +L.search_floor_grate = "Kurbanın derisi bifteğe benziyor. Belli bir sırada kalın çizgiler kurbanın her yerinde görülüyor. Izgaranın üzerinde mi dinlendiler?" +L.search_floor_alienflesh = "Sence uzaylı eti mi? Kulağa biraz tuhaf geliyor ama dedektif yardımcısı kitabınız onu olası bir zemin yüzeyi olarak listeliyor." +L.search_floor_snow = "İlk bakışta giysileri sadece ıslak ve buz gibi geliyor ama kenarlardaki beyaz köpüğü gördüğünüzde anlarsınız. Bu kar!" +L.search_floor_plastic = "'Ah, bu acıtmış olmalı.' Cesetleri yanıklarla kaplıdır. Plastik bir yüzey üzerinde kayarken meydana gelenlere benziyorlar." +L.search_floor_metal = "En azından artık öldükleri için tetanoz olamazlar. Yaralarını pas kaplıyor. Muhtemelen metal bir yüzeyde öldüler." +L.search_floor_sand = "Küçük küçük pürüzlü taşlar soğuk cesetlerine yapışmış. Kumsaldaki kaba kum gibi. Ahh, her yere bulaşıyor!" +L.search_floor_foliage = "Doğa harikadır. Kurbanın kanlı yaraları, neredeyse gizlenecek kadar yeşilliklerle kaplı." +L.search_floor_computer = "Bip-bup. Cesetleri bilgisayar yüzeyiyle kaplı! Bu nasıl görünüyor diye sorabilirsiniz. Ee, yani!" +L.search_floor_slosh = "Islak ve hatta belki biraz sümüksü. Tüm vücudu bununla kaplı ve kıyafetleri sırılsıklam. Çok pis kokuyor!" +L.search_floor_tile = "Küçük parçalar derilerine yapışmış. Darbe aldığında paramparça olan yer fayanslarının parçaları gibi." +L.search_floor_grass = "Taze kesilmiş çimen gibi kokuyor. Koku neredeyse kan ve ölüm kokusunu bastırıyor." +L.search_floor_vent = "Vücudunu hissederken taze bir hava esintisi hissediyorsun. Havalandırmada ölüp yanlarında hava mı aldılar?" +L.search_floor_wood = "Bir parke zeminde oturup düşüncelere dalmaktan daha güzel ne olabilir? En azından kaderi ahşap bir zeminde ölü yatmak!" +L.search_floor_default = "Bu çok basit, çok normal görünüyor. Ne görüyorsanız o gibi. Yüzeyin türü hakkında hiçbir şey söyleyemiyorsunuz." +L.search_floor_glass = "Cesetleri birçok kanlı kesikle kaplı. Bazılarında cam kırıkları sıkışmış ve size oldukça tehditkar görünüyor." +L.search_floor_warpshield = "Warpshield'den yapılmış bir zemin mi? Evet, biz de senin kadar şaşkınız ama notlarımız bunu açıkça belirtiyor. Warpshield." + +L.search_water_1 = "Kurbanın ayakkabıları ıslak, ancak geri kalanı kuru görünüyor. Muhtemelen ayakları suyun içindeyken öldürüldüler." +L.search_water_2 = "Kurbanın ayakkabıları sırılsıklam olmuş. Öldürülmeden önce suda dolaştılar mı?" +L.search_water_3 = "Cesedin tamamı ıslak ve şişmiş. Muhtemelen tamamen su altındayken öldüler." + +L.search_weapon = "Görünüşe göre onları öldürmek için bir {weapon} kullanılmış." +L.search_head = "Ölümcül yara bir kafa vuruşuymuş. Çığlık atacak zaman yok." +L.search_time = "Siz aramayı yapmadan bir süre önce öldüler." +L.search_dna = "Bir DNA Tarayıcısı ile katilin DNA'sının bir örneğini alın. DNA örneği bir süre sonra çürüyecek." + +L.search_kills1 = "{player} oyuncusunun ölümünü doğrulayan bir leş listesi buldun." +L.search_kills2 = "Bu adlara sahip bir leş listesi buldunuz: {player}" +L.search_eyes = "Dedektiflik becerilerini kullanarak, {player} adlı oyuncuyu gördükleri son kişiyi belirledin. Katil ya da bir tesadüf" + +L.search_credits = "Kurbanın cebinde {credits} ekipman kredisi var. Bir alışveriş rolü onları alabilir ve iyi bir şekilde kullanabilir. Gözünüzü dört açın!" + +L.search_kill_distance_point_blank = "Çok yakın bir saldırıydı." +L.search_kill_distance_close = "Saldırı kısa mesafeden geldi." +L.search_kill_distance_far = "Kurban uzak mesafeden saldırıya uğradı." + +L.search_kill_from_front = "Kurban önden vuruldu." +L.search_kill_from_back = "Kurban arkadan vuruldu." +L.search_kill_from_side = "Kurban yandan vuruldu." + +L.search_hitgroup_head = "Mermi başında bulundu." +L.search_hitgroup_chest = "Mermi gövdesinde bulundu." +L.search_hitgroup_stomach = "Mermi karnında bulundu." +L.search_hitgroup_rightarm = "Mermi sağ kolunda bulundu." +L.search_hitgroup_leftarm = "Mermi sol kolunda bulundu." +L.search_hitgroup_rightleg = "Mermi sağ bacağında bulundu." +L.search_hitgroup_leftleg = "Mermi sol bacağında bulundu." +L.search_hitgroup_gear = "Mermi kalçada bulundu." + +L.search_policingrole_report_confirm = [[ +Bir kamu polisliği rolü ancak ölüm doğrulandıktan sonra bir cesede çağrılabilir.]] +L.search_policingrole_confirm_disabled_1 = [[ +Ceset ancak bir kamu polisliği rolü ile doğrulanabilir. Haberdar etmek için cesedi bildirin!]] +L.search_policingrole_confirm_disabled_2 = [[ +Ceset ancak bir kamu polisliği rolü ile doğrulanabilir. Haberdar etmek için cesedi bildirin! +Onlar onayladıktan sonra buradaki bilgileri görebilirsiniz.]] +L.search_spec = [[ +Bir izleyici olarak bir cesedin tüm bilgilerini görebilirsiniz, ancak kullanıcı arayüzü ile etkileşime giremezsiniz.]] + +L.search_title_words = "Kurbanın son sözleri" +L.search_title_c4 = "Parçalarına ayrılma talihsizliği" +L.search_title_dmg_crush = "Ezme hasarı ({amount} SP)" +L.search_title_dmg_bullet = "Mermi hasarı ({amount} SP)" +L.search_title_dmg_fall = "Düşme hasarı ({amount} SP)" +L.search_title_dmg_boom = "Patlama hasarı ({amount} SP)" +L.search_title_dmg_club = "Beyzbol sopası hasarı ({amount} SP)" +L.search_title_dmg_drown = "Boğulma hasarı ({amount} SP)" +L.search_title_dmg_stab = "Bıçaklama hasarı ({amount} SP)" +L.search_title_dmg_burn = "Yanma hasarı ({amount} SP)" +L.search_title_dmg_teleport = "Işınlanma hasarı ({amount} SP)" +L.search_title_dmg_car = "Araba kaza hasarı ({amount} SP)" +L.search_title_dmg_other = "Bilinmeyen hasar ({amount} SP)" +L.search_title_time = "Ölüm zamanı" +L.search_title_dna = "DNA örneği bozunması" +L.search_title_kills = "Kurbanın ölüm listesi" +L.search_title_eyes = "Katilin gölgesi" +L.search_title_floor = "Olay yerinin zemini" +L.search_title_credits = "{credits} Ekipman kredisi" +L.search_title_water = "Su seviyesi {level}" +L.search_title_policingrole_report_confirm = "Ölümü bildirmeyi onayla" +L.search_title_policingrole_confirm_disabled = "Ceset bildir" +L.search_title_spectator = "İzleyicisin" + +L.target_credits_on_confirm = "Harcanmamış kredi almak için onaylayın" +L.target_credits_on_search = "Harcanmamış kredileri almak için arama yapın" +L.corpse_hint_no_inspect_details = "Bu ceset hakkında yalnızca kamu polisliği rolleri bilgi bulabilir." +L.corpse_hint_inspect_limited_details = "Sadece kamu polisliği rolleri cesedi doğrulayabilir." +L.corpse_hint_spectator = "Ceset kullanıcı arayüzünü görüntülemek için [{usekey}] tuşuna basın" +L.corpse_hint_public_policing_searched = "Kamu polisliği rolündeki arama sonuçlarını görüntülemek için [{usekey}] tuşuna basın" + +L.label_inspect_confirm_mode = "Ceset arama modunu seç" +L.choice_inspect_confirm_mode_0 = "mod 0: standart TTT" +L.choice_inspect_confirm_mode_1 = "mod 1: sınırlı onay" +L.choice_inspect_confirm_mode_2 = "mod 2: sınırlı arama" +L.help_inspect_confirm_mode = [[ +Bu oyun modunda üç farklı ceset arama/onaylama modu vardır. Bu modun seçilmesinin, dedektif gibi kamu polisliği rollerinin önemi üzerinde büyük etkileri vardır. + +mode 0: Bu standart TTT'dir. Herkes cesetleri arayabilir ve onaylayabilir. Bir cesedi bildirmek veya ondan kredi almak için önce bedenin onaylanması gerekir. Bu, alışveriş rollerinin gizlice kredi çalmasını biraz zorlaştırır. Bununla birlikte, bir kamu polis oyuncusunun aranması için cesedi ihbar etmek isteyen masum oyuncuların da önce onaylaması gerekir. + +mode 1: Bu mod, onay seçeneğini bunlarla sınırlandırarak kamu polisliği rollerinin önemini artırır. Bu aynı zamanda, bir cesedi onaylamadan önce kredi almanın ve organları rapor etmenin artık mümkün olduğu anlamına gelir. Herkes hala cesetleri arayabilir ve bilgileri bulabilir, ancak bulunan bilgileri açıklayamazlar. + +mode 2: Bu mod, mod 1'den biraz daha katıdır. Bu modda arama yeteneği normal oyunculardan da kaldırılır. Bu, bir cesedi bir kamu polis oyuncusuna bildirmenin artık cesetlerden herhangi bir bilgi almanın tek yolu olduğu anlamına gelir.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +L.label_hud_pulsate_health_enable = "Sağlık %25'in altındayken sağlık göstergesini titret" +L.header_hud_elements_customize = "Arayüz Öğelerini Özelleştir" +L.help_hud_elements_special_settings = "Bunlar, kullanılan arayüz öğeleri için özel ayarlardır." + +-- 2023-10-25 +L.help_keyhelp = [[ +Tuş atama yardımcıları, oyuncuya her zaman güncel tuş atamalarını gösteren ve özellikle yeni oyuncular için yararlı olan bir kullanıcı arayüzü öğesinin bir parçasıdır. Üç farklı türde tuş atama vardır. + +Çekirdek: Bunlar, TTT2'de bulunan en önemli atamaları içerir. Onlar olmadan oyunu tam potansiyeliyle oynamak zordur. +Ekstra Core'a benzer, ancak her zaman onlara ihtiyacınız yoktur. Sohbet, ses veya el feneri gibi şeyler içerirler. Yeni oyuncuların bunu etkinleştirmesi yararlı olabilir. +Bazı ekipman öğelerinin kendi atamaları vardır, bunlar bu kategoride gösterilmiştir. + +Puan tablosu görünür olduğunda devre dışı kategoriler hala gösterilir.]] + +L.label_keyhelp_show_core = "Her zaman çekirdek atamaları göstermeyi etkinleştir" +L.label_keyhelp_show_extra = "Her zaman ekstra atamaları göstermeyi etkinleştir" +L.label_keyhelp_show_equipment = "Ekipman atamalarını her zaman göstermeyi etkinleştir" + +L.header_interface_keys = "Tuş yardımcısı ayarları" +L.header_interface_wepswitch = "Silah değiştirme kullanıcı arayüzü ayarları" + +L.label_keyhelper_help = "oyun modu menüsünü aç" +L.label_keyhelper_mutespec = "izleyici ses modunda dön" +L.label_keyhelper_shop = "ekipman mağazasını aç" +L.label_keyhelper_show_pointer = "serbest fare işaretçisi" +L.label_keyhelper_possess_focus_entity = "odaklanılmış varlığı kontrol et" +L.label_keyhelper_spec_focus_player = "odaklı oyuncuyu izle" +L.label_keyhelper_spec_previous_player = "önceki oyuncu" +L.label_keyhelper_spec_next_player = "sonraki oyuncu" +L.label_keyhelper_spec_player = "rastgele oyuncu izle" +L.label_keyhelper_possession_jump = "Nesne: Zıpla" +L.label_keyhelper_possession_left = "Nesne: Sol" +L.label_keyhelper_possession_right = "Nesne: Sağ" +L.label_keyhelper_possession_forward = "Nesne: İleri" +L.label_keyhelper_possession_backward = "Nesne: Geri" +L.label_keyhelper_free_roam = "nesneyi bırakın ve serbest dolaşın" +L.label_keyhelper_flashlight = "El fenerini aç/kapat" +L.label_keyhelper_quickchat = "hızlı sohbeti aç" +L.label_keyhelper_voice_global = "genel sesli sohbet" +L.label_keyhelper_voice_team = "Takım Sesli Sohbeti" +L.label_keyhelper_chat_global = "genel sohbet" +L.label_keyhelper_chat_team = "takım sohbeti" +L.label_keyhelper_show_all = "tümünü göster" +L.label_keyhelper_disguiser = "Kılık Değiştiriciyi aç/kapat" +L.label_keyhelper_save_exit = "kaydet ve çık" +L.label_keyhelper_spec_third_person = "Üçüncü kişi görünümünü aç/kapat" + +-- 2023-10-26 +L.item_armor_reinforced = "Güçlendirilmiş Zırh" +L.item_armor_sidebar = "Zırh sizi vücudunuza giren mermilere karşı korur. Ama sonsuza kadar değil." +L.item_disguiser_sidebar = "Kılık değiştirici, adınızı diğer oyunculara göstermeyerek kimliğinizi korur." +L.status_speed_name = "Hız Çarpanı" +L.status_speed_description_good = "Normalden daha hızlısın. Eşyalar, ekipmanlar veya etkiler bunu etkileyebilir." +L.status_speed_description_bad = "Normalden daha yavaşsınız. Eşyalar, ekipmanlar veya etkiler bunu etkileyebilir." + +L.status_on = "açık" +L.status_off = "kapalı" + +L.crowbar_help_primary = "Saldır" +L.crowbar_help_secondary = "Oyuncuları it" + +-- 2023-10-27 +L.help_HUD_enable_description = [[ +Tuş yardımcısı veya kenar çubuğu gibi bazı arayüz öğeleri, puan tablosu açıkken ayrıntılı bilgi gösterir. Bu, dağınıklığı azaltmak için devre dışı bırakılabilir.]] +L.label_HUD_enable_description = "Puan tablosu açıkken açıklamaları etkinleştir" +L.label_HUD_enable_box_blur = "Arayüz kutusu arka plan bulanıklığını etkinleştir" + +-- 2023-10-28 +L.submenu_gameplay_voiceandvolume_title = "Ses Düzeyi" +L.header_soundeffect_settings = "Ses Efektleri" +L.header_voiceandvolume_settings = "Ses Ayarları" + +-- 2023-11-06 +L.drop_reserve_prevented = "Bir şey yedek cephanenizi düşürmenizi engelliyor." +L.drop_no_reserve = "Rezervinizde cephane kutusu olarak düşecek yeterli cephane yok." +L.drop_no_room_ammo = "Burada silahını bırakacak yerin yok!" + +-- 2023-11-14 +L.hat_deerstalker_name = "Dedektifin Şapkası" + +-- 2023-11-16 +L.help_prop_spec_dash = [[ +Normal hareketten daha yüksek kuvvette olabilirler. Daha yüksek kuvvet aynı zamanda daha yüksek temel değer tüketimi anlamına gelir. + +Bu değişken itme kuvvetinin bir çarpanıdır.]] +L.label_spec_prop_dash = "Atılma kuvveti çarpanı" +L.label_keyhelper_possession_dash = "nesne: bakılan yönde atıl" +L.label_keyhelper_weapon_drop = "mümkünse seçilen silahı bırak" +L.label_keyhelper_ammo_drop = "seçilen silahın şarjöründen cephane çıkar" + +-- 2023-12-07 +L.c4_help_primary = "C4'ü yerleştir" +L.c4_help_secondary = "Zemine koy" + +-- 2023-12-11 +L.magneto_help_primary = "Varlığı it" +L.magneto_help_secondary = "Varlığı al" +L.knife_help_primary = "Bıçakla" +L.knife_help_secondary = "Bıçağı fırlat" +L.polter_help_primary = "Ateşle" +L.polter_help_secondary = "Uzun menzil atışı şarjla" + +-- 2023-12-12 +L.newton_help_primary = "İtme vuruşu" +L.newton_help_secondary = "Şarjlı itme vuruşu" + +-- 2023-12-13 +L.vis_no_pickup = "Sadece kamu polisliği rolleri görüntüleyiciyi alabilir" +L.newton_force = "GÜÇ" +L.defuser_help_primary = "Hedef C4'ü çöz" +L.radio_help_primary = "Radyoyu yerleştir" +L.radio_help_secondary = "Zemine yapıştır" +L.hstation_help_primary = "Sağlık İstasyonunu yerleştir" +L.flaregun_help_primary = "Varlığı yak" + +-- 2023-12-14 +L.marker_vision_owner = "Sahip: {owner}" +L.marker_vision_distance = "Uzaklık: {distance}m" +L.marker_vision_distance_collapsed = "{distance}m" + +L.c4_marker_vision_time = "Patlamaya: {time}" +L.c4_marker_vision_collapsed = "{time} / {distance}m" + +L.c4_marker_vision_safe_zone = "Bombadan güvenli bölge" +L.c4_marker_vision_damage_zone = "Bomba hasar bölgesi" +L.c4_marker_vision_kill_zone = "Bomba öldürme bölgesi" + +L.beacon_marker_vision_player = "İzlenen Oyuncu" +L.beacon_marker_vision_player_tracked = "Bu oyuncu bir Fener tarafından izleniyor" + +-- 2023-12-18 +L.beacon_help_pri = "Feneri yere at" +L.beacon_help_sec = "Feneri yere yapıştır" +L.beacon_name = "Fener" +L.beacon_desc = [[ +Oyuncu konumlarını bu fenerin etrafındaki kürede herkese yayınlar. + +Haritada görülmesi zor olan konumları takip etmek için kullanın.]] + +L.msg_beacon_destroyed = "Fenerlerinden biri yok edildi!" +L.msg_beacon_death = "Fenerlerinden birinin yakınında bir oyuncu öldü." + +L.beacon_pickup_disabled = "Sadece fenerin sahibi alabilir" +L.beacon_short_desc = "Fenerler, etraflarına yerel duvar hilesi eklemek için polislik rolleri tarafından kullanılır" + +-- 2023-12-18 +L.entity_pickup_owner_only = "Bunu sadece sahibi alabilir" + +-- 2023-12-18 +L.body_confirm_one = "{finder}, {victim} adlı kişinin ölümünü doğruladı." +L.body_confirm_more = "{finder}, {count} ölümü doğruladı: {victims}." + +-- 2023-12-19 +L.builtin_marker = "Bütünleşik." +L.equipmenteditor_desc_builtin = "Bu ekipman TTT2 ile gelen bütünleşik bir öğedir!" +L.help_roles_builtin = "Bu rol TTT2 ile gelen bütünleşik bir öğedir!" +L.header_equipment_info = "Ekipman bilgisi" + + +-- 2023-12-24 +L.submenu_gameplay_accessibility_title = "Erişilebilirlik" + +L.header_accessibility_settings = "Erişilebilirlik Ayarları" + +L.label_enable_dynamic_fov = "Dinamik FOV değişikliğini etkinleştir" +L.label_enable_bobbing = "Sallanmayı etkinleştir" +L.label_enable_bobbing_strafe = "Sağ sol yaparken sallanmayı etkinleştir" + +L.help_enable_dynamic_fov = "Oyuncunun hızına bağlı olarak dinamik FOV uygulanır. Örneğin bir oyuncu koşarken hızı görselleştirmek için FOV artırılır." +L.help_enable_bobbing_strafe = "Ekran sallanması, yürürken, yüzerken veya düşerken hafif kamera sarsıntısıdır." +-- 2023-12-20 +L.equipmenteditor_desc_damage_scaling = [[Bir silahın temel hasar değerini bu faktörle çarpar. +Bir pompalı için bu, her bir saçmayı etkileyecektir. +Bir tüfek için bu sadece kurşunu etkiler. +Afacan Peri için bu, her bir "güm" ü ve son patlamayı etkileyecektir. +0.5 = Hasar miktarının yarısını ver. +2 = Hasar miktarının iki katı kadar hasar ver. +Not: Bazı silahlar bu değiştiricinin etkisiz kalmasına neden olan bu değeri kullanmayabilir.]] + +-- 2023-12-24 +L.binoc_help_reload = "Hedefi kaldırın." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +L.label_crosshair_thickness_outline_enable = "Nişangah dış çizgisini etkinleştir" +L.label_crosshair_outline_high_contrast = "Dış çizgi yüksek kontrast rengini etkinleştir" +L.label_crosshair_mode = "Nişangah modu" +L.label_crosshair_static_length = "Statik nişangah çizgi uzunluğunu etkinleştir" + +L.choice_crosshair_mode_0 = "Çizgiler ve nokta" +L.choice_crosshair_mode_1 = "Sadece çizgiler" +L.choice_crosshair_mode_2 = "Sadece nokta" + +L.help_crosshair_scale_enable = [[ +Dinamik nişangah, silahın konisine bağlı olarak nişangahın ölçeklendirilmesini sağlar. Koni, zıplama ve koşma gibi dış faktörlerle çarpılan silahın taban isabetinden etkilenir. + +Çizgi uzunluğu sabit tutulursa yalnızca konili boşluk ölçeklenir.]] + +L.header_weapon_settings = "Silah Ayarları" + + +L.marker_vision_visible_for_0 = "Sadece size görünür" +--L.marker_vision_visible_for_1 = "Visible for your role" +L.marker_vision_visible_for_2 = "Takıma görünür" +L.marker_vision_visible_for_3 = "Herkese görünür" + +-- 2024-01-27 +L.decoy_help_primary = "Tuzağı yerleştirin" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/uk.lua b/lua/terrortown/lang/uk.lua new file mode 100644 index 000000000..0ef56ec48 --- /dev/null +++ b/lua/terrortown/lang/uk.lua @@ -0,0 +1,2193 @@ +-- Ukrainian language strings + +local L = LANG.CreateLanguage("uk") + +-- Compatibility language name that might be removed soon. +-- the alias name is based on the original TTT language name: +-- https://github.com/Facepunch/garrysmod/blob/master/garrysmod/gamemodes/terrortown/gamemode/lang/ukrainian.lua +L.__alias = "Українська" + +L.lang_name = "Українська (Ukrainian)" + +-- General text used in various places +L.traitor = "Зрадник" +L.detective = "Детектив" +L.innocent = "Невинний" +L.last_words = "Останні слова" + +L.terrorists = "Терористи" +L.spectators = "Спостерігачі" + +--L.nones = "No Team" +--L.innocents = "Team Innocent" +--L.traitors = "Team Traitor" + +-- Round status messages +L.round_minplayers = "Недостатньо гравців, щоб розпочати новий раунд..." +L.round_voting = "Триває голосування, новий раунд відкладено на {num} секунд..." +L.round_begintime = "Новий раунд розпочнеться через {num} секунд. Приготуйтесь." +L.round_selected = "Зрадників було обрано." +L.round_started = "Раунд розпочато!" +L.round_restart = "Раунд перезавантажено адміністратором." + +L.round_traitors_one = "Зрадник, ти один." +L.round_traitors_more = "Зрадник, ось твої союзники: {names}" + +L.win_time = "Час вичерпано. Зрадники програли." +--L.win_traitors = "The Traitors have won!" +--L.win_innocents = "The Innos have won!" +--L.win_nones = "No-one won!" +L.win_showreport = "Подивімось на звіт раунду протягом {num} секунд." + +--L.limit_round = "Round limit reached. The next map will load soon." +--L.limit_time = "Time limit reached. The next map will load soon." +--L.limit_left = "{num} round(s) or {time} minutes remaining before the map changes." + +-- Credit awards +--L.credit_all = "Your team have been awarded {num} equipment credit(s) for your performance." +L.credit_kill = "Ви отримали {num} кредитів за вбивство {role}." + +-- Karma +L.karma_dmg_full = "Ваша карма складає {amount}, отже ви можете заподіяти максимальну шкоду у цьому раунді!" +L.karma_dmg_other = "Ваша карма складає {amount}. У підсумку вся завдана вами шкода буде зменшена на {num}%" + +-- Body identification messages +L.body_found = "{finder} знаходить тіло {victim}. {role}" +--L.body_found_team = "{finder} found the body of {victim}. {role} ({team})" + +-- The {role} in body_found will be replaced by one of the following: +--L.body_found_traitor = "They were a Traitor!" +--L.body_found_det = "They were a Detective." +--L.body_found_inno = "They were Innocent." + +L.body_call = "{player} викликає Детектива до тіла {victim}!" +L.body_call_error = "Ви повинні підтвердити смерть цього гравця перед тим, як викликати Детектива!" + +L.body_burning = "Дідько! Це тіло все у вогні!" +L.body_credits = "Ви знайшли {num} кредитів на цьому тілі!" + +-- Menus and windows +L.close = "Закрити" +L.cancel = "Скасувати" + +-- For navigation buttons +L.next = "Далі" +L.prev = "Назад" + +-- Equipment buying menu +L.equip_title = "Обладнання" +L.equip_tabtitle = "Замовлення обладнання" + +L.equip_status = "Стан замовлення" +L.equip_cost = "У вас залишилося {num} кредитів." +L.equip_help_cost = "Кожне обладнання, яке ви купуєте, коштує 1 кредит." + +L.equip_help_carry = "Ви можете купувати лише те, для чого у вас достатньо місця." +L.equip_carry = "Ви можете отримати це обладнання." +L.equip_carry_own = "Ви вже отримали це обладнання." +L.equip_carry_slot = "Ви вже маєте зброю у слоті {slot}." +--L.equip_carry_minplayers = "There are not enough players on the server to enable this weapon." + +L.equip_help_stock = "Деяке обладнання ви можете купити лише один раз за раунд." +L.equip_stock_deny = "Цього обладнання більше немає." +L.equip_stock_ok = "Це обладнання досі присутнє." + +L.equip_custom = "Користувацьке обладнання, додане цим сервером." + +L.equip_spec_name = "Ім’я" +L.equip_spec_type = "Тип" +L.equip_spec_desc = "Опис" + +L.equip_confirm = "Придбати" + +-- Disguiser tab in equipment menu +L.disg_name = "Маскування" +L.disg_menutitle = "Керування Маскуванням" +L.disg_not_owned = "У вас немає Маскування!" +L.disg_enable = "Застосувати Маскування" + +L.disg_help1 = "Коли ваше маскування активне, ваше ім’я, здоров’я та карма не буде показуватися коли хтось дивиться на вас. Ви також будете сховані від радара Детектива." +L.disg_help2 = "Натисніть Numpad Enter, щоб увімкнути маскування без використання меню. Ви також можете призначити іншу клавішу для 'ttt_toggle_disguise' використовуючи консоль розробника." + +-- Radar tab in equipment menu +L.radar_name = "Радар" +L.radar_menutitle = "Керування Радаром" +L.radar_not_owned = "У вас немає Радара!" +L.radar_scan = "Виконати сканування" +L.radar_auto = "Автоматичне сканування" +L.radar_help = "Результати сканування показуватимуться {num} секунд, після чого Радар буде перезаряджено і його можна буде використати знову." +L.radar_charging = "Ваш Радар досі перезаряджається!" + +-- Transfer tab in equipment menu +L.xfer_name = "Переказ" +L.xfer_menutitle = "Переказ кредитів" +L.xfer_send = "Переказати кредит" + +L.xfer_no_recip = "Одержувач недійсний, переказ кредиту скасовано." +L.xfer_no_credits = "У вас немає кредитів для переказу!" +L.xfer_success = "Переказ кредитів гравцю {player} виконано." +L.xfer_received = "{player} надав вам кредити в розмірі {num} штук." + +-- Radio tab in equipment menu +L.radio_name = "Радіо" +L.radio_help = "Натисніть кнопку, щоб радіо відтворювало обраний звук." +L.radio_notplaced = "Ви повинні розташувати Радіо, щоб відтворювати на ньому звук." + +-- Radio soundboard buttons +L.radio_button_scream = "Крик" +L.radio_button_expl = "Вибух" +L.radio_button_pistol = "Постріли пістолета" +L.radio_button_m16 = "Постріли M16" +L.radio_button_deagle = "Постріли Deagle" +L.radio_button_mac10 = "Постріли MAC10" +L.radio_button_shotgun = "Вогонь з дробовика" +L.radio_button_rifle = "Постріли гвинтівки" +L.radio_button_huge = "Вогонь з H.U.G.E" +L.radio_button_c4 = "Пікання C4" +L.radio_button_burn = "Горіння" +L.radio_button_steps = "Кроки" + +-- Intro screen shown after joining +L.intro_help = "Якщо ви новачок, то натисніть F1 задля інструкції!" + +-- Radiocommands/quickchat +L.quick_title = "Швидкі повідомлення" + +L.quick_yes = "Так." +L.quick_no = "Ні." +L.quick_help = "Допоможіть!" +L.quick_imwith = "Я поряд з {player}." +L.quick_see = "Я бачу {player}." +L.quick_suspect = "{player} виглядає підозріло." +L.quick_traitor = "{player} є зрадником!" +L.quick_inno = "{player} є невинним." +L.quick_check = "Є хто живий?" + +-- {player} in the quickchat text normally becomes a player nickname, but can +-- also be one of the below. Keep these lowercase. +L.quick_nobody = "ніхто" +L.quick_disg = "хтось замаскований" +L.quick_corpse = "невпізнане тіло" +L.quick_corpse_id = "Тіло {player}" + +-- Scoreboard +L.sb_playing = "Ви граєте на..." +L.sb_mapchange = "Мапа зміниться через {num} раундів або через {time}" +--L.sb_mapchange_disabled = "Session limits are disabled." + +L.sb_mia = "Зниклі безвісти" +L.sb_confirmed = "Смерть підтверджено" + +L.sb_ping = "Пінг" +L.sb_deaths = "Смерті" +L.sb_score = "Рахунок" +L.sb_karma = "Карма" + +L.sb_info_help = "Огляньте тіло цього гравця, і ви зможете переглянути результати тут." + +L.sb_tag_friend = "ДРУГ" +L.sb_tag_susp = "ПІДОЗРЮВАНИЙ" +L.sb_tag_avoid = "УНИКАТИ" +L.sb_tag_kill = "ВБИТИ" +L.sb_tag_miss = "ЗНИКЛИЙ" + +-- Equipment actions, like buying and dropping +L.buy_no_stock = "Цієї зброї більше немає: ви вже купували її цього раунду." +L.buy_pending = "Ви зараз маєте замовлення, очікуйте отримання." +L.buy_received = "Ви отримали своє особливе обладнання." + +L.drop_no_room = "Немає місця, щоб викинути вашу зброю!" + +L.disg_turned_on = "Маскування активовано!" +L.disg_turned_off = "Маскування деактивовано." + +-- Equipment item descriptions +L.item_passive = "Предмет з пасивним ефектом" +L.item_active = "Предмет активного користування" +L.item_weapon = "Зброя" + +L.item_armor = "Бронежилет" +L.item_armor_desc = [[ +Зменшує шкоду від куль на 30%, коли ви його маєте. + +Стандартне обладнання для Детективів.]] + +L.item_radar = "Радар" +L.item_radar_desc = [[ +Дозволяє сканувати ознаки життя. + +Починає автоматичне сканування, щойно ви придбаєте його. Додатково налаштуйте його у вкладці Радара.]] + +L.item_disg = "Маскування" +L.item_disg_desc = [[ +При вмиканні приховує вашу інформацію. Також уникає бути людиною, яку востаннє бачила жертва. + +Перемикайте маскування у відповідній вкладці цього меню або натисніть клавішу Numpad Enter.]] + +-- C4 +L.c4_disarm_warn = "Вибухівку C4, яку ви заклали, знешкодили." +L.c4_armed = "Ви успішно заклали бомбу." +L.c4_disarmed = "Ви успішно знешкодили бомбу." +L.c4_no_room = "Ви не можете переносити цю C4." + +L.c4_desc = "Потужна вибухівка з годинниковим механізмом." + +L.c4_arm = "Закласти C4" +L.c4_arm_timer = "Таймер" +L.c4_arm_seconds = "Секунд до вибуху:" +L.c4_arm_attempts = "У разі спроби знешкодити, перерізання {num} з 6 дротів приведе до миттєвого вибуху." + +L.c4_remove_title = "Видалення" +L.c4_remove_pickup = "Підняти C4" +L.c4_remove_destroy1 = "Знищити C4" +L.c4_remove_destroy2 = "Підтвердити" + +L.c4_disarm = "Знешкодити C4" +L.c4_disarm_cut = "Натисніть, щоб перерізати {num} дріт" + +L.c4_disarm_t = "Переріжте дріт, щоб знешкодити вибухівку. Ви граєте за зрадника, отже кожен дріт буде безпечним." +L.c4_disarm_owned = "Переріжте дріт, щоб знешкодити вибухівку. Це ваша вибухівка, отже кожен перерізаний дріт знешкодить її." +L.c4_disarm_other = "Переріжте дріт, щоб знешкодити вибухівку. Вона вибухне, якщо ви зробите помилку!" + +L.c4_status_armed = "ЗАКЛАДЕНА" +L.c4_status_disarmed = "ЗНЕШКОД-\nЖЕНА" + +-- Visualizer +L.vis_name = "Візуалізатор" + +L.vis_desc = [[ +Пристрій, який вміє візуалізувати злочини. + +Проаналізуйте тіло, щоб дізнатися як жертва була вбита, але тільки у випадку, якщо особа загинула від пострілу.]] + +-- Decoy +L.decoy_name = "Приманка" +L.decoy_broken = "Вашу Приманку знищено!" + +--L.decoy_short_desc = "This decoy shows a fake radar sign visible for other teams" +--L.decoy_pickup_wrong_team = "You can't pick it up as it belongs to a different team" + +L.decoy_desc = [[ +Показує хибну мітку на радарі детективів, ламає ДНК сканери таким чином, щоб вони вказували на місце приманки, а не на ваше справжнє місце перебування.]] + +-- Defuser +L.defuser_name = "Знешкоджувач" + +L.defuser_desc = [[ +Миттєво знешкоджує вибухівку C4. + +Користуйтесь скільки забажаєте. Вибухівку C4 буде легше помітити якщо ви маєте це при собі.]] + +-- Flare gun +L.flare_name = "Сигнальний пістолет" + +L.flare_desc = [[ +Можна використовувати для спалювання тіл, щоб їх ніколи більше не знайшли. Обмежена кількість набоїв. + +Спалення тіл створює характерний звук.]] + +-- Health station +L.hstation_name = "Оздоровча Станція" + +L.hstation_broken = "Вашу Оздоровчу Станцію знищено!" + +L.hstation_desc = [[ +Розташувавши це ви зможете лікувати інших людей. + +Повільна регенерація. Кожен може користуватися нею, та вона може бути зламана. Використовуйте її, щоб зібрати ДНК користувачів.]] + +-- Knife +L.knife_name = "Ніж" +L.knife_thrown = "Кинутий ніж" + +L.knife_desc = [[ +Миттєво та тихо вбиває поранені цілі, але має лише одноразове використання. + +Може бути кинутим за допомогою альтернативної атаки.]] + +-- Poltergeist +L.polter_desc = [[ +Встановлює двигуни на об’єкти та насильно штовхає їх навколо. + +Енергетичні вибухи завдають шкоди людям, які знаходяться поряд.]] + +-- Radio +L.radio_broken = "Ваше Радіо знищено!" + +L.radio_desc = [[ +Відтворює звуки, щоб відвернути увагу гравців. + +Поставте Радіо десь неподалік, а потім відтворюйте на ньому звуки за допомогою відповідної вкладки.]] + +-- Silenced pistol +L.sipistol_name = "Пістолет із глушником" + +L.sipistol_desc = [[ +Малошумний пістолет, використовує звичайні пістолетні набої. + +Жертви не кричатимуть, коли їх вбиватимуть.]] + +-- Newton launcher +L.newton_name = "Гармата Ньютона" + +L.newton_desc = [[ +Відштовхує людей з безпечної відстані. + +Нескінченні набої, але повільне перезавантаження.]] + +-- Binoculars +L.binoc_name = "Окуляр" + +L.binoc_desc = [[ +Дозволяє бачити та розпізнавати тіла на великій відстані. + +Необмежене використання, але ідентифікація займає декілька секунд.]] + +-- UMP +L.ump_desc = [[ +Експериментальний SMG, що дезорієнтує цілі. + +Використовує стандартні набої SMG.]] + +-- DNA scanner +L.dna_name = "Сканер ДНК" +L.dna_notfound = "Зразок ДНК на цілі не знайдено." +L.dna_limit = "Досягнуто ліміт пам’яті. Видаліть старі зразки, щоб додати нові." +L.dna_decayed = "Зразок ДНК вбивці розпався." +L.dna_killer = "З тіла взято зразок ДНК вбивці!" +--L.dna_duplicate = "Match! You already have this DNA sample in your scanner." +L.dna_no_killer = "ДНК не вдалося відновити (вбивця вийшов з гри?)." +L.dna_armed = "Ця бомба працює! Спочатку знешкодьте його!" +--L.dna_object = "Collected a sample of the last owner from the object." +L.dna_gone = "ДНК поряд не виявлено." + +L.dna_desc = [[ +Збирайте зразки ДНК і використовуйте їх, щоб знайти власника ДНК. + +Використовуйте на свіжих тілах, щоб отримати ДНК убивці й знайти його.]] + +-- Magneto stick +L.magnet_name = "Магнітна палиця" +L.magnet_help = "Використовуйте {primaryfire}, щоб прикріпити тіло до поверхні." + +-- Grenades and misc +L.grenade_smoke = "Димова шашка" +L.grenade_fire = "Запалювальна граната" + +L.unarmed_name = "Сховати все" +L.crowbar_name = "Лом" +L.pistol_name = "Пістолет" +L.rifle_name = "Гвинтівка" +L.shotgun_name = "Дробовик" + +-- Teleporter +L.tele_name = "Телепорт" +L.tele_failed = "Телепортування невдале." +L.tele_marked = "Локація телепортації встановлена." + +L.tele_no_ground = "Телепортація неможлива поки під ногами немає поверхні!" +L.tele_no_crouch = "Телепортація неможлива навсидячки!" +L.tele_no_mark = "Локація телепортації не встановлена. Позначте пункт призначення перед телепортацією." + +L.tele_no_mark_ground = "Неможливо позначити локацію телепортації поки під ногами немає поверхні!" +L.tele_no_mark_crouch = "Неможливо позначити локацію телепортації навсидячки!" + +--L.tele_help_pri = "Teleports to marked location" +--L.tele_help_sec = "Marks current location" + +L.tele_desc = [[ +Телепортація до попередньо позначеного місця. + +Телепортація відтворює шум. Кількість використань обмежена.]] + +-- Ammo names, shown when picked up +L.ammo_pistol = "Набої 9 мм" + +L.ammo_smg1 = "SMG набої" +L.ammo_buckshot = "Набої до дробовика" +L.ammo_357 = "Набої до гвинтівки" +L.ammo_alyxgun = "Набої до Deagle" +L.ammo_ar2altfire = "Набої сигнального пістолета" +L.ammo_gravity = "Набої Полтергейста" + +-- Round status +L.round_wait = "Очікування" +L.round_prep = "Підготовка" +L.round_active = "В процесі" +L.round_post = "Кінець" + +-- Health, ammo and time area +L.overtime = "ОВЕРТАЙМ" +L.hastemode = "РЕЖИМ ПОСПІХУ" + +-- TargetID health status +L.hp_healthy = "Здоровий" +L.hp_hurt = "Ушкоджений" +L.hp_wounded = "Поранений" +L.hp_badwnd = "Важко поранений" +L.hp_death = "При смерті" + +-- TargetID Karma status +L.karma_max = "Поважний" +L.karma_high = "Неотесаний" +L.karma_med = "Дурний" +L.karma_low = "Небезпечний" +L.karma_min = "Безвідповідальний" + +-- TargetID misc +L.corpse = "Тіло" +L.corpse_hint = "Натисніть {usekey}, щоб оглянути. {walkkey} + {usekey}, щоб оглянути непомітно." + +L.target_disg = " (ЗАМАСКОВАНИЙ)" +L.target_unid = "Невпізнане тіло" +--L.target_unknown = "A Terrorist" + +-- HUD buttons with hand icons that only some roles can see and use +L.tbut_single = "Одноразове використання" +L.tbut_reuse = "Багаторазове використання" +L.tbut_retime = "Можна використати ще раз через {num} секунд" +L.tbut_help = "Натисніть {usekey}, щоб активувати" + +-- Spectator muting of living/dead +L.mute_living = "Живі гравці заглушені" +L.mute_specs = "Спостерігачі заглушені" +L.mute_all = "Всі заглушені" +L.mute_off = "Ніхто не заглушений" + +-- Spectators and prop possession +L.punch_title = "ШТОВХ-О-МЕТР" +L.punch_bonus = "Ваш поганий рахунок знизив ваш ліміт штовх-о-метра на {num}" +L.punch_malus = "Ваш хороший рахунок збільшив ваш ліміт штовх-о-метра на {num}!" + +-- Info popups shown when the round starts +L.info_popup_innocent = [[ +Ви невинний терорист! Але навколо є Зрадники... +Кому можна довіряти, а хто нашпигує вас кулями? + +Бережіть себе та працюйте разом із товаришами, щоб залишитися живим!]] + +L.info_popup_detective = [[ +Ви детектив! Штаб Терористів надав вам спеціальні ресурси для пошуку зрадників. +Використовуйте їх, щоб допомогти невинним, але будьте обережні: +зрадники будуть шукати вас, щоб знищити першими! + +Натисніть {menukey}, щоб отримати своє обладнання!]] + +L.info_popup_traitor_alone = [[ +Ви ЗРАДНИК! У цьому раунді у вас немає побратимів-зрадників. + +Вбийте всіх інших, щоб перемогти! + +Натисніть {menukey}, щоб отримати спеціальне обладнання!]] + +L.info_popup_traitor = [[ +Ви ЗРАДНИК! Працюйте з побратимами-зрадниками, щоб вбити всіх інших. +Але будьте обережні, в інакшому випадку вас можуть розкрити... + +Це ваші товариші: +{traitorlist} + +Натисніть {menukey}, щоб отримати спеціальне обладнання!]] + +-- Various other text +L.name_kick = "Гравця автоматично видалено з гри через зміну імені під час раунду." + +L.idle_popup = [[ +Ви були неактивні протягом {num} секунд і в результаті були переведені в режим Спостерігача. Поки ви перебуваєте в цьому режимі, ви не з’явитися у грі з початком нового раунду. + +Ви можете будь-коли перемкнути режим Спостерігача, натиснувши {helpkey} і знявши прапорець у вкладці Налаштувань. Ви також можете зняти його прямо зараз.]] + +L.idle_popup_close = "Нічого не робити" +L.idle_popup_off = "Вимкнути режим спостерігача зараз же" + +L.idle_warning = "Попередження: ви виглядаєте неактивним/відсутнім, вас змусять спостерігати поки ви не проявите активність!" + +L.spec_mode_warning = "Ви перебуваєте в режимі Спостерігача і не з’явиться з початком раунду. Щоб вимкнути цей режим, натисніть F1, перейдіть до налаштувань і зніміть прапорець з «Режим Спостерігача»." + +-- Tips panel +L.tips_panel_title = "Поради" +L.tips_panel_tip = "Порада:" + +-- Tip texts +L.tip1 = "Зрадники можуть тихо оглянути тіло без підтвердження смерті. Для цього утримуйте {walkkey} і натисніть {usekey} дивлячись на тіло." + +L.tip2 = "Закладення вибухівки C4 з довшим таймером збільшить кількість дротів, які змусять її миттєво вибухнути, коли невинний спробує її знешкодити. Крім того, звуковий сигнал буде тихіше і рідше." + +L.tip3 = "Детективи можуть оглянути тіло, щоб знайти «відбиток вбивці в очах жертви». Цим відбитком буде остання людина, яку бачив мертвий. Це не обов’язково повинен бути вбивця, якщо жертву вбили зі спини." + +L.tip4 = "Ніхто окрім зрадників не дізнається, що ви померли, доки не знайдуть ваше тіло та не ідентифікують його." + +L.tip5 = "Коли Зрадник вбиває Детектива, він миттєво отримує винагороду у вигляді кредитів." + +L.tip6 = "Коли Зрадник помирає, усі Детективи отримують кредити обладнання." + +L.tip7 = "Коли Зрадники досягнуть значного прогресу у вбивстві невинних, вони отримають кредити обладнання як винагороду." + +L.tip8 = "Зрадники та Детективи можуть збирати невитрачені кредити обладнання з мертвих тіл інших Зрадників та Детективів." + +L.tip9 = "Полтергейст може перетворити будь-який фізичний об’єкт на смертоносний снаряд. Кожен удар супроводжується вибухом енергії, що завдає шкоди будь-кому поблизу." + +L.tip10 = "Як Зрадник або Детектив, слідкуйте за червоними повідомленнями у верхньому правому куті. Вони можуть бути важливими для вас." + +L.tip11 = "Майте на увазі, що як зрадник або як детектив ви отримаєте додаткові кредити обладнання, якщо ви та ваші товариші будуть мати успіх у виконанні своїх завдань. Не забудьте їх витратити!" + +L.tip12 = "Сканер ДНК детективів можна використовувати, щоб зібрати зразки ДНК зі зброї та предметів, проаналізувавши їх, знайти місцеперебування гравця, який їх використовував. Корисно, коли ви можете отримати зразок з тіла або знешкодженої вибухівки C4!" + +L.tip13 = "Коли ви поблизу людини, яку вбиваєте, частина вашої ДНК залишається на тілі жертви. Цю ДНК можна використати за допомогою сканера ДНК детектива, щоб визначити ваше поточне місцеперебування. Краще сховати тіло після того, як ви когось зарізали!" + +L.tip14 = "Чим далі ви знаходитесь від того, кого вбиваєте, тим швидше ваш зразок ДНК на його тілі розпадеться." + +L.tip15 = "Ви зрадник і збираєтесь стріляти? Спробуйте Маскування. Якщо ви промахнулися, біжіть у безпечне місце, вимкніть Маскування, і ніхто не дізнається, що це ви стріляли в них." + +L.tip16 = "Як Зрадник, Телепорт може допомогти вам втекти, коли вас переслідують, і дозволяє швидко подорожувати великою мапою. Переконайтеся, що у вас завжди є позначена безпечна локація." + +L.tip17 = "Всі невинні згруповані і їх важко розділити? Спробуйте за допомогою Радіо відтворити звуки C4 або перестрілки, щоб відвести деяких із них." + +L.tip18 = "Як Зрадник, ви можете відтворювати звуки через меню обладнання після розміщення Радіо. Додайте кілька звуків у чергу, натиснувши кілька кнопок у потрібному вам порядку." + +L.tip19 = "Як Детектив, якщо у вас залишилися кредити, ви можете дати надійному Невинному Знешкоджувач. Тоді ви можете витратити свій час на більш серйозне розслідування та залишити ризиковане знешкодження бомби йому." + +L.tip20 = "Окуляр Детектива дозволяє здійснювати пошук та ідентифікацію тіл на великій відстані. Погана новина, якщо зрадники сподівалися використати тіло як приманку. Користуючись окуляром, детектив неозброєний і розсіяний, користуйтеся цим!" + +L.tip21 = "Оздоровча Станція Детективів дозволяє пораненим гравцям лікуватися. Звичайно, ті поранені могли бути зрадниками..." + +L.tip22 = "Оздоровча Станція записує зразок ДНК кожного, хто нею користувався. Детективи можуть використовувати сканер ДНК, щоб дізнатися, хто зцілявся." + +L.tip23 = "На відміну від зброї та вибухівки C4, радіообладнання Зрадників не містить зразки ДНК людини, яка його підклала. Не хвилюйтеся, що детективи знайдуть їх та розкриють ваше прикриття." + +L.tip24 = "Натисніть {helpkey}, щоб переглянути короткий посібник або змінити деякі специфічні налаштування TTT. Наприклад, ви можете назавжди вимкнути ці підказки саме там." + +L.tip25 = "Коли детектив огляне тіло, результат дослідження стане доступним для всіх гравців через табло рахунку. Для цього натисніть на ім’я померлої людини." + +L.tip26 = "На табло рахунку лупа поруч з іменем гравця вказує на те, що у вас є інформація про нього. Якщо значок яскравий, дані надходять від детектива та можуть містити додаткову інформацію." + +L.tip27 = "Тіла з лупою після імені гравця вказують на те, що їх було оглянуто детективом. Інформація про огляд доступна всім іншим гравцям через табло рахунку." + +L.tip28 = "Спостерігачі можуть натискати {mutekey}, щоб вибрати заглушку іншим спостерігачам або живим гравцям." + +L.tip29 = "Якщо на сервері встановлено додаткову мову, ви можете будь-коли перемкнутися на неї у меню «Налаштування»." + +L.tip30 = "Швидкі повідомлення або радіокоманди можна використовувати, натиснувши {zoomkey}." + +L.tip31 = "Як спостерігач натисніть {duckkey}, щоб розблокувати курсор миші й мати можливість натискати кнопки на цій панелі підказок. Натисніть {duckkey} ще раз, щоб повернутися до керування мишею." + +L.tip32 = "Альтернативна атака лома штовхає інших гравців." + +L.tip33 = "Стрільба через приціл зброї дещо збільшить вашу точність і зменшить віддачу. Присідання такого не робить." + +L.tip34 = "Димові шашки ефективні в закритих приміщеннях для створення плутанини в людних місцях." + +L.tip35 = "Як Зрадник, пам’ятайте, що ви можете носити тіла та ховати їх від допитливих очей невинних та детективів." + +L.tip36 = "Посібник, доступний у розділі {helpkey}, містить огляд найважливіших клавіш у грі." + +L.tip37 = "На табло рахунку клацніть ім’я живого гравця, щоб ви могли вибрати для нього тег, наприклад «підозрюваний» або «друг». Цей тег з’явиться, коли вони будуть знаходитись під прицілом." + +L.tip38 = "Багато предметів розміщеного обладнання (наприклад, C4, радіо) можна прикріпити до стін за допомогою альтернативної атаки." + +L.tip39 = "Вибухівка C4, яка вибухає через помилку під час знешкодження, має менший вибух, ніж C4, яка досягає нуля на своєму часовому пристрої." + +L.tip40 = "Якщо над таймером раунду написано «РЕЖИМ ПОСПІХУ», раунд спочатку триватиме лише кілька хвилин, але з кожною смертю доступний час буде збільшуватися (як для захоплення точки в TF2). Цей режим чинить тиск на зрадників, щоб вони продовжували діяти." + +-- Round report +L.report_title = "Доповідь про раунд" + +-- Tabs +L.report_tab_hilite = "Основні моменти" +L.report_tab_hilite_tip = "Основні моменти раунду" +L.report_tab_events = "Події" +L.report_tab_events_tip = "Журнал подій, які відбулися в цьому раунді" +L.report_tab_scores = "Результати гравців" +L.report_tab_scores_tip = "Рахунок, набраний кожним гравцем у цьому раунді" + +-- Event log saving +L.report_save = "Зберегти .txt" +L.report_save_tip = "Зберігає журнал подій у текстовий файл" +L.report_save_error = "Немає даних журналу подій для збереження." +L.report_save_result = "Журнал подій збережено в:" + +-- Columns +L.col_time = "Час" +L.col_event = "Подія" +L.col_player = "Гравець" +--L.col_roles = "Role(s)" +--L.col_teams = "Team(s)" +L.col_kills1 = "Невинних убито" +L.col_kills2 = "Зрадників убито" +L.col_points = "Рахунок" +L.col_team = "Командний бонус" +L.col_total = "Загальний рахунок" + +-- Awards/highlights +L.aw_sui1_title = "Лідер Культу самогубців" +L.aw_sui1_text = "показав іншим самогубцям, як це робиться, покинувши цей світ першим." + +L.aw_sui2_title = "Самотній і пригнічений" +L.aw_sui2_text = "був єдиним, хто вбив себе." + +L.aw_exp1_title = "Грант на дослідження вибухових речовин" +L.aw_exp1_text = "отримав визнання за дослідження вибухів. {num} суб’єктів допомогли з тестуванням." + +L.aw_exp2_title = "Польові дослідження" +L.aw_exp2_text = "перевірив власну стійкість до вибухів. Її було недостатньо." + +L.aw_fst1_title = "Перша кров" +L.aw_fst1_text = "завдав першої смерті невинному від рук зрадника." + +L.aw_fst2_title = "Перше криваве дурне вбивство" +L.aw_fst2_text = "зробив перше вбивство, вистріливши в товариша-зрадника. Хороша робота." + +L.aw_fst3_title = "Перша помилка" +L.aw_fst3_text = "був першим, хто вчинив убивство. Шкода, що це був невинний товариш." + +L.aw_fst4_title = "Перший постріл" +L.aw_fst4_text = "зробив перший постріл на користь невинних терористів, вбивши зрадника" + +L.aw_all1_title = "Найсмертоносніший серед рівних" +L.aw_all1_text = "ніс відповідальність за кожне вбивство, скоєне невинними у цьому раунді." + +L.aw_all2_title = "Одинокий вовк" +L.aw_all2_text = "ніс відповідальність за кожне вбивство скоєне зрадниками в цьому раунді." + +L.aw_nkt1_title = "Один є, Бос!" +L.aw_nkt1_text = "зумів вбити одного невинного. Молодець!" + +L.aw_nkt2_title = "Куля для двох" +L.aw_nkt2_text = "показав, що перший постріл не був вдалим, убивши іншого." + +L.aw_nkt3_title = "Серійний зрадник" +L.aw_nkt3_text = "сьогодні поклав кінець життя трьом невинним терористам." + +L.aw_nkt4_title = "Вовк серед більш вівцеподібних вовків" +L.aw_nkt4_text = "їсть невинних терористів на вечерю. Вечеря складає {num} тіл." + +L.aw_nkt5_title = "Контртерористична операція" +L.aw_nkt5_text = "отримує гроші за вбивства. Тепер можна купити ще одну розкішну яхту." + +L.aw_nki1_title = "Зрадь це" +L.aw_nki1_text = "знайшов зрадника. Застрелив зрадника. Питання?" + +L.aw_nki2_title = "Звернувся до Ліги Справедливості" +L.aw_nki2_text = "провів двох зрадників на той світ." + +L.aw_nki3_title = "Чи мріють зрадники про зрадливих овець?" +L.aw_nki3_text = "відправив трьох зрадників відпочивати." + +L.aw_nki4_title = "Співробітник внутрішніх справ" +L.aw_nki4_text = "отримує гроші за вбивства. Тепер можна замовити свій п’ятий басейн." + +L.aw_fal1_title = "Ні, містере Бонд, я очікую, що ви це впадете" +L.aw_fal1_text = "штовхнув когось із великої висоти." + +L.aw_fal2_title = "На саме дно" +L.aw_fal2_text = "дозволив своєму тілу вдаритися об землю після падіння зі значної висоти." + +L.aw_fal3_title = "Людський метеорит" +L.aw_fal3_text = "роздавив когось, впавши на нього з великої висоти." + +L.aw_hed1_title = "Вправність" +L.aw_hed1_text = "відкрив для себе радість у {num} пострілах у голову." + +L.aw_hed2_title = "Неврологія" +L.aw_hed2_text = "видалив мізки з {num} голів для детальнішого дослідження." + +L.aw_hed3_title = "Відеоігри змусили зробити мене це" +L.aw_hed3_text = "застосував свої знання з симулятора вбивств та вбив {num} ворогів пострілами в голову." + +L.aw_cbr1_title = "Тук-тук-тук" +L.aw_cbr1_text = "має середній замах ломом, як з’ясували {num} жертв." + +L.aw_cbr2_title = "Фрімен" +L.aw_cbr2_text = "прикрасив свій лом залишками мізків не менше ніж {num} людей." + +L.aw_pst1_title = "Наполегливий маленький дурник" +L.aw_pst1_text = "здійснив {num} вбивств із пістолета. Потім він вирушив обіймати когось до смерті." + +L.aw_pst2_title = "Забій малого калібру" +L.aw_pst2_text = "убив невелику армію з {num} людей використавши лише пістолет. Імовірно він встановив крихітний дробовик всередину ствола." + +L.aw_sgn1_title = "Легкий режим" +L.aw_sgn1_text = "застосовує картеч там, де боляче, вбивши {num} цілей." + +L.aw_sgn2_title = "Тисяча маленьких гранул" +L.aw_sgn2_text = "не дуже сподобалися своя картеч, тому він віддав її. {num} одержувачів, на жаль, не вижили." + +L.aw_rfl1_title = "Цілься, стріляй" +L.aw_rfl1_text = "показав, що все, що вам потрібно для {num} вбивств, це гвинтівка та тверда рука." + +L.aw_rfl2_title = "Я бачу твою голову звідси" +L.aw_rfl2_text = "знає свою гвинтівку. Тепер {num} інших людей також знають цю гвинтівку." + +L.aw_dgl1_title = "Це як маленька гвинтівка" +L.aw_dgl1_text = "опанував Deagle і вбив {num} людей." + +L.aw_dgl2_title = "Майстер Орла" +L.aw_dgl2_text = "прибив {num} людей завдяки Deagle." + +L.aw_mac1_title = "Моліться і вбивайте" +L.aw_mac1_text = "убив {num} людей за допомогою MAC10, але не каже, скільки набоїв на це витратив." + +L.aw_mac2_title = "Мак і сир" +L.aw_mac2_text = "цікаво, скільки людей загинуло, якби ця особа могла володіти двома MAC10? {num} треба помножити на два?" + +L.aw_sip1_title = "Слово — срібло, мовчання — золото" +L.aw_sip1_text = "заткнув {num} людей за допомогою пістолета з глушником." + +L.aw_sip2_title = "Тихий вбивця" +L.aw_sip2_text = "вбив {num} людей, які навіть не чули, як померли." + +L.aw_knf1_title = "Ніж впізнав тебе" +L.aw_knf1_text = "встромив комусь ніж в обличчя через Інтернет." + +L.aw_knf2_title = "Звідки ти це взяв?" +L.aw_knf2_text = "не був зрадником, але все одно вбив когось ножем." + +L.aw_knf3_title = "Людина як ніж" +L.aw_knf3_text = "знайшов {num} викинутих ножів і використав їх." + +L.aw_knf4_title = "Найгостріша людина у світі" +L.aw_knf4_text = "вбив {num} людей ножем. Не запитуйте мене як це він зробив." + +L.aw_flg1_title = "На допомогу" +L.aw_flg1_text = "використав свій сигнальний пістолет, щоб просигналізувати {num} смертей." + +L.aw_flg2_title = "Сигнальний пістолет показав нам пожежу" +L.aw_flg2_text = "навчив {num} чоловіків про небезпеку носіння легкозаймистого одягу." + +L.aw_hug1_title = "H.U.G.E Розмах" +L.aw_hug1_text = "був у гармонії зі своїм H.U.G.E, якимось чином йому навіть вдалося змусити кулі вразити {num} людей." + +L.aw_hug2_title = "Вперта пара" +L.aw_hug2_text = "просто продовжував стріляти з H.U.G.E., і побачив, що його H.U.G.E. терпіння було винагороджено {num} вбивствами." + +L.aw_msx1_title = "Пів-пав" +L.aw_msx1_text = "забрав {num} людей з M16." + +L.aw_msx2_title = "Божевілля середньої дальності" +L.aw_msx2_text = "знає, як знищувати цілі з M16, показавши {num} вбивств." + +L.aw_tkl1_title = "Ой, не то" +L.aw_tkl1_text = "зажав свій палець саме тоді, коли цілився в приятеля." + +L.aw_tkl2_title = "Подвійна помилка" +L.aw_tkl2_text = "думав, що двічі знайшов зрадника, але обидва рази помилився." + +L.aw_tkl3_title = "Усвідомив значення карми" +L.aw_tkl3_text = "не міг зупинитися після вбивства двох товаришів по команді. Три — його щасливе число." + +L.aw_tkl4_title = "Вбивця товаришів" +L.aw_tkl4_text = "вбив всю свою команду. Бог ти мій, БАНБАНБАН!" + +L.aw_tkl5_title = "Рольовий гравець" +L.aw_tkl5_text = "грав роль божевільного, я не жартую. Саме тому він вбив більшу частину своєї команди." + +L.aw_tkl6_title = "Придурок" +L.aw_tkl6_text = "не зміг зрозуміти, на чиєму боці він, і вбив більш ніж половину своїх товаришів." + +L.aw_tkl7_title = "Селюк" +L.aw_tkl7_text = "добре захистив свій газон, убивши понад чверті своїх товаришів по команді." + +L.aw_brn1_title = "За рецептом бабусі" +L.aw_brn1_text = "засмажив кілька людей до відмінної скоринки." + +L.aw_brn2_title = "Палій" +L.aw_brn2_text = "почув голосне реготання після спалення однієї зі своїх численних жертв." + +L.aw_brn3_title = "Піррове спалювання" +L.aw_brn3_text = "спалив їх усіх, але запалювальні гранати закінчилися! Як він впорається!?" + +L.aw_fnd1_title = "Слідчий" +L.aw_fnd1_text = "знайшов {num} тіл, що лежать навколо." + +L.aw_fnd2_title = "Треба спіймати їх усіх" +L.aw_fnd2_text = "знайшов {num} тіл для своєї колекції." + +L.aw_fnd3_title = "Запах смерті" +L.aw_fnd3_text = "раз за разом натикався на випадкові тіла. Нарахував {num} разів у цьому раунді." + +L.aw_crd1_title = "Переробник" +L.aw_crd1_text = "підібрав {num} кредитів обладнання, що залишилися на тілах." + +L.aw_tod1_title = "Піррова перемога" +L.aw_tod1_text = "помер за кілька секунд до того, як його команда перемогла раунд." + +L.aw_tod2_title = "Я ненавиджу цю гру" +L.aw_tod2_text = "помер одразу ж на початку раунду." + +-- New and modified pieces of text are placed below this point, marked with the +-- version in which they were added, to make updating translations easier. + +-- v24 +L.drop_no_ammo = "Недостатньо набоїв у обоймі вашої зброї, щоб викинути їх у вигляді коробки." + +-- 2015-05-25 +L.hat_retrieve = "Ви взяли Капелюх детектива." + +-- 2017-09-03 +L.sb_sortby = "Сортувати за:" + +-- 2018-07-24 +L.equip_tooltip_main = "Меню обладнання" +L.equip_tooltip_radar = "Керування радаром" +L.equip_tooltip_disguise = "Керування маскуванням" +L.equip_tooltip_radio = "Керування радіо" +L.equip_tooltip_xfer = "Переказ кредитів" +--L.equip_tooltip_reroll = "Reroll equipment" + +L.confgrenade_name = "Дискомбобулятор" +L.polter_name = "Полтергейст" +L.stungun_name = "Прототип UMP" + +L.knife_instant = "МИТТЄВЕ ВБИВСТВО" + +L.binoc_zoom_level = "РІВЕНЬ" +L.binoc_body = "ТІЛО ЗНАЙДЕНО" + +L.idle_popup_title = "Бездіяльність" + +-- 2019-01-31 +--L.create_own_shop = "Create own shop" +--L.shop_link = "Link with" +--L.shop_disabled = "Disable shop" +--L.shop_default = "Use default shop" + +-- 2019-05-05 +--L.reroll_name = "Reroll" +--L.reroll_menutitle = "Reroll equipment" +--L.reroll_no_credits = "You need {amount} credits to reroll!" +--L.reroll_button = "Reroll" +--L.reroll_help = "Use {amount} credits to get a new random set of equipment in your shop!" + +-- 2019-05-06 +--L.equip_not_alive = "You can view all available items by selecting a role on the right. Don't forget to mark your favorites!" + +-- 2019-06-27 +--L.shop_editor_title = "Shop Editor" +--L.shop_edit_items_weapong = "Edit Items / Weapons" +--L.shop_edit = "Edit Shops" +--L.shop_settings = "Settings" +--L.shop_select_role = "Select Role" +--L.shop_edit_items = "Edit Items" +--L.shop_edit_shop = "Edit Shop" +--L.shop_create_shop = "Create Custom Shop" +--L.shop_selected = "Selected {role}" +--L.shop_settings_desc = "Change the values to adapt Random Shop ConVars. Don't forget to save your changes!" + +--L.bindings_new = "New bound key for {name}: {key}" + +--L.hud_default_failed = "Failed to set the HUD {hudname} as new default. You don't have permission to do that, or this HUD doesn't exist." +--L.hud_forced_failed = "Failed to force the HUD {hudname}. You don't have permission to do that, or this HUD doesn't exist." +--L.hud_restricted_failed = "Failed to restrict the HUD {hudname}. You don't have permission to do that." + +--L.shop_role_select = "Select a role" +--L.shop_role_selected = "{role}'s shop was selected!" +--L.shop_search = "Search" + +-- 2019-10-19 +--L.drop_ammo_prevented = "Something prevents you from dropping your ammo." + +-- 2019-10-28 +--L.target_c4 = "Press [{usekey}] to open C4 menu" +--L.target_c4_armed = "Press [{usekey}] to disarm C4" +--L.target_c4_armed_defuser = "Press [{primaryfire}] to use defuser" +--L.target_c4_not_disarmable = "You can't disarm C4 of a living teammate" +--L.c4_short_desc = "Something very explosive" + +--L.target_pickup = "Press [{usekey}] to pick up" +--L.target_slot_info = "Slot: {slot}" +--L.target_pickup_weapon = "Press [{usekey}] to pickup weapon" +--L.target_switch_weapon = "Press [{usekey}] to swap with your current weapon" +--L.target_pickup_weapon_hidden = ", press [{walkkey} + {usekey}] for hidden pickup" +--L.target_switch_weapon_hidden = ", press [{walkkey} + {usekey}] for hidden switch" +--L.target_switch_weapon_nospace = "There is no inventory slot available for this weapon" +--L.target_switch_drop_weapon_info = "Dropping {name} from slot {slot}" +--L.target_switch_drop_weapon_info_noslot = "There is no droppable weapon in slot {slot}" + +--L.corpse_searched_by_detective = "This corpse was searched by a public policing role" +--L.corpse_too_far_away = "The corpse is too far away." + +--L.radio_short_desc = "Weapon sounds are music to me" + +--L.hstation_subtitle = "Press [{usekey}] to receive health." +--L.hstation_charge = "Remaining charge of health station: {charge}" +--L.hstation_empty = "There is no more charge left in this health station" +--L.hstation_maxhealth = "Your health is full" +--L.hstation_short_desc = "The heath station slowly recharges over time" + +-- 2019-11-03 +--L.vis_short_desc = "Visualizes a crime scene if the victim died by a gunshot wound" +--L.corpse_binoculars = "Press [{key}] to search corpse with binoculars." +--L.binoc_progress = "Search progress: {progress}%" + +--L.pickup_no_room = "You have no space in your inventory for this weapon kind." +--L.pickup_fail = "You cannot pick up this weapon." +--L.pickup_pending = "You already picked up a weapon, wait until you receive it." + +-- 2020-01-07 +--L.tbut_help_admin = "Edit traitor button settings" +--L.tbut_role_toggle = "[{walkkey} + {usekey}] to toggle this button for {role}" +--L.tbut_role_config = "Role: {current}" +--L.tbut_team_toggle = "[SHIFT + {walkkey} + {usekey}] to toggle this button for team {team}" +--L.tbut_team_config = "Team: {current}" +--L.tbut_current_config = "Current config:" +--L.tbut_intended_config = "Intended config by map creator:" +--L.tbut_admin_mode_only = "You see this button because you're an admin and '{cv}' is set to '1'." +--L.tbut_allow = "Allow" +--L.tbut_prohib = "Prohibit" +--L.tbut_default = "Default" + +-- 2020-02-09 +--L.name_door = "Door" +--L.door_open = "Press [{usekey}] to open door." +--L.door_close = "Press [{usekey}] to close door." +--L.door_locked = "This door is locked." + +-- 2020-02-11 +--L.automoved_to_spec = "(AUTOMATED MESSAGE) I have been moved to the Spectator team because I was idle/AFK." +--L.mute_team = "{team} muted." + +-- 2020-02-16 +--L.door_auto_closes = "This door closes automatically." +--L.door_open_touch = "Walk into door to open." +--L.door_open_touch_and_use = "Walk into door or press [{usekey}] to open." + +-- 2020-03-09 +L.help_title = "Допомога та Налаштування" + +--L.menu_changelog_title = "Changelog" +--L.menu_guide_title = "TTT2 Guide" +--L.menu_bindings_title = "Key Bindings" +--L.menu_language_title = "Language" +--L.menu_appearance_title = "Appearance" +--L.menu_gameplay_title = "Gameplay" +--L.menu_addons_title = "Addons" +--L.menu_legacy_title = "Legacy Addons" +--L.menu_administration_title = "Administration" +--L.menu_equipment_title = "Edit Equipment" +--L.menu_shops_title = "Edit Shops" + +--L.menu_changelog_description = "A list of changes and fixes in recent versions." +--L.menu_guide_description = "Helps you to get started with TTT2 and explains some things about gameplay, roles and other stuff." +--L.menu_bindings_description = "Bind specific features of TTT2 and its addons to your own liking." +--L.menu_language_description = "Select the language of the gamemode." +--L.menu_appearance_description = "Tweak the appearance and performance of the UI." +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." +--L.menu_addons_description = "Configure local addons to your liking." +--L.menu_legacy_description = "A panel with converted tabs from the original TTT that should be ported over to the new system." +--L.menu_administration_description = "General settings for HUDs, shops etc." +--L.menu_equipment_description = "Set credits, limitations, availability and other stuff." +--L.menu_shops_description = "Add/Remove shops for roles and configure what equipment they have." + +--L.submenu_guide_gameplay_title = "Gameplay" +--L.submenu_guide_roles_title = "Roles" +--L.submenu_guide_equipment_title = "Equipment" + +--L.submenu_bindings_bindings_title = "Bindings" + +--L.submenu_language_language_title = "Language" + +--L.submenu_appearance_general_title = "General" +--L.submenu_appearance_hudswitcher_title = "HUD Switcher" +--L.submenu_appearance_vskin_title = "VSkin" +--L.submenu_appearance_targetid_title = "TargetID" +--L.submenu_appearance_shop_title = "Shop Settings" +--L.submenu_appearance_crosshair_title = "Crosshair" +--L.submenu_appearance_dmgindicator_title = "Damage Indicator" +--L.submenu_appearance_performance_title = "Performance" +--L.submenu_appearance_interface_title = "Interface" + +--L.submenu_gameplay_general_title = "General" + +--L.submenu_administration_hud_title = "HUD Settings" +--L.submenu_administration_randomshop_title = "Random Shop" + +--L.help_color_desc = "If this setting is enabled, you can choose a global color that will be used for the targetID outline and the crosshair." +--L.help_scale_factor = "This scale factor influences all UI elements (HUD, VGUI and TargetID). It is automatically updated if the screen resolution is changed. Changing this value will reset the HUD!" +--L.help_hud_game_reload = "The HUD is not available right now. Reconnect to the server or relaunch the game." +--L.help_hud_special_settings = "These are specific settings of this HUD." +--L.help_vskin_info = "VSkin (VGUI skin) is the skin applied to all menu elements like the current one. They can be easily created with a simple Lua script and can change colors and some size parameters." +--L.help_targetid_info = "TargetID is the information rendered when pointing your crosshair at an entity. Its color can be configured in the 'General' tab." +--L.help_hud_default_desc = "Sets the default HUD for all players. Players that have not yet selected a HUD will receive this HUD as their default. Changing this won't change the HUD for players that have already selected their HUD." +--L.help_hud_forced_desc = "Forces a HUD for all players. This disables the HUD selection feature for everyone." +--L.help_hud_enabled_desc = "Enable/Disable HUDs to restrict the selection of these HUDs." +--L.help_damage_indicator_desc = "The damage indicator is the overlay shown when the player is damaged. To add a new theme, place a png in 'materials/vgui/ttt/damageindicator/themes/'." +--L.help_shop_key_desc = "Open the shop by pressing the shop key instead of the score menu during preparing / at the end of a round?" + +--L.label_menu_menu = "MENU" +--L.label_menu_admin_spacer = "Admin Area (not shown to normal users)" +--L.label_language_set = "Select language" +--L.label_global_color_enable = "Enable global color" +--L.label_global_color = "Global color" +--L.label_global_scale_factor = "Global scale factor" +--L.label_hud_select = "Select HUD" +--L.label_vskin_select = "Select VSkin" +--L.label_blur_enable = "Enable VSkin background blur" +--L.label_color_enable = "Enable VSkin background color" +--L.label_minimal_targetid = "Minimalist Target ID under crosshair (no Karma text, hints etc.)" +--L.label_shop_always_show = "Always show the shop" +--L.label_shop_double_click_buy = "Enable an item purchase by double-clicking on it in the shop" +--L.label_shop_num_col = "Number of columns" +--L.label_shop_num_row = "Number of rows" +--L.label_shop_item_size = "Icon size" +--L.label_shop_show_slot = "Show slot marker" +--L.label_shop_show_custom = "Show custom item marker" +--L.label_shop_show_fav = "Show favourite item marker" +--L.label_crosshair_enable = "Enable crosshair" +--L.label_crosshair_opacity = "Crosshair opacity" +--L.label_crosshair_ironsight_opacity = "Ironsight crosshair opacity" +--L.label_crosshair_size = "Crosshair size multiplier" +--L.label_crosshair_thickness = "Crosshair thickness multiplier" +--L.label_crosshair_thickness_outline = "Crosshair outline thickness multiplier" +--L.label_crosshair_scale_enable = "Enable dynamic crosshair scale" +--L.label_crosshair_ironsight_low_enabled = "Lower weapon when using ironsights" +--L.label_damage_indicator_enable = "Enable damage indicator" +--L.label_damage_indicator_mode = "Select damage indicator theme" +--L.label_damage_indicator_duration = "Fade time after getting hit (in seconds)" +--L.label_damage_indicator_maxdamage = "Damage needed for the maximum opacity" +--L.label_damage_indicator_maxalpha = "Maximum opacity" +--L.label_performance_halo_enable = "Draw an outline around some entities while looking at them" +--L.label_performance_spec_outline_enable = "Enable controlled objects' outlines" +--L.label_performance_ohicon_enable = "Enable role icons over players' heads" +--L.label_interface_tips_enable = "Show gameplay tips at the bottom of the screen while spectating" +--L.label_interface_popup = "Start of round info popup duration" +--L.label_interface_fastsw_menu = "Enable menu with fast weapon switch" +--L.label_inferface_wswitch_hide_enable = "Enable weapon switch menu auto-closing" +--L.label_inferface_scues_enable = "Play sound cue when a round begins or ends" +--L.label_gameplay_specmode = "Spectate-only mode (always stay spectator)" +--L.label_gameplay_fastsw = "Fast weapon switch" +--L.label_gameplay_hold_aim = "Enable hold to aim" +--L.label_gameplay_mute = "Mute living players when dead" +--L.label_hud_default = "Default HUD" +--L.label_hud_force = "Forced HUD" + +--L.label_bind_weaponswitch = "Pickup Weapon" +--L.label_bind_voice = "Global Voice Chat" +--L.label_bind_voice_team = "Team Voice Chat" + +--L.label_hud_basecolor = "Base Color" + +--L.label_menu_not_populated = "This submenu does not contain any content." + +--L.header_bindings_ttt2 = "TTT2 Bindings" +--L.header_bindings_other = "Other Bindings" +--L.header_language = "Language Settings" +--L.header_global_color = "Select Global Color" +--L.header_hud_select = "Select a HUD" +--L.header_hud_customize = "Customize the HUD" +--L.header_vskin_select = "Select and Customize the VSkin" +--L.header_targetid = "TargetID Settings" +--L.header_shop_settings = "Equipment Shop Settings" +--L.header_shop_layout = "Item List Layout" +--L.header_shop_marker = "Item Marker Settings" +--L.header_crosshair_settings = "Crosshair Settings" +--L.header_damage_indicator = "Damage Indicator Settings" +--L.header_performance_settings = "Performance Settings" +--L.header_interface_settings = "Interface Settings" +--L.header_gameplay_settings = "Gameplay Settings" +--L.header_hud_administration = "Select Default and Forced HUDs" +--L.header_hud_enabled = "Enable/Disable HUDs" + +--L.button_menu_back = "Back" +--L.button_none = "None" +--L.button_press_key = "Press a key" +--L.button_save = "Save" +--L.button_reset = "Reset" +--L.button_close = "Close" +--L.button_hud_editor = "HUD Editor" + +-- 2020-04-20 +--L.item_speedrun = "Speedrun" +--L.item_speedrun_desc = [[Makes you 50% faster!]] +--L.item_no_explosion_damage = "No Explosion Damage" +--L.item_no_explosion_damage_desc = [[Makes you immune to explosion damage.]] +--L.item_no_fall_damage = "No Fall Damage" +--L.item_no_fall_damage_desc = [[Makes you immune to fall damage.]] +--L.item_no_fire_damage = "No Fire Damage" +--L.item_no_fire_damage_desc = [[Makes you immune to fire damage.]] +--L.item_no_hazard_damage = "No Hazard Damage" +--L.item_no_hazard_damage_desc = [[Makes you immune to hazard damage such as poison, radiation and acid.]] +--L.item_no_energy_damage = "No Energy Damage" +--L.item_no_energy_damage_desc = [[Makes you immune to energy damage such as lasers, plasma and lightning.]] +--L.item_no_prop_damage = "No Prop Damage" +--L.item_no_prop_damage_desc = [[Makes you immune to prop damage.]] +--L.item_no_drown_damage = "No Drowning Damage" +--L.item_no_drown_damage_desc = [[Makes you immune to drowning damage.]] + +-- 2020-04-21 +--L.dna_tid_possible = "Scan possible." +--L.dna_tid_impossible = "No scan possible." +--L.dna_screen_ready = "No DNA" +--L.dna_screen_match = "Match" + +-- 2020-04-30 +--L.message_revival_canceled = "Revival canceled." +--L.message_revival_failed = "Revival failed." +--L.message_revival_failed_missing_body = "You have not been revived because your corpse no longer exists." +--L.hud_revival_title = "Time left until revival:" +--L.hud_revival_time = "{time}s" + +-- 2020-05-03 +--L.door_destructible = "This door is destructible ({health}HP)." + +-- 2020-05-28 +--L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." + +-- 2020-06-04 +--L.label_bind_disguiser = "Toggle disguiser" + +-- 2020-06-24 +--L.dna_help_primary = "Collect a DNA sample" +--L.dna_help_secondary = "Switch the DNA slot" +--L.dna_help_reload = "Delete a sample" + +--L.binoc_help_pri = "Search a body." +--L.binoc_help_sec = "Change zoom level." + +--L.vis_help_pri = "Drop the activated device." + + +-- 2020-08-07 +--L.pickup_error_spec = "You cannot pick this up as a spectator." +--L.pickup_error_owns = "You cannot pick this up because you already have this weapon." +--L.pickup_error_noslot = "You cannot pick this up because you have no free slot available." + +-- 2020-11-02 +--L.lang_server_default = "Server Default" +--L.help_lang_info = [[ +--This translation is {coverage}% complete with the English language taken as a default reference. +-- +--Keep in mind that these translations are made by the community. Feel free to contribute if something is missing or incorrect.]] + +-- 2021-04-13 +--L.title_score_info = "Round End Info" +--L.title_score_events = "Event Timeline" + +--L.label_bind_clscore = "Open round report" +--L.title_player_score = "{player}'s score:" + +--L.label_show_events = "Show events from" +--L.button_show_events_you = "You" +--L.button_show_events_global = "Global" +--L.label_show_roles = "Show role distribution from" +--L.button_show_roles_begin = "Round Begin" +--L.button_show_roles_end = "Round End" + +L.hilite_win_traitors = "ЗРАДНИКИ ПЕРЕМОГЛИ" +--L.hilite_win_innocents = "TEAM INNOCENT WON" +--L.hilite_win_tie = "IT IS A TIE" +--L.hilite_win_time = "TIME IS UP" + +--L.tooltip_karma_gained = "Karma changes for this round:" +--L.tooltip_score_gained = "Score changes for this round:" +--L.tooltip_roles_time = "Role changes for this round:" + +--L.tooltip_finish_score_alive_teammates = "Alive teammates: {score}" +--L.tooltip_finish_score_alive_all = "Alive players: {score}" +--L.tooltip_finish_score_timelimit = "Time is up: {score}" +--L.tooltip_finish_score_dead_enemies = "Dead enemies: {score}" +--L.tooltip_kill_score = "Kill: {score}" +--L.tooltip_bodyfound_score = "Body found: {score}" + +--L.finish_score_alive_teammates = "Alive teammates:" +--L.finish_score_alive_all = "Alive players:" +--L.finish_score_timelimit = "Time is up:" +--L.finish_score_dead_enemies = "Dead enemies:" +--L.kill_score = "Kill:" +--L.bodyfound_score = "Body found:" + +--L.title_event_bodyfound = "A body was found" +--L.title_event_c4_disarm = "A C4 was disarmed" +--L.title_event_c4_explode = "A C4 exploded" +--L.title_event_c4_plant = "A C4 was armed" +--L.title_event_creditfound = "Equipment credits were found" +--L.title_event_finish = "The round has ended" +--L.title_event_game = "A new round has started" +--L.title_event_kill = "A player was killed" +--L.title_event_respawn = "A player respawned" +--L.title_event_rolechange = "A player changed their role or team" +--L.title_event_selected = "The roles were distributed" +--L.title_event_spawn = "A player spawned" + +--L.desc_event_bodyfound = "{finder} ({firole} / {fiteam}) has found the body of {found} ({forole} / {foteam}). The corpse has {credits} equipment credit(s)." +--L.desc_event_bodyfound_headshot = "The victim was killed by a headshot." +--L.desc_event_c4_disarm_success = "{disarmer} ({drole} / {dteam}) successfully disarmed the C4 armed by {owner} ({orole} / {oteam})." +--L.desc_event_c4_disarm_failed = "{disarmer} ({drole} / {dteam}) tried to disarm the C4 armed by {owner} ({orole} / {oteam}). They failed." +--L.desc_event_c4_explode = "The C4 armed by {owner} ({role} / {team}) exploded." +--L.desc_event_c4_plant = "{owner} ({role} / {team}) armed an explosive C4." +--L.desc_event_creditfound = "{finder} ({firole} / {fiteam}) has found {credits} equipment credit(s) in the corpse of {found} ({forole} / {foteam})." +--L.desc_event_finish = "The round lasted {minutes}:{seconds}. There were {alive} player(s) alive in the end." +--L.desc_event_game = "A new round has started." +--L.desc_event_respawn = "{player} has respawned." +--L.desc_event_rolechange = "{player} changed their role/team from {orole} ({oteam}) to {nrole} ({nteam})." +--L.desc_event_selected = "The teams and roles were distributed for all {amount} player(s)." +--L.desc_event_spawn = "{player} has spawned." + +-- Name of a trap that killed us that has not been named by the mapper +--L.trap_something = "something" + +-- Kill events +--L.desc_event_kill_suicide = "It was suicide." +--L.desc_event_kill_team = "It was a team kill." + +--L.desc_event_kill_blowup = "{victim} ({vrole} / {vteam}) blew themselves up." +--L.desc_event_kill_blowup_trap = "{victim} ({vrole} / {vteam}) was blown up by {trap}." + +--L.desc_event_kill_tele_self = "{victim} ({vrole} / {vteam}) telefragged themselves." +--L.desc_event_kill_sui = "{victim} ({vrole} / {vteam}) couldn't take it and killed themselves." +--L.desc_event_kill_sui_using = "{victim} ({vrole} / {vteam}) killed themselves using {tool}." + +--L.desc_event_kill_fall = "{victim} ({vrole} / {vteam}) fell to their death." +--L.desc_event_kill_fall_pushed = "{victim} ({vrole} / {vteam}) fell to their death after {attacker} pushed them." +--L.desc_event_kill_fall_pushed_using = "{victim} ({vrole} / {vteam}) fell to their death after {attacker} ({arole} / {ateam}) used {trap} to push them." + +--L.desc_event_kill_shot = "{victim} ({vrole} / {vteam}) was shot by {attacker}." +--L.desc_event_kill_shot_using = "{victim} ({vrole} / {vteam}) was shot by {attacker} ({arole} / {ateam}) using a {weapon}." + +--L.desc_event_kill_drown = "{victim} ({vrole} / {vteam}) was drowned by {attacker}." +--L.desc_event_kill_drown_using = "{victim} ({vrole} / {vteam}) was drowned by {trap} triggered by {attacker} ({arole} / {ateam})." + +--L.desc_event_kill_boom = "{victim} ({vrole} / {vteam}) was exploded by {attacker}." +--L.desc_event_kill_boom_using = "{victim} ({vrole} / {vteam}) was blown up by {attacker} ({arole} / {ateam}) using {trap}." + +--L.desc_event_kill_burn = "{victim} ({vrole} / {vteam}) was fried by {attacker}." +--L.desc_event_kill_burn_using = "{victim} ({vrole} / {vteam}) was burned by {trap} due to {attacker} ({arole} / {ateam})." + +--L.desc_event_kill_club = "{victim} ({vrole} / {vteam}) was beaten up by {attacker}." +--L.desc_event_kill_club_using = "{victim} ({vrole} / {vteam}) was pummeled to death by {attacker} ({arole} / {ateam}) using {trap}." + +--L.desc_event_kill_slash = "{victim} ({vrole} / {vteam}) was stabbed by {attacker}." +--L.desc_event_kill_slash_using = "{victim} ({vrole} / {vteam}) was cut up by {attacker} ({arole} / {ateam}) using {trap}." + +--L.desc_event_kill_tele = "{victim} ({vrole} / {vteam}) was telefragged by {attacker}." +--L.desc_event_kill_tele_using = "{victim} ({vrole} / {vteam}) was atomized by {trap} set by {attacker} ({arole} / {ateam})." + +--L.desc_event_kill_goomba = "{victim} ({vrole} / {vteam}) was crushed by the massive bulk of {attacker} ({arole} / {ateam})." + +--L.desc_event_kill_crush = "{victim} ({vrole} / {vteam}) was crushed by {attacker}." +--L.desc_event_kill_crush_using = "{victim} ({vrole} / {vteam}) was crushed by {trap} of {attacker} ({arole} / {ateam})." + +--L.desc_event_kill_other = "{victim} ({vrole} / {vteam}) was killed by {attacker}." +--L.desc_event_kill_other_using = "{victim} ({vrole} / {vteam}) was killed by {attacker} ({arole} / {ateam}) using {trap}." + +-- 2021-04-20 +--L.none = "No Role" + +-- 2021-04-24 +--L.karma_teamkill_tooltip = "Teammate killed" +--L.karma_teamhurt_tooltip = "Teammate damaged" +--L.karma_enemykill_tooltip = "Enemy killed" +--L.karma_enemyhurt_tooltip = "Enemy damaged" +--L.karma_cleanround_tooltip = "Clean round" +--L.karma_roundheal_tooltip = "Karma restoration" +--L.karma_unknown_tooltip = "Unknown" + +-- 2021-05-07 +--L.header_random_shop_administration = "Random Shop Settings" +--L.header_random_shop_value_administration = "Balance Settings" + +--L.shopeditor_name_random_shops = "Enable random shops" +--L.shopeditor_desc_random_shops = [[Random shops give every player a limited randomized set of all available equipments. +--Team shops forcefully give the same set to all players in a team instead of individual ones. +--Rerolling allows you to get a new randomized set of equipment for credits.]] +--L.shopeditor_name_random_shop_items = "Number of random equipments" +--L.shopeditor_desc_random_shop_items = "This includes equipments, which are marked with \"Always available in shop\". So choose a high enough number or you only get those." +--L.shopeditor_name_random_team_shops = "Enable team shops" +--L.shopeditor_name_random_shop_reroll = "Enable shop reroll availability" +--L.shopeditor_name_random_shop_reroll_cost = "Cost per reroll" +--L.shopeditor_name_random_shop_reroll_per_buy = "Auto reroll after buy" + +-- 2021-06-04 +--L.header_equipment_setup = "Equipment Settings" +--L.header_equipment_value_setup = "Balance Settings" + +--L.equipmenteditor_name_not_buyable = "Can be bought" +--L.equipmenteditor_desc_not_buyable = "If disabled the equipment will not show in the shop. Roles that have this equipment assigned will still receive it." +--L.equipmenteditor_name_not_random = "Always available in shop" +--L.equipmenteditor_desc_not_random = "If enabled, the equipment is always available in the shop. When the random shop is enabled, it takes one available random slot and always reserves it for this equipment." +--L.equipmenteditor_name_global_limited = "Global limited amount" +--L.equipmenteditor_desc_global_limited = "If enabled, the equipment can be bought only once on the server in the active round." +--L.equipmenteditor_name_team_limited = "Team limited amount" +--L.equipmenteditor_desc_team_limited = "If enabled, the equipment can be bought only once per team in the active round." +--L.equipmenteditor_name_player_limited = "Player limited amount" +--L.equipmenteditor_desc_player_limited = "If enabled, the equipment can be bought only once per player in the active round." +--L.equipmenteditor_name_min_players = "Minimum amount of players for buying" +--L.equipmenteditor_name_credits = "Price in credits" + +-- 2021-06-08 +--L.equip_not_added = "not added" +--L.equip_added = "added" +--L.equip_inherit_added = "added (inherit)" +--L.equip_inherit_removed = "removed (inherit)" + +-- 2021-06-09 +--L.layering_not_layered = "Not layered" +--L.layering_layer = "Layer {layer}" +--L.header_rolelayering_role = "{role} layering" +--L.header_rolelayering_baserole = "Base role layering" +--L.submenu_administration_rolelayering_title = "Role Layering" +--L.header_rolelayering_info = "Role layering information" +--L.help_rolelayering_roleselection = "The role distribution process is split into two stages. In the first stage base roles are distributed, which are innocent, traitor and those listed in the 'base role layer' box below. The second stage is used to upgrade those base roles to a subrole." +--L.help_rolelayering_layers = "From each layer only one role is selected. First the roles from the custom layers are distributed starting from the first layer until the last is reached or no more roles can be upgraded. Whichever happens first, if upgradeable slots are still available, the unlayered roles will be distributed as well." +--L.scoreboard_voice_tooltip = "Scroll to change the volume" + +-- 2021-06-15 +--L.header_shop_linker = "Settings" +--L.label_shop_linker_set = "Select shop type:" + +-- 2021-06-18 +--L.xfer_team_indicator = "Team" + +-- 2021-06-25 +--L.searchbar_default_placeholder = "Search in list..." + +-- 2021-07-11 +--L.spec_about_to_revive = "Spectating is limited during revival period." + +-- 2021-09-01 +--L.spawneditor_name = "Spawn Editor Tool" +--L.spawneditor_desc = "Used to place weapon, ammo and player spawns in the world. Can only be used by super admin." + +--L.spawneditor_place = "Place spawn" +--L.spawneditor_remove = "Remove spawn" +--L.spawneditor_change = "Change spawn type (hold [SHIFT] to reverse)" +--L.spawneditor_ammo_edit = "Hold on weapon spawn to edit autospawning ammo" + +--L.spawn_weapon_random = "Random Weapon Spawn" +--L.spawn_weapon_melee = "Melee Weapon Spawn" +--L.spawn_weapon_nade = "Grenade Weapon Spawn" +--L.spawn_weapon_shotgun = "Shotgun Weapon Spawn" +--L.spawn_weapon_heavy = "Heavy Weapon Spawn" +--L.spawn_weapon_sniper = "Sniper Weapon Spawn" +--L.spawn_weapon_pistol = "Pistol Weapon Spawn" +--L.spawn_weapon_special = "Special Weapon Spawn" +--L.spawn_ammo_random = "Random ammo spawn" +--L.spawn_ammo_deagle = "Deagle ammo spawn" +--L.spawn_ammo_pistol = "Pistol ammo spawn" +--L.spawn_ammo_mac10 = "Mac10 ammo spawn" +--L.spawn_ammo_rifle = "Rifle ammo spawn" +--L.spawn_ammo_shotgun = "Shotgun ammo spawn" +--L.spawn_player_random = "Random player spawn" + +--L.spawn_weapon_ammo = "(Ammo: {ammo})" + +--L.spawn_weapon_edit_ammo = "Hold [{walkkey}] and press [{primaryfire} or {secondaryfire}] to increase or decrease the ammo for this weapon spawn" + +--L.spawn_type_weapon = "This is a weapon spawn" +--L.spawn_type_ammo = "This is an ammunition spawn" +--L.spawn_type_player = "This is a player spawn" + +--L.spawn_remove = "Press [{secondaryfire}] to remove this spawn" + +--L.submenu_administration_entspawn_title = "Spawn Editor" +--L.header_entspawn_settings = "Spawn Editor Settings" +--L.button_start_entspawn_edit = "Start Spawn Edit" +--L.button_delete_all_spawns = "Delete all Spawns" + +--L.label_dynamic_spawns_enable = "Enable dynamic spawns for this map" +--L.label_dynamic_spawns_global_enable = "Enable dynamic spawns for all maps" + +--L.header_equipment_weapon_spawn_setup = "Weapon Spawn Settings" + +--L.help_spawn_editor_info = [[ +--The spawn editor is used to place, remove and edit spawns in the world. These spawns are for weapons, ammunition and players. +-- +--These spawns are saved in files located in 'data/ttt/weaponspawnscripts/'. They can be deleted for a hard reset. The initial spawn files are created from spawns found on the map and in the original TTT weapon spawn scripts. Pressing the reset button always reverts to the initial state. +-- +--It should be noted that this spawn system uses dynamic spawns. This is most interesting for weapons because it no longer defines a specific weapon, but a type of weapons. For example instead of a TTT shotgun spawn, there is now a general shotgun spawn where any weapon defined as shotgun can spawn. The spawn type for each weapon can be set in the 'Edit Equipment' menu. This makes it possible for any weapon to spawn on the map, or to disable certain default weapons. +-- +--Keep in mind that many changes only take effect after a new round has started.]] +--L.help_spawn_editor_enable = "On some maps it might be advised to use the original spawns found on the map without replacing them with the dynamic system. Changing this option below only affects the currently active map, so the dynamic system will still be used for every other map." +--L.help_spawn_editor_hint = "Hint: To leave the spawn editor, reopen the gamemode menu." +--L.help_spawn_editor_spawn_amount = [[ +--There currently are {weapon} weapon spawns, {ammo} ammunition spawns and {player} player spawns on this map. +--Click 'start spawn edit' to change this amount. +-- +--{weaponrandom}x Random weapon spawn +--{weaponmelee}x Melee weapon spawn +--{weaponnade}x Grenade weapon spawn +--{weaponshotgun}x Shotgun weapon spawn +--{weaponheavy}x Heavy weapon spawn +--{weaponsniper}x Sniper weapon spawn +--{weaponpistol}x Pistol weapon spawn +--{weaponspecial}x Special weapon spawn +-- +--{ammorandom}x Random ammo spawn +--{ammodeagle}x Deagle ammo spawn +--{ammopistol}x Pistol ammo spawn +--{ammomac10}x Mac10 ammo spawn +--{ammorifle}x Rifle ammo spawn +--{ammoshotgun}x Shotgun ammo spawn +-- +--{playerrandom}x Random player spawn]] + +--L.equipmenteditor_name_auto_spawnable = "Equipment spawns randomly in world" +--L.equipmenteditor_name_spawn_type = "Select spawn type" +--L.equipmenteditor_desc_auto_spawnable = [[ +--The TTT2 spawn system allows every weapon to spawn in the world. By default only weapons marked as 'AutoSpawnable' by the creator will spawn in the world, however this can be changed from within this menu. +-- +--Most of the equipment is set to 'special weapon spawns' by default. This means that equipment only spawns on random weapon spawns. However it is possible to place special weapon spawns in the world or change the spawn type here to use other existing spawn types.]] + +--L.pickup_error_inv_cached = "You cannot pick this up right now because your inventory is cached." + +-- 2021-09-02 +--L.submenu_administration_playermodels_title = "Player Models" +--L.header_playermodels_general = "General Player Model Settings" +--L.header_playermodels_selection = "Select Player Model Pool" + +--L.label_enforce_playermodel = "Enforce role player model" +--L.label_use_custom_models = "Use a randomly selected player model" +--L.label_prefer_map_models = "Prefer map specific models over default models" +--L.label_select_model_per_round = "Select a new random model each round (only on map change if disabled)" + +--L.help_prefer_map_models = [[ +--Some maps define their own player models. By default these models have a higher priority than those that are assigned automatically. By disabling this setting, map specific models are disabled. +-- +--Role specific models always have a higher priority and are unaffected by this setting.]] +--L.help_enforce_playermodel = [[ +--Some roles have custom player models. They can be disabled which can be relevant for compatibility with some player model selectors. +--Random default models can still be selected, if this setting is disabled.]] +--L.help_use_custom_models = [[ +--By default only the CS:S Phoenix player model is assigned to all players. By enabling this option however it is possible to select a player model pool. With this setting enabled each player will still be assigned the same player model, however it is a random model from the defined model pool. +-- +--This selection of models can be extended by installing more player models.]] + +-- 2021-10-06 +--L.menu_server_addons_title = "Server Addons" +--L.menu_server_addons_description = "Server-wide admin only settings for addons." + +--L.tooltip_finish_score_penalty_alive_teammates = "Alive teammates penalty: {score}" +--L.finish_score_penalty_alive_teammates = "Alive teammates penalty:" +--L.tooltip_kill_score_suicide = "Suicide: {score}" +--L.kill_score_suicide = "Suicide:" +--L.tooltip_kill_score_team = "Team kill: {score}" +--L.kill_score_team = "Team kill:" + +-- 2021-10-09 +--L.help_models_select = [[ +--Left click on the models to add them to the player model pool. Left click again to remove them. Right clicking toggles between enabled and disabled detective hats for the focused model. +-- +--The small indicator in the top left shows if the player model has a head hitbox. The icon below shows if this model is applicable for a detective hat.]] + +--L.menu_roles_title = "Role Settings" +--L.menu_roles_description = "Set up the spawning, equipment credits and more." + +--L.submenu_administration_roles_general_title = "General Role Settings" + +--L.header_roles_info = "Role Information" +--L.header_roles_selection = "Role Selection Parameters" +--L.header_roles_tbuttons = "Traitor Buttons Access" +--L.header_roles_credits = "Role Equipment Credits" +--L.header_roles_additional = "Additional Role Settings" +--L.header_roles_reward_credits = "Reward Equipment Credits" + +--L.help_roles_default_team = "Default team: {team}" +--L.help_roles_unselectable = "This role is not distributable. It is not considered in the role distribution process. Most of the times this means that this is a role that is manually assigned during the round through an event like a revival, a sidekick deagle or something similar." +--L.help_roles_selectable = "This role is distributable. If all criteria is met, this role is considered in the role distribution process." +--L.help_roles_credits = "Equipment credits are used to buy equipment in the shop. It mostly makes sense to give them only for those roles that have access to the shops. However, since it is possible to find credits on corpses, you can also give starting credits to roles as a reward to their killer." +--L.help_roles_selection_short = "The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role." +--L.help_roles_selection = [[ +--The role distribution per player defines the percentage of players that are assigned this role. For example, if the value is set to '0.2' every fifth player receives this role. This also means that at least 5 players are needed for this role to be distributed at all. +--Keep in mind that all of this only applies if the role is considered for distribution process. +-- +--The aforementioned role distribution has a special integration with the lower limit of players. If the role is considered for distribution and the minimum value is below the value given by the distribution factor, but the amount of players is equal or greater than the lower limit, a single player can still receive this role. The distribution process then works as usual for the second player.]] +--L.help_roles_award_info = "Some roles (if enabled in their credits settings) receive equipment credits if a certain percentage of enemies has died. Related values can be tweaked here." +--L.help_roles_award_pct = "When this percentage of enemies are dead, specific roles are awarded equipment credits." +--L.help_roles_award_repeat = "Whether the credit award is handed out multiple times. For example, if the percentage is set to '0.25', and this setting is enabled, players will be awarded credits at '25%', '50%' and '75%' dead enemies respectively." +--L.help_roles_advanced_warning = "WARNING: These are advanced settings that can completely mess up the role distribution process. When in doubt keep all values at '0'. This value means that no limits are applied and the role distribution will try to assign as many roles as possible." +--L.help_roles_max_roles = [[ +--The term roles here includes both the base roles and the subroles. By default, there is no limit on how many different roles can be assigned. However, here are two different ways to limit them. +-- +--1. Limit them by a fixed amount. +--2. Limit them by a percentage. +-- +--The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] +--L.help_roles_max_baseroles = [[ +--Base roles are only those roles others inherit from. For example, the Innocent role is a base role, while a Pharaoh is a subrole of this role. By default, there is no limit on how many different base roles can be assigned. However, here are two different ways to limit them. +-- +--1. Limit them by a fixed amount. +--2. Limit them by a percentage. +-- +--The latter is only used if the fixed amount is '0' and sets an upper limit based on the set percentage of available players.]] + +--L.label_roles_enabled = "Enable role" +--L.label_roles_min_inno_pct = "Innocent distribution per player" +--L.label_roles_pct = "Role distribution per player" +--L.label_roles_max = "Upper limit of players assigned for this role" +--L.label_roles_random = "Chance this role is distributed" +--L.label_roles_min_players = "Lower limit of players to consider distribution" +--L.label_roles_tbutton = "Role can use Traitor buttons" +--L.label_roles_credits_starting = "Starting credits" +--L.label_roles_credits_award_pct = "Credit reward percentage" +--L.label_roles_credits_award_size = "Credit reward size" +--L.label_roles_credits_award_repeat = "Credit reward repeat" +--L.label_roles_newroles_enabled = "Enable custom roles" +--L.label_roles_max_roles = "Upper role limit" +--L.label_roles_max_roles_pct = "Upper role limit by percentage" +--L.label_roles_max_baseroles = "Upper base role limit" +--L.label_roles_max_baseroles_pct = "Upper base role limit by percentage" +--L.label_detective_hats = "Enable hats for policing roles like the Detective (if player model allows to have them)" + +--L.ttt2_desc_innocent = "An Innocent has no special abilities. They have to find the evil ones among the terrorists and kill them. But they have to be careful not to kill their teammates." +--L.ttt2_desc_traitor = "The Traitor is the enemy of the Innocent. They have an equipment menu with which they are being able to buy special equipment. They have to kill everyone but their teammates." +--L.ttt2_desc_detective = "The Detective is the one whom the Innocents can trust. But who even is an Innocent? The mighty Detective has to find all the evil terrorists. The equipment in their shop may help them with this task." + +-- 2021-10-10 +--L.button_reset_models = "Reset Player Models" + +-- 2021-10-13 +--L.help_roles_credits_award_kill = "Another way of gaining credits is by killing high value players with a 'public role' such as a Detective. If the killer's role has this enabled, they gain the below defined amount of credits." +--L.help_roles_credits_award = [[ +--There are two different ways to be awarded credits in base TTT2: +-- +--1. If a certain percentage of the enemy team is dead, the whole team is awarded credits. +--2. If a player killed a high value player with a 'public role' such as a Detective, the killer is awarded credits. +-- +--Please note, that this still can be enabled/disabled for every role, even if the whole team is awarded. For example, if team Innocent is awarded, but the Innocent role has this disabled, only the Detective will receive their credits. +--The balancing values for this feature can be set in 'Administration' -> 'General Role Settings'.]] +--L.help_detective_hats = [[ +--Policing roles such as the Detective may wear hats to show their authority. They lose them on death or if damaged at the head. +-- +--Some player models do not support hats by default. This can be changed in 'Administration' -> 'Player Models']] + +--L.label_roles_credits_award_kill = "Credit reward amount for the kill" +--L.label_roles_credits_dead_award = "Enable credits award for certain percentage of dead enemies" +--L.label_roles_credits_kill_award = "Enable credits award for high value player kill" +--L.label_roles_min_karma = "Lower limit of Karma to consider distribution" + +-- 2021-11-07 +--L.submenu_administration_administration_title = "Administration" +--L.submenu_administration_voicechat_title = "Voice chat / Text chat" +--L.submenu_administration_round_setup_title = "Round Settings" +--L.submenu_administration_mapentities_title = "Map Entities" +--L.submenu_administration_inventory_title = "Inventory" +--L.submenu_administration_karma_title = "Karma" +--L.submenu_administration_sprint_title = "Sprinting" +--L.submenu_administration_playersettings_title = "Player Settings" + +--L.header_roles_special_settings = "Special Role Settings" +--L.header_equipment_additional = "Additional Equipment Settings" +--L.header_administration_general = "General Administrative Settings" +--L.header_administration_logging = "Logging" +--L.header_administration_misc = "Miscellaneous" +--L.header_entspawn_plyspawn = "Player Spawn Settings" +--L.header_voicechat_general = "General Voice chat Settings" +--L.header_voicechat_battery = "Voice chat Battery" +--L.header_voicechat_locational = "Proximity Voice chat" +--L.header_playersettings_plyspawn = "Player Spawn Settings" +--L.header_round_setup_prep = "Round: Preparing" +--L.header_round_setup_round = "Round: Active" +--L.header_round_setup_post = "Round: Post" +--L.header_round_setup_map_duration = "Map Session" +--L.header_textchat = "Text chat" +--L.header_round_dead_players = "Dead Player Settings" +--L.header_administration_scoreboard = "Scoreboard Settings" +--L.header_hud_toggleable = "Toggleable HUD Elements" +--L.header_mapentities_prop_possession = "Prop Possession" +--L.header_mapentities_doors = "Doors" +--L.header_karma_tweaking = "Karma Tweaking" +--L.header_karma_kick = "Karma Kick and Ban" +--L.header_karma_logging = "Karma Logging" +--L.header_inventory_gernal = "Inventory Size" +--L.header_inventory_pickup = "Inventory Weapon Pickup" +--L.header_sprint_general = "Sprint Settings" +--L.header_playersettings_armor = "Armor System Settings" + +--L.help_killer_dna_range = "When a player is killed by another player, a DNA sample is left on their body. The setting below defines the maximum distance in hammer units for DNA samples to be left. If the killer is further away than this value when the victim dies, no sample will be left on the corpse." +--L.help_killer_dna_basetime = "The base time in seconds until a DNA sample decays, if the killer is 0 Hammer units away. The farther the killer is, the less time will be given to the DNA sample to decay." +--L.help_dna_radar = "The TTT2 DNA scanner shows the exact distance and direction of the selected DNA sample if equipped. However, there is also a classic DNA scanner mode that updates the selected sample with an in-world rendering every time the cooldown has passed." +--L.help_idle = "The idle mode is used to forcefully move idle players into the spectator mode. To leave this mode, they will have to disable it in their 'gameplay' menu." +--L.help_namechange_kick = [[ +--A name change during an active round could be abused. Therefore, this is prohibited by default and will lead to the offending player being kicked from the server. +-- +--If the bantime is greater than 0, the player will be unable to reconnect to the server until that time has passed.]] +--L.help_damage_log = "Each time a player is damaged, a damage log entry is added to the console if enabled. This can also be stored to disk after a round has ended. The file is located at 'data/terrortown/logs/'" +--L.help_spawn_waves = [[ +--If this variable is set to 0, all players are spawned at once. For servers with huge amounts of players, it can be beneficial to spawn the players in waves. The spawn wave interval is the time between each spawn wave. A spawn wave always spawns as many players as there are valid spawn points. +-- +--Note: Make sure that the preparing time is long enough for the desired amount of spawn waves.]] +--L.help_voicechat_battery = [[ +--Voice chatting with enabled voice chat battery reduces battery charge. When it's empty, the player can't use voice chat and has to wait for it to recharge. This can help to prevent excessive voice chat usage. +-- +--Note: 'Tick' refers to a game tick. For example, if the tick rate is set to 66, then it will be 1/66th of a second.]] +--L.help_ply_spawn = "Player settings that are used on player (re-)spawn." +--L.help_haste_mode = [[ +--Haste mode balances the game by increasing the round time with every dead player. Only roles that see missing in action players can see the real round time. Every other role can only see the haste mode starting time. +-- +--If haste mode is enabled, the fixed round time is ignored.]] +--L.help_round_limit = "After one of the set limit conditions is met, a map change is triggered." +--L.help_armor_balancing = "The following values can be used to balance the armor." +--L.help_item_armor_classic = "If classic armor mode is enabled, only the previous settings matter. Classic armor mode means that a player can only buy armor once in a round, and that this armor blocks 30% of the incoming bullet and crowbar damage until they die." +--L.help_item_armor_dynamic = [[ +--Dynamic armor is the TTT2 approach to make armor more interesting. The amount of armor that can be bought is now unlimited, and the armor value stacks. Getting damaged decreases the armor value. The armor value per bought armor item is set in the 'Equipment Settings' of said item. +-- +--When taking damage, a certain percentage of this damage is converted into armor damage, a different percentage is still applied to the player and the rest vanishes. +-- +--If reinforced armor is enabled, the damage applied to the player is decreased by 15% as long as the armor value is above the reinforcement threshold.]] +--L.help_sherlock_mode = "The sherlock mode is the classic TTT mode. If the sherlock mode is disabled, dead bodies can not be confirmed, the scoreboard shows everyone as alive and the spectators can talk to the living players." +--L.help_prop_possession = [[ +--Prop possession can be used by spectators to possess props lying in the world and use the slowly recharging 'punch-o-meter' to move said prop around. +-- +--The maximum value of the 'punch-o-meter' consists of a possession base value, where the kills/deaths difference clamped inbetween two defined limits is added. The meter slowly recharges over time. The set recharge time is the time needed to recharge a single point in the 'punch-o-meter'.]] +--L.help_karma = "Players start with a certain amount of Karma, and lose it when they damage/kill teammates. The amount they lose is dependent on the Karma of the person they hurt or killed. Lower Karma reduces damage given." +--L.help_karma_strict = "If strict Karma is enabled, the damage penalty increases more quickly as Karma goes down. When it is off, the damage penalty is very low when people stay above 800. Enabling strict mode makes Karma play a larger role in discouraging any unnecessary kills, while disabling it results in a more “loose” game where Karma only hurts players who constantly kill teammates." +--L.help_karma_max = "Setting the value of the max Karma above 1000 doesn't give a damage bonus to players with more than 1000 Karma. It can be used as a Karma buffer." +--L.help_karma_ratio = "The ratio of the damage that is used to compute how much of the victim's Karma is subtracted from the attacker's if both are in the same team. If a team kill happens, a further penalty is applied." +--L.help_karma_traitordmg_ratio = "The ratio of the damage that is used to compute how much of the victim's Karma is added to the attacker's if both are in different teams. If an enemy kill happens, a further bonus is applied." +--L.help_karma_bonus = "There are also two different passive ways to gain Karma during a round. First is a karma restoration which applied to every player at the round end. Then a secondary clean round bonus is given if no teammates were hurt or killed by a player." +--L.help_karma_clean_half = [[ +--When a player's Karma is above the starting level (meaning the Karma max has been configured to be higher than that), all their Karma increases will be reduced based on how far their Karma is above that starting level. So it goes up slower the higher it is. +-- +--This reduction goes in a curve of exponential decay: initially it's fast, and it slows down as the increment gets smaller. This convar sets at what point the bonus has been halved (so the half-life). With the default value of 0.25, if the starting amount of Karma is 1000 and the max 1500, and a player has Karma 1125 ((1500 - 1000) * 0.25 = 125), then his clean round bonus will be 30 / 2 = 15. So to make the bonus go down faster you’d set this convar lower, to make it go down slower you’d increase it towards 1.]] +--L.help_max_slots = "Sets the maximum amount of weapons per slot. '-1' means that there is no limit." +--L.help_item_armor_value = "This is the armor value given by the armor item in dynamic mode. If classic mode is enabled (see 'Administration' -> 'Player Settings') then every value greater than 0 is counted as existing armor." + +--L.label_killer_dna_range = "Max kill range to leave DNA" +--L.label_killer_dna_basetime = "Sample life base time" +--L.label_dna_scanner_slots = "DNA sample slots" +--L.label_dna_radar = "Enable classic DNA scanner mode" +--L.label_dna_radar_cooldown = "DNA scanner cooldown" +--L.label_radar_charge_time = "Recharge time after being used" +--L.label_crowbar_shove_delay = "Cooldown after crowbar push" +--L.label_idle = "Enable idle mode" +--L.label_idle_limit = "Maximum idle time in seconds" +--L.label_namechange_kick = "Enable name change kick" +--L.label_namechange_bantime = "Banned time in minutes after kick" +--L.label_log_damage_for_console = "Enable damage logging in console" +--L.label_damagelog_save = "Save damage log to disk" +--L.label_debug_preventwin = "Prevent any win condition [debug]" +--L.label_bots_are_spectators = "Bots are always spectators" +--L.label_tbutton_admin_show = "Show traitor buttons to admins" +--L.label_ragdoll_carrying = "Enable ragdoll carrying" +--L.label_prop_throwing = "Enable prop throwing" +--L.label_weapon_carrying = "Enable weapon carrying" +--L.label_weapon_carrying_range = "Weapon carry range" +--L.label_prop_carrying_force = "Prop pickup force" +--L.label_teleport_telefrags = "Kill blocking player(s) when teleporting (telefrag)" +--L.label_allow_discomb_jump = "Allow disco jump for grenade thrower" +--L.label_spawn_wave_interval = "Spawn wave interval in seconds" +--L.label_voice_enable = "Enable voice chat" +--L.label_voice_drain = "Enable the voice chat battery feature" +--L.label_voice_drain_normal = "Drain per tick for normal players" +--L.label_voice_drain_admin = "Drain per tick for admins and public policing roles" +--L.label_voice_drain_recharge = "Recharge rate per tick of not voice chatting" +--L.label_locational_voice = "Enable proximity voice chat for living players" +--L.label_armor_on_spawn = "Player armor on (re-)spawn" +--L.label_prep_respawn = "Enable instant respawn during preparing phase" +--L.label_preptime_seconds = "Preparing time in seconds" +--L.label_firstpreptime_seconds = "First preparing time in seconds" +--L.label_roundtime_minutes = "Fixed round time in minutes" +--L.label_haste = "Enable haste mode" +--L.label_haste_starting_minutes = "Haste mode starting time in minutes" +--L.label_haste_minutes_per_death = "Additional time in minutes per death" +--L.label_posttime_seconds = "Postround time in seconds" +--L.label_round_limit = "Upper limit of rounds" +--L.label_time_limit_minutes = "Upper limit of playtime in minutes" +--L.label_nade_throw_during_prep = "Enable grenade throwing during preparing time" +--L.label_postround_dm = "Enable deathmatch after round ended" +--L.label_session_limits_enabled = "Enable session limits" +--L.label_spectator_chat = "Enable spectators chatting with everybody" +--L.label_lastwords_chatprint = "Print last words to chat if killed while typing" +--L.label_identify_body_woconfirm = "Identify corpse without pressing the 'confirm' button" +--L.label_announce_body_found = "Announce that a body was found when the body was confirmed" +--L.label_confirm_killlist = "Announce kill list of confirmed corpse" +--L.label_dyingshot = "Shoot on death if in ironsights [experimental]" +--L.label_armor_block_headshots = "Enable armor blocking headshots" +--L.label_armor_block_blastdmg = "Enable armor blocking blast damage" +--L.label_armor_dynamic = "Enable dynamic armor" +--L.label_armor_value = "Amount of armor given by the armor item" +--L.label_armor_damage_block_pct = "Damage percentage taken by armor" +--L.label_armor_damage_health_pct = "Damage percentage taken by player" +--L.label_armor_enable_reinforced = "Enable reinforced armor" +--L.label_armor_threshold_for_reinforced = "Reinforced armor threshold" +--L.label_sherlock_mode = "Enable sherlock mode" +--L.label_highlight_admins = "Highlight server admins" +--L.label_highlight_dev = "Highlight TTT2 developer" +--L.label_highlight_vip = "Highlight TTT2 supporter" +--L.label_highlight_addondev = "Highlight TTT2 addon developer" +--L.label_highlight_supporter = "Highlight others" +--L.label_enable_hud_element = "Enable {elem} HUD element" +--L.label_spec_prop_control = "Enable prop possession" +--L.label_spec_prop_base = "Possession base value" +--L.label_spec_prop_maxpenalty = "Lower possession bonus limit" +--L.label_spec_prop_maxbonus = "Upper possession bonus limit" +--L.label_spec_prop_force = "Possession push force" +--L.label_spec_prop_rechargetime = "Recharge time in seconds" +--L.label_doors_force_pairs = "Force close-by doors as double doors" +--L.label_doors_destructible = "Enable destructible doors" +--L.label_doors_locked_indestructible = "Initially locked doors are indestructible" +--L.label_doors_health = "Door health" +--L.label_doors_prop_health = "Destructed door health" +--L.label_minimum_players = "Minimum player amount to start round" +--L.label_karma = "Enable Karma" +--L.label_karma_strict = "Enable strict Karma" +--L.label_karma_starting = "Starting Karma" +--L.label_karma_max = "Maximum Karma" +--L.label_karma_ratio = "Penalty ratio for team damage" +--L.label_karma_kill_penalty = "Kill penalty for team kill" +--L.label_karma_round_increment = "Karma restoration" +--L.label_karma_clean_bonus = "Clean round bonus" +--L.label_karma_traitordmg_ratio = "Bonus ratio for enemy damage" +--L.label_karma_traitorkill_bonus = "Kill bonus for enemy kill" +--L.label_karma_clean_half = "Clean round bonus reduction" +--L.label_karma_persist = "Karma persists over map changes" +--L.label_karma_low_autokick = "Automatically kick players with low Karma" +--L.label_karma_low_amount = "Low Karma threshold" +--L.label_karma_low_ban = "Ban picked players with low Karma" +--L.label_karma_low_ban_minutes = "Ban time in minutes" +--L.label_karma_debugspam = "Enable debug output to console about Karma changes" +--L.label_max_melee_slots = "Max melee slots" +--L.label_max_secondary_slots = "Max secondary slots" +--L.label_max_primary_slots = "Max primary slots" +--L.label_max_nade_slots = "Max grenade slots" +--L.label_max_carry_slots = "Max carry slots" +--L.label_max_unarmed_slots = "Max unarmed slots" +--L.label_max_special_slots = "Max special slots" +--L.label_max_extra_slots = "Max extra slots" +--L.label_weapon_autopickup = "Enable automatic weapon pickup" +--L.label_sprint_enabled = "Enable sprinting" +--L.label_sprint_max = "Max sprinting stamina" +--L.label_sprint_stamina_consumption = "Stamina consumption factor" +--L.label_sprint_stamina_regeneration = "Stamina regeneration factor" +--L.label_crowbar_unlocks = "Primary attack can be used as interaction (i.e. unlocking)" +--L.label_crowbar_pushforce = "Crowbar push force" + +-- 2022-07-02 +--L.header_playersettings_falldmg = "Fall Damage Settings" + +--L.label_falldmg_enable = "Enable fall damage" +--L.label_falldmg_min_velocity = "Minimum velocity threshold for fall damage to occur" +--L.label_falldmg_exponent = "Exponent to increase fall damage in relation to velocity" + +--L.help_falldmg_exponent = [[ +--This value modifies how exponentially fall damage is increased with the speed the player hits the ground at. +-- +--Take care when altering this value. Setting it too high can make even the smallest falls lethal, while setting it too low will allow players to fall from extreme heights and suffer little to no damage.]] + +-- 2023-02-08 +--L.testpopup_title = "A Test Popup, now with a multiline title, how NICE!" +--L.testpopup_subtitle = "Well, hello there! This is a fancy popup with some special information. The text can be also multiline, how fancy! Ugh, I could add so much more text if I'd had any ideas..." + +--L.hudeditor_chat_hint1 = "[TTT2][INFO] Hover over an element, press and hold [LMB] and move the mouse to MOVE or RESIZE it." +--L.hudeditor_chat_hint2 = "[TTT2][INFO] Press and hold the ALT key for symmetric resizing." +--L.hudeditor_chat_hint3 = "[TTT2][INFO] Press and hold the SHIFT key to move on axis and to keep the aspect ratio." +--L.hudeditor_chat_hint4 = "[TTT2][INFO] Press [RMB] -> 'Close' to exit the HUD Editor!" + +--L.guide_nothing_title = "Nothing here yet!" +--L.guide_nothing_desc = "This is work in progress, help us by contributing to the project on GitHub." + +--L.sb_rank_tooltip_developer = "TTT2 Developer" +--L.sb_rank_tooltip_vip = "TTT2 Supporter" +--L.sb_rank_tooltip_addondev = "TTT2 Addon Developer" +--L.sb_rank_tooltip_admin = "Server Admin" +--L.sb_rank_tooltip_streamer = "Streamer" +--L.sb_rank_tooltip_heroes = "TTT2 Heroes" +--L.sb_rank_tooltip_team = "Team" + +--L.tbut_adminarea = "ADMIN AREA:" + +-- 2023-08-10 +--L.equipmenteditor_name_damage_scaling = "Damage Scaling" + +-- 2023-08-11 +--L.equipmenteditor_name_allow_drop = "Allow Drop" +--L.equipmenteditor_desc_allow_drop = "If enabled, the equipment can be dropped freely by the player." + +--L.equipmenteditor_name_drop_on_death_type = "Drop on Death" +--L.equipmenteditor_desc_drop_on_death_type = "Attempt overriding the action taken for whether the equipment is dropped on player's death." + +--L.drop_on_death_type_default = "Default (weapon-defined)" +--L.drop_on_death_type_force = "Force Drop on Death" +--L.drop_on_death_type_deny = "Deny Drop on Death" + +-- 2023-08-26 +--L.equipmenteditor_name_kind = "Equipment Slot" +--L.equipmenteditor_desc_kind = "The inventory slot the equipment will occupy." + +--L.slot_weapon_melee = "Melee Slot" +--L.slot_weapon_pistol = "Pistol Slot" +--L.slot_weapon_heavy = "Heavy Slot" +--L.slot_weapon_nade = "Grenade Slot" +--L.slot_weapon_carry = "Carry Slot" +--L.slot_weapon_unarmed = "Unarmed Slot" +--L.slot_weapon_special = "Special Slot" +--L.slot_weapon_extra = "Extra Slot" +--L.slot_weapon_class = "Class Slot" + +-- 2023-10-04 +--L.label_voice_duck_spectator = "Duck spectator voices" +--L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +--L.label_voice_scaling = "Voice Volume Scaling Mode" +--L.label_voice_scaling_mode_linear = "Linear" +--L.label_voice_scaling_mode_power4 = "Power 4" +--L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "Результати Огляду Тіла - {player}" +L.search_info = "Інформація" +L.search_confirm = "Підтвердити Смерть" +--L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +--L.search_take_credits = "Take {credits} Credit(s)" +--L.search_confirm_forbidden = "Confirm forbidden" +--L.search_confirmed = "Death Confirmed" +L.search_call = "Викликати Детектива" +--L.search_called = "Death Reported" + +--L.search_team_role_unknown = "???" + +L.search_words = "Щось вам підказує, що одними з останніх слів цієї особи були: '{lastwords}'" +L.search_armor = "Ця особа носила особливий бронежилет." +--L.search_disguiser = "They were carrying a device that could hide their identity." +L.search_radar = "Ця особа мала деякий прилад, схожий на радар. Цей пристрій більше не працює." +L.search_c4 = "У кишені ви знайшли нотатку. Воно затверджує, що вам необхідно обрізати провід {num}, щоб безпечно знешкодити вибухівку." + +L.search_dmg_crush = "Багато кісток цієї особи зламані. Схоже на те, що удар важкого предмета вбив її." +L.search_dmg_bullet = "Очевидно, що цю особу розстріляли." +L.search_dmg_fall = "Ця особа розбилася об землю." +L.search_dmg_boom = "Рани та обпалений одяг цієї особи вказують на те, що її смерть є наслідком від вибуху." +L.search_dmg_club = "Тіло в синцях і побоях. Цю особу неодмінно забили до смерті." +L.search_dmg_drown = "Тіло має ознаки утоплення." +L.search_dmg_stab = "Ця особа отримала ножові поранення і порізи, перш ніж встигла стекти кров." +L.search_dmg_burn = "Пахне смаженим терористом десь поряд..." +--L.search_dmg_teleport = "It looks like their DNA was scrambled by tachyon emissions!" +L.search_dmg_car = "Коли цей терорист переходив дорогу, на нього наїхав безрозсудний водій." +L.search_dmg_other = "Ви не можете визначити конкретну причину смерті цього терориста." + +--L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +--L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +--L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +--L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +--L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +--L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +--L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +--L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +--L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +--L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +--L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +--L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +--L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +--L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +--L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +--L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +--L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +--L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +--L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +--L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +--L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +--L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +--L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +--L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +--L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "Ви дослідили, що для вбивства було використано {weapon}." +L.search_head = "Смертельний постріл – постріл у голову. Навіть й немає часу закричати" +--L.search_time = "They died a while before you conducted the search." +--L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "Ви знайшли список вбивств, який підтверджує смерть {player}." +L.search_kills2 = "Ви знайшли список вбивств із цими іменами: {player}" +L.search_eyes = "Використовуючи свої навички детектива, ви дізналися останнього, кого бачила ця особа: {player}. Убивця чи збіг обставин?" + +--L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +--L.search_kill_distance_point_blank = "It was a point blank attack." +--L.search_kill_distance_close = "The attack came from a short distance." +--L.search_kill_distance_far = "The victim was attacked from a long distance away." + +--L.search_kill_from_front = "The victim was shot from the front." +--L.search_kill_from_back = "The victim was shot from behind." +--L.search_kill_from_side = "The victim was shot from the side." + +--L.search_hitgroup_head = "The projectile was found in their head." +--L.search_hitgroup_chest = "The projectile was found in their chest." +--L.search_hitgroup_stomach = "The projectile was found in their stomach." +--L.search_hitgroup_rightarm = "The projectile was found in their right arm." +--L.search_hitgroup_leftarm = "The projectile was found in their left arm." +--L.search_hitgroup_rightleg = "The projectile was found in their right leg." +--L.search_hitgroup_leftleg = "The projectile was found in their left leg." +--L.search_hitgroup_gear = "The projectile was found in their hip." + +--L.search_policingrole_report_confirm = [[ +--A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +--L.search_policingrole_confirm_disabled_1 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +--L.search_policingrole_confirm_disabled_2 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know! +--You can see the information in here after they confirmed it.]] +--L.search_spec = [[ +--As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +--L.search_title_words = "Victim's last words" +--L.search_title_c4 = "Defusion mishap" +--L.search_title_dmg_crush = "Crush damage ({amount} HP)" +--L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +--L.search_title_dmg_fall = "Fall damage ({amount} HP)" +--L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +--L.search_title_dmg_club = "Club damage ({amount} HP)" +--L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +--L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +--L.search_title_dmg_burn = "Burning damage ({amount} HP)" +--L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +--L.search_title_dmg_car = "Car accident ({amount} HP)" +--L.search_title_dmg_other = "Unknown damage ({amount} HP)" +--L.search_title_time = "Death time" +--L.search_title_dna = "DNA sample decay" +--L.search_title_kills = "The victim's kill list" +--L.search_title_eyes = "The killer's shadow" +--L.search_title_floor = "Floor of the crime scene" +--L.search_title_credits = "{credits} Equipment credit(s)" +--L.search_title_water = "Water level {level}" +--L.search_title_policingrole_report_confirm = "Confirm to report death" +--L.search_title_policingrole_confirm_disabled = "Report corpse" +--L.search_title_spectator = "You are a spectator" + +--L.target_credits_on_confirm = "Confirm to receive unspent credits" +--L.target_credits_on_search = "Search to receive unspent credits" +--L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +--L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +--L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +--L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +--L.label_inspect_confirm_mode = "Select body search mode" +--L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +--L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +--L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +--L.help_inspect_confirm_mode = [[ +--There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. +-- +--mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. +-- +--mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. +-- +--mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +--L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +--L.header_hud_elements_customize = "Customize the HUD-Elements" +--L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +--L.help_keyhelp = [[ +--Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: +-- +--Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +--Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +--Equipment: Some equipment items have their own bindings, these are shown in this category. +-- +--Disabled categories are still shown when the scoreboard is visible]] + +--L.label_keyhelp_show_core = "Enable always showing the core bindings" +--L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +--L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +--L.header_interface_keys = "Key helper settings" +--L.header_interface_wepswitch = "Weapon switch UI settings" + +--L.label_keyhelper_help = "open gamemode menu" +--L.label_keyhelper_mutespec = "cycle spectator voice mode" +--L.label_keyhelper_shop = "open equipment shop" +--L.label_keyhelper_show_pointer = "free mouse pointer" +--L.label_keyhelper_possess_focus_entity = "possess focused entity" +--L.label_keyhelper_spec_focus_player = "spectate focused player" +--L.label_keyhelper_spec_previous_player = "previous player" +--L.label_keyhelper_spec_next_player = "next player" +--L.label_keyhelper_spec_player = "spectate random player" +--L.label_keyhelper_possession_jump = "prop: jump" +--L.label_keyhelper_possession_left = "prop: left" +--L.label_keyhelper_possession_right = "prop: right" +--L.label_keyhelper_possession_forward = "prop: forward" +--L.label_keyhelper_possession_backward = "prop: backward" +--L.label_keyhelper_free_roam = "leave object and roam free" +--L.label_keyhelper_flashlight = "toggle flashlight" +--L.label_keyhelper_quickchat = "open quickchat" +--L.label_keyhelper_voice_global = "global voice chat" +--L.label_keyhelper_voice_team = "team voice chat" +--L.label_keyhelper_chat_global = "global chat" +--L.label_keyhelper_chat_team = "team chat" +--L.label_keyhelper_show_all = "show all" +--L.label_keyhelper_disguiser = "toggle disguiser" +--L.label_keyhelper_save_exit = "save and exit" +--L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +--L.item_armor_reinforced = "Reinforced Armor" +--L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +--L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +--L.status_speed_name = "Speed Multiplier" +--L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +--L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +--L.status_on = "on" +--L.status_off = "off" + +--L.crowbar_help_primary = "Attack" +--L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +--L.help_HUD_enable_description = [[ +--Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +--L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +--L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +--L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +--L.header_soundeffect_settings = "Sound Effects" +--L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +--L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +--L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +--L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +--L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +--L.help_prop_spec_dash = [[ +--Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. +-- +--This variable is a multiplier of the push force.]] +--L.label_spec_prop_dash = "Dash force multiplier" +--L.label_keyhelper_possession_dash = "prop: dash in view direction" +--L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +--L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +--L.c4_help_primary = "Place the C4" +--L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +--L.magneto_help_primary = "Push entity" +--L.magneto_help_secondary = "Pull / pickup entity" +--L.knife_help_primary = "Stab" +--L.knife_help_secondary = "Throw knife" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +--L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +--L.newton_force = "FORCE" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +--L.beacon_help_pri = "Throw Beacon on the ground" +--L.beacon_help_sec = "Stick Beacon to surface" +--L.beacon_name = "Beacon" +--L.beacon_desc = [[ +--Broadcasts player locations to everyone in a sphere around this beacon. +-- +--Use to keep track of locations on the map that are hard to see.]] + +--L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +--L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +--L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +--L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +--L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder} підтверджує смерть {victim}." +--L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +--L.builtin_marker = "Built-in." +--L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +--L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +--L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +--L.submenu_gameplay_accessibility_title = "Accessibility" + +--L.header_accessibility_settings = "Accessibility Settings" + +--L.label_enable_dynamic_fov = "Enable dynamic FOV change" +--L.label_enable_bobbing = "Enable view bobbing" +--L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +--L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +--L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +--L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +--For a shotgun, this would affect each pellet. +--For a rifle, this would affect just the bullet. +--For the poltergeist, this would affect each "thump" and the final explosion. +-- +--0.5 = Deal half the amount of damage. +--2 = Deal twice the amount of damage. +-- +--Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +--L.binoc_help_reload = "Clear target." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +--L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +--L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +--L.label_crosshair_mode = "Crosshair mode" +--L.label_crosshair_static_length = "Enable static crosshair line length" + +--L.choice_crosshair_mode_0 = "Lines and dot" +--L.choice_crosshair_mode_1 = "Lines only" +--L.choice_crosshair_mode_2 = "Dot only" + +--L.help_crosshair_scale_enable = [[ +--Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. +-- +--If the line length is kept static, only the gap scales with cone changes.]] + +--L.header_weapon_settings = "Weapon Settings" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +--L.decoy_help_primary = "Throw Decoy on the ground" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/zh_hans.lua b/lua/terrortown/lang/zh_hans.lua index 8277af843..98691886b 100644 --- a/lua/terrortown/lang/zh_hans.lua +++ b/lua/terrortown/lang/zh_hans.lua @@ -1,4 +1,4 @@ --- Simplified Chinese language strings (by 8z & TEGTianFan & 波と一緒に漂う) +-- Simplified Chinese language strings (by 8Z & TEGTianFan) local L = LANG.CreateLanguage("zh_hans") @@ -34,8 +34,8 @@ L.round_traitors_one = "叛徒,你将孤身奋斗。" L.round_traitors_more = "叛徒,你的队友是:{names}" L.win_time = "时间用尽,叛徒失败了。" -L.win_traitor = "叛徒取得了胜利!" -L.win_innocent = "叛徒们被击败了!" +L.win_traitors = "叛徒取得了胜利!" +L.win_innocents = "叛徒们被击败了!" L.win_nones = "无人胜出!(平局)" L.win_showreport = "来看一下 {num} 秒的回合总结吧!" @@ -60,10 +60,8 @@ L.body_found_traitor = "他是一位叛徒!" L.body_found_det = "他是一位探长。" L.body_found_inno = "他是一位无辜者。" -L.body_confirm = "{finder} 确认了 {victim} 的死亡。" - L.body_call = "{player} 请求探长前来检查 {victim} 的尸体!" -L.body_call_error = "你必须先确定该玩家的死才能呼叫探长!" +L.body_call_error = "你必须先确认玩家死亡后才能呼叫探长!" L.body_burning = "好烫!这个尸体着火了!" L.body_credits = "你在尸体上找到 {num} 积分!" @@ -108,8 +106,8 @@ L.disg_menutitle = "伪装器控制" L.disg_not_owned = "你没有伪装器!" L.disg_enable = "执行伪装" -L.disg_help1 = "伪装开启后,别人瞄准你时将不会看见你的名字,生命以及人品。除此之外,你也能躲避探长的雷达。" -L.disg_help2 = "可直接在主选单外,使用数字键来切换伪装。你也可以用控制台指令绑定指令 ttt_toggle_disguise。" +L.disg_help1 = "伪装开启后,别人瞄准你时将不会看见你的名字,生命值以及人品。除此之外,你也能躲避探长的雷达。" +L.disg_help2 = "按下小键盘回车键可不通过菜单直接切换伪装。你也可以使用控制台把另一个键位绑定'ttt_toggle_disguise'。" -- Radar tab in equipment menu L.radar_name = "雷达" @@ -172,46 +170,6 @@ L.quick_disg = "伪装着的人" L.quick_corpse = "一具未搜索过的尸体" L.quick_corpse_id = " {player} 的尸体" --- Body search window -L.search_title = "尸体搜索结果" -L.search_info = "信息" -L.search_confirm = "确认死亡" -L.search_call = "呼叫探长" - --- Descriptions of pieces of information found -L.search_nick = "这是 {player} 的尸体。" - -L.search_role_traitor = "这个人是叛徒!" -L.search_role_det = "这个人是探长。" -L.search_role_inno = "这个人是无辜的恐怖分子。" - -L.search_words = "直觉告诉你这个人的遗言是: {lastwords}" -L.search_armor = "他穿着非标准装甲。" -L.search_disg = "他持有一个能隐匿身份的设备" -L.search_radar = "他持有像是雷达的装备,已经无法使用了。" -L.search_c4 = "你在他口袋中找到了一本笔记。记载着第 {num} 根线才能解除炸弹。" - -L.search_dmg_crush = "他多处骨折。看起来是某种重物的冲击撞死了他。" -L.search_dmg_bullet = "他很明显是被射杀身亡的。" -L.search_dmg_fall = "他是坠落身亡的。" -L.search_dmg_boom = "他的伤口以及烧焦的衣物,应是爆炸导致其死亡。" -L.search_dmg_club = "他的身体有许多擦伤打击痕迹,明显是被殴打致死的。" -L.search_dmg_drown = "他身上的蛛丝马迹显示是溺死的。" -L.search_dmg_stab = "他是被刺击与挥砍后,迅速失血致死的。" -L.search_dmg_burn = "闻起来像烧焦的恐怖分子.." -L.search_dmg_tele = "看起来他的DNA以超光速粒子之形式散乱在附近。" -L.search_dmg_car = "他穿越马路时被一个粗心的驾驶碾死了。" -L.search_dmg_other = "你无法找到这恐怖份子的具体死因。" - -L.search_weapon = "死者是被 {weapon} 所杀。" -L.search_head = "最后一击打在头上。完全没机会叫喊。" -L.search_time = "他大约在你搜索前的 {time} 死亡。" -L.search_dna = "用DNA扫描器检索凶手的DNA标本,DNA样本大约在 {time} 前开始衰退。" - -L.search_kills1 = "你找到一个名单,记载着他发现的死者: {player}" -L.search_kills2 = "你找到了一个名单,记载着他杀的这些人:" -L.search_eyes = "透过你的探查技能,你确信他临死前见到的最后一个人是 {player}。凶手,还是巧合?" - -- Scoreboard L.sb_playing = "你正在玩的服务器是..." L.sb_mapchange = "地图将于 {num} 个回合或是 {time} 后更换。" @@ -233,7 +191,7 @@ L.sb_tag_avoid = "躲避" L.sb_tag_kill = "死亡" L.sb_tag_miss = "失踪" --- Equipment actions, like buying and dropping +-- Equipment actions, like buying and dropping L.buy_no_stock = "无法购买此装备:你已拥有它了。" L.buy_pending = "你已订购此装备,请等待配送。" L.buy_received = "你已收到此装备。" @@ -267,7 +225,6 @@ L.item_disg_desc = [[ 需要启用时,使用本页面的伪装菜单或按下小键盘回车键。]] -- C4 -L.c4_hint = "按下 {usekey} 来安放或拆除C4。" L.c4_disarm_warn = "你所安放的C4已被拆除。" L.c4_armed = "C4安放成功。" L.c4_disarmed = "你成功拆除了C4。" @@ -288,6 +245,7 @@ L.c4_remove_destroy2 = "确认:销毁" L.c4_disarm = "拆除C4" L.c4_disarm_cut = "点击以剪断 {num} 号引线" +L.c4_disarm_t = "剪断引线以拆除C4。你是叛徒,因此每条引线都是安全的,但其他人可就没那么容易了!" L.c4_disarm_owned = "剪断引线以拆除C4。你是安放此C4的人,任何引线都能成功拆除。" L.c4_disarm_other = "剪断正确的引线以拆除C4。如果你剪错的话,后果不堪设想!" @@ -296,7 +254,6 @@ L.c4_status_disarmed = "拆除" -- Visualizer L.vis_name = "显像器" -L.vis_hint = "按下 {usekey} 键捡起它(仅限侦探)。" L.vis_desc = [[ 可让犯罪现场显像化的仪器。 @@ -305,7 +262,6 @@ L.vis_desc = [[ -- Decoy L.decoy_name = "雷达诱饵" -L.decoy_no_room = "你无法携带雷达诱饵。" L.decoy_broken = "你的雷达诱饵被摧毁了!" L.decoy_short_desc = "这个诱饵会为其他阵营显示一个假雷达信号" @@ -316,12 +272,11 @@ L.decoy_desc = [[ -- Defuser L.defuser_name = "拆弹器" -L.defuser_help = "{primaryfire} 拆除目标炸弹。" L.defuser_desc = [[ 迅速拆除一个C4。 -不限制使用次数。若你持有此设备,拆除C4时会轻松许多。]] +不限制使用次数。若你持有此装备,拆除C4时会轻松许多。]] -- Flare gun L.flare_name = "信号枪" @@ -335,7 +290,6 @@ L.flare_desc = [[ L.hstation_name = "医疗站" L.hstation_broken = "你的医疗站被摧毁了!" -L.hstation_help = "{primaryfire} 安放了一个医疗站。" L.hstation_desc = [[ 安放后,允许人们用其治疗自己。 @@ -359,7 +313,6 @@ L.polter_desc = [[ -- Radio L.radio_broken = "你的收音机已被摧毁!" -L.radio_help_pri = "{primaryfire} 安放了收音机。" L.radio_desc = [[ 播放音效来误导或欺骗玩家。 @@ -405,7 +358,7 @@ L.dna_killer = "成功采集到凶手的DNA样本!" L.dna_duplicate = "匹配!你的扫描仪里已经有这个DNA样本了。" L.dna_no_killer = "DNA样本无法检索(凶手已离线?)" L.dna_armed = "炸弹已启动!赶紧拆除它!" -L.dna_object = "在目标上采集到 {num} 个新DNA样本。" +L.dna_object = "从目标上采集了最后一位所有者的样本" L.dna_gone = "区域内没侦测到可采集之DNA样本。" L.dna_desc = [[ @@ -421,7 +374,7 @@ L.magnet_help = "{primaryfire} 将其定在墙上。" L.grenade_smoke = "烟雾弹" L.grenade_fire = "燃烧弹" -L.unarmed_name = "无武装" +L.unarmed_name = "收起武器" L.crowbar_name = "撬棍" L.pistol_name = "手枪" L.rifle_name = "狙击枪" @@ -439,15 +392,15 @@ L.tele_no_mark = "标记传送地点后才能传送。" L.tele_no_mark_ground = "站在地面上才能标记传送地点!" L.tele_no_mark_crouch = "站起来才能标记传送点!" -L.tele_help_pri = "{primaryfire} 传送到已标记的传送地点。" -L.tele_help_sec = "{scondaryfire} 标记传送地点。" +L.tele_help_pri = "传送到标记位置" +L.tele_help_sec = "标记当前位置" L.tele_desc = [[ 可以传送到先前标记的地点。 传送器会产生噪音,而且使用次数是有限的。]] --- Ammo names, shown when picked up +-- Ammo names, shown when picked up L.ammo_pistol = "手枪弹药" L.ammo_smg1 = "冲锋枪弹药" @@ -463,7 +416,7 @@ L.round_prep = "准备中" L.round_active = "进行中" L.round_post = "回合结束" --- Health, ammo and time area +-- Health, ammo and time area L.overtime = "加时" L.hastemode = "急速模式" @@ -474,7 +427,7 @@ L.hp_wounded = "受伤" L.hp_badwnd = "重伤" L.hp_death = "濒死" --- TargetID karma status +-- TargetID Karma status L.karma_max = "良好" L.karma_high = "粗鲁" L.karma_med = "不可靠" @@ -483,14 +436,13 @@ L.karma_min = "滥杀者" -- TargetID misc L.corpse = "尸体" -L.corpse_hint = "按下 [{usekey}] 来搜索,用 [{walkkey} + {usekey}] 进行无声搜索。" +L.corpse_hint = "按下 [{usekey}] 进行搜索并确认。按下 [{walkkey} + {usekey}] 进行隐秘搜索。" L.target_disg = "(伪装状态)" L.target_unid = "未确认的尸体" +L.target_unknown = "一名恐怖分子" -L.target_credits = "搜索尸体以获取未被消耗积分" - --- HUD buttons with hand icons that only traitors can see +-- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "一次性" L.tbut_reuse = "重复使用" L.tbut_retime = "{num} 秒可后再次使用" @@ -503,8 +455,7 @@ L.mute_all = "全部静音" L.mute_off = "取消静音" -- Spectators and prop possession -L.punch_title = "飞击量表" --"PUNCH-O-METER" -L.punch_help = "按下行走键或跳跃键以推撞物品;按蹲下键则离开物品控制。" +L.punch_title = "飞击量表" L.punch_bonus = "你的分数较低,飞击量表上限减少 {num}" L.punch_malus = "你的分数较高,飞击量表上限增加 {num}!" @@ -663,7 +614,7 @@ L.col_roles = "角色" L.col_teams = "阵营" L.col_kills1 = "无辜者杀敌数" L.col_kills2 = "叛徒杀敌数" -L.col_points = "积分" +L.col_points = "得分" L.col_team = "团队奖励" L.col_total = "总分" @@ -863,8 +814,8 @@ L.aw_tod1_text = "在他的团队即将获得胜利的前几秒死去。" L.aw_tod2_title = "垃圾游戏!" L.aw_tod2_text = "在这回合刚开始不久即被杀害。" --- New and modified pieces of text are placed below this point, marked with the --- version in which they were added, to make updating translations easier. +-- New and modified pieces of text are placed below this point, marked with the +-- version in which they were added, to make updating translations easier. -- v24 L.drop_no_ammo = "你弹夹内的子弹不足以丢弃成弹药盒。" @@ -932,16 +883,13 @@ L.shop_role_select = "选择身份" L.shop_role_selected = "选中了 {role} 的商店!" L.shop_search = "搜索" -L.spec_help = "点击来观察玩家,或对着物理道具按 {usekey} 来附身。" -L.spec_help2 = "若想离开观察者模式,用 {helpkey} 打开菜单,在“游戏性”选项中勾选选项。" - -- 2019-10-19 L.drop_ammo_prevented = "有什么东西阻挡你丢出子弹。" -- 2019-10-28 L.target_c4 = "按 [{usekey}] 打开C4菜单" L.target_c4_armed = "按 [{usekey}] 拆除C4" -L.target_c4_armed_defuser = "按 [{usekey}] 使用拆弹器" +L.target_c4_armed_defuser = "按 [{primaryfire}] 使用拆弹器" L.target_c4_not_disarmable = "你不能拆除存活队友的C4" L.c4_short_desc = "可以炸得很欢" @@ -949,22 +897,21 @@ L.target_pickup = "按 [{usekey}] 捡起" L.target_slot_info = "槽位:{slot}" L.target_pickup_weapon = "按 [{usekey}] 捡起武器" L.target_switch_weapon = "按 [{usekey}] 和当前武器交换" -L.target_pickup_weapon_hidden = "按 [{usekey} + {walkkey}] 隐秘地捡起" -L.target_switch_weapon_hidden = "按 [{usekey} + {walkkey}] 隐秘地交换" +L.target_pickup_weapon_hidden = "按 [{walkkey} + {usekey}] 隐秘地捡起" +L.target_switch_weapon_hidden = "按 [{walkkey} + {usekey}] 隐秘地交换" L.target_switch_weapon_nospace = "没有提供给这个武器的槽位" L.target_switch_drop_weapon_info = "丢弃槽位 {slot} 的 {name}" L.target_switch_drop_weapon_info_noslot = "槽位 {slot} 没有可丢弃的武器" -L.corpse_searched_by_detective = "这个尸体被探长搜查过" +L.corpse_searched_by_detective = "这具尸体已被公共警察角色搜索过" L.corpse_too_far_away = "这个尸体太远了。" -L.radio_pickup_wrong_team = "你不能捡起其他队伍的收音机" L.radio_short_desc = "武器声音,悦耳动听" -L.hstation_subtitle = "按 [{usekey}] 恢复生命" +L.hstation_subtitle = "按 [{usekey}] 恢复生命值" L.hstation_charge = "剩余充能:{charge}" L.hstation_empty = "这个医疗站没有剩余充能" -L.hstation_maxhealth = "你的生命恢复已满" +L.hstation_maxhealth = "你的生命值已满" L.hstation_short_desc = "医疗站会逐渐回复充能" -- 2019-11-03 @@ -1003,7 +950,6 @@ L.mute_team = "静音 {team}" L.door_auto_closes = "此门会自动关闭" L.door_open_touch = "此门接触后会自动开启" L.door_open_touch_and_use = "接触门或按 [{usekey}] 开门。" -L.hud_health = "生命" -- 2020-03-09 L.help_title = "帮助和设定" @@ -1025,7 +971,7 @@ L.menu_guide_description = "帮助你开始游玩 TTT2 并解释玩法和身份 L.menu_bindings_description = "将 TTT2 和其插件的功能绑到你想要的键位" L.menu_language_description = "选择游戏语言" L.menu_appearance_description = "调整界面的样式和性能" -L.menu_gameplay_description = "避免特定身份和其他游戏相关选项" +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." L.menu_addons_description = "配置本地插件" L.menu_legacy_description = "旧TTT的菜单,应该已被导入新系统" L.menu_administration_description = "界面和商店的通用管理菜单" @@ -1049,10 +995,8 @@ L.submenu_appearance_crosshair_title = "准星" L.submenu_appearance_dmgindicator_title = "伤害指示" L.submenu_appearance_performance_title = "性能" L.submenu_appearance_interface_title = "界面" -L.submenu_appearance_miscellaneous_title = "其他" L.submenu_gameplay_general_title = "通用" -L.submenu_gameplay_avoidroles_title = "避免特定身份" L.submenu_administration_hud_title = "HUD 设置" L.submenu_administration_randomshop_title = "随机商店" @@ -1089,17 +1033,12 @@ L.label_shop_show_slot = "显示装备槽位" L.label_shop_show_custom = "显示自定义标记" L.label_shop_show_fav = "显示最爱标记" L.label_crosshair_enable = "启用十字准星" -L.label_crosshair_gap_enable = "启用自定义准星大小" -L.label_crosshair_gap = "自定义准星大小" L.label_crosshair_opacity = "准星透明度" L.label_crosshair_ironsight_opacity = "瞄准时准星透明度" L.label_crosshair_size = "准星长度" L.label_crosshair_thickness = "准星粗细" L.label_crosshair_thickness_outline = "准星外框粗细" -L.label_crosshair_static_enable = "启用静态准星" -L.label_crosshair_dot_enable = "启用准星中点" -L.label_crosshair_lines_enable = "启用准星直线" -L.label_crosshair_scale_enable = "启用武器对应准星大小" +L.label_crosshair_scale_enable = "启用动态十字准线刻度" L.label_crosshair_ironsight_low_enabled = "瞄准时降低武器模型" L.label_damage_indicator_enable = "启用伤害指示" L.label_damage_indicator_mode = "选择伤害指示主题" @@ -1118,13 +1057,10 @@ L.label_gameplay_specmode = "观察者模式(永远观察)" L.label_gameplay_fastsw = "武器快速切换" L.label_gameplay_hold_aim = "启用持续瞄准" L.label_gameplay_mute = "死亡时静音存活玩家" -L.label_gameplay_dtsprint_enable = "启用双击冲刺" -L.label_gameplay_dtsprint_anykey = "冲刺时任何方向键都持续冲刺" L.label_hud_default = "默认 HUD" L.label_hud_force = "强制 HUD" L.label_bind_weaponswitch = "捡起武器" -L.label_bind_sprint = "冲刺" L.label_bind_voice = "全局语言" L.label_bind_voice_team = "团队语言" @@ -1148,7 +1084,6 @@ L.header_damage_indicator = "伤害指示设置" L.header_performance_settings = "性能设置" L.header_interface_settings = "界面设置" L.header_gameplay_settings = "游戏性设置" -L.header_roleselection = "启用身份分配" L.header_hud_administration = "选择默认和强制 HUD" L.header_hud_enabled = "启用/禁用 HUDs" @@ -1192,14 +1127,10 @@ L.hud_revival_title = "复活剩余时间:" L.hud_revival_time = "{time}秒" -- 2020-05-03 -L.door_destructible = "此门不可摧毁 ({health}生命)" +L.door_destructible = "此门不可摧毁 ({health}生命值)" -- 2020-05-28 -L.confirm_detective_only = "只有侦探能确认死亡。" -L.inspect_detective_only = "只有侦探能检查尸体。" -L.corpse_hint_no_inspect = "只有侦探能检查这个尸体。" -L.corpse_hint_inspect_only = "按 [{usekey}] 搜索。只有侦探能确认死亡。" -L.corpse_hint_inspect_only_credits = "按 [{usekey}] 获取积分。只有侦探能确认死亡。" +L.corpse_hint_inspect_limited = "按下 [{usekey}] 进行搜索。按下 [{walkkey} + {usekey}] 仅查看搜索界面。" -- 2020-06-04 L.label_bind_disguiser = "切换伪装器" @@ -1212,9 +1143,8 @@ L.dna_help_reload = "删除当前样本" L.binoc_help_pri = "确认尸体" L.binoc_help_sec = "切换放大倍率" -L.vis_help_pri = "丢弃当前设备。" +L.vis_help_pri = "丢弃当前装备。" -L.decoy_help_pri = "安放诱饵。" -- 2020-08-07 L.pickup_error_spec = "作为观察者你无法捡起这个。" @@ -1269,7 +1199,7 @@ L.title_event_bodyfound = "发现了一具尸体" L.title_event_c4_disarm = "一枚C4被解除了" L.title_event_c4_explode = "一枚C4爆炸了" L.title_event_c4_plant = "放置了一枚C4" -L.title_event_creditfound = "积分被发现" +L.title_event_creditfound = "搜到积分" L.title_event_finish = "本回合已经结束" L.title_event_game = "新回合已经开始" L.title_event_kill = "一名玩家被杀害" @@ -1298,6 +1228,7 @@ L.trap_something = "某件物品" -- Kill events L.desc_event_kill_suicide = "是自杀的" L.desc_event_kill_team = "是被队友杀的" + L.desc_event_kill_blowup = "{victim} ({vrole} / {vteam}) 被自己炸飞。" L.desc_event_kill_blowup_trap = "{victim} ({vrole} / {vteam}) 被 {trap} 炸飞。" @@ -1357,9 +1288,9 @@ L.header_random_shop_value_administration = "平衡性设置" L.shopeditor_name_random_shops = "启用随机商店" L.shopeditor_desc_random_shops = [[随机商店只给每个玩家提供一套有限的随机化装备。 阵营商店迫使一个阵营中的所有玩家拥有相同的套装,而不是定制化。 -重新投票可以让你用积分获得一套新的随机装备。]] +重选可以让你使用积分来刷新随机装备。]] L.shopeditor_name_random_shop_items = "随机装备的数量" -L.shopeditor_desc_random_shop_items = "这包括那些标有“非随机”的设备。所以请选择一个足够高的数字,否则你只能得到这些。" +L.shopeditor_desc_random_shop_items = "这包括那些标有“非随机”的装备。所以请选择一个足够高的数字,否则你只能得到这些。" L.shopeditor_name_random_team_shops = "启用阵营商店" L.shopeditor_name_random_shop_reroll = "启用商店重选功能" L.shopeditor_name_random_shop_reroll_cost = "每次重选的花费" @@ -1409,12 +1340,6 @@ L.xfer_team_indicator = "阵营" -- 2021-06-25 L.searchbar_default_placeholder = "在列表中搜索..." --- 2021-07-07 -L.header_equipment_weapon_spawn_setup = "武器生成设置" - -L.equipmenteditor_name_auto_spawnable = "装备随机生成" -L.equipmenteditor_name_spawn_type = "生成类型" - -- 2021-07-11 L.spec_about_to_revive = "在复活时,观察将被限制。" @@ -1443,7 +1368,7 @@ L.spawn_ammo_rifle = "狙击枪弹药生成" L.spawn_ammo_shotgun = "霰弹枪弹药生成" L.spawn_player_random = "随机玩家生成" -L.spawn_weapon_ammo = " (弹药:{ammo})" +L.spawn_weapon_ammo = "(弹药:{ammo})" L.spawn_weapon_edit_ammo = "按住 [{walkkey}]并按 [{primaryfire} 或 {secondaryfire}] 以增加或减少此武器生成的弹药量" @@ -1461,7 +1386,7 @@ L.button_delete_all_spawns = "删除所有生成点位置" L.label_dynamic_spawns_enable = "为该地图启用动态生成" L.label_dynamic_spawns_global_enable = "为所有地图启用自定义生成" -L.header_equipment_weapon_spawn_setup = "武器生成设定" +L.header_equipment_weapon_spawn_setup = "武器生成设置" L.help_spawn_editor_info = [[ 生成编辑器是用来放置,移除和编辑世界上的生成点。这些生成点是为武器,弹药和玩家准备的。 @@ -1474,7 +1399,7 @@ L.help_spawn_editor_info = [[ L.help_spawn_editor_enable = "在某些地图上,可能会建议使用在地图自带的原始生成点,而不用动态系统来取代它们。禁用这个复选框只对当前活动地图禁用。其他地图仍将使用动态系统。" L.help_spawn_editor_hint = "提示:要离开生成编辑器,重新打开游戏模式菜单。" L.help_spawn_editor_spawn_amount = [[ -目前在这张地图上有 {weapon} 个武器生成点,{ammo} 个弹药生成点和 player} 个玩家生成点。 +目前在这张地图上有 {weapon} 个武器生成点,{ammo} 个弹药生成点和 {player} 个玩家生成点。 点击'开始编辑生成'来改变这个生成。 {weaponrandom}x 随机武器生成 @@ -1493,9 +1418,9 @@ L.help_spawn_editor_spawn_amount = [[ {ammorifle}x 狙击枪弹药生成 {ammoshotgun}x 霰弹枪弹药生成 -{playerrandom} x 玩家随机位置生成]] +{playerrandom}x 玩家随机位置生成]] -L.equipmenteditor_name_auto_spawnable = "设备在地图中随机产生" +L.equipmenteditor_name_auto_spawnable = "装备可在地图中随机生成" L.equipmenteditor_name_spawn_type = "选择生成类型" L.equipmenteditor_desc_auto_spawnable = [[ TTT2的生成系统允许每种武器在世界中生成,默认情况下,只有被创造者标记为'自动生成'的武器才会在世界中生成,但这些设置可以在该菜单中更改。 @@ -1540,8 +1465,9 @@ L.kill_score_team = "击杀队友:" -- 2021-10-09 L.help_models_select = [[ 左键点击模型,将其添加到玩家模型库中。再次以左键删除它们。右键可在所关注的模型的启用和禁用侦探帽之间进行切换。 - + 左上角的小指示器显示玩家模型是否有头部的命中箱,下面的图标显示了这个模型是否可佩戴侦探帽。]] + L.menu_roles_title = "角色设置" L.menu_roles_description = "设置生成概率、装备积分及更多。" @@ -1557,14 +1483,14 @@ L.header_roles_reward_credits = "奖励装备积分" L.help_roles_default_team = "默认团队:{team}" L.help_roles_unselectable = "这个角色是不可选择的。这意味着它在角色选择系统中不被考虑。大多数情况下,这意味着这是回合中通过某个事件(如复活为僵尸,副手老鹰或类似的东西)手动应用的角色。" L.help_roles_selectable = "这个角色是可选择的,这意味着如果满足所有的标准,这个角色在角色选择过程中会被考虑。" -L.help_roles_credits = "装备积分用于在商店购买装备。大多数情况下,只给那些可以进入商店的角色信用额度是有意义的。然而,由于可从尸体上偷取积分,也可以考虑给角色提供起始积分,作为给加害者的奖励。" +L.help_roles_credits = "积分用于在商店购买装备。大部分情况下,积分只对能使用商店的身份有用。不过,由于尸体上可以搜刮到积分,可以考虑给一个身份提供初始积分来作为杀手的奖励。" L.help_roles_selection_short = "每个玩家的角色分布定义了被分配到这个角色的玩家的百分比。例如,如果该值被设置为'0.2',那么每五名玩家中就有一人会变为此角色。" L.help_roles_selection = [[ 每个玩家的角色分配定义了被分配到这个角色的玩家的百分比。例如,如果该值被设置为 "0.2",那么每五个玩家就会得到这个角色。这也意味着,至少需要5名玩家才能分配到这个角色。 请记住,所有这些都只适用于该角色被考虑分配过程。 前面提到的角色分配与玩家的下限有一个特殊的整合。如果该角色被考虑用于分配,且最小值低于分配系数所给的值,但玩家数量等于或大于下限,则单个玩家仍可获得该角色。然后分配过程对第二个玩家照常进行。]] -L.help_roles_award_info = "部分角色(如果在他们的积分设置中启用)在一定比例的对手死亡后会获得装备积分,该数值可在这里进行调整。" +L.help_roles_award_info = "部分身份(如果在积分设置中启用)在一定比例的敌人死亡后会获得积分。此功能的相关数值可以在这里调整。" L.help_roles_award_pct = "当这个百分比的敌人死亡后,特定的角色会获得积分。" L.help_roles_award_repeat = "积分奖励是否会多次发放。例如,如果你将百分比设置为'0.25',并启用此功能,玩家将在死亡人数到达全玩家的'25%','50%'和'75%'时获得积分。" L.help_roles_advanced_warning = "警告:这些是高级设置,可以完全扰乱角色分配过程。如果有疑问,请将所有值保持在 '0'。这个值意味着不应用任何限制,角色分配将尝试分配尽可能多的角色。" @@ -1593,7 +1519,7 @@ L.label_roles_tbutton = "是否可使用叛徒按钮" L.label_roles_credits_starting = "初始装备积分" L.label_roles_credits_award_pct = "积分奖励报酬率" L.label_roles_credits_award_size = "积分奖励比例" -L.label_roles_credits_award_repeat = "重复学积分奖励" +L.label_roles_credits_award_repeat = "积分奖励重复量" L.label_roles_newroles_enabled = "启用自定义角色" L.label_roles_max_roles = "角色上限" L.label_roles_max_roles_pct = "按百分比计算角色上限" @@ -1603,27 +1529,27 @@ L.label_detective_hats = "为像侦探这样的警察角色启用帽子(如果 L.ttt2_desc_innocent = "无辜者没有特殊能力。他们必须在恐怖分子中找到邪恶的人并杀死他们。但他们必须小心,不要误杀自己的同伴。" L.ttt2_desc_traitor = "叛徒是无辜者的敌人。他们有一个装备菜单,可以购买特殊装备。他们必须杀死所有的人,除了他们的队友。" -L.ttt2_desc_detective = "侦探是无辜者们最信任的人。但谁是无辜者?强大的侦探必须要找到所有图谋不轨恐怖分子。他们商店里的设备可能会帮助他们完成这项任务。" +L.ttt2_desc_detective = "侦探是无辜者们最信任的人。但谁是无辜者?强大的侦探必须要找到所有图谋不轨恐怖分子。他们商店里的装备可能会帮助他们完成这项任务。" -- 2021-10-10 L.button_reset_models = "重置玩家模型" -- 2021-10-13 -L.help_roles_credits_award_kill = "另一种获得积分的方式是通过杀死为'公开角色'(如侦探)的高价值玩家。如果非无辜者阵营的角色启用了这个功能,他们就会获得以下规定的积分。" +L.help_roles_credits_award_kill = "另一种获得积分的方式是通过杀死拥有'公开身份'的高价值玩家,比如侦探。如果杀手的身份启用了这个功能,他们会获得此处配置的积分量。" L.help_roles_credits_award = [[ 在TTT2中,有两种不同的方式可以获得积分: -1.如果敌方队伍中有一定比例的人死亡,整个队伍将会奖励积分。 +1.如果敌方队伍中有一定比例的人死亡,整个队伍将会被奖励积分。 2.如果一个玩家用'公开角色'(如侦探)杀死了一个高价值的角色,那么这位玩家就会得到奖励。 -请注,,即使全队都会获得奖励,这仍然可以为每个角色启用/禁用。例如,如果'无辜者'阵营被奖励,但无辜者角色的装备商店被禁用,所以只有侦探会收到积分。 +请注意,即使整个团队都获得了奖励,每个身份仍可启用/禁用此功能。例如,如果'无辜者'团队获得了奖励,但'无辜者'身份禁用了此功能,则只有'侦探'会获得积分。 这个功能的平衡值可以在'管理'->'通用角色设置'中设置。]] L.help_detective_hats = [[ 侦探等警察角色可以戴帽子以显示其权威。他们在死亡时或头部受损时将失去帽子。 部分玩家模型默认不支持帽子。你可以在'管理'->'玩家模型'中改变这一点。]] -L.label_roles_credits_award_kill = "Credit reward amount for the kill" +L.label_roles_credits_award_kill = "击杀获得的积分奖励" L.label_roles_credits_dead_award = "启用对依据一定比例敌人死亡数提供积分奖励" L.label_roles_credits_kill_award = "启用对击杀高价值角色时提供积分奖励" L.label_roles_min_karma = "分配角色时玩家的最低人品值" @@ -1690,7 +1616,7 @@ L.help_haste_mode = [[ 如果急速模式被启用,固定的回合时间将被忽略。]] L.help_round_limit = "在满足设定的限制条件之一后,将会更换地图。" L.help_armor_balancing = "以下数值可以用来平衡护甲。" -L.help_item_armor_classic = "如果启用了经典护甲模式,只有之前的设置才是生效的。经典护甲模式意味着玩家在一个回合中只能购买一次护甲,并且这个盔甲可以阻挡30%的子弹和撬棍的伤害,直到他们死亡。" +L.help_item_armor_classic = "如果启用了经典护甲模式,只有之前的设置才是生效的。经典护甲模式意味着玩家在一个回合中只能购买一次护甲,并且这个护甲可以阻挡30%的子弹和撬棍的伤害,直到他们死亡。" L.help_item_armor_dynamic = [[ 动态护甲是TTT2的新系统,使护甲系统更加有趣。现在可以购买的护甲数量是无限的,而且护甲的效果可以叠加。受到伤害会降低护甲值,每件物品的护甲值是在该物品的"装备设置"中设置的。 @@ -1733,8 +1659,6 @@ L.label_bots_are_spectators = "机器人永远是观察者" L.label_tbutton_admin_show = "向管理员显示叛徒按钮" L.label_ragdoll_carrying = "启用布娃娃搬运" L.label_prop_throwing = "启用道具投掷" -L.label_ragdoll_pinning = "为非无辜者角色启用布娃娃夹子" -L.label_ragdoll_pinning_innocents = "为无辜者启用布娃娃夹子" L.label_weapon_carrying = "启用武器搬运" L.label_weapon_carrying_range = "武器搬运范围" L.label_prop_carrying_force = "Prop推进力" @@ -1760,17 +1684,16 @@ L.label_round_limit = "回合数上限" L.label_time_limit_minutes = "游戏时间上限,以分钟为单位" L.label_nade_throw_during_prep = "在准备时间内允许投掷手榴弹" L.label_postround_dm = "回合结束后启用死亡竞赛" +L.label_session_limits_enabled = "启用地图更换" L.label_spectator_chat = "启用观察者与大家聊天的功能" L.label_lastwords_chatprint = "如果在打字时被杀,则发出最后一句话至聊天室" L.label_identify_body_woconfirm = "不按'确认'按钮识别尸体" -L.label_announce_body_found = "宣布发现了一具尸体" +L.label_announce_body_found = "当尸体被确认时,宣布找到了一个尸体" L.label_confirm_killlist = "宣布确认尸体时,该尸体的击杀名单" -L.label_inspect_detective_only = "只允许警察角色搜查尸体" -L.label_confirm_detective_only = "只允许警察角色确认死者" L.label_dyingshot = "如果玩家在瞄准中,则在死亡时开枪[试验性]" L.label_armor_block_headshots = "启用护甲阻挡爆头伤害" L.label_armor_block_blastdmg = "启用护甲阻挡爆炸伤害" -L.label_armor_dynamic = "启用动态装甲" +L.label_armor_dynamic = "启用动态护甲" L.label_armor_value = "护甲物品所赋予的护甲" L.label_armor_damage_block_pct = "护甲承受的伤害百分比" L.label_armor_damage_health_pct = "玩家承受的伤害百分比" @@ -1782,7 +1705,7 @@ L.label_highlight_dev = "突出显示TTT2开发者" L.label_highlight_vip = "突出显示TTT2支持者" L.label_highlight_addondev = "突出显示TTT2附加组件的开发者" L.label_highlight_supporter = "突出显示其他人" -L.label_enable_hud_element = "启用{elem}HUD元素" +L.label_enable_hud_element = "启用 {elem} HUD 元素" L.label_spec_prop_control = "启用Prop附体" L.label_spec_prop_base = "附体时的基础值" L.label_spec_prop_maxpenalty = "降低附体奖金下限" @@ -1825,14 +1748,9 @@ L.label_sprint_enabled = "启用冲刺功能" L.label_sprint_max = "冲刺体力最大值" L.label_sprint_stamina_consumption = "体力消耗系数" L.label_sprint_stamina_regeneration = "体力恢复系数" -L.label_sprint_crosshair = "冲刺时显示准星" L.label_crowbar_unlocks = "主要攻击键可以作为互动(即解锁)使用" L.label_crowbar_pushforce = "撬棍推动力" ---2022-04-13 -L.label_session_limits_enabled = "启用地图更换" - - -- 2022-07-02 L.header_playersettings_falldmg = "摔落伤害设置" @@ -1859,10 +1777,417 @@ L.guide_nothing_desc = "这是一项正在进行中的工作,通过在GitHub L.sb_rank_tooltip_developer = "TTT2开发者" L.sb_rank_tooltip_vip = "TTT2支持者" -L.sb_rank_tooltip_addondev = "TTT2附加组件开发者" +L.sb_rank_tooltip_addondev = "TTT2插件开发者" L.sb_rank_tooltip_admin = "服务器管理员" L.sb_rank_tooltip_streamer = "主播" L.sb_rank_tooltip_heroes = "TTT2 英雄" L.sb_rank_tooltip_team = "阵营" -L.tbut_adminarea = "管理区:" \ No newline at end of file +L.tbut_adminarea = "管理区:" + +-- 2023-08-10 +L.equipmenteditor_name_damage_scaling = "伤害缩放" + +-- 2023-08-11 +L.equipmenteditor_name_allow_drop = "允许丢弃" +L.equipmenteditor_desc_allow_drop = "如果启用,玩家可以自由地丢弃装备。" + +L.equipmenteditor_name_drop_on_death_type = "死亡时丢弃" +L.equipmenteditor_desc_drop_on_death_type = "尝试覆盖玩家死亡时装备是否被丢弃的操作。" + +L.drop_on_death_type_default = "默认(由武器定义)" +L.drop_on_death_type_force = "强制死亡时丢弃" +L.drop_on_death_type_deny = "拒绝死亡时丢弃" + +-- 2023-08-26 +L.equipmenteditor_name_kind = "装备槽" +L.equipmenteditor_desc_kind = "装备将占用的库存槽。" + +L.slot_weapon_melee = "近战槽" +L.slot_weapon_pistol = "手枪槽" +L.slot_weapon_heavy = "重型槽" +L.slot_weapon_nade = "手雷槽" +L.slot_weapon_carry = "携带槽" +L.slot_weapon_unarmed = "空手槽" +L.slot_weapon_special = "特殊槽" +L.slot_weapon_extra = "额外槽" +L.slot_weapon_class = "职业槽" + +-- 2023-10-04 +L.label_voice_duck_spectator = "观察者语音淡化" +L.label_voice_duck_spectator_amount = "观察者语音淡化程度" +L.label_voice_scaling = "语音淡化比例" +L.label_voice_scaling_mode_linear = "线性" +L.label_voice_scaling_mode_power4 = "四次方" +L.label_voice_scaling_mode_log = "对数" + +-- 2023-10-07 +L.search_title = "尸检 - {player}" +L.search_info = "信息" +L.search_confirm = "确认死亡" +L.search_confirm_credits = "确认(+{credits} 积分)" +L.search_take_credits = "获取 {credits} 积分" +L.search_confirm_forbidden = "确认死亡被禁用" +L.search_confirmed = "确认死亡" +L.search_call = "呼叫探长" +L.search_called = "死亡报告" + +L.search_team_role_unknown = "???" + +L.search_words = "直觉告诉你这个人的遗言是:{lastwords}" +L.search_armor = "他穿着非标准护甲。" +L.search_disguiser = "他持有一个能隐匿身份的设备" +L.search_radar = "他持有像是雷达的装备,已经无法使用了。" +L.search_c4 = "你在他口袋中找到了一本笔记。记载着第 {num} 根线才能解除炸弹。" + +L.search_dmg_crush = "他多处骨折。看起来是某种重物的冲击撞死了他。" +L.search_dmg_bullet = "他很明显是被射杀身亡的。" +L.search_dmg_fall = "他是坠落身亡的。" +L.search_dmg_boom = "他的伤口以及烧焦的衣物,应是爆炸导致其死亡。" +L.search_dmg_club = "他的身体有许多擦伤打击痕迹,明显是被殴打致死的。" +L.search_dmg_drown = "他身上的蛛丝马迹显示是溺死的。" +L.search_dmg_stab = "他是被刺击与挥砍后,迅速失血致死的。" +L.search_dmg_burn = "闻起来像烧焦的恐怖分子.." +L.search_dmg_teleport = "看起来他的DNA以超光速粒子之形式散乱在附近。" +L.search_dmg_car = "他穿越马路时被一个粗心的驾驶碾死了。" +L.search_dmg_other = "你无法找到这恐怖份子的具体死因。" + +L.search_floor_antlions = "尸体上仍然爬满了蚂蚁。地上也爬满了它们。" +L.search_floor_bloodyflesh = "尸体上的血看起来又老又恶心,甚至有小块血肉粘在鞋上。" +L.search_floor_concrete = "灰色的尘土覆盖了尸体的鞋子和膝盖。看起来犯罪现场的地面是混凝土制的。" +L.search_floor_dirt = "闻起来有泥土的味道。可能是来自粘在受害者鞋子上的泥土。" +L.search_floor_eggshell = "恶心的白色斑点覆盖了受害者的身体。看起来像鸡蛋壳。" +L.search_floor_flesh = "受害者的衣服感觉有点湿。好像摔在了一个潮湿的表面上。比如肉质的表面,或者水体里的沙地。" +L.search_floor_grate = "受害者的皮肤像牛排一样,全身都有网格状的粗线。他们是不是倒在了格栅上?" +L.search_floor_alienflesh = "外星人的肉?听起来有点离谱。但你的侦探助手书将其列为可能的地面材质。" +L.search_floor_snow = "一眼看去,他的衣服只是潮湿而冰冷的。但当你看到边缘的白色泡沫时你就明白了。是雪!" +L.search_floor_plastic = "'哎哟,那一定很疼。' 尸体上布满了烫伤的痕迹,看起来像是和塑料表面摩擦出来的伤势。" +L.search_floor_metal = "尸体上布满了锈迹斑斑的伤口,至少他们现在死了,不会得破伤风。他们可能是在金属表面上死的。" +L.search_floor_sand = "小小的粗糙的岩石粘在他们冰冷的身体上。像海滩上的粗砂。啊,它到处都是!" +L.search_floor_foliage = "大自然真美。受害者的血腥伤口被足够的叶子覆盖,几乎被隐藏起来。" +L.search_floor_computer = "嘀嗒嘀嗒。他的尸体被计算机表面覆盖着!你问这看起来会是什么样子?额,诶嘿!" +L.search_floor_slosh = "湿漉漉的,甚至可能有点黏糊糊的。他们的整个身体都被它覆盖,衣服也被浸湿。它很臭!" +L.search_floor_tile = "许多碎片插在尸体上,像是摔在瓷砖地面上时砸出的痕迹。" +L.search_floor_grass = "闻起来像刚割过的草。这种气味几乎盖过了血和死亡的气味。" +L.search_floor_vent = "当你摸他们的身体时,你感觉到一股新鲜的气流。他是不是在死在了通风口里然后带走了空气?" +L.search_floor_wood = "有什么比坐在硬木地板上沉思更好的事情呢?至少不是躺在木地板上死!" +L.search_floor_default = "那看起来很基础,很普通。像是默认的一样。你无法判断出这个表面的任何特征。" +L.search_floor_glass = "他的尸体上全是血腥的切口。有些伤口里卡着玻璃碎片,看上去挺吓人的。" +L.search_floor_warpshield = "由'warpshield'制成的地板?没错,我们和你一样困惑。但我们的笔记上清楚地这么写着。'Warpshield'。" + +L.search_water_1 = "受害者的鞋子沾了水,但其他地方都是干的。他可能是脚踩在水里时被杀的。" +L.search_water_2 = "受害者的鞋子和裤子都湿透了。他们在被杀时是不是在涉水?" +L.search_water_3 = "潮湿的尸体全身肿胀着。他可能是在完全被淹没的时候死的。" + +L.search_weapon = "死者是被 {weapon} 所杀。" +L.search_head = "最后一击打在头上。完全没机会叫喊。" +L.search_time = "他们在你进行搜索之前就已经死了一段时间了。" +L.search_dna = "用DNA扫描仪获取杀手的DNA样本。DNA样本会在一段时间后消逝。" + +L.search_kills1 = "你找到一个名单,记载着他发现的死者:{player}" +L.search_kills2 = "你找到了一个名单,记载着他杀的这些人:{player}" +L.search_eyes = "透过你的探查技能,你确信他临死前见到的最后一个人是 {player}。是凶手,还是巧合?" + +L.search_credits = "受害者口袋里有 {credits} 积分。带商店的身份也许会拿走它们并好好利用。请留意!" + +L.search_kill_distance_point_blank = "受害者是从近处被攻击的。" +L.search_kill_distance_close = "受害者是从不远处被攻击的。" +L.search_kill_distance_far = "受害者是从远处被攻击的。" + +L.search_kill_from_front = "受害者是正面中弹的。" +L.search_kill_from_back = "受害者是背面中弹的。" +L.search_kill_from_side = "受害者是侧面中弹的。" + +L.search_hitgroup_head = "弹头发现于尸体的头部。" +L.search_hitgroup_chest = "弹头发现于尸体的胸部。" +L.search_hitgroup_stomach = "弹头发现于尸体的胃部。" +L.search_hitgroup_rightarm = "弹头发现于尸体的右臂。" +L.search_hitgroup_leftarm = "弹头发现于尸体的左臂。" +L.search_hitgroup_rightleg = "弹头发现于尸体的右腿。" +L.search_hitgroup_leftleg = "弹头发现于尸体的左腿。" +L.search_hitgroup_gear = "弹头发现于尸体的腰部。" + +L.search_policingrole_report_confirm = [[ +只有在尸体被确认死亡后,才能召唤公开警察角色到死亡尸体的位置。]] +L.search_policingrole_confirm_disabled_1 = [[ +只有公开警察角色才能确认死亡。报告尸体位置来让他们知道!]] +L.search_policingrole_confirm_disabled_2 = [[ +只有公开警察角色才能确认死亡。报告尸体位置来让他们知道! +他们确认后,你可以在这里看到尸检。]] +L.search_spec = [[ +作为观察者,你能看到尸检的全部信息,但不能与界面交互。]] + +L.search_title_words = "受害者遗言" +L.search_title_c4 = "拆弹失误" +L.search_title_dmg_crush = "挤压伤害 ({amount} 生命值)" +L.search_title_dmg_bullet = "枪弹伤害 ({amount} 生命值)" +L.search_title_dmg_fall = "跌落伤害 ({amount} 生命值)" +L.search_title_dmg_boom = "爆炸伤害 ({amount} 生命值)" +L.search_title_dmg_club = "钝器伤害 ({amount} 生命值)" +L.search_title_dmg_drown = "溺水伤害 ({amount} 生命值)" +L.search_title_dmg_stab = "刺伤伤害 ({amount} 生命值)" +L.search_title_dmg_burn = "烧伤伤害 ({amount} 生命值)" +L.search_title_dmg_teleport = "传送伤害 ({amount} 生命值)" +L.search_title_dmg_car = "车祸 ({amount} 生命值)" +L.search_title_dmg_other = "未知伤害 ({amount} 生命值)" +L.search_title_time = "死亡时间" +L.search_title_dna = "DNA样本衰变" +L.search_title_kills = "受害者名单" +L.search_title_eyes = "眼中的倒影" +L.search_title_floor = "犯罪现场的地板" +L.search_title_credits = "{credits} 设备积分" +L.search_title_water = "水位 {level}" +L.search_title_policingrole_report_confirm = "确认死亡" +L.search_title_policingrole_confirm_disabled = "报告尸体位置" +L.search_title_spectator = "你是观察者" + +L.target_credits_on_confirm = "确认死亡可获得未使用的积分" +L.target_credits_on_search = "搜索可获得未使用的积分" +L.corpse_hint_no_inspect_details = "只有公开警察角色可以找到这具尸体的信息。" +L.corpse_hint_inspect_limited_details = "只有公共警察角色可以确认这具尸体。" +L.corpse_hint_spectator = "按 [{usekey}] 查看尸体界面" +L.corpse_hint_public_policing_searched = "按 [{usekey}] 查看公共警察角色的搜索结果" + +L.label_inspect_confirm_mode = "选择尸体搜索模式" +L.choice_inspect_confirm_mode_0 = "模式 0: 标准 TTT" +L.choice_inspect_confirm_mode_1 = "模式 1: 限制确认" +L.choice_inspect_confirm_mode_2 = "模式 2: 限制搜索" +L.help_inspect_confirm_mode = [[ +在这个游戏模式中,有三种不同的尸体搜索/确认模式。选择这个模式对公开警察角色(如侦探)的重要性有很大影响。 + +模式 0: 这是标准的 TTT 行为。每个人都可以搜索和确认尸体。要取得尸检或从尸体上取得积分,首先必须确认死亡。这使得带商店角色偷偷地偷取积分变得有点困难。然而,想要报告尸体位置以召唤公开警察玩家的无辜玩家也需要先确认。 + +模式 1: 这种模式通过将确认死亡选项限制为公开警察角色,提升了公开警察角色的重要性。这也意味着在确认死亡之前就可以取得积分和尸检。每个人仍然可以搜索死尸并找到信息,但他们无法宣布找到的信息。 + +模式 2: 这种模式比模式 1 更严格一些。在这种模式中,普通玩家的搜索能力也被移除了。这意味着向公开警察玩家报告尸体位置现在是获取任何尸体信息的唯一方式。]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +L.label_hud_pulsate_health_enable = "当生命值低于 25% 时,生命条将会开始闪烁" +L.header_hud_elements_customize = "自定义 HUD 元素" +L.help_hud_elements_special_settings = "这些是所使用的 HUD 元素的特殊设置" + +-- 2023-10-25 +L.help_keyhelp = [[ +键位助手是一个向玩家显示相关键位的界面元素,对新玩家特别有帮助。键位分为三种类型: + +核心:包含 TTT2 中最重要的键位。不使用它们的话很难体验游戏的全部潜力。 +附加:类似核心键位,但并非必要使用。这包含聊天、语音或手电筒等功能。显示这些键位也许会对新玩家有所帮助。 +装备:部分装备有自己的键位,这些都显示在此类别中。 + +当记分板可见时,已禁用的类别仍会显示]] + +L.label_keyhelp_show_core = "启用始终显示核心键位" +L.label_keyhelp_show_extra = "启用始终显示额外键位" +L.label_keyhelp_show_equipment = "启用始终显示装备键位" + +L.header_interface_keys = "键位助手设置" +L.header_interface_wepswitch = "武器选择界面设置" + +L.label_keyhelper_help = "打开游戏模式菜单" +L.label_keyhelper_mutespec = "切换观看语音模式" +L.label_keyhelper_shop = "打开装备商店" +L.label_keyhelper_show_pointer = "显示鼠标指针" +L.label_keyhelper_possess_focus_entity = "附体焦点中的Prop" +L.label_keyhelper_spec_focus_player = "观看焦点中的玩家" +L.label_keyhelper_spec_previous_player = "上一个玩家" +L.label_keyhelper_spec_next_player = "下一个玩家" +L.label_keyhelper_spec_player = "观看随机玩家" +L.label_keyhelper_possession_jump = "Prop:跳跃" +L.label_keyhelper_possession_left = "Prop:向左" +L.label_keyhelper_possession_right = "Prop:向右" +L.label_keyhelper_possession_forward = "Prop:向前" +L.label_keyhelper_possession_backward = "Prop:向后" +L.label_keyhelper_free_roam = "离开对象并自由漫游" +L.label_keyhelper_flashlight = "切换手电筒" +L.label_keyhelper_quickchat = "打开快速聊天" +L.label_keyhelper_voice_global = "全局语音聊天" +L.label_keyhelper_voice_team = "团队语音聊天" +L.label_keyhelper_chat_global = "全局聊天" +L.label_keyhelper_chat_team = "团队聊天" +L.label_keyhelper_show_all = "显示全部" +L.label_keyhelper_disguiser = "切换伪装" +L.label_keyhelper_save_exit = "保存退出" +L.label_keyhelper_spec_third_person = "切换第三人称视图" + +-- 2023-10-26 +L.item_armor_reinforced = "强化护甲" +L.item_armor_sidebar = "护甲可以保护你免受子弹穿透身体。但不是永久的。" +L.item_disguiser_sidebar = "伪装者不会向其他玩家显示你的名字,从而保护你的身份。" +L.status_speed_name = "移速倍率" +L.status_speed_description_good = "你的移动速度高于普通。物品,装备或效果可能会影响此值。" +L.status_speed_description_bad = "你的移动速度低于普通。物品,装备或效果可能会影响此值。" + +L.status_on = "开启" +L.status_off = "关闭" + +L.crowbar_help_primary = "攻击" +L.crowbar_help_secondary = "推动玩家" + +-- 2023-10-27 +L.help_HUD_enable_description = [[ +当记分板打开时,一些 HUD 元素(如键位助手或侧边栏)会显示详细信息。可以禁用此功能以减少杂乱。]] +L.label_HUD_enable_description = "打开记分板时启用描述" +L.label_HUD_enable_box_blur = "启用用户界面框背景模糊" + +-- 2023-10-28 +L.submenu_gameplay_voiceandvolume_title = "声音和音量" +L.header_soundeffect_settings = "声音效果" +L.header_voiceandvolume_settings = "声音和音量设置" + +-- 2023-11-06 +L.drop_reserve_prevented = "有东西阻止你丢弃备弹。" +L.drop_no_reserve = "你的备弹不足,无法作为弹药箱投放。" +L.drop_no_room_ammo = "你没有地方放置弹药箱!" + +-- 2023-11-14 +L.hat_deerstalker_name = "侦探帽" + +-- 2023-11-16 +L.help_prop_spec_dash = [[ +Propspec 冲刺是向目标矢量方向的移动。它们可以比正常移动的力度更大。更高的力度也意味着更高的基础值消耗。 + +该变量是推力的乘数。]] +L.label_spec_prop_dash = "冲刺力倍增器" +L.label_keyhelper_possession_dash = "Prop:向视线方向冲刺" +L.label_keyhelper_weapon_drop = "尽可能丢出所选武器" +L.label_keyhelper_ammo_drop = "将选定武器的弹药从弹夹中取出" + +-- 2023-12-07 +L.c4_help_primary = "安放C4" +L.c4_help_secondary = "将其放置在墙面上" + +-- 2023-12-11 +L.magneto_help_primary = "推动实体" +L.magneto_help_secondary = "推动/拾取实体" +L.knife_help_primary = "刺" +L.knife_help_secondary = "投掷小刀" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +L.vis_no_pickup = "只有公开警察角色才能捡起显像器" +L.newton_force = "推力" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +L.beacon_help_pri = "将信标扔在地上" +L.beacon_help_sec = "将信标粘贴到表面" +L.beacon_name = "信标" +L.beacon_desc = [[ +将玩家位置广播给信标周围球形范围内的所有人。 + +用于跟踪地图上难以看到的位置。]] + +L.msg_beacon_destroyed = "你的一个信标已被摧毁!" +L.msg_beacon_death = "一个玩家在你的一个信标的附近死亡。" + +L.beacon_pickup_disabled = "只有信标的拥有者才能拾起它" +L.beacon_short_desc = "警察角色使用信标在他们周围添加本地透视效果" + +-- 2023-12-18 +L.entity_pickup_owner_only = "只有拥有者才能捡起这个" + +-- 2023-12-18 +L.body_confirm_one = "{finder} 确认了 {victim} 的死亡。" +L.body_confirm_more = "{finder} 确认了以下 {count} 人的死亡: {victims}。" + +-- 2023-12-19 +L.builtin_marker = "内置。" +L.equipmenteditor_desc_builtin = "此装备为内置装备(TTT2自带!)" +L.help_roles_builtin = "此角色为内置角色(TTT2自带!)" +L.header_equipment_info = "装备信息" + + +-- 2023-12-24 +L.submenu_gameplay_accessibility_title = "辅助功能" + +L.header_accessibility_settings = "辅助功能设置" + +L.label_enable_dynamic_fov = "启用动态 FOV 更改" +L.label_enable_bobbing = "启用视图晃动" +L.label_enable_bobbing_strafe = "在扫射时启用视图晃动" + +L.help_enable_dynamic_fov = "根据玩家的速度应用动态 FOV。例如,当玩家在冲刺时,FOV 会增加,以显示速度。" +L.help_enable_bobbing_strafe = "视图晃动是指摄像机在行走、游泳或下落时发生轻微抖动。" +-- 2023-12-20 +L.equipmenteditor_desc_damage_scaling = [[将武器的基础伤害值乘以此因子。 +对于霰弹枪,这将影响每个弹丸。 +对于步枪,这将只影响子弹。 +对于鬼怪,这将影响每次"砰"声和最后的爆炸。 + +0.5 = 造成一半的伤害。 +2 = 造成两倍的伤害。 + +注意:有些武器可能不使用这个值,这会导致这个修饰符无效。]] + +-- 2023-12-24 +L.binoc_help_reload = "清除目标。" +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +L.label_crosshair_thickness_outline_enable = "启用准星轮廓" +L.label_crosshair_outline_high_contrast = "启用轮廓高对比度颜色" +L.label_crosshair_mode = "准星模式" +L.label_crosshair_static_length = "启用静态准星长度" + +L.choice_crosshair_mode_0 = "线条和点" +L.choice_crosshair_mode_1 = "仅线条" +L.choice_crosshair_mode_2 = "仅点" + +L.help_crosshair_scale_enable = [[ +动态准星可根据武器的扩散度缩放准星。扩散度受武器的基本精度影响,并与跳跃和冲刺等外部因素相乘。 + +如果线的长度保持不变,则只有间隙会随着扩散度的变化而缩放。]] + +L.header_weapon_settings = "武器设置" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +L.decoy_help_primary = "安放诱饵" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/lang/zh_tw.lua b/lua/terrortown/lang/zh_tw.lua index 62965ce77..32a810a78 100644 --- a/lua/terrortown/lang/zh_tw.lua +++ b/lua/terrortown/lang/zh_tw.lua @@ -34,8 +34,8 @@ L.round_traitors_one = "叛徒,你得自己頂住了。" L.round_traitors_more = "叛徒,你的隊友是:{names}" L.win_time = "時間用盡,叛徒失敗了。" -L.win_traitor = "叛徒取得了勝利!" -L.win_innocent = "叛徒們被擊敗了!" +L.win_traitors = "叛徒取得了勝利!" +L.win_innocents = "叛徒們被擊敗了!" L.win_nones = "無人勝出!(平局)" L.win_showreport = "一起觀看觀看 {num} 秒的回合總結吧!" @@ -60,8 +60,6 @@ L.body_found_traitor = "他是一位叛徒!" L.body_found_det = "他是一位探長。" L.body_found_inno = "他是一位無辜者。" -L.body_confirm = "{finder} 確認了 {victim} 的死。" - L.body_call = "{player} 呼喚探長前來檢查 {victim} 的屍體!" L.body_call_error = "你必須先確定該玩家的死,才能呼叫探長!" @@ -166,52 +164,12 @@ L.quick_inno = " {player} 是無辜者。" L.quick_check = "還有人活著嗎?" -- {player} in the quickchat text normally becomes a player nickname, but can --- also be one of the below. Keep these lowercase. +-- also be one of the below. Keep these lowercase. L.quick_nobody = "沒有人" L.quick_disg = "有人偽裝了" L.quick_corpse = "一具未搜索過的屍體" L.quick_corpse_id = " {player} 的屍體" --- Body search window -L.search_title = "屍體搜索結果" -L.search_info = "訊息" -L.search_confirm = "確認死亡" -L.search_call = "呼叫探長" - --- Descriptions of pieces of information found -L.search_nick = "這是 {player} 的屍體。" - -L.search_role_traitor = "這個人是叛徒!" -L.search_role_det = "這個人是探長。" -L.search_role_inno = "這個人是無辜的恐怖分子。" - -L.search_words = "某些事物讓你了解到此人的遺言: {lastwords}" -L.search_armor = "他穿戴非標準裝甲。" -L.search_disg = "他持有一個能隱匿身份的設備" -L.search_radar = "他持有像是雷達的物品。已經無法使用了。" -L.search_c4 = "你在他口袋中找到了一本筆記。記載著線路 {num} 是解除炸彈須剪除的一條。" - -L.search_dmg_crush = "他多處骨折。看起來是某種重物的衝擊撞死了他。" -L.search_dmg_bullet = "他很明顯是被射殺身亡的。" -L.search_dmg_fall = "他是墜落身亡的。" -L.search_dmg_boom = "他的傷口以及燒焦的衣物,應是爆炸導致其死亡。" -L.search_dmg_club = "他的身體有許多擦傷打擊痕跡,明顯是被毆打致死的。" -L.search_dmg_drown = "他身上的蛛絲馬跡顯示是溺死的。" -L.search_dmg_stab = "他是被刺擊與揮砍後,迅速失血致死的。" -L.search_dmg_burn = "聞起來有燒焦的恐怖分子在附近.." -L.search_dmg_tele = "看起來他的DNA以超光速粒子之形式散亂在附近。" -L.search_dmg_car = "他穿越馬路時被一個粗心的駕駛碾死了。" -L.search_dmg_other = "你無法找到這恐怖份子的具體死因。" - -L.search_weapon = "這顯示死者是被 {weapon} 所殺。" -L.search_head = "最嚴重的傷口在頭部。完全沒機會叫喊。" -L.search_time = "他大約死於你進行搜索的 {time} 前。" -L.search_dna = "用DNA掃描器檢索兇手的DNA標本,DNA樣本大約在 {time} 前開始衰退。" - -L.search_kills1 = "你找到一個名單,記載著他發現的死者:{player}" -L.search_kills2 = "你找到了一個名單,記載著他殺的這些人:" -L.search_eyes = "透過你的探查技能,你確信他臨死前見到的最後一個人: {player}。是兇手,還是巧合?" - -- Scoreboard L.sb_playing = "你正在玩的伺服是.." L.sb_mapchange = "地圖將於 {num} 個回合或是 {time} 後更換" @@ -233,7 +191,7 @@ L.sb_tag_avoid = "應迴避者" L.sb_tag_kill = "已死者" L.sb_tag_miss = "失蹤者" --- Equipment actions, like buying and dropping +-- Equipment actions, like buying and dropping L.buy_no_stock = "無法購買此裝備:你已擁有它了。" L.buy_pending = "你已訂購此裝備,請等待配送。" L.buy_received = "你已收到此裝備。" @@ -267,7 +225,6 @@ L.item_disg_desc = [[ 需要啟用時:在選單裡標記偽裝選項,或是按下相關數字鍵。]] -- C4 -L.c4_hint = "按下 {usekey} 來裝置或拆除C4。" L.c4_disarm_warn = "你所裝置的C4已被拆除。" L.c4_armed = "C4裝置成功。" L.c4_disarmed = "你成功拆除了C4。" @@ -288,6 +245,7 @@ L.c4_remove_destroy2 = "確認:銷毀" L.c4_disarm = "拆除C4" L.c4_disarm_cut = "點擊以剪斷 {num} 號引線" +L.c4_disarm_t = "剪斷引線以拆除C4。您是叛徒,當然每條引線都是安全的,但其他人可就沒那麼容易了!" L.c4_disarm_owned = "剪斷引線以拆除C4。你是裝置此C4的人,細節瞭然於胸,任一條引線都可成功拆除。" L.c4_disarm_other = "剪斷正確的引線以拆除C4。倘若你犯了錯,後果將不堪設想唷!" @@ -296,7 +254,6 @@ L.c4_status_disarmed = "拆除" -- Visualizer L.vis_name = "顯像器" -L.vis_hint = "按下 {usekey} 鍵撿起它(僅限於偵探)。" L.vis_desc = [[ 可讓犯罪現場顯像化的儀器。 @@ -305,7 +262,6 @@ L.vis_desc = [[ -- Decoy L.decoy_name = "雷達誘餌" -L.decoy_no_room = "你無法攜帶雷達誘餌。" L.decoy_broken = "你的雷達誘餌已被摧毀!" L.decoy_short_desc = "這個誘餌會為其他陣營顯示一個假雷達信號" @@ -316,7 +272,6 @@ L.decoy_desc = [[ -- Defuser L.defuser_name = "拆彈器" -L.defuser_help = " {primaryfire} 拆除目標炸彈。" L.defuser_desc = [[ 迅速拆除一個C4。 @@ -335,7 +290,6 @@ L.flare_desc = [[ L.hstation_name = "醫療站" L.hstation_broken = "你的醫療站已被摧毀!" -L.hstation_help = " {primaryfire} 裝置了一個醫療站。" L.hstation_desc = [[ 設置後,允許人們前來治療。恢復速度相當緩慢。 @@ -359,7 +313,6 @@ L.polter_desc = [[ -- Radio L.radio_broken = "你的收音機已被摧毀!" -L.radio_help_pri = " {primaryfire} 裝置了收音機。" L.radio_desc = [[ 播放音樂使人們分心、誤導。 @@ -371,8 +324,8 @@ L.sipistol_name = "消音手槍" L.sipistol_desc = [[ 噪音極小的手槍。使用一般的手槍彈藥。 -被害者被射殺時不會喊叫。]] +被害者被射殺時不會喊叫。]] -- Newton launcher L.newton_name = "牛頓發射器" @@ -405,7 +358,7 @@ L.dna_killer = "成功採集到兇手的DNA樣本!" L.dna_duplicate = "匹配!你的掃描儀裡已經有這個DNA樣本了。" L.dna_no_killer = "DNA樣本無法檢索(兇手已斷線?)" L.dna_armed = "炸彈已啟動!趕緊拆除它!" -L.dna_object = "在目標上採集到 {num} 個新DNA樣本。" +--L.dna_object = "Collected a sample of the last owner from the object." L.dna_gone = "區域內沒偵測到可採集之DNA樣本。" L.dna_desc = [[ @@ -474,7 +427,7 @@ L.hp_wounded = "輕重傷的" L.hp_badwnd = "重傷的" L.hp_death = "近乎死亡" --- TargetID karma status +-- TargetID Karma status L.karma_max = "名聲好" L.karma_high = "有點粗魯" L.karma_med = "扣扳機愛好者" @@ -483,14 +436,13 @@ L.karma_min = "負人命債累累" -- TargetID misc L.corpse = "屍體" -L.corpse_hint = "按下 [{usekey}] 來搜索,用 [{walkkey} + {usekey}] 進行無聲搜索。" +--L.corpse_hint = "Press [{usekey}] to search and confirm. [{walkkey} + {usekey}] to search covertly." L.target_disg = " (偽裝狀態)" L.target_unid = "未確認的屍體" +L.target_unknown = "一名恐怖分子" -L.target_credits = "搜索屍體以獲取未被消耗的信用點數" - --- HUD buttons with hand icons that only traitors can see +-- HUD buttons with hand icons that only some roles can see and use L.tbut_single = "單獨使用" L.tbut_reuse = "重複使用" L.tbut_retime = "在 {num} 秒後重複使用" @@ -503,8 +455,7 @@ L.mute_all = "全部靜音" L.mute_off = "取消靜音" -- Spectators and prop possession -L.punch_title = "重擊測量器 " --"PUNCH-O-METER" -L.punch_help = "按下行走鍵或跳躍鍵以推撞物品;按蹲下鍵則離開物品控制。" +L.punch_title = "重擊測量器 " L.punch_bonus = "你的分數較低,重擊測量器上限減少 {num}" L.punch_malus = "你的分數較高,重擊測量器上限增加 {num}!" @@ -863,8 +814,8 @@ L.aw_tod1_text = "在他的團隊即將獲得勝利的前幾秒死去。" L.aw_tod2_title = "人家不依啦!" L.aw_tod2_text = "在這回合剛開始不久即被殺害。" --- New and modified pieces of text are placed below this point, marked with the --- version in which they were added, to make updating translations easier. +-- New and modified pieces of text are placed below this point, marked with the +-- version in which they were added, to make updating translations easier. -- v24 L.drop_no_ammo = "你彈夾內的子彈不足以丟棄成彈藥盒。" @@ -932,16 +883,13 @@ L.shop_role_select = "選擇身份" L.shop_role_selected = "選中了 {role} 的商店!" L.shop_search = "搜索" -L.spec_help = "點擊來觀察玩家,或對著物理道具按 {usekey} 來附身。" -L.spec_help2 = "若想離開觀察者模式,用 {helpkey} 打開菜單,在“遊戲性”選項中勾選選項。" - -- 2019-10-19 L.drop_ammo_prevented = "有什麽東西阻擋你丟出子彈。" -- 2019-10-28 L.target_c4 = "按 [{usekey}] 打開C4菜單" L.target_c4_armed = "按 [{usekey}] 拆除C4" -L.target_c4_armed_defuser = "按 [{usekey}] 使用拆彈器" +L.target_c4_armed_defuser = "按 [{primaryfire}] 使用拆彈器" L.target_c4_not_disarmable = "你不能拆除存活隊友的C4" L.c4_short_desc = "可以炸得很歡" @@ -949,16 +897,15 @@ L.target_pickup = "按 [{usekey}] 撿起" L.target_slot_info = "槽位:{slot}" L.target_pickup_weapon = "按 [{usekey}] 撿起武器" L.target_switch_weapon = "按 [{usekey}] 和當前武器交換" -L.target_pickup_weapon_hidden = "按 [{usekey} + {walkkey}] 隱秘地撿起" -L.target_switch_weapon_hidden = "按 [{usekey} + {walkkey}] 隱秘地交換" +L.target_pickup_weapon_hidden = "按 [{walkkey} + {usekey}] 隱秘地撿起" +L.target_switch_weapon_hidden = "按 [{walkkey} + {usekey}] 隱秘地交換" L.target_switch_weapon_nospace = "沒有提供給這個武器的槽位" L.target_switch_drop_weapon_info = "丟棄槽位 {slot} 的 {name}" L.target_switch_drop_weapon_info_noslot = "槽位 {slot} 沒有可丟棄的武器" -L.corpse_searched_by_detective = "這個屍體被探長搜查過" +--L.corpse_searched_by_detective = "This corpse was searched by a public policing role" L.corpse_too_far_away = "這個屍體太遠了。" -L.radio_pickup_wrong_team = "你不能撿起其他隊伍的收音機" L.radio_short_desc = "武器聲音,悅耳動聽" L.hstation_subtitle = "按 [{usekey}] 恢復生命" @@ -1003,7 +950,6 @@ L.mute_team = "靜音 {team}" L.door_auto_closes = "此門會自動關閉" L.door_open_touch = "此門接觸後會自動開啟" L.door_open_touch_and_use = "接觸門或按 [{usekey}] 開門。" -L.hud_health = "生命" -- 2020-03-09 L.help_title = "幫助和設定" @@ -1025,7 +971,7 @@ L.menu_guide_description = "幫助你開始遊玩 TTT2 並解釋玩法和身份 L.menu_bindings_description = "將 TTT2 和其插件的功能綁到你想要的鍵位" L.menu_language_description = "選擇遊戲語言" L.menu_appearance_description = "調整界面的樣式和性能" -L.menu_gameplay_description = "避免特定身份和其他遊戲相關選項" +--L.menu_gameplay_description = "Tweak voice and sound volume, accessibility settings, and gameplay settings." L.menu_addons_description = "配置本地插件" L.menu_legacy_description = "舊TTT的菜單,應該已被導入新系統" L.menu_administration_description = "界面和商店的通用管理菜單" @@ -1049,10 +995,8 @@ L.submenu_appearance_crosshair_title = "準星" L.submenu_appearance_dmgindicator_title = "傷害指示" L.submenu_appearance_performance_title = "性能" L.submenu_appearance_interface_title = "界面" -L.submenu_appearance_miscellaneous_title = "其他" L.submenu_gameplay_general_title = "通用" -L.submenu_gameplay_avoidroles_title = "避免特定身份" L.submenu_administration_hud_title = "HUD 設置" L.submenu_administration_randomshop_title = "隨機商店" @@ -1089,17 +1033,12 @@ L.label_shop_show_slot = "顯示裝備槽位" L.label_shop_show_custom = "顯示自定義標記" L.label_shop_show_fav = "顯示最愛標記" L.label_crosshair_enable = "啟用十字準星" -L.label_crosshair_gap_enable = "啟用自定義準星大小" -L.label_crosshair_gap = "自定義準星大小" L.label_crosshair_opacity = "準星透明度" L.label_crosshair_ironsight_opacity = "瞄準時準星透明度" L.label_crosshair_size = "準星長度" L.label_crosshair_thickness = "準星粗細" L.label_crosshair_thickness_outline = "準星外框粗細" -L.label_crosshair_static_enable = "啟用靜態準星" -L.label_crosshair_dot_enable = "啟用準星中點" -L.label_crosshair_lines_enable = "啟用準星直線" -L.label_crosshair_scale_enable = "啟用武器對應準星大小" +--L.label_crosshair_scale_enable = "Enable dynamic crosshair scale" L.label_crosshair_ironsight_low_enabled = "瞄準時降低武器模型" L.label_damage_indicator_enable = "啟用傷害指示" L.label_damage_indicator_mode = "選擇傷害指示主題" @@ -1118,13 +1057,10 @@ L.label_gameplay_specmode = "觀察者模式(永遠觀察)" L.label_gameplay_fastsw = "武器快速切換" L.label_gameplay_hold_aim = "啟用持續瞄準" L.label_gameplay_mute = "死亡時靜音存活玩家" -L.label_gameplay_dtsprint_enable = "啟用雙擊沖刺" -L.label_gameplay_dtsprint_anykey = "沖刺時任何方向鍵都持續沖刺" L.label_hud_default = "默認 HUD" L.label_hud_force = "強製 HUD" L.label_bind_weaponswitch = "撿起武器" -L.label_bind_sprint = "沖刺" L.label_bind_voice = "全局語言" L.label_bind_voice_team = "團隊語言" @@ -1148,7 +1084,6 @@ L.header_damage_indicator = "傷害指示設置" L.header_performance_settings = "性能設置" L.header_interface_settings = "界面設置" L.header_gameplay_settings = "遊戲性設置" -L.header_roleselection = "啟用身份分配" L.header_hud_administration = "選擇默認和強製 HUD" L.header_hud_enabled = "啟用/禁用 HUDs" @@ -1195,11 +1130,7 @@ L.hud_revival_time = "{time}秒" L.door_destructible = "此門不可摧毀({health}生命)" -- 2020-05-28 -L.confirm_detective_only = "只有偵探能確認死亡。" -L.inspect_detective_only = "只有偵探能檢查屍體。" -L.corpse_hint_no_inspect = "只有偵探能檢查這個屍體。" -L.corpse_hint_inspect_only = "按 [{usekey}] 搜索。只有偵探能確認死亡。" -L.corpse_hint_inspect_only_credits = "按 [{usekey}] 獲取信用點數。只有偵探能確認死亡。" +--L.corpse_hint_inspect_limited = "Press [{usekey}] to search. [{walkkey} + {usekey}] to only view search UI." -- 2020-06-04 L.label_bind_disguiser = "切換偽裝器" @@ -1214,7 +1145,6 @@ L.binoc_help_sec = "切換放大倍率" L.vis_help_pri = "丟棄當前設備。" -L.decoy_help_pri = "安放誘餌。" -- 2020-08-07 L.pickup_error_spec = "作為觀察者你無法撿起這個。" @@ -1298,6 +1228,7 @@ L.trap_something = "某件物品" -- Kill events L.desc_event_kill_suicide = "是自殺的" L.desc_event_kill_team = "是被隊友殺的" + L.desc_event_kill_blowup = "{victim} ({vrole} / {vteam}) 被自己炸飛。" L.desc_event_kill_blowup_trap = "{victim} ({vrole} / {vteam}) 被 {trap} 炸飛。" @@ -1409,12 +1340,6 @@ L.xfer_team_indicator = "陣營" -- 2021-06-25 L.searchbar_default_placeholder = "在列表中搜索..." --- 2021-07-07 -L.header_equipment_weapon_spawn_setup = "武器生成設置" - -L.equipmenteditor_name_auto_spawnable = "裝備隨機生成" -L.equipmenteditor_name_spawn_type = "生成類型" - -- 2021-07-11 L.spec_about_to_revive = "在復活時,觀察將被限製。" @@ -1474,7 +1399,7 @@ L.help_spawn_editor_info = [[ L.help_spawn_editor_enable = "在某些地圖上,可能會建議使用在地圖自帶的原始生成點,而不用動態系統來取代它們。禁用這個複選框只對當前活動地圖禁用。其他地圖仍將使用動態系統。" L.help_spawn_editor_hint = "提示:要離開生成編輯器,重新打開遊戲模式菜單。" L.help_spawn_editor_spawn_amount = [[ -目前在這張地圖上有 {weapon} 個武器生成點,{ammo} 個彈藥生成點和 player} 個玩家生成點。 +目前在這張地圖上有 {weapon} 個武器生成點,{ammo} 個彈藥生成點和 {player} 個玩家生成點。 點擊'開始編輯生成'來改變這個生成。 {weaponrandom}x 隨機武器生成 @@ -1540,8 +1465,9 @@ L.kill_score_team = "擊殺隊友:" -- 2021-10-09 L.help_models_select = [[ 左鍵點擊模型,將其添加到玩家模型庫中。再次以左鍵刪除它們。右鍵可在所關注的模型的啟用和禁用偵探帽之間進行切換。 - + 左上角的小指示器顯示玩家模型是否有頭部的命中箱,下面的圖標顯示了這個模型是否可佩戴偵探帽。]] + L.menu_roles_title = "角色設置" L.menu_roles_description = "設置生成概率、裝備積分及更多。" @@ -1733,8 +1659,6 @@ L.label_bots_are_spectators = "機器人永遠是觀察者" L.label_tbutton_admin_show = "向管理員顯示叛徒按鈕" L.label_ragdoll_carrying = "啟用布娃娃搬運" L.label_prop_throwing = "啟用道具投擲" -L.label_ragdoll_pinning = "為非無辜者角色啟用布娃娃夾子" -L.label_ragdoll_pinning_innocents = "為無辜者啟用布娃娃夾子" L.label_weapon_carrying = "啟用武器搬運" L.label_weapon_carrying_range = "武器搬運範圍" L.label_prop_carrying_force = "Prop推進力" @@ -1760,13 +1684,12 @@ L.label_round_limit = "回合數上限" L.label_time_limit_minutes = "遊戲時間上限,以分鐘為單位" L.label_nade_throw_during_prep = "在準備時間內允許投擲手榴彈" L.label_postround_dm = "回合結束後啟用死亡競賽" +L.label_session_limits_enabled = "啟用地圖更換" L.label_spectator_chat = "啟用觀察者與大家聊天的功能" L.label_lastwords_chatprint = "如果在打字時被殺,則發出最後一句話至聊天室" L.label_identify_body_woconfirm = "不按'確認'按鈕識別屍體" -L.label_announce_body_found = "宣布發現了一具屍體" +--L.label_announce_body_found = "Announce that a body was found when the body was confirmed" L.label_confirm_killlist = "宣布確認屍體時,該屍體的擊殺名單" -L.label_inspect_detective_only = "只允許警察角色搜查屍體" -L.label_confirm_detective_only = "只允許警察角色確認死者" L.label_dyingshot = "如果玩家在瞄準中,則在死亡時開槍[試驗性]" L.label_armor_block_headshots = "啟用護甲阻擋爆頭傷害" L.label_armor_block_blastdmg = "啟用護甲阻擋爆炸傷害" @@ -1825,13 +1748,9 @@ L.label_sprint_enabled = "啟用沖刺功能" L.label_sprint_max = "沖刺體力最大值" L.label_sprint_stamina_consumption = "體力消耗系數" L.label_sprint_stamina_regeneration = "體力恢復系數" -L.label_sprint_crosshair = "沖刺時顯示準星" L.label_crowbar_unlocks = "主要攻擊鍵可以作為互動(即解鎖)使用" L.label_crowbar_pushforce = "撬棍推動力" ---2022-04-13 -L.label_session_limits_enabled = "啟用地圖更換" - -- 2022-07-02 L.header_playersettings_falldmg = "摔落傷害設置" @@ -1864,4 +1783,411 @@ L.sb_rank_tooltip_streamer = "主播" L.sb_rank_tooltip_heroes = "TTT2 英雄" L.sb_rank_tooltip_team = "陣營" -L.tbut_adminarea = "管理區:" \ No newline at end of file +L.tbut_adminarea = "管理區:" + +-- 2023-08-10 +L.equipmenteditor_name_damage_scaling = "傷害縮放" + +-- 2023-08-11 +L.equipmenteditor_name_allow_drop = "允許丟棄" +L.equipmenteditor_desc_allow_drop = "如果啟用,玩家可以自由地丟棄裝備。" + +L.equipmenteditor_name_drop_on_death_type = "死亡時丟棄" +L.equipmenteditor_desc_drop_on_death_type = "嘗試覆蓋玩家死亡時裝備是否被丟棄的操作。" + +L.drop_on_death_type_default = "默認(由武器定義)" +L.drop_on_death_type_force = "強制死亡時丟棄" +L.drop_on_death_type_deny = "拒絕死亡時丟棄" + +-- 2023-08-26 +L.equipmenteditor_name_kind = "裝備槽" +L.equipmenteditor_desc_kind = "裝備將佔用的庫存槽。" + +L.slot_weapon_melee = "近戰槽" +L.slot_weapon_pistol = "手槍槽" +L.slot_weapon_heavy = "重型槽" +L.slot_weapon_nade = "手雷槽" +L.slot_weapon_carry = "攜帶槽" +L.slot_weapon_unarmed = "空手槽" +L.slot_weapon_special = "特殊槽" +L.slot_weapon_extra = "額外槽" +L.slot_weapon_class = "職業槽" + +-- 2023-10-04 +--L.label_voice_duck_spectator = "Duck spectator voices" +--L.label_voice_duck_spectator_amount = "Spectator voice duck amount" +--L.label_voice_scaling = "Voice Volume Scaling Mode" +--L.label_voice_scaling_mode_linear = "Linear" +--L.label_voice_scaling_mode_power4 = "Power 4" +--L.label_voice_scaling_mode_log = "Logarithmic" + +-- 2023-10-07 +L.search_title = "屍體搜索結果 - {player}" +L.search_info = "訊息" +L.search_confirm = "確認死亡" +--L.search_confirm_credits = "Confirm (+{credits} Credit(s))" +--L.search_take_credits = "Take {credits} Credit(s)" +--L.search_confirm_forbidden = "Confirm forbidden" +--L.search_confirmed = "Death Confirmed" +--L.search_call = "Report Death" +--L.search_called = "Death Reported" + +--L.search_team_role_unknown = "???" + +L.search_words = "某些事物讓你了解到此人的遺言: {lastwords}" +L.search_armor = "他穿戴非標準裝甲。" +L.search_disguiser = "他持有一個能隱匿身份的設備" +L.search_radar = "他持有像是雷達的物品。已經無法使用了。" +L.search_c4 = "你在他口袋中找到了一本筆記。記載著線路 {num} 是解除炸彈須剪除的一條。" + +L.search_dmg_crush = "他多處骨折。看起來是某種重物的衝擊撞死了他。" +L.search_dmg_bullet = "他很明顯是被射殺身亡的。" +L.search_dmg_fall = "他是墜落身亡的。" +L.search_dmg_boom = "他的傷口以及燒焦的衣物,應是爆炸導致其死亡。" +L.search_dmg_club = "他的身體有許多擦傷打擊痕跡,明顯是被毆打致死的。" +L.search_dmg_drown = "他身上的蛛絲馬跡顯示是溺死的。" +L.search_dmg_stab = "他是被刺擊與揮砍後,迅速失血致死的。" +L.search_dmg_burn = "聞起來有燒焦的恐怖分子在附近.." +L.search_dmg_teleport = "看起來他的DNA以超光速粒子之形式散亂在附近。" +L.search_dmg_car = "他穿越馬路時被一個粗心的駕駛碾死了。" +L.search_dmg_other = "你無法找到這恐怖份子的具體死因。" + +--L.search_floor_antlions = "There are still antlions all over the body. The floor must be covered with them." +--L.search_floor_bloodyflesh = "The blood on this body looks old and disgusting. There are even small bits of bloody flesh stuck to their shoes." +--L.search_floor_concrete = "Gray dust covers their shoes and knees. Looks as if the crime scene had a concrete floor." +--L.search_floor_dirt = "It smells earthy. It probably stems from the dirt that clings to the victims shoes." +--L.search_floor_eggshell = "Disgusting looking white specks cover the body of the victim. It looks like egg shells." +--L.search_floor_flesh = "The victim's clothing feels kinda moist. As if they fell onto a wet surface. Like a fleshy surface, or the sandy ground of a water body." +--L.search_floor_grate = "The skin of the victim looks like a steak. Thick lines arranged in a grid are visible all over them. Did they rest on a grate?" +--L.search_floor_alienflesh = "Alien flesh, you think? Sounds kinda outlandish. But your detective helper book lists it as a possible floor surface." +--L.search_floor_snow = "On first glance their clothing only feels wet and ice-cold. But once you see the white foam on the rims you understand. It's snow!" +--L.search_floor_plastic = "'Ouch, that has to hurt.' Their body is covered in burns. They look like those you get when sliding over a plastic surface." +--L.search_floor_metal = "At least they can't get tetanus now that they are dead. Rust covers their wounds. They probably died on a metal surface." +--L.search_floor_sand = "Small little rough rocks are stuck to their cold body. Like coarse sand from a beach. Argh, it gets everywhere!" +--L.search_floor_foliage = "Nature is wonderful. The victim's bloody wounds are covered with enough foliage that they are almost hidden." +--L.search_floor_computer = "Beep-boop. Their body is covered in computer surface! How does this look, you might ask? Well, duh!" +--L.search_floor_slosh = "Wet and maybe even a bit slimy. Their whole body is covered with it and their clothes are soaked. It stinks!" +--L.search_floor_tile = "Small shards are stuck to their skin. Like shards from floor tiles that shattered on inpact." +--L.search_floor_grass = "It smells like fresh cut grass. The smell almost overpowers the smell of blood and death." +--L.search_floor_vent = "You feel a fresh gust of air when feeling their body. Did they die in a vent and take the air with them?" +--L.search_floor_wood = "What's nicer than sitting on a hardwood floor and dwelling in thoughts? At least lot lying dead on a wooden floor!" +--L.search_floor_default = "That seems so basic, so normal. Almost default. You can't tell anything about the kind of surface." +--L.search_floor_glass = "Their body is covered with many bloody cuts. In some of them glass shards are stuck and look rather threatening to you." +--L.search_floor_warpshield = "A floor made out of warpshield? Yep, we are as confused as you were. But our notes clearly state it. Warpshield." + +--L.search_water_1 = "The victim's shoes are wet, but the rest seems dry. They were probably killed with their feet in water." +--L.search_water_2 = "The victim's shoes are trousers are soaked through. Did they wander through water before they were killed?" +--L.search_water_3 = "The whole body is wet and swollen. They probably died while they were completely submerged." + +L.search_weapon = "這顯示死者是被 {weapon} 所殺。" +L.search_head = "最嚴重的傷口在頭部。完全沒機會叫喊。" +--L.search_time = "They died a while before you conducted the search." +--L.search_dna = "Retrieve a sample of the killer's DNA with a DNA Scanner. The DNA sample will decay after a while." + +L.search_kills1 = "你找到一個名單,記載著他發現的死者:{player}" +L.search_kills2 = "你找到了一個名單,記載著他殺的這些人:{player}" +L.search_eyes = "透過你的探查技能,你確信他臨死前見到的最後一個人: {player}。是兇手,還是巧合?" + +--L.search_credits = "The victim has {credits} equipment credit(s) in their pocket. A shopping role might take them and put them to good use. Keep an eye out!" + +--L.search_kill_distance_point_blank = "It was a point blank attack." +--L.search_kill_distance_close = "The attack came from a short distance." +--L.search_kill_distance_far = "The victim was attacked from a long distance away." + +--L.search_kill_from_front = "The victim was shot from the front." +--L.search_kill_from_back = "The victim was shot from behind." +--L.search_kill_from_side = "The victim was shot from the side." + +--L.search_hitgroup_head = "The projectile was found in their head." +--L.search_hitgroup_chest = "The projectile was found in their chest." +--L.search_hitgroup_stomach = "The projectile was found in their stomach." +--L.search_hitgroup_rightarm = "The projectile was found in their right arm." +--L.search_hitgroup_leftarm = "The projectile was found in their left arm." +--L.search_hitgroup_rightleg = "The projectile was found in their right leg." +--L.search_hitgroup_leftleg = "The projectile was found in their left leg." +--L.search_hitgroup_gear = "The projectile was found in their hip." + +--L.search_policingrole_report_confirm = [[ +--A public policing role can only be called to a dead body after the corpse was confirmed dead.]] +--L.search_policingrole_confirm_disabled_1 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know!]] +--L.search_policingrole_confirm_disabled_2 = [[ +--The corpse can only be confirmed by a public policing role. Report the body to let them know! +--You can see the information in here after they confirmed it.]] +--L.search_spec = [[ +--As a spectator you are able to see all information of a corpse, but unable to interact with the UI.]] + +--L.search_title_words = "Victim's last words" +--L.search_title_c4 = "Defusion mishap" +--L.search_title_dmg_crush = "Crush damage ({amount} HP)" +--L.search_title_dmg_bullet = "Bullet damage ({amount} HP)" +--L.search_title_dmg_fall = "Fall damage ({amount} HP)" +--L.search_title_dmg_boom = "Explosion damage ({amount} HP)" +--L.search_title_dmg_club = "Club damage ({amount} HP)" +--L.search_title_dmg_drown = "Drowning damage ({amount} HP)" +--L.search_title_dmg_stab = "Stabbing damage ({amount} HP)" +--L.search_title_dmg_burn = "Burning damage ({amount} HP)" +--L.search_title_dmg_teleport = "Teleport damage ({amount} HP)" +--L.search_title_dmg_car = "Car accident ({amount} HP)" +--L.search_title_dmg_other = "Unknown damage ({amount} HP)" +--L.search_title_time = "Death time" +--L.search_title_dna = "DNA sample decay" +--L.search_title_kills = "The victim's kill list" +--L.search_title_eyes = "The killer's shadow" +--L.search_title_floor = "Floor of the crime scene" +--L.search_title_credits = "{credits} Equipment credit(s)" +--L.search_title_water = "Water level {level}" +--L.search_title_policingrole_report_confirm = "Confirm to report death" +--L.search_title_policingrole_confirm_disabled = "Report corpse" +--L.search_title_spectator = "You are a spectator" + +--L.target_credits_on_confirm = "Confirm to receive unspent credits" +--L.target_credits_on_search = "Search to receive unspent credits" +--L.corpse_hint_no_inspect_details = "Only public policing roles can find information on this body." +--L.corpse_hint_inspect_limited_details = "Only public policing roles can confirm the body." +--L.corpse_hint_spectator = "Press [{usekey}] to view corpse UI" +--L.corpse_hint_public_policing_searched = "Press [{usekey}] to view search results from public policing role" + +--L.label_inspect_confirm_mode = "Select body search mode" +--L.choice_inspect_confirm_mode_0 = "mode 0: standard TTT" +--L.choice_inspect_confirm_mode_1 = "mode 1: limited confirm" +--L.choice_inspect_confirm_mode_2 = "mode 2: limited search" +--L.help_inspect_confirm_mode = [[ +--There are three different body search/confirm modes in this gamemode. The selection of this mode has huge influences to the importance of public policing roles like the detective. +-- +--mode 0: This is standard TTT behavior. Everyone can search and confirm bodies. To report a body or to take the credits from it, the body first has to be confirmed. This makes it a bit harder for shopping roles to sneakily steal credits. However innocent players that want to report the body to call a public policing player need to confirm first as well. +-- +--mode 1: This mode increases the importance of public policing roles by limiting the confirmation option to them. This also means that taking credits and reporting bodies is now also possible before confirming a body. Everybody can still search dead bodies and find the information, but they are unable to announce the found information. +-- +--mode 2: This mode is yet a bit more strict than mode 1. In this mode the search ability is removed as well from normal players. This means that reporting a dead body to a public policing player is now the only way to get any information from dead bodies.]] + +-- 2023-10-19 +--L.label_grenade_trajectory_ui = "Grenade trajectory indicator" + +-- 2023-10-23 +--L.label_hud_pulsate_health_enable = "Pulsate healthbar when below 25% health" +--L.header_hud_elements_customize = "Customize the HUD-Elements" +--L.help_hud_elements_special_settings = "These are specific settings for the used HUD-Elements." + +-- 2023-10-25 +--L.help_keyhelp = [[ +--Key bind helpers are part of a UI element that always shows relevant keybindings to the player, which is especially helpful for new players. There are three different types of key bindings: +-- +--Core: These contain the most important bindings found in TTT2. Without them the game is hard to play to its full potential. +--Extra: Similar to core, but you don't always need them. They contain stuff like chat, voice or flashlight. It might be helpful for new players to enable this. +--Equipment: Some equipment items have their own bindings, these are shown in this category. +-- +--Disabled categories are still shown when the scoreboard is visible]] + +--L.label_keyhelp_show_core = "Enable always showing the core bindings" +--L.label_keyhelp_show_extra = "Enable always showing the extra bindings" +--L.label_keyhelp_show_equipment = "Enable always showing the equipment bindings" + +--L.header_interface_keys = "Key helper settings" +--L.header_interface_wepswitch = "Weapon switch UI settings" + +--L.label_keyhelper_help = "open gamemode menu" +--L.label_keyhelper_mutespec = "cycle spectator voice mode" +--L.label_keyhelper_shop = "open equipment shop" +--L.label_keyhelper_show_pointer = "free mouse pointer" +--L.label_keyhelper_possess_focus_entity = "possess focused entity" +--L.label_keyhelper_spec_focus_player = "spectate focused player" +--L.label_keyhelper_spec_previous_player = "previous player" +--L.label_keyhelper_spec_next_player = "next player" +--L.label_keyhelper_spec_player = "spectate random player" +--L.label_keyhelper_possession_jump = "prop: jump" +--L.label_keyhelper_possession_left = "prop: left" +--L.label_keyhelper_possession_right = "prop: right" +--L.label_keyhelper_possession_forward = "prop: forward" +--L.label_keyhelper_possession_backward = "prop: backward" +--L.label_keyhelper_free_roam = "leave object and roam free" +--L.label_keyhelper_flashlight = "toggle flashlight" +--L.label_keyhelper_quickchat = "open quickchat" +--L.label_keyhelper_voice_global = "global voice chat" +--L.label_keyhelper_voice_team = "team voice chat" +--L.label_keyhelper_chat_global = "global chat" +--L.label_keyhelper_chat_team = "team chat" +--L.label_keyhelper_show_all = "show all" +--L.label_keyhelper_disguiser = "toggle disguiser" +--L.label_keyhelper_save_exit = "save and exit" +--L.label_keyhelper_spec_third_person = "toggle third person view" + +-- 2023-10-26 +--L.item_armor_reinforced = "Reinforced Armor" +--L.item_armor_sidebar = "Armor protects you against bullets penetrating your body. But not forever." +--L.item_disguiser_sidebar = "The disguiser protects your identity by not showing your name to other players." +--L.status_speed_name = "Speed Multiplier" +--L.status_speed_description_good = "You are faster than normal. Items, equipment or effects can influence this." +--L.status_speed_description_bad = "You are slower than normal. Items, equipment or effects can influence this." + +--L.status_on = "on" +--L.status_off = "off" + +--L.crowbar_help_primary = "Attack" +--L.crowbar_help_secondary = "Push players" + +-- 2023-10-27 +--L.help_HUD_enable_description = [[ +--Some HUD elements like the key helper or sidebar show detailed information when the scoreboard is open. This can be disabled to reduce clutter.]] +--L.label_HUD_enable_description = "Enable descriptions when scoreboard is open" +--L.label_HUD_enable_box_blur = "Enable UI box background blur" + +-- 2023-10-28 +--L.submenu_gameplay_voiceandvolume_title = "Voice & Volume" +--L.header_soundeffect_settings = "Sound Effects" +--L.header_voiceandvolume_settings = "Voice & Volume Settings" + +-- 2023-11-06 +--L.drop_reserve_prevented = "Something prevents you from dropping your reserve ammo." +--L.drop_no_reserve = "Insufficient ammo in your reserve to drop as an ammo box." +--L.drop_no_room_ammo = "You have no room here to drop your ammo!" + +-- 2023-11-14 +--L.hat_deerstalker_name = "Detective's Hat" + +-- 2023-11-16 +--L.help_prop_spec_dash = [[ +--Propspec dashes are movements into the direction of the aim vector. They can be of higher force than the normal movement. Higher force also means higher base value consumption. +-- +--This variable is a multiplier of the push force.]] +--L.label_spec_prop_dash = "Dash force multiplier" +--L.label_keyhelper_possession_dash = "prop: dash in view direction" +--L.label_keyhelper_weapon_drop = "drop selected weapon if possible" +--L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" + +-- 2023-12-07 +--L.c4_help_primary = "Place the C4" +--L.c4_help_secondary = "Stick to surface" + +-- 2023-12-11 +--L.magneto_help_primary = "Push entity" +--L.magneto_help_secondary = "Pull / pickup entity" +--L.knife_help_primary = "Stab" +--L.knife_help_secondary = "Throw knife" +--L.polter_help_primary = "Fire thumper" +--L.polter_help_secondary = "Charge long range shot" + +-- 2023-12-12 +--L.newton_help_primary = "Knockback shot" +--L.newton_help_secondary = "Charged knockback shot" + +-- 2023-12-13 +--L.vis_no_pickup = "Only public policing roles can pick up the visualizer" +--L.newton_force = "FORCE" +--L.defuser_help_primary = "Defuse targeted C4" +--L.radio_help_primary = "Place the Radio" +--L.radio_help_secondary = "Stick to surface" +--L.hstation_help_primary = "Place the Health Station" +--L.flaregun_help_primary = "Burn body/entity" + +-- 2023-12-14 +--L.marker_vision_owner = "Owner: {owner}" +--L.marker_vision_distance = "Distance: {distance}m" +--L.marker_vision_distance_collapsed = "{distance}m" + +--L.c4_marker_vision_time = "Detonation time: {time}" +--L.c4_marker_vision_collapsed = "{time} / {distance}m" + +--L.c4_marker_vision_safe_zone = "Bomb safe zone" +--L.c4_marker_vision_damage_zone = "Bomb damage zone" +--L.c4_marker_vision_kill_zone = "Bomb kill zone" + +--L.beacon_marker_vision_player = "Tracked Player" +--L.beacon_marker_vision_player_tracked = "This player is tracked by a Beacon" + +-- 2023-12-18 +--L.beacon_help_pri = "Throw Beacon on the ground" +--L.beacon_help_sec = "Stick Beacon to surface" +--L.beacon_name = "Beacon" +--L.beacon_desc = [[ +--Broadcasts player locations to everyone in a sphere around this beacon. +-- +--Use to keep track of locations on the map that are hard to see.]] + +--L.msg_beacon_destroyed = "One of your beacons has been destroyed!" +--L.msg_beacon_death = "A player died in close proximity to one of your beacons." + +--L.beacon_pickup_disabled = "Only the owner of the beacon can pick it up" +--L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks around them" + +-- 2023-12-18 +--L.entity_pickup_owner_only = "Only the owner can pick this up" + +-- 2023-12-18 +L.body_confirm_one = "{finder} 確認了 {victim} 的死。" +--L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." + +-- 2023-12-19 +--L.builtin_marker = "Built-in." +--L.equipmenteditor_desc_builtin = "This equipment is built-in, it comes with TTT2!" +--L.help_roles_builtin = "This role is built-in, it comes with TTT2!" +--L.header_equipment_info = "Equipment information" + + +-- 2023-12-24 +--L.submenu_gameplay_accessibility_title = "Accessibility" + +--L.header_accessibility_settings = "Accessibility Settings" + +--L.label_enable_dynamic_fov = "Enable dynamic FOV change" +--L.label_enable_bobbing = "Enable view bobbing" +--L.label_enable_bobbing_strafe = "Enable view bobbing when strafing" + +--L.help_enable_dynamic_fov = "Dynamic FOV is applied depending on the player's speed. When a player is sprinting for example, the FOV is increased to visualize the speed." +--L.help_enable_bobbing_strafe = "View bobbing is the slight camera shake while walking, swimming or falling." +-- 2023-12-20 +--L.equipmenteditor_desc_damage_scaling = [[Multiplies the base damage value of a weapon by this factor. +--For a shotgun, this would affect each pellet. +--For a rifle, this would affect just the bullet. +--For the poltergeist, this would affect each "thump" and the final explosion. +-- +--0.5 = Deal half the amount of damage. +--2 = Deal twice the amount of damage. +-- +--Note: Some weapons might not use this value which causes this modifier to be ineffective.]] + +-- 2023-12-24 +--L.binoc_help_reload = "Clear target." +--L.cl_sb_row_sresult_direct_conf = "Direct confirmation" +--L.cl_sb_row_sresult_pub_police = "Public policing role confirmation" + +-- 2024-01-05 +--L.label_crosshair_thickness_outline_enable = "Enable crosshair outline" +--L.label_crosshair_outline_high_contrast = "Enable outline high contrast color" +--L.label_crosshair_mode = "Crosshair mode" +--L.label_crosshair_static_length = "Enable static crosshair line length" + +--L.choice_crosshair_mode_0 = "Lines and dot" +--L.choice_crosshair_mode_1 = "Lines only" +--L.choice_crosshair_mode_2 = "Dot only" + +--L.help_crosshair_scale_enable = [[ +--Dynamic crosshair enables scaling the crosshair depending on the weapon's cone. The cone is influenced by the weapon's base accuracy, multiplied with external factors such as jumping and sprinting. +-- +--If the line length is kept static, only the gap scales with cone changes.]] + +--L.header_weapon_settings = "Weapon Settings" + + +--L.marker_vision_visible_for_0 = "Visible for you" +--L.marker_vision_visible_for_1 = "Visible for your role" +--L.marker_vision_visible_for_2 = "Visible for your team" +--L.marker_vision_visible_for_3 = "Visible for everyone" + +-- 2024-01-27 +L.decoy_help_primary = "安放誘餌" +--L.decoy_help_secondary = "Stick Decoy to surface" + +-- 2024-01-24 +--L.grenade_fuse = "FUSE" + +-- 2024-01-25 +--L.header_roles_magnetostick = "Magneto Stick" +--L.label_roles_ragdoll_pinning = "Enable ragdoll pinning" +--L.magneto_stick_help_carry_rag_pin = "Pin ragdoll" +--L.magneto_stick_help_carry_rag_drop = "Drop ragdoll" +--L.magneto_stick_help_carry_prop_release = "Release prop" +--L.magneto_stick_help_carry_prop_drop = "Drop prop" + +-- 2024-02-14 +--L.throw_no_room = "You have no space here to throw this device" diff --git a/lua/terrortown/menus/gamemode/addons.lua b/lua/terrortown/menus/gamemode/addons.lua index 637f67a6d..5678e522b 100644 --- a/lua/terrortown/menus/gamemode/addons.lua +++ b/lua/terrortown/menus/gamemode/addons.lua @@ -9,5 +9,5 @@ CLGAMEMODEMENU.priority = 94 -- overwrite and return true to enable a searchbar function CLGAMEMODEMENU:HasSearchbar() - return true + return true end diff --git a/lua/terrortown/menus/gamemode/administration.lua b/lua/terrortown/menus/gamemode/administration.lua index 939f94afa..9b65cd84a 100644 --- a/lua/terrortown/menus/gamemode/administration.lua +++ b/lua/terrortown/menus/gamemode/administration.lua @@ -8,5 +8,5 @@ CLGAMEMODEMENU.description = "menu_administration_description" CLGAMEMODEMENU.priority = 50 function CLGAMEMODEMENU:IsAdminMenu() - return true + return true end diff --git a/lua/terrortown/menus/gamemode/administration/administration.lua b/lua/terrortown/menus/gamemode/administration/administration.lua index 3c9f4cdf8..cb0e584b2 100644 --- a/lua/terrortown/menus/gamemode/administration/administration.lua +++ b/lua/terrortown/menus/gamemode/administration/administration.lua @@ -6,101 +6,101 @@ CLGAMEMODESUBMENU.priority = 100 CLGAMEMODESUBMENU.title = "submenu_administration_administration_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_administration_general") - - form:MakeHelp({ - label = "help_idle" - }) - - local enbIdle = form:MakeCheckBox({ - serverConvar = "ttt_idle", - label = "label_idle" - }) - - form:MakeSlider({ - serverConvar = "ttt_idle_limit", - label = "label_idle_limit", - min = 0, - max = 300, - decimal = 0, - master = enbIdle - }) - - form:MakeHelp({ - label = "help_namechange_kick" - }) - - local enbKick = form:MakeCheckBox({ - serverConvar = "ttt_namechange_kick", - label = "label_namechange_kick" - }) - - form:MakeSlider({ - serverConvar = "ttt_namechange_bantime", - label = "label_namechange_bantime", - min = 0, - max = 60, - decimal = 0, - master = enbKick - }) - - local form2 = vgui.CreateTTT2Form(parent, "header_administration_logging") - - form2:MakeHelp({ - label = "help_damage_log" - }) - - form2:MakeCheckBox({ - serverConvar = "ttt_log_damage_for_console", - label = "label_log_damage_for_console" - }) - - form2:MakeCheckBox({ - serverConvar = "ttt_damagelog_save", - label = "label_damagelog_save" - }) - - local form3 = vgui.CreateTTT2Form(parent, "header_administration_misc") - - form3:MakeCheckBox({ - serverConvar = "ttt_debug_preventwin", - label = "label_debug_preventwin" - }) - - form3:MakeCheckBox({ - serverConvar = "ttt_bots_are_spectators", - label = "label_bots_are_spectators" - }) - - form3:MakeCheckBox({ - serverConvar = "ttt2_tbutton_admin_show", - label = "label_tbutton_admin_show" - }) - - local form4 = vgui.CreateTTT2Form(parent, "header_administration_scoreboard") - - form4:MakeCheckBox({ - serverConvar = "ttt_highlight_admins", - label = "label_highlight_admins" - }) - - form4:MakeCheckBox({ - serverConvar = "ttt_highlight_dev", - label = "label_highlight_dev" - }) - - form4:MakeCheckBox({ - serverConvar = "ttt_highlight_vip", - label = "label_highlight_vip" - }) - - form4:MakeCheckBox({ - serverConvar = "ttt_highlight_addondev", - label = "label_highlight_addondev" - }) - - form4:MakeCheckBox({ - serverConvar = "ttt_highlight_supporter", - label = "label_highlight_supporter" - }) + local form = vgui.CreateTTT2Form(parent, "header_administration_general") + + form:MakeHelp({ + label = "help_idle", + }) + + local enbIdle = form:MakeCheckBox({ + serverConvar = "ttt_idle", + label = "label_idle", + }) + + form:MakeSlider({ + serverConvar = "ttt_idle_limit", + label = "label_idle_limit", + min = 0, + max = 300, + decimal = 0, + master = enbIdle, + }) + + form:MakeHelp({ + label = "help_namechange_kick", + }) + + local enbKick = form:MakeCheckBox({ + serverConvar = "ttt_namechange_kick", + label = "label_namechange_kick", + }) + + form:MakeSlider({ + serverConvar = "ttt_namechange_bantime", + label = "label_namechange_bantime", + min = 0, + max = 60, + decimal = 0, + master = enbKick, + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_administration_logging") + + form2:MakeHelp({ + label = "help_damage_log", + }) + + form2:MakeCheckBox({ + serverConvar = "ttt_log_damage_for_console", + label = "label_log_damage_for_console", + }) + + form2:MakeCheckBox({ + serverConvar = "ttt_damagelog_save", + label = "label_damagelog_save", + }) + + local form3 = vgui.CreateTTT2Form(parent, "header_administration_misc") + + form3:MakeCheckBox({ + serverConvar = "ttt_debug_preventwin", + label = "label_debug_preventwin", + }) + + form3:MakeCheckBox({ + serverConvar = "ttt_bots_are_spectators", + label = "label_bots_are_spectators", + }) + + form3:MakeCheckBox({ + serverConvar = "ttt2_tbutton_admin_show", + label = "label_tbutton_admin_show", + }) + + local form4 = vgui.CreateTTT2Form(parent, "header_administration_scoreboard") + + form4:MakeCheckBox({ + serverConvar = "ttt_highlight_admins", + label = "label_highlight_admins", + }) + + form4:MakeCheckBox({ + serverConvar = "ttt_highlight_dev", + label = "label_highlight_dev", + }) + + form4:MakeCheckBox({ + serverConvar = "ttt_highlight_vip", + label = "label_highlight_vip", + }) + + form4:MakeCheckBox({ + serverConvar = "ttt_highlight_addondev", + label = "label_highlight_addondev", + }) + + form4:MakeCheckBox({ + serverConvar = "ttt_highlight_supporter", + label = "label_highlight_supporter", + }) end diff --git a/lua/terrortown/menus/gamemode/administration/chat.lua b/lua/terrortown/menus/gamemode/administration/chat.lua index 1fa47db59..04fcc42aa 100644 --- a/lua/terrortown/menus/gamemode/administration/chat.lua +++ b/lua/terrortown/menus/gamemode/administration/chat.lua @@ -6,67 +6,67 @@ CLGAMEMODESUBMENU.priority = 91 CLGAMEMODESUBMENU.title = "submenu_administration_voicechat_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_voicechat_general") + local form = vgui.CreateTTT2Form(parent, "header_voicechat_general") - form:MakeCheckBox({ - serverConvar = "sv_voiceenable", - label = "label_voice_enable" - }) + form:MakeCheckBox({ + serverConvar = "sv_voiceenable", + label = "label_voice_enable", + }) - local form2 = vgui.CreateTTT2Form(parent, "header_voicechat_battery") + local form2 = vgui.CreateTTT2Form(parent, "header_voicechat_battery") - form2:MakeHelp({ - label = "help_voicechat_battery" - }) + form2:MakeHelp({ + label = "help_voicechat_battery", + }) - local enbBat = form2:MakeCheckBox({ - serverConvar = "ttt_voice_drain", - label = "label_voice_drain" - }) + local enbBat = form2:MakeCheckBox({ + serverConvar = "ttt_voice_drain", + label = "label_voice_drain", + }) - form2:MakeSlider({ - serverConvar = "ttt_voice_drain_normal", - label = "label_voice_drain_normal", - min = 0, - max = 1, - decimal = 2, - master = enbBat - }) + form2:MakeSlider({ + serverConvar = "ttt_voice_drain_normal", + label = "label_voice_drain_normal", + min = 0, + max = 1, + decimal = 2, + master = enbBat, + }) - form2:MakeSlider({ - serverConvar = "ttt_voice_drain_admin", - label = "label_voice_drain_admin", - min = 0, - max = 1, - decimal = 2, - master = enbBat - }) + form2:MakeSlider({ + serverConvar = "ttt_voice_drain_admin", + label = "label_voice_drain_admin", + min = 0, + max = 1, + decimal = 2, + master = enbBat, + }) - form2:MakeSlider({ - serverConvar = "ttt_voice_drain_recharge", - label = "label_voice_drain_recharge", - min = 0, - max = 1, - decimal = 2, - master = enbBat - }) + form2:MakeSlider({ + serverConvar = "ttt_voice_drain_recharge", + label = "label_voice_drain_recharge", + min = 0, + max = 1, + decimal = 2, + master = enbBat, + }) - local form3 = vgui.CreateTTT2Form(parent, "header_voicechat_locational") + local form3 = vgui.CreateTTT2Form(parent, "header_voicechat_locational") - form3:MakeCheckBox({ - serverConvar = "ttt_locational_voice", - label = "label_locational_voice" - }) + form3:MakeCheckBox({ + serverConvar = "ttt_locational_voice", + label = "label_locational_voice", + }) - local form4 = vgui.CreateTTT2Form(parent, "header_textchat") + local form4 = vgui.CreateTTT2Form(parent, "header_textchat") - form4:MakeCheckBox({ - serverConvar = "ttt_spectators_chat_globally", - label = "label_spectator_chat" - }) + form4:MakeCheckBox({ + serverConvar = "ttt_spectators_chat_globally", + label = "label_spectator_chat", + }) - form4:MakeCheckBox({ - serverConvar = "ttt_lastwords_chatprint", - label = "label_lastwords_chatprint" - }) + form4:MakeCheckBox({ + serverConvar = "ttt_lastwords_chatprint", + label = "label_lastwords_chatprint", + }) end diff --git a/lua/terrortown/menus/gamemode/administration/entspawn.lua b/lua/terrortown/menus/gamemode/administration/entspawn.lua index e6712de16..d2a619a54 100644 --- a/lua/terrortown/menus/gamemode/administration/entspawn.lua +++ b/lua/terrortown/menus/gamemode/administration/entspawn.lua @@ -10,170 +10,187 @@ local updateCheckBoxes = {} local updateHelpBox = nil local function UpdateButtons(state) - for i = 1, #updateButtons do - local updateElem = updateButtons[i] + for i = 1, #updateButtons do + local updateElem = updateButtons[i] - if not IsValid(updateElem) then continue end + if not IsValid(updateElem) then + continue + end - updateElem:SetEnabled(state) - end + updateElem:SetEnabled(state) + end end local function UpdateCheckBoxes(state) - for i = 1, #updateCheckBoxes do - local updateElem = updateCheckBoxes[i] + for i = 1, #updateCheckBoxes do + local updateElem = updateCheckBoxes[i] - if not IsValid(updateElem) then continue end + if not IsValid(updateElem) then + continue + end - updateElem:SetValue(state) - end + updateElem:SetValue(state) + end end -- set up variable change callback -ttt2net.OnUpdate({"entspawnscript", "settings", "blacklisted"}, function(_, newval) - local state = not tobool(newval) +ttt2net.OnUpdate({ "entspawnscript", "settings", "blacklisted" }, function(_, newval) + local state = not tobool(newval) - UpdateButtons(state) - UpdateCheckBoxes(state) + UpdateButtons(state) + UpdateCheckBoxes(state) end) -ttt2net.OnUpdate({"entspawnscript", "spawnamount"}, function(_, newval, reversePath) - local paramType = reversePath[1] +ttt2net.OnUpdate({ "entspawnscript", "spawnamount" }, function(_, newval, reversePath) + local paramType = reversePath[1] - if not IsValid(updateHelpBox) or not paramType then return end + if not IsValid(updateHelpBox) or not paramType then + return + end - updateHelpBox:GetParams()[paramType] = newval + updateHelpBox:GetTextParams()[paramType] = newval end) - function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_entspawn_settings") - - -- store the reference to the checkbox in a variable - -- because the other settings are enabled based on - -- the state of this checkbox - local dynSpawnEnable = form:MakeCheckBox({ - label = "label_dynamic_spawns_global_enable", - serverConvar = "ttt_use_weapon_spawn_scripts", - OnChange = function(_, value) - -- make sure that both the checkbox value and the map setting value are considered - UpdateButtons(tobool(value) and not tobool(ttt2net.Get({"entspawnscript", "settings", "blacklisted"}))) - end - }) - - form:MakeHelp({ - label = "help_spawn_editor_info" - }) - - form:MakeHelp({ - label = "help_spawn_editor_enable" - }) - - updateCheckBoxes[1] = form:MakeCheckBox({ - label = "label_dynamic_spawns_enable", - initial = not tobool(ttt2net.Get({"entspawnscript", "settings", "blacklisted"})), - OnChange = function(_, value) - entspawnscript.SetSetting("blacklisted", not value) - end, - default = true, - master = dynSpawnEnable - }) - - form:MakeHelp({ - label = "help_spawn_editor_hint" - }) - - updateHelpBox = form:MakeHelp({ - label = "help_spawn_editor_spawn_amount", - params = { - weapon = ttt2net.Get({"entspawnscript", "spawnamount", "weapon"}) or 0, - ammo = ttt2net.Get({"entspawnscript", "spawnamount", "ammo"}) or 0, - player = ttt2net.Get({"entspawnscript", "spawnamount", "player"}) or 0, - weaponrandom = ttt2net.Get({"entspawnscript", "spawnamount", "weaponrandom"}) or 0, - weaponmelee = ttt2net.Get({"entspawnscript", "spawnamount", "weaponmelee"}) or 0, - weaponnade = ttt2net.Get({"entspawnscript", "spawnamount", "weaponnade"}) or 0, - weaponshotgun = ttt2net.Get({"entspawnscript", "spawnamount", "weaponshotgun"}) or 0, - weaponheavy = ttt2net.Get({"entspawnscript", "spawnamount", "weaponheavy"}) or 0, - weaponsniper = ttt2net.Get({"entspawnscript", "spawnamount", "weaponsniper"}) or 0, - weaponpistol = ttt2net.Get({"entspawnscript", "spawnamount", "weaponpistol"}) or 0, - weaponspecial = ttt2net.Get({"entspawnscript", "spawnamount", "weaponspecial"}) or 0, - ammorandom = ttt2net.Get({"entspawnscript", "spawnamount", "ammorandom"}) or 0, - ammodeagle = ttt2net.Get({"entspawnscript", "spawnamount", "ammodeagle"}) or 0, - ammopistol = ttt2net.Get({"entspawnscript", "spawnamount", "ammopistol"}) or 0, - ammomac10 = ttt2net.Get({"entspawnscript", "spawnamount", "ammomac10"}) or 0, - ammorifle = ttt2net.Get({"entspawnscript", "spawnamount", "ammorifle"}) or 0, - ammoshotgun = ttt2net.Get({"entspawnscript", "spawnamount", "ammoshotgun"}) or 0, - playerrandom = ttt2net.Get({"entspawnscript", "spawnamount", "playerrandom"}) or 0 - } - }) - - local form2 = vgui.CreateTTT2Form(parent, "header_entspawn_plyspawn") - - form2:MakeHelp({ - label = "help_spawn_waves" - }) - - form2:MakeSlider({ - serverConvar = "ttt_spawn_wave_interval", - label = "label_spawn_wave_interval", - min = 0, - max = 30, - decimal = 0 - }) - - -- REGISTER UNHIDE FUNCTION TO STOP SPAWN EDITOR - HELPSCRN.menuFrame.OnShow = function(slf) - if HELPSCRN:GetOpenMenu() ~= "administration_entspawn" then return end - - entspawnscript.StopEditing() - end + local form = vgui.CreateTTT2Form(parent, "header_entspawn_settings") + + form:MakeHelp({ + label = "help_spawn_editor_info", + }) + + -- store the reference to the checkbox in a variable + -- because the other settings are enabled based on + -- the state of this checkbox + local dynSpawnEnable = form:MakeCheckBox({ + label = "label_dynamic_spawns_global_enable", + serverConvar = "ttt_use_weapon_spawn_scripts", + OnChange = function(_, value) + -- make sure that both the checkbox value and the map setting value are considered + UpdateButtons( + tobool(value) + and not tobool(ttt2net.Get({ "entspawnscript", "settings", "blacklisted" })) + ) + end, + }) + + form:MakeHelp({ + label = "help_spawn_editor_enable", + master = dynSpawnEnable, + }) + + updateCheckBoxes[1] = form:MakeCheckBox({ + label = "label_dynamic_spawns_enable", + initial = not tobool(ttt2net.Get({ "entspawnscript", "settings", "blacklisted" })), + OnChange = function(_, value) + entspawnscript.SetSetting("blacklisted", not value) + end, + default = true, + master = dynSpawnEnable, + }) + + form:MakeHelp({ + label = "help_spawn_editor_hint", + master = dynSpawnEnable, + }) + + updateHelpBox = form:MakeHelp({ + label = "help_spawn_editor_spawn_amount", + params = { + weapon = ttt2net.Get({ "entspawnscript", "spawnamount", "weapon" }) or 0, + ammo = ttt2net.Get({ "entspawnscript", "spawnamount", "ammo" }) or 0, + player = ttt2net.Get({ "entspawnscript", "spawnamount", "player" }) or 0, + weaponrandom = ttt2net.Get({ "entspawnscript", "spawnamount", "weaponrandom" }) or 0, + weaponmelee = ttt2net.Get({ "entspawnscript", "spawnamount", "weaponmelee" }) or 0, + weaponnade = ttt2net.Get({ "entspawnscript", "spawnamount", "weaponnade" }) or 0, + weaponshotgun = ttt2net.Get({ "entspawnscript", "spawnamount", "weaponshotgun" }) or 0, + weaponheavy = ttt2net.Get({ "entspawnscript", "spawnamount", "weaponheavy" }) or 0, + weaponsniper = ttt2net.Get({ "entspawnscript", "spawnamount", "weaponsniper" }) or 0, + weaponpistol = ttt2net.Get({ "entspawnscript", "spawnamount", "weaponpistol" }) or 0, + weaponspecial = ttt2net.Get({ "entspawnscript", "spawnamount", "weaponspecial" }) or 0, + ammorandom = ttt2net.Get({ "entspawnscript", "spawnamount", "ammorandom" }) or 0, + ammodeagle = ttt2net.Get({ "entspawnscript", "spawnamount", "ammodeagle" }) or 0, + ammopistol = ttt2net.Get({ "entspawnscript", "spawnamount", "ammopistol" }) or 0, + ammomac10 = ttt2net.Get({ "entspawnscript", "spawnamount", "ammomac10" }) or 0, + ammorifle = ttt2net.Get({ "entspawnscript", "spawnamount", "ammorifle" }) or 0, + ammoshotgun = ttt2net.Get({ "entspawnscript", "spawnamount", "ammoshotgun" }) or 0, + playerrandom = ttt2net.Get({ "entspawnscript", "spawnamount", "playerrandom" }) or 0, + }, + master = dynSpawnEnable, + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_entspawn_plyspawn") + + form2:MakeHelp({ + label = "help_spawn_waves", + }) + + form2:MakeSlider({ + serverConvar = "ttt_spawn_wave_interval", + label = "label_spawn_wave_interval", + min = 0, + max = 30, + decimal = 0, + }) + + -- REGISTER UNHIDE FUNCTION TO STOP SPAWN EDITOR + HELPSCRN.menuFrame.OnShow = function(slf) + if HELPSCRN:GetOpenMenu() ~= "administration_entspawn" then + return + end + + entspawnscript.StopEditing() + end end function CLGAMEMODESUBMENU:PopulateButtonPanel(parent) - local buttonReset = vgui.Create("DButtonTTT2", parent) + local buttonReset = vgui.Create("DButtonTTT2", parent) + + buttonReset:SetText("button_reset") + buttonReset:SetSize(100, 45) + buttonReset:SetPos(20, 20) + buttonReset.DoClick = function() + entspawnscript.ResetMapToDefault() - buttonReset:SetText("button_reset") - buttonReset:SetSize(100, 45) - buttonReset:SetPos(parent:GetWide() - 120, 20) - buttonReset.DoClick = function() - entspawnscript.ResetMapToDefault() + cvars.ChangeServerConVar("ttt_use_weapon_spawn_scripts", "1") + end - cvars.ChangeServerConVar("ttt_use_weapon_spawn_scripts", "1") - end + local buttonDelete = vgui.Create("DButtonTTT2", parent) - local buttonToggle = vgui.Create("DButtonTTT2", parent) + buttonDelete:SetText("button_delete_all_spawns") + buttonDelete:SetSize(195, 45) + buttonDelete:SetPos(parent:GetWide() - (195 + 20 + 180 + 20), 20) + buttonDelete.DoClick = function(slf) + entspawnscript.DeleteAllSpawns() + end - buttonToggle:SetText("button_start_entspawn_edit") - buttonToggle:SetSize(180, 45) - buttonToggle:SetPos(20, 20) - buttonToggle.DoClick = function(slf) - entspawnscript.StartEditing() + local buttonToggle = vgui.Create("DButtonTTT2", parent) - HELPSCRN.menuFrame:HideFrame() - end + buttonToggle:SetText("button_start_entspawn_edit") + buttonToggle:SetSize(180, 45) - local buttonDelete = vgui.Create("DButtonTTT2", parent) + buttonToggle:SetPos(parent:GetWide() - (180 + 20), 20) + buttonToggle.DoClick = function(slf) + entspawnscript.StartEditing() - buttonDelete:SetText("button_delete_all_spawns") - buttonDelete:SetSize(195, 45) - buttonDelete:SetPos(220, 20) - buttonDelete.DoClick = function(slf) - entspawnscript.DeleteAllSpawns() - end + HELPSCRN.menuFrame:HideFrame() + end - updateButtons[1] = buttonToggle - updateButtons[2] = buttonDelete + updateButtons[1] = buttonToggle + updateButtons[2] = buttonDelete - -- set initial value for buttons dependant on convar request - cvars.ServerConVarGetValue("ttt_use_weapon_spawn_scripts", function(wasSuccess, value) - if not wasSuccess or not value then return end + -- set initial value for buttons dependant on convar request + cvars.ServerConVarGetValue("ttt_use_weapon_spawn_scripts", function(wasSuccess, value) + if not wasSuccess or not value then + return + end - local state = tobool(value) and not tobool(ttt2net.Get({"entspawnscript", "settings", "blacklisted"})) + local state = tobool(value) + and not tobool(ttt2net.Get({ "entspawnscript", "settings", "blacklisted" })) - UpdateButtons(state) - end) + UpdateButtons(state) + end) end function CLGAMEMODESUBMENU:HasButtonPanel() - return true + return true end diff --git a/lua/terrortown/menus/gamemode/administration/hud.lua b/lua/terrortown/menus/gamemode/administration/hud.lua index ca8bf3c49..3330888d2 100644 --- a/lua/terrortown/menus/gamemode/administration/hud.lua +++ b/lua/terrortown/menus/gamemode/administration/hud.lua @@ -6,92 +6,96 @@ CLGAMEMODESUBMENU.priority = 99 CLGAMEMODESUBMENU.title = "submenu_administration_hud_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_hud_administration") - - local restrictedHUDs = ttt2net.GetGlobal({"hud_manager", "restrictedHUDs"}) - local hudList = huds.GetList() - local hudElemList = hudelements.GetList() - local validHUDsDefault = {} - local validHUDsRestriction = {[1] = "None"} - - for i = 1, #hudList do - validHUDsDefault[i] = hudList[i].id - validHUDsRestriction[i + 1] = hudList[i].id - end - - form:MakeHelp({ - label = "help_hud_default_desc" - }) - - form:MakeComboBox({ - label = "label_hud_default", - choices = validHUDsDefault, - selectName = ttt2net.GetGlobal({"hud_manager", "defaultHUD"}) or "None", - default = "None", - OnChange = function(value) - net.Start("TTT2DefaultHUDRequest") - net.WriteString(value == "None" and "" or value) - net.SendToServer() - end - }) - - form:MakeHelp({ - label = "help_hud_forced_desc" - }) - - form:MakeComboBox({ - label = "label_hud_force", - choices = validHUDsRestriction, - selectName = ttt2net.GetGlobal({"hud_manager", "forcedHUD"}) or "None", - default = "None", - OnChange = function(value) - net.Start("TTT2ForceHUDRequest") - net.WriteString(value == "None" and "" or value) - net.SendToServer() - end - }) - - local form2 = vgui.CreateTTT2Form(parent, "header_hud_enabled") - - form2:MakeHelp({ - label = "help_hud_enabled_desc" - }) - - for i = 1, #hudList do - local hud = hudList[i] - - form2:MakeCheckBox({ - label = hud.id, - initial = not table.HasValue(restrictedHUDs, hud.id), - default = true, - OnChange = function(_, value) - net.Start("TTT2RestrictHUDRequest") - net.WriteString(hud.id) - net.WriteBool(not value) - net.SendToServer() - end - }) - end - - local form3 = vgui.CreateTTT2Form(parent, "header_hud_toggleable") - - for i = 1, #hudElemList do - local elem = hudElemList[i] - - if not elem.togglable then continue end - - form3:MakeCheckBox({ - serverConvar = "ttt2_elem_toggled_" .. elem.id, - label = "label_enable_hud_element", - params = {elem = elem.id} - }) - end + local form = vgui.CreateTTT2Form(parent, "header_hud_administration") + + local restrictedHUDs = ttt2net.GetGlobal({ "hud_manager", "restrictedHUDs" }) + local hudList = huds.GetList() + local hudElemList = hudelements.GetList() + local validHUDsDefault = {} + local validHUDsRestriction = { [1] = "None" } + + for i = 1, #hudList do + validHUDsDefault[i] = hudList[i].id + validHUDsRestriction[i + 1] = hudList[i].id + end + + form:MakeHelp({ + label = "help_hud_default_desc", + }) + + form:MakeComboBox({ + label = "label_hud_default", + choices = validHUDsDefault, + selectName = ttt2net.GetGlobal({ "hud_manager", "defaultHUD" }) or "None", + default = "None", + OnChange = function(value) + net.Start("TTT2DefaultHUDRequest") + net.WriteString(value == "None" and "" or value) + net.SendToServer() + end, + }) + + form:MakeHelp({ + label = "help_hud_forced_desc", + }) + + form:MakeComboBox({ + label = "label_hud_force", + choices = validHUDsRestriction, + selectName = ttt2net.GetGlobal({ "hud_manager", "forcedHUD" }) or "None", + default = "None", + OnChange = function(value) + net.Start("TTT2ForceHUDRequest") + net.WriteString(value == "None" and "" or value) + net.SendToServer() + end, + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_hud_enabled") + + form2:MakeHelp({ + label = "help_hud_enabled_desc", + }) + + for i = 1, #hudList do + local hud = hudList[i] + + form2:MakeCheckBox({ + label = hud.id, + initial = not table.HasValue(restrictedHUDs, hud.id), + default = true, + OnChange = function(_, value) + net.Start("TTT2RestrictHUDRequest") + net.WriteString(hud.id) + net.WriteBool(not value) + net.SendToServer() + end, + }) + end + + local form3 = vgui.CreateTTT2Form(parent, "header_hud_toggleable") + + for i = 1, #hudElemList do + local elem = hudElemList[i] + + if not elem.togglable then + continue + end + + form3:MakeCheckBox({ + serverConvar = "ttt2_elem_toggled_" .. elem.id, + label = "label_enable_hud_element", + params = { elem = elem.id }, + }) + end end -ttt2net.OnUpdateGlobal({"hud_manager", "restrictedHUDs"}, function() - if HELPSCRN:GetOpenMenu() ~= "administration_hud" then return end +ttt2net.OnUpdateGlobal({ "hud_manager", "restrictedHUDs" }, function() + if HELPSCRN:GetOpenMenu() ~= "administration_hud" then + return + end - -- rebuild the content area so that data is refreshed - -- based on the newly restricted HUDs - vguihandler.Rebuild() + -- rebuild the content area so that data is refreshed + -- based on the newly restricted HUDs + vguihandler.Rebuild() end) diff --git a/lua/terrortown/menus/gamemode/administration/inventory.lua b/lua/terrortown/menus/gamemode/administration/inventory.lua index d8350d4e3..cb96064c2 100644 --- a/lua/terrortown/menus/gamemode/administration/inventory.lua +++ b/lua/terrortown/menus/gamemode/administration/inventory.lua @@ -6,80 +6,80 @@ CLGAMEMODESUBMENU.priority = 88 CLGAMEMODESUBMENU.title = "submenu_administration_inventory_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_inventory_gernal") + local form = vgui.CreateTTT2Form(parent, "header_inventory_gernal") - form:MakeHelp({ - label = "help_max_slots" - }) + form:MakeHelp({ + label = "help_max_slots", + }) - form:MakeSlider({ - serverConvar = "ttt2_max_melee_slots", - label = "label_max_melee_slots", - min = -1, - max = 10, - decimal = 0 - }) + form:MakeSlider({ + serverConvar = "ttt2_max_melee_slots", + label = "label_max_melee_slots", + min = -1, + max = 10, + decimal = 0, + }) - form:MakeSlider({ - serverConvar = "ttt2_max_secondary_slots", - label = "label_max_secondary_slots", - min = -1, - max = 10, - decimal = 0 - }) + form:MakeSlider({ + serverConvar = "ttt2_max_secondary_slots", + label = "label_max_secondary_slots", + min = -1, + max = 10, + decimal = 0, + }) - form:MakeSlider({ - serverConvar = "ttt2_max_primary_slots", - label = "label_max_primary_slots", - min = -1, - max = 10, - decimal = 0 - }) + form:MakeSlider({ + serverConvar = "ttt2_max_primary_slots", + label = "label_max_primary_slots", + min = -1, + max = 10, + decimal = 0, + }) - form:MakeSlider({ - serverConvar = "ttt2_max_nade_slots", - label = "label_max_nade_slots", - min = -1, - max = 10, - decimal = 0 - }) + form:MakeSlider({ + serverConvar = "ttt2_max_nade_slots", + label = "label_max_nade_slots", + min = -1, + max = 10, + decimal = 0, + }) - form:MakeSlider({ - serverConvar = "ttt2_max_carry_slots", - label = "label_max_carry_slots", - min = -1, - max = 10, - decimal = 0 - }) + form:MakeSlider({ + serverConvar = "ttt2_max_carry_slots", + label = "label_max_carry_slots", + min = -1, + max = 10, + decimal = 0, + }) - form:MakeSlider({ - serverConvar = "ttt2_max_unarmed_slots", - label = "label_max_unarmed_slots", - min = -1, - max = 10, - decimal = 0 - }) + form:MakeSlider({ + serverConvar = "ttt2_max_unarmed_slots", + label = "label_max_unarmed_slots", + min = -1, + max = 10, + decimal = 0, + }) - form:MakeSlider({ - serverConvar = "ttt2_max_special_slots", - label = "label_max_special_slots", - min = -1, - max = 10, - decimal = 0 - }) + form:MakeSlider({ + serverConvar = "ttt2_max_special_slots", + label = "label_max_special_slots", + min = -1, + max = 10, + decimal = 0, + }) - form:MakeSlider({ - serverConvar = "ttt2_max_extra_slots", - label = "label_max_extra_slots", - min = -1, - max = 10, - decimal = 0 - }) + form:MakeSlider({ + serverConvar = "ttt2_max_extra_slots", + label = "label_max_extra_slots", + min = -1, + max = 10, + decimal = 0, + }) - local form2 = vgui.CreateTTT2Form(parent, "header_inventory_pickup") + local form2 = vgui.CreateTTT2Form(parent, "header_inventory_pickup") - form2:MakeCheckBox({ - serverConvar = "ttt_weapon_autopickup", - label = "label_weapon_autopickup", - }) + form2:MakeCheckBox({ + serverConvar = "ttt_weapon_autopickup", + label = "label_weapon_autopickup", + }) end diff --git a/lua/terrortown/menus/gamemode/administration/karma.lua b/lua/terrortown/menus/gamemode/administration/karma.lua index 9c1044adf..034f3117a 100644 --- a/lua/terrortown/menus/gamemode/administration/karma.lua +++ b/lua/terrortown/menus/gamemode/administration/karma.lua @@ -6,171 +6,177 @@ CLGAMEMODESUBMENU.priority = 89 CLGAMEMODESUBMENU.title = "submenu_administration_karma_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_karma_tweaking") - - form:MakeHelp({ - label = "help_karma" - }) - - local enbKma = form:MakeCheckBox({ - serverConvar = "ttt_karma", - label = "label_karma" - }) - - form:MakeHelp({ - label = "help_karma_strict" - }) - - form:MakeCheckBox({ - serverConvar = "ttt_karma_strict", - label = "label_karma_strict", - master = enbKma - }) - - form:MakeSlider({ - serverConvar = "ttt_karma_starting", - label = "label_karma_starting", - min = 0, - max = 1500, - decimal = 0, - master = enbKma - }) - - form:MakeHelp({ - label = "help_karma_max" - }) - - form:MakeSlider({ - serverConvar = "ttt_karma_max", - label = "label_karma_max", - min = 0, - max = 1500, - decimal = 0, - master = enbKma - }) - - form:MakeHelp({ - label = "help_karma_ratio" - }) - - form:MakeSlider({ - serverConvar = "ttt_karma_ratio", - label = "label_karma_ratio", - min = 0, - max = 0.01, - decimal = 4, - master = enbKma - }) - - form:MakeSlider({ - serverConvar = "ttt_karma_kill_penalty", - label = "label_karma_kill_penalty", - min = 0, - max = 100, - decimal = 0, - master = enbKma - }) - - form:MakeHelp({ - label = "help_karma_traitordmg_ratio" - }) - - form:MakeSlider({ - serverConvar = "ttt_karma_traitordmg_ratio", - label = "label_karma_traitordmg_ratio", - min = 0, - max = 0.01, - decimal = 4, - master = enbKma - }) - - form:MakeSlider({ - serverConvar = "ttt_karma_traitorkill_bonus", - label = "label_karma_traitorkill_bonus", - min = 0, - max = 100, - decimal = 0, - master = enbKma - }) - - form:MakeHelp({ - label = "help_karma_bonus" - }) - - form:MakeSlider({ - serverConvar = "ttt_karma_round_increment", - label = "label_karma_round_increment", - min = 0, - max = 100, - decimal = 0, - master = enbKma - }) - - form:MakeSlider({ - serverConvar = "ttt_karma_clean_bonus", - label = "label_karma_clean_bonus", - min = 0, - max = 100, - decimal = 0, - master = enbKma - }) - - form:MakeHelp({ - label = "help_karma_clean_half" - }) - - form:MakeSlider({ - serverConvar = "ttt_karma_clean_half", - label = "label_karma_clean_half", - min = 0, - max = 5, - decimal = 2, - master = enbKma - }) - - form:MakeCheckBox({ - serverConvar = "ttt_karma_persist", - label = "label_karma_persist", - master = enbKma - }) - - local form2 = vgui.CreateTTT2Form(parent, "header_karma_kick") - - local enbKick = form2:MakeCheckBox({ - serverConvar = "ttt_karma_low_autokick", - label = "label_karma_low_autokick", - master = enbKma - }) - - form2:MakeSlider({ - serverConvar = "ttt_karma_low_amount", - label = "label_karma_low_amount", - min = 0, - max = 1500, - decimal = 0, - master = enbKick - }) - - local enbBan = form2:MakeCheckBox({ - serverConvar = "ttt_karma_low_ban", - label = "label_karma_low_ban", - master = enbKick - }) - - form2:MakeSlider({ - serverConvar = "ttt_karma_low_ban_minutes", - label = "label_karma_low_ban_minutes", - min = 0, - max = 120, - decimal = 0, - master = enbBan - }) - - local form3 = vgui.CreateTTT2Form(parent, "header_karma_logging") - - form3:MakeCheckBox({ - serverConvar = "ttt_karma_debugspam", - label = "label_karma_debugspam", - master = enbKma - }) + local form = vgui.CreateTTT2Form(parent, "header_karma_tweaking") + + form:MakeHelp({ + label = "help_karma", + }) + + local enbKma = form:MakeCheckBox({ + serverConvar = "ttt_karma", + label = "label_karma", + }) + + form:MakeHelp({ + label = "help_karma_strict", + master = enbKma, + }) + + form:MakeCheckBox({ + serverConvar = "ttt_karma_strict", + label = "label_karma_strict", + master = enbKma, + }) + + form:MakeSlider({ + serverConvar = "ttt_karma_starting", + label = "label_karma_starting", + min = 0, + max = 1500, + decimal = 0, + master = enbKma, + }) + + form:MakeHelp({ + label = "help_karma_max", + master = enbKma, + }) + + form:MakeSlider({ + serverConvar = "ttt_karma_max", + label = "label_karma_max", + min = 0, + max = 1500, + decimal = 0, + master = enbKma, + }) + + form:MakeHelp({ + label = "help_karma_ratio", + master = enbKma, + }) + + form:MakeSlider({ + serverConvar = "ttt_karma_ratio", + label = "label_karma_ratio", + min = 0, + max = 0.01, + decimal = 4, + master = enbKma, + }) + + form:MakeSlider({ + serverConvar = "ttt_karma_kill_penalty", + label = "label_karma_kill_penalty", + min = 0, + max = 100, + decimal = 0, + master = enbKma, + }) + + form:MakeHelp({ + label = "help_karma_traitordmg_ratio", + master = enbKma, + }) + + form:MakeSlider({ + serverConvar = "ttt_karma_traitordmg_ratio", + label = "label_karma_traitordmg_ratio", + min = 0, + max = 0.01, + decimal = 4, + master = enbKma, + }) + + form:MakeSlider({ + serverConvar = "ttt_karma_traitorkill_bonus", + label = "label_karma_traitorkill_bonus", + min = 0, + max = 100, + decimal = 0, + master = enbKma, + }) + + form:MakeHelp({ + label = "help_karma_bonus", + master = enbKma, + }) + + form:MakeSlider({ + serverConvar = "ttt_karma_round_increment", + label = "label_karma_round_increment", + min = 0, + max = 100, + decimal = 0, + master = enbKma, + }) + + form:MakeSlider({ + serverConvar = "ttt_karma_clean_bonus", + label = "label_karma_clean_bonus", + min = 0, + max = 100, + decimal = 0, + master = enbKma, + }) + + form:MakeHelp({ + label = "help_karma_clean_half", + master = enbKma, + }) + + form:MakeSlider({ + serverConvar = "ttt_karma_clean_half", + label = "label_karma_clean_half", + min = 0, + max = 5, + decimal = 2, + master = enbKma, + }) + + form:MakeCheckBox({ + serverConvar = "ttt_karma_persist", + label = "label_karma_persist", + master = enbKma, + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_karma_kick") + + local enbKick = form2:MakeCheckBox({ + serverConvar = "ttt_karma_low_autokick", + label = "label_karma_low_autokick", + master = enbKma, + }) + + form2:MakeSlider({ + serverConvar = "ttt_karma_low_amount", + label = "label_karma_low_amount", + min = 0, + max = 1500, + decimal = 0, + master = enbKick, + }) + + local enbBan = form2:MakeCheckBox({ + serverConvar = "ttt_karma_low_ban", + label = "label_karma_low_ban", + master = enbKick, + }) + + form2:MakeSlider({ + serverConvar = "ttt_karma_low_ban_minutes", + label = "label_karma_low_ban_minutes", + min = 0, + max = 120, + decimal = 0, + master = enbBan, + }) + + local form3 = vgui.CreateTTT2Form(parent, "header_karma_logging") + + form3:MakeCheckBox({ + serverConvar = "ttt_karma_debugspam", + label = "label_karma_debugspam", + master = enbKma, + }) end diff --git a/lua/terrortown/menus/gamemode/administration/mapentities.lua b/lua/terrortown/menus/gamemode/administration/mapentities.lua index a4d996576..a6993a2c5 100644 --- a/lua/terrortown/menus/gamemode/administration/mapentities.lua +++ b/lua/terrortown/menus/gamemode/administration/mapentities.lua @@ -6,95 +6,109 @@ CLGAMEMODESUBMENU.priority = 90 CLGAMEMODESUBMENU.title = "submenu_administration_mapentities_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_mapentities_prop_possession") - - form:MakeHelp({ - label = "help_prop_possession" - }) - - local enbPP = form:MakeCheckBox({ - serverConvar = "ttt_spec_prop_control", - label = "label_spec_prop_control" - }) - - form:MakeSlider({ - serverConvar = "ttt_spec_prop_base", - label = "label_spec_prop_base", - min = 0, - max = 50, - decimal = 0, - master = enbPP - }) - - form:MakeSlider({ - serverConvar = "ttt_spec_prop_maxpenalty", - label = "label_spec_prop_maxpenalty", - min = -50, - max = 0, - decimal = 0, - master = enbPP - }) - - form:MakeSlider({ - serverConvar = "ttt_spec_prop_maxbonus", - label = "label_spec_prop_maxbonus", - min = 0, - max = 50, - decimal = 0, - master = enbPP - }) - - form:MakeSlider({ - serverConvar = "ttt_spec_prop_force", - label = "label_spec_prop_force", - min = 50, - max = 300, - decimal = 0, - master = enbPP - }) - - form:MakeSlider({ - serverConvar = "ttt_spec_prop_rechargetime", - label = "label_spec_prop_rechargetime", - min = 0, - max = 10, - decimal = 1, - master = enbPP - }) - - local form2 = vgui.CreateTTT2Form(parent, "header_mapentities_doors") - - form2:MakeCheckBox({ - serverConvar = "ttt2_doors_force_pairs", - label = "label_doors_force_pairs" - }) - - local enbDoor = form2:MakeCheckBox({ - serverConvar = "ttt2_doors_destructible", - label = "label_doors_destructible" - }) - - form2:MakeCheckBox({ - serverConvar = "ttt2_doors_locked_indestructible", - label = "label_doors_locked_indestructible", - master = enbDoor - }) - - form2:MakeSlider({ - serverConvar = "ttt2_doors_health", - label = "label_doors_health", - min = 0, - max = 500, - decimal = 0, - master = enbDoor - }) - - form2:MakeSlider({ - serverConvar = "ttt2_doors_prop_health", - label = "label_doors_prop_health", - min = 0, - max = 500, - decimal = 0, - master = enbDoor - }) + local form = vgui.CreateTTT2Form(parent, "header_mapentities_prop_possession") + + form:MakeHelp({ + label = "help_prop_possession", + }) + + local enbPP = form:MakeCheckBox({ + serverConvar = "ttt_spec_prop_control", + label = "label_spec_prop_control", + }) + + form:MakeSlider({ + serverConvar = "ttt_spec_prop_base", + label = "label_spec_prop_base", + min = 0, + max = 50, + decimal = 0, + master = enbPP, + }) + + form:MakeSlider({ + serverConvar = "ttt_spec_prop_maxpenalty", + label = "label_spec_prop_maxpenalty", + min = -50, + max = 0, + decimal = 0, + master = enbPP, + }) + + form:MakeSlider({ + serverConvar = "ttt_spec_prop_maxbonus", + label = "label_spec_prop_maxbonus", + min = 0, + max = 50, + decimal = 0, + master = enbPP, + }) + + form:MakeSlider({ + serverConvar = "ttt_spec_prop_force", + label = "label_spec_prop_force", + min = 50, + max = 300, + decimal = 0, + master = enbPP, + }) + + form:MakeSlider({ + serverConvar = "ttt_spec_prop_rechargetime", + label = "label_spec_prop_rechargetime", + min = 0, + max = 5, + decimal = 1, + master = enbPP, + }) + + form:MakeHelp({ + label = "help_prop_spec_dash", + master = enbPP, + }) + + form:MakeSlider({ + serverConvar = "ttt_spec_prop_dash", + label = "label_spec_prop_dash", + min = 1, + max = 10, + decimal = 0, + master = enbPP, + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_mapentities_doors") + + form2:MakeCheckBox({ + serverConvar = "ttt2_doors_force_pairs", + label = "label_doors_force_pairs", + }) + + local enbDoor = form2:MakeCheckBox({ + serverConvar = "ttt2_doors_destructible", + label = "label_doors_destructible", + }) + + form2:MakeCheckBox({ + serverConvar = "ttt2_doors_locked_indestructible", + label = "label_doors_locked_indestructible", + master = enbDoor, + }) + + form2:MakeSlider({ + serverConvar = "ttt2_doors_health", + label = "label_doors_health", + min = 0, + max = 500, + decimal = 0, + master = enbDoor, + }) + + form2:MakeSlider({ + serverConvar = "ttt2_doors_prop_health", + label = "label_doors_prop_health", + min = 0, + max = 500, + decimal = 0, + master = enbDoor, + }) end diff --git a/lua/terrortown/menus/gamemode/administration/playermodels.lua b/lua/terrortown/menus/gamemode/administration/playermodels.lua index d8964877b..f7102502d 100644 --- a/lua/terrortown/menus/gamemode/administration/playermodels.lua +++ b/lua/terrortown/menus/gamemode/administration/playermodels.lua @@ -8,112 +8,134 @@ CLGAMEMODESUBMENU.title = "submenu_administration_playermodels_title" local boxCache = {} function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_playermodels_general") - - form:MakeHelp({ - label = "help_enforce_playermodel" - }) - - form:MakeCheckBox({ - label = "label_enforce_playermodel", - serverConvar = "ttt_enforce_playermodel" - }) - - form:MakeHelp({ - label = "help_prefer_map_models" - }) - - form:MakeCheckBox({ - label = "label_prefer_map_models", - serverConvar = "ttt2_prefer_map_models" - }) - - form:MakeHelp({ - label = "help_use_custom_models" - }) - - local customModelsEnb = form:MakeCheckBox({ - label = "label_use_custom_models", - serverConvar = "ttt2_use_custom_models" - }) - - form:MakeCheckBox({ - label = "label_select_model_per_round", - serverConvar = "ttt2_select_model_per_round", - master = customModelsEnb - }) - - local form2 = vgui.CreateTTT2Form(parent, "header_playermodels_selection") - - form2:MakeHelp({ - label = "help_models_select" - }) - - local base = form2:MakeIconLayout() - for name, model in SortedPairs(player_manager.AllValidModels()) do - boxCache[name] = form2:MakeImageCheckBox({ - label = name, - model = model, - headbox = playermodels.HasHeadHitBox(name) or false, - OnModelSelected = function(_, state) - playermodels.UpdateModel(name, playermodels.state.selected, state) - end, - OnModelHattable = function(_, state) - playermodels.UpdateModel(name, playermodels.state.hattable, state) - end - }, base) - - playermodels.IsSelectedModel(name, function(value) - boxCache[name]:SetModelSelected(value, false) - end) - - playermodels.IsHattableModel(name, function(value) - boxCache[name]:SetModelHattable(value, false) - end) - - playermodels.RemoveChangeCallback(name, playermodels.state.selected, "TTT2F1MenuPlayermodels") - - playermodels.AddChangeCallback(name, playermodels.state.selected, function(value) - local box = boxCache[name] - - if not IsValid(box) then - playermodels.RemoveChangeCallback(name, playermodels.state.selected, "TTT2F1MenuPlayermodels") - - return - end - - box:SetModelSelected(value, false) - end, - "TTT2F1MenuPlayermodels") - - playermodels.RemoveChangeCallback(name, playermodels.state.hattable, "TTT2F1MenuPlayermodels") - - playermodels.AddChangeCallback(name, playermodels.state.hattable, function(value) - local box = boxCache[name] - - if not IsValid(box) then - playermodels.RemoveChangeCallback(name, playermodels.state.hattable, "TTT2F1MenuPlayermodels") - - return - end - - box:SetModelHattable(value, false) - end, - "TTT2F1MenuPlayermodels") - end + local form = vgui.CreateTTT2Form(parent, "header_playermodels_general") + + form:MakeHelp({ + label = "help_enforce_playermodel", + }) + + form:MakeCheckBox({ + label = "label_enforce_playermodel", + serverConvar = "ttt_enforce_playermodel", + }) + + form:MakeHelp({ + label = "help_prefer_map_models", + }) + + form:MakeCheckBox({ + label = "label_prefer_map_models", + serverConvar = "ttt2_prefer_map_models", + }) + + form:MakeHelp({ + label = "help_use_custom_models", + }) + + local customModelsEnb = form:MakeCheckBox({ + label = "label_use_custom_models", + serverConvar = "ttt2_use_custom_models", + }) + + form:MakeCheckBox({ + label = "label_select_model_per_round", + serverConvar = "ttt2_select_model_per_round", + master = customModelsEnb, + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_playermodels_selection") + + form2:MakeHelp({ + label = "help_models_select", + }) + + local base = form2:MakeIconLayout() + for name, model in SortedPairs(player_manager.AllValidModels()) do + boxCache[name] = form2:MakeImageCheckBox({ + label = name, + model = model, + headbox = playermodels.HasHeadHitBox(name) or false, + OnModelSelected = function(_, state) + playermodels.UpdateModel(name, playermodels.state.selected, state) + end, + OnModelHattable = function(_, state) + playermodels.UpdateModel(name, playermodels.state.hattable, state) + end, + }, base) + + playermodels.IsSelectedModel(name, function(value) + if not boxCache[name] then + return + end + + boxCache[name]:SetModelSelected(value, false) + end) + + playermodels.IsHattableModel(name, function(value) + if not boxCache[name] then + return + end + + boxCache[name]:SetModelHattable(value, false) + end) + + playermodels.RemoveChangeCallback( + name, + playermodels.state.selected, + "TTT2F1MenuPlayermodels" + ) + + playermodels.AddChangeCallback(name, playermodels.state.selected, function(value) + local box = boxCache[name] + + if not IsValid(box) then + playermodels.RemoveChangeCallback( + name, + playermodels.state.selected, + "TTT2F1MenuPlayermodels" + ) + + return + end + + box:SetModelSelected(value, false) + end, "TTT2F1MenuPlayermodels") + + playermodels.RemoveChangeCallback( + name, + playermodels.state.hattable, + "TTT2F1MenuPlayermodels" + ) + + playermodels.AddChangeCallback(name, playermodels.state.hattable, function(value) + local box = boxCache[name] + + if not IsValid(box) then + playermodels.RemoveChangeCallback( + name, + playermodels.state.hattable, + "TTT2F1MenuPlayermodels" + ) + + return + end + + box:SetModelHattable(value, false) + end, "TTT2F1MenuPlayermodels") + end end function CLGAMEMODESUBMENU:PopulateButtonPanel(parent) - local buttonReset = vgui.Create("DButtonTTT2", parent) - - buttonReset:SetText("button_reset_models") - buttonReset:SetSize(225, 45) - buttonReset:SetPos(parent:GetWide() - 245, 20) - buttonReset.DoClick = function() - playermodels.Reset() - end + local buttonReset = vgui.Create("DButtonTTT2", parent) + + buttonReset:SetText("button_reset_models") + buttonReset:SetSize(225, 45) + buttonReset:SetPos(20, 20) + buttonReset.DoClick = function() + playermodels.Reset() + end end function CLGAMEMODESUBMENU:HasButtonPanel() - return true + return true end diff --git a/lua/terrortown/menus/gamemode/administration/playersettings.lua b/lua/terrortown/menus/gamemode/administration/playersettings.lua index d6d7a91e4..4d19225a6 100644 --- a/lua/terrortown/menus/gamemode/administration/playersettings.lua +++ b/lua/terrortown/menus/gamemode/administration/playersettings.lua @@ -6,108 +6,110 @@ CLGAMEMODESUBMENU.priority = 93 CLGAMEMODESUBMENU.title = "submenu_administration_playersettings_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_playersettings_plyspawn") - - form:MakeHelp({ - label = "help_ply_spawn" - }) - - form:MakeSlider({ - serverConvar = "ttt_armor_on_spawn", - label = "label_armor_on_spawn", - min = 0, - max = 250, - decimal = 0 - }) - - local form2 = vgui.CreateTTT2Form(parent, "header_playersettings_armor") - - form2:MakeHelp({ - label = "help_armor_balancing" - }) - - form2:MakeCheckBox({ - serverConvar = "ttt_item_armor_block_headshots", - label = "label_armor_block_headshots" - }) - - form2:MakeCheckBox({ - serverConvar = "ttt_item_armor_block_blastdmg", - label = "label_armor_block_blastdmg" - }) - - form2:MakeHelp({ - label = "help_item_armor_classic" - }) - - local enbDyn = form2:MakeCheckBox({ - serverConvar = "ttt_armor_dynamic", - label = "label_armor_dynamic" - }) - - form2:MakeHelp({ - label = "help_item_armor_dynamic" - }) - - form2:MakeSlider({ - serverConvar = "ttt_armor_damage_block_pct", - label = "label_armor_damage_block_pct", - min = 0, - max = 1, - decimal = 2, - master = enbDyn - }) - - form2:MakeSlider({ - serverConvar = "ttt_armor_damage_health_pct", - label = "label_armor_damage_health_pct", - min = 0, - max = 1, - decimal = 2, - master = enbDyn - }) - - local enbReInf = form2:MakeCheckBox({ - serverConvar = "ttt_armor_enable_reinforced", - label = "label_armor_enable_reinforced", - master = enbDyn - }) - - form2:MakeSlider({ - serverConvar = "ttt_armor_threshold_for_reinforced", - label = "label_armor_threshold_for_reinforced", - min = 0, - max = 100, - decimal = 0, - master = enbReInf - }) - - local formFallDmg = vgui.CreateTTT2Form(parent, "header_playersettings_falldmg") - - local enbFallDmg = formFallDmg:MakeCheckBox({ - serverConvar = "ttt2_falldmg_enable", - label = "label_falldmg_enable" - }) - - formFallDmg:MakeSlider({ - serverConvar = "ttt2_falldmg_min_velocity", - label = "label_falldmg_min_velocity", - min = 0, - max = 1500, - decimal = 0, - parent = enbFallDmg - }) - - formFallDmg:MakeHelp({ - label = "help_falldmg_exponent" - }) - - formFallDmg:MakeSlider({ - serverConvar = "ttt2_falldmg_exponent", - label = "label_falldmg_exponent", - min = 0, - max = 5, - decimal = 2, - parent = enbFallDmg - }) + local form = vgui.CreateTTT2Form(parent, "header_playersettings_plyspawn") + + form:MakeHelp({ + label = "help_ply_spawn", + }) + + form:MakeSlider({ + serverConvar = "ttt_armor_on_spawn", + label = "label_armor_on_spawn", + min = 0, + max = 250, + decimal = 0, + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_playersettings_armor") + + form2:MakeHelp({ + label = "help_armor_balancing", + }) + + form2:MakeCheckBox({ + serverConvar = "ttt_item_armor_block_headshots", + label = "label_armor_block_headshots", + }) + + form2:MakeCheckBox({ + serverConvar = "ttt_item_armor_block_blastdmg", + label = "label_armor_block_blastdmg", + }) + + form2:MakeHelp({ + label = "help_item_armor_classic", + }) + + local enbDyn = form2:MakeCheckBox({ + serverConvar = "ttt_armor_dynamic", + label = "label_armor_dynamic", + }) + + form2:MakeHelp({ + label = "help_item_armor_dynamic", + master = enbDyn, + }) + + form2:MakeSlider({ + serverConvar = "ttt_armor_damage_block_pct", + label = "label_armor_damage_block_pct", + min = 0, + max = 1, + decimal = 2, + master = enbDyn, + }) + + form2:MakeSlider({ + serverConvar = "ttt_armor_damage_health_pct", + label = "label_armor_damage_health_pct", + min = 0, + max = 1, + decimal = 2, + master = enbDyn, + }) + + local enbReInf = form2:MakeCheckBox({ + serverConvar = "ttt_armor_enable_reinforced", + label = "label_armor_enable_reinforced", + master = enbDyn, + }) + + form2:MakeSlider({ + serverConvar = "ttt_armor_threshold_for_reinforced", + label = "label_armor_threshold_for_reinforced", + min = 0, + max = 100, + decimal = 0, + master = enbReInf, + }) + + local formFallDmg = vgui.CreateTTT2Form(parent, "header_playersettings_falldmg") + + local enbFallDmg = formFallDmg:MakeCheckBox({ + serverConvar = "ttt2_falldmg_enable", + label = "label_falldmg_enable", + }) + + formFallDmg:MakeSlider({ + serverConvar = "ttt2_falldmg_min_velocity", + label = "label_falldmg_min_velocity", + min = 0, + max = 1500, + decimal = 0, + master = enbFallDmg, + }) + + formFallDmg:MakeHelp({ + label = "help_falldmg_exponent", + master = enbFallDmg, + }) + + formFallDmg:MakeSlider({ + serverConvar = "ttt2_falldmg_exponent", + label = "label_falldmg_exponent", + min = 0, + max = 5, + decimal = 2, + master = enbFallDmg, + }) end diff --git a/lua/terrortown/menus/gamemode/administration/randomshop.lua b/lua/terrortown/menus/gamemode/administration/randomshop.lua index 503697711..30d463d19 100644 --- a/lua/terrortown/menus/gamemode/administration/randomshop.lua +++ b/lua/terrortown/menus/gamemode/administration/randomshop.lua @@ -6,41 +6,41 @@ CLGAMEMODESUBMENU.priority = 92 CLGAMEMODESUBMENU.title = "submenu_administration_randomshop_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_random_shop_administration") - local form2 = vgui.CreateTTT2Form(parent, "header_random_shop_value_administration") + local form = vgui.CreateTTT2Form(parent, "header_random_shop_administration") + local form2 = vgui.CreateTTT2Form(parent, "header_random_shop_value_administration") - for convar, data in SortedPairsByMemberValue(ShopEditor.cvars, "order", false) do - local convarName = tostring(convar) - local name = "shopeditor_name_" .. data.name - local desc = "shopeditor_desc_" .. data.name + for convar, data in SortedPairsByMemberValue(ShopEditor.cvars, "order", false) do + local convarName = tostring(convar) + local name = "shopeditor_name_" .. data.name + local desc = "shopeditor_desc_" .. data.name - if data.typ == "bool" then - if data.b_desc then - form:MakeHelp({ - label = desc - }) - end + if data.typ == "bool" then + if data.b_desc then + form:MakeHelp({ + label = desc, + }) + end - form:MakeCheckBox({ - label = name, - default = data.default, - serverConvar = convarName - }) - elseif data.typ == "number" then - if data.b_desc then - form2:MakeHelp({ - label = desc - }) - end + form:MakeCheckBox({ + label = name, + default = data.default, + serverConvar = convarName, + }) + elseif data.typ == "number" or data.typ == "float" then + if data.b_desc then + form2:MakeHelp({ + label = desc, + }) + end - form2:MakeSlider({ - label = name, - min = data.min, - max = data.max, - decimal = 0, - default = data.default, - serverConvar = convarName - }) - end - end + form2:MakeSlider({ + label = name, + min = data.min, + max = data.max, + decimal = data.decimal or 0, + default = data.default, + serverConvar = convarName, + }) + end + end end diff --git a/lua/terrortown/menus/gamemode/administration/rolelayering.lua b/lua/terrortown/menus/gamemode/administration/rolelayering.lua index 92f25a6fc..dd54743b7 100644 --- a/lua/terrortown/menus/gamemode/administration/rolelayering.lua +++ b/lua/terrortown/menus/gamemode/administration/rolelayering.lua @@ -12,133 +12,146 @@ CLGAMEMODESUBMENU.title = "submenu_administration_rolelayering_title" CLGAMEMODESUBMENU.forms = {} function CLGAMEMODESUBMENU:Populate(parent) - -- first add a tutorial form - local form = vgui.CreateTTT2Form(parent, "header_rolelayering_info") + -- first add a tutorial form + local form = vgui.CreateTTT2Form(parent, "header_rolelayering_info") - form:MakeHelp({ - label = "help_rolelayering_roleselection" - }) + form:MakeHelp({ + label = "help_rolelayering_roleselection", + }) - form:MakeHelp({ - label = "help_rolelayering_layers" - }) + form:MakeHelp({ + label = "help_rolelayering_layers", + }) - self.baseroleList, self.subroleList = rolelayering.GetLayerableBaserolesWithSubroles() + self.baseroleList, self.subroleList = rolelayering.GetLayerableBaserolesWithSubroles() - -- clear the form table because there might be old data - self.forms = {} + -- clear the form table because there might be old data + self.forms = {} - self.forms[ROLE_NONE] = vgui.CreateTTT2Form(parent, "header_rolelayering_baserole") + self.forms[ROLE_NONE] = vgui.CreateTTT2Form(parent, "header_rolelayering_baserole") - rolelayering.RequestDataFromServer(ROLE_NONE) + rolelayering.RequestDataFromServer(ROLE_NONE) - for subrole in pairs(self.subroleList) do - self.forms[subrole] = vgui.CreateTTT2Form(parent, ParT("header_rolelayering_role", {role = TryT(roles.GetByIndex(subrole).name)})) + for subrole in pairs(self.subroleList) do + self.forms[subrole] = vgui.CreateTTT2Form( + parent, + ParT("header_rolelayering_role", { role = TryT(roles.GetByIndex(subrole).name) }) + ) - rolelayering.RequestDataFromServer(subrole) - end + rolelayering.RequestDataFromServer(subrole) + end end function CLGAMEMODESUBMENU:PopulateButtonPanel(parent) - local buttonReset = vgui.Create("DButtonTTT2", parent) - - buttonReset:SetText("button_reset") - buttonReset:SetSize(100, 45) - buttonReset:SetPos(parent:GetWide() - 120, 20) - buttonReset.DoClick = function() - rolelayering.SendDataToServer(ROLE_NONE, {}) - - for subrole in pairs(self.subroleList) do - rolelayering.SendDataToServer(subrole, {}) - end - end + local buttonReset = vgui.Create("DButtonTTT2", parent) + + buttonReset:SetText("button_reset") + buttonReset:SetSize(100, 45) + buttonReset:SetPos(20, 20) + buttonReset.DoClick = function() + rolelayering.SendDataToServer(ROLE_NONE, {}) + + for subrole in pairs(self.subroleList) do + rolelayering.SendDataToServer(subrole, {}) + end + end end function CLGAMEMODESUBMENU:HasButtonPanel() - return true + return true end hook.Add("TTT2ReceivedRolelayerData", "received_layer_data", function(role, layerTable) - local menuReference = HELPSCRN.submenuClass + local menuReference = HELPSCRN.submenuClass - if not menuReference or HELPSCRN:GetOpenMenu() ~= "administration_rolelayering" then return end + if not menuReference or HELPSCRN:GetOpenMenu() ~= "administration_rolelayering" then + return + end - menuReference.forms[role]:Clear() + menuReference.forms[role]:Clear() - local roleList, leftRoles = {}, {} + local roleList, leftRoles = {}, {} - if role == ROLE_NONE then - roleList = menuReference.baseroleList - else - roleList = menuReference.subroleList[role] - end + if role == ROLE_NONE then + roleList = menuReference.baseroleList + else + roleList = menuReference.subroleList[role] + end - -- a layer wouldn't make any sense if there are less than 2 available entries / related roles - if #roleList < 2 then return end + -- a layer wouldn't make any sense if there are less than 2 available entries / related roles + if #roleList < 2 then + return + end - for cRoles = 1, #roleList do - local subrole = roleList[cRoles] + for cRoles = 1, #roleList do + local subrole = roleList[cRoles] - -- don't insert roles that are getting automatically / statically selected - if subrole == ROLE_TRAITOR or subrole == ROLE_INNOCENT then continue end + -- don't insert roles that are getting automatically / statically selected + if subrole == ROLE_TRAITOR or subrole == ROLE_INNOCENT then + continue + end - local found = false + local found = false - for cLayer = 1, #layerTable do - local currentLayer = layerTable[cLayer] + for cLayer = 1, #layerTable do + local currentLayer = layerTable[cLayer] - for cEntry = 1, #currentLayer do - if currentLayer[cEntry] == subrole then - found = true + for cEntry = 1, #currentLayer do + if currentLayer[cEntry] == subrole then + found = true - break - end - end + break + end + end - if found then break end - end + if found then + break + end + end - if found then continue end + if found then + continue + end - leftRoles[#leftRoles + 1] = subrole - end + leftRoles[#leftRoles + 1] = subrole + end - local basePanel = menuReference.forms[role]:MakeIconLayout() + local basePanel = menuReference.forms[role]:MakeIconLayout() - local dragSender = basePanel:Add("DRoleLayeringSenderTTT2") - dragSender:SetLeftMargin(108) - dragSender:Dock(TOP) - dragSender:SetPadding(5) - dragSender:MakeDroppable("drop_group_" .. role) + local dragSender = basePanel:Add("DRoleLayeringSenderTTT2") + dragSender:SetLeftMargin(108) + dragSender:Dock(TOP) + dragSender:SetPadding(5) + dragSender:MakeDroppable("drop_group_" .. role) - -- modify the dragReceiver - local dragReceiver = basePanel:Add("DRoleLayeringReceiverTTT2") - dragReceiver:SetLeftMargin(108) - dragReceiver:Dock(TOP) - dragReceiver:SetPadding(5) - dragReceiver:MakeDroppable("drop_group_" .. role) - dragReceiver:InitRoles(layerTable) - dragReceiver.OnLayerUpdated = function(slf) - rolelayering.SendDataToServer(role, slf.layerList) - end + -- modify the dragReceiver + local dragReceiver = basePanel:Add("DRoleLayeringReceiverTTT2") + dragReceiver:SetLeftMargin(108) + dragReceiver:Dock(TOP) + dragReceiver:SetPadding(5) + dragReceiver:MakeDroppable("drop_group_" .. role) + dragReceiver:InitRoles(layerTable) + dragReceiver.OnLayerUpdated = function(slf) + rolelayering.SendDataToServer(role, slf.layerList) + end - dragSender:SetReceiver(dragReceiver) + dragSender:SetReceiver(dragReceiver) - for i = 1, #leftRoles do - local subrole = leftRoles[i] - local roleData = roles.GetByIndex(subrole) + for i = 1, #leftRoles do + local subrole = leftRoles[i] + local roleData = roles.GetByIndex(subrole) - local ic = vgui.Create("DRoleImageTTT2", dragSender) - ic:SetSize(64, 64) - ic:SetMaterial(roleData.iconMaterial) - ic:SetColor(roleData.color) - ic:SetTooltip(roleData.name) - ic:SetTooltipFixedPosition(0, 64) + local ic = vgui.Create("DRoleImageTTT2", dragSender) + ic:SetSize(64, 64) + ic:SetMaterial(roleData.iconMaterial) + ic:SetColor(roleData.color) + ic:SetTooltip(roleData.name) + ic:SetTooltipFixedPosition(0, 64) - ic.subrole = subrole + ic.subrole = subrole - dragSender:Add(ic) - end + dragSender:Add(ic) + end - dragReceiver:SetSender(dragSender) + dragReceiver:SetSender(dragSender) end) diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index e8ebc1fdd..dc1673565 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -6,116 +6,119 @@ CLGAMEMODESUBMENU.priority = 97 CLGAMEMODESUBMENU.title = "submenu_administration_roles_general_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_roles_additional") - - local masterEnb = form:MakeCheckBox({ - serverConvar = "ttt_newroles_enabled", - label = "label_roles_newroles_enabled" - }) - - form:MakeHelp({ - label = "help_roles_advanced_warning" - }) - - form:MakeHelp({ - label = "help_roles_max_roles" - }) - - form:MakeSlider({ - serverConvar = "ttt_max_roles", - label = "label_roles_max_roles", - min = 0, - max = 64, - decimal = 0, - master = masterEnb - }) - - form:MakeSlider({ - serverConvar = "ttt_max_roles_pct", - label = "label_roles_max_roles_pct", - min = 0, - max = 1, - decimal = 2, - master = masterEnb - }) - - form:MakeHelp({ - label = "help_roles_max_baseroles" - }) - - form:MakeSlider({ - serverConvar = "ttt_max_baseroles", - label = "label_roles_max_baseroles", - min = 0, - max = 64, - decimal = 0, - master = masterEnb - }) - - form:MakeSlider({ - serverConvar = "ttt_max_baseroles_pct", - label = "label_roles_max_baseroles_pct", - min = 0, - max = 1, - decimal = 2, - master = masterEnb - }) - - local form2 = vgui.CreateTTT2Form(parent, "header_roles_reward_credits") - - form2:MakeHelp({ - label = "help_roles_award_info" - }) - - form2:MakeSlider({ - serverConvar = "ttt_credits_award_size", - label = "label_roles_credits_award_size", - min = 0, - max = 5, - decimal = 0 - }) - - form2:MakeHelp({ - label = "help_roles_award_pct" - }) - - form2:MakeSlider({ - serverConvar = "ttt_credits_award_pct", - label = "label_roles_credits_award_pct", - min = 0, - max = 1, - decimal = 2 - }) - - form2:MakeHelp({ - label = "help_roles_award_repeat" - }) - - form2:MakeCheckBox({ - serverConvar = "ttt_credits_award_repeat", - label = "label_roles_credits_award_repeat" - }) - - form2:MakeHelp({ - label = "help_roles_credits_award_kill" - }) - - form2:MakeSlider({ - serverConvar = "ttt_credits_award_kill", - label = "label_roles_credits_award_kill", - min = 0, - max = 10, - decimal = 0 - }) - - local form3 = vgui.CreateTTT2Form(parent, "header_roles_special_settings") - - form3:MakeHelp({ - label = "help_detective_hats" - }) - - form3:MakeCheckBox({ - serverConvar = "ttt_detective_hats", - label = "label_detective_hats" - }) + local form = vgui.CreateTTT2Form(parent, "header_roles_additional") + + local masterEnb = form:MakeCheckBox({ + serverConvar = "ttt_newroles_enabled", + label = "label_roles_newroles_enabled", + }) + + form:MakeHelp({ + label = "help_roles_advanced_warning", + master = masterEnb, + }) + + form:MakeHelp({ + label = "help_roles_max_roles", + master = masterEnb, + }) + + form:MakeSlider({ + serverConvar = "ttt_max_roles", + label = "label_roles_max_roles", + min = 0, + max = 64, + decimal = 0, + master = masterEnb, + }) + + form:MakeSlider({ + serverConvar = "ttt_max_roles_pct", + label = "label_roles_max_roles_pct", + min = 0, + max = 1, + decimal = 2, + master = masterEnb, + }) + + form:MakeHelp({ + label = "help_roles_max_baseroles", + master = masterEnb, + }) + + form:MakeSlider({ + serverConvar = "ttt_max_baseroles", + label = "label_roles_max_baseroles", + min = 0, + max = 64, + decimal = 0, + master = masterEnb, + }) + + form:MakeSlider({ + serverConvar = "ttt_max_baseroles_pct", + label = "label_roles_max_baseroles_pct", + min = 0, + max = 1, + decimal = 2, + master = masterEnb, + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_roles_reward_credits") + + form2:MakeHelp({ + label = "help_roles_award_info", + }) + + form2:MakeSlider({ + serverConvar = "ttt_credits_award_size", + label = "label_roles_credits_award_size", + min = 0, + max = 5, + decimal = 0, + }) + + form2:MakeHelp({ + label = "help_roles_award_pct", + }) + + form2:MakeSlider({ + serverConvar = "ttt_credits_award_pct", + label = "label_roles_credits_award_pct", + min = 0, + max = 1, + decimal = 2, + }) + + form2:MakeHelp({ + label = "help_roles_award_repeat", + }) + + form2:MakeCheckBox({ + serverConvar = "ttt_credits_award_repeat", + label = "label_roles_credits_award_repeat", + }) + + form2:MakeHelp({ + label = "help_roles_credits_award_kill", + }) + + form2:MakeSlider({ + serverConvar = "ttt_credits_award_kill", + label = "label_roles_credits_award_kill", + min = 0, + max = 10, + decimal = 0, + }) + + local form3 = vgui.CreateTTT2Form(parent, "header_roles_special_settings") + + form3:MakeHelp({ + label = "help_detective_hats", + }) + + form3:MakeCheckBox({ + serverConvar = "ttt_detective_hats", + label = "label_detective_hats", + }) end diff --git a/lua/terrortown/menus/gamemode/administration/roundsetup.lua b/lua/terrortown/menus/gamemode/administration/roundsetup.lua index 27f614bc9..2778326ac 100644 --- a/lua/terrortown/menus/gamemode/administration/roundsetup.lua +++ b/lua/terrortown/menus/gamemode/administration/roundsetup.lua @@ -1,166 +1,173 @@ --- @ignore +local TryT = LANG.TryTranslation + CLGAMEMODESUBMENU.base = "base_gamemodesubmenu" CLGAMEMODESUBMENU.priority = 98 CLGAMEMODESUBMENU.title = "submenu_administration_round_setup_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_round_dead_players") - - form:MakeCheckBox({ - serverConvar = "ttt_identify_body_woconfirm", - label = "label_identify_body_woconfirm" - }) - - form:MakeCheckBox({ - serverConvar = "ttt_announce_body_found", - label = "label_announce_body_found" - }) - - form:MakeCheckBox({ - serverConvar = "ttt2_confirm_killlist", - label = "label_confirm_killlist" - }) - - form:MakeCheckBox({ - serverConvar = "ttt2_inspect_detective_only", - label = "label_inspect_detective_only" - }) - - form:MakeCheckBox({ - serverConvar = "ttt2_confirm_detective_only", - label = "label_confirm_detective_only" - }) - - form:MakeCheckBox({ - serverConvar = "ttt_dyingshot", - label = "label_dyingshot" - }) - - local form2 = vgui.CreateTTT2Form(parent, "header_round_setup_prep") - - form2:MakeCheckBox({ - serverConvar = "ttt2_prep_respawn", - label = "label_prep_respawn" - }) - - form2:MakeCheckBox({ - serverConvar = "ttt_nade_throw_during_prep", - label = "label_nade_throw_during_prep" - }) - - form2:MakeSlider({ - serverConvar = "ttt_preptime_seconds", - label = "label_preptime_seconds", - min = 0, - max = 120, - decimal = 0 - }) - - form2:MakeSlider({ - serverConvar = "ttt_firstpreptime", - label = "label_firstpreptime_seconds", - min = 0, - max = 120, - decimal = 0 - }) - - local form3 = vgui.CreateTTT2Form(parent, "header_round_setup_round") - - form3:MakeSlider({ - serverConvar = "ttt_roundtime_minutes", - label = "label_roundtime_minutes", - min = 0, - max = 60, - decimal = 0, - }) - - form3:MakeHelp({ - label = "help_haste_mode" - }) - - local enbHaste = form3:MakeCheckBox({ - serverConvar = "ttt_haste", - label = "label_haste" - }) - - form3:MakeSlider({ - serverConvar = "ttt_haste_starting_minutes", - label = "label_haste_starting_minutes", - min = 0, - max = 60, - decimal = 0, - master = enbHaste - }) - - form3:MakeSlider({ - serverConvar = "ttt_haste_minutes_per_death", - label = "label_haste_minutes_per_death", - min = 0, - max = 10, - decimal = 1, - master = enbHaste - }) - - form3:MakeHelp({ - label = "help_sherlock_mode" - }) - - form3:MakeCheckBox({ - serverConvar = "ttt_sherlock_mode", - label = "label_sherlock_mode" - }) - - form3:MakeSlider({ - serverConvar = "ttt_minimum_players", - label = "label_minimum_players", - min = 0, - max = 15, - decimal = 0 - }) - - local form4 = vgui.CreateTTT2Form(parent, "header_round_setup_post") - - form4:MakeCheckBox({ - serverConvar = "ttt_postround_dm", - label = "label_postround_dm" - }) - - form4:MakeSlider({ - serverConvar = "ttt_posttime_seconds", - label = "label_posttime_seconds", - min = 0, - max = 120, - decimal = 0 - }) - - local form5 = vgui.CreateTTT2Form(parent, "header_round_setup_map_duration") - - local enbSessionLimitsEnabled = form5:MakeCheckBox({ - serverConvar = "ttt_session_limits_enabled", - label = "label_session_limits_enabled" - }) - - form5:MakeHelp({ - label = "help_round_limit" - }) - - form5:MakeSlider({ - serverConvar = "ttt_round_limit", - label = "label_round_limit", - min = 0, - max = 100, - decimal = 0, - master = enbSessionLimitsEnabled - }) - - form5:MakeSlider({ - serverConvar = "ttt_time_limit_minutes", - label = "label_time_limit_minutes", - min = 0, - max = 175, - decimal = 0, - master = enbSessionLimitsEnabled - }) + local form = vgui.CreateTTT2Form(parent, "header_round_dead_players") + + form:MakeCheckBox({ + serverConvar = "ttt_identify_body_woconfirm", + label = "label_identify_body_woconfirm", + }) + + form:MakeCheckBox({ + serverConvar = "ttt_announce_body_found", + label = "label_announce_body_found", + }) + + form:MakeCheckBox({ + serverConvar = "ttt2_confirm_killlist", + label = "label_confirm_killlist", + }) + + form:MakeCheckBox({ + serverConvar = "ttt_dyingshot", + label = "label_dyingshot", + }) + + form:MakeHelp({ + label = "help_inspect_confirm_mode", + }) + + form:MakeComboBox({ + label = "label_inspect_confirm_mode", + serverConvar = "ttt2_inspect_confirm_mode", + choices = { + { title = TryT("choice_inspect_confirm_mode_0"), value = 0 }, + { title = TryT("choice_inspect_confirm_mode_1"), value = 1 }, + { title = TryT("choice_inspect_confirm_mode_2"), value = 2 }, + }, + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_round_setup_prep") + + form2:MakeCheckBox({ + serverConvar = "ttt2_prep_respawn", + label = "label_prep_respawn", + }) + + form2:MakeCheckBox({ + serverConvar = "ttt_nade_throw_during_prep", + label = "label_nade_throw_during_prep", + }) + + form2:MakeSlider({ + serverConvar = "ttt_preptime_seconds", + label = "label_preptime_seconds", + min = 0, + max = 120, + decimal = 0, + }) + + form2:MakeSlider({ + serverConvar = "ttt_firstpreptime", + label = "label_firstpreptime_seconds", + min = 0, + max = 120, + decimal = 0, + }) + + local form3 = vgui.CreateTTT2Form(parent, "header_round_setup_round") + + form3:MakeSlider({ + serverConvar = "ttt_roundtime_minutes", + label = "label_roundtime_minutes", + min = 0, + max = 60, + decimal = 0, + }) + + form3:MakeHelp({ + label = "help_haste_mode", + }) + + local enbHaste = form3:MakeCheckBox({ + serverConvar = "ttt_haste", + label = "label_haste", + }) + + form3:MakeSlider({ + serverConvar = "ttt_haste_starting_minutes", + label = "label_haste_starting_minutes", + min = 0, + max = 60, + decimal = 0, + master = enbHaste, + }) + + form3:MakeSlider({ + serverConvar = "ttt_haste_minutes_per_death", + label = "label_haste_minutes_per_death", + min = 0, + max = 10, + decimal = 1, + master = enbHaste, + }) + + form3:MakeHelp({ + label = "help_sherlock_mode", + }) + + form3:MakeCheckBox({ + serverConvar = "ttt_sherlock_mode", + label = "label_sherlock_mode", + }) + + form3:MakeSlider({ + serverConvar = "ttt_minimum_players", + label = "label_minimum_players", + min = 0, + max = 15, + decimal = 0, + }) + + local form4 = vgui.CreateTTT2Form(parent, "header_round_setup_post") + + form4:MakeCheckBox({ + serverConvar = "ttt_postround_dm", + label = "label_postround_dm", + }) + + form4:MakeSlider({ + serverConvar = "ttt_posttime_seconds", + label = "label_posttime_seconds", + min = 0, + max = 120, + decimal = 0, + }) + + local form5 = vgui.CreateTTT2Form(parent, "header_round_setup_map_duration") + + local enbSessionLimitsEnabled = form5:MakeCheckBox({ + serverConvar = "ttt_session_limits_enabled", + label = "label_session_limits_enabled", + }) + + form5:MakeHelp({ + label = "help_round_limit", + master = enbSessionLimitsEnabled, + }) + + form5:MakeSlider({ + serverConvar = "ttt_round_limit", + label = "label_round_limit", + min = 0, + max = 100, + decimal = 0, + master = enbSessionLimitsEnabled, + }) + + form5:MakeSlider({ + serverConvar = "ttt_time_limit_minutes", + label = "label_time_limit_minutes", + min = 0, + max = 175, + decimal = 0, + master = enbSessionLimitsEnabled, + }) end diff --git a/lua/terrortown/menus/gamemode/administration/sprint.lua b/lua/terrortown/menus/gamemode/administration/sprint.lua index ea33c1684..7f50e5ca5 100644 --- a/lua/terrortown/menus/gamemode/administration/sprint.lua +++ b/lua/terrortown/menus/gamemode/administration/sprint.lua @@ -6,43 +6,37 @@ CLGAMEMODESUBMENU.priority = 87 CLGAMEMODESUBMENU.title = "submenu_administration_sprint_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_sprint_general") - - local enbSprint = form:MakeCheckBox({ - serverConvar = "ttt2_sprint_enabled", - label = "label_sprint_enabled" - }) - - form:MakeSlider({ - serverConvar = "ttt2_sprint_max", - label = "label_sprint_max", - min = 0, - max = 2, - decimal = 2, - master = enbSprint - }) - - form:MakeSlider({ - serverConvar = "ttt2_sprint_stamina_consumption", - label = "label_sprint_stamina_consumption", - min = 0, - max = 2, - decimal = 2, - master = enbSprint - }) - - form:MakeSlider({ - serverConvar = "ttt2_sprint_stamina_regeneration", - label = "label_sprint_stamina_regeneration", - min = 0, - max = 2, - decimal = 2, - master = enbSprint - }) - - form:MakeCheckBox({ - serverConvar = "ttt2_sprint_crosshair", - label = "label_sprint_crosshair", - master = enbSprint - }) + local form = vgui.CreateTTT2Form(parent, "header_sprint_general") + + local enbSprint = form:MakeCheckBox({ + serverConvar = "ttt2_sprint_enabled", + label = "label_sprint_enabled", + }) + + form:MakeSlider({ + serverConvar = "ttt2_sprint_max", + label = "label_sprint_max", + min = 0, + max = 2, + decimal = 2, + master = enbSprint, + }) + + form:MakeSlider({ + serverConvar = "ttt2_sprint_stamina_consumption", + label = "label_sprint_stamina_consumption", + min = 0, + max = 2, + decimal = 2, + master = enbSprint, + }) + + form:MakeSlider({ + serverConvar = "ttt2_sprint_stamina_regeneration", + label = "label_sprint_stamina_regeneration", + min = 0, + max = 2, + decimal = 2, + master = enbSprint, + }) end diff --git a/lua/terrortown/menus/gamemode/appearance/crosshair.lua b/lua/terrortown/menus/gamemode/appearance/crosshair.lua index caf8abbd0..0970f2008 100644 --- a/lua/terrortown/menus/gamemode/appearance/crosshair.lua +++ b/lua/terrortown/menus/gamemode/appearance/crosshair.lua @@ -1,114 +1,111 @@ --- @ignore +local TryT = LANG.TryTranslation + CLGAMEMODESUBMENU.base = "base_gamemodesubmenu" CLGAMEMODESUBMENU.priority = 95 CLGAMEMODESUBMENU.title = "submenu_appearance_crosshair_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_crosshair_settings") - - -- store the reference to the checkbox in a variable - -- because the other settings are enabled based on - -- the state of this checkbox - local crossEnb = form:MakeCheckBox({ - label = "label_crosshair_enable", - convar = "ttt_enable_crosshair" - }) - - -- store the reference to the checkbox in a variable - -- because the other settings are enabled based on - -- the state of this checkbox - local crossGapEnb = form:MakeCheckBox({ - label = "label_crosshair_gap_enable", - convar = "ttt_crosshair_gap_enable", - master = crossEnb - }) - - form:MakeSlider({ - label = "label_crosshair_gap", - convar = "ttt_crosshair_gap", - min = 0, - max = 30, - decimal = 0, - -- this master depends on crossEnb, therefore this - -- slider also depends on crossEnb, while also depending - -- on crossGapEnb - master = crossGapEnb - }) - - form:MakeSlider({ - label = "label_crosshair_opacity", - convar = "ttt_crosshair_opacity", - min = 0, - max = 1, - decimal = 1, - master = crossEnb - }) - - form:MakeSlider({ - label = "label_crosshair_ironsight_opacity", - convar = "ttt_ironsights_crosshair_opacity", - min = 0, - max = 1, - decimal = 1, - master = crossEnb - }) - - form:MakeSlider({ - label = "label_crosshair_size", - convar = "ttt_crosshair_size", - min = 0.1, - max = 3, - decimal = 1, - master = crossEnb - }) - - form:MakeSlider({ - label = "label_crosshair_thickness", - convar = "ttt_crosshair_thickness", - min = 1, - max = 10, - decimal = 0, - master = crossEnb - }) - - form:MakeSlider({ - label = "label_crosshair_thickness_outline", - convar = "ttt_crosshair_outlinethickness", - min = 0, - max = 5, - decimal = 0, - master = crossEnb - }) - - form:MakeCheckBox({ - label = "label_crosshair_static_enable", - convar = "ttt_crosshair_static", - master = crossEnb - }) - - form:MakeCheckBox({ - label = "label_crosshair_dot_enable", - convar = "ttt_crosshair_dot", - master = crossEnb - }) - - form:MakeCheckBox({ - label = "label_crosshair_lines_enable", - convar = "ttt_crosshair_lines", - master = crossEnb - }) - - form:MakeCheckBox({ - label = "label_crosshair_scale_enable", - convar = "ttt_crosshair_weaponscale", - master = crossEnb - }) - - form:MakeCheckBox({ - label = "label_crosshair_ironsight_low_enabled", - convar = "ttt_ironsights_lowered", - master = crossEnb - }) + local form = vgui.CreateTTT2Form(parent, "header_crosshair_settings") + + -- store the reference to the checkbox in a variable + -- because the other settings are enabled based on + -- the state of this checkbox + local crossEnb = form:MakeCheckBox({ + label = "label_crosshair_enable", + convar = "ttt_enable_crosshair", + }) + + form:MakeHelp({ + label = "help_crosshair_scale_enable", + master = crossEnb, + }) + + local crossDynScaleEnb = form:MakeCheckBox({ + label = "label_crosshair_scale_enable", + convar = "ttt_crosshair_weaponscale", + master = crossEnb, + }) + + form:MakeCheckBox({ + label = "label_crosshair_static_length", + convar = "ttt_crosshair_static_length", + master = crossDynScaleEnb, + }) + + form:MakeSlider({ + label = "label_crosshair_size", + convar = "ttt_crosshair_size", + min = 0.1, + max = 3, + decimal = 1, + master = crossEnb, + }) + + form:MakeSlider({ + label = "label_crosshair_thickness", + convar = "ttt_crosshair_thickness", + min = 1, + max = 10, + decimal = 0, + master = crossEnb, + }) + + form:MakeComboBox({ + label = "label_crosshair_mode", + convar = "ttt_crosshair_mode", + choices = { + { title = TryT("choice_crosshair_mode_0"), value = 0 }, + { title = TryT("choice_crosshair_mode_1"), value = 1 }, + { title = TryT("choice_crosshair_mode_2"), value = 2 }, + }, + master = crossEnb, + }) + + local crossOutlineEnb = form:MakeCheckBox({ + label = "label_crosshair_thickness_outline_enable", + convar = "ttt_crosshair_outline_enable", + master = crossEnb, + }) + + form:MakeSlider({ + label = "label_crosshair_thickness_outline", + convar = "ttt_crosshair_outline_thickness", + min = 0, + max = 5, + decimal = 0, + master = crossOutlineEnb, + }) + + form:MakeCheckBox({ + label = "label_crosshair_outline_high_contrast", + convar = "ttt_crosshair_outline_high_contrast", + master = crossOutlineEnb, + }) + + form:MakeSlider({ + label = "label_crosshair_opacity", + convar = "ttt_crosshair_opacity", + min = 0, + max = 1, + decimal = 1, + master = crossEnb, + }) + + form:MakeSlider({ + label = "label_crosshair_ironsight_opacity", + convar = "ttt_ironsights_crosshair_opacity", + min = 0, + max = 1, + decimal = 1, + master = crossEnb, + }) + + form:MakeCheckBox({ + label = "label_grenade_trajectory_ui", + convar = "ttt2_grenade_trajectory_ui", + master = crossEnb, + }) end diff --git a/lua/terrortown/menus/gamemode/appearance/damageindicator.lua b/lua/terrortown/menus/gamemode/appearance/damageindicator.lua index 2db129bbc..bab896720 100644 --- a/lua/terrortown/menus/gamemode/appearance/damageindicator.lua +++ b/lua/terrortown/menus/gamemode/appearance/damageindicator.lua @@ -6,51 +6,51 @@ CLGAMEMODESUBMENU.priority = 94 CLGAMEMODESUBMENU.title = "submenu_appearance_dmgindicator_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_damage_indicator") - - form:MakeHelp({ - label = "help_damage_indicator_desc" - }) - - -- store the reference to the checkbox in a variable - -- because the other settings are enabled based on - -- the state of this checkbox - local dmgEnb = form:MakeCheckBox({ - label = "label_damage_indicator_enable", - convar = "ttt_dmgindicator_enable" - }) - - form:MakeComboBox({ - label = "label_damage_indicator_mode", - convar = "ttt_dmgindicator_mode", - choices = dmgindicator.GetThemeNames(), - master = dmgEnb - }) - - form:MakeSlider({ - label = "label_damage_indicator_duration", - convar = "ttt_dmgindicator_duration", - min = 0, - max = 30, - decimal = 2, - master = dmgEnb - }) - - form:MakeSlider({ - label = "label_damage_indicator_maxdamage", - convar = "ttt_dmgindicator_maxdamage", - min = 0, - max = 100, - decimal = 1, - master = dmgEnb - }) - - form:MakeSlider({ - label = "label_damage_indicator_maxalpha", - convar = "ttt_dmgindicator_maxalpha", - min = 0, - max = 255, - decimal = 0, - master = dmgEnb - }) + local form = vgui.CreateTTT2Form(parent, "header_damage_indicator") + + form:MakeHelp({ + label = "help_damage_indicator_desc", + }) + + -- store the reference to the checkbox in a variable + -- because the other settings are enabled based on + -- the state of this checkbox + local dmgEnb = form:MakeCheckBox({ + label = "label_damage_indicator_enable", + convar = "ttt_dmgindicator_enable", + }) + + form:MakeComboBox({ + label = "label_damage_indicator_mode", + convar = "ttt_dmgindicator_mode", + choices = dmgindicator.GetThemeNames(), + master = dmgEnb, + }) + + form:MakeSlider({ + label = "label_damage_indicator_duration", + convar = "ttt_dmgindicator_duration", + min = 0, + max = 30, + decimal = 2, + master = dmgEnb, + }) + + form:MakeSlider({ + label = "label_damage_indicator_maxdamage", + convar = "ttt_dmgindicator_maxdamage", + min = 0, + max = 100, + decimal = 1, + master = dmgEnb, + }) + + form:MakeSlider({ + label = "label_damage_indicator_maxalpha", + convar = "ttt_dmgindicator_maxalpha", + min = 0, + max = 255, + decimal = 0, + master = dmgEnb, + }) end diff --git a/lua/terrortown/menus/gamemode/appearance/general.lua b/lua/terrortown/menus/gamemode/appearance/general.lua index 89c142b42..2d02aeb60 100644 --- a/lua/terrortown/menus/gamemode/appearance/general.lua +++ b/lua/terrortown/menus/gamemode/appearance/general.lua @@ -6,46 +6,46 @@ CLGAMEMODESUBMENU.priority = 100 CLGAMEMODESUBMENU.title = "submenu_appearance_general_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_global_color") - - form:MakeHelp({ - label = "help_color_desc" - }) - - -- store the reference to the checkbox in a variable - -- because the other settings are enabled based on - -- the state of this checkbox - local enbColor = form:MakeCheckBox({ - label = "label_global_color_enable", - initial = appearance.ShouldUseGlobalFocusColor(), - OnChange = function(_, value) - appearance.SetUseGlobalFocusColor(value) - end, - default = false - }) - - form:MakeColorMixer({ - label = "label_global_color", - initial = appearance.GetFocusColor(), - OnChange = function(_, color) - appearance.SetFocusColor(color) - end, - master = enbColor - }) - - form:MakeHelp({ - label = "help_scale_factor" - }) - - form:MakeSlider({ - label = "label_global_scale_factor", - min = 0.1, - max = 3, - decimal = 1, - initial = appearance.GetGlobalScale(), - OnChange = function(_, value) - appearance.SetGlobalScale(value) - end, - default = appearance.GetDefaultGlobalScale() - }) + local form = vgui.CreateTTT2Form(parent, "header_global_color") + + form:MakeHelp({ + label = "help_color_desc", + }) + + -- store the reference to the checkbox in a variable + -- because the other settings are enabled based on + -- the state of this checkbox + local enbColor = form:MakeCheckBox({ + label = "label_global_color_enable", + initial = appearance.ShouldUseGlobalFocusColor(), + OnChange = function(_, value) + appearance.SetUseGlobalFocusColor(value) + end, + default = false, + }) + + form:MakeColorMixer({ + label = "label_global_color", + initial = appearance.GetFocusColor(), + OnChange = function(_, color) + appearance.SetFocusColor(color) + end, + master = enbColor, + }) + + form:MakeHelp({ + label = "help_scale_factor", + }) + + form:MakeSlider({ + label = "label_global_scale_factor", + min = 0.1, + max = 3, + decimal = 1, + initial = appearance.GetGlobalScale(), + OnChange = function(_, value) + appearance.SetGlobalScale(value) + end, + default = appearance.GetDefaultGlobalScale(), + }) end diff --git a/lua/terrortown/menus/gamemode/appearance/hudswitcher.lua b/lua/terrortown/menus/gamemode/appearance/hudswitcher.lua index 504fe7143..ff743c4a5 100644 --- a/lua/terrortown/menus/gamemode/appearance/hudswitcher.lua +++ b/lua/terrortown/menus/gamemode/appearance/hudswitcher.lua @@ -1,68 +1,93 @@ --- @ignore local hudSwicherSettings = { - ["color"] = function(parent, currentHUD, key, data) - parent:MakeColorMixer({ - label = data.desc or key, - initial = currentHUD[key], - showAlphaBar = true, - showPalette = true, - OnChange = function(_, color) - currentHUD[key] = color - - if isfunction(data.OnChange) then - data.OnChange(currentHUD, color) - end - end - }) - end, - - ["number"] = function(parent, currentHUD, key, data) - parent:MakeSlider({ - label = data.desc or key, - min = data.min or 0.1, - max = data.max or 4, - decimal = data.decimal or 1, - initial = math.Round(currentHUD[key] or 1, 1), - default = data.default, - OnChange = function(_, value) - value = math.Round(value, 1) - currentHUD[key] = value - - if isfunction(data.OnChange) then - data.OnChange(currentHUD, value) - end - end - }) - end, - - ["boolean"] = function(parent, currentHUD, key, data) - parent:MakeCheckBox({ - label = data.desc or key, - initial = math.Round(currentHUD[key] or 1, 1), - default = data.default, - OnChange = function(_, value) - value = value or false - currentHUD[key] = value - - if isfunction(data.OnChange) then - data.OnChange(currentHUD, value) - end - end - }) - end + ["color"] = function(parent, currentHUD, key, data) + parent:MakeColorMixer({ + label = data.desc or key, + initial = currentHUD[key], + showAlphaBar = true, + showPalette = true, + OnChange = function(_, color) + currentHUD[key] = color + + if isfunction(data.OnChange) then + data.OnChange(currentHUD, color) + end + end, + }) + end, + + ["number"] = function(parent, currentHUD, key, data) + parent:MakeSlider({ + label = data.desc or key, + min = data.min or 0.1, + max = data.max or 4, + decimal = data.decimal or 1, + initial = math.Round(currentHUD[key] or 1, 1), + default = data.default, + OnChange = function(_, value) + value = math.Round(value, 1) + currentHUD[key] = value + + if isfunction(data.OnChange) then + data.OnChange(currentHUD, value) + end + end, + }) + end, + + ["bool"] = function(parent, currentHUD, key, data) + parent:MakeCheckBox({ + label = data.desc or key, + initial = currentHUD[key] == nil and true or currentHUD[key], + default = data.default, + OnChange = function(_, value) + value = value or false + currentHUD[key] = value + + if isfunction(data.OnChange) then + data.OnChange(currentHUD, value) + end + end, + }) + end, } local function PopulateHUDSwitcherPanelSettings(parent, currentHUD) - parent:Clear() + parent:Clear() - parent:MakeHelp({ - label = "help_hud_special_settings" - }) + parent:MakeHelp({ + label = "help_hud_special_settings", + }) - for key, data in pairs(currentHUD:GetSavingKeys() or {}) do - hudSwicherSettings[data.typ](parent, currentHUD, key, data) - end + for key, data in pairs(currentHUD:GetSavingKeys() or {}) do + hudSwicherSettings[data.typ](parent, currentHUD, key, data) + end +end + +local function PopulateHUDSwitcherPanelSettingsElements(parent, currentHUD) + parent:Clear() + + parent:MakeHelp({ + label = "help_hud_elements_special_settings", + }) + + local hudElements = huds.GetStored(HUDManager.GetHUD()):GetElements() + for i = 1, #hudElements do + local elemName = hudElements[i] + + local el = hudelements.GetStored(elemName) + if not el then + continue + end + + for key, data in pairs(el:GetSavingKeys() or {}) do + if not hudSwicherSettings[data.typ] then + continue + end + hudSwicherSettings[data.typ](parent, el, key, data) + end + end end CLGAMEMODESUBMENU.base = "base_gamemodesubmenu" @@ -71,96 +96,113 @@ CLGAMEMODESUBMENU.priority = 99 CLGAMEMODESUBMENU.title = "submenu_appearance_hudswitcher_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_hud_select") - - local currentHUDName = HUDManager.GetHUD() - local currentHUD = huds.GetStored(currentHUDName) - local hudList = huds.GetList() - local restrictedHUDs = ttt2net.GetGlobal({"hud_manager", "restrictedHUDs"}) - local forcedHUD = ttt2net.GetGlobal({"hud_manager", "forcedHUD"}) - local validHUDs = {} - - if not currentHUD.GetSavingKeys then - form:MakeHelp({ - label = "help_hud_game_reload" - }) - - return - end - - if forcedHUD then - validHUDs[1] = forcedHUD - else - for i = 1, #hudList do - local hud = hudList[i] - - -- do not add HUD to the selection list if restricted - if table.HasValue(restrictedHUDs, hud.id) then continue end - - validHUDs[#validHUDs + 1] = hud.id - end - end - - form:MakeComboBox({ - label = "label_hud_select", - choices = validHUDs, - selectName = currentHUDName, - OnChange = function(value) - HUDManager.SetHUD(value) - end, - default = ttt2net.GetGlobal({"hud_manager", "defaultHUD"}) - }) - - PopulateHUDSwitcherPanelSettings(vgui.CreateTTT2Form(parent, "header_hud_customize"), currentHUD) - - -- REGISTER UNHIDE FUNCTION TO STOP HUD EDITOR - HELPSCRN.menuFrame.OnShow = function(slf) - if HELPSCRN:GetOpenMenu() ~= "appearance_hudswitcher" then return end - - HUDEditor.StopEditHUD() - end + local form = vgui.CreateTTT2Form(parent, "header_hud_select") + + local currentHUDName = HUDManager.GetHUD() + local currentHUD = huds.GetStored(currentHUDName) + local hudList = huds.GetList() + local restrictedHUDs = ttt2net.GetGlobal({ "hud_manager", "restrictedHUDs" }) + local forcedHUD = ttt2net.GetGlobal({ "hud_manager", "forcedHUD" }) + local validHUDs = {} + + if not currentHUD.GetSavingKeys then + form:MakeHelp({ + label = "help_hud_game_reload", + }) + + return + end + + if forcedHUD then + validHUDs[1] = forcedHUD + else + for i = 1, #hudList do + local hud = hudList[i] + + -- do not add HUD to the selection list if restricted + if table.HasValue(restrictedHUDs, hud.id) then + continue + end + + validHUDs[#validHUDs + 1] = hud.id + end + end + + form:MakeComboBox({ + label = "label_hud_select", + choices = validHUDs, + selectName = currentHUDName, + OnChange = function(value) + HUDManager.SetHUD(value) + end, + default = ttt2net.GetGlobal({ "hud_manager", "defaultHUD" }), + }) + + PopulateHUDSwitcherPanelSettings( + vgui.CreateTTT2Form(parent, "header_hud_customize"), + currentHUD + ) + PopulateHUDSwitcherPanelSettingsElements( + vgui.CreateTTT2Form(parent, "header_hud_elements_customize"), + currentHUD + ) + + -- REGISTER UNHIDE FUNCTION TO STOP HUD EDITOR + HELPSCRN.menuFrame.OnShow = function(slf) + if HELPSCRN:GetOpenMenu() ~= "appearance_hudswitcher" then + return + end + + HUDEditor.StopEditHUD() + end end function CLGAMEMODESUBMENU:PopulateButtonPanel(parent) - local currentHUDName = HUDManager.GetHUD() - local currentHUD = huds.GetStored(currentHUDName) - - local buttonEditor = vgui.Create("DButtonTTT2", parent) - - buttonEditor:SetText("button_hud_editor") - buttonEditor:SetSize(175, 45) - buttonEditor:SetPos(20, 20) - buttonEditor.DoClick = function(btn) - if not currentHUDName then return end - - HUDEditor.EditHUD(currentHUDName) - - HELPSCRN.menuFrame:HideFrame() - end - buttonEditor:SetEnabled(not currentHUD.disableHUDEditor) - - local buttonReset = vgui.Create("DButtonTTT2", parent) - - buttonReset:SetText("button_reset") - buttonReset:SetSize(100, 45) - buttonReset:SetPos(parent:GetWide() - 120, 20) - buttonReset.DoClick = function(btn) - if not currentHUD then return end - - currentHUD:Reset() - currentHUD:SaveData() - end - buttonReset:SetEnabled(not currentHUD.disableHUDEditor) + local currentHUDName = HUDManager.GetHUD() + local currentHUD = huds.GetStored(currentHUDName) + + local buttonReset = vgui.Create("DButtonTTT2", parent) + + buttonReset:SetText("button_reset") + buttonReset:SetSize(100, 45) + buttonReset:SetPos(20, 20) + buttonReset.DoClick = function(btn) + if not currentHUD then + return + end + + currentHUD:Reset() + currentHUD:SaveData() + end + buttonReset:SetEnabled(not currentHUD.disableHUDEditor) + + local buttonEditor = vgui.Create("DButtonTTT2", parent) + + buttonEditor:SetText("button_hud_editor") + buttonEditor:SetSize(175, 45) + buttonEditor:SetPos(parent:GetWide() - 195, 20) + buttonEditor.DoClick = function(btn) + if not currentHUDName then + return + end + + HUDEditor.EditHUD() + + HELPSCRN.menuFrame:HideFrame() + end + buttonEditor:SetEnabled(not currentHUD.disableHUDEditor) end function CLGAMEMODESUBMENU:HasButtonPanel() - return true + return true end hook.Add("TTT2HUDUpdated", "UpdateHUDSwitcherData", function() - if HELPSCRN:GetOpenMenu() ~= "appearance_hudswitcher" then return end + if HELPSCRN:GetOpenMenu() ~= "appearance_hudswitcher" then + return + end - -- rebuild the content area so that data is refreshed - -- based on the newly selected HUD - vguihandler.Rebuild() + -- rebuild the content area so that data is refreshed + -- based on the newly selected HUD + vguihandler.Rebuild() end) diff --git a/lua/terrortown/menus/gamemode/appearance/interface.lua b/lua/terrortown/menus/gamemode/appearance/interface.lua index e7030810c..804b0c1c5 100644 --- a/lua/terrortown/menus/gamemode/appearance/interface.lua +++ b/lua/terrortown/menus/gamemode/appearance/interface.lua @@ -6,33 +6,65 @@ CLGAMEMODESUBMENU.priority = 92 CLGAMEMODESUBMENU.title = "submenu_appearance_interface_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_interface_settings") - - form:MakeCheckBox({ - label = "label_interface_tips_enable", - convar = "ttt_tips_enable" - }) - - form:MakeSlider({ - label = "label_interface_popup", - convar = "ttt_startpopup_duration", - min = 0, - max = 60, - decimal = 0 - }) - - form:MakeCheckBox({ - label = "label_interface_fastsw_menu", - convar = "ttt_weaponswitcher_displayfast" - }) - - form:MakeCheckBox({ - label = "label_inferface_wswitch_hide_enable", - convar = "ttt_weaponswitcher_hide" - }) - - form:MakeCheckBox({ - label = "label_inferface_scues_enable", - convar = "ttt_cl_soundcues" - }) + local form = vgui.CreateTTT2Form(parent, "header_interface_settings") + + form:MakeCheckBox({ + label = "label_interface_tips_enable", + convar = "ttt_tips_enable", + }) + + form:MakeSlider({ + label = "label_interface_popup", + convar = "ttt_startpopup_duration", + min = 0, + max = 60, + decimal = 0, + }) + + form:MakeHelp({ + label = "help_HUD_enable_description", + }) + + form:MakeCheckBox({ + label = "label_HUD_enable_box_blur", + convar = "ttt2_hud_enable_box_blur", + }) + + form:MakeCheckBox({ + label = "label_HUD_enable_description", + convar = "ttt2_hud_enable_description", + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_interface_keys") + + form2:MakeHelp({ + label = "help_keyhelp", + }) + + form2:MakeCheckBox({ + label = "label_keyhelp_show_core", + convar = "ttt2_keyhelp_show_core", + }) + + form2:MakeCheckBox({ + label = "label_keyhelp_show_extra", + convar = "ttt2_keyhelp_show_extra", + }) + + form2:MakeCheckBox({ + label = "label_keyhelp_show_equipment", + convar = "ttt2_keyhelp_show_equipment", + }) + + local form3 = vgui.CreateTTT2Form(parent, "header_interface_wepswitch") + + form3:MakeCheckBox({ + label = "label_interface_fastsw_menu", + convar = "ttt_weaponswitcher_displayfast", + }) + + form3:MakeCheckBox({ + label = "label_inferface_wswitch_hide_enable", + convar = "ttt_weaponswitcher_hide", + }) end diff --git a/lua/terrortown/menus/gamemode/appearance/miscellaneous.lua b/lua/terrortown/menus/gamemode/appearance/miscellaneous.lua deleted file mode 100644 index 7cf1bce3f..000000000 --- a/lua/terrortown/menus/gamemode/appearance/miscellaneous.lua +++ /dev/null @@ -1,10 +0,0 @@ ---- @ignore - -CLGAMEMODESUBMENU.base = "base_gamemodesubmenu" - -CLGAMEMODESUBMENU.priority = 91 -CLGAMEMODESUBMENU.title = "submenu_appearance_miscellaneous_title" - -function CLGAMEMODESUBMENU:Populate(parent) - -end diff --git a/lua/terrortown/menus/gamemode/appearance/performance.lua b/lua/terrortown/menus/gamemode/appearance/performance.lua index d1c5fd673..6d711525b 100644 --- a/lua/terrortown/menus/gamemode/appearance/performance.lua +++ b/lua/terrortown/menus/gamemode/appearance/performance.lua @@ -6,20 +6,20 @@ CLGAMEMODESUBMENU.priority = 93 CLGAMEMODESUBMENU.title = "submenu_appearance_performance_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_performance_settings") + local form = vgui.CreateTTT2Form(parent, "header_performance_settings") - form:MakeCheckBox({ - label = "label_performance_halo_enable", - convar = "ttt_entity_draw_halo" - }) + form:MakeCheckBox({ + label = "label_performance_halo_enable", + convar = "ttt_entity_draw_halo", + }) - form:MakeCheckBox({ - label = "label_performance_spec_outline_enable", - convar = "ttt2_enable_spectatorsoutline" - }) + form:MakeCheckBox({ + label = "label_performance_spec_outline_enable", + convar = "ttt2_enable_spectatorsoutline", + }) - form:MakeCheckBox({ - label = "label_performance_ohicon_enable", - convar = "ttt2_enable_overheadicons" - }) + form:MakeCheckBox({ + label = "label_performance_ohicon_enable", + convar = "ttt2_enable_overheadicons", + }) end diff --git a/lua/terrortown/menus/gamemode/appearance/shop.lua b/lua/terrortown/menus/gamemode/appearance/shop.lua index e76c21e7c..4d5347d2a 100644 --- a/lua/terrortown/menus/gamemode/appearance/shop.lua +++ b/lua/terrortown/menus/gamemode/appearance/shop.lua @@ -6,59 +6,59 @@ CLGAMEMODESUBMENU.priority = 96 CLGAMEMODESUBMENU.title = "submenu_appearance_shop_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_shop_settings") + local form = vgui.CreateTTT2Form(parent, "header_shop_settings") - form:MakeHelp({ - label = "help_shop_key_desc" - }) + form:MakeHelp({ + label = "help_shop_key_desc", + }) - form:MakeCheckBox({ - label = "label_shop_always_show", - convar = "ttt_bem_always_show_shop" - }) + form:MakeCheckBox({ + label = "label_shop_always_show", + convar = "ttt_bem_always_show_shop", + }) - if GetConVar("ttt_bem_allow_change"):GetBool() then - local form2 = vgui.CreateTTT2Form(parent, "header_shop_layout") + if GetConVar("ttt_bem_allow_change"):GetBool() then + local form2 = vgui.CreateTTT2Form(parent, "header_shop_layout") - form2:MakeSlider({ - label = "label_shop_num_col", - convar = "ttt_bem_cols", - min = 1, - max = 20, - decimal = 0 - }) + form2:MakeSlider({ + label = "label_shop_num_col", + convar = "ttt_bem_cols", + min = 1, + max = 20, + decimal = 0, + }) - form2:MakeSlider({ - label = "label_shop_num_row", - convar = "ttt_bem_rows", - min = 1, - max = 20, - decimal = 0 - }) + form2:MakeSlider({ + label = "label_shop_num_row", + convar = "ttt_bem_rows", + min = 1, + max = 20, + decimal = 0, + }) - form2:MakeSlider({ - label = "label_shop_item_size", - convar = "ttt_bem_size", - min = 32, - max = 128, - decimal = 0 - }) - end + form2:MakeSlider({ + label = "label_shop_item_size", + convar = "ttt_bem_size", + min = 32, + max = 128, + decimal = 0, + }) + end - local form3 = vgui.CreateTTT2Form(parent, "header_shop_marker") + local form3 = vgui.CreateTTT2Form(parent, "header_shop_marker") - form3:MakeCheckBox({ - label = "label_shop_show_slot", - convar = "ttt_bem_marker_slot" - }) + form3:MakeCheckBox({ + label = "label_shop_show_slot", + convar = "ttt_bem_marker_slot", + }) - form3:MakeCheckBox({ - label = "label_shop_show_custom", - convar = "ttt_bem_marker_custom" - }) + form3:MakeCheckBox({ + label = "label_shop_show_custom", + convar = "ttt_bem_marker_custom", + }) - form3:MakeCheckBox({ - label = "label_shop_show_fav", - convar = "ttt_bem_marker_fav" - }) + form3:MakeCheckBox({ + label = "label_shop_show_fav", + convar = "ttt_bem_marker_fav", + }) end diff --git a/lua/terrortown/menus/gamemode/appearance/targetid.lua b/lua/terrortown/menus/gamemode/appearance/targetid.lua index 799de926b..c9c0d8dc4 100644 --- a/lua/terrortown/menus/gamemode/appearance/targetid.lua +++ b/lua/terrortown/menus/gamemode/appearance/targetid.lua @@ -6,14 +6,14 @@ CLGAMEMODESUBMENU.priority = 97 CLGAMEMODESUBMENU.title = "submenu_appearance_targetid_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_targetid") + local form = vgui.CreateTTT2Form(parent, "header_targetid") - form:MakeHelp({ - label = "help_targetid_info" - }) + form:MakeHelp({ + label = "help_targetid_info", + }) - form:MakeCheckBox({ - label = "label_minimal_targetid", - convar = "ttt_minimal_targetid" - }) + form:MakeCheckBox({ + label = "label_minimal_targetid", + convar = "ttt_minimal_targetid", + }) end diff --git a/lua/terrortown/menus/gamemode/appearance/vskin.lua b/lua/terrortown/menus/gamemode/appearance/vskin.lua index 2f6867611..4067d8355 100644 --- a/lua/terrortown/menus/gamemode/appearance/vskin.lua +++ b/lua/terrortown/menus/gamemode/appearance/vskin.lua @@ -6,37 +6,37 @@ CLGAMEMODESUBMENU.priority = 98 CLGAMEMODESUBMENU.title = "submenu_appearance_vskin_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_vskin_select") + local form = vgui.CreateTTT2Form(parent, "header_vskin_select") - form:MakeHelp({ - label = "help_vskin_info" - }) + form:MakeHelp({ + label = "help_vskin_info", + }) - form:MakeComboBox({ - label = "label_vskin_select", - choices = vskin.GetVSkinList(), - selectName = vskin.GetVSkinName(), - OnChange = function(value) - vskin.SelectVSkin(value) - end, - default = vskin.GetDefaultVSkinName() - }) + form:MakeComboBox({ + label = "label_vskin_select", + choices = vskin.GetVSkinList(), + selectName = vskin.GetVSkinName(), + OnChange = function(value) + vskin.SelectVSkin(value) + end, + default = vskin.GetDefaultVSkinName(), + }) - form:MakeCheckBox({ - label = "label_blur_enable", - initial = vskin.ShouldBlurBackground(), - OnChange = function(_, value) - vskin.SetBlurBackground(value) - end, - default = true - }) + form:MakeCheckBox({ + label = "label_blur_enable", + initial = vskin.ShouldBlurBackground(), + OnChange = function(_, value) + vskin.SetBlurBackground(value) + end, + default = true, + }) - form:MakeCheckBox({ - label = "label_color_enable", - initial = vskin.ShouldColorBackground(), - OnChange = function(_, value) - vskin.SetColorBackground(value) - end, - default = true - }) + form:MakeCheckBox({ + label = "label_color_enable", + initial = vskin.ShouldColorBackground(), + OnChange = function(_, value) + vskin.SetColorBackground(value) + end, + default = true, + }) end diff --git a/lua/terrortown/menus/gamemode/base_gamemodemenu.lua b/lua/terrortown/menus/gamemode/base_gamemodemenu.lua index 99fe8095f..8861b3b42 100644 --- a/lua/terrortown/menus/gamemode/base_gamemodemenu.lua +++ b/lua/terrortown/menus/gamemode/base_gamemodemenu.lua @@ -18,25 +18,28 @@ CLGAMEMODEMENU.submenus = {} -- excludes admin menus for non admin players and menus without any content. -- @note This function should be overwritten but not not called. -- @return boolean Returns true if this menu should be visible --- @internal +-- @hook -- @realm client function CLGAMEMODEMENU:ShouldShow() - --- - -- @realm client - if self:IsAdminMenu() and not hook.Run("TTT2AdminCheck", LocalPlayer()) then - return false - end - - return self:HasVisibleSubmenus() + --- + -- @realm client + -- stylua: ignore + if self:IsAdminMenu() and not hook.Run("TTT2AdminCheck", LocalPlayer()) then + return false + end + + return self:HasVisibleSubmenus() end --- -- Checks if this menu has any visible submenus. They are visible if they are -- registered and @{CLGAMEMODEMENU:ShouldShow()} returns true. +-- @note This function can be overwritten, but probably shouldn't. -- @return boolean Returns true if there is at least one visible submenu +-- @hook -- @realm client function CLGAMEMODEMENU:HasVisibleSubmenus() - return #self:GetVisibleSubmenus() > 0 + return #self:GetVisibleSubmenus() > 0 end --- @@ -46,27 +49,29 @@ end -- @return table Returns a table of all registered and visible submenus -- @realm client function CLGAMEMODEMENU:GetVisibleSubmenus() - local visibleSubmenus = {} - local allSubmenus = self:GetSubmenus() + local visibleSubmenus = {} + local allSubmenus = self:GetSubmenus() - for i = 1, #allSubmenus do - local submenu = allSubmenus[i] + for i = 1, #allSubmenus do + local submenu = allSubmenus[i] - if not submenu:ShouldShow() then continue end + if not submenu:ShouldShow() then + continue + end - visibleSubmenus[#visibleSubmenus + 1] = submenu - end + visibleSubmenus[#visibleSubmenus + 1] = submenu + end - return visibleSubmenus + return visibleSubmenus end --- -- Used to define whether this menu is available for all or only for admins. --- @note This function should be overwritten but not not called. --- @internal +-- @note This function should be overwritten but not called. +-- @hook -- @realm client function CLGAMEMODEMENU:IsAdminMenu() - return false + return false end --- @@ -74,22 +79,24 @@ end -- @return table A table with all found submenus -- @realm client function CLGAMEMODEMENU:GetSubmenus() - return self.submenus + return self.submenus end --- -- Returns the reference to the submenu class if available. -- @param string name The name of the class (usually the type defined by the filename) --- @return[default=nil] Returns the reference to the found submenu class +-- @return[default=nil] table Returns the reference to the found submenu class -- @realm client function CLGAMEMODEMENU:GetSubmenuByName(name) - for i = 1, #self.submenus do - local submenu = self.submenus[i] + for i = 1, #self.submenus do + local submenu = self.submenus[i] - if submenu.type ~= name then continue end + if submenu.type ~= name then + continue + end - return submenu - end + return submenu + end end --- @@ -97,7 +104,7 @@ end -- @param table submenuTable The new submenu class table -- @realm client function CLGAMEMODEMENU:SetSubmenuTable(submenuTable) - self.submenus = submenuTable + self.submenus = submenuTable end --- @@ -105,46 +112,48 @@ end -- @param CLGAMEMODESUBMENU submenu The new submenu class -- @realm client function CLGAMEMODEMENU:AddSubmenu(submenu) - self.submenus[#self.submenus + 1] = submenu + self.submenus[#self.submenus + 1] = submenu end --- -- Checks if the menu has a searchbar enabled. -- @note This function should be overwritten and return true, if you want a searchbar. -- @return boolean Return true if searchbar should be available +-- @hook -- @realm client function CLGAMEMODEMENU:HasSearchbar() - return false + return false end --- -- Filters the list with a searchText and returns full list if nothing is entered. --- @note Overwrite MatchesSearchString for a custom search! --- This function can be overwritten, but probably shouldnt. +-- @note Overwrite MatchesSearchString for a custom search! +-- This function can be overwritten, but probably shouldn't. -- @param string searchText -- @return menuClasses Returns a list of all matching submenus, needs to be indexed with ascending numbers +-- @hook -- @realm client function CLGAMEMODEMENU:GetMatchingSubmenus(searchText) - local submenuClasses = self:GetVisibleSubmenus() + local submenuClasses = self:GetVisibleSubmenus() - if searchText == "" then - return submenuClasses - end + if searchText == "" then + return submenuClasses + end - local filteredSubmenuClasses = {} + local filteredSubmenuClasses = {} - local counter = 0 + local counter = 0 - for i = 1, #submenuClasses do - local submenuClass = submenuClasses[i] + for i = 1, #submenuClasses do + local submenuClass = submenuClasses[i] - if self:MatchesSearchString(submenuClass, searchText) then - counter = counter + 1 - filteredSubmenuClasses[counter] = submenuClass - end - end + if self:MatchesSearchString(submenuClass, searchText) then + counter = counter + 1 + filteredSubmenuClasses[counter] = submenuClass + end + end - return filteredSubmenuClasses + return filteredSubmenuClasses end --- @@ -154,21 +163,20 @@ end -- @note This function can be overwritten to use a custom searchfunction. -- @param menuClass submenuClass -- @param string searchText --- @return bool Returns if the searchText is somewhere matched inside the submenuClass +-- @return boolean Returns if the searchText is somewhere matched inside the submenuClass +-- @hook -- @realm client function CLGAMEMODEMENU:MatchesSearchString(submenuClass, searchText) - local txt = stringLower(searchText) - local title = stringLower(TryT(submenuClass.title)) - local start = stringFind(title, txt) + local txt = stringLower(searchText) + local title = stringLower(TryT(submenuClass.title)) + local start = stringFind(title, txt) - return tobool(start) + return tobool(start) end --- -- Called after the class is initialized and has finished inheriting. --- @note This function should be overwritten but not not called. --- @internal +-- @note This function should be overwritten but not called. +-- @hook -- @realm client -function CLGAMEMODEMENU:Initialize() - -end +function CLGAMEMODEMENU:Initialize() end diff --git a/lua/terrortown/menus/gamemode/base_gamemodemenu/base_gamemodesubmenu.lua b/lua/terrortown/menus/gamemode/base_gamemodemenu/base_gamemodesubmenu.lua index f60f7c896..e102e9f84 100644 --- a/lua/terrortown/menus/gamemode/base_gamemodemenu/base_gamemodesubmenu.lua +++ b/lua/terrortown/menus/gamemode/base_gamemodemenu/base_gamemodesubmenu.lua @@ -9,48 +9,42 @@ CLGAMEMODESUBMENU.title = "" -- This function is used to populate the submenu on open/refresh. -- @note This function should be overwritten but not not called. -- @param Panel parent The parent panel --- @internal +-- @hook -- @realm client -function CLGAMEMODESUBMENU:Populate(parent) - -end +function CLGAMEMODESUBMENU:Populate(parent) end --- -- This function is used to populate the button panel of a submenu on open/refresh. -- @note This function should be overwritten but not called. -- @note @{CLGAMEMODESUBMENU:HasButtonPanel} has to return true to display any button panel at all. -- @param Panel parent The parent panel --- @internal +-- @hook -- @realm client -function CLGAMEMODESUBMENU:PopulateButtonPanel(parent) - -end +function CLGAMEMODESUBMENU:PopulateButtonPanel(parent) end --- -- Function to overwrite if the menu should have a button panel. -- @note This function should be overwritten but not not called. --- @return[default=false] Returns if this submenu has a button panel --- @internal +-- @return[default=false] boolean Returns if this submenu has a button panel +-- @hook -- @realm client function CLGAMEMODESUBMENU:HasButtonPanel() - return false + return false end --- -- Called after the class is initialized and has finished inheriting. -- @note This function should be overwritten but not not called. --- @internal +-- @hook -- @realm client -function CLGAMEMODESUBMENU:Initialize() - -end +function CLGAMEMODESUBMENU:Initialize() end --- -- Used to define whether this submenu should be shown at all. -- @note This function should be overwritten but not not called. --- @return[default=true] Returns true if this submenu should be visible --- @internal +-- @return[default=true] boolean Returns true if this submenu should be visible +-- @hook -- @realm client function CLGAMEMODESUBMENU:ShouldShow() - return true + return true end diff --git a/lua/terrortown/menus/gamemode/bindings/bindings.lua b/lua/terrortown/menus/gamemode/bindings/bindings.lua index 76937a986..6afe18845 100644 --- a/lua/terrortown/menus/gamemode/bindings/bindings.lua +++ b/lua/terrortown/menus/gamemode/bindings/bindings.lua @@ -1,50 +1,53 @@ --- @ignore local function AddBindingCategory(category, parent) - local client = LocalPlayer() + local client = LocalPlayer() - local form = vgui.CreateTTT2Form(parent, category) + local form = vgui.CreateTTT2Form(parent, category) - local bindings = bind.GetSettingsBindings() + local bindings = bind.GetSettingsBindings() - for i = 1, #bindings do - local binding = bindings[i] + for i = 1, #bindings do + local binding = bindings[i] - if binding.category ~= category then continue end + if binding.category ~= category then + continue + end - local currentBinding = bind.Find(binding.name) + local currentBinding = bind.Find(binding.name) - form:MakeBinder({ - label = binding.label, - select = currentBinding, - default = binding.defaultKey, - OnDisable = function(slf, binder) - bind.Remove(currentBinding, binding.name, true) + form:MakeBinder({ + label = binding.label, + select = currentBinding, + default = binding.defaultKey, + OnDisable = function(slf, binder) + bind.Remove(currentBinding, binding.name, true) - binder:SetValue(bind.Find(binding.name)) - end, - OnChange = function(slf, keyNum) - if currentBinding == keyNum then return end + binder:SetValue(bind.Find(binding.name)) + end, + OnChange = function(slf, keyNum) + if currentBinding == keyNum then + return + end - bind.Remove(currentBinding, binding.name, true) + bind.Remove(currentBinding, binding.name, true) - if keyNum ~= 0 then - bind.Add(keyNum, binding.name, true) - end + if keyNum ~= 0 then + bind.Add(keyNum, binding.name, true) + end - local key = keyNum ~= 0 and string.upper(input.GetKeyName(keyNum)) or LANG.GetTranslation("button_none") + local key = keyNum ~= 0 and string.upper(input.GetKeyName(keyNum)) + or LANG.GetTranslation("button_none") - client:ChatPrint( - LANG.GetParamTranslation("bindings_new", { - name = binding.name, - key = key - }) - ) + client:ChatPrint(LANG.GetParamTranslation("bindings_new", { + name = binding.name, + key = key, + })) - currentBinding = keyNum - end - }) - end + currentBinding = keyNum + end, + }) + end end CLGAMEMODESUBMENU.base = "base_gamemodesubmenu" @@ -53,9 +56,9 @@ CLGAMEMODESUBMENU.priority = 100 CLGAMEMODESUBMENU.title = "submenu_bindings_bindings_title" function CLGAMEMODESUBMENU:Populate(parent) - local categories = bind.GetSettingsBindingsCategories() + local categories = bind.GetSettingsBindingsCategories() - for i = 1, #categories do - AddBindingCategory(categories[i], parent) - end + for i = 1, #categories do + AddBindingCategory(categories[i], parent) + end end diff --git a/lua/terrortown/menus/gamemode/changelog.lua b/lua/terrortown/menus/gamemode/changelog.lua index 568f4a07b..9fff2e160 100644 --- a/lua/terrortown/menus/gamemode/changelog.lua +++ b/lua/terrortown/menus/gamemode/changelog.lua @@ -14,49 +14,46 @@ CLGAMEMODEMENU.title = "menu_changelog_title" CLGAMEMODEMENU.description = "menu_changelog_description" CLGAMEMODEMENU.priority = 100 - function CLGAMEMODEMENU:Initialize() - -- add "virtual" submenus that are treated as real one even without files - local changelog = GetSortedChanges() - local changelogMenuBase = self:GetSubmenuByName("base_changelog") + -- add "virtual" submenus that are treated as real one even without files + local changelog = GetSortedChanges() + local changelogMenuBase = self:GetSubmenuByName("base_changelog") - for i = 1, #changelog do - local change = changelog[i] + for i = 1, #changelog do + local change = changelog[i] - virtualSubmenus[i] = tableCopy(changelogMenuBase) - virtualSubmenus[i].title = change.version - virtualSubmenus[i].change = change - end + virtualSubmenus[i] = tableCopy(changelogMenuBase) + virtualSubmenus[i].title = change.version + virtualSubmenus[i].change = change + end end -- overwrite the normal submenu function to return our custom virtual submenus function CLGAMEMODEMENU:GetSubmenus() - return virtualSubmenus + return virtualSubmenus end -- overwrite and return true to enable a searchbar function CLGAMEMODEMENU:HasSearchbar() - return true + return true end -- overwrite and do a custom search inside title, date and content function CLGAMEMODEMENU:MatchesSearchString(submenuClass, searchText) - local txt = stringLower(searchText) - local change = submenuClass.change + local txt = stringLower(searchText) + local change = submenuClass.change - if stringFind(stringLower(TryT(submenuClass.title)), txt) then - return true - end + if stringFind(stringLower(TryT(submenuClass.title)), txt) then + return true + end - if change.date > 0 - and stringFind(stringLower(os.date("%Y/%m/%d", change.date)), txt) - then - return true - end + if change.date > 0 and stringFind(stringLower(os.date("%Y/%m/%d", change.date)), txt) then + return true + end - if stringFind(stringLower(submenuClass.change.text), txt) then - return true - end + if stringFind(stringLower(submenuClass.change.text), txt) then + return true + end - return false + return false end diff --git a/lua/terrortown/menus/gamemode/changelog/base_changelog.lua b/lua/terrortown/menus/gamemode/changelog/base_changelog.lua index 9642d2bd8..f1d733198 100644 --- a/lua/terrortown/menus/gamemode/changelog/base_changelog.lua +++ b/lua/terrortown/menus/gamemode/changelog/base_changelog.lua @@ -1,44 +1,44 @@ --- @ignore local htmlStart = [[ - - - - + + + + ]] local htmlEnd = [[ - + ]] CLGAMEMODESUBMENU.priority = 0 CLGAMEMODESUBMENU.title = "" function CLGAMEMODESUBMENU:Populate(parent) - local header = "

        " .. self.change.version .. " Update" + local header = "

        " .. self.change.version .. " Update" - if self.change.date > 0 then - header = header .. " - (date: " .. os.date("%Y/%m/%d", self.change.date) .. ")" - end + if self.change.date > 0 then + header = header .. " - (date: " .. os.date("%Y/%m/%d", self.change.date) .. ")" + end - header = header .. "

        " + header = header .. "" - local html = vgui.Create("DHTML", parent) - html:SetSize(500, 640) - html:Dock(FILL) - html:SetHTML(htmlStart .. header .. self.change.text .. htmlEnd) + local html = vgui.Create("DHTML", parent) + html:SetSize(500, 640) + html:Dock(FILL) + html:SetHTML(htmlStart .. header .. self.change.text .. htmlEnd) end diff --git a/lua/terrortown/menus/gamemode/equipment.lua b/lua/terrortown/menus/gamemode/equipment.lua index 6253c4ab7..a30bbdede 100644 --- a/lua/terrortown/menus/gamemode/equipment.lua +++ b/lua/terrortown/menus/gamemode/equipment.lua @@ -16,70 +16,86 @@ CLGAMEMODEMENU.isInitialized = false CLGAMEMODEMENU.langConVar = nil CLGAMEMODEMENU.lang = nil -function CLGAMEMODEMENU:InitializeVirtualMenus() - -- add "virtual" submenus that are treated as real one even without files - virtualSubmenus = {} - - local equipments = ShopEditor.GetEquipmentForRoleAll() - local equipmentMenuBase = self:GetSubmenuByName("base_equipment") - - -- Assign all equipments to a virtual menu - local counter = 0 - for i = 1, #equipments do - local equipment = equipments[i] - - -- Only keep ttt-equipments that are cached - if not equipment.ttt2_cached_material and not equipment.ttt2_cached_model then continue end - - counter = counter + 1 +local builtinIcon = Material("vgui/ttt/vskin/markers/builtin") - virtualSubmenus[counter] = tableCopy(equipmentMenuBase) - virtualSubmenus[counter].equipment = equipment - virtualSubmenus[counter].isItem = items.IsItem(equipment) - virtualSubmenus[counter].icon = equipment.ttt2_cached_material - virtualSubmenus[counter].iconFullSize = true - end +function CLGAMEMODEMENU:InitializeVirtualMenus() + -- add "virtual" submenus that are treated as real one even without files + virtualSubmenus = {} + + local equipments = ShopEditor.GetEquipmentForRoleAll() + local equipmentMenuBase = self:GetSubmenuByName("base_equipment") + + -- Assign all equipments to a virtual menu + local counter = 0 + for i = 1, #equipments do + local equipment = equipments[i] + + counter = counter + 1 + + virtualSubmenus[counter] = tableCopy(equipmentMenuBase) + virtualSubmenus[counter].equipment = equipment + virtualSubmenus[counter].isItem = items.IsItem(equipment) + virtualSubmenus[counter].icon = equipment.iconMaterial + virtualSubmenus[counter].iconFullSize = true + virtualSubmenus[counter].iconBadge = equipment.builtin and builtinIcon + virtualSubmenus[counter].iconBadgeSize = 16 + virtualSubmenus[counter].tooltip = equipment.id + end end function CLGAMEMODEMENU:TranslateAndSortMenus() - -- In a first pass translate all equipment names and assign them to a title - for i = 1, #virtualSubmenus do - local vMenu = virtualSubmenus[i] - local equipment = vMenu.equipment - local name = equipment.EquipMenuData and equipment.EquipMenuData.name - - vMenu.title = TryT(name) ~= name and TryT(name) or TryT(equipment.PrintName) or equipment.id or "UNDEFINED" - end - - -- In a second pass we sort by their translated title - table.SortByMember(virtualSubmenus, "title", true) + -- In a first pass translate all equipment names and assign them to a title + for i = 1, #virtualSubmenus do + local vMenu = virtualSubmenus[i] + local equipment = vMenu.equipment + local name = equipment.EquipMenuData and equipment.EquipMenuData.name + + vMenu.title = TryT(name) ~= name and TryT(name) + or TryT(equipment.PrintName) + or equipment.id + or "UNDEFINED" + end + + -- In a second pass we sort by their translated title + table.SortByMember(virtualSubmenus, "title", true) end -- overwrite the normal submenu function to return our custom virtual submenus function CLGAMEMODEMENU:GetSubmenus() - if not self.isInitialized then - self.isInitialized = true - self.langConVar = GetConVar("ttt_language") + if not self.isInitialized then + self.isInitialized = true + self.langConVar = GetConVar("ttt_language") - self:InitializeVirtualMenus() - end + self:InitializeVirtualMenus() + end - local lang = self.langConVar:GetString() + local lang = self.langConVar:GetString() - if lang ~= self.lang then - self.lang = lang + if lang ~= self.lang then + self.lang = lang - self:TranslateAndSortMenus() - end + self:TranslateAndSortMenus() + end - return virtualSubmenus + return virtualSubmenus end function CLGAMEMODEMENU:IsAdminMenu() - return true + return true end -- overwrite and return true to enable a searchbar function CLGAMEMODEMENU:HasSearchbar() - return true + return true end + +--- +-- This hook can be used by addons to populate the settings page of equipment addons +-- with custom convars. The parent is the submenu, where a new form has to +-- be added. +-- This is for extending existing addons, role authors should use @{ITEM:AddToSettingsMenu}. +-- @param ITEM equipment The @{ITEM} or @{Weapon} which the settings menu is for +-- @param DPanel parent The parent panel which is the submenu +-- @hook +-- @realm client +function GM:TTT2OnEquipmentAddToSettingsMenu(equipment, parent) end diff --git a/lua/terrortown/menus/gamemode/equipment/base_equipment.lua b/lua/terrortown/menus/gamemode/equipment/base_equipment.lua index 5393410d2..bfb331af3 100644 --- a/lua/terrortown/menus/gamemode/equipment/base_equipment.lua +++ b/lua/terrortown/menus/gamemode/equipment/base_equipment.lua @@ -5,106 +5,197 @@ local TryT = LANG.TryTranslation CLGAMEMODESUBMENU.priority = 0 CLGAMEMODESUBMENU.title = "" -local function Invert(data, value) - if data.inverted then - return not value - else - return value - end -end - function CLGAMEMODESUBMENU:Populate(parent) - local equipment = self.equipment - local forms = {} - local form - local masterRefs = {} - - for key, data in SortedPairsByMemberValue(ShopEditor.savingKeys, "order", false) do - if self.isItem and not data.showForItem then continue end - - form = forms[data.group or 1] - - if not form then - form = vgui.CreateTTT2Form(parent, ShopEditor.groupTitles[data.group]) - forms[data.group or 1] = form - end - - local name = "equipmenteditor_name_" .. data.name - local desc = "equipmenteditor_desc_" .. data.name - - if data.b_desc then - form:MakeHelp({ - label = desc - }) - end - - local option - - if data.typ == "bool" then - option = form:MakeCheckBox({ - label = name, - default = Invert(data, equipment.defaultValues[key]), - initial = Invert(data, equipment[key]), - OnChange = function(_, value) - net.Start("TTT2SESaveItem") - net.WriteString(equipment.id) - net.WriteUInt(1, ShopEditor.savingKeysBitCount or 16) - net.WriteString(key) - net.WriteBool(Invert(data, tobool(value))) - net.SendToServer() - end, - master = data.master and masterRefs[data.master] - }) - elseif data.typ == "number" then - if data.subtype == "enum" then - option = form:MakeComboBox({ - label = name, - default = equipment.defaultValues[key], - OnChange = function(enum) - net.Start("TTT2SESaveItem") - net.WriteString(equipment.id) - net.WriteUInt(1, ShopEditor.savingKeysBitCount or 16) - net.WriteString(key) - net.WriteUInt(math.Round(enum), data.bits or 16) - net.SendToServer() - end, - master = data.master and masterRefs[data.master] - }) - - local enum - for i = 1, #data.choices do - enum = data.choices[i] - option:AddChoice(TryT(data.lookupNamesFunc(enum)), enum, equipment[key] == enum) - end - else - option = form:MakeSlider({ - label = name, - min = data.min, - max = data.max, - decimal = 0, - default = equipment.defaultValues[key], - initial = equipment[key], - OnChange = function(_, value) - net.Start("TTT2SESaveItem") - net.WriteString(equipment.id) - net.WriteUInt(1, ShopEditor.savingKeysBitCount or 16) - net.WriteString(key) - net.WriteUInt(math.Round(value), data.bits or 16) - net.SendToServer() - end, - master = data.master and masterRefs[data.master] - }) - end - end - - masterRefs[key] = option - end - - -- Get inheritable version for weapons to access inherited functions - if not self.isItem then - equipment = weapons.Get(WEPS.GetClass(equipment)) - end - - -- now add custom equipment settings - equipment:AddToSettingsMenu(parent) + local equipment = self.equipment + local accessName = ShopEditor.accessName + local itemName = equipment.id + + if equipment.builtin then + local form = vgui.CreateTTT2Form(parent, "header_equipment_info") + form:MakeHelp({ + label = "equipmenteditor_desc_builtin", + }) + end + + if not self.isItem then + local form = vgui.CreateTTT2Form(parent, "header_equipment_weapon_spawn_setup") + + form:MakeHelp({ + label = "equipmenteditor_desc_auto_spawnable", + }) + + local master = form:MakeCheckBox({ + label = "equipmenteditor_name_auto_spawnable", + database = DatabaseElement(accessName, itemName, "AutoSpawnable"), + }) + + local entType + local entTypeList = + entspawnscript.GetEntTypeList(SPAWN_TYPE_WEAPON, { [WEAPON_TYPE_RANDOM] = true }) + local choices = {} + for i = 1, #entTypeList do + entType = entTypeList[i] + choices[i] = { + title = TryT( + entspawnscript.GetLangIdentifierFromSpawnType(SPAWN_TYPE_WEAPON, entType) + ), + value = entType, + } + end + + form:MakeComboBox({ + label = "equipmenteditor_name_spawn_type", + database = DatabaseElement(accessName, itemName, "spawnType"), + choices = choices, + master = master, + }) + end + + local form = vgui.CreateTTT2Form(parent, "header_equipment_setup") + + form:MakeHelp({ + label = "equipmenteditor_desc_kind", + }) + + form:MakeComboBox({ + label = "equipmenteditor_name_kind", + database = DatabaseElement(accessName, itemName, "Kind"), + choices = { + { title = TryT("slot_weapon_melee"), value = WEAPON_MELEE }, + { title = TryT("slot_weapon_pistol"), value = WEAPON_PISTOL }, + { title = TryT("slot_weapon_heavy"), value = WEAPON_HEAVY }, + { title = TryT("slot_weapon_nade"), value = WEAPON_NADE }, + { title = TryT("slot_weapon_carry"), value = WEAPON_CARRY }, + { title = TryT("slot_weapon_special"), value = WEAPON_SPECIAL }, + { title = TryT("slot_weapon_extra"), value = WEAPON_EXTRA }, + { title = TryT("slot_weapon_class"), value = WEAPON_CLASS }, + }, + }) + + form:MakeHelp({ + label = "equipmenteditor_desc_not_buyable", + }) + + local master = form:MakeCheckBox({ + label = "equipmenteditor_name_not_buyable", + database = DatabaseElement(accessName, itemName, "notBuyable"), + invert = true, + }) + + form:MakeHelp({ + label = "equipmenteditor_desc_not_random", + master = master, + }) + + form:MakeCheckBox({ + label = "equipmenteditor_name_not_random", + database = DatabaseElement(accessName, itemName, "NoRandom"), + master = master, + }) + + form:MakeHelp({ + label = "equipmenteditor_desc_global_limited", + master = master, + }) + + form:MakeCheckBox({ + label = "equipmenteditor_name_global_limited", + database = DatabaseElement(accessName, itemName, "globalLimited"), + master = master, + }) + + form:MakeHelp({ + label = "equipmenteditor_desc_team_limited", + master = master, + }) + + form:MakeCheckBox({ + label = "equipmenteditor_name_team_limited", + database = DatabaseElement(accessName, itemName, "teamLimited"), + master = master, + }) + + form:MakeHelp({ + label = "equipmenteditor_desc_player_limited", + master = master, + }) + + form:MakeCheckBox({ + label = "equipmenteditor_name_player_limited", + database = DatabaseElement(accessName, itemName, "limited"), + master = master, + }) + + form:MakeHelp({ + label = "equipmenteditor_desc_allow_drop", + }) + + form:MakeCheckBox({ + label = "equipmenteditor_name_allow_drop", + database = DatabaseElement(accessName, itemName, "AllowDrop"), + }) + + form:MakeHelp({ + label = "equipmenteditor_desc_drop_on_death_type", + }) + + form:MakeComboBox({ + label = "equipmenteditor_name_drop_on_death_type", + database = DatabaseElement(accessName, itemName, "overrideDropOnDeath"), + choices = { + { title = TryT("drop_on_death_type_default"), value = DROP_ON_DEATH_TYPE_DEFAULT }, + { title = TryT("drop_on_death_type_force"), value = DROP_ON_DEATH_TYPE_FORCE }, + { title = TryT("drop_on_death_type_deny"), value = DROP_ON_DEATH_TYPE_DENY }, + }, + }) + + form = vgui.CreateTTT2Form(parent, "header_equipment_value_setup") + + form:MakeSlider({ + label = "equipmenteditor_name_min_players", + min = 0, + max = 63, + decimal = 0, + database = DatabaseElement(accessName, itemName, "minPlayers"), + }) + + form:MakeSlider({ + label = "equipmenteditor_name_credits", + min = 0, + max = 20, + decimal = 0, + database = DatabaseElement(accessName, itemName, "credits"), + }) + + form:MakeHelp({ + label = "equipmenteditor_desc_damage_scaling", + }) + + form:MakeSlider({ + label = "equipmenteditor_name_damage_scaling", + min = 0, + max = 8, + decimal = 2, + database = DatabaseElement(accessName, itemName, "damageScaling"), + }) + + -- Get inheritable version for weapons to access inherited functions + if not self.isItem then + equipment = weapons.Get(WEPS.GetClass(equipment)) + end + + -- now add custom equipment settings + if isfunction(equipment.AddToSettingsMenu) then + equipment:AddToSettingsMenu(parent) + else + Dev( + 1, + "Weapon '" + .. equipment:GetClass() + .. "' doesn't use the weapon_tttbase and cannot be added to the settings panel." + ) + end + + -- stylua: ignore + hook.Run("TTT2OnEquipmentAddToSettingsMenu", equipment, parent) end diff --git a/lua/terrortown/menus/gamemode/gameplay/accessibility.lua b/lua/terrortown/menus/gamemode/gameplay/accessibility.lua new file mode 100644 index 000000000..f0d4f7e98 --- /dev/null +++ b/lua/terrortown/menus/gamemode/gameplay/accessibility.lua @@ -0,0 +1,34 @@ +--- @ignore + +CLGAMEMODESUBMENU.base = "base_gamemodesubmenu" + +CLGAMEMODESUBMENU.priority = 98 +CLGAMEMODESUBMENU.title = "submenu_gameplay_accessibility_title" +CLGAMEMODESUBMENU.icon = Material("vgui/ttt/vskin/helpscreen/accessibility") + +function CLGAMEMODESUBMENU:Populate(parent) + local form = vgui.CreateTTT2Form(parent, "header_accessibility_settings") + + form:MakeHelp({ + label = "help_enable_dynamic_fov", + }) + + form:MakeCheckBox({ + label = "label_enable_dynamic_fov", + convar = "ttt2_enable_dynamic_fov", + }) + + form:MakeHelp({ + label = "help_enable_bobbing_strafe", + }) + + form:MakeCheckBox({ + label = "label_enable_bobbing", + convar = "ttt2_enable_bobbing", + }) + + form:MakeCheckBox({ + label = "label_enable_bobbing_strafe", + convar = "ttt2_enable_bobbing_strafe", + }) +end diff --git a/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua b/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua deleted file mode 100644 index 6ae5bbf7a..000000000 --- a/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua +++ /dev/null @@ -1,27 +0,0 @@ ---- @ignore - -CLGAMEMODESUBMENU.base = "base_gamemodesubmenu" - -CLGAMEMODESUBMENU.priority = 99 -CLGAMEMODESUBMENU.title = "submenu_gameplay_avoidroles_title" - -function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_roleselection") - - local roles = roles.GetList() - - for i = 1, #roles do - local v = roles[i] - - if ConVarExists("ttt_avoid_" .. v.name) then - local rolename = v.name - - form:MakeCheckBox({ - label = rolename, - convar = "ttt_avoid_" .. rolename - }) - end - end - - form:Dock(TOP) -end diff --git a/lua/terrortown/menus/gamemode/gameplay/general.lua b/lua/terrortown/menus/gamemode/gameplay/general.lua index eac15dd97..2ba8895c5 100644 --- a/lua/terrortown/menus/gamemode/gameplay/general.lua +++ b/lua/terrortown/menus/gamemode/gameplay/general.lua @@ -4,43 +4,36 @@ CLGAMEMODESUBMENU.base = "base_gamemodesubmenu" CLGAMEMODESUBMENU.priority = 100 CLGAMEMODESUBMENU.title = "submenu_gameplay_general_title" +CLGAMEMODESUBMENU.icon = Material("vgui/ttt/vskin/helpscreen/gameplay") function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_gameplay_settings") - - form:MakeCheckBox({ - label = "label_gameplay_specmode", - convar = "ttt_spectator_mode" - }) - - form:MakeCheckBox({ - label = "label_gameplay_fastsw", - convar = "ttt_weaponswitcher_fast" - }) - - form:MakeCheckBox({ - label = "label_gameplay_hold_aim", - convar = "ttt2_hold_aim" - }) - - form:MakeCheckBox({ - label = "label_gameplay_mute", - convar = "ttt_mute_team_check" - }) - - local enbSprint = form:MakeCheckBox({ - label = "label_gameplay_dtsprint_enable", - convar = "ttt2_enable_doubletap_sprint" - }) - - form:MakeCheckBox({ - label = "label_gameplay_dtsprint_anykey", - convar = "ttt2_doubletap_sprint_anykey", - master = enbSprint - }) - - form:MakeCheckBox({ - label = "label_shop_double_click_buy", - convar = "ttt_bem_enable_doubleclick_buy" - }) + local form = vgui.CreateTTT2Form(parent, "header_gameplay_settings") + + form:MakeCheckBox({ + label = "label_gameplay_specmode", + convar = "ttt_spectator_mode", + }) + + form:MakeCheckBox({ + label = "label_shop_double_click_buy", + convar = "ttt_bem_enable_doubleclick_buy", + }) + + local form2 = vgui.CreateTTT2Form(parent, "header_weapon_settings") + + form2:MakeCheckBox({ + label = "label_gameplay_fastsw", + convar = "ttt_weaponswitcher_fast", + }) + + form2:MakeCheckBox({ + label = "label_gameplay_hold_aim", + convar = "ttt2_hold_aim", + }) + + form2:MakeCheckBox({ + label = "label_crosshair_ironsight_low_enabled", + convar = "ttt_ironsights_lowered", + master = crossEnb, + }) end diff --git a/lua/terrortown/menus/gamemode/gameplay/voiceandvolume.lua b/lua/terrortown/menus/gamemode/gameplay/voiceandvolume.lua new file mode 100644 index 000000000..fef5ca51c --- /dev/null +++ b/lua/terrortown/menus/gamemode/gameplay/voiceandvolume.lua @@ -0,0 +1,48 @@ +--- @ignore + +CLGAMEMODESUBMENU.base = "base_gamemodesubmenu" + +CLGAMEMODESUBMENU.priority = 99 +CLGAMEMODESUBMENU.title = "submenu_gameplay_voiceandvolume_title" +CLGAMEMODESUBMENU.icon = Material("vgui/ttt/vskin/helpscreen/voiceandvolume") + +function CLGAMEMODESUBMENU:Populate(parent) + local form = vgui.CreateTTT2Form(parent, "header_soundeffect_settings") + + form:MakeCheckBox({ + label = "label_inferface_scues_enable", + convar = "ttt_cl_soundcues", + }) + + form = vgui.CreateTTT2Form(parent, "header_voiceandvolume_settings") + + form:MakeCheckBox({ + label = "label_gameplay_mute", + convar = "ttt_mute_team_check", + }) + + form:MakeComboBox({ + label = "label_voice_scaling", + convar = "ttt2_voice_scaling", + choices = VOICE.GetScalingFunctions(), + OnChange = function() + for _, ply in ipairs(player.GetAll()) do + VOICE.UpdatePlayerVoiceVolume(ply) + end + end, + }) + + local enbSpecDuck = form:MakeCheckBox({ + label = "label_voice_duck_spectator", + convar = "ttt2_voice_duck_spectator", + }) + + form:MakeSlider({ + label = "label_voice_duck_spectator_amount", + convar = "ttt2_voice_duck_spectator_amount", + min = 0, + max = 1, + decimal = 2, + master = enbSpecDuck, + }) +end diff --git a/lua/terrortown/menus/gamemode/guide/equipment.lua b/lua/terrortown/menus/gamemode/guide/equipment.lua index ab71d90f7..85d61ecb8 100644 --- a/lua/terrortown/menus/gamemode/guide/equipment.lua +++ b/lua/terrortown/menus/gamemode/guide/equipment.lua @@ -5,16 +5,16 @@ CLGAMEMODESUBMENU.priority = 98 CLGAMEMODESUBMENU.title = "submenu_guide_equipment_title" function CLGAMEMODESUBMENU:Populate(parent) - local line1 = vgui.Create("DLabel", parent) - line1:SetPos(40, 40) - line1:SetFont("DermaLarge") - line1:SetText(LANG.TryTranslation("guide_nothing_title")) - line1:SizeToContents() + local line1 = vgui.Create("DLabel", parent) + line1:SetPos(40, 40) + line1:SetFont("DermaLarge") + line1:SetText(LANG.TryTranslation("guide_nothing_title")) + line1:SizeToContents() - local line2 = vgui.Create("DLabel", parent) - line2:SetPos(40, 40) - line2:MoveBelow(line1, 10) - line2:SetFont("Trebuchet24") - line2:SetText(LANG.TryTranslation("guide_nothing_desc")) - line2:SizeToContents() + local line2 = vgui.Create("DLabel", parent) + line2:SetPos(40, 40) + line2:MoveBelow(line1, 10) + line2:SetFont("Trebuchet24") + line2:SetText(LANG.TryTranslation("guide_nothing_desc")) + line2:SizeToContents() end diff --git a/lua/terrortown/menus/gamemode/guide/gameplay.lua b/lua/terrortown/menus/gamemode/guide/gameplay.lua index d3bfa6ed9..7f2d6a8d7 100644 --- a/lua/terrortown/menus/gamemode/guide/gameplay.lua +++ b/lua/terrortown/menus/gamemode/guide/gameplay.lua @@ -6,16 +6,16 @@ CLGAMEMODESUBMENU.priority = 100 CLGAMEMODESUBMENU.title = "submenu_guide_gameplay_title" function CLGAMEMODESUBMENU:Populate(parent) - local line1 = vgui.Create("DLabel", parent) - line1:SetPos(40, 40) - line1:SetFont("DermaLarge") - line1:SetText(LANG.TryTranslation("guide_nothing_title")) - line1:SizeToContents() + local line1 = vgui.Create("DLabel", parent) + line1:SetPos(40, 40) + line1:SetFont("DermaLarge") + line1:SetText(LANG.TryTranslation("guide_nothing_title")) + line1:SizeToContents() - local line2 = vgui.Create("DLabel", parent) - line2:SetPos(40, 40) - line2:MoveBelow(line1, 10) - line2:SetFont("Trebuchet24") - line2:SetText(LANG.TryTranslation("guide_nothing_desc")) - line2:SizeToContents() + local line2 = vgui.Create("DLabel", parent) + line2:SetPos(40, 40) + line2:MoveBelow(line1, 10) + line2:SetFont("Trebuchet24") + line2:SetText(LANG.TryTranslation("guide_nothing_desc")) + line2:SizeToContents() end diff --git a/lua/terrortown/menus/gamemode/guide/roles.lua b/lua/terrortown/menus/gamemode/guide/roles.lua index b4c274eb2..055a5a9e5 100644 --- a/lua/terrortown/menus/gamemode/guide/roles.lua +++ b/lua/terrortown/menus/gamemode/guide/roles.lua @@ -6,16 +6,16 @@ CLGAMEMODESUBMENU.priority = 99 CLGAMEMODESUBMENU.title = "submenu_guide_roles_title" function CLGAMEMODESUBMENU:Populate(parent) - local line1 = vgui.Create("DLabel", parent) - line1:SetPos(40, 40) - line1:SetFont("DermaLarge") - line1:SetText(LANG.TryTranslation("guide_nothing_title")) - line1:SizeToContents() + local line1 = vgui.Create("DLabel", parent) + line1:SetPos(40, 40) + line1:SetFont("DermaLarge") + line1:SetText(LANG.TryTranslation("guide_nothing_title")) + line1:SizeToContents() - local line2 = vgui.Create("DLabel", parent) - line2:SetPos(40, 40) - line2:MoveBelow(line1, 10) - line2:SetFont("Trebuchet24") - line2:SetText(LANG.TryTranslation("guide_nothing_desc")) - line2:SizeToContents() + local line2 = vgui.Create("DLabel", parent) + line2:SetPos(40, 40) + line2:MoveBelow(line1, 10) + line2:SetFont("Trebuchet24") + line2:SetText(LANG.TryTranslation("guide_nothing_desc")) + line2:SizeToContents() end diff --git a/lua/terrortown/menus/gamemode/language/language.lua b/lua/terrortown/menus/gamemode/language/language.lua index f2c20dc56..cd885dbd6 100644 --- a/lua/terrortown/menus/gamemode/language/language.lua +++ b/lua/terrortown/menus/gamemode/language/language.lua @@ -10,31 +10,37 @@ CLGAMEMODESUBMENU.priority = 100 CLGAMEMODESUBMENU.title = "submenu_language_language_title" function CLGAMEMODESUBMENU:Populate(parent) - local form = vgui.CreateTTT2Form(parent, "header_language") - - local choices = {} - local activeLanguageName = GetActiveLanguageName() - - choices[1] = {title = TryT("lang_server_default"), value = "auto", select = activeLanguageName == "auto"} - - for _, lang in pairs(LANG.GetLanguages()) do - choices[#choices + 1] = {title = GetTranslatedLanguageName(lang), value = lang, select = activeLanguageName == lang} - end - - form:MakeComboBox({ - label = "label_language_set", - convar = "ttt_language", - choices = choices, - OnChange = function(value) - if value == GetActiveLanguageName() then return end - - vguihandler.InvalidateVSkin() - vguihandler.Rebuild() - end - }) - - form:MakeHelp({ - label = "help_lang_info", - params = {coverage = math.Round(100 * LANG.GetDefaultCoverage(activeLanguageName), 1)} - }) + local form = vgui.CreateTTT2Form(parent, "header_language") + + local choices = {} + local activeLanguageName = GetActiveLanguageName() + + choices[1] = { + title = TryT("lang_server_default"), + value = "auto", + select = activeLanguageName == "auto", + } + + for _, lang in pairs(LANG.GetLanguages()) do + choices[#choices + 1] = { + title = GetTranslatedLanguageName(lang), + value = lang, + select = activeLanguageName == lang, + } + end + + form:MakeComboBox({ + label = "label_language_set", + convar = "ttt_language", + choices = choices, + OnChange = function(value) + vguihandler.InvalidateVSkin() + vguihandler.Rebuild() + end, + }) + + form:MakeHelp({ + label = "help_lang_info", + params = { coverage = math.Round(100 * LANG.GetDefaultCoverage(activeLanguageName), 1) }, + }) end diff --git a/lua/terrortown/menus/gamemode/legacy.lua b/lua/terrortown/menus/gamemode/legacy.lua index bad9a4a03..a0adf236d 100644 --- a/lua/terrortown/menus/gamemode/legacy.lua +++ b/lua/terrortown/menus/gamemode/legacy.lua @@ -16,73 +16,80 @@ local virtualSubmenus = {} local elemStore local function RegisterLegacyTabCache() - elemStore = vgui.Create("DPropertySheet") - elemStore:SetVisible(false) + elemStore = vgui.Create("DPropertySheet") + elemStore:SetVisible(false) - elemStore.AddSheet = function(slf, label, panel, material, noStretchX, noStretchY, tooltip) - if not IsValid(panel) then - ErrorNoHalt("DPropertySheet:AddSheet tried to add invalid panel!") - debug.Trace() + elemStore.AddSheet = function(slf, label, panel, material, noStretchX, noStretchY, tooltip) + if not IsValid(panel) then + ErrorNoHaltWithStack("DPropertySheet:AddSheet tried to add invalid panel!") - return - end + return + end - local sheet = {} + local sheet = {} - sheet.name = label - sheet.panel = panel - sheet.panel.NoStretchX = noStretchX - sheet.panel.NoStretchY = noStretchY + sheet.name = label + sheet.panel = panel + sheet.panel.NoStretchX = noStretchX + sheet.panel.NoStretchY = noStretchY - slf.Items[#slf.Items + 1] = sheet + slf.Items[#slf.Items + 1] = sheet - return sheet - end + return sheet + end - elemStore.ResetItems = function(slf) - slf.Items = {} - end + elemStore.ResetItems = function(slf) + slf.Items = {} + end - -- moves children to tab cache to prevent deleting of children - hook.Add("TTT2OnHelpSubMenuClear", "TTT2HandleLegacyClear", function(parent, nameMenuOpen, lastMenuData, menuData) - if nameMenuOpen ~= "ttt2_legacy" or not elemStore then return end + -- moves children to tab cache to prevent deleting of children + hook.Add( + "TTT2OnHelpSubMenuClear", + "TTT2HandleLegacyClear", + function(parent, nameMenuOpen, lastMenuData, menuData) + if nameMenuOpen ~= "ttt2_legacy" or not elemStore then + return + end - local children = parent:GetChildren() + local children = parent:GetChildren() - for i = 1, #children do - children[i]:SetParent(elemStore) - end - end) + for i = 1, #children do + children[i]:SetParent(elemStore) + end + end + ) end local function GetLegacyTabs() - if not elemStore then - return {} - end + if not elemStore then + return {} + end - elemStore:Clear() - elemStore:ResetItems() + elemStore:Clear() + elemStore:ResetItems() - --- - -- @realm client - hook.Run("TTTSettingsTabs", elemStore) + --- + -- @realm client + -- stylua: ignore + hook.Run("TTTSettingsTabs", elemStore) - return elemStore:GetItems() + return elemStore:GetItems() end -- check if there are any legacy menues by probing the hook local function CheckForLegacyTabs() - local dtabs = vgui.Create("DPropertySheet") + local dtabs = vgui.Create("DPropertySheet") - --- - -- @realm client - hook.Run("TTTSettingsTabs", dtabs) + --- + -- @realm client + -- stylua: ignore + hook.Run("TTTSettingsTabs", dtabs) - local amount = #dtabs:GetItems() + local amount = #dtabs:GetItems() - dtabs:Remove() + dtabs:Remove() - return amount > 0 + return amount > 0 end CLGAMEMODEMENU.base = "base_gamemodemenu" @@ -93,29 +100,31 @@ CLGAMEMODEMENU.description = "menu_legacy_description" CLGAMEMODEMENU.priority = 94 function CLGAMEMODEMENU:Initialize() - -- add "virtual" submenus that are treated as real one even without files + -- add "virtual" submenus that are treated as real one even without files - if not CheckForLegacyTabs() then return end + if not CheckForLegacyTabs() then + return + end - -- register legacy tab cache - RegisterLegacyTabCache() + -- register legacy tab cache + RegisterLegacyTabCache() - local legacyTabs = GetLegacyTabs() - local legacyMenuBase = self:GetSubmenuByName("base_legacy") + local legacyTabs = GetLegacyTabs() + local legacyMenuBase = self:GetSubmenuByName("base_legacy") - for i = 1, #legacyTabs do - local legacyTab = legacyTabs[i] + for i = 1, #legacyTabs do + local legacyTab = legacyTabs[i] - virtualSubmenus[i] = tableCopy(legacyMenuBase) - virtualSubmenus[i].title = legacyTab.name - virtualSubmenus[i].elemStore = elemStore - virtualSubmenus[i].index = i - end + virtualSubmenus[i] = tableCopy(legacyMenuBase) + virtualSubmenus[i].title = legacyTab.name + virtualSubmenus[i].elemStore = elemStore + virtualSubmenus[i].index = i + end end -- overwrite the normal submenu function to return our custom virtual submenus function CLGAMEMODEMENU:GetSubmenus() - return virtualSubmenus + return virtualSubmenus end --- @@ -124,6 +133,4 @@ end -- @deprecated -- @hook -- @realm client -function GM:TTTSettingsTabs() - -end +function GM:TTTSettingsTabs() end diff --git a/lua/terrortown/menus/gamemode/legacy/base_legacy.lua b/lua/terrortown/menus/gamemode/legacy/base_legacy.lua index a560c363c..450f11500 100644 --- a/lua/terrortown/menus/gamemode/legacy/base_legacy.lua +++ b/lua/terrortown/menus/gamemode/legacy/base_legacy.lua @@ -4,35 +4,35 @@ CLGAMEMODESUBMENU.priority = 0 CLGAMEMODESUBMENU.title = "" local function GetLegacyTabs(elemStore) - if not elemStore then - return {} - end + if not elemStore then + return {} + end - elemStore:Clear() - elemStore:ResetItems() + elemStore:Clear() + elemStore:ResetItems() - --- - -- @realm client - hook.Run("TTTSettingsTabs", elemStore) + --- + -- @realm client + -- stylua: ignore + hook.Run("TTTSettingsTabs", elemStore) - return elemStore:GetItems() + return elemStore:GetItems() end function CLGAMEMODESUBMENU:Populate(parent) - -- Always recreate LegacyTabs to prevent a NULL panel from parent deletion - local legacyTab = GetLegacyTabs(self.elemStore)[self.index] + -- Always recreate LegacyTabs to prevent a NULL panel from parent deletion + local legacyTab = GetLegacyTabs(self.elemStore)[self.index] - if not legacyTab then return end + if not legacyTab then + return + end - local panel = legacyTab.panel + local panel = legacyTab.panel - local psizeX, psizeY = parent:GetSize() - local ppadLeft, _, ppadRight, _ = parent:GetDockPadding() + local psizeX, psizeY = parent:GetSize() + local ppadLeft, _, ppadRight, _ = parent:GetDockPadding() - panel:SetParent(parent) - panel:SetSize( - psizeX - ppadLeft - ppadRight, - psizeY - 2 * HELPSCRN.padding - ) - panel:Dock(FILL) + panel:SetParent(parent) + panel:SetSize(psizeX - ppadLeft - ppadRight, psizeY - 2 * HELPSCRN.padding) + panel:Dock(FILL) end diff --git a/lua/terrortown/menus/gamemode/roles.lua b/lua/terrortown/menus/gamemode/roles.lua index 572560b87..c21ef5b2a 100644 --- a/lua/terrortown/menus/gamemode/roles.lua +++ b/lua/terrortown/menus/gamemode/roles.lua @@ -15,43 +15,60 @@ CLGAMEMODEMENU.priority = 47 CLGAMEMODEMENU.isInitialized = false CLGAMEMODEMENU.roles = nil +local builtinIcon = Material("vgui/ttt/vskin/markers/builtin") + function CLGAMEMODEMENU:IsAdminMenu() - return true + return true end function CLGAMEMODEMENU:InitializeVirtualMenus() - -- add "virtual" submenus that are treated as real one even without files - virtualSubmenus = {} + -- add "virtual" submenus that are treated as real one even without files + virtualSubmenus = {} - self.roles = roles.GetSortedRoles() - local rolesMenuBase = self:GetSubmenuByName("base_roles") + self.roles = roles.GetSortedRoles() + local rolesMenuBase = self:GetSubmenuByName("base_roles") - local counter = 0 - for _, roleData in pairs(self.roles) do - if roleData.index == ROLE_NONE then continue end + local counter = 0 + for _, roleData in pairs(self.roles) do + if roleData.index == ROLE_NONE then + continue + end - counter = counter + 1 + counter = counter + 1 - virtualSubmenus[counter] = tableCopy(rolesMenuBase) - virtualSubmenus[counter].title = roleData.name - virtualSubmenus[counter].icon = roleData.iconMaterial - virtualSubmenus[counter].roleData = roleData - virtualSubmenus[counter].basemenu = self - end + virtualSubmenus[counter] = tableCopy(rolesMenuBase) + virtualSubmenus[counter].title = roleData.name + virtualSubmenus[counter].icon = roleData.iconMaterial + virtualSubmenus[counter].roleData = roleData + virtualSubmenus[counter].iconBadge = roleData.builtin and builtinIcon + virtualSubmenus[counter].iconBadgeSize = 8 + virtualSubmenus[counter].basemenu = self + end end -- overwrite the normal submenu function to return our custom virtual submenus function CLGAMEMODEMENU:GetSubmenus() - if not self.isInitialized then - self.isInitialized = true + if not self.isInitialized then + self.isInitialized = true - self:InitializeVirtualMenus() - end + self:InitializeVirtualMenus() + end - return virtualSubmenus + return virtualSubmenus end -- overwrite and return true to enable a searchbar function CLGAMEMODEMENU:HasSearchbar() - return true + return true end + +--- +-- This hook can be used to extend role settings page of role addons +-- with custom convars. The parent is the submenu, where a new form has to +-- be added. +-- This is for extending existing addons, role authors should use @{ROLE:AddToSettingsMenu}. +-- @param ROLE role @{ROLE} data, +-- @param DPanel parent The parent panel which is the submenu +-- @hook +-- @realm client +function GM:TTT2OnRoleAddToSettingsMenu(role, parent) end diff --git a/lua/terrortown/menus/gamemode/roles/base_roles.lua b/lua/terrortown/menus/gamemode/roles/base_roles.lua index 870868e19..bcd362078 100644 --- a/lua/terrortown/menus/gamemode/roles/base_roles.lua +++ b/lua/terrortown/menus/gamemode/roles/base_roles.lua @@ -4,148 +4,167 @@ CLGAMEMODESUBMENU.priority = 0 CLGAMEMODESUBMENU.title = "" local function PopulateInfo(parent, roleData) - parent:MakeHelp({ - label = "help_roles_default_team", - params = roleData.defaultTeam - }) - - parent:MakeHelp({ - label = roleData.notSelectable and "help_roles_unselectable" or "help_roles_selectable" - }) - - parent:MakeHelp({ - label = "ttt2_desc_" .. roleData.name - }) + if roleData.builtin then + parent:MakeHelp({ + label = "help_roles_builtin", + }) + end + + parent:MakeHelp({ + label = "help_roles_default_team", + params = roleData.defaultTeam, + }) + + parent:MakeHelp({ + label = roleData.notSelectable and "help_roles_unselectable" or "help_roles_selectable", + }) + + parent:MakeHelp({ + label = "ttt2_desc_" .. roleData.name, + }) end local function PopulateSelection(parent, roleData) - if roleData.index == ROLE_INNOCENT or roleData.index == ROLE_TRAITOR then - parent:MakeHelp({ - label = "help_roles_selection_short" - }) - else - parent:MakeHelp({ - label = "help_roles_selection" - }) - end - - if roleData.index == ROLE_INNOCENT then - parent:MakeSlider({ - serverConvar = "ttt_min_inno_pct", - label = "label_roles_min_inno_pct", - min = 0, - max = 1, - decimal = 2 - }) - - return - end - - local masterEnb - - if roleData.index ~= ROLE_TRAITOR then - masterEnb = parent:MakeCheckBox({ - serverConvar = "ttt_" .. roleData.name .. "_enabled", - label = "label_roles_enabled" - }) - end - - parent:MakeSlider({ - serverConvar = "ttt_" .. roleData.name .. "_pct", - label = "label_roles_pct", - min = 0, - max = 1, - decimal = 2, - master = masterEnb - }) - - parent:MakeSlider({ - serverConvar = "ttt_" .. roleData.name .. "_max", - label = "label_roles_max", - min = 0, - max = 64, - decimal = 0, - master = masterEnb - }) - - if roleData.index == ROLE_TRAITOR then return end - - parent:MakeSlider({ - serverConvar = "ttt_" .. roleData.name .. "_random", - label = "label_roles_random", - min = 0, - max = 100, - decimal = 0, - master = masterEnb - }) - - parent:MakeSlider({ - serverConvar = "ttt_" .. roleData.name .. "_min_players", - label = "label_roles_min_players", - min = 0, - max = 64, - decimal = 0, - master = masterEnb - }) - - parent:MakeSlider({ - serverConvar = "ttt_" .. roleData.name .. "_karma_min", - label = "label_roles_min_karma", - min = 0, - max = 1000, - decimal = 0, - master = masterEnb - }) + if roleData.index == ROLE_INNOCENT or roleData.index == ROLE_TRAITOR then + parent:MakeHelp({ + label = "help_roles_selection_short", + }) + else + parent:MakeHelp({ + label = "help_roles_selection", + }) + end + + if roleData.index == ROLE_INNOCENT then + parent:MakeSlider({ + serverConvar = "ttt_min_inno_pct", + label = "label_roles_min_inno_pct", + min = 0, + max = 1, + decimal = 2, + }) + + return + end + + local masterEnb + + if roleData.index ~= ROLE_TRAITOR then + masterEnb = parent:MakeCheckBox({ + serverConvar = "ttt_" .. roleData.name .. "_enabled", + label = "label_roles_enabled", + }) + end + + parent:MakeSlider({ + serverConvar = "ttt_" .. roleData.name .. "_pct", + label = "label_roles_pct", + min = 0, + max = 1, + decimal = 2, + master = masterEnb, + }) + + parent:MakeSlider({ + serverConvar = "ttt_" .. roleData.name .. "_max", + label = "label_roles_max", + min = 0, + max = 64, + decimal = 0, + master = masterEnb, + }) + + if roleData.index == ROLE_TRAITOR then + return + end + + parent:MakeSlider({ + serverConvar = "ttt_" .. roleData.name .. "_random", + label = "label_roles_random", + min = 0, + max = 100, + decimal = 0, + master = masterEnb, + }) + + parent:MakeSlider({ + serverConvar = "ttt_" .. roleData.name .. "_min_players", + label = "label_roles_min_players", + min = 0, + max = 64, + decimal = 0, + master = masterEnb, + }) + + parent:MakeSlider({ + serverConvar = "ttt_" .. roleData.name .. "_karma_min", + label = "label_roles_min_karma", + min = 0, + max = 1000, + decimal = 0, + master = masterEnb, + }) +end + +local function PopulateMagnetoStick(parent, roleData) + parent:MakeCheckBox({ + serverConvar = "ttt2_ragdoll_pinning_" .. roleData.name, + label = "label_roles_ragdoll_pinning", + }) end local function PopulateTButtons(parent, roleData) - parent:MakeCheckBox({ - serverConvar = "ttt_" .. roleData.name .. "_traitor_button", - label = "label_roles_tbutton" - }) + parent:MakeCheckBox({ + serverConvar = "ttt_" .. roleData.name .. "_traitor_button", + label = "label_roles_tbutton", + }) end local function PopulateCredits(parent, roleData) - parent:MakeHelp({ - label = "help_roles_credits" - }) - - parent:MakeSlider({ - serverConvar = "ttt_" .. roleData.abbr .. "_credits_starting", - label = "label_roles_credits_starting", - min = 0, - max = 10, - decimal = 0 - }) - - parent:MakeHelp({ - label = "help_roles_credits_award" - }) - - parent:MakeCheckBox({ - serverConvar = "ttt_" .. roleData.abbr .. "_credits_award_dead_enb", - label = "label_roles_credits_dead_award" - }) - - parent:MakeCheckBox({ - serverConvar = "ttt_" .. roleData.abbr .. "_credits_award_kill_enb", - label = "label_roles_credits_kill_award" - }) - - -- run a hook to add role specific custom credit convars - roleData:AddToSettingsMenuCreditsForm(parent) + parent:MakeHelp({ + label = "help_roles_credits", + }) + + parent:MakeSlider({ + serverConvar = "ttt_" .. roleData.abbr .. "_credits_starting", + label = "label_roles_credits_starting", + min = 0, + max = 10, + decimal = 0, + }) + + parent:MakeHelp({ + label = "help_roles_credits_award", + }) + + parent:MakeCheckBox({ + serverConvar = "ttt_" .. roleData.abbr .. "_credits_award_dead_enb", + label = "label_roles_credits_dead_award", + }) + + parent:MakeCheckBox({ + serverConvar = "ttt_" .. roleData.abbr .. "_credits_award_kill_enb", + label = "label_roles_credits_kill_award", + }) + + -- run a hook to add role specific custom credit convars + roleData:AddToSettingsMenuCreditsForm(parent) end function CLGAMEMODESUBMENU:Populate(parent) - PopulateInfo(vgui.CreateTTT2Form(parent, "header_roles_info"), self.roleData) + PopulateInfo(vgui.CreateTTT2Form(parent, "header_roles_info"), self.roleData) + + if not self.roleData.notSelectable then + PopulateSelection(vgui.CreateTTT2Form(parent, "header_roles_selection"), self.roleData) + end - if not self.roleData.notSelectable then - PopulateSelection(vgui.CreateTTT2Form(parent, "header_roles_selection"), self.roleData) - end + PopulateMagnetoStick(vgui.CreateTTT2Form(parent, "header_roles_magnetostick"), self.roleData) + PopulateTButtons(vgui.CreateTTT2Form(parent, "header_roles_tbuttons"), self.roleData) + PopulateCredits(vgui.CreateTTT2Form(parent, "header_roles_credits"), self.roleData) - PopulateTButtons(vgui.CreateTTT2Form(parent, "header_roles_tbuttons"), self.roleData) - PopulateCredits(vgui.CreateTTT2Form(parent, "header_roles_credits"), self.roleData) + -- run a hook to add role specific custom convars + self.roleData:AddToSettingsMenu(parent) - -- run a hook to add role specific custom convars - self.roleData:AddToSettingsMenu(parent) + -- stylua: ignore + hook.Run("TTT2OnRoleAddToSettingsMenu", self.roleData, parent) end diff --git a/lua/terrortown/menus/gamemode/server_addons.lua b/lua/terrortown/menus/gamemode/server_addons.lua index 3fc79d6a8..56cbb5f55 100644 --- a/lua/terrortown/menus/gamemode/server_addons.lua +++ b/lua/terrortown/menus/gamemode/server_addons.lua @@ -9,9 +9,9 @@ CLGAMEMODEMENU.priority = 46 -- overwrite and return true to enable a searchbar function CLGAMEMODEMENU:HasSearchbar() - return true + return true end function CLGAMEMODEMENU:IsAdminMenu() - return true + return true end diff --git a/lua/terrortown/menus/gamemode/shops.lua b/lua/terrortown/menus/gamemode/shops.lua index 753471e5c..1ec5d0a20 100644 --- a/lua/terrortown/menus/gamemode/shops.lua +++ b/lua/terrortown/menus/gamemode/shops.lua @@ -15,44 +15,50 @@ CLGAMEMODEMENU.priority = 48 CLGAMEMODEMENU.isInitialized = false CLGAMEMODEMENU.roles = nil +local builtinIcon = Material("vgui/ttt/vskin/markers/builtin") + function CLGAMEMODEMENU:IsAdminMenu() - return true + return true end function CLGAMEMODEMENU:InitializeVirtualMenus() - -- add "virtual" submenus that are treated as real one even without files - virtualSubmenus = {} + -- add "virtual" submenus that are treated as real one even without files + virtualSubmenus = {} - self.roles = roles.GetSortedRoles() - local shopsMenuBase = self:GetSubmenuByName("base_shops") + self.roles = roles.GetSortedRoles() + local shopsMenuBase = self:GetSubmenuByName("base_shops") - local counter = 0 - for _, roleData in pairs(self.roles) do - if roleData.index == ROLE_NONE then continue end + local counter = 0 + for _, roleData in pairs(self.roles) do + if roleData.index == ROLE_NONE then + continue + end - counter = counter + 1 + counter = counter + 1 - virtualSubmenus[counter] = tableCopy(shopsMenuBase) - virtualSubmenus[counter].title = roleData.name - virtualSubmenus[counter].icon = roleData.iconMaterial - virtualSubmenus[counter].roleData = roleData - virtualSubmenus[counter].roles = self.roles - virtualSubmenus[counter].basemenu = self - end + virtualSubmenus[counter] = tableCopy(shopsMenuBase) + virtualSubmenus[counter].title = roleData.name + virtualSubmenus[counter].icon = roleData.iconMaterial + virtualSubmenus[counter].roleData = roleData + virtualSubmenus[counter].iconBadge = roleData.builtin and builtinIcon + virtualSubmenus[counter].iconBadgeSize = 8 + virtualSubmenus[counter].roles = self.roles + virtualSubmenus[counter].basemenu = self + end end -- overwrite the normal submenu function to return our custom virtual submenus function CLGAMEMODEMENU:GetSubmenus() - if not self.isInitialized then - self.isInitialized = true + if not self.isInitialized then + self.isInitialized = true - self:InitializeVirtualMenus() - end + self:InitializeVirtualMenus() + end - return virtualSubmenus + return virtualSubmenus end -- overwrite and return true to enable a searchbar function CLGAMEMODEMENU:HasSearchbar() - return true + return true end diff --git a/lua/terrortown/menus/gamemode/shops/base_shops.lua b/lua/terrortown/menus/gamemode/shops/base_shops.lua index f83f7fb73..9b016fe09 100644 --- a/lua/terrortown/menus/gamemode/shops/base_shops.lua +++ b/lua/terrortown/menus/gamemode/shops/base_shops.lua @@ -14,103 +14,120 @@ CLGAMEMODESUBMENU.priority = 0 CLGAMEMODESUBMENU.title = "" function CLGAMEMODESUBMENU:Populate(parent) - local myRoleName = self.roleData.name - local roleIndex = self.roleData.index + local myRoleName = self.roleData.name + local roleIndex = self.roleData.index - local form = vgui.CreateTTT2Form(parent, "header_shop_linker") - - local shopLink = form:MakeComboBox({ - label = "label_shop_linker_set", - OnChange = function(roleName) - if ShopEditor.fallback[myRoleName] == roleName then return end - - ShopEditor.fallback[myRoleName] = roleName - - net.Start("shopFallback") - net.WriteUInt(roleIndex, ROLE_BITS) - net.WriteString(roleName) - net.SendToServer() - - ShopEditor.customShopRefresh = true - vguihandler.Rebuild() - end - }) - - shopLink:SetSortItems(false) - - ShopEditor.fallback[myRoleName] = ShopEditor.fallback[myRoleName] or GetGlobalString("ttt_" .. self.roleData.abbr .. "_shop_fallback") - - local fallback = ShopEditor.fallback[myRoleName] - local iconPlaceholder = nil - - -- Add own data for creating own shop - shopLink:AddChoice(TryT("create_own_shop"), myRoleName, fallback == myRoleName, iconPlaceholder) - - -- Since these are no simple roles, the choices have to be added manually - shopLink:AddChoice(TryT("shop_default"), SHOP_UNSET, fallback == SHOP_UNSET, iconPlaceholder) - shopLink:AddChoice(TryT("shop_disabled"), SHOP_DISABLED, fallback == SHOP_DISABLED, iconPlaceholder) - - for _, roleData in pairs(self.roles) do - if roleData.index == ROLE_NONE or roleData.index == roleIndex then continue end - - shopLink:AddChoice(TryT("shop_link") .. ": " .. TryT(roleData.name), roleData.name, fallback == roleData.name, iconPlaceholder) - end - - -- Display all items as cards for custom shop if selected and shopFallback is refreshed - if fallback ~= myRoleName or ShopEditor.customShopRefresh then return end - - local sortedEquipmentList = ShopEditor.sortedEquipmentList[GetActiveLanguageName] or {} - - -- If there is no language specific sorted equipment list, create one - if IsEmpty(sortedEquipmentList) then - local items = ShopEditor.GetEquipmentForRoleAll() - - local counter = 0 - for i = 1, #items do - local item = items[i] - - -- Only keep ttt-equipments that are cached - if not item.ttt2_cached_material and not item.ttt2_cached_model then continue end - - counter = counter + 1 - - sortedEquipmentList[counter] = item - end - - for i = 1, #sortedEquipmentList do - local item = sortedEquipmentList[i] - local name = item.EquipMenuData and item.EquipMenuData.name - - item.shopTitle = TryT(name) ~= name and TryT(name) or TryT(item.PrintName) or item.id or "UNDEFINED" - end - - SortByMember(sortedEquipmentList, "shopTitle", true) - - -- Save the translated and sorted list - ShopEditor.sortedEquipmentList[GetActiveLanguageName] = sortedEquipmentList - end - - -- Create toggelable cards to create a custom shop - local base = form:MakeIconLayout() - - for i = 1, #sortedEquipmentList do - local item = sortedEquipmentList[i] - - form:MakeCard({ - label = item.shopTitle, - icon = item.ttt2_cached_material, - initial = item.CanBuy[roleIndex] and MODE_ADDED or MODE_DEFAULT, -- todo: this should be the current mode - OnChange = function(_, _, newMode) - local isAdded = newMode == MODE_ADDED or newMode == MODE_INHERIT_ADDED - - item.CanBuy[roleIndex] = isAdded and roleIndex or nil - - net.Start("shop") - net.WriteBool(isAdded) - net.WriteUInt(roleIndex, ROLE_BITS) - net.WriteString(item.id) - net.SendToServer() - end - }, base) - end + local form = vgui.CreateTTT2Form(parent, "header_shop_linker") + + local shopLink = form:MakeComboBox({ + label = "label_shop_linker_set", + OnChange = function(roleName) + if ShopEditor.fallback[myRoleName] == roleName then + return + end + + ShopEditor.fallback[myRoleName] = roleName + + net.Start("shopFallback") + net.WriteUInt(roleIndex, ROLE_BITS) + net.WriteString(roleName) + net.SendToServer() + + ShopEditor.customShopRefresh = true + vguihandler.Rebuild() + end, + }) + + shopLink:SetSortItems(false) + + ShopEditor.fallback[myRoleName] = ShopEditor.fallback[myRoleName] + or GetGlobalString("ttt_" .. self.roleData.abbr .. "_shop_fallback") + + local fallback = ShopEditor.fallback[myRoleName] + local iconPlaceholder = nil + + -- Add own data for creating own shop + shopLink:AddChoice(TryT("create_own_shop"), myRoleName, fallback == myRoleName, iconPlaceholder) + + -- Since these are no simple roles, the choices have to be added manually + shopLink:AddChoice(TryT("shop_default"), SHOP_UNSET, fallback == SHOP_UNSET, iconPlaceholder) + shopLink:AddChoice( + TryT("shop_disabled"), + SHOP_DISABLED, + fallback == SHOP_DISABLED, + iconPlaceholder + ) + + for _, roleData in pairs(self.roles) do + if roleData.index == ROLE_NONE or roleData.index == roleIndex then + continue + end + + shopLink:AddChoice( + TryT("shop_link") .. ": " .. TryT(roleData.name), + roleData.name, + fallback == roleData.name, + iconPlaceholder + ) + end + + -- Display all items as cards for custom shop if selected and shopFallback is refreshed + if fallback ~= myRoleName or ShopEditor.customShopRefresh then + return + end + + local sortedEquipmentList = ShopEditor.sortedEquipmentList[GetActiveLanguageName] or {} + + -- If there is no language specific sorted equipment list, create one + if IsEmpty(sortedEquipmentList) then + local items = ShopEditor.GetEquipmentForRoleAll() + + local counter = 0 + for i = 1, #items do + local item = items[i] + + counter = counter + 1 + + sortedEquipmentList[counter] = item + end + + for i = 1, #sortedEquipmentList do + local item = sortedEquipmentList[i] + local name = item.EquipMenuData and item.EquipMenuData.name + + item.shopTitle = TryT(name) ~= name and TryT(name) + or TryT(item.PrintName) + or item.id + or "UNDEFINED" + end + + SortByMember(sortedEquipmentList, "shopTitle", true) + + -- Save the translated and sorted list + ShopEditor.sortedEquipmentList[GetActiveLanguageName] = sortedEquipmentList + end + + -- Create toggelable cards to create a custom shop + local base = form:MakeIconLayout() + + for i = 1, #sortedEquipmentList do + local item = sortedEquipmentList[i] + + form:MakeCard({ + label = item.shopTitle, + icon = item.iconMaterial, + initial = item.CanBuy[roleIndex] and MODE_ADDED or MODE_DEFAULT, -- todo: this should be the current mode + OnChange = function(_, _, newMode) + local isAdded = newMode == MODE_ADDED or newMode == MODE_INHERIT_ADDED + + item.CanBuy[roleIndex] = isAdded and roleIndex or nil + + net.Start("shop") + net.WriteBool(isAdded) + net.WriteUInt(roleIndex, ROLE_BITS) + net.WriteString(item.id) + net.SendToServer() + end, + }, base) + end end diff --git a/lua/terrortown/menus/score/base_scoremenu.lua b/lua/terrortown/menus/score/base_scoremenu.lua index 84f4167b9..714841965 100644 --- a/lua/terrortown/menus/score/base_scoremenu.lua +++ b/lua/terrortown/menus/score/base_scoremenu.lua @@ -12,6 +12,4 @@ CLSCOREMENU.title = "base_menu_title" -- @note This function should be overwritten but not not called. -- @internal -- @realm client -function CLSCOREMENU:Populate(parent) - -end +function CLSCOREMENU:Populate(parent) end diff --git a/lua/terrortown/menus/score/events.lua b/lua/terrortown/menus/score/events.lua index a0384bf40..30c220b8d 100644 --- a/lua/terrortown/menus/score/events.lua +++ b/lua/terrortown/menus/score/events.lua @@ -1,26 +1,30 @@ --- @ignore local function PopulateEventTable(parent, sizes, events, onlyLocalPlayer) - local sid64 = LocalPlayer():SteamID64() + local sid64 = LocalPlayer():SteamID64() - parent:Clear() + parent:Clear() - -- SPLIT MAIN FRAME INTO FRAMEBOXES - local frameBoxesScroll = vgui.Create("DIconLayout", parent) - frameBoxesScroll:Dock(FILL) + -- SPLIT MAIN FRAME INTO FRAMEBOXES + local frameBoxesScroll = vgui.Create("DIconLayout", parent) + frameBoxesScroll:Dock(FILL) - for i = 1, #events do - local event = events[i] + for i = 1, #events do + local event = events[i] - event.onlyLocalPlayer = onlyLocalPlayer + event.onlyLocalPlayer = onlyLocalPlayer - if event.event.roundState ~= ROUND_ACTIVE then continue end - if onlyLocalPlayer and not event:HasAffectedPlayer(sid64) then continue end + if event.event.roundState ~= ROUND_ACTIVE then + continue + end + if onlyLocalPlayer and not event:HasAffectedPlayer(sid64) then + continue + end - local eventBox = frameBoxesScroll:Add("DEventBoxTTT2") - eventBox:SetEvent(event) - eventBox:SetSize(sizes.widthMainArea, eventBox:GetContentHeight(sizes.widthMainArea)) - end + local eventBox = frameBoxesScroll:Add("DEventBoxTTT2") + eventBox:SetEvent(event) + eventBox:SetSize(sizes.widthMainArea, eventBox:GetContentHeight(sizes.widthMainArea)) + end end CLSCOREMENU.base = "base_scoremenu" @@ -30,79 +34,79 @@ CLSCOREMENU.title = "title_score_events" CLSCOREMENU.priority = 99 function CLSCOREMENU:Populate(parent) - local sizes = CLSCORE.sizes - local events = CLSCORE.events + local sizes = CLSCORE.sizes + local events = CLSCORE.events - local frameBoxes = vgui.Create("DIconLayout", parent) - frameBoxes:Dock(FILL) + local frameBoxes = vgui.Create("DIconLayout", parent) + frameBoxes:Dock(FILL) - -- SWITCHER BETWEEN ROUND BEGIN AND ROUND END - local buttonBox = frameBoxes:Add("DPanelTTT2") - buttonBox:SetSize(sizes.widthMainArea, sizes.heightTopButtonPanel + 2 * sizes.padding) - buttonBox:DockPadding(0, sizes.padding, 0, sizes.padding) + -- SWITCHER BETWEEN ROUND BEGIN AND ROUND END + local buttonBox = frameBoxes:Add("DPanelTTT2") + buttonBox:SetSize(sizes.widthMainArea, sizes.heightTopButtonPanel + 2 * sizes.padding) + buttonBox:DockPadding(0, sizes.padding, 0, sizes.padding) - local buttonBoxRow = vgui.Create("DIconLayout", buttonBox) - buttonBoxRow:Dock(FILL) + local buttonBoxRow = vgui.Create("DIconLayout", buttonBox) + buttonBoxRow:Dock(FILL) - local buttonBoxRowLabel = buttonBoxRow:Add("DLabelTTT2") - buttonBoxRowLabel:SetSize(sizes.widthTopLabel, sizes.heightTopButtonPanel) - buttonBoxRowLabel:SetText("label_show_events") - buttonBoxRowLabel.Paint = function(slf, w, h) - derma.SkinHook("Paint", "LabelRightTTT2", slf, w, h) + local buttonBoxRowLabel = buttonBoxRow:Add("DLabelTTT2") + buttonBoxRowLabel:SetSize(sizes.widthTopLabel, sizes.heightTopButtonPanel) + buttonBoxRowLabel:SetText("label_show_events") + buttonBoxRowLabel.Paint = function(slf, w, h) + derma.SkinHook("Paint", "LabelRightTTT2", slf, w, h) - return true - end + return true + end - local buttonBoxRowPanel = buttonBoxRow:Add("DPanelTTT2") - buttonBoxRowPanel:SetSize(2 * sizes.widthTopButton + sizes.padding, sizes.heightTopButtonPanel) + local buttonBoxRowPanel = buttonBoxRow:Add("DPanelTTT2") + buttonBoxRowPanel:SetSize(2 * sizes.widthTopButton + sizes.padding, sizes.heightTopButtonPanel) - local buttonBoxRowButton1 = vgui.Create("DButtonTTT2", buttonBoxRowPanel) - buttonBoxRowButton1:SetSize(sizes.widthTopButton, sizes.heightTopButton) - buttonBoxRowButton1:DockMargin(sizes.padding, sizes.padding, 0, sizes.padding) - buttonBoxRowButton1:Dock(LEFT) - buttonBoxRowButton1:SetText("button_show_events_you") - buttonBoxRowButton1.Paint = function(slf, w, h) - derma.SkinHook("Paint", "ButtonRoundEndLeftTTT2", slf, w, h) + local buttonBoxRowButton1 = vgui.Create("DButtonTTT2", buttonBoxRowPanel) + buttonBoxRowButton1:SetSize(sizes.widthTopButton, sizes.heightTopButton) + buttonBoxRowButton1:DockMargin(sizes.padding, sizes.padding, 0, sizes.padding) + buttonBoxRowButton1:Dock(LEFT) + buttonBoxRowButton1:SetText("button_show_events_you") + buttonBoxRowButton1.Paint = function(slf, w, h) + derma.SkinHook("Paint", "ButtonRoundEndLeftTTT2", slf, w, h) - return true - end + return true + end - local buttonBoxRowButton2 = vgui.Create("DButtonTTT2", buttonBoxRowPanel) - buttonBoxRowButton2:SetSize(sizes.widthTopButton, sizes.heightTopButton) - buttonBoxRowButton2:DockMargin(0, sizes.padding, 0, sizes.padding) - buttonBoxRowButton2:Dock(RIGHT) - buttonBoxRowButton2:SetText("button_show_events_global") - buttonBoxRowButton2.Paint = function(slf, w, h) - derma.SkinHook("Paint", "ButtonRoundEndRightTTT2", slf, w, h) + local buttonBoxRowButton2 = vgui.Create("DButtonTTT2", buttonBoxRowPanel) + buttonBoxRowButton2:SetSize(sizes.widthTopButton, sizes.heightTopButton) + buttonBoxRowButton2:DockMargin(0, sizes.padding, 0, sizes.padding) + buttonBoxRowButton2:Dock(RIGHT) + buttonBoxRowButton2:SetText("button_show_events_global") + buttonBoxRowButton2.Paint = function(slf, w, h) + derma.SkinHook("Paint", "ButtonRoundEndRightTTT2", slf, w, h) - return true - end + return true + end - -- MAKE MAIN FRAME SCROLLABLE - local scrollPanel = frameBoxes:Add("DScrollPanelTTT2") - scrollPanel:SetSize(sizes.widthMainArea, sizes.heightContentLarge) + -- MAKE MAIN FRAME SCROLLABLE + local scrollPanel = frameBoxes:Add("DScrollPanelTTT2") + scrollPanel:SetSize(sizes.widthMainArea, sizes.heightContentLarge) - -- default button state - buttonBoxRowButton1.isActive = false - buttonBoxRowButton2.isActive = true - PopulateEventTable(scrollPanel, sizes, events, false) + -- default button state + buttonBoxRowButton1.isActive = false + buttonBoxRowButton2.isActive = true + PopulateEventTable(scrollPanel, sizes, events, false) - -- onclick functions for buttons - buttonBoxRowButton1.DoClick = function() - buttonBoxRowButton1.isActive = true - buttonBoxRowButton2.isActive = false + -- onclick functions for buttons + buttonBoxRowButton1.DoClick = function() + buttonBoxRowButton1.isActive = true + buttonBoxRowButton2.isActive = false - PopulateEventTable(scrollPanel, sizes, events, true) + PopulateEventTable(scrollPanel, sizes, events, true) - scrollPanel:InvalidateLayout() - end + scrollPanel:InvalidateLayout() + end - buttonBoxRowButton2.DoClick = function() - buttonBoxRowButton1.isActive = false - buttonBoxRowButton2.isActive = true + buttonBoxRowButton2.DoClick = function() + buttonBoxRowButton1.isActive = false + buttonBoxRowButton2.isActive = true - PopulateEventTable(scrollPanel, sizes, events, false) + PopulateEventTable(scrollPanel, sizes, events, false) - scrollPanel:InvalidateLayout() - end + scrollPanel:InvalidateLayout() + end end diff --git a/lua/terrortown/menus/score/info.lua b/lua/terrortown/menus/score/info.lua index 6712335e2..c35a01f77 100644 --- a/lua/terrortown/menus/score/info.lua +++ b/lua/terrortown/menus/score/info.lua @@ -3,271 +3,293 @@ local TryT = LANG.TryTranslation local ParT = LANG.GetParamTranslation -local function MakePlayerRoleTooltip(parent, width, ply) - local plyRoles = CLSCORE.eventsPlayerRoles[ply.sid64] - local height = 25 - - local boxLayout = vgui.Create("DIconLayout", parent) - boxLayout:Dock(FILL) - - local titleBox = boxLayout:Add("DColoredTextBoxTTT2") - titleBox:SetSize(width, 25) - titleBox:SetDynamicColor(parent, 0) - titleBox:SetTitle("tooltip_roles_time") - titleBox:SetTitleAlign(TEXT_ALIGN_LEFT) - - for i = 1, #plyRoles do - local plyRole = plyRoles[i] - local roleData = roles.GetByIndex(plyRole.role) - - local plyRoleBox = boxLayout:Add("DColoredTextBoxTTT2") - plyRoleBox:SetSize(width, 20) - plyRoleBox:SetDynamicColor(parent, 0) - plyRoleBox:SetTitleAlign(TEXT_ALIGN_LEFT) - plyRoleBox:SetIcon(roleData.iconMaterial) - - plyRoleBox.GetTitle = function() - return tostring(i) .. ". " .. TryT(roleData.name) .. " (" .. TryT(plyRole.team) .. ")" - end - - height = height + 20 - end - - return height +local function GetPanelTextSize(pnl) + surface.SetFont(pnl:GetTitleFont()) + return surface.GetTextSize(TryT(pnl:GetTitle())) end -local function MakePlayerScoreTooltip(parent, width, ply) - local plyScores = CLSCORE.eventsPlayerScores[ply.sid64] - local height = 25 - - local boxLayout = vgui.Create("DIconLayout", parent) - boxLayout:Dock(FILL) - - local titleBox = boxLayout:Add("DColoredTextBoxTTT2") - titleBox:SetSize(width, 25) - titleBox:SetDynamicColor(parent, 0) - titleBox:SetTitle("tooltip_score_gained") - titleBox:SetTitleAlign(TEXT_ALIGN_LEFT) - - -- In a first Pass filter all scoreevents and scores by name, positivy and number of Events - local filteredPlyScoreList = {} - for i = 1, #plyScores do - local plyScoreEvent = plyScores[i] - local rawScoreTexts = plyScoreEvent:GetRawScoreText(ply.sid64) - - for k = 1, #rawScoreTexts do - local rawScoreText = rawScoreTexts[k] - local scoreName = rawScoreText.name - local score = rawScoreText.score - - filteredPlyScoreList[scoreName] = filteredPlyScoreList[scoreName] or {score = {0,0}, numEvents = {0,0}} - - local posIndex = score < 0 and 1 or 2 -- first entry contains negative and second positive entries - local filteredPlyScore = filteredPlyScoreList[scoreName] - - filteredPlyScore.score[posIndex] = filteredPlyScore.score[posIndex] + score - filteredPlyScore.numEvents[posIndex] = filteredPlyScore.numEvents[posIndex] + 1 - end - end - - -- In a second pass we create the tooltip boxes with summarized scoreevents - for rawScoreTextName, scoreObj in pairs(filteredPlyScoreList) do - for i = 1, 2 do - local score = scoreObj.score[i] - local numberEvents = scoreObj.numEvents[i] - - if score == 0 then continue end - - local plyRoleBox = boxLayout:Add("DColoredTextBoxTTT2") - plyRoleBox:SetSize(width, 20) - plyRoleBox:SetDynamicColor(parent, 0) - plyRoleBox:SetTitleAlign(TEXT_ALIGN_LEFT) - - plyRoleBox.GetTitle = function() - return "- " .. (numberEvents > 1 and (numberEvents .. "x ") or "") .. ParT("tooltip_" .. rawScoreTextName, {score = score}) - end - - height = height + 20 - end - end - - return height -end - -local function MakePlayerKarmaTooltip(parent, width, ply) - local plyKarmaList = CLSCORE.eventsPlayerKarma[ply.sid64] - local height = 25 - - local boxLayout = vgui.Create("DIconLayout", parent) - boxLayout:Dock(FILL) - - local titleBox = boxLayout:Add("DColoredTextBoxTTT2") - titleBox:SetSize(width, 25) - titleBox:SetDynamicColor(parent, 0) - titleBox:SetTitle("tooltip_karma_gained") - titleBox:SetTitleAlign(TEXT_ALIGN_LEFT) - - if not istable(plyKarmaList) then - return height - end - - for karmaText, karma in pairs(plyKarmaList) do - local plyRoleBox = boxLayout:Add("DColoredTextBoxTTT2") - plyRoleBox:SetSize(width, 20) - plyRoleBox:SetDynamicColor(parent, 0) - plyRoleBox:SetTitleAlign(TEXT_ALIGN_LEFT) - - plyRoleBox.GetTitle = function() - return "- " .. TryT(karmaText) .. ": " .. karma - end - - height = height + 20 - end - - return height +local function MakePlayerGenericTooltip(parent, ply, items, title) + local initTitleHeight = 25 + local lineHeight = 20 + local iconOffset = ( + (lineHeight - 2 * math.Round(0.1 * lineHeight)) + 4 * math.Round(0.1 * lineHeight) + ) + + local boxLayout = vgui.Create("DIconLayout", parent) + boxLayout:Dock(FILL) + + local titleBox = boxLayout:Add("DColoredTextBoxTTT2") + titleBox:SetDynamicColor(parent, 0) + titleBox:SetTitle(title) + titleBox:SetTitleAlign(TEXT_ALIGN_LEFT) + + local lengthLongestLine, titleHeight = GetPanelTextSize(titleBox) + local height = math.max(initTitleHeight, titleHeight) + titleBox:SetHeight(height) + + for i = 1, #items do + local thing = items[i] + local plyRow = boxLayout:Add("DColoredTextBoxTTT2") + plyRow:SetDynamicColor(parent, 0) + plyRow:SetTitleAlign(TEXT_ALIGN_LEFT) + if thing.title then + plyRow:SetTitle(thing.title) + end + + local rowWidth, rowHeight = GetPanelTextSize(plyRow) + + if thing.iconMaterial then + plyRow:SetIcon(thing.iconMaterial) + rowWidth = rowWidth + iconOffset + end + + if rowWidth > lengthLongestLine then + lengthLongestLine = rowWidth + end + + rowHeight = math.max(lineHeight, rowHeight) + plyRow:SetHeight(rowHeight) + height = height + rowHeight + end + + -- add some padding to the right margin + lengthLongestLine = lengthLongestLine + iconOffset + + -- we found the longest line, now make all entries that long + local children = boxLayout:GetChildren() + for i = 1, #children do + children[i]:SetWidth(lengthLongestLine) + end + + return lengthLongestLine, height end local function PopulatePlayerView(parent, sizes, columnData, columnTeams, showDeath) - parent:Clear() - - -- SPLIT FRAME INTO A GRID LAYOUT - local playerCoumns = vgui.Create("DIconLayout", parent) - playerCoumns:Dock(FILL) - playerCoumns:SetSpaceX(sizes.padding) - playerCoumns:SetSpaceY(sizes.padding) - - local _, heightScroll = parent:GetSize() - local maxHeightColumn = heightScroll - - -- DETERTMINE THE SIZE FIRST TO TAKE THE SCROLLBAR SIZE INTO ACCOUNT - for i = 1, 3 do - local teamPlayersList = columnData[i] - local heightColumn = 0 - - for k = 1, #teamPlayersList do - local plys = teamPlayersList[k] - local amountPly = #plys - - heightColumn = heightColumn + sizes.heightTitleRow + amountPly * sizes.heightRow + (amountPly + 1) * sizes.paddingSmall - - -- if there is a second team, add padding to size - if k > 1 then - heightColumn = heightColumn + sizes.padding - end - end - - maxHeightColumn = math.max(maxHeightColumn, heightColumn) - end - - local widthColumn = (sizes.widthMainArea - 2 * sizes.padding - (heightScroll < maxHeightColumn and 15 or 0)) / 3 - local widthName = widthColumn - sizes.widthKarma - sizes.widthScore - 4 * sizes.padding - - local teamInfoBox = {} - - local localPly64 = LocalPlayer():SteamID64() - - -- FILL THE COLUMNS - for numColumn = 1, 3 do - teamInfoBox[numColumn] = playerCoumns:Add("DColoredBoxTTT2") - teamInfoBox[numColumn]:SetSize(widthColumn, maxHeightColumn) - teamInfoBox[numColumn]:SetDynamicColor(parent, 30) - - local teamPlayersList = columnData[numColumn] - local teamNamesList = columnTeams[numColumn] - - local columnBox = teamInfoBox[numColumn]:Add("DIconLayout") - columnBox:SetSpaceY(sizes.padding) - columnBox:Dock(FILL) - - for numTeam = 1, #teamPlayersList do - local plys = teamPlayersList[numTeam] - local amountPly = #plys - local teamData = TEAMS[teamNamesList[numTeam]] - local colorTeam = teamData.color - local colorTeamLight = util.ColorLighten(colorTeam, 35) - local colorTeamDark = util.ColorDarken(colorTeam, 20) - - local teamBox = columnBox:Add("DColoredTextBoxTTT2") - teamBox:SetDynamicColor(parent, 15) - teamBox:SetSize(widthColumn, sizes.heightTitleRow + amountPly * sizes.heightRow + (amountPly + 1) * sizes.paddingSmall) - - local teamPlayerBox = vgui.Create("DIconLayout", teamBox) - teamPlayerBox:SetSpaceY(sizes.paddingSmall) - teamPlayerBox:Dock(FILL) - - local teamNameBox = teamPlayerBox:Add("DColoredTextBoxTTT2") - teamNameBox:SetSize(widthColumn, sizes.heightTitleRow) - teamNameBox:SetColor(colorTeam) - teamNameBox:SetTitle(teamNamesList[numTeam]) - teamNameBox:SetTitleFont("DermaTTT2TextLarger") - teamNameBox:SetIcon(teamData.iconMaterial) - - for numPly = 1, amountPly do - local ply = plys[numPly] - - local plyRowPanel = teamPlayerBox:Add("DPanelTTT2") - plyRowPanel:SetSize(widthColumn, sizes.heightRow) - - local plyRow = vgui.Create("DIconLayout", plyRowPanel) - plyRow:SetSpaceX(sizes.padding) - plyRow:DockMargin(sizes.padding, 0, 0, 0) - plyRow:Dock(FILL) - - local plyNameBox = plyRow:Add("DColoredTextBoxTTT2") - plyNameBox:SetSize(widthName, sizes.heightRow) - plyNameBox:SetDynamicColor(parent, 15) - plyNameBox:SetTitle(ply.nick .. ((showDeath and not ply.alive) and " (†)" or "")) - plyNameBox:SetTitleAlign(TEXT_ALIGN_LEFT) - plyNameBox:SetIcon(roles.GetByIndex(ply.role).iconMaterial) - - -- highlight local player - if ply.sid64 == localPly64 then - plyNameBox:EnableFlashColor(true) - end - - local plyRolesTooltipPanel = vgui.Create("DPanelTTT2") - - local heightRolesTooltip = MakePlayerRoleTooltip(plyRolesTooltipPanel, widthName, ply) - - plyNameBox:SetTooltipPanel(plyRolesTooltipPanel) - plyNameBox:SetTooltipFixedPosition(0, sizes.heightRow + 1) - plyNameBox:SetTooltipFixedSize(widthName, heightRolesTooltip) - plyRolesTooltipPanel:SetSize(widthName, heightRolesTooltip) - - local plyKarmaBox = plyRow:Add("DColoredTextBoxTTT2") - plyKarmaBox:SetSize(sizes.widthKarma, sizes.heightRow) - plyKarmaBox:SetColor(colorTeamLight) - plyKarmaBox:SetTitle(CLSCORE.eventsInfoKarma[ply.sid64] or 0) - plyKarmaBox:SetTitleFont("DermaTTT2CatHeader") - plyKarmaBox:SetTooltip("tooltip_karma_gained") - - local plyKarmaTooltipPanel = vgui.Create("DPanelTTT2") - - local heightKarmaTooltip = MakePlayerKarmaTooltip(plyKarmaTooltipPanel, 200, ply) - - plyKarmaBox:SetTooltipPanel(plyKarmaTooltipPanel) - plyKarmaBox:SetTooltipFixedPosition(0, sizes.heightRow + 1) - plyKarmaBox:SetTooltipFixedSize(200, heightKarmaTooltip) - plyKarmaTooltipPanel:SetSize(200, heightKarmaTooltip) - - local plyPointsBox = plyRow:Add("DColoredTextBoxTTT2") - plyPointsBox:SetSize(sizes.widthScore, sizes.heightRow) - plyPointsBox:SetColor(colorTeamDark) - plyPointsBox:SetTitle(CLSCORE.eventsInfoScores[ply.sid64] or 0) - plyPointsBox:SetTitleFont("DermaTTT2CatHeader") - plyPointsBox:SetTooltip("tooltip_score_gained") - - local plyScoreTooltipPanel = vgui.Create("DPanelTTT2") - - local heightScoreTooltip = MakePlayerScoreTooltip(plyScoreTooltipPanel, 200, ply) - - plyPointsBox:SetTooltipPanel(plyScoreTooltipPanel) - plyPointsBox:SetTooltipFixedPosition(0, sizes.heightRow + 1) - plyPointsBox:SetTooltipFixedSize(200, heightScoreTooltip) - plyScoreTooltipPanel:SetSize(200, heightScoreTooltip) - end - end - end + parent:Clear() + + -- SPLIT FRAME INTO A GRID LAYOUT + local playerCoumns = vgui.Create("DIconLayout", parent) + playerCoumns:Dock(FILL) + playerCoumns:SetSpaceX(sizes.padding) + playerCoumns:SetSpaceY(sizes.padding) + + local _, heightScroll = parent:GetSize() + local maxHeightColumn = heightScroll + + -- DETERTMINE THE SIZE FIRST TO TAKE THE SCROLLBAR SIZE INTO ACCOUNT + for i = 1, 3 do + local teamPlayersList = columnData[i] + local heightColumn = 0 + + for k = 1, #teamPlayersList do + local plys = teamPlayersList[k] + local amountPly = #plys + + heightColumn = heightColumn + + sizes.heightTitleRow + + amountPly * sizes.heightRow + + (amountPly + 1) * sizes.paddingSmall + + -- if there is a second team, add padding to size + if k > 1 then + heightColumn = heightColumn + sizes.padding + end + end + + maxHeightColumn = math.max(maxHeightColumn, heightColumn) + end + + local widthColumn = ( + sizes.widthMainArea + - 2 * sizes.padding + - (heightScroll < maxHeightColumn and 15 or 0) + ) / 3 + local widthName = widthColumn - sizes.widthKarma - sizes.widthScore - 4 * sizes.padding + + local teamInfoBox = {} + + local localPly64 = LocalPlayer():SteamID64() + + -- FILL THE COLUMNS + for numColumn = 1, 3 do + teamInfoBox[numColumn] = playerCoumns:Add("DColoredBoxTTT2") + teamInfoBox[numColumn]:SetSize(widthColumn, maxHeightColumn) + teamInfoBox[numColumn]:SetDynamicColor(parent, 30) + + local teamPlayersList = columnData[numColumn] + local teamNamesList = columnTeams[numColumn] + + local columnBox = teamInfoBox[numColumn]:Add("DIconLayout") + columnBox:SetSpaceY(sizes.padding) + columnBox:Dock(FILL) + + for numTeam = 1, #teamPlayersList do + local plys = teamPlayersList[numTeam] + local amountPly = #plys + local teamData = TEAMS[teamNamesList[numTeam]] + local colorTeam = teamData.color + local colorTeamLight = util.ColorLighten(colorTeam, 35) + local colorTeamDark = util.ColorDarken(colorTeam, 20) + + local teamBox = columnBox:Add("DColoredTextBoxTTT2") + teamBox:SetDynamicColor(parent, 15) + teamBox:SetSize( + widthColumn, + sizes.heightTitleRow + + amountPly * sizes.heightRow + + (amountPly + 1) * sizes.paddingSmall + ) + + local teamPlayerBox = vgui.Create("DIconLayout", teamBox) + teamPlayerBox:SetSpaceY(sizes.paddingSmall) + teamPlayerBox:Dock(FILL) + + local teamNameBox = teamPlayerBox:Add("DColoredTextBoxTTT2") + teamNameBox:SetSize(widthColumn, sizes.heightTitleRow) + teamNameBox:SetColor(colorTeam) + teamNameBox:SetTitle(teamNamesList[numTeam]) + teamNameBox:SetTitleFont("DermaTTT2TextLarger") + teamNameBox:SetIcon(teamData.iconMaterial) + + for numPly = 1, amountPly do + local ply = plys[numPly] + + local plyRowPanel = teamPlayerBox:Add("DPanelTTT2") + plyRowPanel:SetSize(widthColumn, sizes.heightRow) + + local plyRow = vgui.Create("DIconLayout", plyRowPanel) + plyRow:SetSpaceX(sizes.padding) + plyRow:DockMargin(sizes.padding, 0, 0, 0) + plyRow:Dock(FILL) + + local plyNameBox = plyRow:Add("DColoredTextBoxTTT2") + plyNameBox:SetSize(widthName, sizes.heightRow) + plyNameBox:SetDynamicColor(parent, 15) + plyNameBox:SetTitle(ply.nick .. ((showDeath and not ply.alive) and " (†)" or "")) + plyNameBox:SetTitleAlign(TEXT_ALIGN_LEFT) + plyNameBox:SetIcon(roles.GetByIndex(ply.role).iconMaterial) + + -- highlight local player + if ply.sid64 == localPly64 then + plyNameBox:EnableFlashColor(true) + end + + local plyRolesTooltipPanel = vgui.Create("DPanelTTT2") + + local roleBucket = {} + local plyRoles = CLSCORE.eventsPlayerRoles[ply.sid64] or {} + for i = 1, #plyRoles do + local plyRole = plyRoles[i] + local roleData = roles.GetByIndex(plyRole.role) + + roleBucket[#roleBucket + 1] = { + title = tostring(i) .. ". " .. TryT(roleData.name) .. " (" .. TryT( + plyRole.team + ) .. ")", + iconMaterial = roleData.iconMaterial, + } + end + local widthRolesTooltip, heightRolesTooltip = MakePlayerGenericTooltip( + plyRolesTooltipPanel, + ply, + roleBucket, + "tooltip_roles_time" + ) + + plyNameBox:SetTooltipPanel(plyRolesTooltipPanel) + plyNameBox:SetTooltipFixedPosition(0, sizes.heightRow + 1) + plyNameBox:SetTooltipFixedSize(widthRolesTooltip, heightRolesTooltip) + plyRolesTooltipPanel:SetSize(widthRolesTooltip, heightRolesTooltip) + + local plyKarmaBox = plyRow:Add("DColoredTextBoxTTT2") + plyKarmaBox:SetSize(sizes.widthKarma, sizes.heightRow) + plyKarmaBox:SetColor(colorTeamLight) + plyKarmaBox:SetTitle(CLSCORE.eventsInfoKarma[ply.sid64] or 0) + plyKarmaBox:SetTitleFont("DermaTTT2CatHeader") + plyKarmaBox:SetTooltip("tooltip_karma_gained") + + local plyKarmaTooltipPanel = vgui.Create("DPanelTTT2") + + local karmaBucket = {} + local plyKarmaList = CLSCORE.eventsPlayerKarma[ply.sid64] or {} + for karmaText, karma in pairs(plyKarmaList) do + karmaBucket[#karmaBucket + 1] = { + title = "- " .. TryT(karmaText) .. ": " .. karma, + } + end + local widthKarmaTooltip, heightKarmaTooltip = MakePlayerGenericTooltip( + plyKarmaTooltipPanel, + ply, + karmaBucket, + "tooltip_karma_gained" + ) + + plyKarmaBox:SetTooltipPanel(plyKarmaTooltipPanel) + plyKarmaBox:SetTooltipFixedPosition(0, sizes.heightRow + 1) + plyKarmaBox:SetTooltipFixedSize(widthKarmaTooltip, heightKarmaTooltip) + plyKarmaTooltipPanel:SetSize(widthKarmaTooltip, heightKarmaTooltip) + + local plyPointsBox = plyRow:Add("DColoredTextBoxTTT2") + plyPointsBox:SetSize(sizes.widthScore, sizes.heightRow) + plyPointsBox:SetColor(colorTeamDark) + plyPointsBox:SetTitle(CLSCORE.eventsInfoScores[ply.sid64] or 0) + plyPointsBox:SetTitleFont("DermaTTT2CatHeader") + plyPointsBox:SetTooltip("tooltip_score_gained") + + local plyScoreTooltipPanel = vgui.Create("DPanelTTT2") + + local scoreBucket = {} + local plyScores = CLSCORE.eventsPlayerScores[ply.sid64] or {} + + -- In a first Pass filter all scoreevents and scores by name, positivy and number of Events + local filteredPlyScoreList = {} + for i = 1, #plyScores do + local plyScoreEvent = plyScores[i] + local rawScoreTexts = plyScoreEvent:GetRawScoreText(ply.sid64) + + for k = 1, #rawScoreTexts do + local rawScoreText = rawScoreTexts[k] + local scoreName = rawScoreText.name + local score = rawScoreText.score + + filteredPlyScoreList[scoreName] = filteredPlyScoreList[scoreName] + or { score = 0, numEvents = 0 } + + local filteredPlyScore = filteredPlyScoreList[scoreName] + + filteredPlyScore.score = filteredPlyScore.score + score + filteredPlyScore.numEvents = filteredPlyScore.numEvents + 1 + end + end + + for rawScoreTextName, scoreObj in pairs(filteredPlyScoreList) do + local score = scoreObj.score + local numberEvents = scoreObj.numEvents + + if score == 0 then + continue + end + + scoreBucket[#scoreBucket + 1] = { + title = "- " + .. (numberEvents > 1 and (numberEvents .. "x ") or "") + .. ParT("tooltip_" .. rawScoreTextName, { score = score }), + } + end + local widthScoreTooltip, heightScoreTooltip = MakePlayerGenericTooltip( + plyScoreTooltipPanel, + ply, + scoreBucket, + "tooltip_score_gained" + ) + + plyPointsBox:SetTooltipPanel(plyScoreTooltipPanel) + plyPointsBox:SetTooltipFixedPosition(0, sizes.heightRow + 1) + plyPointsBox:SetTooltipFixedSize(widthScoreTooltip, heightScoreTooltip) + plyScoreTooltipPanel:SetSize(widthScoreTooltip, heightScoreTooltip) + end + end + end end CLSCOREMENU.base = "base_scoremenu" @@ -277,81 +299,99 @@ CLSCOREMENU.title = "title_score_info" CLSCOREMENU.priority = 100 function CLSCOREMENU:Populate(parent) - local sizes = CLSCORE.sizes - - local frameBoxes = vgui.Create("DIconLayout", parent) - frameBoxes:Dock(FILL) - - -- CREATE WIN TITLE BOX - local winBox = frameBoxes:Add("DColoredTextBoxTTT2") - winBox:SetColor(CLSCORE.eventsInfoTitleColor) - winBox:SetSize(sizes.widthMainArea, sizes.heightHeaderPanel) - winBox:SetTitle(CLSCORE.eventsInfoTitleText) - winBox:SetTitleFont("DermaTTT2TextHuge") - - -- SWITCHER BETWEEN ROUND BEGIN AND ROUND END - local buttonBox = frameBoxes:Add("DPanelTTT2") - buttonBox:SetSize(sizes.widthMainArea, sizes.heightTopButtonPanel + 2 * sizes.padding) - buttonBox:DockPadding(0, sizes.padding, 0, sizes.padding) - - local buttonBoxRow = vgui.Create("DIconLayout", buttonBox) - buttonBoxRow:Dock(FILL) - - local buttonBoxRowLabel = buttonBoxRow:Add("DLabelTTT2") - buttonBoxRowLabel:SetSize(sizes.widthTopLabel, sizes.heightTopButtonPanel) - buttonBoxRowLabel:SetText("label_show_roles") - buttonBoxRowLabel.Paint = function(slf, w, h) - derma.SkinHook("Paint", "LabelRightTTT2", slf, w, h) - - return true - end - - local buttonBoxRowPanel = buttonBoxRow:Add("DPanelTTT2") - buttonBoxRowPanel:SetSize(2 * sizes.widthTopButton + sizes.padding, sizes.heightTopButtonPanel) - - local buttonBoxRowButton1 = vgui.Create("DButtonTTT2", buttonBoxRowPanel) - buttonBoxRowButton1:SetSize(sizes.widthTopButton, sizes.heightTopButton) - buttonBoxRowButton1:DockMargin(sizes.padding, sizes.padding, 0, sizes.padding) - buttonBoxRowButton1:Dock(LEFT) - buttonBoxRowButton1:SetText("button_show_roles_begin") - buttonBoxRowButton1.Paint = function(slf, w, h) - derma.SkinHook("Paint", "ButtonRoundEndLeftTTT2", slf, w, h) - - return true - end - - local buttonBoxRowButton2 = vgui.Create("DButtonTTT2", buttonBoxRowPanel) - buttonBoxRowButton2:SetSize(sizes.widthTopButton, sizes.heightTopButton) - buttonBoxRowButton2:DockMargin(0, sizes.padding, 0, sizes.padding) - buttonBoxRowButton2:Dock(RIGHT) - buttonBoxRowButton2:SetText("button_show_roles_end") - buttonBoxRowButton2.Paint = function(slf, w, h) - derma.SkinHook("Paint", "ButtonRoundEndRightTTT2", slf, w, h) - - return true - end - - -- MAKE MAIN FRAME SCROLLABLE - local scrollPanel = frameBoxes:Add("DScrollPanelTTT2") - scrollPanel:SetSize(sizes.widthMainArea, sizes.heightContent) - - -- default button state - buttonBoxRowButton1.isActive = false - buttonBoxRowButton2.isActive = true - PopulatePlayerView(scrollPanel, sizes, CLSCORE.eventsInfoColumnDataEnd, CLSCORE.eventsInfoColumnTeamsEnd, true) - - -- onclick functions for buttons - buttonBoxRowButton1.DoClick = function() - buttonBoxRowButton1.isActive = true - buttonBoxRowButton2.isActive = false - - PopulatePlayerView(scrollPanel, sizes, CLSCORE.eventsInfoColumnDataStart, CLSCORE.eventsInfoColumnTeamsStart, false) - end - - buttonBoxRowButton2.DoClick = function() - buttonBoxRowButton1.isActive = false - buttonBoxRowButton2.isActive = true - - PopulatePlayerView(scrollPanel, sizes, CLSCORE.eventsInfoColumnDataEnd, CLSCORE.eventsInfoColumnTeamsEnd, true) - end + local sizes = CLSCORE.sizes + + local frameBoxes = vgui.Create("DIconLayout", parent) + frameBoxes:Dock(FILL) + + -- CREATE WIN TITLE BOX + local winBox = frameBoxes:Add("DColoredTextBoxTTT2") + winBox:SetColor(CLSCORE.eventsInfoTitleColor) + winBox:SetSize(sizes.widthMainArea, sizes.heightHeaderPanel) + winBox:SetTitle(CLSCORE.eventsInfoTitleText) + winBox:SetTitleFont("DermaTTT2TextHuge") + + -- SWITCHER BETWEEN ROUND BEGIN AND ROUND END + local buttonBox = frameBoxes:Add("DPanelTTT2") + buttonBox:SetSize(sizes.widthMainArea, sizes.heightTopButtonPanel + 2 * sizes.padding) + buttonBox:DockPadding(0, sizes.padding, 0, sizes.padding) + + local buttonBoxRow = vgui.Create("DIconLayout", buttonBox) + buttonBoxRow:Dock(FILL) + + local buttonBoxRowLabel = buttonBoxRow:Add("DLabelTTT2") + buttonBoxRowLabel:SetSize(sizes.widthTopLabel, sizes.heightTopButtonPanel) + buttonBoxRowLabel:SetText("label_show_roles") + buttonBoxRowLabel.Paint = function(slf, w, h) + derma.SkinHook("Paint", "LabelRightTTT2", slf, w, h) + + return true + end + + local buttonBoxRowPanel = buttonBoxRow:Add("DPanelTTT2") + buttonBoxRowPanel:SetSize(2 * sizes.widthTopButton + sizes.padding, sizes.heightTopButtonPanel) + + local buttonBoxRowButton1 = vgui.Create("DButtonTTT2", buttonBoxRowPanel) + buttonBoxRowButton1:SetSize(sizes.widthTopButton, sizes.heightTopButton) + buttonBoxRowButton1:DockMargin(sizes.padding, sizes.padding, 0, sizes.padding) + buttonBoxRowButton1:Dock(LEFT) + buttonBoxRowButton1:SetText("button_show_roles_begin") + buttonBoxRowButton1.Paint = function(slf, w, h) + derma.SkinHook("Paint", "ButtonRoundEndLeftTTT2", slf, w, h) + + return true + end + + local buttonBoxRowButton2 = vgui.Create("DButtonTTT2", buttonBoxRowPanel) + buttonBoxRowButton2:SetSize(sizes.widthTopButton, sizes.heightTopButton) + buttonBoxRowButton2:DockMargin(0, sizes.padding, 0, sizes.padding) + buttonBoxRowButton2:Dock(RIGHT) + buttonBoxRowButton2:SetText("button_show_roles_end") + buttonBoxRowButton2.Paint = function(slf, w, h) + derma.SkinHook("Paint", "ButtonRoundEndRightTTT2", slf, w, h) + + return true + end + + -- MAKE MAIN FRAME SCROLLABLE + local scrollPanel = frameBoxes:Add("DScrollPanelTTT2") + scrollPanel:SetSize(sizes.widthMainArea, sizes.heightContent) + + -- default button state + buttonBoxRowButton1.isActive = false + buttonBoxRowButton2.isActive = true + PopulatePlayerView( + scrollPanel, + sizes, + CLSCORE.eventsInfoColumnDataEnd, + CLSCORE.eventsInfoColumnTeamsEnd, + true + ) + + -- onclick functions for buttons + buttonBoxRowButton1.DoClick = function() + buttonBoxRowButton1.isActive = true + buttonBoxRowButton2.isActive = false + + PopulatePlayerView( + scrollPanel, + sizes, + CLSCORE.eventsInfoColumnDataStart, + CLSCORE.eventsInfoColumnTeamsStart, + false + ) + end + + buttonBoxRowButton2.DoClick = function() + buttonBoxRowButton1.isActive = false + buttonBoxRowButton2.isActive = true + + PopulatePlayerView( + scrollPanel, + sizes, + CLSCORE.eventsInfoColumnDataEnd, + CLSCORE.eventsInfoColumnTeamsEnd, + true + ) + end end diff --git a/lua/terrortown/vskin/dark.lua b/lua/terrortown/vskin/dark.lua index 03258bf65..309c4186f 100644 --- a/lua/terrortown/vskin/dark.lua +++ b/lua/terrortown/vskin/dark.lua @@ -1,18 +1,18 @@ vskin.RegisterVSkin("dark_ttt2", { - colors = { - background = Color(40, 45, 52, 255), - accent = Color(34, 93, 200, 255), - accent_dark = Color(23, 67, 139, 255), - scroll = Color(20, 30, 40, 255), - shadow = Color(0, 0, 0, 100), - screen = Color(10, 15, 20, 200), - title_text = Color(255, 255, 255, 255) - }, - params = { - shadow_size = 4, - header_height = 45, - collapsable_height = 30, - border_size = 3, - corner_radius = 6 - } + colors = { + background = Color(40, 45, 52, 255), + accent = Color(34, 93, 200, 255), + accent_dark = Color(23, 67, 139, 255), + scroll = Color(20, 30, 40, 255), + shadow = Color(0, 0, 0, 100), + screen = Color(10, 15, 20, 200), + title_text = Color(255, 255, 255, 255), + }, + params = { + shadow_size = 4, + header_height = 45, + collapsable_height = 30, + border_size = 3, + corner_radius = 6, + }, }) diff --git a/lua/terrortown/vskin/light.lua b/lua/terrortown/vskin/light.lua index 4d572c035..7eee6965a 100644 --- a/lua/terrortown/vskin/light.lua +++ b/lua/terrortown/vskin/light.lua @@ -1,18 +1,18 @@ vskin.RegisterVSkin("light_ttt2", { - colors = { - background = Color(255, 255, 255, 255), - accent = Color(47, 137, 221, 255), - accent_dark = Color(18, 87, 151, 255), - scroll = Color(150, 150, 150, 255), - shadow = Color(0, 0, 0, 100), - screen = Color(220, 220, 220, 75), - title_text = Color(255, 255, 255, 255) - }, - params = { - shadow_size = 4, - header_height = 45, - collapsable_height = 30, - border_size = 3, - corner_radius = 6 - } + colors = { + background = Color(255, 255, 255, 255), + accent = Color(47, 137, 221, 255), + accent_dark = Color(18, 87, 151, 255), + scroll = Color(150, 150, 150, 255), + shadow = Color(0, 0, 0, 100), + screen = Color(220, 220, 220, 75), + title_text = Color(255, 255, 255, 255), + }, + params = { + shadow_size = 4, + header_height = 45, + collapsable_height = 30, + border_size = 3, + corner_radius = 6, + }, }) diff --git a/lua/ttt2/extensions/cvars.lua b/lua/ttt2/extensions/cvars.lua index 77266f171..56c57fd28 100644 --- a/lua/ttt2/extensions/cvars.lua +++ b/lua/ttt2/extensions/cvars.lua @@ -4,7 +4,7 @@ -- @module cvars if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local cvars = cvars @@ -24,240 +24,265 @@ local playersCache = {} local broadcastTable = {} if CLIENT then - --- - -- Checks if the conVar exists on the server or was already cached - -- @param string conVarName - -- @param function OnReceiveFunc(conVarExists) The function that gets called with the result if the conVar exists - -- @realm client - function cvars.ConVarExistsOnServer(conVarName, OnReceiveFunc) - if serverConVars[conVarName] then - OnReceiveFunc(true) - - return - end - - messageIdentifier = messageIdentifier % maxUInt + 1 - functionCache[messageIdentifier] = OnReceiveFunc - - net.Start("TTT2ConVarExistsOnServer") - net.WriteUInt(messageIdentifier, identityBitCount) - net.WriteString(conVarName) - net.SendToServer() - end - - net.Receive("TTT2ConVarExistsOnServer", function(len) - if len < 1 then return end - - local identifier = net.ReadUInt(identityBitCount) - local conVarExists = net.ReadBool() - local receiveFunc = functionCache[identifier] - - if isfunction(receiveFunc) then - receiveFunc(conVarExists) - functionCache[identifier] = nil - end - end) - - --- - -- Changes the conVar on the server if it exists and the user has admin rights - -- @param string conVarName - -- @param any value - -- @note ConVar values are saved as strings and are therefore converted - -- @realm client - function cvars.ChangeServerConVar(conVarName, value) - net.Start("TTT2ChangeServerConVar") - net.WriteString(conVarName) - net.WriteString(tostring(value)) - net.SendToServer() - end - - net.Receive("TTT2ChangeServerConVar", function(len) - if len < 1 then return end - - local conVarName = net.ReadString() - local conVar = serverConVars[conVarName] or {value = ""} - - local oldValue = conVar.value - local newValue = net.ReadString() - - conVar.value = newValue - serverConVars[conVarName] = conVar - - cvars.OnConVarChanged(conVarName, oldValue, newValue) - end) - - local function SendAllServerConVarRequests() - sendRequestsNextUpdate = false - - if requestCacheSize < 1 then return end - - net.Start("TTT2ServerConVarGetValue") - net.WriteUInt(requestCacheSize, identityBitCount) - - for i = 1, requestCacheSize do - local request = requestCache[i] - - net.WriteUInt(request.identifier, identityBitCount) - net.WriteString(request.conVarName) - end - - net.SendToServer() - - requestCacheSize = 0 - requestCache = {} - end - - --- - -- Get the conVar's current and default value of if it exists on the server or was already cached - -- @param string conVarName - -- @param function OnReceiveFunc(conVarExists, value, default) The function that gets called with the results if the conVar exists - -- @realm client - function cvars.ServerConVarGetValue(conVarName, OnReceiveFunc) - local conVar = serverConVars[conVarName] or {} - - if conVar.value and conVar.default then - OnReceiveFunc(true, conVar.value, conVar.default) - - return - end - - messageIdentifier = messageIdentifier % maxUInt + 1 - functionCache[messageIdentifier] = OnReceiveFunc - - requestCacheSize = requestCacheSize + 1 - requestCache[requestCacheSize] = {identifier = messageIdentifier, conVarName = conVarName} - - if not sendRequestsNextUpdate then - sendRequestsNextUpdate = true - - timer.Simple(0, SendAllServerConVarRequests) - end - end - - net.Receive("TTT2ServerConVarGetValue", function(len) - if len < 1 then return end - - local requestSize = net.ReadUInt(identityBitCount) - - for i = 1, requestSize do - local identifier = net.ReadUInt(identityBitCount) - local wasSuccess = net.ReadBool() - local value = nil - local default = nil - - if wasSuccess then - local conVarName = net.ReadString() - value = net.ReadString() - default = net.ReadString() - serverConVars[conVarName] = { - value = value, - default = default - } - end - - local receiveFunc = functionCache[identifier] - - if isfunction(receiveFunc) then - receiveFunc(wasSuccess, value, default) - functionCache[identifier] = nil - end - end - - end) + --- + -- Checks if the conVar exists on the server or was already cached + -- @param string conVarName + -- @param function OnReceiveFunc The function that gets called with a boolean whether the conVar exists + -- @realm client + function cvars.ConVarExistsOnServer(conVarName, OnReceiveFunc) + if serverConVars[conVarName] then + OnReceiveFunc(true) + + return + end + + messageIdentifier = messageIdentifier % maxUInt + 1 + functionCache[messageIdentifier] = OnReceiveFunc + + net.Start("TTT2ConVarExistsOnServer") + net.WriteUInt(messageIdentifier, identityBitCount) + net.WriteString(conVarName) + net.SendToServer() + end + + net.Receive("TTT2ConVarExistsOnServer", function(len) + if len < 1 then + return + end + + local identifier = net.ReadUInt(identityBitCount) + local conVarExists = net.ReadBool() + local receiveFunc = functionCache[identifier] + + if isfunction(receiveFunc) then + receiveFunc(conVarExists) + functionCache[identifier] = nil + end + end) + + --- + -- Changes the conVar on the server if it exists and the user has admin rights + -- @param string conVarName + -- @param any value + -- @note ConVar values are saved as strings and are therefore converted + -- @realm client + function cvars.ChangeServerConVar(conVarName, value) + net.Start("TTT2ChangeServerConVar") + net.WriteString(conVarName) + net.WriteString(tostring(value)) + net.SendToServer() + end + + net.Receive("TTT2ChangeServerConVar", function(len) + if len < 1 then + return + end + + local conVarName = net.ReadString() + local conVar = serverConVars[conVarName] or { value = "" } + + local oldValue = conVar.value + local newValue = net.ReadString() + + conVar.value = newValue + serverConVars[conVarName] = conVar + + cvars.OnConVarChanged(conVarName, oldValue, newValue) + end) + + local function SendAllServerConVarRequests() + sendRequestsNextUpdate = false + + if requestCacheSize < 1 then + return + end + + net.Start("TTT2ServerConVarGetValue") + net.WriteUInt(requestCacheSize, identityBitCount) + + for i = 1, requestCacheSize do + local request = requestCache[i] + + net.WriteUInt(request.identifier, identityBitCount) + net.WriteString(request.conVarName) + end + + net.SendToServer() + + requestCacheSize = 0 + requestCache = {} + end + + --- + -- Get the conVar's current and default value of if it exists on the server or was already cached + -- @param string conVarName + -- @param function OnReceiveFunc The function that gets called with the following parameters: boolean, whether the convar exists; any, the value of the convar; any, the default of the convar + -- @realm client + function cvars.ServerConVarGetValue(conVarName, OnReceiveFunc) + local conVar = serverConVars[conVarName] or {} + + if conVar.value and conVar.default then + OnReceiveFunc(true, conVar.value, conVar.default) + + return + end + + messageIdentifier = messageIdentifier % maxUInt + 1 + functionCache[messageIdentifier] = OnReceiveFunc + + requestCacheSize = requestCacheSize + 1 + requestCache[requestCacheSize] = { identifier = messageIdentifier, conVarName = conVarName } + + if not sendRequestsNextUpdate then + sendRequestsNextUpdate = true + + timer.Simple(0, SendAllServerConVarRequests) + end + end + + net.Receive("TTT2ServerConVarGetValue", function(len) + if len < 1 then + return + end + + local requestSize = net.ReadUInt(identityBitCount) + + for i = 1, requestSize do + local identifier = net.ReadUInt(identityBitCount) + local wasSuccess = net.ReadBool() + local value = nil + local default = nil + + if wasSuccess then + local conVarName = net.ReadString() + value = net.ReadString() + default = net.ReadString() + serverConVars[conVarName] = { + value = value, + default = default, + } + end + + local receiveFunc = functionCache[identifier] + + if isfunction(receiveFunc) then + receiveFunc(wasSuccess, value, default) + functionCache[identifier] = nil + end + end + end) elseif SERVER then - util.AddNetworkString("TTT2ConVarExistsOnServer") - util.AddNetworkString("TTT2ChangeServerConVar") - util.AddNetworkString("TTT2ServerConVarGetValue") - - net.Receive("TTT2ConVarExistsOnServer", function(len, ply) - if len < 1 then return end - - local identifier = net.ReadUInt(identityBitCount) - local conVarName = net.ReadString() - - net.Start("TTT2ConVarExistsOnServer") - net.WriteUInt(identifier, identityBitCount) - net.WriteBool(ConVarExists(conVarName)) - net.Send(ply) - end) - - net.Receive("TTT2ChangeServerConVar", function(len, ply) - if len < 1 then return end - - local conVarName = net.ReadString() - local value = net.ReadString() - - --- - -- @realm server - if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end - - RunConsoleCommand(conVarName, value) - end) - - local function BroadcastServerConVarChanges(conVarName, oldValue, newValue) - if oldValue == newValue then return end - - net.Start("TTT2ChangeServerConVar") - net.WriteString(conVarName) - net.WriteString(newValue) - net.Send(broadcastTable) - end - - net.Receive("TTT2ServerConVarGetValue", function(len, ply) - if len < 1 then return end - - --- - -- @realm server - local isAdmin = IsValid(ply) and hook.Run("TTT2AdminCheck", ply) - - requestCacheSize = net.ReadUInt(identityBitCount) - requestCache = {} - - for i = 1, requestCacheSize do - local identifier = net.ReadUInt(identityBitCount) - local conVarName = net.ReadString() - - requestCache[i] = {identifier = identifier, conVarName = conVarName} - end - - net.Start("TTT2ServerConVarGetValue") - net.WriteUInt(requestCacheSize, identityBitCount) - - for i = 1, requestCacheSize do - local request = requestCache[i] - local isSuccess = ConVarExists(request.conVarName) and isAdmin - - net.WriteUInt(request.identifier, identityBitCount) - net.WriteBool(isSuccess) - - if not isSuccess then continue end - - net.WriteString(request.conVarName) - - local conVar = GetConVar(request.conVarName) - - net.WriteString(conVar:GetString()) - net.WriteString(conVar:GetDefault()) - - cvars.AddChangeCallback(request.conVarName, BroadcastServerConVarChanges, "TTT2ServerConVarGetValueCallback") - end - - net.Send(ply) - - if isAdmin then - local plyId = ply:SteamID64() - - if not playersCache[plyId] then - playersCache[plyId] = true - broadcastTable[#broadcastTable + 1] = ply - end - end - end) - - hook.Add("PlayerDisconnected", "TTT2RemovePlayerOfConVarBroadcastTable", function(ply) - if not IsValid(ply) or not playersCache[ply:SteamID64()] then return end - - playersCache[ply:SteamID64()] = nil - table.RemoveByValue(broadcastTable, ply) - end) + util.AddNetworkString("TTT2ConVarExistsOnServer") + util.AddNetworkString("TTT2ChangeServerConVar") + util.AddNetworkString("TTT2ServerConVarGetValue") + + net.Receive("TTT2ConVarExistsOnServer", function(len, ply) + if len < 1 then + return + end + + local identifier = net.ReadUInt(identityBitCount) + local conVarName = net.ReadString() + + net.Start("TTT2ConVarExistsOnServer") + net.WriteUInt(identifier, identityBitCount) + net.WriteBool(ConVarExists(conVarName)) + net.Send(ply) + end) + + net.Receive("TTT2ChangeServerConVar", function(len, ply) + if len < 1 then + return + end + + local conVarName = net.ReadString() + local value = net.ReadString() + + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end + + RunConsoleCommand(conVarName, value) + end) + + local function BroadcastServerConVarChanges(conVarName, oldValue, newValue) + if oldValue == newValue then + return + end + + net.Start("TTT2ChangeServerConVar") + net.WriteString(conVarName) + net.WriteString(newValue) + net.Send(broadcastTable) + end + + net.Receive("TTT2ServerConVarGetValue", function(len, ply) + if len < 1 then + return + end + + --- + -- @realm server + -- stylua: ignore + local isAdmin = IsValid(ply) and hook.Run("TTT2AdminCheck", ply) + + requestCacheSize = net.ReadUInt(identityBitCount) + requestCache = {} + + for i = 1, requestCacheSize do + local identifier = net.ReadUInt(identityBitCount) + local conVarName = net.ReadString() + + requestCache[i] = { identifier = identifier, conVarName = conVarName } + end + + net.Start("TTT2ServerConVarGetValue") + net.WriteUInt(requestCacheSize, identityBitCount) + + for i = 1, requestCacheSize do + local request = requestCache[i] + local isSuccess = ConVarExists(request.conVarName) and isAdmin + + net.WriteUInt(request.identifier, identityBitCount) + net.WriteBool(isSuccess) + + if not isSuccess then + continue + end + + net.WriteString(request.conVarName) + + local conVar = GetConVar(request.conVarName) + + net.WriteString(conVar:GetString()) + net.WriteString(conVar:GetDefault()) + + cvars.AddChangeCallback( + request.conVarName, + BroadcastServerConVarChanges, + "TTT2ServerConVarGetValueCallback" + ) + end + + net.Send(ply) + + if isAdmin then + local plyId = ply:SteamID64() + + if not playersCache[plyId] then + playersCache[plyId] = true + broadcastTable[#broadcastTable + 1] = ply + end + end + end) + + hook.Add("PlayerDisconnected", "TTT2RemovePlayerOfConVarBroadcastTable", function(ply) + if not IsValid(ply) or not playersCache[ply:SteamID64()] then + return + end + + playersCache[ply:SteamID64()] = nil + table.RemoveByValue(broadcastTable, ply) + end) end diff --git a/lua/ttt2/extensions/debug.lua b/lua/ttt2/extensions/debug.lua new file mode 100644 index 000000000..21ac7362d --- /dev/null +++ b/lua/ttt2/extensions/debug.lua @@ -0,0 +1,72 @@ +--- +-- debug extension +-- @author ZenBre4ker +-- @module debug + +local debug = debug + +if SERVER then + AddCSLuaFile() +end + +--- +-- Adds quotation marks to strings otherwise just converts them to strings +-- @param any object The object to convert +-- @realm shared +-- @internal +local function ConvertToString(object) + if isstring(object) then + return "\"" .. object .. "\"" + else + return tostring(object) + end +end + +--- +-- Converts nil entries in an otherwise sequential table +-- Also directly converts all entries to strings +-- @param table tbl The table to convert +-- @return boolean If the table could be fully converted to a sequential table? +-- @realm shared +-- @internal +local function TryConvertToSequentialTable(tbl) + if not istable(tbl) then + return false + end + + -- Check that all keys are numbers + local largestIndex = 1 + for key, tableContent in pairs(tbl) do + if not isnumber(key) then + return false + end + if largestIndex < key then + largestIndex = key + end + end + + for i = 1, largestIndex do + tbl[i] = ConvertToString(tbl[i]) + end + + return true +end + +--- +-- Print messages with added quotation marks to strings +-- @param any message The message to display +-- @note The message can be a variable or a table and even nil. In case of a table it automatically concatenates all entries and checks every object if it is a string +-- @realm shared +function debug.print(message) + local printMessage = "" + + if TryConvertToSequentialTable(message) then + for i = 1, #message do + printMessage = printMessage .. message[i] .. " " + end + else + printMessage = ConvertToString(message) + end + + Dev(2, printMessage) +end diff --git a/lua/ttt2/extensions/draw.lua b/lua/ttt2/extensions/draw.lua index 862664779..82bbe2e82 100644 --- a/lua/ttt2/extensions/draw.lua +++ b/lua/ttt2/extensions/draw.lua @@ -4,9 +4,9 @@ -- @module draw if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return -- the rest of the draw library is client only + return -- the rest of the draw library is client only end local render = render @@ -17,6 +17,12 @@ local table = table local cam = cam local tableCopy = table.Copy local mathRound = math.Round +local mathFloor = math.floor +local mathMax = math.max +local mathRad = math.rad +local mathCos = math.cos +local mathSin = math.sin +local mathAbs = math.abs local colorShadowDark = Color(0, 0, 0, 220) local colorShadowBright = Color(0, 0, 0, 75) @@ -24,11 +30,12 @@ local colorShadowBright = Color(0, 0, 0, 75) local materialBlurScreen = Material("pp/blurscreen") local function GetShadowColor(color) - local tmpCol = color.r + color.g + color.b > 200 and tableCopy(colorShadowDark) or tableCopy(colorShadowBright) + local tmpCol = color.r + color.g + color.b > 200 and tableCopy(colorShadowDark) + or tableCopy(colorShadowBright) - tmpCol.a = mathRound(tmpCol.a * (color.a / 255)) + tmpCol.a = mathRound(tmpCol.a * (color.a / 255)) - return tmpCol + return tmpCol end --- @@ -42,13 +49,13 @@ end -- @2D -- @realm client function draw.OutlinedBox(x, y, w, h, t, color) - t = t or 1 + t = t or 1 - surface.SetDrawColor(color or COLOR_WHITE) + surface.SetDrawColor(color or COLOR_WHITE) - for i = 0, t - 1 do - surface.DrawOutlinedRect(x + i, y + i, w - i * 2, h - i * 2) - end + for i = 0, t - 1 do + surface.DrawOutlinedRect(x + i, y + i, w - i * 2, h - i * 2) + end end local drawOutlinedBox = draw.OutlinedBox @@ -64,14 +71,14 @@ local drawOutlinedBox = draw.OutlinedBox -- @2D -- @realm client function draw.OutlinedShadowedBox(x, y, w, h, t, color) - color = color or COLOR_WHITE + color = color or COLOR_WHITE - local tmpCol = GetShadowColor(color) + local tmpCol = GetShadowColor(color) - drawOutlinedBox(x + 2, y + 2, w, h, t, tmpCol) - drawOutlinedBox(x + 1, y + 1, w, h, t, tmpCol) - drawOutlinedBox(x + 1, y + 1, w, h, t, tmpCol) - drawOutlinedBox(x, y, w, h, t, color) + drawOutlinedBox(x + 2, y + 2, w, h, t, tmpCol) + drawOutlinedBox(x + 1, y + 1, w, h, t, tmpCol) + drawOutlinedBox(x + 1, y + 1, w, h, t, tmpCol) + drawOutlinedBox(x, y, w, h, t, color) end --- @@ -84,8 +91,8 @@ end -- @2D -- @realm client function draw.Box(x, y, w, h, color) - surface.SetDrawColor(color or COLOR_WHITE) - surface.DrawRect(x, y, w, h) + surface.SetDrawColor(color or COLOR_WHITE) + surface.DrawRect(x, y, w, h) end local drawBox = draw.Box @@ -101,18 +108,18 @@ local drawBox = draw.Box -- @2D -- @realm client function draw.ShadowedBox(x, y, w, h, color, scale) - color = color or COLOR_WHITE - scale = scale or 1 + color = color or COLOR_WHITE + scale = scale or 1 - local shift1 = mathRound(scale) - local shift2 = mathRound(scale * 2) + local shift1 = mathRound(scale) + local shift2 = shift1 * 2 - local tmpCol = GetShadowColor(color) + local tmpCol = GetShadowColor(color) - drawBox(x + shift2, y + shift2, w, h, tmpCol) - drawBox(x + shift1, y + shift1, w, h, tmpCol) - drawBox(x + shift1, y + shift1, w, h, tmpCol) - drawBox(x, y, w, h, t, color) + drawBox(x + shift2, y + shift2, w, h, tmpCol) + drawBox(x + shift1, y + shift1, w, h, tmpCol) + drawBox(x + shift1, y + shift1, w, h, tmpCol) + drawBox(x, y, w, h, color) end --- @@ -124,9 +131,9 @@ end -- @2D -- @realm client function draw.OutlinedCircle(x, y, r, color) - color = color or COLOR_WHITE + color = color or COLOR_WHITE - surface.DrawCircle(x, y, r, color.r, color.g, color.b, color.a) + surface.DrawCircle(x, y, r, color.r, color.g, color.b, color.a) end local drawOutlinedCircle = draw.OutlinedCircle @@ -141,18 +148,18 @@ local drawOutlinedCircle = draw.OutlinedCircle -- @2D -- @realm client function draw.OutlinedShadowedCircle(x, y, r, color, scale) - color = color or COLOR_WHITE - scale = scale or 1 + color = color or COLOR_WHITE + scale = scale or 1 - local shift1 = mathRound(scale) - local shift2 = mathRound(scale * 2) + local shift1 = mathRound(scale) + local shift2 = mathRound(scale * 2) - local tmpCol = GetShadowColor(color) + local tmpCol = GetShadowColor(color) - drawOutlinedCircle(x + shift2, y + shift2, r, tmpCol) - drawOutlinedCircle(x + shift1, y + shift1, r, tmpCol) - drawOutlinedCircle(x + shift1, y + shift1, r, tmpCol) - drawOutlinedCircle(x, y, r, color) + drawOutlinedCircle(x + shift2, y + shift2, r, tmpCol) + drawOutlinedCircle(x + shift1, y + shift1, r, tmpCol) + drawOutlinedCircle(x + shift1, y + shift1, r, tmpCol) + drawOutlinedCircle(x, y, r, color) end --- @@ -164,11 +171,11 @@ end -- @2D -- @realm client function draw.Circle(x, y, radius, color) - color = color or COLOR_WHITE + color = color or COLOR_WHITE - local diameter = radius * 2 + local diameter = radius * 2 - draw.RoundedBox(radius, x - radius, y - radius, diameter, diameter, color) + draw.RoundedBox(radius, x - radius, y - radius, diameter, diameter, color) end local drawCircle = draw.Circle @@ -183,18 +190,18 @@ local drawCircle = draw.Circle -- @2D -- @realm client function draw.ShadowedCircle(x, y, radius, color, scale) - color = color or COLOR_WHITE - scale = scale or 1 + color = color or COLOR_WHITE + scale = scale or 1 - local shift1 = mathRound(scale) - local shift2 = mathRound(scale * 2) + local shift1 = mathRound(scale) + local shift2 = shift1 * 2 - local tmpCol = GetShadowColor(color) + local tmpCol = GetShadowColor(color) - drawCircle(x + shift2, y + shift2, radius, tmpCol) - drawCircle(x + shift1, y + shift1, radius, tmpCol) - drawCircle(x + shift1, y + shift1, radius, tmpCol) - drawCircle(x, y, radius, color) + drawCircle(x + shift2, y + shift2, radius, tmpCol) + drawCircle(x + shift1, y + shift1, radius, tmpCol) + drawCircle(x + shift1, y + shift1, radius, tmpCol) + drawCircle(x, y, radius, color) end --- @@ -207,8 +214,8 @@ end -- @2D -- @realm client function draw.Line(startX, startY, endX, endY, color) - surface.SetDrawColor(color or COLOR_WHITE) - surface.DrawLine(startX, startY, endX, endY) + surface.SetDrawColor(color or COLOR_WHITE) + surface.DrawLine(startX, startY, endX, endY) end local drawLine = draw.Line @@ -223,14 +230,14 @@ local drawLine = draw.Line -- @2D -- @realm client function draw.ShadowedLine(startX, startY, endX, endY, color) - color = color or COLOR_WHITE + color = color or COLOR_WHITE - local tmpCol = GetShadowColor(color) + local tmpCol = GetShadowColor(color) - drawLine(startX + 2, startY + 2, endX + 2, endY + 2, tmpCol) - drawLine(startX + 1, startY + 1, endX + 1, endY + 1, tmpCol) - drawLine(startX + 1, startY + 1, endX + 1, endY + 1, tmpCol) - drawLine(startX, startY, endX, endY, color) + drawLine(startX + 2, startY + 2, endX + 2, endY + 2, tmpCol) + drawLine(startX + 1, startY + 1, endX + 1, endY + 1, tmpCol) + drawLine(startX + 1, startY + 1, endX + 1, endY + 1, tmpCol) + drawLine(startX, startY, endX, endY, color) end --- @@ -245,13 +252,13 @@ end -- @2D -- @realm client function draw.Texture(x, y, w, h, material, alpha, color) - alpha = alpha or 255 - color = color or COLOR_WHITE + alpha = alpha or 255 + color = color or COLOR_WHITE - surface.SetDrawColor(color.r, color.g, color.b, alpha) - surface.SetMaterial(material) + surface.SetDrawColor(color.r, color.g, color.b, alpha) + surface.SetMaterial(material) - surface.DrawTexturedRect(x, y, w, h) + surface.DrawTexturedRect(x, y, w, h) end local drawTexture = draw.Texture @@ -269,18 +276,18 @@ local drawTexture = draw.Texture -- @2D -- @realm client function draw.ShadowedTexture(x, y, w, h, material, alpha, color, scale) - alpha = alpha or 255 - color = color or COLOR_WHITE - scale = scale or 1 + alpha = alpha or 255 + color = color or COLOR_WHITE + scale = scale or 1 - local tmpCol = GetShadowColor(color) + local tmpCol = GetShadowColor(color) - local shift_tex_1 = mathRound(scale) - local shift_tex_2 = mathRound(2 * scale) + local shift_tex_1 = mathRound(scale) + local shift_tex_2 = 2 * shift_tex_1 - drawTexture(x + shift_tex_2, y + shift_tex_2, w, h, material, tmpCol.a, tmpCol) - drawTexture(x + shift_tex_1, y + shift_tex_1, w, h, material, tmpCol.a, tmpCol) - drawTexture(x, y, w, h, material, alpha, color) + drawTexture(x + shift_tex_2, y + shift_tex_2, w, h, material, tmpCol.a, tmpCol) + drawTexture(x + shift_tex_1, y + shift_tex_1, w, h, material, tmpCol.a, tmpCol) + drawTexture(x, y, w, h, material, alpha, color) end --- @@ -295,13 +302,13 @@ end -- @2D -- @realm client function draw.FilteredTexture(x, y, w, h, material, alpha, color) - render.PushFilterMag(TEXFILTER.LINEAR) - render.PushFilterMin(TEXFILTER.LINEAR) + render.PushFilterMag(TEXFILTER.LINEAR) + render.PushFilterMin(TEXFILTER.LINEAR) - drawTexture(x, y, w, h, material, alpha, color) + drawTexture(x, y, w, h, material, alpha, color) - render.PopFilterMag() - render.PopFilterMin() + render.PopFilterMag() + render.PopFilterMin() end local drawFilteredTexture = draw.FilteredTexture @@ -319,18 +326,18 @@ local drawFilteredTexture = draw.FilteredTexture -- @2D -- @realm client function draw.FilteredShadowedTexture(x, y, w, h, material, alpha, color, scale) - alpha = alpha or 255 - color = color or COLOR_WHITE - scale = scale or 1 + alpha = alpha or 255 + color = color or COLOR_WHITE + scale = scale or 1 - local tmpCol = GetShadowColor(color) + local tmpCol = GetShadowColor(color) - local shift_tex_1 = mathRound(scale) - local shift_tex_2 = mathRound(2 * scale) + local shift_tex_1 = mathRound(scale) + local shift_tex_2 = mathRound(2 * scale) - drawFilteredTexture(x + shift_tex_2, y + shift_tex_2, w, h, material, tmpCol.a, tmpCol) - drawFilteredTexture(x + shift_tex_1, y + shift_tex_1, w, h, material, tmpCol.a, tmpCol) - drawFilteredTexture(x, y, w, h, material, alpha, color) + drawFilteredTexture(x + shift_tex_2, y + shift_tex_2, w, h, material, tmpCol.a, tmpCol) + drawFilteredTexture(x + shift_tex_1, y + shift_tex_1, w, h, material, tmpCol.a, tmpCol) + drawFilteredTexture(x, y, w, h, material, alpha, color) end --- @@ -343,19 +350,21 @@ end -- @2D -- @realm client function draw.BlurredBox(x, y, w, h, fraction) - fraction = fraction or 1 + fraction = fraction or 1 - surface.SetMaterial(materialBlurScreen) - surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(materialBlurScreen) + surface.SetDrawColor(255, 255, 255, 255) - for i = 0.33, 1, 0.33 do - materialBlurScreen:SetFloat("$blur", fraction * i * 5) - materialBlurScreen:Recompute() + for i = 0.33, 1, 0.33 do + materialBlurScreen:SetFloat("$blur", fraction * i * 5) + materialBlurScreen:Recompute() - render.UpdateScreenEffectTexture() + render.UpdateScreenEffectTexture() - surface.DrawTexturedRect(x, y, ScrW(), ScrH()) - end + render.SetScissorRect(x, y, x + w, y + h, true) + surface.DrawTexturedRect(0, 0, ScrW(), ScrH()) + render.SetScissorRect(0, 0, 0, 0, false) + end end --- @@ -363,30 +372,30 @@ end -- @2D -- @param string text The text to be drawn -- @param[default="DermaDefault"] nil|string font The font. See @{surface.CreateFont} to create your own, --- or see Default +-- or see Default -- Fonts for a list of default fonts -- @param number x The X Coordinate -- @param number y The Y Coordinate -- @param Color color The color of the text. Uses the Color structure. -- @param number xalign The alignment of the X coordinate using --- TEXT_ALIGN_Enums. +-- TEXT_ALIGN_Enums. -- @param number yalign The alignment of the Y coordinate using --- TEXT_ALIGN_Enums. +-- TEXT_ALIGN_Enums. -- @param number scale The scale (float number) --- @ref https://wiki.garrysmod.com/page/draw/SimpleText +-- @ref https://wiki.facepunch.com/gmod/draw.SimpleText -- @realm client function draw.ShadowedText(text, font, x, y, color, xalign, yalign, scale) - scale = scale or 1.0 + scale = scale or 1.0 - local tmpCol = GetShadowColor(color) + local tmpCol = GetShadowColor(color) - local shift1 = mathRound(scale) - local shift2 = mathRound(scale * 2) + local shift1 = mathRound(scale) + local shift2 = shift1 * 2 - drawSimpleText(text, font, x + shift2, y + shift2, tmpCol, xalign, yalign) - drawSimpleText(text, font, x + shift1, y + shift1, tmpCol, xalign, yalign) - drawSimpleText(text, font, x + shift1, y + shift1, tmpCol, xalign, yalign) - drawSimpleText(text, font, x, y, color, xalign, yalign) + drawSimpleText(text, font, x + shift2, y + shift2, tmpCol, xalign, yalign) + drawSimpleText(text, font, x + shift1, y + shift1, tmpCol, xalign, yalign) + drawSimpleText(text, font, x + shift1, y + shift1, tmpCol, xalign, yalign) + drawSimpleText(text, font, x, y, color, xalign, yalign) end local drawShadowedText = draw.ShadowedText @@ -400,109 +409,218 @@ local drawShadowedText = draw.ShadowedText -- @param number y The y coordinate -- @param Color color The color of the text. Uses the Color structure. -- @param number xalign The alignment of the x coordinate using --- TEXT_ALIGN_Enums. +-- TEXT_ALIGN_Enums. -- @param number yalign The alignment of the y coordinate using --- TEXT_ALIGN_Enums. +-- TEXT_ALIGN_Enums. -- @param boolean shadow whether there should be a shadow of the text --- @param number scale The scale (float number) +-- @param[default=1.0] number scale The text scale (float number) +-- @param[default=0] number angle The rotational angle in degree -- @2D -- @realm client -function draw.AdvancedText(text, font, x, y, color, xalign, yalign, shadow, scale) - local scaleModifier = 1.0 - local t_font = fonts.GetFont(font) - - if t_font then - scaleModifier = fonts.GetScaleModifier(scale) - font = t_font[scaleModifier] - scale = scale / scaleModifier - end - - local scaled = isvector(scale) or scale ~= 1.0 - local mat - - if scaled then - local hw = ScrW() * 0.5 - local hh = ScrH() * 0.5 - - mat = Matrix() - mat:Translate(Vector(x, y)) - mat:Scale(isvector(scale) and scale or Vector(scale, scale, scale)) - mat:Translate(-Vector(hw, hh)) - - render.PushFilterMag(TEXFILTER.LINEAR) - render.PushFilterMin(TEXFILTER.LINEAR) - - cam.PushModelMatrix(mat) - - x = hw - y = hh - end - - if shadow then - drawShadowedText(text, font, x, y, color, xalign, yalign, scaleModifier) - else - drawSimpleText(text, font, x, y, color, xalign, yalign) - end - - if scaled then - cam.PopModelMatrix(mat) +function draw.AdvancedText(text, font, x, y, color, xalign, yalign, shadow, scale, angle) + local scaleModifier = 1.0 + local t_font = fonts.GetFont(font) + + if t_font then + scaleModifier = fonts.GetScaleModifier(scale) + font = t_font[scaleModifier] + scale = scale / scaleModifier + end + + local scaled = isvector(scale) or scale ~= 1.0 + local rotated = angle and angle ~= 0 and angle ~= 360 + local mat + + if scaled or rotated then + local hw = ScrW() * 0.5 + local hh = ScrH() * 0.5 + + mat = Matrix() + mat:Translate(Vector(x, y)) + mat:Scale(isvector(scale) and scale or Vector(scale, scale, scale)) + mat:Rotate(Angle(0, angle, 0)) + mat:Translate(-Vector(hw, hh)) + + render.PushFilterMag(TEXFILTER.LINEAR) + render.PushFilterMin(TEXFILTER.LINEAR) + + cam.PushModelMatrix(mat) + + x = hw + y = hh + end + + if shadow then + drawShadowedText(text, font, x, y, color, xalign, yalign, scaleModifier) + else + drawSimpleText(text, font, x, y, color, xalign, yalign) + end + + if scaled or rotated then + cam.PopModelMatrix() + + render.PopFilterMag() + render.PopFilterMin() + end +end - render.PopFilterMag() - render.PopFilterMin() - end +-- If there are no spaces, we have to cut the string at some point. +-- To improve performance, we don't want to iterate over every single +-- character. Therefore we assume the length based on an average first. +local function InternalSplitLongWord(word, width, widthWord) + local charCount = fastutf8.len(word) + local wCharAverage = widthWord / charCount + -- limit to at least 1, to prevent infinite loops + local charCountPerLine = math.max(1, math.floor(width / wCharAverage)) + + local lines = { "" } + + local currentStartPos = 1 + local currentLineNumber = 1 + + while true do + local nextStartPos = currentStartPos + charCountPerLine + local currentEndPos = nextStartPos - 1 + + -- Check if we need to end the algorithm and can finish + if nextStartPos > charCount then + -- put the remainder into the next line in this special case + -- this case is reached when calculating the last line and we + -- overshoot the end of the word, so we need to put the rest + -- into the next line + if currentStartPos <= charCount then + lines[currentLineNumber] = fastutf8.sub(word, currentStartPos, charCount) + end + + break + end + + local nextLine = fastutf8.sub(word, currentStartPos, currentEndPos) + local widthNextLine = surface.GetTextSize(nextLine) + + -- Check if our estimated cut needs adjustment and does not fit + if widthNextLine > width then + -- We need to keep removing characters until the line fits + local charsToRemove = 0 + -- We keep track of the width of the removed chars + -- to not use the expensive utf8.sub function for each char + local widthOfRemovedChars = 0 + + -- Iterate from the end of the new line to the start + -- To never remove the first char of a line, we add +1 to the start pos, + -- so we prevent infinite loops + for i = currentEndPos, currentStartPos + 1, -1 do + widthOfRemovedChars = widthOfRemovedChars + + surface.GetTextSize(fastutf8.GetChar(word, i)) + charsToRemove = charsToRemove + 1 + + if widthNextLine - widthOfRemovedChars <= width then + break + end + end + + -- Only do something if we actually removed chars + if charsToRemove > 0 then + -- Remove the chars from the line & shift the next start position + nextStartPos = nextStartPos - charsToRemove + nextLine = fastutf8.sub(word, currentStartPos, nextStartPos - 1) + end + elseif widthNextLine < width then + -- We need to add characters until the line does not fit anymore + local charsToAdd = 0 + -- We keep track of the width of the added chars + -- to not use the expensive utf8.sub function for each char + local widthOfAddedChars = 0 + + -- Iterate from the end of the current position to the end of the word + for i = currentEndPos, charCount, 1 do + widthOfAddedChars = widthOfAddedChars + + surface.GetTextSize(fastutf8.GetChar(word, i)) + + -- Break if the next char would not fit into the line + if widthNextLine + widthOfAddedChars >= width then + break + end + + -- Only add a char that still fits into the line + charsToAdd = charsToAdd + 1 + end + + -- Only do something if we actually added chars + if charsToAdd > 0 then + -- Add the chars to the line & shift the next start position + nextStartPos = nextStartPos + charsToAdd + nextLine = fastutf8.sub(word, currentStartPos, nextStartPos - 1) + end + end + + -- Add the line to the table + lines[currentLineNumber] = nextLine + + -- Set the index to the new start position + currentStartPos = nextStartPos + currentLineNumber = currentLineNumber + 1 + end + + return lines end -local function InternalGetWrappedText(text, width, scale) - -- Any wrapping required? - local w, h = surface.GetTextSize(text) +local function InternalGetWrappedText(text, allowedWidth, scale) + -- Any wrapping required? + local width, height = surface.GetTextSize(text) - if w <= width then - return {text}, w, h -- Nope, but wrap in table for uniformity - end + if width <= allowedWidth then + return { text }, width, height -- Nope, but wrap in table for uniformity + end - local words = string.Explode(" ", text) -- No spaces means you're screwed - local lines = {""} + local words = string.Explode(" ", text) + local lines = { "" } - for i = 1, #words do - local wrd = words[i] + for i = 1, #words do + local word = words[i] - if i == 1 then - -- add the first word whether or not it matches the size to prevent - -- weird empty first lines and ' ' in front of the first line - lines[1] = wrd + -- first, check the length of the word; if it is longer than a line, then + -- it has to be split as well + local widthWord = surface.GetTextSize(word) - continue - end + if widthWord > allowedWidth then + table.Add(lines, InternalSplitLongWord(word, allowedWidth, widthWord)) - local lns = #lines - local added = lines[lns] .. " " .. wrd + continue + end - w = surface.GetTextSize(added) + local amountLines = #lines + local combinedString = "" - if w > width then - lines[lns + 1] = wrd -- New line needed - else - lines[lns] = added -- Safe to tack it on - end - end + if i == 1 then + combinedString = word + else + combinedString = lines[amountLines] .. " " .. word + end - local lns = #lines + width = surface.GetTextSize(combinedString) - -- get length of longest line - local length = 0 + if width > allowedWidth then + lines[amountLines + 1] = word -- New line needed + else + lines[amountLines] = combinedString -- Safe to tack it on + end + end - for i = 1, lns do - local line_w = surface.GetTextSize(lines[i]) + local lns = #lines - if line_w > length then - length = line_w - end - end + -- get length of longest line + local length = 0 - -- get height of lines - local _, line_h = surface.GetTextSize(text) + for i = 1, lns do + local line_w = surface.GetTextSize(lines[i]) - return lines, length * scale, line_h * lns * scale + if line_w > length then + length = line_w + end + end + + return lines, length * scale, height * lns * scale end --- @@ -517,40 +635,231 @@ end -- @return number The height of all lines -- @realm client function draw.GetWrappedText(text, width, font, scale) - scale = scale or 1.0 - width = width / scale + scale = scale or 1.0 + width = width / scale - if not text then - return {}, 0, 0 - end + if not text then + return {}, 0, 0 + end - surface.SetFont(font or "DefaultBold") + surface.SetFont(font or "DefaultBold") - local lines = string.Explode("\n", text) - local returnLines = {} - local returnWidth = 0 - local returnHeight = 0 + local lines = string.Explode("\n", text) + local returnLines = {} + local returnWidth = 0 + local returnHeight = 0 - for i = 1, #lines do - local newLines, newWidth, newHeight = InternalGetWrappedText(lines[i], width, scale) + for i = 1, #lines do + local newLines, newWidth, newHeight = InternalGetWrappedText(lines[i], width, scale) - table.Add(returnLines, newLines) - returnWidth = math.max(returnWidth, newWidth) - returnHeight = returnHeight + newHeight - end + table.Add(returnLines, newLines) + returnWidth = math.max(returnWidth, newWidth) + returnHeight = returnHeight + newHeight + end - return returnLines, returnWidth, returnHeight + return returnLines, returnWidth, returnHeight end -- Returns the size of a inserted string -- @param string text The text that the length should be calculated -- @param[default="DefaultBold"] string font The font ID --- @warning This function changes the font to the passed font --- @return number, number w, h The size of the given text +-- @param[default=1.0] number scale The UI scale factor +-- @return number,number w, h The size of the given text +-- @warning This function changes the font in surface to the passed font -- @2D -- @realm client -function draw.GetTextSize(text, font) - surface.SetFont(font or "DefaultBold") +function draw.GetTextSize(text, font, scale) + scale = scale or 1.0 + + surface.SetFont(font or "DefaultBold") - return surface.GetTextSize(text) + local w, h = surface.GetTextSize(text) + + return w * scale, h * scale +end + +local cachedArcs = {} + +-- Generates an arc out of triangles that is cached in a table to reduce rendering time +local function PrecacheArc(id, x, y, radius, thickness, angleStart, angleEnd, roughness) + if + cachedArcs[id] + and cachedArcs[id].x == x + and cachedArcs[id].y == y + and cachedArcs[id].radius == radius + and cachedArcs[id].angleStart == angleStart + and cachedArcs[id].angleEnd == angleEnd + then + return cachedArcs[id].arcs + else + cachedArcs[id] = {} + cachedArcs[id].x = x + cachedArcs[id].y = y + cachedArcs[id].radius = radius + cachedArcs[id].angleStart = angleStart + cachedArcs[id].angleEnd = angleEnd + end + + local triarc = {} + + -- Define step + local step = mathMax(roughness or 1, 1) + + -- Correct start/end ang + angleStart, angleEnd = angleStart or 0, angleEnd or 0 + + if angleStart > angleEnd then + step = mathAbs(step) * -1 + end + + -- Create the inner circle's points. + local inner2 = {} + local r = radius - thickness + + for deg = angleStart, angleEnd, step do + local rad = mathRad(deg) + -- local rad = deg2rad * deg + local ox, oy = x + (mathCos(rad) * r), y + (-mathSin(rad) * r) + + inner2[#inner2 + 1] = { + x = ox, + y = oy, + u = (ox - x) / radius + 0.5, + v = (oy - y) / radius + 0.5, + } + end + + -- Create the outer circle's points. + local outer2 = {} + + for deg = angleStart, angleEnd, step do + local rad = mathRad(deg) + -- local rad = deg2rad * deg + local ox, oy = x + (mathCos(rad) * radius), y + (-mathSin(rad) * radius) + + outer2[#outer2 + 1] = { + x = ox, + y = oy, + u = (ox - x) / radius + 0.5, + v = (oy - y) / radius + 0.5, + } + end + + local inn = #inner2 * 2 + + -- Triangulize the points. + for tri = 1, inn do -- twice as many triangles as there are degrees. + local p1, p2, p3 + + p1 = outer2[mathFloor(tri * 0.5) + 1] + p3 = inner2[mathFloor((tri + 1) * 0.5) + 1] + + if tri % 2 == 0 then -- if the number is even use outer. + p2 = outer2[mathFloor((tri + 1) * 0.5)] + else + p2 = inner2[mathFloor((tri + 1) * 0.5)] + end + + triarc[#triarc + 1] = { p1, p2, p3 } + end + + cachedArcs[id].arcs = triarc + + return triarc +end + +-- A function that draws an arc that can be a full or part circle. +-- @param[default=nil] number identifier The numeric identifier for the caching, automatically set if nil +-- @param number x The arc center x position +-- @param number y The arc center y position +-- @param number radius The arc radius +-- @param number thickness The arc thickness, the arc is drawn to the inside +-- @param[default=0] number angleStart The arc start angle +-- @param[default=0] number angleStart The arc end angle +-- @param[default=1] number roughness The arc's roughness, aka degrees per step +-- @param[default=COLOR_WHITE] number color The arc's color +-- @realm client +function draw.Arc(identifier, x, y, radius, thickness, angleStart, angleEnd, roughness, color) + identifier = identifier or #cachedArcs + + surface.SetDrawColor(color or COLOR_WHITE) + draw.NoTexture() + + surface.DrawPolyTable( + PrecacheArc(identifier, x, y, radius, thickness, angleStart, angleEnd, roughness) + ) + + return identifier +end + +local drawArc = draw.Arc + +-- A function that draws a shadowed arc that can be a full or part circle. +-- @param[default=nil] number identifier The numeric identifier for the caching, automatically set if nil +-- @param number x The arc center x position +-- @param number y The arc center y position +-- @param number radius The arc radius +-- @param number thickness The arc thickness, the arc is drawn to the inside +-- @param[default=0] number angleStart The arc start angle +-- @param[default=0] number angleStart The arc end angle +-- @param[default=1] number roughness The arc's roughness, aka degrees per step +-- @param[default=COLOR_WHITE] number color The arc's color +-- @param[default=1.0] number scale A scaling factor that is used for the shadows +-- @realm client +function draw.ShadowedArc( + identifier, + x, + y, + radius, + thickness, + angleStart, + angleEnd, + roughness, + color, + scale +) + identifier = identifier or #cachedArcs + scale = scale or 1 + + local shift1 = mathRound(scale) + local shift2 = shift1 * 2 + + local tmpCol = GetShadowColor(color) + + drawArc( + identifier + 0, + x + shift2, + y + shift2, + radius, + thickness, + angleStart, + angleEnd, + roughness, + tmpCol + ) + drawArc( + identifier + 1, + x + shift1, + y + shift1, + radius, + thickness, + angleStart, + angleEnd, + roughness, + tmpCol + ) + drawArc( + identifier + 2, + x + shift1, + y + shift1, + radius, + thickness, + angleStart, + angleEnd, + roughness, + tmpCol + ) + drawArc(identifier + 3, x, y, radius, thickness, angleStart, angleEnd, roughness, color) + + return identifier end diff --git a/lua/ttt2/extensions/input.lua b/lua/ttt2/extensions/input.lua index 9c6cb9377..8c713233f 100644 --- a/lua/ttt2/extensions/input.lua +++ b/lua/ttt2/extensions/input.lua @@ -3,9 +3,9 @@ -- @module input if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return + return end local inputIsButtonDown = input.IsButtonDown @@ -18,5 +18,5 @@ local inputLookupBinding = input.LookupBinding -- @return boolean Returns true if the binding is pressed -- @realm client function input.IsBindingDown(binding) - return inputIsButtonDown(inputGetKeyCode(inputLookupBinding(binding))) + return inputIsButtonDown(inputGetKeyCode(inputLookupBinding(binding))) end diff --git a/lua/ttt2/extensions/math.lua b/lua/ttt2/extensions/math.lua index c7bbe8ea2..7abefa16f 100644 --- a/lua/ttt2/extensions/math.lua +++ b/lua/ttt2/extensions/math.lua @@ -3,7 +3,7 @@ -- @module math if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local exp = math.exp @@ -16,7 +16,6 @@ local exp = math.exp -- @return number -- @realm shared function math.ExponentialDecay(halflife, dt) - -- ln(0.5) = -0.69.. - return exp((-0.69314718 / halflife) * dt) + -- ln(0.5) = -0.69.. + return exp((-0.69314718 / halflife) * dt) end - diff --git a/lua/ttt2/extensions/net.lua b/lua/ttt2/extensions/net.lua index d05725949..82370afcc 100644 --- a/lua/ttt2/extensions/net.lua +++ b/lua/ttt2/extensions/net.lua @@ -11,54 +11,148 @@ local isfunction = isfunction -- Stream network message name constant local NETMSG_STREAM = "TTT2_NET_STREAM" +local NETMSG_REQUEST_NEXT_SPLIT = "TTT2_NET_REQUEST_NEXT_SPLIT" if SERVER then - AddCSLuaFile() + AddCSLuaFile() - -- Add the network string for streaming data - util.AddNetworkString(NETMSG_STREAM) + -- Add the network string for streaming data + util.AddNetworkString(NETMSG_STREAM) + -- Add the network string for requesting single splits + util.AddNetworkString(NETMSG_REQUEST_NEXT_SPLIT) end -- Size to split the network stream at (currently a bit lower than the max value, just to have some buffer) +-- Can be up to 65.533KB see: https://wiki.facepunch.com/gmod/net.Start net.STREAM_FRAGMENTATION_SIZE = 65400 -- Stream cache variables -net.stream_cache = {} +net.send_stream_cache = {} +net.receiving_players = {} +net.receive_stream_cache = {} net.stream_callbacks = {} +--- +-- Sends next part of the stream +-- @param string messageId a unique message id similar to the network strings +-- @param number streamId the current stream number the split is requested for +-- @param number split the part of the Stream that should be sent +-- @realm shared +-- @internal +local function SendNextStream(messageId, streamId, split, plys) + net.Start(NETMSG_STREAM) + -- Write the messageId + net.WriteString(messageId) + -- Write the streamId + net.WriteUInt(streamId, 32) + -- Write the current split + net.WriteUInt(split, 8) + + local data = net.send_stream_cache[messageId][streamId] + -- Write the actual data fragment as a string, which internally will also send its size + net.WriteString(data[split]) + + if SERVER then + if plys then + net.Send(plys) + else + net.Broadcast() + end + else + net.SendToServer() + end + + -- Delete cache if all players requested the last split + local receivingPlayerList = net.receiving_players[messageId][streamId] + if split <= 1 and (receivingPlayerList == nil or next(receivingPlayerList) == nil) then + net.receiving_players[messageId][streamId] = nil + net.send_stream_cache[messageId][streamId] = nil + end +end + +--- +-- It sends the next requested part of the stream and checks if the player is eligible for it +-- @param number len Length of message in Bits +-- @param Player ply player that the message has received, `nil` on the client +-- @realm shared +-- @internal +local function SendNextSplit(len, ply) + local messageId = net.ReadString() + local streamId = net.ReadUInt(32) + local nextSplit = net.ReadUInt(8) + + local receivingPlayerList = net.receiving_players[messageId][streamId] + + if nextSplit < 1 or receivingPlayerList and not receivingPlayerList[ply:SteamID64()] then + return + end + + -- Remove players that requested the last split + if nextSplit <= 1 then + receivingPlayerList[ply:SteamID64()] = nil + end + + SendNextStream(messageId, streamId, nextSplit, ply) +end +net.Receive(NETMSG_REQUEST_NEXT_SPLIT, SendNextSplit) + --- -- Initiates a stream message, usually for data that can be longer than -- the 64kb limit of a single net message. This will split up the data and send them in -- smaller fragments. The data will be converted (with sPON) to an encoded string during this process. -- -- @param string messageId A unique message id similar to the network strings --- @param table data The data table to send, this will be reconstructed at the client. --- @param[opt] table|player client SERVERSIDE only! Optional, use it to send a stream to a single client or a group of clients. +-- @param table data The data table to send, this will be reconstructed at the destination. +-- @param[opt] table|player plys SERVERSIDE only! Optional, use it to send a stream to a single player or a group of players otherwise it's broadcasted. +-- @realm shared +function net.SendStream(messageId, data, plys) + local encodedString = pon.encode(data) + local splits = string.SplitAtSize(encodedString, net.STREAM_FRAGMENTATION_SIZE) + + net.send_stream_cache[messageId] = net.send_stream_cache[messageId] or {} + net.receiving_players[messageId] = net.receiving_players[messageId] or {} + + local streamId = #net.send_stream_cache[messageId] + 1 + + net.send_stream_cache[messageId][streamId] = splits + + if SERVER and plys and #splits > 1 then + net.receiving_players[messageId][streamId] = {} + plys = istable(plys) and plys or { plys } + + for i = 1, #plys do + net.receiving_players[messageId][streamId][plys[i]:SteamID64()] = true + end + end + + -- Send first stream directly + SendNextStream(messageId, streamId, #splits, plys) +end + +--- +-- Request the next part of the stream +-- @param string messageId a unique message id similar to the network strings +-- @param number streamId the current stream number the split is requested for +-- @param number split the part of the Stream that should be sent +-- @param[opt] table|player plys SERVERSIDE only! Optional, use it to send a stream to a single player or a group of players. -- @realm shared -function net.SendStream(messageId, data, client) - local encodedString = pon.encode(data) - local split = string.SplitAtSize(encodedString, net.STREAM_FRAGMENTATION_SIZE) - local splitSize = #split - - for i = 1, splitSize do - net.Start(NETMSG_STREAM) - -- Write the messageId - net.WriteUInt(util.CRC(messageId), 32) - -- Write if there are still fragments coming after this one - net.WriteBool(i < splitSize) - -- Write the actual data fragment as a string, which internally will also send its size - net.WriteString(split[i]) - - if SERVER then - if client then - net.Send(client) - else - net.Broadcast() - end - else - net.SendToServer() - end - end +-- @internal +local function RequestNextSplit(messageId, streamId, split, plys) + net.Start(NETMSG_REQUEST_NEXT_SPLIT) + + net.WriteString(messageId) + net.WriteUInt(streamId, 32) + net.WriteUInt(split, 8) + + if SERVER then + if plys then + net.Send(plys) + else + net.Broadcast() + end + else + net.SendToServer() + end end --- @@ -68,43 +162,51 @@ end -- the data is reconstructed from all fragments. -- -- @param string messageId a unique message id similar to the network strings --- @param function callback This is the function that is called after the data was received. +-- @param function callback(receivedTable, ply) This is the function that is called with the received table. -- @realm shared function net.ReceiveStream(messageId, callback) - -- has to be saved as string, otherwise the key lookups will fail on the table - local msg = tostring(util.CRC(messageId)) + -- has to be saved as string, otherwise the key lookups will fail on the table + local msg = tostring(messageId) - net.stream_callbacks[msg] = callback + net.stream_callbacks[msg] = callback end --- -- Receive the internal stream message and add it to the cache -- If all fragments have arrived, reconstruct the data and call -- the registered callback. +-- @param number len Length of message in Bits +-- @param Player ply player that the message has received, `nil` on the client +-- @realm shared -- @internal -local function ReceiveStream() - local messageId = tostring(net.ReadUInt(32)) - local fragmented = net.ReadBool() - local data = net.ReadString() - - -- Create cache table if it does not exist yet for this message - net.stream_cache[messageId] = net.stream_cache[messageId] or {} - -- Write data to cache table - net.stream_cache[messageId][#net.stream_cache[messageId] + 1] = data - - -- Check if there are still fragments on their way - if not fragmented then - -- Otherwise this was the last packet, so reconstruct the data - local encodedStr = table.concat(net.stream_cache[messageId]) - local callback = net.stream_callbacks[messageId] - - -- Clear cache - net.stream_cache[messageId] = nil - - -- Check if a callback is registered - if isfunction(callback) then - callback(pon.decode(encodedStr)) - end - end +local function ReceiveStream(len, ply) + local messageId = net.ReadString() + local streamId = net.ReadUInt(32) + local split = net.ReadUInt(8) + local data = net.ReadString() + + -- Create cache table if it does not exist yet for this message + net.receive_stream_cache[messageId] = net.receive_stream_cache[messageId] or {} + net.receive_stream_cache[messageId][streamId] = net.receive_stream_cache[messageId][streamId] + or {} + -- Write data to cache table + net.receive_stream_cache[messageId][streamId][split] = data + + -- Check if this was the last fragment + if split <= 1 then + -- Otherwise this was the last packet, so reconstruct the data + local encodedStr = table.concat(net.receive_stream_cache[messageId][streamId]) + local callback = net.stream_callbacks[messageId] + + -- Clear cache + net.receive_stream_cache[messageId][streamId] = nil + + -- Check if a callback is registered + if isfunction(callback) then + callback(pon.decode(encodedStr), ply) + end + else + RequestNextSplit(messageId, streamId, split - 1, ply) + end end net.Receive(NETMSG_STREAM, ReceiveStream) diff --git a/lua/ttt2/extensions/player.lua b/lua/ttt2/extensions/player.lua new file mode 100644 index 000000000..7a56ab041 --- /dev/null +++ b/lua/ttt2/extensions/player.lua @@ -0,0 +1,21 @@ +--- +-- Functions that are related to players in general +-- @module player + +if SERVER then + AddCSLuaFile() +end + +if SERVER then + player.playerSettingRegistry = {} + + --- + -- Registers a synced client setting on the server. This has to be done so that settings set by + -- `ply:SetSettingOnServer` are not discarded on the server. + -- @param string identifier The identifier of the synced setting + -- @param string type The data type of the setting, e.g. `number`, `bool` or `string` + -- @realm server + function player.RegisterSettingOnServer(identifier, type) + player.playerSettingRegistry[identifier] = type + end +end diff --git a/lua/ttt2/extensions/render.lua b/lua/ttt2/extensions/render.lua new file mode 100644 index 000000000..5d964489f --- /dev/null +++ b/lua/ttt2/extensions/render.lua @@ -0,0 +1,26 @@ +--- +-- render extension +-- @author WardenPotato +-- @module render + +if SERVER then + AddCSLuaFile() + + return +end + +--- +-- Completely resets stencils. +-- This is useful for things near @{surface.DrawPoly} when `gmod_mcore_test` is 1, +-- because stencils seem to act unstable across different architectures. +-- @realm client +function render.FullReset() + render.SetStencilWriteMask(0xFF) + render.SetStencilTestMask(0xFF) + render.SetStencilReferenceValue(0) + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilPassOperation(STENCIL_KEEP) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_KEEP) + render.ClearStencil() +end diff --git a/lua/ttt2/extensions/sql.lua b/lua/ttt2/extensions/sql.lua index 1b0e95d1d..897577536 100644 --- a/lua/ttt2/extensions/sql.lua +++ b/lua/ttt2/extensions/sql.lua @@ -4,7 +4,7 @@ -- @module sql if SERVER then - AddCSLuaFile() + AddCSLuaFile() end --- @@ -14,15 +14,15 @@ end -- @return boolean Returns true if the column exists in the table -- @realm shared function sql.ColumnExists(tableName, columnName) - local result = sql.Query("PRAGMA table_info(" .. sql.SQLIdent(tableName) .. ")") + local result = sql.Query("PRAGMA table_info(" .. sql.SQLIdent(tableName) .. ")") - for i = 1, #result do - if result[i].name == columnName then - return true - end - end + for i = 1, #result do + if result[i].name == columnName then + return true + end + end - return false + return false end --- @@ -31,21 +31,23 @@ end -- @return table|nil Returns a table of the primarykey columns and nil in case of an error -- @realm shared function sql.GetPrimaryKey(tableName) - local result = sql.Query("PRAGMA table_info(" .. sql.SQLIdent(tableName) .. ")") + local result = sql.Query("PRAGMA table_info(" .. sql.SQLIdent(tableName) .. ")") - if result == false then return end + if result == false then + return + end - local primaryKeys = {} + local primaryKeys = {} - for i = 1, #result do - local pk = tonumber(result[i].pk) + for i = 1, #result do + local pk = tonumber(result[i].pk) - if pk ~= 0 then - primaryKeys[pk] = result[i].name - end - end + if pk ~= 0 then + primaryKeys[pk] = result[i].name + end + end - return primaryKeys + return primaryKeys end --- @@ -54,17 +56,19 @@ end -- @return table|nil Returns a table of the column names and nil in case of an error. -- @realm shared function sql.GetTableColumns(tableName) - local result = sql.Query("PRAGMA table_info(" .. sql.SQLIdent(tableName) .. ")") + local result = sql.Query("PRAGMA table_info(" .. sql.SQLIdent(tableName) .. ")") - if result == false then return end + if result == false then + return + end - local columnNames = {} + local columnNames = {} - for i = 1, #result do - columnNames[i] = result[i].name - end + for i = 1, #result do + columnNames[i] = result[i].name + end - return columnNames + return columnNames end --- @@ -72,7 +76,7 @@ end -- @param string tableName The name of the table to remove -- @realm shared function sql.DropTable(tableName) - return sql.Query("DROP TABLE " .. sql.SQLIdent(tableName)) + return sql.Query("DROP TABLE " .. sql.SQLIdent(tableName)) end --- @@ -81,7 +85,7 @@ end -- @return string Returns the escaped string -- @realm shared function sql.SQLIdent(str) - return "\"" .. str:gsub("\"", "\"\"") .. "\"" + return "\"" .. str:gsub("\"", "\"\"") .. "\"" end --- @@ -89,5 +93,5 @@ end -- This is equivalent to `sql.Query("Rollback;")`. -- @realm shared function sql.Rollback() - sql.Query("Rollback;") + sql.Query("Rollback;") end diff --git a/lua/ttt2/extensions/string.lua b/lua/ttt2/extensions/string.lua index 53b8c38ea..f9db7a75c 100644 --- a/lua/ttt2/extensions/string.lua +++ b/lua/ttt2/extensions/string.lua @@ -4,7 +4,7 @@ -- @module string if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local stringGSub = string.gsub @@ -20,30 +20,30 @@ local stringSub = string.sub -- @return table The table that contains the strings. -- @realm shared function string.SplitAtSize(str, splitSize) - local result = {} - local size = #str + local result = {} + local size = #str - -- If the string is already small enough, just return it. - if size <= splitSize then - return { str } - end + -- If the string is already small enough, just return it. + if size <= splitSize then + return { str } + end - local integralPart, fractionalPart = math.modf(size / splitSize) - -- If the number can not be perfectly divided into the given size, we have to add another for the last part - local splitCount = (fractionalPart == 0) and integralPart or (integralPart + 1) + local integralPart, fractionalPart = math.modf(size / splitSize) + -- If the number can not be perfectly divided into the given size, we have to add another for the last part + local splitCount = (fractionalPart == 0) and integralPart or (integralPart + 1) - for i = 1, splitCount do - -- first need to subtract one of the iterator, because we want to calculate the end position of the previous split. - -- And add one to the result because lua starts with 1 instead of 0. - local offset = ((i - 1) * splitSize) + 1 - -- Calculate the endPos with the current offset (next start position) plus the splitSize, minus 1 because the first symbol at the offset is already included - local endPos = offset + splitSize - 1 - -- If the calculated endPos is higher than the size, just set it to nil, to select the string until the end - endPos = endPos < size and endPos or nil - result[i] = string.sub(str, offset, endPos) - end + for i = 1, splitCount do + -- first need to subtract one of the iterator, because we want to calculate the end position of the previous split. + -- And add one to the result because lua starts with 1 instead of 0. + local offset = ((i - 1) * splitSize) + 1 + -- Calculate the endPos with the current offset (next start position) plus the splitSize, minus 1 because the first symbol at the offset is already included + local endPos = offset + splitSize - 1 + -- If the calculated endPos is higher than the size, just set it to nil, to select the string until the end + endPos = endPos < size and endPos or nil + result[i] = string.sub(str, offset, endPos) + end - return result + return result end --- @@ -52,7 +52,7 @@ end -- @return string -- @realm shared function string.Capitalize(str) - return stringUpper(stringSub(str, 1, 1)) .. stringSub(str, 2) + return stringUpper(stringSub(str, 1, 1)) .. stringSub(str, 2) end --- @@ -65,5 +65,5 @@ end -- @return string -- @realm shared function string.Interp(str, tbl) - return stringGSub(str, "{(%w+)}", tbl) + return stringGSub(str, "{(%w+)}", tbl) end diff --git a/lua/ttt2/extensions/surface.lua b/lua/ttt2/extensions/surface.lua index d1efcbfb0..afb94240d 100644 --- a/lua/ttt2/extensions/surface.lua +++ b/lua/ttt2/extensions/surface.lua @@ -4,9 +4,9 @@ -- @module surface if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return + return end --- @@ -16,5 +16,18 @@ end -- @param table fontData -- @realm client function surface.CreateAdvancedFont(fontName, fontData) - fonts.AddFont(fontName, fontData.size, fontData) + fonts.AddFont(fontName, fontData.size, fontData) +end + +--- +-- A function that takes a table with triangles and draws them to the screen. +-- @note The draw color has to be set beforehand +-- @param table tbl A table with triangles +-- @realm client +function surface.DrawPolyTable(tbl) + for i = 1, #tbl do + render.FullReset() + + surface.DrawPoly(tbl[i]) + end end diff --git a/lua/ttt2/extensions/table.lua b/lua/ttt2/extensions/table.lua index f935039de..892fdc400 100644 --- a/lua/ttt2/extensions/table.lua +++ b/lua/ttt2/extensions/table.lua @@ -4,7 +4,7 @@ -- @module table if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local table = table @@ -19,13 +19,13 @@ local isfunction = isfunction -- @param table t -- @realm shared function table.Randomize(t) - local out = {} + local out = {} - while #t > 0 do - out[#out + 1] = table.remove(t, math.random(#t)) - end + while #t > 0 do + out[#out + 1] = table.remove(t, math.random(#t)) + end - t = out + t = out end --- @@ -38,22 +38,24 @@ end -- @return any the value at the given path or nil if it does not exist -- @realm shared function table.GetWithPath(dataTable, path) - assert(path, "table.GetWithPath(..) missing path parameter.") + assert(path, "table.GetWithPath(..) missing path parameter.") - -- Convert single key to table - if not istable(path) then - path = { path } - end + -- Convert single key to table + if not istable(path) then + path = { path } + end - local currentDataTable = dataTable + local currentDataTable = dataTable - for i = 1, #path do - if currentDataTable == nil then return end + for i = 1, #path do + if currentDataTable == nil then + return + end - currentDataTable = currentDataTable[path[i]] - end + currentDataTable = currentDataTable[path[i]] + end - return currentDataTable + return currentDataTable end --- @@ -66,30 +68,30 @@ end -- @param any value -- @realm shared function table.SetWithPath(dataTable, path, value) - assert(path, "table.SetWithPath(..) missing path parameter.") + assert(path, "table.SetWithPath(..) missing path parameter.") - -- Convert single key to table - if not istable(path) then - path = { path } - end + -- Convert single key to table + if not istable(path) then + path = { path } + end - local currentDataTable = dataTable or {} + local currentDataTable = dataTable or {} - -- Create new table entries along the path if they do not exist - -- This will be done until the second last table is reached. - for i = 1, (#path - 1) do - currentDataTable[path[i]] = currentDataTable[path[i]] or {} - currentDataTable = currentDataTable[path[i]] - end + -- Create new table entries along the path if they do not exist + -- This will be done until the second last table is reached. + for i = 1, (#path - 1) do + currentDataTable[path[i]] = currentDataTable[path[i]] or {} + currentDataTable = currentDataTable[path[i]] + end - -- Set the value on the table (the last table on the path) - currentDataTable[path[#path]] = value + -- Set the value on the table (the last table on the path) + currentDataTable[path[#path]] = value end --- -- Checks if a table has a value. -- @note For optimization, functions that look for a value by sorting the table should never be needed if you work on a table that you built yourself. --- @note Override of the original table.HasValue check with nil check +-- @note Override of the original table.HasValue check with nil check -- @warning This function is very inefficient for large tables (O(n)) and should probably not be called in things that run each frame. Instead, consider a table structure such as example 2 below. -- @param table tbl Table to check -- @param any val Value to search for @@ -102,15 +104,17 @@ end -- > nil true -- @realm shared function table.HasValue(tbl, val) - if not tbl then return end + if not tbl then + return + end - for _, v in pairs(tbl) do - if v == val then - return true - end - end + for _, v in pairs(tbl) do + if v == val then + return true + end + end - return false + return false end --- @@ -120,17 +124,17 @@ end -- @return boolean -- @realm shared function table.EqualValues(a, b) - if a == b then - return true - end + if a == b then + return true + end - for k, v in pairs(a) do - if v ~= b[k] then - return false - end - end + for k, v in pairs(a) do + if v ~= b[k] then + return false + end + end - return true + return true end --- @@ -141,17 +145,19 @@ end -- @return boolean -- @realm shared function table.HasTable(tbl, needle) - if not tbl then return end - - for _, v in pairs(tbl) do - if v == needle then - return true - elseif table.EqualValues(v, needle) then - return true - end - end - - return false + if not tbl then + return + end + + for _, v in pairs(tbl) do + if v == needle then + return true + elseif table.EqualValues(v, needle) then + return true + end + end + + return false end --- @@ -161,22 +167,24 @@ end -- @return table -- @realm shared function table.CopyKeys(tbl, keys) - if not (tbl and keys) then return end + if not (tbl and keys) then + return + end - local out = {} - local val + local out = {} + local val - for _, k in pairs(keys) do - val = tbl[k] + for _, k in pairs(keys) do + val = tbl[k] - if istable(val) then - out[k] = table.Copy(val) - else - out[k] = val - end - end + if istable(val) then + out[k] = table.Copy(val) + else + out[k] = val + end + end - return out + return out end --- @@ -186,17 +194,21 @@ end -- @param[opt] boolean iterable -- @realm shared function table.AddMissing(target, source, iterable) - if #source == 0 then return end + if #source == 0 then + return + end - local fn = not iterable and pairs or ipairs - local index = #target + 1 + local fn = not iterable and pairs or ipairs + local index = #target + 1 - for _, v in fn(source) do - if table.HasValue(target, v) then continue end + for _, v in fn(source) do + if table.HasValue(target, v) then + continue + end - target[index] = v - index = index + 1 - end + target[index] = v + index = index + 1 + end end --- @@ -208,19 +220,21 @@ end -- @param number tableSize the number of entries in dataTable -- @realm shared function table.RemoveEmptyEntries(dataTable, tableSize) - local j = 1 + local j = 1 - for i = 1, tableSize do - if not dataTable[i] then continue end + for i = 1, tableSize do + if not dataTable[i] then + continue + end - if i ~= j then - -- Keep i's value, move it to j's pos. - dataTable[j] = dataTable[i] - dataTable[i] = nil - end + if i ~= j then + -- Keep i's value, move it to j's pos. + dataTable[j] = dataTable[i] + dataTable[i] = nil + end - j = j + 1 - end + j = j + 1 + end end --- @@ -230,18 +244,18 @@ end -- @return table the given t, but sorted -- @realm shared function table.Shuffle(t) - local n = #t + local n = #t - while n > 2 do - -- n is now the last pertinent index - local k = rand(n) -- 1 <= k <= n + while n > 2 do + -- n is now the last pertinent index + local k = rand(n) -- 1 <= k <= n - -- Quick swap - t[n], t[k] = t[k], t[n] - n = n - 1 - end + -- Quick swap + t[n], t[k] = t[k], t[n] + n = n - 1 + end - return t + return t end --- @@ -253,38 +267,38 @@ end -- @warning The returned entry will get removed from the given @{table}. If you wanna keep the original table untouched, create a copy for this function. -- @realm shared function table.ExtractRandomEntry(tbl, filterFn) - local cTbl = #tbl + local cTbl = #tbl - -- if no filterFn is defined, get a any random entry of the given @{table} - if not isfunction(filterFn) then - local index = rand(cTbl) - local entry = tbl[index] + -- if no filterFn is defined, get a any random entry of the given @{table} + if not isfunction(filterFn) then + local index = rand(cTbl) + local entry = tbl[index] - tremove(tbl, index) + tremove(tbl, index) - return entry - end + return entry + end - local tmpTbl = {} + local tmpTbl = {} - -- create a temporary table used for easy and fast access after shuffling - for i = 1, cTbl do - tmpTbl[i] = i - end + -- create a temporary table used for easy and fast access after shuffling + for i = 1, cTbl do + tmpTbl[i] = i + end - table.Shuffle(tmpTbl) + table.Shuffle(tmpTbl) - for i = 1, cTbl do - local index = tmpTbl[i] + for i = 1, cTbl do + local index = tmpTbl[i] - if filterFn(tbl[index]) then - local entry = tbl[index] + if filterFn(tbl[index]) then + local entry = tbl[index] - tremove(tbl, index) + tremove(tbl, index) - return entry - end - end + return entry + end + end end --- @@ -299,23 +313,25 @@ end -- @return[default=0] number|string The index where the biggest subtable was found -- @realm shared function table.GetAndRemoveBiggestSubTable(tbl) - local subTbl = {} - local subIdx = 0 + local subTbl = {} + local subIdx = 0 - for i, t in pairs(tbl) do - if #t < #subTbl then continue end + for i, t in pairs(tbl) do + if #t < #subTbl then + continue + end - subTbl = t - subIdx = i - end + subTbl = t + subIdx = i + end - if isnumber(subItx) then - table.remove(tbl, subIdx) - else - tbl[subIdx] = nil - end + if isnumber(subIdx) then + table.remove(tbl, subIdx) + else + tbl[subIdx] = nil + end - return subTbl, subIdx + return subTbl, subIdx end -- Returns an indexed table of indexes that exist in both tables. @@ -325,18 +341,22 @@ end -- @return table A table with the keys that exist in both tables -- @realm shared function table.GetEqualEntryKeys(tbl, reference) - -- return an empty table if tbl is nil - if not tbl then return {} end + -- return an empty table if tbl is nil + if not tbl then + return {} + end - local equalTbl = {} + local equalTbl = {} - for index in pairs(tbl) do - if not reference[index] then continue end + for index in pairs(tbl) do + if not reference[index] then + continue + end - equalTbl[#equalTbl + 1] = index - end + equalTbl[#equalTbl + 1] = index + end - return equalTbl + return equalTbl end --- @@ -347,7 +367,7 @@ end -- @return number The amount of indexes that exist in both tables -- @realm shared function table.GetEqualEntriesAmount(tbl, reference) - return #table.GetEqualEntryKeys(tbl, reference) + return #table.GetEqualEntryKeys(tbl, reference) end -- Copies any missing data from base table to the target table. @@ -357,17 +377,43 @@ end -- @return table The modified target table -- @realm shared function table.DeepInherit(t, base) - if not base then - return t - end - - for k, v in pairs(base) do - if t[k] == nil then - t[k] = v - elseif k ~= "BaseClass" and istable(t[k]) then - table.DeepInherit(t[k], v) - end - end - - return t + if not base then + return t + end + + for k, v in pairs(base) do + if t[k] == nil then + t[k] = v + elseif k ~= "BaseClass" and istable(t[k]) then + table.DeepInherit(t[k], v) + end + end + + return t +end + +-- Fully copies the table, meaning all tables inside this table are copied too. +-- Normal table.Copy copies only their reference. +-- @note Does not copy entities as well, only copies their reference. +-- @warning Do not use on tables that contain themselves somewhere down the line or +-- you'll get an infinite loop +-- @param table tbl The table that should be copied +-- @return table The copied table +-- @realm shared +function table.FullCopy(tbl) + local result = {} + + for key, value in pairs(tbl) do + if type(value) == "table" then + result[key] = table.FullCopy(value) + elseif type(v) == "Vector" then + result[key] = Vector(value.x, value.y, value.z) + elseif type(v) == "Angle" then + result[key] = Angle(value.p, value.y, value.r) + else + result[key] = value + end + end + + return result end diff --git a/lua/ttt2/extensions/util.lua b/lua/ttt2/extensions/util.lua index 12510acb9..1f4d9424e 100644 --- a/lua/ttt2/extensions/util.lua +++ b/lua/ttt2/extensions/util.lua @@ -7,7 +7,7 @@ -- @module util if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local band = bit.band @@ -32,32 +32,32 @@ local mathFloor = math.floor --- -- Attempts to get the weapon used from a DamageInfo instance needed because the -- GetAmmoType value is useless and inflictor isn't properly set (yet) --- @param DamageInfo dmg +-- @param CTakeDamageInfo dmg -- @return Weapon -- @realm shared function util.WeaponFromDamage(dmg) - local inf = dmg:GetInflictor() - local wep = nil - - if IsValid(inf) then - if inf:IsWeapon() or inf.Projectile then - wep = inf - elseif dmg:IsDamageType(DMG_DIRECT) or dmg:IsDamageType(DMG_CRUSH) then - -- DMG_DIRECT is the player burning, no weapon involved - -- DMG_CRUSH is physics or falling on someone - wep = nil - elseif inf:IsPlayer() then - wep = inf:GetActiveWeapon() - - if not IsValid(wep) then - -- this may have been a dying shot, in which case we need a - -- workaround to find the weapon because it was dropped on death - wep = IsValid(inf.dying_wep) and inf.dying_wep or nil - end - end - end - - return wep + local inf = dmg:GetInflictor() + local wep = nil + + if IsValid(inf) then + if inf:IsWeapon() or inf.Projectile then + wep = inf + elseif dmg:IsDamageType(DMG_DIRECT) or dmg:IsDamageType(DMG_CRUSH) then + -- DMG_DIRECT is the player burning, no weapon involved + -- DMG_CRUSH is physics or falling on someone + wep = nil + elseif inf:IsPlayer() then + wep = inf:GetActiveWeapon() + + if not IsValid(wep) then + -- this may have been a dying shot, in which case we need a + -- workaround to find the weapon because it was dropped on death + wep = IsValid(inf.dying_wep) and inf.dying_wep or nil + end + end + end + + return wep end --- @@ -68,20 +68,19 @@ end -- @return Weapon -- @realm shared function util.WeaponForClass(cls) - local wep = weaponsGetStored(cls) - - if not wep then - wep = sentsGetStored(cls) - if wep then - - -- don't like to rely on this, but the alternative is - -- sentsGet which does a full table copy, so only do - -- that as last resort - wep = wep.t or sentsGet(cls) - end - end - - return wep + local wep = weaponsGetStored(cls) + + if not wep then + wep = sentsGetStored(cls) + if wep then + -- don't like to rely on this, but the alternative is + -- sentsGet which does a full table copy, so only do + -- that as last resort + wep = wep.t or sentsGet(cls) + end + end + + return wep end --- @@ -90,21 +89,21 @@ end -- @return table -- @realm shared function util.GetFilteredPlayers(filterFn) - local plys = playerGetAll() + local plys = playerGetAll() - if not isfunction(filterFn) then - return plys - end + if not isfunction(filterFn) then + return plys + end - local tmp = {} + local tmp = {} - for i = 1, #plys do - if filterFn(plys[i]) then - tmp[#tmp + 1] = plys[i] - end - end + for i = 1, #plys do + if filterFn(plys[i]) then + tmp[#tmp + 1] = plys[i] + end + end - return tmp + return tmp end --- @@ -112,18 +111,18 @@ end -- @return table -- @realm shared function util.GetAlivePlayers() - local plys = playerGetAll() - local tmp = {} + local plys = playerGetAll() + local tmp = {} - for i = 1, #plys do - local ply = plys[i] + for i = 1, #plys do + local ply = plys[i] - if ply:Alive() and ply:IsTerror() then - tmp[#tmp + 1] = ply - end - end + if ply:Alive() and ply:IsTerror() then + tmp[#tmp + 1] = ply + end + end - return tmp + return tmp end --- @@ -132,22 +131,60 @@ end -- @return Player -- @realm shared function util.GetNextAlivePlayer(ply) - local alive = util.GetAlivePlayers() - if #alive < 1 then return end + local alive = util.GetAlivePlayers() + if #alive < 1 then + return + end - if IsValid(ply) then - local prev = nil + if IsValid(ply) then + local prev = nil - for i = 1, #alive do - if prev == ply then - return alive[i] - end + for i = 1, #alive do + if prev == ply then + return alive[i] + end - prev = alive[i] - end - end + prev = alive[i] + end + end - return alive[1] + return alive[1] +end + +--- +-- Returns the list of active @{Player}s that are not forced spectators +-- @return table List of active @{Player}s +-- @realm shared +function util.GetActivePlayers() + return util.GetFilteredPlayers(function(ply) + return IsValid(ply) and not ply:GetForceSpec() + end) +end + +--- +-- Returns the previous available @{Player} based on the given @{Player} in the global list +-- @param Player ply +-- @return Player +-- @realm shared +function util.GetPreviousAlivePlayer(ply) + local alive = util.GetAlivePlayers() + if #alive < 1 then + return + end + + if IsValid(ply) then + local prev = nil + + for i = #alive, 1, -1 do + if prev == ply then + return alive[i] + end + + prev = alive[i] + end + end + + return alive[#alive] end --- @@ -157,14 +194,14 @@ end -- @return Color The darkened color -- @realm shared function util.ColorDarken(color, value) - value = mathClamp(value, 0, 255) - - return Color( - mathMax(color.r - value, 0), - mathMax(color.g - value, 0), - mathMax(color.b - value, 0), - color.a - ) + value = mathClamp(value, 0, 255) + + return Color( + mathMax(color.r - value, 0), + mathMax(color.g - value, 0), + mathMax(color.b - value, 0), + color.a + ) end --- @@ -174,29 +211,29 @@ end -- @return Color The lightened color -- @realm shared function util.ColorLighten(color, value) - value = mathClamp(value, 0, 255) - - return Color( - mathMin(color.r + value, 255), - mathMin(color.g + value, 255), - mathMin(color.b + value, 255), - color.a - ) + value = mathClamp(value, 0, 255) + + return Color( + mathMin(color.r + value, 255), + mathMin(color.g + value, 255), + mathMin(color.b + value, 255), + color.a + ) end -- shifts the hue local function HueShift(hue, shift) - hue = hue + shift + hue = hue + shift - while hue >= 360 do - hue = hue - 360 - end + while hue >= 360 do + hue = hue - 360 + end - while hue < 0 do - hue = hue + 360 - end + while hue < 0 do + hue = hue + 360 + end - return hue + return hue end --- @@ -205,12 +242,12 @@ end -- @return Color The complementary color -- @realm shared function util.ColorComplementary(color) - local c_hsv, saturation, value = ColorToHSV(color) + local c_hsv, saturation, value = ColorToHSV(color) - local c_new = HSVToColor(HueShift(c_hsv, 180), saturation, value) - c_new.a = color.a + local c_new = HSVToColor(HueShift(c_hsv, 180), saturation, value) + c_new.a = color.a - return c_new + return c_new end --- @@ -219,11 +256,11 @@ end -- @return Color The color based on the background color -- @realm shared function util.GetDefaultColor(color) - if color.r + color.g + color.b < 500 then - return COLOR_WHITE - else - return COLOR_BLACK - end + if color.r + color.g + color.b < 500 then + return COLOR_WHITE + else + return COLOR_BLACK + end end --- @@ -233,11 +270,11 @@ end -- @return Color The color based on the original color -- @realm shared function util.GetChangedColor(color, value) - if color.r + color.g + color.b < 383 then - return util.ColorLighten(color, value or 20) - else - return util.ColorDarken(color, value or 20) - end + if color.r + color.g + color.b < 383 then + return util.ColorLighten(color, value or 20) + else + return util.ColorDarken(color, value or 20) + end end --- @@ -247,7 +284,7 @@ end -- @return Color The color based on the original color -- @realm shared function util.GetHoverColor(color) - return util.GetChangedColor(color, 20) + return util.GetChangedColor(color, 20) end --- @@ -257,41 +294,51 @@ end -- @return Color The color based on the original color -- @realm shared function util.GetActiveColor(color) - return util.GetChangedColor(color, 40) + return util.GetChangedColor(color, 40) end local function DoBleed(ent) - if not IsValid(ent) or (ent:IsPlayer() and (not ent:Alive() or not ent:IsTerror())) then return end + if not IsValid(ent) or (ent:IsPlayer() and (not ent:Alive() or not ent:IsTerror())) then + return + end - local jitter = VectorRand() * 30 - jitter.z = 20 + local jitter = VectorRand() * 30 + jitter.z = 20 - util.PaintDown(ent:GetPos() + jitter, "Blood", ent) + util.PaintDown(ent:GetPos() + jitter, "Blood", ent) end --- -- Something hurt us, start bleeding for a bit depending on the amount -- @param Entity ent --- @param DamageInfo dmg +-- @param CTakeDamageInfo dmg -- @param number t times -- @realm shared -- @todo improve description function util.StartBleeding(ent, dmg, t) - if dmg < 5 or not IsValid(ent) or ent:IsPlayer() and (not ent:Alive() or not ent:IsTerror()) then return end - - local times = mathClamp(mathRound(dmg / 15), 1, 20) - local delay = mathClamp(t / times, 0.1, 2) - - if ent:IsPlayer() then - times = times * 2 - delay = delay * 0.5 - end - - timer.Create("bleed" .. ent:EntIndex(), delay, times, function() - if not IsValid(ent) then return end - - DoBleed(ent) - end) + if + dmg < 5 + or not IsValid(ent) + or ent:IsPlayer() and (not ent:Alive() or not ent:IsTerror()) + then + return + end + + local times = mathClamp(mathRound(dmg / 15), 1, 20) + local delay = mathClamp(t / times, 0.1, 2) + + if ent:IsPlayer() then + times = times * 2 + delay = delay * 0.5 + end + + timer.Create("bleed" .. ent:EntIndex(), delay, times, function() + if not IsValid(ent) then + return + end + + DoBleed(ent) + end) end --- @@ -299,7 +346,7 @@ end -- @param Entity ent -- @realm shared function util.StopBleeding(ent) - timer.Remove("bleed" .. ent:EntIndex()) + timer.Remove("bleed" .. ent:EntIndex()) end local zapsound = Sound("npc/assassin/ball_zap1.wav") @@ -309,12 +356,12 @@ local zapsound = Sound("npc/assassin/ball_zap1.wav") -- @param Vector pos -- @realm shared function util.EquipmentDestroyed(pos) - local effect = EffectData() + local effect = EffectData() - effect:SetOrigin(pos) + effect:SetOrigin(pos) - util.Effect("cball_explode", effect) - sound.Play(zapsound, pos) + util.Effect("cball_explode", effect) + sound.Play(zapsound, pos) end --- @@ -324,12 +371,12 @@ end -- @realm shared -- @todo improve description function util.BasicKeyHandler(pnl, kc) - -- passthrough F5 - if kc == KEY_F5 then - RunConsoleCommand("jpeg") - else - pnl:Close() - end + -- passthrough F5 + if kc == KEY_F5 then + RunConsoleCommand("jpeg") + else + pnl:Close() + end end --- @@ -339,19 +386,17 @@ end -- @realm shared -- @deprecated function util.SafeRemoveHook(event, name) - local h = hook.GetTable() - if h and h[event] and h[event][name] then - hook.Remove(event, name) - end + local h = hook.GetTable() + if h and h[event] and h[event][name] then + hook.Remove(event, name) + end end --- -- Just a noop @{function} that is doing NOTHING -- @realm shared -- @see util.passthrough -function util.noop() - -end +function util.noop() end --- -- Just a passthrough @{function} that is doing NOTHING but returning the given value @@ -360,7 +405,7 @@ end -- @realm shared -- @see util.noop function util.passthrough(x) - return x + return x end --- @@ -370,11 +415,11 @@ end -- @return boolean -- @realm shared function util.BitSet(val, bit2) - if istable(val) then - return items.TableHasItem(val, bit2) - end + if istable(val) then + return items.TableHasItem(val, bit2) + end - return band(val, bit2) == bit2 + return band(val, bit2) == bit2 end --- @@ -382,35 +427,35 @@ end -- @param string file path -- @realm shared function util.IncludeClientFile(file) - if CLIENT then - include(file) - else - AddCSLuaFile(file) - end + if CLIENT then + include(file) + else + AddCSLuaFile(file) + end end --- -- Like @{string.FormatTime} but simpler (and working), always a string, no hour support -- @param number seconds --- @param string fmt the format +-- @param string fmt the format -- @return string -- @realm shared function util.SimpleTime(seconds, fmt) - if not seconds then - seconds = 0 - end + if not seconds then + seconds = 0 + end - local ms = (seconds - mathFloor(seconds)) * 100 + local ms = (seconds - mathFloor(seconds)) * 100 - seconds = mathFloor(seconds) + seconds = mathFloor(seconds) - local s = seconds % 60 + local s = seconds % 60 - seconds = (seconds - s) / 60 + seconds = (seconds - s) / 60 - local m = seconds % 60 + local m = seconds % 60 - return string.format(fmt, m, s, ms) + return string.format(fmt, m, s, ms) end --- @@ -418,20 +463,20 @@ end -- This creates an infinite recursion problem (stack overflow). Registering the function with -- this helper function fixes the problem. -- @param string name The name of the original function --- @return Function The pointer to the original functions +-- @return function The pointer to the original functions -- @realm shared function util.OverwriteFunction(name) - local str = stringSplit(name, ".") + local str = stringSplit(name, ".") - if not _G[name .. "_backup"] then - if #str == 1 then - _G[name .. "_backup"] = _G[str[1]] - elseif #str == 2 then - _G[name .. "_backup"] = _G[str[1]][str[2]] - end - end + if not _G[name .. "_backup"] then + if #str == 1 then + _G[name .. "_backup"] = _G[str[1]] + elseif #str == 2 then + _G[name .. "_backup"] = _G[str[1]][str[2]] + end + end - return _G[name .. "_backup"] + return _G[name .. "_backup"] end --- @@ -440,9 +485,9 @@ end -- @return string The file name without file ending -- @realm shared function util.GetFileName(name) - local splitString = stringSplit(name, '.') + local splitString = stringSplit(name, ".") - return tableConcat(splitString, ".", 1, #splitString - 1) + return tableConcat(splitString, ".", 1, #splitString - 1) end --- @@ -461,13 +506,13 @@ util.Capitalize = string.Capitalize -- @return boolean Returns if a team is evil -- @realm shared function util.IsEvilTeam(team) - -- players without a team are counted as neutral - if not team or team == TEAM_NONE then - return false - end + -- players without a team are counted as neutral + if not team or team == TEAM_NONE then + return false + end - -- all non inno roles are counted as evil - return team ~= TEAM_INNOCENT + -- all non inno roles are counted as evil + return team ~= TEAM_INNOCENT end --- @@ -478,98 +523,154 @@ end -- @return boolean Returns true if the vector is bounded -- @realm shared function util.VectorInBounds(vec, lowerBound, upperBound) - return vec.x > lowerBound.x and vec.x < upperBound.x - and vec.y > lowerBound.y and vec.y < upperBound.y - and vec.z > lowerBound.z and vec.z < upperBound.z + return vec.x > lowerBound.x + and vec.x < upperBound.x + and vec.y > lowerBound.y + and vec.y < upperBound.y + and vec.z > lowerBound.z + and vec.z < upperBound.z +end + +--- +-- Adjusts a numeric value from one range to a different one in a relative transformation. +-- @param number value The value that should be mapped +-- @param number minValue The minimum value that 'value' can have +-- @param number maxValue The maximum value that 'value' can have +-- @param number minTargetValue The minimum value that the mapped value can have +-- @param number maxTargetValue The maximum value that the mapped value can have +-- @realm shared +function util.TransformToRange(value, minValue, maxValue, minTargetValue, maxTargetValue) + value = mathMax(minValue, mathMin(maxValue, value)) + + return minTargetValue + + (maxTargetValue - minTargetValue) * (value - minValue) / (maxValue - minValue) +end + +--- +-- This is a helper function that checks if any of the current edit modes is active +-- that has to be left by pressing F1. +-- @param Player ply The player who might be editing +-- @return boolean Returns if an editing mode is active +-- @note Due to how the edit modes are implemented, some checks might only work in the client +-- realm. So make sure to check it not only on the server. +-- @realm shared +function util.EditingModeActive(ply) + return (HUDEditor and HUDEditor.IsEditing) or entspawnscript.IsEditing(ply) +end + +--- +-- Converts hammer units to meters. +-- @param number value The value in hammer units +-- @return number The converted value in meters +-- @realm shared +function util.HammerUnitsToMeters(value) + return value * 19.05 / 1000 end if CLIENT then - local colorsHealth = { - healthy = Color(0, 255, 0, 255), - hurt = Color(170, 230, 10, 255), - wounded = Color(230, 215, 10, 255), - badwound = Color(255, 140, 0, 255), - death = Color(255, 0, 0, 255) - } - - --- - -- Checks whether a given position is on screen - -- @param table scrpos table with x and y attributes - -- @return boolean - -- @realm client - function util.IsOffScreen(scrpos) - return not scrpos.visible or scrpos.x < 0 or scrpos.y < 0 or scrpos.x > ScrW() or scrpos.y > ScrH() - end - - -- Backward compatibility - IsOffScreen = util.IsOffScreen - - --- - -- Creates a @{string} based on the given health and maxhealth - -- @param number health - -- @param number maxhealth - -- @return string - -- @realm client - function util.HealthToString(health, maxhealth) - maxhealth = maxhealth or 100 - - if health > maxhealth * 0.9 then - return "hp_healthy", colorsHealth.healthy - elseif health > maxhealth * 0.7 then - return "hp_hurt", colorsHealth.hurt - elseif health > maxhealth * 0.45 then - return "hp_wounded", colorsHealth.wounded - elseif health > maxhealth * 0.2 then - return "hp_badwnd", colorsHealth.badwound - else - return "hp_death", colorsHealth.death - end - end - - local colorsKarma = { - max = COLOR_WHITE, - high = Color(255, 240, 135, 255), - med = Color(245, 220, 60, 255), - low = Color(255, 180, 0, 255), - min = Color(255, 130, 0, 255), - } - - --- - -- Creates a @{string} based on the given karma and the ttt_karma_max cvar - -- @param number karma - -- @return string - -- @realm client - function util.KarmaToString(karma) - local maxkarma = GetGlobalInt("ttt_karma_max", 1000) - - if karma > maxkarma * 0.89 then - return "karma_max", colorsKarma.max - elseif karma > maxkarma * 0.8 then - return "karma_high", colorsKarma.high - elseif karma > maxkarma * 0.65 then - return "karma_med", colorsKarma.med - elseif karma > maxkarma * 0.5 then - return "karma_low", colorsKarma.low - else - return "karma_min", colorsKarma.min - end - end - - --- - -- Draws a filtered textured rectangle / image / icon - -- @param number x - -- @param number y - -- @param number w width - -- @param number h height - -- @param Material material - -- @param number alpha - -- @param Color col the alpha value will be ignored - -- @deprecated draw.FilteredTexture should be used instead - -- @realm client - -- @author Mineotopia - function util.DrawFilteredTexturedRect(x, y, w, h, material, alpha, col) - --print("[TTT2][DEPRECATION][util.DrawFilteredTexturedRect] draw.FilteredTexture should be used instead") - - draw.FilteredTexture(x, y, w, h, material, alpha, col) - end + local colorsHealth = { + healthy = Color(0, 255, 0, 255), + hurt = Color(170, 230, 10, 255), + wounded = Color(230, 215, 10, 255), + badwound = Color(255, 140, 0, 255), + death = Color(255, 0, 0, 255), + } + + --- + -- Checks whether a given position is on screen + -- @param table scrpos table with x and y attributes + -- @return boolean + -- @realm client + function util.IsOffScreen(scrpos) + return not scrpos.visible + or scrpos.x < 0 + or scrpos.y < 0 + or scrpos.x > ScrW() + or scrpos.y > ScrH() + end + + -- Backward compatibility + IsOffScreen = util.IsOffScreen + + --- + -- Creates a @{string} based on the given health and maxhealth + -- @param number health + -- @param number maxhealth + -- @return string + -- @realm client + function util.HealthToString(health, maxhealth) + maxhealth = maxhealth or 100 + + if health > maxhealth * 0.9 then + return "hp_healthy", colorsHealth.healthy + elseif health > maxhealth * 0.7 then + return "hp_hurt", colorsHealth.hurt + elseif health > maxhealth * 0.45 then + return "hp_wounded", colorsHealth.wounded + elseif health > maxhealth * 0.2 then + return "hp_badwnd", colorsHealth.badwound + else + return "hp_death", colorsHealth.death + end + end + + local colorsKarma = { + max = COLOR_WHITE, + high = Color(255, 240, 135, 255), + med = Color(245, 220, 60, 255), + low = Color(255, 180, 0, 255), + min = Color(255, 130, 0, 255), + } + + --- + -- Creates a @{string} based on the given karma and the ttt_karma_max cvar + -- @param number karma + -- @return string + -- @realm client + function util.KarmaToString(karma) + local maxkarma = GetGlobalInt("ttt_karma_max", 1000) + + if karma > maxkarma * 0.89 then + return "karma_max", colorsKarma.max + elseif karma > maxkarma * 0.8 then + return "karma_high", colorsKarma.high + elseif karma > maxkarma * 0.65 then + return "karma_med", colorsKarma.med + elseif karma > maxkarma * 0.5 then + return "karma_low", colorsKarma.low + else + return "karma_min", colorsKarma.min + end + end + + --- + -- Draws a filtered textured rectangle / image / icon + -- @param number x + -- @param number y + -- @param number w width + -- @param number h height + -- @param Material material + -- @param number alpha + -- @param Color col the alpha value will be ignored + -- @deprecated draw.FilteredTexture should be used instead + -- @realm client + -- @author Mineotopia + function util.DrawFilteredTexturedRect(x, y, w, h, material, alpha, col) + draw.FilteredTexture(x, y, w, h, material, alpha, col) + end + + --- + -- Checks recursively the parents until none is found and the highest parent is returned + -- @ignore + function util.getHighestPanelParent(panel) + local parent = panel + local checkParent = panel:GetParent() + + while ispanel(checkParent) do + parent = checkParent + checkParent = parent:GetParent() + end + + return parent + end end diff --git a/lua/ttt2/libraries/appearance.lua b/lua/ttt2/libraries/appearance.lua index 4931cd7f6..b0c3ebbaf 100644 --- a/lua/ttt2/libraries/appearance.lua +++ b/lua/ttt2/libraries/appearance.lua @@ -4,50 +4,58 @@ -- @module appearance if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return -- the rest of the appearance library is client only + return -- the rest of the appearance library is client only end --- -- @realm client +-- stylua: ignore local cv_last_width = CreateConVar("ttt2_resolution_last_width", 1920, {FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cv_last_height = CreateConVar("ttt2_resolution_last_height", 1080, {FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cv_scale = CreateConVar("ttt2_resolution_scale", 1.0, {FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cv_use_global_color = CreateConVar("ttt2_use_global_color", 0, {FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cv_global_color_r = CreateConVar("ttt2_global_color_r", "30", {FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cv_global_color_g = CreateConVar("ttt2_global_color_g", "160", {FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cv_global_color_b = CreateConVar("ttt2_global_color_b", "160", {FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cv_global_color_a = CreateConVar("ttt2_global_color_a", "160", {FCVAR_ARCHIVE}) local function SetCachedColor() - appearance.focusColor = Color( - cv_global_color_r:GetInt(), - cv_global_color_g:GetInt(), - cv_global_color_b:GetInt(), - cv_global_color_a:GetInt() - ) + appearance.focusColor = Color( + cv_global_color_r:GetInt(), + cv_global_color_g:GetInt(), + cv_global_color_b:GetInt(), + cv_global_color_a:GetInt() + ) end appearance = appearance or {} @@ -62,10 +70,10 @@ appearance.focusColor = nil -- @internal -- @realm client function appearance.UpdateResolution(width, height) - appearance.SetGlobalScale(appearance.GetGlobalScale() * width / appearance.GetLastWidth()) + appearance.SetGlobalScale(appearance.GetGlobalScale() * width / appearance.GetLastWidth()) - cv_last_width:SetInt(width) - cv_last_height:SetInt(height) + cv_last_width:SetInt(width) + cv_last_height:SetInt(height) end --- @@ -73,7 +81,7 @@ end -- @return number The last stored width -- @realm client function appearance.GetLastWidth() - return cv_last_width:GetInt() or 0 + return cv_last_width:GetInt() or 0 end --- @@ -81,7 +89,7 @@ end -- @return number The last stored height -- @realm client function appearance.GetLastHeight() - return cv_last_height:GetInt() or 0 + return cv_last_height:GetInt() or 0 end --- @@ -89,17 +97,19 @@ end -- @param number scale The scale as a floating point value -- @realm client function appearance.SetGlobalScale(scale) - local oldScale = appearance.GetGlobalScale() + local oldScale = appearance.GetGlobalScale() - if oldScale == scale then return end + if oldScale == scale then + return + end - cv_scale:SetFloat(scale) + cv_scale:SetFloat(scale) - local appearanceCallbacks = appearance.callbacks + local appearanceCallbacks = appearance.callbacks - for i = 1, #appearanceCallbacks do - appearanceCallbacks[i](oldScale, scale) - end + for i = 1, #appearanceCallbacks do + appearanceCallbacks[i](oldScale, scale) + end end --- @@ -107,7 +117,7 @@ end -- @return number The scale as a floating point value -- @realm client function appearance.GetGlobalScale() - return cv_scale:GetFloat() or 1.0 + return cv_scale:GetFloat() or 1.0 end --- @@ -116,7 +126,7 @@ end -- @return number The scale as a floating point value -- @realm client function appearance.GetDefaultGlobalScale() - return math.Round(ScrW() / 1920, 1) + return math.Round(ScrW() / 1920, 1) end --- @@ -125,9 +135,11 @@ end -- @param function fn The callback function -- @realm client function appearance.RegisterScaleChangeCallback(fn) - if not isfunction(fn) or table.HasValue(appearance.callbacks, fn) then return end + if not isfunction(fn) or table.HasValue(appearance.callbacks, fn) then + return + end - appearance.callbacks[#appearance.callbacks + 1] = fn + appearance.callbacks[#appearance.callbacks + 1] = fn end --- @@ -135,10 +147,10 @@ end -- @param Color clr The new focus color -- @realm client function appearance.SetFocusColor(clr) - cv_global_color_r:SetInt(clr.r) - cv_global_color_g:SetInt(clr.g) - cv_global_color_b:SetInt(clr.b) - cv_global_color_a:SetInt(clr.a) + cv_global_color_r:SetInt(clr.r) + cv_global_color_g:SetInt(clr.g) + cv_global_color_b:SetInt(clr.b) + cv_global_color_a:SetInt(clr.a) end --- @@ -146,24 +158,24 @@ end -- @return Color The current focus color -- @realm client function appearance.GetFocusColor() - if not IsColor(appearance.focusColor) then - SetCachedColor() - end + if not IsColor(appearance.focusColor) then + SetCachedColor() + end - return appearance.focusColor + return appearance.focusColor end cvars.AddChangeCallback(cv_global_color_r:GetName(), function(cv, old, new) - SetCachedColor() + SetCachedColor() end) cvars.AddChangeCallback(cv_global_color_g:GetName(), function(cv, old, new) - SetCachedColor() + SetCachedColor() end) cvars.AddChangeCallback(cv_global_color_b:GetName(), function(cv, old, new) - SetCachedColor() + SetCachedColor() end) cvars.AddChangeCallback(cv_global_color_a:GetName(), function(cv, old, new) - SetCachedColor() + SetCachedColor() end) --- @@ -172,7 +184,7 @@ end) -- @param boolean state The new use state -- @realm client function appearance.SetUseGlobalFocusColor(state) - cv_use_global_color:SetBool(state == nil and true or state) + cv_use_global_color:SetBool(state == nil and true or state) end --- @@ -180,7 +192,7 @@ end -- @return boolean The color state -- @realm client function appearance.ShouldUseGlobalFocusColor() - return cv_use_global_color:GetBool() + return cv_use_global_color:GetBool() end --- @@ -190,9 +202,9 @@ end -- @return Color The chosen color -- @realm client function appearance.SelectFocusColor(clr) - if appearance.ShouldUseGlobalFocusColor() then - return appearance.GetFocusColor() - end + if appearance.ShouldUseGlobalFocusColor() then + return appearance.GetFocusColor() + end - return clr + return clr end diff --git a/lua/ttt2/libraries/bind.lua b/lua/ttt2/libraries/bind.lua index 526f5fe02..d01e75d42 100644 --- a/lua/ttt2/libraries/bind.lua +++ b/lua/ttt2/libraries/bind.lua @@ -5,9 +5,9 @@ -- @module bind if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return + return end local tableInsert = table.insert @@ -23,159 +23,213 @@ bind = bind or {} bind.bindings = bind.bindings or {} bind.registry = bind.registry or {} bind.settingsBindings = bind.settingsBindings or {} -bind.settingsBindingsCategories = bind.settingsBindingsCategories or { - "header_bindings_ttt2", - "header_bindings_other" -} +bind.settingsBindingsCategories = bind.settingsBindingsCategories + or { + "header_bindings_ttt2", + "header_bindings_other", + } --- -- @internal local function DBCreateBindsTable() - if not sql.TableExists(BIND_TABLE_NAME) then - local result = sql.Query("CREATE TABLE " .. BIND_TABLE_NAME .. " (guid TEXT, name TEXT, button TEXT)") + if not sql.TableExists(BIND_TABLE_NAME) then + local result = + sql.Query("CREATE TABLE " .. BIND_TABLE_NAME .. " (guid TEXT, name TEXT, button TEXT)") - if result == false then - ErrorNoHalt("[TTT2][BIND][ERROR] Could not create the database table...") + if result == false then + ErrorNoHaltWithStack("[TTT2][BIND][ERROR] Could not create the database table...") - return false - end - end + return false + end + end - return true + return true end --- -- @internal local function DBCreateDefaultBindsFlagTable() - if not sql.TableExists(BIND_FLAG_TABLE_NAME) then - local result = sql.Query("CREATE TABLE " .. BIND_FLAG_TABLE_NAME .. " (guid TEXT, name TEXT)") + if not sql.TableExists(BIND_FLAG_TABLE_NAME) then + local result = + sql.Query("CREATE TABLE " .. BIND_FLAG_TABLE_NAME .. " (guid TEXT, name TEXT)") - if result == false then - ErrorNoHalt("[TTT2][BIND][ERROR] Could not create the flag database table...") + if result == false then + ErrorNoHaltWithStack("[TTT2][BIND][ERROR] Could not create the flag database table...") - return false - end - end + return false + end + end - return true + return true end --- -- @internal local function SaveBinding(name, button) - if DBCreateBindsTable() then - local result = sql.Query("INSERT INTO " .. BIND_TABLE_NAME .. " VALUES('" .. LocalPlayer():SteamID64() .. "', " .. sql.SQLStr(name) .. ", " .. sql.SQLStr(button) .. ")") - - if result == false then - ErrorNoHalt("[TTT2][BIND][ERROR] Wasn't able to save binding to database...") - end - end + if DBCreateBindsTable() then + local result = sql.Query( + "INSERT INTO " + .. BIND_TABLE_NAME + .. " VALUES('" + .. LocalPlayer():SteamID64() + .. "', " + .. sql.SQLStr(name) + .. ", " + .. sql.SQLStr(button) + .. ")" + ) + + if result == false then + ErrorNoHaltWithStack("[TTT2][BIND][ERROR] Wasn't able to save binding to database...") + end + end end --- -- @internal local function DBRemoveBinding(name, button) - if DBCreateBindsTable() then - local result = sql.Query("DELETE FROM " .. BIND_TABLE_NAME .. " WHERE guid = '" .. LocalPlayer():SteamID64() .. "' AND name = " .. sql.SQLStr(name) .. " AND button = " .. sql.SQLStr(button) ) - - if result == false then - ErrorNoHalt("[TTT2][BIND][ERROR] Wasn't able to remove binding from database...") - end - end + if DBCreateBindsTable() then + local result = sql.Query( + "DELETE FROM " + .. BIND_TABLE_NAME + .. " WHERE guid = '" + .. LocalPlayer():SteamID64() + .. "' AND name = " + .. sql.SQLStr(name) + .. " AND button = " + .. sql.SQLStr(button) + ) + + if result == false then + ErrorNoHaltWithStack( + "[TTT2][BIND][ERROR] Wasn't able to remove binding from database..." + ) + end + end end --- -- @internal local function DBSetDefaultAppliedFlag(name) - if DBCreateDefaultBindsFlagTable() then - local result = sql.Query("INSERT INTO " .. BIND_FLAG_TABLE_NAME .. " VALUES('" .. LocalPlayer():SteamID64() .. "', " .. sql.SQLStr(name) .. ")") - - if result == false then - ErrorNoHalt("[TTT2][BIND][ERROR] Wasn't able to save binding flag to database...") - end - end + if DBCreateDefaultBindsFlagTable() then + local result = sql.Query( + "INSERT INTO " + .. BIND_FLAG_TABLE_NAME + .. " VALUES('" + .. LocalPlayer():SteamID64() + .. "', " + .. sql.SQLStr(name) + .. ")" + ) + + if result == false then + ErrorNoHaltWithStack( + "[TTT2][BIND][ERROR] Wasn't able to save binding flag to database..." + ) + end + end end --- -- @internal local function WasDefaultApplied(name) - if DBCreateDefaultBindsFlagTable() then - local result = sql.Query("SELECT * FROM " .. BIND_FLAG_TABLE_NAME .. " WHERE guid = '" .. LocalPlayer():SteamID64() .. "' AND name = " .. sql.SQLStr(name)) - - return istable(result) - end - - return false + if DBCreateDefaultBindsFlagTable() then + local result = sql.Query( + "SELECT * FROM " + .. BIND_FLAG_TABLE_NAME + .. " WHERE guid = '" + .. LocalPlayer():SteamID64() + .. "' AND name = " + .. sql.SQLStr(name) + ) + + return istable(result) + end + + return false end --- -- @internal local function TTT2LoadBindings() - if DBCreateBindsTable() then - local result = sql.Query("SELECT * FROM " .. BIND_TABLE_NAME .. " WHERE guid = '" .. LocalPlayer():SteamID64() .. "'") - - if istable(result) then - local resultCount = #result - - for i = 1, resultCount do - local tbl = result[i] - local tmp = tbl.button - - bind.RemoveAll(tbl.name, false) - - bind.bindings[tmp] = bind.bindings[tmp] or {} - - tableInsert(bind.bindings[tmp], tbl.name) - end - - print("[TTT2][BIND] Loaded bindings...") - end - end - - -- Try assigning the default key bind once if none is defined - for name in pairs(bind.registry) do - local item = bind.registry[name] - - if item.defaultKey and not WasDefaultApplied(name) then - if bind.Find(name) == KEY_NONE then - bind.Set(item.defaultKey, name, true) - end - - -- We tried to assign the default, but a bind was already present, so do not retry - -- and always set the applied flag. - DBSetDefaultAppliedFlag(name) - end - end + if DBCreateBindsTable() then + local result = sql.Query( + "SELECT * FROM " + .. BIND_TABLE_NAME + .. " WHERE guid = '" + .. LocalPlayer():SteamID64() + .. "'" + ) + + if istable(result) then + local resultCount = #result + + for i = 1, resultCount do + local tbl = result[i] + local tmp = tbl.button + + bind.RemoveAll(tbl.name, false) + + bind.bindings[tmp] = bind.bindings[tmp] or {} + + tableInsert(bind.bindings[tmp], tbl.name) + end + + Dev(1, "[TTT2][BIND] Loaded bindings...") + end + end + + -- Try assigning the default key bind once if none is defined + for name in pairs(bind.registry) do + local item = bind.registry[name] + + if item.defaultKey and not WasDefaultApplied(name) then + if bind.Find(name) == KEY_NONE then + bind.Set(item.defaultKey, name, true) + end + + -- We tried to assign the default, but a bind was already present, so do not retry + -- and always set the applied flag. + DBSetDefaultAppliedFlag(name) + end + end end --- -- @internal local function TTT2BindCheckThink() - -- Make sure the user is currently not typing anything, to prevent unwanted execution of a binding. - if vgui.GetKeyboardFocus() ~= nil or LocalPlayer():IsTyping() or gui.IsConsoleVisible() or vguihandler.IsOpen() then return end - - for btn, tbl in pairs(bind.bindings) do - local cache = input.IsButtonDown(btn) - - if cache and FirstPressed[btn] then - for _, name in pairs(tbl) do - if bind.registry[name] and isfunction(bind.registry[name].OnPressed) then - bind.registry[name].OnPressed() - end - end - end - - if not cache and WasPressed[btn] then - for _, name in pairs(tbl) do - if bind.registry[name] and isfunction(bind.registry[name].OnReleased) then - bind.registry[name].OnReleased() - end - end - end - - WasPressed[btn] = cache - FirstPressed[btn] = not cache - end + -- Make sure the user is currently not typing anything, to prevent unwanted execution of a binding. + if + vgui.GetKeyboardFocus() ~= nil + or LocalPlayer():IsTyping() + or gui.IsConsoleVisible() + or vguihandler.IsBlockingBindings() + then + return + end + + for btn, tbl in pairs(bind.bindings) do + local cache = input.IsButtonDown(btn) + + if cache and FirstPressed[btn] then + for _, name in pairs(tbl) do + if bind.registry[name] and isfunction(bind.registry[name].OnPressed) then + bind.registry[name].OnPressed() + end + end + end + + if not cache and WasPressed[btn] then + for _, name in pairs(tbl) do + if bind.registry[name] and isfunction(bind.registry[name].OnReleased) then + bind.registry[name].OnReleased() + end + end + end + + WasPressed[btn] = cache + FirstPressed[btn] = not cache + end end hook.Add("Think", "TTT2CallBindings", TTT2BindCheckThink) @@ -186,7 +240,7 @@ hook.Add("InitPostEntity", "TTT2LoadBindings", TTT2LoadBindings) -- @return table -- @realm client function bind.GetTable() - return bind.bindings + return bind.bindings end --- @@ -196,20 +250,23 @@ end -- @return[default=KEY_NONE] number -- @realm client function bind.Find(name) - if not name then return end + if not name then + return + end - for btn, tbl in pairs(bind.bindings) do - for _, id in pairs(tbl) do - if id ~= name then continue end + for btn, tbl in pairs(bind.bindings) do + for _, id in pairs(tbl) do + if id ~= name then + continue + end - return btn - end - end + return btn + end + end - return KEY_NONE + return KEY_NONE end - --- -- Finds all buttons associated with a specific binding and returns -- the buttons. Returns an empty table if no button is found. @@ -217,19 +274,23 @@ end -- @return table -- @realm client function bind.FindAll(name) - if not name then return end + if not name then + return + end - local bt = {} + local bt = {} - for btn, tbl in pairs(bind.bindings) do - for _, id in pairs(tbl) do - if id ~= name then continue end + for btn, tbl in pairs(bind.bindings) do + for _, id in pairs(tbl) do + if id ~= name then + continue + end - tableInsert(bt, btn) - end - end + tableInsert(bt, btn) + end + end - return bt + return bt end --- @@ -238,17 +299,19 @@ end -- @return boolean -- @realm client function bind.IsPressed(name) - if not name then return end + if not name then + return + end - local buttons = bind.FindAll(name) + local buttons = bind.FindAll(name) - for i = 1, #buttons do - if WasPressed[buttons[i]] then - return true - end - end + for i = 1, #buttons do + if WasPressed[buttons[i]] then + return true + end + end - return false + return false end --- @@ -258,17 +321,21 @@ end -- @param boolean persistent -- @realm client function bind.Remove(btn, name, persistent) - if persistent then - DBRemoveBinding(name, btn) -- Still try to delete from DB - end + if persistent then + DBRemoveBinding(name, btn) -- Still try to delete from DB + end - if not bind.bindings[btn] then return end + if not bind.bindings[btn] then + return + end - for i, v in pairs(bind.bindings[btn]) do - if v ~= name then continue end + for i, v in pairs(bind.bindings[btn]) do + if v ~= name then + continue + end - bind.bindings[btn][i] = nil - end + bind.bindings[btn][i] = nil + end end --- @@ -277,13 +344,13 @@ end -- @param boolean persistent -- @realm client function bind.RemoveAll(name, persistent) - local foundBinds = bind.FindAll(name) - local foundBindsCount = #foundBinds + local foundBinds = bind.FindAll(name) + local foundBindsCount = #foundBinds - -- clear all bindings - for i = 1, foundBindsCount do - bind.Remove(foundBinds[i], name, persistent) - end + -- clear all bindings + for i = 1, foundBindsCount do + bind.Remove(foundBinds[i], name, persistent) + end end --- @@ -294,28 +361,29 @@ end -- @param[optchain] number defaultKey -- @realm client function bind.AddSettingsBinding(name, label, category, defaultKey) - if not category then - category = "header_bindings_other" - end + if not category then + category = "header_bindings_other" + end - if not table.HasValue(bind.settingsBindingsCategories, category) then - bind.settingsBindingsCategories[#bind.settingsBindingsCategories + 1] = category - end + if not table.HasValue(bind.settingsBindingsCategories, category) then + bind.settingsBindingsCategories[#bind.settingsBindingsCategories + 1] = category + end - -- check if it already exists - for i = 1, #bind.settingsBindings do - local tbl = bind.settingsBindings[i] + -- check if it already exists + for i = 1, #bind.settingsBindings do + local tbl = bind.settingsBindings[i] - if tbl.name == name then - tbl.label = label -- update - tbl.category = category - tbl.defaultKey = defaultKey + if tbl.name == name then + tbl.label = label -- update + tbl.category = category + tbl.defaultKey = defaultKey - return -- don't insert again - end - end + return -- don't insert again + end + end - bind.settingsBindings[#bind.settingsBindings + 1] = {name = name, label = label, category = category, defaultKey = defaultKey} + bind.settingsBindings[#bind.settingsBindings + 1] = + { name = name, label = label, category = category, defaultKey = defaultKey } end --- @@ -333,36 +401,40 @@ end -- @param[optchain] number defaultKey -- @realm client function bind.Register(name, OnPressed, OnReleased, dontShowOrCategory, settingsLabel, defaultKey) - if not isfunction(OnPressed) and not isfunction(OnReleased) then return end - - bind.registry[name] = { - OnPressed = OnPressed, - OnReleased = OnReleased, - defaultKey = defaultKey - } - - if dontShowOrCategory ~= true then - bind.AddSettingsBinding(name, settingsLabel or name, dontShowOrCategory, defaultKey) - end + if not isfunction(OnPressed) and not isfunction(OnReleased) then + return + end + + bind.registry[name] = { + OnPressed = OnPressed, + OnReleased = OnReleased, + defaultKey = defaultKey, + } + + if dontShowOrCategory ~= true then + bind.AddSettingsBinding(name, settingsLabel or name, dontShowOrCategory, defaultKey) + end end --- -- Add a binding to run a command when the button is pressed. This function is not used to register a new binding shown in -- the UI, but to bind a specific key to a command --- @param number btn The button ID, see: https://wiki.garrysmod.com/page/Enums/BUTTON_CODE +-- @param number btn The button ID, see: BUTTON_CODE_Enums -- @param string name The command that should be executed -- @param boolean persistent -- @realm client function bind.Add(btn, name, persistent) - if not name or name == "" or not isnumber(btn) then return end + if not name or name == "" or not isnumber(btn) then + return + end - bind.bindings[btn] = bind.bindings[btn] or {} + bind.bindings[btn] = bind.bindings[btn] or {} - tableInsert(bind.bindings[btn], name) + tableInsert(bind.bindings[btn], name) - if persistent then - SaveBinding(name, btn) - end + if persistent then + SaveBinding(name, btn) + end end --- @@ -373,10 +445,12 @@ end -- @param boolean persistent -- @realm client function bind.Set(btn, name, persistent) - if not name or name == "" or not isnumber(btn) then return end + if not name or name == "" or not isnumber(btn) then + return + end - bind.RemoveAll(name, persistent) - bind.Add(btn, name, persistent) + bind.RemoveAll(name, persistent) + bind.Add(btn, name, persistent) end --- @@ -384,7 +458,7 @@ end -- @return table -- @realm client function bind.GetSettingsBindings() - return bind.settingsBindings + return bind.settingsBindings end --- @@ -392,5 +466,5 @@ end -- @return table -- @realm client function bind.GetSettingsBindingsCategories() - return bind.settingsBindingsCategories + return bind.settingsBindingsCategories end diff --git a/lua/ttt2/libraries/bodysearch.lua b/lua/ttt2/libraries/bodysearch.lua new file mode 100644 index 000000000..7abb8e5cd --- /dev/null +++ b/lua/ttt2/libraries/bodysearch.lua @@ -0,0 +1,1211 @@ +--- +-- Handling body search data and data processing. Is shared between the server and client +-- @author Mineotopia + +---@class BaseData +---@field inspector Player The player that inspected the body +---@field isPublicPolicingSearch boolean Whether to inspector is a public policing role (this check here also includes if the player was not a spectator and the search was not covered) + +---@class SceneData +---@field base BaseData The base data that is not overwritten, even if the data is merged +---@field playerModel string The string to the player model of the dead player +---@field ragOwner Player The owner of the ragdoll, in general the dead player +---@field credits number The amount of credits stored in the body +---@field searchUID number The search UID that is sued to track search requests +---@field isCovert boolean Whether the search was covered (ALT + search) +---@field isLongRange boolean Whether the search was long range (e.g. binoculars) +---@field nick string The dead player's nick +---@field subrole number The dead player's role ID +---@field roleColor Color The dead player's role color +---@field team string The dead player's team +---@field rag Entity The ragdoll that is searched +---@field eq table The equipment that the player carried before dying. Defaults to {} +---@field c4CutWire number The c4 wire the player cut, if they cut any. Defaults to -1 +---@field dmgType number The damage type that killed the player. Defaults to DMG_GENERIC +---@field wep string The weapon that killed the player +---@field lastWords string The last words that were typed in the chat while being killed +---@field wasHeadshot boolean Whether the killing shot was a head shot +---@field deathTime number The death time +---@field sid64 string The dead player's SteamID64 +---@field lastDamage number The last damage amount the player took before dying +---@field killFloorSurface number The ground surface where the player died +---@field killWaterLevel number The water level of the player when they were killed +---@field lastSeenEnt Player The last seen player entity +---@field killDistance number The distance to the killer when it happened as an obscured enum. Defaults to CORPSE_KILL_NO_DATA +---@field killHitGroup number The damage hitgroup of the killing blow. Default to HITGROUP_GENERIC +---@field killOrientation number The orientation to the killer when it happened as an obscured enum. Defaults to CORPSE_KILL_NO_DATA +---@field sampleDecayTime number The DNA sample decay time. Defaults to 0 +---@field killEntityIDList table A table of the entity indexes of all the player the dead player killed + +if SERVER then + AddCSLuaFile() +end + +CORPSE_KILL_NO_DATA = 0 + +CORPSE_KILL_DISTANCE_POINT_BLANK = 1 +CORPSE_KILL_DISTANCE_CLOSE = 2 +CORPSE_KILL_DISTANCE_FAR = 3 + +CORPSE_KILL_DIRECTION_FRONT = 1 +CORPSE_KILL_DIRECTION_BACK = 2 +CORPSE_KILL_DIRECTION_SIDE = 3 + +--- +-- @realm shared +-- mode 0: normal behavior, everyone can search/confirm bodies +-- mode 1: only public policing roles can confirm bodies, but everyone can still see all data in the menu +-- mode 2: only public policing roles can confirm and search bodies +-- stylua: ignore +local cvInspectConfirmMode = CreateConVar("ttt2_inspect_confirm_mode", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED}) + +local materialWeaponFallback = Material("vgui/ttt/missing_equip_icon") + +bodysearch = {} + +--- +-- Returns the current body inspect/confirm mode that is defined on the server. +-- @note This is basically a wrapper for the convar `ttt2_inspect_confirm_mode`. +-- @return number The body inspect/confirm mode +-- @realm shared +function bodysearch.GetInspectConfirmMode() + return cvInspectConfirmMode:GetInt() +end + +--- +-- Checks if a given player is allowed to take credits from a given corpse- +-- @param Payer ply The player that tries to take credits +-- @param Entity rag The ragdoll where the credits should be taken from +-- @param[default=false] isLongRange Whether the search is a long range search +-- @return boolean Returns if the player is able to take credits +-- @realm shared +function bodysearch.CanTakeCredits(ply, rag, isLongRange) + --- + -- @realm shared + -- stylua: ignore + if hook.Run("TTT2CheckFindCredits", ply, rag) == false then + return false + end + + local credits = CORPSE.GetCredits(rag, 0) + + return ply:IsActiveShopper() + and not ply:GetSubRoleData().preventFindCredits + and credits > 0 + and not isLongRange +end + +if SERVER then + local mathMax = math.max + local mathRound = math.Round + local mathFloor = math.floor + + util.AddNetworkString("ttt2_client_reports_corpse") + util.AddNetworkString("ttt2_client_confirm_corpse") + util.AddNetworkString("ttt2_credits_were_taken") + + net.Receive("ttt2_client_confirm_corpse", function(_, ply) + if not IsValid(ply) then + return + end + + local rag = net.ReadEntity() + local searchUID = net.ReadUInt(16) + local isLongRange = net.ReadBool() + local creditsOnly = net.ReadBool() + + -- if the search ID doesn't match the ID cached on the player, this search is not + -- valid and should be discarded + if ply.searchID ~= searchUID then + ply.searchID = nil + + return + end + + -- the search ID should always be set back to nil after a body was confirmed + -- meaning that the search procedure was ended and the UID is no longer needed + ply.searchID = nil + + if creditsOnly then + bodysearch.GiveFoundCredits(ply, rag, false, searchUID) + + return + end + + if + IsValid(rag) + and (rag:GetPos():Distance(ply:GetPos()) < 128 or isLongRange) + and not CORPSE.GetFound(rag, false) + then + CORPSE.IdentifyBody(ply, rag, searchUID) + + bodysearch.GiveFoundCredits(ply, rag, false, searchUID) + end + end) + + net.Receive("ttt2_client_reports_corpse", function(_, ply) + if not IsValid(ply) or not ply:IsActive() then + return + end + + local rag = net.ReadEntity() + + if not IsValid(rag) or rag:GetPos():Distance(ply:GetPos()) > 128 then + return + end + + -- in mode 0 the body has to be confirmed to call a detective + if cvInspectConfirmMode:GetInt() == 0 and not CORPSE.GetFound(rag, false) then + return + end + + local plyTable = util.GetFilteredPlayers(function(p) + local roleData = p:GetSubRoleData() + + return roleData.isPolicingRole and roleData.isPublicRole and p:IsTerror() + end) + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2ModifyCorpseCallRadarRecipients", plyTable, rag, ply) + + -- show indicator in radar to detectives + net.Start("TTT_CorpseCall") + net.WriteVector(rag:GetPos()) + net.Send(plyTable) + + LANG.MsgAll( + "body_call", + { player = ply:Nick(), victim = CORPSE.GetPlayerNick(rag, "someone") }, + MSG_MSTACK_PLAIN + ) + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2CalledPolicingRole", plyTable, ply, rag, CORPSE.GetPlayer(rag)) + end) + + --- + -- Gives the credits from a ragdoll to a player that is searching the ragdoll. It checks + -- whether the player is able to take those credits or not. + -- @param Player ply The player that should receive those credits + -- @param Entity rag The ragdoll entity that is searched + -- @param[default=false] boolean isLongRange Whether the search is long or short range + -- @param[default=0] number searchUID The UID from this body search, can be ignored if not called from within UI + -- @realm server + function bodysearch.GiveFoundCredits(ply, rag, isLongRange, searchUID) + if bodysearch.CanTakeCredits(ply, rag, isLongRange) == false then + return + end + + local corpseNick = CORPSE.GetPlayerNick(rag) + local credits = CORPSE.GetCredits(rag, 0) + + LANG.Msg(ply, "body_credits", { num = credits }) + + ply:AddCredits(credits) + + CORPSE.SetCredits(rag, 0) + + ServerLog( + ply:Nick() .. " took " .. credits .. " credits from the body of " .. corpseNick .. "\n" + ) + + events.Trigger(EVENT_CREDITFOUND, ply, rag, credits) + + -- update clients so their UIs can be updated + net.Start("ttt2_credits_were_taken") + net.WriteUInt(searchUID or 0, 16) + net.Broadcast() + end + + --- + -- Assimilates the scene data from the player death. Uses multiple sources of data collected in different + -- hooks and puts them all inside the sceneData table that is returned. The table has a few entries that are + -- always present if scene data is collected. Some more in-depth information might depend on the role of + -- the player that is currently searching the body. + -- @param Player inspector The player that searches the corpse + -- @param Entity rag The ragdoll entity that is searched + -- @param[default=false] boolen isCovert Whether the body search is covert or announced + -- @param[default=false] boolean isLongRange Whether the search is long or short range + -- @return table The scene data table + -- @realm server + function bodysearch.AssimilateSceneData(inspector, rag, isCovert, isLongRange) + local sceneData = {} + local inspectorRoleData = inspector:GetSubRoleData() + local isPublicPolicingSearch = inspectorRoleData.isPolicingRole + and inspectorRoleData.isPublicRole + + -- data that is available to everyone and is not overwritten on data update on the client + sceneData.base = {} + sceneData.base.inspector = inspector + sceneData.base.isPublicPolicingSearch = isPublicPolicingSearch + and inspector:IsActive() + and not isCovert + + sceneData.playerModel = rag.scene.plyModel or "" + sceneData.ragOwner = player.GetBySteamID64(rag.sid64) + sceneData.credits = CORPSE.GetCredits(rag, 0) + sceneData.searchUID = mathFloor(rag:EntIndex() + (rag.time or 0)) + + sceneData.isCovert = isCovert + sceneData.isLongRange = isLongRange + + -- if a non-public or non-policing role tries to search a body in mode 2, nothing happens + if + cvInspectConfirmMode:GetInt() == 2 + and not isPublicPolicingSearch + and not inspector:IsSpec() + then + return sceneData + end + + sceneData.nick = CORPSE.GetPlayerNick(rag) + sceneData.subrole = rag.was_role + sceneData.roleColor = rag.role_color + sceneData.team = rag.was_team + + if not sceneData.nick or not sceneData.subrole or not sceneData.team then + return + end + + sceneData.rag = rag + sceneData.eq = rag.equipment or {} + sceneData.c4CutWire = rag.bomb_wire or -1 + sceneData.dmgType = rag.dmgtype or DMG_GENERIC + sceneData.wep = rag.dmgwep or "" + sceneData.lastWords = rag.last_words + sceneData.wasHeadshot = rag.was_headshot or false + sceneData.deathTime = rag.time or 0 + sceneData.sid64 = rag.scene.plySID64 or "" + sceneData.lastDamage = mathRound(mathMax(0, rag.scene.lastDamage or 0)) + sceneData.killFloorSurface = rag.scene.floorSurface or 0 + sceneData.killWaterLevel = rag.scene.waterLevel or 0 + + -- only add last seen id if searched by public policing role + if isPublicPolicingSearch then + sceneData.lastSeenEnt = rag.lastid and rag.lastid.ent or nil + end + + sceneData.killDistance = CORPSE_KILL_NO_DATA + if rag.scene.hit_trace and isvector(rag.scene.hit_trace.StartPos) then + local rawKillDistance = + rag.scene.hit_trace.StartPos:Distance(rag.scene.hit_trace.HitPos) + if rawKillDistance < 200 then + sceneData.killDistance = CORPSE_KILL_DISTANCE_POINT_BLANK + elseif rawKillDistance < 700 then + sceneData.killDistance = CORPSE_KILL_DISTANCE_CLOSE + else + sceneData.killDistance = CORPSE_KILL_DISTANCE_FAR + end + end + + sceneData.killHitGroup = HITGROUP_GENERIC + if rag.scene.hit_group and rag.scene.hit_group > 0 then + sceneData.killHitGroup = rag.scene.hit_group + end + + sceneData.killOrientation = CORPSE_KILL_NO_DATA + if + rag.scene.hit_trace + and isangle(rag.scene.hit_trace.StartAng) + and rag.scene.damageInfoData.isBulletDamage + then + local rawKillAngle = math.abs( + math.AngleDifference(rag.scene.hit_trace.StartAng.yaw, rag.scene.victim.aim_yaw) + ) + + if rawKillAngle < 45 then + sceneData.killOrientation = CORPSE_KILL_DIRECTION_BACK + elseif rawKillAngle < 135 then + sceneData.killOrientation = CORPSE_KILL_DIRECTION_SIDE + else + sceneData.killOrientation = CORPSE_KILL_DIRECTION_FRONT + end + end + + sceneData.sampleDecayTime = 0 + if rag.killer_sample then + sceneData.sampleDecayTime = rag.killer_sample.t + end + + -- build list of people this player killed, but only if convar is enabled + sceneData.killEntityIDList = {} + if GetConVar("ttt2_confirm_killlist"):GetBool() then + local ragKills = rag.kills or {} + + for i = 1, #ragKills do + local victimSIDs = ragKills[i] + + -- also send disconnected players as a marker + local vic = player.GetBySteamID64(victimSIDs) + + sceneData.killEntityIDList[#sceneData.killEntityIDList + 1] = IsValid(vic) + and vic:EntIndex() + or -1 + end + end + + return sceneData + end + + --- + -- Streams the provided scene data to the given clients, is broadcasted if no client is defined. + -- @param SceneData sceneData The scene data table that should be streamed to the client(s) + -- @param[opt] table|player client Optional, use it to send a stream to a single client or a group of clients + -- @realm server + function bodysearch.StreamSceneData(sceneData, client) + net.SendStream("TTT2_BodySearchData", sceneData, client) + end +end + +if CLIENT then + -- cache functions + local utilSimpleTime = util.SimpleTime + local CurTime = CurTime + local utilBitSet = util.BitSet + local mathMax = math.max + local table = table + local IsValid = IsValid + local pairs = pairs + + net.ReceiveStream("TTT2_BodySearchData", function(searchStreamData) + local eq = {} -- placeholder for the hook, not used right now + --- + -- @realm shared + -- stylua: ignore + hook.Run("TTTBodySearchEquipment", searchStreamData, eq) + + searchStreamData.show = LocalPlayer() == searchStreamData.base.inspector + -- add this hack here to keep compatibility to the old scoreboard + searchStreamData.show_sb = searchStreamData.show + or searchStreamData.base.isPublicPolicingSearch + + -- cache search result in rag.bodySearchResult, e.g. useful for scoreboard + bodysearch.StoreSearchResult(searchStreamData) + + if searchStreamData.show then + -- if there is more elaborate data already available + -- confirming this body, then this should be used instead + if bodysearch.PlayerHasDetailedSearchResult(searchStreamData.ragOwner) then + SEARCHSCREEN:Show(bodysearch.GetSearchResult(searchStreamData.ragOwner)) + else + SEARCHSCREEN:Show(searchStreamData) + end + end + end) + + local damageToText = { + ["crush"] = DMG_CRUSH, + ["bullet"] = DMG_BULLET, + ["fall"] = DMG_FALL, + ["boom"] = DMG_BLAST, + ["club"] = DMG_CLUB, + ["drown"] = DMG_DROWN, + ["stab"] = DMG_SLASH, + ["burn"] = DMG_BURN, + ["teleport"] = DMG_SONIC, + ["car"] = DMG_VEHICLE, + } + + local damageFromType = { + ["bullet"] = DMG_BULLET, + ["rock"] = DMG_CRUSH, + ["splode"] = DMG_BLAST, + ["fall"] = DMG_FALL, + ["fire"] = DMG_BURN, + ["drown"] = DMG_DROWN, + } + + local distanceToText = { + [CORPSE_KILL_DISTANCE_POINT_BLANK] = "search_kill_distance_point_blank", + [CORPSE_KILL_DISTANCE_CLOSE] = "search_kill_distance_close", + [CORPSE_KILL_DISTANCE_FAR] = "search_kill_distance_far", + } + + local orientationToText = { + [CORPSE_KILL_DIRECTION_FRONT] = "search_kill_from_front", + [CORPSE_KILL_DIRECTION_BACK] = "search_kill_from_back", + [CORPSE_KILL_DIRECTION_SIDE] = "search_kill_from_side", + } + + local floorIDToText = { + [MAT_ANTLION] = "search_floor_antlionss", + [MAT_BLOODYFLESH] = "search_floor_bloodyflesh", + [MAT_CONCRETE] = "search_floor_concrete", + [MAT_DIRT] = "search_floor_dirt", + [MAT_EGGSHELL] = "search_floor_eggshell", + [MAT_FLESH] = "search_floor_flesh", + [MAT_GRATE] = "search_floor_grate", + [MAT_ALIENFLESH] = "search_floor_alienflesh", + [MAT_SNOW] = "search_floor_snow", + [MAT_PLASTIC] = "search_floor_plastic", + [MAT_METAL] = "search_floor_metal", + [MAT_SAND] = "search_floor_sand", + [MAT_FOLIAGE] = "search_floor_foliage", + [MAT_COMPUTER] = "search_floor_computer", + [MAT_SLOSH] = "search_floor_slosh", + [MAT_TILE] = "search_floor_tile", + [MAT_GRASS] = "search_floor_grass", + [MAT_VENT] = "search_floor_vent", + [MAT_WOOD] = "search_floor_wood", + [MAT_DEFAULT] = "search_floor_default", + [MAT_GLASS] = "search_floor_glass", + [MAT_WARPSHIELD] = "search_floor_warpshield", + } + + local hitgroup_to_text = { + [HITGROUP_HEAD] = "search_hitgroup_head", + [HITGROUP_CHEST] = "search_hitgroup_chest", + [HITGROUP_STOMACH] = "search_hitgroup_stomach", + [HITGROUP_RIGHTARM] = "search_hitgroup_rightarm", + [HITGROUP_LEFTARM] = "search_hitgroup_leftarm", + [HITGROUP_RIGHTLEG] = "search_hitgroup_rightleg", + [HITGROUP_LEFTLEG] = "search_hitgroup_leftleg", + [HITGROUP_GEAR] = "search_hitgroup_gear", + } + + local function DamageToText(dmg) + for key, value in pairs(damageToText) do + if utilBitSet(dmg, value) then + return key + end + end + + if utilBitSet(dmg, DMG_DIRECT) then + return "burn" + end + + return "other" + end + + local DataToText = { + last_words = function(data) + if not data.lastWords or data.lastWords == "" then + return + end + + -- only append "--" if there's no ending interpunction + local final = string.match(data.lastWords, "[\\.\\!\\?]$") ~= nil + + return { + title = { + body = "search_title_words", + params = nil, + }, + text = { + { + body = "search_words", + params = { lastwords = data.lastWords .. (final and "" or "--.") }, + }, + }, + } + end, + c4_disarm = function(data) + if not data.c4CutWire or data.c4CutWire <= 0 then + return + end + + return { + title = { + body = "search_title_c4", + params = nil, + }, + text = { + { + body = "search_c4", + params = { num = data.c4CutWire }, + }, + }, + } + end, + dmg = function(data) + if not data.dmgType then + return + end + + local rawText = { + title = { + body = "search_title_dmg_" .. DamageToText(data.dmgType), + params = { amount = data.lastDamage }, + }, + text = { + { + body = "search_dmg_" .. DamageToText(data.dmgType), + params = nil, + }, + }, + } + + if data.killOrientation ~= CORPSE_KILL_NO_DATA then + rawText.text[#rawText.text + 1] = { + body = orientationToText[data.killOrientation], + params = nil, + } + end + + if data.wasHeadshot then + rawText.text[#rawText.text + 1] = { + body = "search_head", + params = nil, + } + end + + return rawText + end, + wep = function(data) + if not data.wep then + return + end + + local wep = util.WeaponForClass(data.wep) + + local wname = wep and wep.PrintName + + if not wname then + return + end + + local rawText = { + title = { + body = wname, + params = nil, + }, + text = { + { + body = "search_weapon", + params = { weapon = wname }, + }, + }, + } + + if data.dist ~= CORPSE_KILL_NO_DATA then + rawText.text[#rawText.text + 1] = { + body = distanceToText[data.killDistance], + params = nil, + } + end + + if data.killHitGroup > 0 then + rawText.text[#rawText.text + 1] = { + body = hitgroup_to_text[data.killHitGroup], + params = nil, + } + end + + return rawText + end, + death_time = function(data) + if not data.deathTime then + return + end + + return { + title = { + body = "search_title_time", + params = nil, + }, + text = { + { + body = "search_time", + params = nil, + }, + }, + } + end, + dna_time = function(data) + if not data.sampleDecayTime or data.sampleDecayTime - CurTime() <= 0 then + return + end + + return { + title = { + body = "search_title_dna", + params = nil, + }, + text = { + { + body = "search_dna", + params = nil, + }, + }, + } + end, + kill_list = function(data) + if not data.killEntityIDList then + return + end + + local num = table.Count(data.killEntityIDList) + + if num == 1 then + local vic = Entity(data.killEntityIDList[1]) + local disconnected = data.killEntityIDList[1] == -1 + + if disconnected or IsValid(vic) and vic:IsPlayer() then + return { + title = { + body = "search_title_kills", + params = nil, + }, + text = { + { + body = "search_kills1", + params = { + player = disconnected and "" or vic:Nick(), + }, + }, + }, + } + end + elseif num > 1 then + local nicks = {} + + for k, idx in pairs(data.killEntityIDList) do + local vic = Entity(idx) + local disconnected = idx == -1 + + if disconnected or IsValid(vic) and vic:IsPlayer() then + nicks[#nicks + 1] = disconnected and "" or vic:Nick() + end + end + + return { + title = { + body = "search_title_kills", + params = nil, + }, + text = { + { + body = "search_kills2", + params = { player = table.concat(nicks, "\n", 1) }, + }, + }, + } + end + end, + last_id = function(data) + if not IsValid(data.lastSeenEnt) or not data.lastSeenEnt:IsPlayer() then + return + end + + return { + title = { + body = "search_title_eyes", + params = nil, + }, + text = { + { + body = "search_eyes", + params = { player = data.lastSeenEnt:Nick() }, + }, + }, + } + end, + floor_surface = function(data) + if not data.killFloorSurface or not floorIDToText[data.killFloorSurface] then + return + end + + return { + title = { + body = "search_title_floor", + params = nil, + }, + text = { + { + body = floorIDToText[data.killFloorSurface], + params = nil, + }, + }, + } + end, + credits = function(data) + if not data.credits or data.credits == 0 then + return + end + + -- special case: mode 2, only shopping roles can see credits + local client = LocalPlayer() + if + cvInspectConfirmMode:GetInt() == 2 + and not bodysearch.CanTakeCredits(client, data.rag) + then + return + end + + return { + title = { + body = "search_title_credits", + params = { credits = data.credits }, + }, + text = { + { + body = "search_credits", + params = { credits = data.credits }, + }, + }, + } + end, + water_level = function(data) + if not data.killWaterLevel or data.killWaterLevel == 0 then + return + end + + return { + title = { + body = "search_title_water", + params = { level = data.killWaterLevel }, + }, + text = { + { + body = "search_water_" .. data.killWaterLevel, + params = nil, + }, + }, + } + end, + } + + local materialDamage = { + ["bullet"] = Material("vgui/ttt/icon_bullet"), + ["rock"] = Material("vgui/ttt/icon_rock"), + ["splode"] = Material("vgui/ttt/icon_splode"), + ["fall"] = Material("vgui/ttt/icon_fall"), + ["fire"] = Material("vgui/ttt/icon_fire"), + ["drown"] = Material("vgui/ttt/icon_drown"), + ["generic"] = Material("vgui/ttt/icon_skull"), + } + + local materialWaterLevel = { + [1] = Material("vgui/ttt/icon_water_1"), + [2] = Material("vgui/ttt/icon_water_2"), + [3] = Material("vgui/ttt/icon_water_3"), + } + + local materialHeadShot = Material("vgui/ttt/icon_head") + local materialDeathTime = Material("vgui/ttt/icon_time") + local materialCredits = Material("vgui/ttt/icon_credits") + local materialDNA = Material("vgui/ttt/icon_wtester") + local materialFloor = Material("vgui/ttt/icon_floor") + local materialC4Disarm = Material("vgui/ttt/icon_code") + local materialLastID = Material("vgui/ttt/icon_lastid") + local materialKillList = Material("vgui/ttt/icon_list") + local materialLastWords = Material("vgui/ttt/icon_halp") + + local function DamageToIconMaterial(data) + -- handle headshots first + if data.wasHeadshot then + return materialHeadShot + end + + -- the damage type + local dmg = data.dmgType + + -- handle most generic damage types + for key, value in pairs(damageFromType) do + if utilBitSet(dmg, value) then + return materialDamage[key] + end + end + + -- special case handling with a fallback for generic damage + if utilBitSet(dmg, DMG_DIRECT) then + return materialDamage["fire"] + else + return materialDamage["generic"] + end + end + + local function TypeToMaterial(type, data) + if type == "wep" then + local wep = util.WeaponForClass(data.wep) + + -- in most cases the inflictor is a weapon and the weapon has a cached + -- material that can be used + if wep.iconMaterial then + return wep.iconMaterial + + -- sometimes the projectile is a custom entity that kills the player + -- which means it is not a weapon with a cached material + else + local mat = Material(wep.Icon) + + if not mat:IsError() then + return mat + end + + -- as a fallback use this missing texture icon + return materialWeaponFallback + end + elseif type == "dmg" then + return DamageToIconMaterial(data) + elseif type == "death_time" then + return materialDeathTime + elseif type == "credits" then + return materialCredits + elseif type == "dna_time" then + return materialDNA + elseif type == "floor_surface" then + return materialFloor + elseif type == "water_level" then + return materialWaterLevel[data.killWaterLevel] + elseif type == "c4_disarm" then + return materialC4Disarm + elseif type == "last_id" then + return materialLastID + elseif type == "kill_list" then + return materialKillList + elseif type == "last_words" then + return materialLastWords + end + end + + local function TypeToIconText(type, data) + if type == "death_time" then + return function() + return utilSimpleTime(CurTime() - data.deathTime, "%02i:%02i") + end + elseif type == "dna_time" then + return function() + return utilSimpleTime(mathMax(0, data.sampleDecayTime - CurTime()), "%02i:%02i") + end + end + end + + local function TypeToColor(type, data) + if type == "dna_time" then + return roles.DETECTIVE.color + elseif type == "credits" then + return COLOR_GOLD + end + end + + bodysearch.searchResultOrder = { + "wep", + "dmg", + "death_time", + "credits", + "dna_time", + "floor_surface", + "water_level", + "c4_disarm", + "last_id", + "kill_list", + "last_words", + } + + --- + -- Generate the search box data from the provided data for a given type. + -- @note This function is used to populate the UI of the bodysearch menu. You probably don't want to use this. + -- @param string type The element type identifier + -- @param SceneData sceneData The scene data that is provided to get the box contents + -- @return nil|table Returns `nil` if no data for the given type is available, table with box content if available + -- @realm client + function bodysearch.GetContentFromData(type, sceneData) + -- make sure type is valid + if not isfunction(DataToText[type]) then + return + end + + local text = DataToText[type](sceneData) + + -- DataToText checks if criteria for display is met, no box should be + -- shown if criteria is not met. + if not text then + return + end + + return { + iconMaterial = TypeToMaterial(type, sceneData), + iconText = TypeToIconText(type, sceneData), + colorBox = TypeToColor(type, sceneData), + text = text, + } + end + + --- + -- Creates a table with icons, text,... out of raw table for the scoreboard + -- @param table raw The raw data + -- @return table A converted search data table + -- @deprecated This function only functions as a fallback for the old scoreboard. Do not use this! + -- @note This function is old and should be redone on a scoreboard rework + -- @realm client + function bodysearch.PreprocSearch(raw) + local search = {} + + local index = 1 + + for i = 1, #bodysearch.searchResultOrder do + local type = bodysearch.searchResultOrder[i] + local searchData = bodysearch.GetContentFromData(type, raw) + + if not searchData then + continue + end + + -- a workaround to build the text for the scoreboard + local text = searchData.text.text + + -- only use the first text entry here + local transText = LANG.GetDynamicTranslation(text[1].body, text[1].params, true) + + if searchData.iconMaterial then + -- note: GetName only returns the material name. This fails if the addon uses a + -- png for its material, we therefore have to check if the material exists on disk + local materialFile = searchData.iconMaterial:GetName() + + if file.Exists("materials/" .. materialFile .. ".png", "GAME") then + materialFile = materialFile .. ".png" + elseif file.Exists("materials/" .. materialFile .. ".jpg", "GAME") then + materialFile = materialFile .. ".jpg" + elseif file.Exists("materials/" .. materialFile .. ".jpeg", "GAME") then + materialFile = materialFile .. ".jpeg" + end + + search[type] = { + img = materialFile, + text = transText, + p = index, -- sorting number + } + + index = index + 1 + end + + -- special cases with icon text + local iconTextFn = TypeToIconText(type, raw) + if isfunction(iconTextFn) then + search[type].text_icon = iconTextFn() + end + end + + --- + -- @realm client + -- stylua: ignore + hook.Run("TTTBodySearchPopulate", search, raw) + + return search + end + + --- + -- This function handles the storing of the streamed search result data. + -- New data will append/overwrite existing data, but not remove it. + -- This functions considers the roles and the settings of the local player and the player that + -- inspected the body. + -- @param SceneData sceneData The table of scene data that should be stored + -- @note The data is stored as `bodySearchResult` on the ragdoll and the owner of the ragdoll + -- @realm client + function bodysearch.StoreSearchResult(sceneData) + if not sceneData.ragOwner then + return + end + + local ply = sceneData.ragOwner + local rag = sceneData.rag + + -- do not store if searching player (client) is spectator + if LocalPlayer():IsSpec() then + return + end + + -- if the currently stored search result is by a public policing role, it should be kept + -- it can be overwritten by another public policing role though + -- data can still be updated, but the previous base is kept + local previousSceneDataBase + if + ply.bodySearchResult + and ply.bodySearchResult.base + and ply.bodySearchResult.base.isPublicPolicingSearch + and not sceneData.base.isPublicPolicingSearch + then + previousSceneDataBase = table.Copy(sceneData.base) + end + + -- merge new data into old data + -- this is useful if a player had good data on a body from another source + -- and now gets updated info on it as it now only replaces the newly added + -- entries + local newData = ply.bodySearchResult or {} + table.Merge(newData, sceneData) + + -- keep the original finder info if previously searched by public policing role + newData.base = previousSceneDataBase or newData.base + + ply.bodySearchResult = newData + + -- also store data in the ragdoll for targetID + if not IsValid(rag) then + return + end + + rag.bodySearchResult = newData + end + + --- + -- Checks if the local player has a detailed search result of a given player. + -- @param Player ply Then player whose search result should be checked + -- @return boolean Returns if the local player has a detailed search result + -- @note A detailed search result means that the player knows the name of the + -- searched body. This is only known if the full data was transmitted. + -- @realm client + function bodysearch.PlayerHasDetailedSearchResult(ply) + -- note: the nick is only transmitted if there is full search data available + return IsValid(ply) and ply.bodySearchResult and ply.bodySearchResult.nick ~= nil + end + + --- + -- Returns the reference to the search result of a given player + -- @param Player ply Then player whose search result should be returned + -- @return table The search result that is available for this player + -- @realm client + function bodysearch.GetSearchResult(ply) + -- initialize the table if not set so that a valid reference can be returned + ply.bodySearchResult = ply.bodySearchResult or {} + + return ply.bodySearchResult + end + + --- + -- Resets the body search result of a given player. + -- @param Player ply Then player whose search result should be reset + -- @realm client + function bodysearch.ResetSearchResult(ply) + if not IsValid(ply) then + return + end + + ply.bodySearchResult = nil + end + + --- + -- Used to trigger the confirmation of a corpse. This might announce that a body was found to the + -- whole server, depending on the server settings. The server ignores this request if the player + -- is not allowed to confirm the corpse. The searching player receives credits if they are able to. + -- @param Entity rag The ragdoll entity whose owner should be confirmed + -- @param[default=0] number searchUID The UID of the search, used for keeping track of searches in the UI + -- @param[default=false] boolean isLongRange Whether the search is a long range search + -- @param[default=false] boolean playerCanTakeCredits Whether or not the player could be able to take credits + -- @realm client + function bodysearch.ClientConfirmsCorpse(rag, searchUID, isLongRange, playerCanTakeCredits) + local clientRoleData = LocalPlayer():GetSubRoleData() + local creditsOnly = playerCanTakeCredits + and cvInspectConfirmMode:GetInt() > 0 + and not (clientRoleData.isPolicingRole and clientRoleData.isPublicRole) + + net.Start("ttt2_client_confirm_corpse") + net.WriteEntity(rag) + net.WriteUInt(searchUID or 0, 16) + net.WriteBool(isLongRange or false) + net.WriteBool(creditsOnly or false) + net.SendToServer() + end + + --- + -- Reports the body as dead. Functions as call for public poling roles so that they know where the corpse + -- can be found. The server ignores this request if the player is not allowed to report the corpse. + -- @param Entity rag The ragdoll entity which should be reported + -- @note: Reporting is what previously was called "call detective" + -- @realm client + function bodysearch.ClientReportsCorpse(rag) + net.Start("ttt2_client_reports_corpse") + net.WriteEntity(rag) + net.SendToServer() + end + + --- + -- Helper function that checks if the body of a given player was confirmed. + -- @param Player ragOwner The dead player whose body might be confirmed + -- @return boolean Returns if their corpse was confirmed + -- @realm client + function bodysearch.IsConfirmed(ragOwner) + return IsValid(ragOwner) and ragOwner:TTT2NETGetBool("body_found", false) + end + + --- + -- Checks if the local player can confirm the body. Depends on the local player and the + -- current body search mode. + -- @return boolean Returns if the local player can confirm the body + -- @realm client + function bodysearch.CanConfirmBody() + local client = LocalPlayer() + + if client:IsSpec() then + return false + end + + -- in mode 0 everyone can confirm corpses + if cvInspectConfirmMode:GetInt() == 0 then + return true + end + + local roleData = client:GetSubRoleData() + + -- in mode 1 and 2 only public policing roles can confirm corpses + if roleData.isPolicingRole and roleData.isPublicRole then + return true + end + + return false + end + + --- + -- Checks if the local player can report the body. Depends on the local player, the dead + -- player and the current body search mode. + -- @param Player ragOwner The dead player whose body might be reported + -- @return boolean Returns if the local player can report the body + -- @note: Reporting is what previously was called "call detective" + -- @realm client + function bodysearch.CanReportBody(ragOwner) + local client = LocalPlayer() + + if client:IsSpec() then + return false + end + + -- in mode 0 the ragdoll has to be found to report body + if cvInspectConfirmMode:GetInt() == 0 and not bodysearch.IsConfirmed(ragOwner) then + return false + end + + return true + end + + -- HOOKS -- + + --- + -- This hook can be used to populate the body search panel. + -- @param table search The search data table + -- @param table raw The raw search data + -- @hook + -- @realm client + function GM:TTTBodySearchPopulate(search, raw) end + + --- + -- This hook can be used to modify the equipment info of a corpse. + -- @param table search The search data table + -- @param table equip The raw equipment table + -- @hook + -- @realm client + function GM:TTTBodySearchEquipment(search, equip) end + + --- + -- This hook is called right before the killer found @{MSTACK} notification + -- is added. + -- @param string finder The nickname of the finder + -- @param string victim The nickname of the victim + -- @hook + -- @realm client + function GM:TTT2ConfirmedBody(finder, victim) end +end + +--- +-- Use this hook to prevent the transfer of credits from a body to a player. Is also used +-- on the client to check if the player is able to take the credits and update the UI. +-- @param Entity rag The ragdoll that is inspected +-- @param Player ply The player attempting to find credits from ragdoll +-- @return nil|boolean Return false to prevent transfer +-- @hook +-- @realm shared +function GM:TTT2CheckFindCredits(ply, rag) end diff --git a/lua/ttt2/libraries/classbuilder.lua b/lua/ttt2/libraries/classbuilder.lua index ecd40c6d4..e1158860c 100644 --- a/lua/ttt2/libraries/classbuilder.lua +++ b/lua/ttt2/libraries/classbuilder.lua @@ -4,7 +4,7 @@ -- @module classbuilder if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local tableDeepInherit = table.DeepInherit @@ -15,31 +15,34 @@ local isfunction = isfunction --- -- recursive inheritance function local function Inherit(class, classTable, passthrough, SpecialCheck) - -- do not do anything if the base has no base class name assigned - if not class.base then - return class - end - - -- get the base class entry from the class table - local baseclass = classTable[class.base] or passthrough[class.base] - - -- if the special inheritance check is failed, no inheritance should take place - if isfunction(SpecialCheck) and not SpecialCheck(class, baseclass) then - return class - end - - local deepbaseclass = classTable[baseclass.base] or passthrough[baseclass.base] - - -- assign a reference to the base class functions - class.BaseClass = baseclass - - if not baseclass.base or (isfunction(SpecialCheck) and not SpecialCheck(baseclass, deepbaseclass)) then - -- the base of the baseclass is non-existent: only inherit one last time - return tableDeepInherit(class, baseclass) - else - -- the base of the baseclass exists: call inheritance function recursively - return tableDeepInherit(class, Inherit(baseclass, classTable, passthrough, SpecialCheck)) - end + -- do not do anything if the base has no base class name assigned + if not class.base then + return class + end + + -- get the base class entry from the class table + local baseclass = classTable[class.base] or passthrough[class.base] + + -- if the special inheritance check is failed, no inheritance should take place + if isfunction(SpecialCheck) and not SpecialCheck(class, baseclass) then + return class + end + + local deepbaseclass = classTable[baseclass.base] or passthrough[baseclass.base] + + -- assign a reference to the base class functions + class.BaseClass = baseclass + + if + not baseclass.base + or (isfunction(SpecialCheck) and not SpecialCheck(baseclass, deepbaseclass)) + then + -- the base of the baseclass is non-existent: only inherit one last time + return tableDeepInherit(class, baseclass) + else + -- the base of the baseclass exists: call inheritance function recursively + return tableDeepInherit(class, Inherit(baseclass, classTable, passthrough, SpecialCheck)) + end end classbuilder = classbuilder or {} @@ -58,59 +61,68 @@ classbuilder = classbuilder or {} -- @param[opt] function PostInherit A callback function that is called for all classes post inheritance -- @return table Returns a table of all the created classes -- @realm shared -function classbuilder.BuildFromFolder(path, realm, scope, OnInitialization, shouldInherit, SpecialCheck, passthrough, PostInherit) - -- In case this function is run on the server but the class should only exist - -- on the client, this function should work only as a proxy. - if SERVER and realm == CLIENT_FILE then - fileloader.LoadFolder(path, false, realm) - - return - end - - -- In the first step make sure to cache the scope variable to keep - -- compatibility with addons that may use the same scope. - local cachedScope = _G[scope] - - -- Now a global scope has to be created. - _G[scope] = {} - - -- This variable will be used to store the loaded classes from the folder. - local classTable = {} - - fileloader.LoadFolder(path, false, realm, function(filePath) - -- get the filename from the file path - local pathArray = stringSplit(filePath, "/") - local name = stringLower(util.GetFileName(pathArray[#pathArray])) - - -- copy the table from the loaded class file - classTable[name] = _G[scope] - - -- reset the scope for the next class - _G[scope] = {} - - if isfunction(OnInitialization) then - OnInitialization(classTable[name], path, name) - end - end) - - -- In the last step, the cached scope should be reset to the global scope. - _G[scope] = cachedScope - - -- Now that all classes are loaded, they should inherit from their base - -- class if enabled. - if shouldInherit then - for name, class in pairs(classTable) do - classTable[name] = Inherit(class, classTable, passthrough, SpecialCheck) - end - end - - -- call callback function for all classes once they are all set up and finished - -- their inheritance - if isfunction(PostInherit) then - for name, class in pairs(classTable) do - PostInherit(class, path, name) - end - end - - return classTable +function classbuilder.BuildFromFolder( + path, + realm, + scope, + OnInitialization, + shouldInherit, + SpecialCheck, + passthrough, + PostInherit +) + -- In case this function is run on the server but the class should only exist + -- on the client, this function should work only as a proxy. + if SERVER and realm == CLIENT_FILE then + fileloader.LoadFolder(path, false, realm) + + return + end + + -- In the first step make sure to cache the scope variable to keep + -- compatibility with addons that may use the same scope. + local cachedScope = _G[scope] + + -- Now a global scope has to be created. + _G[scope] = {} + + -- This variable will be used to store the loaded classes from the folder. + local classTable = {} + + fileloader.LoadFolder(path, false, realm, function(filePath) + -- get the filename from the file path + local pathArray = stringSplit(filePath, "/") + local name = stringLower(util.GetFileName(pathArray[#pathArray])) + + -- copy the table from the loaded class file + classTable[name] = _G[scope] + + -- reset the scope for the next class + _G[scope] = {} + + if isfunction(OnInitialization) then + OnInitialization(classTable[name], path, name) + end + end) + + -- In the last step, the cached scope should be reset to the global scope. + _G[scope] = cachedScope + + -- Now that all classes are loaded, they should inherit from their base + -- class if enabled. + if shouldInherit then + for name, class in pairs(classTable) do + classTable[name] = Inherit(class, classTable, passthrough, SpecialCheck) + end + end + + -- call callback function for all classes once they are all set up and finished + -- their inheritance + if isfunction(PostInherit) then + for name, class in pairs(classTable) do + PostInherit(class, path, name) + end + end + + return classTable end diff --git a/lua/ttt2/libraries/credits.lua b/lua/ttt2/libraries/credits.lua index fd52ff6d2..864184a32 100644 --- a/lua/ttt2/libraries/credits.lua +++ b/lua/ttt2/libraries/credits.lua @@ -3,7 +3,9 @@ -- @author Mineotopia -- @module credits -if CLIENT then return end -- this is a serverside-only module +if CLIENT then + return +end -- this is a serverside-only module credits = credits or {} @@ -15,121 +17,149 @@ credits = credits or {} -- @param Player attacker The player that killed -- @realm server function credits.HandleKillCreditsAward(victim, attacker) - if GetRoundState() ~= ROUND_ACTIVE or not IsValid(victim) - or not IsValid(attacker) or not attacker:IsPlayer() or not attacker:IsActive() - then return end - - --- - -- @realm server - if hook.Run("TTT2CheckCreditAward", victim, attacker) == false then return end - - local roleDataAttacker = attacker:GetSubRoleData() - local roleDataVictim = victim:GetSubRoleData() - - -- HANDLE CREDITS FOR KILL - if roleDataVictim.isPublicRole and not victim:IsInTeam(attacker) and roleDataAttacker:IsAwardedCreditsForKill() then - -- A high profile role, such as a policing role, is a dangerous target to kill. - -- If a player from a different team is able to kill them, they should be awarded - -- with a bonus to stock up their equipment. - - local creditsAmount = GetConVar("ttt_credits_award_kill"):GetInt() - - attacker:AddCredits(creditsAmount) + if + GetRoundState() ~= ROUND_ACTIVE + or not IsValid(victim) + or not IsValid(attacker) + or not attacker:IsPlayer() + or not attacker:IsActive() + then + return + end + + --- + -- @realm server + -- stylua: ignore + if hook.Run("TTT2CheckCreditAward", victim, attacker) == false then return end + + local roleDataAttacker = attacker:GetSubRoleData() + local roleDataVictim = victim:GetSubRoleData() + + -- HANDLE CREDITS FOR KILL + if + roleDataVictim.isPublicRole + and not victim:IsInTeam(attacker) + and roleDataAttacker:IsAwardedCreditsForKill() + then + -- A high profile role, such as a policing role, is a dangerous target to kill. + -- If a player from a different team is able to kill them, they should be awarded + -- with a bonus to stock up their equipment. + + local creditsAmount = GetConVar("ttt_credits_award_kill"):GetInt() + + attacker:AddCredits(creditsAmount) hook.Run("TTT2ReceivedKillCredits", victim, attacker, creditsAmount) - LANG.Msg(attacker, "credit_kill", {num = creditsAmount, role = LANG.NameParam(victim:GetRoleString())}, MSG_MSTACK_ROLE) - end - - -- HANDLE CREDITS FOR CERTAIN AMOUNT OF PLAYERS DEAD - - -- This is a special case scenario where for every kill it is checked for every player, - -- how many players are dead from different teams than their own team. - local plys = player.GetAll() - local plysAmount = #plys - local plysByTeams = {} - - -- At first a table with players sorted by teams is created, because those rewards are teambased. - for i = 1, plysAmount do - local ply = plys[i] - - -- ignore forced spectator players - if ply:IsSpec() and ply:GetForceSpec() then continue end - - local team = ply:GetTeam() - - plysByTeams[team] = plysByTeams[team] or {} - plysByTeams[team][#plysByTeams[team] + 1] = ply - end - - -- Now iterate over the team table and grant credits if limits are reached - for team, plysByTeam in pairs(plysByTeams) do - local teamTable = TEAMS[team] - - -- make sure that the team wasn't already awarded and their award is limited to once - if teamTable.wasAwardedCreditsDead and not GetConVar("ttt_credits_award_repeat"):GetBool() then continue end - - -- check if a reward should be issued - local plysEnemyAlive = 0 - local plysEnemyDead = 0 - - for i = 1, plysAmount do - -- now iterate over all players to count them - - local plyToCheck = plys[i] - - if plyToCheck:GetTeam() ~= team then - if plyToCheck == victim or not plyToCheck:IsTerror() then - -- Note: The player that just died is still counted as alive, so check them specially. - plysEnemyDead = plysEnemyDead + 1 - elseif not plyToCheck:GetForceSpec() then - -- if a player is not in the terror team, they could also be a forced spectator - plysEnemyAlive = plysEnemyAlive + 1 - end - end - end - - -- only repeat-award if we have reached the pct again since last time - local plysEnemyDeadModified = plysEnemyDead - (teamTable.deadPlayersOnAward or 0) - - -- now calculate the percentage of dead players from the other teams - local pctDead = plysEnemyDeadModified / (plysEnemyDead + plysEnemyAlive) - - -- only reward the player if the percentage of dead players is bigger than the threshold - if pctDead < GetConVar("ttt_credits_award_pct"):GetFloat() then continue end - - -- now the team table is update since a new award was given - teamTable.wasAwardedCreditsDead = true - teamTable.deadPlayersOnAward = plysEnemyDead - - local creditsAmount = GetConVar("ttt_credits_award_size"):GetInt() - - -- now give the award to the players - for i = 1, #plysByTeam do - local plyToAward = plysByTeam[i] - - -- first check that the player is actually alive - if not plyToAward:IsTerror() then continue end - - -- second make sure that the player's role can receive credits for dead players - if not plyToAward:GetSubRoleData():IsAwardedCreditsForPlayerDead() then continue end - - -- now reward their player for their good game - plyToAward:AddCredits(creditsAmount) + LANG.Msg( + attacker, + "credit_kill", + { num = creditsAmount, role = LANG.NameParam(victim:GetRoleString()) }, + MSG_MSTACK_ROLE + ) + end + + -- HANDLE CREDITS FOR CERTAIN AMOUNT OF PLAYERS DEAD + + -- This is a special case scenario where for every kill it is checked for every player, + -- how many players are dead from different teams than their own team. + local plys = player.GetAll() + local plysAmount = #plys + local plysByTeams = {} + + -- At first a table with players sorted by teams is created, because those rewards are teambased. + for i = 1, plysAmount do + local ply = plys[i] + + -- ignore forced spectator players + if ply:IsSpec() and ply:GetForceSpec() then + continue + end + + local team = ply:GetTeam() + + plysByTeams[team] = plysByTeams[team] or {} + plysByTeams[team][#plysByTeams[team] + 1] = ply + end + + -- Now iterate over the team table and grant credits if limits are reached + for team, plysByTeam in pairs(plysByTeams) do + local teamTable = TEAMS[team] + + -- make sure that the team wasn't already awarded and their award is limited to once + if + teamTable.wasAwardedCreditsDead and not GetConVar("ttt_credits_award_repeat"):GetBool() + then + continue + end + + -- check if a reward should be issued + local plysEnemyAlive = 0 + local plysEnemyDead = 0 + + for i = 1, plysAmount do + -- now iterate over all players to count them + + local plyToCheck = plys[i] + + if plyToCheck:GetTeam() ~= team then + if plyToCheck == victim or not plyToCheck:IsTerror() then + -- Note: The player that just died is still counted as alive, so check them specially. + plysEnemyDead = plysEnemyDead + 1 + elseif not plyToCheck:GetForceSpec() then + -- if a player is not in the terror team, they could also be a forced spectator + plysEnemyAlive = plysEnemyAlive + 1 + end + end + end + + -- only repeat-award if we have reached the pct again since last time + local plysEnemyDeadModified = plysEnemyDead - (teamTable.deadPlayersOnAward or 0) + + -- now calculate the percentage of dead players from the other teams + local pctDead = plysEnemyDeadModified / (plysEnemyDead + plysEnemyAlive) + + -- only reward the player if the percentage of dead players is bigger than the threshold + if pctDead < GetConVar("ttt_credits_award_pct"):GetFloat() then + continue + end + + -- now the team table is update since a new award was given + teamTable.wasAwardedCreditsDead = true + teamTable.deadPlayersOnAward = plysEnemyDead + + local creditsAmount = GetConVar("ttt_credits_award_size"):GetInt() + + -- now give the award to the players + for i = 1, #plysByTeam do + local plyToAward = plysByTeam[i] + + -- first check that the player is actually alive + if not plyToAward:IsTerror() then + continue + end + + -- second make sure that the player's role can receive credits for dead players + if not plyToAward:GetSubRoleData():IsAwardedCreditsForPlayerDead() then + continue + end + + -- now reward their player for their good game + plyToAward:AddCredits(creditsAmount) hook.Run("TTT2ReceivedTeamAwardCredits", plyToAward, creditsAmount) - LANG.Msg(plyToAward, "credit_all", {num = creditsAmount}, MSG_MSTACK_ROLE) - end - end + LANG.Msg(plyToAward, "credit_all", { num = creditsAmount }, MSG_MSTACK_ROLE) + end + end end --- -- Resets the team states that get set for the credits distributions. -- @realm server function credits.ResetTeamStates() - for _, teamTable in pairs(TEAMS) do - teamTable.wasAwardedCreditsDead = nil - teamTable.deadPlayersOnAward = nil - end + for _, teamTable in pairs(TEAMS) do + teamTable.wasAwardedCreditsDead = nil + teamTable.deadPlayersOnAward = nil + end end diff --git a/lua/ttt2/libraries/database.lua b/lua/ttt2/libraries/database.lua index 2a052151c..fcb5312f8 100644 --- a/lua/ttt2/libraries/database.lua +++ b/lua/ttt2/libraries/database.lua @@ -7,15 +7,13 @@ database = database or {} if SERVER then - AddCSLuaFile() + AddCSLuaFile() - util.AddNetworkString("TTT2SynchronizeDatabase") + util.AddNetworkString("TTT2SynchronizeDatabase") end local messageIdentifier = 0 -local maxBytesPerMessage = 32 * 1000 -- Can be up to 65.533KB see: https://wiki.facepunch.com/gmod/net.Start -local uIntBits = 8 -- we can synchronize up to 2 ^ uIntBits - 1 different sqlTables -local maxUInt = 2 ^ uIntBits - 1 +local identifiersReceived = 0 -- Save for hotreloadability database.registeredDatabases = database.registeredDatabases or {} @@ -49,7 +47,6 @@ local INDEX_NONE = "none" -- Stores the data and the players it is send to local dataStore = {} -local sendDataFunctions = {} local receiveDataFunctions = {} local sendRequestsNextUpdate = false @@ -82,34 +79,67 @@ local callbackIdentifiers = database.callbackIdentifiers -- General Shared functions -- +--- +-- Creates a databaseElement combining all infos necessary to make changes to the serverside sql database +-- @param string accessName the networkable name of the sql table +-- @param string itemName the name or primaryKey of the item inside of the sql table +-- @param string key the name of the key in the database +-- @return table Return all necessary infos for database access +-- @realm shared +function DatabaseElement(accessName, itemName, key) + return { name = accessName, itemName = itemName, key = key } +end + --- -- Call this function if a value was received -- @param number index The local index of the database -- @param string itemName The name of the item in the database --- @param string key The name of the key in the database +-- @param[opt] string key The name of the key in the database, or all keys -- @realm shared -- @internal local function ValueReceived(index, itemName, key) - local receiver = receivedValues[index] or {} - - receiver[itemName] = receiver[itemName] or {} - receiver[itemName][key] = true - - receivedValues[index] = receiver + local receiver = receivedValues[index] or {} + + receiver[itemName] = receiver[itemName] or {} + if isstring(key) then + receiver[itemName][key] = true + else + local keys = registeredDatabases[index].keys + for checkKey in pairs(keys) do + receiver[itemName][checkKey] = true + end + end + + receivedValues[index] = receiver end --- -- Check if a value was already received via network -- @param number index the local index of the database -- @param string itemName the name of the item in the database --- @param string key the name of the key in the database +-- @param[opt] string key the name of the key in the database, otherwise check all keys -- @realm shared -- @internal local function IsValueReceived(index, itemName, key) - return receivedValues[index] - and receivedValues[index][itemName] - and receivedValues[index][itemName][key] - or false + if not receivedValues[index] or not receivedValues[index][itemName] then + return false + end + + local isReceived = true + if not isstring(key) then + local keys = registeredDatabases[index].keys + for checkKey in pairs(keys) do + if not receivedValues[index][itemName][checkKey] then + isReceived = false + + break + end + end + elseif not receivedValues[index][itemName][key] then + isReceived = false + end + + return isReceived end --- @@ -120,34 +150,43 @@ end -- @return any the defaultValue -- @realm shared function database.GetDefaultValue(accessName, itemName, key) - local index = nameToIndex[accessName] + local index = nameToIndex[accessName] - if not index then - ErrorNoHalt("[TTT2] database.GetDefaultValue failed. The registered Database of " .. accessName .. " is not available or is not synced.") + if not index then + ErrorNoHaltWithStack( + "[TTT2] database.GetDefaultValue failed. The registered Database of " + .. accessName + .. " is not available or is not synced." + ) - return - end + return + end - local dataTable = registeredDatabases[index] - local defaultTable = dataTable.defaultData + local dataTable = registeredDatabases[index] + local defaultTable = dataTable.defaultData - -- Try to get an item-specific default value - local defaultValue = defaultTable[itemName] and defaultTable[itemName][key] + -- Try to get an item-specific default value + local defaultValue = defaultTable[itemName] and defaultTable[itemName][key] - -- Otherwise get the key-specific default value - if defaultValue == nil then - local savingKey = dataTable.keys[key] + -- Otherwise get the key-specific default value + if defaultValue == nil then + local savingKey = dataTable.keys[key] - if not istable(savingKey) then - ErrorNoHalt("[TTT2] database.GetDefaultValue failed. The registered Database of " .. accessName .. " doesnt have a key named " .. key) + if not istable(savingKey) then + ErrorNoHaltWithStack( + "[TTT2] database.GetDefaultValue failed. The registered Database of " + .. accessName + .. " doesnt have a key named " + .. key + ) - return - end + return + end - defaultValue = savingKey.default - end + defaultValue = savingKey.default + end - return defaultValue + return defaultValue end --- @@ -159,52 +198,62 @@ end -- @realm shared -- @internal local function OnChange(index, itemName, key, newValue) - local dataTable = registeredDatabases[index] - - -- Cache the old Value and store the newValue instead - local storedData = dataTable.storedData - local dataEntry = storedData[itemName] or {} - local oldValue = dataEntry[key] - dataEntry[key] = newValue - storedData[itemName] = dataEntry - - if oldValue == newValue then return end - - -- Get all callbacks - local accessName = dataTable.accessName - local cache = callbackCache[accessName] - - if not cache then return end - - -- Call all callbacks - local itemNameTable = {itemName, allCallbackString} - for i = 1, #itemNameTable do - local tempItemName = itemNameTable[i] - - local funcCache = cache[tempItemName] - - if not istable(funcCache) then continue end - - local keyTable = {key, allCallbackString} - for j = 1, #keyTable do - local tempKey = keyTable[j] - - local funcKeyCache = funcCache[tempKey] - - if not istable(funcKeyCache) then continue end - - -- Execute callbacks - for identifier, functions in pairs(funcKeyCache) do - for k = 1, #functions do - local func = functions[k] - - if not isfunction(func) then continue end - - func(accessName, itemName, key, oldValue, newValue) - end - end - end - end + local dataTable = registeredDatabases[index] + + -- Cache the old Value and store the newValue instead + local storedData = dataTable.storedData + local dataEntry = storedData[itemName] or {} + local oldValue = dataEntry[key] + dataEntry[key] = newValue + storedData[itemName] = dataEntry + + if oldValue == newValue then + return + end + + -- Get all callbacks + local accessName = dataTable.accessName + local cache = callbackCache[accessName] + + if not cache then + return + end + + -- Call all callbacks + local itemNameTable = { itemName, allCallbackString } + for i = 1, #itemNameTable do + local tempItemName = itemNameTable[i] + + local funcCache = cache[tempItemName] + + if not istable(funcCache) then + continue + end + + local keyTable = { key, allCallbackString } + for j = 1, #keyTable do + local tempKey = keyTable[j] + + local funcKeyCache = funcCache[tempKey] + + if not istable(funcKeyCache) then + continue + end + + -- Execute callbacks + for identifier, functions in pairs(funcKeyCache) do + for k = 1, #functions do + local func = functions[k] + + if not isfunction(func) then + continue + end + + func(accessName, itemName, key, oldValue, newValue) + end + end + end + end end --- @@ -214,15 +263,20 @@ end -- @realm shared -- @internal local function ResetDatabase(index) - local dataTable = registeredDatabases[index] - - if dataTable and not table.IsEmpty(dataTable.storedData) then - for itemName, keys in pairs(dataTable.storedData) do - for key in pairs(keys) do - OnChange(index, itemName, key, database.GetDefaultValue(dataTable.accessName, itemName, key)) - end - end - end + local dataTable = registeredDatabases[index] + + if dataTable and not table.IsEmpty(dataTable.storedData) then + for itemName, keys in pairs(dataTable.storedData) do + for key in pairs(keys) do + OnChange( + index, + itemName, + key, + database.GetDefaultValue(dataTable.accessName, itemName, key) + ) + end + end + end end --- @@ -235,41 +289,43 @@ end -- @param[opt] string identifier An identifier by which you can remove the callback more granular -- @realm shared function database.AddChangeCallback(accessName, itemName, key, callback, identifier) - -- Allow every accessName in case the database is only later registered - if not isstring(accessName) or not isfunction(callback) then return end - - -- If no itemName is given, subscribe to changes of all items - if not isstring(itemName) then - itemName = allCallbackString - end - - -- If no key is given, subscribe to changes of all keys of the item - if not isstring(key) then - key = allCallbackString - end - - -- If no identifier is given just choose one - if not isstring(identifier) then - identifier = allCallbackString - end - - -- Save callback in cache - local cache = callbackCache[accessName] or {} - - cache[itemName] = cache[itemName] or {} - cache[itemName][key] = cache[itemName][key] or {} - cache[itemName][key][identifier] = cache[itemName][key][identifier] or {} - cache[itemName][key][identifier][#cache[itemName][key][identifier] + 1] = callback - - callbackCache[accessName] = cache - - -- Index identifiers for faster removal access later - callbackIdentifiers[identifier] = callbackIdentifiers[identifier] or {} - callbackIdentifiers[identifier][#callbackIdentifiers[identifier] + 1] = { - accessName = accessName, - itemName = itemName, - key = key - } + -- Allow every accessName in case the database is only later registered + if not isstring(accessName) or not isfunction(callback) then + return + end + + -- If no itemName is given, subscribe to changes of all items + if not isstring(itemName) then + itemName = allCallbackString + end + + -- If no key is given, subscribe to changes of all keys of the item + if not isstring(key) then + key = allCallbackString + end + + -- If no identifier is given just choose one + if not isstring(identifier) then + identifier = allCallbackString + end + + -- Save callback in cache + local cache = callbackCache[accessName] or {} + + cache[itemName] = cache[itemName] or {} + cache[itemName][key] = cache[itemName][key] or {} + cache[itemName][key][identifier] = cache[itemName][key][identifier] or {} + cache[itemName][key][identifier][#cache[itemName][key][identifier] + 1] = callback + + callbackCache[accessName] = cache + + -- Index identifiers for faster removal access later + callbackIdentifiers[identifier] = callbackIdentifiers[identifier] or {} + callbackIdentifiers[identifier][#callbackIdentifiers[identifier] + 1] = { + accessName = accessName, + itemName = itemName, + key = key, + } end --- @@ -281,64 +337,82 @@ end -- @param[opt] string identifier The identifier by which the callbacks to remove are filtered -- @realm shared function database.RemoveChangeCallback(accessName, itemName, key, identifier) - callbacks = callbackIdentifiers[identifier] - - if not accessName or not istable(callbacks) then return end - - local cache = callbackCache[accessName] - - if not istable(cache) then return end - - -- If no itemName is given, remove callbacks of all items - local skipWrongItemName = true - if not isstring(itemName) then - skipWrongItemName = false - end - - -- If no key is given, remove callbacks of all keys of the item - local skipWrongKey = true - if not isstring(key) then - skipWrongKey = false - end - - for i = #callbacks, 1, -1 do - callback = callbacks[i] - - -- AccesName has to be the same, because registrating callbacks for all sql tables is not allowed - if callback.accessName ~= accessName then continue end - - -- If neither itemName nor the key fit, skip that callback unless you want to remove all - local cItemName = callback.itemName - if cItemName ~= itemName and skipWrongItemName then continue end - - local cKey = callback.key - if cKey ~= key and skipWrongKey then continue end - - table.remove(callbacks, i) - - if not istable(cache[cItemName]) or not istable(cache[cItemName][cKey]) then continue end - cache[cItemName][cKey][identifier] = nil - - -- Delete empty table entries in callbackCache - if not table.IsEmpty(cache[cItemName][cKey]) then continue end - cache[cItemName][cKey] = nil - - if not table.IsEmpty(cache[cItemName]) then continue end - cache[cItemName] = nil - - if not table.IsEmpty(cache) then continue end - callbackCache[accessName] = nil - end - - -- If no callbacks for that identifier are left, delete it - if #callbacks < 1 then - callbackIdentifiers[identifier] = nil - end + callbacks = callbackIdentifiers[identifier] + + if not accessName or not istable(callbacks) then + return + end + + local cache = callbackCache[accessName] + + if not istable(cache) then + return + end + + -- If no itemName is given, remove callbacks of all items + local skipWrongItemName = true + if not isstring(itemName) then + skipWrongItemName = false + end + + -- If no key is given, remove callbacks of all keys of the item + local skipWrongKey = true + if not isstring(key) then + skipWrongKey = false + end + + for i = #callbacks, 1, -1 do + local callback = callbacks[i] + + -- AccesName has to be the same, because registrating callbacks for all sql tables is not allowed + if callback.accessName ~= accessName then + continue + end + + -- If neither itemName nor the key fit, skip that callback unless you want to remove all + local cItemName = callback.itemName + if cItemName ~= itemName and skipWrongItemName then + continue + end + + local cKey = callback.key + if cKey ~= key and skipWrongKey then + continue + end + + table.remove(callbacks, i) + + if not istable(cache[cItemName]) or not istable(cache[cItemName][cKey]) then + continue + end + cache[cItemName][cKey][identifier] = nil + + -- Delete empty table entries in callbackCache + if not table.IsEmpty(cache[cItemName][cKey]) then + continue + end + cache[cItemName][cKey] = nil + + if not table.IsEmpty(cache[cItemName]) then + continue + end + cache[cItemName] = nil + + if not table.IsEmpty(cache) then + continue + end + callbackCache[accessName] = nil + end + + -- If no callbacks for that identifier are left, delete it + if #callbacks < 1 then + callbackIdentifiers[identifier] = nil + end end --- -- Converts the given value of a database with its key --- @warning this function shall be removed and replaced with orm conversion. +-- @warning this function shall be removed and replaced with orm conversion. -- `sql.GetParsedData` is not compatible with orm as sql.SQLStr is used (e.g. converts `false` => "false" -- while the other uses "0" and "1" for booleans -- @param string value the value to convert with a key @@ -348,26 +422,30 @@ end -- @realm shared -- @internal local function ConvertValueWithKey(value, accessName, key) - if not value or value == "nil" or value == "NULL" then return end + if value == nil or value == "nil" or value == "NULL" then + return + end - local index = nameToIndex[accessName] + local index = nameToIndex[accessName] - if not index then return end + if not index then + return + end - local data = registeredDatabases[index].keys[key] + local data = registeredDatabases[index].keys[key] - if data.typ == "bool" then - value = tobool(value) - elseif data.typ == "number" then - value = tonumber(value) - end + if data.typ == "bool" then + value = tobool(value) + elseif data.typ == "number" or data.typ == "float" then + value = tonumber(value) + end - return value + return value end --- -- Converts the given table of a database --- @warning this function shall be removed and replaced with orm conversion. +-- @warning this function shall be removed and replaced with orm conversion. -- `sql.GetParsedData` is not compatible with orm as sql.SQLStr is used (e.g. converts `false` => "false" -- while the other uses "0" and "1" for booleans -- @param table dataTable the table to convert with their respective keys @@ -375,17 +453,21 @@ end -- @realm shared -- @internal local function ConvertTable(dataTable, accessName) - if not istable(dataTable) then return end + if not istable(dataTable) then + return + end - local index = nameToIndex[accessName] + local index = nameToIndex[accessName] - if not index then return end + if not index then + return + end - local keys = registeredDatabases[index].keys + local keys = registeredDatabases[index].keys - for key, data in pairs(keys) do - dataTable[key] = ConvertValueWithKey(dataTable[key], accessName, key) - end + for key, data in pairs(keys) do + dataTable[key] = ConvertValueWithKey(dataTable[key], accessName, key) + end end --- @@ -393,311 +475,169 @@ end -- To be able to directly see message sending- and receive-structure between server and client -- They are always paired by either client-Send and server-Receive or server-Send and client-Receive --- Client send and receive functions -local clientSendFunctions = {} +-- Client and server receive functions local clientReceiveFunctions = {} - --- Server send and receive functions -local serverSendFunctions = {} local serverReceiveFunctions = {} --- --- Send query for registered databases to server --- @param table data contains only the messageIdentifier here +-- An identifier was received, checks if all sent identifiers were received and resets the identifiers +-- Should make sure, that it doesnt go to infinity -- @realm client -- @internal -clientSendFunctions[MESSAGE_REGISTER] = function(data) - net.WriteUInt(data, uIntBits) +local function receivedIdentifier() + identifiersReceived = identifiersReceived + 1 + + if identifiersReceived < messageIdentifier then + return + end + + identifiersReceived = 0 + messageIdentifier = 0 end --- -- Receive query for registered databases from client -- and sync them +-- @param table data = {identifier} -- @param string plyID64 the playerID64 of the player who sent the message -- @realm server -- @internal -serverReceiveFunctions[MESSAGE_REGISTER] = function(plyID64) - database.SyncRegisteredDatabases(plyID64, net.ReadUInt(uIntBits)) -end - ---- --- Send requested registered databases to client --- @param table data contains identifier, tableCount, index here --- @realm server --- @internal -serverSendFunctions[MESSAGE_REGISTER] = function(data) - local databaseInfo = registeredDatabases[data.index] - - net.WriteUInt(data.index, uIntBits) - net.WriteString(databaseInfo.accessName) - net.WriteTable(databaseInfo.keys) - net.WriteTable(databaseInfo.data) - - -- AdditionalInfo determines if there is more than one database registered and sent - -- This makes sure, that the client doesnt start to use the databases before all databases are received - local sendAdditionalInfo = data.identifier ~= nil - - net.WriteBool(sendAdditionalInfo) - - if sendAdditionalInfo then - net.WriteUInt(data.identifier, uIntBits) - net.WriteUInt(data.tableCount, uIntBits) - end +serverReceiveFunctions[MESSAGE_REGISTER] = function(data, plyID64) + database.SyncRegisteredDatabases(plyID64, data.identifier) end --- -- Receive requested registered databases from server -- and cache them as well as call all cached functions, that are waiting for the databases +-- @param table data = {index, accessName, savingKeys, additionalData, sentAdditionalInfo, identifier, databaseSize} -- @realm client -- @internal -clientReceiveFunctions[MESSAGE_REGISTER] = function() - local index = net.ReadUInt(uIntBits) - local accessName = net.ReadString() - local savingKeys = net.ReadTable() - local additionalData = net.ReadTable() - - nameToIndex[accessName] = index - registeredDatabases[index] = { - accessName = accessName, - keys = savingKeys, - data = additionalData, - storedData = {}, - defaultData = {} - } - - -- If more than one database will be send, then sentAdditionalInfo is true - local sentAdditionalInfo = net.ReadBool() - - if sentAdditionalInfo then - local identifier = net.ReadUInt(uIntBits) - local tableCount = net.ReadUInt(uIntBits) - - -- Only call the cached functions, when all databases are succesfully registered clientside - if table.Count(registeredDatabases) == tableCount then - functionCache[identifier]() - end - end -end - ---- --- Send query for getting a value to server --- @param table data contains only the messageIdentifier here --- @realm client --- @internal -clientSendFunctions[MESSAGE_GET_VALUE] = function(data) - local request = requestCache[data] - - net.WriteUInt(data, uIntBits) - net.WriteUInt(request.index, uIntBits) - - net.WriteString(request.itemName) - net.WriteString(request.key) +clientReceiveFunctions[MESSAGE_REGISTER] = function(data) + nameToIndex[data.accessName] = data.index + registeredDatabases[data.index] = { + accessName = data.accessName, + keys = data.savingKeys, + data = data.additionalData, + storedData = {}, + defaultData = {}, + } + + if data.identifier and table.Count(registeredDatabases) == data.databaseSize then + -- Only call the cached functions, when all databases are succesfully registered clientside + functionCache[data.identifier]() + receivedIdentifier() + end end --- -- Receive query for getting a value from client -- and returning it +-- @param table data = {index, accessName, itemName, key, identifier} -- @param string plyID64 the playerID64 of the player who sent the message -- @realm server -- @internal -serverReceiveFunctions[MESSAGE_GET_VALUE] = function(plyID64) - local data = { - plyID64 = plyID64, - identifier = net.ReadUInt(uIntBits), - index = net.ReadUInt(uIntBits) - } - - data.itemName = net.ReadString() - data.key = net.ReadString() - - database.ReturnGetValue(data) -end - ---- --- Send requested value to client --- if available/isSuccess --- @param table data contains identifier, isSuccess and value here --- @realm server --- @internal -serverSendFunctions[MESSAGE_GET_VALUE] = function(data) - net.WriteUInt(data.identifier, uIntBits) - net.WriteBool(data.isSuccess) - - if data.isSuccess then - net.WriteString(tostring(data.value)) - end -end +--serverReceiveFunctions[MESSAGE_GET_VALUE] = database.ReturnGetValue --- -- Receive requested value from server -- Store it, mark value as received and call function cache +-- @param table data = {isSuccess, value, identifier} -- @realm client -- @internal -clientReceiveFunctions[MESSAGE_GET_VALUE] = function() - local identifier = net.ReadUInt(uIntBits) - local isSuccess = net.ReadBool() - local request = requestCache[identifier] - local value - - if isSuccess then - value = net.ReadString() - value = ConvertValueWithKey(value, request.accessName, request.key) - - local storedData = registeredDatabases[request.index].storedData - - local storedItemData = storedData[request.itemName] or {} - storedItemData[request.key] = value - - storedData[request.itemName] = storedItemData - end - - ValueReceived(request.index, request.itemName, request.key) - functionCache[identifier](isSuccess, value) -end - ---- --- Send query for setting a value to server --- @param table data contains index, itemName, key and value here --- @realm client --- @internal -clientSendFunctions[MESSAGE_SET_VALUE] = function(data) - net.WriteUInt(data.index, uIntBits) - net.WriteString(data.itemName) - net.WriteString(data.key) - net.WriteString(tostring(data.value)) +clientReceiveFunctions[MESSAGE_GET_VALUE] = function(data) + local request = requestCache[data.identifier] + + if data.isSuccess then + local storedData = registeredDatabases[request.index].storedData + local storedItemData + + if request.key then + storedItemData = storedData[request.itemName] or {} + storedItemData[request.key] = data.value + else + storedItemData = data.value + end + + storedData[request.itemName] = storedItemData + end + + ValueReceived(request.index, request.itemName, request.key) + functionCache[data.identifier](data.isSuccess, data.value) + receivedIdentifier() end --- -- Receive query for setting a value from client --- and set that value in the database if player is a superadmin +-- and set that value in the database if they have access-rights +-- @param table data = {index, itemName, key, value} -- @param string plyID64 the playerID64 of the player who sent the message -- @realm server -- @internal -serverReceiveFunctions[MESSAGE_SET_VALUE] = function(plyID64) - local index = net.ReadUInt(uIntBits) - local itemName = net.ReadString() - local key = net.ReadString() - local value = net.ReadString() - - local accessName = registeredDatabases[index].accessName - value = ConvertValueWithKey(value, accessName, key) - - database.SetValue(accessName, itemName, key, value, plyID64) -end - ---- --- Send set value to client --- @param table data contains index, itemName, key, value here --- @realm server --- @internal -serverSendFunctions[MESSAGE_SET_VALUE] = function(data) - net.WriteUInt(data.index, uIntBits) - net.WriteString(data.itemName) - net.WriteString(data.key) - net.WriteString(tostring(data.value)) -end - -clientReceiveFunctions[MESSAGE_SET_VALUE] = function() - local index = net.ReadUInt(uIntBits) - local itemName = net.ReadString() - local key = net.ReadString() - local value = net.ReadString() +serverReceiveFunctions[MESSAGE_SET_VALUE] = function(data, plyID64) + local accessName = registeredDatabases[data.index].accessName - value = ConvertValueWithKey(value, registeredDatabases[index].accessName, key) - OnChange(index, itemName, key, value) - ValueReceived(index, itemName, key) + database.SetValue(accessName, data.itemName, data.key, data.value, plyID64) end --- --- Send query for resetting the database to server --- @param table data contains index +-- Receives changed value from server +-- Mark value as received and call OnChange-function +-- @param table data = {index, itemName, key, value} -- @realm client -- @internal -clientSendFunctions[MESSAGE_RESET] = function(data) - net.WriteUInt(data.index, uIntBits) +clientReceiveFunctions[MESSAGE_SET_VALUE] = function(data) + OnChange(data.index, data.itemName, data.key, data.value) + ValueReceived(data.index, data.itemName, data.key) end --- -- Receive query for resetting the database from client +-- @param table data = {index} -- @param string plyID64 the playerID64 of the player who sent the message -- @realm server -- @internal -serverReceiveFunctions[MESSAGE_RESET] = function(plyID64) - database.Reset(registeredDatabases[net.ReadUInt(uIntBits)].accessName, plyID64) -end - ---- --- Send reset command to client --- To make sure the client resets all stored values to the default --- @param table data contains index --- @realm server --- @internal -serverSendFunctions[MESSAGE_RESET] = function(data) - net.WriteUInt(data.index, uIntBits) +serverReceiveFunctions[MESSAGE_RESET] = function(data, plyID64) + database.Reset(registeredDatabases[data.index].accessName, plyID64) end --- -- Receive reset command from server -- Reset all stored values back to the defaults +-- @param table data = {index} -- @realm client -- @internal -clientReceiveFunctions[MESSAGE_RESET] = function() - ResetDatabase(net.ReadUInt(uIntBits)) -end - ---- --- Send requested default values to client --- Can either be a single value or a table --- @param table data contains index, itemName, key, value, sendTable and defaultData here --- @realm server --- @internal -serverSendFunctions[MESSAGE_GET_DEFAULTVALUE] = function(data) - net.WriteUInt(data.index, uIntBits) - net.WriteBool(data.sendTable or false) - - if data.sendTable then - net.WriteTable(data.defaultData) - else - net.WriteString(data.itemName) - net.WriteString(data.key) - net.WriteString(tostring(data.value)) - end +clientReceiveFunctions[MESSAGE_RESET] = function(data) + ResetDatabase(data.index) end --- -- Receive requested default values from server -- and cache it +-- @param table data = {index, itemName, key, value, sentTable,} -- @realm client -- @internal -clientReceiveFunctions[MESSAGE_GET_DEFAULTVALUE] = function() - local index = net.ReadUInt(uIntBits) - local dataTable = registeredDatabases[index] - - local sentTable = net.ReadBool() - - if sentTable then - dataTable.defaultData = net.ReadTable() - else - local itemName = net.ReadString() - local key = net.ReadString() - local value = ConvertValueWithKey(net.ReadString(), dataTable.accessName, key) - - local defaultData = dataTable.defaultData - defaultData[itemName] = defaultData[itemName] or {} - defaultData[itemName][key] = value - end +clientReceiveFunctions[MESSAGE_GET_DEFAULTVALUE] = function(data) + local dataTable = registeredDatabases[data.index] + + if data.sentDefaults then + dataTable.defaultData = data.defaultData + else + local itemName = data.itemName + + local defaultData = dataTable.defaultData + defaultData[itemName] = defaultData[itemName] or {} + defaultData[itemName][data.key] = data.value + end end -- Put local functions into global table clientside if CLIENT then - sendDataFunctions = clientSendFunctions - receiveDataFunctions = clientReceiveFunctions + receiveDataFunctions = clientReceiveFunctions end -- Put local functions into global table serverside if SERVER then - sendDataFunctions = serverSendFunctions - receiveDataFunctions = serverReceiveFunctions + receiveDataFunctions = serverReceiveFunctions end -- @@ -706,128 +646,57 @@ end --- -- The function to be called on received synchronisation messages between server and client --- @param number len length of the message --- @param Player ply player that the message is received, `nil` on the client +-- @param table data Contains per messageIdentifier a number of Entries with the data +-- @param Player ply player that the message has received, `nil` on the client -- @realm shared -- @internal -local function SynchronizeStates(len, ply) - if len < 1 then return end - - local plyID64 - - if SERVER then - if not ply:IsValid() then return end - - plyID64 = ply:SteamID64() - - if not playerID64Cache[plyID64] then return end - end - - -- As size is unclear at start of the message and `net.BytesLeft` is not working - -- we instead always check if there is more to send with booleans - local continueReading = net.ReadBool() - - while continueReading do - local identifier = net.ReadUInt(uIntBits) - local readNextValue = net.ReadBool() - - while readNextValue do - receiveDataFunctions[identifier](plyID64) - readNextValue = net.ReadBool() - end - - continueReading = net.ReadBool() - end +local function SynchronizeStates(data, ply) + local plyID64 + + if SERVER then + if not ply:IsValid() then + return + end + + plyID64 = ply:SteamID64() + + if not playerID64Cache[plyID64] then + return + end + end + + for identifier, indexedData in pairs(data) do + for i = 1, #indexedData do + receiveDataFunctions[identifier](indexedData[i], plyID64) + end + end end -net.Receive("TTT2SynchronizeDatabase", SynchronizeStates) +net.ReceiveStream("TTT2SynchronizeDatabase", SynchronizeStates) --- -- The function to call when synchronizing all messages between server and client -- @realm shared -- @internal local function SendUpdatesNow() - sendRequestsNextUpdate = false - - if table.IsEmpty(dataStore) then return end - - -- Send one message per plyIdentifier and index - -- This can either be a limited playerList or just one player - local plyDeleteIdentifiers = {} - for plyIdentifier, indexList in pairs(dataStore) do - local indexDeleteIdentifiers = {} - for index, identifierList in pairs(indexList) do - net.Start("TTT2SynchronizeDatabase") - - -- Then go through all message identifiers and their cached data - -- and send them accordingly - local stopSending = false - local deleteIdentifiers = {} - for identifier, indexedData in pairs(identifierList) do - net.WriteBool(true) - net.WriteUInt(identifier, uIntBits) - net.WriteBool(#indexedData > 0) - - -- Add data to one message as long as `net.BytesWritten` are not exceeding the limit - -- Use bools to determine the end as `net.BytesLeft` is not working and we dont know the size before this loop - for i = #indexedData, 1, -1 do - sendDataFunctions[identifier](indexedData[i]) - indexedData[i] = nil - - stopSending = net.BytesWritten() >= maxBytesPerMessage - - net.WriteBool( not (stopSending or i == 1)) - - if stopSending then break end - end - - if #indexedData <= 0 then - deleteIdentifiers[identifier] = true - end - - if stopSending then break end - end - net.WriteBool(false) - - if SERVER then - if plyIdentifier == SEND_TO_PLY_ALL then - net.Broadcast() - elseif plyIdentifier == SEND_TO_PLY_REGISTERED then - net.Send(registeredPlayersIndexTable[index] or {}) - elseif IsPlayer(playerID64Cache[plyIdentifier]) then - net.Send(playerID64Cache[plyIdentifier] or {}) - end - elseif CLIENT then - net.SendToServer() - end - - -- Delete the cache of data that was already sent - for identifier in pairs(deleteIdentifiers) do - identifierList[identifier] = nil - end - - if table.IsEmpty(identifierList) then - indexDeleteIdentifiers[index] = true - end - - -- If data was left, send them next frame - if stopSending and not sendRequestsNextUpdate then - sendRequestsNextUpdate = true - timer.Simple(0, SendUpdatesNow) - end - end - - for index in pairs(indexDeleteIdentifiers) do - indexList[index] = nil - end - - if table.IsEmpty(indexList) then - plyDeleteIdentifiers[plyIdentifier] = true - end - end - - for plyIdentifier in pairs(plyDeleteIdentifiers) do - dataStore[plyIdentifier] = nil - end + -- Send one message per plyIdentifier and index + -- This can either be a limited playerList or just one player + for plyIdentifier, indexList in pairs(dataStore) do + for index, identifierList in pairs(indexList) do + local plys + if SERVER then + if plyIdentifier == SEND_TO_PLY_REGISTERED then + plys = registeredPlayersIndexTable[index] or {} + elseif IsPlayer(playerID64Cache[plyIdentifier]) then + plys = playerID64Cache[plyIdentifier] or {} + end + end + + net.SendStream("TTT2SynchronizeDatabase", identifierList, plys) + end + end + + dataStore = {} + sendRequestsNextUpdate = false end --- @@ -840,649 +709,860 @@ end -- @realm shared -- @internal local function SendUpdateNextTick(identifier, data, registerIndex, plyIdentifier) - if not sendRequestsNextUpdate then - sendRequestsNextUpdate = true - timer.Simple(0, SendUpdatesNow) - end + if not sendRequestsNextUpdate then + sendRequestsNextUpdate = true + timer.Simple(0, SendUpdatesNow) + end - registerIndex = registerIndex or INDEX_NONE + registerIndex = registerIndex or INDEX_NONE - if CLIENT then - plyIdentifier = SEND_TO_PLY_SERVER - end + if CLIENT then + plyIdentifier = SEND_TO_PLY_SERVER + end - if not isstring(plyIdentifier) and SERVER then - plyIdentifier = SEND_TO_PLY_REGISTERED - end + if not isstring(plyIdentifier) and SERVER then + plyIdentifier = SEND_TO_PLY_REGISTERED + end - -- Store data - local tempStore = dataStore[plyIdentifier] or {} - tempStore[registerIndex] = tempStore[registerIndex] or {} - tempStore[registerIndex][identifier] = tempStore[registerIndex][identifier] or {} - tempStore[registerIndex][identifier][#tempStore[registerIndex][identifier] + 1] = data + -- Store data + local tempStore = dataStore[plyIdentifier] or {} + tempStore[registerIndex] = tempStore[registerIndex] or {} + tempStore[registerIndex][identifier] = tempStore[registerIndex][identifier] or {} + tempStore[registerIndex][identifier][#tempStore[registerIndex][identifier] + 1] = data - dataStore[plyIdentifier] = tempStore + dataStore[plyIdentifier] = tempStore end -- Public Client only functions if CLIENT then - --- - -- Is automatically called internally when a client joins, can be called by a player to force an update, but is normally not necessary - -- @param function OnReceiveFunc() the function that is called when the registered databases are received - -- @realm client - -- @internal - function database.GetRegisteredDatabases(OnReceiveFunc) - messageIdentifier = messageIdentifier % maxUInt + 1 - functionCache[messageIdentifier] = OnReceiveFunc - - SendUpdateNextTick(MESSAGE_REGISTER, messageIdentifier) - end - - --- - -- Is called after databases are received. All pending getRequests are sent. - -- @realm client - -- @internal - local function cleanUpWaitReceiveCache() - for i = 1, #onWaitReceiveFunctionCache do - local data = onWaitReceiveFunctionCache[i] - - waitForRegisteredDatabases[data.accessName] = false - triedToGetRegisteredDatabases[data.accessName] = true - - database.GetValue(data.accessName, data.itemName, data.key, data.OnReceiveFunc) - end - - onWaitReceiveFunctionCache = {} - end - - --- - -- Get the stored key value of the given database if it exists on the server or was already cached - -- @param string accessName the chosen networkable name of the sql table - -- @param string itemName the name or primaryKey of the item inside of the sql table - -- @param string key the name of the key in the database - -- @param function OnReceiveFunc(databaseExists, value) The function that gets called with the results if the database exists - -- @realm client - function database.GetValue(accessName, itemName, key, OnReceiveFunc) - local index = nameToIndex[accessName] - - if not index then - if triedToGetRegisteredDatabases[accessName] then - ErrorNoHalt("[TTT2] database.GetValue failed. The registered Database of " .. accessName .. " is not available or synced.") - OnReceiveFunc(false) - - return - end - - -- In case the database wasnt registered, try to get it and then send the requests again later - if not waitForRegisteredDatabases[accessName] then - waitForRegisteredDatabases[accessName] = true - database.GetRegisteredDatabases(cleanUpWaitReceiveCache) - end - - onWaitReceiveFunctionCache[#onWaitReceiveFunctionCache + 1] = { - accessName = accessName, - itemName = itemName, - key = key, - OnReceiveFunc = OnReceiveFunc - } - - return - end - - local dataTable = registeredDatabases[index] - - if not dataTable.keys[key] then - ErrorNoHalt("[TTT2] database.GetValue failed. The registered Database of " .. accessName .. " doesnt have a key named " .. key) - - return - end - - -- Use cached data if value was received from the server - if IsValueReceived(index, itemName, key) then - OnReceiveFunc(true, dataTable.storedData[itemName] and dataTable.storedData[itemName][key]) - - return - end - - messageIdentifier = messageIdentifier % maxUInt + 1 - functionCache[messageIdentifier] = OnReceiveFunc - - requestCache[messageIdentifier] = { - accessName = accessName, - itemName = itemName, - key = key, - index = index - } - - SendUpdateNextTick(MESSAGE_GET_VALUE, messageIdentifier) - end - - --- - -- Request to set the value for a key of an item of an sql-table on the server - -- @param string accessName the chosen networkable name of the sql table - -- @param string itemName the name or primaryKey of the item inside of the sql table - -- @param string key the name of the key in the database - -- @param any value the value you want to set in the database - -- @realm client - function database.SetValue(accessName, itemName, key, value) - local index = nameToIndex[accessName] - - if not index then - ErrorNoHalt("[TTT2] database.SetValue failed. The registered Database of " .. accessName .. " is not available or synced.") - - return - end - - if not registeredDatabases[index].keys[key] then - ErrorNoHalt("[TTT2] database.SetValue failed. The registered Database of " .. accessName .. " doesnt have a key named " .. key) - - return - end - - SendUpdateNextTick(MESSAGE_SET_VALUE, {index = index, itemName = itemName, key = key, value = value}) - end - - --- - -- Request to reset the database on the server - -- @param string accessName the chosen networkable name of the sql table - -- @realm client - function database.Reset(accessName) - local index = nameToIndex[accessName] - - if not index then - ErrorNoHalt("[TTT2] database.Reset failed. The registered Database of " .. accessName .. " is not available or synced.") - - return - end - - SendUpdateNextTick(MESSAGE_RESET, {index = index}) - end + --- + -- Gives the next messageIdentifier to identify functions on callback. + -- Its a placeholder in case a better way is needed + -- @return number, the next messageIdentifier + -- @realm client + -- @internal + local function getNextMessageIdentifier() + messageIdentifier = messageIdentifier + 1 + + return messageIdentifier + end + + --- + -- Is automatically called internally when a client joins, can be called by a player to force an update, but is normally not necessary + -- @param function OnReceiveFunc the function that is called when the registered databases are received + -- @realm client + -- @internal + function database.GetRegisteredDatabases(OnReceiveFunc) + local identifier = getNextMessageIdentifier() + functionCache[identifier] = OnReceiveFunc + + SendUpdateNextTick(MESSAGE_REGISTER, { identifier = identifier }) + end + + --- + -- Is called after databases are received. All pending getRequests are sent. + -- @realm client + -- @internal + local function cleanUpWaitReceiveCache() + for i = 1, #onWaitReceiveFunctionCache do + local data = onWaitReceiveFunctionCache[i] + + waitForRegisteredDatabases[data.accessName] = false + triedToGetRegisteredDatabases[data.accessName] = true + + data.OnWaitEndFunc() + end + + onWaitReceiveFunctionCache = {} + end + + --- + -- Tries to get registered databases and caches the call until its confirmed that databases are registered or not + -- @param string accessName the chosen networkable name of the sql table + -- @param function OnWaitEndFunc() The function that gets called when the database is accessible + -- @return bool, if waiting for the database is still possible + -- @realm client + local function tryGetRegisteredDatabase(accessName, OnWaitEndFunc) + if triedToGetRegisteredDatabases[accessName] then + return false + end + + -- In case the database wasnt registered, try to get it and then send the requests again later + if not waitForRegisteredDatabases[accessName] then + waitForRegisteredDatabases[accessName] = true + database.GetRegisteredDatabases(cleanUpWaitReceiveCache) + end + + onWaitReceiveFunctionCache[#onWaitReceiveFunctionCache + 1] = { + accessName = accessName, + OnWaitEndFunc = OnWaitEndFunc, + } + + return true + end + + --- + -- Get the stored key value of the given database if it exists on the server or was already cached + -- @param string accessName the chosen networkable name of the sql table + -- @param string itemName the name or primaryKey of the item inside of the sql table + -- @param string key the name of the key in the database + -- @param function OnReceiveFunc The function that gets called with the following parameters: boolean, whether the database table exists; any?, the value of the requested item + -- @realm client + function database.GetValue(accessName, itemName, key, OnReceiveFunc) + local index = nameToIndex[accessName] + + if not index then + local function OnWaitEndFunc() + database.GetValue(accessName, itemName, key, OnReceiveFunc) + end + + if not tryGetRegisteredDatabase(accessName, OnWaitEndFunc) then + ErrorNoHaltWithStack( + "[TTT2] database.GetValue failed. The registered Database of " + .. accessName + .. " is not available or synced." + ) + OnReceiveFunc(false) + end + + return + end + + local dataTable = registeredDatabases[index] + + if isstring(key) and not dataTable.keys[key] then + ErrorNoHaltWithStack( + "[TTT2] database.GetValue failed. The registered Database of " + .. accessName + .. " doesnt have a key named " + .. key + ) + + return + end + + -- Use cached data if value was received from the server + if isstring(key) and IsValueReceived(index, itemName, key) then + OnReceiveFunc( + true, + dataTable.storedData[itemName] and dataTable.storedData[itemName][key] + ) + + return + elseif IsValueReceived(index, itemName, key) then + OnReceiveFunc(true, dataTable.storedData[itemName]) + + return + end + + local identifier = getNextMessageIdentifier() + functionCache[identifier] = OnReceiveFunc + + local data = { + accessName = accessName, + itemName = itemName, + key = key, + index = index, + identifier = identifier, + } + + requestCache[identifier] = data + + SendUpdateNextTick(MESSAGE_GET_VALUE, data) + end + + --- + -- Request to set the value for a key of an item of an sql-table on the server + -- @param string accessName the chosen networkable name of the sql table + -- @param string itemName the name or primaryKey of the item inside of the sql table + -- @param string key the name of the key in the database + -- @param any value the value you want to set in the database + -- @realm client + function database.SetValue(accessName, itemName, key, value) + local index = nameToIndex[accessName] + + if not index then + ErrorNoHaltWithStack( + "[TTT2] database.SetValue failed. The registered Database of " + .. accessName + .. " is not available or synced." + ) + + return + end + + if not registeredDatabases[index].keys[key] then + ErrorNoHaltWithStack( + "[TTT2] database.SetValue failed. The registered Database of " + .. accessName + .. " doesnt have a key named " + .. key + ) + + return + end + + -- Make sure the value has the right type + value = ConvertValueWithKey(value, accessName, key) + + SendUpdateNextTick( + MESSAGE_SET_VALUE, + { index = index, itemName = itemName, key = key, value = value } + ) + end + + --- + -- Request to reset the database on the server + -- @param string accessName the chosen networkable name of the sql table + -- @realm client + function database.Reset(accessName) + local index = nameToIndex[accessName] + + if not index then + ErrorNoHaltWithStack( + "[TTT2] database.Reset failed. The registered Database of " + .. accessName + .. " is not available or synced." + ) + + return + end + + SendUpdateNextTick(MESSAGE_RESET, { index = index }) + end end -- Public Server only functions if SERVER then - --- - -- Registers players that are notified of all changes - -- @note this function is called when a player makes a request to the server - -- @param number index the index of the database you want to register a player for with access rights - -- @param string plyID64 the player steam ID 64 - -- @realm server - -- @internal - local function RegisterPlayer(index, plyID64) - if registeredPlayersCache[plyID64] and registeredPlayersCache[plyID64][index] then return end - - -- If player wasnt cached yet, add them to all registered Players - if not registeredPlayersCache[plyID64] then - registeredPlayersIndexTable[INDEX_NONE] = registeredPlayersIndexTable[INDEX_NONE] or {} - registeredPlayersIndexTable[INDEX_NONE][#registeredPlayersIndexTable[INDEX_NONE] + 1] = playerID64Cache[plyID64] - end - - registeredPlayersCache[plyID64] = registeredPlayersCache[plyID64] or {INDEX_NONE = true} - registeredPlayersCache[plyID64][index] = true - - if index == INDEX_NONE then return end - - registeredPlayersIndexTable[index] = registeredPlayersIndexTable[index] or {} - registeredPlayersIndexTable[index][#registeredPlayersIndexTable[index] + 1] = playerID64Cache[plyID64] - end - - --- - -- Checks if the player has the necessary accessLevel - -- @note Only Admins can write to the database, no matter the accessLevel - -- @param number index the local index of the database - -- @param[opt] string plyID64 the player steam ID 64. Leave this empty when calling on the server. This only makes sure values are only set by superadmins - -- @return bool, bool hasReadAccess, hasWriteAccess, if the player can read from or write to the database - -- @realm server - -- @internal - local function hasAccessToDatabase(index, plyID64) - local isAdmin = false - local isServer = true - - if plyID64 and playerID64Cache[plyID64] then - --- - -- @realm server - isAdmin = hook.Run("TTT2AdminCheck", playerID64Cache[plyID64]) - isServer = false - end - - if isServer then return true, true end - - local accessLevelNeeded = registeredDatabases[index].accessLevel - - local hasAdminAccess = isAdmin and accessLevelNeeded >= TTT2_DATABASE_ACCESS_ADMIN - local hasReadAccess = hasAdminAccess or accessLevelNeeded > TTT2_DATABASE_ACCESS_ADMIN - - return hasReadAccess, hasAdminAccess - end - - --- - -- Synchronizes all registered Databases with the given players defined by the plyIdentifier - -- @note This is used internally to sync between server and client, you dont need to call it manually - -- @param string plyIdentifier the player identifier to determine who receives the message, defined in `SEND_TO_PLY_`-enums or can be a plyID64 - -- @param[opt] string identifier the identifier used to get correct onreceive functions - -- @realm server - -- @internal - function database.SyncRegisteredDatabases(plyIdentifier, identifier) - local tableCount = #registeredDatabases - - for databaseNumber = 1, tableCount do - --contains additional data in case an identifier is given - local dataRegister = { - index = databaseNumber, - identifier = identifier, - tableCount = tableCount - } - - SendUpdateNextTick(MESSAGE_REGISTER, dataRegister, INDEX_NONE, plyIdentifier) - - local defaultData = registeredDatabases[databaseNumber].defaultData - - -- Also sync registered item-specific defaults if available - if table.IsEmpty(defaultData) then continue end - - local dataDefault = { - index = databaseNumber, - sendTable = true, - defaultData = defaultData - } - - SendUpdateNextTick(MESSAGE_GET_DEFAULTVALUE, dataDefault, INDEX_NONE, plyIdentifier) - end - end - - --- - -- Call this when you want to setup a database that needs to be accessible by server and client - -- If you dont call this function before anything else, it wont work. Choose any name as accessName so that others can easily use it. - -- @note If the SqlTable does not exist, it will be created with the given savingKeys - -- @param string databaseName the real name of the database - -- @param string accessName the name to quickly access databases and differentiate between a pseudo used accessName and the migrated actual databaseName - -- @param table savingKeys the savingKeys = {keyName = {typ, bits, default, ..}, ..} defining the keyNames and their information - -- @param[default = TTT2_DATABASE_ACCESS_ADMIN] number accessLevel the access level needed to get values of a database, defined in `TTT2_DATABASE_ACCESS_`-enums (_ANY, _ADMIN, _SERVER) - -- @note If accessLevel is set to TTT2_DATABASE_ACCESS_SERVER it fully prevents any client read- and write-access, whereas TTT2_DATABASE_ACCESS_ANY only gives read-, but not write-access to anyone - -- @param[opt] table additionalData the data that doesnt belong to a database but might be needed for other purposes like enums - -- @return bool isSuccessful if the database exists and is successfully registered - -- @realm server - function database.Register(databaseName, accessName, savingKeys, accessLevel, additionalData) - accessLevel = accessLevel or TTT2_DATABASE_ACCESS_ADMIN - - -- Create Sql table if not already done - if not sql.CreateSqlTable(databaseName, savingKeys) or not isstring(accessName) then - return false - end - - -- Return if already registered - if nameToIndex[accessName] then - return true - end - - local databaseCount = #registeredDatabases + 1 - - registeredDatabases[databaseCount] = { - accessName = accessName, - accessLevel = accessLevel, - databaseName = databaseName, - orm = orm.Make(databaseName), - keys = savingKeys or {}, - data = additionalData or {}, - storedData = {}, - defaultData = {} - } - nameToIndex[accessName] = databaseCount - - local data = {index = databaseCount} - - SendUpdateNextTick(MESSAGE_REGISTER, data, INDEX_NONE, SEND_TO_PLY_ALL) - - return true - end - - --- - -- This is called upon receiving a get request from a player to send a value back - -- @warning Dont use this function if you want to get a value from the database, this is meant to be used internally for a client request - -- @param table requestData = {plyID64, identifier, index, itemName, key} contains player and the data they requested - -- @realm server - -- @internal - function database.ReturnGetValue(requestData) - local index = requestData.index - local accessName = index and registeredDatabases[index] and registeredDatabases[index].accessName - - local plyID64 = requestData.plyID64 - local hasReadAccess, _ = hasAccessToDatabase(index, plyID64) - - local value - local isSuccess = false - - if hasReadAccess and accessName then - isSuccess, value = database.GetValue(accessName, requestData.itemName, requestData.key) - end - - local serverData = { - identifier = requestData.identifier, - isSuccess = isSuccess, - value = value - } - - -- If accessLevel is the lowest, register the player for every callback on that database - local registerIndex = index - if registeredDatabases[index].accessLevel >= TTT2_DATABASE_ACCESS_ANY then - registerIndex = INDEX_NONE - end - RegisterPlayer(registerIndex, plyID64) - - SendUpdateNextTick(MESSAGE_GET_VALUE, serverData, INDEX_NONE, plyID64) - end - - --- - -- Get the stored key value of the given database if it exists and was registered - -- @note While itemName and key are optional, leaving them out only gets the saved and converted sql Tables, they dont include every possible item with their default values. - -- So to get default Values you have to specify itemName and key. This is designed to be used for single requests. - -- @param string accessName the chosen networkable name of the sql table - -- @param[opt] string itemName the name or primaryKey of the item inside of the sql table, if not given selects whole sql table - -- @param[opt] string key the name of the key in the database, is ignored when no itemName is given, if not given selects whole item - -- @return bool, if the requested item and/or key was successfully registered in the sql datatable - -- @return any, the value that was saved in the database or the default - -- @realm server - function database.GetValue(accessName, itemName, key) - local index = nameToIndex[accessName] - - if not index then - ErrorNoHalt("[TTT2] database.GetValue failed. The registered Database of " .. accessName .. " is not registered.") - - return false - end - - local dataTable = index and registeredDatabases[index] - - -- If itemName and key are given but the key is not registered, throw an error - if itemName and key and not dataTable.keys[key] then - ErrorNoHalt("[TTT2] database.GetValue failed. The registered Database of " .. accessName .. " doesnt have a key named " .. key) - - return false - end - - -- Get storedValues first if a concrete item-key pair is given - if isstring(itemName) and isstring(key) then - local storedValue = dataTable.storedData[itemName] and dataTable.storedData[itemName][key] - - if storedValue ~= nil then - return true, storedValue - end - end - - local sqlData - - if itemName then - -- Find saved item data - sqlData = dataTable.orm:Find(itemName) - - if key then - -- Get the specific key data - local value = sqlData and ConvertValueWithKey(sqlData[key], accessName, key) - - -- Get default values if no value was saved - if value == nil then - value = database.GetDefaultValue(accessName, itemName, key) - end - - dataTable.storedData[itemName] = dataTable.storedData[itemName] or {} - dataTable.storedData[itemName][key] = value - - return true, value - end - - ConvertTable(sqlData, accessName) - dataTable.storedData[itemName] = sqlData - else - -- Get all data, convert and return it - sqlData = dataTable.orm:All() - - if not istable(sqlData) then - return false - end - - for _, item in pairs(sqlData) do - ConvertTable(item, accessName) - end - - -- Convert numerical indices to string indices with the itemName - local hashableData = {} - - for i = 1, #sqlData do - local sqlTable = sqlData[i] - hashableData[sqlTable.name] = sqlTable - hashableData[sqlTable.name].name = nil - end - - sqlData = hashableData - - dataTable.storedData = table.Copy(sqlData) - end - - return istable(sqlData), sqlData - end - - --- - -- Get the stored table database if it exists and was registered - -- @note Only gets the saved and converted sql Tables, they dont include every possible item with their default values. - -- @param string accessName the chosen networkable name of the sql table - -- @return bool, if the requested table was successfully registered in the sql datatable - -- @return table datatable that was saved - -- @realm server - function database.GetTable(accessName) - return database.GetValue(accessName) - end - - --- - -- Set the value for a key of an item of an sql-table - -- also sends it to the clients - -- @note It is restricted to players with TTT2_DATABASE_ACCESS_ADMIN or higher at all times - -- @param string accessName the chosen networkable name of the sql table - -- @param string itemName the name or primaryKey of the item inside of the sql table - -- @param string key the name of the key in the database - -- @param any value the value you want to set in the database - -- @param[opt] string plyID64 the player steam ID 64. Leave this empty when calling on the server. This only makes sure values are only set by superadmins - -- @realm server - function database.SetValue(accessName, itemName, key, value, plyID64) - local index = nameToIndex[accessName] - - if not index then - ErrorNoHalt("[TTT2] database.SetValue failed. The registered Database of " .. accessName .. " is not registered.") - - return - end - - local _, hasWriteAccess = hasAccessToDatabase(index, plyID64) - - if not hasWriteAccess then - ErrorNoHalt("[TTT2] database.SetValue failed. The player with the ID64 " .. plyID64 .. " has no write access to the database " .. accessName .. " .") - - return - end - - local dataTable = registeredDatabases[index] - - if not dataTable.keys[key] then - ErrorNoHalt("[TTT2] database.SetValue failed. The registered Database of " .. accessName .. " doesnt have a key named " .. key) - - return - end - - local saveValue = value - - -- If the value is just the default, then delete it from the sql database by setting it nil - if saveValue == database.GetDefaultValue(accessName, itemName, key) then - saveValue = nil - end - - local itemPoolModel = dataTable.orm - - local item = itemPoolModel:Find(itemName) - - if not item then - item = itemPoolModel:New({ - name = itemName, - }) - end - - item[key] = saveValue - - local saveItem = true - - if saveValue == nil then - ConvertTable(item, accessName) - - saveItem = false - - -- Check if there is still a value saved for any of the keys - for curKey in pairs(dataTable.keys) do - if item[curKey] ~= nil then - saveItem = true - - break - end - end - - -- Delete the item if there is nothing to save - if not saveItem then - item:Delete() - end - end - - if saveItem then - item:Save() - end - - OnChange(index, itemName, key, value) - - -- If accessLevel is the lowest, send database update to every player - local registerIndex = index - if dataTable.accessLevel >= TTT2_DATABASE_ACCESS_ANY then - registerIndex = INDEX_NONE - end - - SendUpdateNextTick(MESSAGE_SET_VALUE, {index = index, itemName = itemName, key = key, value = value}, registerIndex, SEND_TO_PLY_REGISTERED) - end - - --- - -- Use this to set item-specific defaults, to save storage space in the sql database - -- also syncs this to the clients - -- @note You dont need to check manually if it is a key-specific default. They are excluded anyways - -- @param string accessName the chosen networkable name of the sql table - -- @param string itemName the name or primaryKey of the item inside of the sql table - -- @param string key the name of the key in the database - -- @param any value the value you want to set in the database - -- @realm server - function database.SetDefaultValue(accessName, itemName, key, value) - local index = nameToIndex[accessName] - - if not index then - ErrorNoHalt("[TTT2] database.SetDefaultValue failed. The registered Database of " .. accessName .. " is not registered.") - - return - end - - local dataTable = registeredDatabases[index] - - if not dataTable.keys[key] then - ErrorNoHalt("[TTT2] database.SetDefaultValue failed. The registered Database of " .. accessName .. " doesnt have a key named " .. key) - - return - end - - -- Ignore it if this is the key-default anyways - if value == dataTable.keys[key].default then return end - - local defaultData = dataTable.defaultData - defaultData[itemName] = defaultData[itemName] or {} - defaultData[itemName][key] = value - - dataTable.defaultData = defaultData - - -- If accessLevel is the lowest, send database update to every player - local registerIndex = index - if dataTable.accessLevel >= TTT2_DATABASE_ACCESS_ANY then - registerIndex = INDEX_NONE - end - - SendUpdateNextTick(MESSAGE_GET_DEFAULTVALUE, {index = index, itemName = itemName, key = key, value = value, sendTable = false}, registerIndex, SEND_TO_PLY_REGISTERED) - end - - --- - -- Reset the database and send a message to the client - -- @note It is restricted to players with TTT2_DATABASE_ACCESS_ADMIN or higher at all times - -- @param string accessName the chosen networkable name of the sql table - -- @param[opt] string plyID64 the player steam ID 64. Leave this empty when calling on the server. This only makes sure values are only set by superadmins - -- @realm server - function database.Reset(accessName, plyID64) - local index = nameToIndex[accessName] - - if not index then - ErrorNoHalt("[TTT2] database.Reset failed. The registered Database of " .. accessName .. " is not available or synced.") - - return - end - - local _, hasWriteAccess = hasAccessToDatabase(index, plyID64) - - if not hasWriteAccess then - ErrorNoHalt("[TTT2] database.Reset failed. The player with the ID64 " .. plyID64 .. " has no write access to the database " .. accessName .. " .") - - return - end - - local dataTable = registeredDatabases[index] - local databaseName = dataTable.databaseName - - -- Drop the table and create a new one as well as an orm-object - sql.DropTable(databaseName) - sql.CreateSqlTable(databaseName, dataTable.keys) - dataTable.orm = orm.Make(databaseName) - - ResetDatabase(index) - - -- If accessLevel is the lowest, send database update to every player - local registerIndex = index - if dataTable.accessLevel >= TTT2_DATABASE_ACCESS_ANY then - registerIndex = INDEX_NONE - end - - SendUpdateNextTick(MESSAGE_RESET, {index = index}, registerIndex, SEND_TO_PLY_REGISTERED) - end - - -- Sync databases to all authenticated players - hook.Add("PlayerAuthed", "TTT2SyncDatabaseIndexTableToAuthorizedPlayers", function(ply, steamID, uniqueID) - local plyID64 = ply:SteamID64() - - if not IsValid(ply) then return end - - playerID64Cache[plyID64] = ply - - database.SyncRegisteredDatabases(plyID64) - end) - - -- Remove disconnected players, that were additionally registered due to requesting or setting data - hook.Add("PlayerDisconnected", "TTT2RemovePlayerOfRegisteredPlayersTable", function(ply) - if not IsValid(ply) then return end - - local plyID64 = ply:SteamID64() - playerID64Cache[plyID64] = nil - - if not registeredPlayersCache[plyID64] then return end - - registeredPlayersCache[plyID64] = nil - - local deleteIndices = {} - - for index, players in pairs(registeredPlayersIndexTable) do - table.RemoveByValue(players, ply) - - if table.IsEmpty(players) then - deleteIndices[#deleteIndices + 1] = index - end - end - - for i = 1, #deleteIndices do - registeredPlayersIndexTable[deleteIndices[i]] = nil - end - end) + --- + -- Registers players that are notified of all changes + -- @note this function is called when a player makes a request to the server + -- @param number index the index of the database you want to register a player for with access rights + -- @param string plyID64 the player steam ID 64 + -- @realm server + -- @internal + local function RegisterPlayer(index, plyID64) + if registeredPlayersCache[plyID64] and registeredPlayersCache[plyID64][index] then + return + end + + -- If player wasnt cached yet, add them to all registered Players + if not registeredPlayersCache[plyID64] then + registeredPlayersIndexTable[INDEX_NONE] = registeredPlayersIndexTable[INDEX_NONE] or {} + registeredPlayersIndexTable[INDEX_NONE][#registeredPlayersIndexTable[INDEX_NONE] + 1] = + playerID64Cache[plyID64] + end + + registeredPlayersCache[plyID64] = registeredPlayersCache[plyID64] or { INDEX_NONE = true } + registeredPlayersCache[plyID64][index] = true + + if index == INDEX_NONE then + return + end + + registeredPlayersIndexTable[index] = registeredPlayersIndexTable[index] or {} + registeredPlayersIndexTable[index][#registeredPlayersIndexTable[index] + 1] = + playerID64Cache[plyID64] + end + + --- + -- Checks if the player has the necessary accessLevel + -- @note Only Admins can write to the database, no matter the accessLevel + -- @param number index the local index of the database + -- @param[opt] string plyID64 the player steam ID 64. Leave this empty when calling on the server. This only makes sure values are only set by superadmins + -- @return boolean,boolean hasReadAccess, hasWriteAccess, if the player can read from or write to the database + -- @realm server + -- @internal + local function hasAccessToDatabase(index, plyID64) + local isAdmin = false + local isServer = true + + if plyID64 and playerID64Cache[plyID64] then + --- + -- @realm server + -- stylua: ignore + isAdmin = hook.Run("TTT2AdminCheck", playerID64Cache[plyID64]) + isServer = false + end + + if isServer then + return true, true + end + + local accessLevelNeeded = registeredDatabases[index].accessLevel + + local hasAdminAccess = isAdmin and accessLevelNeeded >= TTT2_DATABASE_ACCESS_ADMIN + local hasReadAccess = hasAdminAccess or accessLevelNeeded > TTT2_DATABASE_ACCESS_ADMIN + + return hasReadAccess, hasAdminAccess + end + + --- + -- Synchronizes all registered Databases with the given players defined by the plyIdentifier + -- @note This is used internally to sync between server and client, you dont need to call it manually + -- @param string plyIdentifier the player identifier to determine who receives the message, defined in `SEND_TO_PLY_`-enums or can be a plyID64 + -- @param[opt] string identifier the identifier used to get correct onreceive functions + -- @realm server + -- @internal + function database.SyncRegisteredDatabases(plyIdentifier, identifier) + local databaseSize = #registeredDatabases + + for databaseNumber = 1, databaseSize do + local dataTable = registeredDatabases[databaseNumber] + + --contains additional data in case an identifier is given + local dataRegister = { + index = databaseNumber, + accessName = dataTable.accessName, + savingKeys = dataTable.keys, + additionalData = dataTable.data, + identifier = identifier, + databaseSize = databaseSize, + } + + SendUpdateNextTick(MESSAGE_REGISTER, dataRegister, INDEX_NONE, plyIdentifier) + + local defaultData = registeredDatabases[databaseNumber].defaultData + + -- Also sync registered item-specific defaults if available + if table.IsEmpty(defaultData) then + continue + end + + local dataDefault = { + index = databaseNumber, + sentDefaults = true, + defaultData = defaultData, + } + + SendUpdateNextTick(MESSAGE_GET_DEFAULTVALUE, dataDefault, INDEX_NONE, plyIdentifier) + end + end + + --- + -- Call this when you want to setup a database that needs to be accessible by server and client + -- If you dont call this function before anything else, it wont work. Choose any name as accessName so that others can easily use it. + -- @note If the SqlTable does not exist, it will be created with the given savingKeys + -- @param string databaseName the real name of the database + -- @param string accessName the name to quickly access databases and differentiate between a pseudo used accessName and the migrated actual databaseName + -- @param table savingKeys the savingKeys = {keyName = {typ, bits, default, ..}, ..} defining the keyNames and their information + -- @param[default = TTT2_DATABASE_ACCESS_ADMIN] number accessLevel the access level needed to get values of a database, defined in `TTT2_DATABASE_ACCESS_`-enums (_ANY, _ADMIN, _SERVER) + -- @note If accessLevel is set to TTT2_DATABASE_ACCESS_SERVER it fully prevents any client read- and write-access, whereas TTT2_DATABASE_ACCESS_ANY only gives read-, but not write-access to anyone + -- @param[opt] table additionalData the data that doesnt belong to a database but might be needed for other purposes like enums + -- @return boolean isSuccessful if the database exists and is successfully registered + -- @realm server + function database.Register(databaseName, accessName, savingKeys, accessLevel, additionalData) + accessLevel = accessLevel or TTT2_DATABASE_ACCESS_ADMIN + + -- Create Sql table if not already done + if not sql.CreateSqlTable(databaseName, savingKeys) or not isstring(accessName) then + return false + end + + -- Return if already registered + if nameToIndex[accessName] then + return true + end + + local databaseCount = #registeredDatabases + 1 + + registeredDatabases[databaseCount] = { + accessName = accessName, + accessLevel = accessLevel, + databaseName = databaseName, + orm = orm.Make(databaseName), + keys = savingKeys or {}, + data = additionalData or {}, + storedData = {}, + defaultData = {}, + } + nameToIndex[accessName] = databaseCount + + local dataTable = registeredDatabases[databaseCount] + + local dataRegister = { + index = databaseCount, + accessName = dataTable.accessName, + savingKeys = dataTable.keys, + additionalData = dataTable.data, + identifier = nil, + databaseSize = databaseCount, + } + + SendUpdateNextTick(MESSAGE_REGISTER, dataRegister, INDEX_NONE, SEND_TO_PLY_ALL) + + return true + end + + --- + -- This is called upon receiving a get request from a player to send a value back + -- @warning Dont use this function if you want to get a value from the database, this is meant to be used internally for a client request + -- @param table requestData = {identifier, index, itemName, key} contains player and the data they requested + -- @param string plyID64 the playerID64 of the player who sent the message + -- @realm server + -- @internal + function database.ReturnGetValue(requestData, plyID64) + local index = requestData.index + local accessName = index + and registeredDatabases[index] + and registeredDatabases[index].accessName + + local hasReadAccess, _ = hasAccessToDatabase(index, plyID64) + + local value + local isSuccess = false + + if hasReadAccess and accessName then + isSuccess, value = database.GetValue(accessName, requestData.itemName, requestData.key) + end + + local serverData = { + identifier = requestData.identifier, + isSuccess = isSuccess, + value = value, + } + + -- If accessLevel is the lowest, register the player for every callback on that database + local registerIndex = index + if registeredDatabases[index].accessLevel >= TTT2_DATABASE_ACCESS_ANY then + registerIndex = INDEX_NONE + end + RegisterPlayer(registerIndex, plyID64) + + SendUpdateNextTick(MESSAGE_GET_VALUE, serverData, INDEX_NONE, plyID64) + end + + receiveDataFunctions[MESSAGE_GET_VALUE] = database.ReturnGetValue + + --- + -- Get the stored key value of the given database if it exists and was registered + -- @note While itemName and key are optional, leaving them out only gets the saved and converted sql Tables, they dont include every possible item with their default values. + -- So to get default Values you have to specify itemName and key. This is designed to be used for single requests. + -- @param string accessName the chosen networkable name of the sql table + -- @param[opt] string itemName the name or primaryKey of the item inside of the sql table, if not given selects whole sql table + -- @param[opt] string key the name of the key in the database, is ignored when no itemName is given, if not given selects whole item + -- @return boolean if the requested item and/or key was successfully registered in the sql datatable + -- @return any, the value that was saved in the database or the default + -- @realm server + function database.GetValue(accessName, itemName, key) + local index = nameToIndex[accessName] + + if not index then + ErrorNoHaltWithStack( + "[TTT2] database.GetValue failed. The registered Database of " + .. accessName + .. " is not registered." + ) + + return false + end + + local dataTable = index and registeredDatabases[index] + + -- If itemName and key are given but the key is not registered, throw an error + if itemName and key and not dataTable.keys[key] then + ErrorNoHaltWithStack( + "[TTT2] database.GetValue failed. The registered Database of " + .. accessName + .. " doesnt have a key named " + .. key + ) + + return false + end + + -- Get storedValues first if a concrete item-key pair is given + if isstring(itemName) and isstring(key) then + local storedValue = dataTable.storedData[itemName] + and dataTable.storedData[itemName][key] + + if storedValue ~= nil then + return true, storedValue + end + end + + local sqlData + + if itemName then + -- Find saved item data + sqlData = dataTable.orm:Find(itemName) or {} + + if key then + -- Get the specific key data + local value = sqlData and ConvertValueWithKey(sqlData[key], accessName, key) + + -- Get default values if no value was saved + if value == nil then + value = database.GetDefaultValue(accessName, itemName, key) + end + + dataTable.storedData[itemName] = dataTable.storedData[itemName] or {} + dataTable.storedData[itemName][key] = value + + return true, value + end + + ConvertTable(sqlData, accessName) + + local newTable = {} + for _key in pairs(dataTable.keys) do + -- Get default values if no value was saved + if sqlData[_key] == nil then + sqlData[_key] = database.GetDefaultValue(accessName, itemName, _key) + end + newTable[_key] = sqlData[_key] + end + + sqlData = newTable + dataTable.storedData[itemName] = sqlData + else + -- Get all data, convert and return it + sqlData = dataTable.orm:All() + + if not istable(sqlData) then + return false + end + + for _, _itemName in pairs(sqlData) do + ConvertTable(_itemName, accessName) + -- Get default values if no value was saved + for _key in pairs(dataTable.keys) do + if _itemName[_key] == nil then + _itemName[_key] = database.GetDefaultValue(accessName, _itemName, _key) + end + end + end + + -- Convert numerical indices to string indices with the itemName + local hashableData = {} + + for i = 1, #sqlData do + local sqlTable = sqlData[i] + hashableData[sqlTable.name] = sqlTable + hashableData[sqlTable.name].name = nil + end + + sqlData = hashableData + + dataTable.storedData = table.Copy(sqlData) + end + + return istable(sqlData), sqlData + end + + --- + -- Get the stored table database if it exists and was registered + -- @note Only gets the saved and converted sql Tables, they dont include every possible item with their default values. + -- @param string accessName the chosen networkable name of the sql table + -- @return boolean if the requested table was successfully registered in the sql datatable + -- @return table datatable that was saved + -- @realm server + function database.GetTable(accessName) + return database.GetValue(accessName) + end + + --- + -- Set the value for a key of an item of an sql-table + -- also sends it to the clients + -- @note It is restricted to players with TTT2_DATABASE_ACCESS_ADMIN or higher at all times + -- @param string accessName the chosen networkable name of the sql table + -- @param string itemName the name or primaryKey of the item inside of the sql table + -- @param string key the name of the key in the database + -- @param any value the value you want to set in the database + -- @param[opt] string plyID64 the player steam ID 64. Leave this empty when calling on the server. This only makes sure values are only set by superadmins + -- @realm server + function database.SetValue(accessName, itemName, key, value, plyID64) + local index = nameToIndex[accessName] + + if not index then + ErrorNoHaltWithStack( + "[TTT2] database.SetValue failed. The registered Database of " + .. accessName + .. " is not registered." + ) + + return + end + + local _, hasWriteAccess = hasAccessToDatabase(index, plyID64) + + if not hasWriteAccess then + ErrorNoHaltWithStack( + "[TTT2] database.SetValue failed. The player with the ID64 " + .. plyID64 + .. " has no write access to the database " + .. accessName + .. " ." + ) + + return + end + + local dataTable = registeredDatabases[index] + + if not dataTable.keys[key] then + ErrorNoHaltWithStack( + "[TTT2] database.SetValue failed. The registered Database of " + .. accessName + .. " doesnt have a key named " + .. key + ) + + return + end + + -- Make sure the value has the right type + value = ConvertValueWithKey(value, accessName, key) + local saveValue = value + + -- If the value is just the default, then delete it from the sql database by setting it nil + if saveValue == database.GetDefaultValue(accessName, itemName, key) then + saveValue = nil + end + + local itemPoolModel = dataTable.orm + + local item = itemPoolModel:Find(itemName) + + if not item then + item = itemPoolModel:New({ + name = itemName, + }) + end + + item[key] = saveValue + + local saveItem = true + + if saveValue == nil then + ConvertTable(item, accessName) + + saveItem = false + + -- Check if there is still a value saved for any of the keys + for curKey in pairs(dataTable.keys) do + if item[curKey] ~= nil then + saveItem = true + + break + end + end + + -- Delete the item if there is nothing to save + if not saveItem then + item:Delete() + end + end + + if saveItem then + item:Save() + end + + OnChange(index, itemName, key, value) + + -- If accessLevel is the lowest, send database update to every player + local registerIndex = index + if dataTable.accessLevel >= TTT2_DATABASE_ACCESS_ANY then + registerIndex = INDEX_NONE + end + + SendUpdateNextTick( + MESSAGE_SET_VALUE, + { index = index, itemName = itemName, key = key, value = value }, + registerIndex, + SEND_TO_PLY_REGISTERED + ) + end + + --- + -- Use this to set item-specific defaults, to save storage space in the sql database + -- also syncs this to the clients + -- @note You dont need to check manually if it is a key-specific default. They are excluded anyways + -- @param string accessName the chosen networkable name of the sql table + -- @param string itemName the name or primaryKey of the item inside of the sql table + -- @param string key the name of the key in the database + -- @param any value the value you want to set in the database + -- @realm server + function database.SetDefaultValue(accessName, itemName, key, value) + local index = nameToIndex[accessName] + + if not index then + ErrorNoHaltWithStack( + "[TTT2] database.SetDefaultValue failed. The registered Database of " + .. accessName + .. " is not registered." + ) + + return + end + + local dataTable = registeredDatabases[index] + + if not dataTable.keys[key] then + ErrorNoHaltWithStack( + "[TTT2] database.SetDefaultValue failed. The registered Database of " + .. accessName + .. " doesnt have a key named " + .. key + ) + + return + end + + -- Ignore it if this is the key-default anyways + if value == dataTable.keys[key].default then + return + end + + local defaultData = dataTable.defaultData + defaultData[itemName] = defaultData[itemName] or {} + defaultData[itemName][key] = value + + dataTable.defaultData = defaultData + + -- If accessLevel is the lowest, send database update to every player + local registerIndex = index + if dataTable.accessLevel >= TTT2_DATABASE_ACCESS_ANY then + registerIndex = INDEX_NONE + end + + SendUpdateNextTick( + MESSAGE_GET_DEFAULTVALUE, + { index = index, itemName = itemName, key = key, value = value, sendDefaults = false }, + registerIndex, + SEND_TO_PLY_REGISTERED + ) + end + + --- + -- Use this to set item-specific defaults for all saved keys, to save storage space in the sql database + -- also syncs this to the clients + -- @param string accessName The chosen networkable name of the sql table + -- @param string itemName the name or primaryKey of the item inside of the sql table + -- @param table item The item-table you want to set the default values from + -- @realm server + function database.SetDefaultValuesFromItem(accessName, itemName, item) + local index = nameToIndex[accessName] + if not index then + ErrorNoHaltWithStack( + "[TTT2] database.SetDefaultValuesFromItem failed. The registered Database of " + .. accessName + .. " is not registered." + ) + return + end + if not istable(item) then + ErrorNoHaltWithStack( + "[TTT2] database.SetDefaultValuesFromItem failed. The given item has no accessible values." + ) + return + end + local dataTable = registeredDatabases[index] + for key, keyData in pairs(dataTable.keys) do + local value = item[key] + if value == nil then + continue + end + database.SetDefaultValue(accessName, itemName, key, value) + end + end + + --- + -- Reset the database and send a message to the client + -- @note It is restricted to players with TTT2_DATABASE_ACCESS_ADMIN or higher at all times + -- @param string accessName the chosen networkable name of the sql table + -- @param[opt] string plyID64 the player steam ID 64. Leave this empty when calling on the server. This only makes sure values are only set by superadmins + -- @realm server + function database.Reset(accessName, plyID64) + local index = nameToIndex[accessName] + + if not index then + ErrorNoHaltWithStack( + "[TTT2] database.Reset failed. The registered Database of " + .. accessName + .. " is not available or synced." + ) + + return + end + + local _, hasWriteAccess = hasAccessToDatabase(index, plyID64) + + if not hasWriteAccess then + ErrorNoHaltWithStack( + "[TTT2] database.Reset failed. The player with the ID64 " + .. plyID64 + .. " has no write access to the database " + .. accessName + .. " ." + ) + + return + end + + local dataTable = registeredDatabases[index] + local databaseName = dataTable.databaseName + + -- Drop the table and create a new one as well as an orm-object + sql.DropTable(databaseName) + sql.CreateSqlTable(databaseName, dataTable.keys) + dataTable.orm = orm.Make(databaseName) + + ResetDatabase(index) + + -- If accessLevel is the lowest, send database update to every player + local registerIndex = index + if dataTable.accessLevel >= TTT2_DATABASE_ACCESS_ANY then + registerIndex = INDEX_NONE + end + + SendUpdateNextTick(MESSAGE_RESET, { index = index }, registerIndex, SEND_TO_PLY_REGISTERED) + end + + -- Sync databases to all authenticated players + hook.Add( + "PlayerAuthed", + "TTT2SyncDatabaseIndexTableToAuthorizedPlayers", + function(ply, steamID, uniqueID) + local plyID64 = ply:SteamID64() + + if not IsValid(ply) then + return + end + + playerID64Cache[plyID64] = ply + + database.SyncRegisteredDatabases(plyID64) + end + ) + + -- Remove disconnected players, that were additionally registered due to requesting or setting data + hook.Add("PlayerDisconnected", "TTT2RemovePlayerOfRegisteredPlayersTable", function(ply) + if not IsValid(ply) then + return + end + + local plyID64 = ply:SteamID64() + playerID64Cache[plyID64] = nil + + if not registeredPlayersCache[plyID64] then + return + end + + registeredPlayersCache[plyID64] = nil + + local deleteIndices = {} + + for index, players in pairs(registeredPlayersIndexTable) do + table.RemoveByValue(players, ply) + + if table.IsEmpty(players) then + deleteIndices[#deleteIndices + 1] = index + end + end + + for i = 1, #deleteIndices do + registeredPlayersIndexTable[deleteIndices[i]] = nil + end + end) end diff --git a/lua/ttt2/libraries/door.lua b/lua/ttt2/libraries/door.lua index 48028484d..0d0e86ef4 100644 --- a/lua/ttt2/libraries/door.lua +++ b/lua/ttt2/libraries/door.lua @@ -13,21 +13,24 @@ local IsValid = IsValid local Angle = Angle if SERVER then - AddCSLuaFile() + AddCSLuaFile() - util.AddNetworkString("TTT2SyncDoorEntities") + util.AddNetworkString("TTT2SyncDoorEntities") end --- -- @realm client +-- stylua: ignore local cvDestructableDoorForced = CreateConVar("ttt2_doors_force_pairs", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cvDestructableDoor = CreateConVar("ttt2_doors_destructible", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cvDestructableDoorLocked = CreateConVar("ttt2_doors_locked_indestructible", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) --- @@ -56,171 +59,192 @@ SF_FUNC_DOOR_SILENT_GENERAL = 4096 door = door or {} local door_list = { - doors = {} + doors = {}, } local valid_doors = { - special = { - ["func_door"] = true, - ["func_door_rotating"] = true - }, - normal = { - ["prop_door_rotating"] = true - } + special = { + ["func_door"] = true, + ["func_door_rotating"] = true, + }, + normal = { + ["prop_door_rotating"] = true, + }, } local function GetClosedAngle(ent) - local data = ent:GetInternalVariable("m_angRotationClosed") + local data = ent:GetInternalVariable("m_angRotationClosed") - if not data then return end + if not data then + return + end - return Angle(data[1], data[2], data[3]) + return Angle(data[1], data[2], data[3]) end local function FindPair(ent) - local entsTable = ents.FindInSphere(ent:GetPos(), 94) + local entsTable = ents.FindInSphere(ent:GetPos(), 94) - for i = 1, #entsTable do - local foundEnt = entsTable[i] + for i = 1, #entsTable do + local foundEnt = entsTable[i] - -- a door can't be a pair with itself - if foundEnt == ent or foundEnt:GetClass() ~= ent:GetClass() then continue end + -- a door can't be a pair with itself + if foundEnt == ent or foundEnt:GetClass() ~= ent:GetClass() then + continue + end - -- both doors are only a pair if they have mirrored angles - local ang1 = GetClosedAngle(ent) - local ang2 = GetClosedAngle(foundEnt) + -- both doors are only a pair if they have mirrored angles + local ang1 = GetClosedAngle(ent) + local ang2 = GetClosedAngle(foundEnt) - if not ang1 or not ang2 then continue end + if not ang1 or not ang2 then + continue + end - ang1:Normalize() + ang1:Normalize() - ang2.y = ang2.y + 180 - ang2:Normalize() + ang2.y = ang2.y + 180 + ang2:Normalize() - if ang1 ~= ang2 then continue end + if ang1 ~= ang2 then + continue + end - return foundEnt - end + return foundEnt + end end local function HandleDoorPairs(ent) - local master = ent:GetInternalVariable("m_hMaster") - local owner = ent:GetInternalVariable("m_hOwnerEntity") - - local pair, rigged - - if IsValid(master) then - pair = master - elseif IsValid(owner) then - pair = owner - elseif cvDestructableDoorForced:GetBool() then - pair = FindPair(ent) - rigged = true - end - - if not IsValid(pair) then return end - - ent.otherPairDoor = pair - pair.otherPairDoor = ent - - -- add flag if door combo was rigged - if rigged then - ent.otherPairRigged = true - pair.otherPairRigged = true - end + local master = ent:GetInternalVariable("m_hMaster") + local owner = ent:GetInternalVariable("m_hOwnerEntity") + + local pair, rigged + + if IsValid(master) then + pair = master + elseif IsValid(owner) then + pair = owner + elseif cvDestructableDoorForced:GetBool() then + pair = FindPair(ent) + rigged = true + end + + if not IsValid(pair) then + return + end + + ent.otherPairDoor = pair + pair.otherPairDoor = ent + + -- add flag if door combo was rigged + if rigged then + ent.otherPairRigged = true + pair.otherPairRigged = true + end end local function HandleUseCancel(ent) - ent:EmitSound("doors/door_locked2.wav") + ent:EmitSound("doors/door_locked2.wav") end local function RemoveSpawnFlag(ent, flag) - if not ent:HasSpawnFlags(flag) then return end + if not ent:HasSpawnFlags(flag) then + return + end - ent:SetKeyValue("spawnflags", ent:GetSpawnFlags() - flag) + ent:SetKeyValue("spawnflags", ent:GetSpawnFlags() - flag) end local function AddSpawnFlag(ent, flag) - if ent:HasSpawnFlags(flag) then return end + if ent:HasSpawnFlags(flag) then + return + end - ent:SetKeyValue("spawnflags", ent:GetSpawnFlags() + flag) + ent:SetKeyValue("spawnflags", ent:GetSpawnFlags() + flag) end if SERVER then - --- - -- Setting up all doors found on a map, this is done on every map reset (on prepare round) - -- @internal - -- @realm server - function door.SetUp() - local all_ents = ents.GetAll() - local doors = {} - - -- search for new doors - for i = 1, #all_ents do - local ent = all_ents[i] - - if not ent:IsDoor() then continue end - - doors[#doors + 1] = ent - - ent:SetNWBool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false) - ent:SetNWBool("ttt2_door_forceclosed", ent:GetInternalVariable("forceclosed") or false) - ent:SetNWBool("ttt2_door_open", door.IsOpen(ent) or false) - - ent:SetNWBool("ttt2_door_player_use", door.PlayerCanUse(ent)) - ent:SetNWBool("ttt2_door_player_touch", door.PlayerCanTouch(ent)) - ent:SetNWBool("ttt2_door_auto_close", door.AutoCloses(ent)) - ent:SetNWBool("ttt2_door_is_destructable", door.IsDestructible(ent)) - - entityOutputs.RegisterMapEntityOutput(ent, "OnOpen", "TTT2DoorOpens") - entityOutputs.RegisterMapEntityOutput(ent, "OnClose", "TTT2DoorCloses") - entityOutputs.RegisterMapEntityOutput(ent, "OnFullyOpen", "TTT2DoorFullyOpen") - entityOutputs.RegisterMapEntityOutput(ent, "OnFullyClosed", "TTT2DoorFullyClosed") - - -- handles door pairs, this means double doors will be handles as one - -- door by the door module to prevent weird problems - HandleDoorPairs(ent) - - -- makes doors destructible if enabled by convar - if cvDestructableDoor:GetBool() and not (cvDestructableDoorLocked:GetBool() and ent:IsDoorLocked()) then - ent:MakeDoorDestructable(true) - end - end - - door_list.doors = doors - - --- - -- @realm server - hook.Run("TTT2PostDoorSetup", doors) - - local amountDoors = #doors - - net.Start("TTT2SyncDoorEntities") - net.WriteUInt(amountDoors, 16) - - -- sync door list with clients - for i = 1, amountDoors do - net.WriteEntity(doors[i]) - end - - net.Broadcast() - end + --- + -- Setting up all doors found on a map, this is done on every map reset (on prepare round) + -- @internal + -- @realm server + function door.SetUp() + local all_ents = ents.GetAll() + local doors = {} + + -- search for new doors + for i = 1, #all_ents do + local ent = all_ents[i] + + if not ent:IsDoor() then + continue + end + + doors[#doors + 1] = ent + + ent:SetNWBool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false) + ent:SetNWBool("ttt2_door_forceclosed", ent:GetInternalVariable("forceclosed") or false) + ent:SetNWBool("ttt2_door_open", door.IsOpen(ent) or false) + + ent:SetNWBool("ttt2_door_player_use", door.PlayerCanUse(ent)) + ent:SetNWBool("ttt2_door_player_touch", door.PlayerCanTouch(ent)) + ent:SetNWBool("ttt2_door_auto_close", door.AutoCloses(ent)) + ent:SetNWBool("ttt2_door_is_destructable", door.IsDestructible(ent)) + + entityOutputs.RegisterMapEntityOutput(ent, "OnOpen", "TTT2DoorOpens") + entityOutputs.RegisterMapEntityOutput(ent, "OnClose", "TTT2DoorCloses") + entityOutputs.RegisterMapEntityOutput(ent, "OnFullyOpen", "TTT2DoorFullyOpen") + entityOutputs.RegisterMapEntityOutput(ent, "OnFullyClosed", "TTT2DoorFullyClosed") + + -- handles door pairs, this means double doors will be handles as one + -- door by the door module to prevent weird problems + HandleDoorPairs(ent) + + -- makes doors destructible if enabled by convar + if + cvDestructableDoor:GetBool() + and not (cvDestructableDoorLocked:GetBool() and ent:IsDoorLocked()) + then + ent:MakeDoorDestructable(true) + end + end + + door_list.doors = doors + + --- + -- @realm server + -- stylua: ignore + hook.Run("TTT2PostDoorSetup", doors) + + local amountDoors = #doors + + net.Start("TTT2SyncDoorEntities") + net.WriteUInt(amountDoors, 16) + + -- sync door list with clients + for i = 1, amountDoors do + net.WriteEntity(doors[i]) + end + + net.Broadcast() + end else -- CLIENT - net.Receive("TTT2SyncDoorEntities", function() - local amount = net.ReadUInt(16) + net.Receive("TTT2SyncDoorEntities", function() + local amount = net.ReadUInt(16) - local doors = {} + local doors = {} - for i = 1, amount do - doors[i] = net.ReadEntity() - end + for i = 1, amount do + doors[i] = net.ReadEntity() + end - door_list.doors = doors + door_list.doors = doors - --- - -- @realm client - hook.Run("TTT2PostDoorSetup", doors) - end) + --- + -- @realm client + -- stylua: ignore + hook.Run("TTT2PostDoorSetup", doors) + end) end --- @@ -228,7 +252,7 @@ end -- @return table A table of door class names -- @realm shared function door.GetValid() - return valid_doors + return valid_doors end --- @@ -237,7 +261,7 @@ end -- @return boolean True if it is a valid normal door -- @realm shared function door.IsValidNormal(cls) - return valid_doors.normal[cls] or false + return valid_doors.normal[cls] or false end --- @@ -246,7 +270,7 @@ end -- @return boolean True if it is a valid special door -- @realm shared function door.IsValidSpecial(cls) - return valid_doors.special[cls] or false + return valid_doors.special[cls] or false end --- @@ -254,477 +278,490 @@ end -- @return table A table of door entities -- @realm shared function door.GetAll() - return door_list.doors + return door_list.doors end if SERVER then - --- - -- Returns if a door is open by reading out the internal variable. - -- @param Entity ent The door entity that should be checked - -- @return boolean Returns if the door is open - -- @internal - -- @realm server - function door.IsOpen(ent) - local cls = ent:GetClass() - - if door.IsValidNormal(cls) then - return ent:GetInternalVariable("m_eDoorState") ~= 0 - elseif door.IsValidSpecial(cls) then - return ent:GetInternalVariable("m_toggle_state") == 0 - end - - return false - end - - --- - -- Returns if a player can use interact with a door by reading out the internal variable. - -- @param Entity ent The door entity that should be checked - -- @return boolean Returns if the door reacts to a player use - -- @internal - -- @realm server - function door.PlayerCanUse(ent) - local cls = ent:GetClass() - - if door.IsValidNormal(cls) then - return not ent:HasSpawnFlags(SF_PROP_DOOR_IGNORE_PLAYER_USE) - elseif door.IsValidSpecial(cls) then - return ent:HasSpawnFlags(SF_FUNC_DOOR_PLAYER_USE_OPENS) - end - - return false - end - - --- - -- Returns if touching a door opens it by reading out the internal variable. - -- @param Entity ent The door entity that should be checked - -- @return boolean Returns if the door opens by a player touch - -- @internal - -- @realm server - function door.PlayerCanTouch(ent) - local cls = ent:GetClass() - - if door.IsValidNormal(cls) then - -- this door type has no touch mode - return false - elseif door.IsValidSpecial(cls) then - return ent:HasSpawnFlags(SF_FUNC_DOOR_TOUCH_OPENS) - end - - return false - end - - --- - -- Returns if a door autocloses after some time by reading out the internal variable. - -- @param Entity ent The door entity that should be checked - -- @return boolean Returns if the door autocloses after some time - -- @internal - -- @realm server - function door.AutoCloses(ent) - local cls = ent:GetClass() - - if door.IsValidNormal(cls) then - return not ent:HasSpawnFlags(SF_PROP_DOOR_USE_CLOSES) - elseif door.IsValidSpecial(cls) then - return not ent:HasSpawnFlags(SF_FUNC_DOOR_MODE_TOGGLE) - end - - return false - end - - --- - -- Returns if a door is destructible by reading out the internal variable. - -- @param Entity ent The door entity that should be checked - -- @return boolean Returns if the door is destructible - -- @internal - -- @realm server - function door.IsDestructible(ent) - local cls = ent:GetClass() - - if door.IsValidNormal(cls) then - return ent:HasSpawnFlags(SF_PROP_DOOR_START_BREAKABLE) - elseif door.IsValidSpecial(cls) then - return false - end - - return false - end - - --- - -- Sets if a player can use interact with a door by setting the internal variable. - -- @param Entity ent The door entity that should be checked - -- @param boolean state The new player use state - -- @internal - -- @realm server - function door.SetPlayerCanUse(ent, state) - local cls = ent:GetClass() - - if door.IsValidNormal(cls) then - if state then - RemoveSpawnFlag(ent, SF_PROP_DOOR_IGNORE_PLAYER_USE) - else - AddSpawnFlag(ent, SF_PROP_DOOR_IGNORE_PLAYER_USE) - end - elseif door.IsValidSpecial(cls) then - -- 256: use opens - if state then - AddSpawnFlag(ent, SF_FUNC_DOOR_PLAYER_USE_OPENS) - else - RemoveSpawnFlag(ent, SF_FUNC_DOOR_PLAYER_USE_OPENS) - end - end - end - - --- - -- Sets if a player can touch interact with a door by setting the internal variable. - -- @param Entity ent The door entity that should be checked - -- @param boolean state The new player touch state - -- @internal - -- @realm server - function door.SetPlayerCanTouch(ent, state) - local cls = ent:GetClass() - - if door.IsValidSpecial(cls) then - -- 1024: touch opens - if state then - AddSpawnFlag(ent, SF_FUNC_DOOR_TOUCH_OPENS) - else - RemoveSpawnFlag(ent, SF_FUNC_DOOR_TOUCH_OPENS) - end - end - end - - --- - -- Sets if a door autocloses by setting the internal variable. - -- @param Entity ent The door entity that should be checked - -- @param boolean state The new autoclose state - -- @internal - -- @realm server - function door.SetAutoClose(ent, state) - local cls = ent:GetClass() - - if door.IsValidNormal(cls) then - -- 8192: door closes on use - if state then - RemoveSpawnFlag(ent, SF_PROP_DOOR_USE_CLOSES) - ent:SetKeyValue("returndelay", 3) - else - AddSpawnFlag(ent, SF_PROP_DOOR_USE_CLOSES) - end - elseif door.IsValidSpecial(cls) then - if state then - RemoveSpawnFlag(ent, SF_FUNC_DOOR_MODE_TOGGLE) - else - AddSpawnFlag(ent, SF_FUNC_DOOR_MODE_TOGGLE) - end - end - end - - --- - -- Handles the damage of doors that are still in the wall. - -- Called in @{GM:EntityTakeDamage}. - -- @param Entity ent The entity that is damaged - -- @param DamageInfo dmginfo The damage info object - -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? - -- @internal - -- @realm server - function door.HandleDamage(ent, dmginfo, surpressPair) - if not ent:DoorIsDestructible() then return end - - local damage = math.max(0, dmginfo:GetDamage()) - local health = ent:Health() - damage - - -- if the door is grouped as a pair, call the other one as well - if not surpressPair and IsValid(ent.otherPairDoor) then - door.HandleDamage(ent.otherPairDoor, dmginfo, true) - end - - if health <= 0 then - -- capping the force factor is sufficient because the forward vector is normalized - local forceFactor = math.min(50000, 500 * damage) - local attacker = dmginfo:GetAttacker() - - if not ent:SafeDestroyDoor(attacker, forceFactor * attacker:GetForward(), true) then return end - end - - ent:SetHealth(health) - ent:SetNWInt("fast_sync_health", health) - end - - --- - -- Handles the damage of doors that are lying as props on the ground. - -- Called in @{GM:EntityTakeDamage}. - -- @param Entity ent The entity that is damages - -- @param DamageInfo dmginfo The damage info object - -- @internal - -- @realm server - function door.HandlePropDamage(ent, dmginfo) - if not ent.isDoorProp then return end - - ent:SetHealth(ent:Health() - dmginfo:GetDamage()) - - if ent:Health() > 0 then return end - - door.HandleDestruction(ent) - - ent:Remove() - end - - --- - -- Handles the door desctruction particles of a given door. - -- @param Entity ent The entity that is destroxed - -- @internal - -- @realm server - function door.HandleDestruction(ent) - ent:EmitSound("physics/wood/wood_crate_break3.wav") - - local effectdata = EffectData() - effectdata:SetOrigin(ent:GetPos() + ent:OBBCenter()) - effectdata:SetMagnitude(5) - effectdata:SetScale(2) - effectdata:SetRadius(5) - - util.Effect("Sparks", effectdata) - end - - --- - -- Called in @{GM:AcceptInput} when a map I/O event occurs. - -- @param Entity ent Entity that receives the input - -- @param string input The input name. Is not guaranteed to be a valid input on the entity. - -- @param Entity activator Activator of the input - -- @param Entity caller Caller of the input - -- @param any data Data provided with the input - -- @return boolean Return true to prevent this input from being processed. - -- @internal - -- @realm server - function door.AcceptInput(ent, name, activator, caller, data) - if not IsValid(ent) or not ent:IsDoor() then return end - - name = string.lower(name) - - -- if there is a SID64 in the data string, it should be extracted - local sid - - if data and data ~= "" then - local dataTable = string.Explode("||", data) - local dataTableCleared = {} - - for i = 1, #dataTable do - local dataLine = dataTable[i] - - if string.sub(dataLine, 1, 4) == "sid=" then - sid = string.sub(dataLine, 5) - else - dataTableCleared[#dataTableCleared + 1] = dataLine - end - end - - data = string.Implode("||", dataTableCleared) - end - - local ply = player.GetBySteamID64(sid) - - if IsValid(ply) then - activator = IsValid(activator) and activator or ply - caller = IsValid(caller) and caller or ply - end - - -- always play sound if door is locked - if name == "use" and door.IsValidSpecial(ent:GetClass()) and ent:IsDoorLocked() then - HandleUseCancel(ent) - end - - -- handle the entity input types - if name == "lock" then - --- - -- @realm server - local shouldCancel = hook.Run("TTT2BlockDoorLock", ent, activator, caller) - - if shouldCancel then - return true - end - - -- we expect the door to be locked now, but we check the real state after a short - -- amount of time to be sure - ent:SetNWBool("ttt2_door_locked", true) - - -- check if the assumed state was correct - timer.Create("ttt2_recheck_door_lock_" .. ent:EntIndex(), 1, 1, function() - if not IsValid(ent) then return end - - ent:SetNWBool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false) - end) - elseif name == "unlock" then - --- - -- @realm server - local shouldCancel = hook.Run("TTT2BlockDoorUnlock", ent, activator, caller) - - if shouldCancel then - return true - end - - -- we expect the door to be unlocked now, but we check the real state after a short - -- amount of time to be sure - ent:SetNWBool("ttt2_door_locked", false) - - -- check if the assumed state was correct - timer.Create("ttt2_recheck_door_unlock_" .. ent:EntIndex(), 1, 1, function() - if not IsValid(ent) then return end - - ent:SetNWBool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false) - end) - elseif name == "use" and ent:IsDoorOpen() then - -- do not stack closing time if door closes automatically - if ent:DoorAutoCloses() then - return true - end - - --- - -- @realm server - local shouldCancel = hook.Run("TTT2BlockDoorClose", ent, activator, caller) - - if shouldCancel then - HandleUseCancel(ent) - - return true - end - - if IsValid(ent.otherPairDoor) and ent.otherPairRigged then - ent.otherPairDoor:CloseDoor(activator, nil, 0.2, true) - end - - elseif name == "use" and not ent:IsDoorOpen() then - --- - -- @realm server - local shouldCancel = hook.Run("TTT2BlockDoorOpen", ent, activator, caller) - - if shouldCancel then - HandleUseCancel(ent) - - return true - end - - if IsValid(ent.otherPairDoor) and ent.otherPairRigged then - ent.otherPairDoor:OpenDoor(activator, nil, 0.2, true) - end - end - end - - -- HOOKS AND STUFF - - --- - -- This hook is called after the door started opening. - -- @param Entity doorEntity The door entity - -- @param Entity activator The activator entity, it seems to be the door entity for most doors - -- @hook - -- @realm server - function GM:TTT2DoorOpens(doorEntity, activator) - if not doorEntity:IsDoor() then return end - - doorEntity:SetNWBool("ttt2_door_open", true) - end - - --- - -- This hook is called after the door finished opening and is fully opened. - -- @param Entity doorEntity The door entity - -- @param Entity activator The activator entity, it seems to be the door entity for most doors - -- @hook - -- @realm server - function GM:TTT2DoorFullyOpen(doorEntity, activator) - if not doorEntity:IsDoor() then return end - - doorEntity:SetNWBool("ttt2_door_open", true) - end - - --- - -- This hook is called after the door started closing. - -- @param Entity doorEntity The door entity - -- @param Entity activator The activator entity, it seems to be the door entity for most doors - -- @hook - -- @realm server - function GM:TTT2DoorCloses(doorEntity, activator) - if not doorEntity:IsDoor() then return end - - doorEntity:SetNWBool("ttt2_door_open", false) - end - - --- - -- This hook is called after the door finished closing and is fully closed. - -- @param Entity doorEntity The door entity - -- @param Entity activator The activator entity, it seems to be the door entity for most doors - -- @hook - -- @realm server - function GM:TTT2DoorFullyClosed(doorEntity, activator) - if not doorEntity:IsDoor() then return end - - doorEntity:SetNWBool("ttt2_door_open", false) - end - - --- - -- This hook is called after the door is destroyed and the door prop is spawned. - -- @param Entity doorPropEntity The door prop entity that is created - -- @param Entity activator The activator entity - -- @hook - -- @realm server - function GM:TTT2DoorDestroyed(doorPropEntity, activator) - - end - - --- - -- This hook is called when the door is about to be locked. You can cancel the event. - -- @param Entity doorEntity The door entity - -- @param Entity activator The activator entity - -- @param Entity caller The caller entity - -- @return boolean Return true to cancel the door lock - -- @hook - -- @realm server - function GM:TTT2BlockDoorLock(doorEntity, activator, caller) - - end - - --- - -- This hook is called when the door is about to be unlocked. You can cancel the event. - -- @param Entity doorEntity The door entity - -- @param Entity activator The activator entity - -- @param Entity caller The caller entity - -- @return boolean Return true to cancel the door unlock - -- @hook - -- @realm server - function GM:TTT2BlockDoorUnlock(doorEntity, activator, caller) - - end - - --- - -- This hook is called when the door is about to be opened. You can cancel the event. - -- @param Entity doorEntity The door entity - -- @param Entity activator The activator entity - -- @param Entity caller The caller entity - -- @return boolean Return true to cancel the door opening - -- @hook - -- @realm server - function GM:TTT2BlockDoorOpen(doorEntity, activator, caller) - - end - - --- - -- This hook is called when the door is about to be closed. You can cancel the event. - -- @param Entity doorEntity The door entity - -- @param Entity activator The activator entity - -- @param Entity caller The caller entity - -- @return boolean Return true to cancel the door closing - -- @hook - -- @realm server - function GM:TTT2BlockDoorClose(doorEntity, activator, caller) - - end - - --- - -- This hook is called when the door is about to be destroyed. You can cancel the event. - -- @param Entity doorEntity The door entity - -- @param Entity activator The activator entity - -- @return boolean Return true to cancel the door destruction - -- @hook - -- @realm server - function GM:TTT2BlockDoorDestruction(doorEntity, activator) - - end + --- + -- Returns if a door is open by reading out the internal variable. + -- @param Entity ent The door entity that should be checked + -- @return boolean Returns if the door is open + -- @internal + -- @realm server + function door.IsOpen(ent) + local cls = ent:GetClass() + + if door.IsValidNormal(cls) then + return ent:GetInternalVariable("m_eDoorState") ~= 0 + elseif door.IsValidSpecial(cls) then + return ent:GetInternalVariable("m_toggle_state") == 0 + end + + return false + end + + --- + -- Returns if a player can use interact with a door by reading out the internal variable. + -- @param Entity ent The door entity that should be checked + -- @return boolean Returns if the door reacts to a player use + -- @internal + -- @realm server + function door.PlayerCanUse(ent) + local cls = ent:GetClass() + + if door.IsValidNormal(cls) then + return not ent:HasSpawnFlags(SF_PROP_DOOR_IGNORE_PLAYER_USE) + elseif door.IsValidSpecial(cls) then + return ent:HasSpawnFlags(SF_FUNC_DOOR_PLAYER_USE_OPENS) + end + + return false + end + + --- + -- Returns if touching a door opens it by reading out the internal variable. + -- @param Entity ent The door entity that should be checked + -- @return boolean Returns if the door opens by a player touch + -- @internal + -- @realm server + function door.PlayerCanTouch(ent) + local cls = ent:GetClass() + + if door.IsValidNormal(cls) then + -- this door type has no touch mode + return false + elseif door.IsValidSpecial(cls) then + return ent:HasSpawnFlags(SF_FUNC_DOOR_TOUCH_OPENS) + end + + return false + end + + --- + -- Returns if a door autocloses after some time by reading out the internal variable. + -- @param Entity ent The door entity that should be checked + -- @return boolean Returns if the door autocloses after some time + -- @internal + -- @realm server + function door.AutoCloses(ent) + local cls = ent:GetClass() + + if door.IsValidNormal(cls) then + return not ent:HasSpawnFlags(SF_PROP_DOOR_USE_CLOSES) + elseif door.IsValidSpecial(cls) then + return not ent:HasSpawnFlags(SF_FUNC_DOOR_MODE_TOGGLE) + end + + return false + end + + --- + -- Returns if a door is destructible by reading out the internal variable. + -- @param Entity ent The door entity that should be checked + -- @return boolean Returns if the door is destructible + -- @internal + -- @realm server + function door.IsDestructible(ent) + local cls = ent:GetClass() + + if door.IsValidNormal(cls) then + return ent:HasSpawnFlags(SF_PROP_DOOR_START_BREAKABLE) + elseif door.IsValidSpecial(cls) then + return false + end + + return false + end + + --- + -- Sets if a player can use interact with a door by setting the internal variable. + -- @param Entity ent The door entity that should be checked + -- @param boolean state The new player use state + -- @internal + -- @realm server + function door.SetPlayerCanUse(ent, state) + local cls = ent:GetClass() + + if door.IsValidNormal(cls) then + if state then + RemoveSpawnFlag(ent, SF_PROP_DOOR_IGNORE_PLAYER_USE) + else + AddSpawnFlag(ent, SF_PROP_DOOR_IGNORE_PLAYER_USE) + end + elseif door.IsValidSpecial(cls) then + -- 256: use opens + if state then + AddSpawnFlag(ent, SF_FUNC_DOOR_PLAYER_USE_OPENS) + else + RemoveSpawnFlag(ent, SF_FUNC_DOOR_PLAYER_USE_OPENS) + end + end + end + + --- + -- Sets if a player can touch interact with a door by setting the internal variable. + -- @param Entity ent The door entity that should be checked + -- @param boolean state The new player touch state + -- @internal + -- @realm server + function door.SetPlayerCanTouch(ent, state) + local cls = ent:GetClass() + + if door.IsValidSpecial(cls) then + -- 1024: touch opens + if state then + AddSpawnFlag(ent, SF_FUNC_DOOR_TOUCH_OPENS) + else + RemoveSpawnFlag(ent, SF_FUNC_DOOR_TOUCH_OPENS) + end + end + end + + --- + -- Sets if a door autocloses by setting the internal variable. + -- @param Entity ent The door entity that should be checked + -- @param boolean state The new autoclose state + -- @internal + -- @realm server + function door.SetAutoClose(ent, state) + local cls = ent:GetClass() + + if door.IsValidNormal(cls) then + -- 8192: door closes on use + if state then + RemoveSpawnFlag(ent, SF_PROP_DOOR_USE_CLOSES) + ent:SetKeyValue("returndelay", 3) + else + AddSpawnFlag(ent, SF_PROP_DOOR_USE_CLOSES) + end + elseif door.IsValidSpecial(cls) then + if state then + RemoveSpawnFlag(ent, SF_FUNC_DOOR_MODE_TOGGLE) + else + AddSpawnFlag(ent, SF_FUNC_DOOR_MODE_TOGGLE) + end + end + end + + --- + -- Handles the damage of doors that are still in the wall. + -- Called in @{GM:EntityTakeDamage}. + -- @param Entity ent The entity that is damaged + -- @param CTakeDamageInfo dmginfo The damage info object + -- @param[default=false] boolean surpressPair Should the call of the other door (if in a pair) be omitted? + -- @internal + -- @realm server + function door.HandleDamage(ent, dmginfo, surpressPair) + if not ent:DoorIsDestructible() then + return + end + + local damage = math.max(0, dmginfo:GetDamage()) + local health = ent:Health() - damage + + -- if the door is grouped as a pair, call the other one as well + if not surpressPair and IsValid(ent.otherPairDoor) then + door.HandleDamage(ent.otherPairDoor, dmginfo, true) + end + + if health <= 0 then + -- capping the force factor is sufficient because the forward vector is normalized + local forceFactor = math.min(50000, 500 * damage) + local attacker = dmginfo:GetAttacker() + + if not ent:SafeDestroyDoor(attacker, forceFactor * attacker:GetForward(), true) then + return + end + end + + ent:SetHealth(health) + ent:SetNWInt("fast_sync_health", health) + end + + --- + -- Handles the damage of doors that are lying as props on the ground. + -- Called in @{GM:EntityTakeDamage}. + -- @param Entity ent The entity that is damages + -- @param CTakeDamageInfo dmginfo The damage info object + -- @internal + -- @realm server + function door.HandlePropDamage(ent, dmginfo) + if not ent.isDoorProp then + return + end + + ent:SetHealth(ent:Health() - dmginfo:GetDamage()) + + if ent:Health() > 0 then + return + end + + door.HandleDestruction(ent) + + ent:Remove() + end + + --- + -- Handles the door desctruction particles of a given door. + -- @param Entity ent The entity that is destroxed + -- @internal + -- @realm server + function door.HandleDestruction(ent) + ent:EmitSound("physics/wood/wood_crate_break3.wav") + + local effectdata = EffectData() + effectdata:SetOrigin(ent:GetPos() + ent:OBBCenter()) + effectdata:SetMagnitude(5) + effectdata:SetScale(2) + effectdata:SetRadius(5) + + util.Effect("Sparks", effectdata) + end + + --- + -- Called in @{GM:AcceptInput} when a map I/O event occurs. + -- @param Entity ent Entity that receives the input + -- @param string input The input name. Is not guaranteed to be a valid input on the entity. + -- @param Entity activator Activator of the input + -- @param Entity caller Caller of the input + -- @param any data Data provided with the input + -- @return boolean Return true to prevent this input from being processed. + -- @internal + -- @realm server + function door.AcceptInput(ent, name, activator, caller, data) + if not IsValid(ent) or not ent:IsDoor() then + return + end + + name = string.lower(name) + + -- if there is a SID64 in the data string, it should be extracted + local sid + + if data and data ~= "" then + local dataTable = string.Explode("||", data) + local dataTableCleared = {} + + for i = 1, #dataTable do + local dataLine = dataTable[i] + + if string.sub(dataLine, 1, 4) == "sid=" then + sid = string.sub(dataLine, 5) + else + dataTableCleared[#dataTableCleared + 1] = dataLine + end + end + + data = table.concat(dataTableCleared, "||") + end + + local ply = player.GetBySteamID64(sid) + + if IsValid(ply) then + activator = IsValid(activator) and activator or ply + caller = IsValid(caller) and caller or ply + end + + -- always play sound if door is locked + if name == "use" and door.IsValidSpecial(ent:GetClass()) and ent:IsDoorLocked() then + HandleUseCancel(ent) + end + + -- handle the entity input types + if name == "lock" then + --- + -- @realm server + -- stylua: ignore + local shouldCancel = hook.Run("TTT2BlockDoorLock", ent, activator, caller) + + if shouldCancel then + return true + end + + -- we expect the door to be locked now, but we check the real state after a short + -- amount of time to be sure + ent:SetNWBool("ttt2_door_locked", true) + + -- check if the assumed state was correct + timer.Create("ttt2_recheck_door_lock_" .. ent:EntIndex(), 1, 1, function() + if not IsValid(ent) then + return + end + + ent:SetNWBool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false) + end) + elseif name == "unlock" then + --- + -- @realm server + -- stylua: ignore + local shouldCancel = hook.Run("TTT2BlockDoorUnlock", ent, activator, caller) + + if shouldCancel then + return true + end + + -- we expect the door to be unlocked now, but we check the real state after a short + -- amount of time to be sure + ent:SetNWBool("ttt2_door_locked", false) + + -- check if the assumed state was correct + timer.Create("ttt2_recheck_door_unlock_" .. ent:EntIndex(), 1, 1, function() + if not IsValid(ent) then + return + end + + ent:SetNWBool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false) + end) + elseif name == "use" and ent:IsDoorOpen() then + -- do not stack closing time if door closes automatically + if ent:DoorAutoCloses() then + return true + end + + --- + -- @realm server + -- stylua: ignore + local shouldCancel = hook.Run("TTT2BlockDoorClose", ent, activator, caller) + + if shouldCancel then + HandleUseCancel(ent) + + return true + end + + if IsValid(ent.otherPairDoor) and ent.otherPairRigged then + ent.otherPairDoor:CloseDoor(activator, nil, 0.2, true) + end + elseif name == "use" and not ent:IsDoorOpen() then + --- + -- @realm server + -- stylua: ignore + local shouldCancel = hook.Run("TTT2BlockDoorOpen", ent, activator, caller) + + if shouldCancel then + HandleUseCancel(ent) + + return true + end + + if IsValid(ent.otherPairDoor) and ent.otherPairRigged then + ent.otherPairDoor:OpenDoor(activator, nil, 0.2, true) + end + end + end + + -- HOOKS AND STUFF + + --- + -- This hook is called after the door started opening. + -- @param Entity doorEntity The door entity + -- @param Entity activator The activator entity, it seems to be the door entity for most doors + -- @hook + -- @realm server + function GM:TTT2DoorOpens(doorEntity, activator) + if not doorEntity:IsDoor() then + return + end + + doorEntity:SetNWBool("ttt2_door_open", true) + end + + --- + -- This hook is called after the door finished opening and is fully opened. + -- @param Entity doorEntity The door entity + -- @param Entity activator The activator entity, it seems to be the door entity for most doors + -- @hook + -- @realm server + function GM:TTT2DoorFullyOpen(doorEntity, activator) + if not doorEntity:IsDoor() then + return + end + + doorEntity:SetNWBool("ttt2_door_open", true) + end + + --- + -- This hook is called after the door started closing. + -- @param Entity doorEntity The door entity + -- @param Entity activator The activator entity, it seems to be the door entity for most doors + -- @hook + -- @realm server + function GM:TTT2DoorCloses(doorEntity, activator) + if not doorEntity:IsDoor() then + return + end + + doorEntity:SetNWBool("ttt2_door_open", false) + end + + --- + -- This hook is called after the door finished closing and is fully closed. + -- @param Entity doorEntity The door entity + -- @param Entity activator The activator entity, it seems to be the door entity for most doors + -- @hook + -- @realm server + function GM:TTT2DoorFullyClosed(doorEntity, activator) + if not doorEntity:IsDoor() then + return + end + + doorEntity:SetNWBool("ttt2_door_open", false) + end + + --- + -- This hook is called after the door is destroyed and the door prop is spawned. + -- @param Entity doorPropEntity The door prop entity that is created + -- @param Entity activator The activator entity + -- @hook + -- @realm server + function GM:TTT2DoorDestroyed(doorPropEntity, activator) end + + --- + -- This hook is called when the door is about to be locked. You can cancel the event. + -- @param Entity doorEntity The door entity + -- @param Entity activator The activator entity + -- @param Entity caller The caller entity + -- @return boolean Return true to cancel the door lock + -- @hook + -- @realm server + function GM:TTT2BlockDoorLock(doorEntity, activator, caller) end + + --- + -- This hook is called when the door is about to be unlocked. You can cancel the event. + -- @param Entity doorEntity The door entity + -- @param Entity activator The activator entity + -- @param Entity caller The caller entity + -- @return boolean Return true to cancel the door unlock + -- @hook + -- @realm server + function GM:TTT2BlockDoorUnlock(doorEntity, activator, caller) end + + --- + -- This hook is called when the door is about to be opened. You can cancel the event. + -- @param Entity doorEntity The door entity + -- @param Entity activator The activator entity + -- @param Entity caller The caller entity + -- @return boolean Return true to cancel the door opening + -- @hook + -- @realm server + function GM:TTT2BlockDoorOpen(doorEntity, activator, caller) end + + --- + -- This hook is called when the door is about to be closed. You can cancel the event. + -- @param Entity doorEntity The door entity + -- @param Entity activator The activator entity + -- @param Entity caller The caller entity + -- @return boolean Return true to cancel the door closing + -- @hook + -- @realm server + function GM:TTT2BlockDoorClose(doorEntity, activator, caller) end + + --- + -- This hook is called when the door is about to be destroyed. You can cancel the event. + -- @param Entity doorEntity The door entity + -- @param Entity activator The activator entity + -- @return boolean Return true to cancel the door destruction + -- @hook + -- @realm server + function GM:TTT2BlockDoorDestruction(doorEntity, activator) end end diff --git a/lua/ttt2/libraries/drawsc.lua b/lua/ttt2/libraries/drawsc.lua index 7ecea63ef..6ec331f77 100644 --- a/lua/ttt2/libraries/drawsc.lua +++ b/lua/ttt2/libraries/drawsc.lua @@ -6,9 +6,9 @@ -- @module drawsc if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return + return end local GetGlobalScale = appearance.GetGlobalScale @@ -39,9 +39,16 @@ drawsc = {} -- @2D -- @realm client function drawsc.OutlinedBox(x, y, w, h, t, color) - local scale = GetGlobalScale() + local scale = GetGlobalScale() - drawOutlinedBox(mRound(x * scale), mRound(y * scale), mRound(w * scale), mRound(h * scale), mRound(t * scale), color) + drawOutlinedBox( + mRound(x * scale), + mRound(y * scale), + mRound(w * scale), + mRound(h * scale), + mRound(t * scale), + color + ) end --- @@ -56,9 +63,17 @@ end -- @2D -- @realm client function drawsc.OutlinedShadowedBox(x, y, w, h, t, color) - local scale = GetGlobalScale() + local scale = GetGlobalScale() - drawOutlinedShadowedBox(mRound(x * scale), mRound(y * scale), mRound(w * scale), mRound(h * scale), mRound(t * scale), color, scale) + drawOutlinedShadowedBox( + mRound(x * scale), + mRound(y * scale), + mRound(w * scale), + mRound(h * scale), + mRound(t * scale), + color, + scale + ) end --- @@ -72,9 +87,9 @@ end -- @2D -- @realm client function drawsc.Box(x, y, w, h, color) - local scale = GetGlobalScale() + local scale = GetGlobalScale() - drawBox(mRound(x * scale), mRound(y * scale), mRound(w * scale), mRound(h * scale), color) + drawBox(mRound(x * scale), mRound(y * scale), mRound(w * scale), mRound(h * scale), color) end --- @@ -88,9 +103,16 @@ end -- @2D -- @realm client function drawsc.ShadowedBox(x, y, w, h, color) - local scale = GetGlobalScale() + local scale = GetGlobalScale() - drawShadowedBox(mRound(x * scale), mRound(y * scale), mRound(w * scale), mRound(h * scale), color, scale) + drawShadowedBox( + mRound(x * scale), + mRound(y * scale), + mRound(w * scale), + mRound(h * scale), + color, + scale + ) end --- @@ -103,9 +125,9 @@ end -- @2D -- @realm client function drawsc.OutlinedCircle(x, y, r, color) - local scale = GetGlobalScale() + local scale = GetGlobalScale() - drawOutlinedCircle(mRound(x * scale), mRound(y * scale), mRound(r * scale), color) + drawOutlinedCircle(mRound(x * scale), mRound(y * scale), mRound(r * scale), color) end --- @@ -118,9 +140,15 @@ end -- @2D -- @realm client function drawsc.OutlinedShadowedCircle(x, y, r, color) - local scale = GetGlobalScale() + local scale = GetGlobalScale() - drawOutlinedShadowedCircle(mRound(x * scale), mRound(y * scale), mRound(r * scale), color, scale) + drawOutlinedShadowedCircle( + mRound(x * scale), + mRound(y * scale), + mRound(r * scale), + color, + scale + ) end --- @@ -136,9 +164,17 @@ end -- @2D -- @realm client function drawsc.FilteredTexture(x, y, w, h, material, alpha, color) - local scale = GetGlobalScale() + local scale = GetGlobalScale() - drawFilteredTexture(mRound(x * scale), mRound(y * scale), mRound(w * scale), mRound(h * scale), material, alpha, color) + drawFilteredTexture( + mRound(x * scale), + mRound(y * scale), + mRound(w * scale), + mRound(h * scale), + material, + alpha, + color + ) end --- @@ -154,9 +190,18 @@ end -- @2D -- @realm client function drawsc.FilteredShadowedTexture(x, y, w, h, material, alpha, color) - local scale = GetGlobalScale() + local scale = GetGlobalScale() - drawFilteredShadowedTexture(mRound(x * scale), mRound(y * scale), mRound(w * scale), mRound(h * scale), material, alpha, color, scale) + drawFilteredShadowedTexture( + mRound(x * scale), + mRound(y * scale), + mRound(w * scale), + mRound(h * scale), + material, + alpha, + color, + scale + ) end --- @@ -168,15 +213,27 @@ end -- @param number y The y coordinate -- @param Color color The color of the text. Uses the Color structure. -- @param number xalign The alignment of the x coordinate using --- TEXT_ALIGN_Enums. +-- TEXT_ALIGN_Enums. -- @param number yalign The alignment of the y coordinate using --- TEXT_ALIGN_Enums. +-- TEXT_ALIGN_Enums. +-- @param[default=0] number angle The rotational angle in degree -- @2D -- @realm client -function drawsc.AdvancedText(text, font, x, y, color, xalign, yalign) - local scale = GetGlobalScale() +function drawsc.AdvancedText(text, font, x, y, color, xalign, yalign, angle) + local scale = GetGlobalScale() - drawAdvancedText(text, font, mRound(x * scale), mRound(y * scale), color, xalign, yalign, false, scale) + drawAdvancedText( + text, + font, + mRound(x * scale), + mRound(y * scale), + color, + xalign, + yalign, + false, + scale, + angle + ) end --- @@ -188,15 +245,27 @@ end -- @param number y The y coordinate -- @param Color color The color of the text. Uses the Color structure. -- @param number xalign The alignment of the x coordinate using --- TEXT_ALIGN_Enums. +-- TEXT_ALIGN_Enums. -- @param number yalign The alignment of the y coordinate using --- TEXT_ALIGN_Enums. +-- TEXT_ALIGN_Enums. +-- @param[default=0] number angle The rotational angle in degree -- @2D -- @realm client -function drawsc.AdvancedShadowedText(text, font, x, y, color, xalign, yalign) - local scale = GetGlobalScale() +function drawsc.AdvancedShadowedText(text, font, x, y, color, xalign, yalign, angle) + local scale = GetGlobalScale() - drawAdvancedText(text, font, mRound(x * scale), mRound(y * scale), color, xalign, yalign, true, scale) + drawAdvancedText( + text, + font, + mRound(x * scale), + mRound(y * scale), + color, + xalign, + yalign, + true, + scale, + angle + ) end --- @@ -209,7 +278,13 @@ end -- @2D -- @realm client function drawsc.BlurredBox(x, y, w, h, fraction) - local scale = GetGlobalScale() + local scale = GetGlobalScale() - drawBlurredBox(mRound(x * scale), mRound(y * scale), mRound(w * scale), mRound(h * scale), fraction) + drawBlurredBox( + mRound(x * scale), + mRound(y * scale), + mRound(w * scale), + mRound(h * scale), + fraction + ) end diff --git a/lua/ttt2/libraries/entity_outputs.lua b/lua/ttt2/libraries/entity_outputs.lua index 1e150096b..bf0e53f17 100644 --- a/lua/ttt2/libraries/entity_outputs.lua +++ b/lua/ttt2/libraries/entity_outputs.lua @@ -4,7 +4,9 @@ -- @author Mineotopia -- @module entityOutputs -if CLIENT then return end -- this is a serverside-only module +if CLIENT then + return +end -- this is a serverside-only module entityOutputs = entityOutputs or {} entityOutputs.hooks = entityOutputs.hooks or {} @@ -15,11 +17,13 @@ entityOutputs.hooks = entityOutputs.hooks or {} -- @internal -- @realm server function entityOutputs.SetUp() - if IsValid(entityOutputs.mapLuaRun) then return end + if IsValid(entityOutputs.mapLuaRun) then + return + end - entityOutputs.mapLuaRun = ents.Create("lua_run") - entityOutputs.mapLuaRun:SetName("triggerhook") - entityOutputs.mapLuaRun:Spawn() + entityOutputs.mapLuaRun = ents.Create("lua_run") + entityOutputs.mapLuaRun:SetName("triggerhook") + entityOutputs.mapLuaRun:Spawn() end --- @@ -28,11 +32,11 @@ end -- @internal -- @realm server function entityOutputs.CleanUp() - for hookName in pairs(entityOutputs.hooks) do - hook.Remove(hookName .. "_Internal", hookName .. "_name") - end + for hookName in pairs(entityOutputs.hooks) do + hook.Remove(hookName .. "_Internal", hookName .. "_name") + end - entityOutputs.hooks = {} + entityOutputs.hooks = {} end --- @@ -41,17 +45,20 @@ end -- @internal -- @realm server function entityOutputs.RegisterHook(hookName) - if entityOutputs.hooks[hookName] then return end + if entityOutputs.hooks[hookName] then + return + end - entityOutputs.hooks[hookName] = true + entityOutputs.hooks[hookName] = true - hook.Add(hookName .. "_Internal", hookName .. "_name", function() - local activator, caller = ACTIVATOR, CALLER + hook.Add(hookName .. "_Internal", hookName .. "_name", function() + local activator, caller = ACTIVATOR, CALLER - --- - -- @ignore - hook.Run(hookName, caller, activator) - end) + --- + -- @ignore + -- stylua: ignore + hook.Run(hookName, caller, activator) + end) end --- @@ -68,14 +75,17 @@ end -- @ref https://wiki.facepunch.com/gmod/Entity:Fire -- @realm server function entityOutputs.RegisterMapEntityOutput(ent, outputName, hookName, delay, repititions) - if not IsValid(ent) then return end + if not IsValid(ent) then + return + end - delay = delay or 0 - repititions = repititions or -1 + delay = delay or 0 + repititions = repititions or -1 - --- - -- @ignore - ent:Fire("AddOutput", outputName .. " triggerhook:RunPassedCode:hook.Run('" .. hookName .. "_Internal'):" .. delay .. ":" .. repititions) + --- + -- @ignore + -- stylua: ignore + ent:Fire("AddOutput", outputName .. " triggerhook:RunPassedCode:hook.Run('" .. hookName .. "_Internal'):" .. delay .. ":" .. repititions) - entityOutputs.RegisterHook(hookName) + entityOutputs.RegisterHook(hookName) end diff --git a/lua/ttt2/libraries/entspawn.lua b/lua/ttt2/libraries/entspawn.lua index 26c711cf4..42274323c 100644 --- a/lua/ttt2/libraries/entspawn.lua +++ b/lua/ttt2/libraries/entspawn.lua @@ -3,10 +3,13 @@ -- @author Mineotopia -- @module entspawn -if CLIENT then return end -- this is a serverside-ony module +if CLIENT then + return +end -- this is a serverside-ony module --- -- @realm server +-- stylua: ignore local cvSpawnWaveInterval = CreateConVar("ttt_spawn_wave_interval", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) local pairs = pairs @@ -25,18 +28,18 @@ entspawn = entspawn or {} -- Enable or disable forced random spawns for 'env_entity_maker' spawning non available -- random spawns at map start. -- @note see: https://developer.valvesoftware.com/wiki/Env_entity_maker --- @param bool enable The state to set it to. +-- @param boolean enable The state to set it to. -- @realm server function entspawn.SetForcedRandomSpawn(enable) - allowForcedRandomSpawn = enable + allowForcedRandomSpawn = enable end --- -- To check if forced random spawns are available. --- @return bool if forced random spawns are enabled +-- @return boolean if forced random spawns are enabled -- @realm server function entspawn.IsForcedRandomSpawnEnabled() - return allowForcedRandomSpawn + return allowForcedRandomSpawn end --- @@ -44,18 +47,18 @@ end -- @param Entity ent the entity holding the random weapon data -- @realm server function entspawn.SpawnRandomWeapon(ent) - local spawns = { - [SPAWN_TYPE_WEAPON] = { - [1] = { - pos = ent:GetPos(), - ang = ent:GetAngles(), - ammo = ent.autoAmmoAmount or 0 - } - } - } - - local wepsForTypes, weps = WEPS.GetWeaponsForSpawnTypes() - entspawn.SpawnEntities(spawns, wepsForTypes, weps, WEAPON_TYPE_RANDOM) + local spawns = { + [WEAPON_TYPE_RANDOM] = { + [1] = { + pos = ent:GetPos(), + ang = ent:GetAngles(), + ammo = ent.autoAmmoAmount or 0, + }, + }, + } + + local wepsForTypes, weps = WEPS.GetWeaponsForSpawnTypes() + entspawn.SpawnEntities(spawns, wepsForTypes, weps, WEAPON_TYPE_RANDOM) end --- @@ -63,50 +66,54 @@ end -- @param Entity ent the entity holding the random ammo data -- @realm server function entspawn.SpawnRandomAmmo(ent) - local spawns = { - [SPAWN_TYPE_AMMO] = { - [1] = { - pos = ent:GetPos(), - ang = ent:GetAngles(), - ammo = 1 - } - } - } - - local ammoForTypes, ammo = WEPS.GetAmmoForSpawnTypes() - entspawn.SpawnEntities(spawns, ammoForTypes, ammo, AMMO_TYPE_RANDOM) + local spawns = { + [AMMO_TYPE_RANDOM] = { + [1] = { + pos = ent:GetPos(), + ang = ent:GetAngles(), + ammo = 1, + }, + }, + } + + local ammoForTypes, ammo = WEPS.GetAmmoForSpawnTypes() + entspawn.SpawnEntities(spawns, ammoForTypes, ammo, AMMO_TYPE_RANDOM) end local function RemoveEntities(entTable, spawnTable, spawnType) - local useDefaultSpawns = not entspawnscript.ShouldUseCustomSpawns() + local useDefaultSpawns = not entspawnscript.ShouldUseCustomSpawns() - for _, ents in pairs(entTable) do - for i = 1, #ents do - local ent = ents[i] + for _, ents in pairs(entTable) do + for i = 1, #ents do + local ent = ents[i] - -- do not remove entity if no custom spawns are used - if useDefaultSpawns then - if map.IsDefaultTerrortownMapEntity(ent) then continue end + -- do not remove entity if no custom spawns are used + if useDefaultSpawns then + if map.IsDefaultTerrortownMapEntity(ent) then + continue + end - -- since some obscure spawn entities are valid for multiple different spawn types, - -- they can be used to spawn different types of entities. Therefore this is a table. - local entType, data = map.GetDataFromSpawnEntity(ent, spawnType) + -- since some obscure spawn entities are valid for multiple different spawn types, + -- they can be used to spawn different types of entities. Therefore this is a table. + local entType, data = map.GetDataFromSpawnEntity(ent, spawnType) - spawnTable[spawnType] = spawnTable[spawnType] or {} - spawnTable[spawnType][entType] = spawnTable[spawnType][entType] or {} + spawnTable[spawnType] = spawnTable[spawnType] or {} + spawnTable[spawnType][entType] = spawnTable[spawnType][entType] or {} - spawnTable[spawnType][entType][#spawnTable[spawnType][entType] + 1] = data - end + spawnTable[spawnType][entType][#spawnTable[spawnType][entType] + 1] = data + end - ent:Remove() - end - end + ent:Remove() + end + end end local function GetRandomEntityFromTable(ents) - if not ents then return end + if not ents then + return + end - return ents[math.random(#ents)] + return ents[math.random(#ents)] end --- @@ -117,13 +124,13 @@ end -- @return table spawnTable A table of entities that should be spawned additionally -- @realm server function entspawn.RemoveMapEntities() - local spawnTable = entspawnscript.GetEmptySpawnTableStructure() + local spawnTable = entspawnscript.GetEmptySpawnTableStructure() - RemoveEntities(map.GetWeaponSpawnEntities(), spawnTable, SPAWN_TYPE_WEAPON) - RemoveEntities(map.GetAmmoSpawnEntities(), spawnTable, SPAWN_TYPE_AMMO) - RemoveEntities(map.GetPlayerSpawnEntities(), spawnTable, SPAWN_TYPE_PLAYER) + RemoveEntities(map.GetWeaponSpawnEntities(), spawnTable, SPAWN_TYPE_WEAPON) + RemoveEntities(map.GetAmmoSpawnEntities(), spawnTable, SPAWN_TYPE_AMMO) + RemoveEntities(map.GetPlayerSpawnEntities(), spawnTable, SPAWN_TYPE_PLAYER) - return spawnTable + return spawnTable end --- @@ -135,51 +142,63 @@ end -- @param number randomType The spawn type that should be used as random -- @realm server function entspawn.SpawnEntities(spawns, entsForTypes, entTable, randomType) - if not spawns then return end - - for entType, spawnTable in pairs(spawns) do - for i = 1, #spawnTable do - local spawn = spawnTable[i] - --Check if spawn.pos is valid - if not spawn or not spawn.pos then continue end - -- if the weapon spawn is a random weapon spawn, select any spawnable weapon - local selectedEnt - if entType == randomType then - selectedEnt = GetRandomEntityFromTable(entTable) - else - selectedEnt = GetRandomEntityFromTable(entsForTypes[entType]) - end - - if not selectedEnt then continue end - - -- create the weapon entity - local spawnedEnt = ents.Create(WEPS.GetClass(selectedEnt)) - - if not IsValid(spawnedEnt) then continue end - - -- set the position and angle of the spawned weapon entity - spawnedEnt:SetPos(spawn.pos) - spawnedEnt:SetAngles(spawn.ang) - spawnedEnt:Spawn() - spawnedEnt:PhysWake() - - -- if the spawn has a defined auto ammo spawn, the ammo should be spawned now - if spawn.ammo == 0 or not spawnedEnt.AmmoEnt then continue end - - for k = 1, spawn.ammo do - local ammoEnt = ents.Create(spawnedEnt.AmmoEnt) - - if not IsValid(ammoEnt) then continue end - - local pos = spawn.pos + Vector(mathRand(-35, 35), mathRand(-35, 35), 25) - - ammoEnt:SetPos(pos) - ammoEnt:SetAngles(VectorRand():Angle()) - ammoEnt:Spawn() - ammoEnt:PhysWake() - end - end - end + if not spawns then + return + end + + for entType, spawnTable in pairs(spawns) do + for i = 1, #spawnTable do + local spawn = spawnTable[i] + --Check if spawn.pos is valid + if not spawn or not spawn.pos then + continue + end + -- if the weapon spawn is a random weapon spawn, select any spawnable weapon + local selectedEnt + if entType == randomType then + selectedEnt = GetRandomEntityFromTable(entTable) + else + selectedEnt = GetRandomEntityFromTable(entsForTypes[entType]) + end + + if not selectedEnt then + continue + end + + -- create the weapon entity + local spawnedEnt = ents.Create(WEPS.GetClass(selectedEnt)) + + if not IsValid(spawnedEnt) then + continue + end + + -- set the position and angle of the spawned weapon entity + spawnedEnt:SetPos(spawn.pos) + spawnedEnt:SetAngles(spawn.ang) + spawnedEnt:Spawn() + spawnedEnt:PhysWake() + + -- if the spawn has a defined auto ammo spawn, the ammo should be spawned now + if spawn.ammo == 0 or not spawnedEnt.AmmoEnt then + continue + end + + for k = 1, spawn.ammo do + local ammoEnt = ents.Create(spawnedEnt.AmmoEnt) + + if not IsValid(ammoEnt) then + continue + end + + local pos = spawn.pos + Vector(mathRand(-35, 35), mathRand(-35, 35), 25) + + ammoEnt:SetPos(pos) + ammoEnt:SetAngles(VectorRand():Angle()) + ammoEnt:Spawn() + ammoEnt:PhysWake() + end + end + end end --- @@ -187,57 +206,57 @@ end -- @param[opt] boolean deadOnly Set to true to only respawn dead players -- @realm server function entspawn.SpawnPlayers(deadOnly) - local waveDelay = cvSpawnWaveInterval:GetFloat() - local plys = player.GetAll() + local waveDelay = cvSpawnWaveInterval:GetFloat() + local plys = player.GetAll() - -- simple method, spawn everybody at once - if waveDelay <= 0 or deadOnly then - for i = 1, #plys do - plys[i]:SpawnForRound(deadOnly) - end - else - -- wave method - local amountSpawnPoints = #plyspawn.GetPlayerSpawnPoints() - local playersToSpawn = {} + -- simple method, spawn everybody at once + if waveDelay <= 0 or deadOnly then + for i = 1, #plys do + plys[i]:SpawnForRound(deadOnly) + end + else + -- wave method + local amountSpawnPoints = #plyspawn.GetPlayerSpawnPoints() + local playersToSpawn = {} - for _, ply in RandomPairs(plys) do - if ply:ShouldSpawn() then - playersToSpawn[#playersToSpawn + 1] = ply + for _, ply in RandomPairs(plys) do + if ply:ShouldSpawn() then + playersToSpawn[#playersToSpawn + 1] = ply - GAMEMODE:PlayerSpawnAsSpectator(ply) - end - end + GAMEMODE:PlayerSpawnAsSpectator(ply) + end + end - local spawnFunction = function() - local sizePlayersToSpawn = #playersToSpawn - local sizeSpawnWave = mathMin(amountSpawnPoints, sizePlayersToSpawn) + local spawnFunction = function() + local sizePlayersToSpawn = #playersToSpawn + local sizeSpawnWave = mathMin(amountSpawnPoints, sizePlayersToSpawn) - -- fill the available spawnpoints with players that need spawning - for i = 1, sizeSpawnWave do - playersToSpawn[i]:SpawnForRound(deadOnly) + -- fill the available spawnpoints with players that need spawning + for i = 1, sizeSpawnWave do + playersToSpawn[i]:SpawnForRound(deadOnly) - playersToSpawn[i] = nil -- mark for deletion - end + playersToSpawn[i] = nil -- mark for deletion + end - -- clean up table - table.RemoveEmptyEntries(playersToSpawn, sizePlayersToSpawn) + -- clean up table + table.RemoveEmptyEntries(playersToSpawn, sizePlayersToSpawn) - MsgN("Spawned " .. sizeSpawnWave .. " players in spawn wave.") + Dev(1, "Spawned " .. sizeSpawnWave .. " players in spawn wave.") - if #playersToSpawn == 0 then - timerRemove("spawnwave") + if #playersToSpawn == 0 then + timerRemove("spawnwave") - MsgN("Spawn waves ending, all players spawned.") - end - end + Dev(1, "Spawn waves ending, all players spawned.") + end + end - MsgN("Spawn waves starting.") + Dev(1, "Spawn waves starting.") - timerCreate("spawnwave", waveDelay, 0, spawnFunction) + timerCreate("spawnwave", waveDelay, 0, spawnFunction) - -- already run one wave, which may stop the timer if everyone is spawned in one go - spawnFunction() - end + -- already run one wave, which may stop the timer if everyone is spawned in one go + spawnFunction() + end end --- @@ -246,36 +265,36 @@ end -- @internal -- @realm server function entspawn.HandleSpawns() - -- in a first pass, all weapon entities are removed; if in classic spawn mode, a few - -- spawn points that should be replaced are returned - local spawnTable = entspawn.RemoveMapEntities() - - -- if in classic mode, set those spawn points to data table - if not entspawnscript.ShouldUseCustomSpawns() then - entspawnscript.SetSpawns(spawnTable) - end - - -- in the next tick the new entities are spawned - timer.Simple(0, function() - -- SPAWN WEAPONS - local wepSpawns = entspawnscript.GetSpawnsForSpawnType(SPAWN_TYPE_WEAPON) - -- This function returns two tables, the first is ordered by weapon spawn types, - -- the second is just a normal indexed list with all spawnable weapons. This is - -- done like this to improve the performance for random weapon spawns. - local wepsForTypes, weps = WEPS.GetWeaponsForSpawnTypes() - - entspawn.SpawnEntities(wepSpawns, wepsForTypes, weps, WEAPON_TYPE_RANDOM) - - -- SPAWN AMMO - local ammoSpawns = entspawnscript.GetSpawnsForSpawnType(SPAWN_TYPE_AMMO) - -- This function returns two tables, the first is ordered by weapon spawn types, - -- the second is just a normal indexed list with all spawnable weapons. This is - -- done like this to improve the performance for random weapon spawns. - local ammoForTypes, ammo = WEPS.GetAmmoForSpawnTypes() - - entspawn.SpawnEntities(ammoSpawns, ammoForTypes, ammo, AMMO_TYPE_RANDOM) - - -- SPAWN PLAYER - entspawn.SpawnPlayers(false) - end) + -- in a first pass, all weapon entities are removed; if in classic spawn mode, a few + -- spawn points that should be replaced are returned + local spawnTable = entspawn.RemoveMapEntities() + + -- if in classic mode, set those spawn points to data table + if not entspawnscript.ShouldUseCustomSpawns() then + entspawnscript.SetSpawns(spawnTable) + end + + -- in the next tick the new entities are spawned + timer.Simple(0, function() + -- SPAWN WEAPONS + local wepSpawns = entspawnscript.GetSpawnsForSpawnType(SPAWN_TYPE_WEAPON) + -- This function returns two tables, the first is ordered by weapon spawn types, + -- the second is just a normal indexed list with all spawnable weapons. This is + -- done like this to improve the performance for random weapon spawns. + local wepsForTypes, weps = WEPS.GetWeaponsForSpawnTypes() + + entspawn.SpawnEntities(wepSpawns, wepsForTypes, weps, WEAPON_TYPE_RANDOM) + + -- SPAWN AMMO + local ammoSpawns = entspawnscript.GetSpawnsForSpawnType(SPAWN_TYPE_AMMO) + -- This function returns two tables, the first is ordered by weapon spawn types, + -- the second is just a normal indexed list with all spawnable weapons. This is + -- done like this to improve the performance for random weapon spawns. + local ammoForTypes, ammo = WEPS.GetAmmoForSpawnTypes() + + entspawn.SpawnEntities(ammoSpawns, ammoForTypes, ammo, AMMO_TYPE_RANDOM) + + -- SPAWN PLAYER + entspawn.SpawnPlayers(false) + end) end diff --git a/lua/ttt2/libraries/entspawnscript.lua b/lua/ttt2/libraries/entspawnscript.lua index f300b62e3..e6243a65f 100644 --- a/lua/ttt2/libraries/entspawnscript.lua +++ b/lua/ttt2/libraries/entspawnscript.lua @@ -4,7 +4,7 @@ -- @module entspawnscript if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local fileExists = file.Exists @@ -24,619 +24,727 @@ local spawnPointList = {} local settingsList = {} local defaultSettings = { - ["blacklisted"] = 0 + ["blacklisted"] = 0, } local spawndir = "terrortown/entityspawns/spawnpoints/" local settingsdir = "terrortown/entityspawns/settings/" local spawnTypeNameKeys = { - [SPAWN_TYPE_WEAPON] = "SPAWN_TYPE_WEAPON", - [SPAWN_TYPE_AMMO] = "SPAWN_TYPE_AMMO", - [SPAWN_TYPE_PLAYER] = "SPAWN_TYPE_PLAYER" + [SPAWN_TYPE_WEAPON] = "SPAWN_TYPE_WEAPON", + [SPAWN_TYPE_AMMO] = "SPAWN_TYPE_AMMO", + [SPAWN_TYPE_PLAYER] = "SPAWN_TYPE_PLAYER", } local spawnTypes = { - SPAWN_TYPE_WEAPON, - SPAWN_TYPE_AMMO, - SPAWN_TYPE_PLAYER + SPAWN_TYPE_WEAPON, + SPAWN_TYPE_AMMO, + SPAWN_TYPE_PLAYER, } local spawnColors = { - [SPAWN_TYPE_WEAPON] = Color(0, 175, 175, 255), - [SPAWN_TYPE_AMMO] = Color(175, 75, 75, 255), - [SPAWN_TYPE_PLAYER] = Color(75, 175, 50, 255) + [SPAWN_TYPE_WEAPON] = Color(0, 175, 175, 255), + [SPAWN_TYPE_AMMO] = Color(175, 75, 75, 255), + [SPAWN_TYPE_PLAYER] = Color(75, 175, 50, 255), } local spawnData = { - [SPAWN_TYPE_WEAPON] = { - [WEAPON_TYPE_RANDOM] = { - material = Material("vgui/ttt/tid/tid_big_weapon_random"), - name = "spawn_weapon_random", - var = "WEAPON_TYPE_RANDOM" - }, - [WEAPON_TYPE_MELEE] = { - material = Material("vgui/ttt/tid/tid_big_weapon_melee"), - name = "spawn_weapon_melee", - var = "WEAPON_TYPE_MELEE" - }, - [WEAPON_TYPE_NADE] = { - material = Material("vgui/ttt/tid/tid_big_weapon_nade"), - name = "spawn_weapon_nade", - var = "WEAPON_TYPE_NADE" - }, - [WEAPON_TYPE_SHOTGUN] = { - material = Material("vgui/ttt/tid/tid_big_weapon_shotgun"), - name = "spawn_weapon_shotgun", - var = "WEAPON_TYPE_SHOTGUN" - }, - [WEAPON_TYPE_HEAVY] = { - material = Material("vgui/ttt/tid/tid_big_weapon_assault"), - name = "spawn_weapon_heavy", - var = "WEAPON_TYPE_HEAVY" - }, - [WEAPON_TYPE_SNIPER] = { - material = Material("vgui/ttt/tid/tid_big_weapon_sniper"), - name = "spawn_weapon_sniper", - var = "WEAPON_TYPE_SNIPER" - }, - [WEAPON_TYPE_PISTOL] = { - material = Material("vgui/ttt/tid/tid_big_weapon_pistol"), - name = "spawn_weapon_pistol", - var = "WEAPON_TYPE_PISTOL" - }, - [WEAPON_TYPE_SPECIAL] = { - material = Material("vgui/ttt/tid/tid_big_weapon_special"), - name = "spawn_weapon_special", - var = "WEAPON_TYPE_SPECIAL" - } - }, - [SPAWN_TYPE_AMMO] = { - [AMMO_TYPE_RANDOM] = { - material = Material("vgui/ttt/tid/tid_big_ammo_random"), - name = "spawn_ammo_random", - var = "AMMO_TYPE_RANDOM" - }, - [AMMO_TYPE_DEAGLE] = { - material = Material("vgui/ttt/tid/tid_big_ammo_deagle"), - name = "spawn_ammo_deagle", - var = "AMMO_TYPE_DEAGLE" - }, - [AMMO_TYPE_PISTOL] = { - material = Material("vgui/ttt/tid/tid_big_ammo_pistol"), - name = "spawn_ammo_pistol", - var = "AMMO_TYPE_PISTOL" - }, - [AMMO_TYPE_MAC10] = { - material = Material("vgui/ttt/tid/tid_big_ammo_mac10"), - name = "spawn_ammo_mac10", - var = "AMMO_TYPE_MAC10" - }, - [AMMO_TYPE_RIFLE] = { - material = Material("vgui/ttt/tid/tid_big_ammo_rifle"), - name = "spawn_ammo_rifle", - var = "AMMO_TYPE_RIFLE" - }, - [AMMO_TYPE_SHOTGUN] = { - material = Material("vgui/ttt/tid/tid_big_ammo_shotgun"), - name = "spawn_ammo_shotgun", - var = "AMMO_TYPE_SHOTGUN" - } - }, - [SPAWN_TYPE_PLAYER] = { - [PLAYER_TYPE_RANDOM] = { - material = Material("vgui/ttt/tid/tid_big_player"), - name = "spawn_player_random", - var = "PLAYER_TYPE_RANDOM" - } - } + [SPAWN_TYPE_WEAPON] = { + [WEAPON_TYPE_RANDOM] = { + material = Material("vgui/ttt/tid/tid_big_weapon_random"), + name = "spawn_weapon_random", + var = "WEAPON_TYPE_RANDOM", + }, + [WEAPON_TYPE_MELEE] = { + material = Material("vgui/ttt/tid/tid_big_weapon_melee"), + name = "spawn_weapon_melee", + var = "WEAPON_TYPE_MELEE", + }, + [WEAPON_TYPE_NADE] = { + material = Material("vgui/ttt/tid/tid_big_weapon_nade"), + name = "spawn_weapon_nade", + var = "WEAPON_TYPE_NADE", + }, + [WEAPON_TYPE_SHOTGUN] = { + material = Material("vgui/ttt/tid/tid_big_weapon_shotgun"), + name = "spawn_weapon_shotgun", + var = "WEAPON_TYPE_SHOTGUN", + }, + [WEAPON_TYPE_HEAVY] = { + material = Material("vgui/ttt/tid/tid_big_weapon_assault"), + name = "spawn_weapon_heavy", + var = "WEAPON_TYPE_HEAVY", + }, + [WEAPON_TYPE_SNIPER] = { + material = Material("vgui/ttt/tid/tid_big_weapon_sniper"), + name = "spawn_weapon_sniper", + var = "WEAPON_TYPE_SNIPER", + }, + [WEAPON_TYPE_PISTOL] = { + material = Material("vgui/ttt/tid/tid_big_weapon_pistol"), + name = "spawn_weapon_pistol", + var = "WEAPON_TYPE_PISTOL", + }, + [WEAPON_TYPE_SPECIAL] = { + material = Material("vgui/ttt/tid/tid_big_weapon_special"), + name = "spawn_weapon_special", + var = "WEAPON_TYPE_SPECIAL", + }, + }, + [SPAWN_TYPE_AMMO] = { + [AMMO_TYPE_RANDOM] = { + material = Material("vgui/ttt/tid/tid_big_ammo_random"), + name = "spawn_ammo_random", + var = "AMMO_TYPE_RANDOM", + }, + [AMMO_TYPE_DEAGLE] = { + material = Material("vgui/ttt/tid/tid_big_ammo_deagle"), + name = "spawn_ammo_deagle", + var = "AMMO_TYPE_DEAGLE", + }, + [AMMO_TYPE_PISTOL] = { + material = Material("vgui/ttt/tid/tid_big_ammo_pistol"), + name = "spawn_ammo_pistol", + var = "AMMO_TYPE_PISTOL", + }, + [AMMO_TYPE_MAC10] = { + material = Material("vgui/ttt/tid/tid_big_ammo_mac10"), + name = "spawn_ammo_mac10", + var = "AMMO_TYPE_MAC10", + }, + [AMMO_TYPE_RIFLE] = { + material = Material("vgui/ttt/tid/tid_big_ammo_rifle"), + name = "spawn_ammo_rifle", + var = "AMMO_TYPE_RIFLE", + }, + [AMMO_TYPE_SHOTGUN] = { + material = Material("vgui/ttt/tid/tid_big_ammo_shotgun"), + name = "spawn_ammo_shotgun", + var = "AMMO_TYPE_SHOTGUN", + }, + }, + [SPAWN_TYPE_PLAYER] = { + [PLAYER_TYPE_RANDOM] = { + material = Material("vgui/ttt/tid/tid_big_player"), + name = "spawn_player_random", + var = "PLAYER_TYPE_RANDOM", + }, + }, } local kindToSpawnType = { - [WEAPON_PISTOL] = WEAPON_TYPE_PISTOL, - [WEAPON_HEAVY] = WEAPON_TYPE_HEAVY, - [WEAPON_NADE] = WEAPON_TYPE_NADE, - [WEAPON_EQUIP1] = WEAPON_TYPE_SPECIAL, - [WEAPON_EQUIP2] = WEAPON_TYPE_SPECIAL, - [WEAPON_ROLE] = WEAPON_TYPE_SPECIAL, - [WEAPON_MELEE] = WEAPON_TYPE_MELEE, - [WEAPON_CARRY] = WEAPON_TYPE_MELEE, + [WEAPON_PISTOL] = WEAPON_TYPE_PISTOL, + [WEAPON_HEAVY] = WEAPON_TYPE_HEAVY, + [WEAPON_NADE] = WEAPON_TYPE_NADE, + [WEAPON_EQUIP1] = WEAPON_TYPE_SPECIAL, + [WEAPON_EQUIP2] = WEAPON_TYPE_SPECIAL, + [WEAPON_ROLE] = WEAPON_TYPE_SPECIAL, + [WEAPON_MELEE] = WEAPON_TYPE_MELEE, + [WEAPON_CARRY] = WEAPON_TYPE_MELEE, } entspawnscript = entspawnscript or {} if SERVER then - entspawnscript.defaultSpawnsSaved = entspawnscript.defaultSpawnsSaved or false - entspawnscript.defaultSpawnTable = entspawnscript.defaultSpawnTable or {} - entspawnscript.editingPlayers = entspawnscript.editingPlayers or {} - - --- - -- @realm server - local cvUseWeaponSpawnScript = CreateConVar("ttt_use_weapon_spawn_scripts", "1") - --- - -- Called when the entities on the map are available and the spawn entities can be read.. - -- @realm server - function entspawnscript.OnLoaded() - -- save default map data in memory - if not entspawnscript.defaultSpawnsSaved then - entspawnscript.defaultSpawnsSaved = true - entspawnscript.SaveMapStateAsDefault() - end - - if not entspawnscript.SpawnFileExists() or not entspawnscript.SettingsFileExists() then - -- if the map was never changed, check if there is an old spawn script and convert it to the new system - local spawnPoints, settings = entspawnscript.InitOldWeaponSpawnScript() - - entspawnscript.SetSpawns(spawnPoints) - entspawnscript.SetSettings(settings) - end - - if entspawnscript.SpawnFileExists() then - entspawnscript.SetSpawns(entspawnscript.ReadSpawnFile()) - end - - if entspawnscript.SettingsFileExists() then - entspawnscript.SetSettings(entspawnscript.ReadSettingsFile()) - end - - -- Most of the time this set up is done before the player is ready. However to make sure the update - -- is called at least once after the data is generated, it is also called here. - - -- trigger a sync of the settings table - entspawnscript.UpdateSettingsOnClients() - - -- trigger a sync of the spawn amount info - entspawnscript.UpdateSpawnCountOnClients() - end - - --- - -- Saves the current map state as default map state in memory only - -- @realm server - function entspawnscript.SaveMapStateAsDefault() - entspawnscript.defaultSpawnTable = { - [SPAWN_TYPE_WEAPON] = map.GetWeaponSpawns(), - [SPAWN_TYPE_AMMO] = map.GetAmmoSpawns(), - [SPAWN_TYPE_PLAYER] = map.GetPlayerSpawns() - } - end - - --- - -- Initializes the map and generates all spawn points from the old weaponspawnscripts. This does - -- not save those values automatically, but returns the data to be saved. - -- @note Called on first load of a map if there is no existing spawn file. - -- @warning This can break the weapon spawn files if called at any time after the initial spawn wave. - -- @return table A table with the default spawn points provided by the map - -- @return table A table with the default settings provided by the map - -- @realm server - function entspawnscript.InitOldWeaponSpawnScript() - local mapName = gameGetMap() - local spawnTable = tableCopy(entspawnscript.defaultSpawnTable) - local settingsTable = tableCopy(defaultSettings) - - -- check if there is a deprecated ttt weapon spawn script and convert the data to - -- the new ttt2 system as well - if ents.TTT.CanImportEntities(mapName) then - local spawns, settings = ents.TTT.ImportEntities(mapName) - local importSpawnTable = map.GetSpawnsFromClassTable(spawns) - - if settings.replacespawns == 1 then - spawnTable = importSpawnTable - else - -- add new spawns to existing table - for spawnType, typeTable in pairs(importSpawnTable) do - for entType, addSpawns in pairs(typeTable) do - spawnTable[spawnType][entType] = spawnTable[spawnType][entType] or {} - - tableAdd(spawnTable[spawnType][entType], addSpawns) - end - end - end - end - - return spawnTable, settingsTable - end - - --- - -- Updates the spawn file. Used to save changes done in the spawn editor. - -- @realm server - function entspawnscript.UpdateSpawnFile() - local spawnPoints = entspawnscript.GetSpawns() - local jsonSaveTable = {} - - for spawnType, spawnTypeName in pairs(spawnTypeNameKeys) do - jsonSaveTable[spawnTypeName] = {} - - for entType, spawns in pairs(spawnPoints[spawnType]) do - local entTypeName = entspawnscript.GetVarNameFromSpawnType(spawnType, entType) - - jsonSaveTable[spawnTypeName][entTypeName] = spawns - end - end - - entspawnscript.WriteFile(spawndir, jsonSaveTable) - end - - --- - -- Updates the settings file if some map settings were changed on the server. - -- @realm server - function entspawnscript.UpdateSettingsFile() - entspawnscript.WriteFile(settingsdir, entspawnscript.GetSettings()) - end - - --- - -- Reads the spawn point file on the server. Returns a table with the read data. - -- @note Make sure the file exists first. - -- @return table The spawn point table - -- @realm server - function entspawnscript.ReadSpawnFile() - local jsonSaveTable = entspawnscript.ReadFile(spawndir) - local spawnPoints = {} - - for spawnType, spawnTypeName in pairs(spawnTypeNameKeys) do - spawnPoints[spawnType] = {} - - for entTypeName, spawns in pairs(jsonSaveTable[spawnTypeName]) do - spawnPoints[spawnType][_G[entTypeName]] = spawns - end - end - - return spawnPoints - end - - --- - -- Reads the settings file on the server. Returns a table with the read data. - -- @note Make sure the file exists first. - -- @return table The settings table - -- @realm server - function entspawnscript.ReadSettingsFile() - return entspawnscript.ReadFile(settingsdir) - end - - --- - -- Removes the spawn file from the spawn file directory. - -- @realm server - function entspawnscript.RemoveSpawnFile() - entspawnscript.RemoveFile(spawndir) - end - - --- - -- Removes the settings file from the settings file directory - -- @realm server - function entspawnscript.RemoveSettingsFile() - entspawnscript.RemoveFile(settingsdir) - end - - --- - -- Checks whether a spawn file for the current map exists. - -- @return boolean Returns true if a spawn file exists - -- @realm server - function entspawnscript.SpawnFileExists() - return entspawnscript.Exists(spawndir) - end - - --- - -- Checks whether a settings file for the current map exists. - -- @return boolean Returns true if a settings file exists - -- @realm server - function entspawnscript.SettingsFileExists() - return entspawnscript.Exists(settingsdir) - end - - --- - -- Checks wether a file for the currently selected map exists. - -- @param string dir The directory where the file is expected - -- @return boolean Returns true if the spawnn script already exists - -- @realm server - function entspawnscript.Exists(dir) - return fileExists(dir .. gameGetMap() .. ".json", "DATA") - end - - - --- - -- Writes the spawn script data to the disc. Is used for the initial file, the default data - -- and to save changes done to the spawn data. - -- @note If the directory does not already exist, it is automatically created. - -- @param string dir The directory where the file is expected - -- @param table dataTable The table with the data that should be stored - -- @internal - -- @realm server - function entspawnscript.WriteFile(dir, dataTable) - fileCreateDir(dir) - - fileWrite(dir .. gameGetMap() .. ".json", utilTableToJSON(dataTable, true)) - end - - --- - -- Reads the spawn file and returns the read data. - -- @param string dir The directory where the file is expected - -- @return table The table with the data read from the file - -- @internal - -- @realm server - function entspawnscript.ReadFile(dir) - return utilJSONToTable(fileRead(dir .. gameGetMap() .. ".json", "DATA")) - end - - --- - -- Removes the spawn file of the current map and returns if it existed - -- @param string dir The directory where the file is expected - -- @return bool if the file existed and was therefore successfully deleted - -- @internal - -- @realm server - function entspawnscript.RemoveFile(dir) - local fileExisted = entspawnscript.Exists(dir) - - if fileExisted then - fileDelete(dir .. gameGetMap() .. ".json") - end - - return fileExisted - end - - --- - -- Returns the table of all currently defined settings. - -- @return table The settings table - -- @realm server - function entspawnscript.GetSettings() - return settingsList - end - - --- - -- Returns a specific setting defined by the key. - -- @param string key The key of the requested setting - -- @return[default=0] number The setting value - -- @realm server - function entspawnscript.GetSetting(key) - return settingsList[key] or 0 - end - - --- - -- Sets the whole settings table to the provided table - -- @param table settings The new settings table - -- @realm server - function entspawnscript.SetSettings(settings) - settingsList = settings - end - - --- - -- Proxy function to directly set the `blacklisted` setting to disable custom spawns - -- for the currently loaded map. - -- @param boolean Set to true if custom spawns should be used - -- @realm server - function entspawnscript.SetUseCustomSpawns(state) - entspawnscript.SetSetting("blacklisted", not state, false) - end - - --- - -- Returns if the currently selected map should use custom spawns. Takes the map specific - -- setting `blacklisted` and the convar `ttt_use_weapon_spawn_scripts` into consideration. - -- @return boolean Returns true if custom spawns should be used for the map - -- @realm server - function entspawnscript.ShouldUseCustomSpawns() - return not tobool(entspawnscript.GetSetting("blacklisted")) and cvUseWeaponSpawnScript:GetBool() - end - - --- - -- Sets the player editing state and syncs it to the client. Also, when setting the state to true, - -- the current spawn types are synced to the client. - -- @param Player ply The playser whose state should be changed - -- @param boolean state The new editing state - -- @realm server - function entspawnscript.SetEditing(ply, state) - ply:SetNWBool("is_spawn_editing", state) - - local playerIndex = nil - - for i = 1, #entspawnscript.editingPlayers do - if entspawnscript.editingPlayers[i] ~= ply then continue end - - playerIndex = i - - break - end - - if state then - if not playerIndex then - entspawnscript.editingPlayers[#entspawnscript.editingPlayers + 1] = ply - end - - net.SendStream("TTT2_WeaponSpawnEntities", entspawnscript.GetSpawns(), ply) - elseif playerIndex then - tableRemove(entspawnscript.editingPlayers, playerIndex) - end - end - - --- - -- Returns a list of all players that currently are in the spawn editing mode. - -- @return table Returns an indexed table of all players currently editing the spawn points - -- @realm server - function entspawnscript.GetEditingPlayers() - return entspawnscript.editingPlayers - end - - --- - -- Gets a list of all players that should receive a spawn point update. These are all - -- editing players besides the player that made the change. - -- @param Player ply The player that made the change - -- @return table An indexed list with all receiving players - -- @realm server - function entspawnscript.GetReceivingPlayers(ply) - local recPlys = {} - - for i = 1, #entspawnscript.editingPlayers do - local editPly = entspawnscript.editingPlayers[i] - - if editPly == ply then continue end - - recPlys[#recPlys + 1] = editPly - end - - return recPlys - end - - --- - -- Updates the map settings for the provided players. If no player table is - -- provided, the update is done on all clients. - -- @param table plys A table of players that should be updated - -- @realm server - function entspawnscript.UpdateSettingsOnClients(plys) - plys = plys or player.GetAll() - - for i = 1, #plys do - local ply = plys[i] - - --- - -- @realm server - if not hook.Run("TTT2AdminCheck", ply) then continue end - - for key, value in pairs(entspawnscript.GetSettings()) do - ttt2net.Set({"entspawnscript", "settings", key}, {type = "int", bits = 16}, entspawnscript.GetSetting(key), ply) - end - end - end - - --- - -- Updates the amount of spawns for the provided players. If no player table is - -- provided, the update is done on all clients. - -- @param table plys A table of players that should be updated - -- @realm server - function entspawnscript.UpdateSpawnCountOnClients(plys) - local amountSpawns = { - [SPAWN_TYPE_WEAPON] = 0, - [SPAWN_TYPE_AMMO] = 0, - [SPAWN_TYPE_PLAYER] = 0 - } - - local spawnList = entspawnscript.GetSpawns() - - for spawnType, spawnTables in pairs(spawnList) do - for entType, spawns in pairs(spawnTables) do - amountSpawns[spawnType] = amountSpawns[spawnType] + #spawns - end - end - - plys = plys or player.GetAll() - - for i = 1, #plys do - local ply = plys[i] - - --- - -- @realm server - if not hook.Run("TTT2AdminCheck", ply) then continue end - - ttt2net.Set({"entspawnscript", "spawnamount", "weapon"}, {type = "int", bits = 16}, amountSpawns[SPAWN_TYPE_WEAPON], ply) - ttt2net.Set({"entspawnscript", "spawnamount", "ammo"}, {type = "int", bits = 16}, amountSpawns[SPAWN_TYPE_AMMO], ply) - ttt2net.Set({"entspawnscript", "spawnamount", "player"}, {type = "int", bits = 16}, amountSpawns[SPAWN_TYPE_PLAYER], ply) - - ttt2net.Set({"entspawnscript", "spawnamount", "weaponrandom"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_RANDOM] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "weaponmelee"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_MELEE] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "weaponnade"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_NADE] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "weaponshotgun"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_SHOTGUN] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "weaponheavy"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_HEAVY] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "weaponsniper"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_SNIPER] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "weaponpistol"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_PISTOL] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "weaponspecial"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_SPECIAL] or {}), ply) - - ttt2net.Set({"entspawnscript", "spawnamount", "ammorandom"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_RANDOM] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "ammodeagle"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_DEAGLE] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "ammopistol"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_PISTOL] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "ammomac10"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_MAC10] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "ammorifle"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_RIFLE] or {}), ply) - ttt2net.Set({"entspawnscript", "spawnamount", "ammoshotgun"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_SHOTGUN] or {}), ply) - - ttt2net.Set({"entspawnscript", "spawnamount", "playerrandom"}, {type = "int", bits = 16}, #(spawnList[SPAWN_TYPE_PLAYER][PLAYER_TYPE_RANDOM] or {}), ply) - end - end - - --- - -- Transmits the settings and the spawn point amount to the provided player. - -- This function is called in @{GM:TTT2PlayerReady}. - -- @param Player ply The player who should receive the update - -- @realm server - function entspawnscript.TransmitToPlayer(ply) - -- trigger a sync of the settings table - entspawnscript.UpdateSettingsOnClients({ply}) - - -- trigger a sync of the spawn amount info - entspawnscript.UpdateSpawnCountOnClients({ply}) - end - - --- - -- Reloads the spawn points from either the spawn files or from the defaults. Is mostly used after - -- switching from classic TTT weapon spawns to dynamic TTT2 weapons spawns. - -- @realm server - function entspawnscript.ReloadSpawns() - -- if switched back to custom spawn loading, we want to load again the spawn file to restore the spawn points - if entspawnscript.SpawnFileExists() then - entspawnscript.SetSpawns(entspawnscript.ReadSpawnFile()) - else - -- if the map was never changed, check if there is an old spawn script and convert it to the new system - local spawnPoints = entspawnscript.InitOldWeaponSpawnScript() - - entspawnscript.SetSpawns(spawnPoints) - end - end - - cvars.AddChangeCallback(cvUseWeaponSpawnScript:GetName(), function(_, _, new) - if tobool(new) then - entspawnscript.ReloadSpawns() - end - end) + entspawnscript.defaultSpawnsSaved = entspawnscript.defaultSpawnsSaved or false + entspawnscript.defaultSpawnTable = entspawnscript.defaultSpawnTable or {} + entspawnscript.editingPlayers = entspawnscript.editingPlayers or {} + + --- + -- @realm server + -- stylua: ignore + local cvUseWeaponSpawnScript = CreateConVar("ttt_use_weapon_spawn_scripts", "1") + --- + -- Called when the entities on the map are available and the spawn entities can be read.. + -- @realm server + function entspawnscript.OnLoaded() + -- save default map data in memory + if not entspawnscript.defaultSpawnsSaved then + entspawnscript.defaultSpawnsSaved = true + entspawnscript.SaveMapStateAsDefault() + end + + if not entspawnscript.SpawnFileExists() or not entspawnscript.SettingsFileExists() then + -- if the map was never changed, check if there is an old spawn script and convert it to the new system + local spawnPoints, settings = entspawnscript.InitOldWeaponSpawnScript() + + entspawnscript.SetSpawns(spawnPoints) + entspawnscript.SetSettings(settings) + end + + if entspawnscript.SpawnFileExists() then + entspawnscript.SetSpawns(entspawnscript.ReadSpawnFile()) + end + + if entspawnscript.SettingsFileExists() then + entspawnscript.SetSettings(entspawnscript.ReadSettingsFile()) + end + + -- Most of the time this set up is done before the player is ready. However to make sure the update + -- is called at least once after the data is generated, it is also called here. + + -- trigger a sync of the settings table + entspawnscript.UpdateSettingsOnClients() + + -- trigger a sync of the spawn amount info + entspawnscript.UpdateSpawnCountOnClients() + end + + --- + -- Saves the current map state as default map state in memory only + -- @realm server + function entspawnscript.SaveMapStateAsDefault() + entspawnscript.defaultSpawnTable = { + [SPAWN_TYPE_WEAPON] = map.GetWeaponSpawns(), + [SPAWN_TYPE_AMMO] = map.GetAmmoSpawns(), + [SPAWN_TYPE_PLAYER] = map.GetPlayerSpawns(), + } + end + + --- + -- Initializes the map and generates all spawn points from the old weaponspawnscripts. This does + -- not save those values automatically, but returns the data to be saved. + -- @note Called on first load of a map if there is no existing spawn file. + -- @warning This can break the weapon spawn files if called at any time after the initial spawn wave. + -- @return table A table with the default spawn points provided by the map + -- @return table A table with the default settings provided by the map + -- @realm server + function entspawnscript.InitOldWeaponSpawnScript() + local mapName = gameGetMap() + local spawnTable = tableCopy(entspawnscript.defaultSpawnTable) + local settingsTable = tableCopy(defaultSettings) + + -- check if there is a deprecated ttt weapon spawn script and convert the data to + -- the new ttt2 system as well + if ents.TTT.CanImportEntities(mapName) then + local spawns, settings = ents.TTT.ImportEntities(mapName) + local importSpawnTable = map.GetSpawnsFromClassTable(spawns) + + if settings.replacespawns == 1 then + spawnTable = importSpawnTable + else + -- add new spawns to existing table + for spawnType, typeTable in pairs(importSpawnTable) do + for entType, addSpawns in pairs(typeTable) do + spawnTable[spawnType][entType] = spawnTable[spawnType][entType] or {} + + tableAdd(spawnTable[spawnType][entType], addSpawns) + end + end + end + end + + return spawnTable, settingsTable + end + + --- + -- Updates the spawn file. Used to save changes done in the spawn editor. + -- @realm server + function entspawnscript.UpdateSpawnFile() + local spawnPoints = entspawnscript.GetSpawns() + local jsonSaveTable = {} + + for spawnType, spawnTypeName in pairs(spawnTypeNameKeys) do + jsonSaveTable[spawnTypeName] = {} + + for entType, spawns in pairs(spawnPoints[spawnType]) do + local entTypeName = entspawnscript.GetVarNameFromSpawnType(spawnType, entType) + + jsonSaveTable[spawnTypeName][entTypeName] = spawns + end + end + + entspawnscript.WriteFile(spawndir, jsonSaveTable) + end + + --- + -- Updates the settings file if some map settings were changed on the server. + -- @realm server + function entspawnscript.UpdateSettingsFile() + entspawnscript.WriteFile(settingsdir, entspawnscript.GetSettings()) + end + + --- + -- Reads the spawn point file on the server. Returns a table with the read data. + -- @note Make sure the file exists first. + -- @return table The spawn point table + -- @realm server + function entspawnscript.ReadSpawnFile() + local jsonSaveTable = entspawnscript.ReadFile(spawndir) + local spawnPoints = {} + + for spawnType, spawnTypeName in pairs(spawnTypeNameKeys) do + spawnPoints[spawnType] = {} + + for entTypeName, spawns in pairs(jsonSaveTable[spawnTypeName]) do + spawnPoints[spawnType][_G[entTypeName]] = spawns + end + end + + return spawnPoints + end + + --- + -- Reads the settings file on the server. Returns a table with the read data. + -- @note Make sure the file exists first. + -- @return table The settings table + -- @realm server + function entspawnscript.ReadSettingsFile() + return entspawnscript.ReadFile(settingsdir) + end + + --- + -- Removes the spawn file from the spawn file directory. + -- @realm server + function entspawnscript.RemoveSpawnFile() + entspawnscript.RemoveFile(spawndir) + end + + --- + -- Removes the settings file from the settings file directory + -- @realm server + function entspawnscript.RemoveSettingsFile() + entspawnscript.RemoveFile(settingsdir) + end + + --- + -- Checks whether a spawn file for the current map exists. + -- @return boolean Returns true if a spawn file exists + -- @realm server + function entspawnscript.SpawnFileExists() + return entspawnscript.Exists(spawndir) + end + + --- + -- Checks whether a settings file for the current map exists. + -- @return boolean Returns true if a settings file exists + -- @realm server + function entspawnscript.SettingsFileExists() + return entspawnscript.Exists(settingsdir) + end + + --- + -- Checks wether a file for the currently selected map exists. + -- @param string dir The directory where the file is expected + -- @return boolean Returns true if the spawnn script already exists + -- @realm server + function entspawnscript.Exists(dir) + local fullDir = dir .. gameGetMap() .. ".json" + return fileExists(fullDir, "DATA") or fileExists("data_static/" .. fullDir, "GAME") + end + + --- + -- Writes the spawn script data to the disc. Is used for the initial file, the default data + -- and to save changes done to the spawn data. + -- @note If the directory does not already exist, it is automatically created. + -- @param string dir The directory where the file is expected + -- @param table dataTable The table with the data that should be stored + -- @internal + -- @realm server + function entspawnscript.WriteFile(dir, dataTable) + fileCreateDir(dir) + + fileWrite(dir .. gameGetMap() .. ".json", utilTableToJSON(dataTable, true)) + end + + --- + -- Reads the spawn file and returns the read data. + -- @param string dir The directory where the file is expected + -- @return table The table with the data read from the file + -- @internal + -- @realm server + function entspawnscript.ReadFile(dir) + local fullDir = dir .. gameGetMap() .. ".json" + if fileExists(fullDir, "DATA") then + return utilJSONToTable(fileRead(fullDir, "DATA")) + else + return utilJSONToTable(fileRead("data_static/" .. fullDir, "GAME")) + end + end + + --- + -- Removes the spawn file of the current map and returns if it existed + -- @param string dir The directory where the file is expected + -- @return boolean if the file existed and was therefore successfully deleted + -- @internal + -- @realm server + function entspawnscript.RemoveFile(dir) + local fileExisted = entspawnscript.Exists(dir) + + if fileExisted then + fileDelete(dir .. gameGetMap() .. ".json", "DATA") + end + + return fileExisted + end + + --- + -- Returns the table of all currently defined settings. + -- @return table The settings table + -- @realm server + function entspawnscript.GetSettings() + return settingsList + end + + --- + -- Returns a specific setting defined by the key. + -- @param string key The key of the requested setting + -- @return[default=0] number The setting value + -- @realm server + function entspawnscript.GetSetting(key) + return settingsList[key] or 0 + end + + --- + -- Sets the whole settings table to the provided table + -- @param table settings The new settings table + -- @realm server + function entspawnscript.SetSettings(settings) + settingsList = settings + end + + --- + -- Proxy function to directly set the `blacklisted` setting to disable custom spawns + -- for the currently loaded map. + -- @param boolean Set to true if custom spawns should be used + -- @realm server + function entspawnscript.SetUseCustomSpawns(state) + entspawnscript.SetSetting("blacklisted", not state, false) + end + + --- + -- Returns if the currently selected map should use custom spawns. Takes the map specific + -- setting `blacklisted` and the convar `ttt_use_weapon_spawn_scripts` into consideration. + -- @return boolean Returns true if custom spawns should be used for the map + -- @realm server + function entspawnscript.ShouldUseCustomSpawns() + return not tobool(entspawnscript.GetSetting("blacklisted")) + and cvUseWeaponSpawnScript:GetBool() + end + + --- + -- Sets the player editing state and syncs it to the client. Also, when setting the state to true, + -- the current spawn types are synced to the client. + -- @param Player ply The playser whose state should be changed + -- @param boolean state The new editing state + -- @realm server + function entspawnscript.SetEditing(ply, state) + ply:SetNWBool("is_spawn_editing", state) + + local playerIndex = nil + + for i = 1, #entspawnscript.editingPlayers do + if entspawnscript.editingPlayers[i] ~= ply then + continue + end + + playerIndex = i + + break + end + + if state then + if not playerIndex then + entspawnscript.editingPlayers[#entspawnscript.editingPlayers + 1] = ply + end + + net.SendStream("TTT2_WeaponSpawnEntities", entspawnscript.GetSpawns(), ply) + elseif playerIndex then + tableRemove(entspawnscript.editingPlayers, playerIndex) + end + end + + --- + -- Returns a list of all players that currently are in the spawn editing mode. + -- @return table Returns an indexed table of all players currently editing the spawn points + -- @realm server + function entspawnscript.GetEditingPlayers() + return entspawnscript.editingPlayers + end + + --- + -- Gets a list of all players that should receive a spawn point update. These are all + -- editing players besides the player that made the change. + -- @param Player ply The player that made the change + -- @return table An indexed list with all receiving players + -- @realm server + function entspawnscript.GetReceivingPlayers(ply) + local recPlys = {} + + for i = 1, #entspawnscript.editingPlayers do + local editPly = entspawnscript.editingPlayers[i] + + if editPly == ply then + continue + end + + recPlys[#recPlys + 1] = editPly + end + + return recPlys + end + + --- + -- Updates the map settings for the provided players. If no player table is + -- provided, the update is done on all clients. + -- @param table plys A table of players that should be updated + -- @realm server + function entspawnscript.UpdateSettingsOnClients(plys) + plys = plys or player.GetAll() + + for i = 1, #plys do + local ply = plys[i] + + --- + -- @realm server + -- stylua: ignore + if not hook.Run("TTT2AdminCheck", ply) then continue end + + for key, value in pairs(entspawnscript.GetSettings()) do + ttt2net.Set( + { "entspawnscript", "settings", key }, + { type = "int", bits = 16 }, + entspawnscript.GetSetting(key), + ply + ) + end + end + end + + --- + -- Updates the amount of spawns for the provided players. If no player table is + -- provided, the update is done on all clients. + -- @param table plys A table of players that should be updated + -- @realm server + function entspawnscript.UpdateSpawnCountOnClients(plys) + local amountSpawns = { + [SPAWN_TYPE_WEAPON] = 0, + [SPAWN_TYPE_AMMO] = 0, + [SPAWN_TYPE_PLAYER] = 0, + } + + local spawnList = entspawnscript.GetSpawns() + + for spawnType, spawnTables in pairs(spawnList) do + for entType, spawns in pairs(spawnTables) do + amountSpawns[spawnType] = amountSpawns[spawnType] + #spawns + end + end + + plys = plys or player.GetAll() + + for i = 1, #plys do + local ply = plys[i] + + --- + -- @realm server + -- stylua: ignore + if not hook.Run("TTT2AdminCheck", ply) then continue end + + ttt2net.Set( + { "entspawnscript", "spawnamount", "weapon" }, + { type = "int", bits = 16 }, + amountSpawns[SPAWN_TYPE_WEAPON], + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "ammo" }, + { type = "int", bits = 16 }, + amountSpawns[SPAWN_TYPE_AMMO], + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "player" }, + { type = "int", bits = 16 }, + amountSpawns[SPAWN_TYPE_PLAYER], + ply + ) + + ttt2net.Set( + { "entspawnscript", "spawnamount", "weaponrandom" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_RANDOM] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "weaponmelee" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_MELEE] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "weaponnade" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_NADE] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "weaponshotgun" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_SHOTGUN] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "weaponheavy" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_HEAVY] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "weaponsniper" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_SNIPER] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "weaponpistol" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_PISTOL] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "weaponspecial" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_WEAPON][WEAPON_TYPE_SPECIAL] or {}), + ply + ) + + ttt2net.Set( + { "entspawnscript", "spawnamount", "ammorandom" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_RANDOM] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "ammodeagle" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_DEAGLE] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "ammopistol" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_PISTOL] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "ammomac10" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_MAC10] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "ammorifle" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_RIFLE] or {}), + ply + ) + ttt2net.Set( + { "entspawnscript", "spawnamount", "ammoshotgun" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_AMMO][AMMO_TYPE_SHOTGUN] or {}), + ply + ) + + ttt2net.Set( + { "entspawnscript", "spawnamount", "playerrandom" }, + { type = "int", bits = 16 }, + #(spawnList[SPAWN_TYPE_PLAYER][PLAYER_TYPE_RANDOM] or {}), + ply + ) + end + end + + --- + -- Transmits the settings and the spawn point amount to the provided player. + -- This function is called in @{GM:TTT2PlayerReady}. + -- @param Player ply The player who should receive the update + -- @realm server + function entspawnscript.TransmitToPlayer(ply) + -- trigger a sync of the settings table + entspawnscript.UpdateSettingsOnClients({ ply }) + + -- trigger a sync of the spawn amount info + entspawnscript.UpdateSpawnCountOnClients({ ply }) + end + + --- + -- Reloads the spawn points from either the spawn files or from the defaults. Is mostly used after + -- switching from classic TTT weapon spawns to dynamic TTT2 weapons spawns. + -- @realm server + function entspawnscript.ReloadSpawns() + -- if switched back to custom spawn loading, we want to load again the spawn file to restore the spawn points + if entspawnscript.SpawnFileExists() then + entspawnscript.SetSpawns(entspawnscript.ReadSpawnFile()) + else + -- if the map was never changed, check if there is an old spawn script and convert it to the new system + local spawnPoints = entspawnscript.InitOldWeaponSpawnScript() + + entspawnscript.SetSpawns(spawnPoints) + end + end + + cvars.AddChangeCallback(cvUseWeaponSpawnScript:GetName(), function(_, _, new) + if tobool(new) then + entspawnscript.ReloadSpawns() + end + end) end if CLIENT then - local focusedSpawn = nil - local spawnInfoEnt = nil - - --- - -- Sets the focused spawn point to be used elsewhere (like in targetID). - -- @param number spawnType The type of the spawn, set to nil to reset - -- @param number entType The specific entity type for the specific spawn type - -- @param number id The unique ID of the spawn point - -- @param table spawn The spawn point data table - -- @realm client - function entspawnscript.SetFocusedSpawn(spawnType, entType, id, spawn) - if not spawnType then - focusedSpawn = nil - else - focusedSpawn = { - spawnType = spawnType, - entType = entType, - id = id, - spawn = spawn - } - end - end - - --- - -- Returns the table with the focused spawn data. - -- @return table Returns the focused spawn data table - -- @realm client - function entspawnscript.GetFocusedSpawn() - return focusedSpawn - end - - --- - -- Sets the spawn info entity that is used for all spawn points in targetID. - -- @param Entity ent The spawn info entity - -- @realm client - function entspawnscript.SetSpawnInfoEntity(ent) - spawnInfoEnt = ent - end - - --- - -- Returns the spawn info entity that is used for all spawn points in targetID. - -- @return Entity Returns the spawn info entity - -- @realm client - function entspawnscript.GetSpawnInfoEntity() - return spawnInfoEnt - end - - --- - -- Clears the local spawn point table cache. - -- @realm client - function entspawnscript.ClearLocalCache() - entspawnscript.SetSpawns(entspawnscript.GetEmptySpawnTableStructure()) - end + local focusedSpawn = nil + local spawnInfoEnt = nil + + --- + -- Sets the focused spawn point to be used elsewhere (like in targetID). + -- @param number spawnType The type of the spawn, set to nil to reset + -- @param number entType The specific entity type for the specific spawn type + -- @param number id The unique ID of the spawn point + -- @param table spawn The spawn point data table + -- @realm client + function entspawnscript.SetFocusedSpawn(spawnType, entType, id, spawn) + if not spawnType then + focusedSpawn = nil + else + focusedSpawn = { + spawnType = spawnType, + entType = entType, + id = id, + spawn = spawn, + } + end + end + + --- + -- Returns the table with the focused spawn data. + -- @return table Returns the focused spawn data table + -- @realm client + function entspawnscript.GetFocusedSpawn() + return focusedSpawn + end + + --- + -- Sets the spawn info entity that is used for all spawn points in targetID. + -- @param Entity ent The spawn info entity + -- @realm client + function entspawnscript.SetSpawnInfoEntity(ent) + spawnInfoEnt = ent + end + + --- + -- Returns the spawn info entity that is used for all spawn points in targetID. + -- @return Entity Returns the spawn info entity + -- @realm client + function entspawnscript.GetSpawnInfoEntity() + return spawnInfoEnt + end + + --- + -- Clears the local spawn point table cache. + -- @realm client + function entspawnscript.ClearLocalCache() + entspawnscript.SetSpawns(entspawnscript.GetEmptySpawnTableStructure()) + end end --- @@ -644,7 +752,7 @@ end -- @param table spawnPoints The new spawnPoints table -- @realm server function entspawnscript.SetSpawns(spawnPoints) - spawnPointList = spawnPoints + spawnPointList = spawnPoints end --- @@ -656,42 +764,44 @@ end -- @param[default=false] boolean omitSaving If set to true, the setting will not be saved -- @realm shared function entspawnscript.SetSetting(key, value, omitSaving) - omitSaving = omitSaving or false - - if isbool(value) then - value = value and 1 or 0 - end - - if not isnumber(value) then - ErrorNoHalt("WARNING: Only number and boolean values for map settings allowed.") - - return - end - - if SERVER then - -- only continue if the value has changed - if settingsList[key] == value then return end - - settingsList[key] = value - - if not omitSaving then - entspawnscript.UpdateSettingsFile() - end - - -- trigger an update of the synced settings table - entspawnscript.UpdateSettingsOnClients() - - -- special handling for some settings - if key == "blacklisted" and value == 0 then - entspawnscript.ReloadSpawns() - end - else -- CLIENT - net.Start("ttt2_entspawn_setting_update") - net.WriteString(key) - net.WriteInt(value, 16) - net.WriteBool(omitSaving) - net.SendToServer() - end + omitSaving = omitSaving or false + + if isbool(value) then + value = value and 1 or 0 + end + + if not isnumber(value) then + ErrorNoHaltWithStack("WARNING: Only number and boolean values for map settings allowed.") + + return + end + + if SERVER then + -- only continue if the value has changed + if settingsList[key] == value then + return + end + + settingsList[key] = value + + if not omitSaving then + entspawnscript.UpdateSettingsFile() + end + + -- trigger an update of the synced settings table + entspawnscript.UpdateSettingsOnClients() + + -- special handling for some settings + if key == "blacklisted" and value == 0 then + entspawnscript.ReloadSpawns() + end + else -- CLIENT + net.Start("ttt2_entspawn_setting_update") + net.WriteString(key) + net.WriteInt(value, 16) + net.WriteBool(omitSaving) + net.SendToServer() + end end --- @@ -700,7 +810,7 @@ end -- @return boolean Returns true if the player is editing -- @realm shared function entspawnscript.IsEditing(ply) - return ply:GetNWBool("is_spawn_editing", false) + return ply:GetNWBool("is_spawn_editing", false) end --- @@ -708,17 +818,17 @@ end -- @return table An indexed table of all spawn types -- @realm shared function entspawnscript.GetSpawnTypes() - return spawnTypes + return spawnTypes end --- -- Returns the language identifier for a specific spawnType/entType combination. -- @param number spawnType The type of the spawn -- @param number entType The specific entity type for the specific spawn type --- @return string Returns the language identifer +-- @return string Returns the language identifier -- @realm shared function entspawnscript.GetLangIdentifierFromSpawnType(spawnType, entType) - return spawnData[spawnType][entType].name or "undefined_lang_identifier" + return spawnData[spawnType][entType].name or "undefined_lang_identifier" end --- @@ -728,7 +838,7 @@ end -- @return string Returns the storage variable name -- @realm shared function entspawnscript.GetVarNameFromSpawnType(spawnType, entType) - return spawnData[spawnType][entType].var or "UNDEFINED" + return spawnData[spawnType][entType].var or "UNDEFINED" end --- @@ -737,7 +847,7 @@ end -- @return[default=COLOR_WHITE] Color Returns the color for the spawn type -- @realm shared function entspawnscript.GetColorFromSpawnType(spawnType) - return spawnColors[spawnType] or COLOR_WHITE + return spawnColors[spawnType] or COLOR_WHITE end --- @@ -747,7 +857,7 @@ end -- @return Material Returns the icon material -- @realm shared function entspawnscript.GetIconFromSpawnType(spawnType, entType) - return spawnData[spawnType][entType].material + return spawnData[spawnType][entType].material end --- @@ -758,17 +868,19 @@ end -- @return table Returns an indexed table with the available entity Types -- @realm shared function entspawnscript.GetEntTypeList(spawnType, excludeTypes) - local indexedTable = {} + local indexedTable = {} - local spawns = spawnData[spawnType] + local spawns = spawnData[spawnType] - for entType in pairs(spawns) do - if excludeTypes[entType] then continue end + for entType in pairs(spawns) do + if excludeTypes[entType] then + continue + end - indexedTable[#indexedTable + 1] = entType - end + indexedTable[#indexedTable + 1] = entType + end - return indexedTable + return indexedTable end --- @@ -776,18 +888,18 @@ end -- @return table An indexed table with all available spawn type / entity type combinations -- @realm shared function entspawnscript.GetSpawnTypeList() - local indexedTable = {} - - for spawnType, spawns in pairs(spawnData) do - for entType in pairs(spawns) do - indexedTable[#indexedTable + 1] = { - spawnType = spawnType, - entType = entType - } - end - end - - return indexedTable + local indexedTable = {} + + for spawnType, spawns in pairs(spawnData) do + for entType in pairs(spawns) do + indexedTable[#indexedTable + 1] = { + spawnType = spawnType, + entType = entType, + } + end + end + + return indexedTable end --- @@ -797,7 +909,7 @@ end -- @return number The weapon spawn type assosiated with a weapon kind -- @realm shared function entspawnscript.GetSpawnTypeFromKind(kind) - return kindToSpawnType[kind] + return kindToSpawnType[kind] end --- @@ -806,7 +918,7 @@ end -- @return table A table with all spawns -- @realm shared function entspawnscript.GetSpawns() - return spawnPointList + return spawnPointList end --- @@ -816,7 +928,7 @@ end -- @return table A table with all spawns -- @realm shared function entspawnscript.GetSpawnsForSpawnType(spawnType) - return spawnPointList[spawnType] + return spawnPointList[spawnType] end --- @@ -832,31 +944,35 @@ end -- the server to the client for net performance reasons -- @realm shared function entspawnscript.RemoveSpawnById(spawnType, entType, id, shouldSync, ply) - local spawnPoints = entspawnscript.GetSpawns() + local spawnPoints = entspawnscript.GetSpawns() - if not spawnPoints or not spawnPoints[spawnType] or not spawnPoints[spawnType][entType] then return end + if not spawnPoints or not spawnPoints[spawnType] or not spawnPoints[spawnType][entType] then + return + end - local list = spawnPoints[spawnType][entType] + local list = spawnPoints[spawnType][entType] - tableRemove(list, id) + tableRemove(list, id) - if SERVER then - -- update amount of spawns on clients - entspawnscript.UpdateSpawnCountOnClients() - end + if SERVER then + -- update amount of spawns on clients + entspawnscript.UpdateSpawnCountOnClients() + end - if not shouldSync then return end + if not shouldSync then + return + end - net.Start("ttt2_remove_spawn_ent") - net.WriteUInt(spawnType, 4) - net.WriteUInt(entType, 4) - net.WriteUInt(id, 32) + net.Start("ttt2_remove_spawn_ent") + net.WriteUInt(spawnType, 4) + net.WriteUInt(entType, 4) + net.WriteUInt(id, 32) - if SERVER then - net.Send(entspawnscript.GetReceivingPlayers(ply)) - else -- CLIENT - net.SendToServer() - end + if SERVER then + net.Send(entspawnscript.GetReceivingPlayers(ply)) + else -- CLIENT + net.SendToServer() + end end --- @@ -875,41 +991,43 @@ end -- the server to the client for net performance reasons -- @realm shared function entspawnscript.AddSpawn(spawnType, entType, pos, ang, ammo, shouldSync, ply) - local spawnPoints = entspawnscript.GetSpawns() - - ammo = ammo or 0 - - spawnPoints = spawnPoints or {} - spawnPoints[spawnType] = spawnPoints[spawnType] or {} - spawnPoints[spawnType][entType] = spawnPoints[spawnType][entType] or {} - - local list = spawnPoints[spawnType][entType] - - list[#list + 1] = { - pos = pos, - ang = ang, - ammo = ammo - } - - if SERVER then - -- update amount of spawns on clients - entspawnscript.UpdateSpawnCountOnClients() - end - - if not shouldSync then return end - - net.Start("ttt2_add_spawn_ent") - net.WriteUInt(spawnType, 4) - net.WriteUInt(entType, 4) - net.WriteVector(pos) - net.WriteAngle(ang) - net.WriteUInt(ammo, 8) - - if SERVER then - net.Send(entspawnscript.GetReceivingPlayers(ply)) - else -- CLIENT - net.SendToServer() - end + local spawnPoints = entspawnscript.GetSpawns() + + ammo = ammo or 0 + + spawnPoints = spawnPoints or {} + spawnPoints[spawnType] = spawnPoints[spawnType] or {} + spawnPoints[spawnType][entType] = spawnPoints[spawnType][entType] or {} + + local list = spawnPoints[spawnType][entType] + + list[#list + 1] = { + pos = pos, + ang = ang, + ammo = ammo, + } + + if SERVER then + -- update amount of spawns on clients + entspawnscript.UpdateSpawnCountOnClients() + end + + if not shouldSync then + return + end + + net.Start("ttt2_add_spawn_ent") + net.WriteUInt(spawnType, 4) + net.WriteUInt(entType, 4) + net.WriteVector(pos) + net.WriteAngle(ang) + net.WriteUInt(ammo, 8) + + if SERVER then + net.Send(entspawnscript.GetReceivingPlayers(ply)) + else -- CLIENT + net.SendToServer() + end end --- @@ -929,39 +1047,45 @@ end -- the server to the client for net performance reasons -- @realm shared function entspawnscript.UpdateSpawn(spawnType, entType, id, pos, ang, ammo, shouldSync, ply) - local spawnPoints = entspawnscript.GetSpawns() - - if not spawnPoints or not spawnPoints[spawnType] or not spawnPoints[spawnType][entType] then return end - - local listEntry = spawnPoints[spawnType][entType] - - if not listEntry[id] then return end - - pos = pos or listEntry[id].pos - ang = ang or listEntry[id].ang - ammo = ammo or listEntry[id].ammo - - listEntry[id] = { - pos = pos, - ang = ang, - ammo = ammo - } - - if not shouldSync then return end - - net.Start("ttt2_update_spawn_ent") - net.WriteUInt(spawnType, 4) - net.WriteUInt(entType, 4) - net.WriteUInt(id, 32) - net.WriteVector(pos) - net.WriteAngle(ang) - net.WriteUInt(ammo, 8) - - if SERVER then - net.Send(entspawnscript.GetReceivingPlayers(ply)) - else -- CLIENT - net.SendToServer() - end + local spawnPoints = entspawnscript.GetSpawns() + + if not spawnPoints or not spawnPoints[spawnType] or not spawnPoints[spawnType][entType] then + return + end + + local listEntry = spawnPoints[spawnType][entType] + + if not listEntry[id] then + return + end + + pos = pos or listEntry[id].pos + ang = ang or listEntry[id].ang + ammo = ammo or listEntry[id].ammo + + listEntry[id] = { + pos = pos, + ang = ang, + ammo = ammo, + } + + if not shouldSync then + return + end + + net.Start("ttt2_update_spawn_ent") + net.WriteUInt(spawnType, 4) + net.WriteUInt(entType, 4) + net.WriteUInt(id, 32) + net.WriteVector(pos) + net.WriteAngle(ang) + net.WriteUInt(ammo, 8) + + if SERVER then + net.Send(entspawnscript.GetReceivingPlayers(ply)) + else -- CLIENT + net.SendToServer() + end end --- @@ -969,19 +1093,23 @@ end -- @note Deleting all spawns does update the weapon spawn file immediately. Be careful. -- @realm shared function entspawnscript.DeleteAllSpawns() - if CLIENT then - net.Start("ttt2_delete_all_spawns") - net.SendToServer() - else - entspawnscript.SetSpawns(entspawnscript.GetEmptySpawnTableStructure()) - - entspawnscript.UpdateSpawnFile() - - net.SendStream("TTT2_WeaponSpawnEntities", entspawnscript.GetSpawns(), entspawnscript.editingPlayers) - - -- update amount of spawns on clients - entspawnscript.UpdateSpawnCountOnClients() - end + if CLIENT then + net.Start("ttt2_delete_all_spawns") + net.SendToServer() + else + entspawnscript.SetSpawns(entspawnscript.GetEmptySpawnTableStructure()) + + entspawnscript.UpdateSpawnFile() + + net.SendStream( + "TTT2_WeaponSpawnEntities", + entspawnscript.GetSpawns(), + entspawnscript.editingPlayers + ) + + -- update amount of spawns on clients + entspawnscript.UpdateSpawnCountOnClients() + end end --- @@ -990,25 +1118,30 @@ end -- @param Player ply The player that starts editing; only relevant on the server -- @realm shared function entspawnscript.StartEditing(ply) - if CLIENT then - net.Start("ttt2_toggle_entspawn_editing") - net.WriteBool(true) - net.SendToServer() - else - if entspawnscript.IsEditing(ply) then return end - - ply:CacheAndStripWeapons() - - timer.Simple(0, function() - if not IsValid(ply) then return end - - local wep = ply:Give("weapon_ttt_spawneditor") - - wep:Equip() - - entspawnscript.SetEditing(ply, true) - end) - end + if CLIENT then + net.Start("ttt2_toggle_entspawn_editing") + net.WriteBool(true) + net.SendToServer() + else + if entspawnscript.IsEditing(ply) then + return + end + + ply:CacheAndStripWeapons(true) + ply:CacheAndStripItems() + + timer.Simple(0, function() + if not IsValid(ply) then + return + end + + local wep = ply:Give("weapon_ttt_spawneditor") + + wep:Equip() + + entspawnscript.SetEditing(ply, true) + end) + end end --- @@ -1017,18 +1150,21 @@ end -- @param Player ply The player that stops editing; only relevant on the server -- @realm shared function entspawnscript.StopEditing(ply) - if CLIENT then - net.Start("ttt2_toggle_entspawn_editing") - net.WriteBool(false) - net.SendToServer() - else - if not entspawnscript.IsEditing(ply) then return end - - ply:RestoreCachedWeapons() - ply:StripWeapon("weapon_ttt_spawneditor") - - entspawnscript.SetEditing(ply, false) - end + if CLIENT then + net.Start("ttt2_toggle_entspawn_editing") + net.WriteBool(false) + net.SendToServer() + else + if not entspawnscript.IsEditing(ply) then + return + end + + ply:RestoreCachedWeapons() + ply:RestoreCachedItems() + ply:StripWeapon("weapon_ttt_spawneditor") + + entspawnscript.SetEditing(ply, false) + end end --- @@ -1037,29 +1173,29 @@ end -- @note default state includes old weapon scripts -- @realm shared function entspawnscript.ResetMapToDefault() - if CLIENT then - net.Start("ttt2_entspawn_reset") - net.SendToServer() - else - -- delete the changed files - entspawnscript.RemoveSpawnFile() - entspawnscript.RemoveSettingsFile() + if CLIENT then + net.Start("ttt2_entspawn_reset") + net.SendToServer() + else + -- delete the changed files + entspawnscript.RemoveSpawnFile() + entspawnscript.RemoveSettingsFile() - -- load old weapon scripts and reset to default - local spawnPoints, settings = entspawnscript.InitOldWeaponSpawnScript() + -- load old weapon scripts and reset to default + local spawnPoints, settings = entspawnscript.InitOldWeaponSpawnScript() - entspawnscript.SetSpawns(spawnPoints) - entspawnscript.SetSettings(settings) + entspawnscript.SetSpawns(spawnPoints) + entspawnscript.SetSettings(settings) - -- Most of the time this set up is done before the player is ready. However to make sure the update - -- is called at least once after the data is generated, it is also called here. + -- Most of the time this set up is done before the player is ready. However to make sure the update + -- is called at least once after the data is generated, it is also called here. - -- trigger a sync of the settings table - entspawnscript.UpdateSettingsOnClients() + -- trigger a sync of the settings table + entspawnscript.UpdateSettingsOnClients() - -- trigger a sync of the spawn amount info - entspawnscript.UpdateSpawnCountOnClients() - end + -- trigger a sync of the spawn amount info + entspawnscript.UpdateSpawnCountOnClients() + end end --- @@ -1068,99 +1204,144 @@ end -- @return table An empty spawn table -- @realm shared function entspawnscript.GetEmptySpawnTableStructure() - return tableCopy({ - [SPAWN_TYPE_WEAPON] = {}, - [SPAWN_TYPE_AMMO] = {}, - [SPAWN_TYPE_PLAYER] = {} - }) + return tableCopy({ + [SPAWN_TYPE_WEAPON] = {}, + [SPAWN_TYPE_AMMO] = {}, + [SPAWN_TYPE_PLAYER] = {}, + }) end -- SYNCING if SERVER then - util.AddNetworkString("ttt2_remove_spawn_ent") - util.AddNetworkString("ttt2_add_spawn_ent") - util.AddNetworkString("ttt2_update_spawn_ent") - util.AddNetworkString("ttt2_delete_all_spawns") - util.AddNetworkString("ttt2_toggle_entspawn_editing") - util.AddNetworkString("ttt2_entspawn_reset") - util.AddNetworkString("ttt2_entspawn_setting_update") - - net.Receive("ttt2_remove_spawn_ent", function(_, ply) - --- - -- @realm server - if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end - - entspawnscript.RemoveSpawnById(net.ReadUInt(4), net.ReadUInt(4), net.ReadUInt(32), false, ply) - end) - - net.Receive("ttt2_add_spawn_ent", function(_, ply) - --- - -- @realm server - if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end - - entspawnscript.AddSpawn(net.ReadUInt(4), net.ReadUInt(4), net.ReadVector(), net.ReadAngle(), net.ReadUInt(8), false, ply) - end) - - net.Receive("ttt2_update_spawn_ent", function(_, ply) - --- - -- @realm server - if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end - - entspawnscript.UpdateSpawn(net.ReadUInt(4), net.ReadUInt(4), net.ReadUInt(32), net.ReadVector(), net.ReadAngle(), net.ReadUInt(8), false, ply) - end) - - net.Receive("ttt2_delete_all_spawns", function(_, ply) - --- - -- @realm server - if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end - - entspawnscript.DeleteAllSpawns() - end) - - net.Receive("ttt2_entspawn_setting_update", function(_, ply) - --- - -- @realm server - if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end - - entspawnscript.SetSetting(net.ReadString(), net.ReadInt(16), net.ReadBool()) - end) - - net.Receive("ttt2_entspawn_reset", function(_, ply) - --- - -- @realm server - if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end - - entspawnscript.ResetMapToDefault() - end) - - net.Receive("ttt2_toggle_entspawn_editing", function(_, ply) - --- - -- @realm server - if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end - - if net.ReadBool() then - entspawnscript.StartEditing(ply) - else - entspawnscript.StopEditing(ply) - end - end) + util.AddNetworkString("ttt2_remove_spawn_ent") + util.AddNetworkString("ttt2_add_spawn_ent") + util.AddNetworkString("ttt2_update_spawn_ent") + util.AddNetworkString("ttt2_delete_all_spawns") + util.AddNetworkString("ttt2_toggle_entspawn_editing") + util.AddNetworkString("ttt2_entspawn_reset") + util.AddNetworkString("ttt2_entspawn_setting_update") + + net.Receive("ttt2_remove_spawn_ent", function(_, ply) + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end + + entspawnscript.RemoveSpawnById( + net.ReadUInt(4), + net.ReadUInt(4), + net.ReadUInt(32), + false, + ply + ) + end) + + net.Receive("ttt2_add_spawn_ent", function(_, ply) + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end + + entspawnscript.AddSpawn( + net.ReadUInt(4), + net.ReadUInt(4), + net.ReadVector(), + net.ReadAngle(), + net.ReadUInt(8), + false, + ply + ) + end) + + net.Receive("ttt2_update_spawn_ent", function(_, ply) + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end + + entspawnscript.UpdateSpawn( + net.ReadUInt(4), + net.ReadUInt(4), + net.ReadUInt(32), + net.ReadVector(), + net.ReadAngle(), + net.ReadUInt(8), + false, + ply + ) + end) + + net.Receive("ttt2_delete_all_spawns", function(_, ply) + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end + + entspawnscript.DeleteAllSpawns() + end) + + net.Receive("ttt2_entspawn_setting_update", function(_, ply) + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end + + entspawnscript.SetSetting(net.ReadString(), net.ReadInt(16), net.ReadBool()) + end) + + net.Receive("ttt2_entspawn_reset", function(_, ply) + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end + + entspawnscript.ResetMapToDefault() + end) + + net.Receive("ttt2_toggle_entspawn_editing", function(_, ply) + --- + -- @realm server + -- stylua: ignore + if not IsValid(ply) or not hook.Run("TTT2AdminCheck", ply) then return end + + if net.ReadBool() then + entspawnscript.StartEditing(ply) + else + entspawnscript.StopEditing(ply) + end + end) end if CLIENT then - net.ReceiveStream("TTT2_WeaponSpawnEntities", function(spawnPoints) - entspawnscript.SetSpawns(spawnPoints) - end) - - net.Receive("ttt2_remove_spawn_ent", function() - entspawnscript.RemoveSpawnById(net.ReadUInt(4), net.ReadUInt(4), net.ReadUInt(32), false) - end) - - net.Receive("ttt2_add_spawn_ent", function() - entspawnscript.AddSpawn(net.ReadUInt(4), net.ReadUInt(4), net.ReadVector(), net.ReadAngle(), net.ReadUInt(8), false) - end) - - net.Receive("ttt2_update_spawn_ent", function() - entspawnscript.UpdateSpawn(net.ReadUInt(4), net.ReadUInt(4), net.ReadUInt(32), net.ReadVector(), net.ReadAngle(), net.ReadUInt(8), false) - end) + net.ReceiveStream("TTT2_WeaponSpawnEntities", function(spawnPoints) + entspawnscript.SetSpawns(spawnPoints) + end) + + net.Receive("ttt2_remove_spawn_ent", function() + entspawnscript.RemoveSpawnById(net.ReadUInt(4), net.ReadUInt(4), net.ReadUInt(32), false) + end) + + net.Receive("ttt2_add_spawn_ent", function() + entspawnscript.AddSpawn( + net.ReadUInt(4), + net.ReadUInt(4), + net.ReadVector(), + net.ReadAngle(), + net.ReadUInt(8), + false + ) + end) + + net.Receive("ttt2_update_spawn_ent", function() + entspawnscript.UpdateSpawn( + net.ReadUInt(4), + net.ReadUInt(4), + net.ReadUInt(32), + net.ReadVector(), + net.ReadAngle(), + net.ReadUInt(8), + false + ) + end) end diff --git a/lua/ttt2/libraries/eventdata.lua b/lua/ttt2/libraries/eventdata.lua index 099ade897..db77867a1 100644 --- a/lua/ttt2/libraries/eventdata.lua +++ b/lua/ttt2/libraries/eventdata.lua @@ -5,7 +5,7 @@ -- @module eventdata if SERVER then - AddCSLuaFile() + AddCSLuaFile() end eventdata = eventdata or {} @@ -17,20 +17,22 @@ eventdata = eventdata or {} -- @return table A table with the amounts of deaths per player -- @realm shared function eventdata.GetPlayerTotalDeaths() - local eventList = events.list - local deathList = {} + local eventList = events.list + local deathList = {} - for i = 1, #eventList do - local event = eventList[i] + for i = 1, #eventList do + local event = eventList[i] - if event.type ~= EVENT_KILL then continue end + if event.type ~= EVENT_KILL then + continue + end - local victim64 = event.event.victim.sid64 + local victim64 = event.event.victim.sid64 - deathList[victim64] = (deathList[victim64] or 0) + 1 - end + deathList[victim64] = (deathList[victim64] or 0) + 1 + end - return deathList + return deathList end --- @@ -39,15 +41,17 @@ end -- @return table A table with the nick, sid64, role and team of each player -- @realm shared function eventdata.GetPlayerBeginRoles() - local eventList = events.list + local eventList = events.list - for i = 1, #eventList do - local event = eventList[i] + for i = 1, #eventList do + local event = eventList[i] - if event.type ~= EVENT_SELECTED then continue end + if event.type ~= EVENT_SELECTED then + continue + end - return event.event.plys - end + return event.event.plys + end end --- @@ -56,15 +60,17 @@ end -- @return table A table with the nick, sid64, alive, role and team of each player -- @realm shared function eventdata.GetPlayerEndRoles() - local eventList = events.list + local eventList = events.list - for i = 1, #eventList do - local event = eventList[i] + for i = 1, #eventList do + local event = eventList[i] - if event.type ~= EVENT_FINISH then continue end + if event.type ~= EVENT_FINISH then + continue + end - return event.event.plys - end + return event.event.plys + end end --- @@ -73,40 +79,42 @@ end -- @return table A table with all rolechanges per player, each entry contains the new role and the new team -- @realm shared function eventdata.GetPlayerRoles() - local eventList = events.list - local plyRoles = {} - - -- we can use the fact that the eventlist is chronological - for i = 1, #eventList do - local event = eventList[i] - - if event.type == EVENT_SELECTED then - local plys = event.event.plys - - for k = 1, #plys do - local ply = plys[k] - - plyRoles[ply.sid64] = {{ - role = ply.role, - team = ply.team - }} - end - elseif event.type == EVENT_ROLECHANGE and event.event.roundState == ROUND_ACTIVE then - local ply = event.event - - -- if a player connects after the round started, they don't have a starting role - if not plyRoles[ply.sid64] then - plyRoles[ply.sid64] = {} - end - - plyRoles[ply.sid64][#plyRoles[ply.sid64] + 1] = { - role = ply.newRole, - team = ply.newTeam - } - end - end - - return plyRoles + local eventList = events.list + local plyRoles = {} + + -- we can use the fact that the eventlist is chronological + for i = 1, #eventList do + local event = eventList[i] + + if event.type == EVENT_SELECTED then + local plys = event.event.plys + + for k = 1, #plys do + local ply = plys[k] + + plyRoles[ply.sid64] = { + { + role = ply.role, + team = ply.team, + }, + } + end + elseif event.type == EVENT_ROLECHANGE and event.event.roundState == ROUND_ACTIVE then + local ply = event.event + + -- if a player connects after the round started, they don't have a starting role + if not plyRoles[ply.sid64] then + plyRoles[ply.sid64] = {} + end + + plyRoles[ply.sid64][#plyRoles[ply.sid64] + 1] = { + role = ply.newRole, + team = ply.newTeam, + } + end + end + + return plyRoles end --- @@ -114,26 +122,28 @@ end -- @return table Returns a table of all scored events per player -- @realm shared function eventdata.GetPlayerScores() - local eventList = events.list - local plyScores = {} + local eventList = events.list + local plyScores = {} - for i = 1, #eventList do - local event = eventList[i] + for i = 1, #eventList do + local event = eventList[i] - if not event:HasScore() then continue end + if not event:HasScore() then + continue + end - local plys64 = event:GetScoredPlayers() + local plys64 = event:GetScoredPlayers() - for k = 1, #plys64 do - local ply64 = plys64[k] + for k = 1, #plys64 do + local ply64 = plys64[k] - plyScores[ply64] = plyScores[ply64] or {} + plyScores[ply64] = plyScores[ply64] or {} - plyScores[ply64][#plyScores[ply64] + 1] = event - end - end + plyScores[ply64][#plyScores[ply64] + 1] = event + end + end - return plyScores + return plyScores end --- @@ -143,24 +153,26 @@ end -- @return table A table with the score per player -- @realm shared function eventdata.GetPlayerTotalScores() - local eventList = events.list - local scoreList = {} + local eventList = events.list + local scoreList = {} - for i = 1, #eventList do - local event = eventList[i] + for i = 1, #eventList do + local event = eventList[i] - if not event:HasScore() then continue end + if not event:HasScore() then + continue + end - local plys64 = event:GetScoredPlayers() + local plys64 = event:GetScoredPlayers() - for k = 1, #plys64 do - local ply64 = plys64[k] + for k = 1, #plys64 do + local ply64 = plys64[k] - scoreList[ply64] = (scoreList[ply64] or 0) + event:GetSummedPlayerScore(ply64) - end - end + scoreList[ply64] = (scoreList[ply64] or 0) + event:GetSummedPlayerScore(ply64) + end + end - return scoreList + return scoreList end --- @@ -168,20 +180,22 @@ end -- @return table Returns a table of all karma events per player -- @realm shared function eventdata.GetPlayerKarma() - local eventList = events.list - local plysKarma = {} + local eventList = events.list + local plysKarma = {} - -- Go table from back to front as only the newest sync is relevant - for i = #eventList, 1, -1 do - local event = eventList[i] + -- Go table from back to front as only the newest sync is relevant + for i = #eventList, 1, -1 do + local event = eventList[i] - if not event:HasKarma() then continue end - plysKarma = event:GetKarma() + if not event:HasKarma() then + continue + end + plysKarma = event:GetKarma() - break - end + break + end - return plysKarma + return plysKarma end --- @@ -191,25 +205,27 @@ end -- @return table A table with the karma per player -- @realm shared function eventdata.GetPlayerTotalKarma() - local eventList = events.list - local plysKarma = {} + local eventList = events.list + local plysKarma = {} - -- Go table from back to front as only the newest sync is relevant - for i = #eventList, 1, -1 do - local event = eventList[i] + -- Go table from back to front as only the newest sync is relevant + for i = #eventList, 1, -1 do + local event = eventList[i] - if not event:HasKarma() then continue end + if not event:HasKarma() then + continue + end - for sid64, reasonList in pairs(event:GetKarma()) do - plysKarma[sid64] = 0 + for sid64, reasonList in pairs(event:GetKarma()) do + plysKarma[sid64] = 0 - for _, karma in pairs(reasonList) do - plysKarma[sid64] = plysKarma[sid64] + karma - end - end + for _, karma in pairs(reasonList) do + plysKarma[sid64] = plysKarma[sid64] + karma + end + end - break - end + break + end - return plysKarma + return plysKarma end diff --git a/lua/ttt2/libraries/events.lua b/lua/ttt2/libraries/events.lua index 5939d61f9..df67af91d 100644 --- a/lua/ttt2/libraries/events.lua +++ b/lua/ttt2/libraries/events.lua @@ -4,7 +4,7 @@ -- @module events if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local tableCopy = table.Copy @@ -14,40 +14,39 @@ local eventTypes = {} events = events or {} events.list = events.list or {} - --- -- Returns a table of all available events. -- @return table A table of all events -- @realm shared function events.GetAll() - return eventTypes + return eventTypes end --- -- Returns the reference to a table of the event. --- @param string name The name identifer of the event +-- @param string name The name identifier of the event -- @return table The reference to the event table -- @realm shared function events.Get(name) - return eventTypes[string.lower(name)] + return eventTypes[string.lower(name)] end --- -- Returns the reference to a copy of the table of the event. --- @param string name The name identifer of the event +-- @param string name The name identifier of the event -- @return table The reference to the copied event table -- @realm shared function events.Create(name) - return tableCopy(events.Get(name)) + return tableCopy(events.Get(name)) end --- -- Checks if an event with this ID is registered in the event list. --- @param string name The name identifer of the event +-- @param string name The name identifier of the event -- @return boolean Returns true if an event of this name exists -- @realm shared function events.Exist(name) - return name and events.Get(name) ~= nil + return name and events.Get(name) ~= nil end --- @@ -55,7 +54,7 @@ end -- @internal -- @realm shared function events.Reset() - events.list = {} + events.list = {} end --- @@ -66,27 +65,27 @@ end -- @return table A table with the reordered events -- @realm shared function events.SortByPlayerAndEvent() - local eventList = events.list - local sortedList = {} + local eventList = events.list + local sortedList = {} - for i = 1, #eventList do - local event = eventList[i] - local type = event.type - local plys64 = event:GetAffectedPlayer64s() + for i = 1, #eventList do + local event = eventList[i] + local type = event.type + local plys64 = event:GetAffectedPlayer64s() - -- now that we have a list of all players affected by this event - -- each of those players should have this event added to their list - for k = 1, #plys64 do - local ply64 = plys64[k] + -- now that we have a list of all players affected by this event + -- each of those players should have this event added to their list + for k = 1, #plys64 do + local ply64 = plys64[k] - sortedList[ply64] = sortedList[ply64] or {} - sortedList[ply64][type] = sortedList[ply64][type] or {} + sortedList[ply64] = sortedList[ply64] or {} + sortedList[ply64][type] = sortedList[ply64][type] or {} - sortedList[ply64][type][#sortedList[ply64][type] + 1] = event - end - end + sortedList[ply64][type][#sortedList[ply64][type] + 1] = event + end + end - return sortedList + return sortedList end --- @@ -95,18 +94,20 @@ end -- @return table The deprecated event list -- @realm shared function events.GetDeprecatedEventList() - local deprecatedEvents = {} + local deprecatedEvents = {} - for i = 1, #events.list do - local event = events.list[i] - local deprecatedEventData = event:GetDeprecatedFormat() + for i = 1, #events.list do + local event = events.list[i] + local deprecatedEventData = event:GetDeprecatedFormat() - if not deprecatedEventData then continue end + if not deprecatedEventData then + continue + end - deprecatedEvents[#deprecatedEvents + 1] = deprecatedEventData - end + deprecatedEvents[#deprecatedEvents + 1] = deprecatedEventData + end - return deprecatedEvents + return deprecatedEvents end --- @@ -114,144 +115,149 @@ end -- @return table The event list. -- @realm shared function events.GetEventList() - return events.list + return events.list end if SERVER then - --- - -- Triggers an event, adds it to a managed list and starts the score calculation for this event. - -- @param string name The name identifer of the event - -- @param any ... The arguments that should be passed to the event, see the @{EVENT:Trigger} function - -- @realm server - function events.Trigger(name, ...) - if not events.Exist(name) then - ErrorNoHalt("[TTT2] ERROR: An event with the name '" .. tostring(name) .. "' does not exist.\n") - - return - end - - local newEvent = events.Create(name) - - -- only add new event to managed event list, if addition was not aborted - if not newEvent:Trigger(...) then return end - - events.list[#events.list + 1] = newEvent - - -- add to deprecated score list - local deprecatedEventData = newEvent:GetDeprecatedFormat() - - if deprecatedEventData then - SCORE:AddEvent(deprecatedEventData) - end - - --- - -- run a hook with the newly added event - -- @realm server - hook.Run("TTT2AddedEvent", name, newEvent) - end - - --- - -- Streams the whole event table to all clients, usually done after the round ended. - -- @internal - -- @realm server - function events.StreamToClients() - local eventList = events.list - local eventStreamData = {} - - for i = 1, #eventList do - eventStreamData[i] = eventList[i]:GetNetworkedData() - end - - net.SendStream("TTT2_EventReport", eventStreamData) - end - - --- - -- This function is called in @{GM:TTTEndRound} and updates the scores and kills - -- of every player in the scoreboard. - -- @internal - -- @realm server - function events.UpdateScoreboard() - local scores = eventdata.GetPlayerTotalScores() - local deaths = eventdata.GetPlayerTotalDeaths() - - for ply64, score in pairs(scores) do - ply = player.GetBySteamID64(ply64) - - if not IsValid(ply) or not ply:ShouldScore() then continue end - - ply:AddFrags(score) - end - - for ply64, death in pairs(deaths) do - ply = player.GetBySteamID64(ply64) - - if not IsValid(ply) or not ply:ShouldScore() then continue end - - ply:AddDeaths(death) - end - end - - --- - -- This hook is called once an event occured, the data is processed - -- and it is about to be added. This hook can be used to modify the data - -- or to cancel the event by returning `false`. - -- @param string type The type of the event as in `EVENT_XXX` - -- @param table eventData The table with the event data - -- @return boolean Return false to cancel the addition of this event - -- @realm server - -- @hook - function GM:TTT2OnTriggeredEvent(type, eventData) - - end - - --- - -- This hook is called after the event was successfully added to the - -- eventmanager. - -- @param string type The type of the event as in `EVENT_XXX` - -- @param EVENT event The event that was added with all its functions - -- @realm server - -- @hook - function GM:TTT2AddedEvent(type, event) - - end + --- + -- Triggers an event, adds it to a managed list and starts the score calculation for this event. + -- @param string name The name identifier of the event + -- @param any ... The arguments that should be passed to the event, see the @{EVENT:Trigger} function + -- @realm server + function events.Trigger(name, ...) + if not events.Exist(name) then + ErrorNoHaltWithStack( + "[TTT2] ERROR: An event with the name '" .. tostring(name) .. "' does not exist.\n" + ) + + return + end + + local newEvent = events.Create(name) + + -- only add new event to managed event list, if addition was not aborted + if not newEvent:Trigger(...) then + return + end + + events.list[#events.list + 1] = newEvent + + -- add to deprecated score list + local deprecatedEventData = newEvent:GetDeprecatedFormat() + + if deprecatedEventData then + SCORE:AddEvent(deprecatedEventData) + end + + --- + -- run a hook with the newly added event + -- @realm server + -- stylua: ignore + hook.Run("TTT2AddedEvent", name, newEvent) + end + + --- + -- Streams the whole event table to all clients, usually done after the round ended. + -- @internal + -- @realm server + function events.StreamToClients() + local eventList = events.list + local eventStreamData = {} + + for i = 1, #eventList do + eventStreamData[i] = eventList[i]:GetNetworkedData() + end + + net.SendStream("TTT2_EventReport", eventStreamData) + end + + --- + -- This function is called in @{GM:TTTEndRound} and updates the scores and kills + -- of every player in the scoreboard. + -- @internal + -- @realm server + function events.UpdateScoreboard() + local scores = eventdata.GetPlayerTotalScores() + local deaths = eventdata.GetPlayerTotalDeaths() + + for ply64, score in pairs(scores) do + ply = player.GetBySteamID64(ply64) + + if not IsValid(ply) or not ply:ShouldScore() then + continue + end + + ply:AddFrags(score) + end + + for ply64, death in pairs(deaths) do + ply = player.GetBySteamID64(ply64) + + if not IsValid(ply) or not ply:ShouldScore() then + continue + end + + ply:AddDeaths(death) + end + end + + --- + -- This hook is called once an event occured, the data is processed + -- and it is about to be added. This hook can be used to modify the data + -- or to cancel the event by returning `false`. + -- @param string type The type of the event as in `EVENT_XXX` + -- @param table eventData The table with the event data + -- @return boolean Return false to cancel the addition of this event + -- @realm server + -- @hook + function GM:TTT2OnTriggeredEvent(type, eventData) end + + --- + -- This hook is called after the event was successfully added to the + -- eventmanager. + -- @param string type The type of the event as in `EVENT_XXX` + -- @param EVENT event The event that was added with all its functions + -- @realm server + -- @hook + function GM:TTT2AddedEvent(type, event) end else --CLIENT - net.ReceiveStream("TTT2_EventReport", function(eventStreamData) - events.Reset() + net.ReceiveStream("TTT2_EventReport", function(eventStreamData) + events.Reset() - for i = 1, #eventStreamData do - local eventData = eventStreamData[i] + for i = 1, #eventStreamData do + local eventData = eventStreamData[i] - local newEvent = events.Create(eventData.type) - newEvent:SetEventTable(eventData.event) - newEvent:SetScoreTable(eventData.score) - newEvent:SetKarmaTable(eventData.karma) - newEvent:SetPlayersTable(eventData.plys64, eventData.plys) + local newEvent = events.Create(eventData.type) + newEvent:SetEventTable(eventData.event) + newEvent:SetScoreTable(eventData.score) + newEvent:SetKarmaTable(eventData.karma) + newEvent:SetPlayersTable(eventData.plys64, eventData.plys) - events.list[i] = newEvent - end + events.list[i] = newEvent + end - CLSCORE:ReportEvents() - end) + CLSCORE:ReportEvents() + end) end local function ShouldInherit(t, base) - return t.base ~= t.type + return t.base ~= t.type end local function OnInitialization(class, path, name) - class.type = name - class.base = class.base or "base_event" + class.type = name + class.base = class.base or "base_event" - _G["EVENT_" .. string.upper(name)] = name + _G["EVENT_" .. string.upper(name)] = name - MsgN("Added TTT2 event file: ", path, name) + Dev(1, "Added TTT2 event file: ", path, name) end eventTypes = classbuilder.BuildFromFolder( - "terrortown/events/", - SHARED_FILE, - "EVENT", -- class scope - OnInitialization, -- on class loaded - true, -- should inherit - ShouldInherit -- special inheritance check + "terrortown/events/", + SHARED_FILE, + "EVENT", -- class scope + OnInitialization, -- on class loaded + true, -- should inherit + ShouldInherit -- special inheritance check ) diff --git a/lua/ttt2/libraries/fastutf8.lua b/lua/ttt2/libraries/fastutf8.lua new file mode 100644 index 000000000..2910bab26 --- /dev/null +++ b/lua/ttt2/libraries/fastutf8.lua @@ -0,0 +1,186 @@ +--- +-- A faster utf8 alternative to the default gmod utf8 library. +-- This code is taken from https://github.com/blitmap/lua-utf8-simple +-- LICENSED under the MIT License +-- Copyright (c) 2015 blitmap +-- +-- @note There is no guarantee that this library behaves exactly like the default one or is even correct in every case. +-- @module fastutf8 + +if SERVER then + AddCSLuaFile() +end + +fastutf8 = {} + +-- ABNF from RFC 3629 +-- +-- UTF8-octets = *( UTF8-char ) +-- UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 +-- UTF8-1 = %x00-7F +-- UTF8-2 = %xC2-DF UTF8-tail +-- UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / +-- %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) +-- UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / +-- %xF4 %x80-8F 2( UTF8-tail ) +-- UTF8-tail = %x80-BF + +-- 0xxxxxxx | 007F (127) +-- 110xxxxx 10xxxxxx | 07FF (2047) +-- 1110xxxx 10xxxxxx 10xxxxxx | FFFF (65535) +-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 10FFFF (1114111) +local pattern = "[%z\1-\127\194-\244][\128-\191]*" + +-- helper function +local posrelat = function(pos, len) + if pos < 0 then + pos = len + pos + 1 + end + + return pos +end + +-- THE MEAT + +-- maps f over s's utf8 characters f can accept args: (visual_index, utf8_character, byte_index) +-- @param string s The string to map over +-- @param func f The function to map over the string +-- @param[opt] boolean no_subs If true, the function will not yield the utf8 characters +-- @realm shared +fastutf8.map = function(s, f, no_subs) + local i = 0 + + if no_subs then + for b, e in s:gmatch("()" .. pattern .. "()") do + i = i + 1 + local c = e - b + f(i, c, b) + end + else + for b, c in s:gmatch("()(" .. pattern .. ")") do + i = i + 1 + f(i, c, b) + end + end +end + +-- THE REST + +--- +-- generator function -- to iterate over all utf8 chars +-- @param string s The string to iterate over +-- @param[opt] boolean no_subs If true, the generator will not yield the utf8 characters +-- @return func Returns a generator function +-- @realm shared +fastutf8.chars = function(s, no_subs) + return coroutine.wrap(function() + return fastutf8.map(s, coroutine.yield, no_subs) + end) +end + +--- +-- returns the number of characters in a UTF-8 string +-- @param string s The string to get the length of +-- @return number Returns the amount of characters of the string +-- @realm shared +fastutf8.len = function(s) + -- count the number of non-continuing bytes + return select(2, s:gsub("[^\128-\193]", "")) +end + +--- +-- replace all utf8 chars with mapping +-- @param string s The string to replace the characters in +-- @param string|table|func map The replacement for the characters +-- @return string,number Returns the string with the replaced characters & the replaced count +-- @realm shared +fastutf8.replace = function(s, map) + return s:gsub(pattern, map) +end + +--- +-- reverse a utf8 string +-- @param string s The string to reverse +-- @return string Returns the reversed string +-- @realm shared +fastutf8.reverse = function(s) + -- reverse the individual greater-than-single-byte characters + s = s:gsub(pattern, function(c) + return #c > 1 and c:reverse() + end) + + return s:reverse() +end + +--- +-- strip non-ascii characters from a utf8 string +-- @param string s The string to strip +-- @return string,number Returns the stripped string & the stripped count +-- @realm shared +fastutf8.strip = function(s) + return s:gsub(pattern, function(c) + return #c > 1 and "" + end) +end + +--- +-- like string.sub() but i, j are utf8 strings +-- a utf8-safe string.sub() +-- @param string s The string to get the substring from +-- @param number i The start index +-- @param[opt] number j The end index +-- @return string Returns the substring +-- @realm shared +fastutf8.sub = function(s, i, j) + local l = fastutf8.len(s) + + i = posrelat(i, l) + j = j and posrelat(j, l) or l + + if i < 1 then + i = 1 + end + if j > l then + j = l + end + + if i > j then + return "" + end + + local diff = j - i + local iter = fastutf8.chars(s, true) + + -- advance up to i + for _ = 1, i - 1 do + iter() + end + + local c, b = select(2, iter()) + + -- i and j are the same, single-charaacter sub + if diff == 0 then + return string.sub(s, b, b + c - 1) + end + + i = b + + -- advance up to j + for _ = 1, diff - 1 do + iter() + end + + c, b = select(2, iter()) + + return string.sub(s, i, b + c - 1) +end + +--- +-- Get the character at index i in the string s +-- @param string s The string to get the character from +-- @param number i The index of the character +-- @return string Returns the character +-- @realm shared +fastutf8.GetChar = function(s, i) + return fastutf8.sub(s, i, i) +end diff --git a/lua/ttt2/libraries/fileloader.lua b/lua/ttt2/libraries/fileloader.lua index acbbbd287..9e73d8f43 100644 --- a/lua/ttt2/libraries/fileloader.lua +++ b/lua/ttt2/libraries/fileloader.lua @@ -4,7 +4,7 @@ -- @module fileloader if SERVER then - AddCSLuaFile() + AddCSLuaFile() end CLIENT_FILE = 0 @@ -26,66 +26,81 @@ fileloader = fileloader or {} -- @param[opt] function preFolderCallback A function that is called before the load of the given folder is started -- @param[opt] function postFolderCallback A function that is called after the load of the given folder is finished -- @realm shared -function fileloader.LoadFolder(path, deepsearch, realm, callback, preFolderCallback, postFolderCallback) - deepsearch = deepsearch or false - realm = realm or SHARED_FILE - - local file_paths = {} - - if isfunction(preFolderCallback) then - preFolderCallback(path, deepsearch, realm) - end - - if deepsearch then - local _, sub_folders = fileFind(path .. "*", "LUA") - - if not sub_folders then return end - - for k = 1, #sub_folders do - local subname = sub_folders[k] - local files = fileFind(path .. subname .. "/*.lua", "LUA") - - if not files then continue end - - for i = 1, #files do - file_paths[#file_paths + 1] = path .. subname .. "/" .. files[i] - end - end - else - local files = fileFind(path .. "*.lua", "LUA") - - if not files then return end - - for i = 1, #files do - file_paths[#file_paths + 1] = path .. files[i] - end - end - - for i = 1, #file_paths do - local file_path = file_paths[i] - - -- filter out directories and temp files (like .lua~) - if stringRight(file_path, 3) ~= "lua" then continue end - - if SERVER and realm == CLIENT_FILE then - AddCSLuaFile(file_path) - elseif SERVER and realm == SHARED_FILE then - AddCSLuaFile(file_path) - include(file_path) - elseif SERVER and realm ~= CLIENT_FILE then - include(file_path) - elseif CLIENT and realm ~= SERVER_FILE then - include(file_path) - else - continue - end - - if isfunction(callback) then - callback(file_path, path, deepsearch, realm) - end - end - - if isfunction(postFolderCallback) then - postFolderCallback(file_paths, path, deepsearch, realm) - end +function fileloader.LoadFolder( + path, + deepsearch, + realm, + callback, + preFolderCallback, + postFolderCallback +) + deepsearch = deepsearch or false + realm = realm or SHARED_FILE + + local file_paths = {} + + if isfunction(preFolderCallback) then + preFolderCallback(path, deepsearch, realm) + end + + if deepsearch then + local _, sub_folders = fileFind(path .. "*", "LUA") + + if not sub_folders then + return + end + + for k = 1, #sub_folders do + local subname = sub_folders[k] + local files = fileFind(path .. subname .. "/*.lua", "LUA") + + if not files then + continue + end + + for i = 1, #files do + file_paths[#file_paths + 1] = path .. subname .. "/" .. files[i] + end + end + else + local files = fileFind(path .. "*.lua", "LUA") + + if not files then + return + end + + for i = 1, #files do + file_paths[#file_paths + 1] = path .. files[i] + end + end + + for i = 1, #file_paths do + local file_path = file_paths[i] + + -- filter out directories and temp files (like .lua~) + if stringRight(file_path, 3) ~= "lua" then + continue + end + + if SERVER and realm == CLIENT_FILE then + AddCSLuaFile(file_path) + elseif SERVER and realm == SHARED_FILE then + AddCSLuaFile(file_path) + include(file_path) + elseif SERVER and realm ~= CLIENT_FILE then + include(file_path) + elseif CLIENT and realm ~= SERVER_FILE then + include(file_path) + else + continue + end + + if isfunction(callback) then + callback(file_path, path, deepsearch, realm) + end + end + + if isfunction(postFolderCallback) then + postFolderCallback(file_paths, path, deepsearch, realm) + end end diff --git a/lua/ttt2/libraries/fonts.lua b/lua/ttt2/libraries/fonts.lua index 5dada650f..4c99e1bae 100644 --- a/lua/ttt2/libraries/fonts.lua +++ b/lua/ttt2/libraries/fonts.lua @@ -5,9 +5,9 @@ -- @module fonts if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return + return end local surface = surface @@ -18,31 +18,31 @@ local tostring = tostring fonts = {} fonts.fonts = {} -fonts.scales = {1, 1.5, 2, 2.5} +fonts.scales = { 1, 1.5, 2, 2.5 } --- -- Gets the scale modifer based on a given scale. This function tries to find one of -- the given steps defined in `fonts.scales` that fits best to the given scale. If it -- fails to find a fitting scale, it returns the largest available. --- @param number|vector scale The font scale +-- @param number|Vector scale The font scale -- @return number The font scale -- @internal -- @realm client function fonts.GetScaleModifier(scale) - local scaleFactor = isvector(scale) and mathMax(scale.x, mathMax(scale.y, scale.z)) or scale + local scaleFactor = isvector(scale) and mathMax(scale.x, mathMax(scale.y, scale.z)) or scale - scaleFactor = tonumber(scaleFactor) + scaleFactor = tonumber(scaleFactor) - local fontScales = fonts.scales + local fontScales = fonts.scales - for i = 1, #fontScales do - if scaleFactor < fontScales[i] then - return i - 1 > 0 and fontScales[i - 1] or fontScales[i] - end - end + for i = 1, #fontScales do + if scaleFactor < fontScales[i] then + return i - 1 > 0 and fontScales[i - 1] or fontScales[i] + end + end - --fallback (return the last scale) - return fontScales[#fontScales] + --fallback (return the last scale) + return fontScales[#fontScales] end --- @@ -53,20 +53,20 @@ end -- @internal -- @realm client function fonts.AddFont(name, baseSize, fontData) - baseSize = baseSize or 13 + baseSize = baseSize or 13 - fonts.fonts[name] = {} + fonts.fonts[name] = {} - for i = 1, #fonts.scales do - local scale = fonts.scales[i] - local nameScaled = scale == 1 and name or name .. tostring(scale) + for i = 1, #fonts.scales do + local scale = fonts.scales[i] + local nameScaled = scale == 1 and name or name .. tostring(scale) - fontData.size = scale * baseSize + fontData.size = scale * baseSize - surface.CreateFont(nameScaled, fontData) + surface.CreateFont(nameScaled, fontData) - fonts.fonts[name][scale] = nameScaled - end + fonts.fonts[name][scale] = nameScaled + end end --- @@ -75,7 +75,7 @@ end -- @return table A table of the font with different scales -- @realm client function fonts.GetFont(name) - return fonts.fonts[name] + return fonts.fonts[name] end --- @@ -83,5 +83,5 @@ end -- @return table A table of all font scales -- @realm client function fonts.GetScales() - return fonts.scales + return fonts.scales end diff --git a/lua/ttt2/libraries/game_effects.lua b/lua/ttt2/libraries/game_effects.lua new file mode 100644 index 000000000..7fdec2a78 --- /dev/null +++ b/lua/ttt2/libraries/game_effects.lua @@ -0,0 +1,132 @@ +--- +-- A library to consolidate some common effects code. +-- @author EntranceJew +-- @module gameEffects +if SERVER then + AddCSLuaFile() +end + +gameEffects = {} + +--- +-- Create a bundle of fires all from a central location. +-- This is used for incendiary grenades or C4 detonation. +-- @param Vector pos The position the fires should originate from. +-- @param TraceResult tr A trace to orient the creation of the fires around. +-- @param number num The number of individual balls of fire that should be created. +-- @param number lifetime The base lifetime of all fires in the bundle. +-- @param boolean explode Should the fires explode when they reach the end of their lives? +-- @param nil|Player dmgowner The player to attribute the fire damage to. +-- @param number spread_force The force that each fire will be flung with. +-- @param boolean immobile If true, fires will become stationary once they begin burning. +-- @param number size The physical scale of the fires. +-- @param number lifetime_variance The amount each lifetime for each fire can vary. +-- @return table A table full of the fire entities. +-- @realm shared +function gameEffects.StartFires( + pos, + tr, + num, + lifetime, + explode, + dmgowner, + spread_force, + immobile, + size, + lifetime_variance +) + local flames = {} + for i = 1, num do + local ang = Angle(-math.Rand(0, 180), math.Rand(0, 360), math.Rand(0, 360)) + local vstart = pos + tr.HitNormal * 64 + local ttl = lifetime + math.Rand(-lifetime_variance, lifetime_variance) + + local flame = ents.Create("ttt_flame") + flame:SetPos(vstart) + flame:SetFlameSize(size) + flame:SetLifeSpan(ttl) + flame:SetImmobile(immobile) + + if IsValid(dmgowner) and dmgowner:IsPlayer() then + flame:SetDamageParent(dmgowner) + flame:SetOwner(dmgowner) + end + + flame:SetDieTime(CurTime() + ttl) + flame:SetExplodeOnDeath(explode) + flame:Spawn() + flame:PhysWake() + + local phys = flame:GetPhysicsObject() + + if IsValid(phys) then + -- the balance between mass and force is subtle, be careful adjusting + phys:SetMass(2) + phys:ApplyForceCenter(ang:Forward() * spread_force) + phys:AddAngleVelocity(Vector(ang.p, ang.r, ang.y)) + end + + flames[#flames + 1] = flame + end + + return flames +end + +--- +-- Creates a single point of fire. +-- @param Vector pos The position to create the fire at. +-- @param number scale Controls the height of the flame more than its radius. Informs the size. +-- @param number life_span How long a fire will burn for. +-- @param nil|Entity owner The creator of the fire. +-- @param nil|Entity parent The thing to attach the fire to. +-- @return nil|Entity The fire it created, or nil if it was merged / couldn't be created. +-- @realm server +function gameEffects.SpawnFire(pos, scale, life_span, owner, parent) + local fire = ents.Create("env_fire") + + if not IsValid(fire) then + return + end + + fire:SetParent(parent) + fire:SetOwner(owner) + fire:SetPos(pos) + --no glow + delete when out + start on + last forever + fire:SetKeyValue("spawnflags", tostring(128 + 32 + 4 + 2 + 1)) + -- hardly controls size, hitbox is goofy, impossible to work with + fire:SetKeyValue("firesize", tostring(scale)) + fire:SetKeyValue("health", tostring(life_span)) + fire:SetKeyValue("ignitionpoint", "64") + -- don't hurt the player because we're managing the hurtbox ourselves + fire:SetKeyValue("damagescale", "0") + fire:Spawn() + fire:Activate() + + return fire +end + +--- +-- greatly simplified version of SDK's game_shard/gamerules.cpp:RadiusDamage +-- does no block checking, radius should be very small +-- @note only hits players! +-- @param DamageInfo dmginfo +-- @param Vector pos +-- @param number radius +-- @param Entity inflictor +-- @realm shared +function gameEffects.RadiusDamage(dmginfo, pos, radius, inflictor) + local entsFound = ents.FindInSphere(pos, radius) + for i = 1, #entsFound do + local vic = entsFound[i] + + if + IsValid(vic) + and inflictor:Visible(vic) + and vic:IsPlayer() + and vic:Alive() + and vic:IsTerror() + then + vic:TakeDamageInfo(dmginfo) + end + end +end diff --git a/lua/ttt2/libraries/hudelements.lua b/lua/ttt2/libraries/hudelements.lua index 9710eb146..ebf13b766 100644 --- a/lua/ttt2/libraries/hudelements.lua +++ b/lua/ttt2/libraries/hudelements.lua @@ -10,10 +10,10 @@ local baseclass = baseclass local pairs = pairs if SERVER then - AddCSLuaFile() + AddCSLuaFile() end -local HUDElementList = HUDElementList or {} +local HUDElementList = {} --- -- Copies any missing data from base table to the target table @@ -22,15 +22,15 @@ local HUDElementList = HUDElementList or {} -- @return table t target table -- @realm shared local function TableInherit(t, base) - for k, v in pairs(base) do - if t[k] == nil then - t[k] = v - elseif k ~= "BaseClass" and istable(t[k]) and istable(v[k]) then - TableInherit(t[k], v) - end - end - - return t + for k, v in pairs(base) do + if t[k] == nil then + t[k] = v + elseif k ~= "BaseClass" and istable(t[k]) and istable(v[k]) then + TableInherit(t[k], v) + end + end + + return t end --- @@ -40,21 +40,21 @@ end -- @return boolean returns whether name is based on base -- @realm shared function hudelements.IsBasedOn(name, base) - local t = hudelements.GetStored(name) + local t = hudelements.GetStored(name) - if not t then - return false - end + if not t then + return false + end - if t.Base == name then - return false - end + if t.Base == name then + return false + end - if t.Base == base then - return true - end + if t.Base == base then + return true + end - return hudelements.IsBasedOn(t.Base, base) + return hudelements.IsBasedOn(t.Base, base) end --- @@ -64,12 +64,12 @@ end -- @param string name hud element name -- @realm shared function hudelements.Register(t, name) - name = string.lower(name) + name = string.lower(name) - t.ClassName = name - t.id = name + t.ClassName = name + t.id = name - HUDElementList[name] = t + HUDElementList[name] = t end --- @@ -77,25 +77,24 @@ end -- @local -- @realm shared function hudelements.OnLoaded() - - -- - -- Once all the scripts are loaded we can set up the baseclass - -- - we have to wait until they're all setup because load order - -- could cause some entities to load before their bases! - -- - for k in pairs(HUDElementList) do - local newTable = hudelements.Get(k) - HUDElementList[k] = newTable - - baseclass.Set(k, newTable) - end - - if CLIENT then - -- Call PreInitialize on all hudelements - for _, v in pairs(HUDElementList) do - v:PreInitialize() - end - end + -- + -- Once all the scripts are loaded we can set up the baseclass + -- - we have to wait until they're all setup because load order + -- could cause some entities to load before their bases! + -- + for k in pairs(HUDElementList) do + local newTable = hudelements.Get(k) + HUDElementList[k] = newTable + + baseclass.Set(k, newTable) + end + + if CLIENT then + -- Call PreInitialize on all hudelements + for _, v in pairs(HUDElementList) do + v:PreInitialize() + end + end end --- @@ -105,35 +104,43 @@ end -- @return table returns the modified retTbl or the new hud element table -- @realm shared function hudelements.Get(name, retTbl) - local Stored = hudelements.GetStored(name) - if not Stored then return end - - -- Create/copy a new table - local retval = retTbl or {} - - for k, v in pairs(Stored) do - if istable(v) then - retval[k] = table.Copy(v) - else - retval[k] = v - end - end - - retval.Base = retval.Base or "hud_element_base" - - -- If we're not derived from ourselves (a base HUD element) - -- then derive from our 'Base' HUD element. - if retval.Base ~= name then - local base = hudelements.Get(retval.Base) - - if not base then - Msg("ERROR: Trying to derive HUD Element " .. tostring(name) .. " from non existant HUD Element " .. tostring(retval.Base) .. "!\n") - else - retval = TableInherit(retval, base) - end - end - - return retval + local Stored = hudelements.GetStored(name) + if not Stored then + return + end + + -- Create/copy a new table + local retval = retTbl or {} + + for k, v in pairs(Stored) do + if istable(v) then + retval[k] = table.Copy(v) + else + retval[k] = v + end + end + + retval.Base = retval.Base or "hud_element_base" + + -- If we're not derived from ourselves (a base HUD element) + -- then derive from our 'Base' HUD element. + if retval.Base ~= name then + local base = hudelements.Get(retval.Base) + + if not base then + ErrorNoHaltWithStack( + "ERROR: Trying to derive HUD Element " + .. tostring(name) + .. " from non existant HUD Element " + .. tostring(retval.Base) + .. "!\n" + ) + else + retval = TableInherit(retval, base) + end + end + + return retval end --- @@ -142,7 +149,7 @@ end -- @return table returns the real hud element table -- @realm shared function hudelements.GetStored(name) - return HUDElementList[name] + return HUDElementList[name] end --- @@ -150,13 +157,13 @@ end -- @return table registered hud elements -- @realm shared function hudelements.GetList() - local result = {} + local result = {} - for _, v in pairs(HUDElementList) do - result[#result + 1] = v - end + for _, v in pairs(HUDElementList) do + result[#result + 1] = v + end - return result + return result end --- @@ -164,15 +171,15 @@ end -- @return table returns a list of all the registered hud element types -- @realm shared function hudelements.GetElementTypes() - local typetbl = {} + local typetbl = {} - for _, v in pairs(HUDElementList) do - if v.type and not table.HasValue(typetbl, v.type) then - table.insert(typetbl, v.type) - end - end + for _, v in pairs(HUDElementList) do + if v.type and not table.HasValue(typetbl, v.type) then + table.insert(typetbl, v.type) + end + end - return typetbl + return typetbl end --- @@ -181,11 +188,11 @@ end -- @return nil|table returns the first element matching the type of all the registered hud elements -- @realm shared function hudelements.GetTypeElement(type) - for _, v in pairs(HUDElementList) do - if v.type and v.type == type then - return v - end - end + for _, v in pairs(HUDElementList) do + if v.type and v.type == type then + return v + end + end end --- @@ -194,15 +201,15 @@ end -- @return table returns all hud elements matching the type of all the registered hud elements -- @realm shared function hudelements.GetAllTypeElements(type) - local retTbl = {} + local retTbl = {} - for _, v in pairs(HUDElementList) do - if v.type and v.type == type then - retTbl[#retTbl + 1] = v - end - end + for _, v in pairs(HUDElementList) do + if v.type and v.type == type then + retTbl[#retTbl + 1] = v + end + end - return retTbl + return retTbl end --- @@ -219,29 +226,41 @@ end -- @todo example / usage -- @realm shared function hudelements.RegisterChildRelation(childid, parentid, parent_is_type) - local child = hudelements.GetStored(childid) - if not child then - MsgN("Error: Cannot add child " .. childid .. " to " .. parentid .. ". child element instance was not found or registered yet!") - - return - end - - if not parent_is_type then - local parent = hudelements.GetStored(parentid) - if not parent then - MsgN("Error: Cannot add child " .. childid .. " to " .. parentid .. ". parent element was not found or registered yet!") - - return - end - - parent:AddChild(childid) - else - local elems = hudelements.GetAllTypeElements(parentid) - - for i = 1, #elems do - elems[i]:AddChild(childid) - end - end - - child:SetParentRelation(parentid, parent_is_type) + local child = hudelements.GetStored(childid) + if not child then + ErrorNoHaltWithStack( + "Error: Cannot add child " + .. childid + .. " to " + .. parentid + .. ". child element instance was not found or registered yet!" + ) + + return + end + + if not parent_is_type then + local parent = hudelements.GetStored(parentid) + if not parent then + ErrorNoHaltWithStack( + "Error: Cannot add child " + .. childid + .. " to " + .. parentid + .. ". parent element was not found or registered yet!" + ) + + return + end + + parent:AddChild(childid) + else + local elems = hudelements.GetAllTypeElements(parentid) + + for i = 1, #elems do + elems[i]:AddChild(childid) + end + end + + child:SetParentRelation(parentid, parent_is_type) end diff --git a/lua/ttt2/libraries/huds.lua b/lua/ttt2/libraries/huds.lua index c3c66960e..377b380da 100644 --- a/lua/ttt2/libraries/huds.lua +++ b/lua/ttt2/libraries/huds.lua @@ -10,10 +10,10 @@ local baseclass = baseclass local pairs = pairs if SERVER then - AddCSLuaFile() + AddCSLuaFile() end -local HUDList = HUDList or {} +local HUDList = {} --- -- Copies any missing data from base table to the target table @@ -22,15 +22,15 @@ local HUDList = HUDList or {} -- @return table t target table -- @realm shared local function TableInherit(t, base) - for k, v in pairs(base) do - if t[k] == nil then - t[k] = v - elseif k ~= "BaseClass" and istable(t[k]) and istable(v[k]) then - TableInherit(t[k], v) - end - end - - return t + for k, v in pairs(base) do + if t[k] == nil then + t[k] = v + elseif k ~= "BaseClass" and istable(t[k]) and istable(v[k]) then + TableInherit(t[k], v) + end + end + + return t end --- @@ -40,21 +40,21 @@ end -- @return boolean returns whether name is based on base -- @realm shared function huds.IsBasedOn(name, base) - local t = huds.GetStored(name) + local t = huds.GetStored(name) - if not t then - return false - end + if not t then + return false + end - if t.Base == name then - return false - end + if t.Base == name then + return false + end - if t.Base == base then - return true - end + if t.Base == base then + return true + end - return huds.IsBasedOn(t.Base, base) + return huds.IsBasedOn(t.Base, base) end --- @@ -64,13 +64,13 @@ end -- @param string name hud name -- @realm shared function huds.Register(t, name) - name = string.lower(name) + name = string.lower(name) - t.ClassName = name - t.id = name - t.isAbstract = t.isAbstract or false + t.ClassName = name + t.id = name + t.isAbstract = t.isAbstract or false - HUDList[name] = t + HUDList[name] = t end --- @@ -78,18 +78,17 @@ end -- @local -- @realm shared function huds.OnLoaded() - - -- - -- Once all the scripts are loaded we can set up the baseclass - -- - we have to wait until they're all setup because load order - -- could cause some entities to load before their bases! - -- - for k in pairs(HUDList) do - local newTable = huds.Get(k) - HUDList[k] = newTable - - baseclass.Set(k, newTable) - end + -- + -- Once all the scripts are loaded we can set up the baseclass + -- - we have to wait until they're all setup because load order + -- could cause some entities to load before their bases! + -- + for k in pairs(HUDList) do + local newTable = huds.Get(k) + HUDList[k] = newTable + + baseclass.Set(k, newTable) + end end --- @@ -99,35 +98,43 @@ end -- @return table returns the modified retTbl or the new hud table -- @realm shared function huds.Get(name, retTbl) - local Stored = huds.GetStored(name) - if not Stored then return end - - -- Create/copy a new table - local retval = retTbl or {} - - for k, v in pairs(Stored) do - if istable(v) then - retval[k] = table.Copy(v) - else - retval[k] = v - end - end - - retval.Base = retval.Base or "hud_base" - - -- If we're not derived from ourselves (a base HUD element) - -- then derive from our 'Base' HUD element. - if retval.Base ~= name then - local base = huds.Get(retval.Base) - - if not base then - Msg("ERROR: Trying to derive HUD " .. tostring(name) .. " from non existant HUD " .. tostring(retval.Base) .. "!\n") - else - retval = TableInherit(retval, base) - end - end - - return retval + local Stored = huds.GetStored(name) + if not Stored then + return + end + + -- Create/copy a new table + local retval = retTbl or {} + + for k, v in pairs(Stored) do + if istable(v) then + retval[k] = table.Copy(v) + else + retval[k] = v + end + end + + retval.Base = retval.Base or "hud_base" + + -- If we're not derived from ourselves (a base HUD element) + -- then derive from our 'Base' HUD element. + if retval.Base ~= name then + local base = huds.Get(retval.Base) + + if not base then + ErrorNoHaltWithStack( + "ERROR: Trying to derive HUD " + .. tostring(name) + .. " from non existant HUD " + .. tostring(retval.Base) + .. "!\n" + ) + else + retval = TableInherit(retval, base) + end + end + + return retval end --- @@ -136,7 +143,7 @@ end -- @return table returns the real hud table -- @realm shared function huds.GetStored(name) - return HUDList[name] + return HUDList[name] end --- @@ -144,15 +151,15 @@ end -- @return table available huds -- @realm shared function huds.GetList() - local result = {} + local result = {} - for _, v in pairs(HUDList) do - if not v.isAbstract then - result[#result + 1] = v - end - end + for _, v in pairs(HUDList) do + if not v.isAbstract then + result[#result + 1] = v + end + end - return result + return result end --- @@ -160,11 +167,11 @@ end -- @return table all registered huds -- @realm shared function huds.GetRealList() - local result = {} + local result = {} - for _, v in pairs(HUDList) do - result[#result + 1] = v - end + for _, v in pairs(HUDList) do + result[#result + 1] = v + end - return result + return result end diff --git a/lua/ttt2/libraries/items.lua b/lua/ttt2/libraries/items.lua index 78f8bc34e..895b369b8 100644 --- a/lua/ttt2/libraries/items.lua +++ b/lua/ttt2/libraries/items.lua @@ -10,10 +10,10 @@ local baseclass = baseclass local pairs = pairs if SERVER then - AddCSLuaFile() + AddCSLuaFile() end -local ItemList = ItemList or {} +local ItemList = {} --- -- Copies any missing data from base table to the target table @@ -22,17 +22,17 @@ local ItemList = ItemList or {} -- @return table t target table -- @realm shared local function TableInherit(t, base) - for k, v in pairs(base) do - if t[k] == nil then - t[k] = v - elseif k ~= "BaseClass" and istable(t[k]) then - TableInherit(t[k], v) - end - end + for k, v in pairs(base) do + if t[k] == nil then + t[k] = v + elseif k ~= "BaseClass" and istable(t[k]) then + TableInherit(t[k], v) + end + end - t.BaseClass = base + t.BaseClass = base - return t + return t end --- @@ -42,21 +42,21 @@ end -- @return boolean returns whether name is based on base -- @realm shared function items.IsBasedOn(name, base) - local t = items.GetStored(name) + local t = items.GetStored(name) - if not t then - return false - end + if not t then + return false + end - if t.Base == name then - return false - end + if t.Base == name then + return false + end - if t.Base == base then - return true - end + if t.Base == base then + return true + end - return items.IsBasedOn(t.Base, base) + return items.IsBasedOn(t.Base, base) end --- @@ -66,56 +66,93 @@ end -- @param string name item name -- @realm shared function items.Register(t, name) - name = string.lower(name) + name = string.lower(name) - t.ClassName = name - t.id = name + t.ClassName = name + t.id = name - ItemList[name] = t + ItemList[name] = t end +local callbackIdentifier = "TTT2RegisteredItemsCallback" + +--- +-- Add callback for item and insert changes in the given equipmentTable +-- @param string name the database-name of the item +-- @param table equipmentTable the table to insert changes to +-- @realm shared +local function AddCallbacks(name, equipmentTable) + -- Make sure that on hot reloads old callbacks are removed before adding the new one + database.RemoveChangeCallback(ShopEditor.accessName, name, nil, callbackIdentifier) + database.AddChangeCallback( + ShopEditor.accessName, + name, + nil, + function(accessName, itemName, key, oldValue, newValue) + if not istable(equipmentTable) then + database.RemoveChangeCallback(ShopEditor.accessName, name, nil, callbackIdentifier) + + return + end + + equipmentTable[key] = newValue + end, + callbackIdentifier + ) +end --- -- All scripts have been loaded... -- @local -- @realm shared function items.OnLoaded() - - -- - -- Once all the scripts are loaded we can set up the baseclass - -- - we have to wait until they're all setup because load order - -- could cause some entities to load before their bases! - -- - for k in pairs(ItemList) do - local newTable = items.Get(k) - ItemList[k] = newTable - - baseclass.Set(k, newTable) - end - - local isSqlTableCreated = SERVER and sql.CreateSqlTable("ttt2_items", ShopEditor.savingKeys) - - for _, item in pairs(ItemList) do - InitDefaultEquipment(item) - ShopEditor.InitDefaultData(item) -- initialize the default data - - if SERVER and isSqlTableCreated then - local name = GetEquipmentFileName(WEPS.GetClass(item)) - local loaded, changed = sql.Load("ttt2_items", name, item, ShopEditor.savingKeys) - - if not loaded then - sql.Init("ttt2_items", name, item, ShopEditor.savingKeys) - elseif changed then - CHANGED_EQUIPMENT[#CHANGED_EQUIPMENT + 1] = {name, item} - end - end - - CreateEquipment(item) -- init items - - item.CanBuy = {} -- reset normal items equipment - - item:Initialize() - end + -- + -- Once all the scripts are loaded we can set up the baseclass + -- - we have to wait until they're all setup because load order + -- could cause some entities to load before their bases! + -- + for k in pairs(ItemList) do + local newTable = items.Get(k) + ItemList[k] = newTable + + baseclass.Set(k, newTable) + end + + local isSqlTableCreated = SERVER + and database.Register( + ShopEditor.sqlItemsName, + ShopEditor.accessName, + ShopEditor.savingKeys, + TTT2_DATABASE_ACCESS_ANY + ) + + for _, item in pairs(ItemList) do + InitDefaultEquipment(item) + ShopEditor.InitDefaultData(item) -- initialize the default data + local name = GetEquipmentFileName(WEPS.GetClass(item)) + + if isSqlTableCreated then + database.SetDefaultValuesFromItem(ShopEditor.accessName, name, item) + local databaseExists, itemTable = database.GetValue(ShopEditor.accessName, name) + if databaseExists then + table.Merge(item, itemTable) + end + AddCallbacks(name, item) + elseif CLIENT then + database.GetValue(ShopEditor.accessName, name, nil, function(databaseExists, itemTable) + if databaseExists then + table.Merge(item, itemTable) + AddCallbacks(name, item) + end + end) + end + + CreateEquipment(item) -- init items + + item.CanBuy = {} -- reset normal items equipment + + item:Initialize() + end end --- @@ -125,35 +162,43 @@ end -- @return table returns the modified retTbl or the new item table -- @realm shared function items.Get(name, retTbl) - local Stored = items.GetStored(name) - if not Stored then return end - - -- Create/copy a new table - local retval = retTbl or {} - - for k, v in pairs(Stored) do - if istable(v) then - retval[k] = table.Copy(v) - else - retval[k] = v - end - end - - retval.Base = retval.Base or "item_base" - - -- If we're not derived from ourselves (a base item) - -- then derive from our 'Base' item. - if retval.Base ~= name then - local base = items.Get(retval.Base) - - if not base then - Msg("ERROR: Trying to derive item " .. tostring(name) .. " from non existant item " .. tostring(retval.Base) .. "!\n") - else - retval = TableInherit(retval, base) - end - end - - return retval + local Stored = items.GetStored(name) + if not Stored then + return + end + + -- Create/copy a new table + local retval = retTbl or {} + + for k, v in pairs(Stored) do + if istable(v) then + retval[k] = table.Copy(v) + else + retval[k] = v + end + end + + retval.Base = retval.Base or "item_base" + + -- If we're not derived from ourselves (a base item) + -- then derive from our 'Base' item. + if retval.Base ~= name then + local base = items.Get(retval.Base) + + if not base then + ErrorNoHaltWithStack( + "ERROR: Trying to derive item " + .. tostring(name) + .. " from non existant item " + .. tostring(retval.Base) + .. "!\n" + ) + else + retval = TableInherit(retval, base) + end + end + + return retval end --- @@ -162,22 +207,21 @@ end -- @return table returns the real item table -- @realm shared function items.GetStored(name) - return ItemList[name] + return ItemList[name] end - --- -- Get a list of all the registered items -- @return table all registered items -- @realm shared function items.GetList() - local result = {} + local result = {} - for _, v in pairs(ItemList) do - result[#result + 1] = v - end + for _, v in pairs(ItemList) do + result[#result + 1] = v + end - return result + return result end --- @@ -186,23 +230,23 @@ end -- @return boolean returns true if the inserted table is an item -- @realm shared function items.IsItem(val) - if not val then - return false - end - - local tmp = val - - if tonumber(val) then - for _, item in pairs(ItemList) do - if item.oldId and item.oldId == val then - return true - end - end - elseif not isstring(val) and (IsValid(val) or istable(val)) then - tmp = WEPS.GetClass(val) - end - - return items.GetStored(tmp) ~= nil + if not val then + return false + end + + local tmp = val + + if tonumber(val) then + for _, item in pairs(ItemList) do + if item.oldId and item.oldId == val then + return true + end + end + elseif not isstring(val) and (IsValid(val) or istable(val)) then + tmp = WEPS.GetClass(val) + end + + return items.GetStored(tmp) ~= nil end --- @@ -214,27 +258,27 @@ end -- @return boolean whether the input table has a specific item -- @realm shared function items.TableHasItem(tbl, val) - if not tbl or not val then - return false - end - - local tmp = val - - if not isstring(val) then - if tonumber(val) then -- still support the old item system - for _, item in pairs(ItemList) do - if item.oldId and item.oldId == val then - tmp = item.id - - break - end - end - elseif IsValid(val) or istable(val) then - tmp = WEPS.GetClass(val) - end - end - - return table.HasValue(tbl, tmp) + if not tbl or not val then + return false + end + + local tmp = val + + if not isstring(val) then + if tonumber(val) then -- still support the old item system + for _, item in pairs(ItemList) do + if item.oldId and item.oldId == val then + tmp = item.id + + break + end + end + elseif IsValid(val) or istable(val) then + tmp = WEPS.GetClass(val) + end + end + + return table.HasValue(tbl, tmp) end --- @@ -243,18 +287,18 @@ end -- @return table role items table -- @realm shared function items.GetRoleItems(subrole) - local itms = items.GetList() - local tbl = {} + local itms = items.GetList() + local tbl = {} - for i = 1, #itms do - local item = itms[i] + for i = 1, #itms do + local item = itms[i] - if item and item.CanBuy and table.HasValue(item.CanBuy, subrole) then - tbl[#tbl + 1] = item - end - end + if item and item.CanBuy and table.HasValue(item.CanBuy, subrole) then + tbl[#tbl + 1] = item + end + end - return tbl + return tbl end --- @@ -263,23 +307,25 @@ end -- @param string|number id item id / name -- @realm shared function items.GetRoleItem(subrole, id) - if tonumber(id) then - for _, item in pairs(ItemList) do - if item.oldId and item.oldId == id then - id = item.id + if tonumber(id) then + for _, item in pairs(ItemList) do + if item.oldId and item.oldId == id then + id = item.id - break - end - end + break + end + end - if tonumber(id) then return end - end + if tonumber(id) then + return + end + end - local item = items.GetStored(id) + local item = items.GetStored(id) - if item and item.CanBuy and table.HasValue(item.CanBuy, subrole) then - return item - end + if item and item.CanBuy and table.HasValue(item.CanBuy, subrole) then + return item + end end --- @@ -287,53 +333,64 @@ end -- @note This should be called after all entites have been loaded eg after InitPostEntity. -- @realm shared function items.MigrateLegacyItems() - for subrole, tbl in pairs(EquipmentItems or {}) do - for i = 1, #tbl do - local v = tbl[i] - - if v.avoidTTT2 then continue end - - local name = v.ClassName or v.name or WEPS.GetClass(v) - - if not name then continue end - - local item = items.GetStored(GetEquipmentFileName(name)) - - if not item then - local ITEMDATA = table.Copy(v) - ITEMDATA.oldId = v.id - ITEMDATA.id = name - ITEMDATA.EquipMenuData = v.EquipMenuData or { - type = v.type, - name = v.name, - desc = v.desc - } - ITEMDATA.type = nil - ITEMDATA.desc = nil - ITEMDATA.name = name - ITEMDATA.material = v.material - ITEMDATA.CanBuy = { [subrole] = subrole } - ITEMDATA.limited = v.limited or v.LimitedStock or true - - -- reset this old hud bool - if ITEMDATA.hud == true then - ITEMDATA.oldHud = true - ITEMDATA.hud = nil - end - - -- set the converted indicator - ITEMDATA.converted = true - - -- don't add icon and desc to the search panel if it's not intended - ITEMDATA.noCorpseSearch = ITEMDATA.noCorpseSearch or true - - items.Register(ITEMDATA, GetEquipmentFileName(name)) - - print("[TTT2][INFO] Automatically converted legacy item: ", name, ITEMDATA.oldId) - else - item.CanBuy = item.CanBuy or {} - item.CanBuy[subrole] = subrole - end - end - end + for subrole, tbl in pairs(EquipmentItems or {}) do + for i = 1, #tbl do + local v = tbl[i] + + if v.avoidTTT2 then + continue + end + + local name = v.ClassName or v.name or WEPS.GetClass(v) + + if not name then + continue + end + + local item = items.GetStored(GetEquipmentFileName(name)) + + if not item then + local ITEMDATA = table.Copy(v) + ITEMDATA.oldId = v.id + ITEMDATA.id = name + ITEMDATA.EquipMenuData = v.EquipMenuData + or { + type = v.type, + name = v.name, + desc = v.desc, + } + ITEMDATA.type = nil + ITEMDATA.desc = nil + ITEMDATA.name = name + ITEMDATA.material = v.material + ITEMDATA.CanBuy = { [subrole] = subrole } + ITEMDATA.limited = v.limited or v.LimitedStock or true + + -- reset this old hud bool + if ITEMDATA.hud == true then + ITEMDATA.oldHud = true + ITEMDATA.hud = nil + end + + -- set the converted indicator + ITEMDATA.converted = true + + -- don't add icon and desc to the search panel if it's not intended + ITEMDATA.noCorpseSearch = ITEMDATA.noCorpseSearch or true + + items.Register(ITEMDATA, GetEquipmentFileName(name)) + + Dev( + 1, + "[TTT2][INFO] Automatically converted legacy item:\t" + .. name + .. "\t" + .. ITEMDATA.oldId + ) + else + item.CanBuy = item.CanBuy or {} + item.CanBuy[subrole] = subrole + end + end + end end diff --git a/lua/ttt2/libraries/keyhelp.lua b/lua/ttt2/libraries/keyhelp.lua new file mode 100644 index 000000000..5bbd8c945 --- /dev/null +++ b/lua/ttt2/libraries/keyhelp.lua @@ -0,0 +1,720 @@ +if SERVER then + AddCSLuaFile() + + return +end + +KEYHELP_INTERNAL = 1 +KEYHELP_CORE = 2 +KEYHELP_EXTRA = 3 +KEYHELP_EQUIPMENT = 4 +KEYHELP_SCOREBOARD = 5 + +local Key = Key +local stringUpper = string.upper +local inputGetKeyName = input.GetKeyName +local bindFind = bind.Find + +local offsetCenter = 230 +local height = 48 +local width = 18 +local padding = 5 +local thicknessLine = 2 + +local heightScaled = 0 +local widthScaled = 0 +local paddingScaled = 0 +local thicknessLineScaled = 0 + +local colorBox = Color(0, 0, 0, 100) + +local materialSettings = Material("vgui/ttt/hudhelp/settings") +local materialMute = Material("vgui/ttt/hudhelp/mute") +local materialShoppingRole = Material("vgui/ttt/hudhelp/shopping_role") +local materialPosessing = Material("vgui/ttt/hudhelp/possessing") +local materialPlayer = Material("vgui/ttt/hudhelp/player") +local materialPlayerPrev = Material("vgui/ttt/hudhelp/player_prev") +local materialPlayerNext = Material("vgui/ttt/hudhelp/player_next") +local materialPlayerRandom = Material("vgui/ttt/hudhelp/player_random") +local materialPropJump = Material("vgui/ttt/hudhelp/prop_jump") +local materialPropLeft = Material("vgui/ttt/hudhelp/prop_left") +local materialPropRight = Material("vgui/ttt/hudhelp/prop_right") +local materialPropFront = Material("vgui/ttt/hudhelp/prop_front") +local materialPropBack = Material("vgui/ttt/hudhelp/prop_back") +local materialPropDash = Material("vgui/ttt/hudhelp/prop_dash") +local materialLeaveTarget = Material("vgui/ttt/hudhelp/leave_target") +local materialVoiceGlobal = Material("vgui/ttt/hudhelp/voice_global") +local materialVoiceTeam = Material("vgui/ttt/hudhelp/voice_team") +local materialChatGlobal = Material("vgui/ttt/hudhelp/chat_global") +local materialChatTeam = Material("vgui/ttt/hudhelp/chat_team") +local materialFlashlight = Material("vgui/ttt/hudhelp/flashlight") +local materialWeaponDrop = Material("vgui/ttt/hudhelp/weapon_drop") +local materialAmmoDrop = Material("vgui/ttt/hudhelp/ammo_drop") +local materialQuickchat = Material("vgui/ttt/hudhelp/quickchat") +local materialShowmore = Material("vgui/ttt/hudhelp/showmore") +local materialPointer = Material("vgui/ttt/hudhelp/pointer") +local materialThirdPerson = Material("vgui/ttt/hudhelp/third_person") +local materialSave = Material("vgui/ttt/hudhelp/save") + +--- +-- @realm client +-- stylua: ignore +local cvEnableCore = CreateConVar("ttt2_keyhelp_show_core", "1", FCVAR_ARCHIVE) + +--- +-- @realm client +-- stylua: ignore +local cvEnableExtra = CreateConVar("ttt2_keyhelp_show_extra", "0", FCVAR_ARCHIVE) + +--- +-- @realm client +-- stylua: ignore +local cvEnableEquipment = CreateConVar("ttt2_keyhelp_show_equipment", "1", FCVAR_ARCHIVE) + +--- +-- @realm client +-- stylua: ignore +local cvEnableBoxBlur = CreateConVar("ttt2_hud_enable_box_blur", "1", FCVAR_ARCHIVE) + +--- +-- @realm client +-- stylua: ignore +local cvEnableDescription = CreateConVar("ttt2_hud_enable_description", "1", FCVAR_ARCHIVE) + +keyhelp = keyhelp or {} +keyhelp.keyHelpers = {} + +local function DrawKeyContent(x, y, keyString, iconMaterial, bindingName, scoreboardShown, scale) + local wKeyString = draw.GetTextSize(keyString, "weapon_hud_help_key", scale) + local wBox = math.max(widthScaled, wKeyString) + 2 * paddingScaled + local xIcon = x + 0.5 * (wBox - widthScaled) + local yIcon = y + paddingScaled + thicknessLineScaled + local xKeyString = x + math.floor(0.5 * wBox) + local yKeyString = yIcon + widthScaled + paddingScaled + + if cvEnableBoxBlur:GetBool() then + draw.BlurredBox(x, y, wBox, heightScaled + paddingScaled) + draw.Box(x, y, wBox, heightScaled + paddingScaled, colorBox) -- background color + draw.Box(x, y, wBox, math.Round(0.5 * thicknessLineScaled), colorBox) -- top line shadow + draw.Box(x, y, wBox, thicknessLineScaled, colorBox) -- top line shadow + draw.Box(x, y - thicknessLineScaled, wBox, thicknessLineScaled, COLOR_WHITE) -- white top line + end + + draw.FilteredShadowedTexture( + xIcon, + yIcon, + widthScaled, + widthScaled, + iconMaterial, + 255, + COLOR_WHITE, + scale + ) + draw.AdvancedText( + keyString, + "weapon_hud_help_key", + xKeyString, + yKeyString, + COLOR_WHITE, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_TOP, + true, + scale + ) + + if scoreboardShown and cvEnableDescription:GetBool() then + draw.AdvancedText( + LANG.TryTranslation(bindingName), + "weapon_hud_help", + xKeyString, + y - 3 * paddingScaled, + COLOR_WHITE, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + true, + scale, + -45 + ) + end + + return wBox +end + +local function DrawKey(client, xBase, yBase, keyHelper, scoreboardShown, scale) + if not isfunction(keyHelper.callback) or not keyHelper.callback(client) then + return + end + + -- handles both internal GMod bindings and TTT2 bindings + local key = Key(keyHelper.binding) or inputGetKeyName(bindFind(keyHelper.binding)) + + if not key then + return + end + + return xBase + + paddingScaled + + DrawKeyContent( + xBase, + yBase, + stringUpper(key), + keyHelper.iconMaterial, + keyHelper.bindingName, + scoreboardShown, + scale + ) +end + +--- +-- Registers a key helper that will be shown in the key helper area. +-- @note Addons will probaly set the binding type `KEYHELP_EQUIPMENT`. +-- @param string binding The binding that is used here, it is either the gmod or TTT2 binding name +-- @param Material iconMaterial The material for the icon used in the UI +-- @param number bindingType The category in which this binding is sorted +-- @param string bindingName The name for the binding, should be translateable +-- @param function callback The callback function that checks if this binding should be rendered on screen +-- @realm client +function keyhelp.RegisterKeyHelper(binding, iconMaterial, bindingType, bindingName, callback) + keyhelp.keyHelpers[bindingType] = keyhelp.keyHelpers[bindingType] or {} + + keyhelp.keyHelpers[bindingType][#keyhelp.keyHelpers[bindingType] + 1] = { + binding = binding, + iconMaterial = iconMaterial, + bindingType = bindingType, + bindingName = bindingName, + callback = callback, + } +end + +--- +-- Draws the keyhelpers to the screen, is called from within @{GM:HUDPaint}. +-- @internal +-- @realm client +function keyhelp.Draw() + local client = LocalPlayer() + local scoreboardShown = GAMEMODE.ShowScoreboard + + local scale = appearance.GetGlobalScale() + + local xBase = 0.5 * ScrW() + offsetCenter * scale + local yBase = ScrH() - height * scale + + heightScaled = height * scale + widthScaled = width * scale + paddingScaled = padding * scale + thicknessLineScaled = math.Round(thicknessLine * scale) + + if cvEnableCore:GetBool() or scoreboardShown then + for i = 1, #keyhelp.keyHelpers[KEYHELP_INTERNAL] do + xBase = DrawKey( + client, + xBase, + yBase, + keyhelp.keyHelpers[KEYHELP_INTERNAL][i], + scoreboardShown, + scale + ) or xBase + end + end + + if not util.EditingModeActive(client) then + if keyhelp.keyHelpers[KEYHELP_CORE] and (cvEnableCore:GetBool() or scoreboardShown) then + for i = 1, #keyhelp.keyHelpers[KEYHELP_CORE] do + xBase = DrawKey( + client, + xBase, + yBase, + keyhelp.keyHelpers[KEYHELP_CORE][i], + scoreboardShown, + scale + ) or xBase + end + end + + if + keyhelp.keyHelpers[KEYHELP_EQUIPMENT] + and (cvEnableEquipment:GetBool() or scoreboardShown) + then + for i = 1, #keyhelp.keyHelpers[KEYHELP_EQUIPMENT] do + xBase = DrawKey( + client, + xBase, + yBase, + keyhelp.keyHelpers[KEYHELP_EQUIPMENT][i], + scoreboardShown, + scale + ) or xBase + end + end + + if keyhelp.keyHelpers[KEYHELP_EXTRA] and (cvEnableExtra:GetBool() or scoreboardShown) then + for i = 1, #keyhelp.keyHelpers[KEYHELP_EXTRA] do + xBase = DrawKey( + client, + xBase, + yBase, + keyhelp.keyHelpers[KEYHELP_EXTRA][i], + scoreboardShown, + scale + ) or xBase + end + end + end + + -- if anyone of them is disabled, but not all, the show more option is shown + local enbCount = cvEnableCore:GetInt() + cvEnableEquipment:GetInt() + cvEnableExtra:GetInt() + + if not scoreboardShown and enbCount > 0 and enbCount < 3 then + xBase = DrawKey( + client, + xBase, + yBase, + keyhelp.keyHelpers[KEYHELP_SCOREBOARD][1], + scoreboardShown, + scale + ) or xBase + end +end + +--- +-- Initializes the basic keys that should be registered. Called from within @{GM:Initialize} and @{GM:OnReloaded}. +-- @internal +-- @realm client +function keyhelp.InitializeBasicKeys() + -- core bindings that should be visible be default + keyhelp.RegisterKeyHelper( + "gm_showhelp", + materialSettings, + KEYHELP_INTERNAL, + "label_keyhelper_help", + function(client) + if util.EditingModeActive(client) then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "gm_showhelp", + materialSave, + KEYHELP_INTERNAL, + "label_keyhelper_save_exit", + function(client) + if not util.EditingModeActive(client) then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "gm_showteam", + materialMute, + KEYHELP_CORE, + "label_keyhelper_mutespec", + function(client) + if not client:IsSpec() then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+menu_context", + materialShoppingRole, + KEYHELP_CORE, + "label_keyhelper_shop", + function(client) + if client:IsSpec() or not client:IsShopper() then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+duck", + materialPointer, + KEYHELP_CORE, + "label_keyhelper_show_pointer", + function(client) + if not client:IsSpec() or IsValid(client:GetObserverTarget()) then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+use", + materialPosessing, + KEYHELP_CORE, + "label_keyhelper_possess_focus_entity", + function(client) + if not client:IsSpec() or IsValid(client:GetObserverTarget()) then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+use", + materialPlayer, + KEYHELP_CORE, + "label_keyhelper_spec_focus_player", + function(client) + if not client:IsSpec() or IsValid(client:GetObserverTarget()) then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+attack", + materialPlayerPrev, + KEYHELP_CORE, + "label_keyhelper_spec_previous_player", + function(client) + if not client:IsSpec() then + return + end + + local target = client:GetObserverTarget() + + if not IsValid(target) or not target:IsPlayer() then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+attack2", + materialPlayerNext, + KEYHELP_CORE, + "label_keyhelper_spec_next_player", + function(client) + if not client:IsSpec() then + return + end + + local target = client:GetObserverTarget() + + if not IsValid(target) or not target:IsPlayer() then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+reload", + materialThirdPerson, + KEYHELP_CORE, + "label_keyhelper_spec_third_person", + function(client) + if not client:IsSpec() then + return + end + + local target = client:GetObserverTarget() + + if not IsValid(target) or not target:IsPlayer() then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+attack2", + materialPlayerRandom, + KEYHELP_CORE, + "label_keyhelper_spec_player", + function(client) + if not client:IsSpec() or IsValid(client:GetObserverTarget()) then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+jump", + materialPropJump, + KEYHELP_CORE, + "label_keyhelper_possession_jump", + function(client) + if not client:IsSpec() then + return + end + + local target = client:GetObserverTarget() + + if + not IsValid(target) + or target:IsPlayer() + or target:GetNWEntity("spec_owner", nil) ~= client + then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+moveleft", + materialPropLeft, + KEYHELP_CORE, + "label_keyhelper_possession_left", + function(client) + if not client:IsSpec() then + return + end + + local target = client:GetObserverTarget() + + if + not IsValid(target) + or target:IsPlayer() + or target:GetNWEntity("spec_owner", nil) ~= client + then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+moveright", + materialPropRight, + KEYHELP_CORE, + "label_keyhelper_possession_right", + function(client) + if not client:IsSpec() then + return + end + + local target = client:GetObserverTarget() + + if + not IsValid(target) + or target:IsPlayer() + or target:GetNWEntity("spec_owner", nil) ~= client + then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+forward", + materialPropFront, + KEYHELP_CORE, + "label_keyhelper_possession_forward", + function(client) + if not client:IsSpec() then + return + end + + local target = client:GetObserverTarget() + + if + not IsValid(target) + or target:IsPlayer() + or target:GetNWEntity("spec_owner") ~= client + then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+back", + materialPropBack, + KEYHELP_CORE, + "label_keyhelper_possession_backward", + function(client) + if not client:IsSpec() then + return + end + + local target = client:GetObserverTarget() + + if + not IsValid(target) + or target:IsPlayer() + or target:GetNWEntity("spec_owner") ~= client + then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+speed", + materialPropDash, + KEYHELP_CORE, + "label_keyhelper_possession_dash", + function(client) + if not client:IsSpec() then + return + end + + local target = client:GetObserverTarget() + + if + not IsValid(target) + or target:IsPlayer() + or target:GetNWEntity("spec_owner", nil) ~= client + then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+duck", + materialLeaveTarget, + KEYHELP_CORE, + "label_keyhelper_free_roam", + function(client) + if not client:IsSpec() or not IsValid(client:GetObserverTarget()) then + return + end + + return true + end + ) + + -- extra bindings that are not that important but are there as well + keyhelp.RegisterKeyHelper( + "impulse 100", + materialFlashlight, + KEYHELP_EXTRA, + "label_keyhelper_flashlight", + function(client) + if client:IsSpec() then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+menu", + materialWeaponDrop, + KEYHELP_EXTRA, + "label_keyhelper_weapon_drop", + function(client) + if client:IsSpec() then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "gmod_undo", + materialAmmoDrop, + KEYHELP_EXTRA, + "label_keyhelper_ammo_drop", + function(client) + if client:IsSpec() then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "+zoom", + materialQuickchat, + KEYHELP_EXTRA, + "label_keyhelper_quickchat", + function(client) + if client:IsSpec() then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "ttt2_voice", + materialVoiceGlobal, + KEYHELP_EXTRA, + "label_keyhelper_voice_global", + function(client) + if not VOICE.CanEnable() or not GetGlobalBool("sv_voiceenable", true) then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "ttt2_voice_team", + materialVoiceTeam, + KEYHELP_EXTRA, + "label_keyhelper_voice_team", + function(client) + if not VOICE.CanTeamEnable() or not GetGlobalBool("sv_voiceenable", true) then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "messagemode", + materialChatGlobal, + KEYHELP_EXTRA, + "label_keyhelper_chat_global", + function(client) + if client:GetSubRoleData().disabledGeneralChat then + return + end + + return true + end + ) + keyhelp.RegisterKeyHelper( + "messagemode2", + materialChatTeam, + KEYHELP_EXTRA, + "label_keyhelper_chat_team", + function(client) + if client:IsSpec() then + return + end + + local clientRoleData = client:GetSubRoleData() + + if clientRoleData.unknownTeam or clientRoleData.disabledTeamChat then + return + end + + return true + end + ) + + -- internal bindings, there should only be this one + keyhelp.RegisterKeyHelper( + "+showscores", + materialShowmore, + KEYHELP_SCOREBOARD, + "label_keyhelper_show_all", + function(client) + return true + end + ) +end diff --git a/lua/ttt2/libraries/map.lua b/lua/ttt2/libraries/map.lua index 9582436a8..86db75804 100644 --- a/lua/ttt2/libraries/map.lua +++ b/lua/ttt2/libraries/map.lua @@ -4,7 +4,7 @@ -- @module map if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local scripedEntsRegister = scripted_ents.Register @@ -16,165 +16,165 @@ local pairs = pairs local mapType = nil local fallbackWepSpawnEnts = { - -- CS:S - "hostage_entity", - -- TF2 - "item_ammopack_full", - "item_ammopack_medium", - "item_ammopack_small", - "item_healthkit_full", - "item_healthkit_medium", - "item_healthkit_small", - "item_teamflag", - "game_intro_viewpoint", - "info_observer_point", - "team_control_point", - "team_control_point_master", - "team_control_point_round", - -- ZM - "item_ammo_revolver" + -- CS:S + "hostage_entity", + -- TF2 + "item_ammopack_full", + "item_ammopack_medium", + "item_ammopack_small", + "item_healthkit_full", + "item_healthkit_medium", + "item_healthkit_small", + "item_teamflag", + "game_intro_viewpoint", + "info_observer_point", + "team_control_point", + "team_control_point_master", + "team_control_point_round", + -- ZM + "item_ammo_revolver", } local ttt_weapon_spawns = { - ["ttt_random_weapon"] = WEAPON_TYPE_RANDOM, - ["weapon_zm_shotgun"] = WEAPON_TYPE_SHOTGUN, - ["weapon_zm_pistol"] = WEAPON_TYPE_PISTOL, - ["weapon_zm_rifle"] = WEAPON_TYPE_SNIPER, - ["weapon_zm_molotov"] = WEAPON_TYPE_NADE, - ["weapon_ttt_smokegrenade"] = WEAPON_TYPE_NADE, - ["weapon_ttt_confgrenade"] = WEAPON_TYPE_NADE, - ["weapon_zm_mac10"] = WEAPON_TYPE_HEAVY, - ["weapon_zm_revolver"] = WEAPON_TYPE_PISTOL, - ["weapon_zm_sledge"] = WEAPON_TYPE_HEAVY, - ["weapon_ttt_m16"] = WEAPON_TYPE_HEAVY, - ["weapon_ttt_glock"] = WEAPON_TYPE_PISTOL + ["ttt_random_weapon"] = WEAPON_TYPE_RANDOM, + ["weapon_zm_shotgun"] = WEAPON_TYPE_SHOTGUN, + ["weapon_zm_pistol"] = WEAPON_TYPE_PISTOL, + ["weapon_zm_rifle"] = WEAPON_TYPE_SNIPER, + ["weapon_zm_molotov"] = WEAPON_TYPE_NADE, + ["weapon_ttt_smokegrenade"] = WEAPON_TYPE_NADE, + ["weapon_ttt_confgrenade"] = WEAPON_TYPE_NADE, + ["weapon_zm_mac10"] = WEAPON_TYPE_HEAVY, + ["weapon_zm_revolver"] = WEAPON_TYPE_PISTOL, + ["weapon_zm_sledge"] = WEAPON_TYPE_HEAVY, + ["weapon_ttt_m16"] = WEAPON_TYPE_HEAVY, + ["weapon_ttt_glock"] = WEAPON_TYPE_PISTOL, } local ttt_ammo_spawns = { - ["ttt_random_ammo"] = AMMO_TYPE_RANDOM, - ["item_ammo_pistol_ttt"] = AMMO_TYPE_PISTOL, - ["item_ammo_smg1_ttt"] = AMMO_TYPE_MAC10, - ["item_ammo_357_ttt"] = AMMO_TYPE_RIFLE, - ["item_box_buckshot_ttt"] = AMMO_TYPE_SHOTGUN, - ["item_ammo_revolver_ttt"] = AMMO_TYPE_DEAGLE + ["ttt_random_ammo"] = AMMO_TYPE_RANDOM, + ["item_ammo_pistol_ttt"] = AMMO_TYPE_PISTOL, + ["item_ammo_smg1_ttt"] = AMMO_TYPE_MAC10, + ["item_ammo_357_ttt"] = AMMO_TYPE_RIFLE, + ["item_box_buckshot_ttt"] = AMMO_TYPE_SHOTGUN, + ["item_ammo_revolver_ttt"] = AMMO_TYPE_DEAGLE, } local hl2_weapon_spawns = { - ["weapon_smg1"] = WEAPON_TYPE_HEAVY, - ["weapon_shotgun"] = WEAPON_TYPE_SHOTGUN, - ["weapon_ar2"] = WEAPON_TYPE_HEAVY, - ["weapon_357"] = WEAPON_TYPE_SNIPER, - ["weapon_crossbow"] = WEAPON_TYPE_PISTOL, - ["weapon_rpg"] = WEAPON_TYPE_HEAVY, - ["weapon_frag"] = WEAPON_TYPE_PISTOL, - ["weapon_crowbar"] = WEAPON_TYPE_NADE, - ["item_ammo_smg1_grenade"] = WEAPON_TYPE_PISTOL, - ["item_healthkit"] = WEAPON_TYPE_SHOTGUN, - ["item_suitcharger"] = WEAPON_TYPE_HEAVY, - ["item_ammo_ar2_altfire"] = WEAPON_TYPE_HEAVY, - ["item_healthvial"] = WEAPON_TYPE_NADE, - ["item_ammo_crate"] = WEAPON_TYPE_NADE + ["weapon_smg1"] = WEAPON_TYPE_HEAVY, + ["weapon_shotgun"] = WEAPON_TYPE_SHOTGUN, + ["weapon_ar2"] = WEAPON_TYPE_HEAVY, + ["weapon_357"] = WEAPON_TYPE_SNIPER, + ["weapon_crossbow"] = WEAPON_TYPE_PISTOL, + ["weapon_rpg"] = WEAPON_TYPE_HEAVY, + ["weapon_frag"] = WEAPON_TYPE_PISTOL, + ["weapon_crowbar"] = WEAPON_TYPE_NADE, + ["item_ammo_smg1_grenade"] = WEAPON_TYPE_PISTOL, + ["item_healthkit"] = WEAPON_TYPE_SHOTGUN, + ["item_suitcharger"] = WEAPON_TYPE_HEAVY, + ["item_ammo_ar2_altfire"] = WEAPON_TYPE_HEAVY, + ["item_healthvial"] = WEAPON_TYPE_NADE, + ["item_ammo_crate"] = WEAPON_TYPE_NADE, } local hl2_ammo_spawns = { - ["weapon_slam"] = AMMO_TYPE_PISTOL, - ["item_ammo_pistol"] = AMMO_TYPE_PISTOL, - ["item_box_buckshot"] = AMMO_TYPE_SHOTGUN, - ["item_ammo_smg1"] = AMMO_TYPE_MAC10, - ["item_ammo_357"] = AMMO_TYPE_RIFLE, - ["item_ammo_357_large"] = AMMO_TYPE_RIFLE, - ["item_ammo_revolver"] = AMMO_TYPE_DEAGLE, -- zm - ["item_ammo_ar2"] = AMMO_TYPE_PISTOL, - ["item_ammo_ar2_large"] = AMMO_TYPE_MAC10, - ["item_battery"] = AMMO_TYPE_RIFLE, - ["item_rpg_round"] = AMMO_TYPE_RIFLE, - ["item_ammo_crossbow"] = AMMO_TYPE_SHOTGUN, - ["item_healthcharger"] = AMMO_TYPE_DEAGLE, - ["item_item_crate"] = AMMO_TYPE_RANDOM + ["weapon_slam"] = AMMO_TYPE_PISTOL, + ["item_ammo_pistol"] = AMMO_TYPE_PISTOL, + ["item_box_buckshot"] = AMMO_TYPE_SHOTGUN, + ["item_ammo_smg1"] = AMMO_TYPE_MAC10, + ["item_ammo_357"] = AMMO_TYPE_RIFLE, + ["item_ammo_357_large"] = AMMO_TYPE_RIFLE, + ["item_ammo_revolver"] = AMMO_TYPE_DEAGLE, -- zm + ["item_ammo_ar2"] = AMMO_TYPE_PISTOL, + ["item_ammo_ar2_large"] = AMMO_TYPE_MAC10, + ["item_battery"] = AMMO_TYPE_RIFLE, + ["item_rpg_round"] = AMMO_TYPE_RIFLE, + ["item_ammo_crossbow"] = AMMO_TYPE_SHOTGUN, + ["item_healthcharger"] = AMMO_TYPE_DEAGLE, + ["item_item_crate"] = AMMO_TYPE_RANDOM, } local css_weapon_spawns = { - ["info_player_terrorist"] = WEAPON_TYPE_RANDOM, - ["info_player_counterterrorist"] = WEAPON_TYPE_RANDOM, - ["hostage_entity"] = WEAPON_TYPE_RANDOM + ["info_player_terrorist"] = WEAPON_TYPE_RANDOM, + ["info_player_counterterrorist"] = WEAPON_TYPE_RANDOM, + ["hostage_entity"] = WEAPON_TYPE_RANDOM, } local tf2_weapon_spawns = { - ["info_player_teamspawn"] = WEAPON_TYPE_RANDOM, - ["team_control_point"] = WEAPON_TYPE_RANDOM, - ["team_control_point_master"] = WEAPON_TYPE_RANDOM, - ["team_control_point_round"] = WEAPON_TYPE_RANDOM, - ["item_ammopack_full"] = WEAPON_TYPE_RANDOM, - ["item_ammopack_medium"] = WEAPON_TYPE_RANDOM, - ["item_ammopack_small"] = WEAPON_TYPE_RANDOM, - ["item_healthkit_full"] = WEAPON_TYPE_RANDOM, - ["item_healthkit_medium"] = WEAPON_TYPE_RANDOM, - ["item_healthkit_small"] = WEAPON_TYPE_RANDOM, - ["item_teamflag"] = WEAPON_TYPE_RANDOM, - ["game_intro_viewpoint"] = WEAPON_TYPE_RANDOM, - ["info_observer_point"] = WEAPON_TYPE_RANDOM + ["info_player_teamspawn"] = WEAPON_TYPE_RANDOM, + ["team_control_point"] = WEAPON_TYPE_RANDOM, + ["team_control_point_master"] = WEAPON_TYPE_RANDOM, + ["team_control_point_round"] = WEAPON_TYPE_RANDOM, + ["item_ammopack_full"] = WEAPON_TYPE_RANDOM, + ["item_ammopack_medium"] = WEAPON_TYPE_RANDOM, + ["item_ammopack_small"] = WEAPON_TYPE_RANDOM, + ["item_healthkit_full"] = WEAPON_TYPE_RANDOM, + ["item_healthkit_medium"] = WEAPON_TYPE_RANDOM, + ["item_healthkit_small"] = WEAPON_TYPE_RANDOM, + ["item_teamflag"] = WEAPON_TYPE_RANDOM, + ["game_intro_viewpoint"] = WEAPON_TYPE_RANDOM, + ["info_observer_point"] = WEAPON_TYPE_RANDOM, } local ttt_player_spawns = { - ["info_player_deathmatch"] = PLAYER_TYPE_RANDOM, - ["info_player_combine"] = PLAYER_TYPE_RANDOM, - ["info_player_rebel"] = PLAYER_TYPE_RANDOM, - ["info_player_counterterrorist"] = PLAYER_TYPE_RANDOM, - ["info_player_terrorist"] = PLAYER_TYPE_RANDOM, - ["info_player_axis"] = PLAYER_TYPE_RANDOM, - ["info_player_allies"] = PLAYER_TYPE_RANDOM, - ["gmod_player_start"] = PLAYER_TYPE_RANDOM, - ["info_player_teamspawn"] = PLAYER_TYPE_RANDOM + ["info_player_deathmatch"] = PLAYER_TYPE_RANDOM, + ["info_player_combine"] = PLAYER_TYPE_RANDOM, + ["info_player_rebel"] = PLAYER_TYPE_RANDOM, + ["info_player_counterterrorist"] = PLAYER_TYPE_RANDOM, + ["info_player_terrorist"] = PLAYER_TYPE_RANDOM, + ["info_player_axis"] = PLAYER_TYPE_RANDOM, + ["info_player_allies"] = PLAYER_TYPE_RANDOM, + ["gmod_player_start"] = PLAYER_TYPE_RANDOM, + ["info_player_teamspawn"] = PLAYER_TYPE_RANDOM, } local ttt_player_spawns_fallback = { - ["info_player_start"] = PLAYER_TYPE_RANDOM + ["info_player_start"] = PLAYER_TYPE_RANDOM, } local function FindSpawnEntities(spawns, classes) - local amount = 0 + local amount = 0 - for class, entType in pairs(classes) do - spawns[entType] = spawns[entType] or {} + for class, entType in pairs(classes) do + spawns[entType] = spawns[entType] or {} - local spawnsFound = ents.FindByClass(class) + local spawnsFound = ents.FindByClass(class) - tableAdd(spawns[entType], spawnsFound) + tableAdd(spawns[entType], spawnsFound) - amount = amount + #spawnsFound - end + amount = amount + #spawnsFound + end - return amount + return amount end local function DatafySpawnTable(spawnTable) - local spawnDataTable = {} + local spawnDataTable = {} - for entType, spawns in pairs(spawnTable) do - for i = 1, #spawns do - local spn = spawns[i] + for entType, spawns in pairs(spawnTable) do + for i = 1, #spawns do + local spn = spawns[i] - spawnDataTable[entType] = spawnDataTable[entType] or {} + spawnDataTable[entType] = spawnDataTable[entType] or {} - spawnDataTable[entType][#spawnDataTable[entType] + 1] = { - pos = spn:GetPos(), - ang = spn:GetAngles(), - ammo = spn.autoAmmoAmount or 0 - } - end - end + spawnDataTable[entType][#spawnDataTable[entType] + 1] = { + pos = spn:GetPos(), + ang = spn:GetAngles(), + ammo = spn.autoAmmoAmount or 0, + } + end + end - return spawnDataTable + return spawnDataTable end local function AddData(spawnTable, entType, spawn) - spawnTable[entType] = spawnTable[entType] or {} + spawnTable[entType] = spawnTable[entType] or {} - spawnTable[entType][#spawnTable[entType] + 1] = { - pos = spawn.pos, - ang = spawn.ang, - ammo = spawn.ammo or 0 - } + spawnTable[entType][#spawnTable[entType] + 1] = { + pos = spawn.pos, + ang = spawn.ang, + ammo = spawn.ammo or 0, + } end map = map or {} @@ -190,12 +190,12 @@ MAP_TYPE_TEAMFORTRESS = 3 -- @internal -- @realm server function map.DummifyFallbackWeaponEnts() - for i = 1, #fallbackWepSpawnEnts do - scripedEntsRegister({ - Type = "point", - IsWeaponDummy = true - }, fallbackWepSpawnEnts[i], false) - end + for i = 1, #fallbackWepSpawnEnts do + scripedEntsRegister({ + Type = "point", + IsWeaponDummy = true, + }, fallbackWepSpawnEnts[i]) + end end -- automatically run the dummmify function @@ -208,22 +208,22 @@ map.DummifyFallbackWeaponEnts() -- @return[default=MAP_TYPE_TERRORTOWN] number Returns the map type of the currently active map -- @realm shared function map.GetMapGameType() - -- return cached map type if already cached - if mapType then - return mapType - end - - if #ents.FindByClass("info_player_counterterrorist") ~= 0 then - mapType = MAP_TYPE_COUNTERSTRIKE - elseif #ents.FindByClass("info_player_teamspawn") ~= 0 then - mapType = MAP_TYPE_TEAMFORTRESS - else - -- as a fallback use the terrortown map type since most maps are terrotown maps; - -- they are identified with the class 'info_player_deathmatch' - mapType = MAP_TYPE_TERRORTOWN - end - - return mapType + -- return cached map type if already cached + if mapType then + return mapType + end + + if #ents.FindByClass("info_player_counterterrorist") ~= 0 then + mapType = MAP_TYPE_COUNTERSTRIKE + elseif #ents.FindByClass("info_player_teamspawn") ~= 0 then + mapType = MAP_TYPE_TEAMFORTRESS + else + -- as a fallback use the terrortown map type since most maps are terrotown maps; + -- they are identified with the class 'info_player_deathmatch' + mapType = MAP_TYPE_TERRORTOWN + end + + return mapType end --- @@ -233,7 +233,7 @@ end -- @return boolean Returns true if it is a terrortown map -- @realm shared function map.IsTerrortownMap() - return map.GetMapGameType() == MAP_TYPE_TERRORTOWN + return map.GetMapGameType() == MAP_TYPE_TERRORTOWN end --- @@ -243,7 +243,7 @@ end -- @return boolean Returns true if it is a counter strike source map -- @realm shared function map.IsCounterStrikeMap() - return map.GetMapGameType() == MAP_TYPE_COUNTERSTRIKE + return map.GetMapGameType() == MAP_TYPE_COUNTERSTRIKE end --- @@ -253,7 +253,7 @@ end -- @return boolean Returns true if it is a team fortress 2 map -- @realm shared function map.IsTeamFortressMap() - return map.GetMapGameType() == MAP_TYPE_TEAMFORTRESS + return map.GetMapGameType() == MAP_TYPE_TEAMFORTRESS end --- @@ -265,18 +265,26 @@ end -- @return table A table with all the weapon spawn entities grouped by ent types -- @realm shared function map.GetWeaponSpawnEntities() - local spawns = {} + local spawns = {} - FindSpawnEntities(spawns, ttt_weapon_spawns) - FindSpawnEntities(spawns, hl2_weapon_spawns) + FindSpawnEntities(spawns, ttt_weapon_spawns) + FindSpawnEntities(spawns, hl2_weapon_spawns) - if map.IsCounterStrikeMap() then - FindSpawnEntities(spawns, css_weapon_spawns) - elseif map.IsTeamFortressMap() then - FindSpawnEntities(spawns, tf2_weapon_spawns) - end + local hook_weapon_spawns = {} - return spawns + -- @realm shared + -- stylua: ignore + hook.Run("TTT2MapRegisterWeaponSpawns", hook_weapon_spawns) + + FindSpawnEntities(spawns, hook_weapon_spawns) + + if map.IsCounterStrikeMap() then + FindSpawnEntities(spawns, css_weapon_spawns) + elseif map.IsTeamFortressMap() then + FindSpawnEntities(spawns, tf2_weapon_spawns) + end + + return spawns end --- @@ -284,12 +292,20 @@ end -- @return table A table with all the ammo spawn entities grouped by ent types -- @realm shared function map.GetAmmoSpawnEntities() - local spawns = {} + local spawns = {} + + FindSpawnEntities(spawns, ttt_ammo_spawns) + FindSpawnEntities(spawns, hl2_ammo_spawns) + + local hook_ammo_spawns = {} + + -- @realm shared + -- stylua: ignore + hook.Run("TTT2MapRegisterAmmoSpawns", hook_ammo_spawns) - FindSpawnEntities(spawns, ttt_ammo_spawns) - FindSpawnEntities(spawns, hl2_ammo_spawns) + FindSpawnEntities(spawns, hook_ammo_spawns) - return spawns + return spawns end --- @@ -297,13 +313,19 @@ end -- @return table A table with all the player spawn entities grouped by ent types -- @realm shared function map.GetPlayerSpawnEntities() - local spawns = {} + local spawns = {} + local hook_player_spawns = {} - if FindSpawnEntities(spawns, ttt_player_spawns) == 0 then - FindSpawnEntities(spawns, ttt_player_spawns_fallback) - end + -- @realm shared + -- stylua: ignore + hook.Run("TTT2MapRegisterPlayerSpawns", hook_player_spawns) - return spawns + FindSpawnEntities(spawns, hook_player_spawns) + if FindSpawnEntities(spawns, ttt_player_spawns) == 0 then + FindSpawnEntities(spawns, ttt_player_spawns_fallback) + end + + return spawns end --- @@ -312,7 +334,7 @@ end -- @return table A table with all the weapon spawns grouped by ent types -- @realm shared function map.GetWeaponSpawns() - return DatafySpawnTable(map.GetWeaponSpawnEntities()) + return DatafySpawnTable(map.GetWeaponSpawnEntities()) end --- @@ -320,7 +342,7 @@ end -- @return table A table with all the ammo spawns grouped by ent types -- @realm shared function map.GetAmmoSpawns() - return DatafySpawnTable(map.GetAmmoSpawnEntities()) + return DatafySpawnTable(map.GetAmmoSpawnEntities()) end --- @@ -328,7 +350,7 @@ end -- @return table A table with all the player spawns grouped by ent types -- @realm shared function map.GetPlayerSpawns() - return DatafySpawnTable(map.GetPlayerSpawnEntities()) + return DatafySpawnTable(map.GetPlayerSpawnEntities()) end --- @@ -338,53 +360,76 @@ end -- @internal -- @realm shared function map.GetSpawnsFromClassTable(spawns) - local spawnTable = entspawnscript.GetEmptySpawnTableStructure() + local spawnTable = entspawnscript.GetEmptySpawnTableStructure() + + for i = 1, #spawns do + local spawn = spawns[i] + local cls = spawn.class + + -- first check if it is a player spawn, this is independant from the map type + local hook_player_spawns = {} + + -- @realm shared + -- stylua: ignore + hook.Run("TTT2MapRegisterPlayerSpawns", hook_player_spawns) + + local plyType = ttt_player_spawns[cls] + or hook_player_spawns[cls] + or ttt_player_spawns_fallback[cls] - for i = 1, #spawns do - local spawn = spawns[i] - local cls = spawn.class + if plyType then + AddData(spawnTable[SPAWN_TYPE_PLAYER], plyType, spawn) - -- first check if it is a player spawn, this is independant from the map type - local plyType = ttt_player_spawns[cls] or ttt_player_spawns_fallback[cls] + continue + end - if plyType then - AddData(spawnTable[SPAWN_TYPE_PLAYER], plyType, spawn) + -- next check if it is an ammo spawn + local hook_ammo_spawns = {} - continue - end + -- @realm shared + -- stylua: ignore + hook.Run("TTT2MapRegisterAmmoSpawns", hook_ammo_spawns) - -- next check if it is an ammo spawn - local ammoType = ttt_ammo_spawns[cls] or hl2_ammo_spawns[cls] + local ammoType = ttt_ammo_spawns[cls] or hl2_ammo_spawns[cls] or hook_ammo_spawns[cls] - if ammoType then - AddData(spawnTable[SPAWN_TYPE_AMMO], ammoType, spawn) + if ammoType then + AddData(spawnTable[SPAWN_TYPE_AMMO], ammoType, spawn) - continue - end + continue + end - -- next check if it is a weapon spawn - local wepType = ttt_weapon_spawns[cls] or hl2_weapon_spawns[cls] or css_weapon_spawns[cls] or tf2_weapon_spawns[cls] + -- next check if it is a weapon spawn + local hook_weapon_spawns = {} + -- @realm shared + -- stylua: ignore + hook.Run("TTT2MapRegisterWeaponSpawns", hook_weapon_spawns) - if wepType then - AddData(spawnTable[SPAWN_TYPE_WEAPON], wepType, spawn) + local wepType = ttt_weapon_spawns[cls] + or hl2_weapon_spawns[cls] + or css_weapon_spawns[cls] + or tf2_weapon_spawns[cls] + or hook_weapon_spawns[cls] - continue - end + if wepType then + AddData(spawnTable[SPAWN_TYPE_WEAPON], wepType, spawn) - -- if it is still not found, see it as a weapon and check if it has a spawn type flag - local wep = weaponsGetStored(cls) + continue + end - if wep and wep.spawnType then - AddData(spawnTable[SPAWN_TYPE_WEAPON], wep.spawnType, spawn) + -- if it is still not found, see it as a weapon and check if it has a spawn type flag + local wep = weaponsGetStored(cls) - continue - end + if wep and wep.spawnType then + AddData(spawnTable[SPAWN_TYPE_WEAPON], wep.spawnType, spawn) - -- as a last fallback assume that it is a random spawn for a weapon - AddData(spawnTable[SPAWN_TYPE_WEAPON], WEAPON_TYPE_RANDOM, spawn) - end + continue + end - return spawnTable + -- as a last fallback assume that it is a random spawn for a weapon + AddData(spawnTable[SPAWN_TYPE_WEAPON], WEAPON_TYPE_RANDOM, spawn) + end + + return spawnTable end --- @@ -397,15 +442,15 @@ end -- @return boolean Returns true if the given entity is default terrortown entity -- @realm shared function map.IsDefaultTerrortownMapEntity(ent) - local cls = ent:GetClass() + local cls = ent:GetClass() - local type = ttt_weapon_spawns[cls] ~= nil or ttt_ammo_spawns[cls] ~= nil + local type = ttt_weapon_spawns[cls] ~= nil or ttt_ammo_spawns[cls] ~= nil - if not type or type == WEAPON_TYPE_RANDOM or type == AMMO_TYPE_RANDOM then - return false - end + if not type or type == WEAPON_TYPE_RANDOM or type == AMMO_TYPE_RANDOM then + return false + end - return true + return true end --- @@ -416,22 +461,68 @@ end -- @return table The spawn data (pos, ang, ammo) -- @realm shared function map.GetDataFromSpawnEntity(ent, spawnType) - local cls = ent:GetClass() - local data = { - pos = ent:GetPos(), - ang = ent:GetAngles(), - ammo = ent.autoAmmoAmount or 0 - } - - if spawnType == SPAWN_TYPE_WEAPON then - return ttt_weapon_spawns[cls] or hl2_weapon_spawns[cls] or css_weapon_spawns[cls] or tf2_weapon_spawns[cls], data - end - - if spawnType == SPAWN_TYPE_AMMO then - return ttt_ammo_spawns[cls] or hl2_ammo_spawns[cls], data - end - - if spawnType == SPAWN_TYPE_PLAYER then - return ttt_player_spawns[cls] or ttt_player_spawns_fallback[cls], data - end + local cls = ent:GetClass() + local data = { + pos = ent:GetPos(), + ang = ent:GetAngles(), + ammo = ent.autoAmmoAmount or 0, + } + + if spawnType == SPAWN_TYPE_WEAPON then + local hook_weapon_spawns = {} + -- @realm shared + -- stylua: ignore + hook.Run("TTT2MapRegisterWeaponSpawns", hook_weapon_spawns) + + return ttt_weapon_spawns[cls] + or hl2_weapon_spawns[cls] + or css_weapon_spawns[cls] + or tf2_weapon_spawns[cls] + or hook_weapon_spawns[cls], + data + end + + if spawnType == SPAWN_TYPE_AMMO then + local hook_ammo_spawns = {} + + -- @realm shared + -- stylua: ignore + hook.Run("TTT2MapRegisterAmmoSpawns", hook_ammo_spawns) + + return ttt_ammo_spawns[cls] or hl2_ammo_spawns[cls] or hook_ammo_spawns[cls], data + end + + if spawnType == SPAWN_TYPE_PLAYER then + local hook_player_spawns = {} + + -- @realm shared + -- stylua: ignore + hook.Run("TTT2MapRegisterPlayerSpawns", hook_player_spawns) + + return ttt_player_spawns[cls] or hook_player_spawns[cls] or ttt_player_spawns_fallback[cls], + data + end end + +-- HOOKS -- + +--- +-- This hook can be used to register additional ammo spawn entities that TTT2 should use. +-- @param table spawns Table keyed by entity name, values are AMMO_TYPE_* constants. +-- @hook +-- @realm shared +function GM:TTT2MapRegisterAmmoSpawns(spawns) end + +--- +-- This hook can be used to register additional weapon spawn entities that TTT2 should use. +-- @param table spawns Table keyed by entity name, values are WEAPON_TYPE_* constants. +-- @hook +-- @realm shared +function GM:TTT2MapRegisterWeaponSpawns(spawns) end + +--- +-- This hook can be used to register additional player spawn entities that TTT2 should use. +-- @param table spawns Table keyed by entity name, values are SPAWN_TYPE_* constants. +-- @hook +-- @realm shared +function GM:TTT2MapRegisterPlayerSpawns(spawns) end diff --git a/lua/ttt2/libraries/marker_vision.lua b/lua/ttt2/libraries/marker_vision.lua new file mode 100644 index 000000000..0d0e447da --- /dev/null +++ b/lua/ttt2/libraries/marker_vision.lua @@ -0,0 +1,474 @@ +--- +-- A module that let's devs create marker visions for any entity that is automatically synced +-- between server and client and is tied to a player and their team +-- @author Mineotopia +-- @module markerVision + +if SERVER then + AddCSLuaFile() + + util.AddNetworkString("ttt2_marker_vision_entity_removed") +end + +VISIBLE_FOR_PLAYER = 0 +VISIBLE_FOR_ROLE = 1 +VISIBLE_FOR_TEAM = 2 +VISIBLE_FOR_ALL = 3 + +VISIBLE_FOR_BITS = 3 + +markerVision = {} + +markerVision.registry = {} + +--- +-- Creates a new marker vision object for the entity. +-- @note This does not sync to the client, @{MARKER_VISION_ELEMENT:SyncToClients} +-- has to be called on it first +-- @param Entity ent The entity which should receive the marker vision element +-- @param string identifier The unique identifier of this marker vision element +-- @return MARKER_VISION_ELEMENT The marker vision object that was created +-- @realm shared +function markerVision.Add(ent, identifier) + local mvObject = table.Copy(MARKER_VISION_ELEMENT) + mvObject:SetEnt(ent) + mvObject:SetIdentifier(identifier) + + markerVision.registry[#markerVision.registry + 1] = mvObject + + return mvObject +end + +--- +-- Returns the marker vision element if it exists. +-- @param Entity ent The entity which should receive the marker vision element +-- @param string identifier The unique identifier of this marker vision element +-- @return MARKER_VISION_ELEMENT The marker vision object +-- @return number The position in the table, -1 if not found +-- @realm shared +function markerVision.Get(ent, identifier) + for i = 1, #markerVision.registry do + local mvObject = markerVision.registry[i] + + if mvObject:IsObjectFor(ent, identifier) then + return mvObject, i + end + end + + return nil, -1 +end + +--- +-- Removes the marker vision element if it exists. +-- @param Entity ent The entity which should receive the marker vision element +-- @note If called on the server, it is also removed on the client +-- @param string identifier The unique identifier of this marker vision element +-- @realm shared +function markerVision.Remove(ent, identifier) + local _, index = markerVision.Get(ent, identifier) + + if index == -1 then + return + end + + table.remove(markerVision.registry, index) + + -- to simplify the networking and to prevent any artefacts due to + -- role changes, a removal is broadcasted to everyone + if SERVER then + net.Start("ttt2_marker_vision_entity_removed") + net.WriteEntity(ent) + net.WriteString(identifier) + net.Broadcast() + end + + if CLIENT then + marks.Remove({ ent }) + end +end + +local entmeta = assert(FindMetaTable("Entity"), "[TTT2] FAILED TO FIND ENTITY TABLE") + +--- +-- Creates a new marker vision object for the entity. +-- @note This does not sync to the client, @{MARKER_VISION_ELEMENT:SyncToClients} +-- has to be called on it first +-- @param string identifier The unique identifier of this marker vision element +-- @return MARKER_VISION_ELEMENT The marker vision object that was created +-- @realm shared +function entmeta:AddMarkerVision(identifier) + return markerVision.Add(self, identifier) +end + +--- +-- Returns the marker vision element if it exists. +-- @param string identifier The unique identifier of this marker vision element +-- @return MARKER_VISION_ELEMENT The marker vision object +-- @realm shared +function entmeta:GetMarkerVision(identifier) + return markerVision.Get(self, identifier) +end + +--- +-- Removes the marker vision element if it exists. +-- @note If called on the server, it is also removed on the client +-- @param string identifier The unique identifier of this marker vision element +-- @realm shared +function entmeta:RemoveMarkerVision(identifier) + markerVision.Remove(self, identifier) +end + +if SERVER then + --- + -- Handles the update of the team of a player change. Is called internally and should + -- probably not be called somewhere else. + -- @param Entity ent The entity that should be updated + -- @param string oldTeam The old team of the owner + -- @param string newTeam The new team of the owner + -- @internal + -- @realm server + function markerVision.PlayerUpdatedTeam(ply, oldTeam, newTeam) + for i = 1, #markerVision.registry do + local mvObject = markerVision.registry[i] + local mvObjectData = mvObject.data + + if mvObjectData.visibleFor ~= VISIBLE_FOR_TEAM then + continue + end + + -- case 1: the owner is a player and they are the one that changed their team + -- this means that the entity is taken to the new team + if IsPlayer(mvObjectData.owner) and mvObjectData.owner == ply then + -- nothing needs to be updated here, the new receiver list is generated + -- in SyncToClients + mvObject:SyncToClients() + + continue + end + + -- case 2: it is bound to the team and the player's new/old team matches + -- note: in this case the owner is no entity, but a team + if mvObjectData.owner == oldTeam or mvObjectData.owner == newTeam then + -- nothing needs to be updated here, the new receiver list is generated + -- in SyncToClients + mvObject:SyncToClients() + + continue + end + end + end + + --- + -- Handles the update of the role of a player change. Is called internally and should + -- probably not be called somewhere else. + -- @param Entity ent The entity that should be updated + -- @param number oldRole The old role of the owner + -- @param number newRole The new role of the owner + -- @internal + -- @realm server + function markerVision.PlayerUpdatedRole(ply, oldRole, newRole) + for i = 1, #markerVision.registry do + local mvObject = markerVision.registry[i] + local mvObjectData = mvObject.data + + if mvObjectData.visibleFor ~= VISIBLE_FOR_ROLE then + continue + end + + -- case 1: the owner is a player and they are the one that changed their role + -- this means that the entity is taken to the new role + if IsPlayer(mvObjectData.owner) and mvObjectData.owner == ply then + -- nothing needs to be updated here, the new receiver list is generated + -- in SyncToClients + mvObject:SyncToClients() + + continue + end + + -- case 2: it is bound to the role and the player's new/old role matches + -- note: in this case the owner is no entity, but a role + if mvObjectData.owner == oldRole or mvObjectData.owner == newRole then + -- nothing needs to be updated here, the new receiver list is generated + -- in SyncToClients + mvObject:SyncToClients() + + continue + end + end + end +end + +if CLIENT then + net.ReceiveStream("ttt2_marker_vision_entity", function(streamData) + -- handle existing data on the client, the existing one will be updated + if IsValid(markerVision.Get(streamData.ent, streamData.identifier)) then + markerVision.Remove(streamData.ent, streamData.identifier) + end + + local mvObject = markerVision.Add(streamData.ent, streamData.identifier) + mvObject:SetOwner(streamData.owner) + mvObject:SetVisibleFor(streamData.visibleFor) + mvObject:SetColor(streamData.color) + + -- add mark to entity + marks.Add({ streamData.ent }, mvObject:GetColor()) + end) + + net.Receive("ttt2_marker_vision_entity_removed", function() + markerVision.Remove(net.ReadEntity(), net.ReadString()) + end) + + surface.CreateAdvancedFont( + "RadarVision_Title", + { font = "Tahoma", size = 20, weight = 600, extended = true } + ) + surface.CreateAdvancedFont( + "RadarVision_Text", + { font = "Tahoma", size = 14, weight = 300, extended = true } + ) + + --- + -- The draw function of the radar vision module. + -- @internal + -- @realm client + function markerVision.Draw() + local scale = appearance.GetGlobalScale() + + local padding = 3 * scale + local paddingScreen = 10 * scale + + local sizeIcon = 28 * scale + local sizeIconOffScreen = 22 * scale + + local offsetIcon = 0.5 * sizeIcon + local offsetIconOffScreen = 0.5 * sizeIconOffScreen + + local sizeTitleIcon = 14 * scale + local offsetTitleIcon = 0.5 * sizeTitleIcon + + local heightLineDescription = 14 * scale + + local client = LocalPlayer() + local widthScreen = ScrW() + local heightScreen = ScrH() + local xScreenCenter = 0.5 * widthScreen + local yScreenCenter = 0.5 * heightScreen + + for i = 1, #markerVision.registry do + local mvObject = markerVision.registry[i] + local ent = mvObject.data.ent + + if not IsValid(ent) then + continue + end + + local posEnt = ent:GetPos() + ent:OBBCenter() + local screenPos = posEnt:ToScreen() + local isOffScreen = util.IsOffScreen(screenPos) + local isOnScreenCenter = not isOffScreen + and screenPos.x > xScreenCenter - offsetIcon + and screenPos.x < xScreenCenter + offsetIcon + and screenPos.y > yScreenCenter - offsetIcon + and screenPos.y < yScreenCenter + offsetIcon + local distanceEntity = posEnt:Distance(client:EyePos()) + + -- call internal targetID functions first so the data can be modified by addons + local mvData = MARKER_VISION_DATA:Initialize( + ent, + isOffScreen, + isOnScreenCenter, + distanceEntity, + mvObject + ) + + --- + -- now run a hook that can be used by addon devs that changes the appearance + -- of the radar vision + -- @realm client + -- stylua: ignore + hook.Run("TTT2RenderMarkerVisionInfo", mvData) + + local params = mvData.params + + if not params.drawInfo then + continue + end + + local amountIcons = #params.displayInfo.icon + + if isOffScreen or not isOnScreenCenter then + if amountIcons < 1 then + continue + end + + local icon = params.displayInfo.icon[1] + local color = icon.color or COLOR_WHITE + + if screenPos.x > 100000 then + screenPos.x = screenPos.x / 100000 + end + + if screenPos.y > 100000 then + screenPos.y = screenPos.y / 100000 + end + + screenPos.x = math.Clamp( + screenPos.x, + offsetIconOffScreen + paddingScreen, + widthScreen - offsetIconOffScreen - paddingScreen + ) + screenPos.y = math.Clamp( + screenPos.y, + offsetIconOffScreen + paddingScreen, + heightScreen - offsetIconOffScreen - paddingScreen + ) + + draw.FilteredShadowedTexture( + screenPos.x - offsetIconOffScreen, + screenPos.y - offsetIconOffScreen, + sizeIconOffScreen, + sizeIconOffScreen, + icon.material, + color.a, + color + ) + + if not mvData:HasCollapsedLine() then + continue + end + + draw.AdvancedText( + params.displayInfo.collapsedLine.text, + "RadarVision_Text", + screenPos.x + 1 * scale, + screenPos.y + offsetIconOffScreen + padding, + params.displayInfo.collapsedLine.color, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + true, + scale + ) + + continue + end + + screenPos.x = math.Clamp( + screenPos.x, + offsetIcon + paddingScreen, + widthScreen - offsetIcon - paddingScreen + ) + screenPos.y = math.Clamp( + screenPos.y, + offsetIcon + paddingScreen, + heightScreen - offsetIcon - paddingScreen + ) + + -- draw Icons + local xIcon, yIcon + + if amountIcons > 0 then + xIcon = screenPos.x - offsetIcon + yIcon = screenPos.y - offsetIcon + + for j = 1, amountIcons do + local icon = params.displayInfo.icon[j] + local color = icon.color or COLOR_WHITE + + draw.FilteredShadowedTexture( + xIcon, + yIcon, + sizeIcon, + sizeIcon, + icon.material, + color.a, + color + ) + + yIcon = sizeIcon + padding + end + end + + -- draw title + local stringTitle = params.displayInfo.title.text + + local xStringTitle = screenPos.x + offsetIcon + padding + local yStringTitle = screenPos.y + + for j = 1, #params.displayInfo.title.icons do + drawsc.FilteredShadowedTexture( + xStringTitle, + yStringTitle - offsetTitleIcon, + sizeTitleIcon, + sizeTitleIcon, + params.displayInfo.title.icons[j], + params.displayInfo.title.color.a, + params.displayInfo.title.color + ) + + xStringTitle = xStringTitle + 18 * scale + end + + draw.AdvancedText( + stringTitle, + "RadarVision_Title", + xStringTitle, + yStringTitle - 2 * scale, + params.displayInfo.title.color, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + true, + scale + ) + + -- draw description + local linesDescription = params.displayInfo.desc + local amountLinesDescription = #linesDescription + + local xStringDescription = screenPos.x + offsetIcon + padding + + for j = 1, amountLinesDescription do + local text = linesDescription[j].text + local icons = linesDescription[j].icons + local color = linesDescription[j].color + + local xStringDescriptionShifted = xStringDescription + local yStringDescription = yStringTitle + j * heightLineDescription + + for k = 1, #icons do + draw.FilteredShadowedTexture( + xStringDescriptionShifted, + yStringDescription - 13, + 11, + 11, + icons[k], + color.a, + color + ) + + xStringDescriptionShifted = xStringDescriptionShifted + 14 + end + + draw.AdvancedText( + text, + "RadarVision_Text", + xStringDescriptionShifted, + yStringDescription, + color, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + true, + scale + ) + + yStringDescription = yStringDescription + heightLineDescription + end + end + end + + --- + -- Add marker vision information that should be rendered on a marker vision object. + -- @param MARKER_VISION_DATA mvData The @{MARKER_VISION_DATA} data object which contains all information + -- @hook + -- @realm client + function GM:TTT2RenderMarkerVisionInfo(mvData) end +end diff --git a/lua/ttt2/libraries/marks.lua b/lua/ttt2/libraries/marks.lua index 3df69329b..144625287 100644 --- a/lua/ttt2/libraries/marks.lua +++ b/lua/ttt2/libraries/marks.lua @@ -9,15 +9,13 @@ local render = render local table = table local IsValid = IsValid -local cam = cam -local surface = surface local hook = hook local pairs = pairs if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return + return end marks = {} @@ -25,145 +23,179 @@ marks = {} local marksList = {} local marksHookInstalled = false +-- reuse the same memory for each render +local oldR, oldG, oldB = 1, 1, 1 +local oldBlend = 1 +local markedEnt = NULL +local markedColor = color_white + --- --- Renders the entity based on the color --- @param table ents list of @{Entity} +-- Renders marked models, this is a sub function for Render. +-- @param table markedEntsTable list of @{Entity} -- @param Color col color of rendering --- @param Vector pos position of client's view the rendering starts from --- @param Angle ang angle of client's view the rendering starts from +-- @param number size size of markedEntsTable table -- @realm client -local function Render(ents, col, pos, ang, w, h) - -- check for valid data - local tmp = {} - local index = 1 - local remTable = {} - local remSize = 0 - - local entsSize = #ents - - for i = 1, entsSize do - local ent = ents[i] - - if not IsValid(ent) then -- search for invalid data - remSize = remSize + 1 - remTable[remSize] = ent - elseif not ent:IsPlayer() or ent:Alive() and ent:IsTerror() then - tmp[index] = ent - index = index + 1 - end - end - - -- clear invalid data. Should just happen if a player disconnects or an entity is deleted - if remSize ~= 0 then - local tableRemove = table.remove - - for i = 1, remSize do - for x = 1, entsSize do - if ents[x] == remTable[i] then - tableRemove(ents, x) - - entsSize = entsSize - 1 - - break - end - end - end - end - - local size = #tmp - if size == 0 then return end - - render.ClearStencil() - render.SetStencilEnable(true) - render.SetStencilWriteMask(255) - render.SetStencilTestMask(255) - render.SetStencilReferenceValue(15) - render.SetStencilFailOperation(STENCILOPERATION_KEEP) - render.SetStencilZFailOperation(STENCILOPERATION_REPLACE) - render.SetStencilPassOperation(STENCILOPERATION_KEEP) - render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) - render.SetBlend(0) - - for i = 1, size do - tmp[i]:DrawModel() - end - - render.SetBlend(1) - render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) - - -- stencil work is done in postdrawopaquerenderables, where surface doesn't work correctly - -- workaround via 3D2D by Bletotum - - cam.Start3D2D(pos, ang, 1) - - surface.SetDrawColor(col.r, col.g, col.b, col.a) - surface.DrawRect(-w, -h, w * 2, h * 2) - - cam.End3D2D() - - for i = 1, size do - tmp[i]:DrawModel() - end - - render.SetStencilEnable(false) +local function RenderMarkedModels(markedEntsTable, col, size) + for i = 1, size do + markedEnt = markedEntsTable[i] + + -- prevent messing up materials + markedEnt:SetNoDraw(true) + -- get/set the color of our marked entity + markedColor = markedEnt:GetColor() + oldR, oldG, oldB = render.GetColorModulation() + render.SetColorModulation(markedColor.r / 255, markedColor.g / 255, markedColor.b / 255) + -- get/set the transparency of our marked entity + oldBlend = render.GetBlend() + render.SetBlend(markedColor.a / 255) + + -- draw the entity + markedEnt:DrawModel() + + -- reset the values back to what they were + markedEnt:SetNoDraw(false) + render.SetColorModulation(oldR, oldG, oldB) + render.SetBlend(oldBlend) + end +end + +--- +-- Renders the entity based on the color. +-- @param table markedEntsTable list of @{Entity} +-- @param Color col color of rendering +-- @realm client +local function Render(markedEntsTable, col) + -- check for valid data + local tmp = {} + local index = 1 + local remTable = {} + local remSize = 0 + + local entsTableSize = #markedEntsTable + + for i = 1, entsTableSize do + local ent = markedEntsTable[i] + + if not IsValid(ent) then -- search for invalid data + remSize = remSize + 1 + remTable[remSize] = ent + elseif not ent:IsPlayer() or ent:Alive() and ent:IsTerror() then + tmp[index] = ent + index = index + 1 + end + end + + -- clear invalid data. Should just happen if a player disconnects or an entity is deleted + if remSize ~= 0 then + local tableRemove = table.remove + + for i = 1, remSize do + for x = 1, entsTableSize do + if markedEntsTable[x] == remTable[i] then + tableRemove(markedEntsTable, x) + + entsTableSize = entsTableSize - 1 + + break + end + end + end + end + + local size = #tmp + if size == 0 then + return + end + + -- reset everything to known good values + render.SetStencilWriteMask(0xFF) + render.SetStencilTestMask(0xFF) + render.SetStencilReferenceValue(0) + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilPassOperation(STENCIL_KEEP) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_KEEP) + render.ClearStencil() + + -- enable stencils + render.SetStencilEnable(true) + -- set the reference value to 1. This is what the compare function tests against + render.SetStencilReferenceValue(1) + + -- always draw everything, since we need the ZFail to work we cannot use STENCIL_NEVER to avoid drawing to the RT + render.SetStencilCompareFunction(STENCIL_ALWAYS) + -- if something would draw to the screen but is behind something, set the pixels it draws to 1 + render.SetStencilZFailOperation(STENCIL_REPLACE) + + RenderMarkedModels(tmp, col, size) + + -- now we basically do the same thing again but this time we set stencil values to 0 if the operation passes + -- this fixes the entity zfailing with itself + render.SetStencilZFailOperation(STENCIL_KEEP) + render.SetStencilPassOperation(STENCILOPERATION_ZERO) + + RenderMarkedModels(tmp, col, size) + + -- now, only draw things that have their pixels set to 1. This is the hidden parts of the stencil tests + render.SetStencilCompareFunction(STENCIL_EQUAL) + -- flush the screen. This will draw our color onto the screen + render.ClearBuffersObeyStencil(col.r, col.g, col.b, col.a, false) + + -- let everything render normally again + render.SetStencilEnable(false) end --- -- Hook that renders the entities with the highlighting -- @realm client local function RenderHook() - local client = LocalPlayer() - local ang = client:EyeAngles() - local pos = client:EyePos() + ang:Forward() * 10 - - ang = Angle(ang.p + 90, ang.y, 0) - - local w = ScrW() - local h = ScrH() - - for _, list in pairs(marksList) do - Render(list.ents, list.col, pos, ang, w, h) - end + for _, list in pairs(marksList) do + Render(list.ents, list.col) + end end --- -- Hook adding -- @realm client local function AddMarksHook() - if marksHookInstalled then return end + if marksHookInstalled then + return + end - hook.Add("PostDrawOpaqueRenderables", "RenderMarks", RenderHook) + hook.Add("PostDrawOpaqueRenderables", "RenderMarks", RenderHook) end --- -- Hook removing -- @realm client local function RemoveMarksHook() - hook.Remove("PostDrawOpaqueRenderables", "RenderMarks") + hook.Remove("PostDrawOpaqueRenderables", "RenderMarks") - marksHookInstalled = false + marksHookInstalled = false end --- -- Initialization of the markers list -- @realm client local function SetupMarkList(col) - if not col then return end + if not col then + return + end - local str = tostring(col) + local str = tostring(col) - marksList[str] = marksList[str] or {} - marksList[str].ents = marksList[str].ents or {} - marksList[str].col = col + marksList[str] = marksList[str] or {} + marksList[str].ents = marksList[str].ents or {} + marksList[str].col = col end --- -- Clearing the cached @{Entity} list -- @realm client function marks.Clear() - marksList = {} + marksList = {} - RemoveMarksHook() + RemoveMarksHook() end --- @@ -171,34 +203,38 @@ end -- @param table ents list of entities that should get removed -- @realm client function marks.Remove(ents) - local entsSize = #ents + local entsSize = #ents - if entsSize == 0 or table.Count(marksList) == 0 then return end + if entsSize == 0 or table.Count(marksList) == 0 then + return + end - for i = 1, entsSize do - local ent = ents[i] + for i = 1, entsSize do + local ent = ents[i] - for _, list in pairs(marksList) do - local ret = nil - local size = #list.ents + for _, list in pairs(marksList) do + local ret = nil + local size = #list.ents - for x = 1, size do - if ent == list.ents[x] then - table.remove(list.ents, x) + for x = 1, size do + if ent == list.ents[x] then + table.remove(list.ents, x) - ret = true + ret = true - break - end - end + break + end + end - if ret then break end - end - end + if ret then + break + end + end + end - if table.IsEmpty(marksList) then - RemoveMarksHook() - end + if table.IsEmpty(marksList) then + RemoveMarksHook() + end end --- @@ -207,19 +243,21 @@ end -- @param Color col the color the added entities should get rendered -- @realm client function marks.Add(ents, col) - if #ents == 0 or not col then return end + if #ents == 0 or not col then + return + end - -- check if an entity is already inserted and remove it - marks.Remove(ents) + -- check if an entity is already inserted and remove it + marks.Remove(ents) - -- setup the table - SetupMarkList(col) + -- setup the table + SetupMarkList(col) - -- add entities into the table - table.Add(marksList[tostring(col)].ents, ents) + -- add entities into the table + table.Add(marksList[tostring(col)].ents, ents) - -- add the hook if there is something to render - AddMarksHook() + -- add the hook if there is something to render + AddMarksHook() end --- @@ -230,25 +268,27 @@ end -- @usage marks.Set({}, COLOR_WHITE) -- this will clear all entities rendered in white -- @realm client function marks.Set(ents, col) - if not col or not istable(ents) then return end + if not col or not istable(ents) then + return + end - -- check if an entity is already inserted and remove it - marks.Remove(ents) + -- check if an entity is already inserted and remove it + marks.Remove(ents) - -- set the entities or remove table if empty - local str = tostring(col) + -- set the entities or remove table if empty + local str = tostring(col) - SetupMarkList(col) + SetupMarkList(col) - if #ents == 0 then - marksList[str] = nil - else - marksList[str].ents = ents - end + if #ents == 0 then + marksList[str] = nil + else + marksList[str].ents = ents + end - if table.IsEmpty(marksList) then - RemoveMarksHook() - else - AddMarksHook() - end + if table.IsEmpty(marksList) then + RemoveMarksHook() + else + AddMarksHook() + end end diff --git a/lua/ttt2/libraries/migrations.lua b/lua/ttt2/libraries/migrations.lua new file mode 100644 index 000000000..a33f09a68 --- /dev/null +++ b/lua/ttt2/libraries/migrations.lua @@ -0,0 +1,69 @@ +--- +-- A TTT2 version migrations library +-- @author ZenBre4ker +-- @module migrations + +if SERVER then + AddCSLuaFile() +end + +migrations = {} +migrations.folderPath = "terrortown/migrations/" +migrations.databaseName = "ttt2_migrations" +migrations.savingKeys = { + executedAt = { + typ = "string", + default = "", + }, +} + +sql.CreateSqlTable(migrations.databaseName, migrations.savingKeys) +migrations.orm = orm.Make(migrations.databaseName) + +--- +-- Runs all missing forward gamemode migrations. +-- @note Folderpath is lua/terrortown/migrations/*.lua +-- @note Migrations shall be returned as anonymous functions inside those files +-- @note Migrations are shared, so use if CLIENT|SERVER for separation +-- @realm shared +function migrations.Apply() + local files = file.Find(migrations.folderPath .. "*.lua", "LUA", "nameasc") or {} + + local fileExecutionCounter = 0 + for i = 1, #files do + local fileName = files[i] + local fullPath = migrations.folderPath .. files[i] + + local fileInfo = migrations.orm:Find(fileName) + + if fileInfo then + continue + end + + Dev(1, "Migrating: ", fileName) + + if SERVER then + AddCSLuaFile(fullPath) + end + local isSuccess, errorMessage = pcall(include(fullPath)) + + if not isSuccess then + error("[TTT2] Migration failed.\n" .. errorMessage, 1) + + return + end + + local executedAt = os.date("%Y/%m/%d - %H:%M:%S", os.time()) + + migrations.orm + :New({ + name = fileName, + executedAt = executedAt, + }) + :Save() + + fileExecutionCounter = fileExecutionCounter + 1 + end + + Dev(1, "[TTT2] Successfully migrated ", fileExecutionCounter, " Files.") +end diff --git a/lua/ttt2/libraries/none.lua b/lua/ttt2/libraries/none.lua index 385577954..eaa908281 100644 --- a/lua/ttt2/libraries/none.lua +++ b/lua/ttt2/libraries/none.lua @@ -7,7 +7,7 @@ -- @module none if SERVER then - AddCSLuaFile() + AddCSLuaFile() end --- @@ -19,25 +19,31 @@ end -- @return number alpha value of the given color -- @realm shared function clr(color) - return color.r, color.g, color.b, color.a + return color.r, color.g, color.b, color.a end --- --- This @{function} creates a getter and a setter @{function} based on the name and the prefix "Get" and "Set" +-- This @{function} creates a getter and a setter @{function} for DTVars of an entity +-- The create @{function} names are based on the var name and the prefix "Get" and "Set" +-- @note Instead of using this function simply replace `ENT:DTVar()` calls with `ENT:NetworkVar()`. -- @param table tbl the @{table} that should receive the Getter and Setter @{function} -- @param string varname the name the tbl @{table} should have as key value -- @param string name the name that should be concatenated to the prefix "Get" and "Set" +-- @deprecated -- @realm shared function AccessorFuncDT(tbl, varname, name) - tbl["Get" .. name] = function(s) - return s.dt and s.dt[varname] - end + ErrorNoHaltWithStack( + "[DEPRECATION WARNING] Using `AccessorFuncDT` is deprecated and will be removed in a future version." + ) + tbl["Get" .. name] = function(s) + return s.dt and s.dt[varname] + end - tbl["Set" .. name] = function(s, v) - if s.dt then - s.dt[varname] = v - end - end + tbl["Set" .. name] = function(s, v) + if s.dt then + s.dt[varname] = v + end + end end --- @@ -46,14 +52,14 @@ end -- @param string default -- @return string -- @realm shared --- @ref https://wiki.garrysmod.com/page/input/LookupBinding +-- @ref https://wiki.facepunch.com/gmod/input.LookupBinding function Key(binding, default) - local b = input.LookupBinding(binding) - if not b then - return default - end + local b = input.LookupBinding(binding) + if not b then + return default + end - return string.upper(b) + return string.upper(b) end --- @@ -62,27 +68,29 @@ end -- @param any ... anything that should be printed -- @realm shared function Dev(level, ...) - if not cvars or cvars.Number("developer", 0) < level then return end + if not cvars or cvars.Number("developer", 0) < level then + return + end - Msg("[TTT dev]") - -- table.concat does not tostring, derp + Msg("[TTT dev]") + -- table.concat does not tostring, derp - local params = {...} + local params = { ... } - for i = 1, #params do - Msg(" " .. tostring(params[i])) - end + for i = 1, #params do + Msg(" " .. tostring(params[i])) + end - Msg("\n") + Msg("\n") end --- --- A simple check whether an @{Entity} is a valid @{Player} --- @param Entity ent --- @return boolean +-- A simple check whether a variable is a valid @{Player} +-- @param any var The variable that should be checked +-- @return boolean Returns true if the variable is a valid player -- @realm shared -function IsPlayer(ent) - return ent and IsValid(ent) and ent:IsPlayer() +function IsPlayer(var) + return var and isentity(var) and IsValid(var) and var:IsPlayer() end --- @@ -91,5 +99,5 @@ end -- @return boolean -- @realm shared function IsRagdoll(ent) - return ent and IsValid(ent) and ent:GetClass() == "prop_ragdoll" + return ent and IsValid(ent) and ent:GetClass() == "prop_ragdoll" end diff --git a/lua/ttt2/libraries/orm.lua b/lua/ttt2/libraries/orm.lua index 99bf33fba..6d0136081 100644 --- a/lua/ttt2/libraries/orm.lua +++ b/lua/ttt2/libraries/orm.lua @@ -4,14 +4,14 @@ -- @module orm if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local sql = sql orm = orm or {} -local cachedModels = cachedModels or {} +local cachedModels = {} local ORMMODEL = {} local ORMOBJECT = {} @@ -23,51 +23,58 @@ local ORMOBJECT = {} -- @return ORMMODEL|nil Returns the model of the database table or nil if the database table does not exist. -- @realm shared function orm.Make(tableName, force) - if IsValid(cachedModels[tableName]) and not force then - return cachedModels[tableName] - end + if IsValid(cachedModels[tableName]) and not force then + return cachedModels[tableName] + end - if not sql.TableExists(tableName) then return end + if not sql.TableExists(tableName) then + return + end - local primaryKey = sql.GetPrimaryKey(tableName) - local dataStructure = sql.GetTableColumns(tableName) + local primaryKey = sql.GetPrimaryKey(tableName) + local dataStructure = sql.GetTableColumns(tableName) - if not primaryKey or not dataStructure then - ErrorNoHalt("[ORM] An sql error occurred while retrieving the metadata of the following databasetable: " .. tableName) - end + if not primaryKey or not dataStructure then + ErrorNoHaltWithStack( + "[ORM] An sql error occurred while retrieving the metadata of the following databasetable: " + .. tableName + ) + end - local model = {} - model._tableName = tableName - model._dataStructure = dataStructure - model.All = ORMMODEL.All - model.New = ORMMODEL.New - model.Where = ORMMODEL.Where + local model = {} + model._tableName = tableName + model._dataStructure = dataStructure + model.All = ORMMODEL.All + model.New = ORMMODEL.New + model.Where = ORMMODEL.Where - if #primaryKey == 0 then - return model - end + if #primaryKey == 0 then + return model + end - model._primaryKey = primaryKey - model.Find = ORMMODEL.Find + model._primaryKey = primaryKey + model.Find = ORMMODEL.Find - -- Prepare strings that will not change unless the model itself changes. So we don't have to create these strings everytime we use `model.Save()`. - local columnList = {nil, nil} + -- Prepare strings that will not change unless the model itself changes. So we don't have to create these strings everytime we use `model.Save()`. + local columnList = { nil, nil } - for i = 1, #primaryKey do - columnList[i] = sql.SQLIdent(primaryKey[i]) - end + for i = 1, #primaryKey do + ---@cast primaryKey -nil + columnList[i] = sql.SQLIdent(primaryKey[i]) + end - model._primaryKeyList = table.concat(columnList, ",") + model._primaryKeyList = table.concat(columnList, ",") - for i = 1, #dataStructure do - columnList[i] = sql.SQLIdent(dataStructure[i]) - end + for i = 1, #dataStructure do + ---@cast dataStructure -nil + columnList[i] = sql.SQLIdent(dataStructure[i]) + end - model._columnList = table.concat(columnList, ",") + model._columnList = table.concat(columnList, ",") - cachedModels[tableName] = model + cachedModels[tableName] = model - return model + return model end --- @@ -78,24 +85,26 @@ end -- @return table|nil Returns an array of all found @{ORMOBJECT}s or nil in case of an error. -- @realm shared function ORMMODEL:All() - local objects = sql.Query("SELECT * FROM " .. sql.SQLIdent(self._tableName)) + local objects = sql.Query("SELECT * FROM " .. sql.SQLIdent(self._tableName)) - if objects == false then return end + if objects == false then + return + end - -- nothing found, make it an empty table - objects = objects or {} + -- nothing found, make it an empty table + objects = objects or {} - if not self._primaryKey then - return objects - end + if not self._primaryKey then + return objects + end - for i = 1, #objects do - objects[i].Save = self.Save - objects[i].Delete = self.Delete - objects[i].Refresh = self.Refresh - end + for i = 1, #objects do + objects[i].Save = self.Save + objects[i].Delete = self.Delete + objects[i].Refresh = self.Refresh + end - return objects + return objects end --- @@ -105,29 +114,32 @@ end -- @return table|nil Returns the table of the found @{ORMOBJECT}s or nil in case of an error. -- @realm shared function ORMMODEL:Find(primaryValue) - local where = {} - local primaryKey = self._primaryKey - local primaryKeyCount = #primaryKey + local where = {} + local primaryKey = self._primaryKey + local primaryKeyCount = #primaryKey - if not istable(primaryValue) and primaryKeyCount == 1 then - where = sql.SQLIdent(primaryKey[1]) .. "=" .. sql.SQLStr(primaryValue) - elseif istable(primaryValue) and #primaryValue == primaryKeyCount then - for i = 1, primaryKeyCount do - where[i] = sql.SQLIdent(primaryKey[i]) .. "=" .. sql.SQLStr(primaryValue[i]) - end + if not istable(primaryValue) and primaryKeyCount == 1 then + where = sql.SQLIdent(primaryKey[1]) .. "=" .. sql.SQLStr(primaryValue) + elseif istable(primaryValue) and #primaryValue == primaryKeyCount then + for i = 1, primaryKeyCount do + where[i] = sql.SQLIdent(primaryKey[i]) .. "=" .. sql.SQLStr(primaryValue[i]) + end - where = table.concat(where, " AND ") - else - ErrorNoHalt("[ORM] Number of primaryvalues does not match number of primarykeys!") + where = table.concat(where, " AND ") + else + ErrorNoHaltWithStack("[ORM] Number of primaryvalues does not match number of primarykeys!") - return - end + return + end - local result = sql.QueryRow("SELECT * FROM " .. sql.SQLIdent(self._tableName) .. " WHERE " .. where) + local result = + sql.QueryRow("SELECT * FROM " .. sql.SQLIdent(self._tableName) .. " WHERE " .. where) - if result == false then return end + if result == false then + return + end - return result and self:New(result) or nil + return result and self:New(result) or nil end --- @@ -136,27 +148,27 @@ end -- @return ORMOBJECT The created object. -- @realm shared function ORMMODEL:New(data) - local object = data or {} + local object = data or {} - object._tableName = self._tableName - object._dataStructure = self._dataStructure + object._tableName = self._tableName + object._dataStructure = self._dataStructure - -- DO NOT setup delete/save/refresh functions if no primarykey is found. - -- In those cases the 'rowid' column would function as the primarykey, but as the rowid could change anytime (https://www.sqlite.org/rowidtable.html) data could be deleted unintentionally. - -- Most likely rowids wont change in gmod as there is no vacuum operation but just to be safe we will not allow to use such tables. ref: https://wiki.facepunch.com/gmod/sql + -- DO NOT setup delete/save/refresh functions if no primarykey is found. + -- In those cases the 'rowid' column would function as the primarykey, but as the rowid could change anytime (https://www.sqlite.org/rowidtable.html) data could be deleted unintentionally. + -- Most likely rowids wont change in gmod as there is no vacuum operation but just to be safe we will not allow to use such tables. ref: https://wiki.facepunch.com/gmod/sql - if not self._primaryKey then - return object - end + if not self._primaryKey then + return object + end - object.Delete = ORMOBJECT.Delete - object.Save = ORMOBJECT.Save - object.Refresh = ORMOBJECT.Refresh - object._primaryKey = self._primaryKey - object._primaryKeyList = self._primaryKeyList - object._columnList = self._columnList + object.Delete = ORMOBJECT.Delete + object.Save = ORMOBJECT.Save + object.Refresh = ORMOBJECT.Refresh + object._primaryKey = self._primaryKey + object._primaryKeyList = self._primaryKeyList + object._columnList = self._columnList - return object + return object end --- @@ -165,37 +177,42 @@ end -- @return table|nil Returns an array of all found @{ORMOBJECT}s or nil in case of an error. -- @realm shared function ORMMODEL:Where(filters) - local query = "SELECT * FROM " .. sql.SQLIdent(self._tableName) .. " WHERE " - local whereList = {} + local query = "SELECT * FROM " .. sql.SQLIdent(self._tableName) .. " WHERE " + local whereList = {} - for i = 1, #filters do - local curFilter = filters[i] + for i = 1, #filters do + local curFilter = filters[i] - whereList[i] = sql.SQLIdent(curFilter.column) .. (curFilter.op or "=") .. sql.SQLStr(curFilter.value) .. (curFilter.concat or "") - end + whereList[i] = sql.SQLIdent(curFilter.column) + .. (curFilter.op or "=") + .. sql.SQLStr(curFilter.value) + .. (curFilter.concat or "") + end - whereList = table.concat(whereList) + whereList = table.concat(whereList) - query = query .. whereList + query = query .. whereList - local objects = sql.Query(query) + local objects = sql.Query(query) - if objects == false then return end + if objects == false then + return + end - -- nothing found, make it an empty table - objects = objects or {} + -- nothing found, make it an empty table + objects = objects or {} - if not self._primaryKey then - return objects - end + if not self._primaryKey then + return objects + end - for i = 1, #objects do - objects[i].Save = ORMOBJECT.Save - objects[i].Delete = ORMOBJECT.Delete - objects[i].Refresh = ORMOBJECT.Refresh - end + for i = 1, #objects do + objects[i].Save = ORMOBJECT.Save + objects[i].Delete = ORMOBJECT.Delete + objects[i].Refresh = ORMOBJECT.Refresh + end - return objects + return objects end --- @@ -206,18 +223,18 @@ end -- @return boolean Returns true if the object was successfully deleted, false otherwise. -- @realm shared function ORMOBJECT:Delete() - local where = {} - local primaryKey = self._primaryKey + local where = {} + local primaryKey = self._primaryKey - for i = 1, #primaryKey do - where[i] = sql.SQLIdent(primaryKey[i]) .. "=" .. sql.SQLStr(self[primaryKey[i]]) - end + for i = 1, #primaryKey do + where[i] = sql.SQLIdent(primaryKey[i]) .. "=" .. sql.SQLStr(self[primaryKey[i]]) + end - where = table.concat(where, " AND ") + where = table.concat(where, " AND ") - local query = "DELETE FROM " .. sql.SQLIdent(self._tableName) .. " WHERE " .. where + local query = "DELETE FROM " .. sql.SQLIdent(self._tableName) .. " WHERE " .. where - return sql.Query(query) == nil + return sql.Query(query) == nil end --- @@ -225,19 +242,29 @@ end -- @return boolean Returns true if the object was successfully saved to the database, false otherwise. -- @realm shared function ORMOBJECT:Save() - local query = "INSERT INTO " .. sql.SQLIdent(self._tableName) .. "(" - local valueList = {nil, nil} - local dataStructure = self._dataStructure - - for i = 1, #dataStructure do - valueList[i] = sql.SQLStr(self[dataStructure[i]]) - end - - valueList = table.concat(valueList, ",") - - query = query .. self._columnList .. ") VALUES(" .. valueList .. ") ON CONFLICT(" .. self._primaryKeyList .. ") DO UPDATE SET(" .. self._columnList .. ")=(" .. valueList .. ");" - - return sql.Query(query) == nil + local query = "INSERT INTO " .. sql.SQLIdent(self._tableName) .. "(" + local valueList = { nil, nil } + local dataStructure = self._dataStructure + + for i = 1, #dataStructure do + valueList[i] = sql.SQLStr(self[dataStructure[i]]) + end + + valueList = table.concat(valueList, ",") + + query = query + .. self._columnList + .. ") VALUES(" + .. valueList + .. ") ON CONFLICT(" + .. self._primaryKeyList + .. ") DO UPDATE SET(" + .. self._columnList + .. ")=(" + .. valueList + .. ");" + + return sql.Query(query) == nil end --- @@ -245,29 +272,30 @@ end -- @return boolean Returns true if refresh was successful, false otherwise. -- @realm shared function ORMOBJECT:Refresh() - local primaryKey = self._primaryKey - local dataStructure = self._dataStructure - local where = {} + local primaryKey = self._primaryKey + local dataStructure = self._dataStructure + local where = {} - if #primaryKey == 1 then - where = sql.SQLIdent(primaryKey[1]) .. "=" .. sql.SQLStr(self[primaryKey[1]]) - else - for i = 1, #primaryKey do - where[i] = sql.SQLIdent(primaryKey[i]) .. "=" .. sql.SQLStr(self[primaryKey[i]]) - end + if #primaryKey == 1 then + where = sql.SQLIdent(primaryKey[1]) .. "=" .. sql.SQLStr(self[primaryKey[1]]) + else + for i = 1, #primaryKey do + where[i] = sql.SQLIdent(primaryKey[i]) .. "=" .. sql.SQLStr(self[primaryKey[i]]) + end - where = table.concat(where, " AND ") - end + where = table.concat(where, " AND ") + end - local result = sql.QueryRow("SELECT * FROM " .. sql.SQLIdent(self._tableName) .. " WHERE " .. where) + local result = + sql.QueryRow("SELECT * FROM " .. sql.SQLIdent(self._tableName) .. " WHERE " .. where) - if result then - for i = 1, #dataStructure do - self[dataStructure[i]] = result[dataStructure[i]] - end + if result then + for i = 1, #dataStructure do + self[dataStructure[i]] = result[dataStructure[i]] + end - return true - end + return true + end - return false + return false end diff --git a/lua/ttt2/libraries/outline.lua b/lua/ttt2/libraries/outline.lua index 589b80064..1cf4f628f 100644 --- a/lua/ttt2/libraries/outline.lua +++ b/lua/ttt2/libraries/outline.lua @@ -9,9 +9,9 @@ local hook = hook local Material = Material if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return + return end outline = {} @@ -24,8 +24,8 @@ local List, ListSize = {}, 0 local RenderEnt = NULL local outlineMatSettings = { - ["$ignorez"] = 1, - ["$alphatest"] = 1 + ["$ignorez"] = 1, + ["$alphatest"] = 1, } local copyMat = Material("pp/copy") @@ -43,155 +43,164 @@ local MODE = 3 -- @param number mode [OUTLINE_MODE_BOTH, OUTLINE_MODE_NOTVISIBLE, OUTLINE_MODE_VISIBLE] -- @realm client function outline.Add(ents, color, mode) - -- Maximum 255 reference values - if ListSize >= 255 then return end - - -- Support for passing Entity as first argument - if not istable(ents) then - ents = {ents} - end - - -- Do not pass empty tables - if ents[1] == nil then return end - - local t = { - [ENTS] = ents, - [COLOR] = color, - [MODE] = mode or OUTLINE_MODE_BOTH - } - - ListSize = ListSize + 1 - List[ListSize] = t + -- Maximum 255 reference values + if ListSize >= 255 then + return + end + + -- Support for passing Entity as first argument + if not istable(ents) then + ents = { ents } + end + + -- Do not pass empty tables + if ents[1] == nil then + return + end + + local t = { + [ENTS] = ents, + [COLOR] = color, + [MODE] = mode or OUTLINE_MODE_BOTH, + } + + ListSize = ListSize + 1 + List[ListSize] = t end --- -- @return Entity The last rendered @{Entity} -- @realm client function outline.RenderedEntity() - return RenderEnt + return RenderEnt end local function Render() - local client = LocalPlayer() - local IsLineOfSightClear = client.IsLineOfSightClear - local scene = render.GetRenderTarget() + local client = LocalPlayer() + local IsLineOfSightClear = client.IsLineOfSightClear + local scene = render.GetRenderTarget() - render.CopyRenderTargetToTexture(storeTexture) + render.CopyRenderTargetToTexture(storeTexture) - local w = ScrW() - local h = ScrH() + local w = ScrW() + local h = ScrH() - render.Clear(0, 0, 0, 0, true, true) + render.Clear(0, 0, 0, 0, true, true) - -- start stencil modification - render.SetStencilEnable(true) + -- start stencil modification + render.SetStencilEnable(true) - cam.IgnoreZ(true) + cam.IgnoreZ(true) - render.SuppressEngineLighting(true) + render.SuppressEngineLighting(true) - render.SetStencilWriteMask(0xFF) - render.SetStencilTestMask(0xFF) + render.SetStencilWriteMask(0xFF) + render.SetStencilTestMask(0xFF) - render.SetStencilCompareFunction(STENCIL_ALWAYS) - render.SetStencilFailOperation(STENCIL_KEEP) - render.SetStencilZFailOperation(STENCIL_REPLACE) - render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_REPLACE) + render.SetStencilPassOperation(STENCIL_REPLACE) - -- start cam modification - cam.Start3D() + -- start cam modification + cam.Start3D() - for i = 1, ListSize do - local v = List[i] - local mode = v[MODE] - local ents = v[ENTS] + for i = 1, ListSize do + local v = List[i] + local mode = v[MODE] + local ents = v[ENTS] - render.SetStencilReferenceValue(i) + render.SetStencilReferenceValue(i) - for j = 1, #ents do - local ent = ents[j] + for j = 1, #ents do + local ent = ents[j] - if not IsValid(ent) - or mode == OUTLINE_MODE_NOTVISIBLE and IsLineOfSightClear(client, ent) - or mode == OUTLINE_MODE_VISIBLE and not IsLineOfSightClear(client, ent) then - continue - end + if + not IsValid(ent) + or mode == OUTLINE_MODE_NOTVISIBLE and IsLineOfSightClear(client, ent) + or mode == OUTLINE_MODE_VISIBLE and not IsLineOfSightClear(client, ent) + then + continue + end - RenderEnt = ent + RenderEnt = ent - ent:DrawModel() - end - end + ent:DrawModel() + end + end - RenderEnt = NULL + RenderEnt = NULL - cam.End3D() - -- end cam modification + cam.End3D() + -- end cam modification - render.SetStencilCompareFunction(STENCIL_EQUAL) + render.SetStencilCompareFunction(STENCIL_EQUAL) - -- start cam modification - cam.Start2D() + -- start cam modification + cam.Start2D() - for i = 1, ListSize do - render.SetStencilReferenceValue(i) + for i = 1, ListSize do + render.SetStencilReferenceValue(i) - surface.SetDrawColor(List[i][COLOR]) - surface.DrawRect(0, 0, w, h) - end + surface.SetDrawColor(List[i][COLOR]) + surface.DrawRect(0, 0, w, h) + end - cam.End2D() - -- end cam modification + cam.End2D() + -- end cam modification - render.SuppressEngineLighting(false) + render.SuppressEngineLighting(false) - cam.IgnoreZ(false) + cam.IgnoreZ(false) - render.SetStencilEnable(false) - -- end stencil modification + render.SetStencilEnable(false) + -- end stencil modification - render.CopyRenderTargetToTexture(drawTexture) + render.CopyRenderTargetToTexture(drawTexture) - render.SetRenderTarget(scene) + render.SetRenderTarget(scene) - copyMat:SetTexture("$basetexture", storeTexture) + copyMat:SetTexture("$basetexture", storeTexture) - render.SetMaterial(copyMat) - render.DrawScreenQuad() + render.SetMaterial(copyMat) + render.DrawScreenQuad() - -- start stencil modification - render.SetStencilEnable(true) + -- start stencil modification + render.SetStencilEnable(true) - render.SetStencilReferenceValue(0) - render.SetStencilCompareFunction(STENCIL_EQUAL) + render.SetStencilReferenceValue(0) + render.SetStencilCompareFunction(STENCIL_EQUAL) - outlineMat:SetTexture("$basetexture", drawTexture) + outlineMat:SetTexture("$basetexture", drawTexture) - render.SetMaterial(outlineMat) + render.SetMaterial(outlineMat) - render.DrawScreenQuadEx(-1, -1, w ,h) - render.DrawScreenQuadEx(-1, 0, w, h) - render.DrawScreenQuadEx(-1, 1, w, h) - render.DrawScreenQuadEx(0, -1, w, h) - render.DrawScreenQuadEx(0, 1, w, h) - render.DrawScreenQuadEx(1, 1, w, h) - render.DrawScreenQuadEx(1, 0, w, h) - render.DrawScreenQuadEx(1, 1, w, h) + render.DrawScreenQuadEx(-1, -1, w, h) + render.DrawScreenQuadEx(-1, 0, w, h) + render.DrawScreenQuadEx(-1, 1, w, h) + render.DrawScreenQuadEx(0, -1, w, h) + render.DrawScreenQuadEx(0, 1, w, h) + render.DrawScreenQuadEx(1, 1, w, h) + render.DrawScreenQuadEx(1, 0, w, h) + render.DrawScreenQuadEx(1, 1, w, h) - render.SetStencilEnable(false) - -- end stencil modification + render.SetStencilEnable(false) + -- end stencil modification end hook.Add("PostDrawEffects", "RenderOutlines", function() - --- - -- @realm client - hook.Run("PreDrawOutlines") + --- + -- @realm client + -- stylua: ignore + hook.Run("PreDrawOutlines") - if ListSize == 0 then return end + if ListSize == 0 then + return + end - Render() + Render() - List, ListSize = {}, 0 + List, ListSize = {}, 0 end) --- @@ -199,6 +208,4 @@ end) -- @2D -- @hook -- @realm client -function GM:PreDrawOutlines() - -end +function GM:PreDrawOutlines() end diff --git a/lua/ttt2/libraries/playermodels.lua b/lua/ttt2/libraries/playermodels.lua index 46d165fcd..ef4b602fd 100644 --- a/lua/ttt2/libraries/playermodels.lua +++ b/lua/ttt2/libraries/playermodels.lua @@ -3,7 +3,7 @@ -- @author Mineotopia if SERVER then - AddCSLuaFile() + AddCSLuaFile() end local pairs = pairs @@ -13,42 +13,43 @@ local utilPrecacheModel = util.PrecacheModel local mathRandom = math.random local function GetPlayerSize(ply) - local bottom, top = ply:GetHull() + local bottom, top = ply:GetHull() - return top - bottom + return top - bottom end --- -- @realm server +-- stylua: ignore local cvCustomModels = CreateConVar("ttt2_use_custom_models", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) local initialDefaultStates = { - selected = { - ["css_phoenix"] = true, - ["css_arctic"] = true, - ["css_guerilla"] = true, - ["css_leet"] = true - }, - hattable = { - ["css_phoenix"] = true, - ["css_arctic"] = true, - ["monk"] = true, - ["female01"] = true, - ["female02"] = true, - ["female03"] = true, - ["female04"] = true, - ["female05"] = true, - ["female06"] = true, - ["male01"] = true, - ["male02"] = true, - ["male03"] = true, - ["male04"] = true, - ["male05"] = true, - ["male06"] = true, - ["male07"] = true, - ["male08"] = true, - ["male09"] = true - } + selected = { + ["css_phoenix"] = true, + ["css_arctic"] = true, + ["css_guerilla"] = true, + ["css_leet"] = true, + }, + hattable = { + ["css_phoenix"] = true, + ["css_arctic"] = true, + ["monk"] = true, + ["female01"] = true, + ["female02"] = true, + ["female03"] = true, + ["female04"] = true, + ["female05"] = true, + ["female06"] = true, + ["male01"] = true, + ["male02"] = true, + ["male03"] = true, + ["male04"] = true, + ["male05"] = true, + ["male06"] = true, + ["male07"] = true, + ["male08"] = true, + ["male09"] = true, + }, } playermodels = playermodels or {} @@ -56,8 +57,8 @@ playermodels = playermodels or {} playermodels.accessName = "Playermodels" playermodels.sqltable = "ttt2_playermodel_pool_changes" playermodels.savingKeys = { - selected = {typ = "bool", default = false}, - hattable = {typ = "bool", default = false} + selected = { typ = "bool", default = false }, + hattable = { typ = "bool", default = false }, } playermodels.accessLevel = TTT2_DATABASE_ACCESS_ANY @@ -67,8 +68,8 @@ playermodels.hasHeadHitBox = {} -- Enums for the states playermodels.state = { - selected = "selected", - hattable = "hattable" + selected = "selected", + hattable = "hattable", } --- @@ -78,7 +79,7 @@ playermodels.state = { -- @param boolean state The selection state, `true` to enable the model -- @realm shared function playermodels.UpdateModel(name, valueName, state) - database.SetValue(playermodels.accessName, name, valueName, state) + database.SetValue(playermodels.accessName, name, valueName, state) end --- @@ -89,13 +90,18 @@ end -- @return[opt] boolean Returns true, if the model is in the selection pool on the server only -- @realm shared function playermodels.IsSelectedModel(name, OnReceiveFunc) - local _, isSelected = database.GetValue(playermodels.accessName, name, "selected", function(databaseExists, value) - OnReceiveFunc(value) - end) - - if SERVER then - return isSelected - end + local _, isSelected = database.GetValue( + playermodels.accessName, + name, + "selected", + function(databaseExists, value) + OnReceiveFunc(value) + end + ) + + if SERVER then + return isSelected + end end --- @@ -106,22 +112,27 @@ end -- @return[opt] boolean Returns true, if the model is hattable on the server only -- @realm shared function playermodels.IsHattableModel(name, OnReceiveFunc) - local _, isHattable = database.GetValue(playermodels.accessName, name, "hattable", function(databaseExists, value) - OnReceiveFunc(value) - end) - - if SERVER then - return isHattable - end + local _, isHattable = database.GetValue( + playermodels.accessName, + name, + "hattable", + function(databaseExists, value) + OnReceiveFunc(value) + end + ) + + if SERVER then + return isHattable + end end --- -- Checks if a provided model has a head hitbox -- @param string name The name of the model --- @return bool Returns true, if the model has a head hitbox +-- @return boolean Returns true, if the model has a head hitbox -- @realm shared function playermodels.HasHeadHitBox(name) - return ttt2net.Get({"playermodels", "hasHeadHitBox"})[name] + return ttt2net.Get({ "playermodels", "hasHeadHitBox" })[name] end --- @@ -132,10 +143,15 @@ end -- @param string identifier A chosen identifier to be able to remove the callback -- @realm shared function playermodels.AddChangeCallback(modelName, valueName, callback, identifier) - database.AddChangeCallback(playermodels.accessName, modelName, valueName, function(accessName, itemName, key, oldValue, newValue) - callback(newValue) - end, - identifier) + database.AddChangeCallback( + playermodels.accessName, + modelName, + valueName, + function(accessName, itemName, key, oldValue, newValue) + callback(newValue) + end, + identifier + ) end --- @@ -145,34 +161,36 @@ end -- @param string identifier The chosen identifier to remove the callback -- @realm shared function playermodels.RemoveChangeCallback(modelName, valueName, identifier) - database.RemoveChangeCallback(playermodels.accessName, modelName, valueName, identifier) + database.RemoveChangeCallback(playermodels.accessName, modelName, valueName, identifier) end --- -- Reset all selected playermodels, hattability and reinitialize the database. -- @realm shared function playermodels.Reset() - database.Reset(playermodels.accessName) + database.Reset(playermodels.accessName) end --- -- Server only functions from here on -if CLIENT then return end +if CLIENT then + return +end --- -- Returns an indexed table with all the models that are in the selection pool. -- @return table An indexed table with all selected player models -- @realm server function playermodels.GetSelectedModels() - local playerModelPoolNames = {} + local playerModelPoolNames = {} - for name in pairs(playerManagerAllValidModels()) do - if playermodels.IsSelectedModel(name) then - playerModelPoolNames[#playerModelPoolNames + 1] = name - end - end + for name in pairs(playerManagerAllValidModels()) do + if playermodels.IsSelectedModel(name) then + playerModelPoolNames[#playerModelPoolNames + 1] = name + end + end - return playerModelPoolNames + return playerModelPoolNames end --- @@ -181,23 +199,30 @@ end -- @internal -- @realm server function playermodels.Initialize() - if not database.Register(playermodels.sqltable, playermodels.accessName, playermodels.savingKeys, playermodels.accessLevel) then - return false - end - - for name in pairs(playerManagerAllValidModels()) do - for key, defaultStates in pairs(initialDefaultStates) do - local state = defaultStates[name] - - if state ~= nil then - database.SetDefaultValue(playermodels.accessName, name, key, state) - end - end - end - - playermodels.InitializeHeadHitBoxes() - - return true + if + not database.Register( + playermodels.sqltable, + playermodels.accessName, + playermodels.savingKeys, + playermodels.accessLevel + ) + then + return false + end + + for name in pairs(playerManagerAllValidModels()) do + for key, defaultStates in pairs(initialDefaultStates) do + local state = defaultStates[name] + + if state ~= nil then + database.SetDefaultValue(playermodels.accessName, name, key, state) + end + end + end + + playermodels.InitializeHeadHitBoxes() + + return true end --- @@ -206,25 +231,25 @@ end -- @note Do not use before @{GM:InitPostEntity} has been called, otherwise the server will crash! -- @realm server function playermodels.InitializeHeadHitBoxes() - local testingEnt = ents.Create("ttt_model_tester") + local testingEnt = ents.Create("ttt_model_tester") - for name, model in pairs(playerManagerAllValidModels()) do - testingEnt:SetModel(model) + for name, model in pairs(playerManagerAllValidModels()) do + testingEnt:SetModel(model) - for i = 1, testingEnt:GetHitBoxCount(0) do - if testingEnt:GetHitBoxHitGroup(i - 1, 0) == HITGROUP_HEAD then - playermodels.hasHeadHitBox[name] = true + for i = 1, testingEnt:GetHitBoxCount(0) do + if testingEnt:GetHitBoxHitGroup(i - 1, 0) == HITGROUP_HEAD then + playermodels.hasHeadHitBox[name] = true - break - end + break + end - playermodels.hasHeadHitBox[name] = false - end - end + playermodels.hasHeadHitBox[name] = false + end + end - ttt2net.Set({"playermodels", "hasHeadHitBox"}, {type = "table"}, playermodels.hasHeadHitBox) + ttt2net.Set({ "playermodels", "hasHeadHitBox" }, { type = "table" }, playermodels.hasHeadHitBox) - testingEnt:Remove() + testingEnt:Remove() end --- @@ -234,9 +259,9 @@ end -- @internal -- @realm server function playermodels.PrecacheModels() - for _, model in pairs(playerManagerAllValidModels()) do - utilPrecacheModel(model) - end + for _, model in pairs(playerManagerAllValidModels()) do + utilPrecacheModel(model) + end end --- @@ -244,17 +269,38 @@ end -- @return Model model The selected playermodel -- @realm server function playermodels.GetRandomPlayerModel() - local availableModels = playermodels.GetSelectedModels() - local sizeAvailableModels = #availableModels + local availableModels = playermodels.GetSelectedModels() + local sizeAvailableModels = #availableModels + + if cvCustomModels:GetBool() and sizeAvailableModels > 0 then + local modelPaths = playerManagerAllValidModels() + local randomModel = availableModels[mathRandom(sizeAvailableModels)] - if cvCustomModels:GetBool() and sizeAvailableModels > 0 then - local modelPaths = playerManagerAllValidModels() - local randomModel = availableModels[mathRandom(sizeAvailableModels)] + return Model(modelPaths[randomModel]) + else + return Model(playermodels.fallbackModel) + end +end - return Model(modelPaths[randomModel]) - else - return Model(playermodels.fallbackModel) - end +--- +-- Finds the location and angle of the hat. +-- @param Player ply The player whose hat position should be found +-- @return Vector pos The location of the attach point. +-- @return Angle ang The angle of the attach point. +-- @realm shared +function playermodels.GetHatPosition(ply) + local pos, ang + if IsValid(ply) then + local bone = ply:LookupBone("ValveBiped.Bip01_Head1") + if bone then + pos, ang = ply:GetBonePosition(bone) + else + pos, ang = ply:GetPos(), ply:GetAngles() + pos.z = pos.z + GetPlayerSize(ply).z + end + end + + return pos, ang end --- @@ -262,21 +308,30 @@ end -- allows a hat. Use the Filter function for this. -- @param Player ply The player that should receive the hat -- @param[opt] function Filter The filter function that has to return true to apply a hat +-- @param[opt] string hatName The class name of the hat entity, if different from the detective's hat. -- @realm server -function playermodels.ApplyPlayerHat(ply, Filter) - if IsValid(ply.hat) or (isfunction(Filter) and not Filter(ply)) then return end +function playermodels.ApplyPlayerHat(ply, Filter, hatName) + if IsValid(ply.hat) or (isfunction(Filter) and not Filter(ply)) then + return + end - local hat = ents.Create("ttt_hat_deerstalker") + local hat = ents.Create(hatName or "ttt_hat_deerstalker") - if not IsValid(hat) then return end + if not IsValid(hat) then + return + end - hat:SetPos(ply:GetPos() + Vector(0, 0, GetPlayerSize(ply).z)) - hat:SetAngles(ply:GetAngles()) - hat:SetParent(ply) + local pos, ang = playermodels.GetHatPosition(ply) + hat:SetPos(pos) + hat:SetAngles(ang) + hat:SetParent(ply) - hat:Spawn() + if isfunction(hat.EquipTo) then + hat:EquipTo(ply) + end + ply.hat = hat - ply.hat = hat + hat:Spawn() end --- @@ -284,11 +339,13 @@ end -- @param Player ply The player whose hat should be removed -- @realm server function playermodels.RemovePlayerHat(ply) - if not IsValid(ply.hat) then return end + if not IsValid(ply.hat) then + return + end - SafeRemoveEntity(ply.hat) + SafeRemoveEntity(ply.hat) - ply.hat = nil + ply.hat = nil end --- @@ -297,5 +354,5 @@ end -- @return boolean Returns true if the player's model can have a detective hat -- @realm server function playermodels.PlayerCanHaveHat(ply) - return playermodels.IsHattableModel(playerManagerTranslateToPlayerModelName(ply:GetModel())) + return playermodels.IsHattableModel(playerManagerTranslateToPlayerModelName(ply:GetModel())) end diff --git a/lua/ttt2/libraries/plyspawn.lua b/lua/ttt2/libraries/plyspawn.lua index 97cf96067..ba9d44abf 100644 --- a/lua/ttt2/libraries/plyspawn.lua +++ b/lua/ttt2/libraries/plyspawn.lua @@ -3,24 +3,26 @@ -- @author Mineotopia -- @module spawn -if CLIENT then return end -- this is a serverside-ony module +if CLIENT then + return +end -- this is a serverside-ony module local Vector = Vector local table = table local mathSin = math.sin local mathCos = math.cos -local spawnPointVariations = {Vector(0, 0, 0)} +local spawnPointVariations = { Vector(0, 0, 0) } for i = 0, 360, 22.5 do - spawnPointVariations[#spawnPointVariations + 1] = Vector(mathCos(i), mathSin(i), 0) + spawnPointVariations[#spawnPointVariations + 1] = Vector(mathCos(i), mathSin(i), 0) end -- returns the player size as a vector local function GetPlayerSize(ply) - local bottom, top = ply:GetHull() + local bottom, top = ply:GetHull() - return top - bottom + return top - bottom end plyspawn = plyspawn or {} @@ -36,76 +38,81 @@ plyspawn = plyspawn or {} -- @return boolean Returns if the spawn point is safe -- @realm server function plyspawn.IsSpawnPointSafe(ply, pos, force, filter) - local sizePlayer = GetPlayerSize(ply) - - if not util.IsInWorld(pos) then - return false - end - - filter = istable(filter) and filter or {filter} - - -- the center pos is slightly shifted to the top to prevent ground - -- collisions in the walltrace - local posCenter = pos + Vector(0, 0, 0.525 * sizePlayer.z) - - -- Make sure there is enough space around the player - local traceWalls = util.TraceHull({ - start = posCenter, - endpos = posCenter, - mins = -0.5 * sizePlayer, - maxs = 0.5 * sizePlayer, - filter = function(ent) - if not IsValid(ent) or table.HasValue(filter, ent) then - return false - end - - if ent:HasPassableCollisionGrup() then - return false - end - - return true - end, - mask = MASK_SOLID - }) - - if traceWalls.Hit then - return false - end - - -- make sure the new position is on the ground - local traceGround = util.TraceLine({ - start = posCenter, - endpos = posCenter - Vector(0, 0, sizePlayer.z), - filter = player.GetAll(), - mask = MASK_SOLID - }) - - if not traceGround.Hit then - return false - end - - local blockingEntities = ents.FindInBox( - pos + Vector(-0.5 * sizePlayer.x, -0.5 * sizePlayer.y, 0), - pos + sizePlayer - ) - - for i = 1, #blockingEntities do - local blockingEntity = blockingEntities[i] - - if not IsValid(blockingEntity) or not blockingEntity:IsPlayer() - or not blockingEntity:IsTerror() or not blockingEntity:Alive() - then continue end - - if table.HasValue(filter, blockingEntity) then continue end - - if force then - blockingEntity:Kill() - else - return false - end - end - - return true + local sizePlayer = GetPlayerSize(ply) + + if not util.IsInWorld(pos) then + return false + end + + filter = istable(filter) and filter or { filter } + + -- the center pos is slightly shifted to the top to prevent ground + -- collisions in the walltrace + local posCenter = pos + Vector(0, 0, 0.525 * sizePlayer.z) + + -- Make sure there is enough space around the player + local traceWalls = util.TraceHull({ + start = posCenter, + endpos = posCenter, + mins = -0.5 * sizePlayer, + maxs = 0.5 * sizePlayer, + filter = function(ent) + if not IsValid(ent) or table.HasValue(filter, ent) then + return false + end + + if ent:HasPassableCollisionGrup() then + return false + end + + return true + end, + mask = MASK_SOLID, + }) + + if traceWalls.Hit then + return false + end + + -- make sure the new position is on the ground + local traceGround = util.TraceLine({ + start = posCenter, + endpos = posCenter - Vector(0, 0, sizePlayer.z), + filter = player.GetAll(), + mask = MASK_SOLID, + }) + + if not traceGround.Hit then + return false + end + + local blockingEntities = + ents.FindInBox(pos + Vector(-0.5 * sizePlayer.x, -0.5 * sizePlayer.y, 0), pos + sizePlayer) + + for i = 1, #blockingEntities do + local blockingEntity = blockingEntities[i] + + if + not IsValid(blockingEntity) + or not blockingEntity:IsPlayer() + or not blockingEntity:IsTerror() + or not blockingEntity:Alive() + then + continue + end + + if table.HasValue(filter, blockingEntity) then + continue + end + + if force then + blockingEntity:Kill() + else + return false + end + end + + return true end --- @@ -117,18 +124,20 @@ end -- @return table A table of position vectors -- @realm server function plyspawn.GetSpawnPointsAroundSpawn(ply, pos, radiusMultiplier) - local sizePlayer = GetPlayerSize(ply) + local sizePlayer = GetPlayerSize(ply) - if not pos then return {} end + if not pos then + return {} + end - local boundsPlayer = Vector(sizePlayer.x, sizePlayer.y, 0) * 1.5 * (radiusMultiplier or 1) - local positions = {} + local boundsPlayer = Vector(sizePlayer.x, sizePlayer.y, 0) * 1.5 * (radiusMultiplier or 1) + local positions = {} - for i = 1, #spawnPointVariations do - positions[i] = pos + spawnPointVariations[i] * boundsPlayer - end + for i = 1, #spawnPointVariations do + positions[i] = pos + spawnPointVariations[i] * boundsPlayer + end - return positions + return positions end --- @@ -141,15 +150,17 @@ end -- @return Vector|nil Returns the safe spawn position, nil if none was found -- @realm server function plyspawn.MakeSpawnPointSafe(ply, unsafePos, radiusMultiplier) - local spawnPoints = plyspawn.GetSpawnPointsAroundSpawn(ply, unsafePos, radiusMultiplier) + local spawnPoints = plyspawn.GetSpawnPointsAroundSpawn(ply, unsafePos, radiusMultiplier) - for i = 1, #spawnPoints do - local spawnPoint = spawnPoints[i] + for i = 1, #spawnPoints do + local spawnPoint = spawnPoints[i] - if not plyspawn.IsSpawnPointSafe(ply, spawnPoint, false) then continue end + if not plyspawn.IsSpawnPointSafe(ply, spawnPoint, false) then + continue + end - return spawnPoint - end + return spawnPoint + end end --- @@ -157,7 +168,7 @@ end -- @return table Returns a table of unsafe spawn points -- @realm server function plyspawn.GetPlayerSpawnPoints() - return entspawnscript.GetSpawnsForSpawnType(SPAWN_TYPE_PLAYER)[PLAYER_TYPE_RANDOM] + return entspawnscript.GetSpawnsForSpawnType(SPAWN_TYPE_PLAYER)[PLAYER_TYPE_RANDOM] end --- @@ -169,59 +180,73 @@ end -- @return boolean|table A safe spawn point, false if no spawn point was found on the map -- @realm server function plyspawn.GetRandomSafePlayerSpawnPoint(ply) - local spawnPoints = plyspawn.GetPlayerSpawnPoints() + local spawnPoints = plyspawn.GetPlayerSpawnPoints() - if not spawnPoints or #spawnPoints == 0 then - Error("[TTT2][PLYSPAWN] No spawn points found! Make sure there is at least one spawn point on the map.\n") + if not spawnPoints or #spawnPoints == 0 then + Dev( + 1, + "[TTT2][PLYSPAWN] No spawn points found! Make sure there is at least one spawn point on the map.\n" + ) - return false - end + return false + end - -- the table should be shuffled for each spawn point calculation to improve randomness - table.Shuffle(spawnPoints) + -- the table should be shuffled for each spawn point calculation to improve randomness + table.Shuffle(spawnPoints) - if not IsValid(ply) or not ply:IsTerror() then - return spawnPoints[1] - end + if not IsValid(ply) or not ply:IsTerror() then + return spawnPoints[1] + end - -- Optimistic attempt: assume there are sufficient spawns for all and one is free - for i = 1, #spawnPoints do - local spawnPoint = spawnPoints[i] + -- Optimistic attempt: assume there are sufficient spawns for all and one is free + for i = 1, #spawnPoints do + local spawnPoint = spawnPoints[i] - if not plyspawn.IsSpawnPointSafe(ply, spawnPoint.pos, false) then continue end + if not plyspawn.IsSpawnPointSafe(ply, spawnPoint.pos, false) then + continue + end - return spawnPoint - end + return spawnPoint + end - -- That did not work, so now look around spawns - local pickedSpawnPoint + -- That did not work, so now look around spawns + local pickedSpawnPoint - for i = 1, #spawnPoints do - pickedSpawnPoint = spawnPoints[i] + for i = 1, #spawnPoints do + pickedSpawnPoint = spawnPoints[i] - local riggedSpawnPoint = plyspawn.MakeSpawnPointSafe(ply, pickedSpawnPoint.pos) + local riggedSpawnPoint = plyspawn.MakeSpawnPointSafe(ply, pickedSpawnPoint.pos) - if not riggedSpawnPoint then continue end + if not riggedSpawnPoint then + continue + end - ErrorNoHalt("TTT2 WARNING: Map has too few spawn points, using a riggedSpawnPoints spawn for " .. tostring(ply) .. "\n") + Dev( + 1, + "TTT2 WARNING: Map has too few spawn points, using a riggedSpawnPoints spawn for " + .. tostring(ply) + .. "\n" + ) - -- this is an old TTT flag that I will keep for compatibilities sake - GAMEMODE.HaveRiggedSpawn = true + -- this is an old TTT flag that I will keep for compatibilities sake + GAMEMODE.HaveRiggedSpawn = true - return { - pos = riggedSpawnPoint, - ang = pickedSpawnPoint.ang - } - end + return { + pos = riggedSpawnPoint, + ang = pickedSpawnPoint.ang, + } + end - -- Last attempt, force one - for i = 1, #spawnPoints do - local spawnPoint = spawnPoints[i] + -- Last attempt, force one + for i = 1, #spawnPoints do + local spawnPoint = spawnPoints[i] - if not plyspawn.IsSpawnPointSafe(ply, spawnPoint.pos, true) then continue end + if not plyspawn.IsSpawnPointSafe(ply, spawnPoint.pos, true) then + continue + end - return spawnPoint - end + return spawnPoint + end - return pickedSpawnPoint + return pickedSpawnPoint end diff --git a/lua/ttt2/libraries/pon.lua b/lua/ttt2/libraries/pon.lua index 01787878e..afcdd2329 100644 --- a/lua/ttt2/libraries/pon.lua +++ b/lua/ttt2/libraries/pon.lua @@ -5,12 +5,12 @@ DEVELOPMENTAL VERSION; VERSION 1.2.2 Copyright thelastpenguin™ - You may use this for any purpose as long as: - - You don't remove this copyright notice. - - You don't claim this to be your own. - - You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this. - If you modify the code for any purpose, the above still applies to the modified code. - The author is not held responsible for any damages incured from the use of pon, you use it at your own risk. + You may use this for any purpose as long as: + - You don't remove this copyright notice. + - You don't claim this to be your own. + - You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this. + If you modify the code for any purpose, the above still applies to the modified code. + The author is not held responsible for any damages incured from the use of pon, you use it at your own risk. DATA TYPES SUPPORTED: - tables - k,v - pointers - strings - k,v - pointers @@ -36,7 +36,7 @@ Changes (@saibotk): ]] if SERVER then - AddCSLuaFile() + AddCSLuaFile() end pon = pon or {} @@ -46,380 +46,393 @@ local tonumber = tonumber local format = string.format do - local encode = {} - local cacheSize = 0 - - encode['table'] = function(self, tbl, output, cache) - if (cache[tbl]) then - output[#output + 1] = format('(%x)', cache[tbl]) - - return - else - cacheSize = cacheSize + 1 - cache[tbl] = cacheSize - end - - local first = next(tbl, nil) - local predictedNumeric = 1 - - -- starts with a numeric dealio - if first == 1 then - output[#output + 1] = '{' - - for k, v in next, tbl do - if k ~= predictedNumeric then break end - - predictedNumeric = predictedNumeric + 1 - local tv = type(v) - - if tv == 'string' then - local pid = cache[v] - - if pid then - output[#output + 1] = format('(%x)', pid) - else - cacheSize = cacheSize + 1 - cache[v] = cacheSize - self.string(self, v, output, cache) - end - else - self[tv](self, v, output, cache) - end - end - - predictedNumeric = predictedNumeric - 1 - else - predictedNumeric = nil - end - - if predictedNumeric == nil then - output[#output + 1] = '[' -- no array component - else - output[#output + 1] = '~' -- array component came first so shit needs to happen - end - - for k, v in next, tbl, predictedNumeric do - local tk, tv = type(k), type(v) - - -- WRITE KEY - if tk == 'string' then - local pid = cache[k] - - if (pid) then - output[#output + 1] = format('(%x)', pid) - else - cacheSize = cacheSize + 1 - cache[k] = cacheSize - self.string(self, k, output, cache) - end - else - self[tk](self, k, output, cache) - end - - -- WRITE VALUE - if (tv == 'string') then - local pid = cache[v] - - if (pid) then - output[#output + 1] = format('(%x)', pid) - else - cacheSize = cacheSize + 1 - cache[v] = cacheSize - self.string(self, v, output, cache) - end - else - self[tv](self, v, output, cache) - end - end - - output[#output + 1] = '}' - end - - -- ENCODE STRING - local gsub = string.gsub - - encode['string'] = function(self, str, output) - - local estr, countOccurences = gsub(str, ';', "\\;") - - if (countOccurences == 0) then - output[#output + 1] = '\'' .. str .. ';' - else - output[#output + 1] = '"' .. estr .. '";' - end - end - - -- ENCODE NUMBER - encode['number'] = function(self, num, output) - if num % 1 == 0 then - if num < 0 then - output[#output + 1] = format('x%x;', -num) - else - output[#output + 1] = format('X%x;', num) - end - else - output[#output + 1] = tonumber(num) .. ';' - end - end - - -- ENCODE BOOLEAN - encode['boolean'] = function(self, val, output) - output[#output + 1] = val and 't' or 'f' - end - - -- ENCODE VECTOR - encode['Vector'] = function(self, val, output) - output[#output + 1] = ('v' .. val.x .. ',' .. val.y) .. (',' .. val.z .. ';') - end - - -- ENCODE ANGLE - encode['Angle'] = function(self, val, output) - output[#output + 1] = ('a' .. val.p .. ',' .. val.y) .. (',' .. val.r .. ';') - end - - encode['Entity'] = function(self, val, output) - output[#output + 1] = 'E' .. (IsValid(val) and (val:EntIndex() .. ';') or '#') - end - - encode['Player'] = encode['Entity'] - encode['Vehicle'] = encode['Entity'] - encode['Weapon'] = encode['Entity'] - encode['NPC'] = encode['Entity'] - encode['NextBot'] = encode['Entity'] - encode['PhysObj'] = encode['Entity'] - - encode['nil'] = function() - output[#output + 1] = '?' - end - - encode.__index = function(key) - ErrorNoHalt('Type: ' .. key .. ' can not be encoded. Encoded as as pass-over value.') - - return encode['nil'] - end - - do - local concat = table.concat - - --- - -- @param table tbl - -- @realm shared - function pon.encode(tbl) - local finalOutput = {} - cacheSize = 0 - encode['table'](encode, tbl, finalOutput, {}) - local res = concat(finalOutput) - - return res - end - end + local encode = {} + local cacheSize = 0 + + encode["table"] = function(self, tbl, output, cache) + if cache[tbl] then + output[#output + 1] = format("(%x)", cache[tbl]) + + return + else + cacheSize = cacheSize + 1 + cache[tbl] = cacheSize + end + + local first = next(tbl, nil) + local predictedNumeric = 1 + + -- starts with a numeric dealio + if first == 1 then + output[#output + 1] = "{" + + for k, v in next, tbl do + if k ~= predictedNumeric then + break + end + + predictedNumeric = predictedNumeric + 1 + local tv = type(v) + + if tv == "string" then + local pid = cache[v] + + if pid then + output[#output + 1] = format("(%x)", pid) + else + cacheSize = cacheSize + 1 + cache[v] = cacheSize + self.string(self, v, output, cache) + end + else + self[tv](self, v, output, cache) + end + end + + predictedNumeric = predictedNumeric - 1 + else + predictedNumeric = nil + end + + if predictedNumeric == nil then + output[#output + 1] = "[" -- no array component + else + output[#output + 1] = "~" -- array component came first so shit needs to happen + end + + for k, v in next, tbl, predictedNumeric do + local tk, tv = type(k), type(v) + + -- WRITE KEY + if tk == "string" then + local pid = cache[k] + + if pid then + output[#output + 1] = format("(%x)", pid) + else + cacheSize = cacheSize + 1 + cache[k] = cacheSize + self.string(self, k, output, cache) + end + else + self[tk](self, k, output, cache) + end + + -- WRITE VALUE + if tv == "string" then + local pid = cache[v] + + if pid then + output[#output + 1] = format("(%x)", pid) + else + cacheSize = cacheSize + 1 + cache[v] = cacheSize + self.string(self, v, output, cache) + end + else + self[tv](self, v, output, cache) + end + end + + output[#output + 1] = "}" + end + + -- ENCODE STRING + local gsub = string.gsub + + encode["string"] = function(self, str, output) + local estr, countOccurences = gsub(str, ";", "\\;") + + if countOccurences == 0 then + output[#output + 1] = "'" .. str .. ";" + else + output[#output + 1] = "\"" .. estr .. "\";" + end + end + + -- ENCODE NUMBER + encode["number"] = function(self, num, output) + if num % 1 == 0 then + if num < 0 then + output[#output + 1] = format("x%x;", -num) + else + output[#output + 1] = format("X%x;", num) + end + else + output[#output + 1] = tonumber(num) .. ";" + end + end + + -- ENCODE BOOLEAN + encode["boolean"] = function(self, val, output) + output[#output + 1] = val and "t" or "f" + end + + -- ENCODE VECTOR + encode["Vector"] = function(self, val, output) + output[#output + 1] = ("v" .. val.x .. "," .. val.y) .. ("," .. val.z .. ";") + end + + -- ENCODE ANGLE + encode["Angle"] = function(self, val, output) + output[#output + 1] = ("a" .. val.p .. "," .. val.y) .. ("," .. val.r .. ";") + end + + encode["Entity"] = function(self, val, output) + output[#output + 1] = "E" .. (IsValid(val) and (val:EntIndex() .. ";") or "#") + end + + encode["Player"] = encode["Entity"] + encode["Vehicle"] = encode["Entity"] + encode["Weapon"] = encode["Entity"] + encode["NPC"] = encode["Entity"] + encode["NextBot"] = encode["Entity"] + encode["PhysObj"] = encode["Entity"] + + encode["nil"] = function(self, val, output) + output[#output + 1] = "?" + end + + encode.__index = function(key) + ErrorNoHaltWithStack( + "Type: " .. key .. " can not be encoded. Encoded as as pass-over value." + ) + + return encode["nil"] + end + + do + local concat = table.concat + + --- + -- @param table tbl + -- @realm shared + function pon.encode(tbl) + local finalOutput = {} + cacheSize = 0 + encode["table"](encode, tbl, finalOutput, {}) + local res = concat(finalOutput) + + return res + end + end end do - local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode - local Vector, Angle, Entity = Vector, Angle, Entity - local decode = {} - - decode['{'] = function(self, index, str, cache) - local cur = {} - cache[#cache + 1] = cur - local k, v, tk, tv = 1, nil, nil, nil - - while (true) do - tv = sub(str, index, index) - - if (not tv or tv == '~') then - index = index + 1 - break - end - - if (tv == '}') then return index + 1, cur end - -- READ THE VALUE - index = index + 1 - index, v = self[tv](self, index, str, cache) - cur[k] = v - k = k + 1 - end - - while (true) do - tk = sub(str, index, index) - - if (not tk or tk == '}') then - index = index + 1 - break - end - - -- READ THE KEY - index = index + 1 - index, k = self[tk](self, index, str, cache) - -- READ THE VALUE - tv = sub(str, index, index) - index = index + 1 - index, v = self[tv](self, index, str, cache) - cur[k] = v - end - - return index, cur - end - - decode['['] = function(self, index, str, cache) - local cur = {} - cache[#cache + 1] = cur - local k, v, tk, tv = 1, nil, nil, nil - - while (true) do - tk = sub(str, index, index) - - if (not tk or tk == '}') then - index = index + 1 - break - end - - -- READ THE KEY - index = index + 1 - index, k = self[tk](self, index, str, cache) - if not k then continue end - -- READ THE VALUE - tv = sub(str, index, index) - index = index + 1 - - if not self[tv] then - print('did not find type: ' .. tv) - end - - index, v = self[tv](self, index, str, cache) - cur[k] = v - end - - return index, cur - end - - -- STRING - decode['"'] = function(self, index, str, cache) - local finish = find(str, '";', index, true) - local res = gsub(sub(str, index, finish - 1), '\\;', ';') - index = finish + 2 - cache[#cache + 1] = res - - return index, res - end - - -- STRING NO ESCAPING NEEDED - decode['\''] = function(self, index, str, cache) - local finish = find(str, ';', index, true) - local res = sub(str, index, finish - 1) - index = finish + 1 - cache[#cache + 1] = res - - return index, res - end - - -- NUMBER - decode['n'] = function(self, index, str, cache) - index = index - 1 - local finish = find(str, ';', index, true) - local num = tonumber(sub(str, index, finish - 1)) - index = finish + 1 - - return index, num - end - - decode['0'] = decode['n'] - decode['1'] = decode['n'] - decode['2'] = decode['n'] - decode['3'] = decode['n'] - decode['4'] = decode['n'] - decode['5'] = decode['n'] - decode['6'] = decode['n'] - decode['7'] = decode['n'] - decode['8'] = decode['n'] - decode['9'] = decode['n'] - decode['-'] = decode['n'] - - -- positive hex - decode['X'] = function(self, index, str, cache) - local finish = find(str, ';', index, true) - local num = tonumber(sub(str, index, finish - 1), 16) - index = finish + 1 - - return index, num - end - - -- negative hex - decode['x'] = function(self, index, str, cache) - local finish = find(str, ';', index, true) - local num = -tonumber(sub(str, index, finish - 1), 16) - index = finish + 1 - - return index, num - end - - -- POINTER - decode['('] = function(self, index, str, cache) - local finish = find(str, ')', index, true) - local num = tonumber(sub(str, index, finish - 1), 16) - index = finish + 1 - - return index, cache[num] - end - - -- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO. - decode['t'] = function(self, index) return index, true end - decode['f'] = function(self, index) return index, false end - - -- VECTOR - decode['v'] = function(self, index, str, cache) - local finish = find(str, ';', index, true) - local vecStr = sub(str, index, finish - 1) - index = finish + 1 -- update the index. - local segs = Explode(',', vecStr, false) - - return index, Vector(tonumber(segs[1]), tonumber(segs[2]), tonumber(segs[3])) - end - - -- ANGLE - decode['a'] = function(self, index, str, cache) - local finish = find(str, ';', index, true) - local angStr = sub(str, index, finish - 1) - index = finish + 1 -- update the index. - local segs = Explode(',', angStr, false) - - return index, Angle(tonumber(segs[1]), tonumber(segs[2]), tonumber(segs[3])) - end - - -- ENTITY - decode['E'] = function(self, index, str, cache) - if (str[index] == '#') then - index = index + 1 - - return index, NULL - else - local finish = find(str, ';', index, true) - local num = tonumber(sub(str, index, finish - 1)) - index = finish + 1 - - return index, Entity(num) - end - end - - -- PLAYER - decode['P'] = function(self, index, str, cache) - local finish = find(str, ';', index, true) - local num = tonumber(sub(str, index, finish - 1)) - index = finish + 1 - - return index, Entity(num) or NULL - end - - -- NIL - decode['?'] = function(self, index, str, cache) return index + 1, nil end - - --- - -- @param any data - -- @realm shared - function pon.decode(data) - local _, res = decode[sub(data, 1, 1)](decode, 2, data, {}) - - return res - end + local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode + local Vector, Angle, Entity = Vector, Angle, Entity + local decode = {} + + decode["{"] = function(self, index, str, cache) + local cur = {} + cache[#cache + 1] = cur + local k, v, tk, tv = 1, nil, nil, nil + + while true do + tv = sub(str, index, index) + + if not tv or tv == "~" then + index = index + 1 + break + end + + if tv == "}" then + return index + 1, cur + end + -- READ THE VALUE + index = index + 1 + index, v = self[tv](self, index, str, cache) + cur[k] = v + k = k + 1 + end + + while true do + tk = sub(str, index, index) + + if not tk or tk == "}" then + index = index + 1 + break + end + + -- READ THE KEY + index = index + 1 + index, k = self[tk](self, index, str, cache) + -- READ THE VALUE + tv = sub(str, index, index) + index = index + 1 + index, v = self[tv](self, index, str, cache) + cur[k] = v + end + + return index, cur + end + + decode["["] = function(self, index, str, cache) + local cur = {} + cache[#cache + 1] = cur + local k, v, tk, tv = 1, nil, nil, nil + + while true do + tk = sub(str, index, index) + + if not tk or tk == "}" then + index = index + 1 + break + end + + -- READ THE KEY + index = index + 1 + index, k = self[tk](self, index, str, cache) + if not k then + continue + end + -- READ THE VALUE + tv = sub(str, index, index) + index = index + 1 + + if not self[tv] then + Dev(1, "did not find type: " .. tv) + end + + index, v = self[tv](self, index, str, cache) + cur[k] = v + end + + return index, cur + end + + -- STRING + decode["\""] = function(self, index, str, cache) + local finish = find(str, "\";", index, true) + local res = gsub(sub(str, index, finish - 1), "\\;", ";") + index = finish + 2 + cache[#cache + 1] = res + + return index, res + end + + -- STRING NO ESCAPING NEEDED + decode["'"] = function(self, index, str, cache) + local finish = find(str, ";", index, true) + local res = sub(str, index, finish - 1) + index = finish + 1 + cache[#cache + 1] = res + + return index, res + end + + -- NUMBER + decode["n"] = function(self, index, str, cache) + index = index - 1 + local finish = find(str, ";", index, true) + local num = tonumber(sub(str, index, finish - 1)) + index = finish + 1 + + return index, num + end + + decode["0"] = decode["n"] + decode["1"] = decode["n"] + decode["2"] = decode["n"] + decode["3"] = decode["n"] + decode["4"] = decode["n"] + decode["5"] = decode["n"] + decode["6"] = decode["n"] + decode["7"] = decode["n"] + decode["8"] = decode["n"] + decode["9"] = decode["n"] + decode["-"] = decode["n"] + + -- positive hex + decode["X"] = function(self, index, str, cache) + local finish = find(str, ";", index, true) + local num = tonumber(sub(str, index, finish - 1), 16) + index = finish + 1 + + return index, num + end + + -- negative hex + decode["x"] = function(self, index, str, cache) + local finish = find(str, ";", index, true) + local num = -tonumber(sub(str, index, finish - 1), 16) + index = finish + 1 + + return index, num + end + + -- POINTER + decode["("] = function(self, index, str, cache) + local finish = find(str, ")", index, true) + local num = tonumber(sub(str, index, finish - 1), 16) + index = finish + 1 + + return index, cache[num] + end + + -- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO. + decode["t"] = function(self, index) + return index, true + end + decode["f"] = function(self, index) + return index, false + end + + -- VECTOR + decode["v"] = function(self, index, str, cache) + local finish = find(str, ";", index, true) + local vecStr = sub(str, index, finish - 1) + index = finish + 1 -- update the index. + local segs = Explode(",", vecStr, false) + + return index, Vector(tonumber(segs[1]), tonumber(segs[2]), tonumber(segs[3])) + end + + -- ANGLE + decode["a"] = function(self, index, str, cache) + local finish = find(str, ";", index, true) + local angStr = sub(str, index, finish - 1) + index = finish + 1 -- update the index. + local segs = Explode(",", angStr, false) + + return index, Angle(tonumber(segs[1]), tonumber(segs[2]), tonumber(segs[3])) + end + + -- ENTITY + decode["E"] = function(self, index, str, cache) + if str[index] == "#" then + index = index + 1 + + return index, NULL + else + local finish = find(str, ";", index, true) + local num = tonumber(sub(str, index, finish - 1)) + index = finish + 1 + + return index, Entity(num) + end + end + + -- PLAYER + decode["P"] = function(self, index, str, cache) + local finish = find(str, ";", index, true) + local num = tonumber(sub(str, index, finish - 1)) + index = finish + 1 + + return index, Entity(num) or NULL + end + + -- NIL + decode["?"] = function(self, index, str, cache) + return index + 1, nil + end + + --- + -- @param any data + -- @realm shared + function pon.decode(data) + local _, res = decode[sub(data, 1, 1)](decode, 2, data, {}) + + return res + end end diff --git a/lua/ttt2/libraries/roles.lua b/lua/ttt2/libraries/roles.lua index 2d12756b0..bc288c5a1 100644 --- a/lua/ttt2/libraries/roles.lua +++ b/lua/ttt2/libraries/roles.lua @@ -6,10 +6,11 @@ local baseclass = baseclass local pairs = pairs +-- stylua: ignore local CreateConVar = CreateConVar if SERVER then - AddCSLuaFile() + AddCSLuaFile() end roles = {} @@ -22,15 +23,15 @@ roles.roleList = {} -- @return table t target table -- @realm shared local function TableInherit(t, base) - for k, v in pairs(base) do - if t[k] == nil then - t[k] = v - elseif k ~= "BaseClass" and istable(t[k]) and istable(v[k]) then - TableInherit(t[k], v) - end - end - - return t + for k, v in pairs(base) do + if t[k] == nil then + t[k] = v + elseif k ~= "BaseClass" and istable(t[k]) and istable(v[k]) then + TableInherit(t[k], v) + end + end + + return t end --- @@ -40,21 +41,21 @@ end -- @return boolean returns whether name is based on base -- @realm shared function roles.IsBasedOn(name, base) - local t = roles.GetStored(name) + local t = roles.GetStored(name) - if not t then - return false - end + if not t then + return false + end - if t.Base == name then - return false - end + if t.Base == name then + return false + end - if t.Base == base then - return true - end + if t.Base == base then + return true + end - return roles.IsBasedOn(t.Base, base) + return roles.IsBasedOn(t.Base, base) end --- @@ -63,17 +64,17 @@ end -- @todo global vars list -- @realm shared local function SetupGlobals(roleData) - print("[TTT2][ROLE] Setting up '" .. roleData.name .. "' role...") + Dev(1, "[TTT2][ROLE] Setting up '" .. roleData.name .. "' role...") - local upStr = string.upper(roleData.name) + local upStr = string.upper(roleData.name) - if _G[upStr] then - print("[TTT2][ROLE] Overwriting already existing global '" .. upStr .. "' ...") - end + if _G[upStr] then + Dev(1, "[TTT2][ROLE] Overwriting already existing global '" .. upStr .. "' ...") + end - _G["ROLE_" .. upStr] = roleData.index - _G[upStr] = roleData - _G["SHOP_FALLBACK_" .. upStr] = roleData.name + _G["ROLE_" .. upStr] = roleData.index + _G[upStr] = roleData + _G["SHOP_FALLBACK_" .. upStr] = roleData.name end --- @@ -82,97 +83,108 @@ end -- @todo ConVar list -- @realm shared local function SetupData(roleData) - print("[TTT2][ROLE] Adding '" .. roleData.name .. "' role...") - - local conVarData = roleData.conVarData or {} - - -- shared - if not roleData.notSelectable then - if CLIENT then - if conVarData.togglable then - --- - -- @realm client - CreateConVar("ttt_avoid_" .. roleData.name, "0", {FCVAR_ARCHIVE, FCVAR_USERINFO}) - end - else -- SERVER - --- - -- @realm server - CreateConVar("ttt_" .. roleData.name .. "_pct", tostring(conVarData.pct or 1), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - CreateConVar("ttt_" .. roleData.name .. "_max", tostring(conVarData.maximum or 1), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - CreateConVar("ttt_" .. roleData.name .. "_min_players", tostring(conVarData.minPlayers or 1), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - if not roleData.builtin then - --- - -- @realm server - CreateConVar("ttt_" .. roleData.name .. "_karma_min", tostring(conVarData.minKarma or 0), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - CreateConVar("ttt_" .. roleData.name .. "_random", tostring(conVarData.random or 100), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - CreateConVar("ttt_" .. roleData.name .. "_enabled", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - end - end - end - - --- - -- @realm shared - CreateConVar("ttt_" .. roleData.name .. "_traitor_button", tostring(conVarData.traitorButton or 0), SERVER and {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED} or FCVAR_REPLICATED) - - if SERVER then - --- - -- @realm server - CreateConVar("ttt_" .. roleData.abbr .. "_credits_starting", tostring(conVarData.credits or 0), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - CreateConVar("ttt_" .. roleData.abbr .. "_credits_award_dead_enb", tostring(conVarData.creditsAwardDeadEnable or 0), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - --- - -- @realm server - CreateConVar("ttt_" .. roleData.abbr .. "_credits_award_kill_enb", tostring(conVarData.creditsAwardKillEnable or 0), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - - local shopFallbackValue - - if not conVarData.shopFallback and roleData.fallbackTable then - shopFallbackValue = SHOP_UNSET - else - shopFallbackValue = conVarData.shopFallback and tostring(conVarData.shopFallback) or SHOP_DISABLED - end - - --- - -- @realm server - SetGlobalString("ttt_" .. roleData.abbr .. "_shop_fallback", CreateConVar("ttt_" .. roleData.abbr .. "_shop_fallback", shopFallbackValue, {FCVAR_NOTIFY, FCVAR_ARCHIVE}):GetString()) - - if conVarData.traitorKill then - --- - -- @realm server - CreateConVar("ttt_credits_" .. roleData.name .. "kill", tostring(conVarData.traitorKill), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) - end - else -- CLIENT - roleData.icon = roleData.icon or ("vgui/ttt/dynamic/roles/icon_" .. roleData.abbr) - - -- set a roledata icon material to prevent creating new materials each frame - roleData.iconMaterial = Material(roleData.icon) - - -- set default colors - roleData.dkcolor = util.ColorDarken(roleData.color, 30) - roleData.ltcolor = util.ColorLighten(roleData.color, 30) - roleData.bgcolor = util.ColorComplementary(roleData.color) - end - - -- set fallback data if not already exists - roleData.defaultTeam = roleData.defaultTeam or TEAM_NONE -- fix defaultTeam - - print("[TTT2][ROLE] Added '" .. roleData.name .. "' role (index: " .. roleData.index .. ")") + Dev(1, "[TTT2][ROLE] Adding '" .. roleData.name .. "' role...") + + local conVarData = roleData.conVarData or {} + + -- shared + if not roleData.notSelectable and SERVER then + --- + -- @realm server + -- stylua: ignore + CreateConVar("ttt_" .. roleData.name .. "_pct", tostring(conVarData.pct or 1), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + --- + -- @realm server + -- stylua: ignore + CreateConVar("ttt_" .. roleData.name .. "_max", tostring(conVarData.maximum or 1), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + --- + -- @realm server + -- stylua: ignore + CreateConVar("ttt_" .. roleData.name .. "_min_players", tostring(conVarData.minPlayers or 1), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + -- if we don't compare detective here, roles will never get assigned + if not roleData.builtin or roleData.index == ROLE_DETECTIVE then + --- + -- @realm server + -- stylua: ignore + CreateConVar("ttt_" .. roleData.name .. "_karma_min", tostring(conVarData.minKarma or 0), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + --- + -- @realm server + -- stylua: ignore + CreateConVar("ttt_" .. roleData.name .. "_random", tostring(conVarData.random or 100), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + --- + -- @realm server + -- stylua: ignore + CreateConVar("ttt_" .. roleData.name .. "_enabled", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + end + end + + --- + -- @realm shared + -- stylua: ignore + CreateConVar("ttt_" .. roleData.name .. "_traitor_button", tostring(conVarData.traitorButton or 0), SERVER and {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED} or FCVAR_REPLICATED) + + --- + -- @realm shared + -- stylua: ignore + CreateConVar("ttt2_ragdoll_pinning_" .. roleData.name, tostring(conVarData.ragdollPinning or 0), SERVER and {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED} or FCVAR_REPLICATED) + + if SERVER then + --- + -- @realm server + -- stylua: ignore + CreateConVar("ttt_" .. roleData.abbr .. "_credits_starting", tostring(conVarData.credits or 0), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + --- + -- @realm server + -- stylua: ignore + CreateConVar("ttt_" .. roleData.abbr .. "_credits_award_dead_enb", tostring(conVarData.creditsAwardDeadEnable or 0), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + --- + -- @realm server + -- stylua: ignore + CreateConVar("ttt_" .. roleData.abbr .. "_credits_award_kill_enb", tostring(conVarData.creditsAwardKillEnable or 0), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + + local shopFallbackValue + + if not conVarData.shopFallback and roleData.fallbackTable then + shopFallbackValue = SHOP_UNSET + else + shopFallbackValue = conVarData.shopFallback and tostring(conVarData.shopFallback) + or SHOP_DISABLED + end + + --- + -- @realm server + -- stylua: ignore + SetGlobalString("ttt_" .. roleData.abbr .. "_shop_fallback", CreateConVar("ttt_" .. roleData.abbr .. "_shop_fallback", shopFallbackValue, {FCVAR_NOTIFY, FCVAR_ARCHIVE}):GetString()) + + if conVarData.traitorKill then + --- + -- @realm server + -- stylua: ignore + CreateConVar("ttt_credits_" .. roleData.name .. "kill", tostring(conVarData.traitorKill), {FCVAR_NOTIFY, FCVAR_ARCHIVE}) + end + else -- CLIENT + roleData.icon = roleData.icon or ("vgui/ttt/dynamic/roles/icon_" .. roleData.abbr) + + -- set a roledata icon material to prevent creating new materials each frame + roleData.iconMaterial = Material(roleData.icon) + + -- set default colors + roleData.dkcolor = util.ColorDarken(roleData.color, 30) + roleData.ltcolor = util.ColorLighten(roleData.color, 30) + roleData.bgcolor = util.ColorComplementary(roleData.color) + end + + -- set fallback data if not already exists + roleData.defaultTeam = roleData.defaultTeam or TEAM_NONE -- fix defaultTeam + + Dev(1, "[TTT2][ROLE] Added '" .. roleData.name .. "' role (index: " .. roleData.index .. ")") end --- @@ -182,33 +194,39 @@ end -- @param string name role name -- @realm shared function roles.Register(t, name) - name = string.lower(name) + name = string.lower(name) - local old = roles.roleList[name] - if old then return end + local old = roles.roleList[name] + if old then + return + end - t.ClassName = name - t.name = name - t.isAbstract = t.isAbstract or false + t.ClassName = name + t.name = name + t.isAbstract = t.isAbstract or false - if not t.isAbstract then - -- set id - t.index = t.index or roles.GenerateNewRoleID() + if not t.isAbstract then + -- set id + t.index = t.index or roles.GenerateNewRoleID() - SetupGlobals(t) + SetupGlobals(t) - t.id = t.index - end + t.id = t.index + end - roles.roleList[name] = t + roles.roleList[name] = t - local upStr = string.upper(name) + local upStr = string.upper(name) - if roles[upStr] then - print("[TTT2][ROLE] Role '" .. name .. "' interferes with the 'roles' table (function or role with same name is already registered)!") - end + if roles[upStr] then + ErrorNoHaltWithStack( + "[TTT2][ROLE] Role '" + .. name + .. "' interferes with the 'roles' table (function or role with same name is already registered)!" + ) + end - roles[upStr] = t + roles[upStr] = t end --- @@ -216,35 +234,34 @@ end -- @local -- @realm shared function roles.OnLoaded() - - -- - -- Once all the scripts are loaded we can set up the baseclass - -- - we have to wait until they're all setup because load order - -- could cause some entities to load before their bases! - -- - for k, v in pairs(roles.roleList) do - roles.Get(k, v) - - baseclass.Set(k, v) - - if not v.isAbstract then - v:PreInitialize() - end - end - - -- Setup data (eg. convars for all roles) - for _, v in pairs(roles.roleList) do - if not v.isAbstract then - SetupData(v) - end - end - - -- Call Initialize() on all roles - for _, v in pairs(roles.roleList) do - if not v.isAbstract then - v:Initialize() - end - end + -- + -- Once all the scripts are loaded we can set up the baseclass + -- - we have to wait until they're all setup because load order + -- could cause some entities to load before their bases! + -- + for k, v in pairs(roles.roleList) do + roles.Get(k, v) + + baseclass.Set(k, v) + + if not v.isAbstract then + v:PreInitialize() + end + end + + -- Setup data (eg. convars for all roles) + for _, v in pairs(roles.roleList) do + if not v.isAbstract then + SetupData(v) + end + end + + -- Call Initialize() on all roles + for _, v in pairs(roles.roleList) do + if not v.isAbstract then + v:Initialize() + end + end end --- @@ -254,33 +271,41 @@ end -- @return table returns the modified retTbl or the new role table -- @realm shared function roles.Get(name, retTbl) - local stored = roles.GetStored(name) - if not stored then return end - - -- Create/copy a new table - local retval = retTbl or {} - - if retval ~= stored then - for k, v in pairs(stored) do - retval[k] = istable(v) and table.Copy(v) or v - end - end - - retval.Base = retval.Base or "ttt_role_base" - - -- If we're not derived from ourselves (a base role) - -- then derive from our 'Base' role. - if retval.Base ~= name then - local base = roles.Get(retval.Base) - - if not base then - Msg("ERROR: Trying to derive role " .. tostring(name) .. " from non existant role " .. tostring(retval.Base) .. "!\n") - else - retval = TableInherit(retval, base) - end - end - - return retval + local stored = roles.GetStored(name) + if not stored then + return + end + + -- Create/copy a new table + local retval = retTbl or {} + + if retval ~= stored then + for k, v in pairs(stored) do + retval[k] = istable(v) and table.Copy(v) or v + end + end + + retval.Base = retval.Base or "ttt_role_base" + + -- If we're not derived from ourselves (a base role) + -- then derive from our 'Base' role. + if retval.Base ~= name then + local base = roles.Get(retval.Base) + + if not base then + ErrorNoHaltWithStack( + "ERROR: Trying to derive role " + .. tostring(name) + .. " from non existant role " + .. tostring(retval.Base) + .. "!\n" + ) + else + retval = TableInherit(retval, base) + end + end + + return retval end --- @@ -289,7 +314,7 @@ end -- @return table returns the real role table -- @realm shared function roles.GetStored(name) - return roles.roleList[name] + return roles.roleList[name] end --- @@ -297,18 +322,18 @@ end -- @return table all registered roles -- @realm shared function roles.GetList() - local result = {} + local result = {} - local i = 0 + local i = 0 - for _, v in pairs(roles.roleList) do - if not v.isAbstract then - i = i + 1 - result[i] = v - end - end + for _, v in pairs(roles.roleList) do + if not v.isAbstract then + i = i + 1 + result[i] = v + end + end - return result + return result end --- @@ -324,16 +349,16 @@ end -- @return number new generated subrole id -- @realm shared function roles.GenerateNewRoleID() - local id = 3 -- 3 nops (4, 5, 6) - local reservedList = {"INNOCENT", "TRAITOR", "DETECTIVE", "NONE"} + local id = 3 -- 3 nops (4, 5, 6) + local reservedList = { "INNOCENT", "TRAITOR", "DETECTIVE", "NONE" } - for i = 1, #reservedList do - if not roles[reservedList[i]] then - id = id + 1 - end - end + for i = 1, #reservedList do + if not roles[reservedList[i]] then + id = id + 1 + end + end - return #roles.GetList() + id + return #roles.GetList() + id end --- @@ -343,13 +368,13 @@ end -- @return table returns the role table. This will return the NONE role table as fallback. -- @realm shared function roles.GetByIndex(index, fallback) - for _, v in pairs(roles.roleList) do - if not v.isAbstract and v.index == index then - return v - end - end + for _, v in pairs(roles.roleList) do + if not v.isAbstract and v.index == index then + return v + end + end - return fallback or roles.NONE + return fallback or roles.NONE end --- @@ -358,7 +383,7 @@ end -- @return table returns the role table. This will return the NONE role table as fallback. -- @realm shared function roles.GetByName(name) - return roles.GetStored(name) or roles.NONE + return roles.GetStored(name) or roles.NONE end --- @@ -367,13 +392,13 @@ end -- @return table returns the role table. This will return the NONE role table as fallback. -- @realm shared function roles.GetByAbbr(abbr) - for _, v in pairs(roles.roleList) do - if not v.isAbstract and v.abbr == abbr then - return v - end - end + for _, v in pairs(roles.roleList) do + if not v.isAbstract and v.abbr == abbr then + return v + end + end - return roles.NONE + return roles.NONE end --- @@ -383,15 +408,15 @@ end -- @todo data table structure -- @realm shared function roles.InitCustomTeam(name, data) -- creates global var "TEAM_[name]" and other required things - name = string.Trim(name) + name = string.Trim(name) - local teamname = string.lower(name) .. "s" + local teamname = string.lower(name) .. "s" - _G["TEAM_" .. string.upper(name)] = teamname + _G["TEAM_" .. string.upper(name)] = teamname - data.iconMaterial = Material(data.icon) + data.iconMaterial = Material(data.icon) - TEAMS[teamname] = data + TEAMS[teamname] = data end --- @@ -399,11 +424,11 @@ end -- @param table tbl table to sort -- @realm shared function roles.SortTable(tbl) - local _func = function(a, b) - return a.index < b.index - end + local _func = function(a, b) + return a.index < b.index + end - table.sort(tbl, _func) + table.sort(tbl, _func) end --- @@ -411,23 +436,25 @@ end -- @return table list of roles that have access to a shop -- @realm shared function roles.GetShopRoles() - local shopRoles = {} + local shopRoles = {} - local i = 0 + local i = 0 - for _, v in pairs(roles.roleList) do - if v.isAbstract or v == roles.NONE then continue end + for _, v in pairs(roles.roleList) do + if v.isAbstract or v == roles.NONE then + continue + end - local shopFallback = GetGlobalString("ttt_" .. v.abbr .. "_shop_fallback") - if shopFallback ~= SHOP_DISABLED then - i = i + 1 - shopRoles[i] = v - end - end + local shopFallback = GetGlobalString("ttt_" .. v.abbr .. "_shop_fallback") + if shopFallback ~= SHOP_DISABLED then + i = i + 1 + shopRoles[i] = v + end + end - roles.SortTable(shopRoles) + roles.SortTable(shopRoles) - return shopRoles + return shopRoles end --- @@ -436,17 +463,17 @@ end -- @return table returns the role table. This will return the NONE role table as fallback. -- @realm shared function roles.GetDefaultTeamRole(team) - if team == TEAM_NONE then - return roles.NONE - end + if team == TEAM_NONE then + return roles.NONE + end - for _, v in pairs(roles.roleList) do - if not v.isAbstract and v:IsBaseRole() and v.defaultTeam == team then - return v - end - end + for _, v in pairs(roles.roleList) do + if not v.isAbstract and v:IsBaseRole() and v.defaultTeam == team then + return v + end + end - return roles.NONE + return roles.NONE end --- @@ -455,7 +482,7 @@ end -- @return table returns the role tables. This will return the NONE role table as well as its subrole tables as fallback. -- @realm shared function roles.GetDefaultTeamRoles(team) - return roles.GetDefaultTeamRole(team):GetSubRoles() + return roles.GetDefaultTeamRole(team):GetSubRoles() end --- @@ -464,21 +491,23 @@ end -- @return table returns the member table of a role team. -- @realm shared function roles.GetTeamMembers(team) - if team == TEAM_NONE or TEAMS[team].alone then return end + if team == TEAM_NONE or TEAMS[team].alone then + return + end - local tmp = {} - local plys = player.GetAll() + local tmp = {} + local plys = player.GetAll() - local count = 0 + local count = 0 - for i = 1, #plys do - if plys[i]:GetTeam() == team then - count = count + 1 - tmp[count] = plys[i] - end - end + for i = 1, #plys do + if plys[i]:GetTeam() == team then + count = count + 1 + tmp[count] = plys[i] + end + end - return tmp + return tmp end --- @@ -486,18 +515,23 @@ end -- @return table returns a list of all teams that are able to win -- @realm shared function roles.GetWinTeams() - local winTeams = {} - - local i = 0 - - for _, v in pairs(roles.roleList) do - if not v.isAbstract and v.defaultTeam ~= TEAM_NONE and not table.HasValue(winTeams, v.defaultTeam) and not v.preventWin then - i = i + 1 - winTeams[i] = v.defaultTeam - end - end - - return winTeams + local winTeams = {} + + local i = 0 + + for _, v in pairs(roles.roleList) do + if + not v.isAbstract + and v.defaultTeam ~= TEAM_NONE + and not table.HasValue(winTeams, v.defaultTeam) + and not v.preventWin + then + i = i + 1 + winTeams[i] = v.defaultTeam + end + end + + return winTeams end --- @@ -505,18 +539,22 @@ end -- @return table returns a list of all available teams -- @realm shared function roles.GetAvailableTeams() - local availableTeams = {} - - local i = 0 - - for _, v in pairs(roles.roleList) do - if not v.isAbstract and v.defaultTeam ~= TEAM_NONE and not table.HasValue(availableTeams, v.defaultTeam) then - i = i + 1 - availableTeams[i] = v.defaultTeam - end - end - - return availableTeams + local availableTeams = {} + + local i = 0 + + for _, v in pairs(roles.roleList) do + if + not v.isAbstract + and v.defaultTeam ~= TEAM_NONE + and not table.HasValue(availableTeams, v.defaultTeam) + then + i = i + 1 + availableTeams[i] = v.defaultTeam + end + end + + return availableTeams end --- @@ -524,20 +562,20 @@ end -- @return table returns a list of all roles -- @realm shared function roles.GetSortedRoles() - local rls = {} + local rls = {} - local i = 0 + local i = 0 - for _, v in pairs(roles.roleList) do - if not v.isAbstract then - i = i + 1 - rls[i] = v - end - end + for _, v in pairs(roles.roleList) do + if not v.isAbstract then + i = i + 1 + rls[i] = v + end + end - roles.SortTable(rls) + roles.SortTable(rls) - return rls + return rls end --- @@ -547,22 +585,41 @@ end -- @param ROLE baserole the BaseRole -- @realm shared function roles.SetBaseRole(roleTable, baserole) - if roleTable.baserole then - error("[TTT2][ROLE-SYSTEM][ERROR] BaseRole of " .. roleTable.name .. " already set (" .. roleTable.baserole .. ")!") - elseif roleTable.index == baserole then - error("[TTT2][ROLE-SYSTEM][ERROR] BaseRole " .. roleTable.name .. " can't be a baserole of itself!") - else - local br = roles.GetByIndex(baserole) - - if br.baserole then - error("[TTT2][ROLE-SYSTEM][ERROR] Your requested BaseRole can't be any BaseRole of another SubRole because it's a SubRole as well.") - - return - end - - roleTable.baserole = baserole - roleTable.defaultTeam = br.defaultTeam - - print("[TTT2][ROLE-SYSTEM] Connected '" .. roleTable.name .. "' subrole with baserole '" .. br.name .. "'") - end + if roleTable.baserole then + error( + "[TTT2][ROLE-SYSTEM][ERROR] BaseRole of " + .. roleTable.name + .. " already set (" + .. roleTable.baserole + .. ")!" + ) + elseif roleTable.index == baserole then + error( + "[TTT2][ROLE-SYSTEM][ERROR] BaseRole " + .. roleTable.name + .. " can't be a baserole of itself!" + ) + else + local br = roles.GetByIndex(baserole) + + if br.baserole then + error( + "[TTT2][ROLE-SYSTEM][ERROR] Your requested BaseRole can't be any BaseRole of another SubRole because it's a SubRole as well." + ) + + return + end + + roleTable.baserole = baserole + roleTable.defaultTeam = br.defaultTeam + + Dev( + 1, + "[TTT2][ROLE-SYSTEM] Connected '" + .. roleTable.name + .. "' subrole with baserole '" + .. br.name + .. "'" + ) + end end diff --git a/lua/ttt2/libraries/targetid.lua b/lua/ttt2/libraries/targetid.lua index e08679d33..6afb74104 100644 --- a/lua/ttt2/libraries/targetid.lua +++ b/lua/ttt2/libraries/targetid.lua @@ -5,9 +5,9 @@ -- @module TargetID if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return + return end targetid = targetid or {} @@ -22,10 +22,6 @@ local MAX_TRACE_LENGTH = math.sqrt(3) * 32768 -- Key Parameters for doors local key_params = {} --- Convars for targetid -local cvDeteOnlyConfirm -local cvDeteOnlyInspect - -- Materials for targetid local materialTButton = Material("vgui/ttt/tid/tid_big_tbutton_pointer") local materialRing = Material("effects/select_ring") @@ -48,20 +44,19 @@ local materialDNATargetID = Material("vgui/ttt/dnascanner/dna_hud") -- @local -- @realm client function targetid.Initialize() - if bIsInitialized then return end - - bIsInitialized = true - ParT = LANG.GetParamTranslation - TryT = LANG.TryTranslation - key_params = { - primaryfire = Key("+attack", "MOUSE1"), - secondaryfire = Key("+attack2", "MOUSE2"), - usekey = Key("+use", "USE"), - walkkey = Key("+walk", "WALK") - } - - cvDeteOnlyConfirm = GetConVar("ttt2_confirm_detective_only") - cvDeteOnlyInspect = GetConVar("ttt2_inspect_detective_only") + if bIsInitialized then + return + end + + bIsInitialized = true + ParT = LANG.GetParamTranslation + TryT = LANG.TryTranslation + key_params = { + primaryfire = Key("+attack", "MOUSE1"), + secondaryfire = Key("+attack2", "MOUSE2"), + usekey = Key("+use", "USE"), + walkkey = Key("+walk", "WALK"), + } end --- @@ -69,51 +64,54 @@ end -- Use this in combination with the hook @GM:TTTModifyTargetedEntity to create your own Remote Camera with TargetIDs. -- e.g. This is used in @GM:HUDDrawTargetID before drawing the TargetIDs. Use that code as example. -- @note This finds the next Entity, that doesn't get filtered out and can get hit by a bullet, from a position in a direction. --- @param vector pos Position of Ray Origin. --- @param vector dir Direction of the Ray. Should be normalized. +-- @param Vector pos Position of Ray Origin. +-- @param Vector dir Direction of the Ray. Should be normalized. -- @param table filter List of all @{Entity}s that should be filtered out. --- @return entity The Entity that got found +-- @return Entity The Entity that got found -- @return number The Distance between the Origin and the Entity -- @realm client function targetid.FindEntityAlongView(pos, dir, filter) - local endpos = dir - endpos:Mul(MAX_TRACE_LENGTH) - endpos:Add(pos) - - if entspawnscript.IsEditing(LocalPlayer()) then - local focusedSpawn = entspawnscript.GetFocusedSpawn() - local wepEditEnt = entspawnscript.GetSpawnInfoEntity() - - if focusedSpawn and IsValid(wepEditEnt) then - return wepEditEnt, pos:Distance(focusedSpawn.spawn.pos) - end - end - - -- if the user is looking at a traitor button, it should always be handled with priority - if TBHUD.focus_but and IsValid(TBHUD.focus_but.ent) - and (TBHUD.focus_but.access or TBHUD.focus_but.admin) and TBHUD.focus_stick >= CurTime() - then - local ent = TBHUD.focus_but.ent - - return ent, pos:Distance(ent:GetPos()) - end - - local trace = util.TraceLine({ - start = pos, - endpos = endpos, - mask = MASK_SHOT, - filter = filter - }) - - -- this is the entity the player is looking at right now - local ent = trace.Entity - - -- if a vehicle, we identify the driver instead - if IsValid(ent) and IsValid(ent:GetNWEntity("ttt_driver", nil)) then - ent = ent:GetNWEntity("ttt_driver", nil) - end - - return ent, trace.StartPos:Distance(trace.HitPos) + local endpos = dir + endpos:Mul(MAX_TRACE_LENGTH) + endpos:Add(pos) + + if entspawnscript.IsEditing(LocalPlayer()) then + local focusedSpawn = entspawnscript.GetFocusedSpawn() + local wepEditEnt = entspawnscript.GetSpawnInfoEntity() + + if focusedSpawn and IsValid(wepEditEnt) then + return wepEditEnt, pos:Distance(focusedSpawn.spawn.pos) + end + end + + -- if the user is looking at a traitor button, it should always be handled with priority + if + TBHUD.focus_but + and IsValid(TBHUD.focus_but.ent) + and (TBHUD.focus_but.access or TBHUD.focus_but.admin) + and TBHUD.focus_stick >= CurTime() + then + local ent = TBHUD.focus_but.ent + + return ent, pos:Distance(ent:GetPos()) + end + + local trace = util.TraceLine({ + start = pos, + endpos = endpos, + mask = MASK_SHOT, + filter = filter, + }) + + -- this is the entity the player is looking at right now + local ent = trace.Entity + + -- if a vehicle, we identify the driver instead + if IsValid(ent) and IsValid(ent:GetNWEntity("ttt_driver", nil)) then + ent = ent:GetNWEntity("ttt_driver", nil) + end + + return ent, trace.StartPos:Distance(trace.HitPos) end --- @@ -121,58 +119,69 @@ end -- @param TARGET_DATA tData The object to be used in the hook -- @realm client function targetid.HUDDrawTargetIDSpawnEdit(tData) - local client = LocalPlayer() - - if not entspawnscript.IsEditing(client) then return end - - local ent = tData:GetEntity() - local wep = client:GetActiveWeapon() - - if not IsValid(client) or not IsValid(wep) or wep:GetClass() ~= "weapon_ttt_spawneditor" - or not IsValid(ent) or ent:GetClass() ~= "ttt_spawninfo_ent" - then - return - end - - local focusedSpawn = entspawnscript.GetFocusedSpawn() - - if not focusedSpawn then return end - - local spawnType = focusedSpawn.spawnType - local entType = focusedSpawn.entType - local ammoAmount = focusedSpawn.spawn.ammo - - -- enable targetID rendering - tData:EnableText() - tData:AddIcon(entspawnscript.GetIconFromSpawnType(spawnType, entType)) - tData:SetSubtitle(ParT("spawn_remove", key_params)) - - if spawnType == SPAWN_TYPE_WEAPON then - tData:SetTitle(TryT(entspawnscript.GetLangIdentifierFromSpawnType(spawnType, entType)) .. ParT("spawn_weapon_ammo", {ammo = ammoAmount})) - - tData:AddDescriptionLine( - TryT("spawn_type_weapon"), - entspawnscript.GetColorFromSpawnType(SPAWN_TYPE_WEAPON) - ) - - tData:AddDescriptionLine() - - tData:AddDescriptionLine(ParT("spawn_weapon_edit_ammo", key_params)) - elseif spawnType == SPAWN_TYPE_AMMO then - tData:SetTitle(TryT(entspawnscript.GetLangIdentifierFromSpawnType(spawnType, entType))) - - tData:AddDescriptionLine( - TryT("spawn_type_ammo"), - entspawnscript.GetColorFromSpawnType(SPAWN_TYPE_AMMO) - ) - elseif spawnType == SPAWN_TYPE_PLAYER then - tData:SetTitle(TryT(entspawnscript.GetLangIdentifierFromSpawnType(spawnType, entType))) - - tData:AddDescriptionLine( - TryT("spawn_type_player"), - entspawnscript.GetColorFromSpawnType(SPAWN_TYPE_PLAYER) - ) - end + local client = LocalPlayer() + + if not entspawnscript.IsEditing(client) then + return + end + + local ent = tData:GetEntity() + local wep = client:GetActiveWeapon() + + if + not IsValid(client) + or not IsValid(wep) + or wep:GetClass() ~= "weapon_ttt_spawneditor" + or not IsValid(ent) + or ent:GetClass() ~= "ttt_spawninfo_ent" + then + return + end + + local focusedSpawn = entspawnscript.GetFocusedSpawn() + + if not focusedSpawn then + return + end + + local spawnType = focusedSpawn.spawnType + local entType = focusedSpawn.entType + local ammoAmount = focusedSpawn.spawn.ammo + + -- enable targetID rendering + tData:EnableText() + tData:AddIcon(entspawnscript.GetIconFromSpawnType(spawnType, entType)) + tData:SetSubtitle(ParT("spawn_remove", key_params)) + + if spawnType == SPAWN_TYPE_WEAPON then + tData:SetTitle( + TryT(entspawnscript.GetLangIdentifierFromSpawnType(spawnType, entType)) + .. ParT("spawn_weapon_ammo", { ammo = ammoAmount }) + ) + + tData:AddDescriptionLine( + TryT("spawn_type_weapon"), + entspawnscript.GetColorFromSpawnType(SPAWN_TYPE_WEAPON) + ) + + tData:AddDescriptionLine() + + tData:AddDescriptionLine(ParT("spawn_weapon_edit_ammo", key_params)) + elseif spawnType == SPAWN_TYPE_AMMO then + tData:SetTitle(TryT(entspawnscript.GetLangIdentifierFromSpawnType(spawnType, entType))) + + tData:AddDescriptionLine( + TryT("spawn_type_ammo"), + entspawnscript.GetColorFromSpawnType(SPAWN_TYPE_AMMO) + ) + elseif spawnType == SPAWN_TYPE_PLAYER then + tData:SetTitle(TryT(entspawnscript.GetLangIdentifierFromSpawnType(spawnType, entType))) + + tData:AddDescriptionLine( + TryT("spawn_type_player"), + entspawnscript.GetColorFromSpawnType(SPAWN_TYPE_PLAYER) + ) + end end --- @@ -180,114 +189,123 @@ end -- @param TARGET_DATA tData The object to be used in the hook -- @realm client function targetid.HUDDrawTargetIDTButtons(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - - local admin_mode = GetGlobalBool("ttt2_tbutton_admin_show", false) - - if not IsValid(client) or not client:IsTerror() or not client:Alive() - or not IsValid(ent) or ent:GetClass() ~= "ttt_traitor_button" - or tData:GetEntityDistance() > ent:GetUsableRange() - then - return - end - - -- enable targetID rendering - tData:EnableText() - - -- set the title of the traitor button - tData:SetTitle(ent:GetDescription() == "?" and "Traitor Button" or TryT(ent:GetDescription())) - - -- set the subtitle and icon depending on the currently used mode - if TBHUD.focus_but.admin and not TBHUD.focus_but.access then - tData:AddIcon( - materialTButton, - COLOR_LGRAY - ) - - tData:SetSubtitle(TryT("tbut_help_admin")) - else - tData:SetKey(input.GetKeyCode(key_params.usekey)) - - tData:SetSubtitle(ParT("tbut_help", key_params)) - end - - -- add description time with some general info about this specific traitor button - if ent:GetDelay() < 0 then - tData:AddDescriptionLine( - TryT("tbut_single"), - client:GetRoleColor() - ) - elseif ent:GetDelay() == 0 then - tData:AddDescriptionLine( - TryT("tbut_reuse"), - client:GetRoleColor() - ) - else - tData:AddDescriptionLine( - ParT("tbut_retime", {num = ent:GetDelay()}), - client:GetRoleColor() - ) - end - - -- only add more information if in admin mode - if not admin_mode or not client:IsAdmin() then return end - - local but = TBHUD.focus_but - - tData:AddDescriptionLine() -- adding empty line - - tData:AddDescriptionLine( - TryT ("tbut_adminarea"), - COLOR_WHITE - ) - - tData:AddDescriptionLine( - ParT("tbut_role_toggle", {usekey = key_params.usekey, walkkey = key_params.walkkey, role = client:GetRoleString()}), - COLOR_WHITE - ) - - tData:AddDescriptionLine( - ParT("tbut_team_toggle", {usekey = key_params.usekey, walkkey = key_params.walkkey, team = client:GetTeam():gsub("^%l", string.upper)}), - COLOR_WHITE - ) - - tData:AddDescriptionLine() -- adding empty line - - tData:AddDescriptionLine( - TryT("tbut_current_config"), - COLOR_WHITE - ) - - local l_role = but.overrideRole == nil and "tbut_default" or but.overrideRole and "tbut_allow" or "tbut_prohib" - local l_team = but.overrideTeam == nil and "tbut_default" or but.overrideTeam and "tbut_allow" or "tbut_prohib" - - tData:AddDescriptionLine( - ParT("tbut_role_config", {current = TryT(l_role)}) .. ", " .. ParT("tbut_team_config", {current = TryT(l_team)}), - COLOR_LGRAY - ) - - tData:AddDescriptionLine( - TryT("tbut_intended_config"), - COLOR_WHITE - ) - - local l_roleIntend = but.roleIntend == "none" and "tbut_default" or but.roleIntend - local l_teamIntend = but.teamIntend == TEAM_NONE and "tbut_default" or but.teamIntend - - tData:AddDescriptionLine( - ParT("tbut_role_config", {current = LANG.GetRawTranslation(l_roleIntend) or l_roleIntend}) .. ", " .. ParT("tbut_team_config", {current = LANG.GetRawTranslation(l_teamIntend) or l_teamIntend}), - COLOR_LGRAY - ) - - if not TBHUD.focus_but.admin or TBHUD.focus_but.access then return end - - tData:AddDescriptionLine() -- adding empty line - - tData:AddDescriptionLine( - ParT("tbut_admin_mode_only", {cv = "ttt2_tbutton_admin_show"}), - COLOR_ORANGE - ) + local client = LocalPlayer() + local ent = tData:GetEntity() + + local admin_mode = GetGlobalBool("ttt2_tbutton_admin_show", false) + + if + not IsValid(client) + or not client:IsTerror() + or not client:Alive() + or not IsValid(ent) + or ent:GetClass() ~= "ttt_traitor_button" + or tData:GetEntityDistance() > ent:GetUsableRange() + then + return + end + + -- enable targetID rendering + tData:EnableText() + + -- set the title of the traitor button + tData:SetTitle(ent:GetDescription() == "?" and "Traitor Button" or TryT(ent:GetDescription())) + + -- set the subtitle and icon depending on the currently used mode + if TBHUD.focus_but.admin and not TBHUD.focus_but.access then + tData:AddIcon(materialTButton, COLOR_LGRAY) + + tData:SetSubtitle(TryT("tbut_help_admin")) + else + tData:SetKey(input.GetKeyCode(key_params.usekey)) + + tData:SetSubtitle(ParT("tbut_help", key_params)) + end + + -- add description time with some general info about this specific traitor button + if ent:GetDelay() < 0 then + tData:AddDescriptionLine(TryT("tbut_single"), client:GetRoleColor()) + elseif ent:GetDelay() == 0 then + tData:AddDescriptionLine(TryT("tbut_reuse"), client:GetRoleColor()) + else + tData:AddDescriptionLine( + ParT("tbut_retime", { num = ent:GetDelay() }), + client:GetRoleColor() + ) + end + + -- only add more information if in admin mode + if not admin_mode or not client:IsAdmin() then + return + end + + local but = TBHUD.focus_but + + tData:AddDescriptionLine() -- adding empty line + + tData:AddDescriptionLine(TryT("tbut_adminarea"), COLOR_WHITE) + + tData:AddDescriptionLine( + ParT("tbut_role_toggle", { + usekey = key_params.usekey, + walkkey = key_params.walkkey, + role = client:GetRoleString(), + }), + COLOR_WHITE + ) + + tData:AddDescriptionLine( + ParT("tbut_team_toggle", { + usekey = key_params.usekey, + walkkey = key_params.walkkey, + team = client:GetTeam():gsub("^%l", string.upper), + }), + COLOR_WHITE + ) + + tData:AddDescriptionLine() -- adding empty line + + tData:AddDescriptionLine(TryT("tbut_current_config"), COLOR_WHITE) + + local l_role = but.overrideRole == nil and "tbut_default" + or but.overrideRole and "tbut_allow" + or "tbut_prohib" + local l_team = but.overrideTeam == nil and "tbut_default" + or but.overrideTeam and "tbut_allow" + or "tbut_prohib" + + tData:AddDescriptionLine( + ParT("tbut_role_config", { current = TryT(l_role) }) + .. ", " + .. ParT("tbut_team_config", { current = TryT(l_team) }), + COLOR_LGRAY + ) + + tData:AddDescriptionLine(TryT("tbut_intended_config"), COLOR_WHITE) + + local l_roleIntend = but.roleIntend == "none" and "tbut_default" or but.roleIntend + local l_teamIntend = but.teamIntend == TEAM_NONE and "tbut_default" or but.teamIntend + + tData:AddDescriptionLine( + ParT("tbut_role_config", { current = LANG.GetRawTranslation(l_roleIntend) or l_roleIntend }) + .. ", " + .. ParT( + "tbut_team_config", + { current = LANG.GetRawTranslation(l_teamIntend) or l_teamIntend } + ), + COLOR_LGRAY + ) + + if not TBHUD.focus_but.admin or TBHUD.focus_but.access then + return + end + + tData:AddDescriptionLine() -- adding empty line + + tData:AddDescriptionLine( + ParT("tbut_admin_mode_only", { cv = "ttt2_tbutton_admin_show" }), + COLOR_ORANGE + ) end --- @@ -295,81 +313,99 @@ end -- @param TARGET_DATA tData The object to be used in the hook -- @realm client function targetid.HUDDrawTargetIDWeapons(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - - if not IsValid(client) or not client:IsTerror() or not client:Alive() - or not IsValid(ent) or tData:GetEntityDistance() > 100 or not ent:IsWeapon() then - return - end - - local dropWeapon, isActiveWeapon, switchMode = GetBlockingWeapon(client, ent) - local kind_pickup_wep = MakeKindValid(ent.Kind) - - local weapon_name - - if ent.GetPrintName then - weapon_name = ent:GetPrintName() - end - - weapon_name = weapon_name or ent.PrintName or ent:GetClass() or "..." - - -- enable targetID rendering - tData:EnableText() - tData:EnableOutline() - tData:SetOutlineColor(client:GetRoleColor()) - - -- general info - tData:SetKey(bind.Find("ttt2_weaponswitch")) - - tData:SetTitle(TryT(weapon_name) .. " [" .. ParT("target_slot_info", {slot = kind_pickup_wep}) .. "]") - - local key_params_wep = { - usekey = string.upper(input.GetKeyName(bind.Find("ttt2_weaponswitch")) or ""), - walkkey = Key("+walk", "WALK") - } - - -- set subtitle depending on the switchmode - if switchMode == SWITCHMODE_PICKUP then - tData:SetSubtitle(ParT("target_pickup_weapon", key_params_wep) .. (not isActiveWeapon and ParT("target_pickup_weapon_hidden", key_params_wep) or "")) - elseif switchMode == SWITCHMODE_SWITCH then - tData:SetSubtitle(ParT("target_switch_weapon", key_params_wep) .. (not isActiveWeapon and ParT("target_switch_weapon_hidden", key_params_wep) or "")) - elseif switchMode == SWITCHMODE_FULLINV then - tData:SetSubtitle(TryT("target_switch_weapon_nospace")) - end - - -- add additional dropping info if weapon is switched - if switchMode == SWITCHMODE_SWITCH then - local dropWepKind = MakeKindValid(dropWeapon.Kind) - local dropWeapon_name - - if dropWeapon.GetPrintName then - dropWeapon_name = dropWeapon:GetPrintName() - end - - dropWeapon_name = dropWeapon_name or dropWeapon.PrintName or dropWeapon:GetClass() or "..." - - tData:AddDescriptionLine( - ParT("target_switch_drop_weapon_info", {slot = dropWepKind, name = TryT(dropWeapon_name)}), - COLOR_ORANGE - ) - end - - -- add info about full inventory - if switchMode == SWITCHMODE_FULLINV then - tData:AddDescriptionLine( - ParT("target_switch_drop_weapon_info_noslot", {slot = MakeKindValid(ent.Kind)}), - COLOR_ORANGE - ) - end - - -- add info if your weapon can not be dropped - if switchMode == SWITCHMODE_NOSPACE then - tData:AddDescriptionLine( - TryT("drop_no_room"), - COLOR_ORANGE - ) - end + local client = LocalPlayer() + local ent = tData:GetEntity() + + if + not IsValid(client) + or not client:IsTerror() + or not client:Alive() + or not IsValid(ent) + or tData:GetEntityDistance() > 100 + or not ent:IsWeapon() + then + return + end + + local dropWeapon, isActiveWeapon, switchMode = GetBlockingWeapon(client, ent) + local kind_pickup_wep = MakeKindValid(ent.Kind) + + local weapon_name + + if ent.GetPrintName then + weapon_name = ent:GetPrintName() + end + + weapon_name = weapon_name or ent.PrintName or ent:GetClass() or "..." + + -- enable targetID rendering + tData:EnableText() + tData:EnableOutline() + tData:SetOutlineColor(client:GetRoleColor()) + + -- general info + tData:SetKey(bind.Find("ttt2_weaponswitch")) + + tData:SetTitle( + TryT(weapon_name) .. " [" .. ParT("target_slot_info", { slot = kind_pickup_wep }) .. "]" + ) + + local key_params_wep = { + usekey = string.upper(input.GetKeyName(bind.Find("ttt2_weaponswitch")) or ""), + walkkey = Key("+walk", "WALK"), + } + + -- set subtitle depending on the switchmode + if switchMode == SWITCHMODE_PICKUP then + tData:SetSubtitle( + ParT("target_pickup_weapon", key_params_wep) + .. ( + not isActiveWeapon and ParT("target_pickup_weapon_hidden", key_params_wep) or "" + ) + ) + elseif switchMode == SWITCHMODE_SWITCH then + tData:SetSubtitle( + ParT("target_switch_weapon", key_params_wep) + .. ( + not isActiveWeapon and ParT("target_switch_weapon_hidden", key_params_wep) or "" + ) + ) + elseif switchMode == SWITCHMODE_FULLINV then + tData:SetSubtitle(TryT("target_switch_weapon_nospace")) + end + + -- add additional dropping info if weapon is switched + if switchMode == SWITCHMODE_SWITCH then + local dropWepKind = MakeKindValid(dropWeapon.Kind) + local dropWeapon_name + + if dropWeapon.GetPrintName then + dropWeapon_name = dropWeapon:GetPrintName() + end + + dropWeapon_name = dropWeapon_name or dropWeapon.PrintName or dropWeapon:GetClass() or "..." + + tData:AddDescriptionLine( + ParT( + "target_switch_drop_weapon_info", + { slot = dropWepKind, name = TryT(dropWeapon_name) } + ), + COLOR_ORANGE + ) + end + + -- add info about full inventory + if switchMode == SWITCHMODE_FULLINV then + tData:AddDescriptionLine( + ParT("target_switch_drop_weapon_info_noslot", { slot = MakeKindValid(ent.Kind) }), + COLOR_ORANGE + ) + end + + -- add info if your weapon can not be dropped + if switchMode == SWITCHMODE_NOSPACE then + tData:AddDescriptionLine(TryT("drop_no_room"), COLOR_ORANGE) + end end --- @@ -377,90 +413,90 @@ end -- @param TARGET_DATA tData The object to be used in the hook -- @realm client function targetid.HUDDrawTargetIDPlayers(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - local obsTgt = client:GetObserverTarget() - - -- has to be a player - if not ent:IsPlayer() then return end - - local disguised = ent:GetNWBool("disguised", false) - - -- oof TTT, why so hacky?! Sets last seen player. Dear reader I don't like this as well, but it has to stay that way - -- for compatibility reasons. At least it is uncluttered now! - client.last_id = disguised and nil or ent - - -- do not show information when observing a player - if client:IsSpec() and IsValid(obsTgt) and ent == obsTgt then return end - - -- disguised players are not shown to normal players, except: same team, unknown team or to spectators - if disguised and not (client:IsInTeam(ent) and not client:GetSubRoleData().unknownTeam or client:IsSpec()) then return end - - -- show the role of a player if it is known to the client - local rstate = GetRoundState() - local target_role - - if rstate == ROUND_ACTIVE and ent.HasRole and ent:HasRole() then - target_role = ent:GetSubRoleData() - end - - -- add glowing ring around crosshair when role is known - if target_role then - local icon_size = 64 - - draw.FilteredTexture(math.Round(0.5 * (ScrW() - icon_size)), math.Round(0.5 * (ScrH() - icon_size)), icon_size, icon_size, materialRing, 200, target_role.color) - end - - -- enable targetID rendering - tData:EnableText() - - -- add title and subtitle to the focused ent - local h_string, h_color = util.HealthToString(ent:Health(), ent:GetMaxHealth()) - - tData:SetTitle( - ent:Nick() .. (disguised and (" " .. TryT("target_disg")) or ""), - disguised and COLOR_ORANGE or nil, - disguised and {materialDisguised} or nil - ) - - tData:SetSubtitle( - TryT(h_string), - h_color - ) - - -- add icon to the element - tData:AddIcon( - target_role and target_role.iconMaterial or materialRoleUnknown, - target_role and ent:GetRoleColor() or COLOR_SLATEGRAY - ) - - -- add karma string if karma is enabled - if KARMA.IsEnabled() then - local k_string, k_color = util.KarmaToString(ent:GetBaseKarma()) - - tData:AddDescriptionLine( - TryT(k_string), - k_color - ) - end - - -- add scoreboard tags if tag is set - if ent.sb_tag and ent.sb_tag.txt then - tData:AddDescriptionLine( - TryT(ent.sb_tag.txt), - ent.sb_tag.color - ) - end - - -- add hints to the player - local hint = ent.TargetIDHint - - if hint and hint.hint then - tData:AddDescriptionLine( - hint.fmt(ent, hint.hint), - COLOR_LGRAY - ) - end + local client = LocalPlayer() + local ent = tData:GetEntity() + local obsTgt = client:GetObserverTarget() + + -- has to be a player + if not ent:IsPlayer() then + return + end + + local disguised = ent:GetNWBool("disguised", false) + + -- oof TTT, why so hacky?! Sets last seen player. Dear reader I don't like this as well, but it has to stay that way + -- for compatibility reasons. At least it is uncluttered now! + client.last_id = disguised and nil or ent + + -- do not show information when observing a player + if client:IsSpec() and IsValid(obsTgt) and ent == obsTgt then + return + end + + -- disguised players are not shown to normal players, except: same team, unknown team or to spectators + if + disguised + and not ( + client:IsInTeam(ent) and not client:GetSubRoleData().unknownTeam or client:IsSpec() + ) + then + return + end + + -- show the role of a player if it is known to the client + local rstate = GetRoundState() + local target_role + + if rstate == ROUND_ACTIVE and ent.HasRole and ent:HasRole() then + target_role = ent:GetSubRoleData() + end + + -- add glowing ring around crosshair when role is known + if target_role then + local icon_size = 64 + + draw.FilteredTexture( + math.Round(0.5 * (ScrW() - icon_size)), + math.Round(0.5 * (ScrH() - icon_size)), + icon_size, + icon_size, + materialRing, + 200, + target_role.color + ) + end + + -- enable targetID rendering + tData:EnableText() + + -- add title and subtitle to the focused ent + local h_string, h_color = util.HealthToString(ent:Health(), ent:GetMaxHealth()) + + tData:SetTitle( + ent:Nick() .. (disguised and (" " .. TryT("target_disg")) or ""), + disguised and COLOR_ORANGE or nil, + disguised and { materialDisguised } or nil + ) + + tData:SetSubtitle(TryT(h_string), h_color) + + -- add icon to the element + tData:AddIcon( + target_role and target_role.iconMaterial or materialRoleUnknown, + target_role and ent:GetRoleColor() or COLOR_SLATEGRAY + ) + + -- add karma string if karma is enabled + if KARMA.IsEnabled() then + local k_string, k_color = util.KarmaToString(ent:GetBaseKarma()) + + tData:AddDescriptionLine(TryT(k_string), k_color) + end + + -- add scoreboard tags if tag is set + if ent.sb_tag and ent.sb_tag.txt then + tData:AddDescriptionLine(TryT(ent.sb_tag.txt), ent.sb_tag.color) + end end --- @@ -468,145 +504,152 @@ end -- @param TARGET_DATA tData The object to be used in the hook -- @realm client function targetid.HUDDrawTargetIDRagdolls(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - local c_wep = client:GetActiveWeapon() - - -- has to be a ragdoll - if not IsValid(ent) or ent:GetClass() ~= "prop_ragdoll" then return end - - -- only show this if the ragdoll has a nick, else it could be a mattress - if not CORPSE.GetPlayerNick(ent, false) then return end - - local corpse_found = CORPSE.GetFound(ent, false) or not DetectiveMode() - local role_found = corpse_found and ent.search_result and ent.search_result.role - local binoculars_useable = IsValid(c_wep) and c_wep:GetClass() == "weapon_ttt_binoculars" or false - local roleData = roles.GetByIndex(role_found and ent.search_result.role or ROLE_INNOCENT) - local roleDataClient = client:GetSubRoleData() - - -- enable targetID rendering - tData:EnableText() - tData:EnableOutline(tData:GetEntityDistance() <= 100) - tData:SetOutlineColor(COLOR_YELLOW) - - -- add title and subtitle to the focused ent - tData:SetTitle( - corpse_found and CORPSE.GetPlayerNick(ent, TryT("target_unknown")) or TryT("target_unid"), - role_found and COLOR_WHITE or COLOR_YELLOW - ) - - if tData:GetEntityDistance() <= 100 then - if cvDeteOnlyInspect:GetBool() and not roleDataClient.isPolicingRole then - if client:IsActive() and client:IsShopper() and CORPSE.GetCredits(ent, 0) > 0 then - tData:SetSubtitle(ParT("corpse_hint_inspect_only_credits", key_params)) - else - tData:SetSubtitle(TryT("corpse_hint_no_inspect")) - end - elseif cvDeteOnlyConfirm:GetBool() and not roleDataClient.isPolicingRole then - tData:SetSubtitle(ParT("corpse_hint_inspect_only", key_params)) - else - tData:SetSubtitle(ParT("corpse_hint", key_params)) - end - elseif binoculars_useable then - tData:SetSubtitle(ParT("corpse_binoculars", {key = Key("+attack", "ATTACK")})) - else - tData:SetSubtitle(TryT("corpse_too_far_away")) - end - - -- add icon to the element - tData:AddIcon( - role_found and roleData.iconMaterial or materialCorpse, - role_found and roleData.color or COLOR_YELLOW - ) - - -- add hints to the corpse - local hint = ent.TargetIDHint - - if hint and hint.hint then - tData:AddDescriptionLine( - hint.fmt(ent, hint.hint), - COLOR_LGRAY - ) - end - - -- add info if searched by detectives - if ent.search_result and ent.search_result.detective_search and roleDataClient.isPolicingRole then - tData:AddDescriptionLine( - TryT("corpse_searched_by_detective"), - roles.DETECTIVE.ltcolor, - {materialDetective} - ) - end - - -- add credits info when corpse has credits - if client:IsActive() and client:IsShopper() and CORPSE.GetCredits(ent, 0) > 0 then - tData:AddDescriptionLine( - TryT("target_credits"), - COLOR_YELLOW, - {materialCredits} - ) - end + local client = LocalPlayer() + local ent = tData:GetEntity() + local c_wep = client:GetActiveWeapon() + + -- has to be a ragdoll + if not IsValid(ent) or ent:GetClass() ~= "prop_ragdoll" then + return + end + + -- only show this if the ragdoll has a nick, else it could be a mattress + if not CORPSE.GetPlayerNick(ent, false) then + return + end + + local corpse_found = CORPSE.GetFound(ent, false) or not DetectiveMode() + local corpse_ply = corpse_found and CORPSE.GetPlayer(ent) or false + local binoculars_useable = IsValid(c_wep) and c_wep:GetClass() == "weapon_ttt_binoculars" + or false + local role_found = (corpse_found and ent.bodySearchResult and ent.bodySearchResult.subrole) + or (IsValid(corpse_ply) and corpse_ply:GetSubRole()) + local roleData = (IsValid(corpse_ply) and corpse_ply:GetSubRoleData()) + or roles.GetByIndex(role_found and ent.bodySearchResult.subrole or ROLE_INNOCENT) + local roleDataClient = client:GetSubRoleData() + + -- enable targetID rendering + tData:EnableText() + tData:EnableOutline(tData:GetEntityDistance() <= 100) + tData:SetOutlineColor(COLOR_YELLOW) + + -- add title and subtitle to the focused ent + tData:SetTitle( + corpse_found and CORPSE.GetPlayerNick(ent, TryT("target_unknown")) or TryT("target_unid"), + role_found and COLOR_WHITE or COLOR_YELLOW + ) + + if tData:GetEntityDistance() <= 100 then + if client:IsSpec() then + tData:SetSubtitle(ParT("corpse_hint_spectator", key_params)) + elseif + bodysearch.GetInspectConfirmMode() == 2 + and not (roleDataClient.isPolicingRole and roleDataClient.isPublicRole) + then + -- a detective added search results, this should change the targetID + if ent.bodySearchResult and ent.bodySearchResult.base.isPublicPolicingSearch then + tData:SetSubtitle(ParT("corpse_hint_public_policing_searched", key_params)) + else + tData:SetSubtitle(ParT("corpse_hint_inspect_limited", key_params)) + end + tData:AddDescriptionLine(TryT("corpse_hint_inspect_limited_details")) + elseif + bodysearch.GetInspectConfirmMode() == 1 + and not (roleDataClient.isPolicingRole and roleDataClient.isPublicRole) + then + tData:SetSubtitle(ParT("corpse_hint_inspect_limited", key_params)) + tData:AddDescriptionLine(TryT("corpse_hint_inspect_limited_details")) + else + tData:SetSubtitle(ParT("corpse_hint", key_params)) + end + elseif binoculars_useable then + tData:SetSubtitle(ParT("corpse_binoculars", { key = Key("+attack", "ATTACK") })) + else + tData:SetSubtitle(TryT("corpse_too_far_away")) + end + + -- add icon to the element + tData:AddIcon( + role_found and roleData.iconMaterial or materialCorpse, + role_found and roleData.color or COLOR_YELLOW + ) + + -- add info if searched by detectives + if ent.bodySearchResult and ent.bodySearchResult.base.isPublicPolicingSearch then + tData:AddDescriptionLine( + TryT("corpse_searched_by_detective"), + roles.DETECTIVE.ltcolor, + { materialDetective } + ) + end + + -- add credits info when corpse has credits + if bodysearch.CanTakeCredits(client, ent) then + local creditsHint = "target_credits_on_search" + if bodysearch.GetInspectConfirmMode() == 0 then + creditsHint = "target_credits_on_confirm" + end + + tData:AddDescriptionLine(TryT(creditsHint), COLOR_GOLD, { materialCredits }) + end end - --- -- This function handles looking at doors and adds specific descriptions -- @param TARGET_DATA tData The object to be used in the hook -- @realm client function targetid.HUDDrawTargetIDDoors(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - - if not IsValid(client) or not client:IsTerror() or not client:Alive() - or not IsValid(ent) or not ent:IsDoor() or not ent:PlayerCanOpenDoor() or tData:GetEntityDistance() > 90 then - return - end - - -- enable targetID rendering - tData:EnableText() - - tData:SetTitle(TryT("name_door")) - - if ent:UseOpensDoor() and not ent:TouchOpensDoor() then - if ent:DoorAutoCloses() then - tData:SetSubtitle(ParT("door_open", key_params)) - else - tData:SetSubtitle(ent:IsDoorOpen() and ParT("door_close", key_params) or ParT("door_open", key_params)) - end - - tData:SetKey(input.GetKeyCode(key_params.usekey)) - elseif not ent:UseOpensDoor() and ent:TouchOpensDoor() then - tData:SetSubtitle(TryT("door_open_touch")) - tData:AddIcon( - materialDoor, - COLOR_LGRAY - ) - else - tData:SetSubtitle(ParT("door_open_touch_and_use", key_params)) - tData:SetKey(input.GetKeyCode(key_params.usekey)) - end - - if ent:IsDoorLocked() then - tData:AddDescriptionLine( - TryT("door_locked"), - COLOR_ORANGE, - {materialLocked} - ) - elseif ent:DoorAutoCloses() then - tData:AddDescriptionLine( - TryT("door_auto_closes"), - COLOR_SLATEGRAY, - {materialAutoClose} - ) - end - - if ent:DoorIsDestructible() then - tData:AddDescriptionLine( - ParT("door_destructible", {health = ent:GetFastSyncedHealth()}), - COLOR_LBROWN, - {materialDestructible} - ) - end + local client = LocalPlayer() + local ent = tData:GetEntity() + + if + not IsValid(client) + or not client:IsTerror() + or not client:Alive() + or not IsValid(ent) + or not ent:IsDoor() + or not ent:PlayerCanOpenDoor() + or tData:GetEntityDistance() > 90 + then + return + end + + -- enable targetID rendering + tData:EnableText() + + tData:SetTitle(TryT("name_door")) + + if ent:UseOpensDoor() and not ent:TouchOpensDoor() then + if ent:DoorAutoCloses() then + tData:SetSubtitle(ParT("door_open", key_params)) + else + tData:SetSubtitle( + ent:IsDoorOpen() and ParT("door_close", key_params) or ParT("door_open", key_params) + ) + end + + tData:SetKey(input.GetKeyCode(key_params.usekey)) + elseif not ent:UseOpensDoor() and ent:TouchOpensDoor() then + tData:SetSubtitle(TryT("door_open_touch")) + tData:AddIcon(materialDoor, COLOR_LGRAY) + else + tData:SetSubtitle(ParT("door_open_touch_and_use", key_params)) + tData:SetKey(input.GetKeyCode(key_params.usekey)) + end + + if ent:IsDoorLocked() then + tData:AddDescriptionLine(TryT("door_locked"), COLOR_ORANGE, { materialLocked }) + elseif ent:DoorAutoCloses() then + tData:AddDescriptionLine(TryT("door_auto_closes"), COLOR_SLATEGRAY, { materialAutoClose }) + end + + if ent:DoorIsDestructible() then + tData:AddDescriptionLine( + ParT("door_destructible", { health = math.ceil(ent:GetFastSyncedHealth()) }), + COLOR_LBROWN, + { materialDestructible } + ) + end end --- @@ -614,23 +657,31 @@ end -- @param TARGET_DATA tData The object to be used in the hook -- @realm client function targetid.HUDDrawTargetIDDNAScanner(tData) - local client = LocalPlayer() - local ent = tData:GetEntity() - - if not IsValid(client:GetActiveWeapon()) or client:GetActiveWeapon():GetClass() ~= "weapon_ttt_wtester" - or tData:GetEntityDistance() > 400 or not IsValid(ent) then - return - end - - -- add an empty line if there's already data in the description area - if tData:GetAmountDescriptionLines() > 0 then - tData:AddDescriptionLine() - end - - if ent:IsWeapon() or ent.CanHavePrints or ent:GetNWBool("HasPrints", false) - or ent:GetClass() == "prop_ragdoll" and CORPSE.GetPlayerNick(ent, false) then - tData:AddDescriptionLine(TryT("dna_tid_possible"), COLOR_GREEN, {materialDNATargetID}) - else - tData:AddDescriptionLine(TryT("dna_tid_impossible"), COLOR_RED, {materialDNATargetID}) - end + local client = LocalPlayer() + local ent = tData:GetEntity() + + if + not IsValid(client:GetActiveWeapon()) + or client:GetActiveWeapon():GetClass() ~= "weapon_ttt_wtester" + or tData:GetEntityDistance() > 400 + or not IsValid(ent) + then + return + end + + -- add an empty line if there's already data in the description area + if tData:GetAmountDescriptionLines() > 0 then + tData:AddDescriptionLine() + end + + if + ent:IsWeapon() + or ent.CanHavePrints + or ent:GetNWBool("HasPrints", false) + or ent:GetClass() == "prop_ragdoll" and CORPSE.GetPlayerNick(ent, false) + then + tData:AddDescriptionLine(TryT("dna_tid_possible"), COLOR_GREEN, { materialDNATargetID }) + else + tData:AddDescriptionLine(TryT("dna_tid_impossible"), COLOR_RED, { materialDNATargetID }) + end end diff --git a/lua/ttt2/libraries/thermalvision.lua b/lua/ttt2/libraries/thermalvision.lua index e4b57b60f..a30bc2bf6 100644 --- a/lua/ttt2/libraries/thermalvision.lua +++ b/lua/ttt2/libraries/thermalvision.lua @@ -12,13 +12,18 @@ local surface = surface local hook = hook if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return + return end thermalvision = {} +---@alias thermalvision_mode +---| `THERMALVISION_MODE_BOTH` +---| `THERMALVISION_MODE_NOTVISIBLE` +---| `THERMALVISION_MODE_VISIBLE` + THERMALVISION_MODE_BOTH = 0 THERMALVISION_MODE_NOTVISIBLE = 1 THERMALVISION_MODE_VISIBLE = 2 @@ -31,188 +36,198 @@ local overlayColorWithBG = Color(0, 0, 50, 235) local overlayColorWithoutBG = Color(0, 0, 0, 235) local colorModifyBG = { - ["$pp_colour_addr"] = 0, - ["$pp_colour_addg"] = 0.0, - ["$pp_colour_addb"] = 0.8, - ["$pp_colour_brightness"] = 0.1, - ["$pp_colour_contrast"] = 0.15, - ["$pp_colour_colour"] = 0.0, - ["$pp_colour_mulr"] = 0, - ["$pp_colour_mulg"] = 0, - ["$pp_colour_mulb"] = 1 + ["$pp_colour_addr"] = 0, + ["$pp_colour_addg"] = 0.0, + ["$pp_colour_addb"] = 0.8, + ["$pp_colour_brightness"] = 0.1, + ["$pp_colour_contrast"] = 0.15, + ["$pp_colour_colour"] = 0.0, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 1, } local colorModifyEnts = { - ["$pp_colour_addr"] = 0.4, - ["$pp_colour_addg"] = 0.2, - ["$pp_colour_addb"] = 0.0, - ["$pp_colour_brightness"] = 1.0, - ["$pp_colour_contrast"] = 0.35, - ["$pp_colour_colour"] = 30, - ["$pp_colour_mulr"] = 0, - ["$pp_colour_mulg"] = 0, - ["$pp_colour_mulb"] = 0 + ["$pp_colour_addr"] = 0.4, + ["$pp_colour_addg"] = 0.2, + ["$pp_colour_addb"] = 0.0, + ["$pp_colour_brightness"] = 1.0, + ["$pp_colour_contrast"] = 0.35, + ["$pp_colour_colour"] = 30, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0, } --- -- Hook that renders the entities with the thermalvision -- @realm client local function RenderHook() - render.ClearStencil() - render.SetStencilEnable(true) - render.SetStencilWriteMask(255) - render.SetStencilTestMask(255) - render.SetStencilReferenceValue(1) - - --first render pass: draw all entities even when z test fails - if #thermalvisionList > 0 then - --draw stencil mask and let everything through - render.SetStencilCompareFunction(STENCIL_ALWAYS) - render.SetStencilZFailOperation(STENCIL_REPLACE) - render.SetStencilPassOperation(STENCIL_REPLACE) - render.SetStencilFailOperation(STENCIL_ZERO) - - --omit depth test but block writing to depth buffer - render.SuppressEngineLighting(true) - render.OverrideDepthEnable(true, false) - - cam.Start3D() - cam.IgnoreZ(true) - for i = 1, #thermalvisionList do - local entry = thermalvisionList[i] - local ent = entry.ent - - if not IsValid(ent) or entry.mode == THERMALVISION_MODE_VISIBLE then continue end - - ent:DrawModel() - end - cam.End3D() - - render.OverrideDepthEnable(false, false) - render.SuppressEngineLighting(false) - - --screenspace effect on stenciled areas to highlight entities - render.SetStencilCompareFunction(STENCIL_EQUAL) - render.SetStencilZFailOperation(STENCIL_KEEP) - render.SetStencilPassOperation(STENCIL_KEEP) - render.SetStencilFailOperation(STENCIL_KEEP) - - DrawColorModify(colorModifyEnts) - - --weaken the effect behind walls - cam.Start2D() - surface.SetDrawColor(bgColoring and overlayColorWithBG or overlayColorWithoutBG) - surface.DrawRect(0, 0, ScrW(), ScrH()) - cam.End2D() - end - - --draw the blue colored background - if bgColoring then - render.SetStencilCompareFunction(STENCIL_NOTEQUAL) - render.SetStencilZFailOperation(STENCIL_KEEP) - render.SetStencilPassOperation(STENCIL_KEEP) - render.SetStencilFailOperation(STENCIL_KEEP) - - DrawColorModify(colorModifyBG) - end - - --second render pass: draw all parts of entities which are visible (z test succceeds) - if #thermalvisionList > 0 then - render.ClearStencil() - - --draw stencil mask only for visible parts - render.SetStencilCompareFunction(STENCIL_ALWAYS) - render.SetStencilZFailOperation(STENCIL_KEEP) - render.SetStencilPassOperation(STENCIL_REPLACE) - render.SetStencilFailOperation(STENCIL_ZERO) - - --render all entities but keep depth testing - render.SuppressEngineLighting(true) - - for i = 1, #thermalvisionList do - local entry = thermalvisionList[i] - local ent = entry.ent - - if not IsValid(ent) then continue end - - --overwrite with the original model and prevent effect appliance afterwards if the effect should be only applied for non visible things - if entry.mode == THERMALVISION_MODE_NOTVISIBLE then - render.SetStencilWriteMask(0) - render.SuppressEngineLighting(false) - end - - ent:DrawModel() - - render.SetStencilWriteMask(255) - render.SuppressEngineLighting(true) - end - - render.SuppressEngineLighting(false) - - --screenspace effect on stenciled areas again - render.SetStencilCompareFunction(STENCIL_EQUAL) - render.SetStencilZFailOperation(STENCIL_KEEP) - render.SetStencilPassOperation(STENCIL_KEEP) - render.SetStencilFailOperation(STENCIL_KEEP) - - DrawColorModify(colorModifyEnts) - end - - render.SetStencilEnable(false) + render.ClearStencil() + render.SetStencilEnable(true) + render.SetStencilWriteMask(255) + render.SetStencilTestMask(255) + render.SetStencilReferenceValue(1) + + --first render pass: draw all entities even when z test fails + if #thermalvisionList > 0 then + --draw stencil mask and let everything through + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilZFailOperation(STENCIL_REPLACE) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilFailOperation(STENCIL_ZERO) + + --omit depth test but block writing to depth buffer + render.SuppressEngineLighting(true) + render.OverrideDepthEnable(true, false) + + cam.Start3D() + cam.IgnoreZ(true) + for i = 1, #thermalvisionList do + local entry = thermalvisionList[i] + local ent = entry.ent + + if not IsValid(ent) or entry.mode == THERMALVISION_MODE_VISIBLE then + continue + end + + ent:DrawModel() + end + cam.End3D() + + render.OverrideDepthEnable(false, false) + render.SuppressEngineLighting(false) + + --screenspace effect on stenciled areas to highlight entities + render.SetStencilCompareFunction(STENCIL_EQUAL) + render.SetStencilZFailOperation(STENCIL_KEEP) + render.SetStencilPassOperation(STENCIL_KEEP) + render.SetStencilFailOperation(STENCIL_KEEP) + + DrawColorModify(colorModifyEnts) + + --weaken the effect behind walls + cam.Start2D() + surface.SetDrawColor(bgColoring and overlayColorWithBG or overlayColorWithoutBG) + surface.DrawRect(0, 0, ScrW(), ScrH()) + cam.End2D() + end + + --draw the blue colored background + if bgColoring then + render.SetStencilCompareFunction(STENCIL_NOTEQUAL) + render.SetStencilZFailOperation(STENCIL_KEEP) + render.SetStencilPassOperation(STENCIL_KEEP) + render.SetStencilFailOperation(STENCIL_KEEP) + + DrawColorModify(colorModifyBG) + end + + --second render pass: draw all parts of entities which are visible (z test succceeds) + if #thermalvisionList > 0 then + render.ClearStencil() + + --draw stencil mask only for visible parts + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilZFailOperation(STENCIL_KEEP) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilFailOperation(STENCIL_ZERO) + + --render all entities but keep depth testing + render.SuppressEngineLighting(true) + + for i = 1, #thermalvisionList do + local entry = thermalvisionList[i] + local ent = entry.ent + + if not IsValid(ent) then + continue + end + + --overwrite with the original model and prevent effect appliance afterwards if the effect should be only applied for non visible things + if entry.mode == THERMALVISION_MODE_NOTVISIBLE then + render.SetStencilWriteMask(0) + render.SuppressEngineLighting(false) + end + + ent:DrawModel() + + render.SetStencilWriteMask(255) + render.SuppressEngineLighting(true) + end + + render.SuppressEngineLighting(false) + + --screenspace effect on stenciled areas again + render.SetStencilCompareFunction(STENCIL_EQUAL) + render.SetStencilZFailOperation(STENCIL_KEEP) + render.SetStencilPassOperation(STENCIL_KEEP) + render.SetStencilFailOperation(STENCIL_KEEP) + + DrawColorModify(colorModifyEnts) + end + + render.SetStencilEnable(false) end --- -- Hook adding -- @realm client local function AddThermalvisionHook() - if thermalvisionHookInstalled then return end + if thermalvisionHookInstalled then + return + end - hook.Add("PostDrawTranslucentRenderables", "RenderThermalvision", RenderHook) + hook.Add("PostDrawTranslucentRenderables", "RenderThermalvision", RenderHook) - thermalvisionHookInstalled = true + thermalvisionHookInstalled = true end --- -- Hook removing -- @realm client local function RemoveThermalvisionHook() - hook.Remove("PostDrawTranslucentRenderables", "RenderThermalvision") + hook.Remove("PostDrawTranslucentRenderables", "RenderThermalvision") - thermalvisionHookInstalled = false + thermalvisionHookInstalled = false end --- -- Clearing the cached @{Entity} list -- @realm client function thermalvision.Clear() - thermalvisionList = {} + thermalvisionList = {} - RemoveThermalvisionHook() + RemoveThermalvisionHook() end --- -- internal entity removing -- @realm client local function RemoveInternal(ents) - local thermalvisionListSize = #thermalvisionList - local entsSize = #ents + local thermalvisionListSize = #thermalvisionList + local entsSize = #ents - if thermalvisionListSize == 0 or entsSize == 0 then return end + if thermalvisionListSize == 0 or entsSize == 0 then + return + end - for i = 1, thermalvisionListSize do - for j = 1, entsSize do - if thermalvisionList[i].ent ~= ents[j] then continue end + for i = 1, thermalvisionListSize do + for j = 1, entsSize do + if thermalvisionList[i].ent ~= ents[j] then + continue + end - --for now only setting it to nil - thermalvisionList[i] = nil + --for now only setting it to nil + thermalvisionList[i] = nil - break - end - end + break + end + end - --cleanup table by - table.RemoveEmptyEntries(thermalvisionList, thermalvisionListSize) + --cleanup table by + table.RemoveEmptyEntries(thermalvisionList, thermalvisionListSize) - thermalvisionList = {} + thermalvisionList = {} end --- @@ -220,36 +235,38 @@ end -- @param table ents list of entities that should get removed -- @realm client function thermalvision.Remove(ents) - ents = istable(ents) and ents or {ents} + ents = istable(ents) and ents or { ents } - RemoveInternal(ents) + RemoveInternal(ents) - if table.IsEmpty(thermalvisionList) and not bgColoring then - RemoveThermalvisionHook() - end + if table.IsEmpty(thermalvisionList) and not bgColoring then + RemoveThermalvisionHook() + end end --- -- Adds entities into the @{Entity} list that should be rendered with thermalvision -- @param table ents list of @{Entity} that should be added --- @param[default=THERMALVISION_MODE_BOTH] enum mode when should the entity be rendererd +-- @param[default=THERMALVISION_MODE_BOTH] thermalvision_mode mode when should the entity be rendererd -- @realm client function thermalvision.Add(ents, mode) - ents = istable(ents) and ents or {ents} + ents = istable(ents) and ents or { ents } - local thermalvisionListSize = #thermalvisionList - local entsSize = #ents + local thermalvisionListSize = #thermalvisionList + local entsSize = #ents - if entsSize == 0 then return end + if entsSize == 0 then + return + end - mode = mode or THERMALVISION_MODE_BOTH + mode = mode or THERMALVISION_MODE_BOTH - for i = 1, entsSize do - thermalvisionList[thermalvisionListSize + i] = {ent = ents[i], mode = mode} - end + for i = 1, entsSize do + thermalvisionList[thermalvisionListSize + i] = { ent = ents[i], mode = mode } + end - -- add the hook if there is something to render - AddThermalvisionHook() + -- add the hook if there is something to render + AddThermalvisionHook() end --- @@ -257,29 +274,31 @@ end -- @param boolean enabled whether or not the background should get colored -- @realm client function thermalvision.SetBackgroundColoring(enabled) - bgColoring = enabled + bgColoring = enabled end --- -- Sets entities of the @{Entity} list to the specified mode -- @param table ents list of @{Entity} that should be set --- @param enum mode when should the entity be rendererd +-- @param thermalvision_mode mode when should the entity be rendered -- @realm client function thermalvision.Set(ents, mode) - ents = istable(ents) and ents or {ents} + ents = istable(ents) and ents or { ents } - local thermalvisionListSize = #thermalvisionList - local entsSize = #ents + local thermalvisionListSize = #thermalvisionList + local entsSize = #ents - if entsSize == 0 then return end + if entsSize == 0 then + return + end - -- check if an entity is already inserted and remove it - RemoveInternal(ents) + -- check if an entity is already inserted and remove it + RemoveInternal(ents) - for i = 1, entsSize do - thermalvisionList[thermalvisionListSize + i] = {ent = ents[i], mode = mode} - end + for i = 1, entsSize do + thermalvisionList[thermalvisionListSize + i] = { ent = ents[i], mode = mode } + end - -- add the hook if there is something to render - AddThermalvisionHook() + -- add the hook if there is something to render + AddThermalvisionHook() end diff --git a/lua/ttt2/libraries/vguihandler.lua b/lua/ttt2/libraries/vguihandler.lua index 86dc36c10..f7c526b42 100644 --- a/lua/ttt2/libraries/vguihandler.lua +++ b/lua/ttt2/libraries/vguihandler.lua @@ -4,20 +4,21 @@ -- @module vguihandler if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return -- the rest of the vguihandler library is client only + return -- the rest of the vguihandler library is client only end local table = table -vguihandler = vguihandler or { - frames = {}, - callback = { - frame = {}, - hidden = {} - } -} +vguihandler = vguihandler + or { + frames = {}, + callback = { + frame = {}, + hidden = {}, + }, + } -- Call this function to create a new frame. Using this instead of setting the -- frame up manually has the benefit that all basic settings are already set. @@ -26,28 +27,28 @@ vguihandler = vguihandler or { -- @param number w The width of the panel -- @param number h The height of the panel -- @param string title The title of the panel --- @return @{Panel} The created/cleared DFrameTTT2 object +-- @return Panel The created/cleared DFrameTTT2 object -- @realm client function vguihandler.GenerateFrame(w, h, title) - local frame = vgui.Create("DFrameTTT2") + local frame = vgui.Create("DFrameTTT2") - frame:SetSize(w, h) - frame:Center() - frame:SetTitle(title) + frame:SetSize(w, h) + frame:Center() + frame:SetTitle(title) - local OriginalClose = frame.Close + local OriginalClose = frame.Close - frame.Close = function(slf) - if slf:GetDeleteOnClose() then - table.RemoveByValue(vguihandler.frames, frame) - end + frame.Close = function(slf) + if slf:GetDeleteOnClose() then + table.RemoveByValue(vguihandler.frames, frame) + end - OriginalClose(slf) - end + OriginalClose(slf) + end - vguihandler.frames[#vguihandler.frames + 1] = frame + vguihandler.frames[#vguihandler.frames + 1] = frame - return frame + return frame end --- @@ -55,20 +56,22 @@ end -- @return table Returns a table of the frames that are now hidden -- @realm client function vguihandler.HideFrames() - local frames = vguihandler.frames - local hiddenFrames = {} + local frames = vguihandler.frames + local hiddenFrames = {} - for i = 1, #frames do - local frame = frames[i] + for i = 1, #frames do + local frame = frames[i] - if frame:IsFrameHidden() then continue end + if frame:IsFrameHidden() then + continue + end - frame:HideFrame() + frame:HideFrame() - hiddenFrames[#hiddenFrames + 1] = frame - end + hiddenFrames[#hiddenFrames + 1] = frame + end - return hiddenFrames + return hiddenFrames end --- @@ -76,28 +79,32 @@ end -- @param table A table of frames -- @realm client function vguihandler.ShowFrames(frames) - for i = 1, #frames do - local frame = frames[i] + for i = 1, #frames do + local frame = frames[i] - if not IsValid(frame) or not frame:IsFrameHidden() then continue end + if not IsValid(frame) or not frame:IsFrameHidden() then + continue + end - frame:ShowFrame() - end + frame:ShowFrame() + end end --- -- Unhides all frames that are currently registered and hidden. -- @realm client function vguihandler.ShowAllFrames() - local frames = vguihandler.frames + local frames = vguihandler.frames - for i = 1, #frames do - local frame = frames[i] + for i = 1, #frames do + local frame = frames[i] - if not frame:IsFrameHidden() then continue end + if not frame:IsFrameHidden() then + continue + end - frame:ShowFrame() - end + frame:ShowFrame() + end end --- @@ -106,28 +113,28 @@ end -- @internal -- @realm client function vguihandler.InvalidateVSkin() - local frames = vguihandler.frames + local frames = vguihandler.frames - for i = 1, #frames do - frames[i]:InvalidateLayout() - end + for i = 1, #frames do + frames[i]:InvalidateLayout() + end end --- -- Rebuilds the whole menu without a specific changed setting. -- @realm client function vguihandler.Rebuild() - local frames = vguihandler.frames + local frames = vguihandler.frames - for i = 1, #frames do - local frame = frames[i] + for i = 1, #frames do + local frame = frames[i] - if isfunction(frame.OnRebuild) then - frame:OnRebuild() - end - end + if isfunction(frame.OnRebuild) then + frame:OnRebuild() + end + end - vguihandler.InvalidateVSkin() + vguihandler.InvalidateVSkin() end --- @@ -135,15 +142,35 @@ end -- @return boolean True if a menu is open -- @realm client function vguihandler.IsOpen() - local frames = vguihandler.frames + local frames = vguihandler.frames - for i = 1, #frames do - if frames[i]:IsFrameHidden() then continue end + for i = 1, #frames do + if frames[i]:IsFrameHidden() then + continue + end - return true - end + return true + end - return false + return false +end + +--- +-- Returns if a menu is blocking TTT2 Binds or not +-- @return boolean True if a menu is open and blocks Bindings +-- @realm client +function vguihandler.IsBlockingBindings() + local frames = vguihandler.frames + + for i = 1, #frames do + if frames[i]:IsFrameHidden() or not frames[i]:IsBlockingBindings() then + continue + end + + return true + end + + return false end --- @@ -152,18 +179,20 @@ end -- @internal -- @realm client function vguihandler.DrawBackground() - if not vguihandler.IsOpen() then return end - - local width = ScrW() - local height = ScrH() - - if vskin.ShouldBlurBackground() then - draw.BlurredBox(0, 0, width, height, 1) - end - - if vskin.ShouldColorBackground() then - -- for some reason the color has to be bigger than the screen to - -- fill the entire screenspace - draw.Box(-1, -1, width + 2, height + 2, vskin.GetScreenColor()) - end + if not vguihandler.IsOpen() then + return + end + + local width = ScrW() + local height = ScrH() + + if vskin.ShouldBlurBackground() then + draw.BlurredBox(0, 0, width, height, 1) + end + + if vskin.ShouldColorBackground() then + -- for some reason the color has to be bigger than the screen to + -- fill the entire screenspace + draw.Box(-1, -1, width + 2, height + 2, vskin.GetScreenColor()) + end end diff --git a/lua/ttt2/libraries/vskin.lua b/lua/ttt2/libraries/vskin.lua index 1323fe542..c1d6376bc 100644 --- a/lua/ttt2/libraries/vskin.lua +++ b/lua/ttt2/libraries/vskin.lua @@ -4,21 +4,24 @@ -- @module vskin if SERVER then - AddCSLuaFile() + AddCSLuaFile() - return -- the rest of the vskin library is client only + return -- the rest of the vskin library is client only end --- -- @realm client +-- stylua: ignore local cv_selectedVSkin = CreateConVar("ttt2_selected_vskin", "dark_ttt2", {FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cv_blurVSkin = CreateConVar("ttt2_vskin_blur", 1, {FCVAR_ARCHIVE}) --- -- @realm client +-- stylua: ignore local cv_colorVSkin = CreateConVar("ttt2_vskin_color", 1, {FCVAR_ARCHIVE}) vskin = vskin or {} @@ -31,13 +34,13 @@ vskin.skins = vskin.skins or {} -- @param table skin The skin table -- @realm client function vskin.RegisterVSkin(name, skin) - if vskin.skins[name] then - print("[TTT2 Skin] A skin with this name already exists!") + if vskin.skins[name] then + ErrorNoHaltWithStack("[TTT2 Skin] A skin with this name already exists!") - return - end + return + end - vskin.skins[name] = skin + vskin.skins[name] = skin end --- @@ -46,21 +49,19 @@ end -- @return boolean Returns true if skin was selected -- @realm client function vskin.SelectVSkin(skinName) - local oldSkinName = vskin.GetVSkinName() + local oldSkinName = vskin.GetVSkinName() - if not skinName or not vskin.skins[skinName] - or skinName == oldSkinName - then - return false - end + if not skinName or not vskin.skins[skinName] or skinName == oldSkinName then + return false + end - cv_selectedVSkin:SetString(skinName) + cv_selectedVSkin:SetString(skinName) - vguihandler.InvalidateVSkin() + vguihandler.InvalidateVSkin() - vskin.UpdatedVSkin(oldSkinName, skinName) + vskin.UpdatedVSkin(oldSkinName, skinName) - return true + return true end --- @@ -71,13 +72,14 @@ end -- @realm client -- @internal function vskin.UpdatedVSkin(oldSkinName, skinName) - -- run SKIN function to update color table - derma.GetSkinTable()["ttt2_default"]:UpdatedVSkin() - - --- - -- Run hook for other addons to use - -- @realm client - hook.Run("TTT2UpdatedVSkin", oldSkinName, skinName) + -- run SKIN function to update color table + derma.GetSkinTable()["ttt2_default"]:UpdatedVSkin() + + --- + -- Run hook for other addons to use + -- @realm client + -- stylua: ignore + hook.Run("TTT2UpdatedVSkin", oldSkinName, skinName) end --- @@ -85,13 +87,13 @@ end -- @return table The list of all names -- @realm client function vskin.GetVSkinList() - local names = {} + local names = {} - for name in pairs(vskin.skins) do - names[#names + 1] = name - end + for name in pairs(vskin.skins) do + names[#names + 1] = name + end - return names + return names end --- @@ -99,7 +101,7 @@ end -- @return string The name of the vskin -- @realm client function vskin.GetVSkinName() - return cv_selectedVSkin:GetString() + return cv_selectedVSkin:GetString() end --- @@ -107,7 +109,7 @@ end -- @return string The name of the vskin -- @realm client function vskin.GetDefaultVSkinName() - return cv_selectedVSkin:GetDefault() + return cv_selectedVSkin:GetDefault() end --- @@ -115,7 +117,7 @@ end -- @param[default=true] boolean state -- @realm client function vskin.SetBlurBackground(state) - cv_blurVSkin:SetBool(state == nil and true or state) + cv_blurVSkin:SetBool(state == nil and true or state) end --- @@ -124,7 +126,7 @@ end -- @return boolean Should the background be blurred -- @realm client function vskin.ShouldBlurBackground() - return cv_blurVSkin:GetBool() + return cv_blurVSkin:GetBool() end --- @@ -132,7 +134,7 @@ end -- @param[default=true] boolean state -- @realm client function vskin.SetColorBackground(state) - cv_colorVSkin:SetBool(state == nil and true or state) + cv_colorVSkin:SetBool(state == nil and true or state) end --- @@ -141,7 +143,7 @@ end -- @return boolean Should the background be colored -- @realm client function vskin.ShouldColorBackground() - return cv_colorVSkin:GetBool() + return cv_colorVSkin:GetBool() end --- @@ -149,13 +151,13 @@ end -- @return[default=Color(255, 255, 255, 255)] Color The background color -- @realm client function vskin.GetBackgroundColor() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return COLOR_WHITE - end + if not vskinObject then + return COLOR_WHITE + end - return vskinObject.colors.background + return vskinObject.colors.background end --- @@ -163,13 +165,13 @@ end -- @return[default=Color(255, 255, 255, 255)] Color The accent color -- @realm client function vskin.GetAccentColor() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return COLOR_WHITE - end + if not vskinObject then + return COLOR_WHITE + end - return vskinObject.colors.accent + return vskinObject.colors.accent end --- @@ -177,13 +179,13 @@ end -- @return[default=Color(255, 255, 255, 255)] Color The dark accent color -- @realm client function vskin.GetDarkAccentColor() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return COLOR_WHITE - end + if not vskinObject then + return COLOR_WHITE + end - return vskinObject.colors.accent_dark + return vskinObject.colors.accent_dark end --- @@ -191,13 +193,13 @@ end -- @return[default=Color(255, 255, 255, 255)] Color The scrollbar color -- @realm client function vskin.GetScrollbarColor() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return COLOR_WHITE - end + if not vskinObject then + return COLOR_WHITE + end - return vskinObject.colors.scroll + return vskinObject.colors.scroll end --- @@ -205,13 +207,13 @@ end -- @return[default=Color(255, 255, 255, 255)] Color The scrollbar color -- @realm client function vskin.GetScreenColor() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return COLOR_WHITE - end + if not vskinObject then + return COLOR_WHITE + end - return vskinObject.colors.screen + return vskinObject.colors.screen end --- @@ -219,13 +221,13 @@ end -- @return[default=Color(255, 255, 255, 255)] Color The shadow color -- @realm client function vskin.GetShadowColor() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return COLOR_WHITE - end + if not vskinObject then + return COLOR_WHITE + end - return vskinObject.colors.shadow + return vskinObject.colors.shadow end --- @@ -233,13 +235,13 @@ end -- @return[default=Color(255, 255, 255, 255)] Color The title text color -- @realm client function vskin.GetTitleTextColor() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return COLOR_WHITE - end + if not vskinObject then + return COLOR_WHITE + end - return vskinObject.colors.title_text + return vskinObject.colors.title_text end --- @@ -247,13 +249,13 @@ end -- @return[default=5] number The shadow size -- @realm client function vskin.GetShadowSize() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return 5 - end + if not vskinObject then + return 5 + end - return vskinObject.params.shadow_size + return vskinObject.params.shadow_size end --- @@ -261,13 +263,13 @@ end -- @return[default=45] number The header height -- @realm client function vskin.GetHeaderHeight() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return 45 - end + if not vskinObject then + return 45 + end - return vskinObject.params.header_height + return vskinObject.params.header_height end --- @@ -275,13 +277,13 @@ end -- @return[default=45] number The collapsable height -- @realm client function vskin.GetCollapsableHeight() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return 30 - end + if not vskinObject then + return 30 + end - return vskinObject.params.collapsable_height + return vskinObject.params.collapsable_height end --- @@ -289,13 +291,13 @@ end -- @return[default=3] number The border size -- @realm client function vskin.GetBorderSize() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return 3 - end + if not vskinObject then + return 3 + end - return vskinObject.params.border_size + return vskinObject.params.border_size end --- @@ -303,13 +305,13 @@ end -- @return[default=6] number The corner radius -- @realm client function vskin.GetCornerRadius() - local vskinObject = vskin.skins[vskin.GetVSkinName()] + local vskinObject = vskin.skins[vskin.GetVSkinName()] - if not vskinObject then - return 6 - end + if not vskinObject then + return 6 + end - return vskinObject.params.corner_radius + return vskinObject.params.corner_radius end --- @@ -318,6 +320,4 @@ end -- @param string skinName The name of the newly selected vskin -- @hook -- @realm client -function GM:TTT2UpdatedVSkin(oldSkinName, skinName) - -end +function GM:TTT2UpdatedVSkin(oldSkinName, skinName) end diff --git a/lua/ttt2/libraries/weaponrenderer.lua b/lua/ttt2/libraries/weaponrenderer.lua new file mode 100644 index 000000000..7b3c7133f --- /dev/null +++ b/lua/ttt2/libraries/weaponrenderer.lua @@ -0,0 +1,432 @@ +--- +-- Used to build custom world and view models. It also renders those custom models. +-- This module can only be used by entities that are based on `weapon_ttt_base` as this +-- relies on some functions defined in this class. +-- This code is based on the SWEP construction kit. +-- @ref https://github.com/MagicSwap/SWEP_Construction_Kit +-- @author Mineotopia +-- @module weaponrenderer + +---@class ModelData +---@field type string The type of this model, it can be "Model", "Sprite" or "Quad" +---@field model string The path to the model file +---@field bone string The name of the reference bone +---@field rel string The name of a related identifier +---@field pos Vector The position offset of the model +---@field angle Angle The rotation offset of the model +---@field size Vector The size multiplier of the model +---@field color Color The color for the model +---@field surpresslightning boolean Set to true if the engine lightning should be suppressed +---@field material string The model material +---@field skin number The skin index of the model +---@field bodygroup table A table of bodygroups + +---@class BoneData +---@field scale Vector The scale multiplier +---@field pos Vector The position offset +---@field angle Angle The rotation offset + +if SERVER then + AddCSLuaFile() + + return +end + +weaponrenderer = {} + +local propertiesMaterial = { "nocull", "additive", "vertexalpha", "vertexcolor", "ignorez" } + +--- +-- Gets the orientation of a given bone. +-- @param Entity wep The weapon whose bone orientation is desired +-- @param table baseDataTable The base data table, most of the time the view elements +-- @param table dataTable The data table, most of the time the model data +-- @param Entity boneEntity The bone entity, can be the view model, the player or the weapon +-- @param boolean isInWorld If the weapon is placed in the world, without a player holding it +-- @return Vector pos The bone position +-- @return Angle ang The bone angle +-- @realm client +local function GetBoneOrientation(wep, baseDataTable, dataTable, boneEntity, isInWorld) + local bone, pos, ang + + if dataTable.rel and dataTable.rel ~= "" then + local tbl = baseDataTable[dataTable.rel] + + if not tbl then + return + end + + -- Technically, if there exists an element with the same name as a bone + -- you can get in an infinite loop. Let's just hope nobody's that stupid. + pos, ang = GetBoneOrientation(wep, baseDataTable, tbl, boneEntity, isInWorld) + + if not pos then + return + end + + pos = pos + ang:Forward() * tbl.pos.x + ang:Right() * tbl.pos.y + ang:Up() * tbl.pos.z + + ang:RotateAroundAxis(ang:Up(), tbl.angle.y) + ang:RotateAroundAxis(ang:Right(), tbl.angle.p) + ang:RotateAroundAxis(ang:Forward(), tbl.angle.r) + else + bone = boneEntity:LookupBone(dataTable.bone) + + -- as a fallback, always try to use the first bone + -- this is important when the model is thrown in the world and has to be + -- rendered on its own; especially for models that chose random names for + -- their main bone + if isInWorld then + bone = bone or boneEntity:LookupBone(boneEntity:GetBoneName(0)) + end + + if not bone then + return + end + + pos, ang = Vector(0, 0, 0), Angle(0, 0, 0) + + local matrix = boneEntity:GetBoneMatrix(bone) + + if matrix then + pos, ang = matrix:GetTranslation(), matrix:GetAngles() + end + + -- if the entity has no function to determine the owner it is probably a clientside entity + -- meaning that no view model is needed since it only renders a world model + if isfunction(wep.GetOwner) then + local owner = wep:GetOwner() + + if + IsValid(owner) + and owner:IsPlayer() + and boneEntity == owner:GetViewModel() + and wep.ViewModelFlip + then + ang.r = -ang.r -- Fixes mirrored models + end + end + end + + return pos, ang +end + +--- +-- Creates a clientside model that is used as a view or world model with the provided model data. +-- @param Entity wep The weapon for which the model should be created +-- @param ModelData modelData The model data for the model +-- @return table The new created model data table +-- @realm client +function weaponrenderer.CreateModel(wep, modelData) + local modelDataCopy = table.FullCopy(modelData) + + -- handle a model being added to the view or world model + if + modelDataCopy.type == "Model" + and modelDataCopy.model + and modelDataCopy.model ~= "" + and (not IsValid(modelDataCopy.modelEnt) or modelDataCopy.createdModel ~= modelDataCopy.model) + and string.find(modelDataCopy.model, ".mdl") + and file.Exists(modelDataCopy.model, "GAME") + then + -- note on the rendergroup: This rendergroup is not listed in the GMod wiki; however all + -- SWEP construction kit weapons use this rendergroup - and it works + modelDataCopy.modelEnt = + ClientsideModel(modelDataCopy.model, RENDER_GROUP_VIEW_MODEL_OPAQUE) + + if IsValid(modelDataCopy.modelEnt) then + -- for clientside entities wep:GetPos is not defined; since a view model is not needed + -- for these entities, this can be ignored + if isfunction(wep.GetPos) then + modelDataCopy.modelEnt:SetPos(wep:GetPos()) + modelDataCopy.modelEnt:SetAngles(wep:GetAngles()) + modelDataCopy.modelEnt:SetParent(wep) + modelDataCopy.modelEnt:SetNoDraw(true) + end + modelDataCopy.createdModel = modelDataCopy.model + else + modelDataCopy.modelEnt = nil + end + + -- handle a sprite being added to the view or world model + elseif + modelDataCopy.type == "Sprite" + and modelDataCopy.sprite + and modelDataCopy.sprite ~= "" + and (not modelDataCopy.spriteMaterial or modelDataCopy.createdSprite ~= modelDataCopy.sprite) + and file.Exists("materials/" .. modelDataCopy.sprite .. ".vmt", "GAME") + then + local name = modelDataCopy.sprite .. "-" + local params = { + ["$basetexture"] = modelDataCopy.sprite, + } + + -- make sure we create a unique name based on the selected options + for i = 1, #propertiesMaterial do + local property = propertiesMaterial[i] + + if modelDataCopy[property] then + params["$" .. property] = 1 + name = name .. "1" + else + name = name .. "0" + end + end + + modelDataCopy.createdSprite = modelDataCopy.sprite + modelDataCopy.spriteMaterial = CreateMaterial(name, "UnlitGeneric", params) + end + + return modelDataCopy +end + +--- +-- Resets the bone positions of a view model. +-- @param Entity viewModel The view model of the weapon +-- @realm client +function weaponrenderer.ResetBonePositions(viewModel) + if not IsValid(viewModel) then + return + end + + local boneCount = viewModel:GetBoneCount() + + if not boneCount then + return + end + + for i = 0, boneCount do + viewModel:ManipulateBoneScale(i, Vector(1, 1, 1)) + viewModel:ManipulateBoneAngles(i, Angle(0, 0, 0)) + viewModel:ManipulateBonePosition(i, Vector(0, 0, 0)) + end +end + +--- +-- Updates the bone positions of the view model of a weapon. If there are +-- none setup yet, it will reset it to a default state. +-- @param Entity wep The weapon for which the view model should be updated +-- @param Entity viewModel The view model of the weapon +-- @realm client +function weaponrenderer.UpdateBonePositions(wep, viewModel) + if not wep.customViewModelBoneMods then + weaponrenderer.ResetBonePositions(viewModel) + + return + end + + for i = 0, viewModel:GetBoneCount() do + local bonename = viewModel:GetBoneName(i) + local dataTable + + if wep.customViewModelBoneMods[bonename] then + dataTable = wep.customViewModelBoneMods[bonename] + else + dataTable = { + scale = Vector(1, 1, 1), + pos = Vector(0, 0, 0), + angle = Angle(0, 0, 0), + } + end + + local scale = dataTable.scale + local position = dataTable.pos + local modelScale = Vector(1, 1, 1) + + local currentBone = viewModel:GetBoneParent(i) + + while currentBone >= 0 do + local viewModelBoneMod = wep.customViewModelBoneMods[viewModel:GetBoneName(currentBone)] + + if viewModelBoneMod then + modelScale = modelScale * viewModelBoneMod.scale + end + + currentBone = viewModel:GetBoneParent(currentBone) + end + + scale = scale * modelScale + + if viewModel:GetManipulateBoneScale(i) ~= scale then + viewModel:ManipulateBoneScale(i, scale) + end + + if viewModel:GetManipulateBoneAngles(i) ~= dataTable.angle then + viewModel:ManipulateBoneAngles(i, dataTable.angle) + end + + if viewModel:GetManipulateBonePosition(i) ~= position then + viewModel:ManipulateBonePosition(i, position) + end + end +end + +--- +-- Returns an ordered elements table. The render order is necessary +-- because models have to be drawn before sprites and quads. +-- @param table elements the view elements table +-- @return table Returns a new or the cached render order +-- @realm client +local function BuildRenderOrder(elements) + local newRenderOrder = {} + + for identifier, dataTable in pairs(elements) do + if dataTable.type == "Model" then + table.insert(newRenderOrder, 1, identifier) + elseif dataTable.type == "Sprite" or dataTable.type == "Quad" then + table.insert(newRenderOrder, identifier) + end + end + + return newRenderOrder +end + +--- +-- Renders the world or view model. +-- @param Entity wep The weapon whose view or world model should be rendered +-- @param table elements The elements of the view or world model +-- @param Entity boneEntity The bone entity, can be the view model, the player or the weapon +-- @realm client +function weaponrenderer.Render(wep, elements, boneEntity) + local renderOrder = BuildRenderOrder(elements) + + -- if a model has a hand bone, it is probably created as a weapon and should therefore + -- be handled that way. If a model is abused as a weapon then it doesn't have a handbone + -- and should therefore not be rotated + local boneId = boneEntity:LookupBone("ValveBiped.Bip01_R_Hand") + local hasHandBone = boneId and boneId ~= "__INVALIDBONE__" + + for i = 1, #renderOrder do + local identifier = renderOrder[i] + local modelData = elements[identifier] + + if modelData.hide or not modelData.bone then + continue + end + + local model = modelData.modelEnt + local sprite = modelData.spriteMaterial + + local pos, ang = GetBoneOrientation(wep, elements, modelData, boneEntity, not hasHandBone) + + if not pos then + continue + end + + local posModel = pos + + if hasHandBone then + posModel = posModel + + ang:Forward() * modelData.pos.x + + ang:Right() * modelData.pos.y + + ang:Up() * modelData.pos.z + end + + if modelData.type == "Model" and IsValid(model) then + if hasHandBone then + model:SetPos(posModel) + + ang:RotateAroundAxis(ang:Up(), modelData.angle.y) + ang:RotateAroundAxis(ang:Right(), modelData.angle.p) + ang:RotateAroundAxis(ang:Forward(), modelData.angle.r) + + model:SetAngles(ang) + + local matrix = Matrix() + matrix:Scale(modelData.size) + + model:EnableMatrix("RenderMultiply", matrix) + end + + model:SetMaterial(modelData.material) + + if modelData.skin then + model:SetSkin(modelData.skin) + end + + if modelData.bodygroup then + for bodygroup, value in pairs(modelData.bodygroup) do + wep:SetBodygroup(bodygroup, value) + end + end + + if modelData.surpresslightning then + render.SuppressEngineLighting(true) + end + + render.SetColorModulation( + modelData.color.r / 255, + modelData.color.g / 255, + modelData.color.b / 255 + ) + render.SetBlend(modelData.color.a / 255) + + model:DrawModel() + + render.SetBlend(1) + render.SetColorModulation(1, 1, 1) + + if modelData.surpresslightning then + render.SuppressEngineLighting(false) + end + elseif modelData.type == "Sprite" and sprite then + render.SetMaterial(sprite) + render.DrawSprite(posModel, modelData.size.x, modelData.size.y, modelData.color) + elseif modelData.type == "Quad" and modelData.draw_func then + ang:RotateAroundAxis(ang:Up(), modelData.angle.y) + ang:RotateAroundAxis(ang:Right(), modelData.angle.p) + ang:RotateAroundAxis(ang:Forward(), modelData.angle.r) + + cam.Start3D2D(posModel, ang, modelData.size) + modelData.draw_func(wep) + cam.End3D2D() + end + end +end + +--- +-- Renders the view model if valid view model elements are provided. It also updates the bone +-- positions so that the view model tracks the hands. +-- @param Entity wep The weapon whose view model should be rendered +-- @param table elements The elements of the view model +-- @param Entity viewModel The player's view model +-- @realm client +function weaponrenderer.RenderViewModel(wep, elements, viewModel) + if not elements then + return + end + + weaponrenderer.UpdateBonePositions(wep, viewModel) + + weaponrenderer.Render(wep, elements, viewModel) +end + +--- +-- Renders the wold model if valid wold model elements are provided. It also renders the default +-- world model of the weapon if enabled +-- @param Entity wep The weapon whose world model should be rendered +-- @param Entity wepModel The weapon mode whose world model should be rendered, in most cases +-- identical to the first parameter +-- @param table elements The elements of the world model +-- @param[opt] Player owner The owner entity of the weapon, binds the model to their hands +-- @realm client +function weaponrenderer.RenderWorldModel(wep, wepModel, elements, owner) + if wep.ShowDefaultWorldModel then + wepModel:DrawModel() + end + + if not elements then + return + end + + local boneEnt + + if IsValid(owner) then + boneEnt = owner + else + -- when the weapon is dropped for example + boneEnt = wepModel + end + + weaponrenderer.Render(wepModel, elements, boneEnt) +end diff --git a/materials/vgui/ttt/perks/hud_disguiser.png b/materials/vgui/ttt/perks/hud_disguiser.png deleted file mode 100644 index 3227dff74d663ace128d40dbb37e1752dbd758ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4878 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 Apa1{> diff --git a/models/weapons/v_ttt2_dna_scanner.dx80.vtx b/models/weapons/v_ttt2_dna_scanner.dx80.vtx deleted file mode 100644 index 2dc88af23f541174841aef5c8393b780800e4b2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21186 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 LnST6H`j7q(G+_zW diff --git a/models/weapons/v_ttt2_dna_scanner.sw.vtx b/models/weapons/v_ttt2_dna_scanner.sw.vtx deleted file mode 100644 index fd4236b45fb17d75bcb73469fe9114dd21f356ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21170 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 T9+i!`@(P@FWu1u7@PGdYinw&O diff --git a/models/weapons/w_ttt2_dna_scanner.dx80.vtx b/models/weapons/w_ttt2_dna_scanner.dx80.vtx deleted file mode 100644 index e9ecf12c6c7b0fad423121b38b2ece5789cffb0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21186 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 I|NH0v0PWuiTmS$7 diff --git a/models/weapons/w_ttt2_dna_scanner.sw.vtx b/models/weapons/w_ttt2_dna_scanner.sw.vtx deleted file mode 100644 index 3c68afd0eec13b79cb8f35cf40726781c8ad40f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21170 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 ts|d9 Date: Sun, 3 Mar 2024 16:23:50 -0500 Subject: [PATCH 04/10] whitespace only changes --- lua/ttt2/libraries/credits.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/ttt2/libraries/credits.lua b/lua/ttt2/libraries/credits.lua index 864184a32..80c59fd2a 100644 --- a/lua/ttt2/libraries/credits.lua +++ b/lua/ttt2/libraries/credits.lua @@ -49,7 +49,7 @@ function credits.HandleKillCreditsAward(victim, attacker) attacker:AddCredits(creditsAmount) - hook.Run("TTT2ReceivedKillCredits", victim, attacker, creditsAmount) + hook.Run("TTT2ReceivedKillCredits", victim, attacker, creditsAmount) LANG.Msg( attacker, @@ -147,7 +147,7 @@ function credits.HandleKillCreditsAward(victim, attacker) -- now reward their player for their good game plyToAward:AddCredits(creditsAmount) - hook.Run("TTT2ReceivedTeamAwardCredits", plyToAward, creditsAmount) + hook.Run("TTT2ReceivedTeamAwardCredits", plyToAward, creditsAmount) LANG.Msg(plyToAward, "credit_all", { num = creditsAmount }, MSG_MSTACK_ROLE) end From c95331f93cb86150c3d6a27f4fe86d2bad39cfdb Mon Sep 17 00:00:00 2001 From: Luke Tomkus Date: Sun, 3 Mar 2024 16:49:16 -0500 Subject: [PATCH 05/10] Update Changelog. --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25535f434..ed5ff5166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,15 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel - Added hook ENTITY:ClientUse(), which is triggered clientside if an entity is used - Return true to prevent also using this on the server for clientside only usecases +- Added hook `GM:TTT2OnGiveFoundCredits()`, which is triggered serverside when a player has been given credits for searching a corpse. +- Added hook `GM:TTT2ReceivedKillCredits()`, which is called when a player recieves credits for a kill. +- Added hook `GM:TTT2ReceivedTeamAwardCredits()`, which is called when a player recieves credits as a team award. +- Added hook `GM:TTT2TransferedCredits()`, which is called when a player has successfully transfered a credit to another player. ### Changed +- TryRerollShop calls `TTT2OrderedEquipment` hook. + ### Fixed ## [v0.13.1b](https://github.com/TTT-2/TTT2/tree/v0.13.1b) (2024-02-27) From 97d509cb47bd7b1250a9e4e2a8e56d122035f5f2 Mon Sep 17 00:00:00 2001 From: Luke Tomkus Date: Sun, 3 Mar 2024 21:39:43 -0500 Subject: [PATCH 06/10] Add missing realm comments. --- gamemodes/terrortown/gamemode/shared/sh_shop.lua | 9 +++++++++ lua/ttt2/libraries/credits.lua | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/gamemodes/terrortown/gamemode/shared/sh_shop.lua b/gamemodes/terrortown/gamemode/shared/sh_shop.lua index 52968c752..5ca13ca08 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_shop.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_shop.lua @@ -376,6 +376,9 @@ function shop.TryRerollShop(ply) else ply:SubtractCredits(GetGlobalInt("ttt2_random_shop_reroll_cost")) shop.ForceRerollShop(ply) + --- + -- @realm server + -- stylua: ignore hook.Run("TTT2OrderedEquipment", ply, "reroll_shop", false, GetGlobalInt("ttt2_random_shop_reroll_cost"), false) end @@ -428,6 +431,9 @@ function shop.TransferCredits(ply, targetPlyId64, credits) if target:IsTerror() and target:Alive() then target:AddCredits(credits) + --- + -- @realm server + -- stylua: ignore hook.Run("TTT2TransferedCredits", ply, target, credits, false) else -- The would be recipient is dead, which the sender may not know. @@ -436,6 +442,9 @@ function shop.TransferCredits(ply, targetPlyId64, credits) if IsValid(rag) then CORPSE.SetCredits(rag, CORPSE.GetCredits(rag, 0) + credits) + --- + -- @realm server + -- stylua: ignore hook.Run("TTT2TransferedCredits", ply, target, credits, false) end end diff --git a/lua/ttt2/libraries/credits.lua b/lua/ttt2/libraries/credits.lua index 80c59fd2a..74d22ee06 100644 --- a/lua/ttt2/libraries/credits.lua +++ b/lua/ttt2/libraries/credits.lua @@ -49,6 +49,9 @@ function credits.HandleKillCreditsAward(victim, attacker) attacker:AddCredits(creditsAmount) + --- + -- @realm server + -- stylua: ignore hook.Run("TTT2ReceivedKillCredits", victim, attacker, creditsAmount) LANG.Msg( @@ -147,6 +150,9 @@ function credits.HandleKillCreditsAward(victim, attacker) -- now reward their player for their good game plyToAward:AddCredits(creditsAmount) + --- + -- @realm server + -- stylua: ignore hook.Run("TTT2ReceivedTeamAwardCredits", plyToAward, creditsAmount) LANG.Msg(plyToAward, "credit_all", { num = creditsAmount }, MSG_MSTACK_ROLE) From 55f2103f3fbb3ff11105d0051a6c781f2764d921 Mon Sep 17 00:00:00 2001 From: Luke Tomkus Date: Sun, 3 Mar 2024 21:56:04 -0500 Subject: [PATCH 07/10] Add missing newline in sv_player_ext.lua to satisfy stylua. --- gamemodes/terrortown/gamemode/server/sv_player_ext.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua index a8a401a69..ce780f12e 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua @@ -1907,4 +1907,4 @@ function GM:TTT2ReceivedKillCredits(ply, victim, credits) end -- @param number credits The amount of credits the player received -- @hook -- @realm server -function GM:TTT2ReceivedTeamAwardCredits(ply, credits) end \ No newline at end of file +function GM:TTT2ReceivedTeamAwardCredits(ply, credits) end From 7fb4ee8be7f904c1d15873704b6992e548c268eb Mon Sep 17 00:00:00 2001 From: Luke Tomkus Date: Thu, 28 Mar 2024 17:02:53 -0400 Subject: [PATCH 08/10] Rename hooks and fix missing OnGiveFoundCredits() call (missed that the call location moved to bodysearch) --- CHANGELOG.md | 8 ++++---- gamemodes/terrortown/gamemode/server/sv_corpse.lua | 9 --------- .../terrortown/gamemode/server/sv_player_ext.lua | 4 ++-- gamemodes/terrortown/gamemode/server/sv_shop.lua | 2 +- gamemodes/terrortown/gamemode/shared/sh_shop.lua | 4 ++-- lua/ttt2/libraries/bodysearch.lua | 11 +++++++++++ 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5ff5166..257d94e56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,10 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel - Added hook ENTITY:ClientUse(), which is triggered clientside if an entity is used - Return true to prevent also using this on the server for clientside only usecases -- Added hook `GM:TTT2OnGiveFoundCredits()`, which is triggered serverside when a player has been given credits for searching a corpse. -- Added hook `GM:TTT2ReceivedKillCredits()`, which is called when a player recieves credits for a kill. -- Added hook `GM:TTT2ReceivedTeamAwardCredits()`, which is called when a player recieves credits as a team award. -- Added hook `GM:TTT2TransferedCredits()`, which is called when a player has successfully transfered a credit to another player. +- Added hook `GM:TTT2OnGiveFoundCredits()`, which is called when a player has been given credits for searching a corpse. +- Added hook `GM:TTT2OnReceiveKillCredits()`, which is called when a player recieves credits for a kill. +- Added hook `GM:TTT2OnReceiveTeamAwardCredits()`, which is called when a player recieves credits as a team award. +- Added hook `GM:TTT2OnTransferCredits()`, which is called when a player has successfully transfered a credit to another player. ### Changed diff --git a/gamemodes/terrortown/gamemode/server/sv_corpse.lua b/gamemodes/terrortown/gamemode/server/sv_corpse.lua index 6f3faaf53..41b2079af 100644 --- a/gamemodes/terrortown/gamemode/server/sv_corpse.lua +++ b/gamemodes/terrortown/gamemode/server/sv_corpse.lua @@ -732,12 +732,3 @@ function GM:TTT2ModifyRagdollVelocity(deadply, rag, velocity) end -- @hook -- @realm server function GM:TTTOnCorpseCreated(rag, deadply) end - ---- --- Called after a player has been given credits for searching a corpse. --- @param Player ply The player that searched the corpse --- @param Entity rag The ragdoll that was searched --- @param number credits The amount of credits that were given --- @hook --- @realm server -function GM:TTT2OnGiveFoundCredits(ply, rag, credits) end diff --git a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua index ce780f12e..707c346f7 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua @@ -1899,7 +1899,7 @@ function GM:TTT2ModifyDefaultTraitorCredits(ply, credits) end -- @param number credits The amount of credits the player received -- @hook -- @realm server -function GM:TTT2ReceivedKillCredits(ply, victim, credits) end +function GM:TTT2OnReceiveKillCredits(ply, victim, credits) end --- -- Hook that is called when a player recieves credits as a team award. @@ -1907,4 +1907,4 @@ function GM:TTT2ReceivedKillCredits(ply, victim, credits) end -- @param number credits The amount of credits the player received -- @hook -- @realm server -function GM:TTT2ReceivedTeamAwardCredits(ply, credits) end +function GM:TTT2OnReceiveTeamAwardCredits(ply, credits) end diff --git a/gamemodes/terrortown/gamemode/server/sv_shop.lua b/gamemodes/terrortown/gamemode/server/sv_shop.lua index d384c422a..0e02959b4 100644 --- a/gamemodes/terrortown/gamemode/server/sv_shop.lua +++ b/gamemodes/terrortown/gamemode/server/sv_shop.lua @@ -193,7 +193,7 @@ function GM:TTT2CanTransferCredits(sender, recipient, credits_per_xfer) end -- @param boolean isRecipientDead If the recipient is dead or not. -- @hook -- @realm server -function GM:TTT2TransferedCredits(sender, recipient, credits, isRecipientDead) end +function GM:TTT2OnTransferCredits(sender, recipient, credits, isRecipientDead) end local function TransferCredits(ply, cmd, args) if #args ~= 2 then diff --git a/gamemodes/terrortown/gamemode/shared/sh_shop.lua b/gamemodes/terrortown/gamemode/shared/sh_shop.lua index 5ca13ca08..2f67a8d07 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_shop.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_shop.lua @@ -434,7 +434,7 @@ function shop.TransferCredits(ply, targetPlyId64, credits) --- -- @realm server -- stylua: ignore - hook.Run("TTT2TransferedCredits", ply, target, credits, false) + hook.Run("TTT2OnTransferCredits", ply, target, credits, false) else -- The would be recipient is dead, which the sender may not know. -- Instead attempt to send the credits to the target's corpse, where they can be picked up. @@ -445,7 +445,7 @@ function shop.TransferCredits(ply, targetPlyId64, credits) --- -- @realm server -- stylua: ignore - hook.Run("TTT2TransferedCredits", ply, target, credits, false) + hook.Run("TTT2OnTransferCredits", ply, target, credits, false) end end diff --git a/lua/ttt2/libraries/bodysearch.lua b/lua/ttt2/libraries/bodysearch.lua index 7abb8e5cd..e819c090d 100644 --- a/lua/ttt2/libraries/bodysearch.lua +++ b/lua/ttt2/libraries/bodysearch.lua @@ -215,6 +215,8 @@ if SERVER then events.Trigger(EVENT_CREDITFOUND, ply, rag, credits) + hook.Run("TTT2OnGiveFoundCredits", ply, rag, credits) + -- update clients so their UIs can be updated net.Start("ttt2_credits_were_taken") net.WriteUInt(searchUID or 0, 16) @@ -359,6 +361,15 @@ if SERVER then function bodysearch.StreamSceneData(sceneData, client) net.SendStream("TTT2_BodySearchData", sceneData, client) end + + --- + -- Called after a player has been given credits for searching a corpse. + -- @param Player ply The player that searched the corpse + -- @param Entity rag The ragdoll that was searched + -- @param number credits The amount of credits that were given + -- @hook + -- @realm server + function GM:TTT2OnGiveFoundCredits(ply, rag, credits) end end if CLIENT then From 82d7cb173c451afd72b8b8a6e8cb78a55e03cf21 Mon Sep 17 00:00:00 2001 From: Luke Tomkus Date: Thu, 28 Mar 2024 17:14:43 -0400 Subject: [PATCH 09/10] Update hook calls with new names --- lua/ttt2/libraries/credits.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/ttt2/libraries/credits.lua b/lua/ttt2/libraries/credits.lua index 74d22ee06..a35c03d88 100644 --- a/lua/ttt2/libraries/credits.lua +++ b/lua/ttt2/libraries/credits.lua @@ -52,7 +52,7 @@ function credits.HandleKillCreditsAward(victim, attacker) --- -- @realm server -- stylua: ignore - hook.Run("TTT2ReceivedKillCredits", victim, attacker, creditsAmount) + hook.Run("TTT2OnReceiveKillCredits", victim, attacker, creditsAmount) LANG.Msg( attacker, @@ -153,7 +153,7 @@ function credits.HandleKillCreditsAward(victim, attacker) --- -- @realm server -- stylua: ignore - hook.Run("TTT2ReceivedTeamAwardCredits", plyToAward, creditsAmount) + hook.Run("TTT2OnReceiveTeamAwardCredits", plyToAward, creditsAmount) LANG.Msg(plyToAward, "credit_all", { num = creditsAmount }, MSG_MSTACK_ROLE) end From ed62904c18973b9ccc808df78be030c38900c5e2 Mon Sep 17 00:00:00 2001 From: Luke Tomkus Date: Sat, 6 Apr 2024 14:54:08 -0400 Subject: [PATCH 10/10] Add missing doc comment --- lua/ttt2/libraries/bodysearch.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/ttt2/libraries/bodysearch.lua b/lua/ttt2/libraries/bodysearch.lua index 0f6621c72..1be75eccf 100644 --- a/lua/ttt2/libraries/bodysearch.lua +++ b/lua/ttt2/libraries/bodysearch.lua @@ -223,6 +223,9 @@ if SERVER then events.Trigger(EVENT_CREDITFOUND, ply, rag, credits) + --- + -- @realm server + -- stylua: ignore hook.Run("TTT2OnGiveFoundCredits", ply, rag, credits) -- update clients so their UIs can be updated