mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
866 lines
37 KiB
Lua
866 lines
37 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("shared.lua")
|
|
include("shared.lua")
|
|
|
|
ENT.Model = "models/Combine_Strider.mdl"
|
|
ENT.VJ_NPC_Class = {"CLASS_COMBINE"}
|
|
ENT.StartHealth = 720
|
|
--ENT.CustomBlood_Particle = {"blood_impact_synth_01"}
|
|
ENT.BloodColor = "White"
|
|
ENT.VJ_IsHugeMonster = true
|
|
|
|
ENT.TurningSpeed = 8 -- How fast it can turn
|
|
ENT.SightAngle = 100
|
|
|
|
ENT.DeathCorpseApplyForce = false
|
|
|
|
ENT.PoseParameterLooking_Names = {yaw={"minigunYaw"}} -- Custom pose parameters to use, can put as many as needed
|
|
ENT.PoseParameterLooking_InvertYaw = true -- Inverts the pitch poseparameters (X)
|
|
|
|
ENT.InvestigateSoundDistance = 18
|
|
ENT.CallForHelpDistance = 10000 -- -- How far away the SNPC's call for help goes | Counted in World Units
|
|
|
|
ENT.NoChaseAfterCertainRange = true -- Should the SNPC not be able to chase when it's between number x and y?
|
|
ENT.NoChaseAfterCertainRange_FarDistance = 1000 -- How far until it can chase again? | "UseRangeDistance" = Use the number provided by the range attack instead
|
|
ENT.NoChaseAfterCertainRange_CloseDistance = 0 -- How near until it can chase again? | "UseRangeDistance" = Use the number provided by the range attack instead
|
|
|
|
ENT.HasMeleeAttack = false
|
|
ENT.MeleeAttackDamage = 200
|
|
ENT.MeleeAttackDistance = 100 -- How close does it have to be until it attacks?
|
|
ENT.MeleeAttackDamageDistance = 115 -- How far does the damage go?
|
|
|
|
ENT.MovementType = VJ_MOVETYPE_AERIAL
|
|
ENT.AA_MinWanderDist = 500 -- Minimum distance that the NPC should go to when wandering
|
|
ENT.Aerial_FlyingSpeed_Calm = 160 -- The speed it should fly with, when it's wandering, moving slowly, etc. | Basically walking compared to ground SNPCs
|
|
ENT.Aerial_FlyingSpeed_Alerted = 210 -- The speed it should fly with, when it's chasing an enemy, moving away quickly, etc. | Basically running compared to ground SNPCs
|
|
ENT.Aerial_AnimTbl_Calm = {ACT_WALK} -- Animations it plays when it's wandering around while idle
|
|
ENT.Aerial_AnimTbl_Alerted = {ACT_RUN} -- Animations it plays when it's moving while alerted
|
|
ENT.AA_GroundLimit = 450 -- If the NPC's distance from itself to the ground is less than this, it will attempt to move up
|
|
ENT.AA_MoveAccelerate = 2 -- The NPC will gradually speed up to the max movement speed as it moves towards its destination | Calculation = FrameTime * x
|
|
ENT.AA_MoveDecelerate = 2 -- The NPC will slow down as it approaches its destination | Calculation = MaxSpeed / x
|
|
|
|
ENT.SoundTbl_CombatIdle = {
|
|
"npc/strider/dvs_stridervoc_00_35_18.wav",
|
|
"npc/strider/strider_hunt1.wav",
|
|
"npc/strider/strider_hunt2.wav",
|
|
"npc/strider/strider_hunt3.wav",
|
|
}
|
|
ENT.SoundTbl_Alert = {
|
|
"npc/strider/strider_distant1.wav",
|
|
"npc/strider/strider_distant2.wav",
|
|
"npc/strider/strider_distant3.wav",
|
|
}
|
|
ENT.SoundTbl_Pain = {
|
|
"npc/strider/striderx_alert2.wav",
|
|
"npc/strider/striderx_alert4.wav",
|
|
"npc/strider/striderx_alert5.wav",
|
|
"npc/strider/striderx_alert6.wav",
|
|
"npc/strider/striderx_pain2.wav",
|
|
"npc/strider/striderx_pain5.wav",
|
|
"npc/strider/striderx_pain7.wav",
|
|
"npc/strider/striderx_pain8.wav",
|
|
}
|
|
ENT.SoundTbl_Idle = {"npc/strider/strider_legstretch1.wav","npc/strider/strider_legstretch2.wav","npc/strider/strider_legstretch3.wav"}
|
|
ENT.SoundTbl_Death = {"npc/strider/striderx_die1.wav"}
|
|
|
|
ENT.IdleSoundLevel = 95
|
|
ENT.AlertSoundLevel = 115
|
|
ENT.PainSoundLevel = 105
|
|
ENT.DeathSoundLevel = 105
|
|
ENT.CombatIdleSoundLevel = 105
|
|
|
|
-- Shooting in general
|
|
ENT.Fire_TooCloseDist = 0
|
|
ENT.FireDistance = 6000
|
|
ENT.BulletSpread = 0.03
|
|
|
|
-- Regular shoot attack
|
|
ENT.ShootDamage = 12
|
|
ENT.GunAttackFireDelay = 0.15
|
|
|
|
-- Burst shoot attack
|
|
ENT.ConsiderBurstDelay = 4
|
|
ENT.DoBurstChance = 3
|
|
ENT.BurstFireDelay = 0.1
|
|
ENT.BurstFireBulletAmt = 6
|
|
ENT.BurstAccuracyMult = 2
|
|
|
|
-- Strider cannon:
|
|
ENT.ConsiderCannonDelay = 6
|
|
ENT.DoCannonChance = 2
|
|
ENT.StriderCannonDamage = 150
|
|
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:Controller_IntMsg(ply, controlEnt)
|
|
|
|
ply:ChatPrint("NOTE: Controlling is not really supported for this SNPC!!")
|
|
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:GiveBullseyes()
|
|
self.bullseye_front = ents.Create("obj_vj_Bullseye")
|
|
self.bullseye_behind = ents.Create("obj_vj_Bullseye")
|
|
self.bullseye_left = ents.Create("obj_vj_Bullseye")
|
|
self.bullseye_right = ents.Create("obj_vj_Bullseye")
|
|
self.bullseye_over = ents.Create("obj_vj_Bullseye")
|
|
self.bullseye_under = ents.Create("obj_vj_Bullseye")
|
|
|
|
local center = self:GetPos()+self:OBBCenter()
|
|
local bullseye_dist = 65
|
|
|
|
self.bullseyes = {
|
|
{
|
|
ent = self.bullseye_front,
|
|
pos = center+self:GetForward()*bullseye_dist,
|
|
},
|
|
{
|
|
ent = self.bullseye_behind,
|
|
pos = center-self:GetForward()*bullseye_dist,
|
|
},
|
|
{
|
|
ent = self.bullseye_left,
|
|
pos = center-self:GetRight()*bullseye_dist,
|
|
},
|
|
{
|
|
ent = self.bullseye_right,
|
|
pos = center+self:GetRight()*bullseye_dist,
|
|
},
|
|
{
|
|
ent = self.bullseye_over,
|
|
pos = center+self:GetUp()*bullseye_dist,
|
|
},
|
|
{
|
|
ent = self.bullseye_under,
|
|
pos = center-self:GetUp()*bullseye_dist,
|
|
},
|
|
}
|
|
|
|
for _,bullseye_data in pairs(self.bullseyes) do
|
|
local bullseye = bullseye_data.ent
|
|
bullseye:SetModel("models/hunter/blocks/cube05x05x05.mdl")
|
|
bullseye:SetPos(bullseye_data.pos)
|
|
bullseye:SetParent(self,12)
|
|
bullseye:SetAngles(self:GetAngles())
|
|
bullseye:Spawn()
|
|
bullseye:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
|
|
bullseye:AddEFlags(EFL_DONTBLOCKLOS)
|
|
bullseye:DrawShadow(true)
|
|
bullseye:SetNoDraw(true)
|
|
bullseye:SetSolid(SOLID_NONE)
|
|
bullseye.VJ_NPC_Class = self.VJ_NPC_Class
|
|
self:DeleteOnRemove(bullseye)
|
|
end
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
local strider_mins, strider_maxs = Vector(-65, -65, -65), Vector(65, 65, 65)
|
|
|
|
function ENT:CustomOnInitialize()
|
|
self:SetCollisionBounds(strider_mins, strider_maxs)
|
|
|
|
timer.Simple(0.03, function() if IsValid(self) then self:SetPos(self:GetPos()+Vector(0,0,self.AA_GroundLimit)) end end)
|
|
|
|
self.bulletprop1 = ents.Create("base_gmodentity")
|
|
self.bulletprop1:SetModel("models/hunter/blocks/cube025x025x025.mdl")
|
|
self.bulletprop1:SetPos(self:GetAttachment(10).Pos)
|
|
self.bulletprop1:SetParent(self,10)
|
|
self.bulletprop1:SetSolid(SOLID_NONE)
|
|
self.bulletprop1:AddEFlags(EFL_DONTBLOCKLOS)
|
|
self.bulletprop1:SetNoDraw(true)
|
|
self.bulletprop1:Spawn()
|
|
|
|
self.NextStriderFootStepSound = CurTime()
|
|
|
|
self.Strider_Current_Height_Mode = "high"
|
|
|
|
self.CurrentHeightPoseParam = 500
|
|
|
|
self:GiveBullseyes()
|
|
|
|
self.NextRegularShoot = CurTime()
|
|
self.NextConsiderBurst = CurTime()
|
|
self.NextConsiderCannon = CurTime()
|
|
self.BurstRepsLeft = 0
|
|
|
|
self.NextTogggleRandomCrouch = CurTime()
|
|
self.RandomCrouch = math.random(1, 2) == 1
|
|
|
|
self.BurstAttackShootTimerName = "VJ_Z_StriderBurstShoot" .. self:EntIndex()
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
ENT.CurAimPitch = 0
|
|
|
|
function ENT:CustomOn_PoseParameterLookingCode(pitch, yaw, roll)
|
|
local ideal_pitch = pitch
|
|
|
|
if IsValid(self:GetEnemy()) then
|
|
ideal_pitch = pitch-45
|
|
end
|
|
|
|
self.CurAimPitch = Lerp(0.8, self.CurAimPitch, ideal_pitch)
|
|
self:SetPoseParameter("minigunPitch",self.CurAimPitch)
|
|
end
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:Strider_ChangeHeight(mode)
|
|
if self.Strider_Adjusting_Hight then return end
|
|
|
|
local adjust_time = nil
|
|
local new_strider_height = nil
|
|
local new_strider_poseparam = nil
|
|
|
|
if mode == "high" && self.Strider_Current_Height_Mode != "high" then
|
|
|
|
adjust_time = 4
|
|
new_strider_poseparam = 500
|
|
|
|
self:VJ_ACT_PLAYACTIVITY("stand",true,adjust_time,true)
|
|
self.Strider_Current_Height_Mode = "high"
|
|
|
|
elseif mode == "low" && self.Strider_Current_Height_Mode != "low" then
|
|
|
|
adjust_time = 4
|
|
new_strider_poseparam = 0
|
|
|
|
self:VJ_ACT_PLAYACTIVITY("crouch",true,adjust_time,true)
|
|
self.Strider_Current_Height_Mode = "low"
|
|
|
|
end
|
|
|
|
if new_strider_poseparam then
|
|
|
|
self.Strider_Adjusting_Hight = true
|
|
timer.Simple(1, function() if IsValid(self) then self.CurrentHeightPoseParam = new_strider_poseparam end end)
|
|
timer.Simple(adjust_time, function() if IsValid(self) then self.Strider_Adjusting_Hight = false end end)
|
|
|
|
end
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:CustomOnThink()
|
|
local mypos = self:GetPos()
|
|
|
|
-- Decide if we should move or not:
|
|
local should_move = GetConVar("ai_disabled"):GetInt() == 0 && self.Strider_Current_Height_Mode != "low" && !self.PathObstructed && !self.InMidAir && !self.EnemyInNoChaseDist && !self.EnemyPosUnreachable && !self.IsBeingDroppedByDropship
|
|
|
|
if self.MovementType == VJ_MOVETYPE_AERIAL && !should_move then
|
|
self:AA_StopMoving()
|
|
self.MovementType = VJ_MOVETYPE_STATIONARY
|
|
--print("stopping...")
|
|
elseif self.MovementType == VJ_MOVETYPE_STATIONARY && should_move then
|
|
self.MovementType = VJ_MOVETYPE_AERIAL
|
|
--print("moving!")
|
|
end
|
|
|
|
-- Update collision bounds to match head position if it's crouching:
|
|
if self.Strider_Current_Height_Mode == "low" or self.Strider_Adjusting_Hight then
|
|
self:SetCollisionBoundsWS(strider_mins+self:GetBonePosition(0), strider_maxs+self:GetBonePosition(0))
|
|
self.DefaultCollisionBoundsSet = false
|
|
elseif !self.DefaultCollisionBoundsSet then
|
|
self:SetCollisionBounds(strider_mins, strider_maxs)
|
|
self.DefaultCollisionBoundsSet = true
|
|
end
|
|
|
|
-- Update bullseye's npc classes:
|
|
for _,bullseye in pairs(self.bullseyes) do
|
|
bullseye.VJ_NPC_Class = self.VJ_NPC_Class
|
|
end
|
|
|
|
-- Update pose parameters:
|
|
self:SetPoseParameter("body_height",self.CurrentHeightPoseParam)
|
|
|
|
-- If stationary, then ease in velocity.
|
|
if self.MovementType == VJ_MOVETYPE_STATIONARY then
|
|
local ease_in_vel = -self:GetVelocity()
|
|
self:SetLocalVelocity( Vector(ease_in_vel.x,ease_in_vel.y,0) )
|
|
end
|
|
|
|
-- Cannon charge effect:
|
|
if self.StriderCannonCharging then
|
|
local effectdata = EffectData()
|
|
effectdata:SetStart(self:GetAttachment(9).Pos)
|
|
util.Effect("cguard_suck", effectdata)
|
|
end
|
|
|
|
-- Footstep sounds:
|
|
local seq = self:GetSequenceName(self:GetSequence())
|
|
if self.NextStriderFootStepSound < CurTime() && (seq == "walk_all" or seq == "fastwalk_all") then
|
|
self:EmitSound("^npc/strider/strider_step"..math.random(1, 6)..".wav", 110, math.random(65, 75))
|
|
|
|
if seq == "walk_all" then
|
|
self.NextStriderFootStepSound = CurTime()+0.9
|
|
elseif seq == "fastwalk_all" then
|
|
self.NextStriderFootStepSound = CurTime()+0.6
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Simulate gravity based on body height, and make sure height is correct:
|
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
local tr = util.TraceLine({
|
|
start = mypos,
|
|
endpos = mypos - Vector(0,0,10000),
|
|
mask = MASK_NPCWORLDSTATIC,
|
|
})
|
|
local ground_dist = math.abs(mypos.z - tr.HitPos.z)
|
|
|
|
-- local testprop = ents.Create("prop_dynamic")
|
|
-- testprop:SetModel("models/props_c17/FurnitureChair001a.mdl")
|
|
-- testprop:SetPos(mypos)
|
|
|
|
local fallspeed = 50
|
|
local repel_ground_speed = 50
|
|
local extra_height_tolerance = 25
|
|
local vec = Vector(0,0,0)
|
|
|
|
self.InMidAir = false
|
|
if ground_dist > self.AA_GroundLimit+100 then
|
|
self.InMidAir = true
|
|
end
|
|
|
|
if ground_dist > self.AA_GroundLimit+extra_height_tolerance then
|
|
vec = Vector(0,0,-fallspeed)
|
|
elseif ground_dist < self.AA_GroundLimit-extra_height_tolerance then
|
|
vec = Vector(0,0,repel_ground_speed)
|
|
--print("going up")
|
|
else
|
|
-- If its head is at an acceptable height, then ease in velocity.
|
|
vec = Vector(0,0,-self:GetVelocity().z*0.5)
|
|
--print("good height")
|
|
end
|
|
|
|
if self.MovementType == VJ_MOVETYPE_AERIAL then
|
|
self:SetVelocity(vec)
|
|
elseif self.MovementType == VJ_MOVETYPE_STATIONARY then
|
|
self:SetLocalVelocity(vec*10)
|
|
end
|
|
|
|
end
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:CustomOnThink_AIEnabled()
|
|
if self.IsBeingDroppedByDropship then return end
|
|
|
|
local enemy = self:GetEnemy()
|
|
local enemyexists = IsValid(enemy)
|
|
|
|
local enemydist = nil
|
|
if enemyexists then
|
|
enemydist = enemy:GetPos():Distance(self:GetPos())
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Custom enemy visibility check:
|
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
local enemy_visible = false
|
|
if enemyexists then
|
|
enemy_visible = self:Visible(enemy)
|
|
|
|
if !enemy_visible then
|
|
-- Double check enemy visibility with a traceline
|
|
local tr_visibility = util.TraceLine({
|
|
start = self:GetAttachment(10).Pos, -- Muzzle
|
|
endpos = enemy:GetPos()+enemy:OBBCenter(),
|
|
filter = {self,enemy},
|
|
mask = MASK_BLOCKLOS,
|
|
})
|
|
enemy_visible = !tr_visibility.Hit
|
|
|
|
if enemy_visible then
|
|
-- This sadly doesn't seem to work.
|
|
self:VJ_DoSetEnemy(enemy, false, true)
|
|
end
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Crouch:
|
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
local enemy_under_self = false
|
|
local tr_crouch1
|
|
local tr_crouch2
|
|
|
|
|
|
if self.NextTogggleRandomCrouch < CurTime() then
|
|
self.RandomCrouch = !self.RandomCrouch
|
|
self.NextTogggleRandomCrouch = CurTime() + math.Rand(8, 15)
|
|
print(self.RandomCrouch)
|
|
end
|
|
|
|
|
|
if enemyexists then
|
|
enemy_under_self = enemy:GetPos().z - self:GetPos().z < 0
|
|
|
|
-- Check if the strider can't reach its enemy in a standing position:
|
|
tr_crouch1 = util.TraceLine({
|
|
start = self:GetPos(), -- Normal head position, when it's not crouching.
|
|
endpos = enemy:GetPos()+enemy:OBBCenter(),
|
|
filter = {self,enemy},
|
|
mask = MASK_NPCWORLDSTATIC,
|
|
})
|
|
|
|
-- Check if the strider can reach the enemy when crouched:
|
|
tr_crouch2 = util.TraceLine({
|
|
start = self:GetPos()-Vector(0,0,310),
|
|
endpos = enemy:GetPos()+enemy:OBBCenter(),
|
|
filter = {self,enemy},
|
|
mask = MASK_SHOT,
|
|
})
|
|
end
|
|
|
|
local should_crouch = enemyexists && !tr_crouch2.Hit && ((tr_crouch1.Hit && enemy_under_self) or self.RandomCrouch) && enemydist < self.FireDistance
|
|
|
|
local new_strider_height = nil
|
|
if self.Strider_Current_Height_Mode == "high" then
|
|
-- Consider crouching:
|
|
if should_crouch then
|
|
new_strider_height = "low"
|
|
|
|
if !self.NextHeightModeSwitch then
|
|
self.NextHeightModeSwitch = CurTime()+2.5
|
|
end
|
|
|
|
-- If the enemy has been occluded for a certain time, crouch:
|
|
if self.NextHeightModeSwitch && self.NextHeightModeSwitch < CurTime() then
|
|
self:Strider_ChangeHeight(new_strider_height)
|
|
self.NextHeightModeSwitch = nil
|
|
--print("crouching!")
|
|
end
|
|
elseif !should_crouch then
|
|
-- Reset Timer:
|
|
self.NextHeightModeSwitch = nil
|
|
end
|
|
elseif self.Strider_Current_Height_Mode == "low" then
|
|
-- Consider uncrouching:
|
|
if !should_crouch then
|
|
new_strider_height = "high"
|
|
|
|
if !self.NextHeightModeSwitch then
|
|
self.NextHeightModeSwitch = CurTime()+6
|
|
end
|
|
|
|
-- If the enemy has been visible for a certain time, stand up again:
|
|
if self.NextHeightModeSwitch && self.NextHeightModeSwitch < CurTime() then
|
|
self:Strider_ChangeHeight(new_strider_height)
|
|
self.NextHeightModeSwitch = nil
|
|
--print("standing!")
|
|
end
|
|
elseif should_crouch then
|
|
-- Reset Timer:
|
|
self.NextHeightModeSwitch = nil
|
|
end
|
|
end
|
|
|
|
-- if self.NextHeightModeSwitch then
|
|
-- print("time until switch to " .. new_strider_height .. ": " .. self.NextHeightModeSwitch-CurTime())
|
|
-- end
|
|
|
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Movement stuff:
|
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
local tr_path_obstructed = util.TraceHull({
|
|
start = self:GetPos(),
|
|
endpos = self:GetPos()+self:GetForward()*250,
|
|
mins = strider_mins*0.33 - Vector(0,0,175),
|
|
maxs = strider_maxs*0.33,
|
|
mask = MASK_NPCWORLDSTATIC,
|
|
})
|
|
|
|
if tr_path_obstructed.Hit then
|
|
self.PathObstructed = true
|
|
else
|
|
self.PathObstructed = false
|
|
end
|
|
|
|
if enemyexists && enemy_visible && enemydist < self.NoChaseAfterCertainRange_FarDistance && enemydist > self.NoChaseAfterCertainRange_CloseDistance then
|
|
--print("no chase dist")
|
|
self.EnemyInNoChaseDist = true
|
|
else
|
|
self.EnemyInNoChaseDist = false
|
|
end
|
|
|
|
-- Stop moving if enemy is too far up or down from us.
|
|
if enemyexists then
|
|
local move_ang_pitch = self:WorldToLocalAngles( (enemy:GetPos()+enemy:OBBCenter()-self:GetPos()):Angle() ).x
|
|
|
|
if enemyexists && math.abs(move_ang_pitch) > 30 && enemydist > self.NoChaseAfterCertainRange_CloseDistance then
|
|
self.EnemyPosUnreachable = true
|
|
else
|
|
self.EnemyPosUnreachable = false
|
|
end
|
|
else
|
|
self.EnemyPosUnreachable = false
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Attacks:
|
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
if enemyexists then
|
|
if enemy:GetPos():Distance(self:GetAttachment(1).Pos) < self.MeleeAttackDistance then
|
|
self:Strider_MeleeAttack()
|
|
end
|
|
|
|
if enemy_visible then
|
|
self.ShootPos = enemy:GetPos()+enemy:OBBCenter()
|
|
end
|
|
|
|
-- Consider doing strider cannon attack every now and then:
|
|
if self.NextConsiderCannon < CurTime() then
|
|
if !self.ShouldDoStriderCannon && math.random(1, self.DoCannonChance) == 1 then
|
|
self.ShouldDoStriderCannon = true
|
|
end
|
|
|
|
self.NextConsiderCannon = CurTime()+self.ConsiderCannonDelay
|
|
end
|
|
|
|
-- Consider doing burst attack every now and then:
|
|
if self.NextConsiderBurst < CurTime() then
|
|
if self.BurstRepsLeft < 1 && math.random(1, self.DoBurstChance) == 1 then
|
|
self.BurstRepsLeft = 3
|
|
end
|
|
|
|
self.NextConsiderBurst = CurTime()+self.ConsiderBurstDelay
|
|
end
|
|
|
|
-- If we are close to a position to shoot at, do any available range attack.
|
|
if self.ShootPos then
|
|
local dist = self.ShootPos:Distance(self:GetPos())
|
|
|
|
if dist > self.Fire_TooCloseDist && dist < self.FireDistance then
|
|
if self.ShouldDoStriderCannon then
|
|
-- If we should do the strider cannon attack, do it first.
|
|
self:StriderCannon()
|
|
elseif self.BurstRepsLeft > 0 then
|
|
-- If we have burst attacks to do, do them before regular gun attack.
|
|
self:GunBurstAttack()
|
|
else
|
|
-- Otherwise use regular gun attack.
|
|
self:GunAttack()
|
|
end
|
|
end
|
|
end
|
|
else
|
|
self:StopBurstAttack()
|
|
self.BurstRepsLeft = 0
|
|
self.ShootPos = nil
|
|
end
|
|
end
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:Strider_MeleeAttack()
|
|
if self.Strider_MeleeAttacking then return end
|
|
self.Strider_MeleeAttacking = true
|
|
|
|
local anim = "bighitl"
|
|
local time_total = 2.5
|
|
local time_until_damage = 1.5
|
|
|
|
self:VJ_ACT_PLAYACTIVITY(anim,true,time_total,true)
|
|
|
|
timer.Simple(time_until_damage, function() if IsValid(self) then
|
|
local hitents = util.VJ_SphereDamage(self,self, self:GetAttachment(1).Pos + Vector(0,0,35) ,self.MeleeAttackDamageDistance,self.MeleeAttackDamage,bit.bor(DMG_CRUSH, DMG_SLASH),true,false,false,false)
|
|
|
|
for _,ent in pairs(hitents) do
|
|
if ent:IsSolid() then
|
|
self:EmitSound("npc/strider/strider_skewer1.wav",95,math.random(85, 100))
|
|
break
|
|
end
|
|
end
|
|
end end)
|
|
|
|
timer.Simple(time_total, function() if IsValid(self) then
|
|
self.Strider_MeleeAttacking = false
|
|
end end)
|
|
end
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:Shoot(damage,soundpitch,accuracy_mult,tracer)
|
|
local source = self:GetAttachment(10).Pos
|
|
local pos = self.ShootPos or self:GetPos()+self:OBBCenter()+self:GetForward()*100
|
|
local shootdir = pos - self.bulletprop1:GetPos()
|
|
|
|
accuracy_mult = accuracy_mult or 1
|
|
tracer = tracer or "AirboatGunHeavyTracer"
|
|
|
|
ParticleEffectAttach("vj_rifle_full_blue",PATTACH_POINT_FOLLOW,self,10)
|
|
local expLight = ents.Create("light_dynamic")
|
|
expLight:SetKeyValue("brightness", "5")
|
|
expLight:SetKeyValue("distance", "250")
|
|
expLight:Fire("Color", "0 75 255")
|
|
expLight:SetPos(source)
|
|
expLight:Spawn()
|
|
expLight:SetParent(self,10)
|
|
expLight:Fire("TurnOn", "", 0)
|
|
timer.Simple(0.1,function() if IsValid(expLight) then expLight:Remove() end end)
|
|
self:DeleteOnRemove(expLight)
|
|
|
|
self.bulletprop1:FireBullets({
|
|
Src = self.bulletprop1:GetPos(),
|
|
Dir = shootdir:GetNormalized(),
|
|
Damage = damage*0.5,
|
|
Force = 25,
|
|
TracerName = tracer,
|
|
Spread = Vector( self.BulletSpread/accuracy_mult,self.BulletSpread/accuracy_mult,self.BulletSpread/accuracy_mult ),
|
|
Num = 1,
|
|
Tracer = 1,
|
|
Attacker = self,
|
|
Inflictor = self,
|
|
Filter = self,
|
|
Callback = function(attacker, tracer)
|
|
if math.random(1, 2) == 1 then
|
|
local effectdata = EffectData()
|
|
effectdata:SetOrigin(tracer.HitPos)
|
|
effectdata:SetNormal(tracer.HitNormal)
|
|
effectdata:SetRadius( 10 )
|
|
util.Effect( "cball_bounce", effectdata )
|
|
end
|
|
effects.BeamRingPoint( tracer.HitPos, 0.2, 0, 70, 12, 12, Color(255,255,175) )
|
|
util.VJ_SphereDamage(self,self,tracer.HitPos,20,damage*0.5,DMG_SONIC,true,false,false,false)
|
|
end,
|
|
})
|
|
|
|
local randpitch = math.random(soundpitch-10, soundpitch+10)
|
|
self:EmitSound("^npc/strider/strider_minigun.wav",140,randpitch,1,CHAN_STATIC)
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:GunAttack()
|
|
if self.NextRegularShoot > CurTime() then return end
|
|
self.NextRegularShoot = CurTime() + self.GunAttackFireDelay
|
|
self:Shoot(self.ShootDamage,80)
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:StopBurstAttack()
|
|
timer.Remove(self.BurstAttackShootTimerName)
|
|
self.BurstAttacking = false
|
|
self.BurstRepsLeft = self.BurstRepsLeft - 1
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:GunBurstAttack()
|
|
if self.BurstAttacking then return end
|
|
self.BurstAttacking = true
|
|
|
|
local time_until_burst = 0.75
|
|
local burst_cool_off_time = 0.33
|
|
|
|
self:EmitSound("npc/attack_helicopter/aheli_charge_up.wav", 100, math.random(120, 140))
|
|
self:EmitSound("weapons/cguard/charging.wav", 100, math.random(90, 100) , 0.33)
|
|
|
|
timer.Simple(time_until_burst, function() if IsValid(self) then
|
|
|
|
timer.Create(self.BurstAttackShootTimerName, self.BurstFireDelay, self.BurstFireBulletAmt, function()
|
|
self:Shoot(self.ShootDamage,110,self.BurstAccuracyMult,"AirboatGunTracer")
|
|
|
|
if timer.RepsLeft(self.BurstAttackShootTimerName) == 0 then
|
|
timer.Simple(burst_cool_off_time, function() if IsValid(self) then
|
|
self:StopBurstAttack()
|
|
end end)
|
|
end
|
|
end)
|
|
|
|
end end)
|
|
end
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:StriderCannon()
|
|
if self.DoingStriderCannon then return end
|
|
self.DoingStriderCannon = true
|
|
|
|
local pos = self.ShootPos
|
|
local time_until_fire = 1.1
|
|
|
|
-- Charge cannon
|
|
self.StriderCannonCharging = true
|
|
self:SetNWBool("StriderChargingCannon", true)
|
|
|
|
self:SetNWVector("StriderCannonBeamPos",pos)
|
|
|
|
self:EmitSound("npc/strider/charging.wav",120,math.random(80, 90),0.75)
|
|
self:EmitSound("npc/attack_helicopter/aheli_charge_up.wav", 120, math.random(110, 120))
|
|
self:EmitSound("weapons/cguard/charging.wav", 120, math.random(70, 80) , 0.33)
|
|
|
|
local effectdata = EffectData()
|
|
effectdata:SetStart(pos)
|
|
effectdata:SetMagnitude(time_until_fire)
|
|
effectdata:SetEntity(self)
|
|
effectdata:SetAttachment(9)
|
|
effectdata:SetScale(0.66)
|
|
util.Effect("cguard_warp", effectdata)
|
|
|
|
self.BlackHoleLight = ents.Create("light_dynamic")
|
|
self.BlackHoleLight:SetKeyValue("brightness", "3")
|
|
self.BlackHoleLight:SetKeyValue("distance", "250")
|
|
self.BlackHoleLight:Fire("Color", "0 75 255")
|
|
self.BlackHoleLight:SetPos(self:GetAttachment(9).Pos)
|
|
self.BlackHoleLight:SetParent(self,9)
|
|
self.BlackHoleLight:Spawn()
|
|
self.BlackHoleLight:Fire("TurnOn", "", 0)
|
|
self:DeleteOnRemove(self.BlackHoleLight)
|
|
|
|
-- Fire cannon
|
|
timer.Simple(time_until_fire, function() if IsValid(self) then
|
|
self.StriderCannonCharging = false
|
|
self:SetNWBool("StriderChargingCannon", false)
|
|
|
|
self.BlackHoleLight:Remove()
|
|
|
|
if self.ShootPos then
|
|
local tr = util.TraceLine({
|
|
start = self:GetAttachment(9).Pos,
|
|
endpos = self:GetAttachment(9).Pos + (pos-self:GetAttachment(9).Pos):GetNormalized()*10000,
|
|
mask = MASK_SHOT,
|
|
filter = self,
|
|
})
|
|
|
|
local expLight2 = ents.Create("light_dynamic")
|
|
expLight2:SetKeyValue("brightness", "9")
|
|
expLight2:SetKeyValue("distance", "600")
|
|
expLight2:Fire("Color", "0 75 255")
|
|
expLight2:SetPos(tr.HitPos)
|
|
expLight2:Spawn()
|
|
expLight2:Fire("TurnOn", "", 0)
|
|
timer.Simple(0.1,function() if IsValid(expLight2) then expLight2:Remove() end end)
|
|
self:DeleteOnRemove(expLight2)
|
|
|
|
local effectdata2 = EffectData()
|
|
effectdata2:SetOrigin(tr.HitPos)
|
|
effectdata2:SetNormal(tr.HitNormal)
|
|
effectdata2:SetRadius( 300 )
|
|
effectdata2:SetScale( 100 )
|
|
if tr.HitWorld then
|
|
util.Effect( "cball_bounce", effectdata2 )
|
|
util.Effect( "AR2Explosion", effectdata2 )
|
|
end
|
|
util.Effect( "ThumperDust", effectdata2 )
|
|
util.Effect( "ThumperDust", effectdata2 )
|
|
--ParticleEffect( "striderbuster_explode_dummy_core", tr.HitPos, Angle(0,0,00) )
|
|
|
|
util.ScreenShake(tr.HitPos, 16, 200, 2, 4000)
|
|
util.Decal( "Scorch", tr.HitPos + tr.HitNormal, tr.HitPos - tr.HitNormal)
|
|
|
|
effects.BeamRingPoint( tr.HitPos, 0.3, 0, 600, 25, 25, Color(255,255,175) )
|
|
|
|
util.VJ_SphereDamage(self,self,tr.HitPos,225,self.StriderCannonDamage,bit.bor(DMG_DISSOLVE,DMG_SHOCK,DMG_BLAST),true,true,false,false)
|
|
|
|
self:EmitSound("npc/strider/fire.wav",140,math.random(70, 80),1)
|
|
self:EmitSound("npc/vj_combine_guard_z/cguard_fire.wav", 140, math.random(70, 80))
|
|
|
|
sound.Play( "Weapon_Mortar.Impact", tr.HitPos, 120, 100, 1 )
|
|
end
|
|
end end)
|
|
|
|
timer.Simple(2, function() if IsValid(self) then
|
|
self.DoingStriderCannon = false
|
|
self.ShouldDoStriderCannon = false
|
|
end end)
|
|
end
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:DoSpark(pos,intensity)
|
|
intensity = intensity or 1
|
|
|
|
local spark = ents.Create("env_spark")
|
|
spark:SetKeyValue("Magnitude",tostring(intensity))
|
|
spark:SetKeyValue("Spark Trail Length",tostring(intensity))
|
|
spark:SetPos(pos)
|
|
spark:Spawn()
|
|
spark:Fire("StartSpark", "", 0)
|
|
timer.Simple(0.1, function() if IsValid(spark) then spark:Remove() end end)
|
|
end
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:CustomOnTakeDamage_BeforeDamage(dmginfo, hitgroup)
|
|
|
|
self.HasPainSounds = true -- If set to false, it won't play the pain sounds
|
|
self.Bleeds = false
|
|
|
|
local infl = dmginfo:GetInflictor()
|
|
local comballdamage = false
|
|
|
|
if infl && IsValid(infl) then
|
|
if infl:GetClass() == "prop_combine_ball" then
|
|
infl:Fire("Explode")
|
|
comballdamage = true
|
|
end
|
|
|
|
if !infl.DamagedVJ_ZHunter && infl:GetClass() == "obj_vj_combineball" then
|
|
infl.DamagedVJ_ZHunter = true
|
|
infl:DeathEffects()
|
|
comballdamage = true
|
|
end
|
|
end
|
|
|
|
if !dmginfo:IsExplosionDamage() && !comballdamage then
|
|
|
|
if dmginfo:IsBulletDamage() then
|
|
dmginfo:SetDamage(dmginfo:GetDamage() * 0.05) -- Since bullet damage delivered to its head hitgroup is doubled.
|
|
else
|
|
dmginfo:SetDamage(dmginfo:GetDamage() * 0.1)
|
|
end
|
|
|
|
if math.random(1, 4) == 1 then
|
|
self.Bleeds = true
|
|
self:EmitSound("physics/metal/metal_sheet_impact_bullet1.wav", 92, math.random(70, 90))
|
|
self.Spark1 = ents.Create("env_spark")
|
|
self.Spark1:SetPos(dmginfo:GetDamagePosition())
|
|
self.Spark1:Spawn()
|
|
self.Spark1:Fire("StartSpark", "", 0)
|
|
self.Spark1:Fire("StopSpark", "", 0.001)
|
|
self:DeleteOnRemove(self.Spark1)
|
|
end
|
|
|
|
self.HasPainSounds = false -- If set to false, it won't play the pain sounds
|
|
else
|
|
self.Bleeds = true
|
|
|
|
if comballdamage then
|
|
dmginfo:SetDamage(150)
|
|
end
|
|
|
|
if dmginfo:GetDamage() >= 30 then
|
|
self:AddGesture(self:GetSequenceActivity( self:LookupSequence("flinch_gesture") ))
|
|
else
|
|
self.HasPainSounds = false -- If set to false, it won't play the pain sounds
|
|
end
|
|
self:DoSpark(dmginfo:GetDamagePosition(),5)
|
|
end
|
|
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:SetUpGibesOnDeath(dmginfo, hitgroup)
|
|
|
|
self.HasDeathSounds = false
|
|
|
|
local headpos = self:GetBonePosition(0)
|
|
|
|
self:EmitSound("NPC_CombineGunship.Explode", 120, 100)
|
|
local effectdata = EffectData()
|
|
effectdata:SetOrigin(headpos)
|
|
effectdata:SetScale( 500 )
|
|
util.Effect( "Explosion", effectdata )
|
|
self:EmitSound( "Explo.ww2bomb", 130, 100)
|
|
ParticleEffect("vj_explosion1", headpos, Angle(0,0,0), nil)
|
|
ParticleEffect( "striderbuster_explode_goop", headpos, self:GetAngles() )
|
|
|
|
self:CreateGibEntity("obj_vj_gib","models/gibs/strider_gib1.mdl",{Pos = headpos, BloodType = "",Vel = self:GetVelocity()+VectorRand()*600, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
self:CreateGibEntity("obj_vj_gib","models/gibs/strider_gib2.mdl",{Pos = headpos, BloodType = "",Vel = self:GetVelocity()+VectorRand()*600, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
self:CreateGibEntity("obj_vj_gib","models/gibs/strider_gib3.mdl",{Pos = headpos, BloodType = "",Vel = self:GetVelocity()+VectorRand()*600, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
self:CreateGibEntity("obj_vj_gib","models/gibs/strider_gib4.mdl",{Pos = headpos, BloodType = "",Vel = self:GetVelocity()+VectorRand()*600, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
self:CreateGibEntity("obj_vj_gib","models/gibs/strider_gib5.mdl",{Pos = headpos, BloodType = "",Vel = self:GetVelocity()+VectorRand()*600, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
self:CreateGibEntity("obj_vj_gib","models/gibs/strider_gib6.mdl",{Pos = headpos, BloodType = "",Vel = self:GetVelocity()+VectorRand()*600, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
self:CreateGibEntity("obj_vj_gib","models/gibs/strider_gib7.mdl",{Pos = headpos, BloodType = "",Vel = self:GetVelocity()+VectorRand()*600, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
|
|
self:CreateGibEntity("prop_ragdoll","models/gibs/strider_head.mdl",{Pos = headpos-Vector(0,0,500), BloodType = "", Ang = self:GetAngles(), Vel = self:GetVelocity(), AngVel = VectorRand()*500, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
self:CreateGibEntity("prop_ragdoll","models/gibs/strider_weapon.mdl",{Pos = headpos-Vector(0,0,500), BloodType = "", Ang = self:GetAngles(), Vel = self:GetVelocity(), AngVel = VectorRand()*500, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
|
|
self:CreateGibEntity("prop_ragdoll","models/gibs/strider_back_leg.mdl",{Pos = self:GetPos()-Vector(0,0,500), BloodType = "", Ang = self:GetAngles(), Vel = self:GetVelocity(), AngVel = VectorRand()*500, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
self:CreateGibEntity("prop_ragdoll","models/gibs/strider_left_leg.mdl",{Pos = self:GetPos()-Vector(0,0,500), BloodType = "", Ang = self:GetAngles(), Vel = self:GetVelocity(), AngVel = VectorRand()*500, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
self:CreateGibEntity("prop_ragdoll","models/gibs/strider_right_leg.mdl",{Pos = self:GetPos()-Vector(0,0,500), BloodType = "", Ang = self:GetAngles(), Vel = self:GetVelocity(), AngVel = VectorRand()*500, CollideSound = {"SolidMetal.ImpactSoft"}})
|
|
return true
|
|
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:CustomOnRemove()
|
|
timer.Remove(self.BurstAttackShootTimerName)
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |