--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] AddCSLuaFile() -- classic wiremod jank right here if WireLib then ENT.Base = "base_wire_entity" else ENT.Base = "base_entity" -- :( end ENT.Type = "anim" ENT.PrintName = "Burrowed Antlion" ENT.Author = "Silverlan + Straw W Wagen" ENT.Contact = "Silverlan@gmx.de" ENT.Category = "Other" ENT.Editable = true ENT.Spawnable = false ENT.AutomaticFrameAdvance = false -- modifiable vars ENT.DebugModel = "models/balloons/balloon_dog.mdl" ENT.DebugColor = Color( 0, 255, 0 ) ENT.AmbushDist = 384 --128 * 3 ENT.MyClass = "npc_antlion_burrowed" ENT.ModelToPrecache = "models/AntLion.mdl" ENT.AmbusherClass = "npc_antlion" ENT.HintSoundChance = 3 ENT.HintSounds = { "ambient/levels/coast/antlion_hill_ambient1.wav", "ambient/levels/coast/antlion_hill_ambient2.wav", "ambient/levels/coast/antlion_hill_ambient4.wav", } ENT.TeammateSleepers = { "npc_antlion_burrowed", "npc_antlionworker_burrowed", "npc_antlionguard_burrowed", "npc_caveguard_burrowed", } -- end modifiable vars function ENT:SetupDataTables() self:NetworkVar( "Int", 0, "AmbushDistance", { KeyName = "ambushdistance", Edit = { order = 1, type = "Int", min = 1, max = 1000 } } ) self:NetworkVar( "Int", 1, "TeammateWakeDist", { KeyName = "teammatewakedist", Edit = { order = 2, type = "Int", min = -1, max = 1000 } } ) self:NetworkVar( "Float", 0, "WakeDelay", { KeyName = "wakeDelay", Edit = { order = 3, type = "Float", min = 0, max = 30 } } ) self:NetworkVar( "Bool", 0, "WakeNearTeammates", { KeyName = "wakenearteammates", Edit = { order = 4, type = "Bool" } } ) self:NetworkVar( "Bool", 1, "CanChainWakeTeammates", { KeyName = "canchainwaketeammates", Edit = { order = 5, type = "Bool" } } ) self:NetworkVar( "Bool", 2, "IsSilent", { KeyName = "issilent", Edit = { order = 6, type = "Bool" } } ) self:NetworkVar( "Bool", 3, "ForceWake", { KeyName = "forcewake", Edit = { readonly = true } } ) self:SetAmbushDistance( self.AmbushDist ) self:SetTeammateWakeDist( self.AmbushDist ) self:SetWakeDelay( 0 ) self:SetWakeNearTeammates( false ) self:SetCanChainWakeTeammates( false ) self:SetIsSilent( false ) self:SetForceWake( false ) self:PostSetupData() end function ENT:PostSetupData() end if CLIENT then function ENT:Draw() if saveents_IsEditing() or not saveents_EnabledAi() then if not saveents_CanBeUgly() then return end self:DrawModel() end end end if not SERVER then return end function ENT:Initialize() self:SetModel( self.DebugModel ) self:SetColor( self.DebugColor ) self:DrawShadow( false ) if self.ModelToPrecache then util.PrecacheModel( self.ModelToPrecache ) end if saveents_EnabledAi() then self:CreateAmbusher() end if not WireLib then return end self.Inputs = Wire_CreateInputs( self, { "ForceWake" } ) self.Outputs = WireLib.CreateSpecialOutputs( self, { "Awake", "Sleeper" }, { "NORMAL", "ENTITY" } ) end function ENT:TriggerInput( iname, value ) if iname == "ForceWake" and value >= 1 then self:SetForceWake( true ) else self:SetForceWake( false ) end end -- if player is moving slower than this, we only wake up if they're REALLY close! local speedToWakeUp = 65^2 function ENT:Think() if self.ambushed then -- all done! if not IsValid( self.ambusher ) or self.ambusher:Health() <= 0 then SafeRemoveEntity( self ) -- it's still alive, think slow! else self:NextThink( CurTime() + 2 ) return true end end -- let them physgun us! if not saveents_EnabledAi() then if not IsValid( self:GetPhysicsObject() ) then self:SetMoveType( MOVETYPE_VPHYSICS ) self:PhysicsInit( SOLID_VPHYSICS ) self:SetCollisionGroup( COLLISION_GROUP_WORLD ) -- npcs can see through? self:GetPhysicsObject():EnableMotion( false ) self.hasPhysics = true SafeRemoveEntity( self.ambusher ) if self.hadNoPhysics then self:EmitSound( "physics/concrete/rock_impact_hard2.wav", 68, math.random( 90, 110 ) ) local frozen = EffectData() frozen:SetEntity( self ) util.Effect( "phys_freeze", frozen ) end end -- restart from scratch for this think call return elseif not self.hadNoPhysics then self.hadNoPhysics = true end -- no more physgunning, and more importantly, no more catching stray bullets! if self.hasPhysics and IsValid( self:GetPhysicsObject() ) then self:PhysicsInit( SOLID_NONE ) self.hasPhysics = nil if not IsValid( self.ambusher ) then self:CreateAmbusher() end if IsValid( self.ambusher ) then self.ambusher.DoNotDuplicate = true end -- ditto return end -- let people watch if self.ambushed then return end local doAmbush = nil local myPos = self:GetPos() local ambushDist = self:GetAmbushDistance() local wayTooCloseDist = math.min( ambushDist / 4, 50 ) ^ 2 local belowMeCutoff = myPos.z if IsValid( self.ambusher ) then belowMeCutoff = self.ambusher:GetPos().z end belowMeCutoff = belowMeCutoff + -15 --debugoverlay.Sphere( myPos, ambushDist, 5, color_white, true ) -- search for plys very close to ent, very often if not saveents_IgnoringPlayers() then for _, thing in pairs( ents.FindInSphere( myPos, ambushDist ) ) do if thing and IsValid( thing ) and thing:IsPlayer() then local thingPos = thing:GetPos() -- if ents are below me dont wake up! local thingIsOnSameLevelAsMe = thingPos.z > belowMeCutoff local thingIsMoving = thing:GetVelocity():LengthSqr() > speedToWakeUp local thingIsReallyClose = thingPos:DistToSqr( myPos ) < wayTooCloseDist if ( thingIsMoving or thingIsReallyClose ) and thingIsOnSameLevelAsMe then doAmbush = true break end end end end if self.forcedAmbush and self.forcedAmbush < CurTime() then doAmbush = true end if self:GetForceWake() then doAmbush = true end if doAmbush then self.ambushed = true if not IsValid( self.ambusher ) then return end if self.ambusher:GetMaxHealth() > 0 and self.ambusher:Health() <= 0 then return end self:AwakenTeammates() self.ambusher.DoNotDuplicate = true local delay = self:GetWakeDelay() if self.instantWake then delay = 0 end timer.Simple( delay, function() if not IsValid( self ) then return end if not IsValid( self.ambusher ) then return end self:Ambush() self:PostAmbushed( self.ambusher ) if not WireLib then return end Wire_TriggerOutput( self, "Awake", 1 ) end ) return elseif not self.ambushed and IsValid( self.ambusher ) then -- strong :think() frequency optimisations below local farEnoughToSleepDist = ambushDist * 8 farEnoughToSleepDist = math.Clamp( farEnoughToSleepDist, 0, 2500 ) local tooClosePlayer = self.tooClosePlayer -- look for a player anywhere near us, if no player then we hibernate with long thinks, if player then we think fast if not IsValid( tooClosePlayer ) then for _, ply in ipairs( ents.FindByClass( "player" ) ) do local closeEnough = ply:GetPos():DistToSqr( myPos ) < farEnoughToSleepDist^2 if closeEnough then tooClosePlayer = ply self.tooClosePlayer = tooClosePlayer break end end end local timeToCheck -- we either found someone close, or have someone close if IsValid( tooClosePlayer ) and tooClosePlayer:GetPos():DistToSqr( myPos ) < farEnoughToSleepDist^2 and self.ambusher:Health() > 0 then timeToCheck = self:GetAmbushDistance() / 300 if math.random( 0, 100 ) < self.HintSoundChance then self:DoHintSound() end else if IsValid( tooClosePlayer ) then -- they just left the area! there could be another person nearby, think fast! timeToCheck = self:GetAmbushDistance() / 300 else -- they either disconnected, or we didn't have anyone in the first place, think slow timeToCheck = self:GetAmbushDistance() / 40 end self.tooClosePlayer = nil end self:NextThink( CurTime() + timeToCheck + math.Rand( -timeToCheck / 10, timeToCheck / 10 ) ) return true end end function ENT:OnRemove() SafeRemoveEntity( self.ambusher ) end function ENT:AwakenTeammates() if self:GetWakeNearTeammates() ~= true then return end if self.wakenByTeammate and self:GetCanChainWakeTeammates() ~= true then return end local stuff = {} for _, classToWake in ipairs( self.TeammateSleepers ) do local found = ents.FindByClass( classToWake ) table.Add( stuff, found ) end local cutoff = self:GetTeammateWakeDist() ^ 2 local myPos = self:GetPos() for _, toAwaken in ipairs( stuff ) do if toAwaken == self then continue end if toAwaken.ambushed then continue end if myPos:DistToSqr( toAwaken:GetPos() ) > cutoff then continue end local offset = math.Rand( 0.5, 2 ) toAwaken.forcedAmbush = CurTime() + offset toAwaken.wakenByTeammate = true -- this stops endless chains! end end function ENT:CreateAmbusher() self.ambusher = self:InitializeAmbusher() if not IsValid( self.ambusher ) then return end self.ambusher.DoNotDuplicate = true timer.Simple( 0, function() if not IsValid( self ) then return end if not IsValid( self.ambusher ) then return end self:PostInitialized( self.ambusher ) end ) if not WireLib then return end Wire_TriggerOutput( self, "Sleeper", self.ambusher ) end -- modifiable funcs below function ENT:InitializeAmbusher() local ambusher = ents.Create( self.AmbusherClass ) ambusher:SetPos( self:GetPos() ) ambusher:SetAngles( self:GetAngles() ) ambusher:SetKeyValue( "spawnflags", "516" ) ambusher:SetKeyValue( "startburrowed", "1" ) ambusher:Spawn() ambusher:Activate() return ambusher end function ENT:Ambush() self.ambusher:Fire( "unburrow", "", 0 ) end function ENT:PostInitialized() end function ENT:PostAmbushed() end function ENT:DoHintSound() if self:GetIsSilent() then return end local sounds = self.HintSounds local theSound = sounds[ math.random( 1, #sounds ) ] local randOffset = VectorRand() randOffset.z = 0 randOffset:Normalize() randOffset = randOffset * 50 sound.Play( theSound, self:GetPos() + randOffset, 75, math.random( 90, 110 ), 1 ) end