diff --git a/.luacheckrc b/.luacheckrc index 04124a169..5f2e2a49a 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -25,10 +25,100 @@ globals = { "ACT_HL2MP_GESTURE_RELOAD_SHOTGUN", "ACT_HL2MP_GESTURE_RELOAD_SLAM", "ACT_HL2MP_GESTURE_RELOAD_SMG1", + "ACT_HL2MP_IDLE_AR2", + "ACT_HL2MP_IDLE_CAMERA", + "ACT_HL2MP_IDLE_CROSSBOW", + "ACT_HL2MP_IDLE_CROUCH_AR2", + "ACT_HL2MP_IDLE_CROUCH_CAMERA", + "ACT_HL2MP_IDLE_CROUCH_CROSSBOW", + "ACT_HL2MP_IDLE_CROUCH_DUEL", + "ACT_HL2MP_IDLE_CROUCH_FIST", + "ACT_HL2MP_IDLE_CROUCH_GRENADE", + "ACT_HL2MP_IDLE_CROUCH_KNIFE", + "ACT_HL2MP_IDLE_CROUCH_MAGIC", + "ACT_HL2MP_IDLE_CROUCH_MELEE", + "ACT_HL2MP_IDLE_CROUCH_MELEE2", + "ACT_HL2MP_IDLE_CROUCH_PASSIVE", + "ACT_HL2MP_IDLE_CROUCH_PHYSGUN", + "ACT_HL2MP_IDLE_CROUCH_PISTOL", + "ACT_HL2MP_IDLE_CROUCH_REVOLVER", + "ACT_HL2MP_IDLE_CROUCH_RPG", + "ACT_HL2MP_IDLE_CROUCH_SHOTGUN", + "ACT_HL2MP_IDLE_CROUCH_SLAM", + "ACT_HL2MP_IDLE_CROUCH_SMG1", "ACT_HL2MP_IDLE_CROUCH_ZOMBIE", + "ACT_HL2MP_IDLE_DUEL", + "ACT_HL2MP_IDLE_FIST", + "ACT_HL2MP_IDLE_GRENADE", + "ACT_HL2MP_IDLE_KNIFE", + "ACT_HL2MP_IDLE_MAGIC", + "ACT_HL2MP_IDLE_MELEE", + "ACT_HL2MP_IDLE_MELEE2", + "ACT_HL2MP_IDLE_PASSIVE", + "ACT_HL2MP_IDLE_PHYSGUN", + "ACT_HL2MP_IDLE_PISTOL", + "ACT_HL2MP_IDLE_REVOLVER", + "ACT_HL2MP_IDLE_RPG", + "ACT_HL2MP_IDLE_SHOTGUN", + "ACT_HL2MP_IDLE_SLAM", + "ACT_HL2MP_IDLE_SMG1", "ACT_HL2MP_IDLE_ZOMBIE", + "ACT_HL2MP_RUN_AR2", + "ACT_HL2MP_RUN_CAMERA", + "ACT_HL2MP_RUN_CROSSBOW", + "ACT_HL2MP_RUN_DUEL", + "ACT_HL2MP_RUN_FIST", + "ACT_HL2MP_RUN_GRENADE", + "ACT_HL2MP_RUN_KNIFE", + "ACT_HL2MP_RUN_MAGIC", + "ACT_HL2MP_RUN_MELEE", + "ACT_HL2MP_RUN_MELEE2", + "ACT_HL2MP_RUN_PASSIVE", + "ACT_HL2MP_RUN_PHYSGUN", + "ACT_HL2MP_RUN_PISTOL", + "ACT_HL2MP_RUN_REVOLVER", + "ACT_HL2MP_RUN_RPG", + "ACT_HL2MP_RUN_SHOTGUN", + "ACT_HL2MP_RUN_SLAM", + "ACT_HL2MP_RUN_SMG1", "ACT_HL2MP_RUN_ZOMBIE", + "ACT_HL2MP_WALK_AR2", + "ACT_HL2MP_WALK_CAMERA", + "ACT_HL2MP_WALK_CROSSBOW", + "ACT_HL2MP_WALK_CROUCH_AR2", + "ACT_HL2MP_WALK_CROUCH_CAMERA", + "ACT_HL2MP_WALK_CROUCH_CROSSBOW", + "ACT_HL2MP_WALK_CROUCH_DUEL", + "ACT_HL2MP_WALK_CROUCH_FIST", + "ACT_HL2MP_WALK_CROUCH_GRENADE", + "ACT_HL2MP_WALK_CROUCH_KNIFE", + "ACT_HL2MP_WALK_CROUCH_MAGIC", + "ACT_HL2MP_WALK_CROUCH_MELEE", + "ACT_HL2MP_WALK_CROUCH_MELEE2", + "ACT_HL2MP_WALK_CROUCH_PASSIVE", + "ACT_HL2MP_WALK_CROUCH_PHYSGUN", + "ACT_HL2MP_WALK_CROUCH_PISTOL", + "ACT_HL2MP_WALK_CROUCH_REVOLVER", + "ACT_HL2MP_WALK_CROUCH_RPG", + "ACT_HL2MP_WALK_CROUCH_SHOTGUN", + "ACT_HL2MP_WALK_CROUCH_SLAM", + "ACT_HL2MP_WALK_CROUCH_SMG1", "ACT_HL2MP_WALK_CROUCH_ZOMBIE_01", + "ACT_HL2MP_WALK_DUEL", + "ACT_HL2MP_WALK_FIST", + "ACT_HL2MP_WALK_GRENADE", + "ACT_HL2MP_WALK_KNIFE", + "ACT_HL2MP_WALK_MAGIC", + "ACT_HL2MP_WALK_MELEE", + "ACT_HL2MP_WALK_MELEE2", + "ACT_HL2MP_WALK_PASSIVE", + "ACT_HL2MP_WALK_PHYSGUN", + "ACT_HL2MP_WALK_PISTOL", + "ACT_HL2MP_WALK_REVOLVER", + "ACT_HL2MP_WALK_RPG", + "ACT_HL2MP_WALK_SHOTGUN", + "ACT_HL2MP_WALK_SLAM", + "ACT_HL2MP_WALK_SMG1", "ACT_HL2MP_WALK_ZOMBIE_01", "ACT_IDLE", "ACT_GMOD_GESTURE_AGREE", @@ -634,6 +724,7 @@ globals = { "CreateClientConVar", "CreateConVar", "CreateFavTable", + "CreateMaterial", "CreateSound", "CreateTransferMenu", "CurTime", @@ -788,6 +879,7 @@ globals = { "input", "list", "math", + "navmesh", "net", "os", "permissions", @@ -1035,6 +1127,7 @@ globals = { "ROLE_STARTING_HEALTH", "ROLE_STARTING_TEAM", "ROLE_STRINGS", + "ROLE_STRINGS_DEFAULT", "ROLE_STRINGS_EXT", "ROLE_STRINGS_PLURAL", "ROLE_STRINGS_RAW", diff --git a/API/GLOBAL_VARIABLES.md b/API/GLOBAL_VARIABLES.md index 974ba3a61..a5bcad811 100644 --- a/API/GLOBAL_VARIABLES.md +++ b/API/GLOBAL_VARIABLES.md @@ -146,6 +146,11 @@ Table of title-case names for each role.\ *Realm:* Client and Server\ *Added in:* 1.0.0 +### ROLE_STRINGS_DEFAULT +Table of title-case names for each role. Copy of `ROLE_STRINGS` from before role strings are changed by ConVars.\ +*Realm:* Client and Server\ +*Added in:* 2.4.4 + ### ROLE_STRINGS_EXT Table of extended (e.g. prefixed by an article) names for each role.\ *Realm:* Client and Server\ diff --git a/CONVARS.md b/CONVARS.md index b8d887dda..24d58d587 100644 --- a/CONVARS.md +++ b/CONVARS.md @@ -764,6 +764,12 @@ ttt_cannibal_eat_cooldown 10 // The amount of time (in ttt_cannibal_damage_penalty 0 // The fraction a Cannibal's damage will be scaled by when they are attacking (Only applies if ttt_cannibal_is_independent is enabled) ttt_cannibal_can_see_jesters 0 // Whether jesters are revealed (via head icons, color/icon on the scoreboard, etc.) to the Cannibal (Only applies if ttt_cannibal_is_independent is enabled) ttt_cannibal_update_scoreboard 0 // Whether the Cannibal shows dead players as missing in action (Only applies if ttt_cannibal_is_independent is enabled) +ttt_cannibal_gains_health 0 // Whether the Cannibal gains their victim's health when eating them +ttt_cannibal_gained_health_percentage 15 // What percentage of their victim's health the Cannibal gains. Set to 0 to always gain a flat 100HP (Only applies if ttt_cannibal_gains_health is enabled) +ttt_cannibal_digestion 0 // Whether the Cannibal digests and permanently kills their victims over time +ttt_cannibal_digestion_time 30 // How long in seconds a victim takes to be digested when eaten. Set to 0 for immediate digestion (Only applies if ttt_cannibal_digestion is enabled) +ttt_cannibal_digestion_poop 1 // Whether the Cannibal drops poop when a victim is digested (Only applies if ttt_cannibal_digestion is enabled) +ttt_cannibal_digestion_poop_sound 1 // Whether the Cannibal causes a sound when poop is dropped from a digested victim (Only applies if ttt_cannibal_digestion is enabled) // ---------------------------------------- diff --git a/CREATE_YOUR_OWN_ROLE.md b/CREATE_YOUR_OWN_ROLE.md index 7dcbfddfb..8655a50e4 100644 --- a/CREATE_YOUR_OWN_ROLE.md +++ b/CREATE_YOUR_OWN_ROLE.md @@ -56,7 +56,7 @@ In order to create your own role you will need to make sure you have downloaded - **.lua** - This can be done in Notepad in a pinch but at the very least we would recommend [Notepad++](https://notepad-plus-plus.org/). - **.vmt and .vtf** - [VTFEdit Reloaded](https://github.com/Sky-rym/VTFEdit-Reloaded/releases) is the best way to edit these files but if you know what you are doing there are plugins for other apps. -In this guide we will be walking through how we made the Summoner role and you can download all the templates we are using [here](/templates/role). +In this guide we will be walking through how we made the Summoner role and you can download all the templates we are using [here](/templates/role) using a tool that allows downloading directories from a GitHub repo like [this one](https://kinolien.github.io/gitzip/) *(Note: In most cases you won't need to enter a token despite its recommendation)*. Last thing to do before you are ready to get started is to unzip that file which should give you 4 .psd files and a folder like this: @@ -81,7 +81,7 @@ ROLE.desc = [[]] ROLE.shortdesc = "" -ROLE.team = +ROLE.team = ROLE_TEAM_ ROLE.shop = nil ROLE.loadout = {} @@ -835,7 +835,9 @@ end ### Role Registration -The next line simply tells CR for TTT to register your role and passes through all the relevant information. You do not need to edit this line. CR for TTT automatically defines an enumeration for your role, `ROLE_%NAMERAW%` as well as helper functions `Get%NAMERAW%`, `Is%NAMERAW%` and `IsActive%NAMERAW%` if you would like to use them to add extra logic for your role. +The next line simply tells CR for TTT to register your role and passes through all the relevant information. You do not need to edit this line. CR for TTT automatically defines an enumeration for your role, `ROLE_%NAMERAW%` as well as helper functions `Get%NAME%`, `Is%NAME%` and `IsActive%NAME%` if you would like to use them to add extra logic for your role. + +*(Note: All spaces are removed from `%NAME%` before `Get%NAME%`, `Is%NAME%` and `IsActive%NAME%` are created)* ### Final Block diff --git a/README.md b/README.md index d84a89472..291f9d02a 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,16 @@ If you would like to test the available configurations, we recommend using ULX/U - MaxCooljazz from the CR4TTT Community for allowing us to integrate their Spy role into the mod. - [Game icons](https://game-icons.net), [Noun Project](https://thenounproject.com), and [Icons8](https://icons8.com) for the role icons. - [Videvo](https://www.videvo.net/profile/videvo/) for the royalty-free [extinguish sound](https://www.videvo.net/sound-effect/short-light-fire-exti-pe363704/255924/) and cough sounds: [1](https://www.videvo.net/sound-effect/human-cough-33/427996/), [2](https://www.videvo.net/sound-effect/human-cough-36/427999/), [3](https://www.videvo.net/sound-effect/human-cough-39/428002/), [4](https://www.videvo.net/sound-effect/human-cough-63/428026/) +- [freesound.org](https://freesound.org/) for the poop sounds used by the Cannibal's digestion effect + - Licensed under [Creative Commons 0](https://creativecommons.org/public-domain/cc0/) + - [fart](https://freesound.org/people/MacKaffee/sounds/326143/) by [MacKaffee](https://freesound.org/people/MacKaffee/) + - [Short, definite fart](https://freesound.org/people/ycbcr/sounds/249583/) by [ycbcr](https://freesound.org/people/ycbcr/) + - [small fart](https://freesound.org/people/KataVlogsYT/sounds/324453/) by [KataVlogsYT](https://freesound.org/people/KataVlogsYT/) + - [queef_fart](https://freesound.org/people/faen/sounds/167090/) by [faen](https://freesound.org/people/faen/) + - [WAV Fart Vegan 047](https://freesound.org/people/frenkfurth/sounds/650693/) by [frenkfurth](https://freesound.org/people/frenkfurth/) + - Licensed under [Creative Commons BY 4.0](https://creativecommons.org/licenses/by/4.0/) + - [fart_anxious](https://freesound.org/people/IFartInUrGeneralDirection/sounds/37850/) by [IFartInUrGeneralDirection](https://freesound.org/people/IFartInUrGeneralDirection/) +- [Deika](https://steamcommunity.com/id/deikaeus) for the [model](https://steamcommunity.com/sharedfiles/filedetails/?id=304354340) used by the Cannibal's digestion poop effect - Our friends and everyone on the Discord server for their suggestions and help testing. ## Conflicts diff --git a/RELEASE.md b/RELEASE.md index bee2ccbc5..4a19fbbcc 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,31 @@ # Release Notes +## 2.4.4 (Beta) +**Released: March 21st, 2026** + +### Additions +- Added messages to existing twins when a player becomes a twin during an active round +- Added option to allow Cannibal to gain a configurable percentage of their victims' health (disabled by default) (Thanks Joel!) +- Added option to allow Cannibal to "digest" (permanently kill) their victims a configurable amount of time after eating them (disabled by default) (Thanks Joel!) +- Added option to allow Cannibal to drop a poop when a victim is fully digested, with or without an audible cue (enabled by default, but depends on digestion being enabled) (Thanks Joel!) + +### Changes +- Changed role pack to apply next round, if the round is already active +- Changed logic to reassign a player's role if they change roles into being a solo twin during an active round +- Ported "TTT: Use gmod_language setting by default" +- Ported "TTT: Translatability improvements" +- Ported "TTT: Equipment menu sorting options" + +### Fixes +- Fixed cheat sheet getting cut off when it exceeded the height of the screen and a role pack was enabled +- Fixed jester, detective, innocent, and traitor role rename ConVars not working +- Fixed error in Cannibal's role weapon HUD when weapon was selected from last round (Thanks Stig!) +- Fixed detective hats not hiding when the owning player was eaten by the Cannibal +- Ported "Add check for checking weapon GetHeadshotMultiplier" + +### Developer +- Added `ROLE_STRINGS_DEFAULT` as a copy of `ROLE_STRINGS` from before role strings are changed by ConVars + ## 2.4.3 (Beta) **Released: February 21st, 2026** diff --git a/docs/api/global_variables.html b/docs/api/global_variables.html index 9bacb1dda..1fb197e80 100644 --- a/docs/api/global_variables.html +++ b/docs/api/global_variables.html @@ -220,6 +220,13 @@
+ Table of title-case names for each role. Copy of ROLE_STRINGS from before role strings are changed by ConVars.
+ Realm: Client and Server
+ Added in: 2.4.4
+
Table of extended (e.g. prefixed by an article) names for each role.
diff --git a/docs/teams/jester.html b/docs/teams/jester.html
index 4ffd23c2c..a8123bb40 100644
--- a/docs/teams/jester.html
+++ b/docs/teams/jester.html
@@ -580,7 +580,25 @@
ttt_bodysnatcher_is_independent to be enabled.)ttt_cannibal_is_independent to be enabled.)ttt_cannibal_digestion to be enabled.)ttt_cannibal_digestion to be enabled.)ttt_cannibal_gains_health to be enabled.)ttt_bodysnatcher_is_independent to be enabled.)ttt_cannibal_is_independent to be enabled.)In this guide we will be walking through how we made the Summoner role and you can download all the templates we are using here.
+In this guide we will be walking through how we made the Summoner role and you can download all the templates we are using here using a tool that allows downloading directories from a GitHub repo like this one (Note: In most cases you won't need to enter a token despite its recommendation).
Last thing to do before you are ready to get started is to unzip that file which should give you 4 .psd files and a folder like this:

(Note: If you would like to make this information translatable, see the Translations section of this document. )
+(Note: If you would like to make this information translatable, see the Translations section of this document.)
For a more complex example, lets take the same string from before but change the phrase "traitor team" to be the color of the traitor team in TTT. To do that, we're going to use some fairly basic HTML instead of just raw text:
if CLIENT then
hook.Add("TTTTutorialRoleText", "SummonerTutorialRoleText", function(role, titleLabel, roleIcon)
@@ -949,7 +949,9 @@ Tutorial Page
endThe next line simply tells CR for TTT to register your role and passes through all the relevant information. You do not need to edit this line. CR for TTT automatically defines an enumeration for your role, ROLE_%NAMERAW% as well as helper functions Get%NAMERAW%, Is%NAMERAW% and IsActive%NAMERAW% if you would like to use them to add extra logic for your role.
The next line simply tells CR for TTT to register your role and passes through all the relevant information. You do not need to edit this line. CR for TTT automatically defines an enumeration for your role, ROLE_%NAMERAW% as well as helper functions Get%NAME%, Is%NAME% and IsActive%NAME% if you would like to use them to add extra logic for your role.
(Note: All spaces are removed from %NAME% before Get%NAME%, Is%NAME% and IsActive%NAME% are created)
Finally we have this block of code:
diff --git a/gamemodes/terrortown/content/materials/models/poo/poo.vmt b/gamemodes/terrortown/content/materials/models/poo/poo.vmt new file mode 100644 index 000000000..91d40117a --- /dev/null +++ b/gamemodes/terrortown/content/materials/models/poo/poo.vmt @@ -0,0 +1,10 @@ +"VertexLitGeneric" +{ + "$baseTexture" "Models/poo/poo" + "$bumpmap" "Models/poo/poo_n" + // -- From here down is new stuff which will only be applied if $phong is set to 1 -- + "$phong" "1" + "$phongboost" "4" + "$phongexponent" "30" + "$phongfresnelranges" "[.2 .5 1]" +} diff --git a/gamemodes/terrortown/content/materials/models/poo/poo.vtf b/gamemodes/terrortown/content/materials/models/poo/poo.vtf new file mode 100644 index 000000000..f32c30dc3 Binary files /dev/null and b/gamemodes/terrortown/content/materials/models/poo/poo.vtf differ diff --git a/gamemodes/terrortown/content/materials/models/poo/poo_n.vtf b/gamemodes/terrortown/content/materials/models/poo/poo_n.vtf new file mode 100644 index 000000000..3a9d0b58f Binary files /dev/null and b/gamemodes/terrortown/content/materials/models/poo/poo_n.vtf differ diff --git a/gamemodes/terrortown/content/models/poo/poo.dx80.vtx b/gamemodes/terrortown/content/models/poo/poo.dx80.vtx new file mode 100644 index 000000000..3182d23c3 Binary files /dev/null and b/gamemodes/terrortown/content/models/poo/poo.dx80.vtx differ diff --git a/gamemodes/terrortown/content/models/poo/poo.dx90.vtx b/gamemodes/terrortown/content/models/poo/poo.dx90.vtx new file mode 100644 index 000000000..8c3f0539e Binary files /dev/null and b/gamemodes/terrortown/content/models/poo/poo.dx90.vtx differ diff --git a/gamemodes/terrortown/content/models/poo/poo.mdl b/gamemodes/terrortown/content/models/poo/poo.mdl new file mode 100644 index 000000000..6cb10c73c Binary files /dev/null and b/gamemodes/terrortown/content/models/poo/poo.mdl differ diff --git a/gamemodes/terrortown/content/models/poo/poo.phy b/gamemodes/terrortown/content/models/poo/poo.phy new file mode 100644 index 000000000..f2b486025 Binary files /dev/null and b/gamemodes/terrortown/content/models/poo/poo.phy differ diff --git a/gamemodes/terrortown/content/models/poo/poo.sw.vtx b/gamemodes/terrortown/content/models/poo/poo.sw.vtx new file mode 100644 index 000000000..df55c7972 Binary files /dev/null and b/gamemodes/terrortown/content/models/poo/poo.sw.vtx differ diff --git a/gamemodes/terrortown/content/models/poo/poo.vvd b/gamemodes/terrortown/content/models/poo/poo.vvd new file mode 100644 index 000000000..902257872 Binary files /dev/null and b/gamemodes/terrortown/content/models/poo/poo.vvd differ diff --git a/gamemodes/terrortown/content/sound/cannibal/poop1.wav b/gamemodes/terrortown/content/sound/cannibal/poop1.wav new file mode 100644 index 000000000..770aee196 Binary files /dev/null and b/gamemodes/terrortown/content/sound/cannibal/poop1.wav differ diff --git a/gamemodes/terrortown/content/sound/cannibal/poop2.wav b/gamemodes/terrortown/content/sound/cannibal/poop2.wav new file mode 100644 index 000000000..3adc03fee Binary files /dev/null and b/gamemodes/terrortown/content/sound/cannibal/poop2.wav differ diff --git a/gamemodes/terrortown/content/sound/cannibal/poop3.wav b/gamemodes/terrortown/content/sound/cannibal/poop3.wav new file mode 100644 index 000000000..fde256fa8 Binary files /dev/null and b/gamemodes/terrortown/content/sound/cannibal/poop3.wav differ diff --git a/gamemodes/terrortown/content/sound/cannibal/poop4.wav b/gamemodes/terrortown/content/sound/cannibal/poop4.wav new file mode 100644 index 000000000..de94b9400 Binary files /dev/null and b/gamemodes/terrortown/content/sound/cannibal/poop4.wav differ diff --git a/gamemodes/terrortown/content/sound/cannibal/poop5.wav b/gamemodes/terrortown/content/sound/cannibal/poop5.wav new file mode 100644 index 000000000..683fac570 Binary files /dev/null and b/gamemodes/terrortown/content/sound/cannibal/poop5.wav differ diff --git a/gamemodes/terrortown/content/sound/cannibal/poop6.wav b/gamemodes/terrortown/content/sound/cannibal/poop6.wav new file mode 100644 index 000000000..ca0d00158 Binary files /dev/null and b/gamemodes/terrortown/content/sound/cannibal/poop6.wav differ diff --git a/gamemodes/terrortown/entities/weapons/weapon_can_eater.lua b/gamemodes/terrortown/entities/weapons/weapon_can_eater.lua index c4272d36e..b9705de13 100644 --- a/gamemodes/terrortown/entities/weapons/weapon_can_eater.lua +++ b/gamemodes/terrortown/entities/weapons/weapon_can_eater.lua @@ -37,6 +37,15 @@ SWEP.InLoadoutFor = {ROLE_CANNIBAL} SWEP.InLoadoutForDefault = {ROLE_CANNIBAL} SWEP.DeviceCooldownConVar = CreateConVar("ttt_cannibal_eat_cooldown", "10", FCVAR_REPLICATED, "The amount of time (in seconds) between uses of the Cannibal's Cannibalizer", 0, 60) +SWEP.GainsHealthConVar = CreateConVar("ttt_cannibal_gains_health", "0", FCVAR_REPLICATED, "Whether the Cannibal gains their victim's health when eating them", 0, 1) +SWEP.GainedHealthPercentageConVar = CreateConVar("ttt_cannibal_gained_health_percentage", "15", FCVAR_REPLICATED, "What percentage of their victim's health the Cannibal gains (set to 0 to always gain a flat 100HP)", 0, 500) +SWEP.DigestionConVar = CreateConVar("ttt_cannibal_digestion", "0", FCVAR_REPLICATED, "Whether the Cannibal digests and permanently kills their victims over time", 0, 1) +SWEP.DigestionTimeConVar = CreateConVar("ttt_cannibal_digestion_time", "30", FCVAR_REPLICATED, "How long in seconds a victim takes to be digested when eaten (set to 0 for immediate digestion)", 0, 300) + +if SERVER then + SWEP.DigestionPoopConVar = CreateConVar("ttt_cannibal_digestion_poop", "1", FCVAR_NONE, "Whether the Cannibal drops poop when a victim is digested", 0, 1) + SWEP.DigestionPoopSoundConVar = CreateConVar("ttt_cannibal_digestion_poop_sound", "1", FCVAR_NONE, "Whether the Cannibal causes a sound when poop is dropped from a digested victim.", 0, 1) +end local eatSounds = { "cannibal/eat1.wav", @@ -44,6 +53,15 @@ local eatSounds = { "cannibal/eat3.wav" } +local poopSounds = { + "cannibal/poop1.wav", + "cannibal/poop2.wav", + "cannibal/poop3.wav", + "cannibal/poop4.wav", + "cannibal/poop5.wav", + "cannibal/poop6.wav" +} + function SWEP:Initialize() if CLIENT then self:AddHUDHelp("can_eater_help_pri", nil, true) @@ -62,6 +80,14 @@ function SWEP:OnDrop() self:Remove() end +function SWEP:OnRemove() + if SERVER then + for sid64, _ in pairs(CANNIBAL.playerWeapons) do + timer.Remove("TTTCannibalDigestion_" .. sid64) + end + end +end + function SWEP:Deploy() if SERVER and IsValid(self:GetOwner()) then self:GetOwner():DrawViewModel(false) @@ -98,6 +124,9 @@ function SWEP:PrimaryAttack() hitEnt:SpectateEntity(owner) hitEnt:DrawViewModel(false) hitEnt:DrawWorldModel(false) + if IsValid(hitEnt.hat) then + hitEnt.hat:SetNoDraw(true) + end local sID64 = hitEnt:SteamID64() @@ -125,6 +154,73 @@ function SWEP:PrimaryAttack() if cooldown > 0 then self:SetDeviceCooldownEnd(CurTime() + cooldown) end + + -- Cannibal health gain + if self.GainsHealthConVar:GetBool() then + local gained_health_percentage = self.GainedHealthPercentageConVar:GetInt() + local victimHealth = hitEnt:Health() + local cannibalHealth = owner:Health() + + local gainedHealth + if gained_health_percentage == 0 then + gainedHealth = 100 + else + gainedHealth = math.floor((gained_health_percentage / 100) * victimHealth) + end + + if gainedHealth > 0 then + owner:SetHealth(cannibalHealth + gainedHealth) + end + end + + -- Victim digestion + if self.DigestionConVar:GetBool() then + local digestion_time = self.DigestionTimeConVar:GetInt() + -- Ensure there's a short delay to allow time for the vars to be set first + if digestion_time == 0 then + digestion_time = 0.1 + end + + timer.Create("TTTCannibalDigestion_" .. sID64, digestion_time, 1, function() + if not IsPlayer(hitEnt) then return end + if not IsPlayer(owner) then return end + + -- Only digest if they are still in THIS cannibal's tummy + if hitEnt.TTTCannibalEaten ~= owner:SteamID64() then return end + + hitEnt:Kill() + hitEnt:ClearProperty("TTTCannibalEaten") + + hitEnt:SetParent(nil) + hitEnt:SpectateEntity(nil) + + hitEnt:QueueMessage(MSG_PRINTBOTH, "You have been fully digested!") + owner:QueueMessage(MSG_PRINTBOTH, "You have fully digested " .. hitEnt:Nick() .. "!") + + -- Spawn poop at cannibal's position + if self.DigestionPoopConVar:GetBool() then + local poop = ents.Create("prop_physics") + if IsValid(poop) then + local fingerprints = { owner } + poop:SetModel("models/poo/poo.mdl") + + local forward = owner:GetForward() + local dropPos = owner:GetPos() + forward * -30 + Vector(0, 0, 10) + poop:SetPos(dropPos) + + poop:SetAngles(Angle(0, math.random(0, 360), 0)) + poop:Spawn() + poop:Activate() + poop:SetCollisionGroup(COLLISION_GROUP_WEAPON) + poop.fingerprints = fingerprints + + if self.DigestionPoopSoundConVar:GetBool()then + owner:EmitSound(poopSounds[math.random(#poopSounds)], 100) + end + end + end + end) + end end end @@ -152,7 +248,7 @@ if CLIENT then local cooldownLeft = self:GetDeviceCooldownEnd() - CurTime() local progress = 1 - (cooldownLeft / cooldown) - if cooldownLeft > -3 or self.DeployTime > CurTime() - 3 then + if cooldownLeft > -3 or (self.DeployTime and self.DeployTime > CurTime() - 3) then if progress > 1 then CRHUD:PaintProgressBar(x, y, w, Color(0, 255, 0, 155), "READY TO EAT", 1) else @@ -160,4 +256,4 @@ if CLIENT then end end end -end \ No newline at end of file +end diff --git a/gamemodes/terrortown/gamemode/cl_cheatsheet.lua b/gamemodes/terrortown/gamemode/cl_cheatsheet.lua index ea800bf2f..14d6ff304 100644 --- a/gamemodes/terrortown/gamemode/cl_cheatsheet.lua +++ b/gamemodes/terrortown/gamemode/cl_cheatsheet.lua @@ -136,12 +136,9 @@ hook.Add("PlayerButtonDown", "CheatSheet_PlayerButtonDown", function(ply, button local scrollbarWidth = 15 local m = 5 - local packName = GetConVar("ttt_role_pack"):GetString() + local packName = ROLEPACKS.GetCurrentRolePackName() local packDesc - if #packName == 0 then - rolePackHeight = 0 - rolePackDescHeight = 0 - elseif ROLE_PACK_DETAILS then + if #packName > 0 then local displayName = ROLE_PACK_DETAILS.displayName if displayName and #displayName > 0 then packName = displayName @@ -152,6 +149,9 @@ hook.Add("PlayerButtonDown", "CheatSheet_PlayerButtonDown", function(ply, button packDesc = nil rolePackDescHeight = 0 end + else + rolePackHeight = 0 + rolePackDescHeight = 0 end local w, h, detectivesHeight, innocentsHeight, traitorsHeight, jestersHeight, independentsHeight, monstersHeight @@ -244,7 +244,7 @@ hook.Add("PlayerButtonDown", "CheatSheet_PlayerButtonDown", function(ply, button end local dlist = vgui.Create("DScrollPanel", dbackground) - dlist:SetSize(w, h) + dlist:SetSize(w, h - (rolePackHeight + rolePackDescHeight)) dlist:SetPos(0, rolePackHeight + rolePackDescHeight) local dcanvas = dlist:GetCanvas() diff --git a/gamemodes/terrortown/gamemode/cl_equip.lua b/gamemodes/terrortown/gamemode/cl_equip.lua index 5b1fdce2c..b33037055 100644 --- a/gamemodes/terrortown/gamemode/cl_equip.lua +++ b/gamemodes/terrortown/gamemode/cl_equip.lua @@ -19,6 +19,7 @@ local RunHook = hook.Run local GetWeapon = weapons.GetStored local GetTranslation = LANG.GetTranslation local GetPTranslation = LANG.GetParamTranslation +local MathFloor = math.floor local MathRandom = math.random local StringFind = string.find local StringLower = string.lower @@ -29,6 +30,7 @@ local TableMerge = table.Merge local TableRemove = table.remove local TableShuffle = table.Shuffle local TableSort = table.sort +local SafeTranslate = LANG.TryTranslation -- BEM client convars and config menu local numColsVar = CreateClientConVar("ttt_bem_cols", 4, true, false, "Sets the number of columns in the Traitor/Detective menu's item list.") @@ -38,8 +40,10 @@ local showCustomVar = CreateClientConVar("ttt_bem_marker_custom", 1, true, false local showFavoriteVar = CreateClientConVar("ttt_bem_marker_fav", 1, true, false, "Should favorite items get a marker?") local showSlotVar = CreateClientConVar("ttt_bem_marker_slot", 1, true, false, "Should items get a slot-marker?") local showLoadoutEquipment = CreateClientConVar("ttt_show_loadout_equipment", 0, true, false, "Should loadout equipment show in shops?") -local sortAlphabetically = CreateClientConVar("ttt_sort_alphabetically", 1, true, false, "Should the shop sort alphabetically?") -local sortBySlotFirst = CreateClientConVar("ttt_sort_by_slot_first", 0, true, false, "Should the shop sort by slot first?") + +local equipment_sorting = CreateClientConVar("ttt_equipment_sorting", "default", true) +local equipment_ascending = CreateClientConVar("ttt_equipment_ascending", "1", true) +local equipment_hide_unbuyable = CreateClientConVar("ttt_equipment_hide_unbuyable", "0", true) hook.Add("Initialize", "EquipmentMenu_Initialize", function() LANG.AddToLanguage("english", "set_title_equipment", "Equipment/Shop settings") @@ -48,8 +52,6 @@ hook.Add("Initialize", "EquipmentMenu_Initialize", function() LANG.AddToLanguage("english", "set_equipment_convar_custom", "Show custom item marker") LANG.AddToLanguage("english", "set_equipment_convar_fav", "Show favourite item marker") LANG.AddToLanguage("english", "set_equipment_convar_loadout", "Show loadout items") - LANG.AddToLanguage("english", "set_equipment_convar_alpha", "Sort alphabetically") - LANG.AddToLanguage("english", "set_equipment_convar_sort_by_slot", "Sort by slot first") end) hook.Add("TTTSettingsConfigTabSections", "EquipmentMenu_TTTSettingsConfigTabSections", function(dsettings) @@ -72,8 +74,7 @@ hook.Add("TTTSettingsConfigTabSections", "EquipmentMenu_TTTSettingsConfigTabSect dbemsettings:CheckBox(GetTranslation("set_equipment_convar_custom"), "ttt_bem_marker_custom") dbemsettings:CheckBox(GetTranslation("set_equipment_convar_fav"), "ttt_bem_marker_fav") dbemsettings:CheckBox(GetTranslation("set_equipment_convar_loadout"), "ttt_show_loadout_equipment") - dbemsettings:CheckBox(GetTranslation("set_equipment_convar_alpha"), "ttt_sort_alphabetically") - dbemsettings:CheckBox(GetTranslation("set_equipment_convar_sort_by_slot"), "ttt_sort_by_slot_first") + dbemsettings:CheckBox(GetTranslation("set_hide_unbuyable"), "ttt_equipment_hide_unbuyable") CallHook("TTTSettingsConfigTabFields", nil, "BEM", dbemsettings) @@ -142,7 +143,7 @@ local function ItemIsWeapon(item) return not tonumber(item.id) end function GetEquipmentForRole(role, promoted, block_randomization, block_exclusion, ignore_cache, rolepack_weps) WEPS.PrepWeaponsLists(role) - local packName = GetConVar("ttt_role_pack"):GetString() + local packName = ROLEPACKS.GetCurrentRolePackName() if rolepack_weps == nil and #packName > 0 then rolepack_weps = {Buyables = WEPS.RolePackBuyableWeapons[role], Excludes = WEPS.RolePackExcludeWeapons[role], NoRandoms = WEPS.RolePackBypassRandomWeapons[role]} elseif rolepack_weps == false or #packName == 0 then @@ -249,7 +250,7 @@ function GetEquipmentForRole(role, promoted, block_randomization, block_exclusio end -- If we have sync roles and this role is one of them, save the item info for later - if sync_roles and table.HasValue(sync_roles, r) then + if sync_roles and TableHasValue(sync_roles, r) then TableInsert(sync_equipment, i) end end @@ -468,7 +469,7 @@ end local color_bad = Color(220, 60, 60, 255) local color_good = Color(255, 255, 255, 255) --- Creates tabel of labels showing the status of ordering prerequisites +-- Creates table of labels showing the status of ordering prerequisites local function PreqLabels(parent, x, y) local tbl = {} @@ -584,8 +585,6 @@ function PANEL:SelectPanel(pnl) end vgui.Register("EquipSelect", PANEL, "DPanelSelect") -local SafeTranslate = LANG.TryTranslation - local color_darkened = Color(255, 255, 255, 80) -- BEM helper functions @@ -643,6 +642,161 @@ hook.Add("OnPauseMenuShow", "EquipMenu_OnPauseMenuShow", function() end end) +local ListPanel = nil +local function CanOrder(item, owned_ids) + -- not orderable + if not ListPanel.update_preqs(item) then return false end + + local ply = LocalPlayer() + if ply:GetCredits() <= 0 then return false end + + -- already owned + if TableHasValue(owned_ids, item.id) then + return false + end + + if ItemIsWeapon(item) then + -- already carrying a weapon for this slot + if not CanCarryWeapon(item) then return false end + elseif ply:HasEquipmentItem(tonumber(item.id)) then + return false + end + + -- already bought the item before + if item.limited and ply:HasBought(tostring(item.id)) then return false end + -- doesn't have the required items + if not WEPS.PlayerOwnsWepReqs(ply, item) then return false end + + return true +end + +local function GetOwnedEquipment() + -- Determine if we already have equipment + local owned_ids = {} + for _, wep in ipairs(LocalPlayer():GetWeapons()) do + if IsValid(wep) and wep:IsEquipment() then + TableInsert(owned_ids, wep:GetClass()) + end + end + + -- Stick to one value for no equipment + if #owned_ids == 0 then + owned_ids = nil + end + + return owned_ids +end + +local sort_funcs = { + default = { + name = "equip_sort_default", + func = function(a, b) + local aItem, bItem = not ItemIsWeapon(a), not ItemIsWeapon(b) + + if aItem or bItem then + -- sort items by id + if aItem and bItem then + return a.id < b.id + end + + -- keep items above weapons + return aItem + end + + return StringLower(SafeTranslate(a.name)) < StringLower(SafeTranslate(b.name)) + end + }, + name = { + name = "equip_spec_name", + func = function(a, b) + return StringLower(SafeTranslate(a.name)) < StringLower(SafeTranslate(b.name)) + end + }, + slot = { + name = "equip_sort_slot", + func = function(a, b) + local aSlot = a.slot or 0 + local bSlot = b.slot or 0 + + -- sort items by id + if aSlot == 0 and bSlot == 0 then + return a.id < b.id + end + + if aSlot == bSlot then + return StringLower(SafeTranslate(a.name)) < StringLower(SafeTranslate(b.name)) + end + + return aSlot < bSlot + end + } +} + +local function SortEquipmentPanels(pnls) + local sort = equipment_sorting:GetString() or "default" + local sort_func = sort_funcs[sort].func + + local ascending = equipment_ascending:GetBool() + local hide_unbuyable = equipment_hide_unbuyable:GetBool() + + local owned_ids = hide_unbuyable and GetOwnedEquipment() + + TableSort(pnls, function(a, b) + local aItem, bItem = a.item, b.item + + if hide_unbuyable then + local aBuyable, bBuyable = CanOrder(aItem, owned_ids), CanOrder(bItem, owned_ids) + + if aBuyable ~= bBuyable then + return aBuyable + end + end + + local ret = sort_func(aItem, bItem) + + -- if table.sort is comparing an item to itself, don't mess with the result otherwise weird stuff happens + if not ascending and aItem.id ~= bItem.id then + ret = not ret + end + + return ret + end) +end + +local function AddSortedPanels(panels) + if GetConVar("ttt_shop_random_position"):GetBool() then + panels = TableShuffle(panels) + else + SortEquipmentPanels(panels) + end + for _, panel in pairs(panels) do + ListPanel:AddPanel(panel) + end +end + +local function ReSortEquipment() + if not IsValid(ListPanel) then return end + + -- temp table for sorting + local paneltablefav = {} + local paneltable = {} + for _, ic in ipairs(ListPanel:GetItems()) do + if ic.favorite then + TableInsert(paneltablefav, ic) + else + TableInsert(paneltable, ic) + end + end + + ListPanel:Clear() + AddSortedPanels(paneltablefav) + AddSortedPanels(paneltable) + ListPanel:InvalidateLayout() +end +hook.Add("TTTLanguageChanged", "TTT_ReSortEquipment", ReSortEquipment) +cvars.AddChangeCallback("ttt_equipment_sorting", ReSortEquipment) +cvars.AddChangeCallback("ttt_equipment_ascending", ReSortEquipment) + local function DoesValueMatch(item, data, value) if not item[data] then return false end @@ -721,43 +875,69 @@ local function TraitorMenuPopup() dequip:SetPaintBackground(false) dequip:StretchToParent(padding, padding, padding, padding) - -- Determine if we already have equipment - local owned_ids = {} - for _, wep in ipairs(ply:GetWeapons()) do - if IsValid(wep) and wep.IsEquipment and wep:IsEquipment() then - TableInsert(owned_ids, wep:GetClass()) - end - end - - -- Stick to one value for no equipment - if #owned_ids == 0 then - owned_ids = nil - end - local dsearchheight = 25 local dsearchpadding = 5 + local dsortdirsize = 16 + local sw = MathFloor((dlistw - dsearchpadding) / 2) + local dsearch = vgui.Create("DTextEntry", dequip) dsearch:SetPos(0, 0) - dsearch:SetSize(dlistw, dsearchheight) + dsearch:SetSize(sw, dsearchheight) dsearch:SetPlaceholderText("Search...") dsearch:SetUpdateOnType(true) dsearch.OnGetFocus = function() dframe:SetKeyboardInputEnabled(true) end dsearch.OnLoseFocus = function() dframe:SetKeyboardInputEnabled(false) end + local dsort = vgui.Create("DPanel", dequip) + dsort:SetPos(sw + dsearchpadding, 0) + dsort:SetSize(sw, dsearchheight) + dsort:SetPaintBackground(false) + + local dsortlbl = vgui.Create("DLabel", dsort) + dsortlbl:SetFont("DermaDefaultBold") + dsortlbl:SetText(GetTranslation("sb_sortby")) + dsortlbl:SizeToContentsX() + dsortlbl:SetTall(dsearchheight) + dsortlbl:SetColor(COLOR_WHITE) + + local dsorttype = vgui.Create("DComboBox", dsort) + dsorttype:MoveRightOf(dsortlbl, m) + dsorttype:SetSize(dsort:GetWide() - dsortlbl:GetWide() - m - dsortdirsize, dsearchheight) + + for key, data in pairs(sort_funcs) do + dsorttype:AddChoice(GetTranslation(data.name), key, StringLower(equipment_sorting:GetString()) == key) + end + + dsorttype.OnSelect = function(s, idx, val, data) equipment_sorting:SetString(data) end + + local dsortasc = vgui.Create("DButton", dsort) + dsortasc:SetSize(dsortdirsize, dsortdirsize) + dsortasc:MoveRightOf(dsorttype) + dsortasc:SetY(dsearchpadding) + dsortasc:SetText("") + dsortasc:SetTooltip(GetTranslation("equip_sort_direction_tip")) + + dsortasc.Paint = function(s, pw, ph) + local name = equipment_ascending:GetBool() and "ButtonUp" or "ButtonDown" + derma.SkinHook("Paint", name, s, dsortdirsize, dsortdirsize) + end + + dsortasc.DoClick = function(s) equipment_ascending:SetBool(not equipment_ascending:GetBool()) end + --- Construct icon listing --- icon size = 64 x 64 local dlist = vgui.Create("EquipSelect", dequip) - -- local dlistw = 288 dlist:SetPos(0, dsearchheight + dsearchpadding) dlist:SetSize(dlistw, dlisth - dsearchheight - dsearchpadding) dlist:EnableVerticalScrollbar() dlist:EnableHorizontal(true) + ListPanel = dlist + local bw, bh = 102, 25 -- Whole right column local dih = h - bh - m * 5 - -- local diw = w - dlistw - m*6 - 2 local dinfobg = vgui.Create("DPanel", dequip) dinfobg:SetPaintBackground(false) dinfobg:SetSize(diw - m, dih) @@ -791,21 +971,7 @@ local function TraitorMenuPopup() dhelp:SetSize(diw, 64) dhelp:MoveBelow(dinfo, m) - local update_preqs = PreqLabels(dhelp, m * 7, m * 2) - - local function CannotBuyItem(item) - local orderable = update_preqs(item) - return (not orderable) or - -- already owned - TableHasValue(owned_ids, item.id) or - (tonumber(item.id) and ply:HasEquipmentItem(tonumber(item.id))) or - -- already carrying a weapon for this slot - (ItemIsWeapon(item) and (not CanCarryWeapon(item))) or - -- already bought the item before - (item.limited and ply:HasBought(tostring(item.id))) or - -- doesn't have the required items - not WEPS.PlayerOwnsWepReqs(ply, item) - end + ListPanel.update_preqs = PreqLabels(dhelp, m * 7, m * 2) local function FillEquipmentList(itemlist) dlist:Clear() @@ -813,10 +979,6 @@ local function TraitorMenuPopup() -- temp table for sorting local paneltablefav = {} local paneltable = {} - for i = 0, 9 do - paneltablefav[i] = {} - paneltable[i] = {} - end for _, item in pairs(itemlist) do local ic = nil @@ -877,7 +1039,7 @@ local function TraitorMenuPopup() -- Credit to @Angela and @Technofrood on the Lonely Yogs Discord for the fix! -- Clamp the item slot within the correct limits if ic.slot ~= nil then - ic.slot = math.Clamp(ic.slot, 1, #paneltable) + ic.slot = math.Clamp(ic.slot, 1, 9) end slot:SetIconProperties(COLOR_WHITE, @@ -907,7 +1069,7 @@ local function TraitorMenuPopup() ic:SetTooltip(tip) -- If we cannot order this item, darken it - if CannotBuyItem(item) then + if not CanOrder(item, GetOwnedEquipment()) then ic:SetIconColor(color_darkened) end @@ -917,80 +1079,15 @@ local function TraitorMenuPopup() ic:Remove() else if ic.favorite then - TableInsert(paneltablefav[ic.slot or 1], ic) + TableInsert(paneltablefav, ic) else - TableInsert(paneltable[ic.slot or 1], ic) + TableInsert(paneltable, ic) end end end - local AddNameSortedItems = function(panels) - if sortAlphabetically:GetBool() then - TableSort(panels, function(a, b) return StringLower(a.item.name) < StringLower(b.item.name) end) - end - for _, panel in pairs(panels) do - dlist:AddPanel(panel) - end - end - - -- add favorites first - -- Add equipment items separately - AddNameSortedItems(paneltablefav[0]) - - if sortBySlotFirst:GetBool() then - for i = 1, 9 do - AddNameSortedItems(paneltablefav[i]) - end - else - -- Gather all the panels into one list - local panels = {} - for i = 1, 9 do - for _, p in pairs(paneltablefav[i]) do - TableInsert(panels, p) - end - end - - AddNameSortedItems(panels) - end - - -- non favorites second - -- Randomize positions if this is enabled - if GetConVar("ttt_shop_random_position"):GetBool() then - -- Gather all the panels into one list - local panels = {} - for i = 0, 9 do - for _, p in pairs(paneltable[i]) do - TableInsert(panels, p) - end - end - - -- Randomize it - panels = table.Shuffle(panels) - - -- Add them all to the list - for _, p in ipairs(panels) do - dlist:AddPanel(p) - end - else - -- Add equipment items separately - AddNameSortedItems(paneltable[0]) - - if sortBySlotFirst:GetBool() then - for i = 1, 9 do - AddNameSortedItems(paneltable[i]) - end - else - -- Gather all the panels into one list - local panels = {} - for i = 1, 9 do - for _, p in pairs(paneltable[i]) do - TableInsert(panels, p) - end - end - - AddNameSortedItems(panels) - end - end + AddSortedPanels(paneltablefav) + AddSortedPanels(paneltable) -- select first dlist:SelectPanel(dlist:GetItems()[1]) @@ -1037,7 +1134,7 @@ local function TraitorMenuPopup() -- force a good size. dfields.desc:SetTall(70) - can_order = update_preqs(new.item) + can_order = ListPanel.update_preqs(new.item) else for _, v in pairs(dfields) do if v then @@ -1069,7 +1166,7 @@ local function TraitorMenuPopup() if not pnl or not pnl.item then return end if new:GetPanel() == dequip then - can_order = update_preqs(pnl.item) + can_order = ListPanel.update_preqs(pnl.item) dconfirm:SetEnabled(can_order) end end @@ -1121,7 +1218,7 @@ local function TraitorMenuPopup() local item_panels = dlist:GetItems() local buyable_items = {} for _, item_panel in pairs(item_panels) do - if item_panel.item and not CannotBuyItem(item_panel.item) then + if item_panel.item and CanOrder(item_panel.item, GetOwnedEquipment()) then TableInsert(buyable_items, item_panel) end end diff --git a/gamemodes/terrortown/gamemode/cl_help.lua b/gamemodes/terrortown/gamemode/cl_help.lua index 8dfbcc7a4..9122e7d3b 100644 --- a/gamemodes/terrortown/gamemode/cl_help.lua +++ b/gamemodes/terrortown/gamemode/cl_help.lua @@ -982,17 +982,20 @@ function HELPSCRN:CreateConfig(dsettings) dlanguage:SetName(GetTranslation("set_title_lang")) local dlang = vgui.Create("DComboBox", dlanguage) - dlang:SetConVar("ttt_language") - dlang:AddChoice("Server default", "auto") + dlang:AddChoice("Server default", "server default") + dlang:AddChoice("GMod language setting", "auto") for _, lang in pairs(LANG.GetLanguages()) do dlang:AddChoice(string.Capitalize(lang), lang) end + + local ttt_language = LANG.GetLanguageName(GetConVar("ttt_language"):GetString()) + dlang:SetValue(dlang:GetOptionTextByData(ttt_language)) + -- Why is DComboBox not updating the cvar by default? - dlang.OnSelect = function(idx, val, data) + dlang.OnSelect = function(s, idx, val, data) RunConsoleCommand("ttt_language", data) end - dlang.Think = dlang.ConVarStringThink dlanguage:Help(GetTranslation("set_lang")) dlanguage:AddItem(dlang) diff --git a/gamemodes/terrortown/gamemode/cl_hud.lua b/gamemodes/terrortown/gamemode/cl_hud.lua index 694dba182..6f61854bb 100644 --- a/gamemodes/terrortown/gamemode/cl_hud.lua +++ b/gamemodes/terrortown/gamemode/cl_hud.lua @@ -567,7 +567,12 @@ local function InfoPaint(client) if hide_role:GetBool() then text = GetTranslation("hidden") else - text = LANG.GetRawTranslation(client:GetRoleStringRaw()) or client:GetRoleString() + local role_string = client:GetRoleString() + if role_string ~= ROLE_STRINGS_DEFAULT[client:GetRole()] then + text = role_string + else + text = LANG.GetRawTranslation(client:GetRoleStringRaw()) or role_string + end local new_text = CallHook("TTTHUDRoleNameOverride", nil, client, text) if new_text then text = new_text end diff --git a/gamemodes/terrortown/gamemode/cl_init.lua b/gamemodes/terrortown/gamemode/cl_init.lua index cfafaa0de..30db81c87 100644 --- a/gamemodes/terrortown/gamemode/cl_init.lua +++ b/gamemodes/terrortown/gamemode/cl_init.lua @@ -48,6 +48,7 @@ include("corpse_shd.lua") include("player_ext_shd.lua") include("weaponry_shd.lua") include("sprint_shd.lua") +include("rolepacks_shd.lua") include("vgui/ColoredBox.lua") include("vgui/SimpleIcon.lua") diff --git a/gamemodes/terrortown/gamemode/cl_lang.lua b/gamemodes/terrortown/gamemode/cl_lang.lua index ec83c913b..f8ff1254f 100644 --- a/gamemodes/terrortown/gamemode/cl_lang.lua +++ b/gamemodes/terrortown/gamemode/cl_lang.lua @@ -18,6 +18,7 @@ local string = string local table = table LANG.Strings = {} +LANG.Codes = {} local ttt_language = CreateConVar("ttt_language", "auto", FCVAR_ARCHIVE) @@ -28,10 +29,15 @@ LANG.ServerLanguage = "english" local cached_default, cached_active -function LANG.CreateLanguage(raw_lang_name) +function LANG.CreateLanguage(raw_lang_name, lang_code) if not raw_lang_name then return end local lang_name = string.lower(raw_lang_name) + if lang_code then + lang_code = string.lower(lang_code) + LANG.Codes[lang_code] = lang_name + end + if not LANG.IsLanguage(lang_name) then LANG.Strings[lang_name] = { -- Empty string is very convenient to have, so init with that. @@ -55,7 +61,18 @@ function LANG.CreateLanguage(raw_lang_name) end return LANG.Strings[lang_name] - end +end + +function LANG.GetLanguageName(lang_name) + if not lang_name then return end + lang_name = string.lower(lang_name) + + if LANG.Codes[lang_name] then + lang_name = LANG.Codes[lang_name] + end + + return lang_name +end -- Add a string to a language. Should not be used in a language file, only for -- adding strings elsewhere, such as a SWEP script. @@ -138,7 +155,7 @@ function LANG.GetLanguageTable(lang_name) end function LANG.SetActiveLanguage(lang_name) - lang_name = lang_name and string.lower(lang_name) + lang_name = LANG.GetLanguageName(lang_name) if LANG.IsLanguage(lang_name) then local old_name = LANG.ActiveLanguage @@ -164,6 +181,7 @@ function LANG.SetActiveLanguage(lang_name) end end +local gmod_language = GetConVar("gmod_language") function LANG.Init() local lang_name = ttt_language:GetString() @@ -171,6 +189,8 @@ function LANG.Init() -- we hear from the server which one it is, for now use default if LANG.IsServerDefault(lang_name) then lang_name = LANG.ServerLanguage + elseif LANG.IsAuto(lang_name) then + lang_name = gmod_language:GetString() end LANG.SetActiveLanguage(lang_name) @@ -178,7 +198,12 @@ end function LANG.IsServerDefault(lang_name) lang_name = string.lower(lang_name) - return lang_name == "server default" or lang_name == "auto" + return lang_name == "server default" +end + +function LANG.IsAuto(lang_name) + lang_name = string.lower(lang_name) + return lang_name == "auto" end function LANG.IsLanguage(lang_name) @@ -187,14 +212,15 @@ function LANG.IsLanguage(lang_name) end local function LanguageChanged(cv, old, new) - if new and new ~= LANG.ActiveLanguage then + if not new or new == LANG.ActiveLanguage then return end - if LANG.IsServerDefault(new) then - new = LANG.ServerLanguage - end - - LANG.SetActiveLanguage(new) + if LANG.IsServerDefault(new) then + new = LANG.ServerLanguage + elseif LANG.IsAuto(new) then + new = gmod_language:GetString() end + + LANG.SetActiveLanguage(new) end cvars.AddChangeCallback("ttt_language", LanguageChanged) @@ -203,6 +229,13 @@ local function ForceReload() end concommand.Add("ttt_reloadlang", ForceReload) +local function GMLanguageChanged(cv, old, new) + if new and LANG.IsAuto(ttt_language:GetString()) then + LANG.SetActiveLanguage(new) + end +end +cvars.AddChangeCallback("gmod_language", GMLanguageChanged) + -- Get a copy of all available languages (keys in the Strings tbl) function LANG.GetLanguages() local langs = {} diff --git a/gamemodes/terrortown/gamemode/cl_scoring.lua b/gamemodes/terrortown/gamemode/cl_scoring.lua index 69c4cd0cf..7ae50f2e6 100644 --- a/gamemodes/terrortown/gamemode/cl_scoring.lua +++ b/gamemodes/terrortown/gamemode/cl_scoring.lua @@ -412,9 +412,10 @@ function CLSCORE:BuildScorePanel(dpanel) local skull = vgui.Create("DImage", surv) skull:SetMaterial(skull_icon) - skull:SetTooltip("Dead") + skull:SetTooltip(T("dead")) skull:SetKeepAspect(true) skull:SetSize(18, 18) + skull:SetMouseInputEnabled(true) end local points_own = KillsToPoints(s, was_traitor, was_innocent) diff --git a/gamemodes/terrortown/gamemode/cl_scoring_events.lua b/gamemodes/terrortown/gamemode/cl_scoring_events.lua index d2aaebc0a..8028f2a44 100644 --- a/gamemodes/terrortown/gamemode/cl_scoring_events.lua +++ b/gamemodes/terrortown/gamemode/cl_scoring_events.lua @@ -116,7 +116,7 @@ Event(EVENT_GAME, if e.state == ROUND_ACTIVE then return T("ev_start") end end, icon = function(e) - return app_icon, "Game" + return app_icon, T("ev_start_tip") end }) @@ -132,7 +132,7 @@ Event(EVENT_SPAWN, end end, icon = function(e) - return app_icon, "Game" + return app_icon, T("ev_start_tip") end }) Event(EVENT_ROLECHANGE, @@ -146,7 +146,7 @@ Event(EVENT_ROLECHANGE, end end, icon = function(e) - return app_icon, "Game" + return app_icon, T("ev_start_tip") end }) @@ -158,7 +158,7 @@ Event(EVENT_CREDITFOUND, player = e.b}) end, icon = function(e) - return credit_icon, "Credit found" + return credit_icon, T("ev_credit_tip") end }) @@ -167,7 +167,7 @@ Event(EVENT_BODYFOUND, return PT("ev_body", {finder = e.ni, victim = e.b}) end, icon = function(e) - return magnifier_icon, "Body discovered" + return magnifier_icon, T("ev_body_tip") end }) @@ -178,7 +178,7 @@ Event(EVENT_C4DISARM, {player = e.ni, owner = e.own or "aliens"}) end, icon = function(e) - return wrench_icon, "C4 disarm" + return wrench_icon, T("ev_c4_disarm_tip") end }) @@ -187,7 +187,7 @@ Event(EVENT_C4EXPLODE, return PT("ev_c4_boom", {player = e.ni}) end, icon = function(e) - return bomb_icon, "C4 exploded" + return bomb_icon, T("ev_c4_boom_tip") end }) @@ -196,7 +196,7 @@ Event(EVENT_C4PLANT, return PT("ev_c4_plant", {player = e.ni}) end, icon = function(e) - return bomb_icon, "C4 planted" + return bomb_icon, T("ev_c4_plant_tip") end }) @@ -315,17 +315,17 @@ Event(EVENT_KILL, text = KillText, icon = function(e) if e.att.sid64 == e.vic.sid64 or e.att.sid64 == -1 then - return wrong_icon, "Suicide" + return wrong_icon, T("ev_suicide") end - local attacker = (e.att.tr and "Traitor") or (e.att.jes and "Jester") or (e.att.ind and "Independent") or (e.att.mon and "Monster") or "Innocent" - local victim = (e.vic.tr and "Traitor") or (e.vic.jes and "Jester") or (e.vic.ind and "Independent") or (e.vic.mon and "Monster") or "Innocent" + local attacker = (e.att.tr and "traitor") or (e.att.jes and "jester") or (e.att.ind and "independent") or (e.att.mon and "monster") or "innocent" + local victim = (e.vic.tr and "traitor") or (e.vic.jes and "jester") or (e.vic.ind and "independent") or (e.vic.mon and "monster") or "innocent" if e.tk then - return wrong_icon, "Teamkill" + return wrong_icon, T("ev_teamkill") elseif e.att.tr or e.att.ind or e.att.mon then - return right_icon, attacker.." killed "..victim + return right_icon, T(attacker)..T("ev_killed")..T(victim) else - return shield_icon, attacker.." killed "..victim + return shield_icon, T(attacker)..T("ev_killed")..T(victim) end end }) @@ -335,7 +335,7 @@ Event(EVENT_DEFIBRILLATED, { return PT("ev_defi", {victim = e.vic}) end, icon = function(e) - return heart_icon, "Defibrillated" + return heart_icon, T("ev_defi_icon") end}) Event(EVENT_DISCONNECTED, { @@ -343,7 +343,7 @@ Event(EVENT_DISCONNECTED, { return PT("ev_disco", {victim = e.vic}) end, icon = function(e) - return disconnect_icon, "Disconnected" + return disconnect_icon, T("ev_disco_icon") end}) Event(EVENT_LOG, { @@ -351,5 +351,5 @@ Event(EVENT_LOG, { return e.txt end, icon = function(e) - return info_icon, "Information" + return info_icon, T("ev_info_icon") end}) \ No newline at end of file diff --git a/gamemodes/terrortown/gamemode/cl_search.lua b/gamemodes/terrortown/gamemode/cl_search.lua index 03cbf3df3..8f3568e39 100644 --- a/gamemodes/terrortown/gamemode/cl_search.lua +++ b/gamemodes/terrortown/gamemode/cl_search.lua @@ -261,7 +261,7 @@ function PreprocSearch(raw) local vic = Entity(d[1]) local dc = d[1] == 0 -- disconnected if dc or IsPlayer(vic) then - search[t].text = PT("search_kills1", { player = dc and "