53743b1aee
Random starting loadouts now include things that aren't weapons (ammo, traits, etc.). Added an option to disable the automatic suicide when changing zombie classes. Zombies will no longer receive damage resistance when only one human remains. Players can no longer claim the arsenal crate that gets spawned in the human spawn when nobody has one. Any player can pack it up though. The Ghoul's ghoul touch special ability has been changed. It will no longer slow targets but will debuff them for the next 10 seconds. They will take 40% more damage (the extra damage is attributed to the person who ghoul touched them last) for the next 10 seconds as well as slightly disorienting them. The Ghoul movement speed has been reduced from 185 to 170. Added crafting recipe: 'Waraxe' Handgun. Combine two 'Battleaxe' Handguns to craft this. Slightly better version of the Owens. The Flesh Creeper model has been changed to one that doesn't have severely awkward hitboxes. The Flesh Creeper can no longer jump and attack at the same time. The Lead Pipe special trait has been changed from a disorientation to a severe view punch/snap on a global cooldown of 1.5 seconds. The Regenerative trait is now 1 health every 6 seconds under 50% health instead of 1 health every 5 seconds under 50% health. Fast Zombie Legs have been changed to be a slightly faster but slightly weaker version of Zombie Legs. Zombies that have just spawned or enter zombie gas will now have a temporary buff which gives 25% extra speed and 40% damage resistance. This buff lasts for 3 seconds and is refreshed by entering the gas. Gas will no longer heal. Zombies with this buff on will strobe dark green. Added crafting recipe: Bladehack. Combine a saw blade with a manhack to get this. Slower but has more health, does more damage, and has less of a knockback when hitting something. Resupply Boxes now award the owner a point for every 2 people who use their box instead of every single person (so half as many points). Fixed Fast Zombie Legs spawning much more abundantly than intended. Fixed Flesh Creepers not being able to jump over obstacles due to their insanely low jump height. Fixed zombies taking themselves in to account when calculating horde damage resistance (bosses always had full resistance because of this). Fixed allowing people to use worth menu after redeeming.
429 lines
9.9 KiB
Lua
429 lines
9.9 KiB
Lua
local meta = FindMetaTable("Entity")
|
|
if not meta then return end
|
|
|
|
function meta:GetDefaultBarricadeHealth()
|
|
local mass = 2
|
|
local phys = self:GetPhysicsObject()
|
|
if phys:IsValid() then
|
|
mass = phys:GetMass()
|
|
end
|
|
|
|
return math.Clamp(mass * GAMEMODE.BarricadeHealthMassFactor + self:GetVolume() * GAMEMODE.BarricadeHealthVolumeFactor, GAMEMODE.BarricadeHealthMin, GAMEMODE.BarricadeHealthMax)
|
|
end
|
|
|
|
function meta:HitFence(data, phys)
|
|
local pos = phys:GetPos()
|
|
local vel = data.OurOldVelocity
|
|
local endpos = data.HitPos + vel:GetNormalized()
|
|
if util.TraceLine({start = pos, endpos = endpos, mask = MASK_SOLID, filter = self}).Hit and not util.TraceLine({start = pos, endpos = endpos, mask = MASK_SHOT, filter = self}).Hit then -- Essentially hit a fence or passable object.
|
|
self:SetPos(data.HitPos)
|
|
phys:SetPos(data.HitPos)
|
|
phys:SetVelocityInstantaneous(vel)
|
|
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function meta:GhostAllPlayersInMe(timeout, allowrepeat)
|
|
if not allowrepeat then
|
|
if self.GhostedBefore then return end
|
|
self.GhostedBefore = true
|
|
end
|
|
|
|
local ent = ents.Create("point_propnocollide")
|
|
if ent:IsValid() then
|
|
ent:SetPos(self:GetPos())
|
|
ent:Spawn()
|
|
if timeout then
|
|
ent:SetTimeOut(CurTime() + timeout)
|
|
end
|
|
ent:SetTeam(TEAM_HUMAN)
|
|
|
|
ent:SetProp(self)
|
|
end
|
|
end
|
|
|
|
local function SortItems(a, b)
|
|
if a.CleanupPriority == b.CleanupPriority then
|
|
return a.Created < b.Created
|
|
end
|
|
|
|
return a.CleanupPriority < b.CleanupPriority
|
|
end
|
|
local function CheckItemCreated(self)
|
|
if not self:IsValid() or self.PlacedInMap then return end
|
|
|
|
local tab = {}
|
|
for _, ent in pairs(ents.FindByClass("prop_ammo")) do
|
|
if not ent.PlacedInMap then
|
|
table.insert(tab, ent)
|
|
end
|
|
end
|
|
for _, ent in pairs(ents.FindByClass("prop_weapon")) do
|
|
if not ent.PlacedInMap then
|
|
table.insert(tab, ent)
|
|
end
|
|
end
|
|
|
|
if #tab > GAMEMODE.MaxDroppedItems then
|
|
table.sort(tab, SortItems)
|
|
for i = 1, GAMEMODE.MaxDroppedItems do
|
|
tab[i]:Remove()
|
|
end
|
|
end
|
|
end
|
|
function meta:ItemCreated()
|
|
self.Created = self.Created or CurTime()
|
|
timer.Simple(0, function() CheckItemCreated(self) end)
|
|
end
|
|
|
|
function meta:FireOutput(outpt, activator, caller, args)
|
|
local intab = self[outpt]
|
|
if intab then
|
|
for key, tab in pairs(intab) do
|
|
for __, subent in pairs(self:FindByNameHammer(tab.entityname, activator, caller)) do
|
|
local delay = tonumber(tab.delay)
|
|
if delay == nil or delay <= 0 then
|
|
subent:Input(tab.input, activator, caller, tab.args)
|
|
else
|
|
local inp = tab.input
|
|
local args = tab.args
|
|
timer.Simple(delay, function() if subent:IsValid() then subent:Input(inp, activator, caller, args) end end)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function meta:AddOnOutput(key, value)
|
|
self[key] = self[key] or {}
|
|
local tab = string.Explode(",", value)
|
|
table.insert(self[key], {entityname=tab[1], input=tab[2], args=tab[3], delay=tab[4], reps=tab[5]})
|
|
end
|
|
|
|
function meta:FindByNameHammer(name, activator, caller)
|
|
if name == "!self" then return {self} end
|
|
if name == "!activator" then return {activator} end
|
|
if name == "!caller" then return {caller} end
|
|
return ents.FindByName(name)
|
|
end
|
|
|
|
function meta:IsNailed()
|
|
if self:IsValid() and self.Nails then -- In case we're the world.
|
|
for _, nail in pairs(self.Nails) do
|
|
if nail and nail:IsValid() and (nail:GetAttachEntity() == self or nail:GetBaseEntity() == self) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function meta:IsNailedToWorld(hierarchy)
|
|
if self:IsNailed() then
|
|
for _, nail in pairs(self.Nails) do
|
|
if nail:GetAttachEntity():IsWorld() then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
if hierarchy then
|
|
for _, ent in pairs(self:GetAllConstrainedEntities()) do
|
|
if ent ~= self and ent:IsValid() and ent:IsNailedToWorld() then return true end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function meta:IsNailedToWorldHierarchy()
|
|
return self:IsNailedToWorld(true)
|
|
end
|
|
|
|
function meta:GetNailFrozen()
|
|
return self.m_NailFrozen
|
|
end
|
|
meta.IsNailFrozen = meta.GetNailFrozen
|
|
|
|
function meta:SetNailFrozen(frozen)
|
|
if frozen then
|
|
local phys = self:GetPhysicsObject()
|
|
if phys:IsValid() and phys:IsMoveable() then
|
|
self.m_NailFrozen = true
|
|
phys:EnableMotion(false)
|
|
end
|
|
elseif self:IsNailFrozen() then
|
|
local phys = self:GetPhysicsObject()
|
|
if phys:IsValid() then
|
|
self.m_NailFrozen = false
|
|
phys:EnableMotion(true)
|
|
phys:Wake()
|
|
end
|
|
end
|
|
end
|
|
|
|
function constraint.GetAllConstrainedEntitiesOrdered(ent)
|
|
local allcons = constraint.GetAllConstrainedEntities(ent)
|
|
|
|
local tab = {}
|
|
|
|
if allcons then
|
|
for k, v in pairs(allcons) do
|
|
table.insert(tab, v)
|
|
end
|
|
end
|
|
|
|
return tab
|
|
end
|
|
|
|
function meta:GetAllConstrainedEntities()
|
|
local allcons = constraint.GetAllConstrainedEntitiesOrdered(self)
|
|
if not allcons or #allcons == 0 then
|
|
return {self}
|
|
end
|
|
|
|
return allcons
|
|
end
|
|
|
|
function meta:PackUp(pl)
|
|
if not self.CanPackUp then return end
|
|
|
|
local cur = pl:GetStatus("packup")
|
|
if cur and cur:IsValid() then return end
|
|
|
|
local status = pl:GiveStatus("packup")
|
|
if status:IsValid() then
|
|
status:SetPackUpEntity(self)
|
|
status:SetEndTime(CurTime() + (self.PackUpTime or 4))
|
|
|
|
if self.GetObjectOwner then
|
|
local owner = self:GetObjectOwner()
|
|
if owner:IsValid() and owner:Team() == TEAM_HUMAN and owner ~= pl and not gamemode.Call("PlayerIsAdmin", pl) then
|
|
status:SetNotOwner(true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function meta:GetPropsInContraption()
|
|
local allcons = constraint.GetAllConstrainedEntities(self)
|
|
if not allcons or #allcons == 0 then
|
|
return 1
|
|
end
|
|
|
|
return #allcons
|
|
end
|
|
|
|
function meta:HumanNearby()
|
|
for _, pl in pairs(team.GetPlayers(TEAM_HUMAN)) do
|
|
local plpos = pl:GetPos()
|
|
if pl:Alive() and self:NearestPoint(plpos):Distance(plpos) <= 512 then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
function meta:ResetLastBarricadeAttacker(attacker, dmginfo)
|
|
if attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD then
|
|
self.m_LastDamagedByZombie = CurTime()
|
|
|
|
if self:HumanNearby() then
|
|
local dmg = math.ceil(dmginfo:GetDamage())
|
|
attacker.BarricadeDamage = attacker.BarricadeDamage + dmg
|
|
if attacker.LifeBarricadeDamage ~= nil then
|
|
attacker:AddLifeBarricadeDamage(dmg)
|
|
end
|
|
if attacker:GetZombieClassTable().Name == "Crow" then
|
|
attacker.CrowBarricadeDamage = attacker.CrowBarricadeDamage + dmg
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
meta.OldSetPhysicsAttacker = meta.SetPhysicsAttacker
|
|
function meta:SetPhysicsAttacker(ent)
|
|
if self:GetClass() == "func_physbox" and ent:IsValid() then
|
|
self.PBAttacker = ent
|
|
self.NPBAttacker = CurTime() + 1
|
|
end
|
|
self:OldSetPhysicsAttacker(ent)
|
|
end
|
|
|
|
local function randomsort(a, b)
|
|
return a.rand < b.rand
|
|
end
|
|
local function randomize(t)
|
|
for k, v in pairs(t) do v.rand = math.Rand(0, 1) end
|
|
table.sort(t, randomsort)
|
|
end
|
|
|
|
-- Return true to override default behavior.
|
|
function meta:DamageNails(attacker, inflictor, damage, dmginfo)
|
|
if not self:IsNailed() or self.m_NailsDontAbsorb then return end
|
|
|
|
if self:GetBarricadeHealth() <= 0 then return end
|
|
|
|
if not gamemode.Call("CanDamageNail", self, attacker, inflictor, damage, dmginfo) then
|
|
if dmginfo then
|
|
dmginfo:SetDamage(0)
|
|
dmginfo:SetDamageType(DMG_BULLET)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
if damage < 0 then
|
|
if dmginfo then
|
|
dmginfo:SetDamage(0)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
self:ResetLastBarricadeAttacker(attacker, dmginfo)
|
|
|
|
local nails = self:GetLivingNails()
|
|
if #nails <= 0 then return end
|
|
|
|
self:SetBarricadeHealth(self:GetBarricadeHealth() - damage)
|
|
for i, nail in ipairs(nails) do
|
|
nail:OnDamaged(damage, attacker, inflictor, dmginfo)
|
|
end
|
|
|
|
if attacker:IsPlayer() then
|
|
GAMEMODE:DamageFloater(attacker, self, dmginfo)
|
|
end
|
|
|
|
if dmginfo then dmginfo:SetDamage(0) end
|
|
|
|
if self:GetBarricadeHealth() <= 0 then
|
|
if self:GetModel() ~= "" and self:GetModel() ~= "models/error.mdl" then
|
|
if self:GetName() == "" and self:GetVolume() < 100 then
|
|
self:Fire("break", "", 0.01)
|
|
self:Fire("kill", "", 0.05)
|
|
else
|
|
local ent = ents.Create("env_propbroken")
|
|
if ent:IsValid() then
|
|
ent:Spawn()
|
|
ent:AttachTo(self)
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, nail in pairs(nails) do
|
|
self:RemoveNail(nail)
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function meta:GetNails()
|
|
local tab = {}
|
|
|
|
if self.Nails then
|
|
for _, nail in pairs(self.Nails) do
|
|
if nail and nail:IsValid() then
|
|
table.insert(tab, nail)
|
|
end
|
|
end
|
|
end
|
|
|
|
return tab
|
|
end
|
|
|
|
function meta:GetLivingNails()
|
|
local tab = {}
|
|
|
|
if self.Nails then
|
|
for _, nail in pairs(self.Nails) do
|
|
if nail and nail:IsValid() and nail:GetNailHealth() > 0 then
|
|
table.insert(tab, nail)
|
|
end
|
|
end
|
|
end
|
|
|
|
return tab
|
|
end
|
|
|
|
function meta:GetFirstNail()
|
|
if self.Nails then
|
|
for i, nail in ipairs(self.Nails) do
|
|
if nail and nail:IsValid() and not nail:GetAttachEntity():IsValid() then return nail end
|
|
end
|
|
for i, nail in ipairs(self.Nails) do
|
|
if nail and nail:IsValid() then return nail end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function GetNailOwner(nail, filter)
|
|
for _, ent in pairs(ents.GetAll()) do
|
|
if ent ~= filter and ent.Nails then
|
|
for __, n in pairs(ent.Nails) do
|
|
if n == nail then
|
|
return ent
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return game.GetWorld()
|
|
end
|
|
|
|
function meta:RemoveNail(nail, dontremoveentity, removedby)
|
|
if not self:IsNailed() then return end
|
|
|
|
if not nail then
|
|
nail = self:GetFirstNail()
|
|
end
|
|
|
|
if not nail or not nail:IsValid() then return end
|
|
|
|
local cons = nail:GetNailConstraint()
|
|
local othernails = 0
|
|
for _, othernail in pairs(ents.FindByClass("prop_nail")) do
|
|
if othernail ~= nail and othernail:GetNailConstraint():IsValid() and othernail:GetNailConstraint() == cons then
|
|
othernails = othernails + 1
|
|
end
|
|
end
|
|
|
|
-- Only remove the constraint if it's the last nail.
|
|
if othernails == 0 and cons:IsValid() then
|
|
cons:Remove()
|
|
end
|
|
|
|
local ent2 = GetNailOwner(nail, self)
|
|
|
|
for i, n in ipairs(self.Nails) do
|
|
if n == nail then
|
|
table.remove(self.Nails, i)
|
|
break
|
|
end
|
|
end
|
|
|
|
if ent2 and ent2.Nails then
|
|
for i, n in ipairs(ent2.Nails) do
|
|
if n == nail then
|
|
table.remove(ent2.Nails, i)
|
|
ent2:TemporaryBarricadeObject()
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
self:TemporaryBarricadeObject()
|
|
|
|
gamemode.Call("OnNailRemoved", nail, self, ent2, removedby)
|
|
|
|
if not dontremoveentity then
|
|
nail:Remove()
|
|
nail.m_IsRemoving = true
|
|
end
|
|
|
|
return true
|
|
end
|