mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
393 lines
11 KiB
Lua
393 lines
11 KiB
Lua
--[[
|
|
| 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 |