Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CONVARS.md
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,14 @@ ttt_cannibal_damage_penalty 0 // The fraction a Canniba
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)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

ttt_cannibal_gains_health 1 // Whether the Cannibal gains their victim's health when eating them
ttt_cannibal_gained_health_percentage 100 // What percentage of their victim's health the Cannibal gains (set to 0 to always gain a flat 100HP)
ttt_cannibal_digests_victims 1 // Whether the Cannibal digests and permanently kills their victims over time
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ttt_cannibal_digests_victims 1 // Whether the Cannibal digests and permanently kills their victims over time
ttt_cannibal_digestion_bones 1 // 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)
ttt_cannibal_does_poopy 1 // Whether the Cannibal poops themselves when a victim is digested
Copy link
Member

@Malivil Malivil Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ttt_cannibal_does_poopy 1 // Whether the Cannibal poops themselves when a victim is digested
ttt_cannibal_digestion_poop 1 // Whether the Cannibal drops poop when a victim is digested

ttt_cannibal_does_poopy_noise 1 // Whether the Cannibal's poops produce an audible cue
Copy link
Member

@Malivil Malivil Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ttt_cannibal_does_poopy_noise 1 // Whether the Cannibal's poops produce an audible cue
ttt_cannibal_digestion_poop_sound 1 // Whether the Cannibal causes a sound when poop is dropped from a digested victim


// ----------------------------------------

// INDEPENDENT TEAM SETTINGS
Expand Down
10 changes: 10 additions & 0 deletions gamemodes/terrortown/content/materials/models/poo/poo.vmt
Original file line number Diff line number Diff line change
@@ -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]"
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added gamemodes/terrortown/content/models/poo/poo.mdl
Binary file not shown.
Binary file added gamemodes/terrortown/content/models/poo/poo.phy
Binary file not shown.
Binary file not shown.
Binary file added gamemodes/terrortown/content/models/poo/poo.vvd
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
93 changes: 93 additions & 0 deletions gamemodes/terrortown/entities/weapons/weapon_can_eater.lua
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 of the 6 added ConVars are not used on the client can be:

  1. Moved to a if SERVER then block
  2. Be changed from FCVAR_REPLICATED to FCVAR_NONE

Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,28 @@ 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", "1", FCVAR_REPLICATED, "Whether the Cannibal gains their victim's health when eating them", 0, 1)
SWEP.GainedHealthPercentageConVar = CreateConVar("ttt_cannibal_gained_health_percentage", "100", FCVAR_REPLICATED, "What percentage of their victim's health the Cannibal gains (set to 0 to always gain a flat 100HP)", 0, 500)
SWEP.DigestsVictimsConVar = CreateConVar("ttt_cannibal_digests_victims", "1", FCVAR_REPLICATED, "Whether the Cannibal digests and permanently kills their victims over time", 0, 1)
Copy link
Member

@Malivil Malivil Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SWEP.DigestsVictimsConVar = CreateConVar("ttt_cannibal_digests_victims", "1", FCVAR_REPLICATED, "Whether the Cannibal digests and permanently kills their victims over time", 0, 1)
SWEP.DigestionConVar = CreateConVar("ttt_cannibal_digestion", "1", 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)
SWEP.RectallyIncontinentConVar = CreateConVar("ttt_cannibal_does_poopy", "1", FCVAR_REPLICATED, "Whether the Cannibal poops themselves when a victim is digested", 0, 1)
Copy link
Member

@Malivil Malivil Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funny, but no.

Suggested change
SWEP.RectallyIncontinentConVar = CreateConVar("ttt_cannibal_does_poopy", "1", FCVAR_REPLICATED, "Whether the Cannibal poops themselves when a victim is digested", 0, 1)
SWEP.DigestionPoopConVar = CreateConVar("ttt_cannibal_digestion_poop", "1", FCVAR_REPLICATED, "Whether the Cannibal drops poop when a victim is digested", 0, 1)

SWEP.AudibleIncontinenceConVar = CreateConVar("ttt_cannibal_does_poopy_noise", "1", FCVAR_REPLICATED, "Whether the Cannibal's poops produce an audible cue", 0, 1)
Copy link
Member

@Malivil Malivil Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SWEP.AudibleIncontinenceConVar = CreateConVar("ttt_cannibal_does_poopy_noise", "1", FCVAR_REPLICATED, "Whether the Cannibal's poops produce an audible cue", 0, 1)
SWEP.DigestionPoopSoundConVar = CreateConVar("ttt_cannibal_digestion_poop_sound", "1", FCVAR_REPLICATED, "Whether the Cannibal causes a sound when poop is dropped from a digested victim.", 0, 1)


local eatSounds = {
"cannibal/eat1.wav",
"cannibal/eat2.wav",
"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)
Expand Down Expand Up @@ -125,6 +140,84 @@ function SWEP:PrimaryAttack()
if cooldown > 0 then
self:SetDeviceCooldownEnd(CurTime() + cooldown)
end

-- Cannibal health gain
if self.GainsHealthConVar:GetBool() then

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

local gainedhealthpercentage = self.GainedHealthPercentageConVar:GetInt()
local victimhealth = hitEnt:Health()
local cannibalhealth = owner:Health()

if gainedhealthpercentage == 0 then
gainedhealth = 100
else
gainedhealthunrounded = (gainedhealthpercentage / 100) * victimhealth
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
gainedhealthunrounded = (gainedhealthpercentage / 100) * victimhealth
local gainedhealthunrounded = (gainedhealthpercentage / 100) * victimhealth

gainedhealth = math.floor(gainedhealthunrounded)
end

local newcannibalhealth = cannibalhealth + gainedhealth
if newcannibalhealth > cannibalhealth then
owner:SetHealth(newcannibalhealth)
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

end

-- Victim digestion
if self.DigestsVictimsConVar:GetBool() then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.DigestsVictimsConVar:GetBool() then
if self.DigestionConVar:GetBool() then


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

local digestiontime = self.DigestionTimeConVar:GetInt()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

if digestiontime == 0 then
digestiontime = 0.1 -- short delay to avoid fuckyness?
end

local disgestiontimername = "TTTCannibalDigestion_" .. sID64

-- Remove existing digestion timer for victim if one somehow exists
if timer.Exists(disgestiontimername) then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary. timer.Create with the same name overwrites previous. See identifier here: https://wiki.facepunch.com/gmod/timer.Create#arguments

timer.Remove(disgestiontimername)
end

timer.Create(disgestiontimername, digestiontime, 1, function()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

-- Safety checks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-- Safety checks

if not IsValid(hitEnt) or not hitEnt:IsPlayer() then return end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if not IsValid(hitEnt) or not hitEnt:IsPlayer() then return end
if not IsPlayer(hitEnt) then return end

if not IsValid(owner) or not owner:IsPlayer() then return end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if not IsValid(owner) or not owner:IsPlayer() 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
hitEnt:KillSilent()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use KillSilent because it calls a different hook.

Suggested change
hitEnt:KillSilent()
hitEnt:Kill()

hitEnt:ClearProperty("TTTCannibalEaten")

hitEnt:SetParent(nil)
hitEnt:SpectateEntity(nil)

hitEnt:QueueMessage(MSG_PRINTBOTH, "You have been fully digested!", 3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most messages use the default length unless there is a good reason not to

Suggested change
hitEnt:QueueMessage(MSG_PRINTBOTH, "You have been fully digested!", 3)
hitEnt:QueueMessage(MSG_PRINTBOTH, "You have been fully digested!")

owner:QueueMessage(MSG_PRINTBOTH, "You have fully digested " .. hitEnt:Nick() .. "!", 3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
owner:QueueMessage(MSG_PRINTBOTH, "You have fully digested " .. hitEnt:Nick() .. "!", 3)
owner:QueueMessage(MSG_PRINTBOTH, "You have fully digested " .. hitEnt:Nick() .. "!")


-- Spawn poop at cannibal's position
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-- Spawn poop at cannibal's position
-- Spawn bones at cannibal's position

if self.RectallyIncontinentConVar:GetBool() then
Copy link
Member

@Malivil Malivil Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.RectallyIncontinentConVar:GetBool() then
if self.DigestionPoopConVar:GetBool() then

local poop = ents.Create("prop_physics")
if IsValid(poop) then
Copy link
Member

@Malivil Malivil Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set fingerprints on the poop like the Vampire's fangs do to the dropped bones.

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()

if self.AudibleIncontinenceConVar:GetBool()then
Copy link
Member

@Malivil Malivil Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.AudibleIncontinenceConVar:GetBool()then
if self.DigestionPoopSoundConVar:GetBool()then

owner:EmitSound(poopSounds[math.random(1, #poopSounds)], 100)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
owner:EmitSound(poopSounds[math.random(1, #poopSounds)], 100)
owner:EmitSound(poopSounds[math.random(#poopSounds)], 100)

end
end
end
end

end)
end
end
end

Expand Down
2 changes: 2 additions & 0 deletions gamemodes/terrortown/gamemode/roles/cannibal/cannibal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ local function ReleaseEatenPlayers(ply, message)

local sID64 = v:SteamID64()

timer.Remove("TTTCannibalDigestion_" .. sID64)

for _, data in ipairs(CANNIBAL.playerWeapons[sID64]) do
local wep = v:Give(data.class)
if not IsValid(wep) then continue end
Expand Down
15 changes: 13 additions & 2 deletions gamemodes/terrortown/gamemode/roles/cannibal/cl_cannibal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,21 @@ AddHook("TTTTutorialRoleText", "Cannibal_TTTTutorialRoleText", function(role, ti
local roleTeamName, roleColor = GetRoleTeamInfo(roleTeam)
local html = "The " .. ROLE_STRINGS[ROLE_CANNIBAL] .. " is a <span style='color: rgb(" .. roleColor.r .. ", " .. roleColor.g .. ", " .. roleColor.b .. ")'>" .. roleTeamName .. "</span> role whose goal is to eat all other players."

html = html .. "<span style='display: block; margin-top: 10px;'>Eaten players are not dead, but they are <span style='color: rgb(" .. roleColor.r .. ", " .. roleColor.g .. ", " .. roleColor.b .. ")'>unable to do anything</span> except talk with other eaten players and spectate the " .. ROLE_STRINGS[ROLE_CANNIBAL] .. ".</span>"
if GetConVar("ttt_cannibal_digests_victims"):GetBool() then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor point, but the first paragraph is almost identical as below so it could be extracted and de-duplicated.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

html = html .. "<span style='display: block; margin-top: 10px;'>Eaten players are not immediately dead, but they are <span style='color: rgb(" .. roleColor.r .. ", " .. roleColor.g .. ", " .. roleColor.b .. ")'>unable to do anything</span> except talk with other eaten players and spectate the " .. ROLE_STRINGS[ROLE_CANNIBAL] .. ".</span>"

html = html .. "<span style='display: block; margin-top: 10px;'>If the " .. ROLE_STRINGS[ROLE_CANNIBAL] .. " dies, all eaten players are <span style='color: rgb(" .. roleColor.r .. ", " .. roleColor.g .. ", " .. roleColor.b .. ")'>freed</span> at the position where the " .. ROLE_STRINGS[ROLE_CANNIBAL] .. " died.</span>"
html = html .. "<span style='display: block; margin-top: 10px;'>Eaten players are <span style='color: rgb(" .. roleColor.r .. ", " .. roleColor.g .. ", " .. roleColor.b .. ")'>digested and killed </span>" .. GetConVar("ttt_cannibal_digestion_time"):GetInt() .. " seconds after being eaten.</span>"

html = html .. "<span style='display: block; margin-top: 10px;'>If the " .. ROLE_STRINGS[ROLE_CANNIBAL] .. " dies, all undigested eaten players are <span style='color: rgb(" .. roleColor.r .. ", " .. roleColor.g .. ", " .. roleColor.b .. ")'>freed</span> at the position where the " .. ROLE_STRINGS[ROLE_CANNIBAL] .. " died.</span>"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

else

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

html = html .. "<span style='display: block; margin-top: 10px;'>Eaten players are not dead, but they are <span style='color: rgb(" .. roleColor.r .. ", " .. roleColor.g .. ", " .. roleColor.b .. ")'>unable to do anything</span> except talk with other eaten players and spectate the " .. ROLE_STRINGS[ROLE_CANNIBAL] .. ".</span>"

html = html .. "<span style='display: block; margin-top: 10px;'>If the " .. ROLE_STRINGS[ROLE_CANNIBAL] .. " dies, all eaten players are <span style='color: rgb(" .. roleColor.r .. ", " .. roleColor.g .. ", " .. roleColor.b .. ")'>freed</span> at the position where the " .. ROLE_STRINGS[ROLE_CANNIBAL] .. " died.</span>"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

end
return html
end
end)
24 changes: 24 additions & 0 deletions gamemodes/terrortown/gamemode/roles/cannibal/shared.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ table.insert(ROLE_CONVARS[ROLE_CANNIBAL], {
cvar = "ttt_cannibal_update_scoreboard",
type = ROLE_CONVAR_TYPE_BOOL
})
table.insert(ROLE_CONVARS[ROLE_CANNIBAL], {
cvar = "ttt_cannibal_gains_health",
type = ROLE_CONVAR_TYPE_BOOL
})
table.insert(ROLE_CONVARS[ROLE_CANNIBAL], {
cvar = "ttt_cannibal_gained_health_percentage",
type = ROLE_CONVAR_TYPE_NUM
})
table.insert(ROLE_CONVARS[ROLE_CANNIBAL], {
cvar = "ttt_cannibal_digests_victims",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cvar = "ttt_cannibal_digests_victims",
cvar = "ttt_cannibal_digestion",

type = ROLE_CONVAR_TYPE_BOOL
})
table.insert(ROLE_CONVARS[ROLE_CANNIBAL], {
cvar = "ttt_cannibal_digestion_time",
type = ROLE_CONVAR_TYPE_NUM
})
table.insert(ROLE_CONVARS[ROLE_CANNIBAL], {
cvar = "ttt_cannibal_does_poopy",
Copy link
Member

@Malivil Malivil Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cvar = "ttt_cannibal_does_poopy",
cvar = "ttt_cannibal_digestion_poop",

type = ROLE_CONVAR_TYPE_BOOL
})
table.insert(ROLE_CONVARS[ROLE_CANNIBAL], {
cvar = "ttt_cannibal_does_poopy_noise",
Copy link
Member

@Malivil Malivil Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cvar = "ttt_cannibal_does_poopy_noise",
cvar = "ttt_cannibal_digestion_poop_sound",

type = ROLE_CONVAR_TYPE_BOOL
})

-------------------
-- ROLE FEATURES --
Expand Down
Loading